Skip to content

Commit

Permalink
chore: add test for verify_merkle_root_inclusion
Browse files Browse the repository at this point in the history
  • Loading branch information
DhananjayPurohit committed Nov 13, 2023
1 parent 7d93cba commit 9c45daa
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 43 deletions.
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,4 @@ pub mod mainstay;
pub mod inclusionproof;
pub mod verifycommitment;
pub mod rpcclient;
pub mod verifycommitment_test;
113 changes: 70 additions & 43 deletions src/verifycommitment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use bitcoincore_rpc::bitcoin::Txid;
use std::str::FromStr;
use hex::{encode, decode};
use bip32::{ExtendedPublicKey, ExtendedKeyAttrs, PublicKey, DerivationPath, ChildNumber};
use crate::verifycommitment_test::{MockClient, test_merkle_root};

pub fn verify_commitments(event_commitments: Vec<Vec<u8>>, latest_commitment: Vec<u8>) -> bool {
let mut concatenated_hash = Vec::new();
Expand Down Expand Up @@ -50,55 +51,81 @@ pub fn verify_slot_proof(slot: usize, inclusion_proof: &mut InclusionProof) -> b
}

pub fn verify_merkle_root_inclusion(txid: String, inclusion_proof: &mut InclusionProof) -> bool {
let client = Client::new(format!("{}:{}/", inclusion_proof.config.bitcoind_params.host, inclusion_proof.config.bitcoind_params.port).as_str(),
Auth::UserPass(inclusion_proof.config.bitcoind_params.rpc_user.to_string(),
inclusion_proof.config.bitcoind_params.rpc_password.to_string())).unwrap();
if cfg!(test) {
let client = MockClient::new();

match client.get_raw_transaction_info(&Txid::from_str(&txid).unwrap(), None) {
Ok(transaction) => {
let script_pubkey_from_tx = encode(&transaction.vout[0].script_pub_key.hex);
let merkle_root = decode(test_merkle_root).expect("Invalid merkle root hex string");
let initial_public_key_hex = &inclusion_proof.config.mainstay.base_pubkey;
let initial_chain_code_hex = &inclusion_proof.config.mainstay.chain_code;

match client.get_raw_transaction_info(&Txid::from_str(&txid).unwrap(), None) {
Ok(transaction) => {
let script_pubkey_from_tx = encode(&transaction.vout[0].script_pub_key.hex);
let merkle_root = inclusion_proof.merkle_root.lock().unwrap().as_bytes().to_vec();
let rev_merkle_root: Vec<u8> = merkle_root.iter().rev().cloned().collect();
let rev_merkle_root_hex = encode(rev_merkle_root);

let path = get_path_from_commitment(rev_merkle_root_hex).unwrap();

let initial_public_key_hex = &inclusion_proof.config.mainstay.base_pubkey;
let initial_chain_code_hex = &inclusion_proof.config.mainstay.chain_code;

let initial_public_key_bytes = decode(initial_public_key_hex).expect("Invalid public key hex string");
let mut public_key_bytes = [0u8; 33];
public_key_bytes.copy_from_slice(&initial_public_key_bytes);

let initial_public_key = bip32::secp256k1::PublicKey::from_bytes(public_key_bytes).expect("Invalid public key");
let mut initial_chain_code = decode(initial_chain_code_hex).expect("Invalid chain code hex string");
let mut initial_chain_code_array = [0u8; 32];
initial_chain_code_array.copy_from_slice(initial_chain_code.as_mut_slice());

let attrs = ExtendedKeyAttrs {
depth: 0,
parent_fingerprint: Default::default(),
child_number: Default::default(),
chain_code: initial_chain_code_array,
};

let initial_extended_pubkey = ExtendedPublicKey::new(initial_public_key, attrs);
let (child_pubkey, child_chain_code) = derive_child_key_and_chaincode(&initial_extended_pubkey, &path.to_string());

let script = create_1_of_1_multisig_script(child_pubkey);

let address = bitcoin::Address::p2sh(&script, bitcoin::Network::Bitcoin).unwrap();
let script_pubkey = encode(address.script_pubkey());

return script_pubkey == script_pubkey_from_tx;
let script_pubkey = derive_script_pubkey_from_merkle_root(merkle_root, initial_public_key_hex.to_string(), initial_chain_code_hex.to_string());

return script_pubkey == script_pubkey_from_tx;
}
Err(error) => {
println!("Error: {:?}", error);
}
}
Err(error) => {
println!("Error: {:?}", error);
} else {
let client = Client::new(format!("{}:{}/", inclusion_proof.config.bitcoind_params.host, inclusion_proof.config.bitcoind_params.port).as_str(),
Auth::UserPass(inclusion_proof.config.bitcoind_params.rpc_user.to_string(),
inclusion_proof.config.bitcoind_params.rpc_password.to_string())).unwrap();

match client.get_raw_transaction_info(&Txid::from_str(&txid).unwrap(), None) {
Ok(transaction) => {
let script_pubkey_from_tx = encode(&transaction.vout[0].script_pub_key.hex);
let merkle_root = inclusion_proof.merkle_root.lock().unwrap().as_bytes().to_vec();
let initial_public_key_hex = &inclusion_proof.config.mainstay.base_pubkey;
let initial_chain_code_hex = &inclusion_proof.config.mainstay.chain_code;

let script_pubkey = derive_script_pubkey_from_merkle_root(merkle_root, initial_public_key_hex.to_string(), initial_chain_code_hex.to_string());

return script_pubkey == script_pubkey_from_tx;
}
Err(error) => {
println!("Error: {:?}", error);
}
}
}
};

return false;
}

pub fn derive_script_pubkey_from_merkle_root(merkle_root: Vec<u8>, initial_public_key_hex: String, initial_chain_code_hex: String) -> String {
let rev_merkle_root: Vec<u8> = merkle_root.iter().rev().cloned().collect();
let rev_merkle_root_hex = encode(rev_merkle_root);
let path = get_path_from_commitment(rev_merkle_root_hex).unwrap();

let initial_public_key_bytes = decode(initial_public_key_hex).expect("Invalid public key hex string");
let mut public_key_bytes = [0u8; 33];
public_key_bytes.copy_from_slice(&initial_public_key_bytes);

let initial_public_key = bip32::secp256k1::PublicKey::from_bytes(public_key_bytes).expect("Invalid public key");
let mut initial_chain_code = decode(initial_chain_code_hex).expect("Invalid chain code hex string");
let mut initial_chain_code_array = [0u8; 32];
initial_chain_code_array.copy_from_slice(initial_chain_code.as_mut_slice());

let attrs = ExtendedKeyAttrs {
depth: 0,
parent_fingerprint: Default::default(),
child_number: Default::default(),
chain_code: initial_chain_code_array,
};

let initial_extended_pubkey = ExtendedPublicKey::new(initial_public_key, attrs);
let (child_pubkey, child_chain_code) = derive_child_key_and_chaincode(&initial_extended_pubkey, &path.to_string());

let script = create_1_of_1_multisig_script(child_pubkey);

let address = bitcoin::Address::p2sh(&script, bitcoin::Network::Bitcoin).unwrap();
let script_pubkey = encode(address.script_pubkey());

script_pubkey
}

pub fn get_path_from_commitment(commitment: String) -> Option<String> {
let path_size = 16;
let child_size = 4;
Expand Down
93 changes: 93 additions & 0 deletions src/verifycommitment_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
use crate::util;
use std::fs;
use crate::config::Config;
use crate::inclusionproof::InclusionProof;
use crate::verifycommitment::{verify_merkle_root_inclusion};
use bitcoincore_rpc::bitcoin::Txid;
use bitcoin::BlockHash;
use bitcoincore_rpc::Client;
use bitcoincore_rpc::json::{GetRawTransactionResult};
use serde_json::from_str;

const tx_data: &str = r#"
{
"txid": "b891111d35ffc72709140b7bd2a82fde20deca53831f42a96704dede42c793d2",
"hash": "b891111d35ffc72709140b7bd2a82fde20deca53831f42a96704dede42c793d2",
"version": 2,
"size": 194,
"vsize": 194,
"weight": 776,
"locktime": 0,
"vin": [
{
"txid": "047352f01e5e3f8adc04a797311dde3917f274e55ceafb78edc39ff5d87d16c5",
"vout": 0,
"scriptSig": {
"asm": "0 30440220049d3138f841b63e96725cb9e86a53a92cd1d9e1b0740f5d4cd2ae0bcab684bf0220208d555c7e24e4c01cf67dfa9161091533e9efd6d1602bb53a49f7195c16b037[ALL] 5121036bd7943325ed9c9e1a44d98a8b5759c4bf4807df4312810ed5fc09dfb967811951ae",
"hex": "004730440220049d3138f841b63e96725cb9e86a53a92cd1d9e1b0740f5d4cd2ae0bcab684bf0220208d555c7e24e4c01cf67dfa9161091533e9efd6d1602bb53a49f7195c16b03701255121036bd7943325ed9c9e1a44d98a8b5759c4bf4807df4312810ed5fc09dfb967811951ae"
},
"sequence": 4294967293
}
],
"vout": [
{
"value": 0.01040868,
"n": 0,
"scriptPubKey": {
"asm": "OP_HASH160 29d13058087ddf2d48de404376fdcb5c4abff4bc OP_EQUAL",
"desc": "addr(35W8E71bdDhQw4ZC7uUZvXG3qhyWVYxfMB)#4rtfrxzg",
"hex": "a91429d13058087ddf2d48de404376fdcb5c4abff4bc87",
"address": "35W8E71bdDhQw4ZC7uUZvXG3qhyWVYxfMB",
"type": "scripthash"
}
}
],
"hex": "0200000001c5167dd8f59fc3ed78fbea5ce574f21739de1d3197a704dc8a3f5e1ef0527304000000006f004730440220049d3138f841b63e96725cb9e86a53a92cd1d9e1b0740f5d4cd2ae0bcab684bf0220208d555c7e24e4c01cf67dfa9161091533e9efd6d1602bb53a49f7195c16b03701255121036bd7943325ed9c9e1a44d98a8b5759c4bf4807df4312810ed5fc09dfb967811951aefdffffff01e4e10f000000000017a91429d13058087ddf2d48de404376fdcb5c4abff4bc8700000000","blockhash":"000000000000000000036cb20420528cf0f00abb3a5716d80b5c87146b764d47",
"confirmations":15235,
"time":1690540748,
"blocktime":1690540748
}"#;
pub const test_merkle_root: &str = "8d0ad2782d8f6e3f63c6f9611841c239630b55061d558abcc6bac53349edac70";

pub struct MockClient {}

impl MockClient {
pub fn new() -> Self {
MockClient {}
}
pub fn get_raw_transaction_info(&self, txid: &Txid, blockhash: Option<&BlockHash>) -> Result<GetRawTransactionResult, Box<dyn std::error::Error>> {
let tx_info: GetRawTransactionResult = from_str(tx_data)?;
Ok(tx_info)
}
}

#[test]
fn test_verify_merkle_root_inclusion() {

let data_dir = util::get_default_data_dir();

let config_path = data_dir.join("example-config.toml");

// Read the configuration file
let contents = fs::read_to_string(&config_path);
let config = match contents {
Ok(data) => {
toml::from_str(&data).expect("Could not deserialize the config file content")
},
Err(_) => {
// If there's an error reading the file, use the default configuration
Config::default()
}
};

let mut inclusion_proof = InclusionProof::new(
"".to_string(),
"".to_string(),
"".to_string(),
Vec::new(),
config.clone()
);

let result = verify_merkle_root_inclusion("b891111d35ffc72709140b7bd2a82fde20deca53831f42a96704dede42c793d2".to_string(), &mut inclusion_proof);
assert_eq!(result, true);
}

0 comments on commit 9c45daa

Please sign in to comment.