From bcea57b334a4aefa1182b469dc7d08c85f663f06 Mon Sep 17 00:00:00 2001 From: sifnoc Date: Thu, 14 Sep 2023 11:07:14 +0000 Subject: [PATCH 01/12] feat: create generate signature csv by simple signer server --- backend/Cargo.toml | 4 + backend/examples/generate_signatures/main.rs | 87 ++++++++++++++++ .../generate_signatures/remote_signer.rs | 98 +++++++++++++++++++ 3 files changed, 189 insertions(+) create mode 100644 backend/examples/generate_signatures/main.rs create mode 100644 backend/examples/generate_signatures/remote_signer.rs diff --git a/backend/Cargo.toml b/backend/Cargo.toml index eec1ddec..4d6ee6de 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -22,3 +22,7 @@ bincode = "1.3.3" [build-dependencies] ethers = { version = "2.0.7", default-features = false, features = ["ethers-solc"] } + +[[example]] +name = "generate_signatures" +path = "examples/generate_signatures/main.rs" diff --git a/backend/examples/generate_signatures/main.rs b/backend/examples/generate_signatures/main.rs new file mode 100644 index 00000000..f53684c6 --- /dev/null +++ b/backend/examples/generate_signatures/main.rs @@ -0,0 +1,87 @@ +use std::{ + error::Error, + fs::File, + io::{Read, Write}, + net::{TcpListener, TcpStream}, + str::FromStr, + thread::{sleep, spawn}, +}; + +use csv::WriterBuilder; +use ethers::{ + abi::{encode, Token}, + prelude::SignerMiddleware, + signers::{LocalWallet, Signer, WalletError}, + types::Signature, + utils::{keccak256, to_checksum}, +}; +use serde_json::{from_str, to_string_pretty}; + +mod remote_signer; +use remote_signer::start_server; +use summa_backend::apis::csv_parser::SignatureRecord; + +// We provide simple request function for getting signatures from the signer server +fn send_request(message: &str) -> Result { + let mut stream = TcpStream::connect("127.0.0.1:8080")?; + + let request = format!( + "POST /sign HTTP/1.1\r\nContent-Length: {}\r\n\r\n{}", + message.len(), + message + ); + + stream.write_all(request.as_bytes())?; + + let mut response = String::new(); + stream.read_to_string(&mut response)?; + + Ok(response) +} + +fn parse_response(response: &str) -> Result, Box> { + // Split the response into HTTP headers and body + let parts: Vec<&str> = response.split("\r\n\r\n").collect(); + + if parts.len() != 2 { + return Err("Invalid response format".into()); + } + + let json_str = parts[1]; + + // Parse the JSON response into a vector of SignatureRecord + let signatures: Vec = serde_json::from_str(json_str)?; + + Ok(signatures) +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Let's assume the CEX has multiple private keys for generating message signatures for AddressOwnershipProof + // Start the server in a separate thread + spawn(|| start_server()); + + // Give the server a little time to start + sleep(std::time::Duration::from_secs(1)); + + // Given message to sign + // Note that, the message length are fixed for the server. + let message = "Summa proof of solvency for CryptoExchange"; + let path = "src/apis/csv/signatures.csv"; + + // Request signatures with `message` to the signer server + let response = send_request(message)?; + let signatures = parse_response(&response)?; + + // Write the signatures to a CSV file + let file = File::create(path)?; + let mut wtr = WriterBuilder::new().delimiter(b';').from_writer(file); + + for signature in signatures { + wtr.serialize(signature)?; + } + + wtr.flush()?; // This will ensure all bytes are written + + Ok(()) +} diff --git a/backend/examples/generate_signatures/remote_signer.rs b/backend/examples/generate_signatures/remote_signer.rs new file mode 100644 index 00000000..2a0ad46e --- /dev/null +++ b/backend/examples/generate_signatures/remote_signer.rs @@ -0,0 +1,98 @@ +use std::{ + error::Error, + fs::File, + io::{Read, Write}, + net::{TcpListener, TcpStream}, + str::FromStr, + thread::{sleep, spawn}, +}; + +use csv::WriterBuilder; +use ethers::{ + abi::{encode, Token}, + prelude::SignerMiddleware, + signers::{LocalWallet, Signer, WalletError}, + types::Signature, + utils::{keccak256, to_checksum}, +}; +use serde_json::{from_str, to_string_pretty}; + +use summa_backend::apis::csv_parser::SignatureRecord; + +async fn remote_signer(mut stream: TcpStream) { + let mut buffer = [0; 85]; + let bytes_read = stream.read(&mut buffer).unwrap(); + + // This is insecure way to create wallet instances + // TODO: suggest better secure way to generate wallet instances + let private_keys = &[ + "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", + "0xde9be858da4a475276426320d5e9262ecfc3ba460bfac56360bfa6c4c28b4ee0", + ]; + + let signing_wallets: Vec = private_keys + .iter() + .map(|private_key| LocalWallet::from_str(private_key).unwrap()) + .collect(); + + let request = String::from_utf8_lossy(&buffer[..]); + if request.starts_with("POST /sign") { + // Extract the message from the request body + let message = request.split("\r\n\r\n").nth(1).unwrap_or(""); + + let encoded_message = encode(&[Token::String(message.to_owned())]); + let hashed_message = keccak256(encoded_message); + + let mut signatures: Vec = Vec::new(); + + // Iterating signing wallets and generate signatures to put `signatures` vector + for wallet in signing_wallets { + let signature = wallet.sign_message(hashed_message).await.unwrap(); + let record = SignatureRecord::new( + "ETH".to_string(), + to_checksum(&wallet.address(), None), // + format!("0x{}", signature.to_string()), + message.to_string(), + ); + signatures.push(record); + } + + let json_response = to_string_pretty(&signatures).unwrap(); + + let response = format!( + "HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n{}", + json_response.len(), + json_response + ); + + stream.write(response.as_bytes()).unwrap(); + stream.flush().unwrap(); + } else { + let response = "HTTP/1.1 404 NOT FOUND\r\n\r\n"; + stream.write(response.as_bytes()).unwrap(); + stream.flush().unwrap(); + } +} + +fn handle_client(stream: TcpStream) { + let mut rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(remote_signer(stream)); +} + +pub fn start_server() { + let listener = TcpListener::bind("127.0.0.1:8080").unwrap(); + println!("Example Signer server started on 127.0.0.1:8080"); + + for stream in listener.incoming() { + match stream { + Ok(stream) => { + std::thread::spawn(|| { + handle_client(stream); + }); + } + Err(e) => { + eprintln!("Connection failed: {}", e); + } + } + } +} From a36f076b064d10e4149032607312375f67529a0c Mon Sep 17 00:00:00 2001 From: sifnoc Date: Fri, 15 Sep 2023 12:06:14 +0000 Subject: [PATCH 02/12] feat: modify local signer from remote one; removed private keys from 'contracts/signer' --- backend/README.md | 16 +++ backend/examples/generate_signatures/main.rs | 67 +------------ .../generate_signatures/message_signer.rs | 48 +++++++++ .../generate_signatures/remote_signer.rs | 98 ------------------- backend/src/apis/address_ownership.rs | 2 +- backend/src/apis/csv/signatures.csv | 6 +- backend/src/apis/csv_parser.rs | 17 +++- backend/src/apis/mod.rs | 2 +- backend/src/apis/round.rs | 2 +- backend/src/contracts/generated/mod.rs | 4 +- backend/src/contracts/signer.rs | 62 +----------- 11 files changed, 93 insertions(+), 231 deletions(-) create mode 100644 backend/examples/generate_signatures/message_signer.rs delete mode 100644 backend/examples/generate_signatures/remote_signer.rs diff --git a/backend/README.md b/backend/README.md index 16b26423..6d1a62b7 100644 --- a/backend/README.md +++ b/backend/README.md @@ -87,3 +87,19 @@ On successful execution, you'll observe a message indicating the verification ou ``` Verifying the proof result for User #0: true ``` + +### Generating Message Signatures + +This example demonstrates how to generate a CSV file containing signatures derived from a given message, essential for `AddressOwnership`. + +This demonstration is adaptable for various scenarios. For instance, you could compile this example into an executable binary, an operator can run it using a hardware wallet. + This operation should ideally occur in a highly secure environment within a CEX, where the system is isolated from any online networks, ensuring maximum security against potential external threats. + +The resulting signatures are saved in a CSV file using a custom delimiter, allowing for straightforward parsing and verification. You can find the output file at `src/apis/csv/signatures.csv`. + +To run the example: +``` +cargo run --example generate_signatures +``` + +Note: This example uses hardcoded private keys for simplicity. In real-world applications, it's imperative never to expose private keys in such a manner. Instead, consider crate your own `message_signer` to utilize secure mechanisms, such as hardware wallets or secure key vaults. diff --git a/backend/examples/generate_signatures/main.rs b/backend/examples/generate_signatures/main.rs index f53684c6..014e83f6 100644 --- a/backend/examples/generate_signatures/main.rs +++ b/backend/examples/generate_signatures/main.rs @@ -1,77 +1,18 @@ -use std::{ - error::Error, - fs::File, - io::{Read, Write}, - net::{TcpListener, TcpStream}, - str::FromStr, - thread::{sleep, spawn}, -}; +use std::{error::Error, fs::File}; use csv::WriterBuilder; -use ethers::{ - abi::{encode, Token}, - prelude::SignerMiddleware, - signers::{LocalWallet, Signer, WalletError}, - types::Signature, - utils::{keccak256, to_checksum}, -}; -use serde_json::{from_str, to_string_pretty}; -mod remote_signer; -use remote_signer::start_server; -use summa_backend::apis::csv_parser::SignatureRecord; - -// We provide simple request function for getting signatures from the signer server -fn send_request(message: &str) -> Result { - let mut stream = TcpStream::connect("127.0.0.1:8080")?; - - let request = format!( - "POST /sign HTTP/1.1\r\nContent-Length: {}\r\n\r\n{}", - message.len(), - message - ); - - stream.write_all(request.as_bytes())?; - - let mut response = String::new(); - stream.read_to_string(&mut response)?; - - Ok(response) -} - -fn parse_response(response: &str) -> Result, Box> { - // Split the response into HTTP headers and body - let parts: Vec<&str> = response.split("\r\n\r\n").collect(); - - if parts.len() != 2 { - return Err("Invalid response format".into()); - } - - let json_str = parts[1]; - - // Parse the JSON response into a vector of SignatureRecord - let signatures: Vec = serde_json::from_str(json_str)?; - - Ok(signatures) -} +mod message_signer; +use message_signer::sign_message; #[tokio::main] async fn main() -> Result<(), Box> { - // Let's assume the CEX has multiple private keys for generating message signatures for AddressOwnershipProof - // Start the server in a separate thread - spawn(|| start_server()); - - // Give the server a little time to start - sleep(std::time::Duration::from_secs(1)); - // Given message to sign - // Note that, the message length are fixed for the server. let message = "Summa proof of solvency for CryptoExchange"; let path = "src/apis/csv/signatures.csv"; // Request signatures with `message` to the signer server - let response = send_request(message)?; - let signatures = parse_response(&response)?; + let signatures = sign_message(message).await?; // Write the signatures to a CSV file let file = File::create(path)?; diff --git a/backend/examples/generate_signatures/message_signer.rs b/backend/examples/generate_signatures/message_signer.rs new file mode 100644 index 00000000..2b894f16 --- /dev/null +++ b/backend/examples/generate_signatures/message_signer.rs @@ -0,0 +1,48 @@ +use std::{error::Error, str::FromStr}; + +use ethers::{ + abi::{encode, Token}, + signers::{LocalWallet, Signer}, + utils::{keccak256, to_checksum}, +}; + +use summa_backend::apis::csv_parser::SignatureRecord; + +pub async fn sign_message(message: &str) -> Result, Box> { + // Using private keys directly is insecure. + // Instead, consider leveraging hardware wallet support. + // `ethers-rs` provides support for both Ledger and Trezor hardware wallets. + // + // For example, you could use the Ledger wallet as shown below: + // let signing_wallets = (0..2).map(|index| Ledger::new(HDPath::LedgerLive(index), 1).await.unwrap()).collect(); + // + // Refers to: https://docs.rs/ethers/latest/ethers/signers/index.html + let private_keys = &[ + "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", + "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a", + ]; + + let signing_wallets: Vec = private_keys + .iter() + .map(|private_key| LocalWallet::from_str(private_key).unwrap()) + .collect(); + + let encoded_message = encode(&[Token::String(message.to_owned())]); + let hashed_message = keccak256(encoded_message); + + let mut signatures: Vec = Vec::new(); + + // Iterating signing wallets and generate signature to put `signatures` vector + for wallet in signing_wallets { + let signature = wallet.sign_message(hashed_message).await.unwrap(); + let record = SignatureRecord::new( + "ETH".to_string(), + to_checksum(&wallet.address(), None), // + format!("0x{}", signature.to_string()), + message.to_string(), + ); + signatures.push(record); + } + + Ok(signatures) +} diff --git a/backend/examples/generate_signatures/remote_signer.rs b/backend/examples/generate_signatures/remote_signer.rs deleted file mode 100644 index 2a0ad46e..00000000 --- a/backend/examples/generate_signatures/remote_signer.rs +++ /dev/null @@ -1,98 +0,0 @@ -use std::{ - error::Error, - fs::File, - io::{Read, Write}, - net::{TcpListener, TcpStream}, - str::FromStr, - thread::{sleep, spawn}, -}; - -use csv::WriterBuilder; -use ethers::{ - abi::{encode, Token}, - prelude::SignerMiddleware, - signers::{LocalWallet, Signer, WalletError}, - types::Signature, - utils::{keccak256, to_checksum}, -}; -use serde_json::{from_str, to_string_pretty}; - -use summa_backend::apis::csv_parser::SignatureRecord; - -async fn remote_signer(mut stream: TcpStream) { - let mut buffer = [0; 85]; - let bytes_read = stream.read(&mut buffer).unwrap(); - - // This is insecure way to create wallet instances - // TODO: suggest better secure way to generate wallet instances - let private_keys = &[ - "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", - "0xde9be858da4a475276426320d5e9262ecfc3ba460bfac56360bfa6c4c28b4ee0", - ]; - - let signing_wallets: Vec = private_keys - .iter() - .map(|private_key| LocalWallet::from_str(private_key).unwrap()) - .collect(); - - let request = String::from_utf8_lossy(&buffer[..]); - if request.starts_with("POST /sign") { - // Extract the message from the request body - let message = request.split("\r\n\r\n").nth(1).unwrap_or(""); - - let encoded_message = encode(&[Token::String(message.to_owned())]); - let hashed_message = keccak256(encoded_message); - - let mut signatures: Vec = Vec::new(); - - // Iterating signing wallets and generate signatures to put `signatures` vector - for wallet in signing_wallets { - let signature = wallet.sign_message(hashed_message).await.unwrap(); - let record = SignatureRecord::new( - "ETH".to_string(), - to_checksum(&wallet.address(), None), // - format!("0x{}", signature.to_string()), - message.to_string(), - ); - signatures.push(record); - } - - let json_response = to_string_pretty(&signatures).unwrap(); - - let response = format!( - "HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n{}", - json_response.len(), - json_response - ); - - stream.write(response.as_bytes()).unwrap(); - stream.flush().unwrap(); - } else { - let response = "HTTP/1.1 404 NOT FOUND\r\n\r\n"; - stream.write(response.as_bytes()).unwrap(); - stream.flush().unwrap(); - } -} - -fn handle_client(stream: TcpStream) { - let mut rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(remote_signer(stream)); -} - -pub fn start_server() { - let listener = TcpListener::bind("127.0.0.1:8080").unwrap(); - println!("Example Signer server started on 127.0.0.1:8080"); - - for stream in listener.incoming() { - match stream { - Ok(stream) => { - std::thread::spawn(|| { - handle_client(stream); - }); - } - Err(e) => { - eprintln!("Connection failed: {}", e); - } - } - } -} diff --git a/backend/src/apis/address_ownership.rs b/backend/src/apis/address_ownership.rs index 0f1b96dc..1eb6b34b 100644 --- a/backend/src/apis/address_ownership.rs +++ b/backend/src/apis/address_ownership.rs @@ -21,7 +21,7 @@ impl AddressOwnership { Ok(AddressOwnership { address_ownership_proofs, - signer: SummaSigner::new(&[], signer_key, chain_id, rpc_url, summa_sc_address), + signer: SummaSigner::new(signer_key, chain_id, rpc_url, summa_sc_address), }) } diff --git a/backend/src/apis/csv/signatures.csv b/backend/src/apis/csv/signatures.csv index 613bc0a6..89e4ba16 100644 --- a/backend/src/apis/csv/signatures.csv +++ b/backend/src/apis/csv/signatures.csv @@ -1,3 +1,3 @@ -chain;address;signature;message -ETH;0x70997970C51812dc3A010C7d01b50e0d17dc79C8;0x089b32327d332c295dc3b8873c205b72153211de6dc1c51235782b091cefb9d06d6df2661b86a7d441cd322f125b84901486b150e684221a7b7636eb8182af551b;Summa proof of solvency for CryptoExchange -ETH;0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC;0xb17a9e25265d3b88de7bfad81e7accad6e3d5612308ff83cc0fef76a34152b0444309e8fc3dea5139e49b6fc83a8553071a7af3d0cfd3fb8c1aea2a4c171729c1c;Summa proof of solvency for CryptoExchange +chain;address;signature;message +ETH;0x70997970C51812dc3A010C7d01b50e0d17dc79C8;0x089b32327d332c295dc3b8873c205b72153211de6dc1c51235782b091cefb9d06d6df2661b86a7d441cd322f125b84901486b150e684221a7b7636eb8182af551b;Summa proof of solvency for CryptoExchange +ETH;0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC;0xb17a9e25265d3b88de7bfad81e7accad6e3d5612308ff83cc0fef76a34152b0444309e8fc3dea5139e49b6fc83a8553071a7af3d0cfd3fb8c1aea2a4c171729c1c;Summa proof of solvency for CryptoExchange diff --git a/backend/src/apis/csv_parser.rs b/backend/src/apis/csv_parser.rs index e28e1843..3bde75e1 100644 --- a/backend/src/apis/csv_parser.rs +++ b/backend/src/apis/csv_parser.rs @@ -4,18 +4,29 @@ use ethers::{ abi::AbiEncode, types::{Bytes, U256}, }; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use crate::contracts::generated::summa_contract::{AddressOwnershipProof, Asset}; -#[derive(Debug, Deserialize)] -struct SignatureRecord { +#[derive(Debug, Deserialize, Serialize)] +pub struct SignatureRecord { chain: String, address: String, signature: String, message: String, } +impl SignatureRecord { + pub fn new(chain: String, address: String, signature: String, message: String) -> Self { + Self { + chain, + address, + signature, + message, + } + } +} + pub fn parse_signature_csv>( path: P, ) -> Result, Box> { diff --git a/backend/src/apis/mod.rs b/backend/src/apis/mod.rs index 712b5c53..82fca001 100644 --- a/backend/src/apis/mod.rs +++ b/backend/src/apis/mod.rs @@ -1,3 +1,3 @@ pub mod address_ownership; -mod csv_parser; +pub mod csv_parser; pub mod round; diff --git a/backend/src/apis/round.rs b/backend/src/apis/round.rs index 08c2a5e4..780cc831 100644 --- a/backend/src/apis/round.rs +++ b/backend/src/apis/round.rs @@ -95,7 +95,7 @@ where params_path, ) .unwrap(), - signer: SummaSigner::new(&vec![], signer_key, chain_id, rpc_url, summa_sc_address), + signer: SummaSigner::new(signer_key, chain_id, rpc_url, summa_sc_address), }) } diff --git a/backend/src/contracts/generated/mod.rs b/backend/src/contracts/generated/mod.rs index a4524590..a944f3aa 100644 --- a/backend/src/contracts/generated/mod.rs +++ b/backend/src/contracts/generated/mod.rs @@ -1,3 +1,3 @@ -pub mod inclusion_verifier; -pub mod solvency_verifier; pub mod summa_contract; +pub mod solvency_verifier; +pub mod inclusion_verifier; \ No newline at end of file diff --git a/backend/src/contracts/signer.rs b/backend/src/contracts/signer.rs index 67ee632c..7303c841 100644 --- a/backend/src/contracts/signer.rs +++ b/backend/src/contracts/signer.rs @@ -17,26 +17,18 @@ use super::generated::summa_contract::{AddressOwnershipProof, Asset}; #[derive(Debug)] pub struct SummaSigner { - signing_wallets: Vec, summa_contract: Summa, LocalWallet>>, } impl SummaSigner { /// Creates a new SummaSigner instance /// # Arguments - /// * `private_keys` - A list of the private keys of the addresses holding the exchange assets - /// * `main_signer_key` - The private key of wallet that will interact with the chain on behalf of the exchange + /// * `signer_key` - The private key of wallet that will interact with the chain on behalf of the exchange /// * `chain_id` - The chain id of the network /// * `rpc_url` - The RPC URL of the network /// * `address` - The address of the Summa contract - pub fn new( - private_keys: &[&str], - main_signer_key: &str, - chain_id: u64, - rpc_url: &str, - address: Address, - ) -> Self { - let wallet: LocalWallet = LocalWallet::from_str(main_signer_key).unwrap(); + pub fn new(signer_key: &str, chain_id: u64, rpc_url: &str, address: Address) -> Self { + let wallet: LocalWallet = LocalWallet::from_str(signer_key).unwrap(); let provider = Provider::::try_from(rpc_url) .unwrap() @@ -48,10 +40,6 @@ impl SummaSigner { let contract = Summa::new(address, client); Self { - signing_wallets: private_keys - .iter() - .map(|private_key| LocalWallet::from_str(private_key).unwrap()) - .collect(), summa_contract: contract, } } @@ -80,23 +68,6 @@ impl SummaSigner { Ok(address) } - async fn sign_message(wallet: &LocalWallet, message: &str) -> Signature { - let encoded_message = encode(&[Token::String(message.to_owned())]); - let hashed_message = keccak256(encoded_message); - wallet.sign_message(hashed_message).await.unwrap() - } - - pub async fn generate_signatures(&self) -> Result, WalletError> { - let message = std::env::var("SIGNATURE_VERIFICATION_MESSAGE").unwrap(); - let signature_futures: Vec<_> = self - .signing_wallets - .iter() - .map(|wallet| Self::sign_message(wallet, &message)) - .collect(); - - Ok(join_all(signature_futures).await) - } - pub async fn submit_proof_of_address_ownership( &self, address_ownership_proofs: Vec, @@ -128,30 +99,3 @@ impl SummaSigner { Ok(()) } } - -#[cfg(test)] -mod test { - use super::*; - use ethers::{types::Address, utils::Anvil}; - - #[tokio::test] - async fn test_sign_message() { - let anvil = Anvil::new().spawn(); - - let signer = SummaSigner::new( - //Account #1 - &vec!["0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"], - "0xde9be858da4a475276426320d5e9262ecfc3ba460bfac56360bfa6c4c28b4ee0", - 31337, - anvil.endpoint().as_str(), - //Verifier deployment is not necessary for this test - Address::random(), - ); - - let signatures = signer.generate_signatures().await.unwrap(); - assert_eq!(signatures.len(), 1); - //Signature produced by the account #1 - assert_eq!(signatures[0].to_string(), "089b32327d332c295dc3b8873c205b72153211de6dc1c51235782b091cefb9d06d6df2661b86a7d441cd322f125b84901486b150e684221a7b7636eb8182af551b"); - drop(anvil); - } -} From c60dd433bc7266a990f0fd9b9d3ed67b7b22f740 Mon Sep 17 00:00:00 2001 From: sifnoc Date: Fri, 15 Sep 2023 16:08:37 +0000 Subject: [PATCH 03/12] feat: create submit proof of ownership example --- backend/examples/submit_ownership.rs | 81 +++++++++++++++++++++++++++ backend/src/apis/address_ownership.rs | 4 ++ backend/src/contracts/signer.rs | 7 +-- 3 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 backend/examples/submit_ownership.rs diff --git a/backend/examples/submit_ownership.rs b/backend/examples/submit_ownership.rs new file mode 100644 index 00000000..73707318 --- /dev/null +++ b/backend/examples/submit_ownership.rs @@ -0,0 +1,81 @@ +use std::sync::Arc; + +use ethers::{ + abi::{encode, Token}, + types::{Address, U256}, + utils::keccak256, +}; + +use summa_backend::{ + apis::address_ownership::AddressOwnership, contracts::generated::summa_contract::Summa, + tests::initialize_anvil, +}; + +// In this example, we will demonstrate how to submit ownership of address to the Summa contract. +#[tokio::main] +async fn main() { + // We have already demonstrated how to generate a CSV file containing the asset ownership proofs, `AddressOwnershipProof`. + // For more details on this, kindly refer to the "generate_signature" example. + // + // For the current demonstration, we'll use the same CSV file produced in `generate_signature` example. + let signature_csv_path = "src/apis/csv/signatures.csv"; + + // Initialize test environment + let (anvil, _, _, client, _) = initialize_anvil().await; + + // Deploy contracts with null addresses for verifier contracts for this example + let summa_contract = Summa::deploy(Arc::clone(&client), (Address::zero(), Address::zero())) + .unwrap() + .send() + .await + .unwrap(); + + // Initialize `AddressOwnership` client + let mut address_ownership_client = AddressOwnership::new( + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + anvil.chain_id(), + anvil.endpoint().as_str(), + summa_contract.address(), + signature_csv_path, + ) + .unwrap(); + + // Get hashed addresses using the `keccak256` method. + let address_hashes = address_ownership_client + .get_ownership_proofs() + .iter() + .map(|x| keccak256(encode(&[Token::String(x.cex_address.clone())]))) + .collect::>(); + + // Dispatches the proof of address ownership. + // In the client, the `dispatch_proof_of_address_ownership` function sends a transaction to the Summa contract + address_ownership_client + .dispatch_proof_of_address_ownership() + .await + .unwrap(); + + // If the `addressHash` isn't found in the `addressOwnershipProofs` mapping of the Summa contract, + // it will return 0; otherwise, it will return a non-zero value. + // + // You can find unregistered address with null bytes as follows: + // + // let unregistered = summa_contract + // .ownership_proof_by_address([0u8; 32]) + // .call() + // .await + // .unwrap(); + // + // assert_eq!(unregistered, 0); + + // Check if the addresses are registered on the Summa contract. + for address_hash in address_hashes.iter() { + let registered = summa_contract + .ownership_proof_by_address(*address_hash) + .call() + .await + .unwrap(); + + assert_ne!(registered, U256::from(0)); + } + println!("Ownership proofs are submitted successfully!") +} diff --git a/backend/src/apis/address_ownership.rs b/backend/src/apis/address_ownership.rs index 1eb6b34b..7b56348b 100644 --- a/backend/src/apis/address_ownership.rs +++ b/backend/src/apis/address_ownership.rs @@ -25,6 +25,10 @@ impl AddressOwnership { }) } + pub fn get_ownership_proofs(&self) -> &Vec { + &self.address_ownership_proofs + } + // This function dispatches the proof of address ownership. Before calling this function, // ensure externally that the provided `addresses` in `address_ownership_proof` are not already registered // on the Summa contract. diff --git a/backend/src/contracts/signer.rs b/backend/src/contracts/signer.rs index 7303c841..5b4f3ccc 100644 --- a/backend/src/contracts/signer.rs +++ b/backend/src/contracts/signer.rs @@ -1,13 +1,10 @@ use crate::contracts::generated::summa_contract::Summa; use ethers::{ - abi::{encode, Token}, prelude::SignerMiddleware, providers::{Http, Provider}, - signers::{LocalWallet, Signer, WalletError}, - types::{Address, Signature}, - utils::keccak256, + signers::{LocalWallet, Signer}, + types::Address, }; -use futures::future::join_all; use serde_json::Value; use std::{ error::Error, fs::File, io::BufReader, path::Path, str::FromStr, sync::Arc, time::Duration, From 631512d9a62a2cc9ac84d16e58bb1f4490584feb Mon Sep 17 00:00:00 2001 From: sifnoc Date: Fri, 15 Sep 2023 16:27:58 +0000 Subject: [PATCH 04/12] feat: create submit proof of solvency example --- backend/examples/submit_solvency.rs | 78 +++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 backend/examples/submit_solvency.rs diff --git a/backend/examples/submit_solvency.rs b/backend/examples/submit_solvency.rs new file mode 100644 index 00000000..bcb96aa1 --- /dev/null +++ b/backend/examples/submit_solvency.rs @@ -0,0 +1,78 @@ +#![feature(generic_const_exprs)] +use std::sync::Arc; + +use ethers::{types::Address, utils::keccak256}; + +use summa_backend::{ + apis::{address_ownership::AddressOwnership, round::Round}, + contracts::generated::{solvency_verifier::SolvencyVerifier, summa_contract::Summa}, + tests::initialize_anvil, +}; + +#[tokio::main] +async fn main() { + // Initialize test environment + let (anvil, _, _, client, _) = initialize_anvil().await; + + // In this case, We have to deploy Solvency verifier contract first and then deploy Summa contract. + let solvency_verifer_contract = SolvencyVerifier::deploy(Arc::clone(&client), ()) + .unwrap() + .send() + .await + .unwrap(); + + // We will not use Inclusion verifier contract in this example, + // so we will set null address for the inclusion verifier contract. + let summa_contract = Summa::deploy( + Arc::clone(&client), + (solvency_verifer_contract.address(), Address::zero()), + ) + .unwrap() + .send() + .await + .unwrap(); + + // Initialize `Solvency` client for submitting proof of solvency. + // To verify proof of solvency on the contract, at least one ownership address must be registered on the contract. + let mut address_ownership_client = AddressOwnership::new( + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + anvil.chain_id(), + anvil.endpoint().as_str(), + summa_contract.address(), + "src/apis/csv/signatures.csv", + ) + .unwrap(); + + address_ownership_client + .dispatch_proof_of_address_ownership() + .await + .unwrap(); + + // Initialize `Round` for submitting proof of solvency. + let asset_csv = "src/apis/csv/assets.csv"; + let entry_csv = "../zk_prover/src/merkle_sum_tree/csv/entry_16.csv"; + let params_path = "ptau/hermez-raw-11"; + + let mut round = Round::<4, 2, 14>::new( + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", // anvil account [0] + anvil.chain_id(), + anvil.endpoint().as_str(), + summa_contract.address(), + entry_csv, + asset_csv, + params_path, + 1, + ) + .unwrap(); + + round.dispatch_solvency_proof().await.unwrap(); + + let log = summa_contract + .solvency_proof_submitted_filter() + .query() + .await + .unwrap(); + println!("{:?}", log); + + println!("Solvency proof is submitted successfully!") +} From c7fd5d5bec1391f41cc27555a7ca95a0e48c6f2d Mon Sep 17 00:00:00 2001 From: sifnoc Date: Tue, 19 Sep 2023 15:08:12 +0000 Subject: [PATCH 05/12] feat: create all examples; modified generating inclusion proof fn in the Round --- backend/Cargo.toml | 4 - backend/examples/generate_inclusion.rs | 54 +++++++ .../main.rs => generate_signatures.rs} | 6 +- backend/examples/helpers/inclusion_proof.rs | 25 ++++ backend/examples/helpers/mod.rs | 1 + .../message_signer.rs => mock_signer.rs} | 1 + backend/examples/submit_ownership.rs | 26 +--- backend/examples/submit_solvency.rs | 56 ++------ backend/examples/verify_inclusion.rs | 105 -------------- .../examples/verify_inclusion_on_contract.rs | 99 +++++++++++++ backend/examples/verify_inclusion_on_local.rs | 56 ++++++++ backend/src/apis/round.rs | 7 +- backend/src/tests.rs | 136 +++++++++--------- zk_prover/src/circuits/utils.rs | 43 +++++- 14 files changed, 372 insertions(+), 247 deletions(-) create mode 100644 backend/examples/generate_inclusion.rs rename backend/examples/{generate_signatures/main.rs => generate_signatures.rs} (82%) create mode 100644 backend/examples/helpers/inclusion_proof.rs create mode 100644 backend/examples/helpers/mod.rs rename backend/examples/{generate_signatures/message_signer.rs => mock_signer.rs} (95%) delete mode 100644 backend/examples/verify_inclusion.rs create mode 100644 backend/examples/verify_inclusion_on_contract.rs create mode 100644 backend/examples/verify_inclusion_on_local.rs diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 4d6ee6de..eec1ddec 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -22,7 +22,3 @@ bincode = "1.3.3" [build-dependencies] ethers = { version = "2.0.7", default-features = false, features = ["ethers-solc"] } - -[[example]] -name = "generate_signatures" -path = "examples/generate_signatures/main.rs" diff --git a/backend/examples/generate_inclusion.rs b/backend/examples/generate_inclusion.rs new file mode 100644 index 00000000..04f978c7 --- /dev/null +++ b/backend/examples/generate_inclusion.rs @@ -0,0 +1,54 @@ +#![feature(generic_const_exprs)] +use serde_json::{json, to_writer}; +use std::fs; +use summa_backend::{apis::round::Round, tests::initialize_test_env}; + +const USER_INDEX: usize = 0; + +#[tokio::main] +async fn main() { + // Initialize test environment + let (anvil, _, _, _, summa_contract, mut address_ownership_client) = + initialize_test_env().await; + + address_ownership_client + .dispatch_proof_of_address_ownership() + .await + .unwrap(); + + // Initialize `Round` for submitting proof of solvency. + let asset_csv = "src/apis/csv/assets.csv"; + let entry_csv = "../zk_prover/src/merkle_sum_tree/csv/entry_16.csv"; + let params_path = "ptau/hermez-raw-11"; + + let mut round = Round::<4, 2, 14>::new( + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", // anvil account [0] + anvil.chain_id(), + anvil.endpoint().as_str(), + summa_contract.address(), + entry_csv, + asset_csv, + params_path, + 1, + ) + .unwrap(); + + // Before generating inclusion proof, CEX must dispatch proof of solvency for updating the root of merkle sum tree. + // Without that, the user may not trust your `root_hash` in the inclusion proof because it is not published on-chain. + round.dispatch_solvency_proof().await.unwrap(); + + let inclusion_proof = round.get_proof_of_inclusion(USER_INDEX).unwrap(); + let public_input_vec = inclusion_proof.get_public_inputs(); + + let output = json!({ + "proof": serde_json::to_string(&inclusion_proof.get_proof()).unwrap(), + "leaf_hash": serde_json::to_string(&public_input_vec[0][0]).unwrap(), + "root_hash": serde_json::to_string(&public_input_vec[0][1]).unwrap() + }); + + let file = + fs::File::create(format!("user_{}_proof.json", USER_INDEX)).expect("Unable to create file"); + to_writer(file, &output).expect("Failed to write JSON to file"); + + println!("Exported proof to user_{}_proof.json", USER_INDEX); +} diff --git a/backend/examples/generate_signatures/main.rs b/backend/examples/generate_signatures.rs similarity index 82% rename from backend/examples/generate_signatures/main.rs rename to backend/examples/generate_signatures.rs index 014e83f6..276265e3 100644 --- a/backend/examples/generate_signatures/main.rs +++ b/backend/examples/generate_signatures.rs @@ -1,9 +1,10 @@ +#![feature(generic_const_exprs)] use std::{error::Error, fs::File}; use csv::WriterBuilder; -mod message_signer; -use message_signer::sign_message; +mod mock_signer; +use mock_signer::sign_message; #[tokio::main] async fn main() -> Result<(), Box> { @@ -23,6 +24,7 @@ async fn main() -> Result<(), Box> { } wtr.flush()?; // This will ensure all bytes are written + println!("Successfully exported signatures to {}", path); Ok(()) } diff --git a/backend/examples/helpers/inclusion_proof.rs b/backend/examples/helpers/inclusion_proof.rs new file mode 100644 index 00000000..f590b600 --- /dev/null +++ b/backend/examples/helpers/inclusion_proof.rs @@ -0,0 +1,25 @@ +use halo2_proofs::halo2curves::bn256::Fr as Fp; +use num_bigint::BigUint; +use serde::Deserialize; + +use summa_solvency::merkle_sum_tree::Entry; + +#[derive(Debug, Deserialize)] +pub struct InclusionProof { + pub leaf_hash: String, + pub root_hash: String, + pub proof: String, +} + +pub fn generate_leaf_hash(user_name: String, balances: Vec) -> Fp +where + [usize; N_ASSETS + 1]: Sized, +{ + // Convert usize to BigInt for the `Entry` struct + let balances_big_uint: Vec = balances.into_iter().map(BigUint::from).collect(); + + let entry: Entry = + Entry::new(user_name, balances_big_uint.try_into().unwrap()).unwrap(); + + entry.compute_leaf().hash +} diff --git a/backend/examples/helpers/mod.rs b/backend/examples/helpers/mod.rs new file mode 100644 index 00000000..55868e1e --- /dev/null +++ b/backend/examples/helpers/mod.rs @@ -0,0 +1 @@ +pub mod inclusion_proof; diff --git a/backend/examples/generate_signatures/message_signer.rs b/backend/examples/mock_signer.rs similarity index 95% rename from backend/examples/generate_signatures/message_signer.rs rename to backend/examples/mock_signer.rs index 2b894f16..2a122463 100644 --- a/backend/examples/generate_signatures/message_signer.rs +++ b/backend/examples/mock_signer.rs @@ -8,6 +8,7 @@ use ethers::{ use summa_backend::apis::csv_parser::SignatureRecord; +// Separated this function from the `generate_signatures.rs` for clarity on the example. pub async fn sign_message(message: &str) -> Result, Box> { // Using private keys directly is insecure. // Instead, consider leveraging hardware wallet support. diff --git a/backend/examples/submit_ownership.rs b/backend/examples/submit_ownership.rs index 73707318..b1686604 100644 --- a/backend/examples/submit_ownership.rs +++ b/backend/examples/submit_ownership.rs @@ -1,36 +1,22 @@ -use std::sync::Arc; - use ethers::{ abi::{encode, Token}, - types::{Address, U256}, + types::U256, utils::keccak256, }; -use summa_backend::{ - apis::address_ownership::AddressOwnership, contracts::generated::summa_contract::Summa, - tests::initialize_anvil, -}; +use summa_backend::{apis::address_ownership::AddressOwnership, tests::initialize_test_env}; // In this example, we will demonstrate how to submit ownership of address to the Summa contract. #[tokio::main] async fn main() { // We have already demonstrated how to generate a CSV file containing the asset ownership proofs, `AddressOwnershipProof`. // For more details on this, kindly refer to the "generate_signature" example. - // - // For the current demonstration, we'll use the same CSV file produced in `generate_signature` example. - let signature_csv_path = "src/apis/csv/signatures.csv"; - // Initialize test environment - let (anvil, _, _, client, _) = initialize_anvil().await; + // Initialize test environment without `address_ownership` instance from `initialize_test_env` function. + let (anvil, _, _, _, summa_contract, _) = initialize_test_env().await; - // Deploy contracts with null addresses for verifier contracts for this example - let summa_contract = Summa::deploy(Arc::clone(&client), (Address::zero(), Address::zero())) - .unwrap() - .send() - .await - .unwrap(); - - // Initialize `AddressOwnership` client + // For the current demonstration, we'll use the same CSV file produced in `generate_signature` example. + let signature_csv_path = "src/apis/csv/signatures.csv"; let mut address_ownership_client = AddressOwnership::new( "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", anvil.chain_id(), diff --git a/backend/examples/submit_solvency.rs b/backend/examples/submit_solvency.rs index bcb96aa1..349cfa19 100644 --- a/backend/examples/submit_solvency.rs +++ b/backend/examples/submit_solvency.rs @@ -1,47 +1,11 @@ #![feature(generic_const_exprs)] -use std::sync::Arc; - -use ethers::{types::Address, utils::keccak256}; - -use summa_backend::{ - apis::{address_ownership::AddressOwnership, round::Round}, - contracts::generated::{solvency_verifier::SolvencyVerifier, summa_contract::Summa}, - tests::initialize_anvil, -}; +use summa_backend::{apis::round::Round, tests::initialize_test_env}; #[tokio::main] async fn main() { // Initialize test environment - let (anvil, _, _, client, _) = initialize_anvil().await; - - // In this case, We have to deploy Solvency verifier contract first and then deploy Summa contract. - let solvency_verifer_contract = SolvencyVerifier::deploy(Arc::clone(&client), ()) - .unwrap() - .send() - .await - .unwrap(); - - // We will not use Inclusion verifier contract in this example, - // so we will set null address for the inclusion verifier contract. - let summa_contract = Summa::deploy( - Arc::clone(&client), - (solvency_verifer_contract.address(), Address::zero()), - ) - .unwrap() - .send() - .await - .unwrap(); - - // Initialize `Solvency` client for submitting proof of solvency. - // To verify proof of solvency on the contract, at least one ownership address must be registered on the contract. - let mut address_ownership_client = AddressOwnership::new( - "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", - anvil.chain_id(), - anvil.endpoint().as_str(), - summa_contract.address(), - "src/apis/csv/signatures.csv", - ) - .unwrap(); + let (anvil, _, _, _, summa_contract, mut address_ownership_client) = + initialize_test_env().await; address_ownership_client .dispatch_proof_of_address_ownership() @@ -65,14 +29,14 @@ async fn main() { ) .unwrap(); - round.dispatch_solvency_proof().await.unwrap(); + assert_eq!(round.dispatch_solvency_proof().await.unwrap(), ()); - let log = summa_contract - .solvency_proof_submitted_filter() - .query() - .await - .unwrap(); - println!("{:?}", log); + // You can also use the `solvency_proof_submitted_filter` method to check if the solvency proof is submitted. + // println!("{:?}", summa_contract + // .solvency_proof_submitted_filter() + // .query() + // .await + // .unwrap();); println!("Solvency proof is submitted successfully!") } diff --git a/backend/examples/verify_inclusion.rs b/backend/examples/verify_inclusion.rs deleted file mode 100644 index 4262fed0..00000000 --- a/backend/examples/verify_inclusion.rs +++ /dev/null @@ -1,105 +0,0 @@ -#![feature(generic_const_exprs)] -use halo2_proofs::halo2curves::{bn256::Fr as Fp, ff::PrimeField}; -use num_bigint::BigUint; - -use summa_backend::apis::round::Snapshot; -use summa_solvency::{ - circuits::{ - merkle_sum_tree::MstInclusionCircuit, - utils::{full_verifier, generate_setup_artifacts}, - }, - merkle_sum_tree::Entry, -}; - -// The CEX may only provide `balances` and `username` to the user without `leaf_hash`. -// In this case, the user will have to generate `leaf_hash` themselves with this method. -fn generate_leaf_hash(user_name: String, balances: Vec) -> Fp -where - [usize; N_ASSETS + 1]: Sized, -{ - // Convert usize to BigInt for the `Entry` struct - let balances_big_uint: Vec = balances.into_iter().map(BigUint::from).collect(); - - let entry: Entry = - Entry::new(user_name, balances_big_uint.try_into().unwrap()).unwrap(); - - entry.compute_leaf().hash -} - -fn main() { - const LEVELS: usize = 4; - const N_ASSETS: usize = 2; - const N_BYTES: usize = 8; - - let ptau_path = "./ptau/hermez-raw-11"; - let asset_csv_path = "src/apis/csv/assets.csv"; - let entry_csv_path = "../zk_prover/src/merkle_sum_tree/csv/entry_16.csv"; - - // CEX Generate the Merkle Sum Tree and then initialize the circuit. - // Note that `signature_csv` is empty because this is only needed to generate π of Solvency, which is not the case here. - let snapshot = - Snapshot::::new(&asset_csv_path, &entry_csv_path, &ptau_path) - .unwrap(); - - let inclusion_proof = snapshot.generate_proof_of_inclusion(0 as usize).unwrap(); - - let encoded: Vec = bincode::serialize(&inclusion_proof.get_proof()).unwrap(); - - // Most likely, the user will receive the proof file and load it for verification like the one below. - // - // let mut file = File::open("examples/entry_0_proof.bin").unwrap(); - // let mut encoded = Vec::new(); - // file.read_to_end(&mut encoded).unwrap(); - // - // There are two public inputs: root_hash and leaf_hash. - // The root_hash is publicly shared, but the leaf_hash is not. - // Only the user can verify the leaf_hash using their name (username) and balances. - // The verifier should have access to both the username and balances. - // - // root_hash = 0x02e021d9bf99c5bd7267488b6a7a5cf5f7d00222a41b6a9b971899c44089e0c5 - let root_hash = "1300633067792667740851197998552728163078912135282962223512949070409098715333"; - - // When verifying the inclusion proof on the user side, - // you have to load two files: `ptau` and `proof`. - let proof: Vec = bincode::deserialize(&encoded[..]).unwrap(); - - // Importantly, the user should verify the leaf hash using their username and balances. - let user_name = "dxGaEAii".to_string(); - let balances_usize = vec![11888, 41163]; - - // index 0 user's leaf_hash : 0x0e113acd03b98f0bab0ef6f577245d5d008cbcc19ef2dab3608aa4f37f72a407 - let leaf_hash = Fp::from_str_vartime( - "6362822108736413915574850018842190920390136280184018644072260166743334495239", - ) - .unwrap(); - - // This is the purpose of the example - // When the user receives their `leaf_hash`, the user must verify the `leaf_hash` from their `balances` and `username`. - assert_eq!( - leaf_hash, - generate_leaf_hash::(user_name.clone(), balances_usize.clone()) - ); - - let mst_inclusion_circuit = MstInclusionCircuit::::init_empty(); - - // The CEX can serve the params and vk by using the get_trusted_setup_for_mst_inclusion() method from the snapshot instance, as shown below: - // let (params, _, vk) = snapshot.get_trusted_setup_for_mst_inclusion(); - // - // However, on the user side, creating a snapshot is not possible due to the lack of the entry.csv and signature.csv files. - // Therefore, we can generate the artifacts from the ptau file with a specified k, which is the same number used while generating the proof. - let (params, _, vk) = - generate_setup_artifacts(11, Some(ptau_path), mst_inclusion_circuit).unwrap(); - - let verification_result: bool = full_verifier( - ¶ms, - &vk, - proof, - vec![vec![leaf_hash, Fp::from_str_vartime(root_hash).unwrap()]], - ); - - // Given the proper inputs (`root_hash` and `leaf_hash`), the proof is valid. - println!( - "Verifying the proof result for User #0: {}", - verification_result - ); -} diff --git a/backend/examples/verify_inclusion_on_contract.rs b/backend/examples/verify_inclusion_on_contract.rs new file mode 100644 index 00000000..1658cced --- /dev/null +++ b/backend/examples/verify_inclusion_on_contract.rs @@ -0,0 +1,99 @@ +#![feature(generic_const_exprs)] +use std::{error::Error, fs::File, io::BufReader}; + +use ethers::types::{Bytes, U256}; +use halo2_proofs::halo2curves::bn256::Fr as Fp; +use serde_json::from_reader; + +use summa_backend::{apis::round::Round, tests::initialize_test_env}; +mod helpers; +use helpers::inclusion_proof::{generate_leaf_hash, InclusionProof}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Let assume the user can get instance of `summa_contract` and the CEX already submit solvency at timestamp `1`. + let (anvil, _, _, _, summa_contract, mut address_ownership_client) = + initialize_test_env().await; + + address_ownership_client + .dispatch_proof_of_address_ownership() + .await + .unwrap(); + + // Initialize `Round` for submitting proof of solvency. + let asset_csv = "src/apis/csv/assets.csv"; + let entry_csv = "../zk_prover/src/merkle_sum_tree/csv/entry_16.csv"; + let params_path = "ptau/hermez-raw-11"; + + let mut round = Round::<4, 2, 14>::new( + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", // anvil account [0] + anvil.chain_id(), + anvil.endpoint().as_str(), + summa_contract.address(), + entry_csv, + asset_csv, + params_path, + 1, + ) + .unwrap(); + + assert_eq!(round.dispatch_solvency_proof().await.unwrap(), ()); + + // The user will know these constants before or when they receive the proof. + const N_ASSETS: usize = 2; + const USER_INDEX: usize = 0; + + let snapshot_time = U256::from(1); // specific time to dispatch solvency proof + + // When verifying the inclusion proof on the user side,-raw-11"; + let proof_path = format!("user_{}_proof.json", USER_INDEX); + + let file = File::open(proof_path)?; + let reader = BufReader::new(file); + let proof_data: InclusionProof = from_reader(reader)?; + let proof: Vec = serde_json::from_str(&proof_data.proof).unwrap(); + + // These `user_name` and `balances` be assumed that are given from the CEX. + let user_name = "dxGaEAii".to_string(); + let balances = vec![11888, 41163]; + + let leaf_hash: Fp = serde_json::from_str(&proof_data.leaf_hash).unwrap(); + assert_eq!( + leaf_hash, + generate_leaf_hash::(user_name.clone(), balances.clone()) + ); + + // Make public_input from `leaf_hash` and `root_hash` iter and convert it to `Fr` + let root_hash: Fp = serde_json::from_str(&proof_data.root_hash).unwrap(); + let public_inputs: Vec = vec![leaf_hash, root_hash] + .iter() + .map(|x| { + let mut bytes = x.to_bytes(); + bytes.reverse(); + U256::from_big_endian(&bytes) + }) + .collect(); + + // Get `mst_root` from contract. the `mst_root` is disptached by CEX with specific time `snapshot_time`. + let mst_root = summa_contract + .mst_roots(snapshot_time) + .call() + .await + .unwrap(); + + // Compare `mst_root` with `root_hash` from proof. + assert_eq!(mst_root, public_inputs[1]); + + // Verify inclusion proof on contract verifier + let verification_result = summa_contract + .verify_inclusion_proof(Bytes::from(proof), public_inputs, snapshot_time) + .await + .unwrap(); + + println!( + "Verifying the proof on contract veirifer for User #{}: {}", + USER_INDEX, verification_result + ); + + Ok(()) +} diff --git a/backend/examples/verify_inclusion_on_local.rs b/backend/examples/verify_inclusion_on_local.rs new file mode 100644 index 00000000..93eb3787 --- /dev/null +++ b/backend/examples/verify_inclusion_on_local.rs @@ -0,0 +1,56 @@ +#![feature(generic_const_exprs)] +use std::{error::Error, fs::File, io::BufReader}; + +use halo2_proofs::halo2curves::bn256::Fr as Fp; +use serde_json::from_reader; + +use summa_solvency::circuits::{ + merkle_sum_tree::MstInclusionCircuit, + utils::{full_evm_verifier, generate_setup_artifacts}, +}; +mod helpers; +use helpers::inclusion_proof::{generate_leaf_hash, InclusionProof}; + +fn main() -> Result<(), Box> { + // This contants should be matched with the constants used while generating the proof. + const LEVELS: usize = 4; + const N_ASSETS: usize = 2; + const N_BYTES: usize = 14; + const USER_INDEX: usize = 0; + + // When verifying the inclusion proof on local, you have to load two files: `ptau` and `proof`. + let ptau_path = "./ptau/hermez-raw-11"; + let proof_path = "user_0_proof.json"; + + let file = File::open(proof_path)?; + let reader = BufReader::new(file); + let proof_data: InclusionProof = from_reader(reader)?; + let proof: Vec = serde_json::from_str(&proof_data.proof).unwrap(); + + // These `user_name` and `balances` be assumed that are given from the CEX. + let user_name = "dxGaEAii".to_string(); + let balances_usize = vec![11888, 41163]; + + let leaf_hash: Fp = serde_json::from_str(&proof_data.leaf_hash).unwrap(); + assert_eq!( + leaf_hash, + generate_leaf_hash::(user_name.clone(), balances_usize.clone()) + ); + + let root_hash: Fp = serde_json::from_str(&proof_data.root_hash).unwrap(); + + let mst_inclusion_circuit = MstInclusionCircuit::::init_empty(); + + let (params, _, vk) = + generate_setup_artifacts(11, Some(ptau_path), mst_inclusion_circuit).unwrap(); + + let verification_result: bool = + full_evm_verifier(¶ms, &vk, proof, vec![vec![leaf_hash, root_hash]]); + + println!( + "Verifying the proof result for User #{}: {}", + USER_INDEX, verification_result + ); + + Ok(()) +} diff --git a/backend/src/apis/round.rs b/backend/src/apis/round.rs index 780cc831..2c8a3c3f 100644 --- a/backend/src/apis/round.rs +++ b/backend/src/apis/round.rs @@ -7,7 +7,7 @@ use halo2_proofs::{ plonk::{ProvingKey, VerifyingKey}, poly::kzg::commitment::ParamsKZG, }; -use snark_verifier_sdk::CircuitExt; +use snark_verifier_sdk::{evm::gen_evm_proof_shplonk, CircuitExt}; use std::error::Error; use super::csv_parser::parse_asset_csv; @@ -16,7 +16,7 @@ use summa_solvency::{ circuits::{ merkle_sum_tree::MstInclusionCircuit, solvency::SolvencyCircuit, - utils::{full_prover, gen_proof_solidity_calldata, generate_setup_artifacts}, + utils::{gen_proof_solidity_calldata, generate_setup_artifacts}, }, merkle_sum_tree::MerkleSumTree, }; @@ -202,7 +202,8 @@ where let circuit = MstInclusionCircuit::::init(self.mst.clone(), user_index); - let proof = full_prover( + // Currently, default manner of generating a inclusion proof for solidity-verifier. + let proof = gen_evm_proof_shplonk( &self.trusted_setup[0].0, &self.trusted_setup[0].1, circuit.clone(), diff --git a/backend/src/tests.rs b/backend/src/tests.rs index 834bbefe..0e7cbf76 100644 --- a/backend/src/tests.rs +++ b/backend/src/tests.rs @@ -9,15 +9,21 @@ use ethers::{ }; use tokio::time; +use crate::apis::address_ownership::AddressOwnership; +use crate::contracts::generated::{ + inclusion_verifier::InclusionVerifier, solvency_verifier::SolvencyVerifier, + summa_contract::Summa, +}; use crate::contracts::mock::mock_erc20::{MockERC20, MOCKERC20_ABI, MOCKERC20_BYTECODE}; -// Setup test conditions on the anvil instance -pub async fn initialize_anvil() -> ( +// Setup test environment on the anvil instance +pub async fn initialize_test_env() -> ( AnvilInstance, H160, H160, Arc, LocalWallet>>, - MockERC20, LocalWallet>>, + Summa, LocalWallet>>, + AddressOwnership, ) { let anvil: ethers::utils::AnvilInstance = Anvil::new() .mnemonic("test test test test test test test test test test test junk") @@ -67,7 +73,49 @@ pub async fn initialize_anvil() -> ( time::sleep(Duration::from_millis(500)).await; - (anvil, cex_addr_1, cex_addr_2, client, mock_erc20) + // Deploy verifier contracts before deploy Summa contract + let solvency_verifer_contract = SolvencyVerifier::deploy(Arc::clone(&client), ()) + .unwrap() + .send() + .await + .unwrap(); + + let inclusion_verifer_contract = InclusionVerifier::deploy(Arc::clone(&client), ()) + .unwrap() + .send() + .await + .unwrap(); + + // Deploy Summa contract + let summa_contract = Summa::deploy( + Arc::clone(&client), + ( + solvency_verifer_contract.address(), + inclusion_verifer_contract.address(), + ), + ) + .unwrap() + .send() + .await + .unwrap(); + + let address_ownership_client = AddressOwnership::new( + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + anvil.chain_id(), + anvil.endpoint().as_str(), + summa_contract.address(), + "src/apis/csv/signatures.csv", + ) + .unwrap(); + + ( + anvil, + cex_addr_1, + cex_addr_2, + client, + summa_contract, + address_ownership_client, + ) } #[cfg(test)] @@ -81,52 +129,16 @@ mod test { }; use crate::apis::{address_ownership::AddressOwnership, round::Round}; - use crate::contracts::generated::{ - inclusion_verifier::InclusionVerifier, - solvency_verifier::SolvencyVerifier, - summa_contract::{ - AddressOwnershipProof, AddressOwnershipProofSubmittedFilter, Asset, - SolvencyProofSubmittedFilter, Summa, - }, + use crate::contracts::generated::summa_contract::{ + AddressOwnershipProof, AddressOwnershipProofSubmittedFilter, Asset, + SolvencyProofSubmittedFilter, }; - use crate::tests::initialize_anvil; + use crate::tests::initialize_test_env; #[tokio::test] async fn test_round_features() { - let (anvil, cex_addr_1, cex_addr_2, client, _mock_erc20) = initialize_anvil().await; - - let solvency_verifer_contract = SolvencyVerifier::deploy(Arc::clone(&client), ()) - .unwrap() - .send() - .await - .unwrap(); - - let inclusion_verifer_contract = InclusionVerifier::deploy(Arc::clone(&client), ()) - .unwrap() - .send() - .await - .unwrap(); - - let summa_contract = Summa::deploy( - Arc::clone(&client), - ( - solvency_verifer_contract.address(), - inclusion_verifer_contract.address(), - ), - ) - .unwrap() - .send() - .await - .unwrap(); - - let mut address_ownership_client = AddressOwnership::new( - "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", - anvil.chain_id(), - anvil.endpoint().as_str(), - summa_contract.address(), - "src/apis/csv/signatures.csv", - ) - .unwrap(); + let (anvil, cex_addr_1, cex_addr_2, _, summa_contract, mut address_ownership_client) = + initialize_test_env().await; let ownership_submitted_result = address_ownership_client .dispatch_proof_of_address_ownership() @@ -226,27 +238,23 @@ mod test { // Test inclusion proof let inclusion_proof = round.get_proof_of_inclusion(0).unwrap(); let proof = Bytes::from(inclusion_proof.get_proof().clone()); - let public_input_vec = inclusion_proof.get_public_inputs(); - - // Adjust for endianness - let mut leaf_hash = public_input_vec[0][0].to_bytes(); - let mut root_hash = public_input_vec[0][1].to_bytes(); - leaf_hash.reverse(); - root_hash.reverse(); - - let public_inputs = vec![ - U256::from_big_endian(&leaf_hash), - U256::from_big_endian(&root_hash), - ]; - - // Ensure the root hash matches the one from the contract - let onchain_mstroot = summa_contract.mst_roots(U256::from(1)).await.unwrap(); - assert_eq!(onchain_mstroot, U256::from_big_endian(&root_hash)); + let public_inputs: Vec = inclusion_proof + .get_public_inputs() + .iter() + .flat_map(|input_set| { + input_set.iter().map(|input| { + let mut bytes = input.to_bytes(); + bytes.reverse(); + U256::from_big_endian(&bytes) + }) + }) + .collect(); // Verify inclusion proof with onchain function - let _result = summa_contract + summa_contract .verify_inclusion_proof(proof, public_inputs, U256::from(1)) - .await; + .await + .unwrap(); drop(anvil); } diff --git a/zk_prover/src/circuits/utils.rs b/zk_prover/src/circuits/utils.rs index 8c52ab12..80671056 100644 --- a/zk_prover/src/circuits/utils.rs +++ b/zk_prover/src/circuits/utils.rs @@ -17,7 +17,7 @@ use halo2_proofs::{ kzg::{ commitment::{KZGCommitmentScheme, ParamsKZG}, multiopen::{ProverSHPLONK, VerifierSHPLONK}, - strategy::SingleStrategy, + strategy::{AccumulatorStrategy, SingleStrategy}, }, }, transcript::{ @@ -29,7 +29,7 @@ use regex_simple::Regex; use snark_verifier::{ cost::CostEstimation, pcs::kzg::{Bdfg21, KzgAs}, - system::halo2::{compile, Config}, + system::halo2::{compile, transcript::evm::EvmTranscript, Config}, verifier::plonk::PlonkSuccinctVerifier, }; use snark_verifier_sdk::{evm::gen_evm_proof_shplonk, CircuitExt}; @@ -71,7 +71,7 @@ pub fn generate_setup_artifacts + CircuitExt>( } } None => { - let timer = start_timer!(|| "Creating params"); + let timer = start_timer!(|| "None Creating params"); params = ParamsKZG::::setup(k, OsRng); end_timer!(timer); } @@ -134,6 +134,43 @@ pub fn full_verifier( .is_ok() } +/// Verifies a proof that generated by the `gen_evm_proof_shplonk` function. +pub fn full_evm_verifier( + params: &ParamsKZG, + vk: &VerifyingKey, + proof: Vec, + public_inputs: Vec>, +) -> bool { + let instance: Vec<&[Fp]> = public_inputs.iter().map(|input| &input[..]).collect(); + let instances = &[&instance[..]]; + + let mut transcript = TranscriptReadBuffer::<_, G1Affine, _>::init(proof.as_slice()); + verify_proof::<_, VerifierSHPLONK<'_, Bn256>, _, EvmTranscript<_, _, _, _>, _>( + params, + vk, + AccumulatorStrategy::new(params.verifier_params()), + instances, + &mut transcript, + ) + .is_ok() + + // let accept = { + // let mut transcript = TranscriptReadBuffer::<_, G1Affine, _>::init(proof.as_slice()); + // VerificationStrategy::<_, VerifierSHPLONK<'_, Bn256>>::finalize( + // verify_proof::<_, VerifierSHPLONK<'_, Bn256>, _, EvmTranscript<_, _, _, _>, _>( + // params, + // vk, + // AccumulatorStrategy::new(params.verifier_params()), + // instances, + // &mut transcript, + // ) + // .unwrap(), + // ) + // }; + + // accept +} + /// Generate a solidity verifier contract starting from its yul code. /// patterned after https://github.com/zkonduit/ezkl/blob/main/src/eth.rs#L326-L602 fn fix_verifier_sol(yul_code_path: PathBuf) -> Result> { From c11a68a65c9f1d809a23ff8a86adbfaa22299a30 Mon Sep 17 00:00:00 2001 From: sifnoc Date: Wed, 20 Sep 2023 12:40:40 +0000 Subject: [PATCH 06/12] chore: udpate README and added comments for examples --- backend/README.md | 135 +++++++++++++++--- backend/examples/generate_inclusion.rs | 15 +- backend/examples/generate_signatures.rs | 10 +- backend/examples/mock_signer.rs | 3 + backend/examples/submit_solvency.rs | 6 +- backend/examples/verify_inclusion_on_local.rs | 7 +- 6 files changed, 146 insertions(+), 30 deletions(-) diff --git a/backend/README.md b/backend/README.md index 6d1a62b7..128d1a49 100644 --- a/backend/README.md +++ b/backend/README.md @@ -65,41 +65,140 @@ By completing these steps, the backend will be primed with the essential verifie ## Examples -### Running the Inclusion Verification +The sequence in which the examples are introduced closely relates to the steps of the Summa protocol. +These examples will be help to understand how the Summa works with the Summa contract and the user side. -This example demonstrates how a user can verify the inclusion of their account in the Merkle Sum Tree. -In this example, the CEX provides the user with their `balances` and `username`, but not the `leaf_hash`. -The user will generate the `leaf_hash` themselves and then verify its inclusion in the tree. +### 1. Generating Message Signatures -Make sure you have the required files: -- `backend/ptau/hermez-raw-11` -- `backend/src/apis/csv/assets.csv` -- `zk_prover/src/merkle_sum_tree/csv/entry_16.csv` +This example illustrates how to generate a CSV file containing signatures derived from a specific message, crucial for establishing `AddressOwnership`. +Creating the `signatures.csv` file is a preliminary step for initializing a `Round` in Summa. +This demonstration is introduced to be adaptable across various scenarios. For instance, you can compile this example that modified to support harware wallet into an executable binary, enabling an operator to run it with a hardware wallet. This operation should ideally be conducted within a secure environment inside a CEX, ensuring the system is isolated from any online networks to maximize protection against potential external threats. + +The generated signatures are stored in a CSV file, utilizing a custom delimiter for easy parsing and verification. The output file is located at `src/apis/csv/signatures.csv`. To run the example: ``` -cargo run --example verify_inclusion +cargo run --example generate_signatures +``` + +Note: While this example employs hardcoded private keys for simplicity, it's essential to remember that exposing private keys directly in real-world applications can pose serious security risks. Therefore, it's recommended to create your own `signer` that taps into secure mechanisms, such as hardware wallets or protected key vaults. + +### 2. Submitting Address Ownership to the Summa Contract + +This example demonstrates the process of submitting proof of address ownership to the Summa contract. After generating signatures for asset ownership (as shown in the `generate_signature` example), this step is essential to register those proofs on-chain, facilitating the validation of asset ownership within Summa. + +In this example, a test environment is set up with the anvil instance by invoking the `initialize_test_env` method. This environment is also utilized in other examples such as `submit_solvency` and `verify_inclusion_on_contracts`. + +Key points to note: + +The instance of `AddressOwnership` is initialized with `signatures.csv`, and is named `address_ownership_client`. This instance has already loaded the signature data. + +The `dispatch_proof_of_address_ownership` function sends a transaction to the Summa contract, registering the addresses owned by the CEX on the contract. + +After dispatching the transaction via `dispatch_proof_of_address_ownerhip`, the example computes the hashed addresses (address_hashes) to verify they have been correctly registered on the Summa contract. + +To excute this example: +``` +cargo run --example submit_ownership +``` + +Upon successful execution, you should see the message: ``` +Ownership proofs are submitted successfully! +``` + +Reminder: This demonstration takes place in a test environment. In real-world production, always ensure that the Summa contract is correctly deployed on the target chain. + +### 3. Submit Proof of Solvency + +Before generate inclusion proof for every user of the current round, You should submit proof of sovlency to Summa contract. Currently, we made this as mandatory way to commit the root hash of the Merkle Sum Tree. + +Without this process, It seems the user may not trust to the inclusion proof for the round. becuase the `mst_root` is not published on contract. More specifically, it means that the `mst_root` is not correctly verified on the Summa contract. + +In this example, we'll guide you through the process of submitting a solvency proof using the Round to the Summa contract. +The Round serves as the core of the backend in Summa, and we have briefly described it in the Components section. + +To initialize the Round, several parameters are required, including paths to specific CSV files (`assets.csv` and `entry_16.csv`), as well as a path to the ptau file (`ptau/hermez-raw-11`). + +The roles of these files are as follows: +- `assets.csv`: This file is essential for calculating the total balance of assets for the solvency proof. Currently, only the CEX can generate this asset CSV file in its specific manner. + +- `entry_16.csv`: This file is used to build the Merkle sum tree, where each leaf element originates from sixteen entries in the CSV. +X +- `ptau/hermez-raw-11`: Contains the Powers of Tau trusted setup parameters, essential for constructing the zk circuits. + +By employing the round instance, the solvency proof is dispatched to the Summa contract using the `dispatch_solvency_proof` method. + +To excute this example: +``` +cargo run --example submit_solvency +``` + +Upon successful execution, you will see the message: -On successful execution, you'll observe a message indicating the verification outcome: +``` + "Solvency proof is submitted successfully!" +``` + +### 4. Generating and Exporting Inclusion Proofs + +Assuming you are a CEX, let's say you've already committed the `solvency` and `ownership` proofs to the Summa contract. Now, you need to generate inclusion proofs for every user. + +In this example, we demonstrate how to generate and export user-specific inclusion proofs using the Round. This proof is crucial for users as it helps them validate the presence of specific elements within the Merkle sum tree, which forms a part of the solvency proof submitted. + +After generating the inclusion proof, end of example parts the inclusion proof is transformed into a JSON format, making it easily shareable. + +To excute this example: +``` +cargo run --example generate_inclusion +``` + +Upon successful execution, you can see this message and exported file `user_0_proof.json`. + +``` + "Exported proof to user #0, as `user_0_proof.json`" +``` + +### 5. Verify Proof of Inclusion + +This is the final step in the Summa process and the only part that occurs on the user side. + +The user will receive the proof for a specific Round. There are two ways to verify the proof, one is on binary verifier in local environment, another is that the verifier function on the Summa contract. + +In the `verify_inclusion_on_local` example, the key part is that use `full_evm_verifier` method for verifying the proof with publicly downloaded `ptau` file. +We can think the demonstration of verifying in the example is that only shown excutable local verifier that is served from CEX in publicly way, such as github, or IPFS. + +To run the verify inclusion on local example: +``` +cargo run --example verify_inclusion_on_local +``` + +Like the user #0, you will see the result like: ``` Verifying the proof result for User #0: true ``` -### Generating Message Signatures +Another way to verify the inclusion proof, the user can use method on the Summa contract that already deployed on blockchain. -This example demonstrates how to generate a CSV file containing signatures derived from a given message, essential for `AddressOwnership`. +In the `verify_inclusion_on_contract` example, the procedure for verifying the inclusion proof using an on-chain method is illustrated. By leveraging the data from the Summa contract, users can effortlessly ascertain that the provided proof aligns with the data submitted by the CEX. -This demonstration is adaptable for various scenarios. For instance, you could compile this example into an executable binary, an operator can run it using a hardware wallet. - This operation should ideally occur in a highly secure environment within a CEX, where the system is isolated from any online networks, ensuring maximum security against potential external threats. +To elaborate: -The resulting signatures are saved in a CSV file using a custom delimiter, allowing for straightforward parsing and verification. You can find the output file at `src/apis/csv/signatures.csv`. +Retrieving the MST Root: The user fetches the `mst_root` from the Summa contract. This root should match the `root_hash` provided in the proof. This verification process is akin to ensuring that the `leaf_hash` corresponds with the anticipated hash based on the `username` and `balances` provided by the CEX. -To run the example: +On-chain Function Verification: The user then invokes the `verify_inclusion_proof` method on the Summa contract. Since this is a view function, it returns a boolean value without incurring any gas fees, indicating the success or failure of the verification. + +To run the verify inclusion on contract example: ``` -cargo run --example generate_signatures +cargo run --example verify_inclusion_on_contract ``` -Note: This example uses hardcoded private keys for simplicity. In real-world applications, it's imperative never to expose private keys in such a manner. Instead, consider crate your own `message_signer` to utilize secure mechanisms, such as hardware wallets or secure key vaults. +You will see the result like: +``` +Verifying the proof on contract veirifer for User #0: true +``` + + +With the `verify_inclusion_on_local` and `verify_inclusion_on_contract` examples at their disposal, users are equipped with options, allowing them to choose their preferred verification method, be it local or on-chain. Moreover, by employing both verification strategies, users can achieve a heightened level of trust and transparency. diff --git a/backend/examples/generate_inclusion.rs b/backend/examples/generate_inclusion.rs index 04f978c7..8bfb0480 100644 --- a/backend/examples/generate_inclusion.rs +++ b/backend/examples/generate_inclusion.rs @@ -33,22 +33,23 @@ async fn main() { ) .unwrap(); - // Before generating inclusion proof, CEX must dispatch proof of solvency for updating the root of merkle sum tree. - // Without that, the user may not trust your `root_hash` in the inclusion proof because it is not published on-chain. - round.dispatch_solvency_proof().await.unwrap(); - + // In a production environment, the CEX should dispatch the solvency proof to update the root of the Merkle sum tree prior to generating inclusion proofs. + // Otherwise, users might distrust the provided `root_hash` in the inclusion proof, as it hasn't been published on-chain. let inclusion_proof = round.get_proof_of_inclusion(USER_INDEX).unwrap(); let public_input_vec = inclusion_proof.get_public_inputs(); + // The structure of this output file may vary in production. + // For instance, the CEX might substitute `leaf_hash` with attributes like `username` and `balances`. + // Consequently, users would generate the `leaf_hash` on client-side before validating the proof. let output = json!({ "proof": serde_json::to_string(&inclusion_proof.get_proof()).unwrap(), "leaf_hash": serde_json::to_string(&public_input_vec[0][0]).unwrap(), "root_hash": serde_json::to_string(&public_input_vec[0][1]).unwrap() }); - let file = - fs::File::create(format!("user_{}_proof.json", USER_INDEX)).expect("Unable to create file"); + let filename = format!("user_{}_proof.json", USER_INDEX); + let file = fs::File::create(filename.clone()).expect("Unable to create file"); to_writer(file, &output).expect("Failed to write JSON to file"); - println!("Exported proof to user_{}_proof.json", USER_INDEX); + println!("Exported proof to user #{}, as `{}`", USER_INDEX, filename); } diff --git a/backend/examples/generate_signatures.rs b/backend/examples/generate_signatures.rs index 276265e3..6c70fd28 100644 --- a/backend/examples/generate_signatures.rs +++ b/backend/examples/generate_signatures.rs @@ -8,14 +8,18 @@ use mock_signer::sign_message; #[tokio::main] async fn main() -> Result<(), Box> { - // Given message to sign + // You can modify the message to gain better trust from users, or simply follow CEX requirements. + // The message will be used to verify addresses and register them in the `ownershipProofByAddress` mapping on the Summa contract. let message = "Summa proof of solvency for CryptoExchange"; let path = "src/apis/csv/signatures.csv"; - // Request signatures with `message` to the signer server + // Generate signatures for the given 'message' using the mock signer. + // For this example, the 'mock_signer' file contains only the 'sign_message' function. + // CEX should implement their own signer and use it here instead of 'sign_message'. let signatures = sign_message(message).await?; - // Write the signatures to a CSV file + // Write the signatures to a CSV file to be used in the `verify_signatures` example. + // It's envisioned that this CSV file will remain internal to CEX; only the Summa contract will publish its contents. let file = File::create(path)?; let mut wtr = WriterBuilder::new().delimiter(b';').from_writer(file); diff --git a/backend/examples/mock_signer.rs b/backend/examples/mock_signer.rs index 2a122463..a6b5e713 100644 --- a/backend/examples/mock_signer.rs +++ b/backend/examples/mock_signer.rs @@ -47,3 +47,6 @@ pub async fn sign_message(message: &str) -> Result, Box::new( "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", // anvil account [0] anvil.chain_id(), @@ -29,6 +32,7 @@ async fn main() { ) .unwrap(); + // Sends the solvency proof, which should ideally complete without errors. assert_eq!(round.dispatch_solvency_proof().await.unwrap(), ()); // You can also use the `solvency_proof_submitted_filter` method to check if the solvency proof is submitted. diff --git a/backend/examples/verify_inclusion_on_local.rs b/backend/examples/verify_inclusion_on_local.rs index 93eb3787..d0313b9d 100644 --- a/backend/examples/verify_inclusion_on_local.rs +++ b/backend/examples/verify_inclusion_on_local.rs @@ -19,6 +19,7 @@ fn main() -> Result<(), Box> { const USER_INDEX: usize = 0; // When verifying the inclusion proof on local, you have to load two files: `ptau` and `proof`. + // the `ptau` file should be exactly same with the one used while generating the proof. let ptau_path = "./ptau/hermez-raw-11"; let proof_path = "user_0_proof.json"; @@ -37,13 +38,17 @@ fn main() -> Result<(), Box> { generate_leaf_hash::(user_name.clone(), balances_usize.clone()) ); + // Similar to verifying the `leaf_hash` above, the user should check if the `root_hash` matches with the `mst_root` on the Summa contract. + // However, this example does not cover this part, as it's assumed that the user is running the verifier locally. + // For the process of checking the `root_hash`, refer to the `verify_inclusion_on_contract` example. let root_hash: Fp = serde_json::from_str(&proof_data.root_hash).unwrap(); + // Circuit and params are same while generating the proof. let mst_inclusion_circuit = MstInclusionCircuit::::init_empty(); - let (params, _, vk) = generate_setup_artifacts(11, Some(ptau_path), mst_inclusion_circuit).unwrap(); + // This result will be same on the contract method `verifyInclusionProof` on the Summa contract. let verification_result: bool = full_evm_verifier(¶ms, &vk, proof, vec![vec![leaf_hash, root_hash]]); From eacfe7df0542f35ab9367cc34ad8a298b0cdda5d Mon Sep 17 00:00:00 2001 From: sifnoc Date: Thu, 21 Sep 2023 13:12:28 +0000 Subject: [PATCH 07/12] fix: typo and update readme --- backend/README.md | 10 +++++----- backend/examples/generate_signatures.rs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/README.md b/backend/README.md index 128d1a49..18f6e25c 100644 --- a/backend/README.md +++ b/backend/README.md @@ -66,7 +66,7 @@ By completing these steps, the backend will be primed with the essential verifie ## Examples The sequence in which the examples are introduced closely relates to the steps of the Summa protocol. -These examples will be help to understand how the Summa works with the Summa contract and the user side. +These examples will help to understand how the Summa works with the Summa contract and the user side. ### 1. Generating Message Signatures @@ -99,7 +99,7 @@ The `dispatch_proof_of_address_ownership` function sends a transaction to the Su After dispatching the transaction via `dispatch_proof_of_address_ownerhip`, the example computes the hashed addresses (address_hashes) to verify they have been correctly registered on the Summa contract. -To excute this example: +To execute this example: ``` cargo run --example submit_ownership ``` @@ -129,9 +129,9 @@ The roles of these files are as follows: X - `ptau/hermez-raw-11`: Contains the Powers of Tau trusted setup parameters, essential for constructing the zk circuits. -By employing the round instance, the solvency proof is dispatched to the Summa contract using the `dispatch_solvency_proof` method. +An instance of Round dispatches the solvency proof using the `dispatch_solvency_proof` method. -To excute this example: +To execute this example: ``` cargo run --example submit_solvency ``` @@ -150,7 +150,7 @@ In this example, we demonstrate how to generate and export user-specific inclusi After generating the inclusion proof, end of example parts the inclusion proof is transformed into a JSON format, making it easily shareable. -To excute this example: +To execute this example: ``` cargo run --example generate_inclusion ``` diff --git a/backend/examples/generate_signatures.rs b/backend/examples/generate_signatures.rs index 6c70fd28..15d6f71c 100644 --- a/backend/examples/generate_signatures.rs +++ b/backend/examples/generate_signatures.rs @@ -19,7 +19,7 @@ async fn main() -> Result<(), Box> { let signatures = sign_message(message).await?; // Write the signatures to a CSV file to be used in the `verify_signatures` example. - // It's envisioned that this CSV file will remain internal to CEX; only the Summa contract will publish its contents. + // It's envisioned that this CSV file will remain internal to CEX, only the Summa contract will publish its contents. let file = File::create(path)?; let mut wtr = WriterBuilder::new().delimiter(b';').from_writer(file); From c9643c948ebcdae61ba46f72fde841acb6710233 Mon Sep 17 00:00:00 2001 From: sifnoc Date: Mon, 25 Sep 2023 09:09:05 +0000 Subject: [PATCH 08/12] fix: removed some examples and unused imports; update comments --- backend/README.md | 59 ++++-------------- backend/examples/generate_inclusion.rs | 2 +- backend/examples/generate_signatures.rs | 34 ----------- backend/examples/helpers/inclusion_proof.rs | 17 ------ backend/examples/mock_signer.rs | 52 ---------------- .../examples/verify_inclusion_on_contract.rs | 6 +- backend/examples/verify_inclusion_on_local.rs | 61 ------------------- backend/src/tests.rs | 27 ++------ zk_prover/src/circuits/utils.rs | 16 ----- zk_prover/src/merkle_sum_tree/utils/mod.rs | 2 + 10 files changed, 23 insertions(+), 253 deletions(-) delete mode 100644 backend/examples/generate_signatures.rs delete mode 100644 backend/examples/mock_signer.rs delete mode 100644 backend/examples/verify_inclusion_on_local.rs diff --git a/backend/README.md b/backend/README.md index 18f6e25c..ae48b96f 100644 --- a/backend/README.md +++ b/backend/README.md @@ -63,29 +63,12 @@ The following steps are optional and are only required if you need to update the By completing these steps, the backend will be primed with the essential verifiers for its tasks. -## Examples +## Example The sequence in which the examples are introduced closely relates to the steps of the Summa protocol. These examples will help to understand how the Summa works with the Summa contract and the user side. - -### 1. Generating Message Signatures - -This example illustrates how to generate a CSV file containing signatures derived from a specific message, crucial for establishing `AddressOwnership`. -Creating the `signatures.csv` file is a preliminary step for initializing a `Round` in Summa. - -This demonstration is introduced to be adaptable across various scenarios. For instance, you can compile this example that modified to support harware wallet into an executable binary, enabling an operator to run it with a hardware wallet. This operation should ideally be conducted within a secure environment inside a CEX, ensuring the system is isolated from any online networks to maximize protection against potential external threats. - -The generated signatures are stored in a CSV file, utilizing a custom delimiter for easy parsing and verification. The output file is located at `src/apis/csv/signatures.csv`. - -To run the example: -``` -cargo run --example generate_signatures -``` - -Note: While this example employs hardcoded private keys for simplicity, it's essential to remember that exposing private keys directly in real-world applications can pose serious security risks. Therefore, it's recommended to create your own `signer` that taps into secure mechanisms, such as hardware wallets or protected key vaults. - -### 2. Submitting Address Ownership to the Summa Contract +### 1. Submitting Address Ownership to the Summa Contract This example demonstrates the process of submitting proof of address ownership to the Summa contract. After generating signatures for asset ownership (as shown in the `generate_signature` example), this step is essential to register those proofs on-chain, facilitating the validation of asset ownership within Summa. @@ -101,7 +84,7 @@ After dispatching the transaction via `dispatch_proof_of_address_ownerhip`, the To execute this example: ``` -cargo run --example submit_ownership +cargo run --release --example submit_ownership ``` Upon successful execution, you should see the message: @@ -111,9 +94,9 @@ Ownership proofs are submitted successfully! Reminder: This demonstration takes place in a test environment. In real-world production, always ensure that the Summa contract is correctly deployed on the target chain. -### 3. Submit Proof of Solvency +### 2. Submit Proof of Solvency -Before generate inclusion proof for every user of the current round, You should submit proof of sovlency to Summa contract. Currently, we made this as mandatory way to commit the root hash of the Merkle Sum Tree. +Before generate inclusion proof for every user of the current round, You should submit proof of solvency to Summa contract. Currently, we made this as mandatory way to commit the root hash of the Merkle Sum Tree. Without this process, It seems the user may not trust to the inclusion proof for the round. becuase the `mst_root` is not published on contract. More specifically, it means that the `mst_root` is not correctly verified on the Summa contract. @@ -133,7 +116,7 @@ An instance of Round dispatches the solvency proof using the `dispatch_solvency_ To execute this example: ``` -cargo run --example submit_solvency +cargo run --release --example submit_solvency ``` Upon successful execution, you will see the message: @@ -142,7 +125,7 @@ Upon successful execution, you will see the message: "Solvency proof is submitted successfully!" ``` -### 4. Generating and Exporting Inclusion Proofs +### 3. Generating and Exporting Inclusion Proofs Assuming you are a CEX, let's say you've already committed the `solvency` and `ownership` proofs to the Summa contract. Now, you need to generate inclusion proofs for every user. @@ -152,7 +135,7 @@ After generating the inclusion proof, end of example parts the inclusion proof i To execute this example: ``` -cargo run --example generate_inclusion +cargo run --release --example generate_inclusion ``` Upon successful execution, you can see this message and exported file `user_0_proof.json`. @@ -161,26 +144,11 @@ Upon successful execution, you can see this message and exported file `user_0_pr "Exported proof to user #0, as `user_0_proof.json`" ``` -### 5. Verify Proof of Inclusion +### 4. Verify Proof of Inclusion This is the final step in the Summa process and the only part that occurs on the user side. -The user will receive the proof for a specific Round. There are two ways to verify the proof, one is on binary verifier in local environment, another is that the verifier function on the Summa contract. - -In the `verify_inclusion_on_local` example, the key part is that use `full_evm_verifier` method for verifying the proof with publicly downloaded `ptau` file. -We can think the demonstration of verifying in the example is that only shown excutable local verifier that is served from CEX in publicly way, such as github, or IPFS. - -To run the verify inclusion on local example: -``` -cargo run --example verify_inclusion_on_local -``` - -Like the user #0, you will see the result like: -``` -Verifying the proof result for User #0: true -``` - -Another way to verify the inclusion proof, the user can use method on the Summa contract that already deployed on blockchain. +The user will receive the proof for a specific Round. the user can use method on the Summa contract that already deployed on blockchain. It's important to note that the verifier function on the Summa contract is a view function, which means it doesn't require any gas or involve writing state on-chain. In the `verify_inclusion_on_contract` example, the procedure for verifying the inclusion proof using an on-chain method is illustrated. By leveraging the data from the Summa contract, users can effortlessly ascertain that the provided proof aligns with the data submitted by the CEX. @@ -192,13 +160,10 @@ On-chain Function Verification: The user then invokes the `verify_inclusion_proo To run the verify inclusion on contract example: ``` -cargo run --example verify_inclusion_on_contract +cargo run --release --example verify_inclusion_on_contract ``` You will see the result like: ``` -Verifying the proof on contract veirifer for User #0: true +Verifying the proof on contract verifier for User #0: true ``` - - -With the `verify_inclusion_on_local` and `verify_inclusion_on_contract` examples at their disposal, users are equipped with options, allowing them to choose their preferred verification method, be it local or on-chain. Moreover, by employing both verification strategies, users can achieve a heightened level of trust and transparency. diff --git a/backend/examples/generate_inclusion.rs b/backend/examples/generate_inclusion.rs index 8bfb0480..0d1a4fc6 100644 --- a/backend/examples/generate_inclusion.rs +++ b/backend/examples/generate_inclusion.rs @@ -21,7 +21,7 @@ async fn main() { let entry_csv = "../zk_prover/src/merkle_sum_tree/csv/entry_16.csv"; let params_path = "ptau/hermez-raw-11"; - let mut round = Round::<4, 2, 14>::new( + let round = Round::<4, 2, 14>::new( "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", // anvil account [0] anvil.chain_id(), anvil.endpoint().as_str(), diff --git a/backend/examples/generate_signatures.rs b/backend/examples/generate_signatures.rs deleted file mode 100644 index 15d6f71c..00000000 --- a/backend/examples/generate_signatures.rs +++ /dev/null @@ -1,34 +0,0 @@ -#![feature(generic_const_exprs)] -use std::{error::Error, fs::File}; - -use csv::WriterBuilder; - -mod mock_signer; -use mock_signer::sign_message; - -#[tokio::main] -async fn main() -> Result<(), Box> { - // You can modify the message to gain better trust from users, or simply follow CEX requirements. - // The message will be used to verify addresses and register them in the `ownershipProofByAddress` mapping on the Summa contract. - let message = "Summa proof of solvency for CryptoExchange"; - let path = "src/apis/csv/signatures.csv"; - - // Generate signatures for the given 'message' using the mock signer. - // For this example, the 'mock_signer' file contains only the 'sign_message' function. - // CEX should implement their own signer and use it here instead of 'sign_message'. - let signatures = sign_message(message).await?; - - // Write the signatures to a CSV file to be used in the `verify_signatures` example. - // It's envisioned that this CSV file will remain internal to CEX, only the Summa contract will publish its contents. - let file = File::create(path)?; - let mut wtr = WriterBuilder::new().delimiter(b';').from_writer(file); - - for signature in signatures { - wtr.serialize(signature)?; - } - - wtr.flush()?; // This will ensure all bytes are written - println!("Successfully exported signatures to {}", path); - - Ok(()) -} diff --git a/backend/examples/helpers/inclusion_proof.rs b/backend/examples/helpers/inclusion_proof.rs index f590b600..083ebedd 100644 --- a/backend/examples/helpers/inclusion_proof.rs +++ b/backend/examples/helpers/inclusion_proof.rs @@ -1,25 +1,8 @@ -use halo2_proofs::halo2curves::bn256::Fr as Fp; -use num_bigint::BigUint; use serde::Deserialize; -use summa_solvency::merkle_sum_tree::Entry; - #[derive(Debug, Deserialize)] pub struct InclusionProof { pub leaf_hash: String, pub root_hash: String, pub proof: String, } - -pub fn generate_leaf_hash(user_name: String, balances: Vec) -> Fp -where - [usize; N_ASSETS + 1]: Sized, -{ - // Convert usize to BigInt for the `Entry` struct - let balances_big_uint: Vec = balances.into_iter().map(BigUint::from).collect(); - - let entry: Entry = - Entry::new(user_name, balances_big_uint.try_into().unwrap()).unwrap(); - - entry.compute_leaf().hash -} diff --git a/backend/examples/mock_signer.rs b/backend/examples/mock_signer.rs deleted file mode 100644 index a6b5e713..00000000 --- a/backend/examples/mock_signer.rs +++ /dev/null @@ -1,52 +0,0 @@ -use std::{error::Error, str::FromStr}; - -use ethers::{ - abi::{encode, Token}, - signers::{LocalWallet, Signer}, - utils::{keccak256, to_checksum}, -}; - -use summa_backend::apis::csv_parser::SignatureRecord; - -// Separated this function from the `generate_signatures.rs` for clarity on the example. -pub async fn sign_message(message: &str) -> Result, Box> { - // Using private keys directly is insecure. - // Instead, consider leveraging hardware wallet support. - // `ethers-rs` provides support for both Ledger and Trezor hardware wallets. - // - // For example, you could use the Ledger wallet as shown below: - // let signing_wallets = (0..2).map(|index| Ledger::new(HDPath::LedgerLive(index), 1).await.unwrap()).collect(); - // - // Refers to: https://docs.rs/ethers/latest/ethers/signers/index.html - let private_keys = &[ - "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", - "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a", - ]; - - let signing_wallets: Vec = private_keys - .iter() - .map(|private_key| LocalWallet::from_str(private_key).unwrap()) - .collect(); - - let encoded_message = encode(&[Token::String(message.to_owned())]); - let hashed_message = keccak256(encoded_message); - - let mut signatures: Vec = Vec::new(); - - // Iterating signing wallets and generate signature to put `signatures` vector - for wallet in signing_wallets { - let signature = wallet.sign_message(hashed_message).await.unwrap(); - let record = SignatureRecord::new( - "ETH".to_string(), - to_checksum(&wallet.address(), None), // - format!("0x{}", signature.to_string()), - message.to_string(), - ); - signatures.push(record); - } - - Ok(signatures) -} - -// To avoid build error -fn main() {} diff --git a/backend/examples/verify_inclusion_on_contract.rs b/backend/examples/verify_inclusion_on_contract.rs index 1658cced..08799377 100644 --- a/backend/examples/verify_inclusion_on_contract.rs +++ b/backend/examples/verify_inclusion_on_contract.rs @@ -6,14 +6,14 @@ use halo2_proofs::halo2curves::bn256::Fr as Fp; use serde_json::from_reader; use summa_backend::{apis::round::Round, tests::initialize_test_env}; +use summa_solvency::merkle_sum_tree::utils::generate_leaf_hash; mod helpers; -use helpers::inclusion_proof::{generate_leaf_hash, InclusionProof}; +use helpers::inclusion_proof::InclusionProof; #[tokio::main] async fn main() -> Result<(), Box> { // Let assume the user can get instance of `summa_contract` and the CEX already submit solvency at timestamp `1`. - let (anvil, _, _, _, summa_contract, mut address_ownership_client) = - initialize_test_env().await; + let (anvil, _, _, _, summa_contract) = initialize_test_env().await; address_ownership_client .dispatch_proof_of_address_ownership() diff --git a/backend/examples/verify_inclusion_on_local.rs b/backend/examples/verify_inclusion_on_local.rs deleted file mode 100644 index d0313b9d..00000000 --- a/backend/examples/verify_inclusion_on_local.rs +++ /dev/null @@ -1,61 +0,0 @@ -#![feature(generic_const_exprs)] -use std::{error::Error, fs::File, io::BufReader}; - -use halo2_proofs::halo2curves::bn256::Fr as Fp; -use serde_json::from_reader; - -use summa_solvency::circuits::{ - merkle_sum_tree::MstInclusionCircuit, - utils::{full_evm_verifier, generate_setup_artifacts}, -}; -mod helpers; -use helpers::inclusion_proof::{generate_leaf_hash, InclusionProof}; - -fn main() -> Result<(), Box> { - // This contants should be matched with the constants used while generating the proof. - const LEVELS: usize = 4; - const N_ASSETS: usize = 2; - const N_BYTES: usize = 14; - const USER_INDEX: usize = 0; - - // When verifying the inclusion proof on local, you have to load two files: `ptau` and `proof`. - // the `ptau` file should be exactly same with the one used while generating the proof. - let ptau_path = "./ptau/hermez-raw-11"; - let proof_path = "user_0_proof.json"; - - let file = File::open(proof_path)?; - let reader = BufReader::new(file); - let proof_data: InclusionProof = from_reader(reader)?; - let proof: Vec = serde_json::from_str(&proof_data.proof).unwrap(); - - // These `user_name` and `balances` be assumed that are given from the CEX. - let user_name = "dxGaEAii".to_string(); - let balances_usize = vec![11888, 41163]; - - let leaf_hash: Fp = serde_json::from_str(&proof_data.leaf_hash).unwrap(); - assert_eq!( - leaf_hash, - generate_leaf_hash::(user_name.clone(), balances_usize.clone()) - ); - - // Similar to verifying the `leaf_hash` above, the user should check if the `root_hash` matches with the `mst_root` on the Summa contract. - // However, this example does not cover this part, as it's assumed that the user is running the verifier locally. - // For the process of checking the `root_hash`, refer to the `verify_inclusion_on_contract` example. - let root_hash: Fp = serde_json::from_str(&proof_data.root_hash).unwrap(); - - // Circuit and params are same while generating the proof. - let mst_inclusion_circuit = MstInclusionCircuit::::init_empty(); - let (params, _, vk) = - generate_setup_artifacts(11, Some(ptau_path), mst_inclusion_circuit).unwrap(); - - // This result will be same on the contract method `verifyInclusionProof` on the Summa contract. - let verification_result: bool = - full_evm_verifier(¶ms, &vk, proof, vec![vec![leaf_hash, root_hash]]); - - println!( - "Verifying the proof result for User #{}: {}", - USER_INDEX, verification_result - ); - - Ok(()) -} diff --git a/backend/src/tests.rs b/backend/src/tests.rs index 0e7cbf76..04aab334 100644 --- a/backend/src/tests.rs +++ b/backend/src/tests.rs @@ -23,7 +23,6 @@ pub async fn initialize_test_env() -> ( H160, Arc, LocalWallet>>, Summa, LocalWallet>>, - AddressOwnership, ) { let anvil: ethers::utils::AnvilInstance = Anvil::new() .mnemonic("test test test test test test test test test test test junk") @@ -99,36 +98,18 @@ pub async fn initialize_test_env() -> ( .await .unwrap(); - let address_ownership_client = AddressOwnership::new( - "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", - anvil.chain_id(), - anvil.endpoint().as_str(), - summa_contract.address(), - "src/apis/csv/signatures.csv", - ) - .unwrap(); - - ( - anvil, - cex_addr_1, - cex_addr_2, - client, - summa_contract, - address_ownership_client, - ) + (anvil, cex_addr_1, cex_addr_2, client, summa_contract) } #[cfg(test)] mod test { - use std::sync::Arc; - use ethers::{ abi::AbiEncode, types::{Bytes, U256}, utils::to_checksum, }; - use crate::apis::{address_ownership::AddressOwnership, round::Round}; + use crate::apis::round::Round; use crate::contracts::generated::summa_contract::{ AddressOwnershipProof, AddressOwnershipProofSubmittedFilter, Asset, SolvencyProofSubmittedFilter, @@ -251,11 +232,13 @@ mod test { .collect(); // Verify inclusion proof with onchain function - summa_contract + let verified = summa_contract .verify_inclusion_proof(proof, public_inputs, U256::from(1)) .await .unwrap(); + assert_eq!(verified, true); + drop(anvil); } } diff --git a/zk_prover/src/circuits/utils.rs b/zk_prover/src/circuits/utils.rs index 80671056..617cf84d 100644 --- a/zk_prover/src/circuits/utils.rs +++ b/zk_prover/src/circuits/utils.rs @@ -153,22 +153,6 @@ pub fn full_evm_verifier( &mut transcript, ) .is_ok() - - // let accept = { - // let mut transcript = TranscriptReadBuffer::<_, G1Affine, _>::init(proof.as_slice()); - // VerificationStrategy::<_, VerifierSHPLONK<'_, Bn256>>::finalize( - // verify_proof::<_, VerifierSHPLONK<'_, Bn256>, _, EvmTranscript<_, _, _, _>, _>( - // params, - // vk, - // AccumulatorStrategy::new(params.verifier_params()), - // instances, - // &mut transcript, - // ) - // .unwrap(), - // ) - // }; - - // accept } /// Generate a solidity verifier contract starting from its yul code. diff --git a/zk_prover/src/merkle_sum_tree/utils/mod.rs b/zk_prover/src/merkle_sum_tree/utils/mod.rs index 53d4db35..6c77a9b9 100644 --- a/zk_prover/src/merkle_sum_tree/utils/mod.rs +++ b/zk_prover/src/merkle_sum_tree/utils/mod.rs @@ -2,6 +2,7 @@ mod build_tree; mod create_middle_node; mod create_proof; mod csv_parser; +mod generate_leaf_hash; mod hash; mod index_of; mod operation_helpers; @@ -10,6 +11,7 @@ mod proof_verification; pub use build_tree::build_merkle_tree_from_entries; pub use create_proof::create_proof; pub use csv_parser::parse_csv_to_entries; +pub use generate_leaf_hash::generate_leaf_hash; pub use hash::{poseidon_entry, poseidon_node}; pub use index_of::index_of; pub use operation_helpers::*; From 5c9bf49d01ceb21e40d72b56fd4403ec99059f4c Mon Sep 17 00:00:00 2001 From: sifnoc Date: Mon, 25 Sep 2023 11:52:57 +0000 Subject: [PATCH 09/12] fix: combine examples as one; update docs --- backend/README.md | 91 ++++----- backend/examples/generate_inclusion.rs | 55 ----- backend/examples/helpers/inclusion_proof.rs | 8 - backend/examples/helpers/mod.rs | 1 - backend/examples/submit_ownership.rs | 67 ------ backend/examples/submit_solvency.rs | 46 ----- backend/examples/summa_solvency_flow.rs | 190 ++++++++++++++++++ .../examples/verify_inclusion_on_contract.rs | 99 --------- backend/src/apis/round.rs | 3 +- backend/src/tests.rs | 15 +- zk_prover/src/circuits/utils.rs | 25 +-- .../utils/generate_leaf_hash.rs | 17 ++ 12 files changed, 257 insertions(+), 360 deletions(-) delete mode 100644 backend/examples/generate_inclusion.rs delete mode 100644 backend/examples/helpers/inclusion_proof.rs delete mode 100644 backend/examples/helpers/mod.rs delete mode 100644 backend/examples/submit_ownership.rs delete mode 100644 backend/examples/submit_solvency.rs create mode 100644 backend/examples/summa_solvency_flow.rs delete mode 100644 backend/examples/verify_inclusion_on_contract.rs create mode 100644 zk_prover/src/merkle_sum_tree/utils/generate_leaf_hash.rs diff --git a/backend/README.md b/backend/README.md index ae48b96f..3a3eec46 100644 --- a/backend/README.md +++ b/backend/README.md @@ -63,36 +63,37 @@ The following steps are optional and are only required if you need to update the By completing these steps, the backend will be primed with the essential verifiers for its tasks. -## Example +## Summa solvency flow example -The sequence in which the examples are introduced closely relates to the steps of the Summa protocol. -These examples will help to understand how the Summa works with the Summa contract and the user side. +This example illustrates how Summa interacts with the Summa contract and the user side. + +To execute this example, use the command: + +``` +cargo run --release --example summa_solvency_flow +``` ### 1. Submitting Address Ownership to the Summa Contract -This example demonstrates the process of submitting proof of address ownership to the Summa contract. After generating signatures for asset ownership (as shown in the `generate_signature` example), this step is essential to register those proofs on-chain, facilitating the validation of asset ownership within Summa. +First, we submit proof of address ownership to the Summa contract. This is a critical step to register these proofs on-chain, facilitating the validation of asset ownership within Summa. -In this example, a test environment is set up with the anvil instance by invoking the `initialize_test_env` method. This environment is also utilized in other examples such as `submit_solvency` and `verify_inclusion_on_contracts`. +Key points: -Key points to note: +- An instance of `AddressOwnership`, named `address_ownership_client`, is initialized with the `signatures.csv` file, which contains the signature data. -The instance of `AddressOwnership` is initialized with `signatures.csv`, and is named `address_ownership_client`. This instance has already loaded the signature data. +- The `dispatch_proof_of_address_ownership` function sends a transaction to the Summa contract to register CEX-owned addresses. -The `dispatch_proof_of_address_ownership` function sends a transaction to the Summa contract, registering the addresses owned by the CEX on the contract. +- After dispatching the transaction, the example computes the hashed addresses (address_hashes) to verify they've been correctly registered in the Summa contract -After dispatching the transaction via `dispatch_proof_of_address_ownerhip`, the example computes the hashed addresses (address_hashes) to verify they have been correctly registered on the Summa contract. -To execute this example: -``` -cargo run --release --example submit_ownership -``` +Note: This demonstration takes place in a test environment. In real-world production, always ensure that the Summa contract is correctly deployed on the target chain. + +If executed successfully, you'll see: -Upon successful execution, you should see the message: ``` -Ownership proofs are submitted successfully! +1. Ownership proofs are submitted successfully! ``` -Reminder: This demonstration takes place in a test environment. In real-world production, always ensure that the Summa contract is correctly deployed on the target chain. ### 2. Submit Proof of Solvency @@ -103,67 +104,45 @@ Without this process, It seems the user may not trust to the inclusion proof for In this example, we'll guide you through the process of submitting a solvency proof using the Round to the Summa contract. The Round serves as the core of the backend in Summa, and we have briefly described it in the Components section. -To initialize the Round, several parameters are required, including paths to specific CSV files (`assets.csv` and `entry_16.csv`), as well as a path to the ptau file (`ptau/hermez-raw-11`). - -The roles of these files are as follows: -- `assets.csv`: This file is essential for calculating the total balance of assets for the solvency proof. Currently, only the CEX can generate this asset CSV file in its specific manner. +To initialize the `Round` instance, you'll need paths to specific CSV files (`assets.csv` and `entry_16.csv`) and the `ptau/hermez-raw-11` file. Here's what each file does: -- `entry_16.csv`: This file is used to build the Merkle sum tree, where each leaf element originates from sixteen entries in the CSV. -X -- `ptau/hermez-raw-11`: Contains the Powers of Tau trusted setup parameters, essential for constructing the zk circuits. +- `assets.csv`: Calculates the total balance of assets for the solvency proof. Only the CEX can generate this file. +- `entry_16.csv`: Used to build the Merkle sum tree, with each leaf element derived from sixteen entries in the CSV. +- `ptau/hermez-raw-11`: Contains parameters for constructing the zk circuits. -An instance of Round dispatches the solvency proof using the `dispatch_solvency_proof` method. - -To execute this example: -``` -cargo run --release --example submit_solvency -``` +Using the `Round` instance, the solvency proof is dispatched to the Summa contract with the `dispatch_solvency_proof` method. -Upon successful execution, you will see the message: +If this step successfully ran, you can see this message: ``` - "Solvency proof is submitted successfully!" +2. Solvency proof is submitted successfully! ``` ### 3. Generating and Exporting Inclusion Proofs -Assuming you are a CEX, let's say you've already committed the `solvency` and `ownership` proofs to the Summa contract. Now, you need to generate inclusion proofs for every user. - -In this example, we demonstrate how to generate and export user-specific inclusion proofs using the Round. This proof is crucial for users as it helps them validate the presence of specific elements within the Merkle sum tree, which forms a part of the solvency proof submitted. - -After generating the inclusion proof, end of example parts the inclusion proof is transformed into a JSON format, making it easily shareable. +Assuming you're a CEX, after committing the `solvency` and `ownership` proofs to the Summa contract, you should generate inclusion proofs for every user. This proof verifies the presence of specific elements in the Merkle sum tree, which is part of the solvency proof. -To execute this example: -``` -cargo run --release --example generate_inclusion -``` +After generating the inclusion proof, it's transformed into a JSON format for easy sharing. -Upon successful execution, you can see this message and exported file `user_0_proof.json`. +Upon successful execution, you'll find a file named `user_0_proof.json` and see the following message: ``` - "Exported proof to user #0, as `user_0_proof.json`" +3. Exported proof to user #0, as `user_0_proof.json` ``` ### 4. Verify Proof of Inclusion This is the final step in the Summa process and the only part that occurs on the user side. -The user will receive the proof for a specific Round. the user can use method on the Summa contract that already deployed on blockchain. It's important to note that the verifier function on the Summa contract is a view function, which means it doesn't require any gas or involve writing state on-chain. +Users receive the proof for a specific round and use methods available on the deployed Summa contract. Importantly, the Summa contract verifier function is a view function, meaning it doesn't consume gas or change the blockchain's state. -In the `verify_inclusion_on_contract` example, the procedure for verifying the inclusion proof using an on-chain method is illustrated. By leveraging the data from the Summa contract, users can effortlessly ascertain that the provided proof aligns with the data submitted by the CEX. +In this step, you'll see: -To elaborate: - -Retrieving the MST Root: The user fetches the `mst_root` from the Summa contract. This root should match the `root_hash` provided in the proof. This verification process is akin to ensuring that the `leaf_hash` corresponds with the anticipated hash based on the `username` and `balances` provided by the CEX. - -On-chain Function Verification: The user then invokes the `verify_inclusion_proof` method on the Summa contract. Since this is a view function, it returns a boolean value without incurring any gas fees, indicating the success or failure of the verification. - -To run the verify inclusion on contract example: -``` -cargo run --release --example verify_inclusion_on_contract -``` +- Retrieve the `mst_root` from the Summa contract and match it with the `root_hash` in the proof. +- Ensure the `leaf_hash` aligns with the hash based on the `username` and `balances` provided by the CEX. +- Use the `verify_inclusion_proof` method on the Summa contract to validate the proof. -You will see the result like: +The result will display as: ``` -Verifying the proof on contract verifier for User #0: true +4. Verifying the proof on contract verifier for User #0: true ``` diff --git a/backend/examples/generate_inclusion.rs b/backend/examples/generate_inclusion.rs deleted file mode 100644 index 0d1a4fc6..00000000 --- a/backend/examples/generate_inclusion.rs +++ /dev/null @@ -1,55 +0,0 @@ -#![feature(generic_const_exprs)] -use serde_json::{json, to_writer}; -use std::fs; -use summa_backend::{apis::round::Round, tests::initialize_test_env}; - -const USER_INDEX: usize = 0; - -#[tokio::main] -async fn main() { - // Initialize test environment - let (anvil, _, _, _, summa_contract, mut address_ownership_client) = - initialize_test_env().await; - - address_ownership_client - .dispatch_proof_of_address_ownership() - .await - .unwrap(); - - // Initialize `Round` for submitting proof of solvency. - let asset_csv = "src/apis/csv/assets.csv"; - let entry_csv = "../zk_prover/src/merkle_sum_tree/csv/entry_16.csv"; - let params_path = "ptau/hermez-raw-11"; - - let round = Round::<4, 2, 14>::new( - "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", // anvil account [0] - anvil.chain_id(), - anvil.endpoint().as_str(), - summa_contract.address(), - entry_csv, - asset_csv, - params_path, - 1, - ) - .unwrap(); - - // In a production environment, the CEX should dispatch the solvency proof to update the root of the Merkle sum tree prior to generating inclusion proofs. - // Otherwise, users might distrust the provided `root_hash` in the inclusion proof, as it hasn't been published on-chain. - let inclusion_proof = round.get_proof_of_inclusion(USER_INDEX).unwrap(); - let public_input_vec = inclusion_proof.get_public_inputs(); - - // The structure of this output file may vary in production. - // For instance, the CEX might substitute `leaf_hash` with attributes like `username` and `balances`. - // Consequently, users would generate the `leaf_hash` on client-side before validating the proof. - let output = json!({ - "proof": serde_json::to_string(&inclusion_proof.get_proof()).unwrap(), - "leaf_hash": serde_json::to_string(&public_input_vec[0][0]).unwrap(), - "root_hash": serde_json::to_string(&public_input_vec[0][1]).unwrap() - }); - - let filename = format!("user_{}_proof.json", USER_INDEX); - let file = fs::File::create(filename.clone()).expect("Unable to create file"); - to_writer(file, &output).expect("Failed to write JSON to file"); - - println!("Exported proof to user #{}, as `{}`", USER_INDEX, filename); -} diff --git a/backend/examples/helpers/inclusion_proof.rs b/backend/examples/helpers/inclusion_proof.rs deleted file mode 100644 index 083ebedd..00000000 --- a/backend/examples/helpers/inclusion_proof.rs +++ /dev/null @@ -1,8 +0,0 @@ -use serde::Deserialize; - -#[derive(Debug, Deserialize)] -pub struct InclusionProof { - pub leaf_hash: String, - pub root_hash: String, - pub proof: String, -} diff --git a/backend/examples/helpers/mod.rs b/backend/examples/helpers/mod.rs deleted file mode 100644 index 55868e1e..00000000 --- a/backend/examples/helpers/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod inclusion_proof; diff --git a/backend/examples/submit_ownership.rs b/backend/examples/submit_ownership.rs deleted file mode 100644 index b1686604..00000000 --- a/backend/examples/submit_ownership.rs +++ /dev/null @@ -1,67 +0,0 @@ -use ethers::{ - abi::{encode, Token}, - types::U256, - utils::keccak256, -}; - -use summa_backend::{apis::address_ownership::AddressOwnership, tests::initialize_test_env}; - -// In this example, we will demonstrate how to submit ownership of address to the Summa contract. -#[tokio::main] -async fn main() { - // We have already demonstrated how to generate a CSV file containing the asset ownership proofs, `AddressOwnershipProof`. - // For more details on this, kindly refer to the "generate_signature" example. - - // Initialize test environment without `address_ownership` instance from `initialize_test_env` function. - let (anvil, _, _, _, summa_contract, _) = initialize_test_env().await; - - // For the current demonstration, we'll use the same CSV file produced in `generate_signature` example. - let signature_csv_path = "src/apis/csv/signatures.csv"; - let mut address_ownership_client = AddressOwnership::new( - "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", - anvil.chain_id(), - anvil.endpoint().as_str(), - summa_contract.address(), - signature_csv_path, - ) - .unwrap(); - - // Get hashed addresses using the `keccak256` method. - let address_hashes = address_ownership_client - .get_ownership_proofs() - .iter() - .map(|x| keccak256(encode(&[Token::String(x.cex_address.clone())]))) - .collect::>(); - - // Dispatches the proof of address ownership. - // In the client, the `dispatch_proof_of_address_ownership` function sends a transaction to the Summa contract - address_ownership_client - .dispatch_proof_of_address_ownership() - .await - .unwrap(); - - // If the `addressHash` isn't found in the `addressOwnershipProofs` mapping of the Summa contract, - // it will return 0; otherwise, it will return a non-zero value. - // - // You can find unregistered address with null bytes as follows: - // - // let unregistered = summa_contract - // .ownership_proof_by_address([0u8; 32]) - // .call() - // .await - // .unwrap(); - // - // assert_eq!(unregistered, 0); - - // Check if the addresses are registered on the Summa contract. - for address_hash in address_hashes.iter() { - let registered = summa_contract - .ownership_proof_by_address(*address_hash) - .call() - .await - .unwrap(); - - assert_ne!(registered, U256::from(0)); - } - println!("Ownership proofs are submitted successfully!") -} diff --git a/backend/examples/submit_solvency.rs b/backend/examples/submit_solvency.rs deleted file mode 100644 index 512463f8..00000000 --- a/backend/examples/submit_solvency.rs +++ /dev/null @@ -1,46 +0,0 @@ -#![feature(generic_const_exprs)] -use summa_backend::{apis::round::Round, tests::initialize_test_env}; - -#[tokio::main] -async fn main() { - // Initialize test environment. - let (anvil, _, _, _, summa_contract, mut address_ownership_client) = - initialize_test_env().await; - - // Before submitting the solvency proof, it's crucial to submit the proof of address ownership. - // Ensure at least one address is registered in the `addressOwnershipProofs` mapping of the Summa contract. - address_ownership_client - .dispatch_proof_of_address_ownership() - .await - .unwrap(); - - // Initialize `Round` for submitting proof of solvency. - let asset_csv = "src/apis/csv/assets.csv"; - let entry_csv = "../zk_prover/src/merkle_sum_tree/csv/entry_16.csv"; - let params_path = "ptau/hermez-raw-11"; - - // Using the `round` instance, the solvency proof is dispatched to the Summa contract with the `dispatch_solvency_proof` method. - let mut round = Round::<4, 2, 14>::new( - "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", // anvil account [0] - anvil.chain_id(), - anvil.endpoint().as_str(), - summa_contract.address(), - entry_csv, - asset_csv, - params_path, - 1, - ) - .unwrap(); - - // Sends the solvency proof, which should ideally complete without errors. - assert_eq!(round.dispatch_solvency_proof().await.unwrap(), ()); - - // You can also use the `solvency_proof_submitted_filter` method to check if the solvency proof is submitted. - // println!("{:?}", summa_contract - // .solvency_proof_submitted_filter() - // .query() - // .await - // .unwrap();); - - println!("Solvency proof is submitted successfully!") -} diff --git a/backend/examples/summa_solvency_flow.rs b/backend/examples/summa_solvency_flow.rs new file mode 100644 index 00000000..76a0861d --- /dev/null +++ b/backend/examples/summa_solvency_flow.rs @@ -0,0 +1,190 @@ +#![feature(generic_const_exprs)] +use std::{error::Error, fs::File, io::BufReader, io::Write}; + +use ethers::{ + abi::{encode, Token}, + types::{Bytes, U256}, + utils::keccak256, +}; +use serde_json::{from_reader, to_string_pretty}; + +use summa_backend::{ + apis::{ + address_ownership::AddressOwnership, + round::{MstInclusionProof, Round}, + }, + tests::initialize_test_env, +}; +use summa_solvency::merkle_sum_tree::utils::generate_leaf_hash; + +const N_ASSETS: usize = 2; +const USER_INDEX: usize = 0; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Initialize test environment without `address_ownership` instance from `initialize_test_env` function. + let (anvil, _, _, _, summa_contract) = initialize_test_env().await; + + // 1. Submit ownership proof + // + // Each CEX prepares its own `signature` CSV file. + let signature_csv_path = "src/apis/csv/signatures.csv"; + let mut address_ownership_client = AddressOwnership::new( + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + anvil.chain_id(), + anvil.endpoint().as_str(), + summa_contract.address(), + signature_csv_path, + ) + .unwrap(); + + // Retrieve hashed addresses using the `keccak256` method. + let address_hashes = address_ownership_client + .get_ownership_proofs() + .iter() + .map(|x| keccak256(encode(&[Token::String(x.cex_address.clone())]))) + .collect::>(); + + // Dispatch the proof of address ownership. + // the `dispatch_proof_of_address_ownership` function sends a transaction to the Summa contract. + address_ownership_client + .dispatch_proof_of_address_ownership() + .await + .unwrap(); + + // If the `addressHash` isn't found in the `addressOwnershipProofs` mapping of the Summa contract, + // it will return 0; otherwise, it will return a non-zero value. + // + // You can find unregistered address with null bytes as follows: + // + // let unregistered = summa_contract + // .ownership_proof_by_address([0u8; 32]) + // .call() + // .await + // .unwrap(); + // + // assert_eq!(unregistered, 0); + + // Verify whether the addresses have been registered within the Summa contract. + for address_hash in address_hashes.iter() { + let registered = summa_contract + .ownership_proof_by_address(*address_hash) + .call() + .await + .unwrap(); + + assert_ne!(registered, U256::from(0)); + } + println!("1. Ownership proofs are submitted successfully!"); + + // 2. Submit solvency proof + // + // Initialize the `Round` instance to submit the proof of solvency. + let asset_csv = "src/apis/csv/assets.csv"; + let entry_csv = "../zk_prover/src/merkle_sum_tree/csv/entry_16.csv"; + let params_path = "ptau/hermez-raw-11"; + + // Using the `round` instance, the solvency proof is dispatched to the Summa contract with the `dispatch_solvency_proof` method. + let mut round = Round::<4, 2, 14>::new( + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", // anvil account [0] + anvil.chain_id(), + anvil.endpoint().as_str(), + summa_contract.address(), + entry_csv, + asset_csv, + params_path, + 1, + ) + .unwrap(); + + // Sends the solvency proof, which should ideally complete without errors. + assert_eq!(round.dispatch_solvency_proof().await.unwrap(), ()); + + // You can also use the `solvency_proof_submitted_filter` method to check if the solvency proof is submitted. + // println!("{:?}", summa_contract + // .solvency_proof_submitted_filter() + // .query() + // .await + // .unwrap();); + + println!("2. Solvency proof is submitted successfully!"); + + // 3. Generate Inclusion Proof + // + // In a production setup, the CEX should first dispatch the solvency proof to update the Merkle sum tree's root before generating any inclusion proofs. + // Otherwise, users might distrust the provided `root_hash` in the inclusion proof, as it hasn't been published on-chain. + let inclusion_proof = round.get_proof_of_inclusion(USER_INDEX).unwrap(); + + let filename = format!("user_{}_proof.json", USER_INDEX); + let mut file = File::create(filename.clone()).expect("Unable to create file"); + let output = to_string_pretty(&inclusion_proof).unwrap(); + file.write_all(output.as_bytes()) + .expect("Failed to write JSON to file"); + + println!( + "3. Exported proof to user #{}, as `{}`", + USER_INDEX, filename + ); + + // 4. Verify Inclusion Proof + // + // The `snapshot_time` denotes the specific moment when entries were created for the Merkle sum tree. + // This timestamp is established during the initialization of the Round instance. + let snapshot_time = U256::from(1); + + // When verifying the inclusion proof from the user's perspective, the user have to fetch `proof`. + // Assume that the `proof` file has been downloaded from the CEX. + let proof_file = File::open(format!("user_{}_proof.json", USER_INDEX))?; + let reader = BufReader::new(proof_file); + let downloaded_inclusion_proof: MstInclusionProof = from_reader(reader)?; + + let public_inputs = downloaded_inclusion_proof.get_public_inputs(); + + // Verify the `leaf_hash` from the proof file. + // It's assumed that both `user_name` and `balances` are provided by the CEX. + let user_name = "dxGaEAii".to_string(); + let balances = vec![11888, 41163]; + + let leaf_hash = public_inputs[0][0]; + assert_eq!( + leaf_hash, + generate_leaf_hash::(user_name.clone(), balances.clone()) + ); + + // Before verifying `root_hath`, convert type of `proof` and `public_inputs` to the type of `Bytes` and `Vec`. + let proof: Bytes = Bytes::from(inclusion_proof.get_proof().clone()); + let public_inputs: Vec = inclusion_proof + .get_public_inputs() + .iter() + .flat_map(|input_set| { + input_set.iter().map(|input| { + let mut bytes = input.to_bytes(); + bytes.reverse(); + U256::from_big_endian(&bytes) + }) + }) + .collect(); + + // Get `mst_root` from contract. the `mst_root` is disptached by CEX with specific time `snapshot_time`. + let mst_root = summa_contract + .mst_roots(snapshot_time) + .call() + .await + .unwrap(); + + // Match the `mst_root` with the `root_hash` derived from the proof. + assert_eq!(mst_root, public_inputs[1]); + + // Validate the inclusion proof using the contract verifier. + let verification_result = summa_contract + .verify_inclusion_proof(proof, public_inputs, snapshot_time) + .await + .unwrap(); + + println!( + "4. Verifying the proof on contract veirifer for User #{}: {}", + USER_INDEX, verification_result + ); + + Ok(()) +} diff --git a/backend/examples/verify_inclusion_on_contract.rs b/backend/examples/verify_inclusion_on_contract.rs deleted file mode 100644 index 08799377..00000000 --- a/backend/examples/verify_inclusion_on_contract.rs +++ /dev/null @@ -1,99 +0,0 @@ -#![feature(generic_const_exprs)] -use std::{error::Error, fs::File, io::BufReader}; - -use ethers::types::{Bytes, U256}; -use halo2_proofs::halo2curves::bn256::Fr as Fp; -use serde_json::from_reader; - -use summa_backend::{apis::round::Round, tests::initialize_test_env}; -use summa_solvency::merkle_sum_tree::utils::generate_leaf_hash; -mod helpers; -use helpers::inclusion_proof::InclusionProof; - -#[tokio::main] -async fn main() -> Result<(), Box> { - // Let assume the user can get instance of `summa_contract` and the CEX already submit solvency at timestamp `1`. - let (anvil, _, _, _, summa_contract) = initialize_test_env().await; - - address_ownership_client - .dispatch_proof_of_address_ownership() - .await - .unwrap(); - - // Initialize `Round` for submitting proof of solvency. - let asset_csv = "src/apis/csv/assets.csv"; - let entry_csv = "../zk_prover/src/merkle_sum_tree/csv/entry_16.csv"; - let params_path = "ptau/hermez-raw-11"; - - let mut round = Round::<4, 2, 14>::new( - "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", // anvil account [0] - anvil.chain_id(), - anvil.endpoint().as_str(), - summa_contract.address(), - entry_csv, - asset_csv, - params_path, - 1, - ) - .unwrap(); - - assert_eq!(round.dispatch_solvency_proof().await.unwrap(), ()); - - // The user will know these constants before or when they receive the proof. - const N_ASSETS: usize = 2; - const USER_INDEX: usize = 0; - - let snapshot_time = U256::from(1); // specific time to dispatch solvency proof - - // When verifying the inclusion proof on the user side,-raw-11"; - let proof_path = format!("user_{}_proof.json", USER_INDEX); - - let file = File::open(proof_path)?; - let reader = BufReader::new(file); - let proof_data: InclusionProof = from_reader(reader)?; - let proof: Vec = serde_json::from_str(&proof_data.proof).unwrap(); - - // These `user_name` and `balances` be assumed that are given from the CEX. - let user_name = "dxGaEAii".to_string(); - let balances = vec![11888, 41163]; - - let leaf_hash: Fp = serde_json::from_str(&proof_data.leaf_hash).unwrap(); - assert_eq!( - leaf_hash, - generate_leaf_hash::(user_name.clone(), balances.clone()) - ); - - // Make public_input from `leaf_hash` and `root_hash` iter and convert it to `Fr` - let root_hash: Fp = serde_json::from_str(&proof_data.root_hash).unwrap(); - let public_inputs: Vec = vec![leaf_hash, root_hash] - .iter() - .map(|x| { - let mut bytes = x.to_bytes(); - bytes.reverse(); - U256::from_big_endian(&bytes) - }) - .collect(); - - // Get `mst_root` from contract. the `mst_root` is disptached by CEX with specific time `snapshot_time`. - let mst_root = summa_contract - .mst_roots(snapshot_time) - .call() - .await - .unwrap(); - - // Compare `mst_root` with `root_hash` from proof. - assert_eq!(mst_root, public_inputs[1]); - - // Verify inclusion proof on contract verifier - let verification_result = summa_contract - .verify_inclusion_proof(Bytes::from(proof), public_inputs, snapshot_time) - .await - .unwrap(); - - println!( - "Verifying the proof on contract veirifer for User #{}: {}", - USER_INDEX, verification_result - ); - - Ok(()) -} diff --git a/backend/src/apis/round.rs b/backend/src/apis/round.rs index 2c8a3c3f..a344c84a 100644 --- a/backend/src/apis/round.rs +++ b/backend/src/apis/round.rs @@ -7,6 +7,7 @@ use halo2_proofs::{ plonk::{ProvingKey, VerifyingKey}, poly::kzg::commitment::ParamsKZG, }; +use serde::{Deserialize, Serialize}; use snark_verifier_sdk::{evm::gen_evm_proof_shplonk, CircuitExt}; use std::error::Error; @@ -43,7 +44,7 @@ impl SolvencyProof { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct MstInclusionProof { public_inputs: Vec>, proof: Vec, diff --git a/backend/src/tests.rs b/backend/src/tests.rs index 04aab334..faf41cee 100644 --- a/backend/src/tests.rs +++ b/backend/src/tests.rs @@ -9,7 +9,6 @@ use ethers::{ }; use tokio::time; -use crate::apis::address_ownership::AddressOwnership; use crate::contracts::generated::{ inclusion_verifier::InclusionVerifier, solvency_verifier::SolvencyVerifier, summa_contract::Summa, @@ -109,7 +108,7 @@ mod test { utils::to_checksum, }; - use crate::apis::round::Round; + use crate::apis::{address_ownership::AddressOwnership, round::Round}; use crate::contracts::generated::summa_contract::{ AddressOwnershipProof, AddressOwnershipProofSubmittedFilter, Asset, SolvencyProofSubmittedFilter, @@ -118,8 +117,16 @@ mod test { #[tokio::test] async fn test_round_features() { - let (anvil, cex_addr_1, cex_addr_2, _, summa_contract, mut address_ownership_client) = - initialize_test_env().await; + let (anvil, cex_addr_1, cex_addr_2, _, summa_contract) = initialize_test_env().await; + + let mut address_ownership_client = AddressOwnership::new( + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + anvil.chain_id(), + anvil.endpoint().as_str(), + summa_contract.address(), + "src/apis/csv/signatures.csv", + ) + .unwrap(); let ownership_submitted_result = address_ownership_client .dispatch_proof_of_address_ownership() diff --git a/zk_prover/src/circuits/utils.rs b/zk_prover/src/circuits/utils.rs index 617cf84d..96bb2bcb 100644 --- a/zk_prover/src/circuits/utils.rs +++ b/zk_prover/src/circuits/utils.rs @@ -17,7 +17,7 @@ use halo2_proofs::{ kzg::{ commitment::{KZGCommitmentScheme, ParamsKZG}, multiopen::{ProverSHPLONK, VerifierSHPLONK}, - strategy::{AccumulatorStrategy, SingleStrategy}, + strategy::SingleStrategy, }, }, transcript::{ @@ -29,7 +29,7 @@ use regex_simple::Regex; use snark_verifier::{ cost::CostEstimation, pcs::kzg::{Bdfg21, KzgAs}, - system::halo2::{compile, transcript::evm::EvmTranscript, Config}, + system::halo2::{compile, Config}, verifier::plonk::PlonkSuccinctVerifier, }; use snark_verifier_sdk::{evm::gen_evm_proof_shplonk, CircuitExt}; @@ -134,27 +134,6 @@ pub fn full_verifier( .is_ok() } -/// Verifies a proof that generated by the `gen_evm_proof_shplonk` function. -pub fn full_evm_verifier( - params: &ParamsKZG, - vk: &VerifyingKey, - proof: Vec, - public_inputs: Vec>, -) -> bool { - let instance: Vec<&[Fp]> = public_inputs.iter().map(|input| &input[..]).collect(); - let instances = &[&instance[..]]; - - let mut transcript = TranscriptReadBuffer::<_, G1Affine, _>::init(proof.as_slice()); - verify_proof::<_, VerifierSHPLONK<'_, Bn256>, _, EvmTranscript<_, _, _, _>, _>( - params, - vk, - AccumulatorStrategy::new(params.verifier_params()), - instances, - &mut transcript, - ) - .is_ok() -} - /// Generate a solidity verifier contract starting from its yul code. /// patterned after https://github.com/zkonduit/ezkl/blob/main/src/eth.rs#L326-L602 fn fix_verifier_sol(yul_code_path: PathBuf) -> Result> { diff --git a/zk_prover/src/merkle_sum_tree/utils/generate_leaf_hash.rs b/zk_prover/src/merkle_sum_tree/utils/generate_leaf_hash.rs new file mode 100644 index 00000000..8effd32a --- /dev/null +++ b/zk_prover/src/merkle_sum_tree/utils/generate_leaf_hash.rs @@ -0,0 +1,17 @@ +use halo2_proofs::halo2curves::bn256::Fr as Fp; +use num_bigint::BigUint; + +use crate::merkle_sum_tree::Entry; + +pub fn generate_leaf_hash(user_name: String, balances: Vec) -> Fp +where + [usize; N_ASSETS + 1]: Sized, +{ + // Convert usize to BigInt for the `Entry` struct + let balances_big_uint: Vec = balances.into_iter().map(BigUint::from).collect(); + + let entry: Entry = + Entry::new(user_name, balances_big_uint.try_into().unwrap()).unwrap(); + + entry.compute_leaf().hash +} From 1971fbf0862e484c9b0c1c914a9fc255fb79aaf8 Mon Sep 17 00:00:00 2001 From: sifnoc Date: Mon, 25 Sep 2023 12:24:29 +0000 Subject: [PATCH 10/12] feat: added test example on github action --- .github/workflows/rust.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index bbb1f6f5..1154b085 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -33,3 +33,10 @@ jobs: run: | cd backend cargo test --release -- --nocapture + cargo run --release --example summa_solvency_flow + + - name: Test example + run: | + cd backend + cargo run --release --example summa_solvency_flow + \ No newline at end of file From dfb0de39f06142f5e09f6a0a6c7a66042a90c256 Mon Sep 17 00:00:00 2001 From: sifnoc Date: Tue, 26 Sep 2023 05:51:20 +0000 Subject: [PATCH 11/12] fix: removed dup on github action --- .github/workflows/rust.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 1154b085..24f54063 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -33,7 +33,6 @@ jobs: run: | cd backend cargo test --release -- --nocapture - cargo run --release --example summa_solvency_flow - name: Test example run: | From e65280b43d1bab6ed80f7f626fa270560c4526c3 Mon Sep 17 00:00:00 2001 From: sifnoc Date: Tue, 26 Sep 2023 10:03:24 +0000 Subject: [PATCH 12/12] fix: updated README and modify dispatch behavior --- backend/README.md | 7 ++++--- backend/src/apis/round.rs | 11 +++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/backend/README.md b/backend/README.md index 3a3eec46..5168e2b4 100644 --- a/backend/README.md +++ b/backend/README.md @@ -97,11 +97,12 @@ If executed successfully, you'll see: ### 2. Submit Proof of Solvency -Before generate inclusion proof for every user of the current round, You should submit proof of solvency to Summa contract. Currently, we made this as mandatory way to commit the root hash of the Merkle Sum Tree. +This step is crucial for two primary reasons: first, to validate the root hash of the Merkle Sum Tree (`mst_root`); and second, to ensure that the assets held by the CEX exceed their liabilities, as confirmed through the proof verification on the Summa contract. +The CEX must submit this proof of solvency to the Summa contract. Currently, it's a mandatory requirement to provide this proof before generating the inclusion proof for each user in the current round. -Without this process, It seems the user may not trust to the inclusion proof for the round. becuase the `mst_root` is not published on contract. More specifically, it means that the `mst_root` is not correctly verified on the Summa contract. +Without this verification, It seems the user may not trust to the inclusion proof for the round. becuase the `mst_root` is not published on contract. More specifically, it means that the `mst_root` is not correctly verified on the Summa contract. -In this example, we'll guide you through the process of submitting a solvency proof using the Round to the Summa contract. +In this step, we'll guide you through the process of submitting a solvency proof using the Round to the Summa contract. The Round serves as the core of the backend in Summa, and we have briefly described it in the Components section. To initialize the `Round` instance, you'll need paths to specific CSV files (`assets.csv` and `entry_16.csv`) and the `ptau/hermez-raw-11` file. Here's what each file does: diff --git a/backend/src/apis/round.rs b/backend/src/apis/round.rs index a344c84a..c1f47b45 100644 --- a/backend/src/apis/round.rs +++ b/backend/src/apis/round.rs @@ -104,23 +104,22 @@ where self.timestamp } - pub async fn dispatch_solvency_proof(&mut self) -> Result<(), &'static str> { + pub async fn dispatch_solvency_proof(&mut self) -> Result<(), Box> { let proof: SolvencyProof = match self.snapshot.generate_proof_of_solvency() { Ok(p) => p, - Err(_) => return Err("Failed to generate proof of solvency"), + Err(e) => return Err(format!("Failed to generate proof of solvency: {}", e).into()), }; - let result = self - .signer + self.signer .submit_proof_of_solvency( proof.public_inputs[0], self.snapshot.assets_state.to_vec(), proof.proof_calldata, U256::from(self.get_timestamp()), ) - .await; + .await?; - Ok(result.unwrap()) + Ok(()) } pub fn get_proof_of_inclusion(