@btc-vision/transaction 1.6.4 → 1.6.6
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.
- package/browser/_version.d.ts +1 -1
- package/browser/generators/builders/P2WDAGenerator.d.ts +13 -0
- package/browser/index.js +1 -1
- package/browser/keypair/Address.d.ts +3 -2
- package/browser/keypair/AddressVerificator.d.ts +12 -1
- package/browser/keypair/Wallet.d.ts +3 -0
- package/browser/opnet.d.ts +4 -0
- package/browser/p2wda/P2WDADetector.d.ts +16 -0
- package/browser/transaction/TransactionFactory.d.ts +3 -1
- package/browser/transaction/builders/DeploymentTransaction.d.ts +3 -3
- package/browser/transaction/builders/InteractionTransactionP2WDA.d.ts +37 -0
- package/browser/transaction/builders/SharedInteractionTransaction.d.ts +4 -4
- package/browser/transaction/mineable/IP2WSHAddress.d.ts +4 -0
- package/browser/transaction/mineable/TimelockGenerator.d.ts +2 -5
- package/browser/transaction/shared/TweakedTransaction.d.ts +5 -0
- package/build/_version.d.ts +1 -1
- package/build/_version.js +1 -1
- package/build/generators/builders/P2WDAGenerator.d.ts +13 -0
- package/build/generators/builders/P2WDAGenerator.js +62 -0
- package/build/keypair/Address.d.ts +3 -2
- package/build/keypair/Address.js +28 -2
- package/build/keypair/AddressVerificator.d.ts +12 -1
- package/build/keypair/AddressVerificator.js +78 -1
- package/build/keypair/Wallet.d.ts +3 -0
- package/build/keypair/Wallet.js +4 -0
- package/build/opnet.d.ts +4 -0
- package/build/opnet.js +4 -0
- package/build/p2wda/P2WDADetector.d.ts +16 -0
- package/build/p2wda/P2WDADetector.js +97 -0
- package/build/transaction/TransactionFactory.d.ts +3 -1
- package/build/transaction/TransactionFactory.js +35 -4
- package/build/transaction/builders/DeploymentTransaction.d.ts +3 -3
- package/build/transaction/builders/DeploymentTransaction.js +1 -1
- package/build/transaction/builders/InteractionTransactionP2WDA.d.ts +37 -0
- package/build/transaction/builders/InteractionTransactionP2WDA.js +205 -0
- package/build/transaction/builders/MultiSignTransaction.js +2 -2
- package/build/transaction/builders/SharedInteractionTransaction.d.ts +4 -4
- package/build/transaction/builders/SharedInteractionTransaction.js +3 -3
- package/build/transaction/mineable/IP2WSHAddress.d.ts +4 -0
- package/build/transaction/mineable/IP2WSHAddress.js +1 -0
- package/build/transaction/mineable/TimelockGenerator.d.ts +2 -5
- package/build/transaction/shared/TweakedTransaction.d.ts +5 -0
- package/build/transaction/shared/TweakedTransaction.js +19 -0
- package/doc/README.md +0 -0
- package/doc/addresses/P2OP.md +1 -0
- package/doc/addresses/P2WDA.md +240 -0
- package/package.json +9 -9
- package/src/_version.ts +1 -1
- package/src/generators/builders/P2WDAGenerator.ts +174 -0
- package/src/keypair/Address.ts +58 -3
- package/src/keypair/AddressVerificator.ts +140 -1
- package/src/keypair/Wallet.ts +16 -0
- package/src/opnet.ts +4 -0
- package/src/p2wda/P2WDADetector.ts +218 -0
- package/src/transaction/TransactionFactory.ts +79 -5
- package/src/transaction/builders/DeploymentTransaction.ts +4 -3
- package/src/transaction/builders/InteractionTransactionP2WDA.ts +380 -0
- package/src/transaction/builders/MultiSignTransaction.ts +2 -2
- package/src/transaction/builders/SharedInteractionTransaction.ts +7 -6
- package/src/transaction/mineable/IP2WSHAddress.ts +4 -0
- package/src/transaction/mineable/TimelockGenerator.ts +2 -6
- package/src/transaction/shared/TweakedTransaction.ts +36 -0
|
@@ -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
|
}
|
package/src/keypair/Wallet.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 {
|
|
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:
|
|
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
|
|
198
|
+
public getChallenge(): ChallengeSolution {
|
|
198
199
|
return this.challenge;
|
|
199
200
|
}
|
|
200
201
|
|