@btc-vision/transaction 1.6.0 → 1.6.4
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/epoch/ChallengeSolution.d.ts +3 -3
- package/browser/epoch/validator/EpochValidator.d.ts +5 -6
- package/browser/generators/builders/DeploymentGenerator.d.ts +1 -1
- package/browser/generators/builders/LegacyCalldataGenerator.d.ts +1 -1
- package/browser/index.js +1 -1
- package/browser/keypair/AddressVerificator.d.ts +2 -1
- package/browser/transaction/TransactionFactory.d.ts +2 -2
- package/browser/transaction/builders/DeploymentTransaction.d.ts +1 -1
- package/browser/transaction/builders/TransactionBuilder.d.ts +3 -0
- package/browser/transaction/interfaces/ITransactionParameters.d.ts +1 -0
- package/browser/transaction/shared/TweakedTransaction.d.ts +18 -0
- package/browser/utxo/OPNetLimitedProvider.d.ts +0 -4
- package/build/_version.d.ts +1 -1
- package/build/_version.js +1 -1
- package/build/epoch/ChallengeSolution.d.ts +3 -3
- package/build/epoch/ChallengeSolution.js +4 -4
- package/build/epoch/validator/EpochValidator.d.ts +5 -6
- package/build/epoch/validator/EpochValidator.js +16 -17
- package/build/generators/builders/DeploymentGenerator.d.ts +1 -1
- package/build/generators/builders/DeploymentGenerator.js +5 -5
- package/build/generators/builders/LegacyCalldataGenerator.d.ts +1 -1
- package/build/generators/builders/LegacyCalldataGenerator.js +2 -2
- package/build/keypair/AddressVerificator.d.ts +2 -1
- package/build/keypair/AddressVerificator.js +4 -0
- package/build/transaction/TransactionFactory.d.ts +2 -2
- package/build/transaction/TransactionFactory.js +2 -2
- package/build/transaction/builders/DeploymentTransaction.d.ts +1 -1
- package/build/transaction/builders/DeploymentTransaction.js +4 -4
- package/build/transaction/builders/TransactionBuilder.d.ts +3 -0
- package/build/transaction/builders/TransactionBuilder.js +18 -3
- package/build/transaction/interfaces/ITransactionParameters.d.ts +1 -0
- package/build/transaction/shared/TweakedTransaction.d.ts +18 -0
- package/build/transaction/shared/TweakedTransaction.js +135 -18
- package/build/utxo/OPNetLimitedProvider.d.ts +0 -4
- package/build/utxo/OPNetLimitedProvider.js +0 -7
- package/package.json +2 -2
- package/src/_version.ts +1 -1
- package/src/epoch/ChallengeSolution.ts +10 -10
- package/src/epoch/validator/EpochValidator.ts +18 -22
- package/src/generators/builders/DeploymentGenerator.ts +6 -6
- package/src/generators/builders/LegacyCalldataGenerator.ts +3 -3
- package/src/keypair/AddressVerificator.ts +7 -1
- package/src/transaction/TransactionFactory.ts +4 -4
- package/src/transaction/builders/DeploymentTransaction.ts +5 -5
- package/src/transaction/builders/TransactionBuilder.ts +30 -3
- package/src/transaction/interfaces/ITransactionParameters.ts +1 -0
- package/src/transaction/shared/TweakedTransaction.ts +210 -23
- package/src/utxo/OPNetLimitedProvider.ts +0 -17
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { Logger } from '@btc-vision/logger';
|
|
2
|
-
import { address as bitAddress, crypto as bitCrypto, getFinalScripts, isP2MS, isP2PK, isP2PKH, isP2SHScript, isP2TR, isP2WPKH, isP2WSHScript, isUnknownSegwitVersion, opcodes, payments, PaymentType, script, toXOnly, varuint, } from '@btc-vision/bitcoin';
|
|
2
|
+
import { address as bitAddress, crypto as bitCrypto, getFinalScripts, isP2A, isP2MS, isP2PK, isP2PKH, isP2SHScript, isP2TR, isP2WPKH, isP2WSHScript, isUnknownSegwitVersion, opcodes, payments, PaymentType, script, toXOnly, varuint, } from '@btc-vision/bitcoin';
|
|
3
3
|
import { TweakedSigner } from '../../signer/TweakedSigner.js';
|
|
4
4
|
import { canSignNonTaprootInput, isTaprootInput, pubkeyInScript, } from '../../signer/SignerUtils.js';
|
|
5
|
+
import { TransactionBuilder } from '../builders/TransactionBuilder.js';
|
|
5
6
|
export var TransactionSequence;
|
|
6
7
|
(function (TransactionSequence) {
|
|
7
8
|
TransactionSequence[TransactionSequence["REPLACE_BY_FEE"] = 4294967293] = "REPLACE_BY_FEE";
|
|
8
9
|
TransactionSequence[TransactionSequence["FINAL"] = 4294967295] = "FINAL";
|
|
9
10
|
})(TransactionSequence || (TransactionSequence = {}));
|
|
11
|
+
export var CSVModes;
|
|
12
|
+
(function (CSVModes) {
|
|
13
|
+
CSVModes[CSVModes["BLOCKS"] = 0] = "BLOCKS";
|
|
14
|
+
CSVModes[CSVModes["TIMESTAMPS"] = 1] = "TIMESTAMPS";
|
|
15
|
+
})(CSVModes || (CSVModes = {}));
|
|
16
|
+
const CSV_ENABLED_BLOCKS_MASK = 0x3fffffff;
|
|
10
17
|
export class TweakedTransaction extends Logger {
|
|
11
18
|
constructor(data) {
|
|
12
19
|
super();
|
|
@@ -19,9 +26,12 @@ export class TweakedTransaction extends Logger {
|
|
|
19
26
|
this.sequence = TransactionSequence.REPLACE_BY_FEE;
|
|
20
27
|
this.tapLeafScript = null;
|
|
21
28
|
this.isBrowser = false;
|
|
29
|
+
this.csvInputIndices = new Set();
|
|
30
|
+
this.anchorInputIndices = new Set();
|
|
22
31
|
this.regenerated = false;
|
|
23
32
|
this.ignoreSignatureErrors = false;
|
|
24
33
|
this.noSignatures = false;
|
|
34
|
+
this.txVersion = 2;
|
|
25
35
|
this.customFinalizerP2SH = (inputIndex, input, scriptA, isSegwit, isP2SH, isP2WSH) => {
|
|
26
36
|
const inputDecoded = this.inputs[inputIndex];
|
|
27
37
|
if (isP2SH && input.partialSig && inputDecoded && inputDecoded.redeemScript) {
|
|
@@ -32,6 +42,25 @@ export class TweakedTransaction extends Logger {
|
|
|
32
42
|
finalScriptWitness: undefined,
|
|
33
43
|
};
|
|
34
44
|
}
|
|
45
|
+
if (this.anchorInputIndices.has(inputIndex)) {
|
|
46
|
+
return {
|
|
47
|
+
finalScriptSig: undefined,
|
|
48
|
+
finalScriptWitness: Buffer.from([0]),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
if (isP2WSH && isSegwit && input.witnessScript) {
|
|
52
|
+
if (!input.partialSig || input.partialSig.length === 0) {
|
|
53
|
+
throw new Error(`No signatures for P2WSH input #${inputIndex}`);
|
|
54
|
+
}
|
|
55
|
+
const isCSVInput = this.csvInputIndices.has(inputIndex);
|
|
56
|
+
if (isCSVInput) {
|
|
57
|
+
const witnessStack = [input.partialSig[0].signature, input.witnessScript];
|
|
58
|
+
return {
|
|
59
|
+
finalScriptSig: undefined,
|
|
60
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness(witnessStack),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
35
64
|
return getFinalScripts(inputIndex, input, scriptA, isSegwit, isP2SH, isP2WSH, true, this.unlockScript);
|
|
36
65
|
};
|
|
37
66
|
this.signer = data.signer;
|
|
@@ -40,6 +69,9 @@ export class TweakedTransaction extends Logger {
|
|
|
40
69
|
this.nonWitnessUtxo = data.nonWitnessUtxo;
|
|
41
70
|
this.unlockScript = data.unlockScript;
|
|
42
71
|
this.isBrowser = typeof window !== 'undefined';
|
|
72
|
+
if (data.txVersion) {
|
|
73
|
+
this.txVersion = data.txVersion;
|
|
74
|
+
}
|
|
43
75
|
}
|
|
44
76
|
static readScriptWitnessToWitnessStack(Buffer) {
|
|
45
77
|
let offset = 0;
|
|
@@ -120,6 +152,9 @@ export class TweakedTransaction extends Logger {
|
|
|
120
152
|
throw new Error('Transaction is already signed');
|
|
121
153
|
this.sequence = TransactionSequence.FINAL;
|
|
122
154
|
for (const input of this.inputs) {
|
|
155
|
+
if (this.csvInputIndices.has(this.inputs.indexOf(input))) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
123
158
|
input.sequence = TransactionSequence.FINAL;
|
|
124
159
|
}
|
|
125
160
|
}
|
|
@@ -157,6 +192,8 @@ export class TweakedTransaction extends Logger {
|
|
|
157
192
|
return this.signer;
|
|
158
193
|
}
|
|
159
194
|
async signInput(transaction, input, i, signer, reverse = false, errored = false) {
|
|
195
|
+
if (this.anchorInputIndices.has(i))
|
|
196
|
+
return;
|
|
160
197
|
const publicKey = signer.publicKey;
|
|
161
198
|
let isTaproot = isTaprootInput(input);
|
|
162
199
|
if (reverse) {
|
|
@@ -335,17 +372,17 @@ export class TweakedTransaction extends Logger {
|
|
|
335
372
|
return;
|
|
336
373
|
}
|
|
337
374
|
generatePsbtInputExtended(utxo, i, _extra = false) {
|
|
338
|
-
const
|
|
375
|
+
const scriptPub = Buffer.from(utxo.scriptPubKey.hex, 'hex');
|
|
339
376
|
const input = {
|
|
340
377
|
hash: utxo.transactionId,
|
|
341
378
|
index: utxo.outputIndex,
|
|
342
379
|
sequence: this.sequence,
|
|
343
380
|
witnessUtxo: {
|
|
344
381
|
value: Number(utxo.value),
|
|
345
|
-
script,
|
|
382
|
+
script: scriptPub,
|
|
346
383
|
},
|
|
347
384
|
};
|
|
348
|
-
if (isP2PKH(
|
|
385
|
+
if (isP2PKH(scriptPub)) {
|
|
349
386
|
if (utxo.nonWitnessUtxo) {
|
|
350
387
|
input.nonWitnessUtxo = Buffer.isBuffer(utxo.nonWitnessUtxo)
|
|
351
388
|
? utxo.nonWitnessUtxo
|
|
@@ -355,17 +392,12 @@ export class TweakedTransaction extends Logger {
|
|
|
355
392
|
throw new Error('Missing nonWitnessUtxo for P2PKH UTXO');
|
|
356
393
|
}
|
|
357
394
|
}
|
|
358
|
-
else if (isP2WPKH(
|
|
395
|
+
else if (isP2WPKH(scriptPub) || isUnknownSegwitVersion(scriptPub)) {
|
|
359
396
|
}
|
|
360
|
-
else if (isP2WSHScript(
|
|
361
|
-
|
|
362
|
-
throw new Error('Missing witnessScript for P2WSH UTXO');
|
|
363
|
-
}
|
|
364
|
-
input.witnessScript = Buffer.isBuffer(utxo.witnessScript)
|
|
365
|
-
? utxo.witnessScript
|
|
366
|
-
: Buffer.from(utxo.witnessScript, 'hex');
|
|
397
|
+
else if (isP2WSHScript(scriptPub)) {
|
|
398
|
+
this.processP2WSHInput(utxo, input, i);
|
|
367
399
|
}
|
|
368
|
-
else if (isP2SHScript(
|
|
400
|
+
else if (isP2SHScript(scriptPub)) {
|
|
369
401
|
let redeemScriptBuf;
|
|
370
402
|
if (utxo.redeemScript) {
|
|
371
403
|
redeemScriptBuf = Buffer.isBuffer(utxo.redeemScript)
|
|
@@ -401,15 +433,13 @@ export class TweakedTransaction extends Logger {
|
|
|
401
433
|
}
|
|
402
434
|
else if (isP2WSHScript(redeemOutput)) {
|
|
403
435
|
delete input.nonWitnessUtxo;
|
|
404
|
-
|
|
405
|
-
throw new Error('Missing witnessScript for P2SH-P2WSH UTXO');
|
|
406
|
-
}
|
|
436
|
+
this.processP2WSHInput(utxo, input, i);
|
|
407
437
|
}
|
|
408
438
|
else {
|
|
409
439
|
delete input.witnessUtxo;
|
|
410
440
|
}
|
|
411
441
|
}
|
|
412
|
-
else if (isP2TR(
|
|
442
|
+
else if (isP2TR(scriptPub)) {
|
|
413
443
|
if (this.sighashTypes) {
|
|
414
444
|
const inputSign = TweakedTransaction.calculateSignHash(this.sighashTypes);
|
|
415
445
|
if (inputSign)
|
|
@@ -418,7 +448,11 @@ export class TweakedTransaction extends Logger {
|
|
|
418
448
|
this.tweakSigner();
|
|
419
449
|
input.tapInternalKey = this.internalPubKeyToXOnly();
|
|
420
450
|
}
|
|
421
|
-
else if (
|
|
451
|
+
else if (isP2A(scriptPub)) {
|
|
452
|
+
this.anchorInputIndices.add(i);
|
|
453
|
+
input.isPayToAnchor = true;
|
|
454
|
+
}
|
|
455
|
+
else if (isP2PK(scriptPub) || isP2MS(scriptPub)) {
|
|
422
456
|
if (utxo.nonWitnessUtxo) {
|
|
423
457
|
input.nonWitnessUtxo = Buffer.isBuffer(utxo.nonWitnessUtxo)
|
|
424
458
|
? utxo.nonWitnessUtxo
|
|
@@ -441,6 +475,40 @@ export class TweakedTransaction extends Logger {
|
|
|
441
475
|
}
|
|
442
476
|
return input;
|
|
443
477
|
}
|
|
478
|
+
processP2WSHInput(utxo, input, i) {
|
|
479
|
+
if (!utxo.witnessScript) {
|
|
480
|
+
throw new Error('Missing witnessScript for P2WSH UTXO');
|
|
481
|
+
}
|
|
482
|
+
input.witnessScript = Buffer.isBuffer(utxo.witnessScript)
|
|
483
|
+
? utxo.witnessScript
|
|
484
|
+
: Buffer.from(utxo.witnessScript, 'hex');
|
|
485
|
+
const decompiled = script.decompile(input.witnessScript);
|
|
486
|
+
if (decompiled && this.isCSVScript(decompiled)) {
|
|
487
|
+
const decompiled = script.decompile(input.witnessScript);
|
|
488
|
+
if (decompiled && this.isCSVScript(decompiled)) {
|
|
489
|
+
this.csvInputIndices.add(i);
|
|
490
|
+
const csvBlocks = this.extractCSVBlocks(decompiled);
|
|
491
|
+
console.log('csvBlocks', csvBlocks);
|
|
492
|
+
input.sequence = this.setCSVSequence(csvBlocks, this.sequence);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
secondsToCSVTimeUnits(seconds) {
|
|
497
|
+
return Math.floor(seconds / 512);
|
|
498
|
+
}
|
|
499
|
+
createTimeBasedCSV(seconds) {
|
|
500
|
+
const timeUnits = this.secondsToCSVTimeUnits(seconds);
|
|
501
|
+
if (timeUnits > 0xffff) {
|
|
502
|
+
throw new Error(`Time units ${timeUnits} exceeds maximum of 65,535`);
|
|
503
|
+
}
|
|
504
|
+
return timeUnits | (1 << 22);
|
|
505
|
+
}
|
|
506
|
+
isCSVEnabled(sequence) {
|
|
507
|
+
return (sequence & (1 << 31)) === 0;
|
|
508
|
+
}
|
|
509
|
+
extractCSVValue(sequence) {
|
|
510
|
+
return sequence & 0x0000ffff;
|
|
511
|
+
}
|
|
444
512
|
async signInputsWalletBased(transaction) {
|
|
445
513
|
const signer = this.signer;
|
|
446
514
|
await signer.multiSignPsbt([transaction]);
|
|
@@ -449,6 +517,55 @@ export class TweakedTransaction extends Logger {
|
|
|
449
517
|
}
|
|
450
518
|
this.finalized = true;
|
|
451
519
|
}
|
|
520
|
+
isCSVScript(decompiled) {
|
|
521
|
+
return decompiled.some((op) => op === opcodes.OP_CHECKSEQUENCEVERIFY);
|
|
522
|
+
}
|
|
523
|
+
setCSVSequence(csvBlocks, currentSequence) {
|
|
524
|
+
if (this.txVersion < 2) {
|
|
525
|
+
throw new Error('CSV requires transaction version 2 or higher');
|
|
526
|
+
}
|
|
527
|
+
if (csvBlocks > 0xffff) {
|
|
528
|
+
throw new Error(`CSV blocks ${csvBlocks} exceeds maximum of 65,535`);
|
|
529
|
+
}
|
|
530
|
+
const isTimeBased = (csvBlocks & (1 << 22)) !== 0;
|
|
531
|
+
let sequence = csvBlocks & 0x0000ffff;
|
|
532
|
+
if (isTimeBased) {
|
|
533
|
+
sequence |= 1 << 22;
|
|
534
|
+
}
|
|
535
|
+
if (currentSequence === TransactionSequence.REPLACE_BY_FEE) {
|
|
536
|
+
sequence |= 1 << 25;
|
|
537
|
+
}
|
|
538
|
+
sequence = sequence & 0x7fffffff;
|
|
539
|
+
return sequence;
|
|
540
|
+
}
|
|
541
|
+
getCSVType(csvValue) {
|
|
542
|
+
return csvValue & (1 << 22) ? CSVModes.TIMESTAMPS : CSVModes.BLOCKS;
|
|
543
|
+
}
|
|
544
|
+
extractCSVBlocks(decompiled) {
|
|
545
|
+
for (let i = 0; i < decompiled.length; i++) {
|
|
546
|
+
if (decompiled[i] === opcodes.OP_CHECKSEQUENCEVERIFY && i > 0) {
|
|
547
|
+
const csvValue = decompiled[i - 1];
|
|
548
|
+
if (Buffer.isBuffer(csvValue)) {
|
|
549
|
+
return script.number.decode(csvValue);
|
|
550
|
+
}
|
|
551
|
+
else if (typeof csvValue === 'number') {
|
|
552
|
+
if (csvValue === opcodes.OP_0 || csvValue === opcodes.OP_FALSE) {
|
|
553
|
+
return 0;
|
|
554
|
+
}
|
|
555
|
+
else if (csvValue === opcodes.OP_1NEGATE) {
|
|
556
|
+
return -1;
|
|
557
|
+
}
|
|
558
|
+
else if (csvValue >= opcodes.OP_1 && csvValue <= opcodes.OP_16) {
|
|
559
|
+
return csvValue - opcodes.OP_1 + 1;
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
throw new Error(`Unexpected raw number in script: ${csvValue}`);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return 0;
|
|
568
|
+
}
|
|
452
569
|
async attemptSignTaproot(transaction, input, i, signer, publicKey) {
|
|
453
570
|
const isScriptSpend = this.isTaprootScriptSpend(input, publicKey);
|
|
454
571
|
if (isScriptSpend) {
|
|
@@ -7,9 +7,6 @@ export interface WalletUTXOs {
|
|
|
7
7
|
readonly pending: RawUTXOResponse[];
|
|
8
8
|
readonly spentTransactions: RawUTXOResponse[];
|
|
9
9
|
}
|
|
10
|
-
export interface PreimageData {
|
|
11
|
-
readonly preimage: Buffer;
|
|
12
|
-
}
|
|
13
10
|
export declare class OPNetLimitedProvider {
|
|
14
11
|
private readonly opnetAPIUrl;
|
|
15
12
|
private readonly utxoPath;
|
|
@@ -18,7 +15,6 @@ export declare class OPNetLimitedProvider {
|
|
|
18
15
|
fetchUTXO(settings: FetchUTXOParams): Promise<UTXO[]>;
|
|
19
16
|
fetchUTXOMultiAddr(settings: FetchUTXOParamsMultiAddress): Promise<UTXO[]>;
|
|
20
17
|
broadcastTransaction(transaction: string, psbt: boolean): Promise<BroadcastResponse | undefined>;
|
|
21
|
-
getPreimage(): Promise<PreimageData | undefined>;
|
|
22
18
|
splitUTXOs(wallet: Wallet, network: Network, splitInputsInto: number, amountPerUTXO: bigint): Promise<BroadcastResponse | {
|
|
23
19
|
error: string;
|
|
24
20
|
}>;
|
|
@@ -103,13 +103,6 @@ export class OPNetLimitedProvider {
|
|
|
103
103
|
}
|
|
104
104
|
return result;
|
|
105
105
|
}
|
|
106
|
-
async getPreimage() {
|
|
107
|
-
const result = await this.rpcMethod('btc_preimage', []);
|
|
108
|
-
if (!result) {
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
return result;
|
|
112
|
-
}
|
|
113
106
|
async splitUTXOs(wallet, network, splitInputsInto, amountPerUTXO) {
|
|
114
107
|
const utxoSetting = {
|
|
115
108
|
addresses: [wallet.p2wpkh, wallet.p2tr],
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@btc-vision/transaction",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.6.
|
|
4
|
+
"version": "1.6.4",
|
|
5
5
|
"author": "BlobMaster41",
|
|
6
6
|
"description": "OPNet transaction library allows you to create and sign transactions for the OPNet network.",
|
|
7
7
|
"engines": {
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"dependencies": {
|
|
90
90
|
"@babel/plugin-proposal-object-rest-spread": "^7.21.4-esm",
|
|
91
91
|
"@bitcoinerlab/secp256k1": "^1.2.0",
|
|
92
|
-
"@btc-vision/bitcoin": "^6.4.
|
|
92
|
+
"@btc-vision/bitcoin": "^6.4.8",
|
|
93
93
|
"@btc-vision/bitcoin-rpc": "^1.0.2",
|
|
94
94
|
"@btc-vision/logger": "^1.0.6",
|
|
95
95
|
"@eslint/js": "^9.32.0",
|
package/src/_version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '1.6.
|
|
1
|
+
export const version = '1.6.4';
|
|
@@ -90,7 +90,7 @@ export class ChallengeSolution implements IChallengeSolution {
|
|
|
90
90
|
/**
|
|
91
91
|
* Static method to validate from raw data directly
|
|
92
92
|
*/
|
|
93
|
-
public static
|
|
93
|
+
public static validateRaw(data: RawChallenge): boolean {
|
|
94
94
|
return EpochValidator.validateEpochWinner(data);
|
|
95
95
|
}
|
|
96
96
|
|
|
@@ -115,16 +115,16 @@ export class ChallengeSolution implements IChallengeSolution {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
/**
|
|
118
|
-
* Verify this
|
|
119
|
-
* @returns {
|
|
118
|
+
* Verify this challenge
|
|
119
|
+
* @returns {boolean} True if the challenge is valid
|
|
120
120
|
*/
|
|
121
|
-
public
|
|
122
|
-
return EpochValidator.
|
|
121
|
+
public verify(): boolean {
|
|
122
|
+
return EpochValidator.validateChallengeSolution(this);
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
/**
|
|
126
|
-
* Get the preimage
|
|
127
|
-
* @returns {Buffer} The solution/
|
|
126
|
+
* Get the preimage challenge
|
|
127
|
+
* @returns {Buffer} The solution/challenge as a buffer
|
|
128
128
|
*/
|
|
129
129
|
public toBuffer(): Buffer {
|
|
130
130
|
return this.solution;
|
|
@@ -162,10 +162,10 @@ export class ChallengeSolution implements IChallengeSolution {
|
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
/**
|
|
165
|
-
* Calculate the expected solution hash for this
|
|
165
|
+
* Calculate the expected solution hash for this challenge
|
|
166
166
|
* @returns {Promise<Buffer>} The calculated solution hash
|
|
167
167
|
*/
|
|
168
|
-
public
|
|
168
|
+
public calculateSolution(): Buffer {
|
|
169
169
|
return EpochValidator.calculateSolution(
|
|
170
170
|
this.verification.targetChecksum,
|
|
171
171
|
this.publicKey.toBuffer(),
|
|
@@ -174,7 +174,7 @@ export class ChallengeSolution implements IChallengeSolution {
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
/**
|
|
177
|
-
* Check if the
|
|
177
|
+
* Check if the challenge meets a specific difficulty requirement
|
|
178
178
|
* @param {number} minDifficulty The minimum difficulty required
|
|
179
179
|
* @returns {Promise<{valid: boolean; difficulty: number}>} Validation result
|
|
180
180
|
*/
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { IChallengeSolution, RawChallenge } from '../interfaces/IChallengeSolution.js';
|
|
2
2
|
import { ChallengeSolution } from '../ChallengeSolution.js';
|
|
3
|
+
import { crypto } from '@btc-vision/bitcoin';
|
|
3
4
|
|
|
4
5
|
export class EpochValidator {
|
|
5
6
|
private static readonly BLOCKS_PER_EPOCH: bigint = 5n;
|
|
6
|
-
private static readonly GRAFFITI_LENGTH: number = 16;
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Convert Buffer to Uint8Array
|
|
@@ -22,9 +22,8 @@ export class EpochValidator {
|
|
|
22
22
|
/**
|
|
23
23
|
* Calculate SHA-1 hash
|
|
24
24
|
*/
|
|
25
|
-
public static
|
|
26
|
-
|
|
27
|
-
return new Uint8Array(hashBuffer);
|
|
25
|
+
public static sha1(data: Uint8Array | Buffer): Buffer {
|
|
26
|
+
return crypto.sha1(Buffer.isBuffer(data) ? data : Buffer.from(data));
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
/**
|
|
@@ -78,22 +77,19 @@ export class EpochValidator {
|
|
|
78
77
|
/**
|
|
79
78
|
* Verify an epoch solution using IPreimage
|
|
80
79
|
*/
|
|
81
|
-
public static
|
|
82
|
-
preimage: IChallengeSolution,
|
|
83
|
-
log: boolean = false,
|
|
84
|
-
): Promise<boolean> {
|
|
80
|
+
public static verifySolution(challenge: IChallengeSolution, log: boolean = false): boolean {
|
|
85
81
|
try {
|
|
86
|
-
const verification =
|
|
82
|
+
const verification = challenge.verification;
|
|
87
83
|
const calculatedPreimage = this.calculatePreimage(
|
|
88
84
|
verification.targetChecksum,
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
challenge.publicKey.toBuffer(),
|
|
86
|
+
challenge.salt,
|
|
91
87
|
);
|
|
92
88
|
|
|
93
|
-
const computedSolution =
|
|
89
|
+
const computedSolution = this.sha1(calculatedPreimage);
|
|
94
90
|
const computedSolutionBuffer = this.uint8ArrayToBuffer(computedSolution);
|
|
95
91
|
|
|
96
|
-
if (!computedSolutionBuffer.equals(
|
|
92
|
+
if (!computedSolutionBuffer.equals(challenge.solution)) {
|
|
97
93
|
return false;
|
|
98
94
|
}
|
|
99
95
|
|
|
@@ -102,11 +98,11 @@ export class EpochValidator {
|
|
|
102
98
|
verification.targetHash,
|
|
103
99
|
);
|
|
104
100
|
|
|
105
|
-
if (matchingBits !==
|
|
101
|
+
if (matchingBits !== challenge.difficulty) {
|
|
106
102
|
return false;
|
|
107
103
|
}
|
|
108
104
|
|
|
109
|
-
const expectedStartBlock =
|
|
105
|
+
const expectedStartBlock = challenge.epochNumber * this.BLOCKS_PER_EPOCH;
|
|
110
106
|
const expectedEndBlock = expectedStartBlock + this.BLOCKS_PER_EPOCH - 1n;
|
|
111
107
|
|
|
112
108
|
return !(
|
|
@@ -134,16 +130,16 @@ export class EpochValidator {
|
|
|
134
130
|
/**
|
|
135
131
|
* Validate epoch winner from raw data
|
|
136
132
|
*/
|
|
137
|
-
public static
|
|
133
|
+
public static validateEpochWinner(epochData: RawChallenge): boolean {
|
|
138
134
|
const preimage = new ChallengeSolution(epochData);
|
|
139
|
-
return
|
|
135
|
+
return this.verifySolution(preimage);
|
|
140
136
|
}
|
|
141
137
|
|
|
142
138
|
/**
|
|
143
139
|
* Validate epoch winner from Preimage instance
|
|
144
140
|
*/
|
|
145
|
-
public static
|
|
146
|
-
return
|
|
141
|
+
public static validateChallengeSolution(challenge: IChallengeSolution): boolean {
|
|
142
|
+
return this.verifySolution(challenge);
|
|
147
143
|
}
|
|
148
144
|
|
|
149
145
|
/**
|
|
@@ -153,13 +149,13 @@ export class EpochValidator {
|
|
|
153
149
|
* @param salt The salt buffer (32 bytes)
|
|
154
150
|
* @returns The SHA-1 hash of the preimage
|
|
155
151
|
*/
|
|
156
|
-
public static
|
|
152
|
+
public static calculateSolution(
|
|
157
153
|
targetChecksum: Buffer,
|
|
158
154
|
publicKey: Buffer,
|
|
159
155
|
salt: Buffer,
|
|
160
|
-
):
|
|
156
|
+
): Buffer {
|
|
161
157
|
const preimage = this.calculatePreimage(targetChecksum, publicKey, salt);
|
|
162
|
-
const hash =
|
|
158
|
+
const hash = this.sha1(this.bufferToUint8Array(preimage));
|
|
163
159
|
return this.uint8ArrayToBuffer(hash);
|
|
164
160
|
}
|
|
165
161
|
|
|
@@ -19,7 +19,7 @@ export class DeploymentGenerator extends Generator {
|
|
|
19
19
|
* Compile a bitcoin script representing a contract deployment
|
|
20
20
|
* @param {Buffer} contractBytecode - The contract bytecode
|
|
21
21
|
* @param {Buffer} contractSalt - The contract salt
|
|
22
|
-
* @param {ChallengeSolution}
|
|
22
|
+
* @param {ChallengeSolution} challenge - The challenge for reward
|
|
23
23
|
* @param {bigint} maxPriority - The maximum priority for the contract
|
|
24
24
|
* @param {Buffer} [calldata] - The calldata to be passed to the contract
|
|
25
25
|
* @param {Feature<Features>[]} [features] - Optional features to include in the script
|
|
@@ -28,7 +28,7 @@ export class DeploymentGenerator extends Generator {
|
|
|
28
28
|
public compile(
|
|
29
29
|
contractBytecode: Buffer,
|
|
30
30
|
contractSalt: Buffer,
|
|
31
|
-
|
|
31
|
+
challenge: ChallengeSolution,
|
|
32
32
|
maxPriority: bigint,
|
|
33
33
|
calldata?: Buffer,
|
|
34
34
|
features?: Feature<Features>[],
|
|
@@ -36,7 +36,7 @@ export class DeploymentGenerator extends Generator {
|
|
|
36
36
|
const asm = this.getAsm(
|
|
37
37
|
contractBytecode,
|
|
38
38
|
contractSalt,
|
|
39
|
-
|
|
39
|
+
challenge,
|
|
40
40
|
maxPriority,
|
|
41
41
|
calldata,
|
|
42
42
|
features,
|
|
@@ -57,7 +57,7 @@ export class DeploymentGenerator extends Generator {
|
|
|
57
57
|
private getAsm(
|
|
58
58
|
contractBytecode: Buffer,
|
|
59
59
|
contractSalt: Buffer,
|
|
60
|
-
|
|
60
|
+
challenge: ChallengeSolution,
|
|
61
61
|
maxPriority: bigint,
|
|
62
62
|
calldata?: Buffer,
|
|
63
63
|
features?: Feature<Features>[],
|
|
@@ -85,10 +85,10 @@ export class DeploymentGenerator extends Generator {
|
|
|
85
85
|
opcodes.OP_TOALTSTACK,
|
|
86
86
|
|
|
87
87
|
// CHALLENGE PREIMAGE FOR REWARD,
|
|
88
|
-
|
|
88
|
+
challenge.publicKey.originalPublicKeyBuffer(),
|
|
89
89
|
opcodes.OP_TOALTSTACK,
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
challenge.solution,
|
|
92
92
|
opcodes.OP_TOALTSTACK,
|
|
93
93
|
|
|
94
94
|
this.xSenderPubKey,
|
|
@@ -52,7 +52,7 @@ export class LegacyCalldataGenerator extends Generator {
|
|
|
52
52
|
* Compile an interaction bitcoin script
|
|
53
53
|
* @param {Buffer} calldata - The calldata to use
|
|
54
54
|
* @param {Buffer} contractSecret - The contract secret
|
|
55
|
-
* @param {Buffer}
|
|
55
|
+
* @param {Buffer} challenge - The challenge to use
|
|
56
56
|
* @param {bigint} maxPriority - The maximum priority
|
|
57
57
|
* @param {number[]} [features=[]] - The features to use (optional)
|
|
58
58
|
* @returns {Buffer} - The compiled script
|
|
@@ -61,7 +61,7 @@ export class LegacyCalldataGenerator extends Generator {
|
|
|
61
61
|
public compile(
|
|
62
62
|
calldata: Buffer,
|
|
63
63
|
contractSecret: Buffer,
|
|
64
|
-
|
|
64
|
+
challenge: Buffer,
|
|
65
65
|
maxPriority: bigint,
|
|
66
66
|
features: Feature<Features>[] = [],
|
|
67
67
|
): Buffer {
|
|
@@ -83,7 +83,7 @@ export class LegacyCalldataGenerator extends Generator {
|
|
|
83
83
|
opcodes.OP_TOALTSTACK,
|
|
84
84
|
|
|
85
85
|
// CHALLENGE PREIMAGE FOR REWARD,
|
|
86
|
-
|
|
86
|
+
challenge,
|
|
87
87
|
opcodes.OP_TOALTSTACK,
|
|
88
88
|
|
|
89
89
|
this.senderPubKey,
|
|
@@ -12,6 +12,7 @@ export enum AddressTypes {
|
|
|
12
12
|
P2PK = 'P2PK',
|
|
13
13
|
P2TR = 'P2TR',
|
|
14
14
|
P2WPKH = 'P2WPKH',
|
|
15
|
+
P2WSH = 'P2WSH',
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export class AddressVerificator {
|
|
@@ -193,11 +194,16 @@ export class AddressVerificator {
|
|
|
193
194
|
}
|
|
194
195
|
|
|
195
196
|
if (decodedBech32.prefix === network.bech32) {
|
|
196
|
-
// P2WPKH: SegWit address (
|
|
197
|
+
// P2WPKH: SegWit address (20 bytes)
|
|
197
198
|
if (decodedBech32.version === 0 && decodedBech32.data.length === 20) {
|
|
198
199
|
return AddressTypes.P2WPKH;
|
|
199
200
|
}
|
|
200
201
|
|
|
202
|
+
// P2WSH: SegWit script hash (32 bytes)
|
|
203
|
+
if (decodedBech32.version === 0 && decodedBech32.data.length === 32) {
|
|
204
|
+
return AddressTypes.P2WSH;
|
|
205
|
+
}
|
|
206
|
+
|
|
201
207
|
// P2TR: Taproot address (starting with 'bc1p' for mainnet, 'tb1p' for testnet)
|
|
202
208
|
if (decodedBech32.version === 1 && decodedBech32.data.length === 32) {
|
|
203
209
|
return AddressTypes.P2TR;
|
|
@@ -29,7 +29,7 @@ export interface DeploymentResult {
|
|
|
29
29
|
|
|
30
30
|
readonly contractAddress: string;
|
|
31
31
|
readonly contractPubKey: string;
|
|
32
|
-
readonly
|
|
32
|
+
readonly challenge: RawChallenge;
|
|
33
33
|
|
|
34
34
|
readonly utxos: UTXO[];
|
|
35
35
|
}
|
|
@@ -52,7 +52,7 @@ export interface InteractionResponse {
|
|
|
52
52
|
readonly interactionTransaction: string;
|
|
53
53
|
readonly estimatedFees: bigint;
|
|
54
54
|
readonly nextUTXOs: UTXO[];
|
|
55
|
-
readonly
|
|
55
|
+
readonly challenge: RawChallenge;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
export interface BitcoinTransferResponse extends BitcoinTransferBase {
|
|
@@ -253,7 +253,7 @@ export class TransactionFactory {
|
|
|
253
253
|
interactionParameters.from,
|
|
254
254
|
1,
|
|
255
255
|
), // always 1
|
|
256
|
-
|
|
256
|
+
challenge: preTransaction.getPreimage().toRaw(),
|
|
257
257
|
};
|
|
258
258
|
}
|
|
259
259
|
|
|
@@ -358,7 +358,7 @@ export class TransactionFactory {
|
|
|
358
358
|
contractAddress: finalTransaction.getContractAddress(), //finalTransaction.contractAddress.p2tr(deploymentParameters.network),
|
|
359
359
|
contractPubKey: finalTransaction.contractPubKey,
|
|
360
360
|
utxos: [refundUTXO],
|
|
361
|
-
|
|
361
|
+
challenge: preTransaction.getPreimage().toRaw(),
|
|
362
362
|
};
|
|
363
363
|
}
|
|
364
364
|
|
|
@@ -36,7 +36,7 @@ export class DeploymentTransaction extends TransactionBuilder<TransactionType.DE
|
|
|
36
36
|
|
|
37
37
|
public type: TransactionType.DEPLOYMENT = TransactionType.DEPLOYMENT;
|
|
38
38
|
|
|
39
|
-
protected readonly
|
|
39
|
+
protected readonly challenge: ChallengeSolution;
|
|
40
40
|
protected readonly epochChallenge: ITimeLockOutput;
|
|
41
41
|
|
|
42
42
|
/**
|
|
@@ -128,10 +128,10 @@ export class DeploymentTransaction extends TransactionBuilder<TransactionType.DE
|
|
|
128
128
|
if (!parameters.challenge) throw new Error('Challenge solution is required');
|
|
129
129
|
|
|
130
130
|
this.randomBytes = parameters.randomBytes || BitcoinUtils.rndBytes();
|
|
131
|
-
this.
|
|
131
|
+
this.challenge = parameters.challenge;
|
|
132
132
|
|
|
133
133
|
this.epochChallenge = TimeLockGenerator.generateTimeLockAddress(
|
|
134
|
-
this.
|
|
134
|
+
this.challenge.publicKey.originalPublicKeyBuffer(),
|
|
135
135
|
this.network,
|
|
136
136
|
);
|
|
137
137
|
|
|
@@ -147,7 +147,7 @@ export class DeploymentTransaction extends TransactionBuilder<TransactionType.DE
|
|
|
147
147
|
this.compiledTargetScript = this.deploymentGenerator.compile(
|
|
148
148
|
this.bytecode,
|
|
149
149
|
this.randomBytes,
|
|
150
|
-
this.
|
|
150
|
+
this.challenge,
|
|
151
151
|
this.priorityFee,
|
|
152
152
|
this.calldata,
|
|
153
153
|
this.generateFeatures(parameters),
|
|
@@ -195,7 +195,7 @@ export class DeploymentTransaction extends TransactionBuilder<TransactionType.DE
|
|
|
195
195
|
* @returns {Buffer} The contract bytecode
|
|
196
196
|
*/
|
|
197
197
|
public getPreimage(): ChallengeSolution {
|
|
198
|
-
return this.
|
|
198
|
+
return this.challenge;
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
public getContractAddress(): string {
|