From bc385f85abd5407cdfbd74f9474a54753d82ee28 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Wed, 17 Jan 2024 16:31:46 +0100 Subject: [PATCH] Include coinbase data in the `assembleBitcoinSpvProof` function --- typescript/src/lib/bitcoin/spv.ts | 54 ++++- typescript/src/lib/ethereum/bridge.ts | 8 +- typescript/test/data/deposit-sweep.ts | 44 ++++ typescript/test/data/proof.ts | 229 +++++++++++++++++++ typescript/test/data/redemption.ts | 43 ++++ typescript/test/lib/bitcoin.test.ts | 102 ++++++++- typescript/test/lib/ethereum.test.ts | 20 ++ typescript/test/services/maintenance.test.ts | 93 +++++++- typescript/test/utils/mock-bitcoin-client.ts | 23 +- 9 files changed, 597 insertions(+), 19 deletions(-) diff --git a/typescript/src/lib/bitcoin/spv.ts b/typescript/src/lib/bitcoin/spv.ts index 596fd4056..417e683a0 100644 --- a/typescript/src/lib/bitcoin/spv.ts +++ b/typescript/src/lib/bitcoin/spv.ts @@ -1,4 +1,4 @@ -import { BitcoinTx, BitcoinTxHash } from "./tx" +import { BitcoinTx, BitcoinTxHash, extractBitcoinRawTxVectors } from "./tx" import { BitcoinClient } from "./client" import { BigNumber } from "ethers" import { @@ -29,6 +29,17 @@ export interface BitcoinSpvProof { * 80-byte-long. The block header with the lowest height is first. */ bitcoinHeaders: Hex + + /** + * The sha256 preimage of the coinbase transaction hash i.e., + * the sha256 hash of the coinbase transaction. + */ + coinbasePreimage: Hex + + /** + * Merkle proof of coinbase transaction inclusion in a block. + */ + coinbaseProof: Hex } /** @@ -101,10 +112,34 @@ export async function assembleBitcoinSpvProof( const merkleProof = createMerkleProof(merkleBranch) + const coinbaseTxHash = await bitcoinClient.getCoinbaseTxHash(txBlockHeight) + + const coinbaseTx = extractBitcoinRawTxVectors( + await bitcoinClient.getRawTransaction(coinbaseTxHash) + ) + + const coinbasePreimage = BitcoinHashUtils.computeSha256( + Hex.from( + `${coinbaseTx.version.toString()}` + + `${coinbaseTx.inputs.toString()}` + + `${coinbaseTx.outputs.toString()}` + + `${coinbaseTx.locktime.toString()}` + ) + ) + + const coinbaseMerkleBranch = await bitcoinClient.getTransactionMerkle( + coinbaseTxHash, + txBlockHeight + ) + + const coinbaseMerkleProof = createMerkleProof(coinbaseMerkleBranch) + const proof = { merkleProof: merkleProof, txIndexInBlock: merkleBranch.position, bitcoinHeaders: headersChain, + coinbasePreimage: coinbasePreimage, + coinbaseProof: coinbaseMerkleProof, } return { ...transaction, ...proof } @@ -159,6 +194,13 @@ export async function validateBitcoinSpvProof( bitcoinClient ) + if ( + proof.merkleProof.toBuffer().length !== + proof.coinbaseProof.toBuffer().length + ) { + throw new Error("Tx not on same level of merkle tree as coinbase") + } + const bitcoinHeaders: BitcoinHeader[] = BitcoinHeaderSerializer.deserializeHeadersChain(proof.bitcoinHeaders) if (bitcoinHeaders.length != requiredConfirmations) { @@ -166,12 +208,11 @@ export async function validateBitcoinSpvProof( } const merkleRootHash: Hex = bitcoinHeaders[0].merkleRootHash - const intermediateNodeHashes: Hex[] = splitMerkleProof(proof.merkleProof) validateMerkleTree( transactionHash, merkleRootHash, - intermediateNodeHashes, + splitMerkleProof(proof.merkleProof), proof.txIndexInBlock ) @@ -180,6 +221,13 @@ export async function validateBitcoinSpvProof( previousDifficulty, currentDifficulty ) + + validateMerkleTree( + BitcoinHashUtils.computeSha256(proof.coinbasePreimage).reverse(), + merkleRootHash, + splitMerkleProof(proof.coinbaseProof), + 0 + ) } /** diff --git a/typescript/src/lib/ethereum/bridge.ts b/typescript/src/lib/ethereum/bridge.ts index 43a7c2d6e..9d1bcb7c9 100644 --- a/typescript/src/lib/ethereum/bridge.ts +++ b/typescript/src/lib/ethereum/bridge.ts @@ -286,6 +286,8 @@ export class EthereumBridge merkleProof: sweepProof.merkleProof.toPrefixedString(), txIndexInBlock: sweepProof.txIndexInBlock, bitcoinHeaders: sweepProof.bitcoinHeaders.toPrefixedString(), + coinbasePreimage: sweepProof.coinbasePreimage.toPrefixedString(), + coinbaseProof: sweepProof.coinbaseProof.toPrefixedString(), } const mainUtxoParam = { @@ -391,9 +393,11 @@ export class EthereumBridge } const redemptionProofParam = { - merkleProof: `0x${redemptionProof.merkleProof}`, + merkleProof: redemptionProof.merkleProof.toPrefixedString(), txIndexInBlock: redemptionProof.txIndexInBlock, - bitcoinHeaders: `0x${redemptionProof.bitcoinHeaders}`, + bitcoinHeaders: redemptionProof.bitcoinHeaders.toPrefixedString(), + coinbasePreimage: redemptionProof.coinbasePreimage.toPrefixedString(), + coinbaseProof: redemptionProof.coinbaseProof.toPrefixedString(), } const mainUtxoParam = { diff --git a/typescript/test/data/deposit-sweep.ts b/typescript/test/data/deposit-sweep.ts index 35b551e43..0d61d9386 100644 --- a/typescript/test/data/deposit-sweep.ts +++ b/typescript/test/data/deposit-sweep.ts @@ -430,6 +430,8 @@ export interface DepositSweepProofTestData { latestBlockHeight: number headersChain: Hex transactionMerkleBranch: BitcoinTxMerkleBranch + coinbaseRawTransaction: BitcoinRawTx + coinbaseMerkleBranch: BitcoinTxMerkleBranch } expectedSweepProof: { sweepTx: BitcoinRawTxVectors @@ -563,6 +565,38 @@ export const depositSweepProof: DepositSweepProofTestData = { ], position: 6, }, + coinbaseRawTransaction: { + transactionHex: + "02000000000101000000000000000000000000000000000000000000000000000" + + "0000000000000ffffffff4803bb05210445c21c62425443506f6f6cfabe6d6d97" + + "92ca7580b0dccdb4465ab26f697e165c536838032a466ef25b25bb7bebb3ae040" + + "000000d6531d503040db15755000000000000ffffffff0212304d000000000017" + + "a9147bef0b4a4dafa77b2ec52b81659cbcf0d9a91487870000000000000000266" + + "a24aa21a9ed8a716e1e3e8691cfc14df968505d88fae2e240b7f4ca4d59871992" + + "a63c9900640120000000000000000000000000000000000000000000000000000" + + "000000000000000000000", + }, + coinbaseMerkleBranch: { + blockHeight: 2164155, + merkle: [ + Hex.from( + "d654db76daa53b61becba19a542c6156a725d0bce3d0f3a57d713c9a9498ab95" + ), + Hex.from( + "df2a8c9cded17679ecacc119d92bfa5dc795bb80284e320e80e4f9f93d0e5ba4" + ), + Hex.from( + "b7d63f60c09609472aa68fbb6380cffc87b4d921a26edc8b2626154145d603be" + ), + Hex.from( + "a51612d3f3f857e95803a4d86aa6dbbe2e756dc2ed6cc0e04630e8baf597e377" + ), + Hex.from( + "a00501650e0c4f8a1e07a5d6d5bc5e75e4c75de61a65f0410cce354bbae78686" + ), + ], + position: 0, + }, }, expectedSweepProof: { sweepTx: { @@ -613,6 +647,16 @@ export const depositSweepProof: DepositSweepProofTestData = { "99b8e9db517e36f000000000000a494b8034039e7855b75563ab83c9410dd67e89b" + "b58e6cd93b85290a885dd749f4d61c62ed3e031ad9a83746" ), + coinbasePreimage: Hex.from( + "0248a67dec36c38a485ad920afd89dcf082e7e570390380890e3a9465662f449" + ), + coinbaseProof: Hex.from( + "95ab98949a3c717da5f3d0e3bcd025a756612c549aa1cbbe613ba5da76db54d6a45" + + "b0e3df9f9e4800e324e2880bb95c75dfa2bd919c1acec7976d1de9c8c2adfbe03d6" + + "45411526268bdc6ea221d9b487fccf8063bb8fa62a470996c0603fd6b777e397f5b" + + "ae83046e0c06cedc26d752ebedba66ad8a40358e957f8f3d31216a58686e7ba4b35" + + "ce0c41f0651ae65dc7e4755ebcd5d6a5071e8a4f0c0e650105a0" + ), }, mainUtxo: NO_MAIN_UTXO, }, diff --git a/typescript/test/data/proof.ts b/typescript/test/data/proof.ts index 6d3e114c0..8ff28b560 100644 --- a/typescript/test/data/proof.ts +++ b/typescript/test/data/proof.ts @@ -20,6 +20,8 @@ export interface ProofTestData { latestBlockHeight: number headersChain: Hex transactionMerkleBranch: BitcoinTxMerkleBranch + coinbaseRawTransaction: BitcoinRawTx + coinbaseMerkleBranch: BitcoinTxMerkleBranch } expectedProof: BitcoinSpvProof & BitcoinTx } @@ -115,6 +117,44 @@ export const singleInputProofTestData: ProofTestData = { ], position: 11, }, + coinbaseRawTransaction: { + transactionHex: + "02000000000101000000000000000000000000000000000000000000000000000" + + "0000000000000ffffffff4803b8052104e0c01c62425443506f6f6cfabe6d6d56" + + "06828fa0322cc81efd5fdbf7f100c7a8497e88dbc102097b30e08f13465bd9040" + + "000000d6531d503040db19f2e020000000000ffffffff0297814e000000000017" + + "a9147bef0b4a4dafa77b2ec52b81659cbcf0d9a91487870000000000000000266" + + "a24aa21a9ed4a673a4a13930bcc67727968c29960bf8ba07b218ff3811edf3ef3" + + "cc196707880120000000000000000000000000000000000000000000000000000" + + "000000000000000000000", + }, + coinbaseMerkleBranch: { + blockHeight: 2164152, + merkle: [ + Hex.from( + "095b539f725018c6afd940281d22f523ae74f9b07fe685b4f3decf130a6e3d7a" + ), + Hex.from( + "ad6a634865b1e61051b6ecf819d98110db0cf5747eaf2205fe9e82db47318b95" + ), + Hex.from( + "0c99d031690fa507e27c4068516c752297ea7df9a5b7482bfc0c44705c059024" + ), + Hex.from( + "352a81a4e75ea8c39060a6a383a4bd19245d86cf38918d9d49cafd4e54dfba7e" + ), + Hex.from( + "43ad3aadad675e398c59eb846a8e037cf7de8ba3b38f3388175f25d84b777c80" + ), + Hex.from( + "6969c227128793b3c9e99c05f20fb9b91fdb73458fd53151b5fe29d30c10cf9a" + ), + Hex.from( + "0a76bc4d8c3d532357be4d188ba89e9ae364a7d3c365e690e3cb07359b86129c" + ), + ], + position: 0, + }, }, expectedProof: { transactionHash: BitcoinTxHash.from( @@ -165,6 +205,18 @@ export const singleInputProofTestData: ProofTestData = { "d225028aa08ab6139eee31f4f67a010000000000004cda79bc48b970de2fb29c3f" + "38626eb9d70d8bae7b92aad09f2a0ad2d2f334d35bca1c62ffff001d048fc217" ), + coinbasePreimage: Hex.from( + "8e690235847a80c4d300542a2d27b90bfd13d77b3421c1b5590f4220718cd3fd" + ), + coinbaseProof: Hex.from( + "7a3d6e0a13cfdef3b485e67fb0f974ae23f5221d2840d9afc61850729f535b0995" + + "8b3147db829efe0522af7e74f50cdb1081d919f8ecb65110e6b16548636aad2490" + + "055c70440cfc2b48b7a5f97dea9722756c5168407ce207a50f6931d0990c7ebadf" + + "544efdca499d8d9138cf865d2419bda483a3a66090c3a85ee7a4812a35807c774b" + + "d8255f1788338fb3a38bdef77c038e6a84eb598c395e67adad3aad439acf100cd3" + + "29feb55131d58f4573db1fb9b90ff2059ce9c9b393871227c269699c12869b3507" + + "cbe390e665c3d3a764e39a9ea88b184dbe5723533d8c4dbc760a" + ), }, } @@ -295,6 +347,38 @@ export const multipleInputsProofTestData: ProofTestData = { ], position: 6, }, + coinbaseRawTransaction: { + transactionHex: + "02000000000101000000000000000000000000000000000000000000000000000" + + "0000000000000ffffffff4803bb05210445c21c62425443506f6f6cfabe6d6d97" + + "92ca7580b0dccdb4465ab26f697e165c536838032a466ef25b25bb7bebb3ae040" + + "000000d6531d503040db15755000000000000ffffffff0212304d000000000017" + + "a9147bef0b4a4dafa77b2ec52b81659cbcf0d9a91487870000000000000000266" + + "a24aa21a9ed8a716e1e3e8691cfc14df968505d88fae2e240b7f4ca4d59871992" + + "a63c9900640120000000000000000000000000000000000000000000000000000" + + "000000000000000000000", + }, + coinbaseMerkleBranch: { + blockHeight: 2164155, + merkle: [ + Hex.from( + "d654db76daa53b61becba19a542c6156a725d0bce3d0f3a57d713c9a9498ab95" + ), + Hex.from( + "df2a8c9cded17679ecacc119d92bfa5dc795bb80284e320e80e4f9f93d0e5ba4" + ), + Hex.from( + "b7d63f60c09609472aa68fbb6380cffc87b4d921a26edc8b2626154145d603be" + ), + Hex.from( + "a51612d3f3f857e95803a4d86aa6dbbe2e756dc2ed6cc0e04630e8baf597e377" + ), + Hex.from( + "a00501650e0c4f8a1e07a5d6d5bc5e75e4c75de61a65f0410cce354bbae78686" + ), + ], + position: 0, + }, }, expectedProof: { transactionHash: BitcoinTxHash.from( @@ -364,6 +448,16 @@ export const multipleInputsProofTestData: ProofTestData = { "99b8e9db517e36f000000000000a494b8034039e7855b75563ab83c9410dd67e89b" + "b58e6cd93b85290a885dd749f4d61c62ed3e031ad9a83746" ), + coinbasePreimage: Hex.from( + "0248a67dec36c38a485ad920afd89dcf082e7e570390380890e3a9465662f449" + ), + coinbaseProof: Hex.from( + "95ab98949a3c717da5f3d0e3bcd025a756612c549aa1cbbe613ba5da76db54d6a45" + + "b0e3df9f9e4800e324e2880bb95c75dfa2bd919c1acec7976d1de9c8c2adfbe03d6" + + "45411526268bdc6ea221d9b487fccf8063bb8fa62a470996c0603fd6b777e397f5b" + + "ae83046e0c06cedc26d752ebedba66ad8a40358e957f8f3d31216a58686e7ba4b35" + + "ce0c41f0651ae65dc7e4755ebcd5d6a5071e8a4f0c0e650105a0" + ), }, } @@ -378,6 +472,8 @@ export interface TransactionProofData { latestBlockHeight: number headersChain: Hex transactionMerkleBranch: BitcoinTxMerkleBranch + coinbaseRawTransaction: BitcoinRawTx + coinbaseMerkleBranch: BitcoinTxMerkleBranch previousDifficulty: BigNumber currentDifficulty: BigNumber } @@ -499,6 +595,56 @@ export const transactionConfirmationsInOneEpochData: TransactionProofData = { ], position: 17, }, + coinbaseRawTransaction: { + transactionHex: + "010000000001010000000000000000000000000000000000000000000000000000000" + + "000000000ffffffff6003e6d70b192f5669614254432f4d696e656420627920736368" + + "756c747a2f2cfabe6d6dcb674c098d80e8456c9cebe631a3ae9ec16e47185c0684b0c" + + "2fbab45827d4b79100000000000000010548e591b518d91e94608772cd43615000000" + + "0000ffffffff0290519d25000000001976a914536ffa992491508dca0354e52f32a3a" + + "7a679a53a88ac0000000000000000266a24aa21a9edca238b73df37d6eba6f22606c9" + + "38f46786f95c861e96acf85fdc6b61d4b3ecc10120000000000000000000000000000" + + "000000000000000000000000000000000000000000000", + }, + coinbaseMerkleBranch: { + blockHeight: 776166, + merkle: [ + Hex.from( + "438e7c04ee74dd2e79ffd3ace87c45a7b275e3f95783954ba321081e3f92fe99" + ), + Hex.from( + "02c8dcc4cb7b50857853ff31610700cd538e435002a433b318f4efc976912002" + ), + Hex.from( + "43e1894ff1469105951c529a622370460b666c722c84d05d73712e5af84bfd09" + ), + Hex.from( + "bdd6e6d69540a45185b4ad6dbfd108b7dfebc4d6ae7030d2d3bd139e9b2c1e28" + ), + Hex.from( + "cffed70dd62cab291f132fb90c5d6cd11af589db122df862585c6dd43c667d4f" + ), + Hex.from( + "13bdefbf92421aa7861528e16e7046b569d25ee0f4b7649492e42e9ea2331c39" + ), + Hex.from( + "df429494c5eef971a7ab80c8a0f7f9cdfa30148afef706f07923bd93d5a7e22a" + ), + Hex.from( + "c8a3f1bc73146bd4a1a0e848f2b0b4a21be86e4930f239d856af8e9646014236" + ), + Hex.from( + "1f514df87fe2c400e508e01cd8967657ef76db9681f65dc82b0bc6d4004b575f" + ), + Hex.from( + "e463950c8efd9114237189f07ddf1cfdb72658bad23bce667c269652bd0ade3c" + ), + Hex.from( + "3d7ae6df787807320fdc397a7055e86c932a7c36ab1d1f942b92c53bf2a1d2f9" + ), + ], + position: 0, + }, previousDifficulty: BigNumber.from(39156400059293), currentDifficulty: BigNumber.from(39350942467772), }, @@ -596,6 +742,54 @@ export const transactionConfirmationsInTwoEpochsData: TransactionProofData = { ], position: 262, }, + coinbaseRawTransaction: { + transactionHex: + "010000000001010000000000000000000000000000000000000000000000000000000" + + "000000000ffffffff5003fecf0b1362696e616e63652f383235f10213007dfea192fa" + + "be6d6def5afc5d3971282911709ed7cb7d9142bb531ae9cc453036b2b77afa95dc878" + + "304000000000000000000d483e700000000000000ffffffff03599476250000000017" + + "a914ca35b1f4d02907314852f09935b9604507f8d700870000000000000000266a24a" + + "a21a9eddfad2eae7dcecafd0a22651c8e9d197143b7811e503dc77d7fe70931d2fd7f" + + "7600000000000000002b6a2952534b424c4f434b3aed6626f157d5eaf68037ee382bd" + + "47af82d62aab3b7aab4b79058b234004c6cf401200000000000000000000000000000" + + "00000000000000000000000000000000000000000000", + }, + coinbaseMerkleBranch: { + blockHeight: 774142, + merkle: [ + Hex.from( + "294dca224d25b5637e2520e07a5b5942a1e56a91532927f9611b49ce73e8e674" + ), + Hex.from( + "69e58cb5e60eafa3faff8a4a691bb80d6bf541c40eba3d06868a28873b29089d" + ), + Hex.from( + "12b7ab7aae471f8a2208ffb88e2624a9107e99dfe52d653e0237149c08505794" + ), + Hex.from( + "11df9abc347cb8ae1f61e86b5713966780cc052704b145e26ec9591c3c6b819e" + ), + Hex.from( + "19ca1d35c20828cde2e933e0058f7666c2f5b428d135392bc4c444046f06831f" + ), + Hex.from( + "d112040dc1d4952db1f6079f9a79cd18e695495adac9e742b8b28f0adb30f2eb" + ), + Hex.from( + "ea5cbc9f966c8c69e0ecca2719d26fe834dd308e6fe620036d200f0c46454804" + ), + Hex.from( + "5bd7afaed89f37bf36978b52d634c57451215eecdf1780de3b5274f27ce5508b" + ), + Hex.from( + "73a7ab1687e76798fb48089d3965f678b72c74d8dee7d814761f08fa489e15f9" + ), + Hex.from( + "e7e530e181683d272293f19fe18a33f1dc05eded12ec27945b49311b2e14ee42" + ), + ], + position: 0, + }, previousDifficulty: BigNumber.from(37590453655497), currentDifficulty: BigNumber.from(39350942467772), }, @@ -683,6 +877,41 @@ export const testnetTransactionData: TransactionProofData = { ], position: 4, }, + coinbaseRawTransaction: { + transactionHex: + "010000000001010000000000000000000000000000000000000000000000000000000" + + "000000000ffffffff0403bdf124ffffffff02c92638000000000016001416761a41f7" + + "452e32f987702b6deb87f68cd7aa7a0000000000000000266a24aa21a9ed270d869bd" + + "7697384d2e4810cb6a24ba31390e2cc3c02fd60fd3f70e1997d63ec01200000000000" + + "00000000000000000000000000000000000000000000000000000000000000", + }, + coinbaseMerkleBranch: { + blockHeight: 2421181, + merkle: [ + Hex.from( + "9f6f47ff0bc14890fe99236695c3cf5dde83ee93f196737618a81480a3232bc3" + ), + Hex.from( + "6938bd629f8e0fc641d6438ee0d0f5100931cb68f48abf28a8b9a34321904d8b" + ), + Hex.from( + "27598cb2cc1e2131cd718bc8ecc1388d3b2f04c0e641bbb59876267ea465d5c7" + ), + Hex.from( + "0eebd6daa03f6db4a27541a91bcf86612c97d100bc37c3eb321d64d943adb2a5" + ), + Hex.from( + "b25854f31fc046eb0f53cddbf2b6de3d54d52710acd79a796c78c3be235f031a" + ), + Hex.from( + "1fc5ab77039f59ac2494791fc05c75fb53e2dacf57a20f67e7d6727b38778825" + ), + Hex.from( + "5b0acfdbb89af64a583a88e92252b8634bd4da06ee102ecd34c2662955e9f1c7" + ), + ], + position: 0, + }, previousDifficulty: BigNumber.from(1), currentDifficulty: BigNumber.from(1), }, diff --git a/typescript/test/data/redemption.ts b/typescript/test/data/redemption.ts index deeea11a3..e1dcf3aaf 100644 --- a/typescript/test/data/redemption.ts +++ b/typescript/test/data/redemption.ts @@ -546,6 +546,8 @@ export interface RedemptionProofTestData { latestBlockHeight: number headersChain: Hex transactionMerkleBranch: BitcoinTxMerkleBranch + coinbaseRawTransaction: BitcoinRawTx + coinbaseMerkleBranch: BitcoinTxMerkleBranch } expectedRedemptionProof: { redemptionTx: BitcoinRawTxVectors @@ -666,6 +668,37 @@ export const redemptionProof: RedemptionProofTestData = { ], position: 4, }, + coinbaseRawTransaction: { + transactionHex: + "0200000000010100000000000000000000000000000000000000000000000000000000" + + "00000000ffffffff480359832104f6905e62425443506f6f6cfabe6d6d44b54ffb3a8b" + + "00ab84e3907bfb9fe78157ce719a54630a1321f50b37116f9f71040000002a8dcca602" + + "04dad94668000000000000ffffffff028d644b000000000017a9147bef0b4a4dafa77b" + + "2ec52b81659cbcf0d9a91487870000000000000000266a24aa21a9ed533404630c2732" + + "6df0f77bad5822ad39e6ef184f61ac334eb6264af532bee74801200000000000000000" + + "00000000000000000000000000000000000000000000000000000000", + }, + coinbaseMerkleBranch: { + blockHeight: 2196313, + merkle: [ + Hex.from( + "f0ba82de74f59444ebe27dc395ecf2f6c769d29050f455ad1017f3dcdcd2dd59" + ), + Hex.from( + "1e7b3d5fb5b83ecd12bfe89d9bbd9de747ab544f111063339f11b57f08228ba3" + ), + Hex.from( + "983edacf13f754bf8b6f8c584b7fe12d0d0c0e890e7ce2d29e0c7335021d8b71" + ), + Hex.from( + "65ea59172f35ee6db6e4194227bea23daedbda8299bea94710f21c97f3e9cc17" + ), + Hex.from( + "8c5b4ce089d0c450bf6125e7d342114246802bf4c9638d222aa9fcbe8e06024e" + ), + ], + position: 0, + }, }, expectedRedemptionProof: { redemptionTx: { @@ -712,6 +745,16 @@ export const redemptionProof: RedemptionProofTestData = { "eed9f700b9c2b00000000000000465ec2f30447552a4a30ee63964aaebcb0406492" + "69eab449fb51823d58835a4aed9a5e62341f5c192fd94baa" ), + coinbasePreimage: Hex.from( + "5fb81be0d06e1573231f6b5f4eba3055f3160e1b45a7db6c9cb816b0c184b419" + ), + coinbaseProof: Hex.from( + "59ddd2dcdcf31710ad55f45090d269c7f6f2ec95c37de2eb4494f574de82baf0a38" + + "b22087fb5119f336310114f54ab47e79dbd9b9de8bf12cd3eb8b55f3d7b1e718b1d" + + "0235730c9ed2e27c0e890e0c0d2de17f4b588c6f8bbf54f713cfda3e9817cce9f39" + + "71cf21047a9be9982dadbae3da2be274219e4b66dee352f1759ea654e02068ebefc" + + "a92a228d63c9f42b8046421142d3e72561bf50c4d089e04c5b8c" + ), }, mainUtxo: { transactionHash: BitcoinTxHash.from( diff --git a/typescript/test/lib/bitcoin.test.ts b/typescript/test/lib/bitcoin.test.ts index 11dacfaac..fd57a9e25 100644 --- a/typescript/test/lib/bitcoin.test.ts +++ b/typescript/test/lib/bitcoin.test.ts @@ -18,6 +18,8 @@ import { BitcoinSpvProof, assembleBitcoinSpvProof, validateBitcoinSpvProof, + BitcoinRawTx, + BitcoinTxMerkleBranch, } from "../../src" import { BigNumber } from "ethers" import { btcAddresses, btcAddressFromPublicKey } from "../data/bitcoin" @@ -924,6 +926,10 @@ describe("Bitcoin", () => { expect(proof.merkleProof).to.deep.equal(expectedProof.merkleProof) expect(proof.txIndexInBlock).to.equal(expectedProof.txIndexInBlock) expect(proof.bitcoinHeaders).to.deep.equal(expectedProof.bitcoinHeaders) + expect(proof.coinbasePreimage).to.deep.equal( + expectedProof.coinbasePreimage + ) + expect(proof.coinbaseProof).to.deep.equal(expectedProof.coinbaseProof) }) }) @@ -944,6 +950,10 @@ describe("Bitcoin", () => { expect(proof.merkleProof).to.deep.equal(expectedProof.merkleProof) expect(proof.txIndexInBlock).to.equal(expectedProof.txIndexInBlock) expect(proof.bitcoinHeaders).to.deep.equal(expectedProof.bitcoinHeaders) + expect(proof.coinbasePreimage).to.deep.equal( + expectedProof.coinbasePreimage + ) + expect(proof.coinbaseProof).to.deep.equal(expectedProof.coinbaseProof) }) }) @@ -974,10 +984,22 @@ describe("Bitcoin", () => { data.bitcoinChainData.transaction ) bitcoinClient.transactions = transactions + bitcoinClient.latestHeight = data.bitcoinChainData.latestBlockHeight + bitcoinClient.headersChain = data.bitcoinChainData.headersChain - bitcoinClient.transactionMerkle = + + const txBlockHeight = + data.bitcoinChainData.latestBlockHeight - + data.bitcoinChainData.accumulatedTxConfirmations + + 1 + + const transactionMerkle = new Map() + transactionMerkle.set( + `${transactionHash.toString()}${txBlockHeight.toString(16)}`, data.bitcoinChainData.transactionMerkleBranch + ) + const confirmations = new Map() confirmations.set( transactionHash.toString(), @@ -985,6 +1007,29 @@ describe("Bitcoin", () => { ) bitcoinClient.confirmations = confirmations + const coinbaseTxHash = BitcoinTxHash.from( + BitcoinHashUtils.computeHash256( + Hex.from(data.bitcoinChainData.coinbaseRawTransaction.transactionHex) + ).toString() + ) + + const coinbaseHashes = new Map() + coinbaseHashes.set(txBlockHeight, coinbaseTxHash) + bitcoinClient.coinbaseHashes = coinbaseHashes + + const rawTransactions = new Map() + rawTransactions.set( + coinbaseTxHash.toString(), + data.bitcoinChainData.coinbaseRawTransaction + ) + bitcoinClient.rawTransactions = rawTransactions + + transactionMerkle.set( + `${coinbaseTxHash.toString()}${txBlockHeight.toString(16)}`, + data.bitcoinChainData.coinbaseMerkleBranch + ) + bitcoinClient.transactionMerkle = transactionMerkle + const proof = await assembleBitcoinSpvProof( transactionHash, data.requiredConfirmations, @@ -1089,6 +1134,14 @@ describe("Bitcoin", () => { merkle[merkle.length - 1].toString() + "ff" ) + const coinbaseMerkle = [ + ...transactionConfirmationsInOneEpochData.bitcoinChainData + .coinbaseMerkleBranch.merkle, + ] + coinbaseMerkle[coinbaseMerkle.length - 1] = Hex.from( + coinbaseMerkle[coinbaseMerkle.length - 1].toString() + "ff" + ) + const corruptedProofData: TransactionProofData = { ...transactionConfirmationsInOneEpochData, bitcoinChainData: { @@ -1098,6 +1151,11 @@ describe("Bitcoin", () => { .transactionMerkleBranch, merkle: merkle, }, + coinbaseMerkleBranch: { + ...transactionConfirmationsInOneEpochData.bitcoinChainData + .coinbaseMerkleBranch, + merkle: merkle, + }, }, } @@ -1119,6 +1177,11 @@ describe("Bitcoin", () => { .transactionMerkleBranch, merkle: [], }, + coinbaseMerkleBranch: { + ...transactionConfirmationsInOneEpochData.bitcoinChainData + .coinbaseMerkleBranch, + merkle: [], + }, }, } @@ -1252,10 +1315,22 @@ describe("Bitcoin", () => { data.bitcoinChainData.transaction ) bitcoinClient.transactions = transactions + bitcoinClient.latestHeight = data.bitcoinChainData.latestBlockHeight + bitcoinClient.headersChain = data.bitcoinChainData.headersChain - bitcoinClient.transactionMerkle = + + const txBlockHeight = + data.bitcoinChainData.latestBlockHeight - + data.bitcoinChainData.accumulatedTxConfirmations + + 1 + + const transactionMerkle = new Map() + transactionMerkle.set( + `${transactionHash.toString()}${txBlockHeight.toString(16)}`, data.bitcoinChainData.transactionMerkleBranch + ) + const confirmations = new Map() confirmations.set( transactionHash.toString(), @@ -1263,6 +1338,29 @@ describe("Bitcoin", () => { ) bitcoinClient.confirmations = confirmations + const coinbaseTxHash = BitcoinTxHash.from( + BitcoinHashUtils.computeHash256( + Hex.from(data.bitcoinChainData.coinbaseRawTransaction.transactionHex) + ).toString() + ) + + const coinbaseHashes = new Map() + coinbaseHashes.set(txBlockHeight, coinbaseTxHash) + bitcoinClient.coinbaseHashes = coinbaseHashes + + const rawTransactions = new Map() + rawTransactions.set( + coinbaseTxHash.toString(), + data.bitcoinChainData.coinbaseRawTransaction + ) + bitcoinClient.rawTransactions = rawTransactions + + transactionMerkle.set( + `${coinbaseTxHash.toString()}${txBlockHeight.toString(16)}`, + data.bitcoinChainData.coinbaseMerkleBranch + ) + bitcoinClient.transactionMerkle = transactionMerkle + await validateBitcoinSpvProof( data.bitcoinChainData.transaction.transactionHash, data.requiredConfirmations, diff --git a/typescript/test/lib/ethereum.test.ts b/typescript/test/lib/ethereum.test.ts index 0cda5f971..1b89d253f 100644 --- a/typescript/test/lib/ethereum.test.ts +++ b/typescript/test/lib/ethereum.test.ts @@ -203,6 +203,10 @@ describe("Ethereum", () => { merkleProof: Hex.from("44444444"), txIndexInBlock: 5, bitcoinHeaders: Hex.from("66666666"), + coinbasePreimage: BitcoinHashUtils.computeSha256( + Hex.from("77777777") + ), + coinbaseProof: Hex.from("88888888"), }, { transactionHash: BitcoinTxHash.from( @@ -227,6 +231,10 @@ describe("Ethereum", () => { merkleProof: "0x44444444", txIndexInBlock: 5, bitcoinHeaders: "0x66666666", + coinbasePreimage: BitcoinHashUtils.computeSha256( + Hex.from("77777777") + ).toPrefixedString(), + coinbaseProof: "0x88888888", }, { txHash: @@ -301,6 +309,10 @@ describe("Ethereum", () => { merkleProof: Hex.from("44444444"), txIndexInBlock: 5, bitcoinHeaders: Hex.from("66666666"), + coinbasePreimage: BitcoinHashUtils.computeSha256( + Hex.from("77777777") + ), + coinbaseProof: Hex.from("88888888"), }, { transactionHash: BitcoinTxHash.from( @@ -327,6 +339,10 @@ describe("Ethereum", () => { merkleProof: "0x44444444", txIndexInBlock: 5, bitcoinHeaders: "0x66666666", + coinbasePreimage: BitcoinHashUtils.computeSha256( + Hex.from("77777777") + ).toPrefixedString(), + coinbaseProof: "0x88888888", }, { txHash: @@ -357,6 +373,8 @@ describe("Ethereum", () => { revealedAt: 1654774330, sweptAt: 1655033516, treasuryFee: BigNumber.from(200), + extraData: + "0x0000000000000000000000000000000000000000000000000000000000000000", } as any) }) @@ -400,6 +418,8 @@ describe("Ethereum", () => { revealedAt: 1654774330, sweptAt: 1655033516, treasuryFee: BigNumber.from(200), + extraData: + "0x0000000000000000000000000000000000000000000000000000000000000000", } as any) }) diff --git a/typescript/test/services/maintenance.test.ts b/typescript/test/services/maintenance.test.ts index 8b0f3d7bd..18c89f6bc 100644 --- a/typescript/test/services/maintenance.test.ts +++ b/typescript/test/services/maintenance.test.ts @@ -2,11 +2,14 @@ import { BigNumber, BigNumberish } from "ethers" import { MockTBTCContracts } from "../utils/mock-tbtc-contracts" import { MockBitcoinClient } from "../utils/mock-bitcoin-client" import { + BitcoinHashUtils, BitcoinNetwork, BitcoinRawTx, BitcoinTx, BitcoinTxHash, + BitcoinTxMerkleBranch, BitcoinUtxo, + Hex, MaintenanceService, RedemptionRequest, WalletTx, @@ -2439,20 +2442,55 @@ describe("Maintenance", () => { transactionHash.toString(), depositSweepProof.bitcoinChainData.rawTransaction ) - bitcoinClient.rawTransactions = rawTransactions bitcoinClient.latestHeight = depositSweepProof.bitcoinChainData.latestBlockHeight bitcoinClient.headersChain = depositSweepProof.bitcoinChainData.headersChain - bitcoinClient.transactionMerkle = + + const txBlockHeight = + depositSweepProof.bitcoinChainData.latestBlockHeight - + depositSweepProof.bitcoinChainData.accumulatedTxConfirmations + + 1 + + const transactionMerkle = new Map() + transactionMerkle.set( + `${transactionHash.toString()}${txBlockHeight.toString(16)}`, depositSweepProof.bitcoinChainData.transactionMerkleBranch + ) + const confirmations = new Map() confirmations.set( transactionHash.toString(), depositSweepProof.bitcoinChainData.accumulatedTxConfirmations ) bitcoinClient.confirmations = confirmations + + const coinbaseTxHash = BitcoinTxHash.from( + BitcoinHashUtils.computeHash256( + Hex.from( + depositSweepProof.bitcoinChainData.coinbaseRawTransaction + .transactionHex + ) + ).toString() + ) + + const coinbaseHashes = new Map() + coinbaseHashes.set(txBlockHeight, coinbaseTxHash) + bitcoinClient.coinbaseHashes = coinbaseHashes + + rawTransactions.set( + coinbaseTxHash.toString(), + depositSweepProof.bitcoinChainData.coinbaseRawTransaction + ) + bitcoinClient.rawTransactions = rawTransactions + + transactionMerkle.set( + `${coinbaseTxHash.toString()}${txBlockHeight.toString(16)}`, + depositSweepProof.bitcoinChainData.coinbaseMerkleBranch + ) + bitcoinClient.transactionMerkle = transactionMerkle + await maintenanceService.spv.submitDepositSweepProof( transactionHash, NO_MAIN_UTXO @@ -2475,6 +2513,12 @@ describe("Maintenance", () => { expect(bridgeLog[0].sweepProof.bitcoinHeaders).to.deep.equal( depositSweepProof.expectedSweepProof.sweepProof.bitcoinHeaders ) + expect(bridgeLog[0].sweepProof.coinbasePreimage).to.deep.equal( + depositSweepProof.expectedSweepProof.sweepProof.coinbasePreimage + ) + expect(bridgeLog[0].sweepProof.coinbaseProof).to.deep.equal( + depositSweepProof.expectedSweepProof.sweepProof.coinbaseProof + ) }) }) @@ -2515,14 +2559,23 @@ describe("Maintenance", () => { transactionHash.toString(), redemptionProof.bitcoinChainData.rawTransaction ) - bitcoinClient.rawTransactions = rawTransactions bitcoinClient.latestHeight = redemptionProof.bitcoinChainData.latestBlockHeight bitcoinClient.headersChain = redemptionProof.bitcoinChainData.headersChain - bitcoinClient.transactionMerkle = + + const txBlockHeight = + redemptionProof.bitcoinChainData.latestBlockHeight - + redemptionProof.bitcoinChainData.accumulatedTxConfirmations + + 1 + + const transactionMerkle = new Map() + transactionMerkle.set( + `${transactionHash.toString()}${txBlockHeight.toString(16)}`, redemptionProof.bitcoinChainData.transactionMerkleBranch + ) + const confirmations = new Map() confirmations.set( transactionHash.toString(), @@ -2530,6 +2583,31 @@ describe("Maintenance", () => { ) bitcoinClient.confirmations = confirmations + const coinbaseTxHash = BitcoinTxHash.from( + BitcoinHashUtils.computeHash256( + Hex.from( + redemptionProof.bitcoinChainData.coinbaseRawTransaction + .transactionHex + ) + ).toString() + ) + + const coinbaseHashes = new Map() + coinbaseHashes.set(txBlockHeight, coinbaseTxHash) + bitcoinClient.coinbaseHashes = coinbaseHashes + + rawTransactions.set( + coinbaseTxHash.toString(), + redemptionProof.bitcoinChainData.coinbaseRawTransaction + ) + bitcoinClient.rawTransactions = rawTransactions + + transactionMerkle.set( + `${coinbaseTxHash.toString()}${txBlockHeight.toString(16)}`, + redemptionProof.bitcoinChainData.coinbaseMerkleBranch + ) + bitcoinClient.transactionMerkle = transactionMerkle + await maintenanceService.spv.submitRedemptionProof( transactionHash, mainUtxo, @@ -2556,6 +2634,13 @@ describe("Maintenance", () => { expect(bridgeLog[0].redemptionProof.bitcoinHeaders).to.deep.equal( redemptionProof.expectedRedemptionProof.redemptionProof.bitcoinHeaders ) + expect(bridgeLog[0].redemptionProof.coinbasePreimage).to.deep.equal( + redemptionProof.expectedRedemptionProof.redemptionProof + .coinbasePreimage + ) + expect(bridgeLog[0].redemptionProof.coinbaseProof).to.deep.equal( + redemptionProof.expectedRedemptionProof.redemptionProof.coinbaseProof + ) }) }) }) diff --git a/typescript/test/utils/mock-bitcoin-client.ts b/typescript/test/utils/mock-bitcoin-client.ts index 399803331..98bf77958 100644 --- a/typescript/test/utils/mock-bitcoin-client.ts +++ b/typescript/test/utils/mock-bitcoin-client.ts @@ -21,13 +21,10 @@ export class MockBitcoinClient implements BitcoinClient { private _transactionHashes = new Map() private _latestHeight = 0 private _headersChain = Hex.from("") - private _transactionMerkle: BitcoinTxMerkleBranch = { - blockHeight: 0, - merkle: [], - position: 0, - } + private _transactionMerkle = new Map() private _broadcastLog: BitcoinRawTx[] = [] private _transactionHistory = new Map() + private _coinbaseHashes = new Map() set network(value: BitcoinNetwork) { this._network = value @@ -61,7 +58,7 @@ export class MockBitcoinClient implements BitcoinClient { this._headersChain = value } - set transactionMerkle(value: BitcoinTxMerkleBranch) { + set transactionMerkle(value: Map) { this._transactionMerkle = value } @@ -69,6 +66,10 @@ export class MockBitcoinClient implements BitcoinClient { this._transactionHistory = value } + set coinbaseHashes(value: Map) { + this._coinbaseHashes = value + } + get broadcastLog(): BitcoinRawTx[] { return this._broadcastLog } @@ -149,7 +150,11 @@ export class MockBitcoinClient implements BitcoinClient { blockHeight: number ): Promise { return new Promise((resolve, _) => { - resolve(this._transactionMerkle) + resolve( + this._transactionMerkle.get( + `${transactionHash.toString()}${blockHeight.toString(16)}` + ) as BitcoinTxMerkleBranch + ) }) } @@ -161,6 +166,8 @@ export class MockBitcoinClient implements BitcoinClient { } getCoinbaseTxHash(blockHeight: number): Promise { - throw new Error("not implemented") + return new Promise((resolve, _) => { + resolve(this._coinbaseHashes.get(blockHeight) as BitcoinTxHash) + }) } }