UNPKG

@btc-stamps/tx-builder

Version:

Transaction builder for Bitcoin Stamps and SRC-20 tokens with advanced UTXO selection

290 lines (220 loc) 8.23 kB
# Counterparty Protocol Encoding for Bitcoin Stamps ## Overview This document explains the Counterparty protocol encoding used for Bitcoin Stamps, which uses **issuance messages (type 22)** with RC4 encryption. Bitcoin Stamps combine Counterparty protocol for metadata with P2WSH outputs for data storage. **IMPORTANT**: Through extensive testing with `scripts/final-validation-summary.ts`, we have achieved **1:1 parity** with the stampchain.io API implementation. Our encoder produces identical OP_RETURN and P2WSH outputs. ## Architecture Diagram ```mermaid graph TB subgraph "Bitcoin Stamps Transaction Structure" A[User Data] --> B[Bitcoin Stamps Encoder] B --> C[Counterparty Protocol Handler] B --> D[P2WSH Encoder] C --> E[RC4 Encryption] E --> F[OP_RETURN Output] D --> G[Fake P2WSH Creation] G --> H[P2WSH Outputs] F --> I[Complete Transaction] H --> I subgraph "OP_RETURN (First Output)" F1[RC4 Encrypted Data] F2[CNTRPRTY prefix + Message] F3[Type 22 (LR_ISSUANCE_ID)] F4[Asset ID + Quantity + Flags] F5[Description: STAMP:filename] end subgraph "P2WSH Outputs (330 sats each)" H1[OP_0 + 32-byte chunks] H2[Raw image data embedded] H3[NOT actual witness scripts] H4[Data in scriptPubKey directly] end end style A fill:#f9f,stroke:#333,stroke-width:2px style I fill:#9f9,stroke:#333,stroke-width:2px ``` ## Critical Implementation Details Discovered ### 1. **Fake P2WSH Outputs** The P2WSH outputs are NOT real witness scripts. They are "fake" P2WSH outputs where the image data is embedded directly in the 32-byte "script hash" field: ```typescript // CORRECT: Data goes directly in the scriptPubKey const script = bitcoin.script.compile([ bitcoin.opcodes.OP_0, dataChunk, // 32-byte chunk of image data ]); // INCORRECT: NOT using witness scripts // This is what P2WSH normally does, but stamps don't do this ``` ### 2. **RC4 Encryption Key** The RC4 key is the first input's TXID (32 bytes hex): ```typescript // Use the first UTXO's txid as the RC4 key const rc4Key = utxos[0].txid; // Full 32-byte hex string const encryptedMessage = rc4Encrypt(rc4Key, prefixedMessage); ``` ### 3. **Message Type 22 Format** The Counterparty message uses type 22 (LR_ISSUANCE_ID) with this exact structure: ``` [8 bytes] 'CNTRPRTY' prefix (before encryption) [1 byte] 0x16 (22 decimal) - Message type [8 bytes] Asset ID (big-endian) [8 bytes] Quantity (big-endian, usually 1) [1 byte] Divisible flag (0 for stamps) [1 byte] Lock flag (1 for stamps) [1 byte] Reset flag (0 for stamps) [N bytes] Description ('STAMP:filename') ``` The entire message including prefix is RC4 encrypted. ## Process Flow (As Validated) ```mermaid sequenceDiagram participant User participant API as stampchain.io API participant TB as tx-builder participant BC as Bitcoin Network User->>API: POST /stamp {"base64", "filename", etc} API->>API: Generate CPID (A95428956662000000) API->>API: Create Counterparty message API->>API: RC4 encrypt with first input API->>API: Create fake P2WSH outputs API->>User: Return PSBT User->>TB: encode(same inputs) TB->>TB: Use same CPID TB->>TB: Create identical message TB->>TB: RC4 encrypt with same key TB->>TB: Create identical P2WSH outputs TB->>User: Return identical PSBT Note over API,TB: Both produce EXACT same outputs ``` ## Validation Script Results Our `final-validation-summary.ts` script proves exact parity: ### Bitcoin Stamps Validation ``` ✅ P2WSH outputs match EXACTLY ✅ OP_RETURN Counterparty encoding matches ✅ Both decrypt to same message ✅ Asset IDs match: A95428956662062966 ✅ Descriptions match: "STAMP:test.png" ``` ### SRC-20 Validation ``` ✅ P2WSH outputs match EXACTLY ✅ Normalized JSON matches ✅ Direct scriptPubKey embedding works ✅ No OP_RETURN (SRC-20 doesn't use Counterparty) ``` ## Key Differences from Standard Implementations ### 1. Stampchain.io API Format The API sometimes uses a slightly different message format with the asset ID at a different offset, but both formats are valid Counterparty. ### 2. Image Data Handling ```typescript // Add 0x00 prefix and length byte before chunking const prefixedData = Buffer.concat([ Buffer.from([0x00]), Buffer.from([imageData.length]), imageData, ]); // Then chunk into 32-byte pieces for P2WSH outputs ``` ### 3. Sub-Asset Support The encoder supports sub-assets (A12345.SUBASSET format): ```typescript if (assetName.includes('.')) { const [parentAsset, subAssetName] = assetName.split('.'); // Basic sub-asset support using parent ID } ``` ## Transaction Size Limits Based on Bitcoin network relay policy (not consensus rules): - **Maximum standard transaction size**: 100KB - **P2WSH output size**: 34 bytes each - **Maximum P2WSH outputs**: ~2,940 (in theory) - **Practical limit with overhead**: ~2,900 outputs - **Maximum data capacity**: ~93KB per transaction The encoder enforces these as sanity checks but they can be overridden: ```typescript const DEFAULT_MAX_OUTPUTS = Math.floor( MAX_STANDARD_TX_SIZE / P2WSH_OUTPUT_SIZE, ); // About 2,940 outputs maximum ``` ## API Compatibility ### CounterpartyProtocolHandler (in BitcoinStampsEncoder) ```typescript class CounterpartyProtocolHandler { static createOpReturnOutput( utxos: Array<{ txid: string; vout: number; value: number }>, assetName: string, supply: number = 1, filename?: string, options?: { isLocked?: boolean; divisible?: boolean; reset?: boolean; }, ): TransactionOutput; } ``` ### BitcoinStampsEncoder ```typescript const encoder = new BitcoinStampsEncoder(); const result = encoder.encode(stampData, { utxos, // Required for RC4 key cpid, // CPID like A95428956662000000 supply: 1, isLocked: true, }); // Result contains: // - opReturnOutput: Counterparty OP_RETURN // - p2wshOutputs: Fake P2WSH with image data // - metadata: Image info ``` ## Testing Commands Validate implementation against stampchain.io: ```bash # Run the validation script that proves 1:1 parity npx tsx scripts/final-validation-summary.ts ``` The script: 1. Calls stampchain.io API with test data 2. Uses tx-builder with same inputs 3. Compares outputs byte-for-byte 4. Decrypts and validates Counterparty messages 5. Shows exact match for both stamps and SRC-20 ## Production Validation Checklist**Message Type 22** - Issuance with description\ ✅ **RC4 Encryption** - Using first input TXID as key\ ✅ **CNTRPRTY Prefix** - 8 bytes, encrypted\ ✅ **Asset ID Format** - A followed by numeric ID\ ✅ **STAMP:filename** - In description field\ ✅ **Fake P2WSH** - Data in scriptPubKey, not witness\ ✅ **330 sats** - Per P2WSH output\ ✅ **Output Order** - OP_RETURN first, then P2WSH\ ✅ **1:1 Parity** - Exact match with stampchain.io ## Common Pitfalls Avoided 1. ❌ **Don't use real witness scripts** - Data goes in scriptPubKey 2. ❌ **Don't use shortened descriptions** - Always "STAMP:" 3. ❌ **Don't forget the 0x00 prefix** - Required before image data 4. ❌ **Don't use wrong RC4 key** - Must be first input's TXID 5. ❌ **Don't assume fixed message format** - API may vary slightly ## References - [Counterparty Core Implementation](https://github.com/CounterpartyXCP/counterparty-core/blob/master/counterparty-core/counterpartycore/lib/messages/issuance.py) - [Bitcoin Stamps Protocol](https://github.com/mikeinspace/stamps) - [Stampchain.io API](https://stampchain.io/docs) - [Our Validation Script](scripts/final-validation-summary.ts) ## Summary The tx-builder implementation achieves **perfect 1:1 parity** with stampchain.io: 1. **Bitcoin Stamps**: Counterparty OP_RETURN + Fake P2WSH outputs 2. **SRC-20 Tokens**: Fake P2WSH only (no Counterparty) 3. **Validation**: Proven identical outputs with production API All critical encoding details have been validated against real blockchain transactions and the stampchain.io API.