From 802914fcccad564d0150741c758e793d61c79320 Mon Sep 17 00:00:00 2001 From: Luke Rohenaz Date: Fri, 4 Sep 2026 17:09:44 -0400 Subject: [PATCH 1/2] =?UTF-8?q?docs:=20propose=20BRC-183=20Sigma=20?= =?UTF-8?q?=E2=80=94=20Transaction-Bound=20Script=20Signatures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + SUMMARY.md | 1 + scripts/0183.md | 210 +++++++++++++++++++++++++++++++++++ scripts/README.md | 1 + scripts/media/0183-verify.py | 56 ++++++++++ 5 files changed, 269 insertions(+) create mode 100644 scripts/0183.md create mode 100644 scripts/media/0183-verify.py diff --git a/README.md b/README.md index 100a9bbf..b670fa4a 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/SUMMARY.md b/SUMMARY.md index 11501cf9..93bc606c 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -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 diff --git a/scripts/0183.md b/scripts/0183.md new file mode 100644 index 00000000..1cc8a8cd --- /dev/null +++ b/scripts/0183.md @@ -0,0 +1,210 @@ +# 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): + +``` + +OP_RETURN // or "|" if OP_RETURN is already present + SIGMA + +
+ + +``` + +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
"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. + +## Reproducible primitive checks + +Run [the portable Python 3 verifier](./media/0183-verify.py). These checks reproduce the stated primitive vectors; they do not establish complete protocol or independent implementation interoperability. + +## 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 has been independently recomputed, including ECDSA verification; BRC-77 public/private vectors and a pinned release interoperability matrix remain required. + +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 diff --git a/scripts/README.md b/scripts/README.md index e20dda5d..8555ef72 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -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) diff --git a/scripts/media/0183-verify.py b/scripts/media/0183-verify.py new file mode 100644 index 00000000..37f9c7b9 --- /dev/null +++ b/scripts/media/0183-verify.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +"""Independent Python standard-library check of the BRC-504 BSM vector. + +No dependencies. This tests cryptographic preimages and ECDSA, not a Sigma parser. +""" +import hashlib + +H = lambda value: hashlib.sha256(value).digest() +txid = bytes.fromhex('9a1b3c5d7e9f0a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293') +prefix = bytes.fromhex('76a914751e76e8199196d454941c45d1b3a323f1433bd688ac6a223150755161374b36324d694b43747373534c4b79316b683536575755374d745552350353455403617070097369676d6164656d6f') +ih = H(txid + (1).to_bytes(4, 'little')) +dh = H(prefix) +m = H(ih + dh) +assert ih.hex() == '33dd9d77bce09fc606a55046fe025776b73f2f2698937a6516fce470d7d499cd' +assert dh.hex() == '1777f397d3e9794036927163453d01c3fbea8437d1849f6604a62b4af3af2037' +assert m.hex() == 'b4d42dcaccfa952caae539fe9fe5d0111b5612bc92ddc8c11dad54ff5ce4349c' +sig = bytes.fromhex('1fffc4adbaf4b3e426d49f5ec571624f5dcf8c75a059f5a341e7740323ad513b8c5a86c49554f4e3432f66b9ff0e71d2560587a6fd7b5ec82ed7947036f6438a68') +p = 2**256 - 2**32 - 977 +n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 +G = (55066263022277343669578718895168534326250603453777594175500187360389116729240, + 32670510020758816978083085130507043184471273380659243275938904335757337482424) + +def add(a, b): + if a is None: return b + if b is None: return a + x, y = a + X, Y = b + if x == X and (y + Y) % p == 0: return None + slope = ((3*x*x)*pow(2*y, -1, p) if a == b else (Y-y)*pow(X-x, -1, p)) % p + q = (slope*slope-x-X) % p + return q, (slope*(x-q)-y) % p + +def mul(k, a): + q = None + while k: + if k & 1: q = add(q, a) + a = add(a, a) + k >>= 1 + return q + +def verify(message): + z = int.from_bytes(H(H(b'\x18Bitcoin Signed Message:\n\x20' + message)), 'big') + r, s = int.from_bytes(sig[1:33], 'big'), int.from_bytes(sig[33:], 'big') + assert 1 <= r < n and 1 <= s < n + w = pow(s, -1, n) + R = add(mul(z*w % n, G), mul(r*w % n, G)) + return R is not None and R[0] % n == r + +assert len(sig) == 65 and sig[0] == 31 +assert verify(m) +assert not verify(H(H(txid[::-1] + (1).to_bytes(4, 'little')) + dh)) +assert not verify(H(m)) +assert not verify(H(ih + H(prefix + b'\x00'))) +# An identical outpoint/prefix remains valid regardless of unsigned tx fields. +assert verify(H(ih + dh)) +print('PASS: Sigma BSM hashes, ECDSA, changed txid order/double-hash/prefix negatives') From d4123b4eedc55bceda1b93591967ef559fc8d331 Mon Sep 17 00:00:00 2001 From: Luke Rohenaz Date: Fri, 4 Sep 2026 19:45:00 -0400 Subject: [PATCH 2/2] docs: focus proposal on specification and review examples --- scripts/0183.md | 6 +--- scripts/media/0183-verify.py | 56 ------------------------------------ 2 files changed, 1 insertion(+), 61 deletions(-) delete mode 100644 scripts/media/0183-verify.py diff --git a/scripts/0183.md b/scripts/0183.md index 1cc8a8cd..3d4c4d6d 100644 --- a/scripts/0183.md +++ b/scripts/0183.md @@ -190,13 +190,9 @@ Two negative checks, both of which have been real implementation errors: - **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. -## Reproducible primitive checks - -Run [the portable Python 3 verifier](./media/0183-verify.py). These checks reproduce the stated primitive vectors; they do not establish complete protocol or independent implementation interoperability. - ## 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 has been independently recomputed, including ECDSA verification; BRC-77 public/private vectors and a pinned release interoperability matrix remain required. +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. diff --git a/scripts/media/0183-verify.py b/scripts/media/0183-verify.py deleted file mode 100644 index 37f9c7b9..00000000 --- a/scripts/media/0183-verify.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python3 -"""Independent Python standard-library check of the BRC-504 BSM vector. - -No dependencies. This tests cryptographic preimages and ECDSA, not a Sigma parser. -""" -import hashlib - -H = lambda value: hashlib.sha256(value).digest() -txid = bytes.fromhex('9a1b3c5d7e9f0a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293') -prefix = bytes.fromhex('76a914751e76e8199196d454941c45d1b3a323f1433bd688ac6a223150755161374b36324d694b43747373534c4b79316b683536575755374d745552350353455403617070097369676d6164656d6f') -ih = H(txid + (1).to_bytes(4, 'little')) -dh = H(prefix) -m = H(ih + dh) -assert ih.hex() == '33dd9d77bce09fc606a55046fe025776b73f2f2698937a6516fce470d7d499cd' -assert dh.hex() == '1777f397d3e9794036927163453d01c3fbea8437d1849f6604a62b4af3af2037' -assert m.hex() == 'b4d42dcaccfa952caae539fe9fe5d0111b5612bc92ddc8c11dad54ff5ce4349c' -sig = bytes.fromhex('1fffc4adbaf4b3e426d49f5ec571624f5dcf8c75a059f5a341e7740323ad513b8c5a86c49554f4e3432f66b9ff0e71d2560587a6fd7b5ec82ed7947036f6438a68') -p = 2**256 - 2**32 - 977 -n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 -G = (55066263022277343669578718895168534326250603453777594175500187360389116729240, - 32670510020758816978083085130507043184471273380659243275938904335757337482424) - -def add(a, b): - if a is None: return b - if b is None: return a - x, y = a - X, Y = b - if x == X and (y + Y) % p == 0: return None - slope = ((3*x*x)*pow(2*y, -1, p) if a == b else (Y-y)*pow(X-x, -1, p)) % p - q = (slope*slope-x-X) % p - return q, (slope*(x-q)-y) % p - -def mul(k, a): - q = None - while k: - if k & 1: q = add(q, a) - a = add(a, a) - k >>= 1 - return q - -def verify(message): - z = int.from_bytes(H(H(b'\x18Bitcoin Signed Message:\n\x20' + message)), 'big') - r, s = int.from_bytes(sig[1:33], 'big'), int.from_bytes(sig[33:], 'big') - assert 1 <= r < n and 1 <= s < n - w = pow(s, -1, n) - R = add(mul(z*w % n, G), mul(r*w % n, G)) - return R is not None and R[0] % n == r - -assert len(sig) == 65 and sig[0] == 31 -assert verify(m) -assert not verify(H(H(txid[::-1] + (1).to_bytes(4, 'little')) + dh)) -assert not verify(H(m)) -assert not verify(H(ih + H(prefix + b'\x00'))) -# An identical outpoint/prefix remains valid regardless of unsigned tx fields. -assert verify(H(ih + dh)) -print('PASS: Sigma BSM hashes, ECDSA, changed txid order/double-hash/prefix negatives')