Merge bitcoindevkit/rust-electrum-client#122: Add utility to validate GetMerkleRes
54fd52d898Add test coverage for `validate_merkle_proof` (Elias Rohrer)fe33e19bdaAdd utility for validating a Merkle inclusion proof (Elias Rohrer)dd872d6714Make response types `Clone` (Elias Rohrer) Pull request description: I recently needed to validate a Merkle inclusion proof as retrieved via `transaction_get_merkle`. As I figured it might be useful to other people, too, we add it here as a simple utility method. ACKs for top commit: notmandatory: ACK54fd52d898Tree-SHA512: aac12160d5b91a011988f45013eb92924c2dfb244c1720e73dc5bcb731e69065c38e022502c756100d8ee6c9af06efa0de9bbfbb2b9e3c2e34d3223539206e1c
This commit is contained in:
commit
e4d2b1d194
@ -61,6 +61,7 @@ mod config;
|
||||
pub mod raw_client;
|
||||
mod stream;
|
||||
mod types;
|
||||
pub mod utils;
|
||||
|
||||
pub use api::ElectrumApi;
|
||||
pub use batch::Batch;
|
||||
|
||||
@ -1079,6 +1079,8 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
|
||||
mod test {
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::utils;
|
||||
|
||||
use super::RawClient;
|
||||
use api::ElectrumApi;
|
||||
|
||||
@ -1300,23 +1302,43 @@ mod test {
|
||||
|
||||
let client = RawClient::new(get_test_server(), None).unwrap();
|
||||
|
||||
let resp = client
|
||||
.transaction_get_merkle(
|
||||
&Txid::from_str("cc2ca076fd04c2aeed6d02151c447ced3d09be6fb4d4ef36cb5ed4e7a3260566")
|
||||
.unwrap(),
|
||||
630000,
|
||||
)
|
||||
.unwrap();
|
||||
let txid =
|
||||
Txid::from_str("1f7ff3c407f33eabc8bec7d2cc230948f2249ec8e591bcf6f971ca9366c8788d")
|
||||
.unwrap();
|
||||
let resp = client.transaction_get_merkle(&txid, 630000).unwrap();
|
||||
assert_eq!(resp.block_height, 630000);
|
||||
assert_eq!(resp.pos, 0);
|
||||
assert_eq!(resp.pos, 68);
|
||||
assert_eq!(resp.merkle.len(), 12);
|
||||
assert_eq!(
|
||||
resp.merkle[0],
|
||||
[
|
||||
30, 10, 161, 245, 132, 125, 136, 198, 186, 138, 107, 216, 92, 22, 145, 81, 130,
|
||||
126, 200, 65, 121, 158, 105, 111, 38, 151, 38, 147, 144, 224, 5, 218
|
||||
34, 65, 51, 64, 49, 139, 115, 189, 185, 246, 70, 225, 168, 193, 217, 195, 47, 66,
|
||||
179, 240, 153, 24, 114, 215, 144, 196, 212, 41, 39, 155, 246, 25
|
||||
]
|
||||
);
|
||||
|
||||
// Check we can verify the merkle proof validity, but fail if we supply wrong data.
|
||||
let block_header = client.block_header(resp.block_height).unwrap();
|
||||
assert!(utils::validate_merkle_proof(
|
||||
&txid,
|
||||
&block_header.merkle_root,
|
||||
&resp
|
||||
));
|
||||
|
||||
let mut fail_resp = resp.clone();
|
||||
fail_resp.pos = 13;
|
||||
assert!(!utils::validate_merkle_proof(
|
||||
&txid,
|
||||
&block_header.merkle_root,
|
||||
&fail_resp
|
||||
));
|
||||
|
||||
let fail_block_header = client.block_header(resp.block_height + 1).unwrap();
|
||||
assert!(!utils::validate_merkle_proof(
|
||||
&txid,
|
||||
&fail_block_header.merkle_root,
|
||||
&resp
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
18
src/types.rs
18
src/types.rs
@ -161,7 +161,7 @@ where
|
||||
}
|
||||
|
||||
/// Response to a [`script_get_history`](../client/struct.Client.html#method.script_get_history) request.
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct GetHistoryRes {
|
||||
/// Confirmation height of the transaction. 0 if unconfirmed, -1 if unconfirmed while some of
|
||||
/// its inputs are unconfirmed too.
|
||||
@ -173,7 +173,7 @@ pub struct GetHistoryRes {
|
||||
}
|
||||
|
||||
/// Response to a [`script_list_unspent`](../client/struct.Client.html#method.script_list_unspent) request.
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct ListUnspentRes {
|
||||
/// Confirmation height of the transaction that created this output.
|
||||
pub height: usize,
|
||||
@ -186,7 +186,7 @@ pub struct ListUnspentRes {
|
||||
}
|
||||
|
||||
/// Response to a [`server_features`](../client/struct.Client.html#method.server_features) request.
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct ServerFeaturesRes {
|
||||
/// Server version reported.
|
||||
pub server_version: String,
|
||||
@ -204,7 +204,7 @@ pub struct ServerFeaturesRes {
|
||||
}
|
||||
|
||||
/// Response to a [`server_features`](../client/struct.Client.html#method.server_features) request.
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct GetHeadersRes {
|
||||
/// Maximum number of headers returned in a single response.
|
||||
pub max: usize,
|
||||
@ -219,7 +219,7 @@ pub struct GetHeadersRes {
|
||||
}
|
||||
|
||||
/// Response to a [`script_get_balance`](../client/struct.Client.html#method.script_get_balance) request.
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct GetBalanceRes {
|
||||
/// Confirmed balance in Satoshis for the address.
|
||||
pub confirmed: u64,
|
||||
@ -230,7 +230,7 @@ pub struct GetBalanceRes {
|
||||
}
|
||||
|
||||
/// Response to a [`transaction_get_merkle`](../client/struct.Client.html#method.transaction_get_merkle) request.
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct GetMerkleRes {
|
||||
/// Height of the block that confirmed the transaction
|
||||
pub block_height: usize,
|
||||
@ -242,7 +242,7 @@ pub struct GetMerkleRes {
|
||||
}
|
||||
|
||||
/// Notification of a new block header
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct HeaderNotification {
|
||||
/// New block height.
|
||||
pub height: usize,
|
||||
@ -252,7 +252,7 @@ pub struct HeaderNotification {
|
||||
}
|
||||
|
||||
/// Notification of a new block header with the header encoded as raw bytes
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct RawHeaderNotification {
|
||||
/// New block height.
|
||||
pub height: usize,
|
||||
@ -273,7 +273,7 @@ impl TryFrom<RawHeaderNotification> for HeaderNotification {
|
||||
}
|
||||
|
||||
/// Notification of the new status of a script
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct ScriptNotification {
|
||||
/// Address that generated this notification.
|
||||
pub scripthash: ScriptHash,
|
||||
|
||||
43
src/utils.rs
Normal file
43
src/utils.rs
Normal file
@ -0,0 +1,43 @@
|
||||
//! Utilities helping to handle Electrum-related data.
|
||||
|
||||
use bitcoin::hash_types::TxMerkleNode;
|
||||
use bitcoin::hashes::sha256d::Hash as Sha256d;
|
||||
use bitcoin::hashes::Hash;
|
||||
use bitcoin::Txid;
|
||||
use types::GetMerkleRes;
|
||||
|
||||
/// Verifies a Merkle inclusion proof as retrieved via [`transaction_get_merkle`] for a transaction with the
|
||||
/// given `txid` and `merkle_root` as included in the [`BlockHeader`].
|
||||
///
|
||||
/// Returns `true` if the transaction is included in the corresponding block, and `false`
|
||||
/// otherwise.
|
||||
///
|
||||
/// [`transaction_get_merkle`]: crate::ElectrumApi::transaction_get_merkle
|
||||
/// [`BlockHeader`]: bitcoin::BlockHeader
|
||||
pub fn validate_merkle_proof(
|
||||
txid: &Txid,
|
||||
merkle_root: &TxMerkleNode,
|
||||
merkle_res: &GetMerkleRes,
|
||||
) -> bool {
|
||||
let mut index = merkle_res.pos;
|
||||
let mut cur = txid.to_raw_hash();
|
||||
for bytes in &merkle_res.merkle {
|
||||
let mut reversed = [0u8; 32];
|
||||
reversed.copy_from_slice(bytes);
|
||||
reversed.reverse();
|
||||
// unwrap() safety: `reversed` has len 32 so `from_slice` can never fail.
|
||||
let next_hash = Sha256d::from_slice(&reversed).unwrap();
|
||||
|
||||
let (left, right) = if index % 2 == 0 {
|
||||
(cur, next_hash)
|
||||
} else {
|
||||
(next_hash, cur)
|
||||
};
|
||||
|
||||
let data = [&left[..], &right[..]].concat();
|
||||
cur = Sha256d::hash(&data);
|
||||
index /= 2;
|
||||
}
|
||||
|
||||
cur == merkle_root.to_raw_hash()
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user