diff --git a/package.json b/package.json index bc6a7f1..0c40833 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ "author": "", "license": "ISC", "dependencies": { + "@bitcoinerlab/secp256k1": "^1.1.1", + "asn1js": "^3.0.5", "bitcoinjs-lib": "^6.1.5", "ecpair": "^2.1.0", "lodash.clonedeep": "^4.5.0", diff --git a/src/constants.ts b/src/constants.ts index d385ca5..a51a333 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,2 +1,7 @@ -const DIFFICULTY = +export const DIFFICULTY = "0000ffff00000000000000000000000000000000000000000000000000000000"; + +export const WITNESS_RESERVED_VALUE = + "0000000000000000000000000000000000000000000000000000000000000000"; + +export const MAX_VALUE = 0xffffffff; diff --git a/src/features/block/coinbaseTransaction.ts b/src/features/block/coinbaseTransaction.ts index d163c4a..da8aa8b 100644 --- a/src/features/block/coinbaseTransaction.ts +++ b/src/features/block/coinbaseTransaction.ts @@ -1,48 +1,48 @@ -import { Transaction } from "../../types"; +// import { Transaction } from "../../types"; +import { MAX_VALUE, WITNESS_RESERVED_VALUE } from "../../constants"; import { compactSize } from "../encoding/compactSize"; +import { Input, Output, Transaction } from "../transaction"; -export const ZEROS = - "0000000000000000000000000000000000000000000000000000000000000000"; -const MAX_VALUE = 0xffffffff; -const height = 538403; +// export const ZEROS = +// "0000000000000000000000000000000000000000000000000000000000000000"; +// const MAX_VALUE = 0xffffffff; +// const height = 538403; const blockReward = 1250000000; -const coinbaseTemplate = { - version: 0, - locktime: 0, - vin: [ - { - txid: ZEROS, - vout: MAX_VALUE, - prevout: null, - scriptsig: "03233708", - scriptsig_asm: "OP_PUSHBYTES_3 233708", - witness: [ZEROS], - }, - ], - vout: [ - { - scriptpubkey: "76a914edf10a7fac6b32e24daa5305c723f3de58db1bc888ac", - scriptpubkey_asm: - "OP_DUP OP_HASH160 OP_PUSHBYTES_20 edf10a7fac6b32e24daa5305c723f3de58db1bc8 OP_EQUALVERIFY OP_CHECKSIG", - scriptpubkey_type: "p2pkh", - value: blockReward, - }, - { - //op return push 36 bytes 4 btyes commitment + hash256 of witness merkle root + zeros - scriptpubkey: "6a24aa21a9ed", // add the witness commitment later - scriptpubkey_type: "op_return", - value: 0, - }, - ], -} as unknown as Transaction; - export const generateCoinbaseTransaction = ( totalFee: number, commitmentHeader: string ) => { - coinbaseTemplate.vout[1].scriptpubkey = `6a24aa21a9ed${commitmentHeader}`; - coinbaseTemplate.vout[0].value = blockReward + totalFee; + const transaction = new Transaction(0, 0); + const input = new Input({ + txid: WITNESS_RESERVED_VALUE, + vout: MAX_VALUE, + prevout: null, + scriptsig: "03233708", //block number is 233708 + scriptsig_asm: "OP_PUSHBYTES_3 233708", + witness: [WITNESS_RESERVED_VALUE], + sequence: 0, + is_coinbase: true, + }); + + const output1 = new Output({ + scriptpubkey: "76a914edf10a7fac6b32e24daa5305c723f3de58db1bc888ac", + scriptpubkey_asm: + "OP_DUP OP_HASH160 OP_PUSHBYTES_20 edf10a7fac6b32e24daa5305c723f3de58db1bc8 OP_EQUALVERIFY OP_CHECKSIG", + scriptpubkey_type: "p2pkh", + value: blockReward + totalFee, + }); + + const output2 = new Output({ + scriptpubkey: "6a24aa21a9ed" + commitmentHeader, + scriptpubkey_asm: "OP_RETURN OP_PUSHBYTES_36 aa21a9ed" + commitmentHeader, + scriptpubkey_type: "op_return", + value: 0, + }); + + transaction.addInput(input); + transaction.addOutput(output1); + transaction.addOutput(output2); - return coinbaseTemplate; + return transaction; }; diff --git a/src/features/block/fee.ts b/src/features/block/fee.ts index 2856a32..7f9a46a 100644 --- a/src/features/block/fee.ts +++ b/src/features/block/fee.ts @@ -1,5 +1,4 @@ -import { Transaction } from "../../types"; -import { txWeight } from "../encoding/serializer"; +import { Transaction } from "../transaction"; export const totalFee = (txs: Transaction[]) => { let inputValues = 0; @@ -7,6 +6,7 @@ export const totalFee = (txs: Transaction[]) => { for (const tx of txs) { for (const input of tx.vin) { + if (!input.prevout) continue; inputValues += input.prevout.value; } for (const output of tx.vout) { @@ -20,6 +20,7 @@ export const totalFee = (txs: Transaction[]) => { export const feePerByte = (tx: Transaction) => { let fee = 0; for (const input of tx.vin) { + if (!input.prevout) continue; fee += input.prevout.value; } @@ -27,5 +28,5 @@ export const feePerByte = (tx: Transaction) => { fee -= output.value; } - return fee / txWeight(tx); + return fee / tx.weight; }; diff --git a/src/features/block/merkleRoot.ts b/src/features/block/merkleRoot.ts index b1c3864..dce49a4 100644 --- a/src/features/block/merkleRoot.ts +++ b/src/features/block/merkleRoot.ts @@ -1,6 +1,4 @@ -import { Transaction } from "../../types"; -import { reversify, sha256 } from "../../utils"; -import { txSerializer } from "../encoding/serializer"; +import { sha256 } from "../../utils"; export const merkleRoot = (txs: string[]) => { let curr = txs; diff --git a/src/features/block/mine.ts b/src/features/block/mine.ts index 59bad58..0b51f6b 100644 --- a/src/features/block/mine.ts +++ b/src/features/block/mine.ts @@ -1,9 +1,9 @@ import { reversify, sha256 } from "../../utils"; -import { Transaction } from "../../types"; +import { Transaction } from "../transaction"; import { merkleRoot } from "./merkleRoot"; -import { txSerializer } from "../encoding/serializer"; -import { ZEROS, generateCoinbaseTransaction } from "./coinbaseTransaction"; +import { generateCoinbaseTransaction } from "./coinbaseTransaction"; import { totalFee } from "./fee"; +import { DIFFICULTY, WITNESS_RESERVED_VALUE } from "../../constants"; export const mine = ( txs: Transaction[] @@ -16,27 +16,25 @@ export const mine = ( "0000ffff00000000000000000000000000000000000000000000000000000000"; const version = Buffer.alloc(4); version.writeInt32LE(4); - //make it the same as the difficulty - const witnessMerkleRootHash = reversify( - merkleRoot([ - ZEROS, //zeros are for the coinbase transaction - ...txs.map((tx) => - reversify(sha256(sha256(txSerializer(tx).serializedWTx))) - ), - ]) + // const witnessMerkleRootHash = merkleRoot([ + // ZEROS, //zeros are for the coinbase transaction + // ...txs.map((tx) => sha256(sha256(txSerializer(tx).serializedWTx))), + // ]); + const witnessMerkleRootHash = merkleRoot([ + WITNESS_RESERVED_VALUE, + ...txs.map((tx) => reversify(tx.wtxid)), + ]); + const commitmentHash = sha256( + sha256(witnessMerkleRootHash + WITNESS_RESERVED_VALUE) ); - const commitmentHash = sha256(sha256(witnessMerkleRootHash + ZEROS)); const fees = totalFee(txs); const coinbaseTransaction = generateCoinbaseTransaction(fees, commitmentHash); - const prevBlockHash = - "0000ffff00000000000000000000000000000000000000000000000000000000"; //make it the same as the difficulty + const prevBlockHash = DIFFICULTY; //make it the same as the difficulty const merkleRootHash = merkleRoot( - [coinbaseTransaction, ...txs].map((tx) => - sha256(sha256(txSerializer(tx).serializedTx)) - ) + [coinbaseTransaction, ...txs].map((tx) => reversify(tx.txid)) ); const time = Buffer.alloc(4); @@ -54,7 +52,6 @@ export const mine = ( "hex" )}${nonceBuf.toString("hex")}`; - // console.log(serializedBlock); const blockHash = reversify(sha256(sha256(serializedBlock))); if ( Buffer.from(difficulty, "hex").compare(Buffer.from(blockHash, "hex")) < 0 diff --git a/src/features/encoding/serializer.ts b/src/features/encoding/serializer.ts index 599ae83..37b578c 100644 --- a/src/features/encoding/serializer.ts +++ b/src/features/encoding/serializer.ts @@ -1,120 +1,268 @@ -import { Transaction, Input, Output } from "../../types"; +import { Transaction, Input, Output } from "../transaction"; import { reversify, sha256 } from "../../utils"; import { compactSize } from "./compactSize"; -import cloneDeep from "lodash.clonedeep"; -import { ECPairFactory } from "ecpair"; -import * as ecc from "tiny-secp256k1"; -import { bitcoin } from "ecpair/src/networks"; import { getNextNBytes } from "../script/utils"; -import { ZEROS } from "../block/coinbaseTransaction"; -const ECPair = ECPairFactory(ecc); +export class Serializer { + static serializeTx(tx: Transaction) { + let serializedTx = ""; -export const outputSerializer = (outTx: Output) => { - const amount = Buffer.alloc(8); - amount.writeBigInt64LE(BigInt(outTx.value), 0); - return `${amount.toString("hex")}${compactSize( - BigInt(outTx.scriptpubkey.length / 2) - ).toString("hex")}${outTx.scriptpubkey}`; -}; + const version = Buffer.alloc(4); + version.writeInt16LE(tx.version, 0); + serializedTx += version.toString("hex"); -export const inputSerializer = (inTx: Input) => { - let serializedInput = ""; + const numInputs = compactSize(BigInt(tx.vin.length)); + serializedTx += numInputs.toString("hex"); - const txHash = reversify(inTx.txid); - serializedInput += txHash; + for (let i = 0; i < tx.vin.length; i++) { + serializedTx += Serializer.serializeInput(tx.vin[i]); + } - const outputIndex = Buffer.alloc(4); - outputIndex.writeUint32LE(inTx.vout, 0); - serializedInput += outputIndex.toString("hex"); + const numOutputs = compactSize(BigInt(tx.vout.length)); + serializedTx += numOutputs.toString("hex"); + for (let i = 0; i < tx.vout.length; i++) { + serializedTx += Serializer.serializeOutput(tx.vout[i]); + } - const scriptSig = inTx.scriptsig; - const scriptSigSize = compactSize(BigInt(scriptSig.length / 2)); - const sequence = Buffer.alloc(4); - sequence.writeUint32LE(inTx.sequence, 0); + const locktime = Buffer.alloc(4); + locktime.writeUint32LE(tx.locktime, 0); + serializedTx += locktime.toString("hex"); - serializedInput += scriptSigSize.toString("hex"); - serializedInput += scriptSig; - serializedInput += sequence.toString("hex"); + return serializedTx; + } - return serializedInput; -}; + static serializeWTx(tx: Transaction) { + let serializedWTx = ""; -export const txSerializer = (tx: Transaction) => { - let serializedTx = ""; - let serializedWTx = ""; + const version = Buffer.alloc(4); + version.writeInt16LE(tx.version, 0); + serializedWTx += version.toString("hex"); - const version = Buffer.alloc(4); - version.writeInt16LE(tx.version, 0); - serializedTx += version.toString("hex"); - serializedWTx += version.toString("hex"); + serializedWTx += "0001"; - serializedWTx += "0001"; + const numInputs = compactSize(BigInt(tx.vin.length)); + serializedWTx += numInputs.toString("hex"); - const numInputs = compactSize(BigInt(tx.vin.length)); - serializedTx += numInputs.toString("hex"); - serializedWTx += numInputs.toString("hex"); + for (let i = 0; i < tx.vin.length; i++) { + serializedWTx += Serializer.serializeInput(tx.vin[i]); + } - for (let i = 0; i < tx.vin.length; i++) { - serializedTx += inputSerializer(tx.vin[i]); - serializedWTx += inputSerializer(tx.vin[i]); - } + const numOutputs = compactSize(BigInt(tx.vout.length)); + serializedWTx += numOutputs.toString("hex"); - const numOutputs = compactSize(BigInt(tx.vout.length)); - serializedTx += numOutputs.toString("hex"); - serializedWTx += numOutputs.toString("hex"); - for (let i = 0; i < tx.vout.length; i++) { - serializedTx += outputSerializer(tx.vout[i]); - serializedWTx += outputSerializer(tx.vout[i]); - } + for (let i = 0; i < tx.vout.length; i++) { + serializedWTx += Serializer.serializeOutput(tx.vout[i]); + } - let isWitness = false; - for (let i = 0; i < tx.vin.length; i++) { - if (!tx.vin[i].witness || tx.vin[i].witness.length === 0) { - serializedWTx += compactSize(BigInt(0)).toString("hex"); - } else { - isWitness = true; - serializedWTx += compactSize(BigInt(tx.vin[i].witness.length)).toString( - "hex" - ); - for (const witness of tx.vin[i].witness) { - serializedWTx += compactSize(BigInt(witness.length / 2)).toString( + for (let i = 0; i < tx.vin.length; i++) { + const input = tx.vin[i]; + if ( + !input.witness || + (input && input.witness !== undefined && input.witness.length === 0) + ) { + serializedWTx += compactSize(BigInt(0)).toString("hex"); + } else { + serializedWTx += compactSize(BigInt(input.witness.length)).toString( "hex" ); - serializedWTx += witness; + for (const witness of input.witness) { + serializedWTx += compactSize(BigInt(witness.length / 2)).toString( + "hex" + ); + serializedWTx += witness; + } } } + + const locktime = Buffer.alloc(4); + locktime.writeUint32LE(tx.locktime, 0); + serializedWTx += locktime.toString("hex"); + + return serializedWTx; } - const locktime = Buffer.alloc(4); - locktime.writeUint32LE(tx.locktime, 0); - serializedTx += locktime.toString("hex"); - serializedWTx += locktime.toString("hex"); + static serializeInput(input: Input) { + let serializedInput = ""; - return { - serializedTx, - serializedWTx: isWitness ? serializedWTx : serializedTx, - }; -}; + const txHash = reversify(input.txid); + serializedInput += txHash; -export const txWeight = (tx: Transaction) => { - return txSerializer(tx).serializedTx.length / 2; // divide by two cuz 2 hex chars are 1 byte and 1e6 as you cconsider it in mb -}; + const outputIndex = Buffer.alloc(4); + outputIndex.writeUint32LE(input.vout, 0); + serializedInput += outputIndex.toString("hex"); -const txForSigning = (tx: Transaction, input: number) => { - const txCopy = cloneDeep(tx); - for (let i = 0; i < txCopy.vin.length; i++) { - if (i === input) { - txCopy.vin[i].scriptsig = txCopy.vin[i].prevout.scriptpubkey; - } else { - txCopy.vin[i].scriptsig = ""; - } + const scriptSig = input.scriptsig; + const scriptSigSize = compactSize(BigInt(scriptSig.length / 2)); + const sequence = Buffer.alloc(4); + sequence.writeUint32LE(input.sequence, 0); + + serializedInput += scriptSigSize.toString("hex"); + serializedInput += scriptSig; + serializedInput += sequence.toString("hex"); + + return serializedInput; } - return txSerializer(txCopy).serializedTx + "01000000"; //force SIGHASH_ALL + static serializeOutput(output: Output) { + let serializedOutput = ""; + const amount = Buffer.alloc(8); + amount.writeBigInt64LE(BigInt(output.value), 0); + + serializedOutput += amount.toString("hex"); + serializedOutput += compactSize( + BigInt(output.scriptpubkey.length / 2) + ).toString("hex"); + serializedOutput += output.scriptpubkey; + + return serializedOutput; + } +} + +const weight = (val: Buffer | string, multiplier: number) => { + return val instanceof Buffer + ? (val.toString("hex").length / 2) * multiplier + : (val.length / 2) * multiplier; }; -const extractRSFromSignature = (derEncodedSignature: string) => { +// export const outputSerializer = (outTx: Output) => { +// const amount = Buffer.alloc(8); +// amount.writeBigInt64LE(BigInt(outTx.value), 0); +// return `${amount.toString("hex")}${compactSize( +// BigInt(outTx.scriptpubkey.length / 2) +// ).toString("hex")}${outTx.scriptpubkey}`; +// }; + +// export const inputSerializer = (inTx: Input) => { +// let serializedInput = ""; + +// const txHash = reversify(inTx.txid); +// serializedInput += txHash; + +// const outputIndex = Buffer.alloc(4); +// outputIndex.writeUint32LE(inTx.vout, 0); +// serializedInput += outputIndex.toString("hex"); + +// const scriptSig = inTx.scriptsig; +// const scriptSigSize = compactSize(BigInt(scriptSig.length / 2)); +// const sequence = Buffer.alloc(4); +// sequence.writeUint32LE(inTx.sequence, 0); + +// serializedInput += scriptSigSize.toString("hex"); +// serializedInput += scriptSig; +// serializedInput += sequence.toString("hex"); + +// return serializedInput; +// }; + +// export const txSerializer = (tx: Transaction) => { +// let serializedTx = ""; +// let serializedWTx = ""; +// let totalWeight = 0; + +// const version = Buffer.alloc(4); +// version.writeInt16LE(tx.version, 0); +// serializedTx += version.toString("hex"); +// serializedWTx += version.toString("hex"); +// totalWeight += weight(version, 4); + +// serializedWTx += "0001"; +// let witnessWeights = 2; + +// const numInputs = compactSize(BigInt(tx.vin.length)); +// serializedTx += numInputs.toString("hex"); +// serializedWTx += numInputs.toString("hex"); +// totalWeight += weight(numInputs, 4); + +// for (let i = 0; i < tx.vin.length; i++) { +// serializedTx += inputSerializer(tx.vin[i]); +// serializedWTx += inputSerializer(tx.vin[i]); +// totalWeight += weight(inputSerializer(tx.vin[i]), 4); +// } + +// const numOutputs = compactSize(BigInt(tx.vout.length)); +// serializedTx += numOutputs.toString("hex"); +// serializedWTx += numOutputs.toString("hex"); +// totalWeight += weight(numOutputs, 4); +// for (let i = 0; i < tx.vout.length; i++) { +// serializedTx += outputSerializer(tx.vout[i]); +// serializedWTx += outputSerializer(tx.vout[i]); +// totalWeight += weight(outputSerializer(tx.vout[i]), 4); +// } + +// let isWitness = false; +// for (let i = 0; i < tx.vin.length; i++) { +// if (!tx.vin[i].witness || tx.vin[i].witness.length === 0) { +// serializedWTx += compactSize(BigInt(0)).toString("hex"); +// witnessWeights += weight(compactSize(BigInt(0)), 1); +// } else { +// isWitness = true; +// serializedWTx += compactSize(BigInt(tx.vin[i].witness.length)).toString( +// "hex" +// ); +// witnessWeights += weight( +// compactSize(BigInt(tx.vin[i].witness.length)), +// 1 +// ); +// for (const witness of tx.vin[i].witness) { +// serializedWTx += compactSize(BigInt(witness.length / 2)).toString( +// "hex" +// ); +// witnessWeights += weight(compactSize(BigInt(witness.length / 2)), 1); +// serializedWTx += witness; +// witnessWeights += weight(witness, 1); +// } +// } +// } + +// const locktime = Buffer.alloc(4); +// locktime.writeUint32LE(tx.locktime, 0); +// serializedTx += locktime.toString("hex"); +// serializedWTx += locktime.toString("hex"); +// totalWeight += weight(locktime, 4); + +// if (isWitness) totalWeight += witnessWeights; //for marker and flag + +// return { +// serializedTx, +// serializedWTx: isWitness ? serializedWTx : serializedTx, +// weight: totalWeight, +// }; +// }; + +// export const txWeight = (tx: Transaction) => { +// // return txSerializer(tx).serializedWTx.length / 2; +// return txSerializer(tx).weight; +// }; + +// export const txForSigning = ( +// tx: Transaction, +// input: number, +// sighash: SigHash +// ) => { +// const txCopy = cloneDeep(tx); +// let hashcode = Buffer.alloc(4); +// switch (sighash) { +// case SigHash.ALL: +// for (let i = 0; i < txCopy.vin.length; i++) { +// hashcode.writeUint32LE(1, 0); +// if (i === input) { +// txCopy.vin[i].scriptsig = txCopy.vin[i].prevout.scriptpubkey; +// } else { +// txCopy.vin[i].scriptsig = ""; +// } +// } +// break; +// case SigHash.ALL_ANYONECANPAY: +// hashcode.writeUint32LE(0x81, 0); +// txCopy.vin = [txCopy.vin[input]]; +// txCopy.vin[0].scriptsig = txCopy.vin[0].prevout.scriptpubkey; +// break; +// } + +// return txSerializer(txCopy).serializedTx + hashcode.toString("hex"); +// }; + +export const extractRSFromSignature = (derEncodedSignature: string) => { let derEncodingScheme, signatureLength, r, @@ -123,7 +271,8 @@ const extractRSFromSignature = (derEncodedSignature: string) => { sLength, rest, prefix, - padding; + rPadding = "", + sPadding = ""; [derEncodingScheme, rest] = getNextNBytes(derEncodedSignature, 1); if (derEncodingScheme !== "30") throw new Error("Invalid DER encoding scheme"); @@ -131,84 +280,57 @@ const extractRSFromSignature = (derEncodedSignature: string) => { [prefix, rest] = getNextNBytes(rest, 1); [rLength, rest] = getNextNBytes(rest, 1); [r, rest] = getNextNBytes(rest, parseInt(rLength, 16)); - if (r.length === 66) [padding, r] = getNextNBytes(r, 1); //account for 00 padding + if (r.length === 66) [rPadding, r] = getNextNBytes(r, 1); //account for 00 padding + [prefix, rest] = getNextNBytes(rest, 1); [sLength, rest] = getNextNBytes(rest, 1); [s, rest] = getNextNBytes(rest, parseInt(sLength, 16)); - return r + s; -}; + if (s.length === 66) [sPadding, s] = getNextNBytes(s, 1); //account for 00 padding + return r.padStart(64, "0") + s.padStart(64, "0"); +}; const tx = { - version: 2, + version: 1, locktime: 0, vin: [ { - txid: "fb7fe37919a55dfa45a062f88bd3c7412b54de759115cb58c3b9b46ac5f7c925", - vout: 1, + txid: "3b7dc918e5671037effad7848727da3d3bf302b05f5ded9bec89449460473bbb", + vout: 16, prevout: { - scriptpubkey: "76a914286eb663201959fb12eff504329080e4c56ae28788ac", + scriptpubkey: "0014f8d9f2203c6f0773983392a487d45c0c818f9573", scriptpubkey_asm: - "OP_DUP OP_HASH160 OP_PUSHBYTES_20 286eb663201959fb12eff504329080e4c56ae287 OP_EQUALVERIFY OP_CHECKSIG", - scriptpubkey_type: "p2pkh", - scriptpubkey_address: "14gnf7L2DjBYKFuWb6iftBoWE9hmAoFbcF", - value: 433833, + "OP_0 OP_PUSHBYTES_20 f8d9f2203c6f0773983392a487d45c0c818f9573", + scriptpubkey_type: "v0_p2wpkh", + scriptpubkey_address: "bc1qlrvlygpudurh8xpnj2jg04zupjqcl9tnk5np40", + value: 37079526, }, - scriptsig: - "4830450221008f619822a97841ffd26eee942d41c1c4704022af2dd42600f006336ce686353a0220659476204210b21d605baab00bef7005ff30e878e911dc99413edb6c1e022acd012102c371793f2e19d1652408efef67704a2e9953a43a9dd54360d56fc93277a5667d", - scriptsig_asm: - "OP_PUSHBYTES_72 30450221008f619822a97841ffd26eee942d41c1c4704022af2dd42600f006336ce686353a0220659476204210b21d605baab00bef7005ff30e878e911dc99413edb6c1e022acd01 OP_PUSHBYTES_33 02c371793f2e19d1652408efef67704a2e9953a43a9dd54360d56fc93277a5667d", + scriptsig: "", + scriptsig_asm: "", + witness: [ + "30440220780ad409b4d13eb1882aaf2e7a53a206734aa302279d6859e254a7f0a7633556022011fd0cbdf5d4374513ef60f850b7059c6a093ab9e46beb002505b7cba0623cf301", + "022bf8c45da789f695d59f93983c813ec205203056e19ec5d3fbefa809af67e2ec", + ], is_coinbase: false, sequence: 4294967295, }, ], vout: [ { - scriptpubkey: "76a9141ef7874d338d24ecf6577e6eadeeee6cd579c67188ac", + scriptpubkey: "76a9146085312a9c500ff9cc35b571b0a1e5efb7fb9f1688ac", scriptpubkey_asm: - "OP_DUP OP_HASH160 OP_PUSHBYTES_20 1ef7874d338d24ecf6577e6eadeeee6cd579c671 OP_EQUALVERIFY OP_CHECKSIG", + "OP_DUP OP_HASH160 OP_PUSHBYTES_20 6085312a9c500ff9cc35b571b0a1e5efb7fb9f16 OP_EQUALVERIFY OP_CHECKSIG", scriptpubkey_type: "p2pkh", - scriptpubkey_address: "13pjoLcRKqhzPCbJgYW77LSFCcuwmHN2qA", - value: 387156, + scriptpubkey_address: "19oMRmCWMYuhnP5W61ABrjjxHc6RphZh11", + value: 100000, }, { - scriptpubkey: "76a9142e391b6c47778d35586b1f4154cbc6b06dc9840c88ac", + scriptpubkey: "0014ad4cc1cc859c57477bf90d0f944360d90a3998bf", scriptpubkey_asm: - "OP_DUP OP_HASH160 OP_PUSHBYTES_20 2e391b6c47778d35586b1f4154cbc6b06dc9840c OP_EQUALVERIFY OP_CHECKSIG", - scriptpubkey_type: "p2pkh", - scriptpubkey_address: "15DQVhQ7PU6VPsTtvwLxfDsTP4P6A3Z5vP", - value: 37320, + "OP_0 OP_PUSHBYTES_20 ad4cc1cc859c57477bf90d0f944360d90a3998bf", + scriptpubkey_type: "v0_p2wpkh", + scriptpubkey_address: "bc1q44xvrny9n3t5w7lep58egsmqmy9rnx9lt6u0tc", + value: 36977942, }, ], } as unknown as Transaction; - - -const txToBeSigned = txForSigning(tx, 0); -const hash = sha256(sha256(txToBeSigned)); - -const pubkey = ECPair.fromPublicKey( - Buffer.from( - "02c371793f2e19d1652408efef67704a2e9953a43a9dd54360d56fc93277a5667d", - "hex" - ), - { compressed: false, network: bitcoin } -); - -console.log( - extractRSFromSignature( - "30450221008f619822a97841ffd26eee942d41c1c4704022af2dd42600f006336ce686353a0220659476204210b21d605baab00bef7005ff30e878e911dc99413edb6c1e022acd01" - ) -); - -const res = pubkey.verify( - Buffer.from(hash, "hex"), - Buffer.from( - extractRSFromSignature( - //extract r, s from DER encoded ECDSA signature - "30450221008f619822a97841ffd26eee942d41c1c4704022af2dd42600f006336ce686353a0220659476204210b21d605baab00bef7005ff30e878e911dc99413edb6c1e022acd01" - ), - "hex" - ) -); - -console.log(res); diff --git a/src/features/encodingSandbox/p2sh.ts b/src/features/encodingSandbox/p2sh.ts deleted file mode 100644 index 77aa4f6..0000000 --- a/src/features/encodingSandbox/p2sh.ts +++ /dev/null @@ -1,38 +0,0 @@ -import * as bitcoin from "bitcoinjs-lib"; - -import { alice } from "./wallets.json"; -import { text } from "stream/consumers"; -import { reversify } from "../../utils"; - -const network = bitcoin.networks.regtest; - -const redeemScript = bitcoin.script.compile([ - bitcoin.opcodes.OP_ADD, - bitcoin.opcodes.OP_5, - bitcoin.opcodes.OP_EQUAL, -]); -const p2sh = bitcoin.payments.p2sh({ - redeem: { output: redeemScript, network }, - network, -}); - -const transaction = new bitcoin.Transaction(); -transaction.version = 2; -transaction.addInput( - Buffer.from( - reversify( - "82f39b0d951d2a604568eabe47d6013a4d83dcf9c476c30c4d3ffafb72fbb21f" - ), - "hex" - ), - 0 -); - -transaction.addOutput( - bitcoin.address.toOutputScript(alice[1].p2wpkh!, network), - 5000 -); - -const sighash = bitcoin.Transaction.SIGHASH_ALL; - -// transaction.hashForSignature(); diff --git a/src/features/encodingSandbox/wallets.json b/src/features/encodingSandbox/wallets.json deleted file mode 100644 index 02fdf6d..0000000 --- a/src/features/encodingSandbox/wallets.json +++ /dev/null @@ -1,314 +0,0 @@ -{ - "alice": [ - { - "entropy": "182301471f6892728ae56bb95b54396e", - "mnemonic": "blouse blossom fade disagree matrix deer clog pulp rich survey atom tackle", - "seed": "91a60250b6a45c07cdb3e0394cc2ebd1bb91bd2628c586a3131ead9c8c0d407ba06c8b4bea6a6a475e06ea312241de050fc9c88b651ee3ddd5e1379de97b1f32", - "xprivMaster": "tprv8ZgxMBicQKsPemXDEPPcJwAkrgszB75x5Lv99sqqifnLgcqrHBoXrTKbeQAMncrUwV6xwqZmhJXzKzQZuqcETEE2sdvNKYLQnZAKAGp7CRs", - "privKeyMaster": "3db7641394f017746d9f38125eb1e84b6ee8db0e1939949e97802904fc72bdb0", - "wifMaster": "cPefpopKGSrh6jh7scQt8jfq9CEWCNGhruNZ61R5BGqaqVAtgVp6", - "xpubMaster": "tpubD6NzVbkrYhZ4YEZ1834CiLpsRiPvLSGreeWvSPt98wajX76cuad82wwTpZWRn74XpBWWG8s1F2xwjy4d8fZbRE8NrTK4n26a5uChTHhgpf2", - "pubKeyMaster": "0379b9bbb9fff8a286dddcda0ec3fc1172b3320f0a8233032d3071ea70bfab95c2", - "pubKeyFingerprintMaster": "96a1f656" - }, - { - "xpriv": "tprv8fdMUVqCQ5pAQLj3ReLEQevoNeni1eAqCTrbgLKE1cJt7iN3baed3DmGWpR7NRssvdBE8qqQVLVGaWaZ37A4GCvCzrtQzuVjMqT7dydZf7C", - "privKey": "4dce80aad60aee8ea17c307ae00baa6cff261b780af68b86eedae36ba657dd81", - "wif": "cQBwuzEBYQrbWKFZZFpgitRpdDDxUrT1nzvhDWhxMmFtWdRnrCSm", - "xpub": "tpubDCKPcusSYTVqHokqKHzpp4auwgJeAyMjmmTNxrMXRt7GxCcpDyUDDiP8gybjM2kdkZAqh4z9n7QRDeK8mpsmKymY59KLrg9JPbyjTMfTJQY", - "pubKey": "03745c9aceb84dcdeddf2c3cdc1edb0b0b5af2f9bf85612d73fa6394758eaee35d", - "pubKeyHash": "fb8820f35effa054399540b8ca86040d8ddaa4d5", - "pubKeyFingerprint": "fb8820f3", - "p2pkh": "n4SvybJicv79X1Uc4o3fYXWGwXadA53FSq", - "p2sh-p2wpkh": "2MzFvFvnhFskGnVQpUr1ZPr4wYWLwf211s6", - "p2wpkh": "bcrt1qlwyzpu67l7s9gwv4gzuv4psypkxa4fx4ggs05g", - "path": "m/0'/0'/0'" - }, - { - "xpriv": "tprv8fdMUVqCQ5pAUFBkXM8X4JqeSDhTi1mrmKQcxZW1XeGCfqqhREeWZXP2NoMrjYcfqtZWGTdrLTAxNBhRH7kHYuMuvn2W7kR8sm5b5fyXU9B", - "privKey": "19ce51bca110b5790a1d1abc93783a3694c00e5c5148620cd6f27fac611c3f09", - "wif": "cNSs88aArgZeHrq7wpq8B56k1LGi4BVbSHqzYDic9ztju6TruHM5", - "xpub": "tpubDCKPcusSYTVqMiDYQzo7TiVm1FDPsLxmLd1QF5YJwv4bWL6U3dU6k1ztYwTsNzCkqaxkHmLsnGY25JoLgzsyzcUN77RBMLgjXGnGMmtV3ye", - "pubKey": "02b233055e060a554201d227a0b9d019cf24468ab1a7d9446c423a198cfbf956d5", - "pubKeyHash": "0b85d9b75f55ad9d42ea2ae9a245568ca34932f6", - "pubKeyFingerprint": "0b85d9b7", - "p2pkh": "mgZt5Fqzszdwf8hDgZt3mUf7js611aKRPc", - "p2sh-p2wpkh": "2N95xbaytPcRBwi2HmUTumE7YSfLBk8po99", - "p2wpkh": "bcrt1qpwzand6l2kke6sh29t56y32k3j35jvhkrn9s5l", - "path": "m/0'/0'/1'" - }, - { - "xpriv": "tprv8fdMUVqCQ5pAVpmNgQycAZx5WdrxPqVbNaji2Sos97Yg2WE1WWGMGFtBQisYYVxfLN4jGFpFrFLCcoA9bECvkrL8Zb2Ymg2sNA6tTup1mp5", - "privKey": "b9ede4ac299cb7473f2516ed2dbc1a3ec6e8cf9cdadeac00c938fb174d6662d5", - "wif": "cTp88sXB3ZDrWNnD1BtShjREFrw8eCs87bZncrZitryhFbiT2mmQ", - "xpub": "tpubDCKPcusSYTVqPHoAa4eCZycC5fNtZAgVwtLVJxrAZPM4rzUn8u5wSkW3apf4pKfn7ABiEc7e3hwKEiJMpDHq9v8qHRXL1xzHi6bS1KoGeJg", - "pubKey": "0220f2d0315bf811009bf82069dc6be7225cab598d0f3504695539d631711b6252", - "pubKeyHash": "f1c63bbd3d0a20865c43406fcfc09ad953b23aa0", - "pubKeyFingerprint": "f1c63bbd", - "p2pkh": "n3ZLcnCtfRucM4WLnXqukm9bTdb1PWeETk", - "p2sh-p2wpkh": "2NC9jSfJyLLAKQgzxSJ7QGrq4X3Wgh34kCb", - "p2wpkh": "bcrt1q78rrh0fapgsgvhzrgphulsy6m9fmyw4q2ehuh6", - "path": "m/0'/0'/2'" - } - ], - "bob": [ - { - "entropy": "28c8b37e1462a460fafa440d3ec66d29", - "mnemonic": "churn easily test churn clean corn typical embrace artwork wage opera fame", - "seed": "756cc32ffb263732758726f6c1e5a0b3a2a7aa76556567946f0277634f6f9184f33805a1bacc067089c831ee9c6742d8d391a1c08129775756afc36c3b8767c2", - "xprivMaster": "tprv8ZgxMBicQKsPe58FjHQLEzn1FBrxyHPUhQtpayjMWssUfraCxP5GbAcZdwnPfKkQcSA3jWSCGC1WH5An9piEJho6cpRXS9fAWsXHUMYaQzA", - "privKeyMaster": "e5ae5079523415a4290b0c02615732a50fdf84f8d1100bf2f5c8f02e1de6cce0", - "wifMaster": "cVHAtbY8yTpRaFsPwdvRtNMNeTmVQP7LLW7FD53Mbjan4c55oS62", - "xpubMaster": "tpubD6NzVbkrYhZ4XYA3cw4veQS7pDNu8caPGiVbsVmew9fsWLpyamtrmfERp5nTQkGidxAEF5aqGX7QVr7RtQUuUMMNznzdmY7zGmtpaCaKtxP", - "pubKeyMaster": "037092709cc49343652384627fee5c9db3c3680d699d989dfb46a6fd28a1ff8fb2", - "pubKeyFingerprintMaster": "42d18d1f" - }, - { - "xpriv": "tprv8h4mmmehwB5GKzoVGQxqnDNGqbqgsXga2hh4iQ7MmiHA5bd6qDWzqAzReUe3mcH4eHRLcuF7YmHz8ctGpc69wcZL7QoXwjAEc9GRLfWeFZt", - "privKey": "1d9944b5497b6b9ac6f52ac3bc077bc01610b8401d799eaa35e83b44ed858139", - "wif": "cNaEjitvA19JZxWAFyCFMsm16TvGEmVAW3AkPnVr8E9vgwdZWMGV", - "xpub": "tpubDDkovBgx5YkwDTqHA4dSBd2PQdMd2rsUc1Hqzv9fBz5Yv5ssTcLb1fcHpcKqHUpG9cHMZDSpeWcq3XJLvHDJeW91aFuuR3y96dHKZkdw1X2", - "pubKey": "027efbabf425077cdbceb73f6681c7ebe2ade74a65ea57ebcf0c42364d3822c590", - "pubKeyHash": "03f338e0f898ef40f1eb6734dc1f5aa72a4161f8", - "pubKeyFingerprint": "03f338e0", - "p2pkh": "mfsqh18UT3XjpJ8yVeiM7YX1mLxq5REG9d", - "p2sh-p2wpkh": "2N8QRLVT9VTa5ryoqSbGhGqq1x3FKp9SfoJ", - "p2wpkh": "bcrt1qq0en3c8cnrh5pu0tvu6dc8665u4yzc0cw9nweq", - "path": "m/0'/0'/0'" - }, - { - "xpriv": "tprv8h4mmmehwB5GNHiAy8Q4xn7rTT67amTTHdsm3myMcPhXvxcnapcHNhKAWVPmxvAVLs2TFQUQSjw3vzHiVMp1gmb9GPohqAgPJZa9vp97HV5", - "privKey": "03993d9ca5b282eea78ac41afa713be51dad940b9976c073ab190ee80d0a239d", - "wif": "cMhhNRoKiJP37jwwDjoXBhMRk8aEiTTe7fLbFNUc3HwD4jLciNfd", - "xpub": "tpubDDkovBgx5YkwFkjxrn4fNBmy2Uc3k6eMrwUYLJ1f2fVvmSsZDDRsZBw2gdxPy54XpMUPCKruAaUsGa29X16mcYX6n7aK1uvK37EHUpd7cCb", - "pubKey": "02d8698e38dc413e175c60290b223c0cf175d15f22c889bc98849e81548e1a3168", - "pubKeyHash": "1bf41b0d5729e00b36bf0237a84d0b9746fcc1cd", - "pubKeyFingerprint": "1bf41b0d", - "p2pkh": "mi4kxzYMgjqdqjvfLkHGPi7R1ZrLMx2BH7", - "p2sh-p2wpkh": "2N6j5eEZzeouYMkHLXU5vU4BorXPhAR43NZ", - "p2wpkh": "bcrt1qr06pkr2h98sqkd4lqgm6sngtjar0eswdrpttw7", - "path": "m/0'/0'/1'" - }, - { - "xpriv": "tprv8h4mmmehwB5GS36HRJyanaa9Z7DLVry1Z8sp9vDett8df4JiNVUR3oa6uu4S1LSjXmMWHcEbCGYmtddYFyZzTuAa2ofRSm1cWcdZqhNca7w", - "privKey": "9d1dc4bd64a510f1ec30ce1712bc50b2824eabfcae0fcd79976b2110df7ddfbd", - "wif": "cSr7dfYATGQzMaS66caTGX3n2owEr5YcMUdmHSc3prj8zfusFz4s", - "xpub": "tpubDDkovBgx5YkwKW85JxeBBzEG88jGfC9v8SUbSSFxK9w2VYZUztJ1EJBy62ptW9urcCfT74q2kKyBMvoejdks1EzRvZHdYzxbrC8ATcXimvk", - "pubKey": "03091bf7a5f90630c7c6c10d68d5555917668d307565ed7ba458e69d137b33f86b", - "pubKeyHash": "8236b4b6e79b48df5ca2617c6bb73fa281a83e07", - "pubKeyFingerprint": "8236b4b6", - "p2pkh": "msPTg2SY8YasMCcCxgA4LocqdxP9SDz8uu", - "p2sh-p2wpkh": "2NEBdHDRNphUosaHENxyxs3Qp498NHgVtUz", - "p2wpkh": "bcrt1qsgmtfdh8ndyd7h9zv97xhdel52q6s0s8597nzc", - "path": "m/0'/0'/2'" - } - ], - "carol": [ - { - "entropy": "61628dbe355f4675d895d399b984aaf4", - "mnemonic": "gesture behave hurdle height virtual depend girl risk often slow click trial", - "seed": "2c335b239ca3f9f1b4ca0a3026832214ca6209877009e18b90ab5b9f54706161e3109e98b46fc2a78984503467c1899ae0690cfd89112ed0cee5f9b9c7ab84c5", - "xprivMaster": "tprv8ZgxMBicQKsPeDhXotfwyNcbMKXGkqErJZ5eDgsfSpJ31fgry34VBqjnyCSVgRkGHms78MN3EHqp91s2Crzr4aFHm2taEiKWRQk6jAde4Zm", - "privKeyMaster": "2d7d443d24cfe1c5c86669dd0ae28c63812acfc1d9ff08943ae5ecde66f96135", - "wifMaster": "cP78KGzMDDqXcapTjFjakbBAqgyW6q1T8GgTMCbcd22mDS6WNrS3", - "xpubMaster": "tpubD6NzVbkrYhZ4XgjKhYLYNnGhvM3CvARksrgRWCuxs66Rr9wdbRt5NLMf9NcQBbrjbbVYUKNrQwtikYwDMsNGxerTcSrJEcS5sehkxzZJd9m", - "pubKeyMaster": "03d61decee15d4cdae07e0c665cf26e54846272d0791a953dfd426f607280bcfe4", - "pubKeyFingerprintMaster": "8d3c2e13" - }, - { - "xpriv": "tprv8gwH3MfJRRBk2RD4WUeMz5aYttG3NQpCL5W4a97pdyQW5DTCukxA9Bi21vvaB1ys1t89ywak4CdKzzRRbxPhwmkQZHiVkJFmNb5L9bDdG7Y", - "privKey": "5dd33245e949acf42a2d066a498a4060faf87a154d8889a3fbedcf6df2f10ef8", - "wif": "cQj5thuudcupEQo5WTYqVzs6ZtPGkdfCepNUpgJe6VAFeKFfhMtW", - "xpub": "tpubDDdKBmhYZnsQutErQ8JxPVEfTumyXk16uP6qrfA84FCtuhhyY9mkKgKtC3biozuUVov2GXNszmKPVMTbg7ftPY3RBhDw897S7eHrDjzX4vJ", - "pubKey": "023a11cfcedb993ff2e7523f92e359c4454072a66d42e8b74b4b27a8a1258abddd", - "pubKeyHash": "1053863792b523fff2252ea687da9ebcd402ead1", - "pubKeyFingerprint": "10538637", - "p2pkh": "mh1HAVWhKkzcvF41MNRKfakVvPV2sfaf3R", - "p2sh-p2wpkh": "2MyfuZkCtYeSEA4LjJAkkxW6zxdyhKnVyBX", - "p2wpkh": "bcrt1qzpfcvdujk53llu3996ng0k57hn2q96k3dwmytw", - "path": "m/0'/0'/0'" - }, - { - "xpriv": "tprv8gwH3MfJRRBk3m5Ym1CCMg8ixcr2CvBQsFPwPgK4rbKHLPAR5g32WFoeAxuMiAWfjff7e7651QB4ZHSaSVLYGQ7P1g3nVtqemqmvnsnNDQ6", - "privKey": "2b8fba1aa3e324adda8f828aeafb4e5197090deb8b7fdf6f528688a832d80158", - "wif": "cP3NxUDcpQGmSEftJ9VVznpMXh8AHkyj9qczSUz2HHjweK2PjcsJ", - "xpub": "tpubDDdKBmhYZnsQwE7Leernm5nqXeMxNFNKSYzigCMNGs7gAsRBi4rcgkRWM9C5zPjEv4JMwNwV5dCCkK56vUo7a7tBcEJ6pBjYDC4EHLYp5Dm", - "pubKey": "03e3aee22cc50638083afa8bd60da82cc351588619610566ca28cc50620e3f8cd9", - "pubKeyHash": "ed67278516aa8e224479150d8648ca2e0a2c0e9a", - "pubKeyFingerprint": "ed672785", - "p2pkh": "n3AE1ou9qaJvM8yyhEKg8ktDKYY2aJbGcK", - "p2sh-p2wpkh": "2MttMce5bYsZu22bFvXCKcosef9aJ4Bg36s", - "p2wpkh": "bcrt1qa4nj0pgk428zy3rez5xcvjx29c9zcr5654qqzm", - "path": "m/0'/0'/1'" - }, - { - "xpriv": "tprv8gwH3MfJRRBk7Lo7UufdydnNpj1HTQtEAEoG8DWhnakNEkwGLzEmp8Hcev2nFbWgNGj4iDE676VgCHnbi6rzmgfJky8aqm6uXJXnvFbGRsm", - "privKey": "5ec8abddc483200a96ac9f909d30f19fb7a258548888fdc45b85f1840cd8e762", - "wif": "cQkx12ymQJW4yiqRUefqFrYZR6oxHrNBoD7SBcuLTRTRAneFKgTH", - "xpub": "tpubDDdKBmhYZnsQzopuNZLEP3SVPkXDck58jYQ3QjZ1CrYm5FC2yP4MzcuUq5sGGaui58tvE7D7mFyYuKPdgUsNTiVWdoFT9Tg2nMVqUEN8Kdf", - "pubKey": "03db4f9fadddee79aa79591a55548bbb74018ec82460718bba4e46746d5b5e4d32", - "pubKeyHash": "9c936f4d8104d3fe2749497d53e01eee9e074bd3", - "pubKeyFingerprint": "9c936f4d", - "p2pkh": "munrKvk5qGsG63be82rBk34hJqqSnSHWEk", - "p2sh-p2wpkh": "2Mt8ZpgtsnRnY3Lxed4KTExCvJxgePYwuPy", - "p2wpkh": "bcrt1qnjfk7nvpqnfluf6ff9748cq7a60qwj7n99m3l6", - "path": "m/0'/0'/2'" - } - ], - "dave": [ - { - "entropy": "6dc790b775c765abbbd981c0cdbbce9e", - "mnemonic": "horse develop column twist iron still urge coral school horse victory differ", - "seed": "68dff1e20671ca63775bbbb4a939394044ba598c844df10e8e2ed1ecd8f3816f1aee229ab4c53abb59606670c17e1fe920e95dae002b1c6204679ca6842776bf", - "xprivMaster": "tprv8ZgxMBicQKsPdqaJLU6VZJ5jDnsQYjY1yzgWovRFziENRjq4YmwNuTTgGrxLivQmTRbVTpNdgyNMwaE2C8DM4D87RGiPkwQy1jkEWN25GzG", - "privKeyMaster": "d981358d619effdf2f11580f8a4f9b543b8d6b17d4a32dc866d576b2d4dc0a52", - "wifMaster": "cUsW6L1KQiZMLdvVEf9P7etAzTmPrAoQAwfNWCzmQoCRYEPoxjur", - "xpubMaster": "tpubD6NzVbkrYhZ4XJc6E7m5xhjqnpPLi4ivZJHJ6STZQz2mGE5qBAky5x5YT1wm7MXDUjUm4bRWdsWnV35gikapLbQaczSbh9MGYEXft8VLrhq", - "pubKeyMaster": "03e6a1179354dfb1e938e7fdadcb20fa4cd36fe726fe31e723db6197f250699a52", - "pubKeyFingerprintMaster": "56079739" - }, - { - "xpriv": "tprv8g4usdMN5GeR2oru9nZjYXAqNCCG42jt4aYpNLfY2qYFQBUQFnr83KLVjPKvy7Tjfao1Fytb61eJvMbrhF9Z3g14bZNzR84NmB2xLhs7ynE", - "privKey": "22958827ca160b69dc183eb77c340cc9ce3e72045954fd16ad2ffe54979940e9", - "wif": "cNjvpAG2cEqnD8WUb9mpGyqYAczBKHZR5Gj7hWPd9CJGxVoWU1K1", - "xpub": "tpubDCkx23PcDeL5vGth3SEKwvpwwDiCDMvndt9berhqT7LeEfjAtBfiDoxMuXnaZQyTyEtVWPJFmhqgEZzg1Me5Fbrbpz7WyBCibmZUxRJWjXc", - "pubKey": "02e9d617f38f8c3ab9a6bde36ce991bafb295d7adba457699f8620c8160ec9e87a", - "pubKeyHash": "9838d506490d8039a446256c1a20a719d873e9c8", - "pubKeyFingerprint": "9838d506", - "p2pkh": "muPq5yf84ot8JE7FWvaXfqG3YZGpZx9Zg7", - "p2sh-p2wpkh": "2NASxJxnyuV5NgurasHoa6bfAfE8aZwz41T", - "p2wpkh": "bcrt1qnqud2pjfpkqrnfzxy4kp5g98r8v886wgvs9e7r", - "path": "m/0'/0'/0'" - }, - { - "xpriv": "tprv8g4usdMN5GeR5EXkQ2MyjEyQo5DUayML679ctea3regfe8qdUAom2kuHsTwFwaKcNT3n6rvSXrLsuNgtrmSBKEHqBdjS3pFaFfPnevVTi1N", - "privKey": "7e1d9149e1d3e00659f2977a91d9b16fd17e5af51c3b00327ffb2ee8462cd80b", - "wif": "cRorU4b9uwAYuBMdHtLYrfX73hbYGPdedgMQHUJ8Vffz53hDmxn1", - "xpub": "tpubDCkx23PcDeL5xhZYHg2a8edXN6jQkJYEfQkQBAcMGvV4Ud6Q6ZdMDFXA3d8NLQvRdypwYJT2NS7LZFtU8gzrSQqxu4p6bkkrhf2ubHyLBHg", - "pubKey": "03a5c879a6456564f3a09374ab8027e3014517a1a54bceeddc3e29916602b2932b", - "pubKeyHash": "6fccd5594854cca3368a2ebc171a0cdd5a79c7b8", - "pubKeyFingerprint": "6fccd559", - "p2pkh": "mqi6gRN8Faaaf6C9YGgKXXNDEuFiRNTCUM", - "p2sh-p2wpkh": "2MtjZ41f2ELewgXu1FvY4cxEV4ezEU2Y6E7", - "p2wpkh": "bcrt1qdlxd2k2g2nx2xd52967pwxsvm4d8n3acvwg04s", - "path": "m/0'/0'/1'" - }, - { - "xpriv": "tprv8g4usdMN5GeR6bJZq18bgk8BgNqmDQvSwdzQP3wcjwvuq1ktWQxip3RjAsoKty8SWCCgGFk8nadJrGd5nfHWicpCQKRTJMorRXrPWHREz1Z", - "privKey": "543af9d237d3b71d7dd20390d372914c6416709b094bf93849942631814edbfa", - "wif": "cQQS9rEkW5Gi3TWCysdGGaKSZdQKR7qbBStxQtcA8mb7xipGGq45", - "xpub": "tpubDCkx23PcDeL5z4LMieoC69nJFQMhNk7MWwbBfZyvADjJfW1f8onJzY3bLzgVByZQFtQS1dCvALAKqpybK51thXYyKWTxgHT1T9sAKmiFBXx", - "pubKey": "024bbf83821c4102c4bc41846ef02434b78e37a448e8c55a3e6ef0ad701258a755", - "pubKeyHash": "413bf36c4f490f9b86b59a79616b291617db57cd", - "pubKeyFingerprint": "413bf36c", - "p2pkh": "mmTt2ywcvRo91RvutobovP9Q8YZsmpoMTi", - "p2sh-p2wpkh": "2Mw9fS8Zghno4StubxM3X7oQBFkGAg76KrR", - "p2wpkh": "bcrt1qgyalxmz0fy8ehp44nfukz6efzctak47dyxlcv5", - "path": "m/0'/0'/2'" - } - ], - "eve": [ - { - "entropy": "d8ec0331d6228a59b17cb412700761f0", - "mnemonic": "suggest gas small proof chunk coast shine notable bar lens such theme", - "seed": "ae4a2eda97c884469cb593568e2627e47de956bc67e0ba01362cb751c9cfca43477c303063127092f8dae8224a3decc48fa7649a2dae20068103d0aaa456cb09", - "xprivMaster": "tprv8ZgxMBicQKsPf97Q1kY8KhHSLtot3X8mUfoPy4EGjKnRCdGrCk9njeiaSFDXi2Y1JhsyoqReUCM77x4aVNCestM2zEhtGDKyk8CDJyBJ9D8", - "privKeyMaster": "aee3418891a442e70aa994cc1a53cbbcb4871f69ea859539d3ecfd0b079a2da5", - "wifMaster": "cTSfGDXQ27akHj1uUh7omWeWLamhZtiDSNNYg3p8oxwrJi9X4QjA", - "xpubMaster": "tpubD6NzVbkrYhZ4Yc9BuQCij6wYuvKpCrKg3yQBFaGa9bap37Xcq8yNv9LScPDknnEECW7uNDbDxnx1ddnXJqrCd3Rdw6u2Thm5weY4ffZXbaM", - "pubKeyMaster": "033a24eb95576c816e0c7dc1cef571bd9dd2fac8bc11ab3cdba0da49c2cf9070e2", - "pubKeyFingerprintMaster": "8be2c68c" - }, - { - "xpriv": "tprv8gi1x9A98qduZExYc2cwtUtxDAuuRjF5CmR2sWUP73UzcnPLpAmWaFZD85bYvPuhwCwHucydzniA1U6Cmr6XQyVyFEnBhFnYQCS3ignKnPv", - "privKey": "be1cb1b26446b12d82eb0dfa5c3479d75f3b60922baf200d4ca9f2f8ea9349fb", - "wif": "cTxFj3T9nfEjCM21WdYz67yU3FwyptYkXVuznvUS54admvLuNQ6C", - "xpub": "tpubDDQ46ZCPHDKaShzLVgHYHtZ4nCRqb4Ryn51pA2WgXKHPTGe7SZb6kkB5JDh5roQrcGoB43qLnQNhomvzA5u1qtzWLCjH3ebRbEG3Nio4iQn", - "pubKey": "03556bb5a184b64757b70e54c6f067420709971ab4178aebffa338bf6a1db2e3ab", - "pubKeyHash": "6e8f348d0aa3a3ee16440b9496f3d3c0ccf66f6b", - "pubKeyFingerprint": "6e8f348d", - "p2pkh": "mqbYBESF4bib4VTmsqe6twxMDKtVpeeJpt", - "p2sh-p2wpkh": "2N5AemJdXmQbLwF1ndhBtLRocE1UhP2Ja9M", - "p2wpkh": "bcrt1qd68nfrg25w37u9jypw2fdu7ncrx0vmmtwgeht9", - "path": "m/0'/0'/0'" - }, - { - "xpriv": "tprv8gi1x9A98qdubPYqD8DBMZvj5eTUDb6hT7CCWkWFeDfovcWitrN1Fo9vg5HZGNpYeTKyRV2aWk9XJJLipQD4rgi2XAXJbuXAUBDRvRxyfg2", - "privKey": "db1bcd042e7bbb6d05e6babe0c21d0220f0478838a45e1521575b34829653920", - "wif": "cUvcvLkMU6RsTkJNr7JE1rrVPaprPDQCGSELSmmUEWkxUnXUBcHZ", - "xpub": "tpubDDQ46ZCPHDKaUrad6msmkyaqefyQNvHc2QnyoGYZ4VUCm6mVXFBbSHmnrBGbMWcix5hRjYQLg3BEtHTqTZGzFCzez5wbjtGWD6KpP9r9n3R", - "pubKey": "025c459bb32a91d73cad6923de83b7a67887516ce37d7b00947cdeb0c1218d6953", - "pubKeyHash": "40f4168cf1d7aa67906875f089f1b3ada928fc03", - "pubKeyFingerprint": "40f4168c", - "p2pkh": "mmSPwsM5Hb7fp8qoXosGJSZJu1rjwh7CzR", - "p2sh-p2wpkh": "2Mv9PBWD2aeN69TrgTg8ebTKEoAXRtaTdzV", - "p2wpkh": "bcrt1qgr6pdr83674x0yrgwhcgnudn4k5j3lqrseu8hw", - "path": "m/0'/0'/1'" - }, - { - "xpriv": "tprv8gi1x9A98qdud5xGGCSCg1gwaaXi7zyPBPj4xTNGG1xcAeVAbWBxRqEoaDGRJSjZ9ZQ6i7jc8Wx8qR3LEUpiy4krtDNCue1idSkXC36vXvi", - "privKey": "4ba20aa8ebc34a2ac221b910d14e333f0cdd727d9583ad6e83be14b61d49111f", - "wif": "cQ7iqwQsmNYL4SefBytLZ7pG54PwZrtnXVqXcpaarK6XRf1mspRb", - "xpub": "tpubDDQ46ZCPHDKaWYz49r6o5RM49c3eHLAHkhKrEyQZgHm118jwDu1YcKrfkLfPpvFKTx1jsKWEX837DmFUgAGggwyrPRL9aUaBzgzk84oGtko", - "pubKey": "0286d90b1ac602ebf284124fa0176805a082a96142174e873b78ed1b45de12aff1", - "pubKeyHash": "9d9d7e65ba7d3570427bcb76afcad36441d8f1d8", - "pubKeyFingerprint": "9d9d7e65", - "p2pkh": "mutM42bNg6771m5snDwtydYc8Qu8f5s1W4", - "p2sh-p2wpkh": "2Msw3vdHpm76YsqzCFbgbRka2NEecnSFHZe", - "p2wpkh": "bcrt1qnkwhued6056hqsnmedm2ljknv3qa3uwcdzza8r", - "path": "m/0'/0'/2'" - } - ], - "mallory": [ - { - "entropy": "6af8462dd020ff7e2239a0a346d27448", - "mnemonic": "helmet season merge park avocado sample material cross person custom other mountain", - "seed": "9920708cb2a882ac9311183ee34b0d8ec502a05204ab3027cfea6495ff08184fbaa353a8268e3ba06643eab9bf81f023819d9ed5209f412eb2f17adb5a0bfaaa", - "xprivMaster": "tprv8ZgxMBicQKsPfMQxJYYS6L3m1mXgAPQuUfhZVuseZgHKgWcSAgLqUJsuXMR9CxNCnajd7JvtfK9yFoV3cbr5uS4XMVKd2BGxccyN5kRctQe", - "privKeyMaster": "c113fb4b0f3ae465a5981759821dbcca81cb7bf654e2483741d2d820bd5be421", - "wifMaster": "cU4282H3GUTtCjcXG6gByehpxZQ1RdUeB9pgPedeZakYEH77AvAX", - "xpubMaster": "tpubD6NzVbkrYhZ4YpSkCCD2Vjhsao3cKibp3yJLnRuwyx5iWzsCo5AReoVmhVUqMc19owzFx453Cu2MkHZ4PRCznwvCqu6nUyr1S7KV4pxUYzb", - "pubKeyMaster": "035434c0a3de4b7ff76c8f43dbb027ff962eeb8631c9ab520edae163970bf930f5", - "pubKeyFingerprintMaster": "6d851146" - }, - { - "xpriv": "tprv8gxXKvjGCb1ufMx55dAGtijwKHVw262P22dREZ8zDjhVCCf9gNFD63ZFmM9qhVTKZza6yWxCmtErPZFgnL7K5JiQTJFEijCvCUF46MdVFVL", - "privKey": "751bd339c907e21d638e9bad793c81139a9da23ba634be94fc8b64a64f553db2", - "wif": "cRWLzy2pD7cLnERzUk1vy4ouDdjXjjVbWgUg3VZ2PatAZzgtJFPc", - "xpub": "tpubDDeZULmWLxhaYpyryGpsJ8Q3tK1sBRDHbLECX5BHe1Vt2guvJm4oGYB7wTDJorVRG3CEs5sTLMRFG8vGorSEoxkb6vhy6bccwuicePB7f5X", - "pubKey": "020055836de77cc0836ea3d713879929dd4ed4e619b92b607111c6de23c3553a73", - "pubKeyHash": "b2289f5c18e6af46b6786d9e53ed3124b36ff070", - "pubKeyFingerprint": "b2289f5c", - "p2pkh": "mwkyEhauZdkziHJijx9rwZgShr9gYi9Hkh", - "p2sh-p2wpkh": "2N7HGKwJF8R2qVCGm3NnZrnFLZzHfudQirj", - "p2wpkh": "bcrt1qkg5f7hqcu6h5ddncdk098mf3yjeklurs02ny62", - "path": "m/0'/0'/0'" - }, - { - "xpriv": "tprv8gxXKvjGCb1ufuxS6fvYRx76wiMnEufrBt186qtYqx3cdfJR5qC2E3qASHwoCUR7KvWmwMyD4GUsBdTfdYr7fQ6HsDoRkQjZepWxiTPH2fH", - "privKey": "ffdcbe4f5e291fd510e538b6bb3a6baffd773568135a3e7d58d88c3f9802d25e", - "wif": "cWA4h9h9h1rBKEkGFWD31TAWC4z5JVybBenGNZsJQBTC5wL6wDxM", - "xpub": "tpubDDeZULmWLxhaZNzDzKb8qMmDWjsiQErkmBbuPMvrGDr1U9ZBiE1cQYT2cPFH2RMDeNFwF1DTQ5FS7H8EUyhhptKUM5yW4nQ1hfoiJcHPk9R", - "pubKey": "02273553c876ea756448a71d85b52f8d10e3413202cbf60865ee078987e82a9594", - "pubKeyHash": "b7e4a67d7519e27fe055e8f510757e4579274938", - "pubKeyFingerprint": "b7e4a67d", - "p2pkh": "mxHHs4fNt1mBh4gz5MWegw4KYbGwPLXSUk", - "p2sh-p2wpkh": "2Mx5KXS8zWPdSZ85JDYeBWi2HZZ5oTpy8bT", - "p2wpkh": "bcrt1qklj2vlt4r838lcz4ar63qat7g4ujwjfcq44j54", - "path": "m/0'/0'/1'" - }, - { - "xpriv": "tprv8gxXKvjGCb1uidca4SXuUJJdK23mBBxtZUT91vGMLgJEEuxF35FHHCnCwqoFjTgL7Gb1AK8cgwCgy3TDq8oef5oDAFzssDiiVm3bDLcgLau", - "privKey": "658ef2f5f718d9a9d4218ad36e139189d682dcb680e75aedd3fd0a54d3011579", - "wif": "cQz7nqBJBK2ciUBDnwyu5Abe6RhV5o64DjfGyGeTzW1YJaS1bEUD", - "xpub": "tpubDDeZULmWLxhac6eMx6CVshxjt3ZhLX9o8n3vJSJekx6d5QD1fU4sThQ57x3YdXWyS9YVnYs4u6JajhBNQpUJRGTR8dnGaAg8F58hT6GHAs9", - "pubKey": "02095c5d50d7baf0ba1de83e2991a17462b42ea9aa996dcea97e428f23fef56d3f", - "pubKeyHash": "8a8e8595c370c147f5692bc88c61d40bae4c2df5", - "pubKeyFingerprint": "8a8e8595", - "p2pkh": "mt9aH7gacU77TpobHcbMtkBZZ5PmjGryvn", - "p2sh-p2wpkh": "2N8AXBf2RBTA1vBJnHphihrDF9pgAiyim3y", - "p2wpkh": "bcrt1q328gt9wrwrq50atf90ygccw5pwhyct04vdwxnw", - "path": "m/0'/0'/2'" - } - ] -} diff --git a/src/features/transaction/errors.ts b/src/features/transaction/errors.ts new file mode 100644 index 0000000..af37416 --- /dev/null +++ b/src/features/transaction/errors.ts @@ -0,0 +1,3 @@ +export const Errors = { + INVALID_INPUT: "INVALID INPUT", +}; diff --git a/src/features/transaction/index.ts b/src/features/transaction/index.ts new file mode 100644 index 0000000..b029fca --- /dev/null +++ b/src/features/transaction/index.ts @@ -0,0 +1,4 @@ +export * from "./transaction"; +export * from "./input"; +export * from "./output"; +export * from "./types"; diff --git a/src/features/transaction/input.ts b/src/features/transaction/input.ts new file mode 100644 index 0000000..3d65b41 --- /dev/null +++ b/src/features/transaction/input.ts @@ -0,0 +1,34 @@ +import { stringify } from "querystring"; +import { Output } from "./output"; +import { Serializer } from "../encoding/serializer"; +import { TxIn, TxOut } from "./types"; + +export class Input { + txid: string; + vout: number; + prevout: TxOut | null; //null in the case of coinbase + scriptsig: string; + scriptsig_asm: string; + witness?: string[]; + is_coinbase: boolean; + sequence: number; + inner_redeemscript_asm: string | undefined; + inner_witnessscript_asm: string | undefined; + + constructor(inputConfig: TxIn) { + this.txid = inputConfig.txid; + this.vout = inputConfig.vout; + this.prevout = inputConfig.prevout; + this.scriptsig = inputConfig.scriptsig; + this.scriptsig_asm = inputConfig.scriptsig_asm; + this.witness = inputConfig.witness; + this.is_coinbase = inputConfig.is_coinbase; + this.sequence = inputConfig.sequence; + this.inner_redeemscript_asm = inputConfig.inner_redeemscript_asm; + this.inner_witnessscript_asm = inputConfig.inner_witnessscript_asm; + } + + serialize() { + return Serializer.serializeInput(this); + } +} diff --git a/src/features/transaction/output.ts b/src/features/transaction/output.ts new file mode 100644 index 0000000..3f53b00 --- /dev/null +++ b/src/features/transaction/output.ts @@ -0,0 +1,21 @@ +import { Serializer } from "../encoding/serializer"; +import { TxOut } from "./types"; + +export class Output { + scriptpubkey: string; + scriptpubkey_asm: string; + scriptpubkey_type: string; + scriptpubkey_address?: string; + value: number; + constructor(outputConfig: TxOut) { + this.scriptpubkey = outputConfig.scriptpubkey; + this.scriptpubkey_asm = outputConfig.scriptpubkey_asm; + this.scriptpubkey_type = outputConfig.scriptpubkey_type; + this.scriptpubkey_address = outputConfig.scriptpubkey_address; + this.value = outputConfig.value; + } + + serialize() { + return Serializer.serializeOutput(this); + } +} diff --git a/src/features/transaction/transaction.ts b/src/features/transaction/transaction.ts new file mode 100644 index 0000000..74cf982 --- /dev/null +++ b/src/features/transaction/transaction.ts @@ -0,0 +1,108 @@ +import { Input } from "./input"; +import { Output } from "./output"; +import { Serializer } from "../encoding/serializer"; +import { reversify, sha256 } from "../../utils"; +import { SigHash } from "../../types"; +import cloneDeep from "lodash.clonedeep"; +import { Errors } from "./errors"; +import { calculateWeight } from "./utils"; + +//depending on static serializer methods, instead use dependency injection +export class Transaction { + private _txid: string | undefined; //cache these values + private _wtxid: string | undefined; + private _serializedTx: string | undefined; + private _serializedWTx: string | undefined; + private _weight: number | undefined; + version: number; + locktime: number; + vin: Input[] = []; + vout: Output[] = []; + isSegwit = false; + + constructor(version: number, locktime: number) { + this.version = version; + this.locktime = locktime; + } + + addInput(input: Input) { + this.resetState(); + if (input.witness && input.witness.length > 0) this.isSegwit = true; + this.vin.push(input); + } + + addOutput(output: Output) { + this.resetState(); + this.vout.push(output); + } + + signWith(inputIndex: number, sighash: SigHash) { + const txCopy = cloneDeep(this); + let hashcode = Buffer.alloc(4); + switch (sighash) { + case SigHash.ALL: + for (let i = 0; i < txCopy.vin.length; i++) { + hashcode.writeUint32LE(1, 0); + if (i === inputIndex) { + const input = txCopy.vin[i].prevout; + if (!input) throw new Error(Errors.INVALID_INPUT); + txCopy.vin[i].scriptsig = input.scriptpubkey; + } else { + txCopy.vin[i].scriptsig = ""; + } + } + break; + case SigHash.ALL_ANYONECANPAY: + hashcode.writeUint32LE(0x81, 0); + txCopy.vin = [txCopy.vin[inputIndex]]; + const input = txCopy.vin[0].prevout; + if (!input) throw new Error(Errors.INVALID_INPUT); + txCopy.vin[0].scriptsig = input.scriptpubkey; + break; + } + + return txCopy.serializedTx + hashcode.toString("hex"); + } + + get serializedTx() { + if (this._serializedTx) return this._serializedTx; + this._serializedTx = Serializer.serializeTx(this); + return this._serializedTx; + } + + get serializedWTx() { + if (this._serializedWTx) return this._serializedWTx; + this._serializedWTx = Serializer.serializeWTx(this); + return this._serializedWTx; + } + + get txid() { + if (this._txid) return this._txid; + const txid = reversify(sha256(sha256(this.serializedTx))); + this._txid = txid; + return this._txid; + } + + get wtxid() { + if (!this.isSegwit) return this.txid; + if (this._wtxid) return this._wtxid; + const wtxid = reversify(sha256(sha256(this.serializedWTx))); + this._wtxid = wtxid; + return this._wtxid; + } + + get weight() { + if (this._weight) return this._weight; + const weight = calculateWeight(this, this.isSegwit); + this._weight = weight; + return this._weight; + } + + private resetState() { + //remove cache as it gets invalidated when tx gets changed such as when you're adding input or outputs; + this._txid = undefined; + this._wtxid = undefined; + this._serializedTx = undefined; + this._serializedWTx = undefined; + } +} diff --git a/src/features/transaction/types.ts b/src/features/transaction/types.ts new file mode 100644 index 0000000..8b06fa3 --- /dev/null +++ b/src/features/transaction/types.ts @@ -0,0 +1,27 @@ +export type Tx = { + version: number; + locktime: number; + vin: TxIn[]; + vout: TxOut[]; +}; + +export type TxIn = { + txid: string; + vout: number; + prevout: TxOut | null; + scriptsig: string; + scriptsig_asm: string; + witness?: string[]; + is_coinbase: boolean; + sequence: number; + inner_redeemscript_asm?: string; + inner_witnessscript_asm?: string; +}; + +export type TxOut = { + scriptpubkey: string; + scriptpubkey_asm: string; + scriptpubkey_type: string; + scriptpubkey_address?: string; //not required in the case of a coinbase transaction, can be computed later anyways + value: number; +}; diff --git a/src/features/transaction/utils.ts b/src/features/transaction/utils.ts new file mode 100644 index 0000000..7319f08 --- /dev/null +++ b/src/features/transaction/utils.ts @@ -0,0 +1,63 @@ +import { compactSize } from "../encoding/compactSize"; +import { Serializer } from "../encoding/serializer"; +import { Transaction } from "./transaction"; + +const sizeMultiplier = (val: Buffer | string, multiplier: number) => { + return val instanceof Buffer + ? (val.toString("hex").length / 2) * multiplier + : (val.length / 2) * multiplier; +}; + +export const calculateWeight = (tx: Transaction, isSegwit: boolean = false) => { + let nonSegwitWeight = 0; + + const version = Buffer.alloc(4); + version.writeInt16LE(tx.version, 0); + nonSegwitWeight += sizeMultiplier(version, 4); + + let segwitWeight = 2; //for marker and flag as they are 1 byte each + + const numInputs = compactSize(BigInt(tx.vin.length)); + nonSegwitWeight += sizeMultiplier(numInputs, 4); + + for (let i = 0; i < tx.vin.length; i++) { + nonSegwitWeight += sizeMultiplier(Serializer.serializeInput(tx.vin[i]), 4); + } + + const numOutputs = compactSize(BigInt(tx.vout.length)); + nonSegwitWeight += sizeMultiplier(numOutputs, 4); + for (let i = 0; i < tx.vout.length; i++) { + nonSegwitWeight += sizeMultiplier( + Serializer.serializeOutput(tx.vout[i]), + 4 + ); + } + + for (let i = 0; i < tx.vin.length; i++) { + const input = tx.vin[i]; + if ( + !input.witness || + (input && input.witness && input.witness.length === 0) + ) { + segwitWeight += sizeMultiplier(compactSize(BigInt(0)), 1); + } else { + segwitWeight += sizeMultiplier( + compactSize(BigInt(input.witness.length)), + 1 + ); + for (const witness of input.witness) { + segwitWeight += sizeMultiplier( + compactSize(BigInt(witness.length / 2)), + 1 + ); + segwitWeight += sizeMultiplier(witness, 1); + } + } + } + + const locktime = Buffer.alloc(4); + locktime.writeUint32LE(tx.locktime, 0); + nonSegwitWeight += sizeMultiplier(locktime, 4); + + return isSegwit ? nonSegwitWeight + segwitWeight : nonSegwitWeight; +}; diff --git a/src/features/transactionHex.ts b/src/features/transactionHex.ts deleted file mode 100644 index 394a68a..0000000 --- a/src/features/transactionHex.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Transaction } from "../types"; - -export const toRawTransactionHex = (tx: Transaction) => {}; diff --git a/src/features/validator/signature.ts b/src/features/validator/signature.ts index 35e7388..1987e9f 100644 --- a/src/features/validator/signature.ts +++ b/src/features/validator/signature.ts @@ -1 +1,77 @@ -// export const signature; +import { SigHash, TransactionType } from "../../types"; +import { Transaction } from "../transaction"; +import { hash256, sha256 } from "../../utils"; +import * as asn1js from "asn1js"; + +import { ECPairFactory } from "ecpair"; +import * as ecc from "tiny-secp256k1"; + +const ECPair = ECPairFactory(ecc); + +const removePadding = (r: string, s: string) => { + //remove der padding if length === 66 + if (r.length === 66) { + r = r.slice(2); + } + if (s.length === 66) { + s = s.slice(2); + } + + //add padding to make it 32 bytes for ecpair + r = r.padStart(64, "0"); + s = s.padStart(64, "0"); + + return r + s; +}; + +const extractSighashFromSignature = (signature: string) => { + return signature.slice(signature.length - 2) as SigHash; +}; + +export const signatureValidator = (tx: Transaction): boolean => { + let pubkey = ""; + let derEncodedSignature = ""; + for (let i = 0; i < tx.vin.length; i++) { + const input = tx.vin[i]; + if (!input.prevout) return true; //there is nothing to validate + switch (input.prevout.scriptpubkey_type) { + case TransactionType.P2PKH: + const asmTokens = input.scriptsig_asm.split(" "); + derEncodedSignature = asmTokens[1]; + pubkey = asmTokens[asmTokens.length - 1]; + const sighash = extractSighashFromSignature(derEncodedSignature); + const asn1 = asn1js.fromBER(Buffer.from(derEncodedSignature, "hex")); + + let r = Buffer.from( + (asn1.result.valueBlock as any).value[0].valueBlock.valueHexView + ).toString("hex"); + let s = Buffer.from( + (asn1.result.valueBlock as any).value[1].valueBlock.valueHexView + ).toString("hex"); + + const signature = removePadding(r, s); + + const ecpair = ECPair.fromPublicKey(Buffer.from(pubkey, "hex")); + + const msg = tx.signWith(i, sighash); + const hash = sha256(sha256(msg)); + const valid = ecpair.verify( + Buffer.from(hash, "hex"), + Buffer.from(signature, "hex") + ); + if (!valid) return false; + break; + + case TransactionType.P2WPKH: + if (!input.witness) return false; + derEncodedSignature = input.witness[0]; + pubkey = input.witness[1]; + const pubkeyHash = hash256(pubkey); + const pubkeyInScript = input.prevout.scriptpubkey.slice(4); + + if (pubkeyHash !== pubkeyInScript) return false; + } + } + + return true; +}; diff --git a/src/index.ts b/src/index.ts index 0c4b22e..7f71025 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,65 +1,69 @@ import * as fs from "fs"; -import { Transaction } from "./types"; // import { mempool } from "./store/mempool"; import { utxos } from "./store/utxos"; import { LengthValidator } from "./features/validator/length"; import { HashValidator } from "./features/validator/hash"; import { reversify, sha256 } from "./utils"; -import { txSerializer, txWeight } from "./features/encoding/serializer"; import { feePerByte } from "./features/block/fee"; import { mine } from "./features/block/mine"; import * as path from "path"; -import * as bitcoin from "bitcoinjs-lib"; +import { signatureValidator } from "./features/validator/signature"; +import { Input, Output, Transaction, Tx } from "./features/transaction"; +import { Transaction as BitcoinTx } from "bitcoinjs-lib"; (async () => { const files = fs.readdirSync("./mempool"); const outputFile = path.join(__dirname, "..", "output.txt"); let mempool: Transaction[] = []; - const blockSize = 2 * 1e6; + const blockSize = 4 * 1e6; for (const file of files) { - const tx = JSON.parse(fs.readFileSync(`./mempool/${file}`, "utf8")); + const tx = JSON.parse(fs.readFileSync(`./mempool/${file}`, "utf8")) as Tx; + // mempool.set(`${file}`.split(".")[0], { // ...tx, // txid: `${file}`.split(".")[0], // }); - mempool.push(tx); + const transaction = new Transaction(tx.version, tx.locktime); + for (const input of tx.vin) { + transaction.addInput(new Input(input)); + } + + for (const output of tx.vout) { + transaction.addOutput(new Output(output)); + } + mempool.push(transaction); } + // for (const tx of mempool) { + // signatureValidator(tx); + // } + let txs = []; mempool.sort((txA, txB) => feePerByte(txB) - feePerByte(txA)); let blockWeight = 0; for (const tx of mempool) { - if (txWeight(tx) + blockWeight > blockSize) break; + if (tx.weight + blockWeight > blockSize) break; txs.push(tx); - blockWeight += txWeight(tx); + blockWeight += tx.weight; } + try { + fs.unlinkSync(outputFile); + } catch (err) {} const { serializedBlock, blockHash, coinbaseTransaction } = mine(txs); - const ct = bitcoin.Transaction.fromHex( - txSerializer(coinbaseTransaction).serializedWTx - ); - fs.writeFileSync(outputFile, serializedBlock); fs.appendFileSync(outputFile, "\n"); - fs.appendFileSync( - outputFile, - txSerializer(coinbaseTransaction).serializedWTx - ); + fs.appendFileSync(outputFile, coinbaseTransaction.serializedWTx); fs.appendFileSync(outputFile, "\n"); - fs.appendFileSync( - outputFile, - reversify(sha256(sha256(txSerializer(coinbaseTransaction).serializedTx))) - ); + fs.appendFileSync(outputFile, coinbaseTransaction.txid); fs.appendFileSync(outputFile, "\n"); for (const tx of txs) { - fs.appendFileSync( - outputFile, - reversify(sha256(sha256(txSerializer(tx).serializedTx))) - ); + fs.appendFileSync(outputFile, tx.txid); fs.appendFileSync(outputFile, "\n"); } + // console.log(fs.readFileSync(outputFile, "utf8").split("\n").length); })(); diff --git a/src/types.ts b/src/types.ts index 86ca1ef..ca21be8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -32,4 +32,15 @@ export enum TransactionType { P2WPKH = "v0_p2wpkh", P2WSH = "v0_p2wsh", P2TR = "v1_p2tr", + OP_RETURN = "op_return", } + +export enum SigHash { + ALL = "01", //all inputs and outputs + NONE = "02", //all inputs and no output + SINGLE = "03", //all inputs and output with the same index + ALL_ANYONECANPAY = "81", //own input and anyone can pay + NONE_ANYONECANPAY = "82", //own input and no output + SINGLE_ANYONECANPAY = "83", //own input and output with the same index +} +