Skip to content
Closed
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ staking_credentials = { git = "https://github.com/civkit/staking-credentials.git
reqwest = "0.11.20"
base64 = "0.21.4"
jsonrpc = "0.14.0"
rs_merkle = "1.4.1"
hex = "0.4.3"
bip32 = { version = "0.5.1", features = ["secp256k1"] }


[build-dependencies]
tonic-build = "0.9"
Expand Down
13 changes: 13 additions & 0 deletions example-config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,16 @@ cli_port = 50031

[logging]
level = "info"

[mainstay]
url = "http://localhost:4000"
position = 1
token = "14b2b754-5806-4157-883c-732baf88849c"
base_pubkey = "031dd94c5262454986a2f0a6c557d2cbe41ec5a8131c588b9367c9310125a8a7dc"
chain_code = "0a090f710e47968aee906804f211cf10cde9a11e14908ca0f78cc55dd190ceaa"

[bicoind]
host = "http://127.0.0.1"
port = 18443
rpc_user = "civkitd_client"
rpc_password = "hello_world"
7 changes: 5 additions & 2 deletions src/bitcoind_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ use tokio::sync::Mutex as TokioMutex;

use tokio::time::{sleep, Duration};

use crate::inclusionproof::InclusionProof;
use crate::verifycommitment::{verify_merkle_root_inclusion};

#[derive(Debug)]
pub enum BitcoindRequest {
CheckRpcCall,
Expand Down Expand Up @@ -47,8 +50,8 @@ impl BitcoindClient {

}

pub async fn verifytxoutproof() {

pub async fn verifytxoutproof(mut inclusion_proof: InclusionProof) -> bool {
return verify_merkle_root_inclusion(&mut inclusion_proof);
}

//TODO: run and dispatch call to bitcoind
Expand Down
10 changes: 7 additions & 3 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,11 @@ pub struct Logging {

#[derive(Clone, PartialEq, Eq, Debug, Deserialize)]
pub struct Mainstay {
pub url: String,
pub position: i32,
pub token: String,
pub url: String,
pub position: u64,
pub token: String,
pub base_pubkey: String,
pub chain_code: String,
}

#[derive(Clone, PartialEq, Eq, Debug, Deserialize)]
Expand Down Expand Up @@ -84,6 +86,8 @@ impl Default for Config {
url: "https://mainstay.xyz/api/v1".to_string(),
position: 1,
token: "14b2b754-5806-4157-883c-732baf88849c".to_string(),
base_pubkey: "031dd94c5262454986a2f0a6c557d2cbe41ec5a8131c588b9367c9310125a8a7dc".to_string(),
chain_code: "0a090f710e47968aee906804f211cf10cde9a11e14908ca0f78cc55dd190ceaa".to_string(),
},
bitcoind_params: BitcoindParams {
host: "https://127.0.0.1".to_string(),
Expand Down
28 changes: 26 additions & 2 deletions src/inclusionproof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@ use std::sync::Arc;
use std::thread;
use std::sync::Mutex;
use tokio::time::{sleep, Duration};
use serde_json::Value;
use serde_json::{Value, from_str, to_value};

use crate::mainstay::{get_proof};
use crate::config::Config;
use crate::nostr_db::{write_new_inclusion_proof_db};
use crate::rpcclient::{Client, Auth};

pub struct InclusionProof {
pub txid: Arc<Mutex<String>>,
pub commitment: Arc<Mutex<String>>,
pub merkle_root: Arc<Mutex<String>>,
pub ops: Arc<Mutex<Vec<Ops>>>,
pub txoutproof: Arc<Mutex<String>>,
pub raw_tx: Arc<Mutex<Value>>,
pub config: Config,
}

Expand All @@ -31,12 +34,14 @@ pub struct Ops {
}

impl InclusionProof {
pub fn new(txid: String, commitment: String, merkle_root: String, ops: Vec<Ops>, our_config: Config) -> Self {
pub fn new(txid: String, commitment: String, merkle_root: String, ops: Vec<Ops>, txout_proof: String, raw_tx: Value, our_config: Config) -> Self {
InclusionProof {
txid: Arc::new(Mutex::new(txid)),
commitment: Arc::new(Mutex::new(commitment)),
merkle_root: Arc::new(Mutex::new(merkle_root)),
ops: Arc::new(Mutex::new(ops)),
txoutproof: Arc::new(Mutex::new(txout_proof)),
raw_tx: Arc::new(Mutex::new(raw_tx)),
config: our_config,
}
}
Expand Down Expand Up @@ -67,6 +72,25 @@ impl InclusionProof {
Ops { append, commitment }
})
.collect();

let client = Client::new(format!("{}:{}/", self.config.bitcoind_params.host, self.config.bitcoind_params.port).as_str(),
Auth::UserPass(self.config.bitcoind_params.rpc_user.to_string(),
self.config.bitcoind_params.rpc_password.to_string())).unwrap();
let txid_json_value = to_value(txid).unwrap();
let txid_json = Value::Array(vec![txid_json_value]);
if let Ok(response) = client.call("gettxoutproof", &[txid_json]) {
if let Some(raw_value) = response.result {
let mut txout_proof = raw_value.get().to_string();
*self.txoutproof.lock().unwrap() = txout_proof;
}
}

if let Ok(response) = client.call("getrawtransaction", &[Value::String(txid.to_string()), Value::Bool(true)]) {
if let Some(raw_value) = response.result {
let json_value: Value = from_str(raw_value.get()).unwrap();
*self.raw_tx.lock().unwrap() = json_value;
}
}
write_new_inclusion_proof_db(self).await;
}
},
Expand Down
2 changes: 1 addition & 1 deletion src/kindprocessor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use crate::util::is_replaceable;
use tokio::sync::mpsc;
use tokio::sync::Mutex as TokioMutex;
use tokio::time::{sleep, Duration};
use base64::encode;
use hex::encode;

pub struct NoteProcessor {
note_counters: Mutex<u64>,
Expand Down
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;
3 changes: 2 additions & 1 deletion src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ use tokio::sync::{oneshot, mpsc};
use tokio_tungstenite::WebSocketStream;

use tonic::{transport::Server, Request, Response, Status};
use serde_json::Value;

//TODO: rename boarctrl to something like relayctrl ?
pub mod adminctrl {
Expand Down Expand Up @@ -418,7 +419,7 @@ fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let service_manager = ServiceManager::new(node_signer, anchor_manager, service_mngr_events_send, service_mngr_peer_send, manager_send_dbrequests, manager_send_bitcoind_request, config.clone());

// We initialize the inclusion proof with txid, commitment and merkle proof as empty strings.
let mut inclusion_proof = InclusionProof::new("".to_string(), "".to_string(), "".to_string(), Vec::new(), config.clone());
let mut inclusion_proof = InclusionProof::new("".to_string(), "".to_string(), "".to_string(), Vec::new(), "".to_string(), Value::Null, config.clone());

let addr = format!("[::1]:{}", cli.cli_port).parse()?;

Expand Down
137 changes: 134 additions & 3 deletions src/verifycommitment.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
use bitcoin_hashes::{sha256, Hash};
use bitcoin_hashes::{sha256, Hash, hash160};
use crate::inclusionproof::{InclusionProof};
use rs_merkle::{MerkleTree, MerkleProof};
use rs_merkle::algorithms::Sha256;
use crate::rpcclient::{Client, Auth};
use std::str::FromStr;
use hex::{encode, decode};
use bip32::{ExtendedPublicKey, ExtendedKeyAttrs, PublicKey, DerivationPath, ChildNumber};
use serde_json::{from_str, Value};

pub fn verify_commitments(event_commitments: Vec<Vec<u8>>, latest_commitment: Vec<u8>) -> bool {
pub fn verify_commitments(event_commitments: Vec<Vec<u8>>, inclusion_proof: &mut InclusionProof) -> bool {
let mut concatenated_hash = Vec::new();

let mut latest_commitment = inclusion_proof.commitment.lock().unwrap().as_bytes().to_vec();
for event_commitment in &event_commitments {
if concatenated_hash.is_empty() {
concatenated_hash.extend_from_slice(&event_commitments[0]);
Expand All @@ -16,3 +24,126 @@ pub fn verify_commitments(event_commitments: Vec<Vec<u8>>, latest_commitment: Ve

calculated_commitment == latest_commitment
}

pub fn verify_slot_proof(slot: usize, inclusion_proof: &mut InclusionProof) -> bool {
let merkle_root = inclusion_proof.merkle_root.lock().unwrap();
let commitment = inclusion_proof.commitment.lock().unwrap();
let ops = inclusion_proof.ops.lock().unwrap();
let ops_commitments: Vec<&str> = ops.iter().map(|pth| pth.commitment.as_str()).collect();

let leaf_hashes: Vec<[u8; 32]> = ops_commitments
.iter()
.map(|x| sha256::Hash::hash(x.as_bytes()).into_inner())
.collect();

let leaf_to_prove = leaf_hashes.get(slot).unwrap();

let merkle_tree = MerkleTree::<Sha256>::from_leaves(&leaf_hashes);
let merkle_proof = merkle_tree.proof(&[slot]);
let merkle_root = merkle_tree.root().unwrap();

let proof_bytes = merkle_proof.to_bytes();

let proof = MerkleProof::<Sha256>::try_from(proof_bytes).unwrap();

return proof.verify(merkle_root, &[slot], &[*leaf_to_prove], leaf_hashes.len());
}

pub fn verify_merkle_root_inclusion(inclusion_proof: &mut InclusionProof) -> bool {
let script_pubkey_from_tx = &inclusion_proof.raw_tx.lock().unwrap()["vout"][0]["scriptPubKey"]["hex"].as_str().unwrap().to_string();
let merkle_root = decode(inclusion_proof.merkle_root.lock().unwrap().as_bytes().to_vec()).unwrap();
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;
}

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;

if commitment.len() != path_size * child_size {
return None;
}

let mut derivation_path = String::new();
for it in 0..path_size {
let index = &commitment[it * child_size..it * child_size + child_size];
let decoded_index = u64::from_str_radix(index, 16).unwrap();
derivation_path.push_str(&decoded_index.to_string());
if it < path_size - 1 {
derivation_path.push('/');
}
}

Some(derivation_path)
}

fn derive_child_key_and_chaincode(mut parent: &ExtendedPublicKey<bip32::secp256k1::PublicKey>, path: &str) -> (bip32::secp256k1::PublicKey, [u8; 32]) {
let mut extended_key = parent.clone();
let mut chain_code = parent.attrs().chain_code.clone();
let mut public_key = parent.public_key().clone();
for step in path.split('/') {
match step {
"m" => continue,
number => {
if let Ok(index) = number.parse::<u32>() {
let new_extended_key = extended_key.derive_child(ChildNumber(index)).expect("Failed to derive child key");
chain_code = new_extended_key.attrs().chain_code;
public_key = *new_extended_key.public_key();
extended_key = new_extended_key.clone();
} else {
panic!("Invalid derivation path step: {}", step);
}
}
}
}
(public_key, chain_code)
}

fn create_1_of_1_multisig_script(pubkey: bip32::secp256k1::PublicKey) -> bitcoin::blockdata::script::Script {
let public_key = bitcoin::util::key::PublicKey {
inner: bitcoin::secp256k1::PublicKey::from_slice(&pubkey.to_bytes()).unwrap(),
compressed: true,
};
let script = bitcoin::blockdata::script::Builder::new()
.push_opcode(bitcoin::blockdata::opcodes::all::OP_PUSHNUM_1)
.push_key(&public_key)
.push_opcode(bitcoin::blockdata::opcodes::all::OP_PUSHNUM_1)
.push_opcode(bitcoin::blockdata::opcodes::all::OP_CHECKMULTISIG)
.into_script();
script
}
Loading