From 8a3d154d440ca2ab4dc4f31c105ba7dc2c7811b3 Mon Sep 17 00:00:00 2001 From: Joy Wang <108701016+joyqvq@users.noreply.github.com> Date: Wed, 27 Mar 2024 11:40:38 -0400 Subject: [PATCH] fix: add strong typing to Bn254Point (#751) * fix: Use strong type during deser * renames and more test --- fastcrypto-zkp/benches/zklogin.rs | 2 +- .../bn254/unit_tests/zk_login_e2e_tests.rs | 2 +- .../src/bn254/unit_tests/zk_login_tests.rs | 27 +- fastcrypto-zkp/src/bn254/utils.rs | 21 +- fastcrypto-zkp/src/bn254/zk_login.rs | 28 +- fastcrypto-zkp/src/bn254/zk_login_api.rs | 263 ++++++++++++------ fastcrypto-zkp/src/circom.rs | 74 ----- fastcrypto-zkp/src/lib.rs | 2 +- fastcrypto-zkp/src/zk_login_utils.rs | 230 +++++++++++++++ 9 files changed, 452 insertions(+), 197 deletions(-) delete mode 100644 fastcrypto-zkp/src/circom.rs create mode 100644 fastcrypto-zkp/src/zk_login_utils.rs diff --git a/fastcrypto-zkp/benches/zklogin.rs b/fastcrypto-zkp/benches/zklogin.rs index 68c8cc892d..33f36fbf94 100644 --- a/fastcrypto-zkp/benches/zklogin.rs +++ b/fastcrypto-zkp/benches/zklogin.rs @@ -40,7 +40,7 @@ mod zklogin_benches { "25769832374-famecqrhe2gkebt5fvqms2263046lj96.apps.googleusercontent.com", ) .unwrap(); - let input = ZkLoginInputs::from_json("{\"proofPoints\":{\"a\":[\"8247215875293406890829839156897863742504615191361518281091302475904551111016\",\"6872980335748205979379321982220498484242209225765686471076081944034292159666\",\"1\"],\"b\":[[\"21419680064642047510915171723230639588631899775315750803416713283740137406807\",\"21566716915562037737681888858382287035712341650647439119820808127161946325890\"],[\"17867714710686394159919998503724240212517838710399045289784307078087926404555\",\"21812769875502013113255155836896615164559280911997219958031852239645061854221\"],[\"1\",\"0\"]],\"c\":[\"7530826803702928198368421787278524256623871560746240215547076095911132653214\",\"16244547936249959771862454850485726883972969173921727256151991751860694123976\",\"1\"]},\"issBase64Details\":{\"value\":\"yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC\",\"indexMod4\":1},\"headerBase64\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjZmNzI1NDEwMWY1NmU0MWNmMzVjOTkyNmRlODRhMmQ1NTJiNGM2ZjEiLCJ0eXAiOiJKV1QifQ\"}", &address_seed).unwrap(); + let input = ZkLoginInputs::from_json("{\"proofPoints\":{\"a\":[\"8247215875293406890829839156897863742504615191361518281091302475904551111016\",\"6872980335748205979379321982220498484242209225765686471076081944034292159666\",\"1\"],\"b\":[[\"21419680064642047510915171723230639588631899775315750803416713283740137406807\",\"21566716915562037737681888858382287035712341650647439119820808127161946325890\"],[\"17867714710686394159919998503724240212517838710399045289784307078087926404555\",\"21812769875502013113255155836896615164559280911997219958031852239645061854221\"],[\"1\",\"0\"]],\"c\":[\"7530826803702928198368421787278524256623871560746240215547076095911132653214\",\"16244547936249959771862454850485726883972969173921727256151991751860694123976\",\"1\"]},\"issBase64Details\":{\"value\":\"yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC\",\"indexMod4\":1},\"headerBase64\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjZmNzI1NDEwMWY1NmU0MWNmMzVjOTkyNmRlODRhMmQ1NTJiNGM2ZjEiLCJ0eXAiOiJKV1QifQ\"}", &address_seed.to_string()).unwrap(); let kp = Ed25519KeyPair::generate(&mut StdRng::from_seed([0; 32])); let mut eph_pubkey = vec![0x00]; eph_pubkey.extend(kp.public().as_ref()); diff --git a/fastcrypto-zkp/src/bn254/unit_tests/zk_login_e2e_tests.rs b/fastcrypto-zkp/src/bn254/unit_tests/zk_login_e2e_tests.rs index 280372cb0d..3ac3e9e712 100644 --- a/fastcrypto-zkp/src/bn254/unit_tests/zk_login_e2e_tests.rs +++ b/fastcrypto-zkp/src/bn254/unit_tests/zk_login_e2e_tests.rs @@ -212,6 +212,6 @@ async fn get_test_inputs(parsed_token: &str) -> (u64, Vec, ZkLoginInputs) { let (sub, aud) = parse_and_validate_jwt(parsed_token).unwrap(); // Get the address seed. let address_seed = gen_address_seed(user_salt, "sub", &sub, &aud).unwrap(); - let zk_login_inputs = ZkLoginInputs::from_reader(reader, &address_seed).unwrap(); + let zk_login_inputs = ZkLoginInputs::from_reader(reader, &address_seed.to_string()).unwrap(); (max_epoch, eph_pubkey, zk_login_inputs) } diff --git a/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs b/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs index e4ea895961..6bed4fceb4 100644 --- a/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs +++ b/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs @@ -5,14 +5,13 @@ use std::str::FromStr; use crate::bn254::poseidon::poseidon_zk_login; use crate::bn254::utils::{ - big_int_str_to_bytes, gen_address_seed, gen_address_seed_with_salt_hash, get_nonce, - get_zk_login_address, + gen_address_seed, gen_address_seed_with_salt_hash, get_nonce, get_zk_login_address, }; use crate::bn254::zk_login::big_int_array_to_bits; use crate::bn254::zk_login::bitarray_to_bytearray; use crate::bn254::zk_login::{ base64_to_bitarray, convert_base, decode_base64_url, hash_ascii_str_to_field, hash_to_field, - parse_jwks, to_field, trim, verify_extended_claim, Claim, JWTDetails, JwkId, + parse_jwks, trim, verify_extended_claim, Claim, JWTDetails, JwkId, }; use crate::bn254::zk_login::{fetch_jwks, OIDCProvider}; use crate::bn254::zk_login_api::ZkLoginEnv; @@ -21,6 +20,7 @@ use crate::bn254::{ zk_login::{ZkLoginInputs, JWK}, zk_login_api::verify_zk_login, }; +use crate::zk_login_utils::Bn254FrElement; use ark_bn254::Fr; use ark_std::rand::rngs::StdRng; use ark_std::rand::SeedableRng; @@ -109,7 +109,7 @@ async fn test_verify_zk_login_google() { .unwrap(); // Get a proof from endpoint and serialize it. - let zk_login_inputs = ZkLoginInputs::from_json("{\"proofPoints\":{\"a\":[\"8247215875293406890829839156897863742504615191361518281091302475904551111016\",\"6872980335748205979379321982220498484242209225765686471076081944034292159666\",\"1\"],\"b\":[[\"21419680064642047510915171723230639588631899775315750803416713283740137406807\",\"21566716915562037737681888858382287035712341650647439119820808127161946325890\"],[\"17867714710686394159919998503724240212517838710399045289784307078087926404555\",\"21812769875502013113255155836896615164559280911997219958031852239645061854221\"],[\"1\",\"0\"]],\"c\":[\"7530826803702928198368421787278524256623871560746240215547076095911132653214\",\"16244547936249959771862454850485726883972969173921727256151991751860694123976\",\"1\"]},\"issBase64Details\":{\"value\":\"yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC\",\"indexMod4\":1},\"headerBase64\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjZmNzI1NDEwMWY1NmU0MWNmMzVjOTkyNmRlODRhMmQ1NTJiNGM2ZjEiLCJ0eXAiOiJKV1QifQ\"}", &address_seed).unwrap(); + let zk_login_inputs = ZkLoginInputs::from_json("{\"proofPoints\":{\"a\":[\"8247215875293406890829839156897863742504615191361518281091302475904551111016\",\"6872980335748205979379321982220498484242209225765686471076081944034292159666\",\"1\"],\"b\":[[\"21419680064642047510915171723230639588631899775315750803416713283740137406807\",\"21566716915562037737681888858382287035712341650647439119820808127161946325890\"],[\"17867714710686394159919998503724240212517838710399045289784307078087926404555\",\"21812769875502013113255155836896615164559280911997219958031852239645061854221\"],[\"1\",\"0\"]],\"c\":[\"7530826803702928198368421787278524256623871560746240215547076095911132653214\",\"16244547936249959771862454850485726883972969173921727256151991751860694123976\",\"1\"]},\"issBase64Details\":{\"value\":\"yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC\",\"indexMod4\":1},\"headerBase64\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjZmNzI1NDEwMWY1NmU0MWNmMzVjOTkyNmRlODRhMmQ1NTJiNGM2ZjEiLCJ0eXAiOiJKV1QifQ\"}", &address_seed.to_string()).unwrap(); assert_eq!( zk_login_inputs.get_kid(), "6f7254101f56e41cf35c9926de84a2d552b4c6f1".to_string() @@ -118,7 +118,10 @@ async fn test_verify_zk_login_google() { zk_login_inputs.get_iss(), OIDCProvider::Google.get_config().iss ); - assert_eq!(zk_login_inputs.get_address_seed(), address_seed); + assert_eq!( + zk_login_inputs.get_address_seed().to_string(), + address_seed.to_string() + ); assert_eq!( get_zk_login_address( zk_login_inputs.get_address_seed(), @@ -190,6 +193,11 @@ fn test_parse_jwt_details() { #[test] fn test_decode_base64() { + assert_eq!( + decode_base64_url("aa", &1).unwrap_err(), + FastCryptoError::GeneralError("Invalid UTF8 string".to_string()) + ); + assert_eq!( decode_base64_url("", &0).unwrap_err(), FastCryptoError::GeneralError("Base64 string smaller than 2".to_string()) @@ -472,7 +480,7 @@ fn test_gen_seed() { ) .unwrap(); assert_eq!( - address_seed, + address_seed.to_string(), "16657007263003735230240998439420301694514420923267872433517882233836276100450".to_string() ); } @@ -487,7 +495,7 @@ fn test_verify_zk_login() { let aud = "575519204237-msop9ep45u2uo98hapqmngv8d84qdc8k.apps.googleusercontent.com"; let salt = "6588741469050502421550140105345050859"; let iss = "https://accounts.google.com"; - let salt_hash = poseidon_zk_login(vec![to_field(salt).unwrap()]) + let salt_hash = poseidon_zk_login(vec![(&Bn254FrElement::from_str(salt).unwrap()).into()]) .unwrap() .to_string(); assert!(verify_zk_login_id(&address, name, value, aud, iss, &salt_hash).is_ok()); @@ -583,10 +591,11 @@ fn test_alternative_iss_for_google() { let mut eph_pubkey_bytes = vec![0]; eph_pubkey_bytes.extend( - big_int_str_to_bytes( + BigUint::from_str( "3598866369818193253063936208363210863933653800990958031560302098730308306242903464", ) - .unwrap(), + .unwrap() + .to_bytes_be(), ); let mut all_jwk = ImHashMap::new(); all_jwk.insert( diff --git a/fastcrypto-zkp/src/bn254/utils.rs b/fastcrypto-zkp/src/bn254/utils.rs index 0ea526cbd1..ec25456339 100644 --- a/fastcrypto-zkp/src/bn254/utils.rs +++ b/fastcrypto-zkp/src/bn254/utils.rs @@ -4,6 +4,7 @@ use crate::bn254::poseidon::poseidon_zk_login; use crate::bn254::zk_login::{OIDCProvider, ZkLoginInputsReader}; use crate::bn254::zk_login_api::Bn254Fr; +use crate::zk_login_utils::Bn254FrElement; use fastcrypto::error::FastCryptoError; use fastcrypto::hash::{Blake2b256, HashFunction}; use fastcrypto::rsa::Base64UrlUnpadded; @@ -14,7 +15,7 @@ use serde::Deserialize; use serde_json::json; use std::str::FromStr; -use super::zk_login::{hash_ascii_str_to_field, to_field}; +use super::zk_login::hash_ascii_str_to_field; const ZK_LOGIN_AUTHENTICATOR_FLAG: u8 = 0x05; const MAX_KEY_CLAIM_NAME_LENGTH: u8 = 32; @@ -22,13 +23,16 @@ const MAX_KEY_CLAIM_VALUE_LENGTH: u8 = 115; const MAX_AUD_VALUE_LENGTH: u8 = 145; /// Calculate the Sui address based on address seed and address params. -pub fn get_zk_login_address(address_seed: &str, iss: &str) -> Result<[u8; 32], FastCryptoError> { +pub fn get_zk_login_address( + address_seed: &Bn254FrElement, + iss: &str, +) -> Result<[u8; 32], FastCryptoError> { let mut hasher = Blake2b256::default(); hasher.update([ZK_LOGIN_AUTHENTICATOR_FLAG]); let bytes = iss.as_bytes(); hasher.update([bytes.len() as u8]); hasher.update(bytes); - hasher.update(big_int_str_to_bytes(address_seed)?); + hasher.update(address_seed.padded()); Ok(hasher.finalize().digest) } @@ -39,7 +43,7 @@ pub fn gen_address_seed( value: &str, // i.e. the sub value aud: &str, // i.e. the client ID ) -> Result { - let salt_hash = poseidon_zk_login(vec![to_field(salt)?])?; + let salt_hash = poseidon_zk_login(vec![(&Bn254FrElement::from_str(salt)?).into()])?; gen_address_seed_with_salt_hash(&salt_hash.to_string(), name, value, aud) } @@ -54,7 +58,7 @@ pub(crate) fn gen_address_seed_with_salt_hash( hash_ascii_str_to_field(name, MAX_KEY_CLAIM_NAME_LENGTH)?, hash_ascii_str_to_field(value, MAX_KEY_CLAIM_VALUE_LENGTH)?, hash_ascii_str_to_field(aud, MAX_AUD_VALUE_LENGTH)?, - to_field(salt_hash)?, + (&Bn254FrElement::from_str(salt_hash)?).into(), ])? .to_string()) } @@ -196,10 +200,3 @@ pub fn split_to_two_frs(eph_pk_bytes: &[u8]) -> Result<(Bn254Fr, Bn254Fr), FastC let eph_public_key_1 = Bn254Fr::from(second_bigint); Ok((eph_public_key_0, eph_public_key_1)) } - -/// Convert a big int string to a big endian bytearray. -pub fn big_int_str_to_bytes(value: &str) -> Result, FastCryptoError> { - Ok(BigUint::from_str(value) - .map_err(|_| FastCryptoError::InvalidInput)? - .to_bytes_be()) -} diff --git a/fastcrypto-zkp/src/bn254/zk_login.rs b/fastcrypto-zkp/src/bn254/zk_login.rs index 6df9147978..b2b6c677fb 100644 --- a/fastcrypto-zkp/src/bn254/zk_login.rs +++ b/fastcrypto-zkp/src/bn254/zk_login.rs @@ -7,8 +7,9 @@ use serde_json::Value; use super::utils::split_to_two_frs; use crate::bn254::poseidon::poseidon_zk_login; -use crate::circom::{ - g1_affine_from_str_projective, g2_affine_from_str_projective, CircomG1, CircomG2, +use crate::zk_login_utils::{ + g1_affine_from_str_projective, g2_affine_from_str_projective, Bn254FrElement, CircomG1, + CircomG2, }; pub use ark_bn254::{Bn254, Fr as Bn254Fr}; pub use ark_ff::ToConstraintField; @@ -301,7 +302,7 @@ pub struct ZkLoginInputs { proof_points: ZkLoginProof, iss_base64_details: Claim, header_base64: String, - address_seed: String, + address_seed: Bn254FrElement, #[serde(skip)] jwt_details: JWTDetails, } @@ -334,7 +335,8 @@ impl ZkLoginInputs { proof_points: reader.proof_points, iss_base64_details: reader.iss_base64_details, header_base64: reader.header_base64, - address_seed: address_seed.to_owned(), + address_seed: Bn254FrElement::from_str(address_seed) + .map_err(|_| FastCryptoError::InvalidInput)?, jwt_details: reader.jwt_details, } .init() @@ -342,9 +344,6 @@ impl ZkLoginInputs { /// Initialize JWTDetails by parsing header_base64 and iss_base64_details. pub fn init(&mut self) -> Result { - if BigUint::from_str(&self.address_seed).is_err() { - return Err(FastCryptoError::InvalidInput); - } self.jwt_details = JWTDetails::new(&self.header_base64, &self.iss_base64_details)?; Ok(self.to_owned()) } @@ -365,7 +364,7 @@ impl ZkLoginInputs { } /// Get the address seed string. - pub fn get_address_seed(&self) -> &str { + pub fn get_address_seed(&self) -> &Bn254FrElement { &self.address_seed } @@ -380,11 +379,12 @@ impl ZkLoginInputs { return Err(FastCryptoError::GeneralError("Header too long".to_string())); } - let addr_seed = to_field(&self.address_seed)?; + let addr_seed = (&self.address_seed).into(); let (first, second) = split_to_two_frs(eph_pk_bytes)?; - let max_epoch_f = to_field(&max_epoch.to_string())?; - let index_mod_4_f = to_field(&self.iss_base64_details.index_mod_4.to_string())?; + let max_epoch_f = (&Bn254FrElement::from_str(&max_epoch.to_string())?).into(); + let index_mod_4_f = + (&Bn254FrElement::from_str(&self.iss_base64_details.index_mod_4.to_string())?).into(); let iss_base64_f = hash_ascii_str_to_field(&self.iss_base64_details.value, MAX_ISS_LEN_B64)?; @@ -536,12 +536,6 @@ fn bitarray_to_bytearray(bits: &[u8]) -> FastCryptoResult> { .collect()) } -/// Convert a bigint string to a field element. -pub fn to_field(val: &str) -> Result { - Bn254Fr::from_str(val) - .map_err(|_| FastCryptoError::GeneralError("Convert to field error".to_string())) -} - /// Pads a stream of bytes and maps it to a field element pub fn hash_ascii_str_to_field(str: &str, max_size: u8) -> Result { let str_padded = str_to_padded_char_codes(str, max_size)?; diff --git a/fastcrypto-zkp/src/bn254/zk_login_api.rs b/fastcrypto-zkp/src/bn254/zk_login_api.rs index 6410b74e4a..c2e63addf6 100644 --- a/fastcrypto-zkp/src/bn254/zk_login_api.rs +++ b/fastcrypto-zkp/src/bn254/zk_login_api.rs @@ -1,12 +1,16 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use std::str::FromStr; + use ark_snark::SNARK; use fastcrypto::rsa::{Base64UrlUnpadded, Encoding}; use super::zk_login::{JwkId, ZkLoginInputs, JWK}; use crate::bn254::utils::{gen_address_seed_with_salt_hash, get_zk_login_address}; -use crate::circom::{g1_affine_from_str_projective, g2_affine_from_str_projective}; +use crate::zk_login_utils::{ + g1_affine_from_str_projective, g2_affine_from_str_projective, Bn254FqElement, Bn254FrElement, +}; pub use ark_bn254::{Bn254, Fr as Bn254Fr}; pub use ark_ff::ToConstraintField; use ark_groth16::{Groth16, PreparedVerifyingKey, Proof, VerifyingKey}; @@ -41,57 +45,96 @@ static INSECURE_VERIFYING_KEY: Lazy> = Lazy::new(ins fn insecure_pvk() -> PreparedVerifyingKey { // Convert the Circom G1/G2/GT to arkworks G1/G2/GT let vk_alpha_1 = g1_affine_from_str_projective(&vec![ - "20491192805390485299153009773594534940189261866228447918068658471970481763042".to_string(), - "9383485363053290200918347156157836566562967994039712273449902621266178545958".to_string(), - "1".to_string(), + Bn254FqElement::from_str( + "20491192805390485299153009773594534940189261866228447918068658471970481763042", + ) + .unwrap(), + Bn254FqElement::from_str( + "9383485363053290200918347156157836566562967994039712273449902621266178545958", + ) + .unwrap(), + Bn254FqElement::from_str("1").unwrap(), ]) .unwrap(); let vk_beta_2 = g2_affine_from_str_projective(&vec![ vec![ - "6375614351688725206403948262868962793625744043794305715222011528459656738731" - .to_string(), - "4252822878758300859123897981450591353533073413197771768651442665752259397132" - .to_string(), + Bn254FqElement::from_str( + "6375614351688725206403948262868962793625744043794305715222011528459656738731", + ) + .unwrap(), + Bn254FqElement::from_str( + "4252822878758300859123897981450591353533073413197771768651442665752259397132", + ) + .unwrap(), + ], + vec![ + Bn254FqElement::from_str( + "10505242626370262277552901082094356697409835680220590971873171140371331206856", + ) + .unwrap(), + Bn254FqElement::from_str( + "21847035105528745403288232691147584728191162732299865338377159692350059136679", + ) + .unwrap(), ], vec![ - "10505242626370262277552901082094356697409835680220590971873171140371331206856" - .to_string(), - "21847035105528745403288232691147584728191162732299865338377159692350059136679" - .to_string(), + Bn254FqElement::from_str("1").unwrap(), + Bn254FqElement::from_str("0").unwrap(), ], - vec!["1".to_string(), "0".to_string()], ]) .unwrap(); let vk_gamma_2 = g2_affine_from_str_projective(&vec![ vec![ - "10857046999023057135944570762232829481370756359578518086990519993285655852781" - .to_string(), - "11559732032986387107991004021392285783925812861821192530917403151452391805634" - .to_string(), + Bn254FqElement::from_str( + "10857046999023057135944570762232829481370756359578518086990519993285655852781", + ) + .unwrap(), + Bn254FqElement::from_str( + "11559732032986387107991004021392285783925812861821192530917403151452391805634", + ) + .unwrap(), ], vec![ - "8495653923123431417604973247489272438418190587263600148770280649306958101930" - .to_string(), - "4082367875863433681332203403145435568316851327593401208105741076214120093531" - .to_string(), + Bn254FqElement::from_str( + "8495653923123431417604973247489272438418190587263600148770280649306958101930", + ) + .unwrap(), + Bn254FqElement::from_str( + "4082367875863433681332203403145435568316851327593401208105741076214120093531", + ) + .unwrap(), + ], + vec![ + Bn254FqElement::from_str("1").unwrap(), + Bn254FqElement::from_str("0").unwrap(), ], - vec!["1".to_string(), "0".to_string()], ]) .unwrap(); let vk_delta_2 = g2_affine_from_str_projective(&vec![ vec![ - "10857046999023057135944570762232829481370756359578518086990519993285655852781" - .to_string(), - "11559732032986387107991004021392285783925812861821192530917403151452391805634" - .to_string(), + Bn254FqElement::from_str( + "10857046999023057135944570762232829481370756359578518086990519993285655852781", + ) + .unwrap(), + Bn254FqElement::from_str( + "11559732032986387107991004021392285783925812861821192530917403151452391805634", + ) + .unwrap(), + ], + vec![ + Bn254FqElement::from_str( + "8495653923123431417604973247489272438418190587263600148770280649306958101930", + ) + .unwrap(), + Bn254FqElement::from_str( + "4082367875863433681332203403145435568316851327593401208105741076214120093531", + ) + .unwrap(), ], vec![ - "8495653923123431417604973247489272438418190587263600148770280649306958101930" - .to_string(), - "4082367875863433681332203403145435568316851327593401208105741076214120093531" - .to_string(), + Bn254FqElement::from_str("1").unwrap(), + Bn254FqElement::from_str("0").unwrap(), ], - vec!["1".to_string(), "0".to_string()], ]) .unwrap(); @@ -99,18 +142,26 @@ fn insecure_pvk() -> PreparedVerifyingKey { let mut vk_gamma_abc_g1 = Vec::new(); for e in [ vec![ - "20701306374481714853949730154526815782802808896228594855451770849676897643964" - .to_string(), - "2766989084754673216772682210231588284954002353414778477810174100808747060165" - .to_string(), - "1".to_string(), + Bn254FqElement::from_str( + "20701306374481714853949730154526815782802808896228594855451770849676897643964", + ) + .unwrap(), + Bn254FqElement::from_str( + "2766989084754673216772682210231588284954002353414778477810174100808747060165", + ) + .unwrap(), + Bn254FqElement::from_str("1").unwrap(), ], vec![ - "501195541410525737371980194958674422793469475773065719916327137354779402600" - .to_string(), - "13527631693157515024233848630878973193664410306029731429350155106228769355415" - .to_string(), - "1".to_string(), + Bn254FqElement::from_str( + "501195541410525737371980194958674422793469475773065719916327137354779402600", + ) + .unwrap(), + Bn254FqElement::from_str( + "13527631693157515024233848630878973193664410306029731429350155106228769355415", + ) + .unwrap(), + Bn254FqElement::from_str("1").unwrap(), ], ] { let g1 = g1_affine_from_str_projective(&e).unwrap(); @@ -133,57 +184,96 @@ fn insecure_pvk() -> PreparedVerifyingKey { fn global_pvk() -> PreparedVerifyingKey { // Convert the Circom G1/G2/GT to arkworks G1/G2/GT let vk_alpha_1 = g1_affine_from_str_projective(&vec![ - "21529901943976716921335152104180790524318946701278905588288070441048877064089".to_string(), - "7775817982019986089115946956794180159548389285968353014325286374017358010641".to_string(), - "1".to_string(), + Bn254FqElement::from_str( + "21529901943976716921335152104180790524318946701278905588288070441048877064089", + ) + .unwrap(), + Bn254FqElement::from_str( + "7775817982019986089115946956794180159548389285968353014325286374017358010641", + ) + .unwrap(), + Bn254FqElement::from_str("1").unwrap(), ]) .unwrap(); let vk_beta_2 = g2_affine_from_str_projective(&vec![ vec![ - "6600437987682835329040464538375790690815756241121776438004683031791078085074" - .to_string(), - "16207344858883952201936462217289725998755030546200154201671892670464461194903" - .to_string(), + Bn254FqElement::from_str( + "6600437987682835329040464538375790690815756241121776438004683031791078085074", + ) + .unwrap(), + Bn254FqElement::from_str( + "16207344858883952201936462217289725998755030546200154201671892670464461194903", + ) + .unwrap(), ], vec![ - "17943105074568074607580970189766801116106680981075272363121544016828311544390" - .to_string(), - "18339640667362802607939727433487930605412455701857832124655129852540230493587" - .to_string(), + Bn254FqElement::from_str( + "17943105074568074607580970189766801116106680981075272363121544016828311544390", + ) + .unwrap(), + Bn254FqElement::from_str( + "18339640667362802607939727433487930605412455701857832124655129852540230493587", + ) + .unwrap(), + ], + vec![ + Bn254FqElement::from_str("1").unwrap(), + Bn254FqElement::from_str("0").unwrap(), ], - vec!["1".to_string(), "0".to_string()], ]) .unwrap(); let vk_gamma_2 = g2_affine_from_str_projective(&vec![ vec![ - "10857046999023057135944570762232829481370756359578518086990519993285655852781" - .to_string(), - "11559732032986387107991004021392285783925812861821192530917403151452391805634" - .to_string(), + Bn254FqElement::from_str( + "10857046999023057135944570762232829481370756359578518086990519993285655852781", + ) + .unwrap(), + Bn254FqElement::from_str( + "11559732032986387107991004021392285783925812861821192530917403151452391805634", + ) + .unwrap(), + ], + vec![ + Bn254FqElement::from_str( + "8495653923123431417604973247489272438418190587263600148770280649306958101930", + ) + .unwrap(), + Bn254FqElement::from_str( + "4082367875863433681332203403145435568316851327593401208105741076214120093531", + ) + .unwrap(), ], vec![ - "8495653923123431417604973247489272438418190587263600148770280649306958101930" - .to_string(), - "4082367875863433681332203403145435568316851327593401208105741076214120093531" - .to_string(), + Bn254FqElement::from_str("1").unwrap(), + Bn254FqElement::from_str("0").unwrap(), ], - vec!["1".to_string(), "0".to_string()], ]) .unwrap(); let vk_delta_2 = g2_affine_from_str_projective(&vec![ vec![ - "19260309516619721648285279557078789954438346514188902804737557357941293711874" - .to_string(), - "2480422554560175324649200374556411861037961022026590718777465211464278308900" - .to_string(), + Bn254FqElement::from_str( + "19260309516619721648285279557078789954438346514188902804737557357941293711874", + ) + .unwrap(), + Bn254FqElement::from_str( + "2480422554560175324649200374556411861037961022026590718777465211464278308900", + ) + .unwrap(), + ], + vec![ + Bn254FqElement::from_str( + "14489104692423540990601374549557603533921811847080812036788172274404299703364", + ) + .unwrap(), + Bn254FqElement::from_str( + "12564378633583954025611992187142343628816140907276948128970903673042690269191", + ) + .unwrap(), ], vec![ - "14489104692423540990601374549557603533921811847080812036788172274404299703364" - .to_string(), - "12564378633583954025611992187142343628816140907276948128970903673042690269191" - .to_string(), + Bn254FqElement::from_str("1").unwrap(), + Bn254FqElement::from_str("0").unwrap(), ], - vec!["1".to_string(), "0".to_string()], ]) .unwrap(); @@ -191,18 +281,26 @@ fn global_pvk() -> PreparedVerifyingKey { let mut vk_gamma_abc_g1 = Vec::new(); for e in [ vec![ - "1607694606386445293170795095076356565829000940041894770459712091642365695804" - .to_string(), - "18066827569413962196795937356879694709963206118612267170825707780758040578649" - .to_string(), - "1".to_string(), + Bn254FqElement::from_str( + "1607694606386445293170795095076356565829000940041894770459712091642365695804", + ) + .unwrap(), + Bn254FqElement::from_str( + "18066827569413962196795937356879694709963206118612267170825707780758040578649", + ) + .unwrap(), + Bn254FqElement::from_str("1").unwrap(), ], vec![ - "20653794344898475822834426774542692225449366952113790098812854265588083247207" - .to_string(), - "3296759704176575765409730962060698204792513807296274014163938591826372646699" - .to_string(), - "1".to_string(), + Bn254FqElement::from_str( + "20653794344898475822834426774542692225449366952113790098812854265588083247207", + ) + .unwrap(), + Bn254FqElement::from_str( + "3296759704176575765409730962060698204792513807296274014163938591826372646699", + ) + .unwrap(), + Bn254FqElement::from_str("1").unwrap(), ], ] { let g1 = g1_affine_from_str_projective(&e).unwrap(); @@ -285,7 +383,8 @@ pub fn verify_zk_login_id( /// Verify that the given parameters (address_seed and iss) were used to generate the given address. pub fn verify_zk_login_iss(address: &[u8], address_seed: &str, iss: &str) -> FastCryptoResult<()> { - let reconstructed_address = get_zk_login_address(address_seed, iss)?; + let reconstructed_address = + get_zk_login_address(&Bn254FrElement::from_str(address_seed)?, iss)?; match reconstructed_address == address { true => Ok(()), false => Err(FastCryptoError::InvalidProof), diff --git a/fastcrypto-zkp/src/circom.rs b/fastcrypto-zkp/src/circom.rs deleted file mode 100644 index 2c4f3ac72d..0000000000 --- a/fastcrypto-zkp/src/circom.rs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) 2022, Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -use ark_bn254::{Fq, Fq2, G1Affine, G1Projective, G2Affine, G2Projective}; -use fastcrypto::error::FastCryptoError; - -/// A G1 point in BN254 serialized as a vector of three strings which is the canonical decimal -/// representation of the projective coordinates in Fq. -pub type CircomG1 = Vec; - -/// A G2 point in BN254 serialized as a vector of three vectors each being a vector of two strings -/// which are the canonical decimal representation of the coefficients of the projective coordinates -/// in Fq2. -pub type CircomG2 = Vec>; - -/// Parse a string as a field element in BN254. Return an `FastCryptoError::InvalidInput` error if -/// the parsing fails. -fn parse_field_element(s: &str) -> Result { - s.parse::().map_err(|_| FastCryptoError::InvalidInput) -} - -/// Deserialize a G1 projective point in BN254 serialized as a vector of three strings into an affine -/// G1 point in arkworks format. Return an error if the input is not a vector of three strings or if -/// any of the strings cannot be parsed as a field element. -pub(crate) fn g1_affine_from_str_projective(s: &CircomG1) -> Result { - if s.len() != 3 { - return Err(FastCryptoError::InvalidInput); - } - - let g1: G1Affine = G1Projective::new_unchecked( - parse_field_element(&s[0])?, - parse_field_element(&s[1])?, - parse_field_element(&s[2])?, - ) - .into(); - - if !g1.is_on_curve() || !g1.is_in_correct_subgroup_assuming_on_curve() { - return Err(FastCryptoError::InvalidInput); - } - - Ok(g1) -} - -/// Deserialize a G2 projective point from the BN254 construction serialized as a vector of three -/// vectors each being a vector of two strings into an affine G2 point in arkworks format. Return an -/// error if the input is not a vector of the right format or if any of the strings cannot be parsed -/// as a field element. -pub(crate) fn g2_affine_from_str_projective(s: &CircomG2) -> Result { - if s.len() != 3 || s[0].len() != 2 || s[1].len() != 2 || s[2].len() != 2 { - return Err(FastCryptoError::InvalidInput); - } - - let g2: G2Affine = G2Projective::new_unchecked( - Fq2::new( - parse_field_element(&s[0][0])?, - parse_field_element(&s[0][1])?, - ), - Fq2::new( - parse_field_element(&s[1][0])?, - parse_field_element(&s[1][1])?, - ), - Fq2::new( - parse_field_element(&s[2][0])?, - parse_field_element(&s[2][1])?, - ), - ) - .into(); - - if !g2.is_on_curve() || !g2.is_in_correct_subgroup_assuming_on_curve() { - return Err(FastCryptoError::InvalidInput); - } - - Ok(g2) -} diff --git a/fastcrypto-zkp/src/lib.rs b/fastcrypto-zkp/src/lib.rs index 13878899eb..4631dd3b32 100644 --- a/fastcrypto-zkp/src/lib.rs +++ b/fastcrypto-zkp/src/lib.rs @@ -24,4 +24,4 @@ pub mod bn254; pub mod dummy_circuits; /// Circom-compatible deserialization of points -pub mod circom; +pub mod zk_login_utils; diff --git a/fastcrypto-zkp/src/zk_login_utils.rs b/fastcrypto-zkp/src/zk_login_utils.rs new file mode 100644 index 0000000000..75e0918e5b --- /dev/null +++ b/fastcrypto-zkp/src/zk_login_utils.rs @@ -0,0 +1,230 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use ark_bn254::{Fq, Fq2, Fr, G1Affine, G1Projective, G2Affine, G2Projective}; +use ark_ff::BigInteger; +use ark_ff::PrimeField; +use fastcrypto::error::FastCryptoError; +use num_bigint::BigUint; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +/// A G1 point in BN254 serialized as a vector of three strings which is the canonical decimal +/// representation of the projective coordinates in Fq. +pub type CircomG1 = Vec; + +/// A G2 point in BN254 serialized as a vector of three vectors each being a vector of two strings +/// which are the canonical decimal representation of the coefficients of the projective coordinates +/// in Fq2. +pub type CircomG2 = Vec>; + +/// A struct that stores a Bn254 Fq field element as 32 bytes. +#[derive(Debug, Clone, JsonSchema, Eq, PartialEq)] +pub struct Bn254FqElement([u8; 32]); + +impl std::str::FromStr for Bn254FqElement { + type Err = FastCryptoError; + + fn from_str(s: &str) -> Result { + let big_int = Fq::from_str(s).map_err(|_| FastCryptoError::InvalidInput)?; + let be_bytes = big_int.into_bigint().to_bytes_be(); + be_bytes + .try_into() + .map_err(|_| FastCryptoError::InvalidInput) + .map(Bn254FqElement) + } +} + +impl std::fmt::Display for Bn254FqElement { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let big_int = BigUint::from_bytes_be(&self.0); + let radix10 = big_int.to_string(); + f.write_str(&radix10) + } +} + +// Bn254FqElement's serialized format is as a radix10 encoded string +impl Serialize for Bn254FqElement { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.to_string().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Bn254FqElement { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s = std::borrow::Cow::<'de, str>::deserialize(deserializer)?; + std::str::FromStr::from_str(&s).map_err(serde::de::Error::custom) + } +} + +/// A struct that stores a Bn254 Fr field element as 32 bytes. +#[derive(Debug, Clone, JsonSchema, Eq, PartialEq)] +pub struct Bn254FrElement([u8; 32]); + +impl Bn254FrElement { + /// Returns the unpadded version of the field element. This returns with leading zeros removed. + pub fn unpadded(&self) -> &[u8] { + let mut buf = self.0.as_slice(); + + while !buf.is_empty() && buf[0] == 0 { + buf = &buf[1..]; + } + + // If the value is '0' then just return a slice of length 1 of the final byte + if buf.is_empty() { + &self.0[31..] + } else { + buf + } + } + + /// Returns the padded version of the field element. This returns with leading zeros preserved to 32 bytes. + pub fn padded(&self) -> &[u8] { + &self.0 + } +} +impl std::str::FromStr for Bn254FrElement { + type Err = FastCryptoError; + + fn from_str(s: &str) -> Result { + let big_int = Fr::from_str(s).map_err(|_| FastCryptoError::InvalidInput)?; + let be_bytes = big_int.into_bigint().to_bytes_be(); + be_bytes + .try_into() + .map_err(|_| FastCryptoError::InvalidInput) + .map(Bn254FrElement) + } +} + +impl std::fmt::Display for Bn254FrElement { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let big_int = BigUint::from_bytes_be(&self.0); + let radix10 = big_int.to_string(); + f.write_str(&radix10) + } +} + +// Bn254FrElement's serialized format is as a radix10 encoded string +impl Serialize for Bn254FrElement { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.to_string().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Bn254FrElement { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s = std::borrow::Cow::<'de, str>::deserialize(deserializer)?; + std::str::FromStr::from_str(&s).map_err(serde::de::Error::custom) + } +} + +/// Convert Bn254FqElement type to arkworks' Fq. +impl From<&Bn254FqElement> for Fq { + fn from(f: &Bn254FqElement) -> Self { + Fq::from_be_bytes_mod_order(&f.0) + } +} + +/// Convert Bn254FrElement type to arkworks' Fr. +impl From<&Bn254FrElement> for Fr { + fn from(f: &Bn254FrElement) -> Self { + Fr::from_be_bytes_mod_order(&f.0) + } +} + +/// Deserialize a G1 projective point in BN254 serialized as a vector of three strings into an affine +/// G1 point in arkworks format. Return an error if the input is not a vector of three strings or if +/// any of the strings cannot be parsed as a field element. +pub(crate) fn g1_affine_from_str_projective(s: &CircomG1) -> Result { + if s.len() != 3 { + return Err(FastCryptoError::InvalidInput); + } + + let g1: G1Affine = + G1Projective::new_unchecked((&s[0]).into(), (&s[1]).into(), (&s[2]).into()).into(); + + if !g1.is_on_curve() || !g1.is_in_correct_subgroup_assuming_on_curve() { + return Err(FastCryptoError::InvalidInput); + } + + Ok(g1) +} + +/// Deserialize a G2 projective point from the BN254 construction serialized as a vector of three +/// vectors each being a vector of two strings into an affine G2 point in arkworks format. Return an +/// error if the input is not a vector of the right format or if any of the strings cannot be parsed +/// as a field element. +pub(crate) fn g2_affine_from_str_projective(s: &CircomG2) -> Result { + if s.len() != 3 || s[0].len() != 2 || s[1].len() != 2 || s[2].len() != 2 { + return Err(FastCryptoError::InvalidInput); + } + + let g2: G2Affine = G2Projective::new_unchecked( + Fq2::new((&s[0][0]).into(), (&s[0][1]).into()), + Fq2::new((&s[1][0]).into(), (&s[1][1]).into()), + Fq2::new((&s[2][0]).into(), (&s[2][1]).into()), + ) + .into(); + + if !g2.is_on_curve() || !g2.is_in_correct_subgroup_assuming_on_curve() { + return Err(FastCryptoError::InvalidInput); + } + + Ok(g2) +} + +#[cfg(test)] +mod test { + use crate::zk_login_utils::Bn254FqElement; + use std::str::FromStr; + + use super::Bn254FrElement; + use num_bigint::BigUint; + use proptest::prelude::*; + #[test] + fn from_str_on_digits_only() { + // do not allow non digit results. + assert!(Bn254FrElement::from_str("10_________0").is_err()); + assert!(Bn254FqElement::from_str("10_________0").is_err()); + // do not allow leading zeros. + assert!(Bn254FrElement::from_str("000001").is_err()); + assert!(Bn254FqElement::from_str("000001").is_err()); + assert!(Bn254FrElement::from_str("garbage").is_err()); + assert!(Bn254FqElement::from_str("garbage").is_err()); + } + #[test] + fn unpadded_slice() { + let seed = Bn254FrElement([0; 32]); + let zero: [u8; 1] = [0]; + assert_eq!(seed.unpadded(), zero.as_slice()); + + let mut seed = Bn254FrElement([1; 32]); + seed.0[0] = 0; + assert_eq!(seed.unpadded(), [1; 31].as_slice()); + } + + proptest! { + #[test] + fn dont_crash_on_large_inputs( + bytes in proptest::collection::vec(any::(), 33..1024) + ) { + let big_int = BigUint::from_bytes_be(&bytes); + let radix10 = big_int.to_str_radix(10); + + // doesn't crash + let _ = Bn254FrElement::from_str(&radix10); + let _ = Bn254FqElement::from_str(&radix10); + } + } +}