@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
@@ -3,6 +3,7 @@ import { EcKeyPair } from './EcKeyPair.js';
3
3
  import { Network, networks, toXOnly } from '@btc-vision/bitcoin';
4
4
  import { Address } from './Address.js';
5
5
  import { BitcoinUtils } from '../utils/BitcoinUtils.js';
6
+ import { IP2WSHAddress } from '../transaction/mineable/IP2WSHAddress.js';
6
7
 
7
8
  /**
8
9
  * Wallet class
@@ -26,6 +27,12 @@ export class Wallet {
26
27
  */
27
28
  private readonly _p2tr: string;
28
29
 
30
+ /**
31
+ * P2WDA Pay-to-Witness-Data-Authentication
32
+ * @private
33
+ */
34
+ private readonly _p2wda: IP2WSHAddress;
35
+
29
36
  /**
30
37
  * Legacy address for the wallet
31
38
  * @private
@@ -80,6 +87,7 @@ export class Wallet {
80
87
  this._p2wpkh = this._address.p2wpkh(this.network);
81
88
  this._legacy = this._address.p2pkh(this.network);
82
89
  this._segwitLegacy = this._address.p2wpkh(this.network);
90
+ this._p2wda = this._address.p2wda(this.network);
83
91
 
84
92
  this._tweakedKey = this._address.toBuffer();
85
93
  }
@@ -126,6 +134,14 @@ export class Wallet {
126
134
  return this._p2tr;
127
135
  }
128
136
 
137
+ /**
138
+ * Get the P2WDA address for the wallet
139
+ * @returns {string}
140
+ */
141
+ public get p2wda(): IP2WSHAddress {
142
+ return this._p2wda;
143
+ }
144
+
129
145
  /**
130
146
  * Get the legacy address for the wallet
131
147
  * @returns {string}
package/src/opnet.ts CHANGED
@@ -11,10 +11,13 @@ export * from './generators/builders/CustomGenerator.js';
11
11
  export * from './generators/builders/DeploymentGenerator.js';
12
12
  export * from './generators/builders/LegacyCalldataGenerator.js';
13
13
  export * from './generators/builders/MultiSignGenerator.js';
14
+ export * from './generators/builders/P2WDAGenerator.js';
14
15
  export * from './generators/Features.js';
15
16
  export * from './generators/Generator.js';
16
17
 
17
18
  export * from './transaction/mineable/TimelockGenerator.js';
19
+ export * from './transaction/mineable/IP2WSHAddress.js';
20
+ export * from './p2wda/P2WDADetector.js';
18
21
 
19
22
  /** Address */
20
23
  export * from './generators/AddressGenerator.js';
@@ -45,6 +48,7 @@ export * from './transaction/builders/CustomScriptTransaction.js';
45
48
  export * from './transaction/builders/DeploymentTransaction.js';
46
49
  export * from './transaction/builders/FundingTransaction.js';
47
50
  export * from './transaction/builders/InteractionTransaction.js';
51
+ export * from './transaction/builders/InteractionTransactionP2WDA.js';
48
52
  export * from './transaction/builders/MultiSignTransaction.js';
49
53
  export * from './transaction/builders/SharedInteractionTransaction.js';
50
54
  export * from './transaction/builders/TransactionBuilder.js';
@@ -0,0 +1,218 @@
1
+ import { Buffer } from 'buffer';
2
+ import { Network, opcodes, payments, script } from '@btc-vision/bitcoin';
3
+ import { UTXO } from '../utxo/interfaces/IUTXO.js';
4
+ import { IP2WSHAddress } from '../transaction/mineable/IP2WSHAddress.js';
5
+
6
+ /**
7
+ * P2WDA Detection and Validation Utilities
8
+ *
9
+ * This class provides methods to detect and validate P2WDA (Pay-to-Witness-Data-Authentication) addresses
10
+ * and UTXOs. P2WDA addresses have a specific witness script pattern that allows for efficient data storage.
11
+ */
12
+ export class P2WDADetector {
13
+ /**
14
+ * Check if a UTXO is a P2WDA output by examining its script structure
15
+ *
16
+ * @param utxo The UTXO to check
17
+ * @returns true if this is a P2WDA UTXO
18
+ */
19
+ public static isP2WDAUTXO(utxo: UTXO): boolean {
20
+ // P2WDA outputs are P2WSH outputs with a specific witness script pattern
21
+ if (!utxo.witnessScript) {
22
+ return false;
23
+ }
24
+
25
+ const witnessScript = Buffer.isBuffer(utxo.witnessScript)
26
+ ? utxo.witnessScript
27
+ : Buffer.from(utxo.witnessScript, 'hex');
28
+
29
+ return this.isP2WDAWitnessScript(witnessScript);
30
+ }
31
+
32
+ /**
33
+ * Check if a witness script follows the P2WDA pattern
34
+ *
35
+ * P2WDA witness script pattern: (OP_2DROP * 5) <pubkey> OP_CHECKSIG
36
+ * This allows for up to 10 witness data fields (5 * 2 = 10)
37
+ *
38
+ * @param witnessScript The witness script to check
39
+ * @returns true if this is a P2WDA witness script
40
+ */
41
+ public static isP2WDAWitnessScript(witnessScript: Buffer): boolean {
42
+ try {
43
+ const decompiled = script.decompile(witnessScript);
44
+
45
+ if (!decompiled || decompiled.length !== 7) {
46
+ return false;
47
+ }
48
+
49
+ // Check for 5 OP_2DROP operations
50
+ for (let i = 0; i < 5; i++) {
51
+ if (decompiled[i] !== opcodes.OP_2DROP) {
52
+ return false;
53
+ }
54
+ }
55
+
56
+ // Check for pubkey and OP_CHECKSIG
57
+ return (
58
+ Buffer.isBuffer(decompiled[5]) &&
59
+ decompiled[5].length === 33 && // Compressed public key
60
+ decompiled[6] === opcodes.OP_CHECKSIG
61
+ );
62
+ } catch {
63
+ return false;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Generate a P2WDA address from a public key
69
+ *
70
+ * @param publicKey The public key to use (33 bytes compressed)
71
+ * @param network The Bitcoin network
72
+ * @returns The P2WDA address and related payment information
73
+ */
74
+ public static generateP2WDAAddress(
75
+ publicKey: Buffer,
76
+ network: Network,
77
+ ): IP2WSHAddress & {
78
+ scriptPubKey: Buffer;
79
+ } {
80
+ if (publicKey.length !== 33) {
81
+ throw new Error('Public key must be 33 bytes (compressed)');
82
+ }
83
+
84
+ // Create the P2WDA witness script with 5x OP_2DROP
85
+ const witnessScript = script.compile([
86
+ opcodes.OP_2DROP,
87
+ opcodes.OP_2DROP,
88
+ opcodes.OP_2DROP,
89
+ opcodes.OP_2DROP,
90
+ opcodes.OP_2DROP,
91
+ publicKey,
92
+ opcodes.OP_CHECKSIG,
93
+ ]);
94
+
95
+ // Wrap in P2WSH
96
+ const p2wsh = payments.p2wsh({
97
+ redeem: { output: witnessScript },
98
+ network,
99
+ });
100
+
101
+ if (!p2wsh.address || !p2wsh.output) {
102
+ throw new Error('Failed to generate P2WDA address');
103
+ }
104
+
105
+ return {
106
+ address: p2wsh.address,
107
+ witnessScript,
108
+ scriptPubKey: p2wsh.output,
109
+ };
110
+ }
111
+
112
+ /**
113
+ * Extract the public key from a P2WDA witness script
114
+ *
115
+ * @param witnessScript The P2WDA witness script
116
+ * @returns The public key or null if not a valid P2WDA script
117
+ */
118
+ public static extractPublicKeyFromP2WDA(witnessScript: Buffer): Buffer | null {
119
+ try {
120
+ const decompiled = script.decompile(witnessScript);
121
+
122
+ if (!decompiled || decompiled.length !== 7) {
123
+ return null;
124
+ }
125
+
126
+ // Check for 5x OP_2DROP pattern
127
+ for (let i = 0; i < 5; i++) {
128
+ if (decompiled[i] !== opcodes.OP_2DROP) {
129
+ return null;
130
+ }
131
+ }
132
+
133
+ if (
134
+ Buffer.isBuffer(decompiled[5]) &&
135
+ decompiled[5].length === 33 &&
136
+ decompiled[6] === opcodes.OP_CHECKSIG
137
+ ) {
138
+ return decompiled[5];
139
+ }
140
+
141
+ return null;
142
+ } catch {
143
+ return null;
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Create witness data for a simple P2WDA spend (no operation data)
149
+ *
150
+ * For simple transfers, P2WDA requires 10 dummy witness items (zeros) before the signature
151
+ *
152
+ * @param transactionSignature The transaction signature
153
+ * @param witnessScript The P2WDA witness script
154
+ * @returns The witness stack for a simple P2WDA spend
155
+ */
156
+ public static createSimpleP2WDAWitness(
157
+ transactionSignature: Buffer,
158
+ witnessScript: Buffer,
159
+ ): Buffer[] {
160
+ const witnessStack: Buffer[] = [transactionSignature];
161
+
162
+ // Add 10 empty buffers for the 5x OP_2DROP operations
163
+ // Bitcoin stack is reversed!
164
+ for (let i = 0; i < 10; i++) {
165
+ witnessStack.push(Buffer.alloc(0));
166
+ }
167
+
168
+ witnessStack.push(witnessScript);
169
+ return witnessStack;
170
+ }
171
+
172
+ /**
173
+ * Validate P2WDA operation data signature
174
+ *
175
+ * @param publicKey The public key from the witness script
176
+ * @param dataSignature The Schnorr signature
177
+ * @param operationData The operation data that was signed
178
+ * @returns true if the signature is valid
179
+ */
180
+ public static validateP2WDASignature(
181
+ publicKey: Buffer,
182
+ dataSignature: Buffer,
183
+ operationData: Buffer,
184
+ ): boolean {
185
+ // This would use MessageSigner.verifySignature internally
186
+ // For now, we'll assume the signature validation is handled by MessageSigner
187
+ return dataSignature.length === 64; // Schnorr signatures are always 64 bytes
188
+ }
189
+
190
+ /**
191
+ * Calculate the witness size for P2WDA transaction estimation
192
+ *
193
+ * @param dataSize The size of the operation data (0 for simple transfers)
194
+ * @returns The estimated witness size in bytes
195
+ */
196
+ public static estimateP2WDAWitnessSize(dataSize: number = 0): number {
197
+ // Witness structure:
198
+ // - Transaction signature: ~72 bytes
199
+ // - 10 data fields (can be empty or contain data)
200
+ // - Witness script: 39 bytes (5x OP_2DROP + 33-byte pubkey + OP_CHECKSIG)
201
+ // - Overhead for length prefixes: ~12 bytes (1 byte per witness element)
202
+
203
+ // For simple transfers, dataSize is 0 (10 empty fields)
204
+ // For interactions, dataSize is the total size of data split across fields
205
+ return 72 + dataSize + 39 + 12;
206
+ }
207
+
208
+ /**
209
+ * Check if a scriptPubKey is a P2WSH that could be P2WDA
210
+ *
211
+ * @param scriptPubKey The script public key to check
212
+ * @returns true if this could be a P2WDA output
213
+ */
214
+ public static couldBeP2WDA(scriptPubKey: Buffer): boolean {
215
+ // P2WDA uses P2WSH, which is version 0 witness with 32-byte program
216
+ return scriptPubKey.length === 34 && scriptPubKey[0] === 0x00 && scriptPubKey[1] === 0x20; // 32 bytes
217
+ }
218
+ }
@@ -23,6 +23,8 @@ import {
23
23
  } from './browser/Web3Provider.js';
24
24
  import { WindowWithWallets } from './browser/extensions/UnisatSigner.js';
25
25
  import { RawChallenge } from '../epoch/interfaces/IChallengeSolution.js';
26
+ import { P2WDADetector } from '../p2wda/P2WDADetector.js';
27
+ import { InteractionTransactionP2WDA } from './builders/InteractionTransactionP2WDA.js';
26
28
 
27
29
  export interface DeploymentResult {
28
30
  readonly transaction: [string, string];
@@ -48,7 +50,7 @@ export interface BitcoinTransferBase {
48
50
  }
49
51
 
50
52
  export interface InteractionResponse {
51
- readonly fundingTransaction: string;
53
+ readonly fundingTransaction: string | null;
52
54
  readonly interactionTransaction: string;
53
55
  readonly estimatedFees: bigint;
54
56
  readonly nextUTXOs: UTXO[];
@@ -181,6 +183,11 @@ export class TransactionFactory {
181
183
  throw new Error('Field "signer" not provided, OP_WALLET not detected.');
182
184
  }
183
185
 
186
+ const useP2WDA = this.hasP2WDAInputs(interactionParameters.utxos);
187
+ if (useP2WDA) {
188
+ return this.signP2WDAInteraction(interactionParameters);
189
+ }
190
+
184
191
  const inputs = this.parseOptionalInputs(interactionParameters.optionalInputs);
185
192
  const preTransaction: InteractionTransaction = new InteractionTransaction({
186
193
  ...interactionParameters,
@@ -234,7 +241,7 @@ export class TransactionFactory {
234
241
  ...this.getUTXOAsTransaction(signedTransaction.tx, interactionParameters.to, 0),
235
242
  ], // always 0
236
243
  randomBytes: preTransaction.getRndBytes(),
237
- challenge: preTransaction.getPreimage(),
244
+ challenge: preTransaction.getChallenge(),
238
245
  nonWitnessUtxo: signedTransaction.tx.toBuffer(),
239
246
  estimatedFees: preTransaction.estimatedFees,
240
247
  optionalInputs: inputs,
@@ -253,7 +260,7 @@ export class TransactionFactory {
253
260
  interactionParameters.from,
254
261
  1,
255
262
  ), // always 1
256
- challenge: preTransaction.getPreimage().toRaw(),
263
+ challenge: preTransaction.getChallenge().toRaw(),
257
264
  };
258
265
  }
259
266
 
@@ -331,7 +338,7 @@ export class TransactionFactory {
331
338
  ...deploymentParameters,
332
339
  utxos: [newUtxo], // always 0
333
340
  randomBytes: preTransaction.getRndBytes(),
334
- challenge: preTransaction.getPreimage(),
341
+ challenge: preTransaction.getChallenge(),
335
342
  nonWitnessUtxo: signedTransaction.toBuffer(),
336
343
  estimatedFees: preTransaction.estimatedFees,
337
344
  optionalInputs: inputs,
@@ -358,7 +365,7 @@ export class TransactionFactory {
358
365
  contractAddress: finalTransaction.getContractAddress(), //finalTransaction.contractAddress.p2tr(deploymentParameters.network),
359
366
  contractPubKey: finalTransaction.contractPubKey,
360
367
  utxos: [refundUTXO],
361
- challenge: preTransaction.getPreimage().toRaw(),
368
+ challenge: preTransaction.getChallenge().toRaw(),
362
369
  };
363
370
  }
364
371
 
@@ -506,6 +513,20 @@ export class TransactionFactory {
506
513
  };
507
514
  }
508
515
 
516
+ /**
517
+ * Check if the UTXOs contain any P2WDA inputs
518
+ *
519
+ * This method examines both main UTXOs and optional inputs to determine
520
+ * if any of them are P2WDA addresses. P2WDA detection is based on the
521
+ * witness script pattern: (OP_2DROP * 5) <pubkey> OP_CHECKSIG
522
+ *
523
+ * @param utxos The main UTXOs to check
524
+ * @returns true if any UTXO is P2WDA, false otherwise
525
+ */
526
+ private hasP2WDAInputs(utxos: UTXO[]): boolean {
527
+ return utxos.some((utxo) => P2WDADetector.isP2WDAUTXO(utxo));
528
+ }
529
+
509
530
  private writePSBTHeader(type: PSBTTypes, psbt: string): string {
510
531
  const buf = Buffer.from(psbt, 'base64');
511
532
 
@@ -516,6 +537,59 @@ export class TransactionFactory {
516
537
  return Buffer.concat([header, buf]).toString('hex');
517
538
  }
518
539
 
540
+ /**
541
+ * Sign a P2WDA interaction transaction
542
+ *
543
+ * P2WDA interactions are fundamentally different from standard OP_NET interactions.
544
+ * Instead of using a two-transaction model (funding + interaction), P2WDA embeds
545
+ * the operation data directly in the witness field of a single transaction.
546
+ * This achieves significant cost savings through the witness discount.
547
+ *
548
+ * Key differences:
549
+ * - Single transaction instead of two
550
+ * - Operation data in witness field instead of taproot script
551
+ * - 75% cost reduction for data storage
552
+ * - No separate funding transaction needed
553
+ *
554
+ * @param interactionParameters The interaction parameters
555
+ * @returns The signed P2WDA interaction response
556
+ */
557
+ private async signP2WDAInteraction(
558
+ interactionParameters: IInteractionParameters | InteractionParametersWithoutSigner,
559
+ ): Promise<InteractionResponse> {
560
+ if (!interactionParameters.from) {
561
+ throw new Error('Field "from" not provided.');
562
+ }
563
+
564
+ // Ensure we have a signer for P2WDA
565
+ if (!('signer' in interactionParameters)) {
566
+ throw new Error(
567
+ 'P2WDA interactions require a signer. OP_WALLET is not supported for P2WDA.',
568
+ );
569
+ }
570
+
571
+ const inputs = this.parseOptionalInputs(interactionParameters.optionalInputs);
572
+ const p2wdaTransaction = new InteractionTransactionP2WDA({
573
+ ...interactionParameters,
574
+ optionalInputs: inputs,
575
+ });
576
+
577
+ const signedTx = await p2wdaTransaction.signTransaction();
578
+ const txHex = signedTx.toHex();
579
+
580
+ return {
581
+ fundingTransaction: null,
582
+ interactionTransaction: txHex,
583
+ estimatedFees: p2wdaTransaction.estimatedFees,
584
+ nextUTXOs: this.getUTXOAsTransaction(
585
+ signedTx,
586
+ interactionParameters.from,
587
+ signedTx.outs.length - 1, // Last output is typically the change
588
+ ),
589
+ challenge: interactionParameters.challenge.toRaw(),
590
+ };
591
+ }
592
+
519
593
  private getPriorityFee(params: ITransactionParameters): bigint {
520
594
  const totalFee = params.priorityFee + params.gasSatFee;
521
595
  if (totalFee < TransactionBuilder.MINIMUM_DUST) {
@@ -27,9 +27,10 @@ import { SharedInteractionTransaction } from './SharedInteractionTransaction.js'
27
27
  import { ECPairInterface } from 'ecpair';
28
28
  import { Address } from '../../keypair/Address.js';
29
29
  import { UnisatSigner } from '../browser/extensions/UnisatSigner.js';
30
- import { ITimeLockOutput, TimeLockGenerator } from '../mineable/TimelockGenerator.js';
30
+ import { TimeLockGenerator } from '../mineable/TimelockGenerator.js';
31
31
  import { ChallengeSolution } from '../../epoch/ChallengeSolution.js';
32
32
  import { Feature, Features } from '../../generators/Features.js';
33
+ import { IP2WSHAddress } from '../mineable/IP2WSHAddress.js';
33
34
 
34
35
  export class DeploymentTransaction extends TransactionBuilder<TransactionType.DEPLOYMENT> {
35
36
  public static readonly MAXIMUM_CONTRACT_SIZE = 128 * 1024;
@@ -37,7 +38,7 @@ export class DeploymentTransaction extends TransactionBuilder<TransactionType.DE
37
38
  public type: TransactionType.DEPLOYMENT = TransactionType.DEPLOYMENT;
38
39
 
39
40
  protected readonly challenge: ChallengeSolution;
40
- protected readonly epochChallenge: ITimeLockOutput;
41
+ protected readonly epochChallenge: IP2WSHAddress;
41
42
 
42
43
  /**
43
44
  * The contract address
@@ -194,7 +195,7 @@ export class DeploymentTransaction extends TransactionBuilder<TransactionType.DE
194
195
  * Get the contract bytecode
195
196
  * @returns {Buffer} The contract bytecode
196
197
  */
197
- public getPreimage(): ChallengeSolution {
198
+ public getChallenge(): ChallengeSolution {
198
199
  return this.challenge;
199
200
  }
200
201