@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,376 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
import { Psbt, PsbtInput, toXOnly } from '@btc-vision/bitcoin';
|
|
3
|
+
import { TransactionType } from '../enums/TransactionType.js';
|
|
4
|
+
import { IInteractionParameters } from '../interfaces/ITransactionParameters.js';
|
|
5
|
+
import { MINIMUM_AMOUNT_CA, MINIMUM_AMOUNT_REWARD, TransactionBuilder, } from './TransactionBuilder.js';
|
|
6
|
+
import { MessageSigner } from '../../keypair/MessageSigner.js';
|
|
7
|
+
import { Compressor } from '../../bytecode/Compressor.js';
|
|
8
|
+
import { P2WDAGenerator } from '../../generators/builders/P2WDAGenerator.js';
|
|
9
|
+
import { Feature, Features } from '../../generators/Features.js';
|
|
10
|
+
import { BitcoinUtils } from '../../utils/BitcoinUtils.js';
|
|
11
|
+
import { EcKeyPair } from '../../keypair/EcKeyPair.js';
|
|
12
|
+
import { ChallengeSolution } from '../../epoch/ChallengeSolution.js';
|
|
13
|
+
import { ECPairInterface } from 'ecpair';
|
|
14
|
+
import { P2WDADetector } from '../../p2wda/P2WDADetector.js';
|
|
15
|
+
import { IP2WSHAddress } from '../mineable/IP2WSHAddress.js';
|
|
16
|
+
import { TimeLockGenerator } from '../mineable/TimelockGenerator.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* P2WDA Interaction Transaction
|
|
20
|
+
*
|
|
21
|
+
* This transaction type uses the exact same operation data as regular interactions
|
|
22
|
+
* (via CalldataGenerator), but embeds it in the witness field instead of a taproot script.
|
|
23
|
+
* This achieves 75% cost reduction through the witness discount.
|
|
24
|
+
*/
|
|
25
|
+
export class InteractionTransactionP2WDA extends TransactionBuilder<TransactionType.INTERACTION> {
|
|
26
|
+
private static readonly MAX_WITNESS_FIELDS = 10;
|
|
27
|
+
private static readonly MAX_BYTES_PER_WITNESS = 80;
|
|
28
|
+
|
|
29
|
+
public readonly type: TransactionType.INTERACTION = TransactionType.INTERACTION;
|
|
30
|
+
protected readonly epochChallenge: IP2WSHAddress;
|
|
31
|
+
/**
|
|
32
|
+
* Disable auto refund
|
|
33
|
+
* @protected
|
|
34
|
+
*/
|
|
35
|
+
protected readonly disableAutoRefund: boolean;
|
|
36
|
+
private readonly contractAddress: string;
|
|
37
|
+
private readonly contractSecret: Buffer;
|
|
38
|
+
private readonly calldata: Buffer;
|
|
39
|
+
private readonly challenge: ChallengeSolution;
|
|
40
|
+
private readonly randomBytes: Buffer;
|
|
41
|
+
private p2wdaGenerator: P2WDAGenerator;
|
|
42
|
+
private scriptSigner: ECPairInterface;
|
|
43
|
+
private p2wdaInputIndices: Set<number> = new Set();
|
|
44
|
+
/**
|
|
45
|
+
* The compiled operation data from CalldataGenerator
|
|
46
|
+
* This is exactly what would go in a taproot script, but we put it in witness instead
|
|
47
|
+
*/
|
|
48
|
+
private readonly compiledOperationData: Buffer | null = null;
|
|
49
|
+
|
|
50
|
+
public constructor(parameters: IInteractionParameters) {
|
|
51
|
+
super(parameters);
|
|
52
|
+
|
|
53
|
+
if (!parameters.to) {
|
|
54
|
+
throw new Error('Contract address (to) is required');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!parameters.contract) {
|
|
58
|
+
throw new Error('Contract secret is required');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!parameters.calldata) {
|
|
62
|
+
throw new Error('Calldata is required');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!parameters.challenge) {
|
|
66
|
+
throw new Error('Challenge solution is required');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.disableAutoRefund = parameters.disableAutoRefund || false;
|
|
70
|
+
this.contractAddress = parameters.to;
|
|
71
|
+
this.contractSecret = Buffer.from(parameters.contract.replace('0x', ''), 'hex');
|
|
72
|
+
this.calldata = Compressor.compress(parameters.calldata);
|
|
73
|
+
this.challenge = parameters.challenge;
|
|
74
|
+
this.randomBytes = parameters.randomBytes || BitcoinUtils.rndBytes();
|
|
75
|
+
|
|
76
|
+
// Create the script signer (same as SharedInteractionTransaction does)
|
|
77
|
+
this.scriptSigner = this.generateKeyPairFromSeed();
|
|
78
|
+
|
|
79
|
+
// Create the P2WDA generator instead of CalldataGenerator
|
|
80
|
+
// P2WDA needs a different data format optimized for witness embedding
|
|
81
|
+
this.p2wdaGenerator = new P2WDAGenerator(
|
|
82
|
+
Buffer.from(this.signer.publicKey),
|
|
83
|
+
this.scriptSignerXOnlyPubKey(),
|
|
84
|
+
this.network,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// Validate contract secret
|
|
88
|
+
if (this.contractSecret.length !== 32) {
|
|
89
|
+
throw new Error('Invalid contract secret length. Expected 32 bytes.');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
this.epochChallenge = TimeLockGenerator.generateTimeLockAddress(
|
|
93
|
+
this.challenge.publicKey.originalPublicKeyBuffer(),
|
|
94
|
+
this.network,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// Validate P2WDA inputs
|
|
98
|
+
this.validateP2WDAInputs();
|
|
99
|
+
|
|
100
|
+
this.compiledOperationData = this.p2wdaGenerator.compile(
|
|
101
|
+
this.calldata,
|
|
102
|
+
this.contractSecret,
|
|
103
|
+
this.challenge,
|
|
104
|
+
this.priorityFee,
|
|
105
|
+
this.generateFeatures(parameters),
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// Validate size early
|
|
109
|
+
this.validateOperationDataSize();
|
|
110
|
+
|
|
111
|
+
this.internalInit();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get random bytes (for compatibility if needed elsewhere)
|
|
116
|
+
*/
|
|
117
|
+
public getRndBytes(): Buffer {
|
|
118
|
+
return this.randomBytes;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get the challenge (for compatibility if needed elsewhere)
|
|
123
|
+
*/
|
|
124
|
+
public getChallenge(): ChallengeSolution {
|
|
125
|
+
return this.challenge;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get contract secret (for compatibility if needed elsewhere)
|
|
130
|
+
*/
|
|
131
|
+
public getContractSecret(): Buffer {
|
|
132
|
+
return this.contractSecret;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Build the transaction
|
|
137
|
+
*/
|
|
138
|
+
protected async buildTransaction(): Promise<void> {
|
|
139
|
+
if (!this.regenerated) {
|
|
140
|
+
this.addInputsFromUTXO();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Add refund
|
|
144
|
+
await this.createMineableRewardOutputs();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
protected async createMineableRewardOutputs(): Promise<void> {
|
|
148
|
+
if (!this.to) throw new Error('To address is required');
|
|
149
|
+
|
|
150
|
+
const amountSpent: bigint = this.getTransactionOPNetFee();
|
|
151
|
+
|
|
152
|
+
let amountToCA: bigint;
|
|
153
|
+
if (amountSpent > MINIMUM_AMOUNT_REWARD + MINIMUM_AMOUNT_CA) {
|
|
154
|
+
amountToCA = MINIMUM_AMOUNT_CA;
|
|
155
|
+
} else {
|
|
156
|
+
amountToCA = amountSpent;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ALWAYS THE FIRST INPUT.
|
|
160
|
+
this.addOutput({
|
|
161
|
+
value: Number(amountToCA),
|
|
162
|
+
address: this.to,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// ALWAYS SECOND.
|
|
166
|
+
if (
|
|
167
|
+
amountToCA === MINIMUM_AMOUNT_CA &&
|
|
168
|
+
amountSpent - MINIMUM_AMOUNT_CA > MINIMUM_AMOUNT_REWARD
|
|
169
|
+
) {
|
|
170
|
+
this.addOutput({
|
|
171
|
+
value: Number(amountSpent - amountToCA),
|
|
172
|
+
address: this.epochChallenge.address,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const amount = this.addOptionalOutputsAndGetAmount();
|
|
177
|
+
if (!this.disableAutoRefund) {
|
|
178
|
+
await this.addRefundOutput(amountSpent + amount);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Sign inputs with P2WDA-specific handling
|
|
184
|
+
*/
|
|
185
|
+
protected override async signInputs(transaction: Psbt): Promise<void> {
|
|
186
|
+
// Sign all inputs
|
|
187
|
+
for (let i = 0; i < transaction.data.inputs.length; i++) {
|
|
188
|
+
await this.signInput(transaction, transaction.data.inputs[i], i, this.signer);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Finalize with appropriate finalizers
|
|
192
|
+
for (let i = 0; i < transaction.data.inputs.length; i++) {
|
|
193
|
+
if (this.p2wdaInputIndices.has(i)) {
|
|
194
|
+
if (i === 0) {
|
|
195
|
+
transaction.finalizeInput(i, this.finalizePrimaryP2WDA.bind(this));
|
|
196
|
+
} else {
|
|
197
|
+
transaction.finalizeInput(i, this.finalizeSecondaryP2WDA.bind(this));
|
|
198
|
+
}
|
|
199
|
+
} else {
|
|
200
|
+
transaction.finalizeInput(i, this.customFinalizerP2SH.bind(this));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
this.finalized = true;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Generate features array (same as InteractionTransaction)
|
|
209
|
+
*/
|
|
210
|
+
private generateFeatures(parameters: IInteractionParameters): Feature<Features>[] {
|
|
211
|
+
const features: Feature<Features>[] = [];
|
|
212
|
+
|
|
213
|
+
if (parameters.loadedStorage) {
|
|
214
|
+
features.push({
|
|
215
|
+
opcode: Features.ACCESS_LIST,
|
|
216
|
+
data: parameters.loadedStorage,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const submission = parameters.challenge.getSubmission();
|
|
221
|
+
if (submission) {
|
|
222
|
+
features.push({
|
|
223
|
+
opcode: Features.EPOCH_SUBMISSION,
|
|
224
|
+
data: submission,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return features;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Generate keypair from seed (same as SharedInteractionTransaction)
|
|
233
|
+
*/
|
|
234
|
+
private generateKeyPairFromSeed(): ECPairInterface {
|
|
235
|
+
return EcKeyPair.fromSeedKeyPair(this.randomBytes, this.network);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Get script signer x-only pubkey (same as SharedInteractionTransaction)
|
|
240
|
+
*/
|
|
241
|
+
private scriptSignerXOnlyPubKey(): Buffer {
|
|
242
|
+
return toXOnly(Buffer.from(this.scriptSigner.publicKey));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Validate that input 0 is P2WDA
|
|
247
|
+
*/
|
|
248
|
+
private validateP2WDAInputs(): void {
|
|
249
|
+
if (this.utxos.length === 0 || !P2WDADetector.isP2WDAUTXO(this.utxos[0])) {
|
|
250
|
+
throw new Error('Input 0 must be a P2WDA UTXO');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Track all P2WDA inputs
|
|
254
|
+
for (let i = 0; i < this.utxos.length; i++) {
|
|
255
|
+
if (P2WDADetector.isP2WDAUTXO(this.utxos[i])) {
|
|
256
|
+
this.p2wdaInputIndices.add(i);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
for (let i = 0; i < this.optionalInputs.length; i++) {
|
|
261
|
+
const actualIndex = this.utxos.length + i;
|
|
262
|
+
if (P2WDADetector.isP2WDAUTXO(this.optionalInputs[i])) {
|
|
263
|
+
this.p2wdaInputIndices.add(actualIndex);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Validate the compiled operation data will fit in witness fields
|
|
270
|
+
*/
|
|
271
|
+
private validateOperationDataSize(): void {
|
|
272
|
+
if (!this.compiledOperationData) {
|
|
273
|
+
throw new Error('Operation data not compiled');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// The data that goes in witness: COMPRESS(signature + compiledOperationData)
|
|
277
|
+
// Signature is 64 bytes
|
|
278
|
+
const estimatedSize = this.compiledOperationData.length;
|
|
279
|
+
|
|
280
|
+
if (!P2WDAGenerator.validateWitnessSize(estimatedSize)) {
|
|
281
|
+
const signatureSize = 64;
|
|
282
|
+
const totalSize = estimatedSize + signatureSize;
|
|
283
|
+
const compressedEstimate = Math.ceil(totalSize * 0.7);
|
|
284
|
+
const requiredFields = Math.ceil(
|
|
285
|
+
compressedEstimate / InteractionTransactionP2WDA.MAX_BYTES_PER_WITNESS,
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
throw new Error(
|
|
289
|
+
`Please dont use P2WDA for this operation. Data too large. Raw size: ${estimatedSize} bytes, ` +
|
|
290
|
+
`estimated compressed: ${compressedEstimate} bytes, ` +
|
|
291
|
+
`needs ${requiredFields} witness fields, max is ${InteractionTransactionP2WDA.MAX_WITNESS_FIELDS}`,
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Finalize primary P2WDA input with the operation data
|
|
298
|
+
* This is where we create the signature and compress everything
|
|
299
|
+
*/
|
|
300
|
+
private finalizePrimaryP2WDA(
|
|
301
|
+
inputIndex: number,
|
|
302
|
+
input: PsbtInput,
|
|
303
|
+
): {
|
|
304
|
+
finalScriptSig: Buffer | undefined;
|
|
305
|
+
finalScriptWitness: Buffer | undefined;
|
|
306
|
+
} {
|
|
307
|
+
if (!input.partialSig || input.partialSig.length === 0) {
|
|
308
|
+
throw new Error(`No signature for P2WDA input #${inputIndex}`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (!input.witnessScript) {
|
|
312
|
+
throw new Error(`No witness script for P2WDA input #${inputIndex}`);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (!this.compiledOperationData) {
|
|
316
|
+
throw new Error('Operation data not compiled');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const txSignature = input.partialSig[0].signature;
|
|
320
|
+
const messageToSign = Buffer.concat([txSignature, this.compiledOperationData]);
|
|
321
|
+
const signedMessage = MessageSigner.signMessage(
|
|
322
|
+
this.signer as ECPairInterface,
|
|
323
|
+
messageToSign,
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
const schnorrSignature = Buffer.from(signedMessage.signature);
|
|
327
|
+
|
|
328
|
+
// Combine and compress: COMPRESS(signature + compiledOperationData)
|
|
329
|
+
const fullData = Buffer.concat([schnorrSignature, this.compiledOperationData]);
|
|
330
|
+
const compressedData = Compressor.compress(fullData);
|
|
331
|
+
|
|
332
|
+
// Split into chunks
|
|
333
|
+
const chunks = this.splitIntoWitnessChunks(compressedData);
|
|
334
|
+
|
|
335
|
+
if (chunks.length > InteractionTransactionP2WDA.MAX_WITNESS_FIELDS) {
|
|
336
|
+
throw new Error(
|
|
337
|
+
`Compressed data needs ${chunks.length} witness fields, max is ${InteractionTransactionP2WDA.MAX_WITNESS_FIELDS}`,
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Build witness stack
|
|
342
|
+
const witnessStack: Buffer[] = [txSignature];
|
|
343
|
+
|
|
344
|
+
// Add exactly 10 data fields
|
|
345
|
+
// Bitcoin stack is reversed!
|
|
346
|
+
for (let i = 0; i < InteractionTransactionP2WDA.MAX_WITNESS_FIELDS; i++) {
|
|
347
|
+
witnessStack.push(i < chunks.length ? chunks[i] : Buffer.alloc(0));
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
witnessStack.push(input.witnessScript);
|
|
351
|
+
|
|
352
|
+
return {
|
|
353
|
+
finalScriptSig: undefined,
|
|
354
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness(witnessStack),
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Split data into 80-byte chunks
|
|
360
|
+
*/
|
|
361
|
+
private splitIntoWitnessChunks(data: Buffer): Buffer[] {
|
|
362
|
+
const chunks: Buffer[] = [];
|
|
363
|
+
let offset = 0;
|
|
364
|
+
|
|
365
|
+
while (offset < data.length) {
|
|
366
|
+
const size = Math.min(
|
|
367
|
+
InteractionTransactionP2WDA.MAX_BYTES_PER_WITNESS,
|
|
368
|
+
data.length - offset,
|
|
369
|
+
);
|
|
370
|
+
chunks.push(data.subarray(offset, offset + size));
|
|
371
|
+
offset += size;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return chunks;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { P2TRPayment, PaymentType, Psbt, PsbtInput, Signer, Taptree, toXOnly } from '@btc-vision/bitcoin';
|
|
1
|
+
import { P2TRPayment, PaymentType, Psbt, PsbtInput, Signer, Taptree, toXOnly, } from '@btc-vision/bitcoin';
|
|
2
2
|
import { ECPairInterface } from 'ecpair';
|
|
3
|
-
import { MINIMUM_AMOUNT_CA, MINIMUM_AMOUNT_REWARD, TransactionBuilder } from './TransactionBuilder.js';
|
|
3
|
+
import { MINIMUM_AMOUNT_CA, MINIMUM_AMOUNT_REWARD, 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';
|
|
@@ -8,8 +8,9 @@ import { Compressor } from '../../bytecode/Compressor.js';
|
|
|
8
8
|
import { EcKeyPair } from '../../keypair/EcKeyPair.js';
|
|
9
9
|
import { BitcoinUtils } from '../../utils/BitcoinUtils.js';
|
|
10
10
|
import { UnisatSigner } from '../browser/extensions/UnisatSigner.js';
|
|
11
|
-
import {
|
|
11
|
+
import { TimeLockGenerator } from '../mineable/TimelockGenerator.js';
|
|
12
12
|
import { ChallengeSolution } from '../../epoch/ChallengeSolution.js';
|
|
13
|
+
import { IP2WSHAddress } from '../mineable/IP2WSHAddress.js';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Shared interaction transaction
|
|
@@ -33,7 +34,7 @@ export abstract class SharedInteractionTransaction<
|
|
|
33
34
|
protected abstract readonly scriptTree: Taptree;
|
|
34
35
|
|
|
35
36
|
protected readonly challenge: ChallengeSolution;
|
|
36
|
-
protected readonly epochChallenge:
|
|
37
|
+
protected readonly epochChallenge: IP2WSHAddress;
|
|
37
38
|
|
|
38
39
|
protected calldataGenerator: CalldataGenerator;
|
|
39
40
|
|
|
@@ -111,7 +112,7 @@ export abstract class SharedInteractionTransaction<
|
|
|
111
112
|
/**
|
|
112
113
|
* Get the preimage
|
|
113
114
|
*/
|
|
114
|
-
public
|
|
115
|
+
public getChallenge(): ChallengeSolution {
|
|
115
116
|
return this.challenge;
|
|
116
117
|
}
|
|
117
118
|
|
|
@@ -340,7 +341,7 @@ export abstract class SharedInteractionTransaction<
|
|
|
340
341
|
}
|
|
341
342
|
}
|
|
342
343
|
|
|
343
|
-
|
|
344
|
+
protected async createMineableRewardOutputs(): Promise<void> {
|
|
344
345
|
if (!this.to) throw new Error('To address is required');
|
|
345
346
|
|
|
346
347
|
const amountSpent: bigint = this.getTransactionOPNetFee();
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import bitcoin, { Network, networks, opcodes, script } from '@btc-vision/bitcoin';
|
|
2
|
-
|
|
3
|
-
export interface ITimeLockOutput {
|
|
4
|
-
address: string;
|
|
5
|
-
witnessScript: Buffer;
|
|
6
|
-
}
|
|
2
|
+
import { IP2WSHAddress } from './IP2WSHAddress.js';
|
|
7
3
|
|
|
8
4
|
export class TimeLockGenerator {
|
|
9
5
|
private static readonly CSV_BLOCKS = 75;
|
|
@@ -16,7 +12,7 @@ export class TimeLockGenerator {
|
|
|
16
12
|
publicKey: Buffer,
|
|
17
13
|
network: Network = networks.bitcoin,
|
|
18
14
|
csvBlocks: number = TimeLockGenerator.CSV_BLOCKS,
|
|
19
|
-
):
|
|
15
|
+
): IP2WSHAddress {
|
|
20
16
|
const witnessScript = script.compile([
|
|
21
17
|
script.number.encode(csvBlocks),
|
|
22
18
|
opcodes.OP_CHECKSEQUENCEVERIFY,
|
|
@@ -39,6 +39,8 @@ import {
|
|
|
39
39
|
pubkeyInScript,
|
|
40
40
|
} from '../../signer/SignerUtils.js';
|
|
41
41
|
import { TransactionBuilder } from '../builders/TransactionBuilder.js';
|
|
42
|
+
import { Buffer } from 'buffer';
|
|
43
|
+
import { P2WDADetector } from '../../p2wda/P2WDADetector.js';
|
|
42
44
|
|
|
43
45
|
export type SupportedTransactionVersion = 1 | 2 | 3;
|
|
44
46
|
|
|
@@ -954,6 +956,11 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
954
956
|
throw new Error(`No signatures for P2WSH input #${inputIndex}`);
|
|
955
957
|
}
|
|
956
958
|
|
|
959
|
+
const isP2WDA = P2WDADetector.isP2WDAWitnessScript(input.witnessScript);
|
|
960
|
+
if (isP2WDA) {
|
|
961
|
+
return this.finalizeSecondaryP2WDA(inputIndex, input);
|
|
962
|
+
}
|
|
963
|
+
|
|
957
964
|
// Check if this is a CSV input
|
|
958
965
|
const isCSVInput = this.csvInputIndices.has(inputIndex);
|
|
959
966
|
if (isCSVInput) {
|
|
@@ -981,6 +988,35 @@ export abstract class TweakedTransaction extends Logger {
|
|
|
981
988
|
);
|
|
982
989
|
};
|
|
983
990
|
|
|
991
|
+
/**
|
|
992
|
+
* Finalize secondary P2WDA inputs with empty data
|
|
993
|
+
*/
|
|
994
|
+
protected finalizeSecondaryP2WDA(
|
|
995
|
+
inputIndex: number,
|
|
996
|
+
input: PsbtInput,
|
|
997
|
+
): {
|
|
998
|
+
finalScriptWitness: Buffer | undefined;
|
|
999
|
+
finalScriptSig: Buffer | undefined;
|
|
1000
|
+
} {
|
|
1001
|
+
if (!input.partialSig || input.partialSig.length === 0) {
|
|
1002
|
+
throw new Error(`No signature for P2WDA input #${inputIndex}`);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
if (!input.witnessScript) {
|
|
1006
|
+
throw new Error(`No witness script for P2WDA input #${inputIndex}`);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
const witnessStack = P2WDADetector.createSimpleP2WDAWitness(
|
|
1010
|
+
input.partialSig[0].signature,
|
|
1011
|
+
input.witnessScript,
|
|
1012
|
+
);
|
|
1013
|
+
|
|
1014
|
+
return {
|
|
1015
|
+
finalScriptSig: undefined,
|
|
1016
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness(witnessStack),
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
|
|
984
1020
|
protected async signInputsWalletBased(transaction: Psbt): Promise<void> {
|
|
985
1021
|
const signer: UnisatSigner = this.signer as UnisatSigner;
|
|
986
1022
|
|