From 7b77bf80065d0e196da56929e486690fe7ac52ac Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Fri, 23 Jun 2023 13:55:18 -0400 Subject: [PATCH] segwit --- src/models/MiningJob.ts | 88 +++++++++++-------- src/models/StratumV1Client.ts | 27 +++++- .../stratum-messages/MiningSubmitMessage.ts | 5 +- 3 files changed, 77 insertions(+), 43 deletions(-) diff --git a/src/models/MiningJob.ts b/src/models/MiningJob.ts index eefe5d4..0496740 100644 --- a/src/models/MiningJob.ts +++ b/src/models/MiningJob.ts @@ -32,6 +32,7 @@ export class MiningJob { public versionMask: string; public tree: MerkleTree; + public coinbaseTransaction: bitcoinjs.Transaction; constructor(id: string, payoutInformation: AddressObject[], public blockTemplate: IBlockTemplate, public networkDifficulty: number, public clean_jobs: boolean) { @@ -45,76 +46,87 @@ export class MiningJob { this.ntime = Math.floor(new Date().getTime() / 1000); - const transactionFees = blockTemplate.transactions.reduce((pre, cur, i, arr) => { - return pre + cur.fee; - }, 0); + const allTransactions = blockTemplate.transactions.map(t => bitcoinjs.Transaction.fromHex(t.data)); + + this.coinbaseTransaction = this.createCoinbaseTransaction(payoutInformation, this.blockTemplate.height, this.blockTemplate.coinbasevalue); + allTransactions.unshift(this.coinbaseTransaction); + const witnessRootHash = bitcoinjs.Block.calculateMerkleRoot(allTransactions, true); - const { coinbasePart1, coinbasePart2 } = this.createCoinbaseTransaction(payoutInformation, blockTemplate.height, blockTemplate.coinbasevalue); - this.coinb1 = coinbasePart1; + //The commitment is recorded in a scriptPubKey of the coinbase transaction. It must be at least 38 bytes, with the first 6-byte of 0x6a24aa21a9ed, that is: + // 1-byte - OP_RETURN (0x6a) + // 1-byte - Push the following 36 bytes (0x24) + // 4-byte - Commitment header (0xaa21a9ed) + const segwitMagicBits = Buffer.from('aa21a9ed', 'hex'); + // 32-byte - Commitment hash: Double-SHA256(witness root hash|witness reserved value) + const merkleRoot = this.sha256(this.sha256(witnessRootHash)); + // 39th byte onwards: Optional data with no consensus meaning + this.coinbaseTransaction.outs[0].script = bitcoinjs.script.compile([bitcoinjs.opcodes.OP_RETURN, Buffer.concat([segwitMagicBits, merkleRoot])]); + + //@ts-ignore + const serializedTx = this.coinbaseTransaction.__toBuffer().toString('hex'); + + const blockHeightScript = `03${this.blockTemplate.height.toString(16).padStart(8, '0')}` + '00000000' + '00000000'; + const partOneIndex = serializedTx.indexOf(blockHeightScript) + blockHeightScript.length; + + const coinbasePart1 = serializedTx.slice(0, partOneIndex); + const coinbasePart2 = serializedTx.slice(partOneIndex); + this.coinb1 = coinbasePart1.slice(0, coinbasePart1.length - 16); this.coinb2 = coinbasePart2; - const coinbaseHash = this.sha256(this.coinb1 + this.coinb2).toString('hex'); - const coinbaseBuffer = Buffer.from(coinbaseHash, 'hex'); - // Calculate merkle branch - const transactionBuffers = blockTemplate.transactions.map(tx => Buffer.from(tx.hash, 'hex')); - transactionBuffers.unshift(coinbaseBuffer); + const transactionBuffers = allTransactions.map(tx => tx.getHash(false)); + this.tree = new MerkleTree(transactionBuffers, this.sha256, { isBitcoinTree: true }); const rootBuffer = this.tree.getRoot(); this.merkleRoot = rootBuffer.toString('hex'); - this.merkle_branch = this.tree.getProof(coinbaseBuffer).map(p => p.data.toString('hex')); + this.merkle_branch = this.tree.getProof(this.coinbaseTransaction.getHash(false)).map(p => p.data.toString('hex')); this.constructResponse(); } - private createCoinbaseTransaction(addresses: AddressObject[], blockHeight: number, reward: number): { coinbasePart1: string, coinbasePart2: string } { + private createCoinbaseTransaction(addresses: AddressObject[], blockHeight: number, reward: number): bitcoinjs.Transaction { // Part 1 - const blockHeightScript = `03${blockHeight.toString(16).padStart(8, '0')}`; - const outputIndex = 'ffffffff'; + const tx = new bitcoinjs.Transaction(); + // Set the version of the transaction + tx.version = 2; - const version = '01000000'; - const inputCount = '01'; - const fakeCoinbaseInput = '0000000000000000000000000000000000000000000000000000000000000000'; + const blockHeightScript = `03${blockHeight.toString(16).padStart(8, '0')}` + '00000000' + '00000000'; + // const inputScriptBytes = ((blockHeightScript.length + 16) / 2).toString(16).padStart(2, '0'); + // const OP_RETURN = '6a'; + // const inputScript = `${OP_RETURN}${inputScriptBytes}${blockHeightScript}` - const inputScriptBytes = ((blockHeightScript.length + 16) / 2).toString(16).padStart(2, '0'); + const inputScript = bitcoinjs.script.compile([bitcoinjs.opcodes.OP_RETURN, Buffer.from(blockHeightScript, 'hex')]) + // Add the coinbase input (input with no previous output) + tx.addInput(Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex'), 0xffffffff, 0xffffffff, inputScript); - const coinbasePart1 = version + inputCount + fakeCoinbaseInput + outputIndex + inputScriptBytes + blockHeightScript; + // Add an output + const recipientAddress = addresses[0].address; + + const scriptPubKey = bitcoinjs.payments.p2wpkh({ address: recipientAddress, network: bitcoinjs.networks.testnet }); + tx.addOutput(scriptPubKey.output, reward); - // Part 2 - const outputs = addresses - .map((addressObj) => { - const percentage = addressObj.percent / 100; - const satoshis = Math.floor(reward * percentage); - const satoshiBuff = Buffer.alloc(4); - satoshiBuff.writeUInt32LE(satoshis); - const littleEndianSatoshis = satoshiBuff.toString('hex').padEnd(16, '0'); - - const script = bitcoinjs.payments.p2wpkh({ address: addressObj.address }).output.toString('hex') // Convert address to hex - const scriptBytes = (script.length / 2).toString(16).padStart(2, '0'); - return littleEndianSatoshis + scriptBytes + script; - }) - .join(''); - const outputCountHex = addresses.length.toString(16).padStart(2, '0'); + const segwitWitnessReservedValue = Buffer.alloc(32, 0); - const sequence = 'ffffffff'; - const lockTime = '00000000'; + //and the coinbase's input's witness must consist of a single 32-byte array for the witness reserved value - const coinbasePart2 = sequence + outputCountHex + outputs + lockTime; + tx.ins[0].witness = [segwitWitnessReservedValue]; - return { coinbasePart1, coinbasePart2 }; + + + return tx; } private sha256(data) { diff --git a/src/models/StratumV1Client.ts b/src/models/StratumV1Client.ts index 4446329..3b0f2f3 100644 --- a/src/models/StratumV1Client.ts +++ b/src/models/StratumV1Client.ts @@ -1,4 +1,5 @@ import { Block, Transaction } from 'bitcoinjs-lib'; +import * as bitcoinjs from 'bitcoinjs-lib'; import { plainToInstance } from 'class-transformer'; import { validate, ValidatorOptions } from 'class-validator'; import * as crypto from 'crypto'; @@ -271,18 +272,36 @@ export class StratumV1Client extends EasyUnsubscribe { private constructBlockAndBroadcast(job: MiningJob, submission: MiningSubmitMessage) { const block = new Block(); - block.version = job.blockTemplate.version; + const blockHeightScript = `03${job.blockTemplate.height.toString(16).padStart(8, '0')}${job.id}${submission.extraNonce2}`; + + const inputScript = bitcoinjs.script.compile([bitcoinjs.opcodes.OP_RETURN, Buffer.from(blockHeightScript, 'hex')]); + job.coinbaseTransaction.ins[0].script = inputScript; + + + const versionMask = parseInt(submission.versionMask, 16); + let version = job.version; + if (versionMask !== undefined && versionMask != 0) { + version = (version ^ versionMask); + } + + block.version = version; block.prevHash = Buffer.from(job.prevhash, 'hex'); - block.merkleRoot = Buffer.from(job.merkleRoot, 'hex'); + block.timestamp = job.ntime; block.bits = job.nbits; block.nonce = parseInt(submission.nonce, 16); + block.transactions = job.blockTemplate.transactions.map(tx => { return Transaction.fromHex(tx.data); }); + block.transactions.unshift(job.coinbaseTransaction); + + block.merkleRoot = bitcoinjs.Block.calculateMerkleRoot(block.transactions, false); + block.witnessCommit = bitcoinjs.Block.calculateMerkleRoot(block.transactions, true); + + // const test1 = block.getWitnessCommit(); + // const test2 = block.checkTxRoots(); - const coinbaseTx = `${job.coinb1}${this.id}${submission.extraNonce2}${job.coinb2}`; - block.transactions.unshift(Transaction.fromHex(coinbaseTx)); const blockHex = block.toHex(false); this.bitcoinRpcService.SUBMIT_BLOCK(blockHex); diff --git a/src/models/stratum-messages/MiningSubmitMessage.ts b/src/models/stratum-messages/MiningSubmitMessage.ts index c1cc97c..1a1cdf9 100644 --- a/src/models/stratum-messages/MiningSubmitMessage.ts +++ b/src/models/stratum-messages/MiningSubmitMessage.ts @@ -74,6 +74,7 @@ export class MiningSubmitMessage extends StratumBaseMessage { const coinbaseTx = `${job.coinb1}${extraNonce}${extraNonce2}${job.coinb2}`; + const newRoot = this.calculateMerkleRootHash(coinbaseTx, job.merkle_branch) const truediffone = Big('26959535291011309493156476344723991336010898738574164086137773096960'); @@ -94,6 +95,8 @@ export class MiningSubmitMessage extends StratumBaseMessage { header.writeBigUint64LE(BigInt(job.nbits), 72); header.writeUInt32LE(nonce, 76); + console.log(header.toString('hex')) + const hashBuffer: Buffer = crypto.createHash('sha256').update(header).digest(); const hashResult: Buffer = crypto.createHash('sha256').update(hashBuffer).digest(); @@ -142,7 +145,7 @@ export class MiningSubmitMessage extends StratumBaseMessage { bothMerkles.set(newRoot); } - return bothMerkles; + return bothMerkles.subarray(0, 32) } private sha256(data: Buffer) {