@btc-vision/transaction 1.5.4 → 1.6.1
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/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/generators/builders/LegacyCalldataGenerator.d.ts +1 -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/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/utxo/OPNetLimitedProvider.d.ts +0 -4
- package/browser/verification/TapscriptVerificator.d.ts +4 -1
- package/build/_version.d.ts +1 -1
- package/build/_version.js +1 -1
- 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 +6 -4
- package/build/generators/builders/LegacyCalldataGenerator.d.ts +1 -1
- package/build/generators/builders/LegacyCalldataGenerator.js +2 -2
- package/build/keypair/Address.d.ts +2 -0
- package/build/keypair/Address.js +12 -0
- package/build/keypair/EcKeyPair.js +6 -3
- 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/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 +20 -8
- 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/utxo/OPNetLimitedProvider.d.ts +0 -4
- package/build/utxo/OPNetLimitedProvider.js +0 -7
- package/build/verification/TapscriptVerificator.d.ts +4 -1
- package/build/verification/TapscriptVerificator.js +2 -2
- package/package.json +15 -15
- package/src/_version.ts +1 -1
- 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/generators/builders/LegacyCalldataGenerator.ts +3 -3
- package/src/keypair/Address.ts +34 -0
- package/src/keypair/EcKeyPair.ts +9 -4
- package/src/opnet.ts +6 -3
- package/src/transaction/TransactionFactory.ts +7 -62
- package/src/transaction/browser/Web3Provider.ts +3 -3
- package/src/transaction/builders/ChallengeSolutionTransaction.ts +3 -4
- package/src/transaction/builders/CustomScriptTransaction.ts +2 -1
- package/src/transaction/builders/DeploymentTransaction.ts +29 -11
- 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/utxo/OPNetLimitedProvider.ts +0 -17
- 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,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
|
+
challenge: IChallengeSolution,
|
|
83
|
+
log: boolean = false,
|
|
84
|
+
): Promise<boolean> {
|
|
85
|
+
try {
|
|
86
|
+
const verification = challenge.verification;
|
|
87
|
+
const calculatedPreimage = this.calculatePreimage(
|
|
88
|
+
verification.targetChecksum,
|
|
89
|
+
challenge.publicKey.toBuffer(),
|
|
90
|
+
challenge.salt,
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const computedSolution = await this.sha1(this.bufferToUint8Array(calculatedPreimage));
|
|
94
|
+
const computedSolutionBuffer = this.uint8ArrayToBuffer(computedSolution);
|
|
95
|
+
|
|
96
|
+
if (!computedSolutionBuffer.equals(challenge.solution)) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const matchingBits = this.countMatchingBits(
|
|
101
|
+
computedSolutionBuffer,
|
|
102
|
+
verification.targetHash,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
if (matchingBits !== challenge.difficulty) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const expectedStartBlock = challenge.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 validateChallengeSolution(challenge: IChallengeSolution): Promise<boolean> {
|
|
146
|
+
return await this.verifySolution(challenge);
|
|
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} challenge - The challenge 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
|
-
|
|
31
|
+
challenge: 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
|
+
challenge,
|
|
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
|
-
|
|
60
|
+
challenge: 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
|
-
|
|
88
|
+
challenge.publicKey.originalPublicKeyBuffer(),
|
|
89
|
+
opcodes.OP_TOALTSTACK,
|
|
90
|
+
|
|
91
|
+
challenge.solution,
|
|
80
92
|
opcodes.OP_TOALTSTACK,
|
|
81
93
|
|
|
82
94
|
this.xSenderPubKey,
|
|
@@ -52,7 +52,7 @@ export class LegacyCalldataGenerator extends Generator {
|
|
|
52
52
|
* Compile an interaction bitcoin script
|
|
53
53
|
* @param {Buffer} calldata - The calldata to use
|
|
54
54
|
* @param {Buffer} contractSecret - The contract secret
|
|
55
|
-
* @param {Buffer}
|
|
55
|
+
* @param {Buffer} challenge - The challenge to use
|
|
56
56
|
* @param {bigint} maxPriority - The maximum priority
|
|
57
57
|
* @param {number[]} [features=[]] - The features to use (optional)
|
|
58
58
|
* @returns {Buffer} - The compiled script
|
|
@@ -61,7 +61,7 @@ export class LegacyCalldataGenerator extends Generator {
|
|
|
61
61
|
public compile(
|
|
62
62
|
calldata: Buffer,
|
|
63
63
|
contractSecret: Buffer,
|
|
64
|
-
|
|
64
|
+
challenge: Buffer,
|
|
65
65
|
maxPriority: bigint,
|
|
66
66
|
features: Feature<Features>[] = [],
|
|
67
67
|
): Buffer {
|
|
@@ -83,7 +83,7 @@ export class LegacyCalldataGenerator extends Generator {
|
|
|
83
83
|
opcodes.OP_TOALTSTACK,
|
|
84
84
|
|
|
85
85
|
// CHALLENGE PREIMAGE FOR REWARD,
|
|
86
|
-
|
|
86
|
+
challenge,
|
|
87
87
|
opcodes.OP_TOALTSTACK,
|
|
88
88
|
|
|
89
89
|
this.senderPubKey,
|
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
|
package/src/keypair/EcKeyPair.ts
CHANGED
|
@@ -26,9 +26,8 @@ if (!BIP32factory) {
|
|
|
26
26
|
throw new Error('Failed to load BIP32 library');
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
secp256k1.
|
|
30
|
-
|
|
31
|
-
const { Point, CURVE } = secp256k1;
|
|
29
|
+
const Point = secp256k1.Point;
|
|
30
|
+
const CURVE_N = Point.Fn.ORDER;
|
|
32
31
|
|
|
33
32
|
const TAP_TAG = utf8ToBytes('TapTweak');
|
|
34
33
|
const TAP_TAG_HASH = sha256(TAP_TAG);
|
|
@@ -48,6 +47,12 @@ export class EcKeyPair {
|
|
|
48
47
|
public static BIP32: BIP32API = BIP32factory(ecc);
|
|
49
48
|
public static ECPair: ECPairAPI = ECPairFactory(ecc);
|
|
50
49
|
|
|
50
|
+
// Initialize precomputation for better performance
|
|
51
|
+
static {
|
|
52
|
+
// Precompute tables for the base point for better performance
|
|
53
|
+
Point.BASE.precompute(8);
|
|
54
|
+
}
|
|
55
|
+
|
|
51
56
|
/**
|
|
52
57
|
* Generate a keypair from a WIF
|
|
53
58
|
* @param {string} wif - The WIF to use
|
|
@@ -274,7 +279,7 @@ export class EcKeyPair {
|
|
|
274
279
|
|
|
275
280
|
const xBytes = Peven.toBytes(true).subarray(1);
|
|
276
281
|
const tBytes = tapTweakHash(xBytes);
|
|
277
|
-
const t = mod(bytesToNumberBE(tBytes),
|
|
282
|
+
const t = mod(bytesToNumberBE(tBytes), CURVE_N);
|
|
278
283
|
|
|
279
284
|
const Q = Peven.add(Point.BASE.multiply(t));
|
|
280
285
|
return Buffer.from(Q.toBytes(true));
|
package/src/opnet.ts
CHANGED
|
@@ -10,12 +10,11 @@ export * from './generators/builders/CalldataGenerator.js';
|
|
|
10
10
|
export * from './generators/builders/CustomGenerator.js';
|
|
11
11
|
export * from './generators/builders/DeploymentGenerator.js';
|
|
12
12
|
export * from './generators/builders/LegacyCalldataGenerator.js';
|
|
13
|
-
export * from './generators/builders/MineableReward.js';
|
|
14
13
|
export * from './generators/builders/MultiSignGenerator.js';
|
|
15
14
|
export * from './generators/Features.js';
|
|
16
15
|
export * from './generators/Generator.js';
|
|
17
16
|
|
|
18
|
-
export * from './transaction/mineable/
|
|
17
|
+
export * from './transaction/mineable/TimelockGenerator.js';
|
|
19
18
|
|
|
20
19
|
/** Address */
|
|
21
20
|
export * from './generators/AddressGenerator.js';
|
|
@@ -42,7 +41,6 @@ export * from './transaction/interfaces/Tap.js';
|
|
|
42
41
|
export * from './transaction/TransactionFactory.js';
|
|
43
42
|
|
|
44
43
|
/** Builders */
|
|
45
|
-
export * from './transaction/builders/ChallengeSolutionTransaction.js';
|
|
46
44
|
export * from './transaction/builders/CustomScriptTransaction.js';
|
|
47
45
|
export * from './transaction/builders/DeploymentTransaction.js';
|
|
48
46
|
export * from './transaction/builders/FundingTransaction.js';
|
|
@@ -51,6 +49,11 @@ export * from './transaction/builders/MultiSignTransaction.js';
|
|
|
51
49
|
export * from './transaction/builders/SharedInteractionTransaction.js';
|
|
52
50
|
export * from './transaction/builders/TransactionBuilder.js';
|
|
53
51
|
|
|
52
|
+
/** Epoch */
|
|
53
|
+
export * from './epoch/interfaces/IChallengeSolution.js';
|
|
54
|
+
export * from './epoch/validator/EpochValidator.js';
|
|
55
|
+
export * from './epoch/ChallengeSolution.js';
|
|
56
|
+
|
|
54
57
|
/** Utils */
|
|
55
58
|
export * from './utils/BitcoinUtils.js';
|
|
56
59
|
export * from './utils/lengths.js';
|
|
@@ -11,26 +11,25 @@ import { InteractionTransaction } from './builders/InteractionTransaction.js';
|
|
|
11
11
|
import { TransactionBuilder } from './builders/TransactionBuilder.js';
|
|
12
12
|
import { TransactionType } from './enums/TransactionType.js';
|
|
13
13
|
import {
|
|
14
|
-
IChallengeSolutionTransactionParameters,
|
|
15
14
|
IDeploymentParameters,
|
|
16
15
|
IFundingTransactionParameters,
|
|
17
16
|
IInteractionParameters,
|
|
18
17
|
ITransactionParameters,
|
|
19
18
|
} from './interfaces/ITransactionParameters.js';
|
|
20
19
|
import { PSBTTypes } from './psbt/PSBTTypes.js';
|
|
21
|
-
import { ChallengeSolutionTransaction } from './builders/ChallengeSolutionTransaction.js';
|
|
22
20
|
import {
|
|
23
21
|
IDeploymentParametersWithoutSigner,
|
|
24
22
|
InteractionParametersWithoutSigner,
|
|
25
23
|
} from './browser/Web3Provider.js';
|
|
26
24
|
import { WindowWithWallets } from './browser/extensions/UnisatSigner.js';
|
|
25
|
+
import { RawChallenge } from '../epoch/interfaces/IChallengeSolution.js';
|
|
27
26
|
|
|
28
27
|
export interface DeploymentResult {
|
|
29
28
|
readonly transaction: [string, string];
|
|
30
29
|
|
|
31
30
|
readonly contractAddress: string;
|
|
32
31
|
readonly contractPubKey: string;
|
|
33
|
-
readonly
|
|
32
|
+
readonly challenge: RawChallenge;
|
|
34
33
|
|
|
35
34
|
readonly utxos: UTXO[];
|
|
36
35
|
}
|
|
@@ -42,13 +41,6 @@ export interface FundingTransactionResponse {
|
|
|
42
41
|
readonly nextUTXOs: UTXO[];
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
export interface ChallengeSolutionResponse {
|
|
46
|
-
readonly tx: Transaction;
|
|
47
|
-
readonly original: ChallengeSolutionTransaction;
|
|
48
|
-
readonly estimatedFees: bigint;
|
|
49
|
-
readonly nextUTXOs: UTXO[];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
44
|
export interface BitcoinTransferBase {
|
|
53
45
|
readonly tx: string;
|
|
54
46
|
readonly estimatedFees: bigint;
|
|
@@ -60,11 +52,7 @@ export interface InteractionResponse {
|
|
|
60
52
|
readonly interactionTransaction: string;
|
|
61
53
|
readonly estimatedFees: bigint;
|
|
62
54
|
readonly nextUTXOs: UTXO[];
|
|
63
|
-
readonly
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export interface ChallengeSolution extends BitcoinTransferBase {
|
|
67
|
-
readonly original: ChallengeSolutionTransaction;
|
|
55
|
+
readonly challenge: RawChallenge;
|
|
68
56
|
}
|
|
69
57
|
|
|
70
58
|
export interface BitcoinTransferResponse extends BitcoinTransferBase {
|
|
@@ -246,7 +234,7 @@ export class TransactionFactory {
|
|
|
246
234
|
...this.getUTXOAsTransaction(signedTransaction.tx, interactionParameters.to, 0),
|
|
247
235
|
], // always 0
|
|
248
236
|
randomBytes: preTransaction.getRndBytes(),
|
|
249
|
-
|
|
237
|
+
challenge: preTransaction.getPreimage(),
|
|
250
238
|
nonWitnessUtxo: signedTransaction.tx.toBuffer(),
|
|
251
239
|
estimatedFees: preTransaction.estimatedFees,
|
|
252
240
|
optionalInputs: inputs,
|
|
@@ -265,7 +253,7 @@ export class TransactionFactory {
|
|
|
265
253
|
interactionParameters.from,
|
|
266
254
|
1,
|
|
267
255
|
), // always 1
|
|
268
|
-
|
|
256
|
+
challenge: preTransaction.getPreimage().toRaw(),
|
|
269
257
|
};
|
|
270
258
|
}
|
|
271
259
|
|
|
@@ -343,7 +331,7 @@ export class TransactionFactory {
|
|
|
343
331
|
...deploymentParameters,
|
|
344
332
|
utxos: [newUtxo], // always 0
|
|
345
333
|
randomBytes: preTransaction.getRndBytes(),
|
|
346
|
-
|
|
334
|
+
challenge: preTransaction.getPreimage(),
|
|
347
335
|
nonWitnessUtxo: signedTransaction.toBuffer(),
|
|
348
336
|
estimatedFees: preTransaction.estimatedFees,
|
|
349
337
|
optionalInputs: inputs,
|
|
@@ -370,7 +358,7 @@ export class TransactionFactory {
|
|
|
370
358
|
contractAddress: finalTransaction.getContractAddress(), //finalTransaction.contractAddress.p2tr(deploymentParameters.network),
|
|
371
359
|
contractPubKey: finalTransaction.contractPubKey,
|
|
372
360
|
utxos: [refundUTXO],
|
|
373
|
-
|
|
361
|
+
challenge: preTransaction.getPreimage().toRaw(),
|
|
374
362
|
};
|
|
375
363
|
}
|
|
376
364
|
|
|
@@ -395,27 +383,6 @@ export class TransactionFactory {
|
|
|
395
383
|
};
|
|
396
384
|
}
|
|
397
385
|
|
|
398
|
-
/**
|
|
399
|
-
* @description Creates a challenge solution transaction.
|
|
400
|
-
* @param {IChallengeSolutionTransactionParameters} parameters - The challenge solution transaction parameters
|
|
401
|
-
* @returns {Promise<ChallengeSolution>} - The signed transaction
|
|
402
|
-
*/
|
|
403
|
-
public async createChallengeSolution(
|
|
404
|
-
parameters: IChallengeSolutionTransactionParameters,
|
|
405
|
-
): Promise<ChallengeSolution> {
|
|
406
|
-
if (!parameters.from) {
|
|
407
|
-
throw new Error('Field "from" not provided.');
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
const resp = await this._createChallengeSolution(parameters);
|
|
411
|
-
return {
|
|
412
|
-
estimatedFees: resp.estimatedFees,
|
|
413
|
-
original: resp.original,
|
|
414
|
-
tx: resp.tx.toHex(),
|
|
415
|
-
nextUTXOs: this.getAllNewUTXOs(resp.original, resp.tx, parameters.from),
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
|
|
419
386
|
/**
|
|
420
387
|
* Get all new UTXOs of a generated transaction.
|
|
421
388
|
* @param {TransactionBuilder<TransactionType>} original - The original transaction
|
|
@@ -520,28 +487,6 @@ export class TransactionFactory {
|
|
|
520
487
|
return deployment;
|
|
521
488
|
}
|
|
522
489
|
|
|
523
|
-
private async _createChallengeSolution(
|
|
524
|
-
parameters: IChallengeSolutionTransactionParameters,
|
|
525
|
-
): Promise<ChallengeSolutionResponse> {
|
|
526
|
-
if (!parameters.to) throw new Error('Field "to" not provided.');
|
|
527
|
-
|
|
528
|
-
const challengeTransaction: ChallengeSolutionTransaction = new ChallengeSolutionTransaction(
|
|
529
|
-
parameters,
|
|
530
|
-
);
|
|
531
|
-
|
|
532
|
-
const signedTransaction: Transaction = await challengeTransaction.signTransaction();
|
|
533
|
-
if (!signedTransaction) {
|
|
534
|
-
throw new Error('Could not sign funding transaction.');
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
return {
|
|
538
|
-
tx: signedTransaction,
|
|
539
|
-
original: challengeTransaction,
|
|
540
|
-
estimatedFees: challengeTransaction.estimatedFees,
|
|
541
|
-
nextUTXOs: this.getUTXOAsTransaction(signedTransaction, parameters.to, 0),
|
|
542
|
-
};
|
|
543
|
-
}
|
|
544
|
-
|
|
545
490
|
private async createFundTransaction(
|
|
546
491
|
parameters: IFundingTransactionParameters,
|
|
547
492
|
): Promise<FundingTransactionResponse> {
|
|
@@ -8,17 +8,17 @@ import { ICustomTransactionParameters } from '../builders/CustomScriptTransactio
|
|
|
8
8
|
|
|
9
9
|
export type InteractionParametersWithoutSigner = Omit<
|
|
10
10
|
IInteractionParameters,
|
|
11
|
-
'signer' | '
|
|
11
|
+
'signer' | 'challenge'
|
|
12
12
|
>;
|
|
13
13
|
|
|
14
14
|
export type IDeploymentParametersWithoutSigner = Omit<
|
|
15
15
|
IDeploymentParameters,
|
|
16
|
-
'signer' | 'network' | '
|
|
16
|
+
'signer' | 'network' | 'challenge'
|
|
17
17
|
>;
|
|
18
18
|
|
|
19
19
|
export type CustomTransactionWithoutSigner = Omit<
|
|
20
20
|
ICustomTransactionParameters,
|
|
21
|
-
'signer' | '
|
|
21
|
+
'signer' | 'challenge'
|
|
22
22
|
>;
|
|
23
23
|
|
|
24
24
|
export interface BroadcastTransactionOptions {
|