> ## Documentation Index
> Fetch the complete documentation index at: https://starcryptdocs.starsolenterprise.com/lua-docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Watermarking and Fingerprinting Obfuscated Lua/Luau Scripts

> Embed invisible, hard-to-remove identifiers in your Lua and Luau scripts so you can identify the source of leaks and unauthorized redistribution.

Watermarking embeds a unique, hidden identifier in each copy of your script. If a script is leaked, shared without authorization, or redistributed, the watermark reveals which customer, build, or distribution channel the leaked copy originated from. When combined with obfuscation, watermarks become harder to strip because they blend into the transformed code structure.

This page covers static, structural, and runtime watermarking techniques, extraction methods, and strategies for building redundant, resilient fingerprinting systems.

## Why Watermark?

Watermarking serves three primary purposes in Lua and Luau script distribution:

> **Leak Identification**
>
> Identify the specific customer or build that a leaked script came from, enabling targeted responses.

> **Deterrence**
>
> If users know their copy is uniquely identifiable, they are less likely to share it publicly or with competitors.

> **Legal Evidence**
>
> A watermark can serve as evidence in copyright disputes, proving the origin of unauthorized copies.

Watermarking is not a substitute for obfuscation. It is a complementary layer that adds accountability to protection.

## Static Watermarking Techniques

Static watermarks are embedded in the code or bytecode and are present even if the script is never executed. They are the easiest to embed and extract.

### Dead Code Watermarks

Embed a unique constant in a dead code branch that is never executed but is preserved in the bytecode's constant table. Decompilers and disassemblers will see the constant, but it does not affect runtime behavior:

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
local function _unused_watermark_function()
    if false then
        local _wm = "CUST-7A3F-9912"
        return _wm
    end
end
```

The string `"CUST-7A3F-9912"` appears in the bytecode constants but the branch is never taken. After obfuscation, the dead branch may be flattened into a state machine, making it even harder to distinguish from real logic.

### Comment-Based Watermarks

Lua comments are stripped during compilation to bytecode, so they do not survive in `.luac` files. However, if you distribute source code, embed a comment obfuscated as a string assignment to a dummy variable:

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
local _doc_string = "Build: 2024.3.1 | Customer: ACME Corp | ID: 0x4F2A"
```

Then obfuscate the string with your string encoder. The comment becomes an opaque byte array that looks like any other encoded string.

### Junk Identifier Watermarks

Use a specific subset of dummy identifier names that encodes a value in the bit pattern of their lengths or first characters:

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
local a_b = 1   -- length 3, first char 'a' -> bit 0
local ccc = 2   -- length 3, first char 'c' -> bit 1
local ddddd = 3 -- length 5, first char 'd' -> bit 2
```

By controlling the lengths and starting letters of junk identifiers, you encode a binary watermark into the identifier table. This survives renaming obfuscation because the identifiers are the watermark.

### Numeric Watermark in Constants

Embed the customer ID as a constant used in a bogus computation:

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
local function _bogus_math()
    local x = 17493 -- customer ID
    local y = x * 3 + 7
    return y % 2
end
```

The constant `17493` appears in the bytecode and can be mapped back to a customer. The bogus computation prevents simple dead-code elimination from removing it.

## Structural Watermarking

Structural watermarks encode information in the arrangement and ordering of code elements rather than in explicit constants. They are harder to remove because an attacker must restructure the code to strip them.

### Statement Ordering

For N independent statements, there are N factorial possible orderings. Use a specific ordering that encodes bits of the customer ID:

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
local a = 1
local b = 2
local c = 3
local d = 4
```

If these assignments are independent, their order can encode a permutation index. With 4 statements, there are 24 orderings, enough to encode roughly 4.5 bits. Scale this to larger blocks for more capacity.

### Junk Function Count

Embed bits by generating exactly K junk functions where K encodes part of the watermark:

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
local function _dummy_001() return 1 end
local function _dummy_002() return 2 end
local function _dummy_003() return 3 end
-- ... exactly N functions
```

An attacker removing junk functions may alter the count and corrupt the watermark.

### Opaque Predicate Values

Choose specific numeric constants in opaque predicates to encode watermark bits:

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
if (x * 7 + 3) % 11 == 5 then
    -- real code
else
    -- dead code
end
```

The constants `7`, `3`, `11`, and `5` can encode a portion of the customer ID. Because opaque predicates are designed to look like real conditions, an attacker cannot safely remove them without risking behavioral changes.

## Runtime Watermarking

Runtime watermarks are only observable when the script executes. They leave no trace in the static code and are invisible to decompilers.

### License Key Embedding

The script verifies a license key at startup and embeds the key's hash into its execution trace. The hash is never stored as a constant; it is derived at runtime:

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
local function verify_and_watermark(license_key)
    local expected = compute_expected_hash(license_key)
    if expected ~= server_verified_hash then
        error("Invalid license")
    end
    -- Use the hash in a real computation to embed it in the trace
    local seed = tonumber(expected:sub(1, 8), 16)
    math.randomseed(seed)
end
```

An analyst tracing execution will see the hash value in use, but cannot determine what it represents without knowing the license key.

### HTTP-Based Tracking

If network access is available and expected, the script can call a server endpoint on first run with a hash of its own source, enabling server-side tracking:

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
local function report_first_run()
    local source_hash = compute_hash_of_source()
    http_request({
        Url = "https://api.example.com/telemetry",
        Method = "POST",
        Body = json_encode({
            build_id = BUILD_ID,
            source_hash = source_hash,
        }),
    })
end
```

This requires network access and a legitimate reason for the request, or it will appear suspicious to analysts.

## Watermark Extraction

Document your watermarking scheme internally so you can extract the customer ID from a leaked script. Without documentation, even you cannot recover the watermark.

### Simple Extractor Script

Here is an example extractor for a dead-code watermark:

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
local function extract_watermark(source)
    -- Look for the pattern: local _wm = "CUST-XXXX-XXXX"
    local pattern = 'local%s+_%w+%s*=%s*"(CUST%-%x%x%x%x%-%x%x%x%x)"'
    local watermark = string.match(source, pattern)
    return watermark
end
```

For structural watermarks, the extractor must understand the encoding scheme:

```lua theme={"theme":{"light":"ayu-dark","dark":"night-owl"}}
local function extract_from_order(statements)
    -- Map statement order to a permutation index
    -- Then map permutation index to customer ID
end
```

Maintain a mapping table that links extracted watermark values to customer records.

> [!NOTE]
>
> A sophisticated attacker can strip watermarks if they know the watermarking scheme. Use multiple watermark types simultaneously (static + structural) for redundancy. If one watermark is removed, the others may survive. Rotate your watermarking scheme periodically so that exposed schemes cannot be used to decode future leaks.

> [!TIP]
>
> Rotate the watermarking scheme itself periodically. If your scheme is exposed, previous leaks cannot be decoded with the new scheme, but future leaks use a harder scheme. Maintain a version identifier in each build so you know which scheme to apply during extraction.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.