UNPKG

@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
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;