@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.
Files changed (42) hide show
  1. package/browser/_version.d.ts +1 -1
  2. package/browser/generators/Features.d.ts +6 -0
  3. package/browser/generators/builders/CalldataGenerator.d.ts +1 -1
  4. package/browser/generators/builders/LegacyCalldataGenerator.d.ts +1 -1
  5. package/browser/generators/builders/P2WDAGenerator.d.ts +1 -1
  6. package/browser/index.js +1 -1
  7. package/browser/keypair/Address.d.ts +1 -0
  8. package/browser/transaction/mineable/TimelockGenerator.d.ts +3 -0
  9. package/build/_version.d.ts +1 -1
  10. package/build/_version.js +1 -1
  11. package/build/epoch/ChallengeSolution.js +1 -1
  12. package/build/generators/Features.d.ts +6 -0
  13. package/build/generators/Features.js +6 -0
  14. package/build/generators/builders/CalldataGenerator.d.ts +1 -1
  15. package/build/generators/builders/CalldataGenerator.js +9 -6
  16. package/build/generators/builders/DeploymentGenerator.js +3 -2
  17. package/build/generators/builders/LegacyCalldataGenerator.d.ts +1 -1
  18. package/build/generators/builders/LegacyCalldataGenerator.js +9 -6
  19. package/build/generators/builders/P2WDAGenerator.d.ts +1 -1
  20. package/build/generators/builders/P2WDAGenerator.js +2 -1
  21. package/build/keypair/Address.d.ts +1 -0
  22. package/build/keypair/Address.js +10 -0
  23. package/build/transaction/builders/DeploymentTransaction.js +2 -1
  24. package/build/transaction/builders/InteractionTransaction.js +3 -1
  25. package/build/transaction/builders/InteractionTransactionP2WDA.js +3 -1
  26. package/build/transaction/builders/TransactionBuilder.js +12 -1
  27. package/build/transaction/mineable/TimelockGenerator.d.ts +3 -0
  28. package/build/transaction/mineable/TimelockGenerator.js +26 -7
  29. package/package.json +1 -1
  30. package/src/_version.ts +1 -1
  31. package/src/epoch/ChallengeSolution.ts +5 -1
  32. package/src/generators/Features.ts +7 -0
  33. package/src/generators/builders/CalldataGenerator.ts +11 -7
  34. package/src/generators/builders/DeploymentGenerator.ts +6 -2
  35. package/src/generators/builders/LegacyCalldataGenerator.ts +11 -7
  36. package/src/generators/builders/P2WDAGenerator.ts +4 -2
  37. package/src/keypair/Address.ts +35 -1
  38. package/src/transaction/builders/DeploymentTransaction.ts +2 -1
  39. package/src/transaction/builders/InteractionTransaction.ts +3 -1
  40. package/src/transaction/builders/InteractionTransactionP2WDA.ts +3 -1
  41. package/src/transaction/builders/TransactionBuilder.ts +14 -1
  42. 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
  }
@@ -1 +1 @@
1
- export declare const version = "1.7.13";
1
+ export declare const version = "1.7.15";
package/build/_version.js CHANGED
@@ -1 +1 @@
1
- export const version = '1.7.13';
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, features?: Feature<Features>[]): Buffer;
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, features = []) {
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
- 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);
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, features) {
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 (features) {
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, features?: Feature<Features>[]): Buffer;
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, features = []) {
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
- for (let i = 0; i < features.length; i++) {
34
- const feature = features[i];
35
- featuresList.push(feature.opcode);
36
- const data = this.encodeFeature(feature);
37
- featureData.push(...data);
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, features?: Feature<Features>[]): Buffer;
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, features = []) {
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;
@@ -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 = 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.13",
4
+ "version": "1.7.15",
5
5
  "author": "BlobMaster41",
6
6
  "description": "OPNet transaction library allows you to create and sign transactions for the OPNet network.",
7
7
  "engines": {
package/src/_version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '1.7.13';
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(this.publicKey, buffer, this.signature);
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>[]} features - The features to use
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
- features: Feature<Features>[] = [],
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
- const data = this.encodeFeature(feature);
85
- featureData.push(...data);
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
- features?: Feature<Features>[],
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 (features) {
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[]} [features=[]] - The features to use (optional)
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
- features: Feature<Features>[] = [],
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
- for (let i = 0; i < features.length; i++) {
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
- const data = this.encodeFeature(feature);
79
- featureData.push(...data);
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 features Optional features like access lists
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
- features: Feature<Features>[] = [],
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(
@@ -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).
@@ -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
  });