@btc-vision/transaction 1.5.3 → 1.6.0

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 (106) hide show
  1. package/browser/_version.d.ts +1 -1
  2. package/browser/abi/ABICoder.d.ts +1 -0
  3. package/browser/buffer/BinaryWriter.d.ts +1 -1
  4. package/browser/epoch/ChallengeSolution.d.ts +45 -0
  5. package/browser/epoch/interfaces/IChallengeSolution.d.ts +50 -0
  6. package/browser/epoch/validator/EpochValidator.d.ts +19 -0
  7. package/browser/generators/Features.d.ts +6 -1
  8. package/browser/generators/Generator.d.ts +1 -0
  9. package/browser/generators/builders/CalldataGenerator.d.ts +2 -1
  10. package/browser/generators/builders/DeploymentGenerator.d.ts +3 -1
  11. package/browser/index.js +1 -1
  12. package/browser/keypair/Address.d.ts +2 -0
  13. package/browser/opnet.d.ts +4 -3
  14. package/browser/transaction/TransactionFactory.d.ts +4 -15
  15. package/browser/transaction/browser/Web3Provider.d.ts +3 -3
  16. package/browser/transaction/browser/types/Unisat.d.ts +1 -1
  17. package/browser/transaction/builders/ChallengeSolutionTransaction.d.ts +1 -18
  18. package/browser/transaction/builders/CustomScriptTransaction.d.ts +1 -1
  19. package/browser/transaction/builders/DeploymentTransaction.d.ts +6 -4
  20. package/browser/transaction/builders/SharedInteractionTransaction.d.ts +5 -4
  21. package/browser/transaction/builders/TransactionBuilder.d.ts +2 -0
  22. package/browser/transaction/interfaces/ITransactionParameters.d.ts +4 -6
  23. package/browser/transaction/mineable/TimelockGenerator.d.ts +9 -0
  24. package/browser/utils/StringToBuffer.d.ts +1 -0
  25. package/browser/verification/TapscriptVerificator.d.ts +4 -1
  26. package/build/_version.d.ts +1 -1
  27. package/build/_version.js +1 -1
  28. package/build/abi/ABICoder.d.ts +1 -0
  29. package/build/abi/ABICoder.js +4 -0
  30. package/build/buffer/BinaryWriter.d.ts +1 -1
  31. package/build/buffer/BinaryWriter.js +11 -9
  32. package/build/epoch/ChallengeSolution.d.ts +45 -0
  33. package/build/epoch/ChallengeSolution.js +105 -0
  34. package/build/epoch/interfaces/IChallengeSolution.d.ts +50 -0
  35. package/build/epoch/interfaces/IChallengeSolution.js +1 -0
  36. package/build/epoch/validator/EpochValidator.d.ts +19 -0
  37. package/build/epoch/validator/EpochValidator.js +99 -0
  38. package/build/generators/Features.d.ts +6 -1
  39. package/build/generators/Features.js +1 -0
  40. package/build/generators/Generator.d.ts +1 -0
  41. package/build/generators/Generator.js +17 -1
  42. package/build/generators/builders/CalldataGenerator.d.ts +2 -1
  43. package/build/generators/builders/CalldataGenerator.js +4 -2
  44. package/build/generators/builders/DeploymentGenerator.d.ts +3 -1
  45. package/build/generators/builders/DeploymentGenerator.js +5 -3
  46. package/build/keypair/Address.d.ts +2 -0
  47. package/build/keypair/Address.js +12 -0
  48. package/build/keypair/EcKeyPair.js +10 -7
  49. package/build/opnet.d.ts +4 -3
  50. package/build/opnet.js +4 -3
  51. package/build/transaction/TransactionFactory.d.ts +4 -15
  52. package/build/transaction/TransactionFactory.js +4 -32
  53. package/build/transaction/browser/Web3Provider.d.ts +3 -3
  54. package/build/transaction/browser/types/Unisat.d.ts +1 -1
  55. package/build/transaction/builders/ChallengeSolutionTransaction.d.ts +0 -18
  56. package/build/transaction/builders/ChallengeSolutionTransaction.js +1 -51
  57. package/build/transaction/builders/CustomScriptTransaction.d.ts +1 -1
  58. package/build/transaction/builders/DeploymentTransaction.d.ts +6 -4
  59. package/build/transaction/builders/DeploymentTransaction.js +19 -7
  60. package/build/transaction/builders/InteractionTransaction.js +8 -1
  61. package/build/transaction/builders/SharedInteractionTransaction.d.ts +5 -4
  62. package/build/transaction/builders/SharedInteractionTransaction.js +7 -7
  63. package/build/transaction/builders/TransactionBuilder.d.ts +2 -0
  64. package/build/transaction/builders/TransactionBuilder.js +31 -3
  65. package/build/transaction/interfaces/ITransactionParameters.d.ts +4 -6
  66. package/build/transaction/mineable/TimelockGenerator.d.ts +9 -0
  67. package/build/transaction/mineable/TimelockGenerator.js +24 -0
  68. package/build/utils/StringToBuffer.d.ts +1 -0
  69. package/build/utils/StringToBuffer.js +3 -0
  70. package/build/verification/TapscriptVerificator.d.ts +4 -1
  71. package/build/verification/TapscriptVerificator.js +2 -2
  72. package/package.json +16 -16
  73. package/src/_version.ts +1 -1
  74. package/src/abi/ABICoder.ts +4 -0
  75. package/src/buffer/BinaryWriter.ts +13 -11
  76. package/src/epoch/ChallengeSolution.ts +196 -0
  77. package/src/epoch/interfaces/IChallengeSolution.ts +56 -0
  78. package/src/epoch/validator/EpochValidator.ts +180 -0
  79. package/src/generators/Features.ts +6 -0
  80. package/src/generators/Generator.ts +24 -2
  81. package/src/generators/builders/CalldataGenerator.ts +7 -3
  82. package/src/generators/builders/DeploymentGenerator.ts +18 -6
  83. package/src/keypair/Address.ts +34 -0
  84. package/src/keypair/EcKeyPair.ts +13 -8
  85. package/src/opnet.ts +6 -3
  86. package/src/transaction/TransactionFactory.ts +7 -62
  87. package/src/transaction/browser/Web3Provider.ts +3 -3
  88. package/src/transaction/browser/types/Unisat.ts +1 -1
  89. package/src/transaction/builders/ChallengeSolutionTransaction.ts +3 -4
  90. package/src/transaction/builders/CustomScriptTransaction.ts +2 -1
  91. package/src/transaction/builders/DeploymentTransaction.ts +27 -9
  92. package/src/transaction/builders/InteractionTransaction.ts +10 -2
  93. package/src/transaction/builders/SharedInteractionTransaction.ts +12 -28
  94. package/src/transaction/builders/TransactionBuilder.ts +40 -2
  95. package/src/transaction/interfaces/ITransactionParameters.ts +5 -8
  96. package/src/transaction/mineable/TimelockGenerator.ts +42 -0
  97. package/src/utils/StringToBuffer.ts +3 -0
  98. package/src/verification/TapscriptVerificator.ts +8 -4
  99. package/browser/generators/builders/MineableReward.d.ts +0 -7
  100. package/browser/transaction/mineable/ChallengeGenerator.d.ts +0 -9
  101. package/build/generators/builders/MineableReward.d.ts +0 -7
  102. package/build/generators/builders/MineableReward.js +0 -48
  103. package/build/transaction/mineable/ChallengeGenerator.d.ts +0 -9
  104. package/build/transaction/mineable/ChallengeGenerator.js +0 -28
  105. package/src/generators/builders/MineableReward.ts +0 -66
  106. package/src/transaction/mineable/ChallengeGenerator.ts +0 -39
@@ -0,0 +1,196 @@
1
+ import { stringToBuffer } from '../utils/StringToBuffer.js';
2
+ import {
3
+ IChallengeSolution,
4
+ IChallengeSubmission,
5
+ IChallengeVerification,
6
+ RawChallenge,
7
+ RawChallengeSubmission,
8
+ RawChallengeVerification,
9
+ } from './interfaces/IChallengeSolution.js';
10
+ import { Address } from '../keypair/Address.js';
11
+ import { EpochValidator } from './validator/EpochValidator.js';
12
+ import { BinaryWriter } from '../buffer/BinaryWriter.js';
13
+ import { MessageSigner } from '../keypair/MessageSigner.js';
14
+
15
+ export class ChallengeVerification implements IChallengeVerification {
16
+ public readonly epochHash: Buffer;
17
+ public readonly epochRoot: Buffer;
18
+ public readonly targetHash: Buffer;
19
+ public readonly targetChecksum: Buffer;
20
+ public readonly startBlock: bigint;
21
+ public readonly endBlock: bigint;
22
+ public readonly proofs: readonly Buffer[];
23
+
24
+ constructor(data: RawChallengeVerification) {
25
+ this.epochHash = stringToBuffer(data.epochHash);
26
+ this.epochRoot = stringToBuffer(data.epochRoot);
27
+ this.targetHash = stringToBuffer(data.targetHash);
28
+ this.targetChecksum = stringToBuffer(data.targetChecksum);
29
+ this.startBlock = BigInt(data.startBlock);
30
+ this.endBlock = BigInt(data.endBlock);
31
+ this.proofs = Object.freeze(data.proofs.map((proof) => stringToBuffer(proof)));
32
+ }
33
+ }
34
+
35
+ export class ChallengeSubmission implements IChallengeSubmission {
36
+ public readonly publicKey: Address;
37
+ public readonly solution: Buffer;
38
+ public readonly graffiti: Buffer | undefined;
39
+ public readonly signature: Buffer;
40
+
41
+ constructor(
42
+ data: RawChallengeSubmission,
43
+ public readonly epochNumber: bigint,
44
+ ) {
45
+ this.publicKey = Address.fromString(data.publicKey);
46
+ this.solution = stringToBuffer(data.solution);
47
+ this.graffiti = data.graffiti ? stringToBuffer(data.graffiti) : undefined;
48
+ this.signature = stringToBuffer(data.signature);
49
+ }
50
+
51
+ public verifySignature(): boolean {
52
+ const signatureDataWriter = new BinaryWriter();
53
+ signatureDataWriter.writeAddress(this.publicKey);
54
+ signatureDataWriter.writeU64(this.epochNumber);
55
+ signatureDataWriter.writeBytes(this.solution);
56
+
57
+ if (this.graffiti) {
58
+ signatureDataWriter.writeBytes(this.graffiti);
59
+ }
60
+
61
+ const buffer = signatureDataWriter.getBuffer();
62
+ return MessageSigner.verifySignature(this.publicKey, buffer, this.signature);
63
+ }
64
+ }
65
+
66
+ export class ChallengeSolution implements IChallengeSolution {
67
+ public readonly epochNumber: bigint;
68
+ public readonly publicKey: Address;
69
+ public readonly solution: Buffer;
70
+ public readonly salt: Buffer;
71
+ public readonly graffiti: Buffer;
72
+ public readonly difficulty: number;
73
+ public readonly verification: ChallengeVerification;
74
+
75
+ private readonly submission?: ChallengeSubmission;
76
+
77
+ constructor(data: RawChallenge) {
78
+ this.epochNumber = BigInt(data.epochNumber);
79
+ this.publicKey = Address.fromString(data.publicKey);
80
+ this.solution = stringToBuffer(data.solution);
81
+ this.salt = stringToBuffer(data.salt);
82
+ this.graffiti = stringToBuffer(data.graffiti);
83
+ this.difficulty = data.difficulty;
84
+ this.verification = new ChallengeVerification(data.verification);
85
+ this.submission = data.submission
86
+ ? new ChallengeSubmission(data.submission, this.epochNumber + 2n)
87
+ : data.submission;
88
+ }
89
+
90
+ /**
91
+ * Static method to validate from raw data directly
92
+ */
93
+ public static async validateRaw(data: RawChallenge): Promise<boolean> {
94
+ return EpochValidator.validateEpochWinner(data);
95
+ }
96
+
97
+ public verifySubmissionSignature(): boolean {
98
+ if (!this.submission) {
99
+ throw new Error('No submission provided in request.');
100
+ }
101
+
102
+ return this.submission.verifySignature();
103
+ }
104
+
105
+ public getSubmission(): ChallengeSubmission | undefined {
106
+ if (!this.submission) {
107
+ return;
108
+ }
109
+
110
+ if (!this.verifySubmissionSignature()) {
111
+ throw new Error('Invalid submission signature.');
112
+ }
113
+
114
+ return this.submission;
115
+ }
116
+
117
+ /**
118
+ * Verify this preimage
119
+ * @returns {Promise<boolean>} True if the preimage is valid
120
+ */
121
+ public async verify(): Promise<boolean> {
122
+ return EpochValidator.validatePreimage(this);
123
+ }
124
+
125
+ /**
126
+ * Get the preimage buffer (alias for solution)
127
+ * @returns {Buffer} The solution/preimage as a buffer
128
+ */
129
+ public toBuffer(): Buffer {
130
+ return this.solution;
131
+ }
132
+
133
+ /**
134
+ * Get the solution as a hex string
135
+ * @returns {string} The solution as a hex string with 0x prefix
136
+ */
137
+ public toHex(): string {
138
+ return '0x' + this.solution.toString('hex');
139
+ }
140
+
141
+ /**
142
+ * Convert to raw format for serialization
143
+ */
144
+ public toRaw(): RawChallenge {
145
+ return {
146
+ epochNumber: this.epochNumber.toString(),
147
+ publicKey: this.publicKey.toHex(),
148
+ solution: this.toHex(),
149
+ salt: '0x' + this.salt.toString('hex'),
150
+ graffiti: '0x' + this.graffiti.toString('hex'),
151
+ difficulty: this.difficulty,
152
+ verification: {
153
+ epochHash: '0x' + this.verification.epochHash.toString('hex'),
154
+ epochRoot: '0x' + this.verification.epochRoot.toString('hex'),
155
+ targetHash: '0x' + this.verification.targetHash.toString('hex'),
156
+ targetChecksum: '0x' + this.verification.targetChecksum.toString('hex'),
157
+ startBlock: this.verification.startBlock.toString(),
158
+ endBlock: this.verification.endBlock.toString(),
159
+ proofs: this.verification.proofs.map((p) => '0x' + p.toString('hex')),
160
+ },
161
+ };
162
+ }
163
+
164
+ /**
165
+ * Calculate the expected solution hash for this preimage
166
+ * @returns {Promise<Buffer>} The calculated solution hash
167
+ */
168
+ public async calculateSolution(): Promise<Buffer> {
169
+ return EpochValidator.calculateSolution(
170
+ this.verification.targetChecksum,
171
+ this.publicKey.toBuffer(),
172
+ this.salt,
173
+ );
174
+ }
175
+
176
+ /**
177
+ * Check if the preimage meets a specific difficulty requirement
178
+ * @param {number} minDifficulty The minimum difficulty required
179
+ * @returns {Promise<{valid: boolean; difficulty: number}>} Validation result
180
+ */
181
+ public checkDifficulty(minDifficulty: number): { valid: boolean; difficulty: number } {
182
+ return EpochValidator.checkDifficulty(
183
+ this.solution,
184
+ this.verification.targetHash,
185
+ minDifficulty,
186
+ );
187
+ }
188
+
189
+ /**
190
+ * Get the mining target block for this epoch
191
+ * @returns {bigint | null} The target block number or null if epoch 0
192
+ */
193
+ public getMiningTargetBlock(): bigint | null {
194
+ return EpochValidator.getMiningTargetBlock(this.epochNumber);
195
+ }
196
+ }
@@ -0,0 +1,56 @@
1
+ import { Address } from '../../keypair/Address.js';
2
+
3
+ export interface IChallengeVerification {
4
+ readonly epochHash: Buffer;
5
+ readonly epochRoot: Buffer;
6
+ readonly targetHash: Buffer;
7
+ readonly targetChecksum: Buffer;
8
+ readonly startBlock: bigint;
9
+ readonly endBlock: bigint;
10
+ readonly proofs: readonly Buffer[];
11
+ }
12
+
13
+ export interface IChallengeSolution {
14
+ readonly epochNumber: bigint;
15
+ readonly publicKey: Address;
16
+ readonly solution: Buffer;
17
+ readonly salt: Buffer;
18
+ readonly graffiti: Buffer;
19
+ readonly difficulty: number;
20
+ readonly verification: IChallengeVerification;
21
+ }
22
+
23
+ export interface RawChallengeVerification {
24
+ readonly epochHash: string;
25
+ readonly epochRoot: string;
26
+ readonly targetHash: string;
27
+ readonly targetChecksum: string;
28
+ readonly startBlock: string;
29
+ readonly endBlock: string;
30
+ readonly proofs: readonly string[];
31
+ }
32
+
33
+ export interface RawChallengeSubmission {
34
+ readonly publicKey: string;
35
+ readonly solution: string;
36
+ readonly graffiti?: string;
37
+ readonly signature: string;
38
+ }
39
+
40
+ export interface IChallengeSubmission {
41
+ readonly publicKey: Address;
42
+ readonly solution: Buffer;
43
+ readonly graffiti?: Buffer;
44
+ readonly signature: Buffer;
45
+ }
46
+
47
+ export interface RawChallenge {
48
+ readonly epochNumber: string;
49
+ readonly publicKey: string;
50
+ readonly solution: string;
51
+ readonly salt: string;
52
+ readonly graffiti: string;
53
+ readonly difficulty: number;
54
+ readonly verification: RawChallengeVerification;
55
+ readonly submission?: RawChallengeSubmission;
56
+ }
@@ -0,0 +1,180 @@
1
+ import { IChallengeSolution, RawChallenge } from '../interfaces/IChallengeSolution.js';
2
+ import { ChallengeSolution } from '../ChallengeSolution.js';
3
+
4
+ export class EpochValidator {
5
+ private static readonly BLOCKS_PER_EPOCH: bigint = 5n;
6
+ private static readonly GRAFFITI_LENGTH: number = 16;
7
+
8
+ /**
9
+ * Convert Buffer to Uint8Array
10
+ */
11
+ public static bufferToUint8Array(buffer: Buffer): Uint8Array {
12
+ return new Uint8Array(buffer);
13
+ }
14
+
15
+ /**
16
+ * Convert Uint8Array to Buffer
17
+ */
18
+ public static uint8ArrayToBuffer(array: Uint8Array): Buffer {
19
+ return Buffer.from(array);
20
+ }
21
+
22
+ /**
23
+ * Calculate SHA-1 hash
24
+ */
25
+ public static async sha1(data: Uint8Array): Promise<Uint8Array> {
26
+ const hashBuffer = await crypto.subtle.digest('SHA-1', data);
27
+ return new Uint8Array(hashBuffer);
28
+ }
29
+
30
+ /**
31
+ * Calculate mining preimage
32
+ */
33
+ public static calculatePreimage(checksumRoot: Buffer, publicKey: Buffer, salt: Buffer): Buffer {
34
+ // Ensure all are 32 bytes
35
+ if (checksumRoot.length !== 32 || publicKey.length !== 32 || salt.length !== 32) {
36
+ throw new Error('All inputs must be 32 bytes');
37
+ }
38
+
39
+ const preimage = Buffer.alloc(32);
40
+ for (let i = 0; i < 32; i++) {
41
+ preimage[i] = checksumRoot[i] ^ publicKey[i] ^ salt[i];
42
+ }
43
+
44
+ return preimage;
45
+ }
46
+
47
+ /**
48
+ * Count matching bits between two hashes
49
+ */
50
+ public static countMatchingBits(hash1: Buffer, hash2: Buffer): number {
51
+ let matchingBits = 0;
52
+ if (hash1.length !== hash2.length) {
53
+ throw new Error('Hashes must be of the same length');
54
+ }
55
+
56
+ const minLength = Math.min(hash1.length, hash2.length);
57
+ for (let i = 0; i < minLength; i++) {
58
+ const byte1 = hash1[i];
59
+ const byte2 = hash2[i];
60
+
61
+ if (byte1 === byte2) {
62
+ matchingBits += 8;
63
+ } else {
64
+ // Check individual bits
65
+ for (let bit = 7; bit >= 0; bit--) {
66
+ if (((byte1 >> bit) & 1) === ((byte2 >> bit) & 1)) {
67
+ matchingBits++;
68
+ } else {
69
+ return matchingBits;
70
+ }
71
+ }
72
+ }
73
+ }
74
+
75
+ return matchingBits;
76
+ }
77
+
78
+ /**
79
+ * Verify an epoch solution using IPreimage
80
+ */
81
+ public static async verifySolution(
82
+ preimage: IChallengeSolution,
83
+ log: boolean = false,
84
+ ): Promise<boolean> {
85
+ try {
86
+ const verification = preimage.verification;
87
+ const calculatedPreimage = this.calculatePreimage(
88
+ verification.targetChecksum,
89
+ preimage.publicKey.toBuffer(),
90
+ preimage.salt,
91
+ );
92
+
93
+ const computedSolution = await this.sha1(this.bufferToUint8Array(calculatedPreimage));
94
+ const computedSolutionBuffer = this.uint8ArrayToBuffer(computedSolution);
95
+
96
+ if (!computedSolutionBuffer.equals(preimage.solution)) {
97
+ return false;
98
+ }
99
+
100
+ const matchingBits = this.countMatchingBits(
101
+ computedSolutionBuffer,
102
+ verification.targetHash,
103
+ );
104
+
105
+ if (matchingBits !== preimage.difficulty) {
106
+ return false;
107
+ }
108
+
109
+ const expectedStartBlock = preimage.epochNumber * this.BLOCKS_PER_EPOCH;
110
+ const expectedEndBlock = expectedStartBlock + this.BLOCKS_PER_EPOCH - 1n;
111
+
112
+ return !(
113
+ verification.startBlock !== expectedStartBlock ||
114
+ verification.endBlock !== expectedEndBlock
115
+ );
116
+ } catch (error) {
117
+ if (log) console.error('Verification error:', error);
118
+ return false;
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Get the mining target block for an epoch
124
+ */
125
+ public static getMiningTargetBlock(epochNumber: bigint): bigint | null {
126
+ if (epochNumber === 0n) {
127
+ return null; // Epoch 0 cannot be mined
128
+ }
129
+
130
+ // Last block of previous epoch
131
+ return epochNumber * this.BLOCKS_PER_EPOCH - 1n;
132
+ }
133
+
134
+ /**
135
+ * Validate epoch winner from raw data
136
+ */
137
+ public static async validateEpochWinner(epochData: RawChallenge): Promise<boolean> {
138
+ const preimage = new ChallengeSolution(epochData);
139
+ return await this.verifySolution(preimage);
140
+ }
141
+
142
+ /**
143
+ * Validate epoch winner from Preimage instance
144
+ */
145
+ public static async validatePreimage(preimage: IChallengeSolution): Promise<boolean> {
146
+ return await this.verifySolution(preimage);
147
+ }
148
+
149
+ /**
150
+ * Calculate solution hash from preimage components
151
+ * @param targetChecksum The target checksum (32 bytes)
152
+ * @param publicKey The public key buffer (32 bytes)
153
+ * @param salt The salt buffer (32 bytes)
154
+ * @returns The SHA-1 hash of the preimage
155
+ */
156
+ public static async calculateSolution(
157
+ targetChecksum: Buffer,
158
+ publicKey: Buffer,
159
+ salt: Buffer,
160
+ ): Promise<Buffer> {
161
+ const preimage = this.calculatePreimage(targetChecksum, publicKey, salt);
162
+ const hash = await this.sha1(this.bufferToUint8Array(preimage));
163
+ return this.uint8ArrayToBuffer(hash);
164
+ }
165
+
166
+ /**
167
+ * Check if a solution meets the minimum difficulty requirement
168
+ */
169
+ public static checkDifficulty(
170
+ solution: Buffer,
171
+ targetHash: Buffer,
172
+ minDifficulty: number,
173
+ ): { valid: boolean; difficulty: number } {
174
+ const difficulty = this.countMatchingBits(solution, targetHash);
175
+ return {
176
+ valid: difficulty >= minDifficulty,
177
+ difficulty,
178
+ };
179
+ }
180
+ }
@@ -1,7 +1,9 @@
1
1
  import { LoadedStorage } from '../transaction/interfaces/ITransactionParameters.js';
2
+ import { ChallengeSubmission } from '../epoch/ChallengeSolution.js';
2
3
 
3
4
  export enum Features {
4
5
  ACCESS_LIST = 1,
6
+ EPOCH_SUBMISSION = 2,
5
7
  }
6
8
 
7
9
  export interface Feature<T extends Features> {
@@ -12,3 +14,7 @@ export interface Feature<T extends Features> {
12
14
  export interface AccessListFeature extends Feature<Features.ACCESS_LIST> {
13
15
  data: LoadedStorage;
14
16
  }
17
+
18
+ export interface EpochSubmissionFeature extends Feature<Features.EPOCH_SUBMISSION> {
19
+ data: ChallengeSubmission;
20
+ }
@@ -1,6 +1,6 @@
1
1
  import { Network, networks, toXOnly } from '@btc-vision/bitcoin';
2
2
  import { BinaryWriter } from '../buffer/BinaryWriter.js';
3
- import { AccessListFeature, Feature, Features } from './Features.js';
3
+ import { AccessListFeature, EpochSubmissionFeature, Feature, Features } from './Features.js';
4
4
  import { Address } from '../keypair/Address.js';
5
5
  import { Compressor } from '../bytecode/Compressor.js';
6
6
 
@@ -107,10 +107,16 @@ export abstract class Generator {
107
107
 
108
108
  protected encodeFeature(feature: Feature<Features>): Buffer[][] {
109
109
  switch (feature.opcode) {
110
- case Features.ACCESS_LIST:
110
+ case Features.ACCESS_LIST: {
111
111
  return this.splitBufferIntoChunks(
112
112
  this.encodeAccessListFeature(feature as AccessListFeature),
113
113
  );
114
+ }
115
+ case Features.EPOCH_SUBMISSION: {
116
+ return this.splitBufferIntoChunks(
117
+ this.encodeChallengeSubmission(feature as EpochSubmissionFeature),
118
+ );
119
+ }
114
120
  default:
115
121
  throw new Error(`Unknown feature type: ${feature.opcode}`);
116
122
  }
@@ -141,4 +147,20 @@ export abstract class Generator {
141
147
 
142
148
  return Compressor.compress(Buffer.from(writer.getBuffer()));
143
149
  }
150
+
151
+ private encodeChallengeSubmission(feature: EpochSubmissionFeature): Buffer {
152
+ if ('verifySignature' in feature.data && !feature.data.verifySignature()) {
153
+ throw new Error('Invalid signature in challenge submission feature');
154
+ }
155
+
156
+ const writer = new BinaryWriter();
157
+ writer.writeBytes(feature.data.publicKey.originalPublicKeyBuffer());
158
+ writer.writeBytes(feature.data.solution);
159
+
160
+ if (feature.data.graffiti) {
161
+ writer.writeBytesWithLength(feature.data.graffiti);
162
+ }
163
+
164
+ return Buffer.from(writer.getBuffer());
165
+ }
144
166
  }
@@ -4,6 +4,7 @@ import { Compressor } from '../../bytecode/Compressor.js';
4
4
  import { EcKeyPair } from '../../keypair/EcKeyPair.js';
5
5
  import { Feature, Features } from '../Features.js';
6
6
  import { Generator } from '../Generator.js';
7
+ import { ChallengeSolution } from '../../epoch/ChallengeSolution.js';
7
8
 
8
9
  /**
9
10
  * Class to generate bitcoin script for interaction transactions
@@ -56,7 +57,7 @@ export class CalldataGenerator extends Generator {
56
57
  * Compile an interaction bitcoin script
57
58
  * @param {Buffer} calldata - The calldata to use
58
59
  * @param {Buffer} contractSecret - The contract secret
59
- * @param preimage
60
+ * @param {ChallengeSolution} challenge
60
61
  * @param maxPriority - Amount of satoshis to spend max on priority fee
61
62
  * @param {Feature<Features>[]} features - The features to use
62
63
  * @returns {Buffer} - The compiled script
@@ -65,7 +66,7 @@ export class CalldataGenerator extends Generator {
65
66
  public compile(
66
67
  calldata: Buffer,
67
68
  contractSecret: Buffer,
68
- preimage: Buffer,
69
+ challenge: ChallengeSolution,
69
70
  maxPriority: bigint,
70
71
  features: Feature<Features>[] = [],
71
72
  ): Buffer {
@@ -89,7 +90,10 @@ export class CalldataGenerator extends Generator {
89
90
  opcodes.OP_TOALTSTACK,
90
91
 
91
92
  // CHALLENGE PREIMAGE FOR REWARD,
92
- preimage,
93
+ challenge.publicKey.originalPublicKeyBuffer(),
94
+ opcodes.OP_TOALTSTACK,
95
+
96
+ challenge.solution,
93
97
  opcodes.OP_TOALTSTACK,
94
98
 
95
99
  this.xSenderPubKey,
@@ -1,6 +1,7 @@
1
1
  import { crypto, Network, networks, opcodes, script } from '@btc-vision/bitcoin';
2
2
  import { Generator } from '../Generator.js';
3
3
  import { Feature, Features } from '../Features.js';
4
+ import { ChallengeSolution } from '../../epoch/ChallengeSolution.js';
4
5
 
5
6
  export const OPNET_DEPLOYMENT_VERSION = 0x00;
6
7
  export const versionBuffer = Buffer.from([OPNET_DEPLOYMENT_VERSION]);
@@ -18,19 +19,28 @@ export class DeploymentGenerator extends Generator {
18
19
  * Compile a bitcoin script representing a contract deployment
19
20
  * @param {Buffer} contractBytecode - The contract bytecode
20
21
  * @param {Buffer} contractSalt - The contract salt
21
- * @param {Buffer} preimage - The preimage for reward
22
+ * @param {ChallengeSolution} preimage - The preimage for reward
22
23
  * @param {bigint} maxPriority - The maximum priority for the contract
23
24
  * @param {Buffer} [calldata] - The calldata to be passed to the contract
25
+ * @param {Feature<Features>[]} [features] - Optional features to include in the script
24
26
  * @returns {Buffer} - The compiled script
25
27
  */
26
28
  public compile(
27
29
  contractBytecode: Buffer,
28
30
  contractSalt: Buffer,
29
- preimage: Buffer,
31
+ preimage: ChallengeSolution,
30
32
  maxPriority: bigint,
31
33
  calldata?: Buffer,
34
+ features?: Feature<Features>[],
32
35
  ): Buffer {
33
- const asm = this.getAsm(contractBytecode, contractSalt, preimage, maxPriority, calldata);
36
+ const asm = this.getAsm(
37
+ contractBytecode,
38
+ contractSalt,
39
+ preimage,
40
+ maxPriority,
41
+ calldata,
42
+ features,
43
+ );
34
44
  const compiled = script.compile(asm);
35
45
 
36
46
  /**
@@ -47,7 +57,7 @@ export class DeploymentGenerator extends Generator {
47
57
  private getAsm(
48
58
  contractBytecode: Buffer,
49
59
  contractSalt: Buffer,
50
- preimage: Buffer,
60
+ preimage: ChallengeSolution,
51
61
  maxPriority: bigint,
52
62
  calldata?: Buffer,
53
63
  features?: Feature<Features>[],
@@ -55,7 +65,6 @@ export class DeploymentGenerator extends Generator {
55
65
  if (!this.contractSaltPubKey) throw new Error('Contract salt public key not set');
56
66
 
57
67
  const dataChunks: Buffer[][] = this.splitBufferIntoChunks(contractBytecode);
58
-
59
68
  const calldataChunks: Buffer[][] = calldata ? this.splitBufferIntoChunks(calldata) : [];
60
69
 
61
70
  const featuresList: Features[] = [];
@@ -76,7 +85,10 @@ export class DeploymentGenerator extends Generator {
76
85
  opcodes.OP_TOALTSTACK,
77
86
 
78
87
  // CHALLENGE PREIMAGE FOR REWARD,
79
- preimage,
88
+ preimage.publicKey.originalPublicKeyBuffer(),
89
+ opcodes.OP_TOALTSTACK,
90
+
91
+ preimage.solution,
80
92
  opcodes.OP_TOALTSTACK,
81
93
 
82
94
  this.xSenderPubKey,
@@ -5,6 +5,7 @@ import { AddressVerificator } from './AddressVerificator.js';
5
5
  import { EcKeyPair } from './EcKeyPair.js';
6
6
  import { ContractAddress } from '../transaction/ContractAddress.js';
7
7
  import { BitcoinUtils } from '../utils/BitcoinUtils.js';
8
+ import { ITimeLockOutput, TimeLockGenerator } from '../transaction/mineable/TimelockGenerator.js';
8
9
 
9
10
  /**
10
11
  * Objects of type "Address" are the representation of tweaked public keys. They can be converted to different address formats.
@@ -317,6 +318,39 @@ export class Address extends Uint8Array {
317
318
  throw new Error('Public key not set');
318
319
  }
319
320
 
321
+ /**
322
+ * Generate a P2WSH address with CSV (CheckSequenceVerify) timelock
323
+ * The resulting address can only be spent after the specified number of blocks
324
+ * have passed since the UTXO was created.
325
+ *
326
+ * @param {bigint | number | string} duration - The number of blocks that must pass before spending (1-65535)
327
+ * @param {Network} network - The Bitcoin network to use
328
+ * @returns {ITimeLockOutput} The timelocked address and its witness script
329
+ * @throws {Error} If the block number is out of range or public key is not available
330
+ */
331
+ public toCSV(duration: bigint | number | string, network: Network): ITimeLockOutput {
332
+ const n = Number(duration);
333
+
334
+ // First, let's validate the block number to ensure it's within the valid range
335
+ // CSV uses sequence numbers, which have special encoding for block-based locks
336
+ if (n < 1 || n > 65535) {
337
+ throw new Error('CSV block number must be between 1 and 65535');
338
+ }
339
+
340
+ // We need the original public key in compressed format for the script
341
+ // Your class stores this in #originalPublicKey when a key is set
342
+ if (!this.#originalPublicKey) {
343
+ throw new Error('Cannot create CSV address: public key not set');
344
+ }
345
+
346
+ // Convert the public key to Buffer format that TimeLockGenerator expects
347
+ const publicKeyBuffer = Buffer.from(this.#originalPublicKey);
348
+
349
+ // Now we can use your TimeLockGenerator to create the timelocked address
350
+ // Converting bigint to number is safe here because we've already validated the range
351
+ return TimeLockGenerator.generateTimeLockAddress(publicKeyBuffer, network, n);
352
+ }
353
+
320
354
  /**
321
355
  * Get an opnet address encoded in bech32m format.
322
356
  * @param network