@btc-vision/transaction 1.6.4 → 1.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/browser/_version.d.ts +1 -1
  2. package/browser/generators/builders/P2WDAGenerator.d.ts +13 -0
  3. package/browser/index.js +1 -1
  4. package/browser/keypair/Address.d.ts +3 -2
  5. package/browser/keypair/AddressVerificator.d.ts +12 -1
  6. package/browser/keypair/Wallet.d.ts +3 -0
  7. package/browser/opnet.d.ts +4 -0
  8. package/browser/p2wda/P2WDADetector.d.ts +16 -0
  9. package/browser/transaction/TransactionFactory.d.ts +3 -1
  10. package/browser/transaction/builders/DeploymentTransaction.d.ts +3 -3
  11. package/browser/transaction/builders/InteractionTransactionP2WDA.d.ts +37 -0
  12. package/browser/transaction/builders/SharedInteractionTransaction.d.ts +4 -4
  13. package/browser/transaction/mineable/IP2WSHAddress.d.ts +4 -0
  14. package/browser/transaction/mineable/TimelockGenerator.d.ts +2 -5
  15. package/browser/transaction/shared/TweakedTransaction.d.ts +5 -0
  16. package/build/_version.d.ts +1 -1
  17. package/build/_version.js +1 -1
  18. package/build/generators/builders/P2WDAGenerator.d.ts +13 -0
  19. package/build/generators/builders/P2WDAGenerator.js +62 -0
  20. package/build/keypair/Address.d.ts +3 -2
  21. package/build/keypair/Address.js +28 -2
  22. package/build/keypair/AddressVerificator.d.ts +12 -1
  23. package/build/keypair/AddressVerificator.js +78 -1
  24. package/build/keypair/Wallet.d.ts +3 -0
  25. package/build/keypair/Wallet.js +4 -0
  26. package/build/opnet.d.ts +4 -0
  27. package/build/opnet.js +4 -0
  28. package/build/p2wda/P2WDADetector.d.ts +16 -0
  29. package/build/p2wda/P2WDADetector.js +97 -0
  30. package/build/transaction/TransactionFactory.d.ts +3 -1
  31. package/build/transaction/TransactionFactory.js +35 -4
  32. package/build/transaction/builders/DeploymentTransaction.d.ts +3 -3
  33. package/build/transaction/builders/DeploymentTransaction.js +1 -1
  34. package/build/transaction/builders/InteractionTransactionP2WDA.d.ts +37 -0
  35. package/build/transaction/builders/InteractionTransactionP2WDA.js +205 -0
  36. package/build/transaction/builders/SharedInteractionTransaction.d.ts +4 -4
  37. package/build/transaction/builders/SharedInteractionTransaction.js +3 -3
  38. package/build/transaction/mineable/IP2WSHAddress.d.ts +4 -0
  39. package/build/transaction/mineable/IP2WSHAddress.js +1 -0
  40. package/build/transaction/mineable/TimelockGenerator.d.ts +2 -5
  41. package/build/transaction/shared/TweakedTransaction.d.ts +5 -0
  42. package/build/transaction/shared/TweakedTransaction.js +19 -0
  43. package/doc/README.md +0 -0
  44. package/doc/addresses/P2OP.md +1 -0
  45. package/doc/addresses/P2WDA.md +240 -0
  46. package/package.json +1 -1
  47. package/src/_version.ts +1 -1
  48. package/src/generators/builders/P2WDAGenerator.ts +174 -0
  49. package/src/keypair/Address.ts +58 -3
  50. package/src/keypair/AddressVerificator.ts +140 -1
  51. package/src/keypair/Wallet.ts +16 -0
  52. package/src/opnet.ts +4 -0
  53. package/src/p2wda/P2WDADetector.ts +218 -0
  54. package/src/transaction/TransactionFactory.ts +79 -5
  55. package/src/transaction/builders/DeploymentTransaction.ts +4 -3
  56. package/src/transaction/builders/InteractionTransactionP2WDA.ts +376 -0
  57. package/src/transaction/builders/SharedInteractionTransaction.ts +7 -6
  58. package/src/transaction/mineable/IP2WSHAddress.ts +4 -0
  59. package/src/transaction/mineable/TimelockGenerator.ts +2 -6
  60. package/src/transaction/shared/TweakedTransaction.ts +36 -0
@@ -0,0 +1,240 @@
1
+ # Pay-to-Witness-Data-Authentication (P2WDA)
2
+
3
+ ## Executive Summary
4
+
5
+ Bitcoin makes arbitrary authenticated data inclusion expensive unless you exploit the SegWit witness discount, where
6
+ every witness byte weighs 1 unit instead of 4. P2WDA is a spend template that deliberately carries authenticated
7
+ application data in witness stack items and then drops them before a standard signature check. This preserves standard
8
+ Bitcoin validation while letting applications verify the attached data out-of-band.
9
+
10
+ In this implementation, the witness stack reserves **10 data slots** per P2WDA input, each **≤80 bytes** to respect
11
+ default relay policy. The data (after compression) is prefixed by a **BIP340 Schnorr** signature over
12
+ `(tx_signature || uncompressed_data)`, then split across those slots. Applications reassemble and verify the signed
13
+ data, making any miner tampering detectable. On-chain validation remains standard and cheap thanks to the witness
14
+ discount.
15
+
16
+ ## 1. Problem Space
17
+
18
+ ### 1.1 The Traditional Dilemma
19
+
20
+ OP_RETURN is simple but tiny: **80 bytes max** per output under standard policy. Anything larger forces many outputs and
21
+ multiple transactions, compounding base (non-witness) bytes that weigh 4× witness bytes. For realistic payloads (
22
+ airdrops, rich metadata), this is cost-prohibitive.
23
+
24
+ Importantly, even if OP_RETURN size limits were completely removed, it would not solve the fundamental economic problem.
25
+ OP_RETURN data is stored in the transaction's output section, which means every byte counts as non-witness data and
26
+ incurs the full 4× weight penalty. A hypothetical uncapped OP_RETURN storing 800 bytes would cost 3,200 weight units,
27
+ while P2WDA achieves the same data storage for only 800 weight units in the witness section. The economic disadvantage
28
+ of OP_RETURN is architectural, not merely a policy limitation.
29
+
30
+ Commit-reveal styles fix integrity but double touches the chain (commit tx, reveal tx) and fragment UX.
31
+
32
+ ### 1.2 The Witness Discount Opportunity
33
+
34
+ SegWit introduced **weight**: non-witness bytes weigh 4 units each; witness bytes weigh **1**. Fees are proportional to
35
+ weight (vbytes ≈ weight/4). Packing authenticated data into witness can be ~75% cheaper than encoding the same data in
36
+ base tx bytes.
37
+
38
+ ## 2. Technical Architecture
39
+
40
+ ### 2.1 Spend Template
41
+
42
+ P2WDA uses a **P2WSH** spend whose script pre-drops a fixed number of stack items (the data slots), then performs a
43
+ standard single-sig check.
44
+
45
+ The witness script consists of:
46
+
47
+ - 5 consecutive `OP_2DROP` operations (dropping 10 items total)
48
+ - A 33-byte compressed public key
49
+ - An `OP_CHECKSIG` operation
50
+
51
+ This creates a script of approximately 40 bytes that validates like any standard single-signature P2WSH spend, but with
52
+ space for our data payload in the witness stack.
53
+
54
+ ### 2.2 Authentication & Packing
55
+
56
+ The authentication and packing process ensures data integrity while maximizing compression efficiency. Here's how it
57
+ works:
58
+
59
+ **Step 1: Prepare the data**
60
+
61
+ - Start with your uncompressed payload data (application bytes)
62
+ - Get the transaction signature (the DER signature that authorizes spending this input)
63
+
64
+ **Step 2: Create authentication signature**
65
+
66
+ - Compute a BIP340 Schnorr signature over the hash of (transaction_signature || payload_data)
67
+ - This signature proves authorship and binds the data to this specific spend
68
+
69
+ **Step 3: Combine and compress**
70
+
71
+ ```ts
72
+ // Combine the authentication signature with the payload
73
+ const combined_bytes = data_signature + payload_data;
74
+
75
+ // Compress everything using DEFLATE or similar
76
+ const compressed_bytes = COMPRESS(combined_bytes);
77
+
78
+ // Split into chunks of max 80 bytes each
79
+ const chunks = SPLIT_INTO_80_BYTE_CHUNKS(compressed_bytes);
80
+
81
+ // Ensure we don't exceed 10 chunks
82
+ if (chunks.length > 10) {
83
+ throw Error("Payload too large")
84
+ }
85
+ ```
86
+
87
+ **Step 4: Build the witness stack**
88
+ The witness stack must contain exactly 12 items in this order:
89
+
90
+ 1. Data slot 0 (up to 80 bytes, or empty)
91
+ 2. Data slot 1 (up to 80 bytes, or empty)
92
+ 3. ... through Data slot 9
93
+ 4. Transaction signature (DER encoded, ~72 bytes)
94
+ 5. Witness script (~40 bytes)
95
+
96
+ Any unused data slots are filled with empty byte arrays (length 0) to maintain the expected stack structure.
97
+
98
+ **Verification Process for Indexers:**
99
+
100
+ When an indexer encounters a P2WDA spend, it:
101
+
102
+ 1. Extracts and concatenates the first 10 witness items
103
+ 2. Decompresses the result to get (data_signature || original_data)
104
+ 3. Verifies the Schnorr signature against hash(tx_signature || original_data)
105
+ 4. If valid, passes the original_data to the application layer
106
+
107
+ This design ensures that while Bitcoin consensus never inspects the data, any tampering is cryptographically detectable
108
+ by applications.
109
+
110
+ ### 2.3 Input Placement Rules
111
+
112
+ To simplify parsing and minimize duplication:
113
+
114
+ * **All application data must be injected in the first P2WDA input by index** (the lowest-index input spending a P2WDA
115
+ UTXO)
116
+ * Any **additional P2WDA inputs** in the same tx must supply **10 empty data items** (length=0) in their witness
117
+ * Non-P2WDA inputs are unaffected
118
+ * If a transaction flagged as `InteractionTransactionP2WDA` spends **no** P2WDA UTXOs, **throw an error**
119
+
120
+ ## 3. Economics
121
+
122
+ ### 3.1 OP_RETURN Baseline
123
+
124
+ Standard OP_RETURN script is ≤83 bytes total, allowing ≤80 bytes data. All bytes are non-witness, costing **1 vbyte per
125
+ byte**. For 80 bytes of data you typically consume ~91 vbytes including script overhead and output framing.
126
+
127
+ ### 3.2 P2WDA Spend
128
+
129
+ The witness bytes calculation for a P2WDA input includes:
130
+
131
+ - 1 byte for item count
132
+ - 10 bytes for length prefixes (one per data slot)
133
+ - The actual compressed data bytes
134
+ - ~73 bytes for the transaction signature (including length prefix)
135
+ - ~41 bytes for the witness script (including length prefix)
136
+
137
+ Since all of this is witness data, it costs only 1/4 the weight of equivalent non-witness bytes.
138
+
139
+ **Example with 512 bytes of incompressible data:**
140
+
141
+ - Data + signature = 576 bytes total
142
+ - Split across 8 slots (72 bytes each)
143
+ - Total witness bytes ≈ 701
144
+ - Cost in vbytes ≈ 175
145
+
146
+ Compare to OP_RETURN which would need 7 outputs at ~91 vbytes each ≈ 637 vbytes.
147
+ **Result: ~72% savings** while keeping everything in a single transaction.
148
+
149
+ ## 4. Security Model
150
+
151
+ The security model separates on-chain authorization from off-chain data authentication:
152
+
153
+ * **On-chain authorization**: The transaction signature proves spend authorization exactly like any P2WSH single-sig
154
+ spend
155
+ * **Off-chain authorship**: The Schnorr signature authenticates the data and binds it to this spend via the transaction
156
+ signature
157
+ * **Malleability handling**: SegWit allows witness data modification without changing the txid. The Schnorr signature
158
+ ensures any tampering is detectable
159
+
160
+ Practical outcomes:
161
+
162
+ - Funds cannot be redirected (protected by transaction signature)
163
+ - Data forgery is detectable at the application layer (Schnorr signature fails)
164
+ - Miners could theoretically replace data with garbage, but applications will reject it
165
+
166
+ ## 5. Operational Capabilities
167
+
168
+ P2WDA works well for:
169
+
170
+ * Mints
171
+ * Airdrops
172
+ * NFTs
173
+ * Batch updates and state checkpoints
174
+ * Governance votes and attestations
175
+ * Swap listings (like nativeswap) but **NOT TRADES**, a miner could cancel your transaction!
176
+ * etc.
177
+
178
+ The 10 slots serve as a transport layer; keep application formats versioned and compact.
179
+
180
+ ## 6. Advanced Considerations
181
+
182
+ ### 6.1 Why 10 Slots of ≤80 Bytes?
183
+
184
+ Default relay policy limits:
185
+
186
+ - Maximum 100 stack items for P2WSH
187
+ - Maximum 80 bytes per non-script witness item
188
+ - Maximum 3600 bytes for the witness script itself
189
+
190
+ Ten slots provides headroom while keeping scripts simple (5 * OP_2DROP). You can scale by using multiple P2WDA inputs,
191
+ keeping data only in the first P2WDA input and zeros in the rest.
192
+
193
+ ### 6.2 Domain Separation (Recommended)
194
+
195
+ To harden against cross-protocol attacks, consider using domain separation:
196
+
197
+ ```
198
+ message = Hash("P2WDA/v1" || txid || input_index || tx_signature || data)
199
+ data_signature = Schnorr.Sign(auth_private_key, message)
200
+ ```
201
+
202
+ This prevents signatures from being reused across different protocols or transactions.
203
+
204
+ ### 6.3 Compression
205
+
206
+ Use a standard compression algorithm like DEFLATE to maximize data packing efficiency. Ensure your application layer
207
+ can handle decompression and verify the integrity of the decompressed data.
208
+
209
+ ### 6.4 Why SegWit Instead of Taproot?
210
+
211
+ A common question is why P2WDA uses SegWit's P2WSH instead of the newer Taproot technology. This decision is deliberate
212
+ and based on fundamental economics of block space usage. Understanding this choice illuminates the elegant design of
213
+ P2WDA.
214
+
215
+ #### The Block Space Reality
216
+
217
+ When people first hear about P2WDA, they might assume it should use Taproot since it's Bitcoin's newest upgrade.
218
+ However, Taproot would actually consume more block space for our use case, making it less efficient. This
219
+ counterintuitive reality stems from how these technologies were designed for different purposes.
220
+
221
+ Taproot offers two spending paths. The key path is remarkably efficient, requiring only a 64-byte Schnorr signature, but
222
+ it cannot carry arbitrary data. To include data, you must use the script path, which requires a control block containing
223
+ the internal public key, parity information, and Merkle proof of your script's inclusion in the taproot tree. Even with
224
+ the simplest possible tree structure, this control block adds approximately 65 bytes of pure overhead.
225
+
226
+ Let's examine the concrete numbers for storing 500 bytes of authenticated data:
227
+
228
+ With P2WSH (what P2WDA uses), the witness contains the transaction signature (~72 bytes), your data (500 bytes), and the
229
+ witness script (~40 bytes), totaling approximately 612 bytes, which equals 612 weight units.
230
+
231
+ With Taproot's script path, you would need a Schnorr signature (64 bytes), your data (500 bytes), the tapscript (~40
232
+ bytes), and the control block (~65 bytes), totaling approximately 669 bytes, which equals 669 weight units.
233
+
234
+ This represents about 10% more block space consumption for identical functionality. When your goal is cost-efficient
235
+ data storage, every byte matters, and this overhead directly translates to higher fees for users.
236
+
237
+ ### 6.4 Future Extensions
238
+
239
+ - Use annexes (BIP490) to carry larger payloads if needed and stop requiring another signature in the witness stack
240
+ - Explore multi-signature variants for collaborative data signing
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@btc-vision/transaction",
3
3
  "type": "module",
4
- "version": "1.6.4",
4
+ "version": "1.6.5",
5
5
  "author": "BlobMaster41",
6
6
  "description": "OPNet transaction library allows you to create and sign transactions for the OPNet network.",
7
7
  "engines": {
package/src/_version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '1.6.4';
1
+ export const version = '1.6.5';
@@ -0,0 +1,174 @@
1
+ import { Network, networks } from '@btc-vision/bitcoin';
2
+ import { BinaryWriter } from '../../buffer/BinaryWriter.js';
3
+ import { Feature, Features } from '../Features.js';
4
+ import { Generator } from '../Generator.js';
5
+ import { ChallengeSolution } from '../../epoch/ChallengeSolution.js';
6
+
7
+ export class P2WDAGenerator extends Generator {
8
+ private static readonly P2WDA_VERSION = 0x01;
9
+
10
+ constructor(
11
+ senderPubKey: Buffer,
12
+ contractSaltPubKey: Buffer,
13
+ network: Network = networks.bitcoin,
14
+ ) {
15
+ super(senderPubKey, contractSaltPubKey, network);
16
+ }
17
+
18
+ /**
19
+ * Validate that operation data will fit in P2WDA witness fields
20
+ *
21
+ * @param dataSize Size of the operation data
22
+ * @param maxWitnessFields Maximum number of witness fields (default 10)
23
+ * @param maxBytesPerWitness Maximum bytes per witness field (default 80)
24
+ * @returns true if data will fit, false otherwise
25
+ */
26
+ public static validateWitnessSize(
27
+ dataSize: number,
28
+ maxWitnessFields: number = 10,
29
+ maxBytesPerWitness: number = 80,
30
+ ): boolean {
31
+ // Account for Schnorr signature (64 bytes) and compression
32
+ // Assume 30% compression ratio (conservative estimate)
33
+
34
+ const signatureSize = 64;
35
+ const compressionRatio = 0.7;
36
+
37
+ const totalSize = dataSize + signatureSize;
38
+ const compressedSize = Math.ceil(totalSize * compressionRatio);
39
+
40
+ const requiredFields = Math.ceil(compressedSize / maxBytesPerWitness);
41
+
42
+ return requiredFields <= maxWitnessFields;
43
+ }
44
+
45
+ /**
46
+ * Compile operation data for P2WDA witness embedding
47
+ *
48
+ * This creates a binary structure containing all operation information
49
+ * without Bitcoin script opcodes. The structure is:
50
+ *
51
+ * [version(1)] [header(12)] [contract(32)] [challenge_pubkey(33)] [challenge_solution(32)]
52
+ * [calldata_length(4)] [calldata] [features_length(2)] [features_data]
53
+ *
54
+ * @param calldata The compressed calldata for the contract interaction
55
+ * @param contractSecret The 32-byte contract secret
56
+ * @param challenge The challenge solution for epoch rewards
57
+ * @param maxPriority Maximum priority fee in satoshis
58
+ * @param features Optional features like access lists
59
+ * @returns Raw operation data ready for signing and compression
60
+ */
61
+ public compile(
62
+ calldata: Buffer,
63
+ contractSecret: Buffer,
64
+ challenge: ChallengeSolution,
65
+ maxPriority: bigint,
66
+ features: Feature<Features>[] = [],
67
+ ): Buffer {
68
+ if (!this.contractSaltPubKey) {
69
+ throw new Error('Contract salt public key not set');
70
+ }
71
+
72
+ if (contractSecret.length !== 32) {
73
+ throw new Error('Contract secret must be exactly 32 bytes');
74
+ }
75
+
76
+ const writer = new BinaryWriter();
77
+
78
+ // Version byte
79
+ writer.writeU8(P2WDAGenerator.P2WDA_VERSION);
80
+
81
+ // Header
82
+ writer.writeBytes(
83
+ this.getHeader(
84
+ maxPriority,
85
+ features.map((f) => f.opcode),
86
+ ),
87
+ );
88
+
89
+ // Contract secret
90
+ writer.writeBytes(contractSecret);
91
+
92
+ // Challenge components for epoch rewards
93
+ writer.writeBytes(challenge.publicKey.originalPublicKeyBuffer());
94
+ writer.writeBytes(challenge.solution);
95
+
96
+ // Calldata with length prefix
97
+ writer.writeU32(calldata.length);
98
+ writer.writeBytes(calldata);
99
+
100
+ // Features
101
+ this.writeFeatures(writer, features);
102
+
103
+ return Buffer.from(writer.getBuffer());
104
+ }
105
+
106
+ /**
107
+ * Create a minimal header for P2WDA operations
108
+ *
109
+ * The header contains essential transaction metadata in a compact format:
110
+ * [sender_pubkey_prefix(1)] [feature_flags(3)] [max_priority(8)]
111
+ *
112
+ * @param maxPriority Maximum priority fee
113
+ * @param features Feature opcodes to set in flags
114
+ * @returns 12-byte header
115
+ */
116
+ public override getHeader(maxPriority: bigint, features: Features[] = []): Buffer {
117
+ return super.getHeader(maxPriority, features);
118
+ }
119
+
120
+ /**
121
+ * Write features section to the operation data
122
+ *
123
+ * Features are encoded as:
124
+ * [feature_count(2)] [feature1_opcode(1)] [feature1_length(4)] [feature1_data] ...
125
+ *
126
+ * @param writer Binary writer to write to
127
+ * @param features Array of features to encode
128
+ */
129
+ private writeFeatures(writer: BinaryWriter, features: Feature<Features>[]): void {
130
+ // Write feature count
131
+ writer.writeU16(features.length);
132
+
133
+ for (const feature of features) {
134
+ // Write feature opcode
135
+ writer.writeU8(feature.opcode);
136
+
137
+ // Encode feature data
138
+ const encodedData = this.encodeFeatureData(feature);
139
+
140
+ // Write feature data with length prefix
141
+ writer.writeU32(encodedData.length);
142
+ writer.writeBytes(encodedData);
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Encode a single feature's data
148
+ *
149
+ * Unlike the base Generator class, we don't split into chunks here
150
+ * since P2WDA handles chunking at the witness level
151
+ *
152
+ * @param feature The feature to encode
153
+ * @returns Encoded feature data
154
+ */
155
+ private encodeFeatureData(feature: Feature<Features>): Buffer {
156
+ switch (feature.opcode) {
157
+ case Features.ACCESS_LIST: {
158
+ // Access lists are already encoded efficiently by the parent class
159
+ const chunks = this.encodeFeature(feature);
160
+ // Flatten chunks since P2WDA doesn't need script-level chunking
161
+ return Buffer.concat(chunks.flat());
162
+ }
163
+
164
+ case Features.EPOCH_SUBMISSION: {
165
+ // Epoch submissions are also handled by parent
166
+ const chunks = this.encodeFeature(feature);
167
+ return Buffer.concat(chunks.flat());
168
+ }
169
+
170
+ default:
171
+ throw new Error(`Unknown feature type: ${feature.opcode}`);
172
+ }
173
+ }
174
+ }
@@ -5,7 +5,9 @@ import { AddressVerificator } from './AddressVerificator.js';
5
5
  import { EcKeyPair } from './EcKeyPair.js';
6
6
  import { ContractAddress } from '../transaction/ContractAddress.js';
7
7
  import { BitcoinUtils } from '../utils/BitcoinUtils.js';
8
- import { ITimeLockOutput, TimeLockGenerator } from '../transaction/mineable/TimelockGenerator.js';
8
+ import { TimeLockGenerator } from '../transaction/mineable/TimelockGenerator.js';
9
+ import { IP2WSHAddress } from '../transaction/mineable/IP2WSHAddress.js';
10
+ import { P2WDADetector } from '../p2wda/P2WDADetector.js';
9
11
 
10
12
  /**
11
13
  * Objects of type "Address" are the representation of tweaked public keys. They can be converted to different address formats.
@@ -19,6 +21,7 @@ export class Address extends Uint8Array {
19
21
  #keyPair: ECPairInterface | undefined;
20
22
  #uncompressed: UncompressedPublicKey | undefined;
21
23
  #tweakedUncompressed: Buffer | undefined;
24
+ #p2wda: IP2WSHAddress | undefined;
22
25
 
23
26
  public constructor(bytes?: ArrayLike<number>) {
24
27
  super(ADDRESS_BYTE_LENGTH);
@@ -318,6 +321,58 @@ export class Address extends Uint8Array {
318
321
  throw new Error('Public key not set');
319
322
  }
320
323
 
324
+ /**
325
+ * Generate a P2WDA (Pay-to-Witness-Data-Authentication) address
326
+ *
327
+ * P2WDA addresses are a special type of P2WSH address that allows embedding
328
+ * authenticated data directly in the witness field, achieving 75% cost reduction
329
+ * through Bitcoin's witness discount.
330
+ *
331
+ * The witness script pattern is: (OP_2DROP * 5) <pubkey> OP_CHECKSIG
332
+ * This allows up to 10 witness data fields (5 * 2 = 10), where each field
333
+ * can hold up to 80 bytes of data due to relay rules.
334
+ *
335
+ * @param {Network} network - The Bitcoin network to use
336
+ * @returns {IP2WSHAddress} The P2WDA address
337
+ * @throws {Error} If the public key is not set or address generation fails
338
+ *
339
+ * @example
340
+ * ```typescript
341
+ * const address = Address.fromString('0x02...');
342
+ * const p2wdaAddress = address.p2wda(networks.bitcoin);
343
+ * console.log(p2wdaAddress); // bc1q...
344
+ * ```
345
+ */
346
+ public p2wda(network: Network): IP2WSHAddress {
347
+ if (this.#p2wda && this.#network === network) {
348
+ return this.#p2wda;
349
+ }
350
+
351
+ if (!this.#originalPublicKey) {
352
+ throw new Error('Cannot create P2WDA address: public key not set');
353
+ }
354
+
355
+ const publicKeyBuffer = Buffer.from(this.#originalPublicKey);
356
+
357
+ if (publicKeyBuffer.length !== 33) {
358
+ throw new Error('P2WDA requires a compressed public key (33 bytes)');
359
+ }
360
+
361
+ try {
362
+ const p2wdaInfo = P2WDADetector.generateP2WDAAddress(publicKeyBuffer, network);
363
+
364
+ this.#network = network;
365
+ this.#p2wda = p2wdaInfo;
366
+
367
+ return {
368
+ address: p2wdaInfo.address,
369
+ witnessScript: p2wdaInfo.witnessScript,
370
+ };
371
+ } catch (error) {
372
+ throw new Error(`Failed to generate P2WDA address: ${(error as Error).message}`);
373
+ }
374
+ }
375
+
321
376
  /**
322
377
  * Generate a P2WSH address with CSV (CheckSequenceVerify) timelock
323
378
  * The resulting address can only be spent after the specified number of blocks
@@ -325,10 +380,10 @@ export class Address extends Uint8Array {
325
380
  *
326
381
  * @param {bigint | number | string} duration - The number of blocks that must pass before spending (1-65535)
327
382
  * @param {Network} network - The Bitcoin network to use
328
- * @returns {ITimeLockOutput} The timelocked address and its witness script
383
+ * @returns {IP2WSHAddress} The timelocked address and its witness script
329
384
  * @throws {Error} If the block number is out of range or public key is not available
330
385
  */
331
- public toCSV(duration: bigint | number | string, network: Network): ITimeLockOutput {
386
+ public toCSV(duration: bigint | number | string, network: Network): IP2WSHAddress {
332
387
  const n = Number(duration);
333
388
 
334
389
  // First, let's validate the block number to ensure it's within the valid range
@@ -1,7 +1,8 @@
1
- import { address, initEccLib, Network } from '@btc-vision/bitcoin';
1
+ import { address, initEccLib, Network, payments } from '@btc-vision/bitcoin';
2
2
  import * as ecc from '@bitcoinerlab/secp256k1';
3
3
  import { EcKeyPair } from './EcKeyPair.js';
4
4
  import { BitcoinUtils } from '../utils/BitcoinUtils.js';
5
+ import { P2WDADetector } from '../p2wda/P2WDADetector.js';
5
6
 
6
7
  initEccLib(ecc);
7
8
 
@@ -13,6 +14,15 @@ export enum AddressTypes {
13
14
  P2TR = 'P2TR',
14
15
  P2WPKH = 'P2WPKH',
15
16
  P2WSH = 'P2WSH',
17
+ P2WDA = 'P2WDA',
18
+ }
19
+
20
+ export interface ValidatedP2WDAAddress {
21
+ readonly isValid: boolean;
22
+ readonly isPotentiallyP2WDA: boolean;
23
+ readonly isDefinitelyP2WDA: boolean;
24
+ readonly publicKey?: Buffer;
25
+ readonly error?: string;
16
26
  }
17
27
 
18
28
  export class AddressVerificator {
@@ -63,6 +73,22 @@ export class AddressVerificator {
63
73
  return isValidSegWitAddress;
64
74
  }
65
75
 
76
+ /**
77
+ * Check if a given witness script is a P2WDA witness script
78
+ *
79
+ * P2WDA witness scripts have a specific pattern:
80
+ * (OP_2DROP * 5) <pubkey> OP_CHECKSIG
81
+ *
82
+ * This pattern allows for 10 witness data fields (5 * 2 = 10),
83
+ * which can be used to embed authenticated operation data.
84
+ *
85
+ * @param witnessScript The witness script to check
86
+ * @returns true if this is a valid P2WDA witness script
87
+ */
88
+ public static isP2WDAWitnessScript(witnessScript: Buffer): boolean {
89
+ return P2WDADetector.isP2WDAWitnessScript(witnessScript);
90
+ }
91
+
66
92
  /**
67
93
  * Checks if the given address is a valid P2PKH or P2SH address.
68
94
  * @param addy - The address to check.
@@ -213,4 +239,117 @@ export class AddressVerificator {
213
239
 
214
240
  return null; // Not a valid or recognized Bitcoin address type
215
241
  }
242
+
243
+ /**
244
+ * Enhanced detectAddressType that provides hints about P2WDA
245
+ *
246
+ * Note: P2WDA addresses cannot be distinguished from regular P2WSH
247
+ * addresses without the witness script. When a P2WSH address is detected,
248
+ * it could potentially be P2WDA if it has the correct witness script.
249
+ *
250
+ * @param addy The address to analyze
251
+ * @param network The Bitcoin network
252
+ * @param witnessScript Optional witness script for P2WSH addresses
253
+ * @returns The address type, with P2WDA detection if witness script provided
254
+ */
255
+ public static detectAddressTypeWithWitnessScript(
256
+ addy: string,
257
+ network: Network,
258
+ witnessScript?: Buffer,
259
+ ): AddressTypes | null {
260
+ const baseType = AddressVerificator.detectAddressType(addy, network);
261
+
262
+ if (baseType === AddressTypes.P2WSH && witnessScript) {
263
+ if (AddressVerificator.isP2WDAWitnessScript(witnessScript)) {
264
+ return AddressTypes.P2WDA;
265
+ }
266
+ }
267
+
268
+ return baseType;
269
+ }
270
+
271
+ /**
272
+ * Validate a P2WDA address and extract information
273
+ *
274
+ * This method validates that an address is a properly formatted P2WSH
275
+ * address and, if a witness script is provided, verifies it matches
276
+ * the P2WDA pattern and corresponds to the address.
277
+ *
278
+ * @param address The address to validate
279
+ * @param network The Bitcoin network
280
+ * @param witnessScript Optional witness script to verify
281
+ * @returns Validation result with extracted information
282
+ */
283
+ public static validateP2WDAAddress(
284
+ address: string,
285
+ network: Network,
286
+ witnessScript?: Buffer,
287
+ ): ValidatedP2WDAAddress {
288
+ try {
289
+ const addressType = AddressVerificator.detectAddressType(address, network);
290
+ if (addressType !== AddressTypes.P2WSH) {
291
+ return {
292
+ isValid: false,
293
+ isPotentiallyP2WDA: false,
294
+ isDefinitelyP2WDA: false,
295
+ error: 'Not a P2WSH address',
296
+ };
297
+ }
298
+
299
+ if (!witnessScript) {
300
+ return {
301
+ isValid: true,
302
+ isPotentiallyP2WDA: true,
303
+ isDefinitelyP2WDA: false,
304
+ };
305
+ }
306
+
307
+ if (!AddressVerificator.isP2WDAWitnessScript(witnessScript)) {
308
+ return {
309
+ isValid: true,
310
+ isPotentiallyP2WDA: true,
311
+ isDefinitelyP2WDA: false,
312
+ error: 'Witness script does not match P2WDA pattern',
313
+ };
314
+ }
315
+
316
+ const p2wsh = payments.p2wsh({
317
+ redeem: { output: witnessScript },
318
+ network,
319
+ });
320
+
321
+ if (p2wsh.address !== address) {
322
+ return {
323
+ isValid: false,
324
+ isPotentiallyP2WDA: false,
325
+ isDefinitelyP2WDA: false,
326
+ error: 'Witness script does not match address',
327
+ };
328
+ }
329
+
330
+ const publicKey = P2WDADetector.extractPublicKeyFromP2WDA(witnessScript);
331
+ if (!publicKey) {
332
+ return {
333
+ isValid: false,
334
+ isPotentiallyP2WDA: false,
335
+ isDefinitelyP2WDA: false,
336
+ error: 'Failed to extract public key from witness script',
337
+ };
338
+ }
339
+
340
+ return {
341
+ isValid: true,
342
+ isPotentiallyP2WDA: true,
343
+ isDefinitelyP2WDA: true,
344
+ publicKey,
345
+ };
346
+ } catch (error) {
347
+ return {
348
+ isValid: false,
349
+ isPotentiallyP2WDA: false,
350
+ isDefinitelyP2WDA: false,
351
+ error: (error as Error).message,
352
+ };
353
+ }
354
+ }
216
355
  }