@btc-vision/transaction 1.7.13 → 1.7.15
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/Features.d.ts +6 -0
- package/browser/generators/builders/CalldataGenerator.d.ts +1 -1
- package/browser/generators/builders/LegacyCalldataGenerator.d.ts +1 -1
- package/browser/generators/builders/P2WDAGenerator.d.ts +1 -1
- package/browser/index.js +1 -1
- package/browser/keypair/Address.d.ts +1 -0
- package/browser/transaction/mineable/TimelockGenerator.d.ts +3 -0
- package/build/_version.d.ts +1 -1
- package/build/_version.js +1 -1
- package/build/epoch/ChallengeSolution.js +1 -1
- package/build/generators/Features.d.ts +6 -0
- package/build/generators/Features.js +6 -0
- package/build/generators/builders/CalldataGenerator.d.ts +1 -1
- package/build/generators/builders/CalldataGenerator.js +9 -6
- package/build/generators/builders/DeploymentGenerator.js +3 -2
- package/build/generators/builders/LegacyCalldataGenerator.d.ts +1 -1
- package/build/generators/builders/LegacyCalldataGenerator.js +9 -6
- package/build/generators/builders/P2WDAGenerator.d.ts +1 -1
- package/build/generators/builders/P2WDAGenerator.js +2 -1
- package/build/keypair/Address.d.ts +1 -0
- package/build/keypair/Address.js +10 -0
- package/build/transaction/builders/DeploymentTransaction.js +2 -1
- package/build/transaction/builders/InteractionTransaction.js +3 -1
- package/build/transaction/builders/InteractionTransactionP2WDA.js +3 -1
- package/build/transaction/builders/TransactionBuilder.js +12 -1
- package/build/transaction/mineable/TimelockGenerator.d.ts +3 -0
- package/build/transaction/mineable/TimelockGenerator.js +26 -7
- package/package.json +1 -1
- package/src/_version.ts +1 -1
- package/src/epoch/ChallengeSolution.ts +5 -1
- package/src/generators/Features.ts +7 -0
- package/src/generators/builders/CalldataGenerator.ts +11 -7
- package/src/generators/builders/DeploymentGenerator.ts +6 -2
- package/src/generators/builders/LegacyCalldataGenerator.ts +11 -7
- package/src/generators/builders/P2WDAGenerator.ts +4 -2
- package/src/keypair/Address.ts +35 -1
- package/src/transaction/builders/DeploymentTransaction.ts +2 -1
- package/src/transaction/builders/InteractionTransaction.ts +3 -1
- package/src/transaction/builders/InteractionTransactionP2WDA.ts +3 -1
- package/src/transaction/builders/TransactionBuilder.ts +14 -1
- package/src/transaction/mineable/TimelockGenerator.ts +47 -7
|
@@ -44,6 +44,7 @@ export declare class Address extends Uint8Array {
|
|
|
44
44
|
p2tr(network: Network): string;
|
|
45
45
|
p2wda(network: Network): IP2WSHAddress;
|
|
46
46
|
toCSV(duration: bigint | number | string, network: Network): IP2WSHAddress;
|
|
47
|
+
toCSVTweaked(duration: bigint | number | string, network: Network): string;
|
|
47
48
|
p2op(network: Network): string;
|
|
48
49
|
toTweakedHybridPublicKeyHex(): string;
|
|
49
50
|
toTweakedHybridPublicKeyBuffer(): Buffer;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { Network } from '@btc-vision/bitcoin';
|
|
2
2
|
import { IP2WSHAddress } from './IP2WSHAddress.js';
|
|
3
3
|
export declare class TimeLockGenerator {
|
|
4
|
+
private static readonly UNSPENDABLE_INTERNAL_KEY;
|
|
4
5
|
private static readonly CSV_BLOCKS;
|
|
5
6
|
static generateTimeLockAddress(publicKey: Buffer, network?: Network, csvBlocks?: number): IP2WSHAddress;
|
|
7
|
+
static generateTimeLockAddressP2TR(publicKey: Buffer, network?: Network, csvBlocks?: number): string;
|
|
8
|
+
private static generateTimeLockScript;
|
|
6
9
|
}
|
package/build/_version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "1.7.
|
|
1
|
+
export declare const version = "1.7.15";
|
package/build/_version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '1.7.
|
|
1
|
+
export const version = '1.7.15';
|
|
@@ -31,7 +31,7 @@ export class ChallengeSubmission {
|
|
|
31
31
|
signatureDataWriter.writeBytes(this.graffiti);
|
|
32
32
|
}
|
|
33
33
|
const buffer = signatureDataWriter.getBuffer();
|
|
34
|
-
return MessageSigner.verifySignature(this.publicKey, buffer, this.signature);
|
|
34
|
+
return MessageSigner.verifySignature(this.publicKey.tweakedPublicKeyToBuffer(), buffer, this.signature);
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
export class ChallengeSolution {
|
|
@@ -6,9 +6,15 @@ export declare enum Features {
|
|
|
6
6
|
EPOCH_SUBMISSION = 2,
|
|
7
7
|
MLDSA_LINK_PUBKEY = 4
|
|
8
8
|
}
|
|
9
|
+
export declare enum FeaturePriority {
|
|
10
|
+
ACCESS_LIST = 1,
|
|
11
|
+
EPOCH_SUBMISSION = 2,
|
|
12
|
+
MLDSA_LINK_PUBKEY = 3
|
|
13
|
+
}
|
|
9
14
|
export interface Feature<T extends Features> {
|
|
10
15
|
opcode: T;
|
|
11
16
|
data: unknown;
|
|
17
|
+
priority: number;
|
|
12
18
|
}
|
|
13
19
|
export interface AccessListFeature extends Feature<Features.ACCESS_LIST> {
|
|
14
20
|
data: LoadedStorage;
|
|
@@ -4,3 +4,9 @@ export var Features;
|
|
|
4
4
|
Features[Features["EPOCH_SUBMISSION"] = 2] = "EPOCH_SUBMISSION";
|
|
5
5
|
Features[Features["MLDSA_LINK_PUBKEY"] = 4] = "MLDSA_LINK_PUBKEY";
|
|
6
6
|
})(Features || (Features = {}));
|
|
7
|
+
export var FeaturePriority;
|
|
8
|
+
(function (FeaturePriority) {
|
|
9
|
+
FeaturePriority[FeaturePriority["ACCESS_LIST"] = 1] = "ACCESS_LIST";
|
|
10
|
+
FeaturePriority[FeaturePriority["EPOCH_SUBMISSION"] = 2] = "EPOCH_SUBMISSION";
|
|
11
|
+
FeaturePriority[FeaturePriority["MLDSA_LINK_PUBKEY"] = 3] = "MLDSA_LINK_PUBKEY";
|
|
12
|
+
})(FeaturePriority || (FeaturePriority = {}));
|
|
@@ -5,5 +5,5 @@ import { ChallengeSolution } from '../../epoch/ChallengeSolution.js';
|
|
|
5
5
|
export declare class CalldataGenerator extends Generator {
|
|
6
6
|
constructor(senderPubKey: Buffer, contractSaltPubKey: Buffer, network?: Network);
|
|
7
7
|
static getPubKeyAsBuffer(witnessKeys: Buffer[], network: Network): Buffer;
|
|
8
|
-
compile(calldata: Buffer, contractSecret: Buffer, challenge: ChallengeSolution, maxPriority: bigint,
|
|
8
|
+
compile(calldata: Buffer, contractSecret: Buffer, challenge: ChallengeSolution, maxPriority: bigint, featuresRaw?: Feature<Features>[]): Buffer;
|
|
9
9
|
}
|
|
@@ -24,7 +24,7 @@ export class CalldataGenerator extends Generator {
|
|
|
24
24
|
}
|
|
25
25
|
return compressed;
|
|
26
26
|
}
|
|
27
|
-
compile(calldata, contractSecret, challenge, maxPriority,
|
|
27
|
+
compile(calldata, contractSecret, challenge, maxPriority, featuresRaw = []) {
|
|
28
28
|
if (!this.contractSaltPubKey)
|
|
29
29
|
throw new Error('Contract salt public key not set');
|
|
30
30
|
const dataChunks = this.splitBufferIntoChunks(calldata);
|
|
@@ -32,11 +32,14 @@ export class CalldataGenerator extends Generator {
|
|
|
32
32
|
throw new Error('No data chunks found');
|
|
33
33
|
const featuresList = [];
|
|
34
34
|
const featureData = [];
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
const features = featuresRaw.sort((a, b) => a.priority - b.priority);
|
|
36
|
+
if (features.length) {
|
|
37
|
+
for (let i = 0; i < features.length; i++) {
|
|
38
|
+
const feature = features[i];
|
|
39
|
+
featuresList.push(feature.opcode);
|
|
40
|
+
const data = this.encodeFeature(feature);
|
|
41
|
+
featureData.push(...data);
|
|
42
|
+
}
|
|
40
43
|
}
|
|
41
44
|
let compiledData = [
|
|
42
45
|
this.getHeader(maxPriority, featuresList),
|
|
@@ -15,14 +15,15 @@ export class DeploymentGenerator extends Generator {
|
|
|
15
15
|
}
|
|
16
16
|
return compiled;
|
|
17
17
|
}
|
|
18
|
-
getAsm(contractBytecode, contractSalt, challenge, maxPriority, calldata,
|
|
18
|
+
getAsm(contractBytecode, contractSalt, challenge, maxPriority, calldata, featuresRaw) {
|
|
19
19
|
if (!this.contractSaltPubKey)
|
|
20
20
|
throw new Error('Contract salt public key not set');
|
|
21
21
|
const dataChunks = this.splitBufferIntoChunks(contractBytecode);
|
|
22
22
|
const calldataChunks = calldata ? this.splitBufferIntoChunks(calldata) : [];
|
|
23
23
|
const featuresList = [];
|
|
24
24
|
const featureData = [];
|
|
25
|
-
if (
|
|
25
|
+
if (featuresRaw) {
|
|
26
|
+
const features = featuresRaw.sort((a, b) => a.priority - b.priority);
|
|
26
27
|
for (let i = 0; i < features.length; i++) {
|
|
27
28
|
const feature = features[i];
|
|
28
29
|
featuresList.push(feature.opcode);
|
|
@@ -4,5 +4,5 @@ import { Feature, Features } from '../Features.js';
|
|
|
4
4
|
export declare class LegacyCalldataGenerator extends Generator {
|
|
5
5
|
constructor(senderPubKey: Buffer, network?: Network);
|
|
6
6
|
static getPubKeyAsBuffer(witnessKeys: Buffer[], network: Network): Buffer;
|
|
7
|
-
compile(calldata: Buffer, contractSecret: Buffer, challenge: Buffer, maxPriority: bigint,
|
|
7
|
+
compile(calldata: Buffer, contractSecret: Buffer, challenge: Buffer, maxPriority: bigint, featuresRaw?: Feature<Features>[]): Buffer;
|
|
8
8
|
}
|
|
@@ -24,17 +24,20 @@ export class LegacyCalldataGenerator extends Generator {
|
|
|
24
24
|
}
|
|
25
25
|
return compressed;
|
|
26
26
|
}
|
|
27
|
-
compile(calldata, contractSecret, challenge, maxPriority,
|
|
27
|
+
compile(calldata, contractSecret, challenge, maxPriority, featuresRaw = []) {
|
|
28
28
|
const dataChunks = this.splitBufferIntoChunks(calldata);
|
|
29
29
|
if (!dataChunks.length)
|
|
30
30
|
throw new Error('No data chunks found');
|
|
31
31
|
const featuresList = [];
|
|
32
32
|
const featureData = [];
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
const features = featuresRaw.sort((a, b) => a.priority - b.priority);
|
|
34
|
+
if (features.length) {
|
|
35
|
+
for (let i = 0; i < features.length; i++) {
|
|
36
|
+
const feature = features[i];
|
|
37
|
+
featuresList.push(feature.opcode);
|
|
38
|
+
const data = this.encodeFeature(feature);
|
|
39
|
+
featureData.push(...data);
|
|
40
|
+
}
|
|
38
41
|
}
|
|
39
42
|
let compiledData = [
|
|
40
43
|
this.getHeader(maxPriority, featuresList),
|
|
@@ -6,7 +6,7 @@ export declare class P2WDAGenerator extends Generator {
|
|
|
6
6
|
private static readonly P2WDA_VERSION;
|
|
7
7
|
constructor(senderPubKey: Buffer, contractSaltPubKey: Buffer, network?: Network);
|
|
8
8
|
static validateWitnessSize(dataSize: number, maxWitnessFields?: number, maxBytesPerWitness?: number): boolean;
|
|
9
|
-
compile(calldata: Buffer, contractSecret: Buffer, challenge: ChallengeSolution, maxPriority: bigint,
|
|
9
|
+
compile(calldata: Buffer, contractSecret: Buffer, challenge: ChallengeSolution, maxPriority: bigint, featuresRaw?: Feature<Features>[]): Buffer;
|
|
10
10
|
getHeader(maxPriority: bigint, features?: Features[]): Buffer;
|
|
11
11
|
private writeFeatures;
|
|
12
12
|
private encodeFeatureData;
|
|
@@ -14,7 +14,7 @@ export class P2WDAGenerator extends Generator {
|
|
|
14
14
|
const requiredFields = Math.ceil(compressedSize / maxBytesPerWitness);
|
|
15
15
|
return requiredFields <= maxWitnessFields;
|
|
16
16
|
}
|
|
17
|
-
compile(calldata, contractSecret, challenge, maxPriority,
|
|
17
|
+
compile(calldata, contractSecret, challenge, maxPriority, featuresRaw = []) {
|
|
18
18
|
if (!this.contractSaltPubKey) {
|
|
19
19
|
throw new Error('Contract salt public key not set');
|
|
20
20
|
}
|
|
@@ -23,6 +23,7 @@ export class P2WDAGenerator extends Generator {
|
|
|
23
23
|
}
|
|
24
24
|
const writer = new BinaryWriter();
|
|
25
25
|
writer.writeU8(P2WDAGenerator.P2WDA_VERSION);
|
|
26
|
+
const features = featuresRaw.sort((a, b) => a.priority - b.priority);
|
|
26
27
|
writer.writeBytes(this.getHeader(maxPriority, features.map((f) => f.opcode)));
|
|
27
28
|
writer.writeBytes(contractSecret);
|
|
28
29
|
writer.writeBytes(challenge.publicKey.toBuffer());
|
|
@@ -44,6 +44,7 @@ export declare class Address extends Uint8Array {
|
|
|
44
44
|
p2tr(network: Network): string;
|
|
45
45
|
p2wda(network: Network): IP2WSHAddress;
|
|
46
46
|
toCSV(duration: bigint | number | string, network: Network): IP2WSHAddress;
|
|
47
|
+
toCSVTweaked(duration: bigint | number | string, network: Network): string;
|
|
47
48
|
p2op(network: Network): string;
|
|
48
49
|
toTweakedHybridPublicKeyHex(): string;
|
|
49
50
|
toTweakedHybridPublicKeyBuffer(): Buffer;
|
package/build/keypair/Address.js
CHANGED
|
@@ -351,6 +351,16 @@ export class Address extends Uint8Array {
|
|
|
351
351
|
const publicKeyBuffer = Buffer.from(__classPrivateFieldGet(this, _Address_originalPublicKey, "f"));
|
|
352
352
|
return TimeLockGenerator.generateTimeLockAddress(publicKeyBuffer, network, n);
|
|
353
353
|
}
|
|
354
|
+
toCSVTweaked(duration, network) {
|
|
355
|
+
const n = Number(duration);
|
|
356
|
+
if (n < 1 || n > 65535) {
|
|
357
|
+
throw new Error('CSV block number must be between 1 and 65535');
|
|
358
|
+
}
|
|
359
|
+
if (!__classPrivateFieldGet(this, _Address_originalPublicKey, "f")) {
|
|
360
|
+
throw new Error('Cannot create CSV address: public key not set');
|
|
361
|
+
}
|
|
362
|
+
return TimeLockGenerator.generateTimeLockAddressP2TR(this.tweakedPublicKeyToBuffer(), network, n);
|
|
363
|
+
}
|
|
354
364
|
p2op(network) {
|
|
355
365
|
if (__classPrivateFieldGet(this, _Address_p2op, "f") && __classPrivateFieldGet(this, _Address_network, "f") === network) {
|
|
356
366
|
return __classPrivateFieldGet(this, _Address_p2op, "f");
|
|
@@ -8,7 +8,7 @@ import { Compressor } from '../../bytecode/Compressor.js';
|
|
|
8
8
|
import { SharedInteractionTransaction } from './SharedInteractionTransaction.js';
|
|
9
9
|
import { Address } from '../../keypair/Address.js';
|
|
10
10
|
import { TimeLockGenerator } from '../mineable/TimelockGenerator.js';
|
|
11
|
-
import { Features } from '../../generators/Features.js';
|
|
11
|
+
import { FeaturePriority, Features } from '../../generators/Features.js';
|
|
12
12
|
export class DeploymentTransaction extends TransactionBuilder {
|
|
13
13
|
constructor(parameters) {
|
|
14
14
|
super(parameters);
|
|
@@ -202,6 +202,7 @@ export class DeploymentTransaction extends TransactionBuilder {
|
|
|
202
202
|
const submission = parameters.challenge.getSubmission();
|
|
203
203
|
if (submission) {
|
|
204
204
|
features.push({
|
|
205
|
+
priority: FeaturePriority.MLDSA_LINK_PUBKEY,
|
|
205
206
|
opcode: Features.EPOCH_SUBMISSION,
|
|
206
207
|
data: submission,
|
|
207
208
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TransactionType } from '../enums/TransactionType.js';
|
|
2
2
|
import { SharedInteractionTransaction } from './SharedInteractionTransaction.js';
|
|
3
|
-
import { Features } from '../../generators/Features.js';
|
|
3
|
+
import { FeaturePriority, Features } from '../../generators/Features.js';
|
|
4
4
|
export class InteractionTransaction extends SharedInteractionTransaction {
|
|
5
5
|
constructor(parameters) {
|
|
6
6
|
super(parameters);
|
|
@@ -34,6 +34,7 @@ export class InteractionTransaction extends SharedInteractionTransaction {
|
|
|
34
34
|
const features = [];
|
|
35
35
|
if (parameters.loadedStorage) {
|
|
36
36
|
features.push({
|
|
37
|
+
priority: FeaturePriority.ACCESS_LIST,
|
|
37
38
|
opcode: Features.ACCESS_LIST,
|
|
38
39
|
data: parameters.loadedStorage,
|
|
39
40
|
});
|
|
@@ -41,6 +42,7 @@ export class InteractionTransaction extends SharedInteractionTransaction {
|
|
|
41
42
|
const submission = parameters.challenge.getSubmission();
|
|
42
43
|
if (submission) {
|
|
43
44
|
features.push({
|
|
45
|
+
priority: FeaturePriority.EPOCH_SUBMISSION,
|
|
44
46
|
opcode: Features.EPOCH_SUBMISSION,
|
|
45
47
|
data: submission,
|
|
46
48
|
});
|
|
@@ -5,7 +5,7 @@ import { TransactionBuilder } from './TransactionBuilder.js';
|
|
|
5
5
|
import { MessageSigner } from '../../keypair/MessageSigner.js';
|
|
6
6
|
import { Compressor } from '../../bytecode/Compressor.js';
|
|
7
7
|
import { P2WDAGenerator } from '../../generators/builders/P2WDAGenerator.js';
|
|
8
|
-
import { Features } from '../../generators/Features.js';
|
|
8
|
+
import { FeaturePriority, Features } from '../../generators/Features.js';
|
|
9
9
|
import { BitcoinUtils } from '../../utils/BitcoinUtils.js';
|
|
10
10
|
import { EcKeyPair } from '../../keypair/EcKeyPair.js';
|
|
11
11
|
import { P2WDADetector } from '../../p2wda/P2WDADetector.js';
|
|
@@ -106,6 +106,7 @@ export class InteractionTransactionP2WDA extends TransactionBuilder {
|
|
|
106
106
|
const features = [];
|
|
107
107
|
if (parameters.loadedStorage) {
|
|
108
108
|
features.push({
|
|
109
|
+
priority: FeaturePriority.ACCESS_LIST,
|
|
109
110
|
opcode: Features.ACCESS_LIST,
|
|
110
111
|
data: parameters.loadedStorage,
|
|
111
112
|
});
|
|
@@ -113,6 +114,7 @@ export class InteractionTransactionP2WDA extends TransactionBuilder {
|
|
|
113
114
|
const submission = parameters.challenge.getSubmission();
|
|
114
115
|
if (submission) {
|
|
115
116
|
features.push({
|
|
117
|
+
priority: FeaturePriority.EPOCH_SUBMISSION,
|
|
116
118
|
opcode: Features.EPOCH_SUBMISSION,
|
|
117
119
|
data: submission,
|
|
118
120
|
});
|
|
@@ -4,7 +4,7 @@ import { EcKeyPair } from '../../keypair/EcKeyPair.js';
|
|
|
4
4
|
import { AddressVerificator } from '../../keypair/AddressVerificator.js';
|
|
5
5
|
import { TweakedTransaction } from '../shared/TweakedTransaction.js';
|
|
6
6
|
import { P2WDADetector } from '../../p2wda/P2WDADetector.js';
|
|
7
|
-
import { Features } from '../../generators/Features.js';
|
|
7
|
+
import { FeaturePriority, Features } from '../../generators/Features.js';
|
|
8
8
|
import { BITCOIN_PROTOCOL_ID, getChainId } from '../../chain/ChainData.js';
|
|
9
9
|
import { BinaryWriter } from '../../buffer/BinaryWriter.js';
|
|
10
10
|
import { MLDSASecurityLevel } from '@btc-vision/bip32';
|
|
@@ -500,11 +500,16 @@ export class TransactionBuilder extends TweakedTransaction {
|
|
|
500
500
|
throw new Error('Tweaked signer is not defined');
|
|
501
501
|
}
|
|
502
502
|
const tweakedKey = toXOnly(this.tweakedSigner.publicKey);
|
|
503
|
+
const originalKey = this.signer.publicKey;
|
|
504
|
+
if (originalKey.length !== 33) {
|
|
505
|
+
throw new Error('Original public key must be compressed (33 bytes)');
|
|
506
|
+
}
|
|
503
507
|
const chainId = getChainId(this.network);
|
|
504
508
|
const writer = new BinaryWriter();
|
|
505
509
|
writer.writeU8(MLDSASecurityLevel.LEVEL2);
|
|
506
510
|
writer.writeBytes(this.hashedPublicKey);
|
|
507
511
|
writer.writeBytes(tweakedKey);
|
|
512
|
+
writer.writeBytes(originalKey);
|
|
508
513
|
writer.writeBytes(BITCOIN_PROTOCOL_ID);
|
|
509
514
|
writer.writeBytes(chainId);
|
|
510
515
|
const message = writer.getBuffer();
|
|
@@ -524,6 +529,10 @@ export class TransactionBuilder extends TweakedTransaction {
|
|
|
524
529
|
throw new Error('Tweaked signer is not defined');
|
|
525
530
|
}
|
|
526
531
|
const tweakedKey = toXOnly(this.tweakedSigner.publicKey);
|
|
532
|
+
const originalKey = this.signer.publicKey;
|
|
533
|
+
if (originalKey.length !== 33) {
|
|
534
|
+
throw new Error('Original public key must be compressed (33 bytes)');
|
|
535
|
+
}
|
|
527
536
|
const chainId = getChainId(this.network);
|
|
528
537
|
const level = getLevelFromPublicKeyLength(this.mldsaSigner.publicKey.length);
|
|
529
538
|
if (level !== MLDSASecurityLevel.LEVEL2) {
|
|
@@ -534,6 +543,7 @@ export class TransactionBuilder extends TweakedTransaction {
|
|
|
534
543
|
writer.writeBytes(this.hashedPublicKey);
|
|
535
544
|
writer.writeBytes(this.mldsaSigner.publicKey);
|
|
536
545
|
writer.writeBytes(tweakedKey);
|
|
546
|
+
writer.writeBytes(originalKey);
|
|
537
547
|
writer.writeBytes(BITCOIN_PROTOCOL_ID);
|
|
538
548
|
writer.writeBytes(chainId);
|
|
539
549
|
const message = writer.getBuffer();
|
|
@@ -552,6 +562,7 @@ export class TransactionBuilder extends TweakedTransaction {
|
|
|
552
562
|
mldsaSignature = this.generateMLDSASignature();
|
|
553
563
|
}
|
|
554
564
|
const mldsaRequest = {
|
|
565
|
+
priority: FeaturePriority.MLDSA_LINK_PUBKEY,
|
|
555
566
|
opcode: Features.MLDSA_LINK_PUBKEY,
|
|
556
567
|
data: {
|
|
557
568
|
verifyRequest: !!parameters.revealMLDSAPublicKey,
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { Network } from '@btc-vision/bitcoin';
|
|
2
2
|
import { IP2WSHAddress } from './IP2WSHAddress.js';
|
|
3
3
|
export declare class TimeLockGenerator {
|
|
4
|
+
private static readonly UNSPENDABLE_INTERNAL_KEY;
|
|
4
5
|
private static readonly CSV_BLOCKS;
|
|
5
6
|
static generateTimeLockAddress(publicKey: Buffer, network?: Network, csvBlocks?: number): IP2WSHAddress;
|
|
7
|
+
static generateTimeLockAddressP2TR(publicKey: Buffer, network?: Network, csvBlocks?: number): string;
|
|
8
|
+
private static generateTimeLockScript;
|
|
6
9
|
}
|
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import bitcoin, { networks, opcodes, script } from '@btc-vision/bitcoin';
|
|
2
2
|
export class TimeLockGenerator {
|
|
3
3
|
static generateTimeLockAddress(publicKey, network = networks.bitcoin, csvBlocks = TimeLockGenerator.CSV_BLOCKS) {
|
|
4
|
-
const witnessScript =
|
|
5
|
-
script.number.encode(csvBlocks),
|
|
6
|
-
opcodes.OP_CHECKSEQUENCEVERIFY,
|
|
7
|
-
opcodes.OP_DROP,
|
|
8
|
-
publicKey,
|
|
9
|
-
opcodes.OP_CHECKSIG,
|
|
10
|
-
]);
|
|
4
|
+
const witnessScript = this.generateTimeLockScript(publicKey, csvBlocks);
|
|
11
5
|
const p2wsh = bitcoin.payments.p2wsh({
|
|
12
6
|
redeem: { output: witnessScript },
|
|
13
7
|
network,
|
|
@@ -20,5 +14,30 @@ export class TimeLockGenerator {
|
|
|
20
14
|
witnessScript: witnessScript,
|
|
21
15
|
};
|
|
22
16
|
}
|
|
17
|
+
static generateTimeLockAddressP2TR(publicKey, network = networks.bitcoin, csvBlocks = TimeLockGenerator.CSV_BLOCKS) {
|
|
18
|
+
if (publicKey.length !== 32) {
|
|
19
|
+
throw new Error('Public key must be 32 bytes for Taproot');
|
|
20
|
+
}
|
|
21
|
+
const witnessScript = this.generateTimeLockScript(publicKey, csvBlocks);
|
|
22
|
+
const taproot = bitcoin.payments.p2tr({
|
|
23
|
+
redeem: { output: witnessScript },
|
|
24
|
+
network,
|
|
25
|
+
internalPubkey: TimeLockGenerator.UNSPENDABLE_INTERNAL_KEY,
|
|
26
|
+
});
|
|
27
|
+
if (!taproot.address) {
|
|
28
|
+
throw new Error('Failed to generate P2TR address');
|
|
29
|
+
}
|
|
30
|
+
return taproot.address;
|
|
31
|
+
}
|
|
32
|
+
static generateTimeLockScript(publicKey, csvBlocks = TimeLockGenerator.CSV_BLOCKS) {
|
|
33
|
+
return script.compile([
|
|
34
|
+
script.number.encode(csvBlocks),
|
|
35
|
+
opcodes.OP_CHECKSEQUENCEVERIFY,
|
|
36
|
+
opcodes.OP_DROP,
|
|
37
|
+
publicKey,
|
|
38
|
+
opcodes.OP_CHECKSIG,
|
|
39
|
+
]);
|
|
40
|
+
}
|
|
23
41
|
}
|
|
42
|
+
TimeLockGenerator.UNSPENDABLE_INTERNAL_KEY = Buffer.from('50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0', 'hex');
|
|
24
43
|
TimeLockGenerator.CSV_BLOCKS = 75;
|
package/package.json
CHANGED
package/src/_version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '1.7.
|
|
1
|
+
export const version = '1.7.15';
|
|
@@ -59,7 +59,11 @@ export class ChallengeSubmission implements IChallengeSubmission {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
const buffer = signatureDataWriter.getBuffer();
|
|
62
|
-
return MessageSigner.verifySignature(
|
|
62
|
+
return MessageSigner.verifySignature(
|
|
63
|
+
this.publicKey.tweakedPublicKeyToBuffer(),
|
|
64
|
+
buffer,
|
|
65
|
+
this.signature,
|
|
66
|
+
);
|
|
63
67
|
}
|
|
64
68
|
}
|
|
65
69
|
|
|
@@ -8,9 +8,16 @@ export enum Features {
|
|
|
8
8
|
MLDSA_LINK_PUBKEY = 0b100,
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
export enum FeaturePriority {
|
|
12
|
+
ACCESS_LIST = 1,
|
|
13
|
+
EPOCH_SUBMISSION = 2,
|
|
14
|
+
MLDSA_LINK_PUBKEY = 3,
|
|
15
|
+
}
|
|
16
|
+
|
|
11
17
|
export interface Feature<T extends Features> {
|
|
12
18
|
opcode: T;
|
|
13
19
|
data: unknown;
|
|
20
|
+
priority: number;
|
|
14
21
|
}
|
|
15
22
|
|
|
16
23
|
export interface AccessListFeature extends Feature<Features.ACCESS_LIST> {
|
|
@@ -59,7 +59,7 @@ export class CalldataGenerator extends Generator {
|
|
|
59
59
|
* @param {Buffer} contractSecret - The contract secret
|
|
60
60
|
* @param {ChallengeSolution} challenge
|
|
61
61
|
* @param maxPriority - Amount of satoshis to spend max on priority fee
|
|
62
|
-
* @param {Feature<Features>[]}
|
|
62
|
+
* @param {Feature<Features>[]} featuresRaw - The features to use
|
|
63
63
|
* @returns {Buffer} - The compiled script
|
|
64
64
|
* @throws {Error} - If something goes wrong
|
|
65
65
|
*/
|
|
@@ -68,7 +68,7 @@ export class CalldataGenerator extends Generator {
|
|
|
68
68
|
contractSecret: Buffer,
|
|
69
69
|
challenge: ChallengeSolution,
|
|
70
70
|
maxPriority: bigint,
|
|
71
|
-
|
|
71
|
+
featuresRaw: Feature<Features>[] = [],
|
|
72
72
|
): Buffer {
|
|
73
73
|
if (!this.contractSaltPubKey) throw new Error('Contract salt public key not set');
|
|
74
74
|
|
|
@@ -77,12 +77,16 @@ export class CalldataGenerator extends Generator {
|
|
|
77
77
|
|
|
78
78
|
const featuresList: Features[] = [];
|
|
79
79
|
const featureData: (number | Buffer | Buffer[])[] = [];
|
|
80
|
-
for (let i = 0; i < features.length; i++) {
|
|
81
|
-
const feature = features[i];
|
|
82
|
-
featuresList.push(feature.opcode);
|
|
83
80
|
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
const features: Feature<Features>[] = featuresRaw.sort((a, b) => a.priority - b.priority);
|
|
82
|
+
if (features.length) {
|
|
83
|
+
for (let i = 0; i < features.length; i++) {
|
|
84
|
+
const feature = features[i];
|
|
85
|
+
featuresList.push(feature.opcode);
|
|
86
|
+
|
|
87
|
+
const data = this.encodeFeature(feature);
|
|
88
|
+
featureData.push(...data);
|
|
89
|
+
}
|
|
86
90
|
}
|
|
87
91
|
|
|
88
92
|
let compiledData: (number | Buffer | Buffer[])[] = [
|
|
@@ -61,7 +61,7 @@ export class DeploymentGenerator extends Generator {
|
|
|
61
61
|
challenge: ChallengeSolution,
|
|
62
62
|
maxPriority: bigint,
|
|
63
63
|
calldata?: Buffer,
|
|
64
|
-
|
|
64
|
+
featuresRaw?: Feature<Features>[],
|
|
65
65
|
): (number | Buffer)[] {
|
|
66
66
|
if (!this.contractSaltPubKey) throw new Error('Contract salt public key not set');
|
|
67
67
|
|
|
@@ -71,7 +71,11 @@ export class DeploymentGenerator extends Generator {
|
|
|
71
71
|
const featuresList: Features[] = [];
|
|
72
72
|
const featureData: (number | Buffer | Buffer[])[] = [];
|
|
73
73
|
|
|
74
|
-
if (
|
|
74
|
+
if (featuresRaw) {
|
|
75
|
+
const features: Feature<Features>[] = featuresRaw.sort(
|
|
76
|
+
(a, b) => a.priority - b.priority,
|
|
77
|
+
);
|
|
78
|
+
|
|
75
79
|
for (let i = 0; i < features.length; i++) {
|
|
76
80
|
const feature = features[i];
|
|
77
81
|
featuresList.push(feature.opcode);
|
|
@@ -55,7 +55,7 @@ export class LegacyCalldataGenerator extends Generator {
|
|
|
55
55
|
* @param {Buffer} contractSecret - The contract secret
|
|
56
56
|
* @param {Buffer} challenge - The challenge to use
|
|
57
57
|
* @param {bigint} maxPriority - The maximum priority
|
|
58
|
-
* @param {number[]} [
|
|
58
|
+
* @param {number[]} [featuresRaw=[]] - The features to use (optional)
|
|
59
59
|
* @returns {Buffer} - The compiled script
|
|
60
60
|
* @throws {Error} - If something goes wrong
|
|
61
61
|
*/
|
|
@@ -64,19 +64,23 @@ export class LegacyCalldataGenerator extends Generator {
|
|
|
64
64
|
contractSecret: Buffer,
|
|
65
65
|
challenge: Buffer,
|
|
66
66
|
maxPriority: bigint,
|
|
67
|
-
|
|
67
|
+
featuresRaw: Feature<Features>[] = [],
|
|
68
68
|
): Buffer {
|
|
69
69
|
const dataChunks: Buffer[][] = this.splitBufferIntoChunks(calldata);
|
|
70
70
|
if (!dataChunks.length) throw new Error('No data chunks found');
|
|
71
71
|
|
|
72
72
|
const featuresList: Features[] = [];
|
|
73
73
|
const featureData: (number | Buffer | Buffer[])[] = [];
|
|
74
|
-
|
|
75
|
-
const feature = features[i];
|
|
76
|
-
featuresList.push(feature.opcode);
|
|
74
|
+
const features: Feature<Features>[] = featuresRaw.sort((a, b) => a.priority - b.priority);
|
|
77
75
|
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
if (features.length) {
|
|
77
|
+
for (let i = 0; i < features.length; i++) {
|
|
78
|
+
const feature = features[i];
|
|
79
|
+
featuresList.push(feature.opcode);
|
|
80
|
+
|
|
81
|
+
const data = this.encodeFeature(feature);
|
|
82
|
+
featureData.push(...data);
|
|
83
|
+
}
|
|
80
84
|
}
|
|
81
85
|
|
|
82
86
|
let compiledData = [
|
|
@@ -59,7 +59,7 @@ export class P2WDAGenerator extends Generator {
|
|
|
59
59
|
* @param contractSecret The 32-byte contract secret
|
|
60
60
|
* @param challenge The challenge solution for epoch rewards
|
|
61
61
|
* @param maxPriority Maximum priority fee in satoshis
|
|
62
|
-
* @param
|
|
62
|
+
* @param featuresRaw Optional features like access lists
|
|
63
63
|
* @returns Raw operation data ready for signing and compression
|
|
64
64
|
*/
|
|
65
65
|
public compile(
|
|
@@ -67,7 +67,7 @@ export class P2WDAGenerator extends Generator {
|
|
|
67
67
|
contractSecret: Buffer,
|
|
68
68
|
challenge: ChallengeSolution,
|
|
69
69
|
maxPriority: bigint,
|
|
70
|
-
|
|
70
|
+
featuresRaw: Feature<Features>[] = [],
|
|
71
71
|
): Buffer {
|
|
72
72
|
if (!this.contractSaltPubKey) {
|
|
73
73
|
throw new Error('Contract salt public key not set');
|
|
@@ -82,6 +82,8 @@ export class P2WDAGenerator extends Generator {
|
|
|
82
82
|
// Version byte
|
|
83
83
|
writer.writeU8(P2WDAGenerator.P2WDA_VERSION);
|
|
84
84
|
|
|
85
|
+
const features: Feature<Features>[] = featuresRaw.sort((a, b) => a.priority - b.priority);
|
|
86
|
+
|
|
85
87
|
// Header
|
|
86
88
|
writer.writeBytes(
|
|
87
89
|
this.getHeader(
|
package/src/keypair/Address.ts
CHANGED
|
@@ -645,7 +645,7 @@ export class Address extends Uint8Array {
|
|
|
645
645
|
}
|
|
646
646
|
|
|
647
647
|
/**
|
|
648
|
-
* Generate a P2WSH address with CSV (CheckSequenceVerify)
|
|
648
|
+
* Generate a P2WSH address with CSV (CheckSequenceVerify) time lock
|
|
649
649
|
* The resulting address can only be spent after the specified number of blocks
|
|
650
650
|
* have passed since the UTXO was created.
|
|
651
651
|
*
|
|
@@ -677,6 +677,40 @@ export class Address extends Uint8Array {
|
|
|
677
677
|
return TimeLockGenerator.generateTimeLockAddress(publicKeyBuffer, network, n);
|
|
678
678
|
}
|
|
679
679
|
|
|
680
|
+
/**
|
|
681
|
+
* Generate a P2TR address with CSV (CheckSequenceVerify) time lock
|
|
682
|
+
* The resulting address can only be spent after the specified number of blocks
|
|
683
|
+
* have passed since the UTXO was created.
|
|
684
|
+
*
|
|
685
|
+
* @param {bigint | number | string} duration - The number of blocks that must pass before spending (1-65535)
|
|
686
|
+
* @param {Network} network - The Bitcoin network to use
|
|
687
|
+
* @returns {IP2WSHAddress} The timelocked address and its witness script
|
|
688
|
+
* @throws {Error} If the block number is out of range or public key is not available
|
|
689
|
+
*/
|
|
690
|
+
public toCSVTweaked(duration: bigint | number | string, network: Network): string {
|
|
691
|
+
const n = Number(duration);
|
|
692
|
+
|
|
693
|
+
// First, let's validate the block number to ensure it's within the valid range
|
|
694
|
+
// CSV uses sequence numbers, which have special encoding for block-based locks
|
|
695
|
+
if (n < 1 || n > 65535) {
|
|
696
|
+
throw new Error('CSV block number must be between 1 and 65535');
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// We need the original public key in compressed format for the script
|
|
700
|
+
// Your class stores this in #originalPublicKey when a key is set
|
|
701
|
+
if (!this.#originalPublicKey) {
|
|
702
|
+
throw new Error('Cannot create CSV address: public key not set');
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// Now we can use your TimeLockGenerator to create the timelocked address
|
|
706
|
+
// Converting bigint to number is safe here because we've already validated the range
|
|
707
|
+
return TimeLockGenerator.generateTimeLockAddressP2TR(
|
|
708
|
+
this.tweakedPublicKeyToBuffer(),
|
|
709
|
+
network,
|
|
710
|
+
n,
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
|
|
680
714
|
/**
|
|
681
715
|
* Returns the OPNet address encoded in bech32m format, derived from the SHA256 hash of the ML-DSA public key
|
|
682
716
|
* (which is what the Address internally stores).
|
|
@@ -25,7 +25,7 @@ import { Address } from '../../keypair/Address.js';
|
|
|
25
25
|
import { UnisatSigner } from '../browser/extensions/UnisatSigner.js';
|
|
26
26
|
import { TimeLockGenerator } from '../mineable/TimelockGenerator.js';
|
|
27
27
|
import { ChallengeSolution } from '../../epoch/ChallengeSolution.js';
|
|
28
|
-
import { Feature, Features } from '../../generators/Features.js';
|
|
28
|
+
import { Feature, FeaturePriority, Features } from '../../generators/Features.js';
|
|
29
29
|
import { IP2WSHAddress } from '../mineable/IP2WSHAddress.js';
|
|
30
30
|
|
|
31
31
|
export class DeploymentTransaction extends TransactionBuilder<TransactionType.DEPLOYMENT> {
|
|
@@ -379,6 +379,7 @@ export class DeploymentTransaction extends TransactionBuilder<TransactionType.DE
|
|
|
379
379
|
const submission = parameters.challenge.getSubmission();
|
|
380
380
|
if (submission) {
|
|
381
381
|
features.push({
|
|
382
|
+
priority: FeaturePriority.MLDSA_LINK_PUBKEY,
|
|
382
383
|
opcode: Features.EPOCH_SUBMISSION,
|
|
383
384
|
data: submission,
|
|
384
385
|
});
|