@btc-vision/transaction
Version:
OPNet transaction library allows you to create and sign transactions for the OPNet network.
157 lines • 6.2 kB
JavaScript
import { crypto, equals } from '@btc-vision/bitcoin';
import { Address } from '../../keypair/Address.js';
import { stringToBuffer } from '../../utils/StringToBuffer.js';
export class EpochValidator {
static BLOCKS_PER_EPOCH = 5n;
/**
* Calculate SHA-1 hash
*/
static sha1(data) {
return crypto.sha1(data);
}
/**
* Calculate mining preimage
*/
static calculatePreimage(checksumRoot, publicKey, salt) {
// Ensure all are 32 bytes
if (checksumRoot.length !== 32 || publicKey.length !== 32 || salt.length !== 32) {
throw new Error('All inputs must be 32 bytes');
}
const preimage = new Uint8Array(32);
for (let i = 0; i < 32; i++) {
preimage[i] =
checksumRoot[i] ^ publicKey[i] ^ salt[i];
}
return preimage;
}
/**
* Count matching bits between two hashes
*/
static countMatchingBits(hash1, hash2) {
let matchingBits = 0;
if (hash1.length !== hash2.length) {
throw new Error('Hashes must be of the same length');
}
const minLength = Math.min(hash1.length, hash2.length);
for (let i = 0; i < minLength; i++) {
const byte1 = hash1[i];
const byte2 = hash2[i];
if (byte1 === byte2) {
matchingBits += 8;
}
else {
// Check individual bits
for (let bit = 7; bit >= 0; bit--) {
if (((byte1 >> bit) & 1) === ((byte2 >> bit) & 1)) {
matchingBits++;
}
else {
return matchingBits;
}
}
}
}
return matchingBits;
}
/**
* Verify an epoch solution using IPreimage
*/
static verifySolution(challenge, log = false) {
try {
const verification = challenge.verification;
const calculatedPreimage = this.calculatePreimage(verification.targetChecksum, challenge.publicKey.toBuffer(), challenge.salt);
const computedSolution = this.sha1(calculatedPreimage);
if (!equals(computedSolution, challenge.solution)) {
return false;
}
const matchingBits = this.countMatchingBits(computedSolution, verification.targetHash);
if (matchingBits !== challenge.difficulty) {
return false;
}
const expectedStartBlock = challenge.epochNumber * this.BLOCKS_PER_EPOCH;
const expectedEndBlock = expectedStartBlock + this.BLOCKS_PER_EPOCH - 1n;
return !(verification.startBlock !== expectedStartBlock ||
verification.endBlock !== expectedEndBlock);
}
catch (error) {
if (log)
console.error('Verification error:', error);
return false;
}
}
/**
* Get the mining target block for an epoch
*/
static getMiningTargetBlock(epochNumber) {
if (epochNumber === 0n) {
return null; // Epoch 0 cannot be mined
}
// Last block of previous epoch
return epochNumber * this.BLOCKS_PER_EPOCH - 1n;
}
/**
* Validate epoch winner from raw data
*/
static validateEpochWinner(epochData) {
try {
const epochNumber = BigInt(epochData.epochNumber);
const publicKey = Address.fromString(epochData.mldsaPublicKey, epochData.legacyPublicKey);
const solution = stringToBuffer(epochData.solution);
const salt = stringToBuffer(epochData.salt);
const difficulty = epochData.difficulty;
const verification = {
epochHash: stringToBuffer(epochData.verification.epochHash),
epochRoot: stringToBuffer(epochData.verification.epochRoot),
targetHash: stringToBuffer(epochData.verification.targetHash),
targetChecksum: stringToBuffer(epochData.verification.targetChecksum),
startBlock: BigInt(epochData.verification.startBlock),
endBlock: BigInt(epochData.verification.endBlock),
proofs: Object.freeze(epochData.verification.proofs.map((p) => stringToBuffer(p))),
};
const calculatedPreimage = this.calculatePreimage(verification.targetChecksum, publicKey.toBuffer(), salt);
const computedSolution = this.sha1(calculatedPreimage);
if (!equals(computedSolution, solution)) {
return false;
}
const matchingBits = this.countMatchingBits(computedSolution, verification.targetHash);
if (matchingBits !== difficulty) {
return false;
}
const expectedStartBlock = epochNumber * this.BLOCKS_PER_EPOCH;
const expectedEndBlock = expectedStartBlock + this.BLOCKS_PER_EPOCH - 1n;
return !(verification.startBlock !== expectedStartBlock ||
verification.endBlock !== expectedEndBlock);
}
catch {
return false;
}
}
/**
* Validate epoch winner from Preimage instance
*/
static validateChallengeSolution(challenge) {
return this.verifySolution(challenge);
}
/**
* Calculate solution hash from preimage components
* @param targetChecksum The target checksum (32 bytes)
* @param publicKey The public key buffer (32 bytes)
* @param salt The salt buffer (32 bytes)
* @returns The SHA-1 hash of the preimage
*/
static calculateSolution(targetChecksum, publicKey, salt) {
const preimage = this.calculatePreimage(targetChecksum, publicKey, salt);
return this.sha1(preimage);
}
/**
* Check if a solution meets the minimum difficulty requirement
*/
static checkDifficulty(solution, targetHash, minDifficulty) {
const difficulty = this.countMatchingBits(solution, targetHash);
return {
valid: difficulty >= minDifficulty,
difficulty,
};
}
}
//# sourceMappingURL=EpochValidator.js.map