Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ BRC | Standard
174 | [Consensus-Unique Name Tokens — Identity Binding and Verified Resolution](./tokens/0174.md)
176 | [BSV-21 — Validity Proofs](./tokens/0176.md)
177 | [Wallet-Enforced Expiry for `noSend` Actions](./wallet/0177.md)
183 | [Sigma — Transaction-Bound Script Signatures](./scripts/0183.md)
190 | [Access Gates for Metanet Rooms](./apps/0190.md)
210 | [Derived Collectibles](./apps/0210.md)
218 | [Chat-Native Command Grammar for the Metanet](./apps/0218.md)
Expand Down
1 change: 1 addition & 0 deletions SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
* [Pay to Push Drop](./scripts/0048.md)
* [Bitcoin Script ASM Format](./scripts/0106.md)
* [Bitcoin Script to and from Bitcoin BASIC — a compiler and a decompiler](./scripts/0173.md)
* [Sigma — Transaction-Bound Script Signatures](./scripts/0183.md)

## Tokens

Expand Down
206 changes: 206 additions & 0 deletions scripts/0183.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
# BRC-183: Sigma — Transaction-Bound Script Signatures

Open Protocol Labs (info@opl.dev)

**Authors:** David Case (dcase@opl.dev), Luke Rohenaz (luke@opl.dev)

**Contributors:** Kurt Wuckert Jr. (kurt@opl.dev), Michael Boyd (root@opl.dev), Dan Wagner (dan@opl.dev)

## Abstract

Sigma is a digital signature scheme for signing Bitcoin transaction output scripts. A Sigma instance attaches a signature to an output script such that the signature is bound to (1) a chosen **input outpoint** of the transaction and (2) the **script bytes preceding the Sigma tape**. This binds the attestation to that outpoint and prefix, not to the entire transaction or a unique output; the limits are specified in Section 5.

Sigma supports two signing algorithms: `BSM` (Bitcoin Signed Message — publicly verifiable by anyone who knows the signer's address) and `BRC77` ([BRC-77](../peer-to-peer/0077.md) message signing — using derived child keys and an embedded signer public key, supporting both public verification and private verification by a designated recipient). This document specifies the on-output layout, the signed message construction, and verification for both algorithms.

## Motivation

Detached signatures over metadata alone — including AIP ([BRC-181](https://github.com/opldotdev/BRCs/blob/codex/brc-181-aip/scripts/0181.md)) signatures, which cover only push-data content — can be replayed onto counterfeit outputs or different transactions. Sigma addresses replay by signing a hash derived from transaction context (an input outpoint) together with the output script content preceding the signature, so changing either the anchor outpoint or covered prefix changes the signed message. Sigma was inspired by AIP and serves the same authorship-attestation role for use cases that additionally require transaction binding, such as issuer-signed token mints.

### Relationship to Existing BRCs

This codifies Sigma script attestations; it does not replace transaction input signatures. BRC-77 specifies a portable signature envelope without transaction context, while AIP signs pushed metadata. Sigma adds an outpoint-and-script-prefix preimage and an on-script envelope. Applications needing commitment to amounts, output positions, or the full transaction require additional signed data or transaction signatures.

## Specification

The key words "MUST", "SHOULD", and "MAY" in this document are to be interpreted as described in RFC 2119.

### 1. Placement on the Output

Sigma is appended to the **locking script** of the signed output as a [BRC-179](https://github.com/opldotdev/BRCs/blob/codex/brc-179-bitcom/scripts/0179.md)-style protocol tape, identified by the UTF-8 string `SIGMA` (a string identifier per BRC-179 §1.1):

```
<script-before-sigma>
OP_RETURN // or "|" if OP_RETURN is already present
SIGMA
<algorithm>
<address>
<signature>
<vin>
```

Rules:

- The Sigma tape MUST live in the OP_RETURN data path of the locking script.
- If the script has no prior `OP_RETURN`, the tape MUST begin with `OP_RETURN`, then `SIGMA`.
- If the script already has an `OP_RETURN` (for example a MAP tape, [BRC-182](https://github.com/opldotdev/BRCs/blob/codex/brc-182-map/scripts/0182.md)), the Sigma tape is appended with a `|` separator push (`0x7c`), then `SIGMA` — not a second `OP_RETURN`.
- Multiple Sigma instances MAY appear on one output as successive `|`-separated `SIGMA` segments. Instances are indexed 0-based in order of appearance — for example, a user signature followed by a platform signature.

### 2. Fields (after `SIGMA`)

| Order | Field | Encoding | Meaning |
|-------|-------|----------|---------|
| 1 | algorithm | UTF-8 push | `BSM` or `BRC77` (Section 4) |
| 2 | address | UTF-8 push | P2PKH address derived from the signer's public key. When signing with an on-chain identity (for example a BAP identity), this is derived from the identity's current signing key. |
| 3 | signature | binary push | Signature bytes; format depends on the algorithm (Section 4). Displayed in hex within scripts; commonly represented in Base64 off-chain. |
| 4 | vin | UTF-8 push of a decimal integer, optionally `-1` | Index of the transaction input whose outpoint is bound into the signature, or `-1` for self-reference (Section 3.1). |

Verifiers that do not implement an algorithm MUST treat those instances as invalid for any rule that requires a valid Sigma.

### 3. Signed Message Construction

Both algorithms sign the same 32-byte message hash, built from an input outpoint and the target output script.

#### 3.1 Input Hash

Let `vin` be the field value, where `-1` resolves to the index of the **target output** (the output carrying this tape). Self-reference is used when the tape must be built before the anchor input's final position is known, such as while signing a partially built transaction. The field MUST match ASCII `-1` or `[0-9]+` in its entirety; no sign other than the `-` in `-1`, whitespace, or trailing characters is accepted. Values are not limited to one digit. Parse without numeric truncation or loss of precision, then range-check against the input count. Verifiers MUST resolve `-1` before computing the hash.

Let `I` be the transaction input at the resolved index:

```
outpoint_bytes = txid_bytes(32) || vout_uint32_le(4)
input_hash = SHA256(outpoint_bytes)
```

`txid_bytes` MUST be the 32 bytes obtained by hex-decoding the transaction id **as it is written and displayed**. This is the reverse of the order in which a transaction serializes that txid in its inputs; implementations that hold the txid as a raw hash or read it from a serialized outpoint MUST reverse those 32 bytes before hashing. `vout_uint32_le` is the source output index as a 4-byte little-endian unsigned integer.

#### 3.2 Data Hash

Let `S` be the full locking script of the target output. Find the `OP_RETURN` or `|` separator immediately before the `SIGMA` push for **this** instance (by 0-based instance index). Let `prefix` be the script bytes **strictly before** that separator:

```
data_hash = SHA256(prefix)
```

Parse script opcodes and push lengths to locate the tape; do not search raw bytes for `SIGMA` or `0x7c`, which can occur inside pushed data. A Sigma marker must be the first field of its protocol segment. Hash the original serialized prefix bytes, including the original push encodings; do not normalize or reserialize pushes. A malformed push or incomplete Sigma tape is invalid.

The separator and the Sigma tape itself are never included in `prefix`. Changing only bytes after the separator (the Sigma fields themselves) is outside the signed prefix by design, which is what allows a placeholder tape to be replaced by the real signature without altering the covered content.

#### 3.3 Message Hash

```
message_hash = SHA256( input_hash || data_hash )
```

(32-byte input hash concatenated with 32-byte data hash, hashed once with SHA-256.)

An instance whose `message_hash` cannot be computed — a `vin` that does not resolve to an existing input, including `-1` resolving to an output index with no corresponding input, or a `vin` that is not a decimal integer — is **invalid**. Verifiers MUST NOT treat an instance they could not evaluate as valid.

### 4. Algorithms

#### 4.1 `BSM` — Bitcoin Signed Message

- **Signature format:** 65-byte compact recoverable ECDSA signature (1 recovery byte, 32-byte `r`, 32-byte `s`).
- **Signing:** treat `message_hash` as the message bytes for Bitcoin Signed Message signing (the usual BSM magic prefix and double SHA-256 are applied to those 32 bytes as the payload).
- **Verification:** recover the public key from the compact signature and `message_hash` under BSM; the instance is valid if and only if the recovered key's P2PKH address equals the `address` field.
- Anyone who knows the signer's address can verify.

#### 4.2 `BRC77` — BRC-77 Message Signing

- **Signature format:** the complete [BRC-77](../peer-to-peer/0077.md) signed-message envelope, pushed as binary. The envelope embeds the signer's identity public key, the verifier scope, the key derivation identifier, and the ECDSA signature, per BRC-77.
- **Signing:** sign the 32-byte `message_hash` as the BRC-77 message payload with the signer's identity private key. The signer MAY designate a specific verifier public key, in which case only that recipient can verify (private verification); omitting the verifier produces a publicly verifiable signature ("anyone" scope, per BRC-77).
- **Verification:** verify the envelope over `message_hash` per BRC-77 (supplying the recipient private key when the signature was scoped to a specific verifier). Verifiers MUST additionally check that the identity public key embedded in the envelope corresponds to the `address` field; an instance whose embedded key does not match the claimed address is invalid.
- BRC-77's use of derived child keys ([BRC-42](../key-derivation/0042.md)/[BRC-43](../key-derivation/0043.md)) means each signature uses a fresh derived signing key when its key identifier is generated as required by BRC-77 while remaining verifiably bound to the signer's identity key.

### 5. What Is Bound

A valid Sigma instance asserts that the holder of the key behind `address` authorized:

- the **specific input outpoint** at the resolved `vin`, and
- the **script content before the Sigma tape** on the target output (inscription, MAP data, locking code, etc.).

Changing the covered prefix or anchor outpoint invalidates the signature. Sigma does **not** commit to the output amount, target output index, transaction version, locktime, other inputs or outputs, or bytes after its separator. Identical prefixes can carry the same valid signature in multiple outputs of one transaction or in competing transactions spending the same anchor. Changing `vin` to another position that resolves to the same outpoint also preserves validity. Confirmation and prevention of double spending come from transaction validation and chain state, not Sigma verification. Applications MUST NOT interpret one valid instance as proof of a unique mint, a unique output, or authorization of the entire transaction.

### 6. Signing Flow for Partially Built Transactions (Informative)

Wallets commonly:

1. Build the output script with a **placeholder** Sigma tape (appropriately sized placeholder signature) to estimate transaction fees. BRC-77 DER signatures can vary in length, so fees MUST be checked again after replacement.
2. Choose an anchor input outpoint (or use `-1` self-reference).
3. Compute the real signature over the outpoint and script prefix, and replace the placeholder.

Only the final on-chain tape and the verification rules above are normative.

## Test Vectors

A `BSM` instance on a MAP-bearing output, so the tape is reached through a `|` separator rather than directly after `OP_RETURN`.

**Inputs**

| | |
|--|--|
| Private key (test only) | `0000000000000000000000000000000000000000000000000000000000000001` |
| Signer address | `1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH` |
| Source txid (hex-string order) | `9a1b3c5d7e9f0a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293` |
| Source output index | `1` |
| `vin` field | `"0"` (input 0 is the source outpoint above) |

**Signed prefix** — the script bytes before the `|` push, i.e. a P2PKH lock, `OP_RETURN`, then MAP `SET app sigmademo` (the app value is arbitrary content; these exact bytes are what the vector's signature covers):

```
76a914751e76e8199196d454941c45d1b3a323f1433bd688ac6a223150755161374b3632
4d694b43747373534c4b79316b683536575755374d745552350353455403617070097369676d6164656d6f
```

**Intermediate hashes**

```
input_hash 33dd9d77bce09fc606a55046fe025776b73f2f2698937a6516fce470d7d499cd
data_hash 1777f397d3e9794036927163453d01c3fbea8437d1849f6604a62b4af3af2037
message_hash b4d42dcaccfa952caae539fe9fe5d0111b5612bc92ddc8c11dad54ff5ce4349c
```

**Signature** (65-byte compact, as pushed into the tape):

```
1fffc4adbaf4b3e426d49f5ec571624f5dcf8c75a059f5a341e7740323ad513b8c
5a86c49554f4e3432f66b9ff0e71d2560587a6fd7b5ec82ed7947036f6438a68
```

**Full locking script** — signed prefix, `|`, then `SIGMA BSM <address> <signature> "0"`:

```
76a914751e76e8199196d454941c45d1b3a323f1433bd688ac6a223150755161374b3632
4d694b43747373534c4b79316b683536575755374d745552350353455403617070097369
676d6164656d6f017c055349474d410342534d22314267475a3974634e34726d394b427a
446e374b7072517a3837535a323653414d48411fffc4adbaf4b3e426d49f5ec571624f5d
cf8c75a059f5a341e7740323ad513b8c5a86c49554f4e3432f66b9ff0e71d2560587a6fd
7b5ec82ed7947036f6438a680130
```

Two negative checks, both of which have been real implementation errors:

- Hashing the txid in the transaction's serialization order instead of hex-string order yields a different `input_hash`, and this signature does not verify.
- Using a double SHA-256 for `message_hash` yields a different message, and this signature does not verify.

## Security Considerations

- **Replay** — outpoint binding limits reuse, but the exact surviving replay and duplication cases in Section 5 remain valid signatures.
- **vin choice** — signers SHOULD select an anchor whose use they can authorize; Sigma verification itself does not prove ownership of, or spending authority over, that input; verifiers MUST use the `vin` field from the tape, resolving `-1` to the target output's index.
- **Instance index** — when multiple Sigma tapes exist, each is verified with its own instance index for locating `prefix`.
- **Address binding** — for `BSM`, a mismatched network or address encoding fails verification; for `BRC77`, the embedded-key-to-address check in Section 4.2 is what binds the envelope to the claimed identity.
- **Private verification** — a `BRC77` signature scoped to a specific verifier cannot be verified by third parties; applications MUST NOT rely on privately scoped signatures for rules that third parties need to evaluate.

## Implementations

Source implementations are available in [TypeScript](https://github.com/opldotdev/sigma) and [Go](https://github.com/BitcoinSchema/go-sigma). Source availability is not a cross-implementation conformance result. The BSM vector above illustrates the exact intermediate hashes and signature. The BRC77 path is defined by Section 4.2 and BRC-77; implementation release testing is separate from this proposal.

Compatibility review found permissive parsing, missing-input dummy hashes, and prefix reserialization in the reviewed TypeScript source. This document rejects those behaviors for verification; implementers must test their actual release, especially non-minimal pushes, `vin` loaded from a tape, missing inputs, and multiple instances. These stricter rejection requirements are clarification/hardening targets and are not evidence that deployed releases already conform.

## References

- Sigma repository (canonical specification): https://github.com/opldotdev/sigma
- [BRC-179: Bitcom](https://github.com/opldotdev/BRCs/blob/codex/brc-179-bitcom/scripts/0179.md) — protocol tape and pipe conventions; string identifiers
- [BRC-181: AIP — Author Identity Protocol](https://github.com/opldotdev/BRCs/blob/codex/brc-181-aip/scripts/0181.md) — the detached-metadata signature scheme that inspired Sigma
- [BRC-182: MAP — Magic Attribute Protocol](https://github.com/opldotdev/BRCs/blob/codex/brc-182-map/scripts/0182.md) — attribute data commonly covered by Sigma signatures
- [BRC-77: Message Signature Creation and Verification](../peer-to-peer/0077.md) — the `BRC77` algorithm
- [BRC-42](../key-derivation/0042.md) / [BRC-43](../key-derivation/0043.md) — key derivation underlying BRC-77
1 change: 1 addition & 0 deletions scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ BRC | Standard
48 | [Pay to Push Drop](./0048.md)
106 | [Bitcoin Script ASM Format](./0106.md)
173 | [Bitcoin Script to and from Bitcoin BASIC — a compiler and a decompiler](./0173.md)
183 | [Sigma — Transaction-Bound Script Signatures](./0183.md)