@btc-vision/transaction 1.7.12 → 1.7.14

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.
@@ -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
  }
@@ -1 +1 @@
1
- export declare const version = "1.7.12";
1
+ export declare const version = "1.7.14";
package/build/_version.js CHANGED
@@ -1 +1 @@
1
- export const version = '1.7.12';
1
+ export const version = '1.7.14';
@@ -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;
@@ -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");
@@ -4,7 +4,6 @@ import { Address } from './Address.js';
4
4
  import { BitcoinUtils } from '../utils/BitcoinUtils.js';
5
5
  import * as ecc from '@bitcoinerlab/secp256k1';
6
6
  import { getMLDSAConfig, MLDSASecurityLevel, QuantumBIP32Factory, } from '@btc-vision/bip32';
7
- import { randomBytes } from 'crypto';
8
7
  initEccLib(ecc);
9
8
  export class Wallet {
10
9
  constructor(privateKeyOrWif, mldsaPrivateKeyOrBase58, network = networks.bitcoin, securityLevel = MLDSASecurityLevel.LEVEL2, chainCode) {
@@ -41,7 +40,7 @@ export class Wallet {
41
40
  if (chainCode && chainCode.length !== 32) {
42
41
  throw new Error('Chain code must be 32 bytes');
43
42
  }
44
- this._chainCode = chainCode || randomBytes(32);
43
+ this._chainCode = chainCode || Buffer.alloc(32);
45
44
  this._mldsaKeypair = QuantumBIP32Factory.fromPrivateKey(mldsaPrivateKeyBuffer, this._chainCode, this.network, securityLevel);
46
45
  }
47
46
  else {
@@ -55,7 +55,7 @@ export class Mnemonic {
55
55
  if (!quantumChild.privateKey) {
56
56
  throw new Error(`Failed to derive quantum private key at index ${index}`);
57
57
  }
58
- return new Wallet(Buffer.from(classicalChild.privateKey).toString('hex'), Buffer.from(quantumChild.privateKey).toString('hex'), this._network, this._securityLevel);
58
+ return new Wallet(Buffer.from(classicalChild.privateKey).toString('hex'), Buffer.from(quantumChild.privateKey).toString('hex'), this._network, this._securityLevel, Buffer.from(this._quantumRoot.chainCode));
59
59
  }
60
60
  deriveUnisat(addressType = AddressTypes.P2TR, index = 0, account = 0, isChange = false) {
61
61
  let purpose;
@@ -87,7 +87,7 @@ export class Mnemonic {
87
87
  if (!quantumChild.privateKey) {
88
88
  throw new Error(`Failed to derive quantum private key at path ${quantumPath}`);
89
89
  }
90
- return new Wallet(Buffer.from(classicalChild.privateKey).toString('hex'), Buffer.from(quantumChild.privateKey).toString('hex'), this._network, this._securityLevel);
90
+ return new Wallet(Buffer.from(classicalChild.privateKey).toString('hex'), Buffer.from(quantumChild.privateKey).toString('hex'), this._network, this._securityLevel, Buffer.from(this._quantumRoot.chainCode));
91
91
  }
92
92
  deriveMultipleUnisat(addressType = AddressTypes.P2TR, count = 5, startIndex = 0, account = 0, isChange = false) {
93
93
  const wallets = [];
@@ -112,7 +112,7 @@ export class Mnemonic {
112
112
  if (!quantumChild.privateKey) {
113
113
  throw new Error(`Failed to derive quantum private key at path ${quantumPath}`);
114
114
  }
115
- return new Wallet(Buffer.from(classicalChild.privateKey).toString('hex'), Buffer.from(quantumChild.privateKey).toString('hex'), this._network, this._securityLevel);
115
+ return new Wallet(Buffer.from(classicalChild.privateKey).toString('hex'), Buffer.from(quantumChild.privateKey).toString('hex'), this._network, this._securityLevel, Buffer.from(this._quantumRoot.chainCode));
116
116
  }
117
117
  getClassicalRoot() {
118
118
  return this._classicalRoot;
@@ -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();
@@ -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 = script.compile([
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@btc-vision/transaction",
3
3
  "type": "module",
4
- "version": "1.7.12",
4
+ "version": "1.7.14",
5
5
  "author": "BlobMaster41",
6
6
  "description": "OPNet transaction library allows you to create and sign transactions for the OPNet network.",
7
7
  "engines": {
@@ -95,13 +95,13 @@
95
95
  "dependencies": {
96
96
  "@babel/plugin-proposal-object-rest-spread": "^7.20.7",
97
97
  "@bitcoinerlab/secp256k1": "^1.2.0",
98
- "@noble/curves": "^1.9.7",
99
98
  "@btc-vision/bip32": "^6.0.3",
100
99
  "@btc-vision/bitcoin": "^6.4.11",
101
100
  "@btc-vision/bitcoin-rpc": "^1.0.5",
102
101
  "@btc-vision/logger": "^1.0.7",
103
102
  "@btc-vision/post-quantum": "^0.5.3",
104
103
  "@eslint/js": "^9.39.1",
104
+ "@noble/curves": "^1.9.7",
105
105
  "@noble/secp256k1": "^3.0.0",
106
106
  "assert": "^2.1.0",
107
107
  "babel-loader": "^10.0.0",
package/src/_version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '1.7.12';
1
+ export const version = '1.7.14';
@@ -645,7 +645,7 @@ export class Address extends Uint8Array {
645
645
  }
646
646
 
647
647
  /**
648
- * Generate a P2WSH address with CSV (CheckSequenceVerify) timelock
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).
@@ -11,7 +11,6 @@ import {
11
11
  QuantumBIP32Factory,
12
12
  QuantumBIP32Interface,
13
13
  } from '@btc-vision/bip32';
14
- import { randomBytes } from 'crypto';
15
14
 
16
15
  initEccLib(ecc);
17
16
 
@@ -168,7 +167,8 @@ export class Wallet {
168
167
  if (chainCode && chainCode.length !== 32) {
169
168
  throw new Error('Chain code must be 32 bytes');
170
169
  }
171
- this._chainCode = chainCode || randomBytes(32);
170
+
171
+ this._chainCode = chainCode || Buffer.alloc(32);
172
172
 
173
173
  // Create QuantumBIP32Interface from private key and chain code
174
174
  // Pass network to ensure network-specific derivation
@@ -244,6 +244,7 @@ export class Mnemonic {
244
244
  Buffer.from(quantumChild.privateKey).toString('hex'),
245
245
  this._network,
246
246
  this._securityLevel,
247
+ Buffer.from(this._quantumRoot.chainCode),
247
248
  );
248
249
  }
249
250
 
@@ -313,6 +314,7 @@ export class Mnemonic {
313
314
  Buffer.from(quantumChild.privateKey).toString('hex'),
314
315
  this._network,
315
316
  this._securityLevel,
317
+ Buffer.from(this._quantumRoot.chainCode),
316
318
  );
317
319
  }
318
320
 
@@ -393,6 +395,7 @@ export class Mnemonic {
393
395
  Buffer.from(quantumChild.privateKey).toString('hex'),
394
396
  this._network,
395
397
  this._securityLevel,
398
+ Buffer.from(this._quantumRoot.chainCode),
396
399
  );
397
400
  }
398
401
 
@@ -889,6 +889,11 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
889
889
  }
890
890
 
891
891
  const tweakedKey = toXOnly(this.tweakedSigner.publicKey);
892
+ const originalKey = this.signer.publicKey;
893
+ if (originalKey.length !== 33) {
894
+ throw new Error('Original public key must be compressed (33 bytes)');
895
+ }
896
+
892
897
  const chainId = getChainId(this.network);
893
898
 
894
899
  const writer = new BinaryWriter();
@@ -897,6 +902,7 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
897
902
  writer.writeU8(MLDSASecurityLevel.LEVEL2);
898
903
  writer.writeBytes(this.hashedPublicKey);
899
904
  writer.writeBytes(tweakedKey);
905
+ writer.writeBytes(originalKey);
900
906
  writer.writeBytes(BITCOIN_PROTOCOL_ID);
901
907
  writer.writeBytes(chainId);
902
908
 
@@ -923,6 +929,11 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
923
929
  }
924
930
 
925
931
  const tweakedKey = toXOnly(this.tweakedSigner.publicKey);
932
+ const originalKey = this.signer.publicKey;
933
+ if (originalKey.length !== 33) {
934
+ throw new Error('Original public key must be compressed (33 bytes)');
935
+ }
936
+
926
937
  const chainId = getChainId(this.network);
927
938
  const level = getLevelFromPublicKeyLength(this.mldsaSigner.publicKey.length);
928
939
 
@@ -935,6 +946,7 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
935
946
  writer.writeBytes(this.hashedPublicKey);
936
947
  writer.writeBytes(this.mldsaSigner.publicKey);
937
948
  writer.writeBytes(tweakedKey);
949
+ writer.writeBytes(originalKey);
938
950
  writer.writeBytes(BITCOIN_PROTOCOL_ID);
939
951
  writer.writeBytes(chainId);
940
952
 
@@ -2,6 +2,11 @@ import bitcoin, { Network, networks, opcodes, script } from '@btc-vision/bitcoin
2
2
  import { IP2WSHAddress } from './IP2WSHAddress.js';
3
3
 
4
4
  export class TimeLockGenerator {
5
+ private static readonly UNSPENDABLE_INTERNAL_KEY = Buffer.from(
6
+ '50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0',
7
+ 'hex',
8
+ );
9
+
5
10
  private static readonly CSV_BLOCKS = 75;
6
11
 
7
12
  /**
@@ -13,13 +18,7 @@ export class TimeLockGenerator {
13
18
  network: Network = networks.bitcoin,
14
19
  csvBlocks: number = TimeLockGenerator.CSV_BLOCKS,
15
20
  ): IP2WSHAddress {
16
- const witnessScript = script.compile([
17
- script.number.encode(csvBlocks),
18
- opcodes.OP_CHECKSEQUENCEVERIFY,
19
- opcodes.OP_DROP,
20
- publicKey,
21
- opcodes.OP_CHECKSIG,
22
- ]);
21
+ const witnessScript = this.generateTimeLockScript(publicKey, csvBlocks);
23
22
 
24
23
  const p2wsh = bitcoin.payments.p2wsh({
25
24
  redeem: { output: witnessScript },
@@ -35,4 +34,45 @@ export class TimeLockGenerator {
35
34
  witnessScript: witnessScript,
36
35
  };
37
36
  }
37
+
38
+ /**
39
+ * Generate a P2TR address with CSV time lock
40
+ * Note: This uses Schnorr signatures
41
+ */
42
+ public static generateTimeLockAddressP2TR(
43
+ publicKey: Buffer,
44
+ network: Network = networks.bitcoin,
45
+ csvBlocks: number = TimeLockGenerator.CSV_BLOCKS,
46
+ ): string {
47
+ if (publicKey.length !== 32) {
48
+ throw new Error('Public key must be 32 bytes for Taproot');
49
+ }
50
+
51
+ const witnessScript = this.generateTimeLockScript(publicKey, csvBlocks);
52
+
53
+ const taproot = bitcoin.payments.p2tr({
54
+ redeem: { output: witnessScript },
55
+ network,
56
+ internalPubkey: TimeLockGenerator.UNSPENDABLE_INTERNAL_KEY,
57
+ });
58
+
59
+ if (!taproot.address) {
60
+ throw new Error('Failed to generate P2TR address');
61
+ }
62
+
63
+ return taproot.address;
64
+ }
65
+
66
+ private static generateTimeLockScript(
67
+ publicKey: Buffer,
68
+ csvBlocks: number = TimeLockGenerator.CSV_BLOCKS,
69
+ ): Buffer {
70
+ return script.compile([
71
+ script.number.encode(csvBlocks),
72
+ opcodes.OP_CHECKSEQUENCEVERIFY,
73
+ opcodes.OP_DROP,
74
+ publicKey,
75
+ opcodes.OP_CHECKSIG,
76
+ ]);
77
+ }
38
78
  }