diff --git a/README.md b/README.md index 100a9bbf..0e3070dc 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) +181 | [AIP — Author Identity Protocol](./scripts/0181.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..f7ed5212 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) +* [AIP — Author Identity Protocol](./scripts/0181.md) ## Tokens diff --git a/scripts/0181.md b/scripts/0181.md new file mode 100644 index 00000000..6e516513 --- /dev/null +++ b/scripts/0181.md @@ -0,0 +1,175 @@ +# BRC-181: AIP — Author Identity Protocol + +Open Protocol Labs (info@opl.dev) + +**Original protocol authors:** Attila Aros, Luke Rohenaz + +**BRC editor:** Luke Rohenaz (luke@opl.dev) + +**Contributors:** Monkeylord, _unwriter, Libitx — feedback and ideas on the original specification; Kurt Wuckert Jr. (kurt@opl.dev), David Case (dcase@opl.dev), Michael Boyd (root@opl.dev), Dan Wagner (dan@opl.dev) + +## Status and Scope + +This is a draft codification of an existing protocol, not a new wire format. Original protocol attribution is distinguished from BRC editorial attribution; contributor acknowledgments do not imply endorsement of this transcription. Existing identifiers and payloads remain unchanged. Clarifications describe the scope of the documented profile; incompatible extensions require a separate proposal. + +## Abstract + +The Author Identity Protocol (AIP) is a simple and flexible method for signing arbitrary OP_RETURN data with Bitcoin ECDSA signatures. An AIP instance is appended to a data-carrier output as a [BRC-179](https://github.com/opldotdev/BRCs/blob/codex/brc-179-bitcom/scripts/0179.md) pipeline segment under the prefix `15PciHG22SNLQJXMoSUaWVi7WSqc7hCfva`, carrying a signing algorithm, a signing address, a signature, and an optional list of field indexes selecting exactly which push data the signature covers. Because the signing key is independent of the transaction's funding keys, authorship can be proven without exposing the author's identity through an on-chain payment, and multiple signatures can be layered in one output to form multi-party agreements. + +## Motivation + +AIP's design goals: + +- A simple protocol to sign arbitrary OP_RETURN data in a single transaction. +- Decouple signing identity from the funding source address — no on-chain transaction from the signing identity address is required. +- Allow multiple signatures to be layered to form multi-party contracts. + +Decoupling identity from funding means an author can, for example, publish a document while a wallet or faucet pays the mining fee, without the author's identity key ever appearing in an on-chain payment. Content protocols such as B ([BRC-180](https://github.com/opldotdev/BRCs/blob/codex/brc-180-b-protocol/scripts/0180.md)) and attribute protocols such as MAP deliberately carry no identity scheme of their own; AIP is the companion protocol that binds authorship to their data. + +The technique was inspired by Monkeylord's bitcoin-ibe work and builds on _unwriter's B data protocol. + +## Specification + +The key words "MUST", "SHOULD", and "MAY" in this document are to be interpreted as described in RFC 2119. + +### 1. Placement and Format + +An AIP instance occupies a [BRC-179](https://github.com/opldotdev/BRCs/blob/codex/brc-179-bitcom/scripts/0179.md) pipeline segment in a data-carrier output ([BRC-18](./0018.md)), following the fields it signs: + +``` +OP_RETURN + <...data fields being signed...> + | + 15PciHG22SNLQJXMoSUaWVi7WSqc7hCfva + + + + [ ...] +``` + +In the profile described here, the fields being signed MUST appear before (to the left of) the AIP prefix. Any OP_RETURN content can be signed — the example above shows AIP appended after arbitrary data fields, separated by the Bitcom pipe `|`. + +### 2. Fields + +| Order | Field | Encoding | Meaning | +|-------|-------|----------|---------| +| 1 | Signing Algorithm | UTF-8 push | Signature scheme identifier. `BITCOIN_ECDSA` denotes the Bitcoin Signed Message scheme defined in Section 3. | +| 2 | Signing Address | UTF-8 push | Base58Check P2PKH address corresponding to the signing key. | +| 3 | Signature | binary push (65 bytes) | Compact recoverable ECDSA signature: 1 recovery byte followed by 32-byte `r` and 32-byte `s`. Commonly displayed in Base64 off-chain. | +| 4+ | Field Index (optional, repeating) | unsigned big-endian binary push | Indexes of the fields covered by the signature (Section 4). If omitted entirely, all fields to the left of the AIP prefix are signed. | + +`BITCOIN_ECDSA` is the algorithm defined by this specification. Some implementations accept `BitcoinSignedMessage` as a synonym for the same scheme, and a `paymail` variant exists in which the signing component is a public key (hex) rather than an address; verifiers that do not implement an algorithm MUST treat those instances as unverified rather than valid. + +### 3. Signed Message Construction + +The message covered by the signature is the **concatenation of the raw bytes of the selected fields, in the order the indexes are listed**: + +``` +message = field[i₀] || field[i₁] || ... || field[iₙ] +``` + +where each `field[k]` is the raw byte content of the push datum at index `k` per Section 4 (for index 0, the single byte `0x6a`). + +Signing and verification use Bitcoin Signed Message (BSM): + +```text +preimage = CompactSize(24) || UTF8("Bitcoin Signed Message:\n") + || CompactSize(byteLength(message)) || message +digest = SHA256(SHA256(preimage)) +``` + +CompactSize is the unsigned length serialization used in Bitcoin transactions; it is not a Script number. The compact signature is a header byte followed by 32-byte big-endian `r` and `s`. The header is `27 + recoveryId + (compressed ? 4 : 0)`, with recoveryId in 0–3. Verification MUST validate the header and scalar ranges, recover the secp256k1 key using the indicated recovery ID, verify ECDSA over the digest, serialize the key according to the compression flag, and compare its P2PKH address to the Signing Address. The address network is an application parameter; an address checksum alone is not signature verification. + +### 4. Field Indexing + +Field indexes are zero-based positions within the output script: + +- **Index 0 is the `OP_RETURN` opcode itself** — the single byte `0x6a`. (In post-Genesis `OP_FALSE OP_RETURN` outputs, the leading `OP_FALSE` is not indexed.) +- Index `k` (k ≥ 1) is the k-th push datum following `OP_RETURN`; its content is the raw pushed bytes, excluding the push opcode and length prefix. +- The Bitcom pipe `|` is an ordinary push datum (`0x7c`) and occupies an index like any other field. + +**Explicit indexing:** the AIP instance lists each covered index as a trailing field. Each index is decoded from its pushed bytes as an unsigned big-endian integer: payload `01` selects 1, `0100` selects 256; ASCII `31` selects 49, not 1. Zero is shown as a one-byte `00` payload. Index payloads are not decimal text, CompactSize integers, or little-endian Script numbers. Preserve list order rather than sorting it; a repeated index repeats the selected bytes. An empty or out-of-range index is malformed for this profile. Historical tolerance of redundant leading zero bytes is not made a new rejection rule here. + +**Implicit signing (index list omitted):** the signature covers **all** fields from index 0 up to and including the `|` separator immediately preceding the AIP prefix. + +A one-byte payload `00` contributes one zero byte. An empty push contributes zero bytes but still occupies a field index. These are different values and MUST NOT be normalized into each other. In particular, script bytes `00` (OP_0, an empty push) and `0100` (push one zero byte) are distinct. Preserve whitespace and original binary content; do not reconstruct the message by trimming strings or Base64-encoding payloads. + +### 5. Multiple Signatures + +Additional AIP instances MAY be appended as further pipeline segments, each covering the fields selected by its own index list. Because each signature can cover the same content fields (and, with explicit indexes, exclude the other signatures), multiple parties can sign one document in a single output, forming an on-chain multi-party agreement. + +### 6. Verification Procedure + +To verify an AIP instance: + +1. Parse the output script into indexed fields per Section 4 and locate the AIP prefix segment. +2. Read the Signing Algorithm, Signing Address, and Signature fields, and the trailing index list if present. +3. Determine the covered field set: the explicit index list, or the implicit range per Section 4. +4. Concatenate the raw bytes of the covered fields in the order the indexes are listed (Section 3). +5. Verify the compact signature over the concatenation under the BSM scheme against the Signing Address. + +Malformed indexes or signatures, forward references, and failed cryptographic verification MUST NOT yield a valid result. An unrecognized algorithm is **unsupported**, not proof of an invalid signature under that other algorithm. Return the recognized algorithm, selected indexes, and verification result separately so callers can evaluate coverage. Compatibility handling for Base64 text signatures or alternative message construction must be explicitly identified; it must not silently change the meaning of a successful base-profile verification. + +## Example + +A B file signed with a single AIP signature listing its covered indexes explicitly: + +``` +OP_RETURN + 19HxigV4QyBv3tHpQVcUEQyq1pzZVdoAut // B prefix + { "message": "Hello world!" } // Content + application/json // Media Type + UTF-8 // Encoding + 0x00 // One-byte zero filename payload (see below) + | // Pipe separating protocols + 15PciHG22SNLQJXMoSUaWVi7WSqc7hCfva // AIP prefix + BITCOIN_ECDSA // Signing Algorithm + 1EXhSbGFiEAZCE5eeBvUxT6cBVHhrpPWXz // Signing Address + <65-byte signature> // Signature + 0, 1, 2, 3, 4, 5, 6 // Field indexes: OP_RETURN, the five B fields, and the pipe +``` + +The illustration above is schematic, not a complete transaction or signature test vector. The filename field is deliberately a one-byte zero payload for this AIP example; B itself does not define it as a null filename. Each listed index is a separate binary push, with payloads `00`, `01`, `02`, `03`, `04`, `05`, `06`. Omitting them produces the same coverage. + +For the exact content bytes and uppercase `UTF-8` shown above, the concatenation and BSM digest are: + +```text +message.hex = 6a31394878696756345179427633744870515663554551797131707a5a56646f4175747b20226d657373616765223a202248656c6c6f20776f726c642122207d6170706c69636174696f6e2f6a736f6e5554462d38007c +message.length = 87 +BSM.digest = 93a14b01f9609a2ff4312e9bbb2046bcde51aaa03b0fd074c57fbd8114b64a55 +``` + +These schematic values check serialization only. The accompanying [historical vectors](./media/0181-vectors.json) provide complete raw transactions, actual compact signatures, selected field indexes, messages and BSM digests. They are examples for reproducing the specified signing calculation; they do not establish chain inclusion. + +| Transaction / output | Observed coverage | Signing key serialization | +|---|---|---| +| `744a55a8637aa191aa058630da51803abbeadc2de3d65b4acace1f5f10789c5b_0` | Implicit sign-all over BAP ATTEST data and its separating pipe | Compressed, header `1f` | +| `d4738845dc0d045a35c72fcacaa2d4dee19a3be1cbfcb0d333ce2aec6f0de311_0`, first signature | Index payloads `00` through `06` | Uncompressed, header `1c` | +| Same output, second signature | Index payloads `00` through `12` (hexadecimal), including the earlier AIP segment | Uncompressed, header `1b` | + +Both raw transactions were retrieved from the public WhatsOnChain API on 2026-09-04 and their hashes recomputed. All three ECDSA signatures were additionally checked with `@bsv/sdk` 2.4.2 BSM verification. The compact header compression flag MUST control the address derivation; deriving only a compressed-key address fails the two older signatures even though ECDSA verification succeeds. This sample establishes real wire evidence for implicit signing, explicit binary indexes and layered signatures. It does not establish the behavior of every historical producer, prove chain inclusion, or cover empty pushes and every legacy fallback. + +On-chain transaction examples: + +- Verified implicit signature: [`744a55a8…789c5b`](https://whatsonchain.com/tx/744a55a8637aa191aa058630da51803abbeadc2de3d65b4acace1f5f10789c5b) +- Two signatures: [`d4738845…f0de311`](https://whatsonchain.com/tx/d4738845dc0d045a35c72fcacaa2d4dee19a3be1cbfcb0d333ce2aec6f0de311) +- Implicit sign-all: [`5633bb96…867092c`](https://whatsonchain.com/tx/5633bb966d9531d22df7ae98a70966eebe4379d400d74ac948bf5b4f2867092c) + +## Security Considerations + +AIP proves control of a key for a byte string, not a real-world identity, permission to mutate another record, or agreement to an entire transaction. Signatures can be copied into another transaction; they do not inherently commit to inputs, amounts, other outputs, txid, or freshness. Applications must check that required protocol identifiers, command/context fields, and data are actually covered. + +Raw concatenation is not an unambiguous field encoding: selected fields `ab`, `c` and `a`, `bc` produce identical bytes. Empty fields also add no bytes. A valid signature therefore does not prove the original field boundaries or index list. This is a legacy limitation; adding lengths would change the signing protocol and is not done here. Applications requiring structural commitments or replay protection need a separately specified signed payload or protocol. AIP is distinct from BRC-77 message signatures and BRC-103 peer authentication; their framing and identity rules are not interchangeable. + +## Implementations + +The [AIP repository](https://github.com/opldotdev/AIP) is the protocol source. [bmap](https://github.com/rohenaz/bmap/blob/master/src/protocols/aip.ts) and [go-aip](https://github.com/bitcoinschema/go-aip) contain signing/parsing support. Implementations differ in index encoding, empty-field handling, signature representation, and compatibility fallbacks. They are interoperability evidence, not blanket conformance references. The historical fixtures above resolve the basic binary-index and compact-signature questions. Additional historical examples may inform later review; undocumented implementation fallbacks do not extend this specification. Base64 fields in BOB JSON represent binary Script pushes; they are not evidence that those scripts pushed Base64 text. + +## References + +- AIP repository (canonical specification): https://github.com/opldotdev/AIP +- [BRC-179: Bitcom](https://github.com/opldotdev/BRCs/blob/codex/brc-179-bitcom/scripts/0179.md) — protocol prefix and pipeline conventions +- [BRC-180: B — Bitcoin Data Protocol](https://github.com/opldotdev/BRCs/blob/codex/brc-180-b-protocol/scripts/0180.md) — content commonly signed with AIP +- [BRC-18: Pay to False Return](./0018.md) — data-carrier output template +- bitcoin-ibe (Monkeylord): https://github.com/monkeylord/bitcoin-ibe +- B protocol (_unwriter): https://github.com/unwriter/B diff --git a/scripts/README.md b/scripts/README.md index e20dda5d..fac83a0a 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) +181 | [AIP — Author Identity Protocol](./0181.md) diff --git a/scripts/media/0181-vectors.json b/scripts/media/0181-vectors.json new file mode 100644 index 00000000..1fc8ee5b --- /dev/null +++ b/scripts/media/0181-vectors.json @@ -0,0 +1,110 @@ +{ + "source": "Public raw transactions retrieved from api.whatsonchain.com/v1/bsv/main/tx/{txid}/hex on 2026-09-04. Transaction hashes verified; no SPV/header-chain check.", + "transactions": [ + { + "txid": "744a55a8637aa191aa058630da51803abbeadc2de3d65b4acace1f5f10789c5b", + "rawHex": "0100000001c35b90ad932daf05387f43e40cbe724e5149ce256d041c2141b4113a69d4c0ee010000008b483045022100e000f9e33ebac8878269697d368edc5c092ee48be79ec965429e1516d1457a6402202195d036f456a1cf4c02e2aa2d29b5ce893d0c031c6f1f357ce91d6e32e3e8a541410440ffb338848f78bfbb78b9b4a82c231dc728ceef42b341250c84ba99cf458bf2af0095df545bef3d28e717cdbf01102a1c725c695adfe40748619518574df228ffffffff020000000000000000fd0701006a2231424150537561506e66476e53424d33474c56397968785564596534764762644d540641545445535440636633396663353564613234646332336566663138303965366536636633326130666536616563633831323936353433653961633834623863353031626163350130017c22313550636948473232534e4c514a584d6f53556157566937575371633768436676610d424954434f494e5f45434453412231333461365458787a675139417a33773842637667645a7941355571524c38396461411fe96e6df733e59da81bc07b1f098ff19fad00b3fe28d38f81e768ed44d7c16f0b06932480281ab42769bdbb5fef065941220ccfcdde9a602f552e12dc5e57d70a2d22dd00000000001976a914d27f0a6f3b4ccbbacaf945095ed3eeb97b69117d88ac00000000", + "signatures": [ + { + "vout": 0, + "prefixIndex": 6, + "signatureHex": "1fe96e6df733e59da81bc07b1f098ff19fad00b3fe28d38f81e768ed44d7c16f0b06932480281ab42769bdbb5fef065941220ccfcdde9a602f552e12dc5e57d70a", + "address": "134a6TXxzgQ9Az3w8BcvgdZyA5UqRL89da", + "indexPayloads": [], + "selectedIndexes": [ + 0, + 1, + 2, + 3, + 4, + 5 + ], + "messageHex": "6a31424150537561506e66476e53424d33474c56397968785564596534764762644d5441545445535463663339666335356461323464633233656666313830396536653663663332613066653661656363383132393635343365396163383462386335303162616335307c", + "digestHex": "3245970297623eaf01531a3d37104ad5888236749a578ef611bdd604c1f8120d" + } + ] + }, + { + "txid": "d4738845dc0d045a35c72fcacaa2d4dee19a3be1cbfcb0d333ce2aec6f0de311", + "rawHex": "010000000153c7eafdab57435c33444c5e4b972cf7993254481c2f9f5a824211a3a0b961db010000008a473044022067457eb87f2333366d36dc299cd7f8f517399867a7e33e32396b899f9a86b65b02207bf000c79a07d7e491cfd45e9afba8f8d681f41dd410a980c3b69cb147f797c84141043cf0a503fd150ad112de4503f7dd17dcdba99e41cd7f8b52315fa1a4f9e499b9493fddcc15a594022f9734b8cf12a068d51328664192f351c3b618e52ae1f85fffffffff020000000000000000fda8016a2231394878696756345179427633744870515663554551797131707a5a56646f4175740c48656c6c6f20776f726c64210a746578742f706c61696e057574662d380100017c22313550636948473232534e4c514a584d6f53556157566937575371633768436676610d424954434f494e5f45434453412231455868536247466945415a4345356565427655785436634256486872705057587a411cacee1dbe375e3e17a662b560944e0ff78dff9f194744fb2ee462d905bc785727420d5deed4b2dd019023f550af4f4f7934050179e217220592a41882f0251ef40100010101020103010401050106017c22313550636948473232534e4c514a584d6f53556157566937575371633768436676610d424954434f494e5f45434453412231396e6b6e4c68526e474b525233686f6265467575716d48554d694e544b5a487352411b4212864c799a9d2f1ceb7b4e8e14c5cb6d943a380671bbc55dcd699930343cbd1edf62a204589f8a384f894765b4b98b2e1acbd3a9af493007ef85624d2d2c500100010101020103010401050106010701080109010a010b010c010d010e010f011001110112186b0600000000001976a9149467df677dc153a88243465d09ca5fe8f7ba8cf988ac00000000", + "signatures": [ + { + "vout": 0, + "prefixIndex": 7, + "signatureHex": "1cacee1dbe375e3e17a662b560944e0ff78dff9f194744fb2ee462d905bc785727420d5deed4b2dd019023f550af4f4f7934050179e217220592a41882f0251ef4", + "address": "1EXhSbGFiEAZCE5eeBvUxT6cBVHhrpPWXz", + "indexPayloads": [ + "00", + "01", + "02", + "03", + "04", + "05", + "06" + ], + "selectedIndexes": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6 + ], + "messageHex": "6a31394878696756345179427633744870515663554551797131707a5a56646f41757448656c6c6f20776f726c6421746578742f706c61696e7574662d38007c", + "digestHex": "af15896f96ed8271a561cc67981cd27d937f89f8d1e55693dfa427f68a38e9a6" + }, + { + "vout": 0, + "prefixIndex": 19, + "signatureHex": "1b4212864c799a9d2f1ceb7b4e8e14c5cb6d943a380671bbc55dcd699930343cbd1edf62a204589f8a384f894765b4b98b2e1acbd3a9af493007ef85624d2d2c50", + "address": "19nknLhRnGKRR3hobeFuuqmHUMiNTKZHsR", + "indexPayloads": [ + "00", + "01", + "02", + "03", + "04", + "05", + "06", + "07", + "08", + "09", + "0a", + "0b", + "0c", + "0d", + "0e", + "0f", + "10", + "11", + "12" + ], + "selectedIndexes": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18 + ], + "messageHex": "6a31394878696756345179427633744870515663554551797131707a5a56646f41757448656c6c6f20776f726c6421746578742f706c61696e7574662d38007c313550636948473232534e4c514a584d6f5355615756693757537163376843667661424954434f494e5f454344534131455868536247466945415a4345356565427655785436634256486872705057587a1cacee1dbe375e3e17a662b560944e0ff78dff9f194744fb2ee462d905bc785727420d5deed4b2dd019023f550af4f4f7934050179e217220592a41882f0251ef4000102030405067c", + "digestHex": "b844bda7bc070fa97477c767be577de04cfda94bd4c9d42f1799b95551ae26ea" + } + ] + } + ] +}