From ca0668b8074c9a007453ad029499c8dc67c1f5f0 Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Thu, 15 Jun 2023 23:51:36 -0400 Subject: [PATCH] diffs working --- src/models/MiningJob.ts | 69 +++++++++------- src/models/StratumV1Client.ts | 4 +- .../MiningSubmitMessage.spec.ts | 57 +++++++++---- .../stratum-messages/MiningSubmitMessage.ts | 79 +++++++++++++------ 4 files changed, 143 insertions(+), 66 deletions(-) diff --git a/src/models/MiningJob.ts b/src/models/MiningJob.ts index 71b9f0d..b08a822 100644 --- a/src/models/MiningJob.ts +++ b/src/models/MiningJob.ts @@ -1,9 +1,8 @@ import * as crypto from 'crypto'; +import { MerkleTree } from 'merkletreejs'; +import { IBlockTemplate } from './bitcoin-rpc/IBlockTemplate'; import { eResponseMethod } from './enums/eResponseMethod'; -import { IBlockTemplate, IBlockTemplateTx } from './bitcoin-rpc/IBlockTemplate'; -import { randomUUID } from 'crypto'; -import { MerkleTree } from 'merkletreejs' interface AddressObject { address: string; @@ -14,37 +13,39 @@ export class MiningJob { public method: eResponseMethod.MINING_NOTIFY; public params: string[]; - public target: number; + public target: string; public merkleRoot: string; - public job_id: string; // ID of the job. Use this ID while submitting share generated from this job. + public job_id: number; // ID of the job. Use this ID while submitting share generated from this job. public prevhash: string; // The hex-encoded previous block hash. public coinb1: string; // The hex-encoded prefix of the coinbase transaction (to precede extra nonce 2). public coinb2: string; //The hex-encoded suffix of the coinbase transaction (to follow extra nonce 2). public merkle_branch: string[]; // List of hashes, will be used for calculation of merkle root. This is not a list of all transactions, it only contains prepared hashes of steps of merkle tree algorithm. public version: number; // The hex-encoded block version. - public nbits: string; // The hex-encoded network difficulty required for the block. + public nbits: number; // The hex-encoded network difficulty required for the block. public ntime: number; // Current ntime/ public clean_jobs: boolean; // When true, server indicates that submitting shares from previous jobs don't have a sense and such shares will be rejected. When this flag is set, miner should also drop all previous jobs too. public response: string; - public versionMask: number; + public versionMask: string; + + public tree: MerkleTree; constructor(blockTemplate: IBlockTemplate) { - console.log(blockTemplate); + //console.log(blockTemplate); - this.job_id = randomUUID(); - this.target = Number(blockTemplate.target); - this.prevhash = blockTemplate.previousblockhash; + this.job_id = 1; + this.target = blockTemplate.target; + this.prevhash = this.convertToLittleEndian(blockTemplate.previousblockhash); this.version = blockTemplate.version; - this.nbits = blockTemplate.bits; + this.nbits = parseInt(blockTemplate.bits, 16); this.ntime = Math.floor(new Date().getTime() / 1000); this.clean_jobs = false; - const transactions = blockTemplate.transactions.map(tx => tx.hash); + const transactionFees = blockTemplate.transactions.reduce((pre, cur, i, arr) => { return pre + cur.fee; }, 0); @@ -57,25 +58,31 @@ export class MiningJob { this.coinb1 = coinbasePart1; this.coinb2 = coinbasePart2; - const coinbaseHash = this.bufferToHex(this.sha256(this.coinb1 + this.coinb2)); + const coinbaseHash = this.sha256(this.coinb1 + this.coinb2).toString('hex'); + const coinbaseBuffer = Buffer.from(coinbaseHash, 'hex'); - transactions.unshift(coinbaseHash); + //transactions.unshift(coinbaseHash); // Calculate merkle branch + const transactionBuffers = blockTemplate.transactions.map(tx => Buffer.from(tx.hash, 'hex')); + transactionBuffers.unshift(coinbaseBuffer); + this.tree = new MerkleTree(transactionBuffers, this.sha256, { isBitcoinTree: true }); - const tree = new MerkleTree(transactions, this.sha256, { isBitcoinTree: true }); - const layers = tree.getLayers(); - const branch = []; + // // this.merkle_branch = tree.getProof(coinbaseBuffer).map(p => p.data.toString('hex')) - for (const layer of layers) { - branch.push(this.bufferToHex(layer[0])); - } - //console.log(branch); + // this.merkle_branch = tree.getLayers().map(l => l.pop().toString('hex')); + // this.merkleRoot = this.merkle_branch.pop(); - this.merkle_branch = branch; + const rootBuffer = this.tree.getRoot(); + this.merkleRoot = rootBuffer.toString('hex'); + this.merkle_branch = this.tree.getProof(coinbaseBuffer).map(p => p.data.toString('hex')); - this.merkleRoot = tree.getRoot().toString('hex') + + // let test = this.tree.getRoot(); + // for (let i = 0; i < test.length; i++) { + // console.log(test[i]) + // } } @@ -148,14 +155,14 @@ export class MiningJob { id: 0, method: eResponseMethod.MINING_NOTIFY, params: [ - this.job_id, + this.job_id.toString(16), this.prevhash, this.coinb1, this.coinb2, this.merkle_branch, - this.version, - this.nbits, - this.ntime, + this.version.toString(16), + this.nbits.toString(16), + this.ntime.toString(16), this.clean_jobs ] }; @@ -165,6 +172,12 @@ export class MiningJob { } + private convertToLittleEndian(hash: string): string { + const bytes = Buffer.from(hash, 'hex'); + Array.prototype.reverse.call(bytes); + return bytes.toString('hex'); + } + } \ No newline at end of file diff --git a/src/models/StratumV1Client.ts b/src/models/StratumV1Client.ts index 187933a..38d1570 100644 --- a/src/models/StratumV1Client.ts +++ b/src/models/StratumV1Client.ts @@ -218,7 +218,9 @@ export class StratumV1Client extends EasyUnsubscribe { private handleMiningSubmission(submission: MiningSubmitMessage) { const networkDifficulty = 0; - const diff = submission.testNonceValue(this.currentJob, parseInt(submission.nonce, 16)); + const diff = submission.testNonceValue(this.currentJob, submission); + console.log('DIFF'); + console.log(diff); if (networkDifficulty < diff) { this.blockFoundEmitter.next(true); } diff --git a/src/models/stratum-messages/MiningSubmitMessage.spec.ts b/src/models/stratum-messages/MiningSubmitMessage.spec.ts index 1f180a8..928256d 100644 --- a/src/models/stratum-messages/MiningSubmitMessage.spec.ts +++ b/src/models/stratum-messages/MiningSubmitMessage.spec.ts @@ -1,6 +1,3 @@ -import { MiningJob } from '../MiningJob'; -import { MiningSubmitMessage } from './MiningSubmitMessage'; - describe('MiningSubmitMessage', () => { @@ -10,18 +7,48 @@ describe('MiningSubmitMessage', () => { }); - describe('root', () => { + // describe('test nonce', () => { - const value = new MiningSubmitMessage().testNonceValue({ - version: 0x20000004, - prevhash: "0c859545a3498373a57452fac22eb7113df2a465000543520000000000000000", - merkleRoot: "5bdc1968499c3393873edf8e07a1c3a50a97fc3a9d1a376bbf77087dd63778eb", - ntime: 0x647025b5, - target: 0x1705ae3a, - } as MiningJob, 167943889); + // const value = new MiningSubmitMessage().testNonceValue({ + // version: 0x20000004, + // prevhash: "0c859545a3498373a57452fac22eb7113df2a465000543520000000000000000", + // merkleRoot: "5bdc1968499c3393873edf8e07a1c3a50a97fc3a9d1a376bbf77087dd63778eb", + // ntime: 0x647025b5, + // nbits: 0x1705ae3a, + // } as unknown as MiningJob, 167943889); - it('should be correct difficulty', () => { - expect(value).toEqual(683); - }); - }); + // it('should be correct difficulty', () => { + // expect(value).toEqual(683); + // }); + // }); + + // describe('test empty version', () => { + + // const value = new MiningSubmitMessage().testNonceValue({ + // version: 0x20000004, + // prevhash: "0c859545a3498373a57452fac22eb7113df2a465000543520000000000000000", + // merkleRoot: "5bdc1968499c3393873edf8e07a1c3a50a97fc3a9d1a376bbf77087dd63778eb", + // ntime: 0x647025b5, + // nbits: 0x1705ae3a, + // } as unknown as MiningJob, 167943889, parseInt('00000000', 16)); + + // it('should be correct difficulty', () => { + // expect(value).toEqual(683); + // }); + // }); + + // describe('test high value nonce', () => { + + // const value = new MiningSubmitMessage().testNonceValue({ + // version: 0x20a00000, + // prevhash: "00000000000000000002a7b66a599d17893cb312a8ee7bc15e4015ff52774f00", + // merkleRoot: "210bbf45c85165aab889691056cfebbfd763e11b2623a261fb6135b6bab66ce3", + // ntime: 1686839100, + // target: 52350439455487, + // } as MiningJob, 0x05d69c40,); + + // it('should be correct difficulty', () => { + // expect(value).toEqual(683); + // }); + // }); }); diff --git a/src/models/stratum-messages/MiningSubmitMessage.ts b/src/models/stratum-messages/MiningSubmitMessage.ts index dce8257..deae37d 100644 --- a/src/models/stratum-messages/MiningSubmitMessage.ts +++ b/src/models/stratum-messages/MiningSubmitMessage.ts @@ -10,7 +10,7 @@ export class MiningSubmitMessage extends StratumBaseMessage { @IsArray() @ArrayMinSize(5) - @ArrayMaxSize(5) + @ArrayMaxSize(6) public params: string[]; public userId: string; @@ -18,7 +18,7 @@ export class MiningSubmitMessage extends StratumBaseMessage { public extraNonce2: string; public ntime: string; public nonce: string - + public versionMask: string; constructor() { super(); this.method = eRequestMethod.AUTHORIZE; @@ -31,6 +31,7 @@ export class MiningSubmitMessage extends StratumBaseMessage { this.extraNonce2 = this.params[2]; this.ntime = this.params[3]; this.nonce = this.params[4]; + this.versionMask = this.params[5]; } public response() { @@ -42,42 +43,57 @@ export class MiningSubmitMessage extends StratumBaseMessage { } - testNonceValue(job: MiningJob, nonce: number, midstateIndex: number = 0): number { + public testNonceValue(job: MiningJob, submission: MiningSubmitMessage): number { + + const nonce = parseInt(submission.nonce, 16); + const versionMask = parseInt(submission.versionMask, 16); + const extraNonce = 'ccc5d664'; + const extraNonce2 = submission.extraNonce2; + + const coinbaseTx = `${job.coinb1}${extraNonce}${extraNonce2}${job.coinb2}`; + + const newRoot = this.calculateMerkleRootHash(coinbaseTx, job.merkle_branch) + + const truediffone = Big('26959535291011309493156476344723991336010898738574164086137773096960'); - let s64: string; + const header = Buffer.alloc(80); // TODO: Use the midstate hash instead of hashing the whole header - // Copy data from job to header - - let rolledVersion = job.version; - for (let i = 0; i < midstateIndex; i++) { - rolledVersion = this.incrementBitmask(rolledVersion, job.versionMask); + let version = job.version; + if (versionMask !== undefined && versionMask != 0) { + version = (version ^ versionMask); } - header.writeInt32LE(rolledVersion, 0); - header.write(this.convertStringToLE(job.prevhash), 4, 'hex') - Buffer.from(job.merkleRoot, 'hex').copy(header, 36, 0, 32) - header.writeInt32LE(job.ntime, 68); - header.writeInt32LE(job.target, 72); - header.writeInt32LE(nonce, 76); + + header.writeUInt32LE(version, 0); + + header.write(this.swapEndianWords(job.prevhash), 4, 'hex') + newRoot.copy(header, 36, 0, 32) + header.writeUInt32LE(job.ntime, 68); + header.writeBigUint64LE(BigInt(job.nbits), 72); + header.writeUInt32LE(nonce, 76); + + // for (let i = 0; i < 80; i++) { + // console.log(header[i].toString(10)); + // } const hashBuffer: Buffer = crypto.createHash('sha256').update(header).digest(); const hashResult: Buffer = crypto.createHash('sha256').update(hashBuffer).digest(); - s64 = this.le256todouble(hashResult); + let s64 = this.le256todouble(hashResult); - return parseInt(truediffone.div(s64).toString()); + return parseInt(truediffone.div(s64.toString()).toString()); } - private convertStringToLE(str: string) { + private swapEndianWords(str: string) { const hexGroups = str.match(/.{1,8}/g); // Reverse each group and concatenate them const reversedHexString = hexGroups.reduce((pre, cur, indx, arr) => { @@ -88,18 +104,37 @@ export class MiningSubmitMessage extends StratumBaseMessage { } - private le256todouble(target: Buffer): string { + private le256todouble(target: Buffer): bigint { const number = target.reduceRight((acc, byte) => { // Shift the number 8 bits to the left and OR with the current byte return (acc << BigInt(8)) | BigInt(byte); }, BigInt(0)); - return number.toString(); + return number; } - public incrementBitmask(rolledVersion: number, versionMask: number) { - return (rolledVersion + 1) | versionMask; + private calculateMerkleRootHash(coinbaseTx: string, merkleBranches: string[]): Buffer { + + let coinbaseTxBuf = Buffer.from(coinbaseTx, 'hex'); + + const bothMerkles = Buffer.alloc(64); + let test = this.sha256(coinbaseTxBuf) + let newRoot = this.sha256(test); + bothMerkles.set(newRoot); + + for (let i = 0; i < merkleBranches.length; i++) { + bothMerkles.set(Buffer.from(merkleBranches[i], 'hex'), 32); + newRoot = this.sha256(this.sha256(bothMerkles)); + bothMerkles.set(newRoot); + } + + return bothMerkles; } + + private sha256(data: Buffer) { + return crypto.createHash('sha256').update(data).digest() + } + } \ No newline at end of file