diff --git a/custom-pallets/zkp-anonymous-account/docs.md b/custom-pallets/zkp-anonymous-account/docs.md new file mode 100644 index 0000000..bd60f16 --- /dev/null +++ b/custom-pallets/zkp-anonymous-account/docs.md @@ -0,0 +1,28 @@ +# anonymous-account-crates + + +**Zero Knowledge Proof for Anonymous Voting** + +In a democracy, voting must remain private to ensure the integrity of the process. However, maintaining privacy in blockchain systems is challenging because transactions are recorded on a public ledger, making it difficult to keep accounts anonymous. + +**How can zero-knowledge proofs enable anonymous voting?** + +**Anonymous Voting Account Creation** + +1. Start with 100-200 accounts that have undergone KYC (Know Your Customer) verification. +2. Generate a cryptographic hash for these accounts and store it on the blockchain. +3. Create a signature from your crypto account using a password. Keep the password secret and publish the signature on the blockchain. Publishing the signature does not allow the user to change the password. + +**In Risczero:** + +1. Take the hash of the accounts and signatures as input. +2. Retrieve the full list of accounts and signatures. +3. Use the hash and cryptographically sign only one account, which will be used to create an anonymous voting account. +4. Push the following to the journal: + - The newly created anonymous account. + - The input hash. + - A total hash counter (e.g., a hash of 0000010000000 combined with the password). + +To ensure each anonymous account is unique, the hash counter must also be unique, allowing only one anonymous account per verified identity. + +Finally, add the zero-knowledge proof to the blockchain, ensuring that the anonymous voting process is secure and private without revealing the identity of the voter. diff --git a/custom-pallets/zkp-anonymous-account/host/Cargo.toml b/custom-pallets/zkp-anonymous-account/host/Cargo.toml index 981b82d..c098551 100644 --- a/custom-pallets/zkp-anonymous-account/host/Cargo.toml +++ b/custom-pallets/zkp-anonymous-account/host/Cargo.toml @@ -13,3 +13,4 @@ subxt-signer = "0.37.0" sp-io = "38.0.0" sp-core = "34.0.0" subxt-core = "0.37.0" +serde-big-array = "0.5" diff --git a/custom-pallets/zkp-anonymous-account/host/src/accounts_hash.rs b/custom-pallets/zkp-anonymous-account/host/src/accounts_hash.rs index 127e627..6f3560b 100644 --- a/custom-pallets/zkp-anonymous-account/host/src/accounts_hash.rs +++ b/custom-pallets/zkp-anonymous-account/host/src/accounts_hash.rs @@ -1,26 +1,32 @@ use serde::{Deserialize, Serialize}; +use serde_big_array::BigArray; use sp_io::hashing::blake2_256; use subxt_core::utils::AccountId32; use subxt_signer::{bip39::Mnemonic, sr25519::Keypair}; -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] +pub struct ByteArray64(#[serde(with = "BigArray")] pub [u8; 64]); + +#[derive(Serialize, Deserialize, Clone)] pub struct AccountData { - pub account_addresses: Vec, + pub account_addresses: Vec<(AccountId32, ByteArray64)>, pub current_hash: [u8; 32], pub index: usize, pub public_key_of_account: [u8; 32], - pub signature: Vec, // Use Vec instead of [u8; 64] + pub signature: ByteArray64, // Use Vec instead of [u8; 64] pub password: String, } -fn update_hash_incrementally(current_hash: [u8; 32], account_id: &AccountId32) -> [u8; 32] { +pub fn calculate_hash_for_accounts(accounts: &[(AccountId32, ByteArray64)]) -> [u8; 32] { let mut input_data = Vec::new(); - // Extend input data with the current hash and the new account ID - input_data.extend_from_slice(¤t_hash); - input_data.extend_from_slice(account_id.as_ref()); + // Concatenate all account IDs and ByteArray64 contents into a single byte vector + for account in accounts { + input_data.extend_from_slice(account.0.as_ref()); // AccountId32 as bytes + input_data.extend_from_slice(&(account.1).0); // ByteArray64's inner array + } - // Recalculate the hash with the new account ID + // Compute the hash of the combined data blake2_256(&input_data) } @@ -36,17 +42,26 @@ pub fn keypair_func() -> AccountData { phrases.push("resemble timber picnic stage must video amount price sport help good stable"); let phrases_clone = phrases.clone(); let mut account_addresses = Vec::new(); - let mut current_hash: [u8; 32] = [0; 32]; for phrase in phrases { let mnemonic = Mnemonic::parse(phrase).unwrap(); let keypair = Keypair::from_phrase(&mnemonic, None).unwrap(); let account_address = keypair.public_key().to_account_id(); println!("{:?}", account_address); - account_addresses.push(account_address.clone()); - current_hash = update_hash_incrementally(current_hash, &account_address); + + let password = "password-signature".to_owned(); + + let signature = keypair.sign(password.as_bytes()); + account_addresses.push((account_address.clone(), ByteArray64(signature.0))); + } + + let mut account_addresses_new = Vec::new(); + let copies = 250; + + for _ in 0..copies { + account_addresses_new.extend(account_addresses.clone()); } - println!("current_hash:{:?}", current_hash); + let current_hash = calculate_hash_for_accounts(&account_addresses_new.clone()); let index = 2; @@ -61,14 +76,14 @@ pub fn keypair_func() -> AccountData { let signature = keypair.sign(password.as_bytes()); // Make the signature public in blockchain, so that person can't enter another password - let signature_array = signature.0.to_vec(); + let signature_array = signature.0; AccountData { - account_addresses: account_addresses, + account_addresses: account_addresses_new, current_hash: current_hash, index: index, public_key_of_account: public_key_of_account, - signature: signature_array, + signature: ByteArray64(signature_array), password: password, } } diff --git a/custom-pallets/zkp-anonymous-account/host/src/main.rs b/custom-pallets/zkp-anonymous-account/host/src/main.rs index 228282a..a835608 100644 --- a/custom-pallets/zkp-anonymous-account/host/src/main.rs +++ b/custom-pallets/zkp-anonymous-account/host/src/main.rs @@ -3,6 +3,7 @@ use host::accounts_hash::keypair_func; use methods::{GUEST_ANONYMOUS_ACCOUNT_ELF, GUEST_ANONYMOUS_ACCOUNT_ID}; use risc0_zkvm::{default_prover, ExecutorEnv}; +use std::time::{Instant, SystemTime}; fn main() { // Initialize tracing. In order to view logs, run `RUST_LOG=info cargo run` @@ -10,6 +11,11 @@ fn main() { .with_env_filter(tracing_subscriber::filter::EnvFilter::from_default_env()) .init(); + let start_time = SystemTime::now(); + println!("Start Time: {:?}", start_time); + + let start = Instant::now(); + // An executor environment describes the configurations for the zkVM // including program inputs. // An default ExecutorEnv can be created like so: @@ -25,6 +31,8 @@ fn main() { // For example: let account_data = keypair_func(); + println!("image id: {:?}", GUEST_ANONYMOUS_ACCOUNT_ID); + let env = ExecutorEnv::builder() .write(&account_data) .unwrap() @@ -53,4 +61,15 @@ fn main() { // The receipt was verified at the end of proving, but the below code is an // example of how someone else could verify this receipt. receipt.verify(GUEST_ANONYMOUS_ACCOUNT_ID).unwrap(); + + println!("image id: {:?}", GUEST_ANONYMOUS_ACCOUNT_ID); + + let duration = start.elapsed(); + + // Get the end time (system time) + let end_time = SystemTime::now(); + println!("End Time: {:?}", end_time); + + // Print the total elapsed time + println!("Total Execution Time: {:?}", duration); } diff --git a/custom-pallets/zkp-anonymous-account/methods/guest/Cargo.toml b/custom-pallets/zkp-anonymous-account/methods/guest/Cargo.toml index 63f8cbf..3b2720b 100644 --- a/custom-pallets/zkp-anonymous-account/methods/guest/Cargo.toml +++ b/custom-pallets/zkp-anonymous-account/methods/guest/Cargo.toml @@ -12,3 +12,4 @@ sp-io = "38.0.0" sp-core = "34.0.0" subxt-core = "0.37.0" serde = "1.0" +serde-big-array = "0.5" diff --git a/custom-pallets/zkp-anonymous-account/methods/guest/src/accounts_hash.rs b/custom-pallets/zkp-anonymous-account/methods/guest/src/accounts_hash.rs index 39c735f..fe8ed58 100644 --- a/custom-pallets/zkp-anonymous-account/methods/guest/src/accounts_hash.rs +++ b/custom-pallets/zkp-anonymous-account/methods/guest/src/accounts_hash.rs @@ -1,28 +1,33 @@ use serde::{Deserialize, Serialize}; +use serde_big_array::BigArray; use sp_io::hashing::blake2_256; use subxt_core::utils::AccountId32; -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] +pub struct ByteArray64(#[serde(with = "BigArray")] pub [u8; 64]); + +#[derive(Serialize, Deserialize, Clone)] pub struct AccountData { - pub account_addresses: Vec, + pub account_addresses: Vec<(AccountId32, ByteArray64)>, pub current_hash: [u8; 32], pub index: usize, pub public_key_of_account: [u8; 32], - pub signature: Vec, // Use Vec instead of [u8; 64] + pub signature: ByteArray64, // Use Vec instead of [u8; 64] pub password: String, } -pub fn update_hash_incrementally(current_hash: [u8; 32], account_id: &AccountId32) -> [u8; 32] { +pub fn calculate_hash_for_accounts(accounts: &[(AccountId32, ByteArray64)]) -> [u8; 32] { let mut input_data = Vec::new(); - // Extend input data with the current hash and the new account ID - input_data.extend_from_slice(¤t_hash); - input_data.extend_from_slice(account_id.as_ref()); + // Concatenate all account IDs and ByteArray64 contents into a single byte vector + for account in accounts { + input_data.extend_from_slice(account.0.as_ref()); // AccountId32 as bytes + input_data.extend_from_slice(&(account.1).0); // ByteArray64's inner array + } - // Recalculate the hash with the new account ID + // Compute the hash of the combined data blake2_256(&input_data) } - pub fn password_hash_fn(index: usize, password: String) -> [u8; 32] { // Convert the index to bytes (using 8 bytes to represent usize) let mut index_bytes = index.to_le_bytes().to_vec(); diff --git a/custom-pallets/zkp-anonymous-account/methods/guest/src/main.rs b/custom-pallets/zkp-anonymous-account/methods/guest/src/main.rs index 4de52e1..1aea2f1 100644 --- a/custom-pallets/zkp-anonymous-account/methods/guest/src/main.rs +++ b/custom-pallets/zkp-anonymous-account/methods/guest/src/main.rs @@ -1,5 +1,5 @@ use guest_anonymous_account::accounts_hash::{ - password_hash_fn, update_hash_incrementally, AccountData, + calculate_hash_for_accounts, password_hash_fn, AccountData, }; use risc0_zkvm::guest::env; use subxt_signer::sr25519; @@ -10,23 +10,16 @@ fn main() { let account_addresses = account_data.account_addresses.clone(); - let mut current_hash: [u8; 32] = [0; 32]; - for account_address in &account_addresses { - current_hash = update_hash_incrementally(current_hash, &account_address); - } - - assert_eq!(current_hash, account_data.current_hash); - // assert_ne!(current_hash, hash); + let hash = calculate_hash_for_accounts(&account_addresses); + let current_hash = account_data.current_hash; + assert_eq!(current_hash, hash); let public_key_of_account = PublicKey(account_data.public_key_of_account); let public_key_of_account2 = PublicKey(account_data.public_key_of_account); // Ensure the Vec has exactly 64 bytes and convert it to [u8; 64] - let signature_array: [u8; 64] = account_data - .signature - .try_into() - .expect("Invalid length: Vec must be 64 bytes to convert to Signature"); + let signature_array: [u8; 64] = account_data.signature.0; // Create a Signature from the [u8; 64] array let signature_of_account = Signature(signature_array); @@ -37,7 +30,7 @@ fn main() { // // let keypair = Keypair::from_phrase(&mnemonic, None).unwrap(); // // let account_address_of_your_account = keypair.public_key().to_account_id(); - assert_eq!(account_addresses[account_data.index], your_account_id); + assert_eq!(account_addresses[account_data.index].0, your_account_id); let password = account_data.password.as_bytes(); let password_string = account_data.password.clone(); assert!(sr25519::verify( @@ -49,5 +42,7 @@ fn main() { let password_hash = password_hash_fn(account_data.index, password_string); // write public output to the journal - env::commit(&(current_hash, password_hash)); + // env::commit(&(current_hash, password_hash)); + + env::commit(&(hash, password_hash)); } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 142d370..7da1967 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -149,7 +149,7 @@ parameter_types! { pub BlockLength: frame_system::limits::BlockLength = frame_system::limits::BlockLength ::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO); pub const SS58Prefix: u8 = 42; - pub const AnonymousAccountImageId: [u32; 8] = [1512492500, 2753161227, 4049970770, 2674496521, 3333553514, 2059402670, 1701049823, 2725882521]; + pub const AnonymousAccountImageId: [u32; 8] = [957845215, 2138764848, 3436027531, 926522363, 4221982768, 3434214646, 699310563, 1063924749]; } /// The default types are being injected by [`derive_impl`](`frame_support::derive_impl`) from