@btc-vision/transaction 1.6.1 → 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/index.js +1 -1
- package/browser/keypair/AddressVerificator.d.ts +2 -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/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 +3 -3
- package/build/epoch/validator/EpochValidator.d.ts +5 -6
- package/build/epoch/validator/EpochValidator.js +11 -12
- package/build/keypair/AddressVerificator.d.ts +2 -1
- package/build/keypair/AddressVerificator.js +4 -0
- 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/package.json +2 -2
- package/src/_version.ts +1 -1
- package/src/epoch/ChallengeSolution.ts +4 -4
- package/src/epoch/validator/EpochValidator.ts +12 -16
- package/src/keypair/AddressVerificator.ts +7 -1
- package/src/transaction/builders/TransactionBuilder.ts +30 -3
- package/src/transaction/interfaces/ITransactionParameters.ts +1 -0
- package/src/transaction/shared/TweakedTransaction.ts +210 -23
|
@@ -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) {
|
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
|
|
|
@@ -116,9 +116,9 @@ export class ChallengeSolution implements IChallengeSolution {
|
|
|
116
116
|
|
|
117
117
|
/**
|
|
118
118
|
* Verify this challenge
|
|
119
|
-
* @returns {
|
|
119
|
+
* @returns {boolean} True if the challenge is valid
|
|
120
120
|
*/
|
|
121
|
-
public
|
|
121
|
+
public verify(): boolean {
|
|
122
122
|
return EpochValidator.validateChallengeSolution(this);
|
|
123
123
|
}
|
|
124
124
|
|
|
@@ -165,7 +165,7 @@ export class ChallengeSolution implements IChallengeSolution {
|
|
|
165
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(),
|
|
@@ -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,10 +77,7 @@ export class EpochValidator {
|
|
|
78
77
|
/**
|
|
79
78
|
* Verify an epoch solution using IPreimage
|
|
80
79
|
*/
|
|
81
|
-
public static
|
|
82
|
-
challenge: IChallengeSolution,
|
|
83
|
-
log: boolean = false,
|
|
84
|
-
): Promise<boolean> {
|
|
80
|
+
public static verifySolution(challenge: IChallengeSolution, log: boolean = false): boolean {
|
|
85
81
|
try {
|
|
86
82
|
const verification = challenge.verification;
|
|
87
83
|
const calculatedPreimage = this.calculatePreimage(
|
|
@@ -90,7 +86,7 @@ export class EpochValidator {
|
|
|
90
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
92
|
if (!computedSolutionBuffer.equals(challenge.solution)) {
|
|
@@ -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
|
|
|
@@ -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;
|
|
@@ -28,6 +28,7 @@ initEccLib(ecc);
|
|
|
28
28
|
|
|
29
29
|
export const MINIMUM_AMOUNT_REWARD: bigint = 540n;
|
|
30
30
|
export const MINIMUM_AMOUNT_CA: bigint = 297n;
|
|
31
|
+
export const ANCHOR_SCRIPT = Buffer.from('51024e73', 'hex');
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* Allows to build a transaction like you would on Ethereum.
|
|
@@ -148,6 +149,12 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
|
|
|
148
149
|
*/
|
|
149
150
|
protected isPubKeyDestination: boolean;
|
|
150
151
|
|
|
152
|
+
/**
|
|
153
|
+
* @description If the transaction need an anchor output
|
|
154
|
+
* @protected
|
|
155
|
+
*/
|
|
156
|
+
protected anchor: boolean;
|
|
157
|
+
|
|
151
158
|
protected note?: Buffer;
|
|
152
159
|
|
|
153
160
|
protected constructor(parameters: ITransactionParameters) {
|
|
@@ -174,6 +181,8 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
|
|
|
174
181
|
}
|
|
175
182
|
}
|
|
176
183
|
|
|
184
|
+
this.anchor = parameters.anchor ?? false;
|
|
185
|
+
|
|
177
186
|
this.isPubKeyDestination = this.to
|
|
178
187
|
? AddressVerificator.isValidPublicKey(this.to, this.network)
|
|
179
188
|
: false;
|
|
@@ -191,6 +200,7 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
|
|
|
191
200
|
|
|
192
201
|
this.transaction = new Psbt({
|
|
193
202
|
network: this.network,
|
|
203
|
+
version: this.txVersion,
|
|
194
204
|
});
|
|
195
205
|
}
|
|
196
206
|
|
|
@@ -247,6 +257,13 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
|
|
|
247
257
|
});
|
|
248
258
|
}
|
|
249
259
|
|
|
260
|
+
public addAnchor(): void {
|
|
261
|
+
this.addOutput({
|
|
262
|
+
value: 0,
|
|
263
|
+
script: ANCHOR_SCRIPT,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
250
267
|
public async getFundingTransactionParameters(): Promise<IFundingTransactionParameters> {
|
|
251
268
|
if (!this.estimatedFees) {
|
|
252
269
|
this.estimatedFees = await this.estimateTransactionFees();
|
|
@@ -400,8 +417,10 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
|
|
|
400
417
|
throw new Error('Output script is too short');
|
|
401
418
|
}
|
|
402
419
|
|
|
403
|
-
if (script.script[0] !== opcodes.OP_RETURN) {
|
|
404
|
-
throw new Error(
|
|
420
|
+
if (script.script[0] !== opcodes.OP_RETURN && !script.script.equals(ANCHOR_SCRIPT)) {
|
|
421
|
+
throw new Error(
|
|
422
|
+
'Output script must start with OP_RETURN or be an ANCHOR when value is 0',
|
|
423
|
+
);
|
|
405
424
|
}
|
|
406
425
|
} else if (output.value < TransactionBuilder.MINIMUM_DUST) {
|
|
407
426
|
throw new Error(
|
|
@@ -462,7 +481,11 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
|
|
|
462
481
|
}
|
|
463
482
|
|
|
464
483
|
public async rebuildFromBase64(base64: string): Promise<Psbt> {
|
|
465
|
-
this.transaction = Psbt.fromBase64(base64, {
|
|
484
|
+
this.transaction = Psbt.fromBase64(base64, {
|
|
485
|
+
network: this.network,
|
|
486
|
+
version: this.txVersion,
|
|
487
|
+
});
|
|
488
|
+
|
|
466
489
|
this.signed = false;
|
|
467
490
|
|
|
468
491
|
this.sighashTypes = [Transaction.SIGHASH_ANYONECANPAY, Transaction.SIGHASH_ALL];
|
|
@@ -517,6 +540,10 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
|
|
|
517
540
|
this.addOPReturn(this.note);
|
|
518
541
|
}
|
|
519
542
|
|
|
543
|
+
if (this.anchor) {
|
|
544
|
+
this.addAnchor();
|
|
545
|
+
}
|
|
546
|
+
|
|
520
547
|
/** Add the refund output */
|
|
521
548
|
const sendBackAmount: bigint = this.totalInputAmount - amountSpent;
|
|
522
549
|
if (sendBackAmount >= TransactionBuilder.MINIMUM_DUST) {
|