@btc-vision/transaction
Version:
OPNet transaction library allows you to create and sign transactions for the OPNet network.
99 lines (98 loc) • 3.85 kB
JavaScript
import { ChallengeSolution } from '../ChallengeSolution.js';
import { crypto } from '@btc-vision/bitcoin';
export class EpochValidator {
static bufferToUint8Array(buffer) {
return new Uint8Array(buffer);
}
static uint8ArrayToBuffer(array) {
return Buffer.from(array);
}
static sha1(data) {
return crypto.sha1(Buffer.isBuffer(data) ? data : Buffer.from(data));
}
static calculatePreimage(checksumRoot, publicKey, salt) {
if (checksumRoot.length !== 32 || publicKey.length !== 32 || salt.length !== 32) {
throw new Error('All inputs must be 32 bytes');
}
const preimage = Buffer.alloc(32);
for (let i = 0; i < 32; i++) {
preimage[i] = checksumRoot[i] ^ publicKey[i] ^ salt[i];
}
return preimage;
}
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 {
for (let bit = 7; bit >= 0; bit--) {
if (((byte1 >> bit) & 1) === ((byte2 >> bit) & 1)) {
matchingBits++;
}
else {
return matchingBits;
}
}
}
}
return matchingBits;
}
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);
const computedSolutionBuffer = this.uint8ArrayToBuffer(computedSolution);
if (!computedSolutionBuffer.equals(challenge.solution)) {
return false;
}
const matchingBits = this.countMatchingBits(computedSolutionBuffer, 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;
}
}
static getMiningTargetBlock(epochNumber) {
if (epochNumber === 0n) {
return null;
}
return epochNumber * this.BLOCKS_PER_EPOCH - 1n;
}
static validateEpochWinner(epochData) {
const preimage = new ChallengeSolution(epochData);
return this.verifySolution(preimage);
}
static validateChallengeSolution(challenge) {
return this.verifySolution(challenge);
}
static calculateSolution(targetChecksum, publicKey, salt) {
const preimage = this.calculatePreimage(targetChecksum, publicKey, salt);
const hash = this.sha1(this.bufferToUint8Array(preimage));
return this.uint8ArrayToBuffer(hash);
}
static checkDifficulty(solution, targetHash, minDifficulty) {
const difficulty = this.countMatchingBits(solution, targetHash);
return {
valid: difficulty >= minDifficulty,
difficulty,
};
}
}
EpochValidator.BLOCKS_PER_EPOCH = 5n;