@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.
- 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/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 +1 -1
- 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 +376 -0
- 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
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
import { Network } from '@btc-vision/bitcoin';
|
|
3
|
+
import { UTXO } from '../utxo/interfaces/IUTXO.js';
|
|
4
|
+
import { IP2WSHAddress } from '../transaction/mineable/IP2WSHAddress.js';
|
|
5
|
+
export declare class P2WDADetector {
|
|
6
|
+
static isP2WDAUTXO(utxo: UTXO): boolean;
|
|
7
|
+
static isP2WDAWitnessScript(witnessScript: Buffer): boolean;
|
|
8
|
+
static generateP2WDAAddress(publicKey: Buffer, network: Network): IP2WSHAddress & {
|
|
9
|
+
scriptPubKey: Buffer;
|
|
10
|
+
};
|
|
11
|
+
static extractPublicKeyFromP2WDA(witnessScript: Buffer): Buffer | null;
|
|
12
|
+
static createSimpleP2WDAWitness(transactionSignature: Buffer, witnessScript: Buffer): Buffer[];
|
|
13
|
+
static validateP2WDASignature(publicKey: Buffer, dataSignature: Buffer, operationData: Buffer): boolean;
|
|
14
|
+
static estimateP2WDAWitnessSize(dataSize?: number): number;
|
|
15
|
+
static couldBeP2WDA(scriptPubKey: Buffer): boolean;
|
|
16
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
import { opcodes, payments, script } from '@btc-vision/bitcoin';
|
|
3
|
+
export class P2WDADetector {
|
|
4
|
+
static isP2WDAUTXO(utxo) {
|
|
5
|
+
if (!utxo.witnessScript) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
const witnessScript = Buffer.isBuffer(utxo.witnessScript)
|
|
9
|
+
? utxo.witnessScript
|
|
10
|
+
: Buffer.from(utxo.witnessScript, 'hex');
|
|
11
|
+
return this.isP2WDAWitnessScript(witnessScript);
|
|
12
|
+
}
|
|
13
|
+
static isP2WDAWitnessScript(witnessScript) {
|
|
14
|
+
try {
|
|
15
|
+
const decompiled = script.decompile(witnessScript);
|
|
16
|
+
if (!decompiled || decompiled.length !== 7) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
for (let i = 0; i < 5; i++) {
|
|
20
|
+
if (decompiled[i] !== opcodes.OP_2DROP) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return (Buffer.isBuffer(decompiled[5]) &&
|
|
25
|
+
decompiled[5].length === 33 &&
|
|
26
|
+
decompiled[6] === opcodes.OP_CHECKSIG);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
static generateP2WDAAddress(publicKey, network) {
|
|
33
|
+
if (publicKey.length !== 33) {
|
|
34
|
+
throw new Error('Public key must be 33 bytes (compressed)');
|
|
35
|
+
}
|
|
36
|
+
const witnessScript = script.compile([
|
|
37
|
+
opcodes.OP_2DROP,
|
|
38
|
+
opcodes.OP_2DROP,
|
|
39
|
+
opcodes.OP_2DROP,
|
|
40
|
+
opcodes.OP_2DROP,
|
|
41
|
+
opcodes.OP_2DROP,
|
|
42
|
+
publicKey,
|
|
43
|
+
opcodes.OP_CHECKSIG,
|
|
44
|
+
]);
|
|
45
|
+
const p2wsh = payments.p2wsh({
|
|
46
|
+
redeem: { output: witnessScript },
|
|
47
|
+
network,
|
|
48
|
+
});
|
|
49
|
+
if (!p2wsh.address || !p2wsh.output) {
|
|
50
|
+
throw new Error('Failed to generate P2WDA address');
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
address: p2wsh.address,
|
|
54
|
+
witnessScript,
|
|
55
|
+
scriptPubKey: p2wsh.output,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
static extractPublicKeyFromP2WDA(witnessScript) {
|
|
59
|
+
try {
|
|
60
|
+
const decompiled = script.decompile(witnessScript);
|
|
61
|
+
if (!decompiled || decompiled.length !== 7) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
for (let i = 0; i < 5; i++) {
|
|
65
|
+
if (decompiled[i] !== opcodes.OP_2DROP) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (Buffer.isBuffer(decompiled[5]) &&
|
|
70
|
+
decompiled[5].length === 33 &&
|
|
71
|
+
decompiled[6] === opcodes.OP_CHECKSIG) {
|
|
72
|
+
return decompiled[5];
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
static createSimpleP2WDAWitness(transactionSignature, witnessScript) {
|
|
81
|
+
const witnessStack = [transactionSignature];
|
|
82
|
+
for (let i = 0; i < 10; i++) {
|
|
83
|
+
witnessStack.push(Buffer.alloc(0));
|
|
84
|
+
}
|
|
85
|
+
witnessStack.push(witnessScript);
|
|
86
|
+
return witnessStack;
|
|
87
|
+
}
|
|
88
|
+
static validateP2WDASignature(publicKey, dataSignature, operationData) {
|
|
89
|
+
return dataSignature.length === 64;
|
|
90
|
+
}
|
|
91
|
+
static estimateP2WDAWitnessSize(dataSize = 0) {
|
|
92
|
+
return 72 + dataSize + 39 + 12;
|
|
93
|
+
}
|
|
94
|
+
static couldBeP2WDA(scriptPubKey) {
|
|
95
|
+
return scriptPubKey.length === 34 && scriptPubKey[0] === 0x00 && scriptPubKey[1] === 0x20;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -26,7 +26,7 @@ export interface BitcoinTransferBase {
|
|
|
26
26
|
readonly nextUTXOs: UTXO[];
|
|
27
27
|
}
|
|
28
28
|
export interface InteractionResponse {
|
|
29
|
-
readonly fundingTransaction: string;
|
|
29
|
+
readonly fundingTransaction: string | null;
|
|
30
30
|
readonly interactionTransaction: string;
|
|
31
31
|
readonly estimatedFees: bigint;
|
|
32
32
|
readonly nextUTXOs: UTXO[];
|
|
@@ -45,7 +45,9 @@ export declare class TransactionFactory {
|
|
|
45
45
|
private detectInteractionOPWallet;
|
|
46
46
|
private detectDeploymentOPWallet;
|
|
47
47
|
private createFundTransaction;
|
|
48
|
+
private hasP2WDAInputs;
|
|
48
49
|
private writePSBTHeader;
|
|
50
|
+
private signP2WDAInteraction;
|
|
49
51
|
private getPriorityFee;
|
|
50
52
|
private getUTXOAsTransaction;
|
|
51
53
|
}
|
|
@@ -4,6 +4,8 @@ import { DeploymentTransaction } from './builders/DeploymentTransaction.js';
|
|
|
4
4
|
import { FundingTransaction } from './builders/FundingTransaction.js';
|
|
5
5
|
import { InteractionTransaction } from './builders/InteractionTransaction.js';
|
|
6
6
|
import { TransactionBuilder } from './builders/TransactionBuilder.js';
|
|
7
|
+
import { P2WDADetector } from '../p2wda/P2WDADetector.js';
|
|
8
|
+
import { InteractionTransactionP2WDA } from './builders/InteractionTransactionP2WDA.js';
|
|
7
9
|
export class TransactionFactory {
|
|
8
10
|
async createCustomScriptTransaction(interactionParameters) {
|
|
9
11
|
if (!interactionParameters.to) {
|
|
@@ -84,6 +86,10 @@ export class TransactionFactory {
|
|
|
84
86
|
if (!('signer' in interactionParameters)) {
|
|
85
87
|
throw new Error('Field "signer" not provided, OP_WALLET not detected.');
|
|
86
88
|
}
|
|
89
|
+
const useP2WDA = this.hasP2WDAInputs(interactionParameters.utxos);
|
|
90
|
+
if (useP2WDA) {
|
|
91
|
+
return this.signP2WDAInteraction(interactionParameters);
|
|
92
|
+
}
|
|
87
93
|
const inputs = this.parseOptionalInputs(interactionParameters.optionalInputs);
|
|
88
94
|
const preTransaction = new InteractionTransaction({
|
|
89
95
|
...interactionParameters,
|
|
@@ -121,7 +127,7 @@ export class TransactionFactory {
|
|
|
121
127
|
...this.getUTXOAsTransaction(signedTransaction.tx, interactionParameters.to, 0),
|
|
122
128
|
],
|
|
123
129
|
randomBytes: preTransaction.getRndBytes(),
|
|
124
|
-
challenge: preTransaction.
|
|
130
|
+
challenge: preTransaction.getChallenge(),
|
|
125
131
|
nonWitnessUtxo: signedTransaction.tx.toBuffer(),
|
|
126
132
|
estimatedFees: preTransaction.estimatedFees,
|
|
127
133
|
optionalInputs: inputs,
|
|
@@ -133,7 +139,7 @@ export class TransactionFactory {
|
|
|
133
139
|
interactionTransaction: outTx.toHex(),
|
|
134
140
|
estimatedFees: preTransaction.estimatedFees,
|
|
135
141
|
nextUTXOs: this.getUTXOAsTransaction(signedTransaction.tx, interactionParameters.from, 1),
|
|
136
|
-
challenge: preTransaction.
|
|
142
|
+
challenge: preTransaction.getChallenge().toRaw(),
|
|
137
143
|
};
|
|
138
144
|
}
|
|
139
145
|
async signDeployment(deploymentParameters) {
|
|
@@ -189,7 +195,7 @@ export class TransactionFactory {
|
|
|
189
195
|
...deploymentParameters,
|
|
190
196
|
utxos: [newUtxo],
|
|
191
197
|
randomBytes: preTransaction.getRndBytes(),
|
|
192
|
-
challenge: preTransaction.
|
|
198
|
+
challenge: preTransaction.getChallenge(),
|
|
193
199
|
nonWitnessUtxo: signedTransaction.toBuffer(),
|
|
194
200
|
estimatedFees: preTransaction.estimatedFees,
|
|
195
201
|
optionalInputs: inputs,
|
|
@@ -211,7 +217,7 @@ export class TransactionFactory {
|
|
|
211
217
|
contractAddress: finalTransaction.getContractAddress(),
|
|
212
218
|
contractPubKey: finalTransaction.contractPubKey,
|
|
213
219
|
utxos: [refundUTXO],
|
|
214
|
-
challenge: preTransaction.
|
|
220
|
+
challenge: preTransaction.getChallenge().toRaw(),
|
|
215
221
|
};
|
|
216
222
|
}
|
|
217
223
|
async createBTCTransfer(parameters) {
|
|
@@ -307,6 +313,9 @@ export class TransactionFactory {
|
|
|
307
313
|
nextUTXOs: this.getUTXOAsTransaction(signedTransaction, parameters.to, 0),
|
|
308
314
|
};
|
|
309
315
|
}
|
|
316
|
+
hasP2WDAInputs(utxos) {
|
|
317
|
+
return utxos.some((utxo) => P2WDADetector.isP2WDAUTXO(utxo));
|
|
318
|
+
}
|
|
310
319
|
writePSBTHeader(type, psbt) {
|
|
311
320
|
const buf = Buffer.from(psbt, 'base64');
|
|
312
321
|
const header = Buffer.alloc(2);
|
|
@@ -314,6 +323,28 @@ export class TransactionFactory {
|
|
|
314
323
|
header.writeUInt8(currentConsensus, 1);
|
|
315
324
|
return Buffer.concat([header, buf]).toString('hex');
|
|
316
325
|
}
|
|
326
|
+
async signP2WDAInteraction(interactionParameters) {
|
|
327
|
+
if (!interactionParameters.from) {
|
|
328
|
+
throw new Error('Field "from" not provided.');
|
|
329
|
+
}
|
|
330
|
+
if (!('signer' in interactionParameters)) {
|
|
331
|
+
throw new Error('P2WDA interactions require a signer. OP_WALLET is not supported for P2WDA.');
|
|
332
|
+
}
|
|
333
|
+
const inputs = this.parseOptionalInputs(interactionParameters.optionalInputs);
|
|
334
|
+
const p2wdaTransaction = new InteractionTransactionP2WDA({
|
|
335
|
+
...interactionParameters,
|
|
336
|
+
optionalInputs: inputs,
|
|
337
|
+
});
|
|
338
|
+
const signedTx = await p2wdaTransaction.signTransaction();
|
|
339
|
+
const txHex = signedTx.toHex();
|
|
340
|
+
return {
|
|
341
|
+
fundingTransaction: null,
|
|
342
|
+
interactionTransaction: txHex,
|
|
343
|
+
estimatedFees: p2wdaTransaction.estimatedFees,
|
|
344
|
+
nextUTXOs: this.getUTXOAsTransaction(signedTx, interactionParameters.from, signedTx.outs.length - 1),
|
|
345
|
+
challenge: interactionParameters.challenge.toRaw(),
|
|
346
|
+
};
|
|
347
|
+
}
|
|
317
348
|
getPriorityFee(params) {
|
|
318
349
|
const totalFee = params.priorityFee + params.gasSatFee;
|
|
319
350
|
if (totalFee < TransactionBuilder.MINIMUM_DUST) {
|
|
@@ -4,13 +4,13 @@ import { P2TRPayment, Psbt } from '@btc-vision/bitcoin';
|
|
|
4
4
|
import { TransactionBuilder } from './TransactionBuilder.js';
|
|
5
5
|
import { TapLeafScript } from '../interfaces/Tap.js';
|
|
6
6
|
import { Address } from '../../keypair/Address.js';
|
|
7
|
-
import { ITimeLockOutput } from '../mineable/TimelockGenerator.js';
|
|
8
7
|
import { ChallengeSolution } from '../../epoch/ChallengeSolution.js';
|
|
8
|
+
import { IP2WSHAddress } from '../mineable/IP2WSHAddress.js';
|
|
9
9
|
export declare class DeploymentTransaction extends TransactionBuilder<TransactionType.DEPLOYMENT> {
|
|
10
10
|
static readonly MAXIMUM_CONTRACT_SIZE: number;
|
|
11
11
|
type: TransactionType.DEPLOYMENT;
|
|
12
12
|
protected readonly challenge: ChallengeSolution;
|
|
13
|
-
protected readonly epochChallenge:
|
|
13
|
+
protected readonly epochChallenge: IP2WSHAddress;
|
|
14
14
|
protected readonly _contractAddress: Address;
|
|
15
15
|
protected tapLeafScript: TapLeafScript | null;
|
|
16
16
|
private readonly deploymentVersion;
|
|
@@ -31,7 +31,7 @@ export declare class DeploymentTransaction extends TransactionBuilder<Transactio
|
|
|
31
31
|
get contractAddress(): Address;
|
|
32
32
|
get p2trAddress(): string;
|
|
33
33
|
getRndBytes(): Buffer;
|
|
34
|
-
|
|
34
|
+
getChallenge(): ChallengeSolution;
|
|
35
35
|
getContractAddress(): string;
|
|
36
36
|
protected contractSignerXOnlyPubKey(): Buffer;
|
|
37
37
|
protected buildTransaction(): Promise<void>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
import { Psbt } from '@btc-vision/bitcoin';
|
|
3
|
+
import { TransactionType } from '../enums/TransactionType.js';
|
|
4
|
+
import { IInteractionParameters } from '../interfaces/ITransactionParameters.js';
|
|
5
|
+
import { TransactionBuilder } from './TransactionBuilder.js';
|
|
6
|
+
import { ChallengeSolution } from '../../epoch/ChallengeSolution.js';
|
|
7
|
+
import { IP2WSHAddress } from '../mineable/IP2WSHAddress.js';
|
|
8
|
+
export declare class InteractionTransactionP2WDA extends TransactionBuilder<TransactionType.INTERACTION> {
|
|
9
|
+
private static readonly MAX_WITNESS_FIELDS;
|
|
10
|
+
private static readonly MAX_BYTES_PER_WITNESS;
|
|
11
|
+
readonly type: TransactionType.INTERACTION;
|
|
12
|
+
protected readonly epochChallenge: IP2WSHAddress;
|
|
13
|
+
protected readonly disableAutoRefund: boolean;
|
|
14
|
+
private readonly contractAddress;
|
|
15
|
+
private readonly contractSecret;
|
|
16
|
+
private readonly calldata;
|
|
17
|
+
private readonly challenge;
|
|
18
|
+
private readonly randomBytes;
|
|
19
|
+
private p2wdaGenerator;
|
|
20
|
+
private scriptSigner;
|
|
21
|
+
private p2wdaInputIndices;
|
|
22
|
+
private readonly compiledOperationData;
|
|
23
|
+
constructor(parameters: IInteractionParameters);
|
|
24
|
+
getRndBytes(): Buffer;
|
|
25
|
+
getChallenge(): ChallengeSolution;
|
|
26
|
+
getContractSecret(): Buffer;
|
|
27
|
+
protected buildTransaction(): Promise<void>;
|
|
28
|
+
protected createMineableRewardOutputs(): Promise<void>;
|
|
29
|
+
protected signInputs(transaction: Psbt): Promise<void>;
|
|
30
|
+
private generateFeatures;
|
|
31
|
+
private generateKeyPairFromSeed;
|
|
32
|
+
private scriptSignerXOnlyPubKey;
|
|
33
|
+
private validateP2WDAInputs;
|
|
34
|
+
private validateOperationDataSize;
|
|
35
|
+
private finalizePrimaryP2WDA;
|
|
36
|
+
private splitIntoWitnessChunks;
|
|
37
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
import { toXOnly } from '@btc-vision/bitcoin';
|
|
3
|
+
import { TransactionType } from '../enums/TransactionType.js';
|
|
4
|
+
import { MINIMUM_AMOUNT_CA, MINIMUM_AMOUNT_REWARD, TransactionBuilder, } from './TransactionBuilder.js';
|
|
5
|
+
import { MessageSigner } from '../../keypair/MessageSigner.js';
|
|
6
|
+
import { Compressor } from '../../bytecode/Compressor.js';
|
|
7
|
+
import { P2WDAGenerator } from '../../generators/builders/P2WDAGenerator.js';
|
|
8
|
+
import { Features } from '../../generators/Features.js';
|
|
9
|
+
import { BitcoinUtils } from '../../utils/BitcoinUtils.js';
|
|
10
|
+
import { EcKeyPair } from '../../keypair/EcKeyPair.js';
|
|
11
|
+
import { P2WDADetector } from '../../p2wda/P2WDADetector.js';
|
|
12
|
+
import { TimeLockGenerator } from '../mineable/TimelockGenerator.js';
|
|
13
|
+
export class InteractionTransactionP2WDA extends TransactionBuilder {
|
|
14
|
+
constructor(parameters) {
|
|
15
|
+
super(parameters);
|
|
16
|
+
this.type = TransactionType.INTERACTION;
|
|
17
|
+
this.p2wdaInputIndices = new Set();
|
|
18
|
+
this.compiledOperationData = null;
|
|
19
|
+
if (!parameters.to) {
|
|
20
|
+
throw new Error('Contract address (to) is required');
|
|
21
|
+
}
|
|
22
|
+
if (!parameters.contract) {
|
|
23
|
+
throw new Error('Contract secret is required');
|
|
24
|
+
}
|
|
25
|
+
if (!parameters.calldata) {
|
|
26
|
+
throw new Error('Calldata is required');
|
|
27
|
+
}
|
|
28
|
+
if (!parameters.challenge) {
|
|
29
|
+
throw new Error('Challenge solution is required');
|
|
30
|
+
}
|
|
31
|
+
this.disableAutoRefund = parameters.disableAutoRefund || false;
|
|
32
|
+
this.contractAddress = parameters.to;
|
|
33
|
+
this.contractSecret = Buffer.from(parameters.contract.replace('0x', ''), 'hex');
|
|
34
|
+
this.calldata = Compressor.compress(parameters.calldata);
|
|
35
|
+
this.challenge = parameters.challenge;
|
|
36
|
+
this.randomBytes = parameters.randomBytes || BitcoinUtils.rndBytes();
|
|
37
|
+
this.scriptSigner = this.generateKeyPairFromSeed();
|
|
38
|
+
this.p2wdaGenerator = new P2WDAGenerator(Buffer.from(this.signer.publicKey), this.scriptSignerXOnlyPubKey(), this.network);
|
|
39
|
+
if (this.contractSecret.length !== 32) {
|
|
40
|
+
throw new Error('Invalid contract secret length. Expected 32 bytes.');
|
|
41
|
+
}
|
|
42
|
+
this.epochChallenge = TimeLockGenerator.generateTimeLockAddress(this.challenge.publicKey.originalPublicKeyBuffer(), this.network);
|
|
43
|
+
this.validateP2WDAInputs();
|
|
44
|
+
this.compiledOperationData = this.p2wdaGenerator.compile(this.calldata, this.contractSecret, this.challenge, this.priorityFee, this.generateFeatures(parameters));
|
|
45
|
+
this.validateOperationDataSize();
|
|
46
|
+
this.internalInit();
|
|
47
|
+
}
|
|
48
|
+
getRndBytes() {
|
|
49
|
+
return this.randomBytes;
|
|
50
|
+
}
|
|
51
|
+
getChallenge() {
|
|
52
|
+
return this.challenge;
|
|
53
|
+
}
|
|
54
|
+
getContractSecret() {
|
|
55
|
+
return this.contractSecret;
|
|
56
|
+
}
|
|
57
|
+
async buildTransaction() {
|
|
58
|
+
if (!this.regenerated) {
|
|
59
|
+
this.addInputsFromUTXO();
|
|
60
|
+
}
|
|
61
|
+
await this.createMineableRewardOutputs();
|
|
62
|
+
}
|
|
63
|
+
async createMineableRewardOutputs() {
|
|
64
|
+
if (!this.to)
|
|
65
|
+
throw new Error('To address is required');
|
|
66
|
+
const amountSpent = this.getTransactionOPNetFee();
|
|
67
|
+
let amountToCA;
|
|
68
|
+
if (amountSpent > MINIMUM_AMOUNT_REWARD + MINIMUM_AMOUNT_CA) {
|
|
69
|
+
amountToCA = MINIMUM_AMOUNT_CA;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
amountToCA = amountSpent;
|
|
73
|
+
}
|
|
74
|
+
this.addOutput({
|
|
75
|
+
value: Number(amountToCA),
|
|
76
|
+
address: this.to,
|
|
77
|
+
});
|
|
78
|
+
if (amountToCA === MINIMUM_AMOUNT_CA &&
|
|
79
|
+
amountSpent - MINIMUM_AMOUNT_CA > MINIMUM_AMOUNT_REWARD) {
|
|
80
|
+
this.addOutput({
|
|
81
|
+
value: Number(amountSpent - amountToCA),
|
|
82
|
+
address: this.epochChallenge.address,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
const amount = this.addOptionalOutputsAndGetAmount();
|
|
86
|
+
if (!this.disableAutoRefund) {
|
|
87
|
+
await this.addRefundOutput(amountSpent + amount);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async signInputs(transaction) {
|
|
91
|
+
for (let i = 0; i < transaction.data.inputs.length; i++) {
|
|
92
|
+
await this.signInput(transaction, transaction.data.inputs[i], i, this.signer);
|
|
93
|
+
}
|
|
94
|
+
for (let i = 0; i < transaction.data.inputs.length; i++) {
|
|
95
|
+
if (this.p2wdaInputIndices.has(i)) {
|
|
96
|
+
if (i === 0) {
|
|
97
|
+
transaction.finalizeInput(i, this.finalizePrimaryP2WDA.bind(this));
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
transaction.finalizeInput(i, this.finalizeSecondaryP2WDA.bind(this));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
transaction.finalizeInput(i, this.customFinalizerP2SH.bind(this));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
this.finalized = true;
|
|
108
|
+
}
|
|
109
|
+
generateFeatures(parameters) {
|
|
110
|
+
const features = [];
|
|
111
|
+
if (parameters.loadedStorage) {
|
|
112
|
+
features.push({
|
|
113
|
+
opcode: Features.ACCESS_LIST,
|
|
114
|
+
data: parameters.loadedStorage,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
const submission = parameters.challenge.getSubmission();
|
|
118
|
+
if (submission) {
|
|
119
|
+
features.push({
|
|
120
|
+
opcode: Features.EPOCH_SUBMISSION,
|
|
121
|
+
data: submission,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return features;
|
|
125
|
+
}
|
|
126
|
+
generateKeyPairFromSeed() {
|
|
127
|
+
return EcKeyPair.fromSeedKeyPair(this.randomBytes, this.network);
|
|
128
|
+
}
|
|
129
|
+
scriptSignerXOnlyPubKey() {
|
|
130
|
+
return toXOnly(Buffer.from(this.scriptSigner.publicKey));
|
|
131
|
+
}
|
|
132
|
+
validateP2WDAInputs() {
|
|
133
|
+
if (this.utxos.length === 0 || !P2WDADetector.isP2WDAUTXO(this.utxos[0])) {
|
|
134
|
+
throw new Error('Input 0 must be a P2WDA UTXO');
|
|
135
|
+
}
|
|
136
|
+
for (let i = 0; i < this.utxos.length; i++) {
|
|
137
|
+
if (P2WDADetector.isP2WDAUTXO(this.utxos[i])) {
|
|
138
|
+
this.p2wdaInputIndices.add(i);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
for (let i = 0; i < this.optionalInputs.length; i++) {
|
|
142
|
+
const actualIndex = this.utxos.length + i;
|
|
143
|
+
if (P2WDADetector.isP2WDAUTXO(this.optionalInputs[i])) {
|
|
144
|
+
this.p2wdaInputIndices.add(actualIndex);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
validateOperationDataSize() {
|
|
149
|
+
if (!this.compiledOperationData) {
|
|
150
|
+
throw new Error('Operation data not compiled');
|
|
151
|
+
}
|
|
152
|
+
const estimatedSize = this.compiledOperationData.length;
|
|
153
|
+
if (!P2WDAGenerator.validateWitnessSize(estimatedSize)) {
|
|
154
|
+
const signatureSize = 64;
|
|
155
|
+
const totalSize = estimatedSize + signatureSize;
|
|
156
|
+
const compressedEstimate = Math.ceil(totalSize * 0.7);
|
|
157
|
+
const requiredFields = Math.ceil(compressedEstimate / InteractionTransactionP2WDA.MAX_BYTES_PER_WITNESS);
|
|
158
|
+
throw new Error(`Please dont use P2WDA for this operation. Data too large. Raw size: ${estimatedSize} bytes, ` +
|
|
159
|
+
`estimated compressed: ${compressedEstimate} bytes, ` +
|
|
160
|
+
`needs ${requiredFields} witness fields, max is ${InteractionTransactionP2WDA.MAX_WITNESS_FIELDS}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
finalizePrimaryP2WDA(inputIndex, input) {
|
|
164
|
+
if (!input.partialSig || input.partialSig.length === 0) {
|
|
165
|
+
throw new Error(`No signature for P2WDA input #${inputIndex}`);
|
|
166
|
+
}
|
|
167
|
+
if (!input.witnessScript) {
|
|
168
|
+
throw new Error(`No witness script for P2WDA input #${inputIndex}`);
|
|
169
|
+
}
|
|
170
|
+
if (!this.compiledOperationData) {
|
|
171
|
+
throw new Error('Operation data not compiled');
|
|
172
|
+
}
|
|
173
|
+
const txSignature = input.partialSig[0].signature;
|
|
174
|
+
const messageToSign = Buffer.concat([txSignature, this.compiledOperationData]);
|
|
175
|
+
const signedMessage = MessageSigner.signMessage(this.signer, messageToSign);
|
|
176
|
+
const schnorrSignature = Buffer.from(signedMessage.signature);
|
|
177
|
+
const fullData = Buffer.concat([schnorrSignature, this.compiledOperationData]);
|
|
178
|
+
const compressedData = Compressor.compress(fullData);
|
|
179
|
+
const chunks = this.splitIntoWitnessChunks(compressedData);
|
|
180
|
+
if (chunks.length > InteractionTransactionP2WDA.MAX_WITNESS_FIELDS) {
|
|
181
|
+
throw new Error(`Compressed data needs ${chunks.length} witness fields, max is ${InteractionTransactionP2WDA.MAX_WITNESS_FIELDS}`);
|
|
182
|
+
}
|
|
183
|
+
const witnessStack = [txSignature];
|
|
184
|
+
for (let i = 0; i < InteractionTransactionP2WDA.MAX_WITNESS_FIELDS; i++) {
|
|
185
|
+
witnessStack.push(i < chunks.length ? chunks[i] : Buffer.alloc(0));
|
|
186
|
+
}
|
|
187
|
+
witnessStack.push(input.witnessScript);
|
|
188
|
+
return {
|
|
189
|
+
finalScriptSig: undefined,
|
|
190
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness(witnessStack),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
splitIntoWitnessChunks(data) {
|
|
194
|
+
const chunks = [];
|
|
195
|
+
let offset = 0;
|
|
196
|
+
while (offset < data.length) {
|
|
197
|
+
const size = Math.min(InteractionTransactionP2WDA.MAX_BYTES_PER_WITNESS, data.length - offset);
|
|
198
|
+
chunks.push(data.subarray(offset, offset + size));
|
|
199
|
+
offset += size;
|
|
200
|
+
}
|
|
201
|
+
return chunks;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
InteractionTransactionP2WDA.MAX_WITNESS_FIELDS = 10;
|
|
205
|
+
InteractionTransactionP2WDA.MAX_BYTES_PER_WITNESS = 80;
|
|
@@ -4,8 +4,8 @@ import { TransactionBuilder } from './TransactionBuilder.js';
|
|
|
4
4
|
import { TransactionType } from '../enums/TransactionType.js';
|
|
5
5
|
import { CalldataGenerator } from '../../generators/builders/CalldataGenerator.js';
|
|
6
6
|
import { SharedInteractionParameters } from '../interfaces/ITransactionParameters.js';
|
|
7
|
-
import { ITimeLockOutput } from '../mineable/TimelockGenerator.js';
|
|
8
7
|
import { ChallengeSolution } from '../../epoch/ChallengeSolution.js';
|
|
8
|
+
import { IP2WSHAddress } from '../mineable/IP2WSHAddress.js';
|
|
9
9
|
export declare abstract class SharedInteractionTransaction<T extends TransactionType> extends TransactionBuilder<T> {
|
|
10
10
|
static readonly MAXIMUM_CALLDATA_SIZE: number;
|
|
11
11
|
readonly randomBytes: Buffer;
|
|
@@ -14,7 +14,7 @@ export declare abstract class SharedInteractionTransaction<T extends Transaction
|
|
|
14
14
|
protected abstract readonly compiledTargetScript: Buffer;
|
|
15
15
|
protected abstract readonly scriptTree: Taptree;
|
|
16
16
|
protected readonly challenge: ChallengeSolution;
|
|
17
|
-
protected readonly epochChallenge:
|
|
17
|
+
protected readonly epochChallenge: IP2WSHAddress;
|
|
18
18
|
protected calldataGenerator: CalldataGenerator;
|
|
19
19
|
protected readonly calldata: Buffer;
|
|
20
20
|
protected abstract readonly contractSecret: Buffer;
|
|
@@ -23,7 +23,7 @@ export declare abstract class SharedInteractionTransaction<T extends Transaction
|
|
|
23
23
|
protected constructor(parameters: SharedInteractionParameters);
|
|
24
24
|
getContractSecret(): Buffer;
|
|
25
25
|
getRndBytes(): Buffer;
|
|
26
|
-
|
|
26
|
+
getChallenge(): ChallengeSolution;
|
|
27
27
|
protected scriptSignerXOnlyPubKey(): Buffer;
|
|
28
28
|
protected generateKeyPairFromSeed(): ECPairInterface;
|
|
29
29
|
protected buildTransaction(): Promise<void>;
|
|
@@ -37,7 +37,7 @@ export declare abstract class SharedInteractionTransaction<T extends Transaction
|
|
|
37
37
|
};
|
|
38
38
|
protected signInputsWalletBased(transaction: Psbt): Promise<void>;
|
|
39
39
|
protected signInputsNonWalletBased(transaction: Psbt): Promise<void>;
|
|
40
|
-
|
|
40
|
+
protected createMineableRewardOutputs(): Promise<void>;
|
|
41
41
|
private getPubKeys;
|
|
42
42
|
private generateRedeemScripts;
|
|
43
43
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { PaymentType, toXOnly } from '@btc-vision/bitcoin';
|
|
2
|
-
import { MINIMUM_AMOUNT_CA, MINIMUM_AMOUNT_REWARD, TransactionBuilder } from './TransactionBuilder.js';
|
|
1
|
+
import { PaymentType, toXOnly, } from '@btc-vision/bitcoin';
|
|
2
|
+
import { MINIMUM_AMOUNT_CA, MINIMUM_AMOUNT_REWARD, TransactionBuilder, } from './TransactionBuilder.js';
|
|
3
3
|
import { CalldataGenerator } from '../../generators/builders/CalldataGenerator.js';
|
|
4
4
|
import { Compressor } from '../../bytecode/Compressor.js';
|
|
5
5
|
import { EcKeyPair } from '../../keypair/EcKeyPair.js';
|
|
@@ -45,7 +45,7 @@ export class SharedInteractionTransaction extends TransactionBuilder {
|
|
|
45
45
|
getRndBytes() {
|
|
46
46
|
return this.randomBytes;
|
|
47
47
|
}
|
|
48
|
-
|
|
48
|
+
getChallenge() {
|
|
49
49
|
return this.challenge;
|
|
50
50
|
}
|
|
51
51
|
scriptSignerXOnlyPubKey() {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { Network } from '@btc-vision/bitcoin';
|
|
2
|
-
|
|
3
|
-
address: string;
|
|
4
|
-
witnessScript: Buffer;
|
|
5
|
-
}
|
|
2
|
+
import { IP2WSHAddress } from './IP2WSHAddress.js';
|
|
6
3
|
export declare class TimeLockGenerator {
|
|
7
4
|
private static readonly CSV_BLOCKS;
|
|
8
|
-
static generateTimeLockAddress(publicKey: Buffer, network?: Network, csvBlocks?: number):
|
|
5
|
+
static generateTimeLockAddress(publicKey: Buffer, network?: Network, csvBlocks?: number): IP2WSHAddress;
|
|
9
6
|
}
|
|
@@ -5,6 +5,7 @@ import { UTXO } from '../../utxo/interfaces/IUTXO.js';
|
|
|
5
5
|
import { TapLeafScript } from '../interfaces/Tap.js';
|
|
6
6
|
import { ChainId } from '../../network/ChainId.js';
|
|
7
7
|
import { UnisatSigner } from '../browser/extensions/UnisatSigner.js';
|
|
8
|
+
import { Buffer } from 'buffer';
|
|
8
9
|
export type SupportedTransactionVersion = 1 | 2 | 3;
|
|
9
10
|
export interface ITweakedTransactionData {
|
|
10
11
|
readonly signer: Signer | ECPairInterface | UnisatSigner;
|
|
@@ -88,6 +89,10 @@ export declare abstract class TweakedTransaction extends Logger {
|
|
|
88
89
|
finalScriptSig: Buffer | undefined;
|
|
89
90
|
finalScriptWitness: Buffer | undefined;
|
|
90
91
|
};
|
|
92
|
+
protected finalizeSecondaryP2WDA(inputIndex: number, input: PsbtInput): {
|
|
93
|
+
finalScriptWitness: Buffer | undefined;
|
|
94
|
+
finalScriptSig: Buffer | undefined;
|
|
95
|
+
};
|
|
91
96
|
protected signInputsWalletBased(transaction: Psbt): Promise<void>;
|
|
92
97
|
protected isCSVScript(decompiled: (number | Buffer)[]): boolean;
|
|
93
98
|
protected setCSVSequence(csvBlocks: number, currentSequence: number): number;
|
|
@@ -3,6 +3,8 @@ import { address as bitAddress, crypto as bitCrypto, getFinalScripts, isP2A, isP
|
|
|
3
3
|
import { TweakedSigner } from '../../signer/TweakedSigner.js';
|
|
4
4
|
import { canSignNonTaprootInput, isTaprootInput, pubkeyInScript, } from '../../signer/SignerUtils.js';
|
|
5
5
|
import { TransactionBuilder } from '../builders/TransactionBuilder.js';
|
|
6
|
+
import { Buffer } from 'buffer';
|
|
7
|
+
import { P2WDADetector } from '../../p2wda/P2WDADetector.js';
|
|
6
8
|
export var TransactionSequence;
|
|
7
9
|
(function (TransactionSequence) {
|
|
8
10
|
TransactionSequence[TransactionSequence["REPLACE_BY_FEE"] = 4294967293] = "REPLACE_BY_FEE";
|
|
@@ -52,6 +54,10 @@ export class TweakedTransaction extends Logger {
|
|
|
52
54
|
if (!input.partialSig || input.partialSig.length === 0) {
|
|
53
55
|
throw new Error(`No signatures for P2WSH input #${inputIndex}`);
|
|
54
56
|
}
|
|
57
|
+
const isP2WDA = P2WDADetector.isP2WDAWitnessScript(input.witnessScript);
|
|
58
|
+
if (isP2WDA) {
|
|
59
|
+
return this.finalizeSecondaryP2WDA(inputIndex, input);
|
|
60
|
+
}
|
|
55
61
|
const isCSVInput = this.csvInputIndices.has(inputIndex);
|
|
56
62
|
if (isCSVInput) {
|
|
57
63
|
const witnessStack = [input.partialSig[0].signature, input.witnessScript];
|
|
@@ -509,6 +515,19 @@ export class TweakedTransaction extends Logger {
|
|
|
509
515
|
extractCSVValue(sequence) {
|
|
510
516
|
return sequence & 0x0000ffff;
|
|
511
517
|
}
|
|
518
|
+
finalizeSecondaryP2WDA(inputIndex, input) {
|
|
519
|
+
if (!input.partialSig || input.partialSig.length === 0) {
|
|
520
|
+
throw new Error(`No signature for P2WDA input #${inputIndex}`);
|
|
521
|
+
}
|
|
522
|
+
if (!input.witnessScript) {
|
|
523
|
+
throw new Error(`No witness script for P2WDA input #${inputIndex}`);
|
|
524
|
+
}
|
|
525
|
+
const witnessStack = P2WDADetector.createSimpleP2WDAWitness(input.partialSig[0].signature, input.witnessScript);
|
|
526
|
+
return {
|
|
527
|
+
finalScriptSig: undefined,
|
|
528
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness(witnessStack),
|
|
529
|
+
};
|
|
530
|
+
}
|
|
512
531
|
async signInputsWalletBased(transaction) {
|
|
513
532
|
const signer = this.signer;
|
|
514
533
|
await signer.multiSignPsbt([transaction]);
|
package/doc/README.md
ADDED
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|