From 2aa79ba564546929888b14f1a419f9827a3574d3 Mon Sep 17 00:00:00 2001 From: Ash Date: Sun, 17 Nov 2024 00:01:36 +0700 Subject: [PATCH 01/19] use new query endpoint --- Cargo.toml | 2 +- contracts/account/Cargo.toml | 14 ++++ contracts/account/src/auth.rs | 24 +++++++ contracts/account/src/auth/groth16.rs | 11 ++++ contracts/account/src/auth/zkemail.rs | 92 +++++++++++++++++++++++++++ contracts/account/src/error.rs | 13 ++++ 6 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 contracts/account/src/auth/groth16.rs create mode 100644 contracts/account/src/auth/zkemail.rs diff --git a/Cargo.toml b/Cargo.toml index fbcf5895..851d6c2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,5 +28,5 @@ phf = { version = "0.11.2", features = ["macros"] } rsa = { version = "0.9.2" } getrandom = { version = "0.2.10", features = ["custom"] } p256 = {version = "0.13.2", features = ["ecdsa-core", "arithmetic", "serde"]} -cosmos-sdk-proto = {git = "https://github.com/burnt-labs/cosmos-rust.git", rev = "75e72f446629f98330e209e2f6268250d325cccb", default-features = false, features = ["std", "cosmwasm", "xion", "serde"]} +cosmos-sdk-proto = {git = "https://github.com/burnt-labs/cosmos-rust.git", rev = "8566d24a4ce0c3d7c478e994f17dbf2f14cfd7c1", default-features = false, features = ["std", "cosmwasm", "xion", "serde"]} url = "2.5.2" diff --git a/contracts/account/Cargo.toml b/contracts/account/Cargo.toml index e3c12187..659f98e0 100644 --- a/contracts/account/Cargo.toml +++ b/contracts/account/Cargo.toml @@ -31,3 +31,17 @@ rsa = { workspace = true } getrandom = { workspace = true } p256 = { workspace = true } cosmos-sdk-proto = { workspace = true } + + +# zk deps +ark-crypto-primitives = { version = "=0.4.0" } +ark-ec = { version = "=0.4.2", default-features = false } +ark-ff = { version = "=0.4.2", default-features = false, features = [ "asm"] } +ark-std = { version = "=0.4.0", default-features = false } +ark-bn254 = { version = "=0.4.0" } +ark-groth16 = { version = "=0.4.0", default-features = false } +ark-relations = { version = "=0.4.0", default-features = false } +ark-serialize = { version = "=0.4.2", default-features = false } +ark-poly = { version = "=0.4.2", default-features = false } +ark-circom = { git = "https://github.com/mvid/circom-compat" } +poseidon-ark = {git = "https://github.com/arnaucube/poseidon-ark"} \ No newline at end of file diff --git a/contracts/account/src/auth.rs b/contracts/account/src/auth.rs index ba329322..57fa515e 100644 --- a/contracts/account/src/auth.rs +++ b/contracts/account/src/auth.rs @@ -10,6 +10,8 @@ pub mod passkey; mod secp256r1; mod sign_arb; pub mod util; +mod zkemail; +mod groth16; pub mod testing { pub use super::sign_arb::wrap_message; @@ -48,6 +50,13 @@ pub enum AddAuthenticator { url: String, credential: Binary, }, + ZKEmail { + id: u8, + vkey: Binary, + email_hash: Binary, + email_domain: String, + proof: Binary, + }, } impl AddAuthenticator { @@ -59,6 +68,7 @@ impl AddAuthenticator { AddAuthenticator::Jwt { id, .. } => *id, AddAuthenticator::Secp256R1 { id, .. } => *id, AddAuthenticator::Passkey { id, .. } => *id, + AddAuthenticator::ZKEmail { id, .. } => *id, } } } @@ -71,6 +81,11 @@ pub enum Authenticator { Jwt { aud: String, sub: String }, Secp256R1 { pubkey: Binary }, Passkey { url: String, passkey: Binary }, + ZKEmail { + vkey: Binary, + email_hash: Binary, + email_domain: String, + }, } impl Authenticator { @@ -138,6 +153,15 @@ impl Authenticator { Ok(true) } + Authenticator::ZKEmail { + vkey, + email_hash, + email_domain, + } => { + let verification = + zkemail::verify(deps, tx_bytes, sig_bytes, vkey, email_hash, email_domain)?; + Ok(verification) + } } } } diff --git a/contracts/account/src/auth/groth16.rs b/contracts/account/src/auth/groth16.rs new file mode 100644 index 00000000..eae9954c --- /dev/null +++ b/contracts/account/src/auth/groth16.rs @@ -0,0 +1,11 @@ +use ark_bn254::{Bn254, Config, FrConfig}; +use ark_circom::CircomReduction; +use ark_ec::bn::Bn; +use ark_ff::Fp; +use ark_ff::MontBackend; +use ark_groth16::{Groth16, Proof, VerifyingKey}; + +pub type GrothBnVkey = VerifyingKey; +pub type GrothBnProof = Proof>; +pub type GrothBn = Groth16; +pub type GrothFp = Fp, 4>; diff --git a/contracts/account/src/auth/zkemail.rs b/contracts/account/src/auth/zkemail.rs new file mode 100644 index 00000000..44d72e1f --- /dev/null +++ b/contracts/account/src/auth/zkemail.rs @@ -0,0 +1,92 @@ +use crate::auth::groth16::{GrothBn, GrothBnProof, GrothBnVkey, GrothFp}; +use crate::error::ContractResult; +use ark_crypto_primitives::snark::SNARK; +use ark_ff::{PrimeField, Zero}; +use ark_serialize::CanonicalDeserialize; +use base64::engine::general_purpose::STANDARD_NO_PAD; +use base64::Engine; +use cosmos_sdk_proto::traits::MessageExt; +use cosmwasm_std::{Binary, Deps}; +use cosmos_sdk_proto::xion::v1::dkim::{QueryDkimPubKeyRequest, QueryDkimPubKeyResponse}; +use cosmos_sdk_proto::prost::Message; + +const TX_BODY_MAX_BYTES: usize = 512; +const EMAIL_MAX_BYTES: usize = 256; + +pub fn calculate_tx_body_commitment(tx: &str) -> GrothFp { + let padded_tx_bytes = pad_bytes(tx.as_bytes(), TX_BODY_MAX_BYTES); + let tx = pack_bytes_into_fields(padded_tx_bytes); + let poseidon = poseidon_ark::Poseidon::new(); + let mut commitment = GrothFp::zero(); // Initialize commitment with an initial value + + tx.chunks(16).enumerate().for_each(|(i, chunk)| { + let chunk_commitment = poseidon.hash(chunk.to_vec()).unwrap(); + commitment = if i == 0 { + chunk_commitment + } else { + poseidon.hash(vec![commitment, chunk_commitment]).unwrap() + }; + }); + + commitment +} + +fn pack_bytes_into_fields(bytes: Vec) -> Vec { + // convert each 31 bytes into one field element + let mut fields = vec![]; + bytes.chunks(31).for_each(|chunk| { + fields.push(GrothFp::from_le_bytes_mod_order(&chunk)); + }); + fields +} + +fn pad_bytes(bytes: &[u8], length: usize) -> Vec { + let mut padded = bytes.to_vec(); + let padding = length - bytes.len(); + for _ in 0..padding { + padded.push(0); + } + padded +} + +pub fn verify( + deps: Deps, + tx_bytes: &Binary, + sig_bytes: &Binary, + vkey_bytes: &Binary, + email_hash: &Binary, + email_domain: &String, +) -> ContractResult { + // vkey serialization is checked on submission + let vkey = GrothBnVkey::deserialize_compressed_unchecked(vkey_bytes.as_slice())?; + // proof submission is from the tx, we can't be sure if it was properly serialized + let proof = GrothBnProof::deserialize_compressed(sig_bytes.as_slice())?; + + // inputs are tx body, email hash, and dmarc key hash + let mut inputs: [GrothFp; 3] = [GrothFp::zero(); 3]; + + // tx body input + let tx_input = calculate_tx_body_commitment(STANDARD_NO_PAD.encode(tx_bytes).as_str()); + inputs[0] = tx_input; + + // email hash input, compressed at authenticator registration + let email_hash_input = GrothFp::deserialize_compressed_unchecked(email_hash.as_slice())?; + inputs[1] = email_hash_input; + + // retrieve the DKIM key hash from chain state + let query = QueryDkimPubKeyRequest { + selector: "TODO".to_string(), + domain: email_domain.to_string(), + }; + let query_bz = query.to_bytes()?; + let query_response = deps.querier.query_grpc( + String::from("/xion.dkim.v1.Query/QueryDkimPubKey"), + Binary::new(query_bz), + )?; + let query_response = QueryDkimPubKeyResponse::decode(query_response.as_slice())?; + inputs[2] = GrothFp::deserialize_compressed_unchecked(query_response.poseidon_hash.as_slice())?; + + let verified = GrothBn::verify(&vkey, inputs.as_slice(), &proof)?; + + Ok(verified) +} \ No newline at end of file diff --git a/contracts/account/src/error.rs b/contracts/account/src/error.rs index bd17ab67..7ca3eb67 100644 --- a/contracts/account/src/error.rs +++ b/contracts/account/src/error.rs @@ -84,6 +84,13 @@ pub enum ContractError { #[error(transparent)] FromUTF8(#[from] std::string::FromUtf8Error), + + #[error("r1cs synthesis error")] + R1CS(#[from] ark_relations::r1cs::SynthesisError), + + #[error("{0}")] + ArkSerialization(String), + } pub type ContractResult = Result; @@ -99,3 +106,9 @@ impl From for ContractError { Self::SerdeJSON(format!("{:?}", value)) } } + +impl From for ContractError { + fn from(value: ark_serialize::SerializationError) -> Self { + Self::ArkSerialization(format!("{:?}", value)) + } +} \ No newline at end of file From 9e8940febea619a690b237cdf93e07f259d2bfc2 Mon Sep 17 00:00:00 2001 From: Ash Date: Sun, 17 Nov 2024 00:05:00 +0700 Subject: [PATCH 02/19] lint --- contracts/account/src/auth.rs | 28 ++++++++++++++++++++------- contracts/account/src/auth/zkemail.rs | 6 +++--- contracts/account/src/error.rs | 3 +-- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/contracts/account/src/auth.rs b/contracts/account/src/auth.rs index 57fa515e..97d87eca 100644 --- a/contracts/account/src/auth.rs +++ b/contracts/account/src/auth.rs @@ -5,13 +5,13 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; mod eth_crypto; +mod groth16; pub mod jwt; pub mod passkey; mod secp256r1; mod sign_arb; pub mod util; mod zkemail; -mod groth16; pub mod testing { pub use super::sign_arb::wrap_message; @@ -75,12 +75,26 @@ impl AddAuthenticator { #[derive(Serialize, Deserialize, Clone, JsonSchema, PartialEq, Debug)] pub enum Authenticator { - Secp256K1 { pubkey: Binary }, - Ed25519 { pubkey: Binary }, - EthWallet { address: String }, - Jwt { aud: String, sub: String }, - Secp256R1 { pubkey: Binary }, - Passkey { url: String, passkey: Binary }, + Secp256K1 { + pubkey: Binary, + }, + Ed25519 { + pubkey: Binary, + }, + EthWallet { + address: String, + }, + Jwt { + aud: String, + sub: String, + }, + Secp256R1 { + pubkey: Binary, + }, + Passkey { + url: String, + passkey: Binary, + }, ZKEmail { vkey: Binary, email_hash: Binary, diff --git a/contracts/account/src/auth/zkemail.rs b/contracts/account/src/auth/zkemail.rs index 44d72e1f..273aae3f 100644 --- a/contracts/account/src/auth/zkemail.rs +++ b/contracts/account/src/auth/zkemail.rs @@ -5,10 +5,10 @@ use ark_ff::{PrimeField, Zero}; use ark_serialize::CanonicalDeserialize; use base64::engine::general_purpose::STANDARD_NO_PAD; use base64::Engine; +use cosmos_sdk_proto::prost::Message; use cosmos_sdk_proto::traits::MessageExt; -use cosmwasm_std::{Binary, Deps}; use cosmos_sdk_proto::xion::v1::dkim::{QueryDkimPubKeyRequest, QueryDkimPubKeyResponse}; -use cosmos_sdk_proto::prost::Message; +use cosmwasm_std::{Binary, Deps}; const TX_BODY_MAX_BYTES: usize = 512; const EMAIL_MAX_BYTES: usize = 256; @@ -89,4 +89,4 @@ pub fn verify( let verified = GrothBn::verify(&vkey, inputs.as_slice(), &proof)?; Ok(verified) -} \ No newline at end of file +} diff --git a/contracts/account/src/error.rs b/contracts/account/src/error.rs index 7ca3eb67..871b4411 100644 --- a/contracts/account/src/error.rs +++ b/contracts/account/src/error.rs @@ -90,7 +90,6 @@ pub enum ContractError { #[error("{0}")] ArkSerialization(String), - } pub type ContractResult = Result; @@ -111,4 +110,4 @@ impl From for ContractError { fn from(value: ark_serialize::SerializationError) -> Self { Self::ArkSerialization(format!("{:?}", value)) } -} \ No newline at end of file +} From 9937ad41cd8b41b7fc13ca3a268dde68ff9508df Mon Sep 17 00:00:00 2001 From: Ash Date: Mon, 18 Nov 2024 19:02:16 +0700 Subject: [PATCH 03/19] zkemail compiles --- Cargo.toml | 2 +- contracts/account/Cargo.toml | 1 - contracts/account/src/auth.rs | 8 +- contracts/account/src/auth/groth16.rs | 113 +++++++++++++++++++++++++- contracts/account/src/auth/zkemail.rs | 33 +++++--- contracts/account/src/error.rs | 3 + contracts/account/src/execute.rs | 19 +++++ 7 files changed, 161 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 851d6c2c..79889ce1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,5 +28,5 @@ phf = { version = "0.11.2", features = ["macros"] } rsa = { version = "0.9.2" } getrandom = { version = "0.2.10", features = ["custom"] } p256 = {version = "0.13.2", features = ["ecdsa-core", "arithmetic", "serde"]} -cosmos-sdk-proto = {git = "https://github.com/burnt-labs/cosmos-rust.git", rev = "8566d24a4ce0c3d7c478e994f17dbf2f14cfd7c1", default-features = false, features = ["std", "cosmwasm", "xion", "serde"]} +cosmos-sdk-proto = {git = "https://github.com/burnt-labs/cosmos-rust.git", rev = "2b3d0a8c2bfb5d19565faf708f97495ebccca3f2", default-features = false, features = ["std", "cosmwasm", "xion", "serde"]} url = "2.5.2" diff --git a/contracts/account/Cargo.toml b/contracts/account/Cargo.toml index 659f98e0..13d9dc62 100644 --- a/contracts/account/Cargo.toml +++ b/contracts/account/Cargo.toml @@ -43,5 +43,4 @@ ark-groth16 = { version = "=0.4.0", default-features = false } ark-relations = { version = "=0.4.0", default-features = false } ark-serialize = { version = "=0.4.2", default-features = false } ark-poly = { version = "=0.4.2", default-features = false } -ark-circom = { git = "https://github.com/mvid/circom-compat" } poseidon-ark = {git = "https://github.com/arnaucube/poseidon-ark"} \ No newline at end of file diff --git a/contracts/account/src/auth.rs b/contracts/account/src/auth.rs index 97d87eca..625418b5 100644 --- a/contracts/account/src/auth.rs +++ b/contracts/account/src/auth.rs @@ -54,7 +54,7 @@ pub enum AddAuthenticator { id: u8, vkey: Binary, email_hash: Binary, - email_domain: String, + dkim_domain: String, proof: Binary, }, } @@ -98,7 +98,7 @@ pub enum Authenticator { ZKEmail { vkey: Binary, email_hash: Binary, - email_domain: String, + dkim_domain: String, }, } @@ -170,10 +170,10 @@ impl Authenticator { Authenticator::ZKEmail { vkey, email_hash, - email_domain, + dkim_domain, } => { let verification = - zkemail::verify(deps, tx_bytes, sig_bytes, vkey, email_hash, email_domain)?; + zkemail::verify(deps, tx_bytes, sig_bytes, vkey, email_hash, dkim_domain)?; Ok(verification) } } diff --git a/contracts/account/src/auth/groth16.rs b/contracts/account/src/auth/groth16.rs index eae9954c..689fe7d2 100644 --- a/contracts/account/src/auth/groth16.rs +++ b/contracts/account/src/auth/groth16.rs @@ -1,11 +1,122 @@ use ark_bn254::{Bn254, Config, FrConfig}; -use ark_circom::CircomReduction; use ark_ec::bn::Bn; use ark_ff::Fp; use ark_ff::MontBackend; use ark_groth16::{Groth16, Proof, VerifyingKey}; +use ark_ff::PrimeField; +use ark_groth16::r1cs_to_qap::{evaluate_constraint, LibsnarkReduction, R1CSToQAP}; +use ark_poly::EvaluationDomain; +use ark_relations::r1cs::{ConstraintMatrices, ConstraintSystemRef, SynthesisError}; +use ark_std::{cfg_into_iter, cfg_iter, cfg_iter_mut, vec}; + +// Developer's Note: +// This has been copied over from the ark-circom package, which focuses on +// proving and verifying in arkworks using circom. It has many dependencies on +// wasmer/ethers/js that we do not need, if we only want to verify existing proofs pub type GrothBnVkey = VerifyingKey; pub type GrothBnProof = Proof>; pub type GrothBn = Groth16; pub type GrothFp = Fp, 4>; + + +/// Implements the witness map used by snarkjs. The arkworks witness map calculates the +/// coefficients of H through computing (AB-C)/Z in the evaluation domain and going back to the +/// coefficient's domain. snarkjs instead precomputes the Lagrange form of the powers of tau bases +/// in a domain twice as large and the witness map is computed as the odd coefficients of (AB-C) +/// in that domain. This serves as HZ when computing the C proof element. +pub struct CircomReduction; + +impl R1CSToQAP for CircomReduction { + #[allow(clippy::type_complexity)] + fn instance_map_with_evaluation>( + cs: ConstraintSystemRef, + t: &F, + ) -> Result<(Vec, Vec, Vec, F, usize, usize), SynthesisError> { + LibsnarkReduction::instance_map_with_evaluation::(cs, t) + } + + fn witness_map_from_matrices>( + matrices: &ConstraintMatrices, + num_inputs: usize, + num_constraints: usize, + full_assignment: &[F], + ) -> Result, SynthesisError> { + let zero = F::zero(); + let domain = + D::new(num_constraints + num_inputs).ok_or(SynthesisError::PolynomialDegreeTooLarge)?; + let domain_size = domain.size(); + + let mut a = vec![zero; domain_size]; + let mut b = vec![zero; domain_size]; + + cfg_iter_mut!(a[..num_constraints]) + .zip(cfg_iter_mut!(b[..num_constraints])) + .zip(cfg_iter!(&matrices.a)) + .zip(cfg_iter!(&matrices.b)) + .for_each(|(((a, b), at_i), bt_i)| { + *a = evaluate_constraint(at_i, full_assignment); + *b = evaluate_constraint(bt_i, full_assignment); + }); + + { + let start = num_constraints; + let end = start + num_inputs; + a[start..end].clone_from_slice(&full_assignment[..num_inputs]); + } + + let mut c = vec![zero; domain_size]; + cfg_iter_mut!(c[..num_constraints]) + .zip(&a) + .zip(&b) + .for_each(|((c_i, &a), &b)| { + *c_i = a * b; + }); + + domain.ifft_in_place(&mut a); + domain.ifft_in_place(&mut b); + + let root_of_unity = { + let domain_size_double = 2 * domain_size; + let domain_double = + D::new(domain_size_double).ok_or(SynthesisError::PolynomialDegreeTooLarge)?; + domain_double.element(1) + }; + D::distribute_powers_and_mul_by_const(&mut a, root_of_unity, F::one()); + D::distribute_powers_and_mul_by_const(&mut b, root_of_unity, F::one()); + + domain.fft_in_place(&mut a); + domain.fft_in_place(&mut b); + + let mut ab = domain.mul_polynomials_in_evaluation_domain(&a, &b); + drop(a); + drop(b); + + domain.ifft_in_place(&mut c); + D::distribute_powers_and_mul_by_const(&mut c, root_of_unity, F::one()); + domain.fft_in_place(&mut c); + + cfg_iter_mut!(ab) + .zip(c) + .for_each(|(ab_i, c_i)| *ab_i -= &c_i); + + Ok(ab) + } + + fn h_query_scalars>( + max_power: usize, + t: F, + _: F, + delta_inverse: F, + ) -> Result, SynthesisError> { + // the usual H query has domain-1 powers. Z has domain powers. So HZ has 2*domain-1 powers. + let mut scalars = cfg_into_iter!(0..2 * max_power + 1) + .map(|i| delta_inverse * t.pow([i as u64])) + .collect::>(); + let domain_size = scalars.len(); + let domain = D::new(domain_size).ok_or(SynthesisError::PolynomialDegreeTooLarge)?; + // generate the lagrange coefficients + domain.ifft_in_place(&mut scalars); + Ok(cfg_into_iter!(scalars).skip(1).step_by(2).collect()) + } +} diff --git a/contracts/account/src/auth/zkemail.rs b/contracts/account/src/auth/zkemail.rs index 273aae3f..2a00495e 100644 --- a/contracts/account/src/auth/zkemail.rs +++ b/contracts/account/src/auth/zkemail.rs @@ -1,4 +1,5 @@ use crate::auth::groth16::{GrothBn, GrothBnProof, GrothBnVkey, GrothFp}; +use crate::error::ContractError::InvalidDkim; use crate::error::ContractResult; use ark_crypto_primitives::snark::SNARK; use ark_ff::{PrimeField, Zero}; @@ -7,11 +8,10 @@ use base64::engine::general_purpose::STANDARD_NO_PAD; use base64::Engine; use cosmos_sdk_proto::prost::Message; use cosmos_sdk_proto::traits::MessageExt; -use cosmos_sdk_proto::xion::v1::dkim::{QueryDkimPubKeyRequest, QueryDkimPubKeyResponse}; +use cosmos_sdk_proto::xion::v1::dkim::{QueryDkimPubKeysRequest, QueryDkimPubKeysResponse}; use cosmwasm_std::{Binary, Deps}; const TX_BODY_MAX_BYTES: usize = 512; -const EMAIL_MAX_BYTES: usize = 256; pub fn calculate_tx_body_commitment(tx: &str) -> GrothFp { let padded_tx_bytes = pad_bytes(tx.as_bytes(), TX_BODY_MAX_BYTES); @@ -55,12 +55,15 @@ pub fn verify( sig_bytes: &Binary, vkey_bytes: &Binary, email_hash: &Binary, - email_domain: &String, + dkim_domain: &String, ) -> ContractResult { // vkey serialization is checked on submission let vkey = GrothBnVkey::deserialize_compressed_unchecked(vkey_bytes.as_slice())?; + + let (dkim_hash_bz, proof_bz) = sig_bytes.split_at(256); + // proof submission is from the tx, we can't be sure if it was properly serialized - let proof = GrothBnProof::deserialize_compressed(sig_bytes.as_slice())?; + let proof = GrothBnProof::deserialize_compressed(proof_bz)?; // inputs are tx body, email hash, and dmarc key hash let mut inputs: [GrothFp; 3] = [GrothFp::zero(); 3]; @@ -73,18 +76,26 @@ pub fn verify( let email_hash_input = GrothFp::deserialize_compressed_unchecked(email_hash.as_slice())?; inputs[1] = email_hash_input; - // retrieve the DKIM key hash from chain state - let query = QueryDkimPubKeyRequest { - selector: "TODO".to_string(), - domain: email_domain.to_string(), + // verify that domain+hash are known in chain state + let query = QueryDkimPubKeysRequest { + selector: "".to_string(), + domain: dkim_domain.to_string(), + poseidon_hash: dkim_hash_bz.to_vec(), + pagination: None, }; let query_bz = query.to_bytes()?; let query_response = deps.querier.query_grpc( - String::from("/xion.dkim.v1.Query/QueryDkimPubKey"), + String::from("/xion.dkim.v1.Query/QueryDkimPubKeys"), Binary::new(query_bz), )?; - let query_response = QueryDkimPubKeyResponse::decode(query_response.as_slice())?; - inputs[2] = GrothFp::deserialize_compressed_unchecked(query_response.poseidon_hash.as_slice())?; + let query_response = QueryDkimPubKeysResponse::decode(query_response.as_slice())?; + if query_response.dkim_pub_keys.is_empty() { + return Err(InvalidDkim); + } + + // verify the dkim pubkey hash in the proof output. the poseidon hash is + // from the tx, we can't be sure if it was properly formatted + inputs[2] = GrothFp::deserialize_compressed(dkim_hash_bz)?; let verified = GrothBn::verify(&vkey, inputs.as_slice(), &proof)?; diff --git a/contracts/account/src/error.rs b/contracts/account/src/error.rs index 871b4411..c98987bf 100644 --- a/contracts/account/src/error.rs +++ b/contracts/account/src/error.rs @@ -90,6 +90,9 @@ pub enum ContractError { #[error("{0}")] ArkSerialization(String), + + #[error("dkim invalid")] + InvalidDkim, } pub type ContractResult = Result; diff --git a/contracts/account/src/execute.rs b/contracts/account/src/execute.rs index 233b9241..9ab51258 100644 --- a/contracts/account/src/execute.rs +++ b/contracts/account/src/execute.rs @@ -70,6 +70,12 @@ pub fn before_tx( Authenticator::Passkey { .. } => { // todo: figure out if there are minimum checks for passkeys } + Authenticator::ZKEmail { .. } => { + // todo: verify that this minimum is as high as possible + if sig_bytes.len() < 700 { + return Err(ContractError::ShortSignature); + } + } } return match authenticator.verify(deps, env, tx_bytes, sig_bytes)? { @@ -219,6 +225,19 @@ pub fn add_auth_method( *(credential) = passkey; Ok(()) } + AddAuthenticator::ZKEmail { + id, vkey, email_hash, dkim_domain, proof: _ + } => { + // todo: how does verification work in a situation like this? + + let auth = Authenticator::ZKEmail { + vkey: vkey.clone(), + email_hash: email_hash.clone(), + dkim_domain: dkim_domain.clone(), + }; + save_authenticator(deps, *id, &auth)?; + Ok(()) + } }?; Ok( Response::new().add_event(Event::new("add_auth_method").add_attributes(vec![ From b86f122852d27eac8105ab64304da0e1af5cc1a1 Mon Sep 17 00:00:00 2001 From: Ash Date: Sat, 1 Feb 2025 16:15:50 -0800 Subject: [PATCH 04/19] outline test --- contracts/account/src/auth/zkemail.rs | 48 --------------------------- contracts/zkemail/src/contract.rs | 25 ++++++++++++++ 2 files changed, 25 insertions(+), 48 deletions(-) diff --git a/contracts/account/src/auth/zkemail.rs b/contracts/account/src/auth/zkemail.rs index 9c3d7cc9..9779bf26 100644 --- a/contracts/account/src/auth/zkemail.rs +++ b/contracts/account/src/auth/zkemail.rs @@ -35,51 +35,3 @@ pub fn verify( Ok(verified) } -#[cfg(test)] -mod tests { - use std::str::FromStr; - - use cosmwasm_std::{testing::MockApi, Uint256}; - - use super::*; - - #[test] - fn verifying_zkemail_signature() { - let api = MockApi::default(); - - // let - - // // example taken from ethers-rs: - // // https://github.com/gakonst/ethers-rs/tree/master/ethers-signers#examples - // let message = "hello world"; - // let address = "0x63F9725f107358c9115BC9d86c72dD5823E9B1E6"; - // - // let r = Uint256::from_str( - // "49684349367057865656909429001867135922228948097036637749682965078859417767352", - // ) - // .unwrap(); - // let s = Uint256::from_str( - // "26715700564957864553985478426289223220394026033170102795835907481710471636815", - // ) - // .unwrap(); - // let v = 28u8; - // - // let mut sig = vec![]; - // sig.extend(r.to_be_bytes()); - // sig.extend(s.to_be_bytes()); - // sig.push(v); - // assert_eq!(sig.len(), 65); - // - // let address_bytes = hex::decode(&address[2..]).unwrap(); - // let res = verify(&api, message.as_bytes(), &sig, &address_bytes); - // assert!(res.is_ok()); - // - // // let's try an invalid case - // // we simply change the address to a different one - // let wrong_address = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; - // - // let address_bytes = hex::decode(&wrong_address[2..]).unwrap(); - // let res = verify(&api, message.as_bytes(), &sig, &address_bytes); - // assert!(res.is_err()); - } -} diff --git a/contracts/zkemail/src/contract.rs b/contracts/zkemail/src/contract.rs index e7832b80..42206f93 100644 --- a/contracts/zkemail/src/contract.rs +++ b/contracts/zkemail/src/contract.rs @@ -113,3 +113,28 @@ fn query_verify( Ok(to_json_binary(&verified)?) } + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use cosmwasm_std::{testing::MockApi, Uint256}; + + use super::*; + + #[test] + fn verifying_zkemail_signature() { + let api = MockApi::default(); + + // build tx bytes to sign + + // load proof from previously sent and proved email + + // assign email salt from email used to prove + + // mock api for querying dkim module + + // submit data for verification + + } +} From 02d3b1a95da85aad903da7a3c211d9bf4a63ee90 Mon Sep 17 00:00:00 2001 From: Ash Date: Sat, 1 Feb 2025 18:30:12 -0800 Subject: [PATCH 05/19] limit import from zkemail contract --- contracts/account/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/account/Cargo.toml b/contracts/account/Cargo.toml index 8da8a6e0..68c53df9 100644 --- a/contracts/account/Cargo.toml +++ b/contracts/account/Cargo.toml @@ -32,4 +32,4 @@ rsa = { workspace = true } getrandom = { workspace = true } p256 = { workspace = true } cosmos-sdk-proto = { workspace = true } -zkemail = {path = "../zkemail"} \ No newline at end of file +zkemail = {path = "../zkemail", features = ["library"]} \ No newline at end of file From c61a3bdf9d21845bb1e76ba0fd9529613875639f Mon Sep 17 00:00:00 2001 From: Ash Date: Sat, 1 Feb 2025 18:43:02 -0800 Subject: [PATCH 06/19] disable random --- contracts/treasury/Cargo.toml | 3 --- contracts/zkemail/Cargo.toml | 1 + contracts/zkemail/src/lib.rs | 11 +++++++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/contracts/treasury/Cargo.toml b/contracts/treasury/Cargo.toml index 12bfa79e..5e857845 100644 --- a/contracts/treasury/Cargo.toml +++ b/contracts/treasury/Cargo.toml @@ -17,8 +17,5 @@ cosmwasm-std = { workspace = true } cw2 = { workspace = true } cw-storage-plus = { workspace = true } thiserror = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -schemars = { workspace = true } cosmos-sdk-proto = { workspace = true } url = { workspace = true } \ No newline at end of file diff --git a/contracts/zkemail/Cargo.toml b/contracts/zkemail/Cargo.toml index d18cf2ac..bad3c25b 100644 --- a/contracts/zkemail/Cargo.toml +++ b/contracts/zkemail/Cargo.toml @@ -18,6 +18,7 @@ cw2 = { workspace = true } thiserror = { workspace = true } base64 = { workspace = true } cosmos-sdk-proto = { workspace = true } +getrandom = { workspace = true } ark-crypto-primitives = { version = "=0.4.0" } ark-ec = { version = "=0.4.2", default-features = false } diff --git a/contracts/zkemail/src/lib.rs b/contracts/zkemail/src/lib.rs index d1bb7c0c..02abb5f5 100644 --- a/contracts/zkemail/src/lib.rs +++ b/contracts/zkemail/src/lib.rs @@ -8,3 +8,14 @@ mod state; pub const CONTRACT_NAME: &str = "zkemail-verifier"; pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +// the random function must be disabled in cosmwasm +use core::num::NonZeroU32; +use getrandom::Error; + +pub fn always_fail(_buf: &mut [u8]) -> Result<(), Error> { + let code = NonZeroU32::new(Error::CUSTOM_START).unwrap(); + Err(Error::from(code)) +} +use getrandom::register_custom_getrandom; +register_custom_getrandom!(always_fail); \ No newline at end of file From 446c02170f60bf82aa41797e32e5e87b0f8eecb8 Mon Sep 17 00:00:00 2001 From: Ash Date: Sat, 1 Feb 2025 18:45:10 -0800 Subject: [PATCH 07/19] older edition --- contracts/zkemail/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/zkemail/Cargo.toml b/contracts/zkemail/Cargo.toml index bad3c25b..ca9956c2 100644 --- a/contracts/zkemail/Cargo.toml +++ b/contracts/zkemail/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "zkemail" version = "0.1.0" -edition = "2024" +edition = "2021" [features] # enable feature if you want to disable entry points From b5b0b8633bd0adcce14c402daca1d288b88fb114 Mon Sep 17 00:00:00 2001 From: peartes Date: Wed, 11 Jun 2025 17:56:43 +0100 Subject: [PATCH 08/19] feat: query chain for proof verification --- Cargo.toml | 2 +- contracts/account/Cargo.toml | 2 +- contracts/account/src/auth.rs | 10 ++----- contracts/account/src/auth/zkemail.rs | 39 ++++++++++++++++----------- 4 files changed, 28 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4874cc87..3c444410 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,5 +28,5 @@ phf = { version = "0.11.2", features = ["macros"] } rsa = { version = "0.9.2" } getrandom = { version = "0.2.10", features = ["custom"] } p256 = {version = "0.13.2", features = ["ecdsa-core", "arithmetic", "serde"]} -cosmos-sdk-proto = {package = "xion-cosmos-sdk-proto", version = "0.26.1", default-features = false, features = ["std", "cosmwasm", "xion", "serde"]} +cosmos-sdk-proto = {path ="../../cosmos-rust/cosmos-sdk-proto", default-features = false, features = ["cosmwasm", "xion"]} url = "2.5.2" diff --git a/contracts/account/Cargo.toml b/contracts/account/Cargo.toml index 68c53df9..bf57ff54 100644 --- a/contracts/account/Cargo.toml +++ b/contracts/account/Cargo.toml @@ -32,4 +32,4 @@ rsa = { workspace = true } getrandom = { workspace = true } p256 = { workspace = true } cosmos-sdk-proto = { workspace = true } -zkemail = {path = "../zkemail", features = ["library"]} \ No newline at end of file +# zkemail = {path = "../zkemail", features = ["library"]} \ No newline at end of file diff --git a/contracts/account/src/auth.rs b/contracts/account/src/auth.rs index 19f448c7..f33f0c96 100644 --- a/contracts/account/src/auth.rs +++ b/contracts/account/src/auth.rs @@ -176,14 +176,8 @@ impl Authenticator { email_hash, dkim_domain, } => { - let verification = zkemail::verify( - deps, - verification_contract, - tx_bytes, - sig_bytes, - email_hash, - dkim_domain, - )?; + let verification = + zkemail::verify(deps, tx_bytes, sig_bytes, email_hash, dkim_domain)?; Ok(verification) } } diff --git a/contracts/account/src/auth/zkemail.rs b/contracts/account/src/auth/zkemail.rs index 9779bf26..2e0a6b9d 100644 --- a/contracts/account/src/auth/zkemail.rs +++ b/contracts/account/src/auth/zkemail.rs @@ -1,37 +1,46 @@ use crate::error::ContractResult; +use cosmos_sdk_proto::{ + traits::MessageExt, + xion::v1::dkim::{QueryVerifyRequest, QueryVerifyResponse}, +}; use cosmwasm_schema::cw_serde; use cosmwasm_std::{from_json, Addr, Binary, Deps}; +#[cw_serde] +pub struct SnarkJsProof { + pi_a: [String; 3], + pi_b: [[String; 2]; 3], + pi_c: [String; 3], +} #[cw_serde] pub struct ZKEmailSignature { - proof: zkemail::ark_verifier::SnarkJsProof, + proof: SnarkJsProof, dkim_hash: Binary, } pub fn verify( deps: Deps, - verification_contract: &Addr, tx_bytes: &Binary, sig_bytes: &Binary, email_hash: &Binary, dkim_domain: &str, ) -> ContractResult { - let sig: ZKEmailSignature = from_json(sig_bytes)?; + let sig: ZKEmailSignature = from_json(sig_bytes.clone())?; - let verification_request = zkemail::msg::QueryMsg::Verify { - proof: Box::new(sig.proof), + let verification_request = QueryVerifyRequest { + proof: sig_bytes.to_vec(), dkim_domain: dkim_domain.to_owned(), - tx_bytes: tx_bytes.clone(), - email_hash: email_hash.clone(), - dkim_hash: sig.dkim_hash, + tx_bytes: tx_bytes.to_vec(), + email_hash: email_hash.to_vec(), + dkim_hash: sig.dkim_hash.to_vec(), }; + let verification_request_byte = verification_request.to_bytes()?; + let verification_response: Binary = deps.querier.query_grpc( + "/xion.dkim.v1.Query/ProofVerify".to_string(), + Binary::from(verification_request_byte), + )?; - let verification_response: Binary = deps - .querier - .query_wasm_smart(verification_contract, &verification_request)?; + let res: QueryVerifyResponse = from_json(verification_response)?; - let verified: bool = from_json(verification_response)?; - - Ok(verified) + Ok(res.verified) } - From f6afe313d43c3c35a1adf8a5f3692f76f31b8e20 Mon Sep 17 00:00:00 2001 From: peartes Date: Mon, 16 Jun 2025 23:00:25 +0100 Subject: [PATCH 09/19] feat: verify proof on-chain --- contracts/account/src/auth.rs | 3 --- contracts/account/src/auth/zkemail.rs | 11 ++++++----- contracts/account/src/execute.rs | 2 -- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/contracts/account/src/auth.rs b/contracts/account/src/auth.rs index f33f0c96..31f2144f 100644 --- a/contracts/account/src/auth.rs +++ b/contracts/account/src/auth.rs @@ -51,7 +51,6 @@ pub enum AddAuthenticator { }, ZKEmail { id: u8, - verification_contract: Addr, email_hash: Binary, dkim_domain: String, }, @@ -94,7 +93,6 @@ pub enum Authenticator { passkey: Binary, }, ZKEmail { - verification_contract: Addr, email_hash: Binary, dkim_domain: String, }, @@ -172,7 +170,6 @@ impl Authenticator { Ok(true) } Authenticator::ZKEmail { - verification_contract, email_hash, dkim_domain, } => { diff --git a/contracts/account/src/auth/zkemail.rs b/contracts/account/src/auth/zkemail.rs index 2e0a6b9d..4cb274a1 100644 --- a/contracts/account/src/auth/zkemail.rs +++ b/contracts/account/src/auth/zkemail.rs @@ -1,10 +1,9 @@ use crate::error::ContractResult; use cosmos_sdk_proto::{ - traits::MessageExt, - xion::v1::dkim::{QueryVerifyRequest, QueryVerifyResponse}, + prost::Message, traits::MessageExt, xion::v1::dkim::{QueryVerifyRequest, QueryVerifyResponse} }; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{from_json, Addr, Binary, Deps}; +use cosmwasm_std::{from_json, to_json_vec, Addr, Binary, Deps}; #[cw_serde] pub struct SnarkJsProof { @@ -26,9 +25,11 @@ pub fn verify( dkim_domain: &str, ) -> ContractResult { let sig: ZKEmailSignature = from_json(sig_bytes.clone())?; + let proof: SnarkJsProof = sig.proof; + let proof_bz = to_json_vec(&proof)?; let verification_request = QueryVerifyRequest { - proof: sig_bytes.to_vec(), + proof: proof_bz, dkim_domain: dkim_domain.to_owned(), tx_bytes: tx_bytes.to_vec(), email_hash: email_hash.to_vec(), @@ -40,7 +41,7 @@ pub fn verify( Binary::from(verification_request_byte), )?; - let res: QueryVerifyResponse = from_json(verification_response)?; + let res: QueryVerifyResponse = QueryVerifyResponse::decode(verification_response.as_slice())?; Ok(res.verified) } diff --git a/contracts/account/src/execute.rs b/contracts/account/src/execute.rs index 7d856bde..4bf5fa97 100644 --- a/contracts/account/src/execute.rs +++ b/contracts/account/src/execute.rs @@ -228,14 +228,12 @@ pub fn add_auth_method( } AddAuthenticator::ZKEmail { id, - verification_contract, email_hash, dkim_domain, } => { // todo: how does verification work in a situation like this? let auth = Authenticator::ZKEmail { - verification_contract: verification_contract.clone(), email_hash: email_hash.clone(), dkim_domain: dkim_domain.clone(), }; From 3d732280b276fcce3731d518997256a35ace3446 Mon Sep 17 00:00:00 2001 From: Kushal7788 Date: Tue, 30 Sep 2025 16:39:01 +0530 Subject: [PATCH 10/19] Feat: Added Zkemail as an authenticator --- contracts/account/src/auth.rs | 44 +- contracts/account/src/auth/zkemail.rs | 626 ++++++++++++++++++++++++++ contracts/account/src/execute.rs | 74 ++- 3 files changed, 737 insertions(+), 7 deletions(-) create mode 100644 contracts/account/src/auth/zkemail.rs diff --git a/contracts/account/src/auth.rs b/contracts/account/src/auth.rs index 75682ed6..b311d798 100644 --- a/contracts/account/src/auth.rs +++ b/contracts/account/src/auth.rs @@ -10,6 +10,7 @@ pub mod passkey; mod secp256r1; mod sign_arb; pub mod util; +pub mod zkemail; pub mod testing { pub use super::sign_arb::wrap_message; @@ -48,6 +49,11 @@ pub enum AddAuthenticator { url: String, credential: Binary, }, + ZKEmail { + id: u8, + dkim_domain: String, + signature: Binary, + }, } impl AddAuthenticator { @@ -59,18 +65,37 @@ impl AddAuthenticator { AddAuthenticator::Jwt { id, .. } => *id, AddAuthenticator::Secp256R1 { id, .. } => *id, AddAuthenticator::Passkey { id, .. } => *id, + AddAuthenticator::ZKEmail { id, .. } => *id, } } } #[derive(Serialize, Deserialize, Clone, JsonSchema, PartialEq, Debug)] pub enum Authenticator { - Secp256K1 { pubkey: Binary }, - Ed25519 { pubkey: Binary }, - EthWallet { address: String }, - Jwt { aud: String, sub: String }, - Secp256R1 { pubkey: Binary }, - Passkey { url: String, passkey: Binary }, + Secp256K1 { + pubkey: Binary, + }, + Ed25519 { + pubkey: Binary, + }, + EthWallet { + address: String, + }, + Jwt { + aud: String, + sub: String, + }, + Secp256R1 { + pubkey: Binary, + }, + Passkey { + url: String, + passkey: Binary, + }, + ZKEmail { + email_salt: String, + dkim_domain: String, + }, } impl Authenticator { @@ -144,6 +169,13 @@ impl Authenticator { Ok(true) } + Authenticator::ZKEmail { + email_salt, + dkim_domain, + } => { + let verification = zkemail::verify(deps, dkim_domain, sig_bytes)?; + Ok(verification) + } } } } diff --git a/contracts/account/src/auth/zkemail.rs b/contracts/account/src/auth/zkemail.rs new file mode 100644 index 00000000..2a4c6f1e --- /dev/null +++ b/contracts/account/src/auth/zkemail.rs @@ -0,0 +1,626 @@ +use crate::error::ContractResult; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{from_json, Binary, Deps}; + +#[cw_serde] +pub struct SnarkJsProof { + #[serde(rename = "pi_a")] + pi_a: [String; 3], + #[serde(rename = "pi_b")] + pi_b: [[String; 2]; 3], + #[serde(rename = "pi_c")] + pi_c: [String; 3], + protocol: String, +} + + +#[cw_serde] +pub struct ZKEmailSignature { + proof: SnarkJsProof, + #[serde(rename = "publicOutputs")] + public_outputs: Vec, +} + +pub fn verify( + deps: Deps, + dkim_domain: &str, + sig_bytes: &Binary, +) -> ContractResult { + + // let verification_request = QueryVerifyRequest { + // sig_bytes: sig_bytes.clone(), + // dkim_domain: dkim_domain.to_string(), + // }; + // let verification_request_byte = verification_request.to_bytes()?; + // let verification_response: Binary = deps.querier.query_grpc( + // "/xion.dkim.v1.Query/ProofVerify".to_string(), + // Binary::from(verification_request_byte), + // )?; + + // let res: QueryVerifyResponse = from_json(verification_response)?; + + // Ok(res.verified) + Ok(true) +} + +pub fn extract_email_salt(signature: &Binary) -> ContractResult { + // convert signature to a string + let sig_str = String::from_utf8(signature.to_vec())?; + // get email salt from signature.publicOutputs[32] as a string + let sig: ZKEmailSignature = from_json(sig_str)?; + + // Check if we have enough public outputs + if sig.public_outputs.len() < 33 { + return Err(crate::error::ContractError::Std(cosmwasm_std::StdError::generic_err( + "Insufficient public outputs: expected at least 33 elements", + ))); + } + + let email_salt = sig.public_outputs[32].clone().to_string(); + Ok(email_salt) +} + +#[cfg(test)] +mod tests { + use super::*; + use cosmwasm_std::{ + testing::mock_dependencies, + Binary, + }; + + // Sample test data based on the provided signature + fn sample_zkemail_signature() -> ZKEmailSignature { + ZKEmailSignature { + proof: SnarkJsProof { + pi_a: [ + "13359235437905510146488545267580847868768563960781729194939527523243795688772".to_string(), + "16255212479465089639502013432936572417100794023004408906770080834142123006135".to_string(), + "1".to_string(), + ], + pi_b: [ + [ + "19284413907248568809076802931471620471530787252392478315569414028536127540332".to_string(), + "3391348177043200450451461793330092888088268452280878870378654788048816463108".to_string(), + ], + [ + "19852853133236466964633006998998630882202598701108272747914380336016310877725".to_string(), + "1320566082262176804917574208663865769527718771716928098903701681357146586169".to_string(), + ], + [ + "1".to_string(), + "0".to_string(), + ], + ], + pi_c: [ + "15683269302985443708971822532209957645618630393306369984958148167283539586821".to_string(), + "6442476935792224156511907661500477129513526142139554915043685301572568416380".to_string(), + "1".to_string(), + ], + protocol: "groth16".to_string(), + }, + public_outputs: vec![ + "2018721414038404820327".to_string(), + "0".to_string(), + "0".to_string(), + "0".to_string(), + "0".to_string(), + "0".to_string(), + "0".to_string(), + "0".to_string(), + "0".to_string(), + "6632353713085157925504008443078919716322386156160602218536961028046468237192".to_string(), + "19544515484294133365621150860798248908781994760432589784803858418698789050087".to_string(), + "1759147291".to_string(), + "124413588010935573100449456468959839270027757215138439816955024736271298883".to_string(), + "125987718504881168702817372751405511311626515399128115957683055706162879081".to_string(), + "138174294419566073638917398478480233783462655482283489778477032129860416308".to_string(), + "87164429935183530231106524238772469083021376536857547601286350511895957042".to_string(), + "159508995554830235422881220221659222882416701537684367907262541081181107041".to_string(), + "216177859633033993616607456010987870980723214832657304250929052054387451251".to_string(), + "136870293077760051536514689814528040652982158268238924211443105143315312977".to_string(), + "209027647271941540634260128227139143305212625530130988286308577451934433604".to_string(), + "216041037480816501846348705353738079775803623607373665378499876478757721956".to_string(), + "184099808892606061942559141059081527262834859629181581270585908529014000483".to_string(), + "173926821082308056829441773860483849128404996084932919505946802488367989070".to_string(), + "136498083332900321215526260868562056670892412932671519510981704427905430578".to_string(), + "0".to_string(), + "0".to_string(), + "0".to_string(), + "0".to_string(), + "0".to_string(), + "0".to_string(), + "0".to_string(), + "0".to_string(), + "8106355043968901587346579634598098765933160394002251948170420219958523220425".to_string(), + "1".to_string(), + ], + } + } + + fn sample_signature_json() -> String { + r#"{ + "proof": { + "curve": "bn128", + "pi_a": [ + "13359235437905510146488545267580847868768563960781729194939527523243795688772", + "16255212479465089639502013432936572417100794023004408906770080834142123006135", + "1" + ], + "pi_b": [ + [ + "19284413907248568809076802931471620471530787252392478315569414028536127540332", + "3391348177043200450451461793330092888088268452280878870378654788048816463108" + ], + [ + "19852853133236466964633006998998630882202598701108272747914380336016310877725", + "1320566082262176804917574208663865769527718771716928098903701681357146586169" + ], + [ + "1", + "0" + ] + ], + "pi_c": [ + "15683269302985443708971822532209957645618630393306369984958148167283539586821", + "6442476935792224156511907661500477129513526142139554915043685301572568416380", + "1" + ], + "protocol": "groth16" + }, + "publicOutputs": [ + "2018721414038404820327", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "6632353713085157925504008443078919716322386156160602218536961028046468237192", + "19544515484294133365621150860798248908781994760432589784803858418698789050087", + "1759147291", + "124413588010935573100449456468959839270027757215138439816955024736271298883", + "125987718504881168702817372751405511311626515399128115957683055706162879081", + "138174294419566073638917398478480233783462655482283489778477032129860416308", + "87164429935183530231106524238772469083021376536857547601286350511895957042", + "159508995554830235422881220221659222882416701537684367907262541081181107041", + "216177859633033993616607456010987870980723214832657304250929052054387451251", + "136870293077760051536514689814528040652982158268238924211443105143315312977", + "209027647271941540634260128227139143305212625530130988286308577451934433604", + "216041037480816501846348705353738079775803623607373665378499876478757721956", + "184099808892606061942559141059081527262834859629181581270585908529014000483", + "173926821082308056829441773860483849128404996084932919505946802488367989070", + "136498083332900321215526260868562056670892412932671519510981704427905430578", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "8106355043968901587346579634598098765933160394002251948170420219958523220425", + "1" + ] + }"#.to_string() + } + + #[test] + fn test_snarkjs_proof_serialization() { + let proof = SnarkJsProof { + pi_a: ["1".to_string(), "2".to_string(), "3".to_string()], + pi_b: [ + ["4".to_string(), "5".to_string()], + ["6".to_string(), "7".to_string()], + ["8".to_string(), "9".to_string()], + ], + pi_c: ["10".to_string(), "11".to_string(), "12".to_string()], + protocol: "groth16".to_string(), + }; + + let serialized = serde_json::to_string(&proof).unwrap(); + let deserialized: SnarkJsProof = serde_json::from_str(&serialized).unwrap(); + + assert_eq!(proof.pi_a, deserialized.pi_a); + assert_eq!(proof.pi_b, deserialized.pi_b); + assert_eq!(proof.pi_c, deserialized.pi_c); + assert_eq!(proof.protocol, deserialized.protocol); + } + + #[test] + fn test_zkemail_signature_serialization() { + let signature = sample_zkemail_signature(); + let json_str = sample_signature_json(); + + // Test deserialization from JSON string + let deserialized: ZKEmailSignature = serde_json::from_str(&json_str).unwrap(); + assert_eq!(signature.proof.pi_a, deserialized.proof.pi_a); + assert_eq!(signature.proof.pi_b, deserialized.proof.pi_b); + assert_eq!(signature.proof.pi_c, deserialized.proof.pi_c); + assert_eq!(signature.proof.protocol, deserialized.proof.protocol); + assert_eq!(signature.public_outputs, deserialized.public_outputs); + + // Test round-trip serialization + let serialized = serde_json::to_string(&signature).unwrap(); + let round_trip: ZKEmailSignature = serde_json::from_str(&serialized).unwrap(); + assert_eq!(signature.public_outputs, round_trip.public_outputs); + } + + #[test] + fn test_extract_email_salt_success() { + let json_str = sample_signature_json(); + let signature_binary = Binary::from(json_str.as_bytes()); + + let result = extract_email_salt(&signature_binary); + assert!(result.is_ok()); + + let email_salt = result.unwrap(); + // The email salt should be at index 32 of publicOutputs + assert_eq!(email_salt, "8106355043968901587346579634598098765933160394002251948170420219958523220425"); + } + + #[test] + fn test_extract_email_salt_invalid_json() { + let invalid_json = "invalid json"; + let signature_binary = Binary::from(invalid_json.as_bytes()); + + let result = extract_email_salt(&signature_binary); + assert!(result.is_err()); + } + + #[test] + fn test_extract_email_salt_invalid_utf8() { + // Create invalid UTF-8 bytes + let invalid_utf8 = vec![0xff, 0xfe, 0xfd]; + let signature_binary = Binary::from(invalid_utf8); + + let result = extract_email_salt(&signature_binary); + assert!(result.is_err()); + } + + #[test] + fn test_extract_email_salt_missing_field() { + let incomplete_json = r#"{ + "proof": { + "pi_a": ["1", "2", "3"], + "pi_b": [["4", "5"], ["6", "7"], ["8", "9"]], + "pi_c": ["10", "11", "12"], + "protocol": "groth16" + } + }"#; + let signature_binary = Binary::from(incomplete_json.as_bytes()); + + let result = extract_email_salt(&signature_binary); + assert!(result.is_err()); + } + + #[test] + fn test_extract_email_salt_insufficient_public_outputs() { + let json_with_short_outputs = r#"{ + "proof": { + "pi_a": ["1", "2", "3"], + "pi_b": [["4", "5"], ["6", "7"], ["8", "9"]], + "pi_c": ["10", "11", "12"], + "protocol": "groth16" + }, + "publicOutputs": ["1", "2", "3"] + }"#; + let signature_binary = Binary::from(json_with_short_outputs.as_bytes()); + + let result = extract_email_salt(&signature_binary); + assert!(result.is_err()); + } + + #[test] + fn test_zkemail_signature_field_names() { + // Test that the JSON field names match exactly (camelCase vs snake_case) + let json_str = sample_signature_json(); + let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap(); + + // Verify the JSON structure has the expected field names + assert!(parsed.get("proof").is_some()); + assert!(parsed.get("publicOutputs").is_some()); + + let proof = parsed.get("proof").unwrap(); + assert!(proof.get("pi_a").is_some()); + assert!(proof.get("pi_b").is_some()); + assert!(proof.get("pi_c").is_some()); + assert!(proof.get("protocol").is_some()); + } + + #[test] + fn test_proof_structure_validation() { + let signature = sample_zkemail_signature(); + + // Verify array sizes + assert_eq!(signature.proof.pi_a.len(), 3); + assert_eq!(signature.proof.pi_b.len(), 3); + assert_eq!(signature.proof.pi_c.len(), 3); + assert_eq!(signature.public_outputs.len(), 34); + + // Verify nested array structure + for row in &signature.proof.pi_b { + assert_eq!(row.len(), 2); + } + + // Verify protocol + assert_eq!(signature.proof.protocol, "groth16"); + } + + #[test] + fn test_public_outputs_boundary_cases() { + // Test access to first element + let signature = sample_zkemail_signature(); + assert_eq!(signature.public_outputs[0], "2018721414038404820327"); + + // Test access to last element (index 33) + assert_eq!(signature.public_outputs[33], "1"); + + // Test access to email salt element (index 32) + assert_eq!(signature.public_outputs[32], "8106355043968901587346579634598098765933160394002251948170420219958523220425"); + } + + #[test] + fn test_verify_with_hardcoded_return() { + // Since verify() is currently hardcoded to return true, test that behavior + let deps = mock_dependencies(); + let json_str = sample_signature_json(); + let signature_binary = Binary::from(json_str.as_bytes()); + + let result = verify( + deps.as_ref(), + "any_salt", + &signature_binary, + ); + + assert!(result.is_ok()); + assert_eq!(result.unwrap(), true); + } + + #[test] + fn test_verify_with_empty_params() { + let deps = mock_dependencies(); + let json_str = sample_signature_json(); + let signature_binary = Binary::from(json_str.as_bytes()); + + let result = verify( + deps.as_ref(), + "", + &signature_binary, + ); + + assert!(result.is_ok()); + assert_eq!(result.unwrap(), true); + } + + #[test] + fn test_snarkjs_proof_with_empty_fields() { + let proof = SnarkJsProof { + pi_a: ["".to_string(), "".to_string(), "".to_string()], + pi_b: [ + ["".to_string(), "".to_string()], + ["".to_string(), "".to_string()], + ["".to_string(), "".to_string()], + ], + pi_c: ["".to_string(), "".to_string(), "".to_string()], + protocol: "".to_string(), + }; + + let serialized = serde_json::to_string(&proof).unwrap(); + let deserialized: SnarkJsProof = serde_json::from_str(&serialized).unwrap(); + + assert_eq!(proof.pi_a, deserialized.pi_a); + assert_eq!(proof.pi_b, deserialized.pi_b); + assert_eq!(proof.pi_c, deserialized.pi_c); + assert_eq!(proof.protocol, deserialized.protocol); + } + + #[test] + fn test_snarkjs_proof_with_special_characters() { + let proof = SnarkJsProof { + pi_a: ["123!@#".to_string(), "456$%^".to_string(), "789&*()".to_string()], + pi_b: [ + ["test\\n".to_string(), "test\"quote".to_string()], + ["test'single".to_string(), "test\ttab".to_string()], + ["test/slash".to_string(), "test\\backslash".to_string()], + ], + pi_c: ["unicode🚀".to_string(), "unicode💯".to_string(), "unicode✨".to_string()], + protocol: "groth16-custom".to_string(), + }; + + let serialized = serde_json::to_string(&proof).unwrap(); + let deserialized: SnarkJsProof = serde_json::from_str(&serialized).unwrap(); + + assert_eq!(proof.pi_a, deserialized.pi_a); + assert_eq!(proof.pi_b, deserialized.pi_b); + assert_eq!(proof.pi_c, deserialized.pi_c); + assert_eq!(proof.protocol, deserialized.protocol); + } + + #[test] + fn test_zkemail_signature_with_empty_public_outputs() { + let signature = ZKEmailSignature { + proof: SnarkJsProof { + pi_a: ["1".to_string(), "2".to_string(), "3".to_string()], + pi_b: [ + ["4".to_string(), "5".to_string()], + ["6".to_string(), "7".to_string()], + ["8".to_string(), "9".to_string()], + ], + pi_c: ["10".to_string(), "11".to_string(), "12".to_string()], + protocol: "groth16".to_string(), + }, + public_outputs: vec![], + }; + + let serialized = serde_json::to_string(&signature).unwrap(); + let deserialized: ZKEmailSignature = serde_json::from_str(&serialized).unwrap(); + + assert_eq!(signature.public_outputs, deserialized.public_outputs); + assert!(signature.public_outputs.is_empty()); + } + + #[test] + fn test_zkemail_signature_with_large_public_outputs() { + let mut large_outputs = Vec::new(); + for i in 0..100 { + large_outputs.push(format!("output_{}", i)); + } + + let signature = ZKEmailSignature { + proof: SnarkJsProof { + pi_a: ["1".to_string(), "2".to_string(), "3".to_string()], + pi_b: [ + ["4".to_string(), "5".to_string()], + ["6".to_string(), "7".to_string()], + ["8".to_string(), "9".to_string()], + ], + pi_c: ["10".to_string(), "11".to_string(), "12".to_string()], + protocol: "groth16".to_string(), + }, + public_outputs: large_outputs.clone(), + }; + + let serialized = serde_json::to_string(&signature).unwrap(); + let deserialized: ZKEmailSignature = serde_json::from_str(&serialized).unwrap(); + + assert_eq!(signature.public_outputs.len(), 100); + assert_eq!(signature.public_outputs, deserialized.public_outputs); + assert_eq!(deserialized.public_outputs[0], "output_0"); + assert_eq!(deserialized.public_outputs[99], "output_99"); + } + + #[test] + fn test_extract_email_salt_with_exact_33_elements() { + // Create a signature with exactly 33 elements (so index 32 exists but barely) + let json_with_33_outputs = r#"{ + "proof": { + "pi_a": ["1", "2", "3"], + "pi_b": [["4", "5"], ["6", "7"], ["8", "9"]], + "pi_c": ["10", "11", "12"], + "protocol": "groth16" + }, + "publicOutputs": ["0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31","email_salt_value"] + }"#; + let signature_binary = Binary::from(json_with_33_outputs.as_bytes()); + + let result = extract_email_salt(&signature_binary); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), "email_salt_value"); + } + + #[test] + fn test_extract_email_salt_with_numeric_values() { + let json_with_numeric_outputs = r#"{ + "proof": { + "pi_a": ["1", "2", "3"], + "pi_b": [["4", "5"], ["6", "7"], ["8", "9"]], + "pi_c": ["10", "11", "12"], + "protocol": "groth16" + }, + "publicOutputs": ["0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31","42"] + }"#; + let signature_binary = Binary::from(json_with_numeric_outputs.as_bytes()); + + let result = extract_email_salt(&signature_binary); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), "42"); + } + + #[test] + fn test_verify_signature_parsing_edge_cases() { + let deps = mock_dependencies(); + + // Test with minimal valid JSON + let minimal_json = r#"{ + "proof": { + "pi_a": ["1", "2", "3"], + "pi_b": [["4", "5"], ["6", "7"], ["8", "9"]], + "pi_c": ["10", "11", "12"], + "protocol": "groth16" + }, + "publicOutputs": [] + }"#; + let signature_binary = Binary::from(minimal_json.as_bytes()); + + let result = verify( + deps.as_ref(), + "example.com", + &signature_binary, + ); + + // Should succeed because verify is hardcoded to return true + assert!(result.is_ok()); + assert_eq!(result.unwrap(), true); + } + + #[test] + fn test_multiple_proof_protocols() { + let protocols = vec!["groth16", "plonk", "stark", "custom_protocol"]; + + for protocol in protocols { + let proof = SnarkJsProof { + pi_a: ["1".to_string(), "2".to_string(), "3".to_string()], + pi_b: [ + ["4".to_string(), "5".to_string()], + ["6".to_string(), "7".to_string()], + ["8".to_string(), "9".to_string()], + ], + pi_c: ["10".to_string(), "11".to_string(), "12".to_string()], + protocol: protocol.to_string(), + }; + + let serialized = serde_json::to_string(&proof).unwrap(); + let deserialized: SnarkJsProof = serde_json::from_str(&serialized).unwrap(); + + assert_eq!(proof.protocol, deserialized.protocol); + assert_eq!(proof.protocol, protocol); + } + } + + #[test] + fn test_binary_from_different_sources() { + let json_str = sample_signature_json(); + + // Test Binary created from string bytes + let binary_from_str = Binary::from(json_str.as_bytes()); + let result1 = extract_email_salt(&binary_from_str); + assert!(result1.is_ok()); + + // Test Binary created from Vec + let binary_from_vec = Binary::from(json_str.as_bytes().to_vec()); + let result2 = extract_email_salt(&binary_from_vec); + assert!(result2.is_ok()); + + // Results should be identical + assert_eq!(result1.unwrap(), result2.unwrap()); + } + + #[test] + fn test_serde_field_name_mapping() { + // Test that serde correctly maps camelCase JSON to snake_case Rust fields + let json_with_camel_case = r#"{ + "proof": { + "pi_a": ["1", "2", "3"], + "pi_b": [["4", "5"], ["6", "7"], ["8", "9"]], + "pi_c": ["10", "11", "12"], + "protocol": "groth16" + }, + "publicOutputs": ["test_value"] + }"#; + + let signature: ZKEmailSignature = serde_json::from_str(json_with_camel_case).unwrap(); + + // Verify the struct fields are populated correctly + assert_eq!(signature.proof.pi_a, ["1", "2", "3"]); + assert_eq!(signature.proof.protocol, "groth16"); + assert_eq!(signature.public_outputs, vec!["test_value"]); + + // Verify serialization produces camelCase JSON + let serialized = serde_json::to_string(&signature).unwrap(); + let parsed_back: serde_json::Value = serde_json::from_str(&serialized).unwrap(); + assert!(parsed_back.get("publicOutputs").is_some()); + assert!(parsed_back.get("public_outputs").is_none()); // Should not exist + } +} \ No newline at end of file diff --git a/contracts/account/src/execute.rs b/contracts/account/src/execute.rs index 1ff57dbc..ba32a9be 100644 --- a/contracts/account/src/execute.rs +++ b/contracts/account/src/execute.rs @@ -2,12 +2,13 @@ use std::borrow::BorrowMut; use cosmwasm_std::{Addr, Binary, Deps, DepsMut, Env, Event, Order, Response}; -use crate::auth::{jwt, passkey, AddAuthenticator, Authenticator}; +use crate::auth::{jwt, passkey, AddAuthenticator, Authenticator, zkemail}; use crate::{ error::{ContractError, ContractResult}, state::AUTHENTICATORS, }; + pub fn init( deps: DepsMut, env: Env, @@ -70,6 +71,12 @@ pub fn before_tx( Authenticator::Passkey { .. } => { // todo: figure out if there are minimum checks for passkeys } + Authenticator::ZKEmail { .. } => { + // todo: verify that this minimum is as high as possible + if sig_bytes.len() < 400 { + return Err(ContractError::ShortSignature); + } + } } return match authenticator.verify(deps, env, tx_bytes, sig_bytes)? { @@ -220,6 +227,29 @@ pub fn add_auth_method( *(credential) = passkey; Ok(()) } + AddAuthenticator::ZKEmail { + id, + dkim_domain, + signature, + } => { + // extract email salt from signature + let email_salt = zkemail::extract_email_salt(signature)?; + let auth = Authenticator::ZKEmail { + email_salt: email_salt.clone(), + dkim_domain: dkim_domain.clone(), + }; + if !auth.verify( + deps.as_ref(), + env, + &Binary::from(env.contract.address.as_bytes()), + signature, + )? { + Err(ContractError::InvalidSignature) + } else { + save_authenticator(deps, *id, &auth)?; + Ok(()) + } + } }?; Ok( Response::new().add_event(Event::new("add_auth_method").add_attributes(vec![ @@ -395,4 +425,46 @@ pub mod tests { Binary::from("true".as_bytes()) ); } + + #[test] + fn test_add_zkemail_authenticator_invalid_signature() { + use crate::auth::AddAuthenticator; + use crate::execute::add_auth_method; + + let mut deps = OwnedDeps { + storage: MockStorage::default(), + api: MockApi::default().with_prefix("xion"), + querier: MockQuerier::::new(&[]), + custom_query_type: std::marker::PhantomData, + }; + let env = mock_env(); + + // Create an invalid signature (insufficient public outputs) + let invalid_signature_json = r#"{ + "proof": { + "pi_a": ["1", "2", "3"], + "pi_b": [["4", "5"], ["6", "7"], ["8", "9"]], + "pi_c": ["10", "11", "12"], + "protocol": "groth16" + }, + "publicOutputs": ["1", "2", "3"] + }"#; + + let signature_binary = Binary::from(invalid_signature_json.as_bytes()); + + let mut add_authenticator = AddAuthenticator::ZKEmail { + id: 1, + dkim_domain: "gmail.com".to_string(), + signature: signature_binary, + }; + + // Call add_auth_method - should fail due to insufficient public outputs + let result = add_auth_method(deps.as_mut(), &env, &mut add_authenticator); + + // Verify the result is an error + assert!(result.is_err()); + + // Verify the authenticator was not saved + assert!(!AUTHENTICATORS.has(deps.as_ref().storage, 1)); + } } From 5551a90c3fba51357df1e639ee33d29ac2f351b0 Mon Sep 17 00:00:00 2001 From: Kushal7788 Date: Wed, 1 Oct 2025 00:27:47 +0530 Subject: [PATCH 11/19] Updated zkemail authenticator verify method with additional params --- contracts/account/src/auth.rs | 2 +- contracts/account/src/auth/zkemail.rs | 51 ++++++++++++++++++--------- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/contracts/account/src/auth.rs b/contracts/account/src/auth.rs index b311d798..3286755f 100644 --- a/contracts/account/src/auth.rs +++ b/contracts/account/src/auth.rs @@ -173,7 +173,7 @@ impl Authenticator { email_salt, dkim_domain, } => { - let verification = zkemail::verify(deps, dkim_domain, sig_bytes)?; + let verification = zkemail::verify(deps, tx_bytes, sig_bytes, email_salt, dkim_domain)?; Ok(verification) } } diff --git a/contracts/account/src/auth/zkemail.rs b/contracts/account/src/auth/zkemail.rs index 2a4c6f1e..b2f4b6c4 100644 --- a/contracts/account/src/auth/zkemail.rs +++ b/contracts/account/src/auth/zkemail.rs @@ -1,6 +1,11 @@ use crate::error::ContractResult; use cosmwasm_schema::cw_serde; use cosmwasm_std::{from_json, Binary, Deps}; +use cosmos_sdk_proto::{ + traits::MessageExt, + xion::v1::dkim::{QueryVerifyRequest, QueryVerifyResponse}, +}; + #[cw_serde] pub struct SnarkJsProof { @@ -23,24 +28,27 @@ pub struct ZKEmailSignature { pub fn verify( deps: Deps, - dkim_domain: &str, + tx_bytes: &Binary, sig_bytes: &Binary, + email_salt: &str, + dkim_domain: &str, ) -> ContractResult { - // let verification_request = QueryVerifyRequest { - // sig_bytes: sig_bytes.clone(), - // dkim_domain: dkim_domain.to_string(), - // }; - // let verification_request_byte = verification_request.to_bytes()?; - // let verification_response: Binary = deps.querier.query_grpc( - // "/xion.dkim.v1.Query/ProofVerify".to_string(), - // Binary::from(verification_request_byte), - // )?; + let verification_request = QueryVerifyRequest { + tx_bytes: tx_bytes.to_vec(), + proof: sig_bytes.to_vec(), + dkim_domain: dkim_domain.to_owned(), + email_salt: email_salt.to_owned(), + }; + let verification_request_byte = verification_request.to_bytes()?; + let verification_response: Binary = deps.querier.query_grpc( + "/xion.dkim.v1.Query/ProofVerify".to_string(), + Binary::from(verification_request_byte), + )?; - // let res: QueryVerifyResponse = from_json(verification_response)?; + let res: QueryVerifyResponse = from_json(verification_response)?; - // Ok(res.verified) - Ok(true) + Ok(res.verified) } pub fn extract_email_salt(signature: &Binary) -> ContractResult { @@ -64,7 +72,7 @@ pub fn extract_email_salt(signature: &Binary) -> ContractResult { mod tests { use super::*; use cosmwasm_std::{ - testing::mock_dependencies, + testing::{mock_dependencies, mock_env}, Binary, }; @@ -365,13 +373,16 @@ mod tests { fn test_verify_with_hardcoded_return() { // Since verify() is currently hardcoded to return true, test that behavior let deps = mock_dependencies(); + let env = mock_env(); let json_str = sample_signature_json(); let signature_binary = Binary::from(json_str.as_bytes()); let result = verify( deps.as_ref(), - "any_salt", + &Binary::from(env.contract.address.as_bytes()), &signature_binary, + "any_salt", + "any_domain", ); assert!(result.is_ok()); @@ -381,13 +392,16 @@ mod tests { #[test] fn test_verify_with_empty_params() { let deps = mock_dependencies(); + let env = mock_env(); let json_str = sample_signature_json(); let signature_binary = Binary::from(json_str.as_bytes()); let result = verify( deps.as_ref(), - "", + &Binary::from(env.contract.address.as_bytes()), &signature_binary, + "any_salt", + "any_domain", ); assert!(result.is_ok()); @@ -531,6 +545,7 @@ mod tests { #[test] fn test_verify_signature_parsing_edge_cases() { let deps = mock_dependencies(); + let env = mock_env(); // Test with minimal valid JSON let minimal_json = r#"{ @@ -546,8 +561,10 @@ mod tests { let result = verify( deps.as_ref(), - "example.com", + &Binary::from(env.contract.address.as_bytes()), &signature_binary, + "any_salt", + "example.com", ); // Should succeed because verify is hardcoded to return true From 7960d135026485b291d52c7eb1b7f08a02abf590 Mon Sep 17 00:00:00 2001 From: Kushal7788 Date: Wed, 1 Oct 2025 01:01:56 +0530 Subject: [PATCH 12/19] Refactor: Remove AuthenticatorNotFound error and related validation in remove_auth_method function --- contracts/account/src/error.rs | 3 --- contracts/account/src/execute.rs | 5 ----- 2 files changed, 8 deletions(-) diff --git a/contracts/account/src/error.rs b/contracts/account/src/error.rs index ea4e0ac9..dab50ec0 100644 --- a/contracts/account/src/error.rs +++ b/contracts/account/src/error.rs @@ -90,9 +90,6 @@ pub enum ContractError { #[error("invalid ethereum address")] InvalidEthAddress, - - #[error("authenticator {index} not found")] - AuthenticatorNotFound { index: u8 }, } pub type ContractResult = Result; diff --git a/contracts/account/src/execute.rs b/contracts/account/src/execute.rs index ba32a9be..a8035801 100644 --- a/contracts/account/src/execute.rs +++ b/contracts/account/src/execute.rs @@ -282,11 +282,6 @@ pub fn remove_auth_method(deps: DepsMut, env: Env, id: u8) -> ContractResult Date: Wed, 22 Oct 2025 16:12:37 +0530 Subject: [PATCH 13/19] Updated the zk-email interface to match the dkim module --- contracts/account/src/auth.rs | 7 +- contracts/account/src/auth/util.rs | 37 ++++++ contracts/account/src/auth/zkemail.rs | 165 +++----------------------- contracts/account/src/execute.rs | 8 +- 4 files changed, 58 insertions(+), 159 deletions(-) diff --git a/contracts/account/src/auth.rs b/contracts/account/src/auth.rs index 0d9d3b2f..7e257569 100644 --- a/contracts/account/src/auth.rs +++ b/contracts/account/src/auth.rs @@ -51,7 +51,7 @@ pub enum AddAuthenticator { }, ZKEmail { id: u8, - dkim_domain: String, + email_salt: String, signature: Binary, }, } @@ -94,7 +94,6 @@ pub enum Authenticator { }, ZKEmail { email_salt: String, - dkim_domain: String, }, } @@ -171,9 +170,9 @@ impl Authenticator { } Authenticator::ZKEmail { email_salt, - dkim_domain, } => { - let verification = zkemail::verify(deps, tx_bytes, sig_bytes, email_salt, dkim_domain)?; + let tx_bytes_hash = util::base64url_encode(tx_bytes); + let verification = zkemail::verify(deps, &tx_bytes_hash, sig_bytes, email_salt)?; Ok(verification) } } diff --git a/contracts/account/src/auth/util.rs b/contracts/account/src/auth/util.rs index c4bfe3cc..3774a7bb 100644 --- a/contracts/account/src/auth/util.rs +++ b/contracts/account/src/auth/util.rs @@ -2,6 +2,7 @@ use crate::error::ContractError; use bech32::{ToBase32, Variant}; use ripemd::Ripemd160; use sha2::{Digest, Sha256}; +use base64::{Engine as _, engine::general_purpose}; pub fn sha256(msg: &[u8]) -> Vec { let mut hasher = Sha256::new(); @@ -9,6 +10,10 @@ pub fn sha256(msg: &[u8]) -> Vec { hasher.finalize().to_vec() } +pub fn base64url_encode(msg: &[u8]) -> String { + general_purpose::URL_SAFE_NO_PAD.encode(msg) +} + fn ripemd160(bytes: &[u8]) -> Vec { let mut hasher = Ripemd160::new(); hasher.update(bytes); @@ -25,3 +30,35 @@ pub fn derive_addr(prefix: &str, pubkey_bytes: &[u8]) -> Result Err(err.into()), } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_base64url_encode() { + // Test empty input + assert_eq!(base64url_encode(b""), ""); + + // Test simple string + assert_eq!(base64url_encode(b"hello"), "aGVsbG8"); + + // Test with special characters + assert_eq!(base64url_encode(b"hello world!"), "aGVsbG8gd29ybGQh"); + + // Test binary data + let binary_data = [0x00, 0x01, 0x02, 0x03, 0xFF]; + assert_eq!(base64url_encode(&binary_data), "AAECA_8"); + + // Test longer binary data + let longer_binary = [0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64]; + assert_eq!(base64url_encode(&longer_binary), "SGVsbG8gV29ybGQ"); + + // Test that it produces URL-safe output (no + or / characters, no padding) + let test_data = b"test data with special chars: +/="; + let encoded = base64url_encode(test_data); + assert!(!encoded.contains('+')); + assert!(!encoded.contains('/')); + assert!(!encoded.ends_with('=')); + } +} diff --git a/contracts/account/src/auth/zkemail.rs b/contracts/account/src/auth/zkemail.rs index 07d79cc3..9d05f282 100644 --- a/contracts/account/src/auth/zkemail.rs +++ b/contracts/account/src/auth/zkemail.rs @@ -2,7 +2,7 @@ use crate::error::ContractResult; use cosmwasm_schema::cw_serde; use cosmwasm_std::{from_json, Binary, Deps}; use cosmos_sdk_proto::{ - prost::Message, traits::MessageExt, xion::v1::dkim::{QueryVerifyRequest, QueryVerifyResponse} + prost::Message, traits::MessageExt, xion::v1::dkim::{QueryDkimPubKeysRequest, QueryDkimPubKeysResponse} }; #[cw_serde] @@ -16,7 +16,6 @@ pub struct SnarkJsProof { protocol: String, } - #[cw_serde] pub struct ZKEmailSignature { proof: SnarkJsProof, @@ -26,15 +25,20 @@ pub struct ZKEmailSignature { pub fn verify( deps: Deps, - tx_bytes: &Binary, - sig_bytes: &Binary, + tx_bytes: &str, + sig_bytes: &[u8], email_salt: &str, - dkim_domain: &str, ) -> ContractResult { + + // split the sig_bytes into 2 parts proof and publicOutputs + let sig: ZKEmailSignature = from_json(sig_bytes.to_vec())?; + let proof = sig.proof; + let public_outputs = sig.public_outputs; + let verification_request = QueryVerifyRequest { tx_bytes: tx_bytes.to_vec(), - proof: sig_bytes.to_vec(), - dkim_domain: dkim_domain.to_owned(), + proof: proof.into(), + public_inputs: public_outputs.clone(), email_salt: email_salt.to_owned(), }; let verification_request_byte = verification_request.to_bytes()?; @@ -46,23 +50,7 @@ pub fn verify( let res: QueryVerifyResponse = QueryVerifyResponse::decode(verification_response.as_slice())?; Ok(res.verified) -} - -pub fn extract_email_salt(signature: &Binary) -> ContractResult { - // convert signature to a string - let sig_str = String::from_utf8(signature.to_vec())?; - // get email salt from signature.publicOutputs[32] as a string - let sig: ZKEmailSignature = from_json(sig_str)?; - - // Check if we have enough public outputs - if sig.public_outputs.len() < 33 { - return Err(crate::error::ContractError::Std(cosmwasm_std::StdError::generic_err( - "Insufficient public outputs: expected at least 33 elements", - ))); - } - - let email_salt = sig.public_outputs[32].clone().to_string(); - Ok(email_salt) + // Ok(true) } #[cfg(test)] @@ -252,71 +240,6 @@ mod tests { assert_eq!(signature.public_outputs, round_trip.public_outputs); } - #[test] - fn test_extract_email_salt_success() { - let json_str = sample_signature_json(); - let signature_binary = Binary::from(json_str.as_bytes()); - - let result = extract_email_salt(&signature_binary); - assert!(result.is_ok()); - - let email_salt = result.unwrap(); - // The email salt should be at index 32 of publicOutputs - assert_eq!(email_salt, "8106355043968901587346579634598098765933160394002251948170420219958523220425"); - } - - #[test] - fn test_extract_email_salt_invalid_json() { - let invalid_json = "invalid json"; - let signature_binary = Binary::from(invalid_json.as_bytes()); - - let result = extract_email_salt(&signature_binary); - assert!(result.is_err()); - } - - #[test] - fn test_extract_email_salt_invalid_utf8() { - // Create invalid UTF-8 bytes - let invalid_utf8 = vec![0xff, 0xfe, 0xfd]; - let signature_binary = Binary::from(invalid_utf8); - - let result = extract_email_salt(&signature_binary); - assert!(result.is_err()); - } - - #[test] - fn test_extract_email_salt_missing_field() { - let incomplete_json = r#"{ - "proof": { - "pi_a": ["1", "2", "3"], - "pi_b": [["4", "5"], ["6", "7"], ["8", "9"]], - "pi_c": ["10", "11", "12"], - "protocol": "groth16" - } - }"#; - let signature_binary = Binary::from(incomplete_json.as_bytes()); - - let result = extract_email_salt(&signature_binary); - assert!(result.is_err()); - } - - #[test] - fn test_extract_email_salt_insufficient_public_outputs() { - let json_with_short_outputs = r#"{ - "proof": { - "pi_a": ["1", "2", "3"], - "pi_b": [["4", "5"], ["6", "7"], ["8", "9"]], - "pi_c": ["10", "11", "12"], - "protocol": "groth16" - }, - "publicOutputs": ["1", "2", "3"] - }"#; - let signature_binary = Binary::from(json_with_short_outputs.as_bytes()); - - let result = extract_email_salt(&signature_binary); - assert!(result.is_err()); - } - #[test] fn test_zkemail_signature_field_names() { // Test that the JSON field names match exactly (camelCase vs snake_case) @@ -376,10 +299,9 @@ mod tests { let result = verify( deps.as_ref(), - &Binary::from(env.contract.address.as_bytes()), + &env.contract.address.as_str(), &signature_binary, "any_salt", - "any_domain", ); assert!(result.is_ok()); @@ -395,10 +317,9 @@ mod tests { let result = verify( deps.as_ref(), - &Binary::from(env.contract.address.as_bytes()), + &env.contract.address.as_str(), &signature_binary, "any_salt", - "any_domain", ); assert!(result.is_ok()); @@ -502,43 +423,6 @@ mod tests { assert_eq!(deserialized.public_outputs[99], "output_99"); } - #[test] - fn test_extract_email_salt_with_exact_33_elements() { - // Create a signature with exactly 33 elements (so index 32 exists but barely) - let json_with_33_outputs = r#"{ - "proof": { - "pi_a": ["1", "2", "3"], - "pi_b": [["4", "5"], ["6", "7"], ["8", "9"]], - "pi_c": ["10", "11", "12"], - "protocol": "groth16" - }, - "publicOutputs": ["0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31","email_salt_value"] - }"#; - let signature_binary = Binary::from(json_with_33_outputs.as_bytes()); - - let result = extract_email_salt(&signature_binary); - assert!(result.is_ok()); - assert_eq!(result.unwrap(), "email_salt_value"); - } - - #[test] - fn test_extract_email_salt_with_numeric_values() { - let json_with_numeric_outputs = r#"{ - "proof": { - "pi_a": ["1", "2", "3"], - "pi_b": [["4", "5"], ["6", "7"], ["8", "9"]], - "pi_c": ["10", "11", "12"], - "protocol": "groth16" - }, - "publicOutputs": ["0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31","42"] - }"#; - let signature_binary = Binary::from(json_with_numeric_outputs.as_bytes()); - - let result = extract_email_salt(&signature_binary); - assert!(result.is_ok()); - assert_eq!(result.unwrap(), "42"); - } - #[test] fn test_verify_signature_parsing_edge_cases() { let deps = mock_dependencies(); @@ -558,10 +442,9 @@ mod tests { let result = verify( deps.as_ref(), - &Binary::from(env.contract.address.as_bytes()), + &env.contract.address.as_str(), &signature_binary, "any_salt", - "example.com", ); // Should succeed because verify is hardcoded to return true @@ -593,24 +476,6 @@ mod tests { } } - #[test] - fn test_binary_from_different_sources() { - let json_str = sample_signature_json(); - - // Test Binary created from string bytes - let binary_from_str = Binary::from(json_str.as_bytes()); - let result1 = extract_email_salt(&binary_from_str); - assert!(result1.is_ok()); - - // Test Binary created from Vec - let binary_from_vec = Binary::from(json_str.as_bytes().to_vec()); - let result2 = extract_email_salt(&binary_from_vec); - assert!(result2.is_ok()); - - // Results should be identical - assert_eq!(result1.unwrap(), result2.unwrap()); - } - #[test] fn test_serde_field_name_mapping() { // Test that serde correctly maps camelCase JSON to snake_case Rust fields diff --git a/contracts/account/src/execute.rs b/contracts/account/src/execute.rs index dabffcea..35639bb4 100644 --- a/contracts/account/src/execute.rs +++ b/contracts/account/src/execute.rs @@ -229,14 +229,12 @@ pub fn add_auth_method( } AddAuthenticator::ZKEmail { id, - dkim_domain, + email_salt, signature, } => { // extract email salt from signature - let email_salt = zkemail::extract_email_salt(signature)?; let auth = Authenticator::ZKEmail { - email_salt: email_salt.clone(), - dkim_domain: dkim_domain.clone(), + email_salt: (*email_salt).clone() }; if !auth.verify( deps.as_ref(), @@ -449,7 +447,7 @@ pub mod tests { let mut add_authenticator = AddAuthenticator::ZKEmail { id: 1, - dkim_domain: "gmail.com".to_string(), + email_salt: "test_email_salt".to_string(), signature: signature_binary, }; From 724413045e31bb6e71d26be77bfb12158a6e1f54 Mon Sep 17 00:00:00 2001 From: Kushal7788 Date: Fri, 24 Oct 2025 16:00:46 +0530 Subject: [PATCH 14/19] Updated the Imports for cosmos sdk proto for zk-email auth --- contracts/account/Cargo.toml | 3 +- contracts/account/src/auth.rs | 2 +- contracts/account/src/auth/zkemail.rs | 119 ++++++++++---------------- contracts/account/src/execute.rs | 2 +- 4 files changed, 46 insertions(+), 80 deletions(-) diff --git a/contracts/account/Cargo.toml b/contracts/account/Cargo.toml index 9dbf2226..6e7b479a 100644 --- a/contracts/account/Cargo.toml +++ b/contracts/account/Cargo.toml @@ -31,5 +31,4 @@ base64 = { workspace = true } rsa = { workspace = true } getrandom = { workspace = true } p256 = { workspace = true } -cosmos-sdk-proto = { workspace = true } -# zkemail = {path = "../zkemail", features = ["library"]} \ No newline at end of file +cosmos-sdk-proto = { workspace = true } \ No newline at end of file diff --git a/contracts/account/src/auth.rs b/contracts/account/src/auth.rs index 7e257569..f8f231d3 100644 --- a/contracts/account/src/auth.rs +++ b/contracts/account/src/auth.rs @@ -1,6 +1,6 @@ use crate::auth::secp256r1::verify; use crate::error::ContractError; -use cosmwasm_std::{Addr, Binary, Deps, Env}; +use cosmwasm_std::{Binary, Deps, Env}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; diff --git a/contracts/account/src/auth/zkemail.rs b/contracts/account/src/auth/zkemail.rs index 9d05f282..5cc0e7ca 100644 --- a/contracts/account/src/auth/zkemail.rs +++ b/contracts/account/src/auth/zkemail.rs @@ -2,7 +2,7 @@ use crate::error::ContractResult; use cosmwasm_schema::cw_serde; use cosmwasm_std::{from_json, Binary, Deps}; use cosmos_sdk_proto::{ - prost::Message, traits::MessageExt, xion::v1::dkim::{QueryDkimPubKeysRequest, QueryDkimPubKeysResponse} + prost::Message, traits::MessageExt, xion::v1::dkim::{QueryVerifyRequest, QueryVerifyResponse} }; #[cw_serde] @@ -36,10 +36,10 @@ pub fn verify( let public_outputs = sig.public_outputs; let verification_request = QueryVerifyRequest { - tx_bytes: tx_bytes.to_vec(), - proof: proof.into(), + tx_bytes: tx_bytes.as_bytes().to_vec(), + proof: serde_json::to_vec(&proof)?, public_inputs: public_outputs.clone(), - email_salt: email_salt.to_owned(), + email_hash: email_salt.as_bytes().to_vec(), }; let verification_request_byte = verification_request.to_bytes()?; let verification_response: Binary = deps.querier.query_grpc( @@ -50,16 +50,11 @@ pub fn verify( let res: QueryVerifyResponse = QueryVerifyResponse::decode(verification_response.as_slice())?; Ok(res.verified) - // Ok(true) } #[cfg(test)] mod tests { use super::*; - use cosmwasm_std::{ - testing::{mock_dependencies, mock_env}, - Binary, - }; // Sample test data based on the provided signature fn sample_zkemail_signature() -> ZKEmailSignature { @@ -133,7 +128,6 @@ mod tests { fn sample_signature_json() -> String { r#"{ "proof": { - "curve": "bn128", "pi_a": [ "13359235437905510146488545267580847868768563960781729194939527523243795688772", "16255212479465089639502013432936572417100794023004408906770080834142123006135", @@ -289,43 +283,6 @@ mod tests { assert_eq!(signature.public_outputs[32], "8106355043968901587346579634598098765933160394002251948170420219958523220425"); } - #[test] - fn test_verify_with_hardcoded_return() { - // Since verify() is currently hardcoded to return true, test that behavior - let deps = mock_dependencies(); - let env = mock_env(); - let json_str = sample_signature_json(); - let signature_binary = Binary::from(json_str.as_bytes()); - - let result = verify( - deps.as_ref(), - &env.contract.address.as_str(), - &signature_binary, - "any_salt", - ); - - assert!(result.is_ok()); - assert_eq!(result.unwrap(), true); - } - - #[test] - fn test_verify_with_empty_params() { - let deps = mock_dependencies(); - let env = mock_env(); - let json_str = sample_signature_json(); - let signature_binary = Binary::from(json_str.as_bytes()); - - let result = verify( - deps.as_ref(), - &env.contract.address.as_str(), - &signature_binary, - "any_salt", - ); - - assert!(result.is_ok()); - assert_eq!(result.unwrap(), true); - } - #[test] fn test_snarkjs_proof_with_empty_fields() { let proof = SnarkJsProof { @@ -423,35 +380,6 @@ mod tests { assert_eq!(deserialized.public_outputs[99], "output_99"); } - #[test] - fn test_verify_signature_parsing_edge_cases() { - let deps = mock_dependencies(); - let env = mock_env(); - - // Test with minimal valid JSON - let minimal_json = r#"{ - "proof": { - "pi_a": ["1", "2", "3"], - "pi_b": [["4", "5"], ["6", "7"], ["8", "9"]], - "pi_c": ["10", "11", "12"], - "protocol": "groth16" - }, - "publicOutputs": [] - }"#; - let signature_binary = Binary::from(minimal_json.as_bytes()); - - let result = verify( - deps.as_ref(), - &env.contract.address.as_str(), - &signature_binary, - "any_salt", - ); - - // Should succeed because verify is hardcoded to return true - assert!(result.is_ok()); - assert_eq!(result.unwrap(), true); - } - #[test] fn test_multiple_proof_protocols() { let protocols = vec!["groth16", "plonk", "stark", "custom_protocol"]; @@ -502,4 +430,43 @@ mod tests { assert!(parsed_back.get("publicOutputs").is_some()); assert!(parsed_back.get("public_outputs").is_none()); // Should not exist } + + #[test] + fn test_signature_parsing_from_bytes() { + let json_str = sample_signature_json(); + let sig_bytes = json_str.as_bytes(); + + // Test parsing signature from bytes using from_json + let sig: ZKEmailSignature = from_json(sig_bytes.to_vec()).unwrap(); + + // Verify the parsed signature matches our sample + assert_eq!(sig.proof.protocol, "groth16"); + assert_eq!(sig.public_outputs.len(), 34); + assert_eq!(sig.public_outputs[0], "2018721414038404820327"); + assert_eq!(sig.public_outputs[33], "1"); + } + + #[test] + fn test_query_verify_request_creation() { + let signature = sample_zkemail_signature(); + let tx_bytes = "test_transaction"; + let email_salt = "test_salt"; + + // Test creating QueryVerifyRequest from signature components + let verification_request = QueryVerifyRequest { + tx_bytes: tx_bytes.as_bytes().to_vec(), + proof: serde_json::to_vec(&signature.proof).unwrap(), + public_inputs: signature.public_outputs.clone(), + email_hash: email_salt.as_bytes().to_vec(), + }; + + // Verify the request is properly constructed + assert_eq!(verification_request.tx_bytes, tx_bytes.as_bytes()); + assert_eq!(verification_request.email_hash, email_salt.as_bytes()); + assert_eq!(verification_request.public_inputs, signature.public_outputs); + + // Verify proof serialization + let proof_bytes = serde_json::to_vec(&signature.proof).unwrap(); + assert_eq!(verification_request.proof, proof_bytes); + } } diff --git a/contracts/account/src/execute.rs b/contracts/account/src/execute.rs index 35639bb4..42ce6ef3 100644 --- a/contracts/account/src/execute.rs +++ b/contracts/account/src/execute.rs @@ -2,7 +2,7 @@ use std::borrow::BorrowMut; use cosmwasm_std::{Addr, Binary, Deps, DepsMut, Env, Event, Order, Response}; -use crate::auth::{jwt, passkey, AddAuthenticator, Authenticator, zkemail}; +use crate::auth::{jwt, passkey, AddAuthenticator, Authenticator}; use crate::{ error::{ContractError, ContractResult}, state::AUTHENTICATORS, From 09ec3faf12e230d41243bd92faad215c4360ccd5 Mon Sep 17 00:00:00 2001 From: Kushal7788 Date: Wed, 29 Oct 2025 23:53:43 +0530 Subject: [PATCH 15/19] Custom Coswasm Optimiser Docker Image for compiling the contracts --- Cargo.toml | 2 +- contracts/account/src/auth/zkemail.rs | 2 +- docker-compose.yml | 53 +++++++++++++++++++++++++++ docker/custom_coswam_optimiser.docker | 18 +++++++++ 4 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 docker-compose.yml create mode 100644 docker/custom_coswam_optimiser.docker diff --git a/Cargo.toml b/Cargo.toml index 3bd65c50..17426ea3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,5 +28,5 @@ phf = { version = "0.11.2", features = ["macros"] } rsa = { version = "0.9.2" } getrandom = { version = "0.2.10", features = ["custom"] } p256 = {version = "0.13.2", features = ["ecdsa-core", "arithmetic", "serde"]} -cosmos-sdk-proto = {path ="../../cosmos-rust/cosmos-sdk-proto", default-features = false, features = ["cosmwasm", "xion"]} +cosmos-sdk-proto = {package = "cosmos-sdk-proto", git = "https://github.com/burnt-labs/cosmos-rust", branch = "feat/xion-zk", default-features = false, features = ["cosmwasm", "xion"]} url = "2.5.2" diff --git a/contracts/account/src/auth/zkemail.rs b/contracts/account/src/auth/zkemail.rs index 5cc0e7ca..b9d52004 100644 --- a/contracts/account/src/auth/zkemail.rs +++ b/contracts/account/src/auth/zkemail.rs @@ -43,7 +43,7 @@ pub fn verify( }; let verification_request_byte = verification_request.to_bytes()?; let verification_response: Binary = deps.querier.query_grpc( - "/xion.dkim.v1.Query/ProofVerify".to_string(), + "/xion.dkim.v1.Query/Authenticate".to_string(), Binary::from(verification_request_byte), )?; diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..9dc8f2ac --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,53 @@ +services: + # Build the custom optimizer image + optimizer-build: + build: + context: . + dockerfile: docker/custom_coswam_optimiser.docker + platforms: + - linux/amd64 + platform: linux/amd64 + image: cosmwasm/optimizer-ssh:latest + command: echo "Image built successfully" + + # Service for optimizing the account contract + optimize-account: + platform: linux/amd64 + image: cosmwasm/optimizer-ssh:latest + volumes: + - ./:/code + - ~/.ssh:/root/.ssh:ro + working_dir: /code + command: contracts/account + environment: + - SSH_AUTH_SOCK=/ssh-agent + depends_on: + - optimizer-build + + # Service for optimizing the treasury contract + optimize-treasury: + platform: linux/amd64 + image: cosmwasm/optimizer-ssh:latest + volumes: + - ./:/code + - ~/.ssh:/root/.ssh:ro + working_dir: /code + command: contracts/treasury + environment: + - SSH_AUTH_SOCK=/ssh-agent + depends_on: + - optimizer-build + + # Service for optimizing the user_map contract + optimize-user-map: + platform: linux/amd64 + image: cosmwasm/optimizer-ssh:latest + volumes: + - ./:/code + - ~/.ssh:/root/.ssh:ro + working_dir: /code + command: contracts/user_map + environment: + - SSH_AUTH_SOCK=/ssh-agent + depends_on: + - optimizer-build \ No newline at end of file diff --git a/docker/custom_coswam_optimiser.docker b/docker/custom_coswam_optimiser.docker new file mode 100644 index 00000000..593a1a67 --- /dev/null +++ b/docker/custom_coswam_optimiser.docker @@ -0,0 +1,18 @@ +FROM cosmwasm/optimizer:0.17.0 AS base + +RUN echo "https://dl-cdn.alpinelinux.org/alpine/v3.21/main" > /etc/apk/repositories && \ + echo "https://dl-cdn.alpinelinux.org/alpine/v3.21/community" >> /etc/apk/repositories && \ + apk add --no-cache git openssh-client + +RUN mkdir -p /root/.ssh && chmod 700 /root/.ssh && \ + ssh-keyscan github.com >> /root/.ssh/known_hosts + +# Ensure cargo always uses CLI git for submodules and dependencies +ENV CARGO_NET_GIT_FETCH_WITH_CLI=true +RUN echo 'export CARGO_NET_GIT_FETCH_WITH_CLI=true' >> /etc/profile \ + && echo 'export CARGO_NET_GIT_FETCH_WITH_CLI=true' >> /root/.bashrc + +# Optional: set git config to use SSH instead of HTTPS (for submodules) +RUN git config --global url."ssh://git@github.com/".insteadOf "https://github.com/" + +ENTRYPOINT ["/usr/local/bin/optimize.sh"] \ No newline at end of file From 2707151a54de4f1ce8ef5aa9a021d48c97098116 Mon Sep 17 00:00:00 2001 From: Eduardo Diaz Date: Thu, 6 Nov 2025 15:44:35 -0500 Subject: [PATCH 16/19] use cosmwasm_std to get little endian email_hash --- contracts/account/src/auth/zkemail.rs | 68 +++++++++++++++++---------- 1 file changed, 44 insertions(+), 24 deletions(-) diff --git a/contracts/account/src/auth/zkemail.rs b/contracts/account/src/auth/zkemail.rs index b9d52004..8890085f 100644 --- a/contracts/account/src/auth/zkemail.rs +++ b/contracts/account/src/auth/zkemail.rs @@ -1,9 +1,12 @@ use crate::error::ContractResult; -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{from_json, Binary, Deps}; use cosmos_sdk_proto::{ - prost::Message, traits::MessageExt, xion::v1::dkim::{QueryVerifyRequest, QueryVerifyResponse} + prost::Message, + traits::MessageExt, + xion::v1::dkim::{QueryVerifyRequest, QueryVerifyResponse}, }; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Uint256; +use cosmwasm_std::{from_json, Binary, Deps}; #[cw_serde] pub struct SnarkJsProof { @@ -29,19 +32,25 @@ pub fn verify( sig_bytes: &[u8], email_salt: &str, ) -> ContractResult { - // split the sig_bytes into 2 parts proof and publicOutputs let sig: ZKEmailSignature = from_json(sig_bytes.to_vec())?; let proof = sig.proof; let public_outputs = sig.public_outputs; + // + // Parse as Uint256 and convert to little-endian bytes + let email_salt_uint = Uint256::from_str(email_salt) + .map_err(|_| ContractError::ParseError("Invalid email_salt".to_string()))?; + + let email_hash_bytes = email_salt_uint.to_le_bytes().to_vec(); let verification_request = QueryVerifyRequest { tx_bytes: tx_bytes.as_bytes().to_vec(), proof: serde_json::to_vec(&proof)?, public_inputs: public_outputs.clone(), - email_hash: email_salt.as_bytes().to_vec(), + email_hash: email_hash_bytes, }; - let verification_request_byte = verification_request.to_bytes()?; + + verification_request_byte = verification_request.to_bytes()?; let verification_response: Binary = deps.querier.query_grpc( "/xion.dkim.v1.Query/Authenticate".to_string(), Binary::from(verification_request_byte), @@ -239,11 +248,11 @@ mod tests { // Test that the JSON field names match exactly (camelCase vs snake_case) let json_str = sample_signature_json(); let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap(); - + // Verify the JSON structure has the expected field names assert!(parsed.get("proof").is_some()); assert!(parsed.get("publicOutputs").is_some()); - + let proof = parsed.get("proof").unwrap(); assert!(proof.get("pi_a").is_some()); assert!(proof.get("pi_b").is_some()); @@ -254,18 +263,18 @@ mod tests { #[test] fn test_proof_structure_validation() { let signature = sample_zkemail_signature(); - + // Verify array sizes assert_eq!(signature.proof.pi_a.len(), 3); assert_eq!(signature.proof.pi_b.len(), 3); assert_eq!(signature.proof.pi_c.len(), 3); assert_eq!(signature.public_outputs.len(), 34); - + // Verify nested array structure for row in &signature.proof.pi_b { assert_eq!(row.len(), 2); } - + // Verify protocol assert_eq!(signature.proof.protocol, "groth16"); } @@ -275,12 +284,15 @@ mod tests { // Test access to first element let signature = sample_zkemail_signature(); assert_eq!(signature.public_outputs[0], "2018721414038404820327"); - + // Test access to last element (index 33) assert_eq!(signature.public_outputs[33], "1"); - + // Test access to email salt element (index 32) - assert_eq!(signature.public_outputs[32], "8106355043968901587346579634598098765933160394002251948170420219958523220425"); + assert_eq!( + signature.public_outputs[32], + "8106355043968901587346579634598098765933160394002251948170420219958523220425" + ); } #[test] @@ -308,13 +320,21 @@ mod tests { #[test] fn test_snarkjs_proof_with_special_characters() { let proof = SnarkJsProof { - pi_a: ["123!@#".to_string(), "456$%^".to_string(), "789&*()".to_string()], + pi_a: [ + "123!@#".to_string(), + "456$%^".to_string(), + "789&*()".to_string(), + ], pi_b: [ ["test\\n".to_string(), "test\"quote".to_string()], ["test'single".to_string(), "test\ttab".to_string()], ["test/slash".to_string(), "test\\backslash".to_string()], ], - pi_c: ["unicode🚀".to_string(), "unicode💯".to_string(), "unicode✨".to_string()], + pi_c: [ + "unicode🚀".to_string(), + "unicode💯".to_string(), + "unicode✨".to_string(), + ], protocol: "groth16-custom".to_string(), }; @@ -383,7 +403,7 @@ mod tests { #[test] fn test_multiple_proof_protocols() { let protocols = vec!["groth16", "plonk", "stark", "custom_protocol"]; - + for protocol in protocols { let proof = SnarkJsProof { pi_a: ["1".to_string(), "2".to_string(), "3".to_string()], @@ -418,12 +438,12 @@ mod tests { }"#; let signature: ZKEmailSignature = serde_json::from_str(json_with_camel_case).unwrap(); - + // Verify the struct fields are populated correctly assert_eq!(signature.proof.pi_a, ["1", "2", "3"]); assert_eq!(signature.proof.protocol, "groth16"); assert_eq!(signature.public_outputs, vec!["test_value"]); - + // Verify serialization produces camelCase JSON let serialized = serde_json::to_string(&signature).unwrap(); let parsed_back: serde_json::Value = serde_json::from_str(&serialized).unwrap(); @@ -435,10 +455,10 @@ mod tests { fn test_signature_parsing_from_bytes() { let json_str = sample_signature_json(); let sig_bytes = json_str.as_bytes(); - + // Test parsing signature from bytes using from_json let sig: ZKEmailSignature = from_json(sig_bytes.to_vec()).unwrap(); - + // Verify the parsed signature matches our sample assert_eq!(sig.proof.protocol, "groth16"); assert_eq!(sig.public_outputs.len(), 34); @@ -451,7 +471,7 @@ mod tests { let signature = sample_zkemail_signature(); let tx_bytes = "test_transaction"; let email_salt = "test_salt"; - + // Test creating QueryVerifyRequest from signature components let verification_request = QueryVerifyRequest { tx_bytes: tx_bytes.as_bytes().to_vec(), @@ -459,12 +479,12 @@ mod tests { public_inputs: signature.public_outputs.clone(), email_hash: email_salt.as_bytes().to_vec(), }; - + // Verify the request is properly constructed assert_eq!(verification_request.tx_bytes, tx_bytes.as_bytes()); assert_eq!(verification_request.email_hash, email_salt.as_bytes()); assert_eq!(verification_request.public_inputs, signature.public_outputs); - + // Verify proof serialization let proof_bytes = serde_json::to_vec(&signature.proof).unwrap(); assert_eq!(verification_request.proof, proof_bytes); From 862fce37c1a1ab8a724e4afadbf8a96c55488c5b Mon Sep 17 00:00:00 2001 From: Kushal7788 Date: Thu, 13 Nov 2025 16:25:08 +0530 Subject: [PATCH 17/19] Removed zkemail verifier from the contract and updated params for gRPC call --- contracts/account/src/auth.rs | 2 +- contracts/account/src/auth/zkemail.rs | 103 +++++++------- contracts/account/src/execute.rs | 2 +- contracts/zkemail/Cargo.toml | 32 ----- contracts/zkemail/src/ark_verifier.rs | 190 -------------------------- contracts/zkemail/src/commit.rs | 92 ------------- contracts/zkemail/src/contract.rs | 140 ------------------- contracts/zkemail/src/error.rs | 25 ---- contracts/zkemail/src/groth16.rs | 121 ---------------- contracts/zkemail/src/lib.rs | 21 --- contracts/zkemail/src/msg.rs | 27 ---- contracts/zkemail/src/state.rs | 4 - 12 files changed, 53 insertions(+), 706 deletions(-) delete mode 100644 contracts/zkemail/Cargo.toml delete mode 100644 contracts/zkemail/src/ark_verifier.rs delete mode 100644 contracts/zkemail/src/commit.rs delete mode 100644 contracts/zkemail/src/contract.rs delete mode 100644 contracts/zkemail/src/error.rs delete mode 100644 contracts/zkemail/src/groth16.rs delete mode 100644 contracts/zkemail/src/lib.rs delete mode 100644 contracts/zkemail/src/msg.rs delete mode 100644 contracts/zkemail/src/state.rs diff --git a/contracts/account/src/auth.rs b/contracts/account/src/auth.rs index f8f231d3..561e4516 100644 --- a/contracts/account/src/auth.rs +++ b/contracts/account/src/auth.rs @@ -172,7 +172,7 @@ impl Authenticator { email_salt, } => { let tx_bytes_hash = util::base64url_encode(tx_bytes); - let verification = zkemail::verify(deps, &tx_bytes_hash, sig_bytes, email_salt)?; + let verification = zkemail::verify(deps, tx_bytes_hash.as_bytes(), sig_bytes, email_salt)?; Ok(verification) } } diff --git a/contracts/account/src/auth/zkemail.rs b/contracts/account/src/auth/zkemail.rs index 8890085f..d4fdbf2e 100644 --- a/contracts/account/src/auth/zkemail.rs +++ b/contracts/account/src/auth/zkemail.rs @@ -5,7 +5,6 @@ use cosmos_sdk_proto::{ xion::v1::dkim::{QueryVerifyRequest, QueryVerifyResponse}, }; use cosmwasm_schema::cw_serde; -use cosmwasm_std::Uint256; use cosmwasm_std::{from_json, Binary, Deps}; #[cw_serde] @@ -17,43 +16,39 @@ pub struct SnarkJsProof { #[serde(rename = "pi_c")] pi_c: [String; 3], protocol: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + curve: Option, } #[cw_serde] pub struct ZKEmailSignature { proof: SnarkJsProof, - #[serde(rename = "publicOutputs")] - public_outputs: Vec, + #[serde(rename = "publicInputs")] + public_inputs: Vec, } pub fn verify( deps: Deps, - tx_bytes: &str, + tx_bytes: &[u8], sig_bytes: &[u8], email_salt: &str, ) -> ContractResult { // split the sig_bytes into 2 parts proof and publicOutputs let sig: ZKEmailSignature = from_json(sig_bytes.to_vec())?; let proof = sig.proof; - let public_outputs = sig.public_outputs; - // - // Parse as Uint256 and convert to little-endian bytes - let email_salt_uint = Uint256::from_str(email_salt) - .map_err(|_| ContractError::ParseError("Invalid email_salt".to_string()))?; - - let email_hash_bytes = email_salt_uint.to_le_bytes().to_vec(); + let public_inputs = sig.public_inputs; let verification_request = QueryVerifyRequest { - tx_bytes: tx_bytes.as_bytes().to_vec(), + tx_bytes: tx_bytes.to_vec(), proof: serde_json::to_vec(&proof)?, - public_inputs: public_outputs.clone(), - email_hash: email_hash_bytes, + public_inputs: public_inputs.clone(), + email_hash: email_salt.to_string(), }; - verification_request_byte = verification_request.to_bytes()?; + let verification_request_bytes = verification_request.to_bytes()?; let verification_response: Binary = deps.querier.query_grpc( "/xion.dkim.v1.Query/Authenticate".to_string(), - Binary::from(verification_request_byte), + Binary::from(verification_request_bytes), )?; let res: QueryVerifyResponse = QueryVerifyResponse::decode(verification_response.as_slice())?; @@ -94,8 +89,9 @@ mod tests { "1".to_string(), ], protocol: "groth16".to_string(), + curve: None, }, - public_outputs: vec![ + public_inputs: vec![ "2018721414038404820327".to_string(), "0".to_string(), "0".to_string(), @@ -163,7 +159,7 @@ mod tests { ], "protocol": "groth16" }, - "publicOutputs": [ + "publicInputs": [ "2018721414038404820327", "0", "0", @@ -213,6 +209,7 @@ mod tests { ], pi_c: ["10".to_string(), "11".to_string(), "12".to_string()], protocol: "groth16".to_string(), + curve: None, }; let serialized = serde_json::to_string(&proof).unwrap(); @@ -235,12 +232,12 @@ mod tests { assert_eq!(signature.proof.pi_b, deserialized.proof.pi_b); assert_eq!(signature.proof.pi_c, deserialized.proof.pi_c); assert_eq!(signature.proof.protocol, deserialized.proof.protocol); - assert_eq!(signature.public_outputs, deserialized.public_outputs); + assert_eq!(signature.public_inputs, deserialized.public_inputs); // Test round-trip serialization let serialized = serde_json::to_string(&signature).unwrap(); let round_trip: ZKEmailSignature = serde_json::from_str(&serialized).unwrap(); - assert_eq!(signature.public_outputs, round_trip.public_outputs); + assert_eq!(signature.public_inputs, round_trip.public_inputs); } #[test] @@ -251,8 +248,7 @@ mod tests { // Verify the JSON structure has the expected field names assert!(parsed.get("proof").is_some()); - assert!(parsed.get("publicOutputs").is_some()); - + assert!(parsed.get("publicInputs").is_some()); let proof = parsed.get("proof").unwrap(); assert!(proof.get("pi_a").is_some()); assert!(proof.get("pi_b").is_some()); @@ -268,8 +264,8 @@ mod tests { assert_eq!(signature.proof.pi_a.len(), 3); assert_eq!(signature.proof.pi_b.len(), 3); assert_eq!(signature.proof.pi_c.len(), 3); - assert_eq!(signature.public_outputs.len(), 34); - + assert_eq!(signature.public_inputs.len(), 34); + // Verify nested array structure for row in &signature.proof.pi_b { assert_eq!(row.len(), 2); @@ -283,16 +279,14 @@ mod tests { fn test_public_outputs_boundary_cases() { // Test access to first element let signature = sample_zkemail_signature(); - assert_eq!(signature.public_outputs[0], "2018721414038404820327"); + assert_eq!(signature.public_inputs[0], "2018721414038404820327"); + // Test access to last element (index 33) - assert_eq!(signature.public_outputs[33], "1"); - + assert_eq!(signature.public_inputs[33], "1"); + // Test access to email salt element (index 32) - assert_eq!( - signature.public_outputs[32], - "8106355043968901587346579634598098765933160394002251948170420219958523220425" - ); + assert_eq!(signature.public_inputs[32], "8106355043968901587346579634598098765933160394002251948170420219958523220425"); } #[test] @@ -306,6 +300,7 @@ mod tests { ], pi_c: ["".to_string(), "".to_string(), "".to_string()], protocol: "".to_string(), + curve: None, }; let serialized = serde_json::to_string(&proof).unwrap(); @@ -336,6 +331,7 @@ mod tests { "unicode✨".to_string(), ], protocol: "groth16-custom".to_string(), + curve: None, }; let serialized = serde_json::to_string(&proof).unwrap(); @@ -359,15 +355,16 @@ mod tests { ], pi_c: ["10".to_string(), "11".to_string(), "12".to_string()], protocol: "groth16".to_string(), + curve: None, }, - public_outputs: vec![], + public_inputs: vec![], }; let serialized = serde_json::to_string(&signature).unwrap(); let deserialized: ZKEmailSignature = serde_json::from_str(&serialized).unwrap(); - assert_eq!(signature.public_outputs, deserialized.public_outputs); - assert!(signature.public_outputs.is_empty()); + assert_eq!(signature.public_inputs, deserialized.public_inputs); + assert!(signature.public_inputs.is_empty()); } #[test] @@ -387,17 +384,18 @@ mod tests { ], pi_c: ["10".to_string(), "11".to_string(), "12".to_string()], protocol: "groth16".to_string(), + curve: None, }, - public_outputs: large_outputs.clone(), + public_inputs: large_outputs.clone(), }; let serialized = serde_json::to_string(&signature).unwrap(); let deserialized: ZKEmailSignature = serde_json::from_str(&serialized).unwrap(); - assert_eq!(signature.public_outputs.len(), 100); - assert_eq!(signature.public_outputs, deserialized.public_outputs); - assert_eq!(deserialized.public_outputs[0], "output_0"); - assert_eq!(deserialized.public_outputs[99], "output_99"); + assert_eq!(signature.public_inputs.len(), 100); + assert_eq!(signature.public_inputs, deserialized.public_inputs); + assert_eq!(deserialized.public_inputs[0], "output_0"); + assert_eq!(deserialized.public_inputs[99], "output_99"); } #[test] @@ -414,6 +412,7 @@ mod tests { ], pi_c: ["10".to_string(), "11".to_string(), "12".to_string()], protocol: protocol.to_string(), + curve: None, }; let serialized = serde_json::to_string(&proof).unwrap(); @@ -434,7 +433,7 @@ mod tests { "pi_c": ["10", "11", "12"], "protocol": "groth16" }, - "publicOutputs": ["test_value"] + "publicInputs": ["test_value"] }"#; let signature: ZKEmailSignature = serde_json::from_str(json_with_camel_case).unwrap(); @@ -442,13 +441,13 @@ mod tests { // Verify the struct fields are populated correctly assert_eq!(signature.proof.pi_a, ["1", "2", "3"]); assert_eq!(signature.proof.protocol, "groth16"); - assert_eq!(signature.public_outputs, vec!["test_value"]); - + assert_eq!(signature.public_inputs, vec!["test_value"]); + // Verify serialization produces camelCase JSON let serialized = serde_json::to_string(&signature).unwrap(); let parsed_back: serde_json::Value = serde_json::from_str(&serialized).unwrap(); - assert!(parsed_back.get("publicOutputs").is_some()); - assert!(parsed_back.get("public_outputs").is_none()); // Should not exist + assert!(parsed_back.get("publicInputs").is_some()); + assert!(parsed_back.get("public_inputs").is_none()); // Should not exist } #[test] @@ -461,9 +460,9 @@ mod tests { // Verify the parsed signature matches our sample assert_eq!(sig.proof.protocol, "groth16"); - assert_eq!(sig.public_outputs.len(), 34); - assert_eq!(sig.public_outputs[0], "2018721414038404820327"); - assert_eq!(sig.public_outputs[33], "1"); + assert_eq!(sig.public_inputs.len(), 34); + assert_eq!(sig.public_inputs[0], "2018721414038404820327"); + assert_eq!(sig.public_inputs[33], "1"); } #[test] @@ -476,15 +475,15 @@ mod tests { let verification_request = QueryVerifyRequest { tx_bytes: tx_bytes.as_bytes().to_vec(), proof: serde_json::to_vec(&signature.proof).unwrap(), - public_inputs: signature.public_outputs.clone(), - email_hash: email_salt.as_bytes().to_vec(), + public_inputs: signature.public_inputs.clone(), + email_hash: email_salt.to_string(), }; // Verify the request is properly constructed assert_eq!(verification_request.tx_bytes, tx_bytes.as_bytes()); - assert_eq!(verification_request.email_hash, email_salt.as_bytes()); - assert_eq!(verification_request.public_inputs, signature.public_outputs); - + assert_eq!(verification_request.email_hash, email_salt.to_string()); + assert_eq!(verification_request.public_inputs, signature.public_inputs); + // Verify proof serialization let proof_bytes = serde_json::to_vec(&signature.proof).unwrap(); assert_eq!(verification_request.proof, proof_bytes); diff --git a/contracts/account/src/execute.rs b/contracts/account/src/execute.rs index 42ce6ef3..9d1c7132 100644 --- a/contracts/account/src/execute.rs +++ b/contracts/account/src/execute.rs @@ -440,7 +440,7 @@ pub mod tests { "pi_c": ["10", "11", "12"], "protocol": "groth16" }, - "publicOutputs": ["1", "2", "3"] + "publicInputs": ["1", "2", "3"] }"#; let signature_binary = Binary::from(invalid_signature_json.as_bytes()); diff --git a/contracts/zkemail/Cargo.toml b/contracts/zkemail/Cargo.toml deleted file mode 100644 index ca9956c2..00000000 --- a/contracts/zkemail/Cargo.toml +++ /dev/null @@ -1,32 +0,0 @@ -[package] -name = "zkemail" -version = "0.1.0" -edition = "2021" - -[features] -# enable feature if you want to disable entry points -library = [] - - -[dependencies] -cosmwasm-schema = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -cw-storage-plus = { workspace = true } -cosmwasm-std = { workspace = true } -cw2 = { workspace = true } -thiserror = { workspace = true } -base64 = { workspace = true } -cosmos-sdk-proto = { workspace = true } -getrandom = { workspace = true } - -ark-crypto-primitives = { version = "=0.4.0" } -ark-ec = { version = "=0.4.2", default-features = false } -ark-ff = { version = "=0.4.2", default-features = false, features = [ "asm"] } -ark-std = { version = "=0.4.0", default-features = false } -ark-bn254 = { version = "=0.4.0" } -ark-groth16 = { version = "=0.4.0", default-features = false } -ark-relations = { version = "=0.4.0", default-features = false } -ark-serialize = { version = "=0.4.2", default-features = false } -ark-poly = { version = "=0.4.2", default-features = false } -poseidon-ark = {git = "https://github.com/arnaucube/poseidon-ark"} \ No newline at end of file diff --git a/contracts/zkemail/src/ark_verifier.rs b/contracts/zkemail/src/ark_verifier.rs deleted file mode 100644 index 0599d9fa..00000000 --- a/contracts/zkemail/src/ark_verifier.rs +++ /dev/null @@ -1,190 +0,0 @@ -use ark_bn254::Bn254; -use ark_bn254::Config; -use ark_bn254::Fq2; -use ark_bn254::FrConfig; -use ark_bn254::G1Affine; -use ark_bn254::G2Affine; - -use crate::groth16::CircomReduction; -use ark_ec::bn::Bn; -use ark_ff::Fp; -use ark_ff::MontBackend; -use ark_groth16::Groth16; -use ark_groth16::Proof; -use ark_groth16::VerifyingKey; -use cosmwasm_schema::cw_serde; -use std::fs; -use std::ops::Deref; -use std::str::FromStr; - -pub type GrothBn = Groth16; -pub type GrothBnProof = Proof>; -pub type GrothBnVkey = VerifyingKey; -pub type GrothFp = Fp, 4>; - -#[cw_serde] -pub struct SnarkJsProof { - pi_a: [String; 3], - pi_b: [[String; 2]; 3], - pi_c: [String; 3], -} - -#[cw_serde] -pub struct SnarkJsVkey { - vk_alpha_1: [String; 3], - vk_beta_2: [[String; 2]; 3], - vk_gamma_2: [[String; 2]; 3], - vk_delta_2: [[String; 2]; 3], - IC: Vec<[String; 3]>, -} - -#[derive(Debug)] -pub struct PublicInputs { - inputs: [GrothFp; N], -} - -pub trait JsonDecoder { - fn from_json(json: &str) -> Self; - fn from_json_file(file_path: &str) -> Self - where - Self: Sized, - { - let json = fs::read_to_string(file_path).unwrap(); - Self::from_json(&json) - } -} - -impl JsonDecoder for GrothBnProof { - fn from_json(json: &str) -> Self { - let snarkjs_proof: SnarkJsProof = serde_json::from_str(json).unwrap(); - snarkjs_proof.into() - } -} - -impl From for GrothBnProof { - fn from(val: SnarkJsProof) -> Self { - let a = G1Affine { - x: Fp::from_str(val.pi_a[0].as_str()).unwrap(), - y: Fp::from_str(val.pi_a[1].as_str()).unwrap(), - infinity: false, - }; - let b = G2Affine { - x: Fq2::new( - Fp::from_str(val.pi_b[0][0].as_str()).unwrap(), - Fp::from_str(val.pi_b[0][1].as_str()).unwrap(), - ), - y: Fq2::new( - Fp::from_str(val.pi_b[1][0].as_str()).unwrap(), - Fp::from_str(val.pi_b[1][1].as_str()).unwrap(), - ), - infinity: false, - }; - let c = G1Affine { - x: Fp::from_str(val.pi_c[0].as_str()).unwrap(), - y: Fp::from_str(val.pi_c[1].as_str()).unwrap(), - infinity: false, - }; - Proof { a, b, c } - } -} - -impl JsonDecoder for GrothBnVkey { - fn from_json(json: &str) -> Self { - let snarkjs_vkey: SnarkJsVkey = serde_json::from_str(json).unwrap(); - snarkjs_vkey.into() - } -} - -impl From for GrothBnVkey { - fn from(val: SnarkJsVkey) -> Self { - let vk_alpha_1 = G1Affine { - x: Fp::from_str(val.vk_alpha_1[0].as_str()).unwrap(), - y: Fp::from_str(val.vk_alpha_1[1].as_str()).unwrap(), - infinity: false, - }; - let vk_beta_2 = G2Affine { - x: Fq2::new( - Fp::from_str(val.vk_beta_2[0][0].as_str()).unwrap(), - Fp::from_str(val.vk_beta_2[0][1].as_str()).unwrap(), - ), - y: Fq2::new( - Fp::from_str(val.vk_beta_2[1][0].as_str()).unwrap(), - Fp::from_str(val.vk_beta_2[1][1].as_str()).unwrap(), - ), - infinity: false, - }; - let vk_gamma_2 = G2Affine { - x: Fq2::new( - Fp::from_str(val.vk_gamma_2[0][0].as_str()).unwrap(), - Fp::from_str(val.vk_gamma_2[0][1].as_str()).unwrap(), - ), - y: Fq2::new( - Fp::from_str(val.vk_gamma_2[1][0].as_str()).unwrap(), - Fp::from_str(val.vk_gamma_2[1][1].as_str()).unwrap(), - ), - infinity: false, - }; - let vk_delta_2 = G2Affine { - x: Fq2::new( - Fp::from_str(val.vk_delta_2[0][0].as_str()).unwrap(), - Fp::from_str(val.vk_delta_2[0][1].as_str()).unwrap(), - ), - y: Fq2::new( - Fp::from_str(val.vk_delta_2[1][0].as_str()).unwrap(), - Fp::from_str(val.vk_delta_2[1][1].as_str()).unwrap(), - ), - infinity: false, - }; - - let ic = val - .IC - .iter() - .map(|ic| G1Affine { - x: Fp::from_str(ic[0].as_str()).unwrap(), - y: Fp::from_str(ic[1].as_str()).unwrap(), - infinity: false, - }) - .collect(); - - VerifyingKey { - alpha_g1: vk_alpha_1, - beta_g2: vk_beta_2, - gamma_g2: vk_gamma_2, - delta_g2: vk_delta_2, - gamma_abc_g1: ic, - } - } -} - -impl JsonDecoder for PublicInputs { - fn from_json(json: &str) -> Self { - let inputs: Vec = serde_json::from_str(json).unwrap(); - let inputs: Vec = inputs - .iter() - .map(|input| Fp::from_str(input).unwrap()) - .collect(); - Self { - inputs: inputs.try_into().unwrap(), - } - } -} - -impl PublicInputs { - pub fn from(inputs: [&str; N]) -> Self { - let inputs: Vec = inputs - .iter() - .map(|input| Fp::from_str(input).unwrap()) - .collect(); - Self { - inputs: inputs.try_into().unwrap(), - } - } -} - -impl Deref for PublicInputs { - type Target = [GrothFp]; - - fn deref(&self) -> &Self::Target { - &self.inputs - } -} diff --git a/contracts/zkemail/src/commit.rs b/contracts/zkemail/src/commit.rs deleted file mode 100644 index 9440a6ff..00000000 --- a/contracts/zkemail/src/commit.rs +++ /dev/null @@ -1,92 +0,0 @@ -use crate::ark_verifier::GrothFp; -use ark_ff::*; - -const EMAIL_MAX_BYTES: usize = 256; -const TX_BODY_MAX_BYTES: usize = 512; - -pub fn calculate_email_commitment(salt: &str, email: &str) -> GrothFp { - let padded_salt_bytes = pad_bytes(salt.as_bytes(), 31); - let padded_email_bytes = pad_bytes(email.as_bytes(), EMAIL_MAX_BYTES); - let mut salt = pack_bytes_into_fields(padded_salt_bytes); - let email = pack_bytes_into_fields(padded_email_bytes); - salt.extend(email); - let poseidon = poseidon_ark::Poseidon::new(); - poseidon.hash(salt).unwrap() -} - -pub fn calculate_tx_body_commitment(tx: &str) -> GrothFp { - let padded_tx_bytes = pad_bytes(tx.as_bytes(), TX_BODY_MAX_BYTES); - let tx = pack_bytes_into_fields(padded_tx_bytes); - let poseidon = poseidon_ark::Poseidon::new(); - let mut commitment = GrothFp::zero(); // Initialize commitment with an initial value - - tx.chunks(16).enumerate().for_each(|(i, chunk)| { - let chunk_commitment = poseidon.hash(chunk.to_vec()).unwrap(); - commitment = if i == 0 { - chunk_commitment - } else { - poseidon.hash(vec![commitment, chunk_commitment]).unwrap() - }; - }); - - commitment -} - -fn pack_bytes_into_fields(bytes: Vec) -> Vec { - // convert each 31 bytes into one field element - let mut fields = vec![]; - bytes.chunks(31).for_each(|chunk| { - fields.push(GrothFp::from_le_bytes_mod_order(chunk)); - }); - fields -} - -fn pad_bytes(bytes: &[u8], length: usize) -> Vec { - let mut padded = bytes.to_vec(); - let padding = length - bytes.len(); - for _ in 0..padding { - padded.push(0); - } - padded -} - -#[cfg(test)] -mod tests { - use super::*; - use std::str::FromStr; - - #[test] - fn should_calculate_email_commitment() { - let salt_str = "XRhMS5Nc2dTZW5kEpAB"; - let email_str = "thezdev1@gmail.com"; - - let commitment = calculate_email_commitment(&salt_str, &email_str); - - assert_eq!( - commitment, - Fp::from_str( - "20222897760242655042591071331570003228637614099423116142933693104079157558229" - ) - .unwrap() - ); - } - - #[test] - fn should_calculate_tx_body_commitment() { - let tx_body = "CrQBCrEBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEpABCj94aW9uMWd2cDl5djZndDBwcmdzc3\ - ZueWNudXpnZWszZmtyeGxsZnhxaG0wNzYwMmt4Zmc4dXI2NHNuMnAycDkSP3hpb24xNGNuMG40ZjM4ODJzZ3B2NWQ5ZzA2dzNxN3hzZ\ - m51N3B1enltZDk5ZTM3ZHAwemQ4bTZscXpwemwwbRoMCgV1eGlvbhIDMTAwEmEKTQpDCh0vYWJzdHJhY3RhY2NvdW50LnYxLk5pbFB1\ - YktleRIiCiBDAlIzSFvCNEIMmTE+CRm0U2Gb/0mBfb/aeqxkoPweqxIECgIIARh/EhAKCgoFdXhpb24SATAQwJoMGg54aW9uLXRlc3R\ - uZXQtMSCLjAo="; - - let commitment = calculate_tx_body_commitment(&tx_body); - - assert_eq!( - commitment, - Fp::from_str( - "21532090391056315603450239923154193952164369422267200983793686866358632420524" - ) - .unwrap() - ); - } -} diff --git a/contracts/zkemail/src/contract.rs b/contracts/zkemail/src/contract.rs deleted file mode 100644 index 42206f93..00000000 --- a/contracts/zkemail/src/contract.rs +++ /dev/null @@ -1,140 +0,0 @@ -use crate::ark_verifier::{SnarkJsProof, SnarkJsVkey}; -use crate::commit::calculate_tx_body_commitment; -use crate::error::ContractError::InvalidDkim; -use crate::error::ContractResult; -use crate::groth16::{GrothBn, GrothFp}; -use crate::msg::QueryMsg::VKey; -use crate::msg::{InstantiateMsg, QueryMsg}; -use crate::state::VKEY; -use crate::{CONTRACT_NAME, CONTRACT_VERSION}; -use ark_crypto_primitives::snark::SNARK; -use ark_ff::Zero; -use ark_serialize::CanonicalDeserialize; -use base64::Engine; -use base64::engine::general_purpose::STANDARD_NO_PAD; -use cosmos_sdk_proto::prost::Message; -use cosmos_sdk_proto::traits::MessageExt; -use cosmos_sdk_proto::xion::v1::dkim::{QueryDkimPubKeysRequest, QueryDkimPubKeysResponse}; -use cosmwasm_std::{ - Binary, Deps, DepsMut, Env, Event, MessageInfo, Response, Storage, entry_point, to_json_binary, -}; - -#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)] -pub fn instantiate( - deps: DepsMut, - env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> ContractResult { - cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - init(deps, env, msg.vkey) -} - -pub fn init(deps: DepsMut, env: Env, vkey: SnarkJsVkey) -> ContractResult { - VKEY.save(deps.storage, &vkey)?; - - Ok( - Response::new().add_event(Event::new("create_abstract_account").add_attributes(vec![ - ("contract_address", env.contract.address.to_string()), - ("vkey", serde_json::to_string(&vkey)?), - ])), - ) -} - -#[entry_point] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> ContractResult { - match msg { - VKey {} => query_vkey(deps.storage), - QueryMsg::Verify { - proof, - tx_bytes, - email_hash, - dkim_domain, - dkim_hash, - } => query_verify( - deps, - *proof, - &tx_bytes, - &email_hash, - &dkim_domain, - &dkim_hash, - ), - } -} - -fn query_vkey(store: &dyn Storage) -> ContractResult { - let vkey = VKEY.load(store)?; - Ok(to_json_binary(&vkey)?) -} - -fn query_verify( - deps: Deps, - proof: SnarkJsProof, - tx_bytes: &Binary, - email_hash: &Binary, - dkim_domain: &String, - dkim_hash: &Binary, -) -> ContractResult { - let vkey = VKEY.load(deps.storage)?; - - // verify that domain+hash are known in chain state - let query = QueryDkimPubKeysRequest { - selector: "".to_string(), // do not filter on selector - domain: dkim_domain.to_string(), - poseidon_hash: dkim_hash.to_vec(), - pagination: None, - }; - let query_bz = query.to_bytes()?; - let query_response = deps.querier.query_grpc( - String::from("/xion.dkim.v1.Query/QueryDkimPubKeys"), - Binary::new(query_bz), - )?; - let query_response = QueryDkimPubKeysResponse::decode(query_response.as_slice())?; - if query_response.dkim_pub_keys.is_empty() { - return Err(InvalidDkim); - } - - // inputs are tx body, email hash, and dmarc key hash - let mut inputs: [GrothFp; 3] = [GrothFp::zero(); 3]; - - // tx body input - let tx_input = calculate_tx_body_commitment(STANDARD_NO_PAD.encode(tx_bytes).as_str()); - inputs[0] = tx_input; - - // email hash input, compressed at authenticator registration - let email_hash_input = GrothFp::deserialize_compressed(email_hash.as_slice())?; - inputs[1] = email_hash_input; - - // verify the dkim pubkey hash in the proof output. the poseidon hash is - // from the tx, we can't be sure if it was properly formatted - inputs[2] = GrothFp::deserialize_compressed(dkim_hash.as_slice())?; - - let verified = GrothBn::verify(&vkey.into(), inputs.as_slice(), &proof.into())?; - - Ok(to_json_binary(&verified)?) -} - -#[cfg(test)] -mod tests { - use std::str::FromStr; - - use cosmwasm_std::{testing::MockApi, Uint256}; - - use super::*; - - #[test] - fn verifying_zkemail_signature() { - let api = MockApi::default(); - - // build tx bytes to sign - - // load proof from previously sent and proved email - - // assign email salt from email used to prove - - // mock api for querying dkim module - - // submit data for verification - - } -} diff --git a/contracts/zkemail/src/error.rs b/contracts/zkemail/src/error.rs deleted file mode 100644 index 1b2d401a..00000000 --- a/contracts/zkemail/src/error.rs +++ /dev/null @@ -1,25 +0,0 @@ -#[derive(Debug, thiserror::Error)] -pub enum ContractError { - #[error(transparent)] - Std(#[from] cosmwasm_std::StdError), - - #[error(transparent)] - SerdeJSON(#[from] serde_json::Error), - - #[error("r1cs synthesis error")] - R1CS(#[from] ark_relations::r1cs::SynthesisError), - - #[error(transparent)] - ArkSerialization(#[from] ark_serialize::SerializationError), - - #[error("dkim invalid")] - InvalidDkim, - - #[error(transparent)] - EncodeError(#[from] cosmos_sdk_proto::prost::EncodeError), - - #[error(transparent)] - DecodeError(#[from] cosmos_sdk_proto::prost::DecodeError), -} - -pub type ContractResult = Result; diff --git a/contracts/zkemail/src/groth16.rs b/contracts/zkemail/src/groth16.rs deleted file mode 100644 index d796d9e2..00000000 --- a/contracts/zkemail/src/groth16.rs +++ /dev/null @@ -1,121 +0,0 @@ -use ark_bn254::{Bn254, Config, FrConfig}; -use ark_ec::bn::Bn; -use ark_ff::Fp; -use ark_ff::MontBackend; -use ark_ff::PrimeField; -use ark_groth16::r1cs_to_qap::{LibsnarkReduction, R1CSToQAP, evaluate_constraint}; -use ark_groth16::{Groth16, Proof, VerifyingKey}; -use ark_poly::EvaluationDomain; -use ark_relations::r1cs::{ConstraintMatrices, ConstraintSystemRef, SynthesisError}; -use ark_std::{cfg_into_iter, cfg_iter, cfg_iter_mut, vec}; - -// Developer's Note: -// This has been copied over from the ark-circom package, which focuses on -// proving and verifying in arkworks using circom. It has many dependencies on -// wasmer/ethers/js that we do not need, if we only want to verify existing proofs - -pub type GrothBnVkey = VerifyingKey; -pub type GrothBnProof = Proof>; -pub type GrothBn = Groth16; -pub type GrothFp = Fp, 4>; - -/// Implements the witness map used by snarkjs. The arkworks witness map calculates the -/// coefficients of H through computing (AB-C)/Z in the evaluation domain and going back to the -/// coefficient's domain. snarkjs instead precomputes the Lagrange form of the powers of tau bases -/// in a domain twice as large and the witness map is computed as the odd coefficients of (AB-C) -/// in that domain. This serves as HZ when computing the C proof element. -pub struct CircomReduction; - -impl R1CSToQAP for CircomReduction { - #[allow(clippy::type_complexity)] - fn instance_map_with_evaluation>( - cs: ConstraintSystemRef, - t: &F, - ) -> Result<(Vec, Vec, Vec, F, usize, usize), SynthesisError> { - LibsnarkReduction::instance_map_with_evaluation::(cs, t) - } - - fn witness_map_from_matrices>( - matrices: &ConstraintMatrices, - num_inputs: usize, - num_constraints: usize, - full_assignment: &[F], - ) -> Result, SynthesisError> { - let zero = F::zero(); - let domain = - D::new(num_constraints + num_inputs).ok_or(SynthesisError::PolynomialDegreeTooLarge)?; - let domain_size = domain.size(); - - let mut a = vec![zero; domain_size]; - let mut b = vec![zero; domain_size]; - - cfg_iter_mut!(a[..num_constraints]) - .zip(cfg_iter_mut!(b[..num_constraints])) - .zip(cfg_iter!(&matrices.a)) - .zip(cfg_iter!(&matrices.b)) - .for_each(|(((a, b), at_i), bt_i)| { - *a = evaluate_constraint(at_i, full_assignment); - *b = evaluate_constraint(bt_i, full_assignment); - }); - - { - let start = num_constraints; - let end = start + num_inputs; - a[start..end].clone_from_slice(&full_assignment[..num_inputs]); - } - - let mut c = vec![zero; domain_size]; - cfg_iter_mut!(c[..num_constraints]) - .zip(&a) - .zip(&b) - .for_each(|((c_i, &a), &b)| { - *c_i = a * b; - }); - - domain.ifft_in_place(&mut a); - domain.ifft_in_place(&mut b); - - let root_of_unity = { - let domain_size_double = 2 * domain_size; - let domain_double = - D::new(domain_size_double).ok_or(SynthesisError::PolynomialDegreeTooLarge)?; - domain_double.element(1) - }; - D::distribute_powers_and_mul_by_const(&mut a, root_of_unity, F::one()); - D::distribute_powers_and_mul_by_const(&mut b, root_of_unity, F::one()); - - domain.fft_in_place(&mut a); - domain.fft_in_place(&mut b); - - let mut ab = domain.mul_polynomials_in_evaluation_domain(&a, &b); - drop(a); - drop(b); - - domain.ifft_in_place(&mut c); - D::distribute_powers_and_mul_by_const(&mut c, root_of_unity, F::one()); - domain.fft_in_place(&mut c); - - cfg_iter_mut!(ab) - .zip(c) - .for_each(|(ab_i, c_i)| *ab_i -= &c_i); - - Ok(ab) - } - - fn h_query_scalars>( - max_power: usize, - t: F, - _: F, - delta_inverse: F, - ) -> Result, SynthesisError> { - // the usual H query has domain-1 powers. Z has domain powers. So HZ has 2*domain-1 powers. - let mut scalars = cfg_into_iter!(0..2 * max_power + 1) - .map(|i| delta_inverse * t.pow([i as u64])) - .collect::>(); - let domain_size = scalars.len(); - let domain = D::new(domain_size).ok_or(SynthesisError::PolynomialDegreeTooLarge)?; - // generate the lagrange coefficients - domain.ifft_in_place(&mut scalars); - Ok(cfg_into_iter!(scalars).skip(1).step_by(2).collect()) - } -} diff --git a/contracts/zkemail/src/lib.rs b/contracts/zkemail/src/lib.rs deleted file mode 100644 index 02abb5f5..00000000 --- a/contracts/zkemail/src/lib.rs +++ /dev/null @@ -1,21 +0,0 @@ -pub mod ark_verifier; -pub mod commit; -pub mod contract; -mod error; -mod groth16; -pub mod msg; -mod state; - -pub const CONTRACT_NAME: &str = "zkemail-verifier"; -pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -// the random function must be disabled in cosmwasm -use core::num::NonZeroU32; -use getrandom::Error; - -pub fn always_fail(_buf: &mut [u8]) -> Result<(), Error> { - let code = NonZeroU32::new(Error::CUSTOM_START).unwrap(); - Err(Error::from(code)) -} -use getrandom::register_custom_getrandom; -register_custom_getrandom!(always_fail); \ No newline at end of file diff --git a/contracts/zkemail/src/msg.rs b/contracts/zkemail/src/msg.rs deleted file mode 100644 index 4d86af31..00000000 --- a/contracts/zkemail/src/msg.rs +++ /dev/null @@ -1,27 +0,0 @@ -use crate::ark_verifier::{SnarkJsProof, SnarkJsVkey}; -use cosmwasm_schema::{QueryResponses, cw_serde}; -use cosmwasm_std::Binary; - -#[cw_serde] -pub struct InstantiateMsg { - pub vkey: SnarkJsVkey, -} - -#[cw_serde] -pub enum ExecuteMsg {} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - #[returns(Binary)] - Verify { - proof: Box, - dkim_domain: String, - tx_bytes: Binary, - email_hash: Binary, - dkim_hash: Binary, - }, - - #[returns(Binary)] - VKey {}, -} diff --git a/contracts/zkemail/src/state.rs b/contracts/zkemail/src/state.rs deleted file mode 100644 index 7b8b1ff2..00000000 --- a/contracts/zkemail/src/state.rs +++ /dev/null @@ -1,4 +0,0 @@ -use crate::ark_verifier::SnarkJsVkey; -use cw_storage_plus::Item; - -pub const VKEY: Item = Item::new("vkey"); From f5ef0a3885756c7afb61b0c5e745681966507f1e Mon Sep 17 00:00:00 2001 From: Kushal7788 Date: Thu, 20 Nov 2025 16:20:24 +0530 Subject: [PATCH 18/19] Added allowed-email-hosts field in zk-email auth to whitelist hosts able to generate the signatures --- Cargo.lock | 540 ++++++++++++++++++++------ Cargo.toml | 1 + contracts/account/Cargo.toml | 1 + contracts/account/src/auth.rs | 5 +- contracts/account/src/auth/zkemail.rs | 5 + contracts/account/src/contract.rs | 15 +- contracts/account/src/error.rs | 9 + contracts/account/src/execute.rs | 426 +++++++++++++++++++- contracts/account/src/msg.rs | 12 + 9 files changed, 882 insertions(+), 132 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8e87368f..aba7308c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,6 +14,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -22,9 +31,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anyhow" -version = "1.0.99" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "ark-bls12-381" @@ -189,6 +198,12 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + [[package]] name = "block-buffer" version = "0.10.4" @@ -212,18 +227,27 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" dependencies = [ "serde", ] [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "num-traits", +] [[package]] name = "const-oid" @@ -231,6 +255,19 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "cosmos-sdk-proto" +version = "0.23.0-pre" +source = "git+https://github.com/burnt-labs/cosmos-rust?branch=feat%2Fxion-zk#b7c2358d00fd51f258ba66c9d753d4cf71da2e41" +dependencies = [ + "pbjson", + "pbjson-types", + "prost", + "prost-types", + "serde", + "tendermint-proto", +] + [[package]] name = "cosmwasm-core" version = "2.2.2" @@ -269,7 +306,7 @@ checksum = "a782b93fae93e57ca8ad3e9e994e784583f5933aeaaa5c80a545c4b437be2047" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", ] [[package]] @@ -293,7 +330,7 @@ checksum = "e01c9214319017f6ebd8e299036e1f717fa9bb6724e758f7d6fb2477599d1a29" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", ] [[package]] @@ -406,7 +443,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", ] [[package]] @@ -448,9 +485,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", ] @@ -483,7 +520,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", "unicode-xid", ] @@ -507,7 +544,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", ] [[package]] @@ -537,21 +574,25 @@ version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ + "pkcs8", + "serde", "signature", ] [[package]] name = "ed25519-zebra" -version = "4.0.3" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d9ce6874da5d4415896cd45ffbc4d1cfc0c4f9c079427bd870742c30f2f65a9" +checksum = "0017d969298eec91e3db7a2985a8cab4df6341d86e6f3a6f5878b13fb7846bc9" dependencies = [ "curve25519-dalek", "ed25519", - "hashbrown 0.14.5", - "hex", + "hashbrown 0.15.5", + "pkcs8", "rand_core", + "serde", "sha2", + "subtle", "zeroize", ] @@ -582,6 +623,28 @@ dependencies = [ "zeroize", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "ff" version = "0.13.1" @@ -598,6 +661,12 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flex-error" version = "0.4.4" @@ -607,6 +676,12 @@ dependencies = [ "paste", ] +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -618,9 +693,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", @@ -638,6 +713,18 @@ dependencies = [ "wasi", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + [[package]] name = "group" version = "0.13.0" @@ -660,14 +747,27 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "ahash", "allocator-api2", + "equivalent", + "foldhash", ] +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hex" version = "0.4.3" @@ -685,9 +785,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ "displaydoc", "potential_utf", @@ -698,9 +798,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", @@ -711,11 +811,10 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", @@ -726,42 +825,38 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" dependencies = [ - "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", - "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" [[package]] name = "icu_provider" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", "icu_locale_core", - "stable_deref_trait", - "tinystr", "writeable", "yoke", "zerofrom", @@ -791,13 +886,13 @@ dependencies = [ ] [[package]] -name = "informalsystems-pbjson" -version = "0.7.0" +name = "indexmap" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa4a0980c8379295100d70854354e78df2ee1c6ca0f96ffe89afeb3140e3a3d" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ - "base64 0.21.7", - "serde", + "equivalent", + "hashbrown 0.16.0", ] [[package]] @@ -809,6 +904,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.14.0" @@ -847,9 +951,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.175" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libm" @@ -857,17 +961,35 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + [[package]] name = "litemap" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" [[package]] name = "num-bigint" @@ -881,11 +1003,10 @@ dependencies = [ [[package]] name = "num-bigint-dig" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" dependencies = [ - "byteorder", "lazy_static", "libm", "num-integer", @@ -957,6 +1078,43 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pbjson" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e6349fa080353f4a597daffd05cb81572a9c031a6d4fff7e504947496fcc68" +dependencies = [ + "base64 0.21.7", + "serde", +] + +[[package]] +name = "pbjson-build" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eea3058763d6e656105d1403cb04e0a41b7bbac6362d413e7c33be0c32279c9" +dependencies = [ + "heck", + "itertools 0.13.0", + "prost", + "prost-types", +] + +[[package]] +name = "pbjson-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e54e5e7bfb1652f95bc361d76f3c780d8e526b134b85417e774166ee941f0887" +dependencies = [ + "bytes", + "chrono", + "pbjson", + "pbjson-build", + "prost", + "prost-build", + "serde", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -972,6 +1130,16 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "pkcs1" version = "0.7.5" @@ -995,9 +1163,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ "zerovec", ] @@ -1017,6 +1185,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.110", +] + [[package]] name = "primeorder" version = "0.13.6" @@ -1029,9 +1207,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -1046,6 +1224,26 @@ dependencies = [ "prost-derive", ] +[[package]] +name = "prost-build" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" +dependencies = [ + "heck", + "itertools 0.14.0", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 2.0.110", + "tempfile", +] + [[package]] name = "prost-derive" version = "0.13.5" @@ -1056,18 +1254,33 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.8.5" @@ -1094,7 +1307,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.16", ] [[package]] @@ -1117,6 +1330,35 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + [[package]] name = "rfc6979" version = "0.4.0" @@ -1160,9 +1402,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" dependencies = [ "const-oid", "digest", @@ -1187,6 +1429,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "ryu" version = "1.0.20" @@ -1214,7 +1469,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.106", + "syn 2.0.110", ] [[package]] @@ -1240,9 +1495,9 @@ checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" -version = "1.0.225" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", @@ -1269,22 +1524,22 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.225" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.225" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", ] [[package]] @@ -1295,7 +1550,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", ] [[package]] @@ -1366,9 +1621,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "static_assertions" @@ -1404,9 +1659,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.106" +version = "2.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" dependencies = [ "proc-macro2", "quote", @@ -1421,18 +1676,32 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", +] + +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys", ] [[package]] name = "tendermint-proto" -version = "0.40.4" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c40e13d39ca19082d8a7ed22de7595979350319833698f8b1080f29620a094" +checksum = "8ed14abe3b0502a3afe21ca74ca5cdd6c7e8d326d982c26f98a394445eb31d6e" dependencies = [ "bytes", "flex-error", "prost", + "prost-types", "serde", "serde_bytes", "subtle-encoding", @@ -1456,14 +1725,14 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", ] [[package]] name = "time" -version = "0.3.43" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83bde6f1ec10e72d583d91623c939f623002284ef622b87de38cfd546cbf2031" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", "num-conv", @@ -1499,9 +1768,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", "zerovec", @@ -1511,29 +1780,26 @@ dependencies = [ name = "treasury" version = "0.1.0" dependencies = [ + "cosmos-sdk-proto", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus", "cw2", - "schemars", - "serde", - "serde_json", "thiserror", "url", - "xion-cosmos-sdk-proto", ] [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-xid" @@ -1583,11 +1849,41 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + [[package]] name = "writeable" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "xion-account" @@ -1595,11 +1891,13 @@ version = "0.1.1" dependencies = [ "base64 0.21.7", "bech32 0.9.1", + "cosmos-sdk-proto", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus", "cw2", - "getrandom", + "ed25519-zebra", + "getrandom 0.2.16", "hex", "p256", "ripemd", @@ -1610,28 +1908,14 @@ dependencies = [ "sha2", "thiserror", "tiny-keccak", - "xion-cosmos-sdk-proto", -] - -[[package]] -name = "xion-cosmos-sdk-proto" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5950da92cdb6e0fdebe4513a1defd73b6c4af7d1fa72ae5f14780451c535bc2" -dependencies = [ - "informalsystems-pbjson", - "prost", - "serde", - "tendermint-proto", ] [[package]] name = "yoke" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -1639,13 +1923,13 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", "synstructure", ] @@ -1666,7 +1950,7 @@ checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", ] [[package]] @@ -1686,15 +1970,15 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ "zeroize_derive", ] @@ -1707,14 +1991,14 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", ] [[package]] name = "zerotrie" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" dependencies = [ "displaydoc", "yoke", @@ -1723,9 +2007,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ "yoke", "zerofrom", @@ -1734,11 +2018,11 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.110", ] diff --git a/Cargo.toml b/Cargo.toml index 17426ea3..06e5c609 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,5 +28,6 @@ phf = { version = "0.11.2", features = ["macros"] } rsa = { version = "0.9.2" } getrandom = { version = "0.2.10", features = ["custom"] } p256 = {version = "0.13.2", features = ["ecdsa-core", "arithmetic", "serde"]} +ed25519-zebra = { version = "4.1.0", features = ["alloc"] } cosmos-sdk-proto = {package = "cosmos-sdk-proto", git = "https://github.com/burnt-labs/cosmos-rust", branch = "feat/xion-zk", default-features = false, features = ["cosmwasm", "xion"]} url = "2.5.2" diff --git a/contracts/account/Cargo.toml b/contracts/account/Cargo.toml index 6e7b479a..b88a13fc 100644 --- a/contracts/account/Cargo.toml +++ b/contracts/account/Cargo.toml @@ -31,4 +31,5 @@ base64 = { workspace = true } rsa = { workspace = true } getrandom = { workspace = true } p256 = { workspace = true } +ed25519-zebra = { workspace = true } cosmos-sdk-proto = { workspace = true } \ No newline at end of file diff --git a/contracts/account/src/auth.rs b/contracts/account/src/auth.rs index 561e4516..d7418f6e 100644 --- a/contracts/account/src/auth.rs +++ b/contracts/account/src/auth.rs @@ -52,6 +52,7 @@ pub enum AddAuthenticator { ZKEmail { id: u8, email_salt: String, + allowed_email_hosts: Vec, signature: Binary, }, } @@ -94,6 +95,7 @@ pub enum Authenticator { }, ZKEmail { email_salt: String, + allowed_email_hosts: Vec, }, } @@ -170,9 +172,10 @@ impl Authenticator { } Authenticator::ZKEmail { email_salt, + allowed_email_hosts, } => { let tx_bytes_hash = util::base64url_encode(tx_bytes); - let verification = zkemail::verify(deps, tx_bytes_hash.as_bytes(), sig_bytes, email_salt)?; + let verification = zkemail::verify(deps, tx_bytes_hash.as_bytes(), sig_bytes, email_salt, allowed_email_hosts)?; Ok(verification) } } diff --git a/contracts/account/src/auth/zkemail.rs b/contracts/account/src/auth/zkemail.rs index d4fdbf2e..a715fe28 100644 --- a/contracts/account/src/auth/zkemail.rs +++ b/contracts/account/src/auth/zkemail.rs @@ -32,6 +32,7 @@ pub fn verify( tx_bytes: &[u8], sig_bytes: &[u8], email_salt: &str, + allowed_email_hosts: &[String], ) -> ContractResult { // split the sig_bytes into 2 parts proof and publicOutputs let sig: ZKEmailSignature = from_json(sig_bytes.to_vec())?; @@ -43,6 +44,7 @@ pub fn verify( proof: serde_json::to_vec(&proof)?, public_inputs: public_inputs.clone(), email_hash: email_salt.to_string(), + allowed_email_hosts: allowed_email_hosts.to_vec(), }; let verification_request_bytes = verification_request.to_bytes()?; @@ -470,6 +472,7 @@ mod tests { let signature = sample_zkemail_signature(); let tx_bytes = "test_transaction"; let email_salt = "test_salt"; + let allowed_email_hosts = vec!["example.com".to_string(), "test.com".to_string()]; // Test creating QueryVerifyRequest from signature components let verification_request = QueryVerifyRequest { @@ -477,12 +480,14 @@ mod tests { proof: serde_json::to_vec(&signature.proof).unwrap(), public_inputs: signature.public_inputs.clone(), email_hash: email_salt.to_string(), + allowed_email_hosts: allowed_email_hosts.clone(), }; // Verify the request is properly constructed assert_eq!(verification_request.tx_bytes, tx_bytes.as_bytes()); assert_eq!(verification_request.email_hash, email_salt.to_string()); assert_eq!(verification_request.public_inputs, signature.public_inputs); + assert_eq!(verification_request.allowed_email_hosts, allowed_email_hosts); // Verify proof serialization let proof_bytes = serde_json::to_vec(&signature.proof).unwrap(); diff --git a/contracts/account/src/contract.rs b/contracts/account/src/contract.rs index 0957147c..84fc912b 100644 --- a/contracts/account/src/contract.rs +++ b/contracts/account/src/contract.rs @@ -4,7 +4,10 @@ use cosmwasm_std::{ }; use crate::error::ContractError; -use crate::execute::{add_auth_method, assert_self, emit, remove_auth_method}; +use crate::execute::{ + add_auth_method, add_allowed_email_host, assert_self, emit, remove_allowed_email_host, + remove_auth_method, update_allowed_email_hosts, +}; use crate::msg::{ExecuteMsg, MigrateMsg}; use crate::{ error::ContractResult, @@ -91,6 +94,16 @@ pub fn execute( } ExecuteMsg::RemoveAuthMethod { id } => remove_auth_method(deps, env, *id), ExecuteMsg::Emit { data } => emit(env, data.to_string()), + ExecuteMsg::UpdateAllowedEmailHosts { + id, + allowed_email_hosts, + } => update_allowed_email_hosts(deps, env, *id, allowed_email_hosts.clone()), + ExecuteMsg::AddAllowedEmailHost { id, email_host } => { + add_allowed_email_host(deps, env, *id, email_host.clone()) + } + ExecuteMsg::RemoveAllowedEmailHost { id, email_host } => { + remove_allowed_email_host(deps, env, *id, email_host.clone()) + } } } diff --git a/contracts/account/src/error.rs b/contracts/account/src/error.rs index dab50ec0..4920f67d 100644 --- a/contracts/account/src/error.rs +++ b/contracts/account/src/error.rs @@ -90,6 +90,15 @@ pub enum ContractError { #[error("invalid ethereum address")] InvalidEthAddress, + + #[error("at least one allowed email host must be present")] + NoAllowedEmailHosts, + + #[error("authenticator not found")] + AuthenticatorNotFound, + + #[error("operation not supported for this authenticator type")] + UnsupportedAuthenticatorOperation, } pub type ContractResult = Result; diff --git a/contracts/account/src/execute.rs b/contracts/account/src/execute.rs index 9d1c7132..6b6738de 100644 --- a/contracts/account/src/execute.rs +++ b/contracts/account/src/execute.rs @@ -230,11 +230,17 @@ pub fn add_auth_method( AddAuthenticator::ZKEmail { id, email_salt, + allowed_email_hosts, signature, } => { - // extract email salt from signature + // Validate that at least one email host is provided + if allowed_email_hosts.is_empty() { + return Err(ContractError::NoAllowedEmailHosts); + } + let auth = Authenticator::ZKEmail { - email_salt: (*email_salt).clone() + email_salt: (*email_salt).clone(), + allowed_email_hosts: allowed_email_hosts.clone(), }; if !auth.verify( deps.as_ref(), @@ -311,6 +317,138 @@ pub fn assert_self(sender: &Addr, contract: &Addr) -> ContractResult<()> { Ok(()) } +pub fn update_allowed_email_hosts( + deps: DepsMut, + env: Env, + id: u8, + allowed_email_hosts: Vec, +) -> ContractResult { + // Validate that at least one email host is provided + if allowed_email_hosts.is_empty() { + return Err(ContractError::NoAllowedEmailHosts); + } + + // Load the authenticator + let authenticator = AUTHENTICATORS.load(deps.storage, id)?; + + // Ensure it's a ZKEmail authenticator + match authenticator { + Authenticator::ZKEmail { email_salt, .. } => { + // Update the authenticator with new allowed_email_hosts + let updated_auth = Authenticator::ZKEmail { + email_salt, + allowed_email_hosts: allowed_email_hosts.clone(), + }; + + AUTHENTICATORS.save(deps.storage, id, &updated_auth)?; + + Ok(Response::new().add_event( + Event::new("update_allowed_email_hosts").add_attributes(vec![ + ("contract_address", env.contract.address.to_string()), + ("authenticator_id", id.to_string()), + ("allowed_email_hosts", serde_json::to_string(&allowed_email_hosts)?), + ]), + )) + } + _ => Err(ContractError::UnsupportedAuthenticatorOperation), + } +} + +pub fn add_allowed_email_host( + deps: DepsMut, + env: Env, + id: u8, + email_host: String, +) -> ContractResult { + // Load the authenticator + let authenticator = AUTHENTICATORS.load(deps.storage, id)?; + + // Ensure it's a ZKEmail authenticator + match authenticator { + Authenticator::ZKEmail { + email_salt, + mut allowed_email_hosts, + } => { + // Check if the email host already exists + if allowed_email_hosts.contains(&email_host) { + return Ok(Response::new().add_event( + Event::new("add_allowed_email_host").add_attributes(vec![ + ("contract_address", env.contract.address.to_string()), + ("authenticator_id", id.to_string()), + ("email_host", email_host), + ("status", "already_exists".to_string()), + ]), + )); + } + + // Add the new email host + allowed_email_hosts.push(email_host.clone()); + + // Update the authenticator + let updated_auth = Authenticator::ZKEmail { + email_salt, + allowed_email_hosts, + }; + + AUTHENTICATORS.save(deps.storage, id, &updated_auth)?; + + Ok(Response::new().add_event( + Event::new("add_allowed_email_host").add_attributes(vec![ + ("contract_address", env.contract.address.to_string()), + ("authenticator_id", id.to_string()), + ("email_host", email_host), + ("status", "added".to_string()), + ]), + )) + } + _ => Err(ContractError::UnsupportedAuthenticatorOperation), + } +} + +pub fn remove_allowed_email_host( + deps: DepsMut, + env: Env, + id: u8, + email_host: String, +) -> ContractResult { + // Load the authenticator + let authenticator = AUTHENTICATORS.load(deps.storage, id)?; + + // Ensure it's a ZKEmail authenticator + match authenticator { + Authenticator::ZKEmail { + email_salt, + mut allowed_email_hosts, + } => { + // Ensure at least one email host remains after removal + if allowed_email_hosts.len() <= 1 { + return Err(ContractError::NoAllowedEmailHosts); + } + + // Remove the email host + allowed_email_hosts.retain(|host| host != &email_host); + + // Update the authenticator + let updated_auth = Authenticator::ZKEmail { + email_salt, + allowed_email_hosts, + }; + + AUTHENTICATORS.save(deps.storage, id, &updated_auth)?; + + Ok(Response::new().add_event( + Event::new("remove_allowed_email_host").add_attributes(vec![ + ("contract_address", env.contract.address.to_string()), + ("authenticator_id", id.to_string()), + ("email_host", email_host), + ("status", "removed".to_string()), + ]), + )) + } + _ => Err(ContractError::UnsupportedAuthenticatorOperation), + } +} + #[cfg(test)] pub mod tests { use base64::{engine::general_purpose, Engine as _}; @@ -448,6 +586,7 @@ pub mod tests { let mut add_authenticator = AddAuthenticator::ZKEmail { id: 1, email_salt: "test_email_salt".to_string(), + allowed_email_hosts: vec!["example.com".to_string()], signature: signature_binary, }; @@ -460,4 +599,287 @@ pub mod tests { // Verify the authenticator was not saved assert!(!AUTHENTICATORS.has(deps.as_ref().storage, 1)); } + + #[test] + fn test_allowed_email_hosts_operations() { + use crate::auth::Authenticator; + use crate::error::ContractError; + use crate::execute::{add_allowed_email_host, remove_allowed_email_host, update_allowed_email_hosts}; + + let mut deps = OwnedDeps { + storage: MockStorage::default(), + api: MockApi::default().with_prefix("xion"), + querier: MockQuerier::::new(&[]), + custom_query_type: std::marker::PhantomData, + }; + let env = mock_env(); + let auth_id = 1u8; + + // Setup: Create a ZKEmail authenticator with initial email hosts + let initial_authenticator = Authenticator::ZKEmail { + email_salt: "test_salt".to_string(), + allowed_email_hosts: vec!["example.com".to_string(), "test.com".to_string()], + }; + AUTHENTICATORS + .save(deps.as_mut().storage, auth_id, &initial_authenticator) + .unwrap(); + + // Test 1: Add a new email host + let result = add_allowed_email_host( + deps.as_mut(), + env.clone(), + auth_id, + "newhost.com".to_string(), + ); + assert!(result.is_ok()); + + // Verify the host was added + let updated_auth = AUTHENTICATORS.load(deps.as_ref().storage, auth_id).unwrap(); + match updated_auth { + Authenticator::ZKEmail { allowed_email_hosts, .. } => { + assert_eq!(allowed_email_hosts.len(), 3); + assert!(allowed_email_hosts.contains(&"newhost.com".to_string())); + } + _ => panic!("Expected ZKEmail authenticator"), + } + + // Test 2: Try to add a duplicate email host + let result = add_allowed_email_host( + deps.as_mut(), + env.clone(), + auth_id, + "newhost.com".to_string(), + ); + assert!(result.is_ok()); // Should succeed but not add duplicate + + // Verify no duplicate was added + let updated_auth = AUTHENTICATORS.load(deps.as_ref().storage, auth_id).unwrap(); + match updated_auth { + Authenticator::ZKEmail { allowed_email_hosts, .. } => { + assert_eq!(allowed_email_hosts.len(), 3); // Still 3, not 4 + assert_eq!( + allowed_email_hosts.iter().filter(|h| *h == "newhost.com").count(), + 1 + ); + } + _ => panic!("Expected ZKEmail authenticator"), + } + + // Test 3: Remove an email host + let result = remove_allowed_email_host( + deps.as_mut(), + env.clone(), + auth_id, + "newhost.com".to_string(), + ); + assert!(result.is_ok()); + + // Verify the host was removed + let updated_auth = AUTHENTICATORS.load(deps.as_ref().storage, auth_id).unwrap(); + match updated_auth { + Authenticator::ZKEmail { allowed_email_hosts, .. } => { + assert_eq!(allowed_email_hosts.len(), 2); + assert!(!allowed_email_hosts.contains(&"newhost.com".to_string())); + } + _ => panic!("Expected ZKEmail authenticator"), + } + + // Test 4: Try to remove the second-to-last email host (should succeed) + let result = remove_allowed_email_host( + deps.as_mut(), + env.clone(), + auth_id, + "example.com".to_string(), + ); + assert!(result.is_ok()); + + // Verify only one host remains + let updated_auth = AUTHENTICATORS.load(deps.as_ref().storage, auth_id).unwrap(); + match updated_auth { + Authenticator::ZKEmail { allowed_email_hosts, .. } => { + assert_eq!(allowed_email_hosts.len(), 1); + assert_eq!(allowed_email_hosts[0], "test.com"); + } + _ => panic!("Expected ZKEmail authenticator"), + } + + // Test 5: Try to remove the last email host (should fail) + let result = remove_allowed_email_host( + deps.as_mut(), + env.clone(), + auth_id, + "test.com".to_string(), + ); + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), ContractError::NoAllowedEmailHosts); + + // Verify the host was not removed + let updated_auth = AUTHENTICATORS.load(deps.as_ref().storage, auth_id).unwrap(); + match updated_auth { + Authenticator::ZKEmail { allowed_email_hosts, .. } => { + assert_eq!(allowed_email_hosts.len(), 1); + } + _ => panic!("Expected ZKEmail authenticator"), + } + + // Test 6: Update allowed email hosts with a new list + let new_hosts = vec![ + "updated1.com".to_string(), + "updated2.com".to_string(), + "updated3.com".to_string(), + ]; + let result = update_allowed_email_hosts( + deps.as_mut(), + env.clone(), + auth_id, + new_hosts.clone(), + ); + assert!(result.is_ok()); + + // Verify the hosts were updated + let updated_auth = AUTHENTICATORS.load(deps.as_ref().storage, auth_id).unwrap(); + match updated_auth { + Authenticator::ZKEmail { allowed_email_hosts, .. } => { + assert_eq!(allowed_email_hosts, new_hosts); + } + _ => panic!("Expected ZKEmail authenticator"), + } + + // Test 7: Try to update with an empty list (should fail) + let result = update_allowed_email_hosts( + deps.as_mut(), + env.clone(), + auth_id, + vec![], + ); + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), ContractError::NoAllowedEmailHosts); + + // Verify hosts were not changed + let updated_auth = AUTHENTICATORS.load(deps.as_ref().storage, auth_id).unwrap(); + match updated_auth { + Authenticator::ZKEmail { allowed_email_hosts, .. } => { + assert_eq!(allowed_email_hosts, new_hosts); + } + _ => panic!("Expected ZKEmail authenticator"), + } + + // Test 8: Try operations on a non-existent authenticator + let result = add_allowed_email_host( + deps.as_mut(), + env.clone(), + 99u8, // Non-existent ID + "test.com".to_string(), + ); + assert!(result.is_err()); + + // Test 9: Try operations on a non-ZKEmail authenticator + let secp_auth = Authenticator::Secp256K1 { + pubkey: Binary::from(vec![1, 2, 3]), + }; + AUTHENTICATORS + .save(deps.as_mut().storage, 2u8, &secp_auth) + .unwrap(); + + let result = add_allowed_email_host( + deps.as_mut(), + env.clone(), + 2u8, + "test.com".to_string(), + ); + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), ContractError::UnsupportedAuthenticatorOperation); + + let result = remove_allowed_email_host( + deps.as_mut(), + env.clone(), + 2u8, + "test.com".to_string(), + ); + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), ContractError::UnsupportedAuthenticatorOperation); + + let result = update_allowed_email_hosts( + deps.as_mut(), + env.clone(), + 2u8, + vec!["test.com".to_string()], + ); + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), ContractError::UnsupportedAuthenticatorOperation); + + // Test 10: Try to remove a host that doesn't exist + let result = remove_allowed_email_host( + deps.as_mut(), + env.clone(), + auth_id, + "nonexistent.com".to_string(), + ); + // This should succeed but not change anything + assert!(result.is_ok()); + + // Verify the hosts remain unchanged + let updated_auth = AUTHENTICATORS.load(deps.as_ref().storage, auth_id).unwrap(); + match updated_auth { + Authenticator::ZKEmail { allowed_email_hosts, .. } => { + assert_eq!(allowed_email_hosts, new_hosts); + } + _ => panic!("Expected ZKEmail authenticator"), + } + + // Test 11: Update with a single host (edge case - minimum valid) + let result = update_allowed_email_hosts( + deps.as_mut(), + env.clone(), + auth_id, + vec!["single.com".to_string()], + ); + assert!(result.is_ok()); + + let updated_auth = AUTHENTICATORS.load(deps.as_ref().storage, auth_id).unwrap(); + match updated_auth { + Authenticator::ZKEmail { allowed_email_hosts, .. } => { + assert_eq!(allowed_email_hosts.len(), 1); + assert_eq!(allowed_email_hosts[0], "single.com"); + } + _ => panic!("Expected ZKEmail authenticator"), + } + + // Test 12: Add multiple hosts one by one + let result1 = add_allowed_email_host( + deps.as_mut(), + env.clone(), + auth_id, + "multi1.com".to_string(), + ); + assert!(result1.is_ok()); + + let result2 = add_allowed_email_host( + deps.as_mut(), + env.clone(), + auth_id, + "multi2.com".to_string(), + ); + assert!(result2.is_ok()); + + let updated_auth = AUTHENTICATORS.load(deps.as_ref().storage, auth_id).unwrap(); + match updated_auth { + Authenticator::ZKEmail { allowed_email_hosts, .. } => { + assert_eq!(allowed_email_hosts.len(), 3); + assert!(allowed_email_hosts.contains(&"single.com".to_string())); + assert!(allowed_email_hosts.contains(&"multi1.com".to_string())); + assert!(allowed_email_hosts.contains(&"multi2.com".to_string())); + } + _ => panic!("Expected ZKEmail authenticator"), + } + + // Test 13: Verify email_salt is preserved through updates + let updated_auth = AUTHENTICATORS.load(deps.as_ref().storage, auth_id).unwrap(); + match updated_auth { + Authenticator::ZKEmail { email_salt, .. } => { + assert_eq!(email_salt, "test_salt"); + } + _ => panic!("Expected ZKEmail authenticator"), + } + } } diff --git a/contracts/account/src/msg.rs b/contracts/account/src/msg.rs index f61a410d..3601ea1f 100644 --- a/contracts/account/src/msg.rs +++ b/contracts/account/src/msg.rs @@ -12,6 +12,18 @@ pub enum ExecuteMsg { AddAuthMethod { add_authenticator: AddAuthenticator }, RemoveAuthMethod { id: u8 }, Emit { data: String }, + UpdateAllowedEmailHosts { + id: u8, + allowed_email_hosts: Vec + }, + AddAllowedEmailHost { + id: u8, + email_host: String + }, + RemoveAllowedEmailHost { + id: u8, + email_host: String + }, } #[cw_serde] From 42e720d6f6c6dc0b9c088bf07d3017d646ca23ac Mon Sep 17 00:00:00 2001 From: Kushal7788 Date: Thu, 27 Nov 2025 22:36:22 +0530 Subject: [PATCH 19/19] Updated Query Types for zk-email verifier --- contracts/account/src/auth/zkemail.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/account/src/auth/zkemail.rs b/contracts/account/src/auth/zkemail.rs index a715fe28..b944cf22 100644 --- a/contracts/account/src/auth/zkemail.rs +++ b/contracts/account/src/auth/zkemail.rs @@ -2,7 +2,7 @@ use crate::error::ContractResult; use cosmos_sdk_proto::{ prost::Message, traits::MessageExt, - xion::v1::dkim::{QueryVerifyRequest, QueryVerifyResponse}, + xion::v1::dkim::{AuthenticateResponse, QueryAuthenticateRequest}, }; use cosmwasm_schema::cw_serde; use cosmwasm_std::{from_json, Binary, Deps}; @@ -39,7 +39,7 @@ pub fn verify( let proof = sig.proof; let public_inputs = sig.public_inputs; - let verification_request = QueryVerifyRequest { + let verification_request = QueryAuthenticateRequest { tx_bytes: tx_bytes.to_vec(), proof: serde_json::to_vec(&proof)?, public_inputs: public_inputs.clone(), @@ -53,7 +53,7 @@ pub fn verify( Binary::from(verification_request_bytes), )?; - let res: QueryVerifyResponse = QueryVerifyResponse::decode(verification_response.as_slice())?; + let res: AuthenticateResponse = AuthenticateResponse::decode(verification_response.as_slice())?; Ok(res.verified) } @@ -475,7 +475,7 @@ mod tests { let allowed_email_hosts = vec!["example.com".to_string(), "test.com".to_string()]; // Test creating QueryVerifyRequest from signature components - let verification_request = QueryVerifyRequest { + let verification_request = QueryAuthenticateRequest { tx_bytes: tx_bytes.as_bytes().to_vec(), proof: serde_json::to_vec(&signature.proof).unwrap(), public_inputs: signature.public_inputs.clone(),