diff --git a/src/address_verifier.rs b/src/address_verifier.rs index ebc44c4..8d662f2 100644 --- a/src/address_verifier.rs +++ b/src/address_verifier.rs @@ -71,18 +71,20 @@ impl AddressVerifier { let mut interval = interval(Duration::from_secs(600)); loop { + interval.tick().await; + let Ok(publish_date) = Self::publish_date().await else { error!("couldn't extract the OFAC document publish date"); continue; }; + info!("{:?}", *last_update.read().await); if *last_update.read().await >= publish_date { info!("OFAC list is up-to-date"); continue; } let mut sanctioned_addresses = sanctioned_addresses.write().await; - interval.tick().await; let mut last_update = last_update.write().await; *last_update = publish_date; diff --git a/src/bin/zkbtc.rs b/src/bin/zkbtc.rs index 17ef17c..acf3769 100644 --- a/src/bin/zkbtc.rs +++ b/src/bin/zkbtc.rs @@ -174,345 +174,342 @@ enum Commands { #[tokio::main] async fn main() -> Result<()> { - // // init default log level to info (unless RUST_LOG is set) - // env_logger::init_from_env(env_logger::Env::default().default_filter_or("info")); - - // // debug info - // info!( - // "- zkbitcoin_address: {}", - // taproot_addr_from(ZKBITCOIN_PUBKEY).unwrap().to_string() - // ); - // info!( - // "- zkbitcoin_fund_address: {}", - // taproot_addr_from(ZKBITCOIN_FEE_PUBKEY).unwrap().to_string() - // ); - - // // parse CLI - // let cli = Cli::parse(); - // match &cli.command { - // // Alice's command - // Commands::DeployZkapp { - // wallet, - // address, - // auth, - // circom_circuit_path, - // initial_state, - // satoshi_amount, - // } => { - // let ctx = RpcCtx::new( - // Some(BITCOIN_JSON_RPC_VERSION), - // wallet.clone(), - // address.clone(), - // auth.clone(), - // None, - // ); - - // let circom_circuit_path = env::current_dir()?.join(circom_circuit_path); - - // // compile to get VK (and its digest) - // let (vk, vk_hash) = { - // let tmp_dir = TempDir::new("zkbitcoin_").context("couldn't create tmp dir")?; - // let CompilationResult { - // verifier_key, - // circuit_r1cs_path: _, - // prover_key_path: _, - // } = snarkjs::compile(&tmp_dir, &circom_circuit_path).await?; - // let vk_hash = verifier_key.hash(); - // (verifier_key, vk_hash) - // }; - - // // sanity check - // let num_public_inputs = vk.nPublic; - // ensure!( - // num_public_inputs > 0, - // "the circuit must have at least one public input (the txid)" - // ); - - // info!( - // "deploying circuit {} with {num_public_inputs} public inputs", - // hex::encode(vk_hash) - // ); - - // // sanity check for stateful zkapps - // if num_public_inputs > 1 { - // let double_state_len = vk.nPublic - 3; /* txid, amount_in, amount_out */ - // let state_len = double_state_len.checked_div(2).context("the VK")?; - // { - // // TODO: does checked_div errors if its not a perfect division? - // assert_eq!(state_len * 2, double_state_len); - // } - - // // for now we only state of a single element - // ensure!( - // state_len == 1, - // "we only allow states of a single field element" - // ); - - // // check that the circuit makes sense for a stateful zkapp - // ensure!(num_public_inputs == 3 /* txid, amount_in, amount_out */ + state_len * 2, "the circuit passed does not expect the right number of public inputs for a stateful zkapp"); - - // // parse initial state - // ensure!( - // initial_state.is_some(), - // "an initial state should be passed for a stateful zkapp" - // ); - // } - - // // generate and broadcast deploy transaction - // let txid = generate_and_broadcast_transaction( - // &ctx, - // &vk_hash, - // initial_state.as_ref(), - // *satoshi_amount, - // ) - // .await?; - - // info!("- txid broadcast to the network: {txid}"); - // info!("- on an explorer: https://blockstream.info/testnet/tx/{txid}"); - // } - - // // Bob's command - // Commands::UseZkapp { - // wallet, - // address, - // auth, - // orchestrator_address, - // txid, - // recipient_address, - // circom_circuit_path, - // proof_inputs, - // } => { - // let rpc_ctx = RpcCtx::new( - // Some(BITCOIN_JSON_RPC_VERSION), - // wallet.clone(), - // address.clone(), - // auth.clone(), - // None, - // ); - - // // parse circom circuit path - // let circom_circuit_path = env::current_dir()?.join(circom_circuit_path); - - // // parse proof inputs - // let proof_inputs: HashMap> = if let Some(s) = &proof_inputs { - // serde_json::from_str(s)? - // } else { - // HashMap::new() - // }; - - // // parse Bob address - // let bob_address = Address::from_str(recipient_address) - // .unwrap() - // .require_network(get_network()) - // .unwrap(); - - // // parse transaction ID - // let txid = Txid::from_str(txid)?; - - // // create bob request - // let bob_request = BobRequest::new( - // &rpc_ctx, - // bob_address, - // txid, - // &circom_circuit_path, - // proof_inputs, - // ) - // .await?; - - // // send bob's request to the orchestartor. - // let address = orchestrator_address - // .as_deref() - // .unwrap_or(ORCHESTRATOR_ADDRESS); - // let bob_response = send_bob_request(address, bob_request) - // .await - // .context("error while sending request to orchestrator")?; - - // // sign it - // let (signed_tx_hex, _signed_tx) = sign_transaction( - // &rpc_ctx, - // TransactionOrHex::Transaction(&bob_response.unlocked_tx), - // ) - // .await?; - - // // broadcast transaction - // let txid = send_raw_transaction(&rpc_ctx, TransactionOrHex::Hex(signed_tx_hex)).await?; - - // // print useful msg - // info!("- txid broadcast to the network: {txid}"); - // info!("- on an explorer: https://blockstream.info/testnet/tx/{txid}"); - // } - - // Commands::GetZkapp { - // wallet, - // address, - // auth, - // txid, - // } => { - // let ctx = RpcCtx::new( - // Some(BITCOIN_JSON_RPC_VERSION), - // wallet.clone(), - // address.clone(), - // auth.clone(), - // None, - // ); - - // // extract smart contract - // let zkapp = fetch_smart_contract(&ctx, Txid::from_str(txid)?).await?; - - // println!("{zkapp}"); - // } - - // Commands::ListZkapps { - // wallet, - // address, - // auth, - // } => { - // let mut rpc_ctx = RpcCtx::new( - // Some(BITCOIN_JSON_RPC_VERSION), - // wallet.clone(), - // address.clone(), - // auth.clone(), - // None, - // ); - // rpc_ctx.timeout = std::time::Duration::from_secs(20); // scan takes 13s from what I can see - // let zkbitcoin_addr = taproot_addr_from(ZKBITCOIN_PUBKEY).unwrap(); - // let res = scan_txout_set(&rpc_ctx, &zkbitcoin_addr.to_string()).await?; - // for unspent in &res.unspents { - // let txid = unspent.txid; - // if let Ok(zkapp) = fetch_smart_contract(&rpc_ctx, txid).await { - // println!("{zkapp}"); - // } - // } - // } - - // Commands::GenerateCommittee { - // num, - // threshold, - // output_dir, - // } => { - // let output_dir = PathBuf::from(output_dir); - - // // deal until we get a public key starting with 0x02 - // let (mut key_packages, mut pubkey_package) = - // frost::gen_frost_keys(*num, *threshold).unwrap(); - // let mut pubkey = pubkey_package.verifying_key().to_owned(); - // loop { - // if pubkey.serialize()[0] == 2 { - // break; - // } - // (key_packages, pubkey_package) = frost::gen_frost_keys(*num, *threshold).unwrap(); - // pubkey = pubkey_package.verifying_key().to_owned(); - // } - - // // all key packages - // { - // for (id, key_package) in key_packages.values().enumerate() { - // let filename = format!("key-{id}.json"); - - // let path = output_dir.join(filename); - // std::fs::create_dir_all(path.clone().parent().unwrap()) - // .expect("Couldn't create directory"); - // let file = std::fs::File::create(&path) - // .expect("couldn't create file given output dir"); - // serde_json::to_writer_pretty(file, key_package).unwrap(); - // } - // } - - // // public key package - // { - // let path = output_dir.join("publickey-package.json"); - // let file = - // std::fs::File::create(path).expect("couldn't create file given output dir"); - // serde_json::to_writer_pretty(file, &pubkey_package).unwrap(); - // } - - // // create the committee-cfg.json file - // { - // let ip = "http://127.0.0.1:889"; - // let committee_cfg = CommitteeConfig { - // threshold: *threshold as usize, - // members: key_packages - // .iter() - // .enumerate() - // .map(|(id, (member_id, _))| { - // ( - // *member_id, - // Member { - // address: format!("{}{}", ip, id), - // }, - // ) - // }) - // .collect(), - // }; - // let path = output_dir.join("committee-cfg.json"); - // let file = - // std::fs::File::create(path).expect("couldn't create file given output dir"); - // serde_json::to_writer_pretty(file, &committee_cfg).unwrap(); - // } - // } - - // Commands::StartCommitteeNode { - // address, - // key_path, - // publickey_package_path, - // } => { - // let key_package = { - // let full_path = PathBuf::from(key_path); - // let file = std::fs::File::open(full_path).expect("file not found"); - // let key: frost::KeyPackage = - // serde_json::from_reader(file).expect("error while reading file"); - // key - // }; - - // let pubkey_package = { - // let full_path = PathBuf::from(publickey_package_path); - // let file = std::fs::File::open(full_path).expect("file not found"); - // let publickey_package: frost::PublicKeyPackage = - // serde_json::from_reader(file).expect("error while reading file"); - // publickey_package - // }; - - // zkbitcoin::committee::node::run_server(address.as_deref(), key_package, pubkey_package) - // .await - // .unwrap(); - // } - - // Commands::StartOrchestrator { - // address, - // publickey_package_path, - // committee_cfg_path, - // } => { - // let pubkey_package = { - // let full_path = PathBuf::from(publickey_package_path); - // let file = std::fs::File::open(full_path).expect("file not found"); - // let publickey_package: frost::PublicKeyPackage = - // serde_json::from_reader(file).expect("error while reading file"); - // publickey_package - // }; - - // let committee_cfg = { - // let full_path = PathBuf::from(committee_cfg_path); - // let file = std::fs::File::open(full_path).expect("file not found"); - // let publickey_package: CommitteeConfig = - // serde_json::from_reader(file).expect("error while reading file"); - // publickey_package - // }; - - // // sanity check (unfortunately the publickey_package doesn't contain this info) - // assert!(committee_cfg.threshold > 0); - - // zkbitcoin::committee::orchestrator::run_server( - // address.as_deref(), - // pubkey_package, - // committee_cfg, - // ) - // .await - // .unwrap(); - // } - // } - - let address_verifier = AddressVerifier::new(); - address_verifier.start().await; + // init default log level to info (unless RUST_LOG is set) + env_logger::init_from_env(env_logger::Env::default().default_filter_or("info")); + + // debug info + info!( + "- zkbitcoin_address: {}", + taproot_addr_from(ZKBITCOIN_PUBKEY).unwrap().to_string() + ); + info!( + "- zkbitcoin_fund_address: {}", + taproot_addr_from(ZKBITCOIN_FEE_PUBKEY).unwrap().to_string() + ); + + // parse CLI + let cli = Cli::parse(); + match &cli.command { + // Alice's command + Commands::DeployZkapp { + wallet, + address, + auth, + circom_circuit_path, + initial_state, + satoshi_amount, + } => { + let ctx = RpcCtx::new( + Some(BITCOIN_JSON_RPC_VERSION), + wallet.clone(), + address.clone(), + auth.clone(), + None, + ); + + let circom_circuit_path = env::current_dir()?.join(circom_circuit_path); + + // compile to get VK (and its digest) + let (vk, vk_hash) = { + let tmp_dir = TempDir::new("zkbitcoin_").context("couldn't create tmp dir")?; + let CompilationResult { + verifier_key, + circuit_r1cs_path: _, + prover_key_path: _, + } = snarkjs::compile(&tmp_dir, &circom_circuit_path).await?; + let vk_hash = verifier_key.hash(); + (verifier_key, vk_hash) + }; + + // sanity check + let num_public_inputs = vk.nPublic; + ensure!( + num_public_inputs > 0, + "the circuit must have at least one public input (the txid)" + ); + + info!( + "deploying circuit {} with {num_public_inputs} public inputs", + hex::encode(vk_hash) + ); + + // sanity check for stateful zkapps + if num_public_inputs > 1 { + let double_state_len = vk.nPublic - 3; /* txid, amount_in, amount_out */ + let state_len = double_state_len.checked_div(2).context("the VK")?; + { + // TODO: does checked_div errors if its not a perfect division? + assert_eq!(state_len * 2, double_state_len); + } + + // for now we only state of a single element + ensure!( + state_len == 1, + "we only allow states of a single field element" + ); + + // check that the circuit makes sense for a stateful zkapp + ensure!(num_public_inputs == 3 /* txid, amount_in, amount_out */ + state_len * 2, "the circuit passed does not expect the right number of public inputs for a stateful zkapp"); + + // parse initial state + ensure!( + initial_state.is_some(), + "an initial state should be passed for a stateful zkapp" + ); + } + + // generate and broadcast deploy transaction + let txid = generate_and_broadcast_transaction( + &ctx, + &vk_hash, + initial_state.as_ref(), + *satoshi_amount, + ) + .await?; + + info!("- txid broadcast to the network: {txid}"); + info!("- on an explorer: https://blockstream.info/testnet/tx/{txid}"); + } + + // Bob's command + Commands::UseZkapp { + wallet, + address, + auth, + orchestrator_address, + txid, + recipient_address, + circom_circuit_path, + proof_inputs, + } => { + let rpc_ctx = RpcCtx::new( + Some(BITCOIN_JSON_RPC_VERSION), + wallet.clone(), + address.clone(), + auth.clone(), + None, + ); + + // parse circom circuit path + let circom_circuit_path = env::current_dir()?.join(circom_circuit_path); + + // parse proof inputs + let proof_inputs: HashMap> = if let Some(s) = &proof_inputs { + serde_json::from_str(s)? + } else { + HashMap::new() + }; + + // parse Bob address + let bob_address = Address::from_str(recipient_address) + .unwrap() + .require_network(get_network()) + .unwrap(); + + // parse transaction ID + let txid = Txid::from_str(txid)?; + + // create bob request + let bob_request = BobRequest::new( + &rpc_ctx, + bob_address, + txid, + &circom_circuit_path, + proof_inputs, + ) + .await?; + + // send bob's request to the orchestartor. + let address = orchestrator_address + .as_deref() + .unwrap_or(ORCHESTRATOR_ADDRESS); + let bob_response = send_bob_request(address, bob_request) + .await + .context("error while sending request to orchestrator")?; + + // sign it + let (signed_tx_hex, _signed_tx) = sign_transaction( + &rpc_ctx, + TransactionOrHex::Transaction(&bob_response.unlocked_tx), + ) + .await?; + + // broadcast transaction + let txid = send_raw_transaction(&rpc_ctx, TransactionOrHex::Hex(signed_tx_hex)).await?; + + // print useful msg + info!("- txid broadcast to the network: {txid}"); + info!("- on an explorer: https://blockstream.info/testnet/tx/{txid}"); + } + + Commands::GetZkapp { + wallet, + address, + auth, + txid, + } => { + let ctx = RpcCtx::new( + Some(BITCOIN_JSON_RPC_VERSION), + wallet.clone(), + address.clone(), + auth.clone(), + None, + ); + + // extract smart contract + let zkapp = fetch_smart_contract(&ctx, Txid::from_str(txid)?).await?; + + println!("{zkapp}"); + } + + Commands::ListZkapps { + wallet, + address, + auth, + } => { + let mut rpc_ctx = RpcCtx::new( + Some(BITCOIN_JSON_RPC_VERSION), + wallet.clone(), + address.clone(), + auth.clone(), + None, + ); + rpc_ctx.timeout = std::time::Duration::from_secs(20); // scan takes 13s from what I can see + let zkbitcoin_addr = taproot_addr_from(ZKBITCOIN_PUBKEY).unwrap(); + let res = scan_txout_set(&rpc_ctx, &zkbitcoin_addr.to_string()).await?; + for unspent in &res.unspents { + let txid = unspent.txid; + if let Ok(zkapp) = fetch_smart_contract(&rpc_ctx, txid).await { + println!("{zkapp}"); + } + } + } + + Commands::GenerateCommittee { + num, + threshold, + output_dir, + } => { + let output_dir = PathBuf::from(output_dir); + + // deal until we get a public key starting with 0x02 + let (mut key_packages, mut pubkey_package) = + frost::gen_frost_keys(*num, *threshold).unwrap(); + let mut pubkey = pubkey_package.verifying_key().to_owned(); + loop { + if pubkey.serialize()[0] == 2 { + break; + } + (key_packages, pubkey_package) = frost::gen_frost_keys(*num, *threshold).unwrap(); + pubkey = pubkey_package.verifying_key().to_owned(); + } + + // all key packages + { + for (id, key_package) in key_packages.values().enumerate() { + let filename = format!("key-{id}.json"); + + let path = output_dir.join(filename); + std::fs::create_dir_all(path.clone().parent().unwrap()) + .expect("Couldn't create directory"); + let file = std::fs::File::create(&path) + .expect("couldn't create file given output dir"); + serde_json::to_writer_pretty(file, key_package).unwrap(); + } + } + + // public key package + { + let path = output_dir.join("publickey-package.json"); + let file = + std::fs::File::create(path).expect("couldn't create file given output dir"); + serde_json::to_writer_pretty(file, &pubkey_package).unwrap(); + } + + // create the committee-cfg.json file + { + let ip = "http://127.0.0.1:889"; + let committee_cfg = CommitteeConfig { + threshold: *threshold as usize, + members: key_packages + .iter() + .enumerate() + .map(|(id, (member_id, _))| { + ( + *member_id, + Member { + address: format!("{}{}", ip, id), + }, + ) + }) + .collect(), + }; + let path = output_dir.join("committee-cfg.json"); + let file = + std::fs::File::create(path).expect("couldn't create file given output dir"); + serde_json::to_writer_pretty(file, &committee_cfg).unwrap(); + } + } + + Commands::StartCommitteeNode { + address, + key_path, + publickey_package_path, + } => { + let key_package = { + let full_path = PathBuf::from(key_path); + let file = std::fs::File::open(full_path).expect("file not found"); + let key: frost::KeyPackage = + serde_json::from_reader(file).expect("error while reading file"); + key + }; + + let pubkey_package = { + let full_path = PathBuf::from(publickey_package_path); + let file = std::fs::File::open(full_path).expect("file not found"); + let publickey_package: frost::PublicKeyPackage = + serde_json::from_reader(file).expect("error while reading file"); + publickey_package + }; + + zkbitcoin::committee::node::run_server(address.as_deref(), key_package, pubkey_package) + .await + .unwrap(); + } + + Commands::StartOrchestrator { + address, + publickey_package_path, + committee_cfg_path, + } => { + let pubkey_package = { + let full_path = PathBuf::from(publickey_package_path); + let file = std::fs::File::open(full_path).expect("file not found"); + let publickey_package: frost::PublicKeyPackage = + serde_json::from_reader(file).expect("error while reading file"); + publickey_package + }; + + let committee_cfg = { + let full_path = PathBuf::from(committee_cfg_path); + let file = std::fs::File::open(full_path).expect("file not found"); + let publickey_package: CommitteeConfig = + serde_json::from_reader(file).expect("error while reading file"); + publickey_package + }; + + // sanity check (unfortunately the publickey_package doesn't contain this info) + assert!(committee_cfg.threshold > 0); + + zkbitcoin::committee::orchestrator::run_server( + address.as_deref(), + pubkey_package, + committee_cfg, + ) + .await + .unwrap(); + } + } Ok(()) }