diff --git a/package-lock.json b/package-lock.json index 4b796a0..145e57e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@nestjs/config": "^2.3.2", "@nestjs/core": "^9.0.0", "@nestjs/platform-fastify": "^9.4.2", + "big.js": "^6.2.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "merkletreejs": "^0.3.10", @@ -2856,6 +2857,18 @@ "tweetnacl": "^0.14.3" } }, + "node_modules/big.js": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-6.2.1.tgz", + "integrity": "sha512-bCtHMwL9LeDIozFn+oNhhFoq+yQ3BNdnsLSASUxLciOb1vgvpHsIO1dsENiGMgbb4SkP5TrzWzRiLddn8ahVOQ==", + "engines": { + "node": "*" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/bigjs" + } + }, "node_modules/bignumber.js": { "version": "9.1.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", @@ -11090,6 +11103,11 @@ "tweetnacl": "^0.14.3" } }, + "big.js": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-6.2.1.tgz", + "integrity": "sha512-bCtHMwL9LeDIozFn+oNhhFoq+yQ3BNdnsLSASUxLciOb1vgvpHsIO1dsENiGMgbb4SkP5TrzWzRiLddn8ahVOQ==" + }, "bignumber.js": { "version": "9.1.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", diff --git a/package.json b/package.json index ca9ddf2..1f5d76c 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@nestjs/config": "^2.3.2", "@nestjs/core": "^9.0.0", "@nestjs/platform-fastify": "^9.4.2", + "big.js": "^6.2.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "merkletreejs": "^0.3.10", diff --git a/src/app.service.ts b/src/app.service.ts index 233ece1..927d7cc 100644 --- a/src/app.service.ts +++ b/src/app.service.ts @@ -3,6 +3,6 @@ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { - return 'Hello World!@'; + return 'Hello World!'; } } diff --git a/src/models/MiningJob.ts b/src/models/MiningJob.ts index 9a607e8..71b9f0d 100644 --- a/src/models/MiningJob.ts +++ b/src/models/MiningJob.ts @@ -14,7 +14,7 @@ export class MiningJob { public method: eResponseMethod.MINING_NOTIFY; public params: string[]; - public target: string; + public target: number; public merkleRoot: string; public job_id: string; // ID of the job. Use this ID while submitting share generated from this job. @@ -22,24 +22,26 @@ export class MiningJob { 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: string; // The hex-encoded block version. + public version: number; // The hex-encoded block version. public nbits: string; // The hex-encoded network difficulty required for the block. - public ntime: string; // Current ntime/ + 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; + constructor(blockTemplate: IBlockTemplate) { console.log(blockTemplate); this.job_id = randomUUID(); - this.target = blockTemplate.target; + this.target = Number(blockTemplate.target); this.prevhash = blockTemplate.previousblockhash; - this.version = blockTemplate.version.toString(); + this.version = blockTemplate.version; this.nbits = blockTemplate.bits; - this.ntime = Math.floor(new Date().getTime() / 1000).toString(); + this.ntime = Math.floor(new Date().getTime() / 1000); this.clean_jobs = false; const transactions = blockTemplate.transactions.map(tx => tx.hash); diff --git a/src/models/StratumV1Client.ts b/src/models/StratumV1Client.ts index 34009f9..187933a 100644 --- a/src/models/StratumV1Client.ts +++ b/src/models/StratumV1Client.ts @@ -1,16 +1,16 @@ import { plainToInstance } from 'class-transformer'; import { validate, ValidatorOptions } from 'class-validator'; import { Socket } from 'net'; -import { BehaviorSubject, combineLatest, concat, merge, Observable, takeUntil } from 'rxjs'; +import { BehaviorSubject, merge, Observable, takeUntil } from 'rxjs'; import { EasyUnsubscribe } from '../utils/AutoUnsubscribe'; import { eRequestMethod } from './enums/eRequestMethod'; +import { MiningJob } from './MiningJob'; import { AuthorizationMessage } from './stratum-messages/AuthorizationMessage'; import { ConfigurationMessage } from './stratum-messages/ConfigurationMessage'; import { MiningSubmitMessage } from './stratum-messages/MiningSubmitMessage'; import { SubscriptionMessage } from './stratum-messages/SubscriptionMessage'; import { SuggestDifficulty } from './stratum-messages/SuggestDifficultyMessage'; -import { MiningJob } from './MiningJob'; export class StratumV1Client extends EasyUnsubscribe { @@ -24,6 +24,7 @@ export class StratumV1Client extends EasyUnsubscribe { public onInitialized: BehaviorSubject = new BehaviorSubject(null); public localMiningJobEmitter: BehaviorSubject = new BehaviorSubject(null); + public blockFoundEmitter: BehaviorSubject = new BehaviorSubject(null); private currentJob: MiningJob; @@ -216,8 +217,11 @@ export class StratumV1Client extends EasyUnsubscribe { private handleMiningSubmission(submission: MiningSubmitMessage) { - const diff = submission.testNonceValue(this.currentJob, submission.nonce); - console.log(diff); + const networkDifficulty = 0; + const diff = submission.testNonceValue(this.currentJob, parseInt(submission.nonce, 16)); + if (networkDifficulty < diff) { + this.blockFoundEmitter.next(true); + } } // private miningNotify() { diff --git a/src/models/stratum-messages/MiningSubmitMessage.spec.ts b/src/models/stratum-messages/MiningSubmitMessage.spec.ts new file mode 100644 index 0000000..3658324 --- /dev/null +++ b/src/models/stratum-messages/MiningSubmitMessage.spec.ts @@ -0,0 +1,27 @@ +import { MiningJob } from '../MiningJob'; +import { MiningSubmitMessage } from './MiningSubmitMessage'; + +describe('MiningSubmitMessage', () => { + + + beforeEach(async () => { + + + + }); + + describe('root', () => { + + const value = new MiningSubmitMessage().testNonceValue({ + version: 0x20000004, + prevhash: "0c859545a3498373a57452fac22eb7113df2a465000543520000000000000000", + merkleRoot: "5bdc1968499c3393873edf8e07a1c3a50a97fc3a9d1a376bbf77087dd63778eb", + ntime: 0x647025b5, + target: 0x1705ae3a, + } as MiningJob, 0x0a029ed1); + + 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 f87af47..397788b 100644 --- a/src/models/stratum-messages/MiningSubmitMessage.ts +++ b/src/models/stratum-messages/MiningSubmitMessage.ts @@ -1,18 +1,17 @@ +import Big from 'big.js'; import { ArrayMaxSize, ArrayMinSize, IsArray } from 'class-validator'; - -import { eRequestMethod } from '../enums/eRequestMethod'; -import { StratumBaseMessage } from './StratumBaseMessage'; -import { MiningJob } from '../MiningJob'; import * as crypto from 'crypto'; +import { eRequestMethod } from '../enums/eRequestMethod'; +import { MiningJob } from '../MiningJob'; +import { StratumBaseMessage } from './StratumBaseMessage'; -const trueDiffOne = Number('26959535291011309493156476344723991336010898738574164086137773096960'); export class MiningSubmitMessage extends StratumBaseMessage { @IsArray() @ArrayMinSize(5) @ArrayMaxSize(5) - params: string[]; + public params: string[]; public userId: string; public jobId: string; @@ -43,48 +42,60 @@ export class MiningSubmitMessage extends StratumBaseMessage { } - testNonceValue(job: MiningJob, nonce: string): number { + testNonceValue(job: MiningJob, nonce: number, midstateIndex: number = 0): string { + const truediffone = Big('26959535291011309493156476344723991336010898738574164086137773096960'); + let s64: Big, ds: number; const header = Buffer.alloc(80); // TODO: Use the midstate hash instead of hashing the whole header // Copy data from job to header - - header.set(Buffer.from(job.version, 'hex').subarray(0, 4), 0); - header.set(Buffer.from(job.prevhash, 'hex').subarray(0, 32), 4); - header.set(Buffer.from(job.merkleRoot, 'hex').subarray(0, 32), 36); - header.set(Buffer.from(job.ntime).subarray(0, 4), 68); - header.set(Buffer.from(job.target, 'hex').subarray(0, 4), 72); - header.set(Buffer.from(nonce, 'hex').subarray(0, 4), 76); - - - const hashBuffer = crypto.createHash('sha256').update(header).digest(); - const hashResult = crypto.createHash('sha256').update(hashBuffer).digest(); - - const d64 = 1.0; - const s64 = this.littleEndianToDouble(hashResult); - const ds = d64 / s64; - - return ds; - } - - - private littleEndianToDouble(buffer: Buffer): number { - // Reverse the byte order to big-endian - const reversedBytes = Buffer.from(buffer).reverse(); - - // Convert the reversed bytes to a hexadecimal string - const hexString = `0x${reversedBytes.toString('hex')}`; - - // Interpret the hexadecimal string as a double - const doubleValue = Number(hexString); - - // Check for NaN and Infinity - if (!Number.isFinite(doubleValue)) { - throw new Error('Conversion resulted in NaN or Infinity'); + let rolledVersion = job.version; + for (let i = 0; i < midstateIndex; i++) { + rolledVersion = this.incrementBitmask(rolledVersion, job.versionMask); } - return doubleValue; + header.writeUInt32LE(rolledVersion, 0); + Buffer.from(job.prevhash, 'hex').copy(header, 4); + Buffer.from(job.merkleRoot, 'hex').copy(header, 36); + header.writeUInt32LE(job.ntime, 68); + header.writeUInt32LE(job.target, 72); + header.writeUInt32LE(nonce, 76); + + console.log(header); + + const hashBuffer: Buffer = crypto.createHash('sha256').update(header).digest(); + const hashResult: Buffer = crypto.createHash('sha256').update(hashBuffer).digest(); + + s64 = this.le256todouble(hashResult); + ds = truediffone.div(s64); + + return ds.toString(); + } + + + + private le256todouble(target: Buffer): Big { + + const bits192 = new Big(6277101735386680763835789423207666416102355444464034512896); + const bits128 = new Big(340282366920938463463374607431768211456); + const bits64 = new Big(18446744073709551616); + + const data64_3 = target.readBigUInt64LE(24); + const data64_2 = target.readBigUInt64LE(16); + const data64_1 = target.readBigUInt64LE(8); + const data64_0 = target.readBigUInt64LE(0); + + const dcut64 = new Big(data64_3).times(bits192) + .plus(new Big(data64_2).times(bits128)) + .plus(new Big(data64_1).times(bits64)) + .plus(new Big(data64_0)); + + return dcut64; + } + + public incrementBitmask(rolledVersion: number, versionMask: number) { + return (rolledVersion + 1) | versionMask; } } \ No newline at end of file