@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.
- package/browser/_version.d.ts +1 -1
- package/browser/abi/ABICoder.d.ts +1 -0
- package/browser/buffer/BinaryWriter.d.ts +1 -1
- package/browser/epoch/ChallengeSolution.d.ts +45 -0
- package/browser/epoch/interfaces/IChallengeSolution.d.ts +50 -0
- package/browser/epoch/validator/EpochValidator.d.ts +19 -0
- package/browser/generators/Features.d.ts +6 -1
- package/browser/generators/Generator.d.ts +1 -0
- package/browser/generators/builders/CalldataGenerator.d.ts +2 -1
- package/browser/generators/builders/DeploymentGenerator.d.ts +3 -1
- package/browser/index.js +1 -1
- package/browser/keypair/Address.d.ts +2 -0
- package/browser/opnet.d.ts +4 -3
- package/browser/transaction/TransactionFactory.d.ts +4 -15
- package/browser/transaction/browser/Web3Provider.d.ts +3 -3
- package/browser/transaction/browser/types/Unisat.d.ts +1 -1
- package/browser/transaction/builders/ChallengeSolutionTransaction.d.ts +1 -18
- package/browser/transaction/builders/CustomScriptTransaction.d.ts +1 -1
- package/browser/transaction/builders/DeploymentTransaction.d.ts +6 -4
- package/browser/transaction/builders/SharedInteractionTransaction.d.ts +5 -4
- package/browser/transaction/builders/TransactionBuilder.d.ts +2 -0
- package/browser/transaction/interfaces/ITransactionParameters.d.ts +4 -6
- package/browser/transaction/mineable/TimelockGenerator.d.ts +9 -0
- package/browser/utils/StringToBuffer.d.ts +1 -0
- package/browser/verification/TapscriptVerificator.d.ts +4 -1
- package/build/_version.d.ts +1 -1
- package/build/_version.js +1 -1
- package/build/abi/ABICoder.d.ts +1 -0
- package/build/abi/ABICoder.js +4 -0
- package/build/buffer/BinaryWriter.d.ts +1 -1
- package/build/buffer/BinaryWriter.js +11 -9
- package/build/epoch/ChallengeSolution.d.ts +45 -0
- package/build/epoch/ChallengeSolution.js +105 -0
- package/build/epoch/interfaces/IChallengeSolution.d.ts +50 -0
- package/build/epoch/interfaces/IChallengeSolution.js +1 -0
- package/build/epoch/validator/EpochValidator.d.ts +19 -0
- package/build/epoch/validator/EpochValidator.js +99 -0
- package/build/generators/Features.d.ts +6 -1
- package/build/generators/Features.js +1 -0
- package/build/generators/Generator.d.ts +1 -0
- package/build/generators/Generator.js +17 -1
- package/build/generators/builders/CalldataGenerator.d.ts +2 -1
- package/build/generators/builders/CalldataGenerator.js +4 -2
- package/build/generators/builders/DeploymentGenerator.d.ts +3 -1
- package/build/generators/builders/DeploymentGenerator.js +5 -3
- package/build/keypair/Address.d.ts +2 -0
- package/build/keypair/Address.js +12 -0
- package/build/keypair/EcKeyPair.js +10 -7
- package/build/opnet.d.ts +4 -3
- package/build/opnet.js +4 -3
- package/build/transaction/TransactionFactory.d.ts +4 -15
- package/build/transaction/TransactionFactory.js +4 -32
- package/build/transaction/browser/Web3Provider.d.ts +3 -3
- package/build/transaction/browser/types/Unisat.d.ts +1 -1
- package/build/transaction/builders/ChallengeSolutionTransaction.d.ts +0 -18
- package/build/transaction/builders/ChallengeSolutionTransaction.js +1 -51
- package/build/transaction/builders/CustomScriptTransaction.d.ts +1 -1
- package/build/transaction/builders/DeploymentTransaction.d.ts +6 -4
- package/build/transaction/builders/DeploymentTransaction.js +19 -7
- package/build/transaction/builders/InteractionTransaction.js +8 -1
- package/build/transaction/builders/SharedInteractionTransaction.d.ts +5 -4
- package/build/transaction/builders/SharedInteractionTransaction.js +7 -7
- package/build/transaction/builders/TransactionBuilder.d.ts +2 -0
- package/build/transaction/builders/TransactionBuilder.js +31 -3
- package/build/transaction/interfaces/ITransactionParameters.d.ts +4 -6
- package/build/transaction/mineable/TimelockGenerator.d.ts +9 -0
- package/build/transaction/mineable/TimelockGenerator.js +24 -0
- package/build/utils/StringToBuffer.d.ts +1 -0
- package/build/utils/StringToBuffer.js +3 -0
- package/build/verification/TapscriptVerificator.d.ts +4 -1
- package/build/verification/TapscriptVerificator.js +2 -2
- package/package.json +16 -16
- package/src/_version.ts +1 -1
- package/src/abi/ABICoder.ts +4 -0
- package/src/buffer/BinaryWriter.ts +13 -11
- package/src/epoch/ChallengeSolution.ts +196 -0
- package/src/epoch/interfaces/IChallengeSolution.ts +56 -0
- package/src/epoch/validator/EpochValidator.ts +180 -0
- package/src/generators/Features.ts +6 -0
- package/src/generators/Generator.ts +24 -2
- package/src/generators/builders/CalldataGenerator.ts +7 -3
- package/src/generators/builders/DeploymentGenerator.ts +18 -6
- package/src/keypair/Address.ts +34 -0
- package/src/keypair/EcKeyPair.ts +13 -8
- package/src/opnet.ts +6 -3
- package/src/transaction/TransactionFactory.ts +7 -62
- package/src/transaction/browser/Web3Provider.ts +3 -3
- package/src/transaction/browser/types/Unisat.ts +1 -1
- package/src/transaction/builders/ChallengeSolutionTransaction.ts +3 -4
- package/src/transaction/builders/CustomScriptTransaction.ts +2 -1
- package/src/transaction/builders/DeploymentTransaction.ts +27 -9
- package/src/transaction/builders/InteractionTransaction.ts +10 -2
- package/src/transaction/builders/SharedInteractionTransaction.ts +12 -28
- package/src/transaction/builders/TransactionBuilder.ts +40 -2
- package/src/transaction/interfaces/ITransactionParameters.ts +5 -8
- package/src/transaction/mineable/TimelockGenerator.ts +42 -0
- package/src/utils/StringToBuffer.ts +3 -0
- package/src/verification/TapscriptVerificator.ts +8 -4
- package/browser/generators/builders/MineableReward.d.ts +0 -7
- package/browser/transaction/mineable/ChallengeGenerator.d.ts +0 -9
- package/build/generators/builders/MineableReward.d.ts +0 -7
- package/build/generators/builders/MineableReward.js +0 -48
- package/build/transaction/mineable/ChallengeGenerator.d.ts +0 -9
- package/build/transaction/mineable/ChallengeGenerator.js +0 -28
- package/src/generators/builders/MineableReward.ts +0 -66
- 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
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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:
|
|
31
|
+
preimage: ChallengeSolution,
|
|
30
32
|
maxPriority: bigint,
|
|
31
33
|
calldata?: Buffer,
|
|
34
|
+
features?: Feature<Features>[],
|
|
32
35
|
): Buffer {
|
|
33
|
-
const asm = this.getAsm(
|
|
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:
|
|
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,
|
package/src/keypair/Address.ts
CHANGED
|
@@ -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
|