diff --git a/typescript/api-reference/README.md b/typescript/api-reference/README.md index 4d28190ab..c2ca73552 100644 --- a/typescript/api-reference/README.md +++ b/typescript/api-reference/README.md @@ -611,7 +611,7 @@ Bitcoin transaction along with the inclusion proof. #### Defined in -[src/lib/bitcoin/spv.ts:64](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/bitcoin/spv.ts#L64) +[src/lib/bitcoin/spv.ts:75](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/bitcoin/spv.ts#L75) ___ @@ -686,7 +686,7 @@ Electrum script hash as a hex string. #### Defined in -[src/lib/electrum/client.ts:649](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/electrum/client.ts#L649) +[src/lib/electrum/client.ts:667](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/electrum/client.ts#L667) ___ @@ -957,7 +957,7 @@ The function should be used within a try-catch block. #### Defined in -[src/lib/bitcoin/spv.ts:145](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/bitcoin/spv.ts#L145) +[src/lib/bitcoin/spv.ts:180](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/bitcoin/spv.ts#L180) ___ diff --git a/typescript/api-reference/classes/ElectrumClient.md b/typescript/api-reference/classes/ElectrumClient.md index ca7c460ce..2b4bc55ba 100644 --- a/typescript/api-reference/classes/ElectrumClient.md +++ b/typescript/api-reference/classes/ElectrumClient.md @@ -24,6 +24,7 @@ Electrum-based implementation of the Bitcoin client. - [broadcast](ElectrumClient.md#broadcast) - [findAllUnspentTransactionOutputs](ElectrumClient.md#findallunspenttransactionoutputs) +- [getCoinbaseTxHash](ElectrumClient.md#getcoinbasetxhash) - [getHeadersChain](ElectrumClient.md#getheaderschain) - [getNetwork](ElectrumClient.md#getnetwork) - [getRawTransaction](ElectrumClient.md#getrawtransaction) @@ -167,6 +168,32 @@ ___ ___ +### getCoinbaseTxHash + +▸ **getCoinbaseTxHash**(`blockHeight`): `Promise`\<[`BitcoinTxHash`](BitcoinTxHash.md)\> + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `blockHeight` | `number` | + +#### Returns + +`Promise`\<[`BitcoinTxHash`](BitcoinTxHash.md)\> + +**`See`** + +#### Implementation of + +[BitcoinClient](../interfaces/BitcoinClient.md).[getCoinbaseTxHash](../interfaces/BitcoinClient.md#getcoinbasetxhash) + +#### Defined in + +[src/lib/electrum/client.ts:646](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/electrum/client.ts#L646) + +___ + ### getHeadersChain ▸ **getHeadersChain**(`blockHeight`, `chainLength`): `Promise`\<[`Hex`](Hex.md)\> diff --git a/typescript/api-reference/classes/EthereumBridge.md b/typescript/api-reference/classes/EthereumBridge.md index 0a14d40e3..bc84ce9b5 100644 --- a/typescript/api-reference/classes/EthereumBridge.md +++ b/typescript/api-reference/classes/EthereumBridge.md @@ -148,7 +148,7 @@ EthersContractHandle.\_totalRetryAttempts #### Defined in -[src/lib/ethereum/bridge.ts:494](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/ethereum/bridge.ts#L494) +[src/lib/ethereum/bridge.ts:498](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/ethereum/bridge.ts#L498) ___ @@ -177,7 +177,7 @@ Builds the UTXO hash based on the UTXO components. UTXO hash is computed as #### Defined in -[src/lib/ethereum/bridge.ts:618](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/ethereum/bridge.ts#L618) +[src/lib/ethereum/bridge.ts:622](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/ethereum/bridge.ts#L622) ___ @@ -204,7 +204,7 @@ ___ #### Defined in -[src/lib/ethereum/bridge.ts:429](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/ethereum/bridge.ts#L429) +[src/lib/ethereum/bridge.ts:433](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/ethereum/bridge.ts#L433) ___ @@ -334,7 +334,7 @@ Bridge.getNewWalletRegisteredEvents #### Defined in -[src/lib/ethereum/bridge.ts:530](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/ethereum/bridge.ts#L530) +[src/lib/ethereum/bridge.ts:534](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/ethereum/bridge.ts#L534) ___ @@ -361,7 +361,7 @@ Bridge.getRedemptionRequestedEvents #### Defined in -[src/lib/ethereum/bridge.ts:635](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/ethereum/bridge.ts#L635) +[src/lib/ethereum/bridge.ts:639](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/ethereum/bridge.ts#L639) ___ @@ -381,7 +381,7 @@ ___ #### Defined in -[src/lib/ethereum/bridge.ts:515](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/ethereum/bridge.ts#L515) +[src/lib/ethereum/bridge.ts:519](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/ethereum/bridge.ts#L519) ___ @@ -405,7 +405,7 @@ Parsed deposit request. #### Defined in -[src/lib/ethereum/bridge.ts:474](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/ethereum/bridge.ts#L474) +[src/lib/ethereum/bridge.ts:478](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/ethereum/bridge.ts#L478) ___ @@ -454,7 +454,7 @@ Parsed wallet data. #### Defined in -[src/lib/ethereum/bridge.ts:589](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/ethereum/bridge.ts#L589) +[src/lib/ethereum/bridge.ts:593](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/ethereum/bridge.ts#L593) ___ @@ -510,7 +510,7 @@ ___ #### Defined in -[src/lib/ethereum/bridge.ts:336](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/ethereum/bridge.ts#L336) +[src/lib/ethereum/bridge.ts:338](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/ethereum/bridge.ts#L338) ___ @@ -597,7 +597,7 @@ ___ #### Defined in -[src/lib/ethereum/bridge.ts:380](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/ethereum/bridge.ts#L380) +[src/lib/ethereum/bridge.ts:382](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/ethereum/bridge.ts#L382) ___ @@ -644,7 +644,7 @@ ___ #### Defined in -[src/lib/ethereum/bridge.ts:322](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/ethereum/bridge.ts#L322) +[src/lib/ethereum/bridge.ts:324](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/ethereum/bridge.ts#L324) ___ @@ -664,7 +664,7 @@ ___ #### Defined in -[src/lib/ethereum/bridge.ts:555](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/ethereum/bridge.ts#L555) +[src/lib/ethereum/bridge.ts:559](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/ethereum/bridge.ts#L559) ___ @@ -690,7 +690,7 @@ ___ #### Defined in -[src/lib/ethereum/bridge.ts:572](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/ethereum/bridge.ts#L572) +[src/lib/ethereum/bridge.ts:576](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/ethereum/bridge.ts#L576) ___ @@ -715,7 +715,7 @@ Deposit key. #### Defined in -[src/lib/ethereum/bridge.ts:455](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/ethereum/bridge.ts#L455) +[src/lib/ethereum/bridge.ts:459](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/ethereum/bridge.ts#L459) ___ diff --git a/typescript/api-reference/interfaces/BitcoinClient.md b/typescript/api-reference/interfaces/BitcoinClient.md index d09386d9e..5f5faa90b 100644 --- a/typescript/api-reference/interfaces/BitcoinClient.md +++ b/typescript/api-reference/interfaces/BitcoinClient.md @@ -12,6 +12,7 @@ Represents a Bitcoin client. - [broadcast](BitcoinClient.md#broadcast) - [findAllUnspentTransactionOutputs](BitcoinClient.md#findallunspenttransactionoutputs) +- [getCoinbaseTxHash](BitcoinClient.md#getcoinbasetxhash) - [getHeadersChain](BitcoinClient.md#getheaderschain) - [getNetwork](BitcoinClient.md#getnetwork) - [getRawTransaction](BitcoinClient.md#getrawtransaction) @@ -72,6 +73,28 @@ List of UTXOs. ___ +### getCoinbaseTxHash + +▸ **getCoinbaseTxHash**(`blockHeight`): `Promise`\<[`BitcoinTxHash`](../classes/BitcoinTxHash.md)\> + +Gets the hash of the coinbase transaction for the given block height. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `blockHeight` | `number` | Height of the block. | + +#### Returns + +`Promise`\<[`BitcoinTxHash`](../classes/BitcoinTxHash.md)\> + +#### Defined in + +[src/lib/bitcoin/client.ts:109](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/bitcoin/client.ts#L109) + +___ + ### getHeadersChain ▸ **getHeadersChain**(`blockHeight`, `chainLength`): `Promise`\<[`Hex`](../classes/Hex.md)\> diff --git a/typescript/api-reference/interfaces/BitcoinSpvProof.md b/typescript/api-reference/interfaces/BitcoinSpvProof.md index 81058b45f..8141fc10c 100644 --- a/typescript/api-reference/interfaces/BitcoinSpvProof.md +++ b/typescript/api-reference/interfaces/BitcoinSpvProof.md @@ -8,6 +8,8 @@ the Bitcoin blockchain. ### Properties - [bitcoinHeaders](BitcoinSpvProof.md#bitcoinheaders) +- [coinbasePreimage](BitcoinSpvProof.md#coinbasepreimage) +- [coinbaseProof](BitcoinSpvProof.md#coinbaseproof) - [merkleProof](BitcoinSpvProof.md#merkleproof) - [txIndexInBlock](BitcoinSpvProof.md#txindexinblock) @@ -26,6 +28,31 @@ Concatenated block headers in hexadecimal format. Each block header is ___ +### coinbasePreimage + +• **coinbasePreimage**: [`Hex`](../classes/Hex.md) + +The sha256 preimage of the coinbase transaction hash i.e., +the sha256 hash of the coinbase transaction. + +#### Defined in + +[src/lib/bitcoin/spv.ts:37](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/bitcoin/spv.ts#L37) + +___ + +### coinbaseProof + +• **coinbaseProof**: [`Hex`](../classes/Hex.md) + +Merkle proof of coinbase transaction inclusion in a block. + +#### Defined in + +[src/lib/bitcoin/spv.ts:42](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/bitcoin/spv.ts#L42) + +___ + ### merkleProof • **merkleProof**: [`Hex`](../classes/Hex.md) diff --git a/typescript/api-reference/interfaces/BitcoinTxMerkleBranch.md b/typescript/api-reference/interfaces/BitcoinTxMerkleBranch.md index 94c74a9c9..aca610752 100644 --- a/typescript/api-reference/interfaces/BitcoinTxMerkleBranch.md +++ b/typescript/api-reference/interfaces/BitcoinTxMerkleBranch.md @@ -20,7 +20,7 @@ The height of the block the transaction was confirmed in. #### Defined in -[src/lib/bitcoin/spv.ts:41](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/bitcoin/spv.ts#L41) +[src/lib/bitcoin/spv.ts:52](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/bitcoin/spv.ts#L52) ___ @@ -34,7 +34,7 @@ the deepest pairing first. Each hash is an unprefixed hex string. #### Defined in -[src/lib/bitcoin/spv.ts:48](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/bitcoin/spv.ts#L48) +[src/lib/bitcoin/spv.ts:59](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/bitcoin/spv.ts#L59) ___ @@ -46,4 +46,4 @@ The 0-based index of the transaction's position in the block. #### Defined in -[src/lib/bitcoin/spv.ts:53](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/bitcoin/spv.ts#L53) +[src/lib/bitcoin/spv.ts:64](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/bitcoin/spv.ts#L64) diff --git a/typescript/src/lib/bitcoin/client.ts b/typescript/src/lib/bitcoin/client.ts index 7befcb074..ca7699a16 100644 --- a/typescript/src/lib/bitcoin/client.ts +++ b/typescript/src/lib/bitcoin/client.ts @@ -101,4 +101,10 @@ export interface BitcoinClient { * @param transaction - Transaction to broadcast. */ broadcast(transaction: BitcoinRawTx): Promise + + /** + * Gets the hash of the coinbase transaction for the given block height. + * @param blockHeight - Height of the block. + */ + getCoinbaseTxHash(blockHeight: number): Promise } 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/electrum/client.ts b/typescript/src/lib/electrum/client.ts index a773cb86f..0d3d177c5 100644 --- a/typescript/src/lib/electrum/client.ts +++ b/typescript/src/lib/electrum/client.ts @@ -638,6 +638,24 @@ export class ElectrumClient implements BitcoinClient { }) }) } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {BitcoinClient#getCoinbaseTxHash} + */ + getCoinbaseTxHash(blockHeight: number): Promise { + return this.withElectrum(async (electrum: Electrum) => { + const txHash = await this.withBackoffRetrier()(async () => { + return await electrum.request("blockchain.transaction.id_from_pos", [ + blockHeight, + 0, + false, + ]) + }) + + return BitcoinTxHash.from(txHash) + }) + } } /** 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/electrum.test.ts b/typescript/test/lib/electrum.test.ts index 3de68b213..c6b5d47e9 100644 --- a/typescript/test/lib/electrum.test.ts +++ b/typescript/test/lib/electrum.test.ts @@ -274,6 +274,15 @@ describe("Electrum", () => { ) }) }) + + describe("getCoinbaseTxHash", () => { + it("should return proper coinbase tx hash", async () => { + const result = await electrumClient.getCoinbaseTxHash(2135502) + expect(result.toString()).to.be.equal( + "1f523d1ce7553ec609bae104812dede95aa38eb13d2c2c6b64ffe868bbc1a54c" + ) + }) + }) }) }) 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 35bd6a9b3..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 + ) }) } @@ -159,4 +164,10 @@ export class MockBitcoinClient implements BitcoinClient { resolve() }) } + + getCoinbaseTxHash(blockHeight: number): Promise { + return new Promise((resolve, _) => { + resolve(this._coinbaseHashes.get(blockHeight) as BitcoinTxHash) + }) + } } diff --git a/typescript/yarn.lock b/typescript/yarn.lock index bf93f537f..5e86c6ab9 100644 --- a/typescript/yarn.lock +++ b/typescript/yarn.lock @@ -1516,16 +1516,16 @@ resolved "https://registry.yarnpkg.com/@keep-network/bitcoin-spv-sol/-/bitcoin-spv-sol-3.4.0-solc-0.8.tgz#8b44c246ffab8ea993efe196f6bf385b1a3b84dc" integrity sha512-KlpY9BbasyLvYXSS7dsJktgRChu/yjdFLOX8ldGA/pltLicCm/l0F4oqxL8wSws9XD12vq9x0B5qzPygVLB2TQ== -"@keep-network/ecdsa@2.1.0-dev.13", "@keep-network/ecdsa@development": - version "2.1.0-dev.13" - resolved "https://registry.yarnpkg.com/@keep-network/ecdsa/-/ecdsa-2.1.0-dev.13.tgz#31f2f28e74485dcbe03f782f5f67ac299203f3f1" - integrity sha512-Gv9nNQQkE/VTitFSiJqQUXczIbHWOBVG7UH6h3MDo7Pcgl4iuXhM5nmQdRd9vqjU42rI+TyPuSErWjg2+QEEEw== +"@keep-network/ecdsa@2.1.0-dev.17", "@keep-network/ecdsa@development": + version "2.1.0-dev.17" + resolved "https://registry.yarnpkg.com/@keep-network/ecdsa/-/ecdsa-2.1.0-dev.17.tgz#2e6abea11c094adfa9f52dfe0e7d3befa5fe2dfb" + integrity sha512-48zLogWDObqf3uGffDd8N2iP7m3rBinF+tAl7DidY/87eqWNydhpbO4j1H5i9vZ+Axi6nOQcuvpLlS7p9pjOFw== dependencies: - "@keep-network/random-beacon" "2.1.0-dev.13" + "@keep-network/random-beacon" "2.1.0-dev.17" "@keep-network/sortition-pools" "^2.0.0-pre.16" "@openzeppelin/contracts" "^4.6.0" "@openzeppelin/contracts-upgradeable" "^4.6.0" - "@threshold-network/solidity-contracts" "1.3.0-dev.5" + "@threshold-network/solidity-contracts" "1.3.0-dev.11" "@keep-network/keep-core@1.8.0-dev.5": version "1.8.0-dev.5" @@ -1557,25 +1557,15 @@ version "0.0.1" resolved "https://codeload.github.com/keep-network/prettier-config-keep/tar.gz/a1a333e7ac49928a0f6ed39421906dd1e46ab0f3" -"@keep-network/random-beacon@2.1.0-dev.13": - version "2.1.0-dev.13" - resolved "https://registry.yarnpkg.com/@keep-network/random-beacon/-/random-beacon-2.1.0-dev.13.tgz#8b4d20456e17cb76531a25c98370d3a6da8c8be5" - integrity sha512-o5+LvzQB5Sqnpbu5Wr97HvU63rlw9v/O5ZGxDiWe4XwzFhC/FEnza+uWgWm1IJkFVrQj/DzYokqkzgANx/lBnA== +"@keep-network/random-beacon@2.1.0-dev.17": + version "2.1.0-dev.17" + resolved "https://registry.yarnpkg.com/@keep-network/random-beacon/-/random-beacon-2.1.0-dev.17.tgz#5fb2621948aa2fe07ceb134ba76f737b7e6d85cd" + integrity sha512-alfd2sHdMrX15qKzM4zwkZ3l/CXboLoeos4l3WvChW978VJIwUPm2ZIXd8tNTaHlykQ57eSSX7esaLfIjeO3Kg== dependencies: "@keep-network/sortition-pools" "^2.0.0-pre.16" "@openzeppelin/contracts" "4.7.3" "@thesis/solidity-contracts" "github:thesis/solidity-contracts#4985bcf" - "@threshold-network/solidity-contracts" "1.3.0-dev.5" - -"@keep-network/random-beacon@2.1.0-dev.14": - version "2.1.0-dev.14" - resolved "https://registry.yarnpkg.com/@keep-network/random-beacon/-/random-beacon-2.1.0-dev.14.tgz#d9fac9fa8a5a06ea0985114c4ca79e4805c16d55" - integrity sha512-FdVSW2VtUIcwPCrnrWUudbXOFi+SKZ6cEz7P3+gO+49DFas4ApH6lkRILD/DUHQDMV7D56TxAdw/DHt0dbA+wg== - dependencies: - "@keep-network/sortition-pools" "^2.0.0-pre.16" - "@openzeppelin/contracts" "4.7.3" - "@thesis/solidity-contracts" "github:thesis/solidity-contracts#4985bcf" - "@threshold-network/solidity-contracts" "1.3.0-dev.5" + "@threshold-network/solidity-contracts" "1.3.0-dev.8" "@keep-network/sortition-pools@1.2.0-dev.1": version "1.2.0-dev.1" @@ -1593,13 +1583,13 @@ "@thesis/solidity-contracts" "github:thesis/solidity-contracts#4985bcf" "@keep-network/tbtc-v2@development": - version "1.5.0-dev.3" - resolved "https://registry.yarnpkg.com/@keep-network/tbtc-v2/-/tbtc-v2-1.5.0-dev.3.tgz#814682bf9f627780137430c3ad5a6eaf5638eb3c" - integrity sha512-Vf/NOBf0ybN5cPP/R0LXJbZeMWVEkga7mWqie1HyktqJi8pp4XbHoJ6WIro+qrH47AymWv/S2mGYlU4Smsihrw== + version "1.6.0-dev.14" + resolved "https://registry.yarnpkg.com/@keep-network/tbtc-v2/-/tbtc-v2-1.6.0-dev.14.tgz#45e5ec1ca44bd47c8beb246b9718ce93b191b4e7" + integrity sha512-opUFWtQ3tSPCG0z9FcsOVTp4ATA+XyL2nQK9GgJ6BRfVv3RAYKM9Z97NFcAE9bjcbfwvYKg8P64YY680e9XFvA== dependencies: "@keep-network/bitcoin-spv-sol" "3.4.0-solc-0.8" - "@keep-network/ecdsa" "2.1.0-dev.13" - "@keep-network/random-beacon" "2.1.0-dev.14" + "@keep-network/ecdsa" "2.1.0-dev.17" + "@keep-network/random-beacon" "2.1.0-dev.17" "@keep-network/tbtc" "1.1.2-dev.1" "@openzeppelin/contracts" "^4.8.1" "@openzeppelin/contracts-upgradeable" "^4.8.1" @@ -1955,10 +1945,20 @@ dependencies: "@openzeppelin/contracts" "^4.1.0" -"@threshold-network/solidity-contracts@1.3.0-dev.5": - version "1.3.0-dev.5" - resolved "https://registry.yarnpkg.com/@threshold-network/solidity-contracts/-/solidity-contracts-1.3.0-dev.5.tgz#f7a2727d627a10218f0667bc0d33e19ed8f87fdc" - integrity sha512-AInTKQkJ0PKa32q2m8GnZFPYEArsnvOwhIFdBFaHdq9r4EGyqHMf4YY1WjffkheBZ7AQ0DNA8Lst30kBoQd0SA== +"@threshold-network/solidity-contracts@1.3.0-dev.11": + version "1.3.0-dev.11" + resolved "https://registry.yarnpkg.com/@threshold-network/solidity-contracts/-/solidity-contracts-1.3.0-dev.11.tgz#849f20a5094c93359bbdea0c42780c318f985ee0" + integrity sha512-QQJB17BvuU/7UaitneoD7zFmIA3fZQ3FAvOAP2q+FkWEBZPYtAMf3+vB7y+Y+QlrcUl1kcA9wXD5auirsdxCBQ== + dependencies: + "@keep-network/keep-core" ">1.8.1-dev <1.8.1-goerli" + "@openzeppelin/contracts" "~4.5.0" + "@openzeppelin/contracts-upgradeable" "~4.5.2" + "@thesis/solidity-contracts" "github:thesis/solidity-contracts#4985bcf" + +"@threshold-network/solidity-contracts@1.3.0-dev.8": + version "1.3.0-dev.8" + resolved "https://registry.yarnpkg.com/@threshold-network/solidity-contracts/-/solidity-contracts-1.3.0-dev.8.tgz#6de25dc6ce374cfbdf3b67c72097044631222f3a" + integrity sha512-s6SFZyf1xXgOdMK1zYnjsURnVz7Xxzf0z/34vH+hDg8n/G8L0jPR6Iz4laWSSL2y1P3ffFAFTUMvwfJMJitfVw== dependencies: "@keep-network/keep-core" ">1.8.1-dev <1.8.1-goerli" "@openzeppelin/contracts" "~4.5.0"