From 3155c95213b7bda40b8a0ffb2121029708360b73 Mon Sep 17 00:00:00 2001 From: Pavlos Polianidis Date: Fri, 23 Feb 2024 02:52:19 +0200 Subject: [PATCH] Add a command to download any SRS files of any size (#37) Delegate the downloading of SRS files to the end-user. Based on the discussion here https://github.com/sigma0-xyz/zkbitcoin/issues/36 and here https://github.com/sigma0-xyz/zkbitcoin/issues/7 --- README.md | 16 +++++++++-- src/bin/zkbtc.rs | 24 +++++++++++++++-- src/bob_request.rs | 6 +++-- src/lib.rs | 1 - src/snarkjs.rs | 28 +++++++++++-------- src/srs.rs | 67 ---------------------------------------------- 6 files changed, 57 insertions(+), 85 deletions(-) delete mode 100644 src/srs.rs diff --git a/README.md b/README.md index 605cc39..af1274e 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,14 @@ To install `snarkjs`, just run: npm install -g snarkjs@latest ``` +### Download SRS File + +To create a ZKP you would need to download the correct SRS file (based on your circuit size). For example, if your circuit has around 65K constraints then you would need to download the following file https://storage.googleapis.com/zkevm/ptau/powersOfTau28_hez_final_16.ptau. + +You can replace the number 16 in the above URL to download the correct SRS file for your circuit. + +> You can download the file to any location you wish. You will later provide the location of the file when running the CLI tool. + ### Bitcoin wallet On top that, you'll need your own Bitcoin node/wallet. This application will perform queries to your node/wallet in order to fund your zkapp transactions. @@ -63,17 +71,21 @@ The zkapp doesn't have to do anything with the `truncated_txid` field (although You can deploy a stateless zkapp with the following command: ```shell -$ zkbtc deploy-zkapp --circom-circuit-path examples/circuit/stateless.circom --satoshi-amount 1000 +$ zkbtc deploy-zkapp --circom-circuit-path examples/circuit/stateless.circom --srs-path ~/.zkbitcoin/srs_16.ptau --satoshi-amount 1000 ``` +> Use the `--srs-path` where you downloaded the SRS file. Check "Download SRS File" above. + This will lock 1,000 satoshis in the zkapp and return the transaction ID of the transaction that deployed the zkapp. A stateless zkapp can be referenced by that transaction ID. Bob can then unlock the funds from the stateless zkapp with the following command: ```shell -$ zkbtc use-zkapp --txid "e793bdd8dfdd9912d971790a5f385ad3f1215dce97e25dbefe5449faba632836" --circom-circuit-path examples/circuit/stateless.circom --proof-inputs '{"preimage":["1"]}' --recipient-address "tb1q6nkpv2j9lxrm6h3w4skrny3thswgdcca8cx9k6" +$ zkbtc use-zkapp --txid "e793bdd8dfdd9912d971790a5f385ad3f1215dce97e25dbefe5449faba632836" --circom-circuit-path examples/circuit/stateless.circom --srs-path ~/.zkbitcoin/srs_16.ptau --proof-inputs '{"preimage":["1"]}' --recipient-address "tb1q6nkpv2j9lxrm6h3w4skrny3thswgdcca8cx9k6" ``` +> Use the `--srs-path` where you downloaded the SRS file. Check "Download SRS File" above. + ### Stateful zkapps A stateful zkapp is a zkapp that has a state, and which state can be updated without consuming the zkapp. diff --git a/src/bin/zkbtc.rs b/src/bin/zkbtc.rs index 3d979cd..f86d01a 100644 --- a/src/bin/zkbtc.rs +++ b/src/bin/zkbtc.rs @@ -2,7 +2,12 @@ use anyhow::{ensure, Context, Result}; use bitcoin::{Address, Txid}; use clap::{Parser, Subcommand}; use log::info; -use std::{collections::HashMap, env, path::PathBuf, str::FromStr}; +use std::{ + collections::HashMap, + env, + path::{Path, PathBuf}, + str::FromStr, +}; use tempdir::TempDir; use zkbitcoin::{ alice_sign_tx::generate_and_broadcast_transaction, @@ -46,6 +51,10 @@ enum Commands { #[arg(short, long)] circom_circuit_path: PathBuf, + /// The path to the srs file + #[arg(long)] + srs_path: PathBuf, + /// Optionally, an initial state for stateful zkapps. #[arg(short, long)] initial_state: Option, @@ -85,6 +94,10 @@ enum Commands { #[arg(short, long)] circom_circuit_path: PathBuf, + /// The path to the srs file + #[arg(long)] + srs_path: PathBuf, + /// A JSON string of the proof inputs. /// For stateful zkapps, we expect at least `amount_in` and `amount_out`. #[arg(short, long)] @@ -155,6 +168,7 @@ async fn main() -> Result<()> { circom_circuit_path, initial_state, satoshi_amount, + srs_path, } => { let rpc_ctx = RpcCtx::new( Some(BITCOIN_JSON_RPC_VERSION), @@ -167,6 +181,7 @@ async fn main() -> Result<()> { deploy_zkapp( &rpc_ctx, circom_circuit_path, + srs_path, initial_state.as_deref(), *satoshi_amount, ) @@ -182,6 +197,7 @@ async fn main() -> Result<()> { txid, recipient_address, circom_circuit_path, + srs_path, proof_inputs, } => { let rpc_ctx = RpcCtx::new( @@ -198,6 +214,7 @@ async fn main() -> Result<()> { txid, recipient_address, circom_circuit_path, + srs_path, proof_inputs.as_deref(), ) .await?; @@ -250,6 +267,7 @@ async fn main() -> Result<()> { async fn deploy_zkapp( rpc_ctx: &RpcCtx, circom_circuit_path: PathBuf, + srs_path: &Path, initial_state: Option<&str>, satoshi_amount: u64, ) -> Result<()> { @@ -260,7 +278,7 @@ async fn deploy_zkapp( verifier_key, circuit_r1cs_path: _, prover_key_path: _, - } = snarkjs::compile(&tmp_dir, &circom_circuit_path).await?; + } = snarkjs::compile(&tmp_dir, &circom_circuit_path, srs_path).await?; let vk_hash = verifier_key.hash(); (verifier_key, vk_hash) }; @@ -318,6 +336,7 @@ async fn use_zkapp( txid: &str, recipient_address: &str, circom_circuit_path: PathBuf, + srs_path: &Path, proof_inputs: Option<&str>, ) -> Result<()> { // parse proof inputs @@ -342,6 +361,7 @@ async fn use_zkapp( bob_address, txid, &circom_circuit_path, + srs_path, proof_inputs, ) .await?; diff --git a/src/bob_request.rs b/src/bob_request.rs index 604e2a0..bb1f2e4 100644 --- a/src/bob_request.rs +++ b/src/bob_request.rs @@ -112,6 +112,7 @@ impl BobRequest { bob_address: Address, txid: bitcoin::Txid, // of zkapp circom_circuit_path: &Path, + srs_path: &Path, mut proof_inputs: HashMap>, ) -> Result { // fetch transaction + metadata based on txid @@ -140,7 +141,7 @@ impl BobRequest { // prove let (_proof, public_inputs, _vk) = - snarkjs::prove(circom_circuit_path, &proof_inputs).await?; + snarkjs::prove(circom_circuit_path, srs_path, &proof_inputs).await?; // extract new_state let new_state = public_inputs @@ -266,7 +267,8 @@ impl BobRequest { let truncated_txid = truncate_txid(tx.txid()); proof_inputs.insert("truncated_txid".to_string(), vec![truncated_txid]); - let (proof, public_inputs, vk) = snarkjs::prove(circom_circuit_path, &proof_inputs).await?; + let (proof, public_inputs, vk) = + snarkjs::prove(circom_circuit_path, srs_path, &proof_inputs).await?; debug!( "- public_inputs used to create the proof: {:?}", public_inputs.0 diff --git a/src/lib.rs b/src/lib.rs index a1d3f4b..e49ffce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,7 +11,6 @@ pub mod frost; pub mod json_rpc_stuff; pub mod plonk; pub mod snarkjs; -pub mod srs; pub mod utils; /// 1. Alice signs a transaction to deploy a smart contract. diff --git a/src/snarkjs.rs b/src/snarkjs.rs index 81dfdad..44ed751 100644 --- a/src/snarkjs.rs +++ b/src/snarkjs.rs @@ -9,10 +9,7 @@ use anyhow::{bail, Context, Result}; use log::info; use tempdir::TempDir; -use crate::{ - plonk::{self}, - srs, -}; +use crate::plonk; pub struct CompilationResult { pub verifier_key: plonk::VerifierKey, @@ -21,10 +18,11 @@ pub struct CompilationResult { } /// Compiles a circom circuit to a wasm and r1cs file. -pub async fn compile(tmp_dir: &TempDir, circom_circuit_path: &Path) -> Result { - // SRS - let srs_path = srs::srs_path().await; - +pub async fn compile( + tmp_dir: &TempDir, + circom_circuit_path: &Path, + srs_path: &Path, +) -> Result { // set up new paths for files that will be created let circuit_name = circom_circuit_path .file_stem() @@ -113,6 +111,7 @@ pub async fn compile(tmp_dir: &TempDir, circom_circuit_path: &Path) -> Result>, ) -> Result<(plonk::Proof, plonk::PublicInputs, plonk::VerifierKey)> { // create tmp dir @@ -123,7 +122,7 @@ pub async fn prove( verifier_key, circuit_r1cs_path: _, prover_key_path, - } = compile(&tmp_dir, circom_circuit_path).await?; + } = compile(&tmp_dir, circom_circuit_path, srs_path).await?; // write inputs to file let public_inputs_path = tmp_dir.path().join("proof_inputs.json"); @@ -246,6 +245,7 @@ pub fn verify_proof( #[cfg(test)] mod tests { use super::*; + use crate::zkbitcoin_folder; fn full_path_from(path: &Path) -> PathBuf { PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join(path) @@ -255,6 +255,7 @@ mod tests { #[ignore] async fn prove_stateless() { let circom_circuit_path = full_path_from(Path::new("examples/circuit/stateless.circom")); + let srs_path = zkbitcoin_folder().join("srs_16.ptau"); let mut proof_inputs = HashMap::new(); proof_inputs.insert("truncated_txid".to_string(), vec!["0".to_string()]); proof_inputs.insert( @@ -264,7 +265,9 @@ mod tests { .to_string(), ], ); - let (proof, full_inputs, vk) = prove(&circom_circuit_path, &proof_inputs).await.unwrap(); + let (proof, full_inputs, vk) = prove(&circom_circuit_path, &srs_path, &proof_inputs) + .await + .unwrap(); // verify verify_proof(&vk, &full_inputs.0, &proof).unwrap(); @@ -278,6 +281,7 @@ mod tests { .join("examples") .join("circuit"); let circom_circuit_path = circuit_dir.join("circuit.circom"); + let srs_path = zkbitcoin_folder().join("srs_16.ptau"); // // compile to get VK // let vk = { @@ -292,7 +296,9 @@ mod tests { // prove let public_inputs = HashMap::new(); - let (proof, full_inputs, vk) = prove(&circom_circuit_path, &public_inputs).await.unwrap(); + let (proof, full_inputs, vk) = prove(&circom_circuit_path, &srs_path, &public_inputs) + .await + .unwrap(); // verify verify_proof(&vk, &full_inputs.0, &proof).unwrap(); diff --git a/src/srs.rs b/src/srs.rs deleted file mode 100644 index 286710d..0000000 --- a/src/srs.rs +++ /dev/null @@ -1,67 +0,0 @@ -use std::path::PathBuf; - -use log::info; -use tokio::{fs::File, io::AsyncWriteExt}; -use tokio_stream::StreamExt; - -use crate::zkbitcoin_folder; - -// -// Constants -// - -/// The hash of the [SRS_URL]. Taken from https://github.com/iden3/snarkjs#7-prepare-phase-2 -const SRS_HASH: &str = "1c401abb57c9ce531370f3015c3e75c0892e0f32b8b1e94ace0f6682d9695922"; - -/// The URL to download the SRS. Taken from https://github.com/iden3/snarkjs#7-prepare-phase-2 -const SRS_URL: &str = "https://storage.googleapis.com/zkevm/ptau/powersOfTau28_hez_final_16.ptau"; - -/// The max circuit size that can be created with the hardcoded SRS. -const SRS_SIZE: usize = 16; - -// -// SRS Logic -// - -/// Downloads the SRS file into the local zkBitcoin folder -pub async fn download_srs() -> PathBuf { - // create zkbitcoin dir if it doesn't exist - let zkbitcoin_dir = zkbitcoin_folder(); - let srs_path = zkbitcoin_dir.join("srs_28.ptau"); - - // download srs path if it doesn't exists - if srs_path.exists() { - return srs_path; - } - - info!("downloading srs..."); - let mut file = File::create(&srs_path).await.unwrap(); - let mut stream = reqwest::get(SRS_URL).await.unwrap().bytes_stream(); - - while let Some(chunk_result) = stream.next().await { - let chunk = chunk_result.unwrap(); - file.write_all(&chunk).await.unwrap(); - } - - file.flush().await.unwrap(); - - info!( - "Downloaded SRS for 2^{SRS_SIZE} circuits at {}", - srs_path.to_string_lossy() - ); - - srs_path -} - -/// Returns the local path to the SRS file. -pub async fn srs_path() -> PathBuf { - // download if necessary - let srs_path = download_srs().await; - - // always check integrity of file - let hash = sha256::try_digest(&srs_path).unwrap(); - assert_eq!(hash, SRS_HASH); - - // - srs_path -}