From fc2a3fa5bf85145437acb3baba8cb0152e1d21ae Mon Sep 17 00:00:00 2001 From: Joy Wang <108701016+joyqvq@users.noreply.github.com> Date: Tue, 27 Jun 2023 10:02:59 -0400 Subject: [PATCH 01/13] fix: update verifier logic --- fastcrypto-zkp/src/bn254/mod.rs | 3 + fastcrypto-zkp/src/bn254/poseidon.rs | 27 +- .../src/bn254/unit_tests/zk_login_tests.rs | 231 ++++- fastcrypto-zkp/src/bn254/zk_login.rs | 799 +++++++----------- fastcrypto-zkp/src/bn254/zk_login_api.rs | 205 +++++ 5 files changed, 739 insertions(+), 526 deletions(-) create mode 100644 fastcrypto-zkp/src/bn254/zk_login_api.rs diff --git a/fastcrypto-zkp/src/bn254/mod.rs b/fastcrypto-zkp/src/bn254/mod.rs index 089a981b3d..b7c94f5fb6 100644 --- a/fastcrypto-zkp/src/bn254/mod.rs +++ b/fastcrypto-zkp/src/bn254/mod.rs @@ -18,6 +18,9 @@ pub mod poseidon; /// Zk login structs and utilities pub mod zk_login; +/// api +pub mod zk_login_api; + /// A field element in the BN254 construction. Thin wrapper around `api::Bn254Fr`. #[derive(Debug, From)] pub struct FieldElement(pub(crate) api::Bn254Fr); diff --git a/fastcrypto-zkp/src/bn254/poseidon.rs b/fastcrypto-zkp/src/bn254/poseidon.rs index 1c4c718151..007157cdb8 100644 --- a/fastcrypto-zkp/src/bn254/poseidon.rs +++ b/fastcrypto-zkp/src/bn254/poseidon.rs @@ -41,8 +41,8 @@ impl PoseidonWrapper { #[cfg(test)] mod test { use super::PoseidonWrapper; + use crate::bn254::zk_login::to_poseidon_hash; use crate::bn254::zk_login::Bn254Fr; - use crate::bn254::zk_login::{calculate_merklized_hash, to_poseidon_hash}; use ark_bn254::Fr; use std::str::FromStr; @@ -79,33 +79,33 @@ mod test { ); } #[test] - fn test_merklized_hash() { - let masked_content = b"eyJhbGciOiJSUzI1NiIsImtpZCI6ImM5YWZkYTM2ODJlYmYwOWViMzA1NWMxYzRiZDM5Yjc1MWZiZjgxOTUiLCJ0eXAiOiJKV1QifQ.=yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC===========================================================================================================CJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLC==========================================================================================================================================================================================================================================================================================================\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\xd8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; + fn test_to_poseidon_hash() { assert_eq!( - calculate_merklized_hash(masked_content).unwrap(), - "14900420995580824499222150327925943524564997104405553289134597516335134742309" - ); - - assert_eq!( - to_poseidon_hash(to_bigint_arr(vec![1])).unwrap(), + to_poseidon_hash(to_bigint_arr(vec![1])) + .unwrap() + .to_string(), "18586133768512220936620570745912940619677854269274689475585506675881198879027" ); assert_eq!( - to_poseidon_hash(to_bigint_arr(vec![1, 2])).unwrap(), + to_poseidon_hash(to_bigint_arr(vec![1, 2])) + .unwrap() + .to_string(), "7853200120776062878684798364095072458815029376092732009249414926327459813530" ); assert_eq!( to_poseidon_hash(to_bigint_arr(vec![ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 ])) - .unwrap(), + .unwrap() + .to_string(), "4203130618016961831408770638653325366880478848856764494148034853759773445968" ); assert_eq!( to_poseidon_hash(to_bigint_arr(vec![ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ])) - .unwrap(), + .unwrap() + .to_string(), "13895998335546007571506436905298853781676311844723695580596383169075721618652" ); assert_eq!( @@ -113,7 +113,8 @@ mod test { 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 ])) - .unwrap(), + .unwrap() + .to_string(), "14023706212980258922092162104379517008998397500440232747089120702484714603058" ); } 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 934f42161a..7db5b4e184 100644 --- a/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs +++ b/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs @@ -1,64 +1,225 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use fastcrypto::error::FastCryptoError; + +use super::map_to_field; +use crate::bn254::zk_login::OAuthProvider::{Google, Twitch}; use crate::bn254::zk_login::{ - verify_zk_login_proof_with_fixed_vk, AuxInputs, OAuthProvider, PublicInputs, ZkLoginProof, + decode_base64_url, verify_extended_claim, Claim, JWTHeader, ParsedMaskedContent, +}; +use crate::bn254::{ + zk_login::{AuxInputs, OAuthProvider, OAuthProviderContent, PublicInputs, ZkLoginProof}, + zk_login_api::verify_zk_login, }; +use std::collections::HashMap; + +#[test] +fn test_verify_groth16_in_bytes_google() { + const TEST_KID: &str = "c9afda3682ebf09eb3055c1c4bd39b751fbf8195"; + let aux_inputs = AuxInputs::from_json("{\"claims\": [{\"name\": \"iss\",\"value_base64\":\"yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC\",\"index_mod_4\": 1},{\"name\": \"aud\",\"value_base64\": \"CJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLC\",\"index_mod_4\": 1}], \"header_base64\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6ImM5YWZkYTM2ODJlYmYwOWViMzA1NWMxYzRiZDM5Yjc1MWZiZjgxOTUiLCJ0eXAiOiJKV1QifQ\",\"addr_seed\": \"15604334753912523265015800787270404628529489918817818174033741053550755333691\",\"eph_public_key\": [\"17932473587154777519561053972421347139\", \"134696963602902907403122104327765350261\"],\"max_epoch\": 10000,\"key_claim_name\": \"sub\",\"modulus\": \"24501106890748714737552440981790137484213218436093327306276573863830528169633224698737117584784274166505493525052788880030500250025091662388617070057693555892212025614452197230081503494967494355047321073341455279175776092624566907541405624967595499832566905567072654796017464431878680118805774542185299632150122052530877100261682728356139724202453050155758294697161107805717430444408191365063957162605112787073991150691398970840390185880092832325216009234084152827135531285878617366639283552856146367480314853517993661640450694829038343380576312039548353544096265483699391507882147093626719041048048921352351403884619\"}").unwrap().init().unwrap(); + let public_inputs = PublicInputs::from_json( + "[\"6049184272607241856912886413680599526372437331989542437266935645748489874658\"]", + ) + .unwrap(); + + assert_eq!(aux_inputs.get_max_epoch(), 10000); + assert_eq!( + aux_inputs.get_address_seed(), + "15604334753912523265015800787270404628529489918817818174033741053550755333691" + ); + assert_eq!(aux_inputs.get_iss(), OAuthProvider::Google.get_config().0); + assert_eq!( + aux_inputs.get_aud(), + "575519204237-msop9ep45u2uo98hapqmngv8d84qdc8k.apps.googleusercontent.com" + ); + assert_eq!(aux_inputs.get_key_claim_name(), "sub"); + assert_eq!(aux_inputs.get_kid(), TEST_KID); + assert_eq!(aux_inputs.get_mod(), "24501106890748714737552440981790137484213218436093327306276573863830528169633224698737117584784274166505493525052788880030500250025091662388617070057693555892212025614452197230081503494967494355047321073341455279175776092624566907541405624967595499832566905567072654796017464431878680118805774542185299632150122052530877100261682728356139724202453050155758294697161107805717430444408191365063957162605112787073991150691398970840390185880092832325216009234084152827135531285878617366639283552856146367480314853517993661640450694829038343380576312039548353544096265483699391507882147093626719041048048921352351403884619"); + assert_eq!( + aux_inputs.calculate_all_inputs_hash().unwrap(), + public_inputs.get_all_inputs_hash().unwrap() + ); -use super::ParsedMaskedContent; + let mut map = HashMap::new(); + map.insert((TEST_KID, Google.get_config().0), OAuthProviderContent { + kty: "RSA".to_string(), + e: "AQAB".to_string(), + n: "whYOFK2Ocbbpb_zVypi9SeKiNUqKQH0zTKN1-6fpCTu6ZalGI82s7XK3tan4dJt90ptUPKD2zvxqTzFNfx4HHHsrYCf2-FMLn1VTJfQazA2BvJqAwcpW1bqRUEty8tS_Yv4hRvWfQPcc2Gc3-_fQOOW57zVy-rNoJc744kb30NjQxdGp03J2S3GLQu7oKtSDDPooQHD38PEMNnITf0pj-KgDPjymkMGoJlO3aKppsjfbt_AH6GGdRghYRLOUwQU-h-ofWHR3lbYiKtXPn5dN24kiHy61e3VAQ9_YAZlwXC_99GGtw_NpghFAuM4P1JDn0DppJldy3PGFC0GfBCZASw".to_string(), + alg: "RS256".to_string(), + }); + let proof = ZkLoginProof::from_json("{\"pi_a\":[\"21079899190337156604543197959052999786745784780153100922098887555507822163222\",\"4490261504756339299022091724663793329121338007571218596828748539529998991610\",\"1\"],\"pi_b\":[[\"9379167206161123715528853149920855132656754699464636503784643891913740439869\",\"15902897771112804794883785114808675393618430194414793328415185511364403970347\"],[\"16152736996630746506267683507223054358516992879195296708243566008238438281201\",\"15230917601041350929970534508991793588662911174494137634522926575255163535339\"],[\"1\",\"0\"]],\"pi_c\":[\"8242734018052567627683363270753907648903210541694662698981939667442011573249\",\"1775496841914332445297048246214170486364407018954976081505164205395286250461\",\"1\"],\"protocol\":\"groth16\"}"); + assert!(proof.is_ok()); + let res = verify_zk_login(&proof.unwrap(), &public_inputs, &aux_inputs, 1, map); + assert!(res.is_ok()); +} #[test] -fn test_verify_groth16_in_bytes_api() { - let aux_inputs = AuxInputs::from_json("{\"addr_seed\":\"15604334753912523265015800787270404628529489918817818174033741053550755333691\",\"eph_public_key\":[\"17932473587154777519561053972421347139\",\"134696963602902907403122104327765350261\"],\"jwt_sha2_hash\":[\"248987002057371616691124650904415756047\",\"113498781424543581252500776698433499823\"],\"jwt_signature\":\"\",\"key_claim_name\":\"sub\",\"masked_content\":[101,121,74,104,98,71,99,105,79,105,74,83,85,122,73,49,78,105,73,115,73,109,116,112,90,67,73,54,73,109,77,53,89,87,90,107,89,84,77,50,79,68,74,108,89,109,89,119,79,87,86,105,77,122,65,49,78,87,77,120,89,122,82,105,90,68,77,53,89,106,99,49,77,87,90,105,90,106,103,120,79,84,85,105,76,67,74,48,101,88,65,105,79,105,74,75,86,49,81,105,102,81,46,61,121,74,112,99,51,77,105,79,105,74,111,100,72,82,119,99,122,111,118,76,50,70,106,89,50,57,49,98,110,82,122,76,109,100,118,98,50,100,115,90,83,53,106,98,50,48,105,76,67,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,67,74,104,100,87,81,105,79,105,73,49,78,122,85,49,77,84,107,121,77,68,81,121,77,122,99,116,98,88,78,118,99,68,108,108,99,68,81,49,100,84,74,49,98,122,107,52,97,71,70,119,99,87,49,117,90,51,89,52,90,68,103,48,99,87,82,106,79,71,115,117,89,88,66,119,99,121,53,110,98,50,57,110,98,71,86,49,99,50,86,121,89,50,57,117,100,71,86,117,100,67,53,106,98,50,48,105,76,67,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,216,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"max_epoch\":10000,\"num_sha2_blocks\":11,\"payload_len\":564,\"payload_start_index\":103}").unwrap(); +fn test_verify_groth16_in_bytes_twitch() { + let aux_inputs = AuxInputs::from_json("{ \"claims\": [{\"name\": \"iss\", \"value_base64\": \"wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw\", \"index_mod_4\": 2 }, { \"name\": \"aud\", \"value_base64\": \"yJhdWQiOiJkMzFpY3FsNmw4eHpwYTdlZjMxenR4eXNzNDZvY2siLC\", \"index_mod_4\": 1 }], \"header_base64\": \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ\", \"addr_seed\": \"18704353972820279499196832783883157878280522634176394693508060053542990860397\", \"eph_public_key\": [ \"17932473587154777519561053972421347139\", \"134696963602902907403122104327765350261\" ], \"max_epoch\": 10000, \"key_claim_name\": \"sub\", \"modulus\": \"29584508445356008889845267980505093503375439259749620943404021520463333925791823787854637986725604249075338223707087145400343940878353492428289420309978060548116589201208844416027772690277158239820043587577191204733372062526546165382385491203581708579354007308101776609837604633876932723191352425015951165606420826212084566356749532802619014158140227417781995864324396969703870653300311928497704373150532338687663444677910609365123056747321631910862597087829149037858457007019073259229864068519186398691475937234253291322606806349186490894620279606094962475240526774193684946100117999885854009961035928969402039575583\" }").unwrap().init().unwrap(); let public_inputs = PublicInputs::from_json( - "[\"2487117669597822357956926047501254969190518860900347921480370492048882803688\"]", + "[\"17466137871302348802176618957727679727566476655921498778525221619744941215202\"]", ) .unwrap(); + assert_eq!(aux_inputs.get_max_epoch(), 10000); + assert_eq!( + aux_inputs.get_address_seed(), + "18704353972820279499196832783883157878280522634176394693508060053542990860397" + ); + assert_eq!(aux_inputs.get_iss(), OAuthProvider::Twitch.get_config().0); + assert_eq!(aux_inputs.get_key_claim_name(), "sub"); + assert_eq!(aux_inputs.get_aud(), "d31icql6l8xzpa7ef31ztxyss46ock"); + assert_eq!(aux_inputs.get_kid(), "1"); + assert_eq!(aux_inputs.get_mod(), "29584508445356008889845267980505093503375439259749620943404021520463333925791823787854637986725604249075338223707087145400343940878353492428289420309978060548116589201208844416027772690277158239820043587577191204733372062526546165382385491203581708579354007308101776609837604633876932723191352425015951165606420826212084566356749532802619014158140227417781995864324396969703870653300311928497704373150532338687663444677910609365123056747321631910862597087829149037858457007019073259229864068519186398691475937234253291322606806349186490894620279606094962475240526774193684946100117999885854009961035928969402039575583"); assert_eq!( aux_inputs.calculate_all_inputs_hash().unwrap(), - public_inputs.get_all_inputs_hash() + public_inputs.get_all_inputs_hash().unwrap() ); + let mut map = HashMap::new(); + map.insert(("1", Twitch.get_config().0), OAuthProviderContent { + kty: "RSA".to_string(), + e: "AQAB".to_string(), + n: "6lq9MQ-q6hcxr7kOUp-tHlHtdcDsVLwVIw13iXUCvuDOeCi0VSuxCCUY6UmMjy53dX00ih2E4Y4UvlrmmurK0eG26b-HMNNAvCGsVXHU3RcRhVoHDaOwHwU72j7bpHn9XbP3Q3jebX6KIfNbei2MiR0Wyb8RZHE-aZhRYO8_-k9G2GycTpvc-2GBsP8VHLUKKfAs2B6sW3q3ymU6M0L-cFXkZ9fHkn9ejs-sqZPhMJxtBPBxoUIUQFTgv4VXTSv914f_YkNw-EjuwbgwXMvpyr06EyfImxHoxsZkFYB-qBYHtaMxTnFsZBr6fn8Ha2JqT1hoP7Z5r5wxDu3GQhKkHw".to_string(), + alg: "RS256".to_string(), + }); + let proof = ZkLoginProof::from_json("{ \"pi_a\": [ \"14609816250208775088998769033922823275418989011294962335042447516759468155261\", \"20377558696931353568738668428784363385404286135420274775798451001900237387711\", \"1\" ], \"pi_b\": [ [ \"13205564493500587952133306511249429194738679332267485407336676345714082870630\", \"20796060045071998078434479958974217243296767801927986923760870304883706846959\" ], [ \"18144611315874106283809557225033182618356564976139850467162456490949482704538\", \"4318715074202832054732474611176035084202678394565328538059624195976255391002\" ], [ \"1\", \"0\" ] ], \"pi_c\": [ \"4215643272645108456341625420022677634747189283615115637991603989161283548307\", \"5549730540188640204480179088531560793048476496379683802205245590402338452458\", \"1\" ], \"protocol\": \"groth16\"}"); + assert!(proof.is_ok()); + + let res = verify_zk_login(&proof.unwrap(), &public_inputs, &aux_inputs, 1, map); + assert!(res.is_ok()); +} + +#[test] +fn test_parsed_masked_content() { + let header = JWTHeader::new("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ").unwrap(); + assert_eq!(header.alg, "RS256"); + assert_eq!(header.typ, "JWT"); + + // Invalid base64 assert_eq!( - aux_inputs.get_jwt_hash(), - vec![ - 187, 81, 38, 253, 76, 198, 157, 166, 214, 87, 161, 53, 77, 141, 223, 15, 85, 99, 17, - 247, 75, 248, 40, 150, 239, 21, 140, 190, 12, 123, 242, 175 - ] + JWTHeader::new("").unwrap_err(), + FastCryptoError::InvalidInput ); + const VALID_HEADER: &str = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ"; + + // iss not found assert_eq!( - aux_inputs.get_eph_pub_key(), - vec![ - 13, 125, 171, 53, 140, 141, 173, 170, 78, 250, 0, 73, 167, 91, 7, 67, 101, 85, 177, 10, - 54, 130, 25, 187, 104, 15, 112, 87, 19, 73, 215, 117 - ] + ParsedMaskedContent::new(VALID_HEADER, &[]).unwrap_err(), + FastCryptoError::GeneralError("iss not found in claims".to_string()) ); - assert_eq!(aux_inputs.get_max_epoch(), 10000); - assert!(aux_inputs.get_jwt_signature().is_ok()); - assert_eq!(aux_inputs.get_iss(), OAuthProvider::Google.get_config().0); - assert_eq!(aux_inputs.get_claim_name(), "sub"); + + // aud not found assert_eq!( - aux_inputs.get_client_id(), - "575519204237-msop9ep45u2uo98hapqmngv8d84qdc8k.apps.googleusercontent.com" + ParsedMaskedContent::new( + VALID_HEADER, + &[Claim { + name: "iss".to_string(), + value_base64: "wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw".to_string(), + index_mod_4: 2 + }] + ) + .unwrap_err(), + FastCryptoError::GeneralError("aud not found in claims".to_string()) ); - let zk_login_proof = ZkLoginProof::from_json("{\"pi_a\":[\"20070135235140453412363491950139702043798224873934096121884449618027498346650\",\"13452863257899491867230158359348144830940035303347103011373365564048084133173\",\"1\"],\"pi_b\":[[\"20638328149829717497898296893247679667811257514682013496341452050037879873527\",\"14567869016011681044567557818367451228190153931364680049952266175100520394660\"],[\"9918106341458194117820109842171662726217686693538121470076005914489542849473\",\"9007925129766485823687923464528692530984014268682479943901937985832629774609\"],[\"1\",\"0\"]],\"pi_c\":[\"8734022240376125982913134696535916485635821200541495641215567164823074832847\",\"10246591422531428652485093520381188142407035304263437810405902704187483736151\",\"1\"],\"protocol\":\"groth16\"}"); - assert!(zk_login_proof.is_ok()); - let res = verify_zk_login_proof_with_fixed_vk(&zk_login_proof.unwrap(), &public_inputs); - assert!(res.unwrap()); + // unknown claim name + assert_eq!( + ParsedMaskedContent::new( + VALID_HEADER, + &[Claim { + name: "unknown".to_string(), + value_base64: "wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw".to_string(), + index_mod_4: 2 + }] + ) + .unwrap_err(), + FastCryptoError::GeneralError("Invalid claim name".to_string()) + ); + + // bad index_mod_4 + assert_eq!( + ParsedMaskedContent::new( + VALID_HEADER, + &[ + Claim { + name: "iss".to_string(), + value_base64: "wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw".to_string(), + index_mod_4: 2 + }, + Claim { + name: "aud".to_string(), + value_base64: "yJhdWQiOiJkMzFpY3FsNmw4eHpwYTdlZjMxenR4eXNzNDZvY2siLC" + .to_string(), + index_mod_4: 2 + } + ] + ) + .unwrap_err(), + FastCryptoError::GeneralError("Invalid masked content".to_string()) + ); } #[test] -fn test_masked_content_parse() { - // bytes after 64 * num_sha2_blocks contains non-zeros fails. - let content = ParsedMaskedContent::new(&[1; 65], 0, 0, 1); - assert!(content.is_err()); +fn test_decode_base64() { + assert_eq!( + decode_base64_url("", &0).unwrap_err(), + FastCryptoError::GeneralError("Base64 string smaller than 2".to_string()) + ); + assert_eq!( + decode_base64_url("yJhdWQiOiJkMzFpY3FsNmw4eHpwYTdlZjMxenR4eXNzNDZvY2siLC", &0).unwrap_err(), + FastCryptoError::GeneralError("Invalid last_char_offset".to_string()) + ); + assert!(decode_base64_url("yJhdWQiOiJkMzFpY3FsNmw4eHpwYTdlZjMxenR4eXNzNDZvY2siLC", &1).is_ok()); + assert_eq!( + decode_base64_url("yJhdWQiOiJkMzFpY3FsNmw4eHpwYTdlZjMxenR4eXNzNDZvY2siLC", &2).unwrap_err(), + FastCryptoError::GeneralError("Invalid masked content".to_string()) + ); + assert_eq!( + decode_base64_url("yJhdWQiOiJkMzFpY3FsNmw4eHpwYTdlZjMxenR4eXNzNDZvY2siLC", &3).unwrap_err(), + FastCryptoError::GeneralError("Invalid first_char_offset".to_string()) + ); +} - // payload index must be >= 1 - let content = ParsedMaskedContent::new(&[0; 65], 0, 0, 1); - assert!(content.is_err()); +#[test] +fn test_verify_extended_claim() { + // does not end with , or } + assert_eq!( + verify_extended_claim("\"iss\":\"https://accounts.google.com\"", "iss").unwrap_err(), + FastCryptoError::GeneralError("Invalid extended claim".to_string()) + ); + + // Unexpected claim name + assert_eq!( + verify_extended_claim("\"iss\":\"https://accounts.google.com\",", "aud").unwrap_err(), + FastCryptoError::InvalidInput + ); - // value at (payload index - 1) must be "." - // TODO: cover all parsed masked content logic + // Malformed json + assert_eq!( + verify_extended_claim("iss\":\"https://accounts.google.com\"", "iss").unwrap_err(), + FastCryptoError::GeneralError("Invalid extended claim".to_string()) + ); + assert_eq!( + verify_extended_claim("\"iss\"\"https://accounts.google.com\"", "iss").unwrap_err(), + FastCryptoError::GeneralError("Invalid extended claim".to_string()) + ); +} + +#[test] +fn test_map_to_field() { + // Test generated against typescript implementation. + assert_eq!( + map_to_field("sub", 10).unwrap().to_string(), + "18523124550523841778801820019979000409432455608728354507022210389496924497355" + ); + assert_eq!( + map_to_field("yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC", 133) + .unwrap() + .to_string(), + "19198909745930267855439585988170070469004479286780644790990940640914248274464" + ); + assert_eq!(map_to_field("CJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLC", 133).unwrap().to_string(), "6914089902564896687047107167562960781243311797290496295481879371814854678998"); + assert_eq!(map_to_field("eyJhbGciOiJSUzI1NiIsImtpZCI6ImM5YWZkYTM2ODJlYmYwOWViMzA1NWMxYzRiZDM5Yjc1MWZiZjgxOTUiLCJ0eXAiOiJKV1QifQ", 150).unwrap().to_string(), "11195180390614794854381992733393925748746563026948577817495625199891112836762"); } diff --git a/fastcrypto-zkp/src/bn254/zk_login.rs b/fastcrypto-zkp/src/bn254/zk_login.rs index 502170c320..dfff307fd0 100644 --- a/fastcrypto-zkp/src/bn254/zk_login.rs +++ b/fastcrypto-zkp/src/bn254/zk_login.rs @@ -1,38 +1,37 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use ark_crypto_primitives::snark::SNARK; +use serde_json::Value; use std::collections::HashMap; use std::fmt; -use super::{poseidon::PoseidonWrapper, verifier::process_vk_special}; -use crate::bn254::VerifyingKey as Bn254VerifyingKey; +use super::poseidon::PoseidonWrapper; use crate::circom::CircomPublicInputs; -use crate::{ - bn254::verifier::PreparedVerifyingKey, - circom::{g1_affine_from_str_projective, g2_affine_from_str_projective}, -}; +use crate::circom::{g1_affine_from_str_projective, g2_affine_from_str_projective}; pub use ark_bn254::{Bn254, Fr as Bn254Fr}; pub use ark_ff::ToConstraintField; -use ark_groth16::{Groth16, Proof, VerifyingKey}; +use ark_groth16::Proof; pub use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use fastcrypto::{ error::FastCryptoError, rsa::{Base64UrlUnpadded, Encoding}, }; -use num_bigint::{BigInt, BigUint}; +use num_bigint::BigUint; use once_cell::sync::Lazy; -use regex::Regex; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use serde_json::Value; use std::str::FromStr; #[cfg(test)] #[path = "unit_tests/zk_login_tests.rs"] mod zk_login_tests; -static GLOBAL_VERIFYING_KEY: Lazy = Lazy::new(global_pvk); +const MAX_EXTENDED_ISS_LEN: u8 = 99; +const MAX_EXTENDED_ISS_LEN_B64: u8 = 1 + (4 * (MAX_EXTENDED_ISS_LEN / 3)); +const MAX_EXTENDED_AUD_LEN: u8 = 99; +const MAX_EXTENDED_AUD_LEN_B64: u8 = 1 + (4 * (MAX_EXTENDED_AUD_LEN / 3)); +const MAX_HEADER_LEN: u8 = 150; +const PACK_WIDTH: u8 = 248; /// Hardcoded mapping from the provider and its supported key claim name to its map-to-field Big Int in string. /// The field value is computed from the max key claim length and its provider. @@ -45,33 +44,19 @@ static SUPPORTED_KEY_CLAIM_TO_FIELD: Lazy> = Lazy: ), "18523124550523841778801820019979000409432455608728354507022210389496924497355", ); - map.insert( - ( - OAuthProvider::Google.get_config().0, - SupportedKeyClaim::Email.to_string(), - ), - "", - ); map.insert( ( OAuthProvider::Twitch.get_config().0, SupportedKeyClaim::Sub.to_string(), ), - "", - ); - map.insert( - ( - OAuthProvider::Twitch.get_config().0, - SupportedKeyClaim::Email.to_string(), - ), - "", + "18523124550523841778801820019979000409432455608728354507022210389496924497355", ); map }); /// Supported OAuth providers. Must contain "openid" in "scopes_supported" /// and "public" for "subject_types_supported" instead of "pairwise". -#[derive(Debug)] +#[derive(Debug, Hash, PartialEq, Eq)] pub enum OAuthProvider { /// See https://accounts.google.com/.well-known/openid-configuration Google, @@ -79,6 +64,21 @@ pub enum OAuthProvider { Twitch, } +/// Struct that contains all the OAuth provider information. A list of them can +/// be retrieved from the JWK endpoint (e.g. ) +/// and published on the bulletin along with a trusted party's signature. +#[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Hash, Serialize, Deserialize)] +pub struct OAuthProviderContent { + /// Key type parameter, https://datatracker.ietf.org/doc/html/rfc7517#section-4.1 + pub kty: String, + /// RSA public exponent, https://datatracker.ietf.org/doc/html/rfc7517#section-9.3 + pub e: String, + /// RSA modulus, https://datatracker.ietf.org/doc/html/rfc7517#section-9.3 + pub n: String, + /// Algorithm parameter, https://datatracker.ietf.org/doc/html/rfc7517#section-4.4 + pub alg: String, +} + impl OAuthProvider { /// Returns a tuple of iss string and JWK endpoint string for the given provider. pub fn get_config(&self) -> (&str, &str) { @@ -93,24 +93,15 @@ impl OAuthProvider { ), } } - - /// Returns the provider for the given iss string. - pub fn from_iss(iss: &str) -> Result { - match iss { - "https://accounts.google.com" => Ok(Self::Google), - "https://id.twitch.tv/oauth2" => Ok(Self::Twitch), - _ => Err(FastCryptoError::InvalidInput), - } - } } /// The claims in the body signed by OAuth provider that must /// be locally unique to the provider and cannot be reassigned. #[derive(Debug)] pub enum SupportedKeyClaim { - /// Subject id representing an unique account. + /// Subject id representing an unique account for provider. Sub, - /// Email string representing an unique account. + /// Email string representing an unique account for provider. Email, } @@ -123,124 +114,24 @@ impl fmt::Display for SupportedKeyClaim { } } -/// Return whether the claim string is supported for zk login. -pub fn is_claim_supported(claim_name: &str) -> bool { - vec![SupportedKeyClaim::Sub.to_string()].contains(&claim_name.to_owned()) -} - -/// Verify a zk login proof using the fixed verifying key. -pub fn verify_zk_login_proof_with_fixed_vk( - proof: &ZkLoginProof, - public_inputs: &PublicInputs, -) -> Result { - Groth16::::verify_with_processed_vk( - &GLOBAL_VERIFYING_KEY.as_arkworks_pvk(), - &public_inputs.as_arkworks(), - &proof.as_arkworks(), - ) - .map_err(|e| FastCryptoError::GeneralError(e.to_string())) -} - -/// Load a fixed verifying key from zklogin.vkey output from setup -/// https://github.com/MystenLabs/fastcrypto/blob/2a704431e4d2685625c0cc06d19fd7d08a4aafa4/openid-zkp-auth/README.md -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![ - "20491192805390485299153009773594534940189261866228447918068658471970481763042".to_string(), - "9383485363053290200918347156157836566562967994039712273449902621266178545958".to_string(), - "1".to_string(), - ]); - let vk_beta_2 = g2_affine_from_str_projective(vec![ - vec![ - "6375614351688725206403948262868962793625744043794305715222011528459656738731" - .to_string(), - "4252822878758300859123897981450591353533073413197771768651442665752259397132" - .to_string(), - ], - vec![ - "10505242626370262277552901082094356697409835680220590971873171140371331206856" - .to_string(), - "21847035105528745403288232691147584728191162732299865338377159692350059136679" - .to_string(), - ], - vec!["1".to_string(), "0".to_string()], - ]); - let vk_gamma_2 = g2_affine_from_str_projective(vec![ - vec![ - "10857046999023057135944570762232829481370756359578518086990519993285655852781" - .to_string(), - "11559732032986387107991004021392285783925812861821192530917403151452391805634" - .to_string(), - ], - vec![ - "8495653923123431417604973247489272438418190587263600148770280649306958101930" - .to_string(), - "4082367875863433681332203403145435568316851327593401208105741076214120093531" - .to_string(), - ], - vec!["1".to_string(), "0".to_string()], - ]); - let vk_delta_2 = g2_affine_from_str_projective(vec![ - vec![ - "10857046999023057135944570762232829481370756359578518086990519993285655852781" - .to_string(), - "11559732032986387107991004021392285783925812861821192530917403151452391805634" - .to_string(), - ], - vec![ - "8495653923123431417604973247489272438418190587263600148770280649306958101930" - .to_string(), - "4082367875863433681332203403145435568316851327593401208105741076214120093531" - .to_string(), - ], - vec!["1".to_string(), "0".to_string()], - ]); - - // Create a vector of G1Affine elements from the IC - let mut vk_gamma_abc_g1 = Vec::new(); - for e in vec![ - vec![ - "18931764958316061396537365316410279129357566768168194299771466990652581507745" - .to_string(), - "19589594864158083697499253358172374190940731232487666687594341722397321059767" - .to_string(), - "1".to_string(), - ], - vec![ - "6267760579143073538587735682191258967573139158461221609828687320377758856284" - .to_string(), - "18672820669757254021555424652581702101071897282778751499312181111578447239911" - .to_string(), - "1".to_string(), - ], - ] { - let g1 = g1_affine_from_str_projective(e); - vk_gamma_abc_g1.push(g1); - } - - let vk = VerifyingKey { - alpha_g1: vk_alpha_1, - beta_g2: vk_beta_2, - gamma_g2: vk_gamma_2, - delta_g2: vk_delta_2, - gamma_abc_g1: vk_gamma_abc_g1, - }; - process_vk_special(&Bn254VerifyingKey(vk)) +/// Necessary value for claim. +#[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Serialize, Deserialize)] +pub struct Claim { + name: String, + value_base64: String, + index_mod_4: u8, } /// A parsed result of all aux inputs. #[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Serialize, Deserialize)] pub struct AuxInputs { + claims: Vec, + header_base64: String, addr_seed: String, eph_public_key: Vec, - jwt_sha2_hash: Vec, - jwt_signature: String, - key_claim_name: String, - masked_content: Vec, max_epoch: u64, - num_sha2_blocks: u8, - payload_len: u16, - payload_start_index: u16, + key_claim_name: String, + modulus: String, #[serde(skip)] parsed_masked_content: ParsedMaskedContent, } @@ -248,126 +139,92 @@ pub struct AuxInputs { impl AuxInputs { /// Validate and parse masked content bytes into the struct and other json strings into the struct. pub fn from_json(value: &str) -> Result { - let mut inputs: AuxInputs = + let inputs: AuxInputs = serde_json::from_str(value).map_err(|_| FastCryptoError::InvalidInput)?; - inputs.parsed_masked_content = ParsedMaskedContent::new( - &inputs.masked_content, - inputs.payload_start_index, - inputs.payload_len, - inputs.num_sha2_blocks, - )?; Ok(inputs) } - /// Init ParsedMaskedContent + /// Initialize ParsedMaskedContent pub fn init(&mut self) -> Result { - self.parsed_masked_content = ParsedMaskedContent::new( - &self.masked_content, - self.payload_start_index, - self.payload_len, - self.num_sha2_blocks, - )?; + self.parsed_masked_content = ParsedMaskedContent::new(&self.header_base64, &self.claims)?; Ok(self.to_owned()) } - /// Get the jwt hash in byte array format. - pub fn get_jwt_hash(&self) -> Vec { - self.jwt_sha2_hash - .iter() - .flat_map(|x| big_int_str_to_bytes(x)) - .collect() - } - - /// Get the ephemeral pubkey in bytes. - pub fn get_eph_pub_key(&self) -> Vec { - self.eph_public_key - .iter() - .flat_map(|x| big_int_str_to_bytes(x)) - .collect() - } /// Get the max epoch value. pub fn get_max_epoch(&self) -> u64 { self.max_epoch } - /// Get jwt signature in bytes. - pub fn get_jwt_signature(&self) -> Result, FastCryptoError> { - Base64UrlUnpadded::decode_vec(&self.jwt_signature) - .map_err(|_| FastCryptoError::InvalidInput) - } - /// Get the address seed in string. pub fn get_address_seed(&self) -> &str { &self.addr_seed } - /// Get the iss string. + /// Get the parsed iss string. pub fn get_iss(&self) -> &str { - self.parsed_masked_content.get_iss() + &self.parsed_masked_content.iss + } + + /// Get the parsed aud string. + pub fn get_aud(&self) -> &str { + &self.parsed_masked_content.aud } - /// Get the client id string. - pub fn get_client_id(&self) -> &str { - self.parsed_masked_content.get_client_id() + /// Get the key claim name used. + pub fn get_key_claim_name(&self) -> &str { + &self.key_claim_name } - /// Get the kid string. + /// Get the parsed kid string. pub fn get_kid(&self) -> &str { - self.parsed_masked_content.get_kid() + &self.parsed_masked_content.kid } - /// Get the key claim name string. - pub fn get_claim_name(&self) -> &str { - &self.key_claim_name + /// Get the modulus. + pub fn get_mod(&self) -> &str { + &self.modulus } /// Calculate the poseidon hash from 10 selected fields in the aux inputs. pub fn calculate_all_inputs_hash(&self) -> Result { - // TODO(joyqvq): check each string for bigint is valid. let mut poseidon = PoseidonWrapper::new(); - let jwt_sha2_hash_0 = Bn254Fr::from_str(&self.jwt_sha2_hash[0]).unwrap(); - let jwt_sha2_hash_1 = Bn254Fr::from_str(&self.jwt_sha2_hash[1]).unwrap(); - let masked_content_hash = Bn254Fr::from_str(&self.parsed_masked_content.hash).unwrap(); - let payload_start_index = Bn254Fr::from_str(&self.payload_start_index.to_string()).unwrap(); - let payload_len = Bn254Fr::from_str(&self.payload_len.to_string()).unwrap(); - let eph_public_key_0 = Bn254Fr::from_str(&self.eph_public_key[0]).unwrap(); - let eph_public_key_1 = Bn254Fr::from_str(&self.eph_public_key[1]).unwrap(); - let max_epoch = Bn254Fr::from_str(&self.max_epoch.to_string()).unwrap(); - let num_sha2_blocks = Bn254Fr::from_str(&self.num_sha2_blocks.to_string()).unwrap(); - let addr_seed = Bn254Fr::from_str(&self.addr_seed.to_string()).unwrap(); - let key_claim_name_f = Bn254Fr::from_str( + let addr_seed = to_field(&self.addr_seed)?; + let eph_public_key_0 = to_field(&self.eph_public_key[0])?; + let eph_public_key_1 = to_field(&self.eph_public_key[1])?; + let max_epoch = to_field(&self.max_epoch.to_string())?; + let key_claim_name_f = to_field( SUPPORTED_KEY_CLAIM_TO_FIELD - .get(&(self.get_iss(), self.get_claim_name().to_owned())) - .unwrap(), - ) - .unwrap(); + .get(&(self.get_iss(), self.get_key_claim_name().to_owned())) + .ok_or(FastCryptoError::InvalidInput)?, + )?; + let iss_f = map_to_field( + &self.parsed_masked_content.iss_str, + MAX_EXTENDED_ISS_LEN_B64, + )?; + let aud_f = map_to_field( + &self.parsed_masked_content.aud_str, + MAX_EXTENDED_AUD_LEN_B64, + )?; + let header_f = map_to_field(&self.parsed_masked_content.header, MAX_HEADER_LEN)?; + let iss_index = to_field(&self.parsed_masked_content.iss_index.to_string())?; + let aud_index = to_field(&self.parsed_masked_content.aud_index.to_string())?; + Ok(poseidon .hash(vec![ - jwt_sha2_hash_0, - jwt_sha2_hash_1, - masked_content_hash, - payload_start_index, - payload_len, + addr_seed, eph_public_key_0, eph_public_key_1, max_epoch, - num_sha2_blocks, key_claim_name_f, - addr_seed, + iss_f, + iss_index, + aud_f, + aud_index, + header_f, ])? .to_string()) } } - -/// A structed of all parsed and validated values from the masked content bytes. -#[derive(Default, Debug, Clone, PartialEq, Eq, JsonSchema, Hash, Serialize, Deserialize)] -pub struct ParsedMaskedContent { - header: JWTHeader, - iss: String, - client_id: String, - hash: String, -} - /// Struct that represents a standard JWT header according to /// https://openid.net/specs/openid-connect-core-1_0.html #[derive(Default, Debug, Clone, PartialEq, Eq, JsonSchema, Hash, Serialize, Deserialize)] @@ -377,109 +234,80 @@ pub struct JWTHeader { typ: String, } -impl ParsedMaskedContent { - /// Parse the masked content bytes into a [struct ParsedMaskedContent]. - /// payload_start_index, payload_len, num_sha2_blocks are used for - /// validation and parsing. - pub fn new( - masked_content: &[u8], - payload_start_index: u16, - payload_len: u16, - num_sha2_blocks: u8, - ) -> Result { - // Verify the bytes after 64 * num_sha2_blocks should be all 0s. - if !masked_content - .get(64 * num_sha2_blocks as usize..) - .ok_or(FastCryptoError::InvalidInput)? - .iter() - .all(|&x| x == 0) - { - return Err(FastCryptoError::GeneralError( - "Incorrect payload padding".to_string(), - )); - } - - let masked_content_tmp = masked_content - .get(..64 * num_sha2_blocks as usize) - .ok_or(FastCryptoError::InvalidInput)?; - - // Verify the byte at payload start index is indeed b'.'. - if payload_start_index < 1 - || masked_content_tmp.get(payload_start_index as usize - 1) != Some(&b'.') - { - return Err(FastCryptoError::GeneralError( - "Incorrect payload index for separator".to_string(), - )); - } - - let header = parse_and_validate_header( - masked_content_tmp - .get(0..payload_start_index as usize - 1) - .ok_or_else(|| { - FastCryptoError::GeneralError( - "Invalid payload index to parse header".to_string(), - ) - })?, - )?; - - // Parse the jwt length from the last 8 bytes of the masked content. - let jwt_length_bytes = masked_content_tmp - .get(masked_content_tmp.len() - 8..) - .ok_or_else(|| FastCryptoError::GeneralError("Invalid last 8 bytes".to_string()))?; - let jwt_length = calculate_value_from_bytearray(jwt_length_bytes); - - // Verify the jwt length equals to 8*(payload_start_index + payload_len). - if jwt_length != 8 * (payload_start_index as usize + payload_len as usize) { - return Err(FastCryptoError::GeneralError( - "Incorrect jwt length".to_string(), - )); +impl JWTHeader { + /// Parse the header base64 string into a [struct JWTHeader]. + pub fn new(header_base64: &str) -> Result { + let header_bytes = Base64UrlUnpadded::decode_vec(header_base64) + .map_err(|_| FastCryptoError::InvalidInput)?; + let header_str = + std::str::from_utf8(&header_bytes).map_err(|_| FastCryptoError::InvalidInput)?; + let header: JWTHeader = + serde_json::from_str(header_str).map_err(|_| FastCryptoError::InvalidInput)?; + if header.alg != "RS256" || header.typ != "JWT" { + return Err(FastCryptoError::GeneralError("Invalid header".to_string())); } + Ok(header) + } +} - // Parse sha2 pad into a bit array. - let sha_2_pad = bytearray_to_bits( - &masked_content_tmp[payload_start_index as usize + payload_len as usize..], - ); +/// A structed of all parsed and validated values from the masked content bytes. +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub struct ParsedMaskedContent { + kid: String, + header: String, + iss: String, + aud: String, + iss_str: String, + aud_str: String, + iss_index: u8, + aud_index: u8, +} - // Verify that the first bit of the bit array of sha2 pad is 1. - if !sha_2_pad[0] { - return Err(FastCryptoError::GeneralError( - "Incorrect sha2 padding".to_string(), - )); +impl ParsedMaskedContent { + /// Read a list of Claims and header string and parse them into fields + /// header, iss, iss_index, aud, aud_index. + pub fn new(header_base64: &str, claims: &[Claim]) -> Result { + let header = JWTHeader::new(header_base64)?; + let mut iss = None; + let mut aud = None; + for claim in claims { + match claim.name.as_str() { + "iss" => { + iss = Some(( + decode_base64_url(&claim.value_base64, &claim.index_mod_4)?, + claim.value_base64.clone(), + claim.index_mod_4, + )); + } + "aud" => { + aud = Some(( + decode_base64_url(&claim.value_base64, &claim.index_mod_4)?, + claim.value_base64.clone(), + claim.index_mod_4, + )); + } + _ => { + return Err(FastCryptoError::GeneralError( + "Invalid claim name".to_string(), + )); + } + } } - - // Verify the count of 0s in the sha2 pad bit array satifies the condition - // with the jwt length. - validate_zeros_count(&sha_2_pad, jwt_length)?; - - // Splits the masked payload into 3 parts (that reveals iss, aud, nonce respectively) - // separated by a delimiter of "=" of any length. With padding etc - let parts = find_parts_and_indices( - &masked_content_tmp - [payload_start_index as usize..payload_start_index as usize + payload_len as usize], - )?; - - Ok(Self { - header, - iss: parts[0].to_string(), - client_id: parts[1].to_string(), - hash: calculate_merklized_hash(masked_content)?, + let iss_val = iss + .ok_or_else(|| FastCryptoError::GeneralError("iss not found in claims".to_string()))?; + let aud_val = aud + .ok_or_else(|| FastCryptoError::GeneralError("aud not found in claims".to_string()))?; + Ok(ParsedMaskedContent { + kid: header.kid, + header: header_base64.to_string(), + iss: verify_extended_claim(&iss_val.0.to_string(), "iss")?, + aud: verify_extended_claim(&aud_val.0.to_string(), "aud")?, + iss_str: iss_val.1, + aud_str: aud_val.1, + iss_index: iss_val.2, + aud_index: aud_val.2, }) } - - /// Get the iss string value. - pub fn get_iss(&self) -> &str { - &self.iss - } - - /// Get the kid string value. - pub fn get_kid(&self) -> &str { - &self.header.kid - } - - /// Get the client id string value. - pub fn get_client_id(&self) -> &str { - &self.client_id - } } /// The zk login proof. @@ -526,98 +354,103 @@ impl PublicInputs { } /// Convert the public inputs into arkworks format. - pub fn as_arkworks(&self) -> Vec { - // TODO(joyqvq): check safety for valid bigint string. - self.inputs - .iter() - .map(|x| Bn254Fr::from_str(x).unwrap()) - .collect() + pub fn as_arkworks(&self) -> Result, FastCryptoError> { + let mut result = Vec::new(); + for input in &self.inputs { + match Bn254Fr::from_str(input) { + Ok(value) => result.push(value), + Err(_) => return Err(FastCryptoError::InvalidInput), + } + } + Ok(result) } /// Get the all_inputs_hash as big int string. - pub fn get_all_inputs_hash(&self) -> &str { - &self.inputs[0] + pub fn get_all_inputs_hash(&self) -> Result<&str, FastCryptoError> { + if self.inputs.len() != 1 { + return Err(FastCryptoError::InvalidInput); + } + Ok(&self.inputs[0]) } } -/// Parse the ascii string from the input bytearray and split it by delimiter "=" of any -/// length. Return a list of the split parts and a list of start indices of each part. -fn find_parts_and_indices(input: &[u8]) -> Result, FastCryptoError> { - let input_str = std::str::from_utf8(input) - .map_err(|_| FastCryptoError::GeneralError("Invalid masked content".to_string()))?; - let re = Regex::new("=+").expect("Regex string should be valid"); - - let mut chunks = Vec::new(); - let mut start_idx = 0; - - for mat in re.find_iter(input_str) { - let end_idx = mat.start(); - if start_idx < end_idx { - if start_idx % 4 == 3 || end_idx % 4 == 0 { - return Err(FastCryptoError::GeneralError( - "Invalid start or end index".to_string(), - )); - } - let mut chunk_in_bits: Vec = base64_to_bitarray(&input_str[start_idx..end_idx]); - let original_len = chunk_in_bits.len(); - if start_idx % 4 == 1 { - chunk_in_bits.drain(..2); - } else if start_idx % 4 == 2 { - chunk_in_bits.drain(..4); - } +/// Parse the extended claim json value to its claim value, using the expected claim key. +fn verify_extended_claim( + extended_claim: &str, + expected_key: &str, +) -> Result { + // Last character of each extracted_claim must be '}' or ',' + if !(extended_claim.ends_with('}') || extended_claim.ends_with(',')) { + return Err(FastCryptoError::GeneralError( + "Invalid extended claim".to_string(), + )); + } + + let json_str = format!("{{{}}}", &extended_claim[..extended_claim.len() - 1]); + let json: Value = serde_json::from_str(&json_str).map_err(|_| FastCryptoError::InvalidInput)?; + let value = json + .as_object() + .ok_or(FastCryptoError::InvalidInput)? + .get(expected_key) + .ok_or(FastCryptoError::InvalidInput)? + .as_str() + .ok_or(FastCryptoError::InvalidInput)?; + Ok(value.to_string()) +} - if end_idx % 4 == 1 { - chunk_in_bits.drain(original_len - 4..); - } else if end_idx % 4 == 2 { - chunk_in_bits.drain(original_len - 2..); - }; - - let bytearray = bits_to_bytes(&chunk_in_bits); - let input_str = std::str::from_utf8(&bytearray).map_err(|_| { - FastCryptoError::GeneralError("Invalid bytearray from tweaked bits".to_string()) - })?; - chunks.push(input_str.to_string()); +/// Parse the base64 string, add paddings based on offset, and convert to a bytearray. +fn decode_base64_url(s: &str, i: &u8) -> Result { + if s.len() < 2 { + return Err(FastCryptoError::GeneralError( + "Base64 string smaller than 2".to_string(), + )); + } + let mut bits = base64_to_bitarray(s); + let first_char_offset = i % 4; + match first_char_offset { + 0 => {} + 1 => { + bits.drain(..2); + } + 2 => { + bits.drain(..4); + } + _ => { + return Err(FastCryptoError::GeneralError( + "Invalid first_char_offset".to_string(), + )); } - start_idx = mat.end(); } - Ok(vec![ - find_value(&chunks[0], "\"iss\":\"", "\",")?, - find_value(&chunks[1], "\"aud\":\"", "\",")?, - ]) -} -/// Given a part in string, find the value between the prefix and suffix. -/// The index value is used to decide the number of '0' needed to pad to -/// make the parts an valid Base64 encoding. -fn find_value(ascii_string: &str, prefix: &str, suffix: &str) -> Result { - let start = ascii_string - .find(prefix) - .ok_or_else(|| FastCryptoError::GeneralError("Invalid parts prefix".to_string()))? - + prefix.len(); - let end = ascii_string[start..] - .find(suffix) - .ok_or_else(|| FastCryptoError::GeneralError("Invalid ascii suffix".to_string()))? - + start; - Ok(ascii_string[start..end].to_string()) -} + let last_char_offset = (i + s.len() as u8 - 1) % 4; + match last_char_offset { + 3 => {} + 2 => { + bits.drain(bits.len() - 2..); + } + 1 => { + bits.drain(bits.len() - 4..); + } + _ => { + return Err(FastCryptoError::GeneralError( + "Invalid last_char_offset".to_string(), + )); + } + } -/// Count the number of 0s in the bit array and check if the count satifies as the -/// smallest, non-negative solution to equation jwt_length + 1 + K = 448 (mod 512). -/// See more at 4.1(b) https://datatracker.ietf.org/doc/html/rfc4634#section-4.1 -fn validate_zeros_count(arr: &[bool], jwt_length: usize) -> Result<(), FastCryptoError> { - // Count the number of 0s in the bitarray excluding the last 8 bytes (64 bits). - let count = arr.iter().take(arr.len() - 64).filter(|&bit| !bit).count(); - if (jwt_length + 1 + count) % 512 == 448 && count < 512 { - Ok(()) - } else { - Err(FastCryptoError::GeneralError( - "Invalid bitarray".to_string(), - )) + if bits.len() % 8 != 0 { + return Err(FastCryptoError::GeneralError( + "Invalid bits length".to_string(), + )); } + + Ok(std::str::from_utf8(&bitarray_to_bytearray(&bits)) + .map_err(|_| FastCryptoError::GeneralError("Invalid masked content".to_string()))? + .to_owned()) } /// Map a base64 string to a bit array by taking each char's index and covert it to binary form. -fn base64_to_bitarray(input: &str) -> Vec { +fn base64_to_bitarray(input: &str) -> Vec { let base64_url_character_set = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; input @@ -626,21 +459,21 @@ fn base64_to_bitarray(input: &str) -> Vec { let index = base64_url_character_set.find(c).unwrap(); let mut bits = Vec::new(); for i in 0..6 { - bits.push((index >> (5 - i)) & 1 == 1); + bits.push(u8::from((index >> (5 - i)) & 1 == 1)); } bits }) .collect() } -/// Convert a bitarray to a bytearray. -fn bits_to_bytes(bits: &[bool]) -> Vec { +/// Convert a bitarray to a bytearray by taking each 8 bits as a byte. +fn bitarray_to_bytearray(bits: &[u8]) -> Vec { let mut bytes: Vec = Vec::new(); let mut current_byte: u8 = 0; let mut bits_remaining: u8 = 8; for bit in bits.iter() { - if *bit { + if bit == &1 { current_byte |= 1 << (bits_remaining - 1); } bits_remaining -= 1; @@ -658,51 +491,11 @@ fn bits_to_bytes(bits: &[bool]) -> Vec { bytes } -/// Convert a big int string to a big endian bytearray. -pub fn big_int_str_to_bytes(value: &str) -> Vec { - BigInt::from_str(value) - .expect("Invalid big int string") - .to_bytes_be() - .1 -} - -/// Calculate the integer value from the bytearray. -fn calculate_value_from_bytearray(arr: &[u8]) -> usize { - let sized: [u8; 8] = arr.try_into().expect("Invalid byte array"); - ((sized[7] as u16) | (sized[6] as u16) << 8).into() -} - -/// Given a chunk of bytearray, parse it as an ascii string and decode as a JWTHeader. -/// Return the JWTHeader if its fields are valid. -fn parse_and_validate_header(chunk: &[u8]) -> Result { - let header_str = std::str::from_utf8(chunk) - .map_err(|_| FastCryptoError::GeneralError("Cannot parse header string".to_string()))?; - let decoded_header = Base64UrlUnpadded::decode_vec(header_str) - .map_err(|_| FastCryptoError::GeneralError("Invalid jwt header".to_string()))?; - let json_header: Value = serde_json::from_slice(&decoded_header) - .map_err(|_| FastCryptoError::GeneralError("Invalid json".to_string()))?; - let header: JWTHeader = serde_json::from_value(json_header) - .map_err(|_| FastCryptoError::GeneralError("Cannot parse jwt header".to_string()))?; - if header.alg != "RS256" || header.typ != "JWT" { - Err(FastCryptoError::GeneralError("Invalid header".to_string())) - } else { - Ok(header) - } -} - -/// Calculate the merklized hash of the given bytes after 0 paddings. -pub fn calculate_merklized_hash(bytes: &[u8]) -> Result { - let mut bitarray = bytearray_to_bits(bytes); - pad_bitarray(&mut bitarray, 248); - let bigints = convert_to_bigints(&bitarray, 248); - to_poseidon_hash(bigints) -} - -/// Calculate the hash of the inputs. -pub fn to_poseidon_hash(inputs: Vec) -> Result { +/// Calculate the poseidon hash of the field element inputs. +pub fn to_poseidon_hash(inputs: Vec) -> Result { if inputs.len() <= 15 { let mut poseidon1: PoseidonWrapper = PoseidonWrapper::new(); - Ok(poseidon1.hash(inputs)?.to_string()) + poseidon1.hash(inputs) } else if inputs.len() <= 30 { let mut poseidon1: PoseidonWrapper = PoseidonWrapper::new(); let hash1 = poseidon1.hash(inputs[0..15].to_vec())?; @@ -711,46 +504,96 @@ pub fn to_poseidon_hash(inputs: Vec) -> Result let hash2 = poseidon2.hash(inputs[15..].to_vec())?; let mut poseidon3 = PoseidonWrapper::new(); - let hash_final = poseidon3.hash([hash1, hash2].to_vec()); - - Ok(hash_final?.to_string()) + poseidon3.hash([hash1, hash2].to_vec()) } else { - Err(FastCryptoError::InvalidInput) + Err(FastCryptoError::GeneralError( + "Invalid input length for poseidon hash".to_string(), + )) } } -/// Convert a bytearray to a bitarray. -fn bytearray_to_bits(bytearray: &[u8]) -> Vec { - bytearray - .iter() - .flat_map(|&byte| (0..8).rev().map(move |i| (byte >> i) & 1 == 1)) - .collect() -} -/// Convert a bitarray to a bytearray. -fn bitarray_to_string(bitarray: &[bool]) -> Vec { - bitarray.iter().map(|&b| u8::from(b)).collect() +/// Convert a bigint string to a field element. +fn to_field(val: &str) -> Result { + Bn254Fr::from_str(val).map_err(|_| FastCryptoError::InvalidInput) } -/// Pad the bitarray some number of 0s so that its length is a multiple of the segment size. -fn pad_bitarray(bitarray: &mut Vec, segment_size: usize) { - let remainder = bitarray.len() % segment_size; - if remainder != 0 { - bitarray.extend(std::iter::repeat(false).take(segment_size - remainder)); +/// Parse the input to a big int array and calculate the poseidon hash after packing. +fn map_to_field(input: &str, max_size: u8) -> Result { + if input.len() > max_size as usize { + return Err(FastCryptoError::InvalidInput); } + let num_elements = max_size / (PACK_WIDTH / 8) + 1; + let in_arr: Vec = input + .chars() + .map(|c| BigUint::from_slice(&([c as u32]))) + .collect(); + let packed = pack2(&in_arr, 8, PACK_WIDTH, num_elements)?; + to_poseidon_hash(packed) +} + +/// Helper function to pack field elements from big ints. +fn pack2( + in_arr: &[BigUint], + in_width: u8, + out_width: u8, + out_count: u8, +) -> Result, FastCryptoError> { + let packed = pack(in_arr, in_width as usize, out_width as usize)?; + if packed.len() > out_count as usize { + return Err(FastCryptoError::InvalidInput); + } + let mut padded = packed.clone(); + padded.extend(vec![ + to_field("0")?; + out_count as usize - packed.len() as usize + ]); + Ok(padded) } -/// Convert a bitarray to a vector of field elements, padded using segment size. -fn convert_to_bigints(bitarray: &[bool], segment_size: usize) -> Vec { - let chunks = bitarray.chunks(segment_size); - chunks +/// Helper function to pack field elements from big ints. +fn pack( + in_arr: &[BigUint], + in_width: usize, + out_width: usize, +) -> Result, FastCryptoError> { + let bits = big_int_array_to_bits(in_arr, in_width); + let extra_bits = if bits.len() % out_width == 0 { + 0 + } else { + out_width - (bits.len() % out_width) + }; + let mut bits_padded = bits; + bits_padded.extend(vec![0; extra_bits]); + + if bits_padded.len() % out_width != 0 { + return Err(FastCryptoError::InvalidInput); + } + + Ok(bits_padded + .chunks(out_width) .map(|chunk| { - let mut bytes = vec![0; (segment_size + 7) / 8]; - for (i, &bit) in chunk.iter().enumerate() { - bytes[i / 8] |= (bit as u8) << (7 - i % 8); - } - let f = bitarray_to_string(chunk); - let st = BigUint::from_radix_be(&f, 2).unwrap().to_string(); + let st = BigUint::from_radix_be(chunk, 2).unwrap().to_string(); Bn254Fr::from_str(&st).unwrap() }) - .collect() + .collect()) +} + +/// Convert a big int array to a bit array with 0 paddings. +fn big_int_array_to_bits(arr: &[BigUint], int_size: usize) -> Vec { + let mut bitarray: Vec = Vec::new(); + for num in arr { + let mut padded = Vec::new(); + let val = num.to_radix_be(2); + + let extra_bits = if val.len() < int_size { + int_size - val.len() + } else { + 0 + }; + + padded.extend(vec![0; extra_bits]); + padded.extend(val); + bitarray.extend(padded) + } + bitarray } diff --git a/fastcrypto-zkp/src/bn254/zk_login_api.rs b/fastcrypto-zkp/src/bn254/zk_login_api.rs new file mode 100644 index 0000000000..45234b4d0b --- /dev/null +++ b/fastcrypto-zkp/src/bn254/zk_login_api.rs @@ -0,0 +1,205 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use ark_crypto_primitives::snark::SNARK; +use fastcrypto::rsa::{Base64UrlUnpadded, Encoding}; +use num_bigint::BigUint; +use std::collections::HashMap; + +use super::verifier::process_vk_special; +use super::zk_login::{ + AuxInputs, OAuthProvider, OAuthProviderContent, PublicInputs, SupportedKeyClaim, ZkLoginProof, +}; +use crate::bn254::VerifyingKey as Bn254VerifyingKey; +use crate::{ + bn254::verifier::PreparedVerifyingKey, + circom::{g1_affine_from_str_projective, g2_affine_from_str_projective}, +}; +pub use ark_bn254::{Bn254, Fr as Bn254Fr}; +pub use ark_ff::ToConstraintField; +use ark_groth16::{Groth16, VerifyingKey}; +pub use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use fastcrypto::error::FastCryptoError; +use once_cell::sync::Lazy; + +static GLOBAL_VERIFYING_KEY: Lazy = Lazy::new(global_pvk); + +/// Load a fixed verifying key from zklogin.vkey output from setup +/// https://github.com/MystenLabs/fastcrypto/blob/2a704431e4d2685625c0cc06d19fd7d08a4aafa4/openid-zkp-auth/README.md +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![ + "20491192805390485299153009773594534940189261866228447918068658471970481763042".to_string(), + "9383485363053290200918347156157836566562967994039712273449902621266178545958".to_string(), + "1".to_string(), + ]); + let vk_beta_2 = g2_affine_from_str_projective(vec![ + vec![ + "6375614351688725206403948262868962793625744043794305715222011528459656738731" + .to_string(), + "4252822878758300859123897981450591353533073413197771768651442665752259397132" + .to_string(), + ], + vec![ + "10505242626370262277552901082094356697409835680220590971873171140371331206856" + .to_string(), + "21847035105528745403288232691147584728191162732299865338377159692350059136679" + .to_string(), + ], + vec!["1".to_string(), "0".to_string()], + ]); + let vk_gamma_2 = g2_affine_from_str_projective(vec![ + vec![ + "10857046999023057135944570762232829481370756359578518086990519993285655852781" + .to_string(), + "11559732032986387107991004021392285783925812861821192530917403151452391805634" + .to_string(), + ], + vec![ + "8495653923123431417604973247489272438418190587263600148770280649306958101930" + .to_string(), + "4082367875863433681332203403145435568316851327593401208105741076214120093531" + .to_string(), + ], + vec!["1".to_string(), "0".to_string()], + ]); + let vk_delta_2 = g2_affine_from_str_projective(vec![ + vec![ + "10857046999023057135944570762232829481370756359578518086990519993285655852781" + .to_string(), + "11559732032986387107991004021392285783925812861821192530917403151452391805634" + .to_string(), + ], + vec![ + "8495653923123431417604973247489272438418190587263600148770280649306958101930" + .to_string(), + "4082367875863433681332203403145435568316851327593401208105741076214120093531" + .to_string(), + ], + vec!["1".to_string(), "0".to_string()], + ]); + + // Create a vector of G1Affine elements from the IC + let mut vk_gamma_abc_g1 = Vec::new(); + for e in vec![ + vec![ + "4646159977885290315333074199003995943497097760119603432786031341328349612779" + .to_string(), + "16883660321018397536550988255072623983427868378088223250291094422460916984531" + .to_string(), + "1".to_string(), + ], + vec![ + "6837327174314649334165592796561910467712597348860761363984054398343874430321" + .to_string(), + "8986010922336065169810776007712346238931454905016238478271450397492184507492" + .to_string(), + "1".to_string(), + ], + ] { + let g1 = g1_affine_from_str_projective(e); + vk_gamma_abc_g1.push(g1); + } + + let vk = VerifyingKey { + alpha_g1: vk_alpha_1, + beta_g2: vk_beta_2, + gamma_g2: vk_gamma_2, + delta_g2: vk_delta_2, + gamma_abc_g1: vk_gamma_abc_g1, + }; + process_vk_special(&Bn254VerifyingKey(vk)) +} + +/// A whitelist of client_ids (i.e. the value of "aud" in claims) for each provider. +pub static DEFAULT_WHITELIST: Lazy>> = Lazy::new(|| { + let mut map = HashMap::new(); + map.insert( + OAuthProvider::Google.get_config().0, + vec!["946731352276-pk5glcg8cqo38ndb39h7j093fpsphusu.apps.googleusercontent.com"], + ); + map.insert( + OAuthProvider::Twitch.get_config().0, + vec!["d31icql6l8xzpa7ef31ztxyss46ock"], + ); + // TODO: remove this for prod, this is for testing only. + map.insert( + OAuthProvider::Google.get_config().0, + vec!["575519204237-msop9ep45u2uo98hapqmngv8d84qdc8k.apps.googleusercontent.com"], + ); + map +}); + +/// Entry point for the ZkLogin API. +pub fn verify_zk_login( + proof: &ZkLoginProof, + public_inputs: &PublicInputs, + aux_inputs: &AuxInputs, + curr_epoch: u64, + all_jwk: HashMap<(&str, &str), OAuthProviderContent>, +) -> Result<(), FastCryptoError> { + if !is_claim_supported(aux_inputs.get_key_claim_name()) { + return Err(FastCryptoError::GeneralError( + "Unsupported claim found".to_string(), + )); + } + // Verify the max epoch in aux inputs is <= the current epoch of authority. + if aux_inputs.get_max_epoch() <= curr_epoch { + return Err(FastCryptoError::GeneralError( + "Invalid max epoch".to_string(), + )); + } + + let jwk = all_jwk + .get(&(aux_inputs.get_kid(), aux_inputs.get_iss())) + .ok_or_else(|| FastCryptoError::GeneralError("kid not found".to_string()))?; + let jwk_modulus = + BigUint::from_bytes_be(&Base64UrlUnpadded::decode_vec(&jwk.n).map_err(|_| { + FastCryptoError::GeneralError("Invalid Base64 encoded jwk.n".to_string()) + })?); + + if jwk_modulus.to_string() != aux_inputs.get_mod() + || jwk.e != "AQAB" + || jwk.kty != "RSA" + || jwk.alg != "RS256" + { + return Err(FastCryptoError::GeneralError("Invalid modulus".to_string())); + } + + // Verify the JWT signature against one of OAuth provider public keys in the bulletin. + // Since more than one JWKs are available in the bulletin, iterate and find the one with + // matching kid, iss and verify the signature against it. + if !DEFAULT_WHITELIST + .get(aux_inputs.get_iss()) + .ok_or_else(|| FastCryptoError::GeneralError("iss not in whitelist".to_string()))? + .contains(&aux_inputs.get_aud()) + { + return Err(FastCryptoError::GeneralError( + "aud not in whitelist".to_string(), + )); + } + match verify_zk_login_proof_with_fixed_vk(proof, public_inputs) { + Ok(true) => Ok(()), + Ok(false) | Err(_) => Err(FastCryptoError::GeneralError( + "Groth16 proof verify failed".to_string(), + )), + } +} + +/// Verify a zk login proof using the fixed verifying key. +fn verify_zk_login_proof_with_fixed_vk( + proof: &ZkLoginProof, + public_inputs: &PublicInputs, +) -> Result { + Groth16::::verify_with_processed_vk( + &GLOBAL_VERIFYING_KEY.as_arkworks_pvk(), + &public_inputs.as_arkworks()?, + &proof.as_arkworks(), + ) + .map_err(|e| FastCryptoError::GeneralError(e.to_string())) +} + +/// Return whether the claim string is supported for zk login. Currently only "sub" is supported. +pub fn is_claim_supported(claim_name: &str) -> bool { + vec![SupportedKeyClaim::Sub.to_string()].contains(&claim_name.to_owned()) +} From fdc3d2e253c03c3a440a49be1bc4e40ceacafd47 Mon Sep 17 00:00:00 2001 From: Joy Wang <108701016+joyqvq@users.noreply.github.com> Date: Mon, 24 Jul 2023 16:04:20 +0200 Subject: [PATCH 02/13] api changes --- Cargo.lock | 43 +++++++ fastcrypto-zkp/Cargo.toml | 1 + fastcrypto-zkp/src/bn254/poseidon.rs | 13 +-- .../src/bn254/unit_tests/zk_login_tests.rs | 93 ++++++++++++--- fastcrypto-zkp/src/bn254/zk_login.rs | 110 ++++++++++++++++-- fastcrypto-zkp/src/bn254/zk_login_api.rs | 61 +++------- 6 files changed, 245 insertions(+), 76 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5a334d05ad..8769e89213 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -520,6 +520,15 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + [[package]] name = "blake2" version = "0.10.6" @@ -1653,6 +1662,7 @@ dependencies = [ "derive_more", "fastcrypto", "hex", + "im", "num-bigint", "once_cell", "poseidon-ark", @@ -1905,6 +1915,20 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "im" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" +dependencies = [ + "bitmaps", + "rand_core", + "rand_xoshiro", + "sized-chunks", + "typenum", + "version_check", +] + [[package]] name = "indenter" version = "0.3.3" @@ -2627,6 +2651,15 @@ dependencies = [ "rand_core", ] +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core", +] + [[package]] name = "rayon" version = "1.7.0" @@ -3166,6 +3199,16 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps", + "typenum", +] + [[package]] name = "smallvec" version = "1.10.0" diff --git a/fastcrypto-zkp/Cargo.toml b/fastcrypto-zkp/Cargo.toml index 880389dc59..7bc2bf5ae2 100644 --- a/fastcrypto-zkp/Cargo.toml +++ b/fastcrypto-zkp/Cargo.toml @@ -32,6 +32,7 @@ serde = { version = "1.0.152", features = ["derive"] } serde_json = "1.0.93" once_cell = "1.16" poseidon-ark = { git = "https://github.com/MystenLabs/poseidon-ark.git", rev = "39844046ded0c4aa7a247e8545e131b59a330a9c" } +im = "15" [dev-dependencies] ark-bls12-377 = "0.4.0" diff --git a/fastcrypto-zkp/src/bn254/poseidon.rs b/fastcrypto-zkp/src/bn254/poseidon.rs index 007157cdb8..4f9de7265c 100644 --- a/fastcrypto-zkp/src/bn254/poseidon.rs +++ b/fastcrypto-zkp/src/bn254/poseidon.rs @@ -54,13 +54,6 @@ mod test { #[test] fn poseidon_test() { - // TODO (joyqvq): add more test vectors here from circom.js - // Test vector generated from circom.js - // Poseidon([134696963602902907403122104327765350261n, - // 17932473587154777519561053972421347139n, - // 10000, - // 50683480294434968413708503290439057629605340925620961559740848568164438166n]) - // = 2272550810841985018139126931041192927190568084082399473943239080305281957330n let mut poseidon = PoseidonWrapper::new(); let input1 = Fr::from_str("134696963602902907403122104327765350261").unwrap(); let input2 = Fr::from_str("17932473587154777519561053972421347139").unwrap(); @@ -117,6 +110,12 @@ mod test { .to_string(), "14023706212980258922092162104379517008998397500440232747089120702484714603058" ); + + assert!(to_poseidon_hash(to_bigint_arr(vec![ + 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 + ])) + .is_err()); } #[test] 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 7db5b4e184..3d930ab3c4 100644 --- a/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs +++ b/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs @@ -1,23 +1,53 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use fastcrypto::error::FastCryptoError; - use super::map_to_field; use crate::bn254::zk_login::OAuthProvider::{Google, Twitch}; use crate::bn254::zk_login::{ - decode_base64_url, verify_extended_claim, Claim, JWTHeader, ParsedMaskedContent, + big_int_str_to_bytes, decode_base64_url, parse_jwks, trim, verify_extended_claim, Claim, + JWTHeader, ParsedMaskedContent, }; use crate::bn254::{ zk_login::{AuxInputs, OAuthProvider, OAuthProviderContent, PublicInputs, ZkLoginProof}, zk_login_api::verify_zk_login, }; -use std::collections::HashMap; +use fastcrypto::error::FastCryptoError; +use im::hashmap::HashMap as ImHashMap; + +const GOOGLE_JWK_BYTES: &[u8] = r#"{ + "keys": [ + { + "kty": "RSA", + "e": "AQAB", + "alg": "RS256", + "kid": "c9afda3682ebf09eb3055c1c4bd39b751fbf8195", + "use": "sig", + "n": "whYOFK2Ocbbpb_zVypi9SeKiNUqKQH0zTKN1-6fpCTu6ZalGI82s7XK3tan4dJt90ptUPKD2zvxqTzFNfx4HHHsrYCf2-FMLn1VTJfQazA2BvJqAwcpW1bqRUEty8tS_Yv4hRvWfQPcc2Gc3-_fQOOW57zVy-rNoJc744kb30NjQxdGp03J2S3GLQu7oKtSDDPooQHD38PEMNnITf0pj-KgDPjymkMGoJlO3aKppsjfbt_AH6GGdRghYRLOUwQU-h-ofWHR3lbYiKtXPn5dN24kiHy61e3VAQ9_YAZlwXC_99GGtw_NpghFAuM4P1JDn0DppJldy3PGFC0GfBCZASw" + }, + { + "alg": "RS256", + "use": "sig", + "n": "1qrQCTst3RF04aMC9Ye_kGbsE0sftL4FOtB_WrzBDOFdrfVwLfflQuPX5kJ-0iYv9r2mjD5YIDy8b-iJKwevb69ISeoOrmL3tj6MStJesbbRRLVyFIm_6L7alHhZVyqHQtMKX7IaNndrfebnLReGntuNk76XCFxBBnRaIzAWnzr3WN4UPBt84A0KF74pei17dlqHZJ2HB2CsYbE9Ort8m7Vf6hwxYzFtCvMCnZil0fCtk2OQ73l6egcvYO65DkAJibFsC9xAgZaF-9GYRlSjMPd0SMQ8yU9i3W7beT00Xw6C0FYA9JAYaGaOvbT87l_6ZkAksOMuvIPD_jNVfTCPLQ==", + "e": "AQAB", + "kty": "RSA", + "kid": "6083dd5981673f661fde9dae646b6f0380a0145c" + } + ] + }"#.as_bytes(); + +const TWITCH_JWK_BYTES: &[u8] = r#"{ + "keys":[{"alg":"RS256","e":"AQAB","kid":"1","kty":"RSA","n":"6lq9MQ-q6hcxr7kOUp-tHlHtdcDsVLwVIw13iXUCvuDOeCi0VSuxCCUY6UmMjy53dX00ih2E4Y4UvlrmmurK0eG26b-HMNNAvCGsVXHU3RcRhVoHDaOwHwU72j7bpHn9XbP3Q3jebX6KIfNbei2MiR0Wyb8RZHE-aZhRYO8_-k9G2GycTpvc-2GBsP8VHLUKKfAs2B6sW3q3ymU6M0L-cFXkZ9fHkn9ejs-sqZPhMJxtBPBxoUIUQFTgv4VXTSv914f_YkNw-EjuwbgwXMvpyr06EyfImxHoxsZkFYB-qBYHtaMxTnFsZBr6fn8Ha2JqT1hoP7Z5r5wxDu3GQhKkHw","use":"sig"}] + }"#.as_bytes(); #[test] fn test_verify_groth16_in_bytes_google() { + let mut eph_pubkey = big_int_str_to_bytes("17932473587154777519561053972421347139"); + eph_pubkey.extend(big_int_str_to_bytes( + "134696963602902907403122104327765350261", + )); + const TEST_KID: &str = "c9afda3682ebf09eb3055c1c4bd39b751fbf8195"; - let aux_inputs = AuxInputs::from_json("{\"claims\": [{\"name\": \"iss\",\"value_base64\":\"yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC\",\"index_mod_4\": 1},{\"name\": \"aud\",\"value_base64\": \"CJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLC\",\"index_mod_4\": 1}], \"header_base64\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6ImM5YWZkYTM2ODJlYmYwOWViMzA1NWMxYzRiZDM5Yjc1MWZiZjgxOTUiLCJ0eXAiOiJKV1QifQ\",\"addr_seed\": \"15604334753912523265015800787270404628529489918817818174033741053550755333691\",\"eph_public_key\": [\"17932473587154777519561053972421347139\", \"134696963602902907403122104327765350261\"],\"max_epoch\": 10000,\"key_claim_name\": \"sub\",\"modulus\": \"24501106890748714737552440981790137484213218436093327306276573863830528169633224698737117584784274166505493525052788880030500250025091662388617070057693555892212025614452197230081503494967494355047321073341455279175776092624566907541405624967595499832566905567072654796017464431878680118805774542185299632150122052530877100261682728356139724202453050155758294697161107805717430444408191365063957162605112787073991150691398970840390185880092832325216009234084152827135531285878617366639283552856146367480314853517993661640450694829038343380576312039548353544096265483699391507882147093626719041048048921352351403884619\"}").unwrap().init().unwrap(); + let aux_inputs = AuxInputs::from_json("{\"claims\": [{\"name\": \"iss\",\"value_base64\":\"yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC\",\"index_mod_4\": 1},{\"name\": \"aud\",\"value_base64\": \"CJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLC\",\"index_mod_4\": 1}], \"header_base64\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6ImM5YWZkYTM2ODJlYmYwOWViMzA1NWMxYzRiZDM5Yjc1MWZiZjgxOTUiLCJ0eXAiOiJKV1QifQ\",\"addr_seed\": \"15604334753912523265015800787270404628529489918817818174033741053550755333691\",\"max_epoch\": 10000,\"key_claim_name\": \"sub\",\"modulus\": \"24501106890748714737552440981790137484213218436093327306276573863830528169633224698737117584784274166505493525052788880030500250025091662388617070057693555892212025614452197230081503494967494355047321073341455279175776092624566907541405624967595499832566905567072654796017464431878680118805774542185299632150122052530877100261682728356139724202453050155758294697161107805717430444408191365063957162605112787073991150691398970840390185880092832325216009234084152827135531285878617366639283552856146367480314853517993661640450694829038343380576312039548353544096265483699391507882147093626719041048048921352351403884619\"}").unwrap().init().unwrap(); let public_inputs = PublicInputs::from_json( "[\"6049184272607241856912886413680599526372437331989542437266935645748489874658\"]", ) @@ -37,31 +67,47 @@ fn test_verify_groth16_in_bytes_google() { assert_eq!(aux_inputs.get_kid(), TEST_KID); assert_eq!(aux_inputs.get_mod(), "24501106890748714737552440981790137484213218436093327306276573863830528169633224698737117584784274166505493525052788880030500250025091662388617070057693555892212025614452197230081503494967494355047321073341455279175776092624566907541405624967595499832566905567072654796017464431878680118805774542185299632150122052530877100261682728356139724202453050155758294697161107805717430444408191365063957162605112787073991150691398970840390185880092832325216009234084152827135531285878617366639283552856146367480314853517993661640450694829038343380576312039548353544096265483699391507882147093626719041048048921352351403884619"); assert_eq!( - aux_inputs.calculate_all_inputs_hash().unwrap(), + aux_inputs.calculate_all_inputs_hash(&eph_pubkey).unwrap(), public_inputs.get_all_inputs_hash().unwrap() ); - let mut map = HashMap::new(); - map.insert((TEST_KID, Google.get_config().0), OAuthProviderContent { + let mut map = ImHashMap::new(); + map.insert((TEST_KID.to_string(), Google.get_config().0.to_string()), OAuthProviderContent { kty: "RSA".to_string(), + kid: TEST_KID.to_string(), e: "AQAB".to_string(), n: "whYOFK2Ocbbpb_zVypi9SeKiNUqKQH0zTKN1-6fpCTu6ZalGI82s7XK3tan4dJt90ptUPKD2zvxqTzFNfx4HHHsrYCf2-FMLn1VTJfQazA2BvJqAwcpW1bqRUEty8tS_Yv4hRvWfQPcc2Gc3-_fQOOW57zVy-rNoJc744kb30NjQxdGp03J2S3GLQu7oKtSDDPooQHD38PEMNnITf0pj-KgDPjymkMGoJlO3aKppsjfbt_AH6GGdRghYRLOUwQU-h-ofWHR3lbYiKtXPn5dN24kiHy61e3VAQ9_YAZlwXC_99GGtw_NpghFAuM4P1JDn0DppJldy3PGFC0GfBCZASw".to_string(), alg: "RS256".to_string(), }); let proof = ZkLoginProof::from_json("{\"pi_a\":[\"21079899190337156604543197959052999786745784780153100922098887555507822163222\",\"4490261504756339299022091724663793329121338007571218596828748539529998991610\",\"1\"],\"pi_b\":[[\"9379167206161123715528853149920855132656754699464636503784643891913740439869\",\"15902897771112804794883785114808675393618430194414793328415185511364403970347\"],[\"16152736996630746506267683507223054358516992879195296708243566008238438281201\",\"15230917601041350929970534508991793588662911174494137634522926575255163535339\"],[\"1\",\"0\"]],\"pi_c\":[\"8242734018052567627683363270753907648903210541694662698981939667442011573249\",\"1775496841914332445297048246214170486364407018954976081505164205395286250461\",\"1\"],\"protocol\":\"groth16\"}"); assert!(proof.is_ok()); - let res = verify_zk_login(&proof.unwrap(), &public_inputs, &aux_inputs, 1, map); + let res = verify_zk_login( + &proof.unwrap(), + &public_inputs, + &aux_inputs, + &eph_pubkey, + &map, + ); assert!(res.is_ok()); } #[test] fn test_verify_groth16_in_bytes_twitch() { - let aux_inputs = AuxInputs::from_json("{ \"claims\": [{\"name\": \"iss\", \"value_base64\": \"wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw\", \"index_mod_4\": 2 }, { \"name\": \"aud\", \"value_base64\": \"yJhdWQiOiJkMzFpY3FsNmw4eHpwYTdlZjMxenR4eXNzNDZvY2siLC\", \"index_mod_4\": 1 }], \"header_base64\": \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ\", \"addr_seed\": \"18704353972820279499196832783883157878280522634176394693508060053542990860397\", \"eph_public_key\": [ \"17932473587154777519561053972421347139\", \"134696963602902907403122104327765350261\" ], \"max_epoch\": 10000, \"key_claim_name\": \"sub\", \"modulus\": \"29584508445356008889845267980505093503375439259749620943404021520463333925791823787854637986725604249075338223707087145400343940878353492428289420309978060548116589201208844416027772690277158239820043587577191204733372062526546165382385491203581708579354007308101776609837604633876932723191352425015951165606420826212084566356749532802619014158140227417781995864324396969703870653300311928497704373150532338687663444677910609365123056747321631910862597087829149037858457007019073259229864068519186398691475937234253291322606806349186490894620279606094962475240526774193684946100117999885854009961035928969402039575583\" }").unwrap().init().unwrap(); + let mut eph_pubkey = big_int_str_to_bytes("17932473587154777519561053972421347139"); + eph_pubkey.extend(big_int_str_to_bytes( + "134696963602902907403122104327765350261", + )); + let aux_inputs = AuxInputs::from_json("{\"claims\":[{\"name\": \"iss\",\"value_base64\": \"wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw\",\"index_mod_4\": 2 },{\"name\": \"aud\",\"value_base64\": \"yJhdWQiOiJkMzFpY3FsNmw4eHpwYTdlZjMxenR4eXNzNDZvY2siLC\",\"index_mod_4\": 1}],\"header_base64\": \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ\",\"addr_seed\": \"18704353972820279499196832783883157878280522634176394693508060053542990860397\", \"max_epoch\": 10000,\"key_claim_name\": \"sub\",\"modulus\": \"29584508445356008889845267980505093503375439259749620943404021520463333925791823787854637986725604249075338223707087145400343940878353492428289420309978060548116589201208844416027772690277158239820043587577191204733372062526546165382385491203581708579354007308101776609837604633876932723191352425015951165606420826212084566356749532802619014158140227417781995864324396969703870653300311928497704373150532338687663444677910609365123056747321631910862597087829149037858457007019073259229864068519186398691475937234253291322606806349186490894620279606094962475240526774193684946100117999885854009961035928969402039575583\"}").unwrap().init().unwrap(); let public_inputs = PublicInputs::from_json( "[\"17466137871302348802176618957727679727566476655921498778525221619744941215202\"]", ) .unwrap(); + let public_inputs_invalid = PublicInputs::from_json( + "[\"17466137871302348802176618957727679727566476655921498778525221619744941215202\", \"17466137871302348802176618957727679727566476655921498778525221619744941215202\"]", + ).unwrap(); + assert!(public_inputs_invalid.get_all_inputs_hash().is_err()); + assert_eq!(aux_inputs.get_max_epoch(), 10000); assert_eq!( aux_inputs.get_address_seed(), @@ -73,13 +119,14 @@ fn test_verify_groth16_in_bytes_twitch() { assert_eq!(aux_inputs.get_kid(), "1"); assert_eq!(aux_inputs.get_mod(), "29584508445356008889845267980505093503375439259749620943404021520463333925791823787854637986725604249075338223707087145400343940878353492428289420309978060548116589201208844416027772690277158239820043587577191204733372062526546165382385491203581708579354007308101776609837604633876932723191352425015951165606420826212084566356749532802619014158140227417781995864324396969703870653300311928497704373150532338687663444677910609365123056747321631910862597087829149037858457007019073259229864068519186398691475937234253291322606806349186490894620279606094962475240526774193684946100117999885854009961035928969402039575583"); assert_eq!( - aux_inputs.calculate_all_inputs_hash().unwrap(), + aux_inputs.calculate_all_inputs_hash(&eph_pubkey).unwrap(), public_inputs.get_all_inputs_hash().unwrap() ); - let mut map = HashMap::new(); - map.insert(("1", Twitch.get_config().0), OAuthProviderContent { + let mut map = ImHashMap::new(); + map.insert(("1".to_string(), Twitch.get_config().0.to_string()), OAuthProviderContent { kty: "RSA".to_string(), + kid: "1".to_string(), e: "AQAB".to_string(), n: "6lq9MQ-q6hcxr7kOUp-tHlHtdcDsVLwVIw13iXUCvuDOeCi0VSuxCCUY6UmMjy53dX00ih2E4Y4UvlrmmurK0eG26b-HMNNAvCGsVXHU3RcRhVoHDaOwHwU72j7bpHn9XbP3Q3jebX6KIfNbei2MiR0Wyb8RZHE-aZhRYO8_-k9G2GycTpvc-2GBsP8VHLUKKfAs2B6sW3q3ymU6M0L-cFXkZ9fHkn9ejs-sqZPhMJxtBPBxoUIUQFTgv4VXTSv914f_YkNw-EjuwbgwXMvpyr06EyfImxHoxsZkFYB-qBYHtaMxTnFsZBr6fn8Ha2JqT1hoP7Z5r5wxDu3GQhKkHw".to_string(), alg: "RS256".to_string(), @@ -87,7 +134,13 @@ fn test_verify_groth16_in_bytes_twitch() { let proof = ZkLoginProof::from_json("{ \"pi_a\": [ \"14609816250208775088998769033922823275418989011294962335042447516759468155261\", \"20377558696931353568738668428784363385404286135420274775798451001900237387711\", \"1\" ], \"pi_b\": [ [ \"13205564493500587952133306511249429194738679332267485407336676345714082870630\", \"20796060045071998078434479958974217243296767801927986923760870304883706846959\" ], [ \"18144611315874106283809557225033182618356564976139850467162456490949482704538\", \"4318715074202832054732474611176035084202678394565328538059624195976255391002\" ], [ \"1\", \"0\" ] ], \"pi_c\": [ \"4215643272645108456341625420022677634747189283615115637991603989161283548307\", \"5549730540188640204480179088531560793048476496379683802205245590402338452458\", \"1\" ], \"protocol\": \"groth16\"}"); assert!(proof.is_ok()); - let res = verify_zk_login(&proof.unwrap(), &public_inputs, &aux_inputs, 1, map); + let res = verify_zk_login( + &proof.unwrap(), + &public_inputs, + &aux_inputs, + &eph_pubkey, + &map, + ); assert!(res.is_ok()); } @@ -210,6 +263,7 @@ fn test_verify_extended_claim() { #[test] fn test_map_to_field() { // Test generated against typescript implementation. + assert!(map_to_field("sub", 2).is_err()); assert_eq!( map_to_field("sub", 10).unwrap().to_string(), "18523124550523841778801820019979000409432455608728354507022210389496924497355" @@ -223,3 +277,14 @@ fn test_map_to_field() { assert_eq!(map_to_field("CJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLC", 133).unwrap().to_string(), "6914089902564896687047107167562960781243311797290496295481879371814854678998"); assert_eq!(map_to_field("eyJhbGciOiJSUzI1NiIsImtpZCI6ImM5YWZkYTM2ODJlYmYwOWViMzA1NWMxYzRiZDM5Yjc1MWZiZjgxOTUiLCJ0eXAiOiJKV1QifQ", 150).unwrap().to_string(), "11195180390614794854381992733393925748746563026948577817495625199891112836762"); } + +#[test] +fn test_jwk_parse() { + assert_eq!( + trim("wYvSKSQYKnGNV72_uVc9jbyUeTMsMbUgZPP0uVQX900To7A8a0XA3O17wuImgOG_BwGkpZrIRXF_RRYSK8IOH8N_ViTWh1vyEYSYwr_jfCpDoedJT0O6TZpBhBSmimtmO8ZBCkhZJ4w0AFNIMDPhMokbxwkEapjMA5zio_06dKfb3OBNmrwedZY86W1204-Pfma9Ih15Dm4o8SNFo5Sl0NNO4Ithvj2bbg1Bz1ydE4lMrXdSQL5C2uM9JYRJLnIjaYopBENwgf2Egc9CdVY8tr8jED-WQB6bcUBhDV6lJLZbpBlTHLkF1RlEMnIV2bDo02CryjThnz8l_-6G_7pJww==".to_string()), + "wYvSKSQYKnGNV72_uVc9jbyUeTMsMbUgZPP0uVQX900To7A8a0XA3O17wuImgOG_BwGkpZrIRXF_RRYSK8IOH8N_ViTWh1vyEYSYwr_jfCpDoedJT0O6TZpBhBSmimtmO8ZBCkhZJ4w0AFNIMDPhMokbxwkEapjMA5zio_06dKfb3OBNmrwedZY86W1204-Pfma9Ih15Dm4o8SNFo5Sl0NNO4Ithvj2bbg1Bz1ydE4lMrXdSQL5C2uM9JYRJLnIjaYopBENwgf2Egc9CdVY8tr8jED-WQB6bcUBhDV6lJLZbpBlTHLkF1RlEMnIV2bDo02CryjThnz8l_-6G_7pJww" + ); + + assert_eq!(parse_jwks(GOOGLE_JWK_BYTES, Google).unwrap().len(), 2); + assert_eq!(parse_jwks(TWITCH_JWK_BYTES, Twitch).unwrap().len(), 1); +} diff --git a/fastcrypto-zkp/src/bn254/zk_login.rs b/fastcrypto-zkp/src/bn254/zk_login.rs index dfff307fd0..de08a25124 100644 --- a/fastcrypto-zkp/src/bn254/zk_login.rs +++ b/fastcrypto-zkp/src/bn254/zk_login.rs @@ -1,6 +1,7 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use fastcrypto::error::FastCryptoResult; use serde_json::Value; use std::collections::HashMap; use std::fmt; @@ -16,12 +17,13 @@ use fastcrypto::{ error::FastCryptoError, rsa::{Base64UrlUnpadded, Encoding}, }; -use num_bigint::BigUint; +use num_bigint::{BigInt, BigUint, Sign}; use once_cell::sync::Lazy; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::str::FromStr; +type ParsedJWKs = Vec<((String, String), OAuthProviderContent)>; #[cfg(test)] #[path = "unit_tests/zk_login_tests.rs"] mod zk_login_tests; @@ -67,7 +69,8 @@ pub enum OAuthProvider { /// Struct that contains all the OAuth provider information. A list of them can /// be retrieved from the JWK endpoint (e.g. ) /// and published on the bulletin along with a trusted party's signature. -#[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Hash, Serialize, Deserialize)] +// #[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct OAuthProviderContent { /// Key type parameter, https://datatracker.ietf.org/doc/html/rfc7517#section-4.1 pub kty: String, @@ -77,6 +80,81 @@ pub struct OAuthProviderContent { pub n: String, /// Algorithm parameter, https://datatracker.ietf.org/doc/html/rfc7517#section-4.4 pub alg: String, + /// kid + kid: String, +} + +/// Reader struct to parse all fields. +// #[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Hash, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] +pub struct OAuthProviderContentReader { + e: String, + n: String, + #[serde(rename = "use")] + my_use: String, + kid: String, + kty: String, + alg: String, +} + +impl OAuthProviderContent { + /// Get the kid string. + pub fn kid(&self) -> &str { + &self.kid + } + + /// Parse OAuthProviderContent from the reader struct. + pub fn from_reader(reader: OAuthProviderContentReader) -> FastCryptoResult { + if reader.alg != "RS256" || reader.my_use != "sig" || reader.kty != "RSA" { + return Err(FastCryptoError::InvalidInput); + } + Ok(Self { + kty: reader.kty, + kid: reader.kid, + e: trim(reader.e), + n: trim(reader.n), + alg: reader.alg, + }) + } + + /// Parse OAuthProviderContent from the reader struct. + pub fn validate(&self) -> FastCryptoResult<()> { + if self.alg != "RS256" || self.kty != "RSA" || self.e != "AQAB" { + return Err(FastCryptoError::InvalidInput); + } + Ok(()) + } +} + +/// Trim trailing '=' so that it is considered a valid base64 url encoding string by base64ct library. +fn trim(str: String) -> String { + str.trim_end_matches(|c: char| c == '=').to_owned() +} + +/// Parse the JWK bytes received from the oauth provider keys endpoint into a map from kid to +/// OAuthProviderContent. +pub fn parse_jwks( + json_bytes: &[u8], + provider: OAuthProvider, +) -> Result { + let json_str = String::from_utf8_lossy(json_bytes); + let parsed_list: Result = serde_json::from_str(&json_str); + if let Ok(parsed_list) = parsed_list { + if let Some(keys) = parsed_list["keys"].as_array() { + let mut ret = Vec::new(); + for k in keys { + let parsed: OAuthProviderContentReader = serde_json::from_value(k.clone()) + .map_err(|_| FastCryptoError::GeneralError("Parse error".to_string()))?; + + ret.push(( + (parsed.kid.clone(), provider.get_config().0.to_owned()), + OAuthProviderContent::from_reader(parsed)?, + )); + } + return Ok(ret); + } + } + Err(FastCryptoError::GeneralError("JWK not found".to_string())) } impl OAuthProvider { @@ -128,7 +206,6 @@ pub struct AuxInputs { claims: Vec, header_base64: String, addr_seed: String, - eph_public_key: Vec, max_epoch: u64, key_claim_name: String, modulus: String, @@ -186,11 +263,20 @@ impl AuxInputs { } /// Calculate the poseidon hash from 10 selected fields in the aux inputs. - pub fn calculate_all_inputs_hash(&self) -> Result { + pub fn calculate_all_inputs_hash( + &self, + eph_pubkey_bytes: &[u8], + ) -> Result { let mut poseidon = PoseidonWrapper::new(); let addr_seed = to_field(&self.addr_seed)?; - let eph_public_key_0 = to_field(&self.eph_public_key[0])?; - let eph_public_key_1 = to_field(&self.eph_public_key[1])?; + + let (first_half, second_half) = eph_pubkey_bytes.split_at(eph_pubkey_bytes.len() / 2); + let first_bigint = BigInt::from_bytes_be(Sign::Plus, first_half); + let second_bigint = BigInt::from_bytes_be(Sign::Plus, second_half); + + let eph_public_key_0 = to_field(&first_bigint.to_string())?; + let eph_public_key_1 = to_field(&second_bigint.to_string())?; + let max_epoch = to_field(&self.max_epoch.to_string())?; let key_claim_name_f = to_field( SUPPORTED_KEY_CLAIM_TO_FIELD @@ -582,18 +668,24 @@ fn pack( fn big_int_array_to_bits(arr: &[BigUint], int_size: usize) -> Vec { let mut bitarray: Vec = Vec::new(); for num in arr { - let mut padded = Vec::new(); let val = num.to_radix_be(2); - let extra_bits = if val.len() < int_size { int_size - val.len() } else { 0 }; - padded.extend(vec![0; extra_bits]); + let mut padded = vec![0; extra_bits]; padded.extend(val); bitarray.extend(padded) } bitarray } + +/// Convert a big int string to a big endian bytearray. +pub fn big_int_str_to_bytes(value: &str) -> Vec { + BigInt::from_str(value) + .expect("Invalid big int string") + .to_bytes_be() + .1 +} diff --git a/fastcrypto-zkp/src/bn254/zk_login_api.rs b/fastcrypto-zkp/src/bn254/zk_login_api.rs index 45234b4d0b..9c9cd402bb 100644 --- a/fastcrypto-zkp/src/bn254/zk_login_api.rs +++ b/fastcrypto-zkp/src/bn254/zk_login_api.rs @@ -3,12 +3,12 @@ use ark_crypto_primitives::snark::SNARK; use fastcrypto::rsa::{Base64UrlUnpadded, Encoding}; +use im::hashmap::HashMap as ImHashMap; use num_bigint::BigUint; -use std::collections::HashMap; use super::verifier::process_vk_special; use super::zk_login::{ - AuxInputs, OAuthProvider, OAuthProviderContent, PublicInputs, SupportedKeyClaim, ZkLoginProof, + AuxInputs, OAuthProviderContent, PublicInputs, SupportedKeyClaim, ZkLoginProof, }; use crate::bn254::VerifyingKey as Bn254VerifyingKey; use crate::{ @@ -24,8 +24,7 @@ use once_cell::sync::Lazy; static GLOBAL_VERIFYING_KEY: Lazy = Lazy::new(global_pvk); -/// Load a fixed verifying key from zklogin.vkey output from setup -/// https://github.com/MystenLabs/fastcrypto/blob/2a704431e4d2685625c0cc06d19fd7d08a4aafa4/openid-zkp-auth/README.md +/// Load a fixed verifying key from zklogin.vkey output from trusted setup 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![ @@ -111,73 +110,43 @@ fn global_pvk() -> PreparedVerifyingKey { process_vk_special(&Bn254VerifyingKey(vk)) } -/// A whitelist of client_ids (i.e. the value of "aud" in claims) for each provider. -pub static DEFAULT_WHITELIST: Lazy>> = Lazy::new(|| { - let mut map = HashMap::new(); - map.insert( - OAuthProvider::Google.get_config().0, - vec!["946731352276-pk5glcg8cqo38ndb39h7j093fpsphusu.apps.googleusercontent.com"], - ); - map.insert( - OAuthProvider::Twitch.get_config().0, - vec!["d31icql6l8xzpa7ef31ztxyss46ock"], - ); - // TODO: remove this for prod, this is for testing only. - map.insert( - OAuthProvider::Google.get_config().0, - vec!["575519204237-msop9ep45u2uo98hapqmngv8d84qdc8k.apps.googleusercontent.com"], - ); - map -}); - /// Entry point for the ZkLogin API. pub fn verify_zk_login( proof: &ZkLoginProof, public_inputs: &PublicInputs, aux_inputs: &AuxInputs, - curr_epoch: u64, - all_jwk: HashMap<(&str, &str), OAuthProviderContent>, + eph_pubkey_bytes: &[u8], + all_jwk: &ImHashMap<(String, String), OAuthProviderContent>, ) -> Result<(), FastCryptoError> { if !is_claim_supported(aux_inputs.get_key_claim_name()) { return Err(FastCryptoError::GeneralError( "Unsupported claim found".to_string(), )); } - // Verify the max epoch in aux inputs is <= the current epoch of authority. - if aux_inputs.get_max_epoch() <= curr_epoch { - return Err(FastCryptoError::GeneralError( - "Invalid max epoch".to_string(), - )); - } let jwk = all_jwk - .get(&(aux_inputs.get_kid(), aux_inputs.get_iss())) - .ok_or_else(|| FastCryptoError::GeneralError("kid not found".to_string()))?; + .get(&( + aux_inputs.get_kid().to_string(), + aux_inputs.get_iss().to_string(), + )) + .ok_or_else(|| FastCryptoError::GeneralError("JWK not found".to_string()))?; let jwk_modulus = BigUint::from_bytes_be(&Base64UrlUnpadded::decode_vec(&jwk.n).map_err(|_| { FastCryptoError::GeneralError("Invalid Base64 encoded jwk.n".to_string()) })?); - if jwk_modulus.to_string() != aux_inputs.get_mod() - || jwk.e != "AQAB" - || jwk.kty != "RSA" - || jwk.alg != "RS256" - { + if jwk_modulus.to_string() != aux_inputs.get_mod() || jwk.validate().is_err() { return Err(FastCryptoError::GeneralError("Invalid modulus".to_string())); } - // Verify the JWT signature against one of OAuth provider public keys in the bulletin. - // Since more than one JWKs are available in the bulletin, iterate and find the one with - // matching kid, iss and verify the signature against it. - if !DEFAULT_WHITELIST - .get(aux_inputs.get_iss()) - .ok_or_else(|| FastCryptoError::GeneralError("iss not in whitelist".to_string()))? - .contains(&aux_inputs.get_aud()) + if aux_inputs.calculate_all_inputs_hash(eph_pubkey_bytes)? + != public_inputs.get_all_inputs_hash()? { return Err(FastCryptoError::GeneralError( - "aud not in whitelist".to_string(), + "Invalid all inputs hash".to_string(), )); } + match verify_zk_login_proof_with_fixed_vk(proof, public_inputs) { Ok(true) => Ok(()), Ok(false) | Err(_) => Err(FastCryptoError::GeneralError( From 1039bf0e4d66b9c56755d75684e913145de2a173 Mon Sep 17 00:00:00 2001 From: Joy Wang <108701016+joyqvq@users.noreply.github.com> Date: Wed, 26 Jul 2023 20:03:45 +0200 Subject: [PATCH 03/13] rework interface changes --- fastcrypto-zkp/src/bn254/poseidon.rs | 6 +- .../src/bn254/unit_tests/zk_login_tests.rs | 201 ++++---- fastcrypto-zkp/src/bn254/zk_login.rs | 436 ++++++++---------- fastcrypto-zkp/src/bn254/zk_login_api.rs | 61 +-- 4 files changed, 309 insertions(+), 395 deletions(-) diff --git a/fastcrypto-zkp/src/bn254/poseidon.rs b/fastcrypto-zkp/src/bn254/poseidon.rs index 4f9de7265c..0216d20327 100644 --- a/fastcrypto-zkp/src/bn254/poseidon.rs +++ b/fastcrypto-zkp/src/bn254/poseidon.rs @@ -99,7 +99,7 @@ mod test { ])) .unwrap() .to_string(), - "13895998335546007571506436905298853781676311844723695580596383169075721618652" + "9989051620750914585850546081941653841776809718687451684622678807385399211877" ); assert_eq!( to_poseidon_hash(to_bigint_arr(vec![ @@ -108,12 +108,12 @@ mod test { ])) .unwrap() .to_string(), - "14023706212980258922092162104379517008998397500440232747089120702484714603058" + "4123755143677678663754455867798672266093104048057302051129414708339780424023" ); assert!(to_poseidon_hash(to_bigint_arr(vec![ 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 + 24, 25, 26, 27, 28, 29, 30, 31, 32 ])) .is_err()); } 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 3d930ab3c4..e18dbb39fa 100644 --- a/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs +++ b/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs @@ -1,14 +1,13 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use super::map_to_field; use crate::bn254::zk_login::OAuthProvider::{Google, Twitch}; use crate::bn254::zk_login::{ - big_int_str_to_bytes, decode_base64_url, parse_jwks, trim, verify_extended_claim, Claim, - JWTHeader, ParsedMaskedContent, + big_int_str_to_bytes, decode_base64_url, map_bytes_to_field, parse_jwks, trim, + verify_extended_claim, Claim, JWTHeader, ParsedMaskedContent, }; use crate::bn254::{ - zk_login::{AuxInputs, OAuthProvider, OAuthProviderContent, PublicInputs, ZkLoginProof}, + zk_login::{OAuthProviderContent, ZkLoginInputs}, zk_login_api::verify_zk_login, }; use fastcrypto::error::FastCryptoError; @@ -39,110 +38,78 @@ const TWITCH_JWK_BYTES: &[u8] = r#"{ "keys":[{"alg":"RS256","e":"AQAB","kid":"1","kty":"RSA","n":"6lq9MQ-q6hcxr7kOUp-tHlHtdcDsVLwVIw13iXUCvuDOeCi0VSuxCCUY6UmMjy53dX00ih2E4Y4UvlrmmurK0eG26b-HMNNAvCGsVXHU3RcRhVoHDaOwHwU72j7bpHn9XbP3Q3jebX6KIfNbei2MiR0Wyb8RZHE-aZhRYO8_-k9G2GycTpvc-2GBsP8VHLUKKfAs2B6sW3q3ymU6M0L-cFXkZ9fHkn9ejs-sqZPhMJxtBPBxoUIUQFTgv4VXTSv914f_YkNw-EjuwbgwXMvpyr06EyfImxHoxsZkFYB-qBYHtaMxTnFsZBr6fn8Ha2JqT1hoP7Z5r5wxDu3GQhKkHw","use":"sig"}] }"#.as_bytes(); -#[test] -fn test_verify_groth16_in_bytes_google() { - let mut eph_pubkey = big_int_str_to_bytes("17932473587154777519561053972421347139"); - eph_pubkey.extend(big_int_str_to_bytes( - "134696963602902907403122104327765350261", - )); - - const TEST_KID: &str = "c9afda3682ebf09eb3055c1c4bd39b751fbf8195"; - let aux_inputs = AuxInputs::from_json("{\"claims\": [{\"name\": \"iss\",\"value_base64\":\"yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC\",\"index_mod_4\": 1},{\"name\": \"aud\",\"value_base64\": \"CJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLC\",\"index_mod_4\": 1}], \"header_base64\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6ImM5YWZkYTM2ODJlYmYwOWViMzA1NWMxYzRiZDM5Yjc1MWZiZjgxOTUiLCJ0eXAiOiJKV1QifQ\",\"addr_seed\": \"15604334753912523265015800787270404628529489918817818174033741053550755333691\",\"max_epoch\": 10000,\"key_claim_name\": \"sub\",\"modulus\": \"24501106890748714737552440981790137484213218436093327306276573863830528169633224698737117584784274166505493525052788880030500250025091662388617070057693555892212025614452197230081503494967494355047321073341455279175776092624566907541405624967595499832566905567072654796017464431878680118805774542185299632150122052530877100261682728356139724202453050155758294697161107805717430444408191365063957162605112787073991150691398970840390185880092832325216009234084152827135531285878617366639283552856146367480314853517993661640450694829038343380576312039548353544096265483699391507882147093626719041048048921352351403884619\"}").unwrap().init().unwrap(); - let public_inputs = PublicInputs::from_json( - "[\"6049184272607241856912886413680599526372437331989542437266935645748489874658\"]", - ) - .unwrap(); - - assert_eq!(aux_inputs.get_max_epoch(), 10000); - assert_eq!( - aux_inputs.get_address_seed(), - "15604334753912523265015800787270404628529489918817818174033741053550755333691" - ); - assert_eq!(aux_inputs.get_iss(), OAuthProvider::Google.get_config().0); - assert_eq!( - aux_inputs.get_aud(), - "575519204237-msop9ep45u2uo98hapqmngv8d84qdc8k.apps.googleusercontent.com" - ); - assert_eq!(aux_inputs.get_key_claim_name(), "sub"); - assert_eq!(aux_inputs.get_kid(), TEST_KID); - assert_eq!(aux_inputs.get_mod(), "24501106890748714737552440981790137484213218436093327306276573863830528169633224698737117584784274166505493525052788880030500250025091662388617070057693555892212025614452197230081503494967494355047321073341455279175776092624566907541405624967595499832566905567072654796017464431878680118805774542185299632150122052530877100261682728356139724202453050155758294697161107805717430444408191365063957162605112787073991150691398970840390185880092832325216009234084152827135531285878617366639283552856146367480314853517993661640450694829038343380576312039548353544096265483699391507882147093626719041048048921352351403884619"); - assert_eq!( - aux_inputs.calculate_all_inputs_hash(&eph_pubkey).unwrap(), - public_inputs.get_all_inputs_hash().unwrap() - ); - - let mut map = ImHashMap::new(); - map.insert((TEST_KID.to_string(), Google.get_config().0.to_string()), OAuthProviderContent { - kty: "RSA".to_string(), - kid: TEST_KID.to_string(), - e: "AQAB".to_string(), - n: "whYOFK2Ocbbpb_zVypi9SeKiNUqKQH0zTKN1-6fpCTu6ZalGI82s7XK3tan4dJt90ptUPKD2zvxqTzFNfx4HHHsrYCf2-FMLn1VTJfQazA2BvJqAwcpW1bqRUEty8tS_Yv4hRvWfQPcc2Gc3-_fQOOW57zVy-rNoJc744kb30NjQxdGp03J2S3GLQu7oKtSDDPooQHD38PEMNnITf0pj-KgDPjymkMGoJlO3aKppsjfbt_AH6GGdRghYRLOUwQU-h-ofWHR3lbYiKtXPn5dN24kiHy61e3VAQ9_YAZlwXC_99GGtw_NpghFAuM4P1JDn0DppJldy3PGFC0GfBCZASw".to_string(), - alg: "RS256".to_string(), - }); - let proof = ZkLoginProof::from_json("{\"pi_a\":[\"21079899190337156604543197959052999786745784780153100922098887555507822163222\",\"4490261504756339299022091724663793329121338007571218596828748539529998991610\",\"1\"],\"pi_b\":[[\"9379167206161123715528853149920855132656754699464636503784643891913740439869\",\"15902897771112804794883785114808675393618430194414793328415185511364403970347\"],[\"16152736996630746506267683507223054358516992879195296708243566008238438281201\",\"15230917601041350929970534508991793588662911174494137634522926575255163535339\"],[\"1\",\"0\"]],\"pi_c\":[\"8242734018052567627683363270753907648903210541694662698981939667442011573249\",\"1775496841914332445297048246214170486364407018954976081505164205395286250461\",\"1\"],\"protocol\":\"groth16\"}"); - assert!(proof.is_ok()); - let res = verify_zk_login( - &proof.unwrap(), - &public_inputs, - &aux_inputs, - &eph_pubkey, - &map, - ); - assert!(res.is_ok()); -} - -#[test] -fn test_verify_groth16_in_bytes_twitch() { - let mut eph_pubkey = big_int_str_to_bytes("17932473587154777519561053972421347139"); - eph_pubkey.extend(big_int_str_to_bytes( - "134696963602902907403122104327765350261", - )); - let aux_inputs = AuxInputs::from_json("{\"claims\":[{\"name\": \"iss\",\"value_base64\": \"wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw\",\"index_mod_4\": 2 },{\"name\": \"aud\",\"value_base64\": \"yJhdWQiOiJkMzFpY3FsNmw4eHpwYTdlZjMxenR4eXNzNDZvY2siLC\",\"index_mod_4\": 1}],\"header_base64\": \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ\",\"addr_seed\": \"18704353972820279499196832783883157878280522634176394693508060053542990860397\", \"max_epoch\": 10000,\"key_claim_name\": \"sub\",\"modulus\": \"29584508445356008889845267980505093503375439259749620943404021520463333925791823787854637986725604249075338223707087145400343940878353492428289420309978060548116589201208844416027772690277158239820043587577191204733372062526546165382385491203581708579354007308101776609837604633876932723191352425015951165606420826212084566356749532802619014158140227417781995864324396969703870653300311928497704373150532338687663444677910609365123056747321631910862597087829149037858457007019073259229864068519186398691475937234253291322606806349186490894620279606094962475240526774193684946100117999885854009961035928969402039575583\"}").unwrap().init().unwrap(); - let public_inputs = PublicInputs::from_json( - "[\"17466137871302348802176618957727679727566476655921498778525221619744941215202\"]", - ) - .unwrap(); - - let public_inputs_invalid = PublicInputs::from_json( - "[\"17466137871302348802176618957727679727566476655921498778525221619744941215202\", \"17466137871302348802176618957727679727566476655921498778525221619744941215202\"]", - ).unwrap(); - assert!(public_inputs_invalid.get_all_inputs_hash().is_err()); - - assert_eq!(aux_inputs.get_max_epoch(), 10000); - assert_eq!( - aux_inputs.get_address_seed(), - "18704353972820279499196832783883157878280522634176394693508060053542990860397" - ); - assert_eq!(aux_inputs.get_iss(), OAuthProvider::Twitch.get_config().0); - assert_eq!(aux_inputs.get_key_claim_name(), "sub"); - assert_eq!(aux_inputs.get_aud(), "d31icql6l8xzpa7ef31ztxyss46ock"); - assert_eq!(aux_inputs.get_kid(), "1"); - assert_eq!(aux_inputs.get_mod(), "29584508445356008889845267980505093503375439259749620943404021520463333925791823787854637986725604249075338223707087145400343940878353492428289420309978060548116589201208844416027772690277158239820043587577191204733372062526546165382385491203581708579354007308101776609837604633876932723191352425015951165606420826212084566356749532802619014158140227417781995864324396969703870653300311928497704373150532338687663444677910609365123056747321631910862597087829149037858457007019073259229864068519186398691475937234253291322606806349186490894620279606094962475240526774193684946100117999885854009961035928969402039575583"); - assert_eq!( - aux_inputs.calculate_all_inputs_hash(&eph_pubkey).unwrap(), - public_inputs.get_all_inputs_hash().unwrap() - ); - - let mut map = ImHashMap::new(); - map.insert(("1".to_string(), Twitch.get_config().0.to_string()), OAuthProviderContent { - kty: "RSA".to_string(), - kid: "1".to_string(), - e: "AQAB".to_string(), - n: "6lq9MQ-q6hcxr7kOUp-tHlHtdcDsVLwVIw13iXUCvuDOeCi0VSuxCCUY6UmMjy53dX00ih2E4Y4UvlrmmurK0eG26b-HMNNAvCGsVXHU3RcRhVoHDaOwHwU72j7bpHn9XbP3Q3jebX6KIfNbei2MiR0Wyb8RZHE-aZhRYO8_-k9G2GycTpvc-2GBsP8VHLUKKfAs2B6sW3q3ymU6M0L-cFXkZ9fHkn9ejs-sqZPhMJxtBPBxoUIUQFTgv4VXTSv914f_YkNw-EjuwbgwXMvpyr06EyfImxHoxsZkFYB-qBYHtaMxTnFsZBr6fn8Ha2JqT1hoP7Z5r5wxDu3GQhKkHw".to_string(), - alg: "RS256".to_string(), - }); - let proof = ZkLoginProof::from_json("{ \"pi_a\": [ \"14609816250208775088998769033922823275418989011294962335042447516759468155261\", \"20377558696931353568738668428784363385404286135420274775798451001900237387711\", \"1\" ], \"pi_b\": [ [ \"13205564493500587952133306511249429194738679332267485407336676345714082870630\", \"20796060045071998078434479958974217243296767801927986923760870304883706846959\" ], [ \"18144611315874106283809557225033182618356564976139850467162456490949482704538\", \"4318715074202832054732474611176035084202678394565328538059624195976255391002\" ], [ \"1\", \"0\" ] ], \"pi_c\": [ \"4215643272645108456341625420022677634747189283615115637991603989161283548307\", \"5549730540188640204480179088531560793048476496379683802205245590402338452458\", \"1\" ], \"protocol\": \"groth16\"}"); - assert!(proof.is_ok()); - - let res = verify_zk_login( - &proof.unwrap(), - &public_inputs, - &aux_inputs, - &eph_pubkey, - &map, - ); - assert!(res.is_ok()); -} +// #[test] +// fn test_verify_groth16_in_bytes_google() { +// const TEST_KID: &str = "a3bdbfdede3babb2651afca2678dde8c0b35df76"; +// let eph_pubkey = big_int_str_to_bytes( +// "84029355920633174015103288781128426107680789454168570548782290541079926444544", +// ); +// let zklogin_inputs = ZkLoginInputs::from_json("{\"proof_points\":{\"pi_a\":[\"10115636555476176672433949327037256793944494064891616137639788955011782343185\",\"17725279283335201378612298246656982215872669812783655370481651256402307628209\",\"1\"],\"pi_b\":[[\"1375651177372352038685139178931157292667626218805411396900291531433004858962\",\"3658453240474190711383848604264794231798417767090220399646582450741532597799\"],[\"4968731174052339432500136897340201533915869496181174733992611252774559289668\",\"18220101929393838702369882205457098516812487684652915603493215722062774118279\"],[\"1\",\"0\"]],\"pi_c\":[\"21045764731591686814000241054140044009899872387990424352056796837020716033300\",\"14242612448450393885305040218912805962176823915632246598333690909197000368822\",\"1\"],\"protocol\":\"groth16\",\"curve\":\"bn128\"},\"address_seed\":\"1471117909417273104542449813760733381345980113056454572882481692215504994603\",\"claims\":[{\"name\":\"iss\",\"value_base64\":\"yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC\",\"index_mod_4\":1},{\"name\":\"aud\",\"value_base64\":\"CJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLC\",\"index_mod_4\":1}],\"header_base64\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6ImEzYmRiZmRlZGUzYmFiYjI2NTFhZmNhMjY3OGRkZThjMGIzNWRmNzYiLCJ0eXAiOiJKV1QifQ\"}").unwrap().init().unwrap(); +// let mut map = ImHashMap::new(); +// map.insert((TEST_KID.to_string(), Google.get_config().0.to_string()), OAuthProviderContent { +// kty: "RSA".to_string(), +// kid: TEST_KID.to_string(), +// e: "AQAB".to_string(), +// n: "wYvSKSQYKnGNV72_uVc9jbyUeTMsMbUgZPP0uVQX900To7A8a0XA3O17wuImgOG_BwGkpZrIRXF_RRYSK8IOH8N_ViTWh1vyEYSYwr_jfCpDoedJT0O6TZpBhBSmimtmO8ZBCkhZJ4w0AFNIMDPhMokbxwkEapjMA5zio_06dKfb3OBNmrwedZY86W1204-Pfma9Ih15Dm4o8SNFo5Sl0NNO4Ithvj2bbg1Bz1ydE4lMrXdSQL5C2uM9JYRJLnIjaYopBENwgf2Egc9CdVY8tr8jED-WQB6bcUBhDV6lJLZbpBlTHLkF1RlEMnIV2bDo02CryjThnz8l_-6G_7pJww".to_string(), +// alg: "RS256".to_string(), +// }); +// let res = verify_zk_login(&zklogin_inputs, 1, &eph_pubkey, &map); +// println!("{:?}", res); +// assert!(res.is_ok()); +// } + +// #[test] +// fn test_verify_groth16_in_bytes_twitch() { +// let mut eph_pubkey = big_int_str_to_bytes("17932473587154777519561053972421347139"); +// eph_pubkey.extend(big_int_str_to_bytes( +// "134696963602902907403122104327765350261", +// )); +// let aux_inputs = AuxInputs::from_json("{\"claims\":[{\"name\": \"iss\",\"value_base64\": \"wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw\",\"index_mod_4\": 2 },{\"name\": \"aud\",\"value_base64\": \"yJhdWQiOiJkMzFpY3FsNmw4eHpwYTdlZjMxenR4eXNzNDZvY2siLC\",\"index_mod_4\": 1}],\"header_base64\": \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ\",\"addr_seed\": \"18704353972820279499196832783883157878280522634176394693508060053542990860397\", \"max_epoch\": 10000,\"key_claim_name\": \"sub\",\"modulus\": \"29584508445356008889845267980505093503375439259749620943404021520463333925791823787854637986725604249075338223707087145400343940878353492428289420309978060548116589201208844416027772690277158239820043587577191204733372062526546165382385491203581708579354007308101776609837604633876932723191352425015951165606420826212084566356749532802619014158140227417781995864324396969703870653300311928497704373150532338687663444677910609365123056747321631910862597087829149037858457007019073259229864068519186398691475937234253291322606806349186490894620279606094962475240526774193684946100117999885854009961035928969402039575583\"}").unwrap().init().unwrap(); +// let public_inputs = PublicInputs::from_json( +// "[\"17466137871302348802176618957727679727566476655921498778525221619744941215202\"]", +// ) +// .unwrap(); + +// let public_inputs_invalid = PublicInputs::from_json( +// "[\"17466137871302348802176618957727679727566476655921498778525221619744941215202\", \"17466137871302348802176618957727679727566476655921498778525221619744941215202\"]", +// ).unwrap(); +// assert!(public_inputs_invalid.get_all_inputs_hash().is_err()); + +// assert_eq!(aux_inputs.get_max_epoch(), 10000); +// assert_eq!( +// aux_inputs.get_address_seed(), +// "18704353972820279499196832783883157878280522634176394693508060053542990860397" +// ); +// assert_eq!(aux_inputs.get_iss(), OAuthProvider::Twitch.get_config().0); +// assert_eq!(aux_inputs.get_key_claim_name(), "sub"); +// assert_eq!(aux_inputs.get_aud(), "d31icql6l8xzpa7ef31ztxyss46ock"); +// assert_eq!(aux_inputs.get_kid(), "1"); +// assert_eq!(aux_inputs.get_mod(), "29584508445356008889845267980505093503375439259749620943404021520463333925791823787854637986725604249075338223707087145400343940878353492428289420309978060548116589201208844416027772690277158239820043587577191204733372062526546165382385491203581708579354007308101776609837604633876932723191352425015951165606420826212084566356749532802619014158140227417781995864324396969703870653300311928497704373150532338687663444677910609365123056747321631910862597087829149037858457007019073259229864068519186398691475937234253291322606806349186490894620279606094962475240526774193684946100117999885854009961035928969402039575583"); +// assert_eq!( +// aux_inputs.calculate_all_inputs_hash(&eph_pubkey).unwrap(), +// public_inputs.get_all_inputs_hash().unwrap() +// ); + +// let mut map = ImHashMap::new(); +// map.insert(("1".to_string(), Twitch.get_config().0.to_string()), OAuthProviderContent { +// kty: "RSA".to_string(), +// kid: "1".to_string(), +// e: "AQAB".to_string(), +// n: "6lq9MQ-q6hcxr7kOUp-tHlHtdcDsVLwVIw13iXUCvuDOeCi0VSuxCCUY6UmMjy53dX00ih2E4Y4UvlrmmurK0eG26b-HMNNAvCGsVXHU3RcRhVoHDaOwHwU72j7bpHn9XbP3Q3jebX6KIfNbei2MiR0Wyb8RZHE-aZhRYO8_-k9G2GycTpvc-2GBsP8VHLUKKfAs2B6sW3q3ymU6M0L-cFXkZ9fHkn9ejs-sqZPhMJxtBPBxoUIUQFTgv4VXTSv914f_YkNw-EjuwbgwXMvpyr06EyfImxHoxsZkFYB-qBYHtaMxTnFsZBr6fn8Ha2JqT1hoP7Z5r5wxDu3GQhKkHw".to_string(), +// alg: "RS256".to_string(), +// }); +// let proof = ZkLoginProof::from_json("{ \"pi_a\": [ \"14609816250208775088998769033922823275418989011294962335042447516759468155261\", \"20377558696931353568738668428784363385404286135420274775798451001900237387711\", \"1\" ], \"pi_b\": [ [ \"13205564493500587952133306511249429194738679332267485407336676345714082870630\", \"20796060045071998078434479958974217243296767801927986923760870304883706846959\" ], [ \"18144611315874106283809557225033182618356564976139850467162456490949482704538\", \"4318715074202832054732474611176035084202678394565328538059624195976255391002\" ], [ \"1\", \"0\" ] ], \"pi_c\": [ \"4215643272645108456341625420022677634747189283615115637991603989161283548307\", \"5549730540188640204480179088531560793048476496379683802205245590402338452458\", \"1\" ], \"protocol\": \"groth16\"}"); +// assert!(proof.is_ok()); + +// let res = verify_zk_login( +// &proof.unwrap(), +// &public_inputs, +// &aux_inputs, +// &eph_pubkey, +// &map, +// ); +// assert!(res.is_ok()); +// } #[test] fn test_parsed_masked_content() { @@ -160,7 +127,7 @@ fn test_parsed_masked_content() { // iss not found assert_eq!( ParsedMaskedContent::new(VALID_HEADER, &[]).unwrap_err(), - FastCryptoError::GeneralError("iss not found in claims".to_string()) + FastCryptoError::GeneralError("Invalid claim".to_string()) ); // aud not found @@ -174,7 +141,7 @@ fn test_parsed_masked_content() { }] ) .unwrap_err(), - FastCryptoError::GeneralError("aud not found in claims".to_string()) + FastCryptoError::GeneralError("Invalid claim".to_string()) ); // unknown claim name @@ -188,7 +155,7 @@ fn test_parsed_masked_content() { }] ) .unwrap_err(), - FastCryptoError::GeneralError("Invalid claim name".to_string()) + FastCryptoError::GeneralError("iss not found in claims".to_string()) ); // bad index_mod_4 @@ -263,19 +230,15 @@ fn test_verify_extended_claim() { #[test] fn test_map_to_field() { // Test generated against typescript implementation. - assert!(map_to_field("sub", 2).is_err()); - assert_eq!( - map_to_field("sub", 10).unwrap().to_string(), - "18523124550523841778801820019979000409432455608728354507022210389496924497355" - ); + assert!(map_bytes_to_field("sub", 2).is_err()); assert_eq!( - map_to_field("yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC", 133) + map_bytes_to_field("yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC", 133) .unwrap() .to_string(), "19198909745930267855439585988170070469004479286780644790990940640914248274464" ); - assert_eq!(map_to_field("CJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLC", 133).unwrap().to_string(), "6914089902564896687047107167562960781243311797290496295481879371814854678998"); - assert_eq!(map_to_field("eyJhbGciOiJSUzI1NiIsImtpZCI6ImM5YWZkYTM2ODJlYmYwOWViMzA1NWMxYzRiZDM5Yjc1MWZiZjgxOTUiLCJ0eXAiOiJKV1QifQ", 150).unwrap().to_string(), "11195180390614794854381992733393925748746563026948577817495625199891112836762"); + assert_eq!(map_bytes_to_field("CJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLC", 133).unwrap().to_string(), "6914089902564896687047107167562960781243311797290496295481879371814854678998"); + assert_eq!(map_bytes_to_field("eyJhbGciOiJSUzI1NiIsImtpZCI6ImM5YWZkYTM2ODJlYmYwOWViMzA1NWMxYzRiZDM5Yjc1MWZiZjgxOTUiLCJ0eXAiOiJKV1QifQ", 150).unwrap().to_string(), "11195180390614794854381992733393925748746563026948577817495625199891112836762"); } #[test] diff --git a/fastcrypto-zkp/src/bn254/zk_login.rs b/fastcrypto-zkp/src/bn254/zk_login.rs index de08a25124..4d082e001c 100644 --- a/fastcrypto-zkp/src/bn254/zk_login.rs +++ b/fastcrypto-zkp/src/bn254/zk_login.rs @@ -3,11 +3,9 @@ use fastcrypto::error::FastCryptoResult; use serde_json::Value; -use std::collections::HashMap; use std::fmt; use super::poseidon::PoseidonWrapper; -use crate::circom::CircomPublicInputs; use crate::circom::{g1_affine_from_str_projective, g2_affine_from_str_projective}; pub use ark_bn254::{Bn254, Fr as Bn254Fr}; pub use ark_ff::ToConstraintField; @@ -18,7 +16,6 @@ use fastcrypto::{ rsa::{Base64UrlUnpadded, Encoding}, }; use num_bigint::{BigInt, BigUint, Sign}; -use once_cell::sync::Lazy; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::str::FromStr; @@ -28,33 +25,13 @@ type ParsedJWKs = Vec<((String, String), OAuthProviderContent)>; #[path = "unit_tests/zk_login_tests.rs"] mod zk_login_tests; -const MAX_EXTENDED_ISS_LEN: u8 = 99; -const MAX_EXTENDED_ISS_LEN_B64: u8 = 1 + (4 * (MAX_EXTENDED_ISS_LEN / 3)); -const MAX_EXTENDED_AUD_LEN: u8 = 99; -const MAX_EXTENDED_AUD_LEN_B64: u8 = 1 + (4 * (MAX_EXTENDED_AUD_LEN / 3)); -const MAX_HEADER_LEN: u8 = 150; -const PACK_WIDTH: u8 = 248; - -/// Hardcoded mapping from the provider and its supported key claim name to its map-to-field Big Int in string. -/// The field value is computed from the max key claim length and its provider. -static SUPPORTED_KEY_CLAIM_TO_FIELD: Lazy> = Lazy::new(|| { - let mut map = HashMap::new(); - map.insert( - ( - OAuthProvider::Google.get_config().0, - SupportedKeyClaim::Sub.to_string(), - ), - "18523124550523841778801820019979000409432455608728354507022210389496924497355", - ); - map.insert( - ( - OAuthProvider::Twitch.get_config().0, - SupportedKeyClaim::Sub.to_string(), - ), - "18523124550523841778801820019979000409432455608728354507022210389496924497355", - ); - map -}); +const MAX_HEADER_LEN: u16 = 500; +const PACK_WIDTH: u16 = 248; +const ISS: &str = "iss"; +const AUD: &str = "aud"; +const NUM_EXTRACTABLE_STRINGS: u8 = 5; +const MAX_EXTRACTABLE_STR_LEN: u16 = 150; +const MAX_EXTRACTABLE_STR_LEN_B64: u16 = 4 * (1 + MAX_EXTRACTABLE_STR_LEN / 3); /// Supported OAuth providers. Must contain "openid" in "scopes_supported" /// and "public" for "subject_types_supported" instead of "pairwise". @@ -200,23 +177,91 @@ pub struct Claim { index_mod_4: u8, } -/// A parsed result of all aux inputs. -#[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Serialize, Deserialize)] -pub struct AuxInputs { +/// Struct that represents a standard JWT header according to +/// https://openid.net/specs/openid-connect-core-1_0.html +#[derive(Default, Debug, Clone, PartialEq, Eq, JsonSchema, Hash, Serialize, Deserialize)] +pub struct JWTHeader { + alg: String, + kid: String, + typ: String, +} + +impl JWTHeader { + /// Parse the header base64 string into a [struct JWTHeader]. + pub fn new(header_base64: &str) -> Result { + let header_bytes = Base64UrlUnpadded::decode_vec(header_base64) + .map_err(|_| FastCryptoError::InvalidInput)?; + let header_str = + std::str::from_utf8(&header_bytes).map_err(|_| FastCryptoError::InvalidInput)?; + let header: JWTHeader = + serde_json::from_str(header_str).map_err(|_| FastCryptoError::InvalidInput)?; + if header.alg != "RS256" || header.typ != "JWT" { + return Err(FastCryptoError::GeneralError("Invalid header".to_string())); + } + Ok(header) + } +} + +/// A structed of all parsed and validated values from the masked content bytes. +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub struct ParsedMaskedContent { + kid: String, + header: String, + iss: String, + aud: String, +} + +impl ParsedMaskedContent { + /// Read a list of Claims and header string and parse them into fields + /// header, iss, iss_index, aud, aud_index. + pub fn new(header_base64: &str, claims: &[Claim]) -> Result { + let header = JWTHeader::new(header_base64)?; + let claim = claims + .get(0) + .ok_or_else(|| FastCryptoError::GeneralError("Invalid claim".to_string()))?; + if claim.name != ISS { + return Err(FastCryptoError::GeneralError( + "iss not found in claims".to_string(), + )); + } + let ext_iss = decode_base64_url(&claim.value_base64, &claim.index_mod_4)?; + + let claim_2 = claims + .get(1) + .ok_or_else(|| FastCryptoError::GeneralError("Invalid claim".to_string()))?; + if claim_2.name != AUD { + return Err(FastCryptoError::GeneralError( + "aud not found in claims".to_string(), + )); + } + let ext_aud = decode_base64_url(&claim_2.value_base64, &claim_2.index_mod_4)?; + + Ok(ParsedMaskedContent { + kid: header.kid, + header: header_base64.to_string(), + iss: verify_extended_claim(&ext_iss, ISS)?, + aud: verify_extended_claim(&ext_aud, AUD)?, + }) + } +} + +/// All inputs required for the zk login proof verification and other auxiliary inputs. +#[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)] +pub struct ZkLoginInputs { + proof_points: ZkLoginProof, + address_seed: String, claims: Vec, header_base64: String, - addr_seed: String, - max_epoch: u64, - key_claim_name: String, - modulus: String, #[serde(skip)] parsed_masked_content: ParsedMaskedContent, + #[serde(skip)] + all_inputs_hash: Vec, } -impl AuxInputs { +impl ZkLoginInputs { /// Validate and parse masked content bytes into the struct and other json strings into the struct. pub fn from_json(value: &str) -> Result { - let inputs: AuxInputs = + let inputs: ZkLoginInputs = serde_json::from_str(value).map_err(|_| FastCryptoError::InvalidInput)?; Ok(inputs) } @@ -227,14 +272,9 @@ impl AuxInputs { Ok(self.to_owned()) } - /// Get the max epoch value. - pub fn get_max_epoch(&self) -> u64 { - self.max_epoch - } - - /// Get the address seed in string. - pub fn get_address_seed(&self) -> &str { - &self.addr_seed + /// Get the parsed kid string. + pub fn get_kid(&self) -> &str { + &self.parsed_masked_content.kid } /// Get the parsed iss string. @@ -247,28 +287,39 @@ impl AuxInputs { &self.parsed_masked_content.aud } - /// Get the key claim name used. - pub fn get_key_claim_name(&self) -> &str { - &self.key_claim_name + /// Get zk login proof. + pub fn get_proof(&self) -> &ZkLoginProof { + &self.proof_points } - /// Get the parsed kid string. - pub fn get_kid(&self) -> &str { - &self.parsed_masked_content.kid + /// Get public inputs in arkworks format. + pub fn get_public_inputs(&self) -> &[Bn254Fr] { + &self.all_inputs_hash + } + + /// Get address seed string. + pub fn get_address_seed(&self) -> &str { + &self.address_seed } - /// Get the modulus. - pub fn get_mod(&self) -> &str { - &self.modulus + /// Get address seed string. + pub fn get_address_params(&self) -> AddressParams { + AddressParams::new(self.get_iss().to_owned(), self.get_aud().to_owned()) } - /// Calculate the poseidon hash from 10 selected fields in the aux inputs. + /// Calculate the poseidon hash from selected fields from inputs, along with the ephemeral pubkey. pub fn calculate_all_inputs_hash( &self, eph_pubkey_bytes: &[u8], - ) -> Result { + modulus: &[u8], + max_epoch: u64, + ) -> Result, FastCryptoError> { + if self.header_base64.len() > MAX_HEADER_LEN as usize { + return Err(FastCryptoError::GeneralError("Header too long".to_string())); + } + let mut poseidon = PoseidonWrapper::new(); - let addr_seed = to_field(&self.addr_seed)?; + let addr_seed = to_field(&self.address_seed)?; let (first_half, second_half) = eph_pubkey_bytes.split_at(eph_pubkey_bytes.len() / 2); let first_bigint = BigInt::from_bytes_be(Sign::Plus, first_half); @@ -276,133 +327,58 @@ impl AuxInputs { let eph_public_key_0 = to_field(&first_bigint.to_string())?; let eph_public_key_1 = to_field(&second_bigint.to_string())?; - - let max_epoch = to_field(&self.max_epoch.to_string())?; - let key_claim_name_f = to_field( - SUPPORTED_KEY_CLAIM_TO_FIELD - .get(&(self.get_iss(), self.get_key_claim_name().to_owned())) - .ok_or(FastCryptoError::InvalidInput)?, - )?; - let iss_f = map_to_field( - &self.parsed_masked_content.iss_str, - MAX_EXTENDED_ISS_LEN_B64, - )?; - let aud_f = map_to_field( - &self.parsed_masked_content.aud_str, - MAX_EXTENDED_AUD_LEN_B64, - )?; - let header_f = map_to_field(&self.parsed_masked_content.header, MAX_HEADER_LEN)?; - let iss_index = to_field(&self.parsed_masked_content.iss_index.to_string())?; - let aud_index = to_field(&self.parsed_masked_content.aud_index.to_string())?; - - Ok(poseidon - .hash(vec![ - addr_seed, - eph_public_key_0, - eph_public_key_1, - max_epoch, - key_claim_name_f, - iss_f, - iss_index, - aud_f, - aud_index, - header_f, - ])? - .to_string()) - } -} -/// Struct that represents a standard JWT header according to -/// https://openid.net/specs/openid-connect-core-1_0.html -#[derive(Default, Debug, Clone, PartialEq, Eq, JsonSchema, Hash, Serialize, Deserialize)] -pub struct JWTHeader { - alg: String, - kid: String, - typ: String, -} - -impl JWTHeader { - /// Parse the header base64 string into a [struct JWTHeader]. - pub fn new(header_base64: &str) -> Result { - let header_bytes = Base64UrlUnpadded::decode_vec(header_base64) - .map_err(|_| FastCryptoError::InvalidInput)?; - let header_str = - std::str::from_utf8(&header_bytes).map_err(|_| FastCryptoError::InvalidInput)?; - let header: JWTHeader = - serde_json::from_str(header_str).map_err(|_| FastCryptoError::InvalidInput)?; - if header.alg != "RS256" || header.typ != "JWT" { - return Err(FastCryptoError::GeneralError("Invalid header".to_string())); + let max_epoch = to_field(&max_epoch.to_string())?; + let mut padded_claims = self.claims.clone(); + for _ in self.claims.len()..NUM_EXTRACTABLE_STRINGS as usize { + padded_claims.push(Claim { + name: "dummy".to_string(), + value_base64: "e".to_string(), + index_mod_4: 0, + }); } - Ok(header) - } -} - -/// A structed of all parsed and validated values from the masked content bytes. -#[derive(Default, Debug, Clone, PartialEq, Eq)] -pub struct ParsedMaskedContent { - kid: String, - header: String, - iss: String, - aud: String, - iss_str: String, - aud_str: String, - iss_index: u8, - aud_index: u8, -} - -impl ParsedMaskedContent { - /// Read a list of Claims and header string and parse them into fields - /// header, iss, iss_index, aud, aud_index. - pub fn new(header_base64: &str, claims: &[Claim]) -> Result { - let header = JWTHeader::new(header_base64)?; - let mut iss = None; - let mut aud = None; - for claim in claims { - match claim.name.as_str() { - "iss" => { - iss = Some(( - decode_base64_url(&claim.value_base64, &claim.index_mod_4)?, - claim.value_base64.clone(), - claim.index_mod_4, - )); - } - "aud" => { - aud = Some(( - decode_base64_url(&claim.value_base64, &claim.index_mod_4)?, - claim.value_base64.clone(), - claim.index_mod_4, - )); - } - _ => { - return Err(FastCryptoError::GeneralError( - "Invalid claim name".to_string(), - )); - } + let mut claim_f = Vec::new(); + for i in 0..NUM_EXTRACTABLE_STRINGS { + let val = &padded_claims[i as usize].value_base64; + if val.len() > MAX_EXTRACTABLE_STR_LEN_B64 as usize { + return Err(FastCryptoError::GeneralError( + "Invalid claim length".to_string(), + )); } + claim_f.push(map_bytes_to_field( + &padded_claims[i as usize].value_base64, + MAX_EXTRACTABLE_STR_LEN_B64, + )?); } - let iss_val = iss - .ok_or_else(|| FastCryptoError::GeneralError("iss not found in claims".to_string()))?; - let aud_val = aud - .ok_or_else(|| FastCryptoError::GeneralError("aud not found in claims".to_string()))?; - Ok(ParsedMaskedContent { - kid: header.kid, - header: header_base64.to_string(), - iss: verify_extended_claim(&iss_val.0.to_string(), "iss")?, - aud: verify_extended_claim(&aud_val.0.to_string(), "aud")?, - iss_str: iss_val.1, - aud_str: aud_val.1, - iss_index: iss_val.2, - aud_index: aud_val.2, - }) + let mut poseidon_claim = PoseidonWrapper::new(); + let extracted_claims_hash = poseidon_claim.hash(claim_f)?; + + let mut poseidon_index = PoseidonWrapper::new(); + let extracted_index_hash = poseidon_index.hash( + padded_claims + .iter() + .map(|c| to_field(&c.index_mod_4.to_string()).unwrap()) + .collect::>(), + )?; + let header_f = map_bytes_to_field(&self.parsed_masked_content.header, MAX_HEADER_LEN)?; + let modulus_f = map_to_field(&[BigUint::from_bytes_be(modulus)], 2048)?; + Ok(vec![poseidon.hash(vec![ + eph_public_key_0, + eph_public_key_1, + addr_seed, + max_epoch, + extracted_claims_hash, + extracted_index_hash, + header_f, + modulus_f, + ])?]) } } - /// The zk login proof. #[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)] pub struct ZkLoginProof { pi_a: Vec, pi_b: Vec>, pi_c: Vec, - protocol: String, } impl ZkLoginProof { @@ -410,10 +386,7 @@ impl ZkLoginProof { pub fn from_json(value: &str) -> Result { let proof: ZkLoginProof = serde_json::from_str(value).map_err(|_| FastCryptoError::InvalidProof)?; - match proof.protocol == "groth16" { - true => Ok(proof), - false => Err(FastCryptoError::InvalidProof), - } + Ok(proof) } /// Convert the Circom G1/G2/GT to arkworks G1/G2/GT @@ -425,41 +398,6 @@ impl ZkLoginProof { } } -/// The public inputs containing an array of string that is the all inputs hash. -#[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Hash, Serialize, Deserialize)] -pub struct PublicInputs { - inputs: Vec, // Represented the public inputs in canonical serialized form. -} - -impl PublicInputs { - /// Parse the public inputs from a json string. - pub fn from_json(value: &str) -> Result { - let inputs: CircomPublicInputs = - serde_json::from_str(value).map_err(|_| FastCryptoError::InvalidProof)?; - Ok(Self { inputs }) - } - - /// Convert the public inputs into arkworks format. - pub fn as_arkworks(&self) -> Result, FastCryptoError> { - let mut result = Vec::new(); - for input in &self.inputs { - match Bn254Fr::from_str(input) { - Ok(value) => result.push(value), - Err(_) => return Err(FastCryptoError::InvalidInput), - } - } - Ok(result) - } - - /// Get the all_inputs_hash as big int string. - pub fn get_all_inputs_hash(&self) -> Result<&str, FastCryptoError> { - if self.inputs.len() != 1 { - return Err(FastCryptoError::InvalidInput); - } - Ok(&self.inputs[0]) - } -} - /// Parse the extended claim json value to its claim value, using the expected claim key. fn verify_extended_claim( extended_claim: &str, @@ -579,22 +517,23 @@ fn bitarray_to_bytearray(bits: &[u8]) -> Vec { /// Calculate the poseidon hash of the field element inputs. pub fn to_poseidon_hash(inputs: Vec) -> Result { - if inputs.len() <= 15 { + if inputs.len() <= 16 { let mut poseidon1: PoseidonWrapper = PoseidonWrapper::new(); poseidon1.hash(inputs) - } else if inputs.len() <= 30 { + } else if inputs.len() <= 32 { let mut poseidon1: PoseidonWrapper = PoseidonWrapper::new(); - let hash1 = poseidon1.hash(inputs[0..15].to_vec())?; + let hash1 = poseidon1.hash(inputs[0..16].to_vec())?; let mut poseidon2 = PoseidonWrapper::new(); - let hash2 = poseidon2.hash(inputs[15..].to_vec())?; + let hash2 = poseidon2.hash(inputs[16..].to_vec())?; let mut poseidon3 = PoseidonWrapper::new(); poseidon3.hash([hash1, hash2].to_vec()) } else { - Err(FastCryptoError::GeneralError( - "Invalid input length for poseidon hash".to_string(), - )) + Err(FastCryptoError::GeneralError(format!( + "Yet to implement: Unable to hash a vector of length {}", + inputs.len() + ))) } } @@ -603,26 +542,45 @@ fn to_field(val: &str) -> Result { Bn254Fr::from_str(val).map_err(|_| FastCryptoError::InvalidInput) } -/// Parse the input to a big int array and calculate the poseidon hash after packing. -fn map_to_field(input: &str, max_size: u8) -> Result { - if input.len() > max_size as usize { +/// Pads a stream of bytes and maps it to a field element +fn map_bytes_to_field(str: &str, max_size: u16) -> Result { + if str.len() > max_size as usize { return Err(FastCryptoError::InvalidInput); } - let num_elements = max_size / (PACK_WIDTH / 8) + 1; - let in_arr: Vec = input + let in_arr: Vec = str .chars() .map(|c| BigUint::from_slice(&([c as u32]))) .collect(); - let packed = pack2(&in_arr, 8, PACK_WIDTH, num_elements)?; + + let str_padded = pad_with_zeros(in_arr, max_size)?; + map_to_field(&str_padded, 8) +} + +fn pad_with_zeros(in_arr: Vec, out_count: u16) -> Result, FastCryptoError> { + if in_arr.len() > out_count as usize { + return Err(FastCryptoError::GeneralError("in_arr too long".to_string())); + } + let mut padded = in_arr.clone(); + padded.extend(vec![ + BigUint::from_str("0").unwrap(); + out_count as usize - in_arr.len() as usize + ]); + Ok(padded) +} + +/// Parse the input to a big int array and calculate the poseidon hash after packing. +fn map_to_field(input: &[BigUint], in_width: u16) -> Result { + let num_elements = (input.len() * in_width as usize) / PACK_WIDTH as usize + 1; + let packed = pack2(input, in_width, PACK_WIDTH, num_elements)?; to_poseidon_hash(packed) } -/// Helper function to pack field elements from big ints. +/// Helper function to pack into exactly outCount chunks of outWidth bits each. fn pack2( in_arr: &[BigUint], - in_width: u8, - out_width: u8, - out_count: u8, + in_width: u16, + out_width: u16, + out_count: usize, ) -> Result, FastCryptoError> { let packed = pack(in_arr, in_width as usize, out_width as usize)?; if packed.len() > out_count as usize { @@ -689,3 +647,19 @@ pub fn big_int_str_to_bytes(value: &str) -> Vec { .to_bytes_be() .1 } + +/// Parameters for generating an address. +#[derive(Debug, Serialize, Deserialize)] +pub struct AddressParams { + /// The issuer string. + pub iss: String, + /// The audience string. + pub aud: String, +} + +impl AddressParams { + /// Create address params from iss and aud. + pub fn new(iss: String, aud: String) -> Self { + Self { iss, aud } + } +} diff --git a/fastcrypto-zkp/src/bn254/zk_login_api.rs b/fastcrypto-zkp/src/bn254/zk_login_api.rs index 9c9cd402bb..b0010dedbb 100644 --- a/fastcrypto-zkp/src/bn254/zk_login_api.rs +++ b/fastcrypto-zkp/src/bn254/zk_login_api.rs @@ -4,12 +4,9 @@ use ark_crypto_primitives::snark::SNARK; use fastcrypto::rsa::{Base64UrlUnpadded, Encoding}; use im::hashmap::HashMap as ImHashMap; -use num_bigint::BigUint; use super::verifier::process_vk_special; -use super::zk_login::{ - AuxInputs, OAuthProviderContent, PublicInputs, SupportedKeyClaim, ZkLoginProof, -}; +use super::zk_login::{OAuthProviderContent, SupportedKeyClaim, ZkLoginInputs}; use crate::bn254::VerifyingKey as Bn254VerifyingKey; use crate::{ bn254::verifier::PreparedVerifyingKey, @@ -82,16 +79,16 @@ fn global_pvk() -> PreparedVerifyingKey { let mut vk_gamma_abc_g1 = Vec::new(); for e in vec![ vec![ - "4646159977885290315333074199003995943497097760119603432786031341328349612779" + "7601221783497382045435100727010102844416767995017297605284115099608422303035" .to_string(), - "16883660321018397536550988255072623983427868378088223250291094422460916984531" + "8749785198598536603958085261928419291825402152367782685067088145065090991309" .to_string(), "1".to_string(), ], vec![ - "6837327174314649334165592796561910467712597348860761363984054398343874430321" + "2844107402968053321142842260538249836495213364133637503989930436252095154777" .to_string(), - "8986010922336065169810776007712346238931454905016238478271450397492184507492" + "6671443994502368977962577284247390754840595243304510253358092664535353826787" .to_string(), "1".to_string(), ], @@ -112,42 +109,22 @@ fn global_pvk() -> PreparedVerifyingKey { /// Entry point for the ZkLogin API. pub fn verify_zk_login( - proof: &ZkLoginProof, - public_inputs: &PublicInputs, - aux_inputs: &AuxInputs, + input: &ZkLoginInputs, + max_epoch: u64, eph_pubkey_bytes: &[u8], all_jwk: &ImHashMap<(String, String), OAuthProviderContent>, ) -> Result<(), FastCryptoError> { - if !is_claim_supported(aux_inputs.get_key_claim_name()) { - return Err(FastCryptoError::GeneralError( - "Unsupported claim found".to_string(), - )); - } - let jwk = all_jwk - .get(&( - aux_inputs.get_kid().to_string(), - aux_inputs.get_iss().to_string(), - )) + .get(&(input.get_kid().to_string(), input.get_iss().to_string())) .ok_or_else(|| FastCryptoError::GeneralError("JWK not found".to_string()))?; - let jwk_modulus = - BigUint::from_bytes_be(&Base64UrlUnpadded::decode_vec(&jwk.n).map_err(|_| { - FastCryptoError::GeneralError("Invalid Base64 encoded jwk.n".to_string()) - })?); - - if jwk_modulus.to_string() != aux_inputs.get_mod() || jwk.validate().is_err() { - return Err(FastCryptoError::GeneralError("Invalid modulus".to_string())); - } - - if aux_inputs.calculate_all_inputs_hash(eph_pubkey_bytes)? - != public_inputs.get_all_inputs_hash()? - { - return Err(FastCryptoError::GeneralError( - "Invalid all inputs hash".to_string(), - )); - } + let modulus = Base64UrlUnpadded::decode_vec(&jwk.n).map_err(|_| { + FastCryptoError::GeneralError("Invalid Base64 encoded jwk modulus".to_string()) + })?; - match verify_zk_login_proof_with_fixed_vk(proof, public_inputs) { + match verify_zk_login_proof_with_fixed_vk( + input, + &input.calculate_all_inputs_hash(eph_pubkey_bytes, &modulus, max_epoch)?, + ) { Ok(true) => Ok(()), Ok(false) | Err(_) => Err(FastCryptoError::GeneralError( "Groth16 proof verify failed".to_string(), @@ -157,13 +134,13 @@ pub fn verify_zk_login( /// Verify a zk login proof using the fixed verifying key. fn verify_zk_login_proof_with_fixed_vk( - proof: &ZkLoginProof, - public_inputs: &PublicInputs, + input: &ZkLoginInputs, + public_inputs: &[Bn254Fr], ) -> Result { Groth16::::verify_with_processed_vk( &GLOBAL_VERIFYING_KEY.as_arkworks_pvk(), - &public_inputs.as_arkworks()?, - &proof.as_arkworks(), + public_inputs, + &input.get_proof().as_arkworks(), ) .map_err(|e| FastCryptoError::GeneralError(e.to_string())) } From b90bc85b91ed2ce6ed4bc609caf6fa7dc9afe70e Mon Sep 17 00:00:00 2001 From: Joy Wang <108701016+joyqvq@users.noreply.github.com> Date: Wed, 9 Aug 2023 13:04:55 -0400 Subject: [PATCH 04/13] fix tests --- .../src/bn254/unit_tests/zk_login_tests.rs | 126 +++++++----------- 1 file changed, 49 insertions(+), 77 deletions(-) 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 e18dbb39fa..2b5986dff0 100644 --- a/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs +++ b/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs @@ -15,99 +15,71 @@ use im::hashmap::HashMap as ImHashMap; const GOOGLE_JWK_BYTES: &[u8] = r#"{ "keys": [ - { - "kty": "RSA", - "e": "AQAB", - "alg": "RS256", - "kid": "c9afda3682ebf09eb3055c1c4bd39b751fbf8195", - "use": "sig", - "n": "whYOFK2Ocbbpb_zVypi9SeKiNUqKQH0zTKN1-6fpCTu6ZalGI82s7XK3tan4dJt90ptUPKD2zvxqTzFNfx4HHHsrYCf2-FMLn1VTJfQazA2BvJqAwcpW1bqRUEty8tS_Yv4hRvWfQPcc2Gc3-_fQOOW57zVy-rNoJc744kb30NjQxdGp03J2S3GLQu7oKtSDDPooQHD38PEMNnITf0pj-KgDPjymkMGoJlO3aKppsjfbt_AH6GGdRghYRLOUwQU-h-ofWHR3lbYiKtXPn5dN24kiHy61e3VAQ9_YAZlwXC_99GGtw_NpghFAuM4P1JDn0DppJldy3PGFC0GfBCZASw" - }, - { - "alg": "RS256", - "use": "sig", - "n": "1qrQCTst3RF04aMC9Ye_kGbsE0sftL4FOtB_WrzBDOFdrfVwLfflQuPX5kJ-0iYv9r2mjD5YIDy8b-iJKwevb69ISeoOrmL3tj6MStJesbbRRLVyFIm_6L7alHhZVyqHQtMKX7IaNndrfebnLReGntuNk76XCFxBBnRaIzAWnzr3WN4UPBt84A0KF74pei17dlqHZJ2HB2CsYbE9Ort8m7Vf6hwxYzFtCvMCnZil0fCtk2OQ73l6egcvYO65DkAJibFsC9xAgZaF-9GYRlSjMPd0SMQ8yU9i3W7beT00Xw6C0FYA9JAYaGaOvbT87l_6ZkAksOMuvIPD_jNVfTCPLQ==", - "e": "AQAB", - "kty": "RSA", - "kid": "6083dd5981673f661fde9dae646b6f0380a0145c" - } - ] + { + "n": "4kGxcWQdTW43aszLmftsGswmwDDKdfcse-lKeT_zjZTB2KGw9E6LVY6IThJVxzYF6mcyU-Z5_jDAW_yi7D_gXep2rxchZvoFayXynbhxyfjK6RtJ6_k30j-WpsXCSAiNAkupYHUyDIBNocvUcrDJsC3U65l8jl1I3nW98X6d-IlAfEb2In2f0fR6d-_lhIQZjXLupjymJduPjjA8oXCUZ9bfAYPhGYj3ZELUHkAyDpZNrnSi8hFVMSUSnorAt9F7cKMUJDM4-Uopzaqcl_f-HxeKvxN7NjiLSiIYaHdgtTpCEuNvsch6q6JTsllJNr3c__BxrG4UMlJ3_KsPxbcvXw==", + "use": "sig", + "alg": "RS256", + "e": "AQAB", + "kid": "911e39e27928ae9f1e9d1e21646de92d19351b44", + "kty": "RSA" + }, + { + "n": "pGMz603XOzO71r-LpW555Etbn2dXAtY4xToNE_Upr1EHxkHFnVnGPsbOeWzP8xU1IpAL56S3sTsbpCR_Ci_PYq8s4I3VWQM0u9w1D_e45S1KJTSex_aiMQ_cjTXb3Iekc00JIkMJhUaNnbsEt7PlOmnyFqvN-G3ZXVDfTuL2Wsn4tRMYf7YU3jgTVN2M_p7bcZYHhkEB-jzNeK7ub-6mOMkKdYWnk0jIoRfV63d32bub0pQpWv8sVmflgK2xKUSJVMZ7CM0FvJYJgF7y42KBPYc6Gm_UWE0uHazDgZgAvQQoNyEF_TRjVfGiihjPFYCPqvFcfLK4773JTD2fLZTgOQ==", + "kid": "7c9c78e3b00e1bb092d246c887b11220c87b7d20", + "e": "AQAB", + "alg": "RS256", + "kty": "RSA", + "use": "sig" + }, + { + "use": "sig", + "kid": "fd48a75138d9d48f0aa635ef569c4e196f7ae8d6", + "e": "AQAB", + "n": "8KImylelEspnZ0X-ekZb9VPbUFhgB_yEPJuLKOhXOWJLVsU0hJP6B_mQOfVk0CHm66UsAhqV8qrINk-RXgwVaaFLMA827pbOOBhyvHsThcyo7AY5s6M7qbftFKKnkfVHO6c9TsQ9wpIfmhCVL3QgTlqlgFQWcNsY-qemSKpqvVi-We9I3kPvbTf0PKJ_rWA7GQQnU_GA5JRU46uvw4I1ODf0icNBHw7pWc7oTvmSl1G8OWABEyiFakcUG2Xd4qZnmWaKwLHBvifPuIyy2vK-yHH91mVZCuleVu53Vzj77RgUtF2EEuB-zizwC-fzaBmvnfx1kgQLsdK22J0Ivgu4Xw==", + "kty": "RSA", + "alg": "RS256" + } + ] }"#.as_bytes(); const TWITCH_JWK_BYTES: &[u8] = r#"{ "keys":[{"alg":"RS256","e":"AQAB","kid":"1","kty":"RSA","n":"6lq9MQ-q6hcxr7kOUp-tHlHtdcDsVLwVIw13iXUCvuDOeCi0VSuxCCUY6UmMjy53dX00ih2E4Y4UvlrmmurK0eG26b-HMNNAvCGsVXHU3RcRhVoHDaOwHwU72j7bpHn9XbP3Q3jebX6KIfNbei2MiR0Wyb8RZHE-aZhRYO8_-k9G2GycTpvc-2GBsP8VHLUKKfAs2B6sW3q3ymU6M0L-cFXkZ9fHkn9ejs-sqZPhMJxtBPBxoUIUQFTgv4VXTSv914f_YkNw-EjuwbgwXMvpyr06EyfImxHoxsZkFYB-qBYHtaMxTnFsZBr6fn8Ha2JqT1hoP7Z5r5wxDu3GQhKkHw","use":"sig"}] }"#.as_bytes(); -// #[test] -// fn test_verify_groth16_in_bytes_google() { -// const TEST_KID: &str = "a3bdbfdede3babb2651afca2678dde8c0b35df76"; -// let eph_pubkey = big_int_str_to_bytes( -// "84029355920633174015103288781128426107680789454168570548782290541079926444544", -// ); -// let zklogin_inputs = ZkLoginInputs::from_json("{\"proof_points\":{\"pi_a\":[\"10115636555476176672433949327037256793944494064891616137639788955011782343185\",\"17725279283335201378612298246656982215872669812783655370481651256402307628209\",\"1\"],\"pi_b\":[[\"1375651177372352038685139178931157292667626218805411396900291531433004858962\",\"3658453240474190711383848604264794231798417767090220399646582450741532597799\"],[\"4968731174052339432500136897340201533915869496181174733992611252774559289668\",\"18220101929393838702369882205457098516812487684652915603493215722062774118279\"],[\"1\",\"0\"]],\"pi_c\":[\"21045764731591686814000241054140044009899872387990424352056796837020716033300\",\"14242612448450393885305040218912805962176823915632246598333690909197000368822\",\"1\"],\"protocol\":\"groth16\",\"curve\":\"bn128\"},\"address_seed\":\"1471117909417273104542449813760733381345980113056454572882481692215504994603\",\"claims\":[{\"name\":\"iss\",\"value_base64\":\"yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC\",\"index_mod_4\":1},{\"name\":\"aud\",\"value_base64\":\"CJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLC\",\"index_mod_4\":1}],\"header_base64\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6ImEzYmRiZmRlZGUzYmFiYjI2NTFhZmNhMjY3OGRkZThjMGIzNWRmNzYiLCJ0eXAiOiJKV1QifQ\"}").unwrap().init().unwrap(); -// let mut map = ImHashMap::new(); -// map.insert((TEST_KID.to_string(), Google.get_config().0.to_string()), OAuthProviderContent { -// kty: "RSA".to_string(), -// kid: TEST_KID.to_string(), -// e: "AQAB".to_string(), -// n: "wYvSKSQYKnGNV72_uVc9jbyUeTMsMbUgZPP0uVQX900To7A8a0XA3O17wuImgOG_BwGkpZrIRXF_RRYSK8IOH8N_ViTWh1vyEYSYwr_jfCpDoedJT0O6TZpBhBSmimtmO8ZBCkhZJ4w0AFNIMDPhMokbxwkEapjMA5zio_06dKfb3OBNmrwedZY86W1204-Pfma9Ih15Dm4o8SNFo5Sl0NNO4Ithvj2bbg1Bz1ydE4lMrXdSQL5C2uM9JYRJLnIjaYopBENwgf2Egc9CdVY8tr8jED-WQB6bcUBhDV6lJLZbpBlTHLkF1RlEMnIV2bDo02CryjThnz8l_-6G_7pJww".to_string(), -// alg: "RS256".to_string(), -// }); -// let res = verify_zk_login(&zklogin_inputs, 1, &eph_pubkey, &map); -// println!("{:?}", res); -// assert!(res.is_ok()); -// } +#[test] +fn test_verify_groth16_in_bytes_google() { + let eph_pubkey = big_int_str_to_bytes( + "56151737484251736814483548229439134346260227666297800205999380540545421916794", + ); + let zklogin_inputs = ZkLoginInputs::from_json("{\"proof_points\":{\"pi_a\":[\"21873110949718272176264499735051118510902652173449346226388645893968555905454\",\"9365690448451448553918847987625925585660927132009682965576314935347286975528\",\"1\"],\"pi_b\":[[\"15958796868294059768344785719001504259904252886111915738476099643330239502720\",\"5199780263797497491150666057763076365993388827750563298709399606326966788526\"],[\"12251020242741083412146363549260633868128775234600208395200954294062312280014\",\"14706191700752148070300113544073417958401225568211414370186962841591249968729\"],[\"1\",\"0\"]],\"pi_c\":[\"12535507296151794095352527984139224545671930452049988269896859223168464793732\",\"14295848621099014282744065804163532747912895097343096119343520196101998703625\",\"1\"]},\"address_seed\":\"7577247629761003321376053963457717029490787816434302620024795358930497565155\",\"claims\":[{\"name\":\"iss\",\"value_base64\":\"yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC\",\"index_mod_4\":1},{\"name\":\"aud\",\"value_base64\":\"CJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLC\",\"index_mod_4\":1}],\"header_base64\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjkxMWUzOWUyNzkyOGFlOWYxZTlkMWUyMTY0NmRlOTJkMTkzNTFiNDQiLCJ0eXAiOiJKV1QifQ\"}").unwrap().init().unwrap(); + let mut map = ImHashMap::new(); + map.insert(("911e39e27928ae9f1e9d1e21646de92d19351b44".to_string(), Google.get_config().0.to_string()), OAuthProviderContent { + kty: "RSA".to_string(), + kid: "911e39e27928ae9f1e9d1e21646de92d19351b44".to_string(), + e: "AQAB".to_string(), + n: "4kGxcWQdTW43aszLmftsGswmwDDKdfcse-lKeT_zjZTB2KGw9E6LVY6IThJVxzYF6mcyU-Z5_jDAW_yi7D_gXep2rxchZvoFayXynbhxyfjK6RtJ6_k30j-WpsXCSAiNAkupYHUyDIBNocvUcrDJsC3U65l8jl1I3nW98X6d-IlAfEb2In2f0fR6d-_lhIQZjXLupjymJduPjjA8oXCUZ9bfAYPhGYj3ZELUHkAyDpZNrnSi8hFVMSUSnorAt9F7cKMUJDM4-Uopzaqcl_f-HxeKvxN7NjiLSiIYaHdgtTpCEuNvsch6q6JTsllJNr3c__BxrG4UMlJ3_KsPxbcvXw".to_string(), + alg: "RS256".to_string(), + }); + let res = verify_zk_login(&zklogin_inputs, 10, &eph_pubkey, &map); + assert!(res.is_ok()); +} +// TODO: revive when JWK is added in Sui. // #[test] // fn test_verify_groth16_in_bytes_twitch() { -// let mut eph_pubkey = big_int_str_to_bytes("17932473587154777519561053972421347139"); -// eph_pubkey.extend(big_int_str_to_bytes( -// "134696963602902907403122104327765350261", -// )); -// let aux_inputs = AuxInputs::from_json("{\"claims\":[{\"name\": \"iss\",\"value_base64\": \"wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw\",\"index_mod_4\": 2 },{\"name\": \"aud\",\"value_base64\": \"yJhdWQiOiJkMzFpY3FsNmw4eHpwYTdlZjMxenR4eXNzNDZvY2siLC\",\"index_mod_4\": 1}],\"header_base64\": \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ\",\"addr_seed\": \"18704353972820279499196832783883157878280522634176394693508060053542990860397\", \"max_epoch\": 10000,\"key_claim_name\": \"sub\",\"modulus\": \"29584508445356008889845267980505093503375439259749620943404021520463333925791823787854637986725604249075338223707087145400343940878353492428289420309978060548116589201208844416027772690277158239820043587577191204733372062526546165382385491203581708579354007308101776609837604633876932723191352425015951165606420826212084566356749532802619014158140227417781995864324396969703870653300311928497704373150532338687663444677910609365123056747321631910862597087829149037858457007019073259229864068519186398691475937234253291322606806349186490894620279606094962475240526774193684946100117999885854009961035928969402039575583\"}").unwrap().init().unwrap(); -// let public_inputs = PublicInputs::from_json( -// "[\"17466137871302348802176618957727679727566476655921498778525221619744941215202\"]", -// ) -// .unwrap(); - -// let public_inputs_invalid = PublicInputs::from_json( -// "[\"17466137871302348802176618957727679727566476655921498778525221619744941215202\", \"17466137871302348802176618957727679727566476655921498778525221619744941215202\"]", -// ).unwrap(); -// assert!(public_inputs_invalid.get_all_inputs_hash().is_err()); - -// assert_eq!(aux_inputs.get_max_epoch(), 10000); -// assert_eq!( -// aux_inputs.get_address_seed(), -// "18704353972820279499196832783883157878280522634176394693508060053542990860397" -// ); -// assert_eq!(aux_inputs.get_iss(), OAuthProvider::Twitch.get_config().0); -// assert_eq!(aux_inputs.get_key_claim_name(), "sub"); -// assert_eq!(aux_inputs.get_aud(), "d31icql6l8xzpa7ef31ztxyss46ock"); -// assert_eq!(aux_inputs.get_kid(), "1"); -// assert_eq!(aux_inputs.get_mod(), "29584508445356008889845267980505093503375439259749620943404021520463333925791823787854637986725604249075338223707087145400343940878353492428289420309978060548116589201208844416027772690277158239820043587577191204733372062526546165382385491203581708579354007308101776609837604633876932723191352425015951165606420826212084566356749532802619014158140227417781995864324396969703870653300311928497704373150532338687663444677910609365123056747321631910862597087829149037858457007019073259229864068519186398691475937234253291322606806349186490894620279606094962475240526774193684946100117999885854009961035928969402039575583"); -// assert_eq!( -// aux_inputs.calculate_all_inputs_hash(&eph_pubkey).unwrap(), -// public_inputs.get_all_inputs_hash().unwrap() +// let eph_pubkey = big_int_str_to_bytes( +// "56151737484251736814483548229439134346260227666297800205999380540545421916794", // ); - +// let zklogin_inputs = ZkLoginInputs::from_json("{\"proof_points\":{\"pi_a\":[\"21873110949718272176264499735051118510902652173449346226388645893968555905454\",\"9365690448451448553918847987625925585660927132009682965576314935347286975528\",\"1\"],\"pi_b\":[[\"15958796868294059768344785719001504259904252886111915738476099643330239502720\",\"5199780263797497491150666057763076365993388827750563298709399606326966788526\"],[\"12251020242741083412146363549260633868128775234600208395200954294062312280014\",\"14706191700752148070300113544073417958401225568211414370186962841591249968729\"],[\"1\",\"0\"]],\"pi_c\":[\"12535507296151794095352527984139224545671930452049988269896859223168464793732\",\"14295848621099014282744065804163532747912895097343096119343520196101998703625\",\"1\"]},\"address_seed\":\"7577247629761003321376053963457717029490787816434302620024795358930497565155\",\"claims\":[{\"name\":\"iss\",\"value_base64\":\"yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC\",\"index_mod_4\":1},{\"name\":\"aud\",\"value_base64\":\"CJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLC\",\"index_mod_4\":1}],\"header_base64\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjkxMWUzOWUyNzkyOGFlOWYxZTlkMWUyMTY0NmRlOTJkMTkzNTFiNDQiLCJ0eXAiOiJKV1QifQ\"}").unwrap().init().unwrap(); // let mut map = ImHashMap::new(); // map.insert(("1".to_string(), Twitch.get_config().0.to_string()), OAuthProviderContent { // kty: "RSA".to_string(), // kid: "1".to_string(), // e: "AQAB".to_string(), -// n: "6lq9MQ-q6hcxr7kOUp-tHlHtdcDsVLwVIw13iXUCvuDOeCi0VSuxCCUY6UmMjy53dX00ih2E4Y4UvlrmmurK0eG26b-HMNNAvCGsVXHU3RcRhVoHDaOwHwU72j7bpHn9XbP3Q3jebX6KIfNbei2MiR0Wyb8RZHE-aZhRYO8_-k9G2GycTpvc-2GBsP8VHLUKKfAs2B6sW3q3ymU6M0L-cFXkZ9fHkn9ejs-sqZPhMJxtBPBxoUIUQFTgv4VXTSv914f_YkNw-EjuwbgwXMvpyr06EyfImxHoxsZkFYB-qBYHtaMxTnFsZBr6fn8Ha2JqT1hoP7Z5r5wxDu3GQhKkHw".to_string(), +// n: "4kGxcWQdTW43aszLmftsGswmwDDKdfcse-lKeT_zjZTB2KGw9E6LVY6IThJVxzYF6mcyU-Z5_jDAW_yi7D_gXep2rxchZvoFayXynbhxyfjK6RtJ6_k30j-WpsXCSAiNAkupYHUyDIBNocvUcrDJsC3U65l8jl1I3nW98X6d-IlAfEb2In2f0fR6d-_lhIQZjXLupjymJduPjjA8oXCUZ9bfAYPhGYj3ZELUHkAyDpZNrnSi8hFVMSUSnorAt9F7cKMUJDM4-Uopzaqcl_f-HxeKvxN7NjiLSiIYaHdgtTpCEuNvsch6q6JTsllJNr3c__BxrG4UMlJ3_KsPxbcvXw".to_string(), // alg: "RS256".to_string(), // }); -// let proof = ZkLoginProof::from_json("{ \"pi_a\": [ \"14609816250208775088998769033922823275418989011294962335042447516759468155261\", \"20377558696931353568738668428784363385404286135420274775798451001900237387711\", \"1\" ], \"pi_b\": [ [ \"13205564493500587952133306511249429194738679332267485407336676345714082870630\", \"20796060045071998078434479958974217243296767801927986923760870304883706846959\" ], [ \"18144611315874106283809557225033182618356564976139850467162456490949482704538\", \"4318715074202832054732474611176035084202678394565328538059624195976255391002\" ], [ \"1\", \"0\" ] ], \"pi_c\": [ \"4215643272645108456341625420022677634747189283615115637991603989161283548307\", \"5549730540188640204480179088531560793048476496379683802205245590402338452458\", \"1\" ], \"protocol\": \"groth16\"}"); -// assert!(proof.is_ok()); - -// let res = verify_zk_login( -// &proof.unwrap(), -// &public_inputs, -// &aux_inputs, -// &eph_pubkey, -// &map, -// ); +// let res = verify_zk_login(&zklogin_inputs, 10, &eph_pubkey, &map); // assert!(res.is_ok()); // } @@ -248,6 +220,6 @@ fn test_jwk_parse() { "wYvSKSQYKnGNV72_uVc9jbyUeTMsMbUgZPP0uVQX900To7A8a0XA3O17wuImgOG_BwGkpZrIRXF_RRYSK8IOH8N_ViTWh1vyEYSYwr_jfCpDoedJT0O6TZpBhBSmimtmO8ZBCkhZJ4w0AFNIMDPhMokbxwkEapjMA5zio_06dKfb3OBNmrwedZY86W1204-Pfma9Ih15Dm4o8SNFo5Sl0NNO4Ithvj2bbg1Bz1ydE4lMrXdSQL5C2uM9JYRJLnIjaYopBENwgf2Egc9CdVY8tr8jED-WQB6bcUBhDV6lJLZbpBlTHLkF1RlEMnIV2bDo02CryjThnz8l_-6G_7pJww" ); - assert_eq!(parse_jwks(GOOGLE_JWK_BYTES, Google).unwrap().len(), 2); + assert_eq!(parse_jwks(GOOGLE_JWK_BYTES, Google).unwrap().len(), 3); assert_eq!(parse_jwks(TWITCH_JWK_BYTES, Twitch).unwrap().len(), 1); } From 5a52f76d346d67e73a3a188a47869b905f073e50 Mon Sep 17 00:00:00 2001 From: Joy Wang <108701016+joyqvq@users.noreply.github.com> Date: Thu, 10 Aug 2023 13:33:46 -0400 Subject: [PATCH 05/13] minor documentation change --- fastcrypto-zkp/src/bn254/mod.rs | 2 +- .../src/bn254/unit_tests/zk_login_tests.rs | 223 +++++++++++++++--- fastcrypto-zkp/src/bn254/zk_login.rs | 51 ++-- fastcrypto-zkp/src/bn254/zk_login_api.rs | 60 +++-- 4 files changed, 254 insertions(+), 82 deletions(-) diff --git a/fastcrypto-zkp/src/bn254/mod.rs b/fastcrypto-zkp/src/bn254/mod.rs index b7c94f5fb6..135821d31f 100644 --- a/fastcrypto-zkp/src/bn254/mod.rs +++ b/fastcrypto-zkp/src/bn254/mod.rs @@ -18,7 +18,7 @@ pub mod poseidon; /// Zk login structs and utilities pub mod zk_login; -/// api +/// Zk login entrypoints pub mod zk_login_api; /// A field element in the BN254 construction. Thin wrapper around `api::Bn254Fr`. 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 2b5986dff0..04cfbfc9c1 100644 --- a/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs +++ b/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs @@ -1,16 +1,18 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use crate::bn254::zk_login::OAuthProvider::{Google, Twitch}; +use crate::bn254::zk_login::OAuthProvider; use crate::bn254::zk_login::{ big_int_str_to_bytes, decode_base64_url, map_bytes_to_field, parse_jwks, trim, verify_extended_claim, Claim, JWTHeader, ParsedMaskedContent, }; +use crate::bn254::zk_login_api::Environment; use crate::bn254::{ zk_login::{OAuthProviderContent, ZkLoginInputs}, zk_login_api::verify_zk_login, }; use fastcrypto::error::FastCryptoError; +use fastcrypto::rsa::{Base64UrlUnpadded, Encoding}; use im::hashmap::HashMap as ImHashMap; const GOOGLE_JWK_BYTES: &[u8] = r#"{ @@ -46,42 +48,132 @@ const TWITCH_JWK_BYTES: &[u8] = r#"{ "keys":[{"alg":"RS256","e":"AQAB","kid":"1","kty":"RSA","n":"6lq9MQ-q6hcxr7kOUp-tHlHtdcDsVLwVIw13iXUCvuDOeCi0VSuxCCUY6UmMjy53dX00ih2E4Y4UvlrmmurK0eG26b-HMNNAvCGsVXHU3RcRhVoHDaOwHwU72j7bpHn9XbP3Q3jebX6KIfNbei2MiR0Wyb8RZHE-aZhRYO8_-k9G2GycTpvc-2GBsP8VHLUKKfAs2B6sW3q3ymU6M0L-cFXkZ9fHkn9ejs-sqZPhMJxtBPBxoUIUQFTgv4VXTSv914f_YkNw-EjuwbgwXMvpyr06EyfImxHoxsZkFYB-qBYHtaMxTnFsZBr6fn8Ha2JqT1hoP7Z5r5wxDu3GQhKkHw","use":"sig"}] }"#.as_bytes(); +const FACEBOOK_JWK_BYTES: &[u8] = r#"{ + "keys": [ + { + "kid": "5931701331165f07f550ac5f0194942d54f9c249", + "kty": "RSA", + "alg": "RS256", + "use": "sig", + "n": "-GuAIboTsRYNprJQOkdmuKXRx8ARnKXOC9Pajg4KxHHPt3OY8rXRmVeDxTj1-m9TfW6V-wJa_8ncBbbFE-aV-eBi_XeuIToBBvLZp1-UPIjitS8WCDrUhHiJnbvkIZf1B1YBIq_Ua81fzxhtjQ0jDftV2m5aavmJG4_94VG3Md7noQjjUKzxJyUNl4v_joMA6pIRCeeamvfIZorjcR4wVf-wR8NiZjjRbcjKBpc7ztc7Gm778h34RSe9-DLH6uicTROSYNa99pUwhn3XVfAv4hTFpLIcgHYadLZjsHfUvivr76uiYbxDZx6UTkK5jmi51b87u1b6iYmijDIMztzrIQ", + "e": "AQAB" + }, + { + "kid": "a378585d826a933cc207ce31cad63c019a53095c", + "kty": "RSA", + "alg": "RS256", + "use": "sig", + "n": "1aLDAmRq-QeOr1b8WbtpmD5D4CpE5S0YrNklM5BrRjuZ6FTG8AhqvyUUnAb7Dd1gCZgARbuk2yHOOca78JWX2ocAId9R4OV2PUoIYljDZ5gQJBaL6liMpolQjlqovmd7IpF8XZWudWU6Rfhoh-j6dd-8IHeJjBKMYij0CuA6HZ1L98vBW1ehEdnBZPfTe28H57hySzucnC1q1340h2E2cpCfLZ-vNoYQ4Qe-CZKpUAKOoOlC4tWCt2rLcsV_vXvmNlLv_UYGbJEFKS-I1tEwtlD71bvn9WWluE7L4pWlIolgzNyIz4yxe7G7V4jlvSSwsu1ZtIQzt5AtTC--5HEAyQ", + "e": "AQAB" + } + ] + }"#.as_bytes(); + +const BAD_JWK_BYTES: &[u8] = r#"{ + "keys":[{"alg":"RS256","e":"AQAB","kid":"1","kty":"RSA","n":"6lq9MQ-q6hcxr7kOUp-tHlHtdcDsVLwVIw13iXUCvuDOeCi0VSuxCCUY6UmMjy53dX00ih2E4Y4UvlrmmurK0eG26b-HMNNAvCGsVXHU3RcRhVoHDaOwHwU72j7bpHn9XbP3Q3jebX6KIfNbei2MiR0Wyb8RZHE-aZhRYO8_-k9G2GycTpvc-2GBsP8VHLUKKfAs2B6sW3q3ymU6M0L-cFXkZ9fHkn9ejs-sqZPhMJxtBPBxoUIUQFTgv4VXTSv914f_YkNw-EjuwbgwXMvpyr06EyfImxHoxsZkFYB-qBYHtaMxTnFsZBr6fn8Ha2JqT1hoP7Z5r5wxDu3GQhKkHw","use":"wrong usage"}] + }"#.as_bytes(); + #[test] fn test_verify_groth16_in_bytes_google() { + use crate::bn254::zk_login_api::Bn254Fr; + use std::str::FromStr; + let eph_pubkey = big_int_str_to_bytes( - "56151737484251736814483548229439134346260227666297800205999380540545421916794", + "84029355920633174015103288781128426107680789454168570548782290541079926444544", + ); + assert!(ZkLoginInputs::from_json("{\"something\":{\"pi_a\":[\"17906300526443048714387222471528497388165567048979081127218444558531971001212\",\"16347093943573822555530932280098040740968368762067770538848146419225596827968\",\"1\"],\"pi_b\":[[\"604559992637298524596005947885439665413516028337069712707205304781687795569\",\"3442016989288172723305001983346837664894554996521317914830240702746056975984\"],[\"11525538739919950358574045244601652351196410355282682596092151863632911615318\",\"8054528381876103674715157136115660256860302241449545586065224275685056359825\"],[\"1\",\"0\"]],\"pi_c\":[\"12090542001353421590770702288155881067849038975293665701252531703168853963809\",\"8667909164654995486331191860419304610736366583628608454080754129255123340291\",\"1\"]},\"address_seed\":\"7577247629761003321376053963457717029490787816434302620024795358930497565155\",\"claims\":[{\"name\":\"iss\",\"value_base64\":\"yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC\",\"index_mod_4\":1},{\"name\":\"aud\",\"value_base64\":\"CJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLC\",\"index_mod_4\":1}],\"header_base64\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjkxMWUzOWUyNzkyOGFlOWYxZTlkMWUyMTY0NmRlOTJkMTkzNTFiNDQiLCJ0eXAiOiJKV1QifQ\"}").is_err()); + + let zklogin_inputs = ZkLoginInputs::from_json("{\"proof_points\":{\"pi_a\":[\"17906300526443048714387222471528497388165567048979081127218444558531971001212\",\"16347093943573822555530932280098040740968368762067770538848146419225596827968\",\"1\"],\"pi_b\":[[\"604559992637298524596005947885439665413516028337069712707205304781687795569\",\"3442016989288172723305001983346837664894554996521317914830240702746056975984\"],[\"11525538739919950358574045244601652351196410355282682596092151863632911615318\",\"8054528381876103674715157136115660256860302241449545586065224275685056359825\"],[\"1\",\"0\"]],\"pi_c\":[\"12090542001353421590770702288155881067849038975293665701252531703168853963809\",\"8667909164654995486331191860419304610736366583628608454080754129255123340291\",\"1\"]},\"address_seed\":\"7577247629761003321376053963457717029490787816434302620024795358930497565155\",\"claims\":[{\"name\":\"iss\",\"value_base64\":\"yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC\",\"index_mod_4\":1},{\"name\":\"aud\",\"value_base64\":\"CJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLC\",\"index_mod_4\":1}],\"header_base64\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjkxMWUzOWUyNzkyOGFlOWYxZTlkMWUyMTY0NmRlOTJkMTkzNTFiNDQiLCJ0eXAiOiJKV1QifQ\"}").unwrap().init().unwrap(); + assert_eq!( + zklogin_inputs.get_kid(), + "911e39e27928ae9f1e9d1e21646de92d19351b44".to_string() + ); + assert_eq!( + zklogin_inputs.get_iss(), + OAuthProvider::Google.get_config().0.to_string() + ); + assert_eq!( + zklogin_inputs.get_aud(), + "575519204237-msop9ep45u2uo98hapqmngv8d84qdc8k.apps.googleusercontent.com".to_string() ); - let zklogin_inputs = ZkLoginInputs::from_json("{\"proof_points\":{\"pi_a\":[\"21873110949718272176264499735051118510902652173449346226388645893968555905454\",\"9365690448451448553918847987625925585660927132009682965576314935347286975528\",\"1\"],\"pi_b\":[[\"15958796868294059768344785719001504259904252886111915738476099643330239502720\",\"5199780263797497491150666057763076365993388827750563298709399606326966788526\"],[\"12251020242741083412146363549260633868128775234600208395200954294062312280014\",\"14706191700752148070300113544073417958401225568211414370186962841591249968729\"],[\"1\",\"0\"]],\"pi_c\":[\"12535507296151794095352527984139224545671930452049988269896859223168464793732\",\"14295848621099014282744065804163532747912895097343096119343520196101998703625\",\"1\"]},\"address_seed\":\"7577247629761003321376053963457717029490787816434302620024795358930497565155\",\"claims\":[{\"name\":\"iss\",\"value_base64\":\"yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC\",\"index_mod_4\":1},{\"name\":\"aud\",\"value_base64\":\"CJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLC\",\"index_mod_4\":1}],\"header_base64\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjkxMWUzOWUyNzkyOGFlOWYxZTlkMWUyMTY0NmRlOTJkMTkzNTFiNDQiLCJ0eXAiOiJKV1QifQ\"}").unwrap().init().unwrap(); + assert_eq!( + zklogin_inputs.get_address_params().aud, + "575519204237-msop9ep45u2uo98hapqmngv8d84qdc8k.apps.googleusercontent.com".to_string() + ); + assert_eq!( + zklogin_inputs.get_address_params().iss, + OAuthProvider::Google.get_config().0.to_string() + ); + let mut map = ImHashMap::new(); - map.insert(("911e39e27928ae9f1e9d1e21646de92d19351b44".to_string(), Google.get_config().0.to_string()), OAuthProviderContent { + let content = OAuthProviderContent { kty: "RSA".to_string(), kid: "911e39e27928ae9f1e9d1e21646de92d19351b44".to_string(), e: "AQAB".to_string(), n: "4kGxcWQdTW43aszLmftsGswmwDDKdfcse-lKeT_zjZTB2KGw9E6LVY6IThJVxzYF6mcyU-Z5_jDAW_yi7D_gXep2rxchZvoFayXynbhxyfjK6RtJ6_k30j-WpsXCSAiNAkupYHUyDIBNocvUcrDJsC3U65l8jl1I3nW98X6d-IlAfEb2In2f0fR6d-_lhIQZjXLupjymJduPjjA8oXCUZ9bfAYPhGYj3ZELUHkAyDpZNrnSi8hFVMSUSnorAt9F7cKMUJDM4-Uopzaqcl_f-HxeKvxN7NjiLSiIYaHdgtTpCEuNvsch6q6JTsllJNr3c__BxrG4UMlJ3_KsPxbcvXw".to_string(), alg: "RS256".to_string(), - }); - let res = verify_zk_login(&zklogin_inputs, 10, &eph_pubkey, &map); + }; + + map.insert( + ( + "911e39e27928ae9f1e9d1e21646de92d19351b44".to_string(), + OAuthProvider::Google.get_config().0.to_string(), + ), + content.clone(), + ); + let modulus = Base64UrlUnpadded::decode_vec(&content.n).unwrap(); + assert_eq!( + zklogin_inputs + .calculate_all_inputs_hash(&eph_pubkey, &modulus, 10000) + .unwrap(), + vec![Bn254Fr::from_str( + "16145454131073363668578485057126520560259092161919376662788533820152738424329" + ) + .unwrap()] + ); + let res = verify_zk_login(&zklogin_inputs, 10000, &eph_pubkey, &map, Environment::Test); assert!(res.is_ok()); } -// TODO: revive when JWK is added in Sui. -// #[test] -// fn test_verify_groth16_in_bytes_twitch() { -// let eph_pubkey = big_int_str_to_bytes( -// "56151737484251736814483548229439134346260227666297800205999380540545421916794", -// ); -// let zklogin_inputs = ZkLoginInputs::from_json("{\"proof_points\":{\"pi_a\":[\"21873110949718272176264499735051118510902652173449346226388645893968555905454\",\"9365690448451448553918847987625925585660927132009682965576314935347286975528\",\"1\"],\"pi_b\":[[\"15958796868294059768344785719001504259904252886111915738476099643330239502720\",\"5199780263797497491150666057763076365993388827750563298709399606326966788526\"],[\"12251020242741083412146363549260633868128775234600208395200954294062312280014\",\"14706191700752148070300113544073417958401225568211414370186962841591249968729\"],[\"1\",\"0\"]],\"pi_c\":[\"12535507296151794095352527984139224545671930452049988269896859223168464793732\",\"14295848621099014282744065804163532747912895097343096119343520196101998703625\",\"1\"]},\"address_seed\":\"7577247629761003321376053963457717029490787816434302620024795358930497565155\",\"claims\":[{\"name\":\"iss\",\"value_base64\":\"yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC\",\"index_mod_4\":1},{\"name\":\"aud\",\"value_base64\":\"CJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLC\",\"index_mod_4\":1}],\"header_base64\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjkxMWUzOWUyNzkyOGFlOWYxZTlkMWUyMTY0NmRlOTJkMTkzNTFiNDQiLCJ0eXAiOiJKV1QifQ\"}").unwrap().init().unwrap(); -// let mut map = ImHashMap::new(); -// map.insert(("1".to_string(), Twitch.get_config().0.to_string()), OAuthProviderContent { -// kty: "RSA".to_string(), -// kid: "1".to_string(), -// e: "AQAB".to_string(), -// n: "4kGxcWQdTW43aszLmftsGswmwDDKdfcse-lKeT_zjZTB2KGw9E6LVY6IThJVxzYF6mcyU-Z5_jDAW_yi7D_gXep2rxchZvoFayXynbhxyfjK6RtJ6_k30j-WpsXCSAiNAkupYHUyDIBNocvUcrDJsC3U65l8jl1I3nW98X6d-IlAfEb2In2f0fR6d-_lhIQZjXLupjymJduPjjA8oXCUZ9bfAYPhGYj3ZELUHkAyDpZNrnSi8hFVMSUSnorAt9F7cKMUJDM4-Uopzaqcl_f-HxeKvxN7NjiLSiIYaHdgtTpCEuNvsch6q6JTsllJNr3c__BxrG4UMlJ3_KsPxbcvXw".to_string(), -// alg: "RS256".to_string(), -// }); -// let res = verify_zk_login(&zklogin_inputs, 10, &eph_pubkey, &map); -// assert!(res.is_ok()); -// } +#[test] +fn test_verify_groth16_in_bytes_twitch() { + let eph_pubkey = big_int_str_to_bytes( + "84029355920633174015103288781128426107680789454168570548782290541079926444544", + ); + let zklogin_inputs = ZkLoginInputs::from_json("{\"proof_points\":{\"pi_a\":[\"1506355985430918561986621304944206628720275836415696432527226322011786292469\",\"613455373800062466065258859046532491698424798267244949352433118740950052820\",\"1\"],\"pi_b\":[[\"13432605760939024875746197498117682238269308826467106768856049869617495266990\",\"1832067857097543048080313900700732174583103404004828871583233404786491016938\"],[\"12774506805709477874931076799272413990755625719529677477582407925650184326419\",\"9920407680353330309465070375503195109002963389725451415597959332611544046474\"],[\"1\",\"0\"]],\"pi_c\":[\"21460871700073370465890462779698742952227192461460518900819221976004900851129\",\"16417973205725808622705510292771877448246769946278068014196442886693189946200\",\"1\"]},\"address_seed\":\"8309251037759855865804524144086603991988043161256515070528758422897128118794\",\"claims\":[{\"name\":\"iss\",\"value_base64\":\"wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw\",\"index_mod_4\":2},{\"name\":\"aud\",\"value_base64\":\"yJhdWQiOiJyczFiaDA2NWk5eWE0eWR2aWZpeGw0a3NzMHVocHQiLC\",\"index_mod_4\":1}],\"header_base64\":\"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ\"}").unwrap().init().unwrap(); + assert_eq!(zklogin_inputs.get_kid(), "1".to_string()); + assert_eq!( + zklogin_inputs.get_iss(), + OAuthProvider::Twitch.get_config().0.to_string() + ); + assert_eq!( + zklogin_inputs.get_aud(), + "rs1bh065i9ya4ydvifixl4kss0uhpt".to_string() + ); + assert_eq!( + zklogin_inputs.get_address_params().aud, + "rs1bh065i9ya4ydvifixl4kss0uhpt".to_string() + ); + assert_eq!( + zklogin_inputs.get_address_params().iss, + zklogin_inputs.get_iss() + ); + assert_eq!( + zklogin_inputs.get_address_seed(), + "8309251037759855865804524144086603991988043161256515070528758422897128118794" + ); + + let mut map = ImHashMap::new(); + map.insert(("1".to_string(), OAuthProvider::Twitch.get_config().0.to_string()), OAuthProviderContent { + kty: "RSA".to_string(), + kid: "1".to_string(), + e: "AQAB".to_string(), + n: "6lq9MQ-q6hcxr7kOUp-tHlHtdcDsVLwVIw13iXUCvuDOeCi0VSuxCCUY6UmMjy53dX00ih2E4Y4UvlrmmurK0eG26b-HMNNAvCGsVXHU3RcRhVoHDaOwHwU72j7bpHn9XbP3Q3jebX6KIfNbei2MiR0Wyb8RZHE-aZhRYO8_-k9G2GycTpvc-2GBsP8VHLUKKfAs2B6sW3q3ymU6M0L-cFXkZ9fHkn9ejs-sqZPhMJxtBPBxoUIUQFTgv4VXTSv914f_YkNw-EjuwbgwXMvpyr06EyfImxHoxsZkFYB-qBYHtaMxTnFsZBr6fn8Ha2JqT1hoP7Z5r5wxDu3GQhKkHw".to_string(), + alg: "RS256".to_string(), + }); + let res = verify_zk_login(&zklogin_inputs, 10000, &eph_pubkey, &map, Environment::Test); + assert!(res.is_ok()); +} #[test] fn test_parsed_masked_content() { @@ -102,7 +194,7 @@ fn test_parsed_masked_content() { FastCryptoError::GeneralError("Invalid claim".to_string()) ); - // aud not found + // missing claim assert_eq!( ParsedMaskedContent::new( VALID_HEADER, @@ -151,6 +243,41 @@ fn test_parsed_masked_content() { .unwrap_err(), FastCryptoError::GeneralError("Invalid masked content".to_string()) ); + + // first claim is not iss + assert_eq!( + ParsedMaskedContent::new( + VALID_HEADER, + &[Claim { + name: "aud".to_string(), + value_base64: "wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw".to_string(), + index_mod_4: 2 + }] + ) + .unwrap_err(), + FastCryptoError::GeneralError("iss not found in claims".to_string()) + ); + + // second claim is not aud + assert_eq!( + ParsedMaskedContent::new( + VALID_HEADER, + &[ + Claim { + name: "iss".to_string(), + value_base64: "wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw".to_string(), + index_mod_4: 2 + }, + Claim { + name: "iss".to_string(), + value_base64: "wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw".to_string(), + index_mod_4: 2 + } + ] + ) + .unwrap_err(), + FastCryptoError::GeneralError("aud not found in claims".to_string()) + ); } #[test] @@ -220,6 +347,48 @@ fn test_jwk_parse() { "wYvSKSQYKnGNV72_uVc9jbyUeTMsMbUgZPP0uVQX900To7A8a0XA3O17wuImgOG_BwGkpZrIRXF_RRYSK8IOH8N_ViTWh1vyEYSYwr_jfCpDoedJT0O6TZpBhBSmimtmO8ZBCkhZJ4w0AFNIMDPhMokbxwkEapjMA5zio_06dKfb3OBNmrwedZY86W1204-Pfma9Ih15Dm4o8SNFo5Sl0NNO4Ithvj2bbg1Bz1ydE4lMrXdSQL5C2uM9JYRJLnIjaYopBENwgf2Egc9CdVY8tr8jED-WQB6bcUBhDV6lJLZbpBlTHLkF1RlEMnIV2bDo02CryjThnz8l_-6G_7pJww" ); - assert_eq!(parse_jwks(GOOGLE_JWK_BYTES, Google).unwrap().len(), 3); - assert_eq!(parse_jwks(TWITCH_JWK_BYTES, Twitch).unwrap().len(), 1); + parse_jwks(GOOGLE_JWK_BYTES, OAuthProvider::Google) + .unwrap() + .iter() + .for_each(|content| { + assert_eq!(content.0 .0, content.1.kid()); + assert_eq!(content.0 .1, OAuthProvider::Google.get_config().0); + }); + + parse_jwks(TWITCH_JWK_BYTES, OAuthProvider::Twitch) + .unwrap() + .iter() + .for_each(|content| { + assert_eq!(content.0 .0, content.1.kid()); + assert_eq!(content.0 .1, OAuthProvider::Twitch.get_config().0); + }); + + parse_jwks(FACEBOOK_JWK_BYTES, OAuthProvider::Facebook) + .unwrap() + .iter() + .for_each(|content| { + assert_eq!(content.0 .0, content.1.kid()); + assert_eq!(content.0 .1, OAuthProvider::Facebook.get_config().0); + }); + + assert!(parse_jwks(BAD_JWK_BYTES, OAuthProvider::Twitch).is_err()); + + assert!(OAuthProviderContent { + kty: "RSA".to_string(), + e: "something".to_string(), + n: "".to_string(), + alg: "RS256".to_string(), + kid: "".to_string(), + } + .validate() + .is_err()); + + assert!(parse_jwks( + r#"{ + "something":[] + }"# + .as_bytes(), + OAuthProvider::Twitch + ) + .is_err()); } diff --git a/fastcrypto-zkp/src/bn254/zk_login.rs b/fastcrypto-zkp/src/bn254/zk_login.rs index 4d082e001c..31085b853d 100644 --- a/fastcrypto-zkp/src/bn254/zk_login.rs +++ b/fastcrypto-zkp/src/bn254/zk_login.rs @@ -3,7 +3,6 @@ use fastcrypto::error::FastCryptoResult; use serde_json::Value; -use std::fmt; use super::poseidon::PoseidonWrapper; use crate::circom::{g1_affine_from_str_projective, g2_affine_from_str_projective}; @@ -35,12 +34,14 @@ const MAX_EXTRACTABLE_STR_LEN_B64: u16 = 4 * (1 + MAX_EXTRACTABLE_STR_LEN / 3); /// Supported OAuth providers. Must contain "openid" in "scopes_supported" /// and "public" for "subject_types_supported" instead of "pairwise". -#[derive(Debug, Hash, PartialEq, Eq)] +#[derive(Clone, Debug, Hash, PartialEq, Eq)] pub enum OAuthProvider { /// See https://accounts.google.com/.well-known/openid-configuration Google, /// See https://id.twitch.tv/oauth2/.well-known/openid-configuration Twitch, + /// See https://www.facebook.com/.well-known/openid-configuration/ + Facebook, } /// Struct that contains all the OAuth provider information. A list of them can @@ -57,7 +58,7 @@ pub struct OAuthProviderContent { pub n: String, /// Algorithm parameter, https://datatracker.ietf.org/doc/html/rfc7517#section-4.4 pub alg: String, - /// kid + /// Key ID that identifies a JWK in a JWK set, https://datatracker.ietf.org/doc/html/rfc7517#section-4.5 kid: String, } @@ -105,7 +106,7 @@ impl OAuthProviderContent { /// Trim trailing '=' so that it is considered a valid base64 url encoding string by base64ct library. fn trim(str: String) -> String { - str.trim_end_matches(|c: char| c == '=').to_owned() + str.trim_end_matches('=').to_owned() } /// Parse the JWK bytes received from the oauth provider keys endpoint into a map from kid to @@ -131,11 +132,13 @@ pub fn parse_jwks( return Ok(ret); } } - Err(FastCryptoError::GeneralError("JWK not found".to_string())) + Err(FastCryptoError::GeneralError( + "Invalid JWK response".to_string(), + )) } impl OAuthProvider { - /// Returns a tuple of iss string and JWK endpoint string for the given provider. + /// Returns a tuple of iss string and the JWK url string for the given provider. pub fn get_config(&self) -> (&str, &str) { match self { OAuthProvider::Google => ( @@ -146,25 +149,10 @@ impl OAuthProvider { "https://id.twitch.tv/oauth2", "https://id.twitch.tv/oauth2/keys", ), - } - } -} - -/// The claims in the body signed by OAuth provider that must -/// be locally unique to the provider and cannot be reassigned. -#[derive(Debug)] -pub enum SupportedKeyClaim { - /// Subject id representing an unique account for provider. - Sub, - /// Email string representing an unique account for provider. - Email, -} - -impl fmt::Display for SupportedKeyClaim { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - SupportedKeyClaim::Email => write!(f, "email"), - SupportedKeyClaim::Sub => write!(f, "sub"), + OAuthProvider::Facebook => ( + "https://www.facebook.com", + "https://www.facebook.com/.well-known/oauth/openid/jwks/", + ), } } } @@ -321,7 +309,7 @@ impl ZkLoginInputs { let mut poseidon = PoseidonWrapper::new(); let addr_seed = to_field(&self.address_seed)?; - let (first_half, second_half) = eph_pubkey_bytes.split_at(eph_pubkey_bytes.len() / 2); + let (first_half, second_half) = eph_pubkey_bytes.split_at(16); let first_bigint = BigInt::from_bytes_be(Sign::Plus, first_half); let second_bigint = BigInt::from_bytes_be(Sign::Plus, second_half); @@ -490,7 +478,7 @@ fn base64_to_bitarray(input: &str) -> Vec { .collect() } -/// Convert a bitarray to a bytearray by taking each 8 bits as a byte. +/// Convert a bitarray (each bit is represented by u8) to a bytearray by taking each 8 bits as a byte. fn bitarray_to_bytearray(bits: &[u8]) -> Vec { let mut bytes: Vec = Vec::new(); let mut current_byte: u8 = 0; @@ -552,11 +540,11 @@ fn map_bytes_to_field(str: &str, max_size: u16) -> Result, out_count: u16) -> Result, FastCryptoError> { +fn pad_with_zeroes(in_arr: Vec, out_count: u16) -> Result, FastCryptoError> { if in_arr.len() > out_count as usize { return Err(FastCryptoError::GeneralError("in_arr too long".to_string())); } @@ -568,7 +556,10 @@ fn pad_with_zeros(in_arr: Vec, out_count: u16) -> Result, Ok(padded) } -/// Parse the input to a big int array and calculate the poseidon hash after packing. +/// Maps a stream of bigints to a single field element. First we convert the base from +/// inWidth to packWidth. Then we compute the poseidon hash of the "packed" input. +/// input is the input vector containing equal-width big ints. inWidth is the width of +/// each input element. fn map_to_field(input: &[BigUint], in_width: u16) -> Result { let num_elements = (input.len() * in_width as usize) / PACK_WIDTH as usize + 1; let packed = pack2(input, in_width, PACK_WIDTH, num_elements)?; diff --git a/fastcrypto-zkp/src/bn254/zk_login_api.rs b/fastcrypto-zkp/src/bn254/zk_login_api.rs index b0010dedbb..fc95f2231a 100644 --- a/fastcrypto-zkp/src/bn254/zk_login_api.rs +++ b/fastcrypto-zkp/src/bn254/zk_login_api.rs @@ -6,23 +6,31 @@ use fastcrypto::rsa::{Base64UrlUnpadded, Encoding}; use im::hashmap::HashMap as ImHashMap; use super::verifier::process_vk_special; -use super::zk_login::{OAuthProviderContent, SupportedKeyClaim, ZkLoginInputs}; +use super::zk_login::{OAuthProviderContent, ZkLoginInputs}; use crate::bn254::VerifyingKey as Bn254VerifyingKey; -use crate::{ - bn254::verifier::PreparedVerifyingKey, - circom::{g1_affine_from_str_projective, g2_affine_from_str_projective}, -}; +use crate::circom::{g1_affine_from_str_projective, g2_affine_from_str_projective}; pub use ark_bn254::{Bn254, Fr as Bn254Fr}; pub use ark_ff::ToConstraintField; -use ark_groth16::{Groth16, VerifyingKey}; +use ark_groth16::{Groth16, PreparedVerifyingKey, Proof, VerifyingKey}; pub use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use fastcrypto::error::FastCryptoError; use once_cell::sync::Lazy; -static GLOBAL_VERIFYING_KEY: Lazy = Lazy::new(global_pvk); +/// Enum to specify the environment to use for verifying keys. +#[derive(Debug)] +pub enum Environment { + /// Use the secure global verifying key derived from ceremony. + Production, + /// Use the insecure global verifying key. + Test, +} + +// TODO: Replace after ceremony. +static GLOBAL_VERIFYING_KEY: Lazy> = Lazy::new(global_pvk); +static INSECURE_GLOBAL_VERIFYING_KEY: Lazy> = Lazy::new(global_pvk); -/// Load a fixed verifying key from zklogin.vkey output from trusted setup -fn global_pvk() -> PreparedVerifyingKey { +/// Load a fixed verifying key from zklogin.vkey output. This is based on a local setup and should not use in production. +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![ "20491192805390485299153009773594534940189261866228447918068658471970481763042".to_string(), @@ -104,7 +112,9 @@ fn global_pvk() -> PreparedVerifyingKey { delta_g2: vk_delta_2, gamma_abc_g1: vk_gamma_abc_g1, }; - process_vk_special(&Bn254VerifyingKey(vk)) + + // Conver thte verifying key into the prepared form. + process_vk_special(&Bn254VerifyingKey(vk)).as_arkworks_pvk() } /// Entry point for the ZkLogin API. @@ -113,16 +123,22 @@ pub fn verify_zk_login( max_epoch: u64, eph_pubkey_bytes: &[u8], all_jwk: &ImHashMap<(String, String), OAuthProviderContent>, + usage: Environment, ) -> Result<(), FastCryptoError> { + // Load the expected JWK based on (kid, iss). let jwk = all_jwk .get(&(input.get_kid().to_string(), input.get_iss().to_string())) .ok_or_else(|| FastCryptoError::GeneralError("JWK not found".to_string()))?; + + // Decode modulus to bytes. let modulus = Base64UrlUnpadded::decode_vec(&jwk.n).map_err(|_| { FastCryptoError::GeneralError("Invalid Base64 encoded jwk modulus".to_string()) })?; + // Calculat all inputs hash and passed to the verification function. match verify_zk_login_proof_with_fixed_vk( - input, + usage, + input.get_proof().as_arkworks(), &input.calculate_all_inputs_hash(eph_pubkey_bytes, &modulus, max_epoch)?, ) { Ok(true) => Ok(()), @@ -132,20 +148,16 @@ pub fn verify_zk_login( } } -/// Verify a zk login proof using the fixed verifying key. +/// Verify a proof against its public inputs using the fixed verifying key. fn verify_zk_login_proof_with_fixed_vk( - input: &ZkLoginInputs, + usage: Environment, + proof: Proof, public_inputs: &[Bn254Fr], ) -> Result { - Groth16::::verify_with_processed_vk( - &GLOBAL_VERIFYING_KEY.as_arkworks_pvk(), - public_inputs, - &input.get_proof().as_arkworks(), - ) - .map_err(|e| FastCryptoError::GeneralError(e.to_string())) -} - -/// Return whether the claim string is supported for zk login. Currently only "sub" is supported. -pub fn is_claim_supported(claim_name: &str) -> bool { - vec![SupportedKeyClaim::Sub.to_string()].contains(&claim_name.to_owned()) + let pvk = match usage { + Environment::Production => &GLOBAL_VERIFYING_KEY, + Environment::Test => &INSECURE_GLOBAL_VERIFYING_KEY, + }; + Groth16::::verify_with_processed_vk(pvk, public_inputs, &proof) + .map_err(|e| FastCryptoError::GeneralError(e.to_string())) } From afc7481be86ca6b4107c7e642e5f1e7113b8b3f8 Mon Sep 17 00:00:00 2001 From: Joy Wang <108701016+joyqvq@users.noreply.github.com> Date: Fri, 11 Aug 2023 13:35:36 -0400 Subject: [PATCH 06/13] address comments --- Cargo.lock | 636 +++++++++++++++++- fastcrypto-zkp/Cargo.toml | 2 + .../src/bn254/unit_tests/zk_login_tests.rs | 78 +-- fastcrypto-zkp/src/bn254/zk_login.rs | 107 +-- fastcrypto-zkp/src/bn254/zk_login_api.rs | 4 +- 5 files changed, 698 insertions(+), 129 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8769e89213..752c0f304a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -232,7 +232,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -245,7 +245,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -338,7 +338,7 @@ checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -668,7 +668,7 @@ checksum = "e31225543cb46f81a7e224762764f4a6a0f097b1db0b175f69e8065efaa42de5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -677,6 +677,12 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + [[package]] name = "cast" version = "0.3.0" @@ -808,7 +814,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1216,7 +1222,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn", + "syn 1.0.109", ] [[package]] @@ -1233,7 +1239,7 @@ checksum = "0b75aed41bb2e6367cae39e6326ef817a851db13c13e4f3263714ca3cfb8de56" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1257,7 +1263,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 1.0.109", ] [[package]] @@ -1268,7 +1274,7 @@ checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" dependencies = [ "darling_core", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1301,7 +1307,7 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1314,7 +1320,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 1.0.109", ] [[package]] @@ -1442,6 +1448,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "encoding_rs" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "enum-iterator" version = "0.7.0" @@ -1459,7 +1474,7 @@ checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1480,7 +1495,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1618,7 +1633,7 @@ dependencies = [ "convert_case 0.6.0", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1668,9 +1683,11 @@ dependencies = [ "poseidon-ark", "proptest", "regex", + "reqwest", "schemars", "serde", "serde_json", + "tokio", ] [[package]] @@ -1714,6 +1731,75 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.12", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "generic-array" version = "0.14.6" @@ -1792,6 +1878,25 @@ dependencies = [ "subtle", ] +[[package]] +name = "h2" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "1.8.2" @@ -1885,6 +1990,78 @@ dependencies = [ "digest 0.10.6", ] +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" +dependencies = [ + "futures-util", + "http", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -1915,6 +2092,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "im" version = "15.1.0" @@ -1975,6 +2162,12 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "ipnet" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" + [[package]] name = "is-terminal" version = "0.4.4" @@ -2112,7 +2305,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0fbfc88337168279f2e9ae06e157cfed4efd3316e14dc96ed074d4f2e6c5952" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2169,6 +2362,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "miniz_oxide" version = "0.6.2" @@ -2178,6 +2377,18 @@ dependencies = [ "adler", ] +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + [[package]] name = "more-asserts" version = "0.2.2" @@ -2376,12 +2587,24 @@ dependencies = [ "base64ct", ] +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + [[package]] name = "pin-project-lite" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkcs1" version = "0.4.1" @@ -2516,7 +2739,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "version_check", ] @@ -2578,7 +2801,7 @@ checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2690,7 +2913,7 @@ checksum = "c75a383c2bcaa4139436e9826a2e606fac946406cf635f5b2363b86f95c3b756" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2763,6 +2986,45 @@ dependencies = [ "bytecheck", ] +[[package]] +name = "reqwest" +version = "0.11.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" +dependencies = [ + "base64 0.21.0", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-rustls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg", +] + [[package]] name = "rfc6979" version = "0.3.1" @@ -2784,6 +3046,21 @@ dependencies = [ "subtle", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "rkyv" version = "0.7.40" @@ -2806,7 +3083,7 @@ checksum = "ff26ed6c7c4dfc2aa9480b86a60e3c7233543a270a680e10758a507c5a4ce476" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2880,6 +3157,37 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "rustls" +version = "0.21.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +dependencies = [ + "base64 0.21.0", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261e9e0888cba427c3316e6322805653c9425240b6fd96cee7cb671ab70ab8d0" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.12" @@ -2934,7 +3242,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn", + "syn 1.0.109", ] [[package]] @@ -2949,6 +3257,16 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "seahash" version = "4.1.0" @@ -3065,7 +3383,7 @@ checksum = "d7e29c4601e36bcec74a223228dce795f4cd3616341a4af93520ca1a837c087d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3076,7 +3394,7 @@ checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3090,6 +3408,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_with" version = "2.3.1" @@ -3115,7 +3445,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3209,12 +3539,31 @@ dependencies = [ "typenum", ] +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + [[package]] name = "smallvec" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "spin" version = "0.5.2" @@ -3282,6 +3631,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79d9531f94112cfc3e4c8f5f02cb2b58f72c97b7efd85f70203cc6d8efda5927" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "synstructure" version = "0.12.6" @@ -3290,7 +3650,7 @@ checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "unicode-xid", ] @@ -3360,7 +3720,7 @@ checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3419,6 +3779,21 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.26.0" @@ -3426,7 +3801,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" dependencies = [ "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", "pin-project-lite", + "socket2", "tokio-macros", "windows-sys 0.45.0", ] @@ -3439,9 +3820,39 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.37" @@ -3463,7 +3874,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3506,6 +3917,12 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + [[package]] name = "twox-hash" version = "1.6.3" @@ -3529,12 +3946,27 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + [[package]] name = "unicode-ident" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-segmentation" version = "1.10.1" @@ -3563,6 +3995,23 @@ dependencies = [ "subtle", ] +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "valuable" version = "0.1.0" @@ -3594,6 +4043,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -3621,10 +4079,22 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.84" @@ -3643,7 +4113,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3750,7 +4220,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3931,6 +4401,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki", +] + [[package]] name = "which" version = "4.4.0" @@ -3992,12 +4481,12 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.42.2", "windows_aarch64_msvc 0.42.2", "windows_i686_gnu 0.42.2", "windows_i686_msvc 0.42.2", "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.42.2", "windows_x86_64_msvc 0.42.2", ] @@ -4007,7 +4496,16 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets", + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.1", ] [[package]] @@ -4016,21 +4514,42 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.42.2", "windows_aarch64_msvc 0.42.2", "windows_i686_gnu 0.42.2", "windows_i686_msvc 0.42.2", "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.42.2", "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-targets" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + [[package]] name = "windows_aarch64_msvc" version = "0.33.0" @@ -4043,6 +4562,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + [[package]] name = "windows_i686_gnu" version = "0.33.0" @@ -4055,6 +4580,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + [[package]] name = "windows_i686_msvc" version = "0.33.0" @@ -4067,6 +4598,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + [[package]] name = "windows_x86_64_gnu" version = "0.33.0" @@ -4079,12 +4616,24 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + [[package]] name = "windows_x86_64_msvc" version = "0.33.0" @@ -4097,6 +4646,21 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + [[package]] name = "wycheproof" version = "0.5.0" @@ -4126,6 +4690,6 @@ checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "synstructure", ] diff --git a/fastcrypto-zkp/Cargo.toml b/fastcrypto-zkp/Cargo.toml index 7bc2bf5ae2..c42a70695a 100644 --- a/fastcrypto-zkp/Cargo.toml +++ b/fastcrypto-zkp/Cargo.toml @@ -33,6 +33,7 @@ serde_json = "1.0.93" once_cell = "1.16" poseidon-ark = { git = "https://github.com/MystenLabs/poseidon-ark.git", rev = "39844046ded0c4aa7a247e8545e131b59a330a9c" } im = "15" +reqwest = { version = "0.11.18", default_features= false, features = ["blocking", "json", "rustls-tls"] } [dev-dependencies] ark-bls12-377 = "0.4.0" @@ -46,3 +47,4 @@ criterion = "0.4.0" hex = "0.4.3" proptest = "1.1.0" num-bigint = { version = "0.4", default-features = false, features = ["rand"] } +tokio = { version = "1.24.1", features = ["sync", "rt", "macros"] } 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 04cfbfc9c1..ba0cceddf9 100644 --- a/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs +++ b/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs @@ -1,14 +1,14 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use crate::bn254::zk_login::OAuthProvider; use crate::bn254::zk_login::{ big_int_str_to_bytes, decode_base64_url, map_bytes_to_field, parse_jwks, trim, - verify_extended_claim, Claim, JWTHeader, ParsedMaskedContent, + verify_extended_claim, Claim, JWTDetails, JWTHeader, }; +use crate::bn254::zk_login::{fetch_jwks, OIDCProvider}; use crate::bn254::zk_login_api::Environment; use crate::bn254::{ - zk_login::{OAuthProviderContent, ZkLoginInputs}, + zk_login::{ZkLoginInputs, JWK}, zk_login_api::verify_zk_login, }; use fastcrypto::error::FastCryptoError; @@ -74,7 +74,7 @@ const BAD_JWK_BYTES: &[u8] = r#"{ }"#.as_bytes(); #[test] -fn test_verify_groth16_in_bytes_google() { +fn test_verify_zk_login_google() { use crate::bn254::zk_login_api::Bn254Fr; use std::str::FromStr; @@ -90,7 +90,7 @@ fn test_verify_groth16_in_bytes_google() { ); assert_eq!( zklogin_inputs.get_iss(), - OAuthProvider::Google.get_config().0.to_string() + OIDCProvider::Google.get_config().0.to_string() ); assert_eq!( zklogin_inputs.get_aud(), @@ -102,13 +102,12 @@ fn test_verify_groth16_in_bytes_google() { ); assert_eq!( zklogin_inputs.get_address_params().iss, - OAuthProvider::Google.get_config().0.to_string() + OIDCProvider::Google.get_config().0.to_string() ); let mut map = ImHashMap::new(); - let content = OAuthProviderContent { + let content = JWK { kty: "RSA".to_string(), - kid: "911e39e27928ae9f1e9d1e21646de92d19351b44".to_string(), e: "AQAB".to_string(), n: "4kGxcWQdTW43aszLmftsGswmwDDKdfcse-lKeT_zjZTB2KGw9E6LVY6IThJVxzYF6mcyU-Z5_jDAW_yi7D_gXep2rxchZvoFayXynbhxyfjK6RtJ6_k30j-WpsXCSAiNAkupYHUyDIBNocvUcrDJsC3U65l8jl1I3nW98X6d-IlAfEb2In2f0fR6d-_lhIQZjXLupjymJduPjjA8oXCUZ9bfAYPhGYj3ZELUHkAyDpZNrnSi8hFVMSUSnorAt9F7cKMUJDM4-Uopzaqcl_f-HxeKvxN7NjiLSiIYaHdgtTpCEuNvsch6q6JTsllJNr3c__BxrG4UMlJ3_KsPxbcvXw".to_string(), alg: "RS256".to_string(), @@ -117,7 +116,7 @@ fn test_verify_groth16_in_bytes_google() { map.insert( ( "911e39e27928ae9f1e9d1e21646de92d19351b44".to_string(), - OAuthProvider::Google.get_config().0.to_string(), + OIDCProvider::Google.get_config().0.to_string(), ), content.clone(), ); @@ -136,7 +135,7 @@ fn test_verify_groth16_in_bytes_google() { } #[test] -fn test_verify_groth16_in_bytes_twitch() { +fn test_verify_zk_login_facebook() { let eph_pubkey = big_int_str_to_bytes( "84029355920633174015103288781128426107680789454168570548782290541079926444544", ); @@ -144,7 +143,7 @@ fn test_verify_groth16_in_bytes_twitch() { assert_eq!(zklogin_inputs.get_kid(), "1".to_string()); assert_eq!( zklogin_inputs.get_iss(), - OAuthProvider::Twitch.get_config().0.to_string() + OIDCProvider::Twitch.get_config().0.to_string() ); assert_eq!( zklogin_inputs.get_aud(), @@ -164,9 +163,8 @@ fn test_verify_groth16_in_bytes_twitch() { ); let mut map = ImHashMap::new(); - map.insert(("1".to_string(), OAuthProvider::Twitch.get_config().0.to_string()), OAuthProviderContent { + map.insert(("1".to_string(), OIDCProvider::Twitch.get_config().0.to_string()), JWK { kty: "RSA".to_string(), - kid: "1".to_string(), e: "AQAB".to_string(), n: "6lq9MQ-q6hcxr7kOUp-tHlHtdcDsVLwVIw13iXUCvuDOeCi0VSuxCCUY6UmMjy53dX00ih2E4Y4UvlrmmurK0eG26b-HMNNAvCGsVXHU3RcRhVoHDaOwHwU72j7bpHn9XbP3Q3jebX6KIfNbei2MiR0Wyb8RZHE-aZhRYO8_-k9G2GycTpvc-2GBsP8VHLUKKfAs2B6sW3q3ymU6M0L-cFXkZ9fHkn9ejs-sqZPhMJxtBPBxoUIUQFTgv4VXTSv914f_YkNw-EjuwbgwXMvpyr06EyfImxHoxsZkFYB-qBYHtaMxTnFsZBr6fn8Ha2JqT1hoP7Z5r5wxDu3GQhKkHw".to_string(), alg: "RS256".to_string(), @@ -190,13 +188,13 @@ fn test_parsed_masked_content() { // iss not found assert_eq!( - ParsedMaskedContent::new(VALID_HEADER, &[]).unwrap_err(), + JWTDetails::new(VALID_HEADER, &[]).unwrap_err(), FastCryptoError::GeneralError("Invalid claim".to_string()) ); // missing claim assert_eq!( - ParsedMaskedContent::new( + JWTDetails::new( VALID_HEADER, &[Claim { name: "iss".to_string(), @@ -210,7 +208,7 @@ fn test_parsed_masked_content() { // unknown claim name assert_eq!( - ParsedMaskedContent::new( + JWTDetails::new( VALID_HEADER, &[Claim { name: "unknown".to_string(), @@ -224,7 +222,7 @@ fn test_parsed_masked_content() { // bad index_mod_4 assert_eq!( - ParsedMaskedContent::new( + JWTDetails::new( VALID_HEADER, &[ Claim { @@ -241,12 +239,12 @@ fn test_parsed_masked_content() { ] ) .unwrap_err(), - FastCryptoError::GeneralError("Invalid masked content".to_string()) + FastCryptoError::GeneralError("Invalid UTF8 string".to_string()) ); // first claim is not iss assert_eq!( - ParsedMaskedContent::new( + JWTDetails::new( VALID_HEADER, &[Claim { name: "aud".to_string(), @@ -260,7 +258,7 @@ fn test_parsed_masked_content() { // second claim is not aud assert_eq!( - ParsedMaskedContent::new( + JWTDetails::new( VALID_HEADER, &[ Claim { @@ -293,7 +291,7 @@ fn test_decode_base64() { assert!(decode_base64_url("yJhdWQiOiJkMzFpY3FsNmw4eHpwYTdlZjMxenR4eXNzNDZvY2siLC", &1).is_ok()); assert_eq!( decode_base64_url("yJhdWQiOiJkMzFpY3FsNmw4eHpwYTdlZjMxenR4eXNzNDZvY2siLC", &2).unwrap_err(), - FastCryptoError::GeneralError("Invalid masked content".to_string()) + FastCryptoError::GeneralError("Invalid UTF8 string".to_string()) ); assert_eq!( decode_base64_url("yJhdWQiOiJkMzFpY3FsNmw4eHpwYTdlZjMxenR4eXNzNDZvY2siLC", &3).unwrap_err(), @@ -327,7 +325,7 @@ fn test_verify_extended_claim() { } #[test] -fn test_map_to_field() { +fn test_hash_to_field() { // Test generated against typescript implementation. assert!(map_bytes_to_field("sub", 2).is_err()); assert_eq!( @@ -347,48 +345,42 @@ fn test_jwk_parse() { "wYvSKSQYKnGNV72_uVc9jbyUeTMsMbUgZPP0uVQX900To7A8a0XA3O17wuImgOG_BwGkpZrIRXF_RRYSK8IOH8N_ViTWh1vyEYSYwr_jfCpDoedJT0O6TZpBhBSmimtmO8ZBCkhZJ4w0AFNIMDPhMokbxwkEapjMA5zio_06dKfb3OBNmrwedZY86W1204-Pfma9Ih15Dm4o8SNFo5Sl0NNO4Ithvj2bbg1Bz1ydE4lMrXdSQL5C2uM9JYRJLnIjaYopBENwgf2Egc9CdVY8tr8jED-WQB6bcUBhDV6lJLZbpBlTHLkF1RlEMnIV2bDo02CryjThnz8l_-6G_7pJww" ); - parse_jwks(GOOGLE_JWK_BYTES, OAuthProvider::Google) + parse_jwks(GOOGLE_JWK_BYTES, OIDCProvider::Google) .unwrap() .iter() .for_each(|content| { - assert_eq!(content.0 .0, content.1.kid()); - assert_eq!(content.0 .1, OAuthProvider::Google.get_config().0); + assert_eq!(content.0 .1, OIDCProvider::Google.get_config().0); }); - parse_jwks(TWITCH_JWK_BYTES, OAuthProvider::Twitch) + parse_jwks(TWITCH_JWK_BYTES, OIDCProvider::Twitch) .unwrap() .iter() .for_each(|content| { - assert_eq!(content.0 .0, content.1.kid()); - assert_eq!(content.0 .1, OAuthProvider::Twitch.get_config().0); + assert_eq!(content.0 .1, OIDCProvider::Twitch.get_config().0); }); - parse_jwks(FACEBOOK_JWK_BYTES, OAuthProvider::Facebook) + parse_jwks(FACEBOOK_JWK_BYTES, OIDCProvider::Facebook) .unwrap() .iter() .for_each(|content| { - assert_eq!(content.0 .0, content.1.kid()); - assert_eq!(content.0 .1, OAuthProvider::Facebook.get_config().0); + assert_eq!(content.0 .1, OIDCProvider::Facebook.get_config().0); }); - assert!(parse_jwks(BAD_JWK_BYTES, OAuthProvider::Twitch).is_err()); - - assert!(OAuthProviderContent { - kty: "RSA".to_string(), - e: "something".to_string(), - n: "".to_string(), - alg: "RS256".to_string(), - kid: "".to_string(), - } - .validate() - .is_err()); + assert!(parse_jwks(BAD_JWK_BYTES, OIDCProvider::Twitch).is_err()); assert!(parse_jwks( r#"{ "something":[] }"# .as_bytes(), - OAuthProvider::Twitch + OIDCProvider::Twitch ) .is_err()); } + +#[tokio::test] +async fn test_get_jwks() { + let res = fetch_jwks().await; + assert!(res.is_ok()); + assert!(!res.unwrap().is_empty()); +} diff --git a/fastcrypto-zkp/src/bn254/zk_login.rs b/fastcrypto-zkp/src/bn254/zk_login.rs index 31085b853d..4c5aadfa47 100644 --- a/fastcrypto-zkp/src/bn254/zk_login.rs +++ b/fastcrypto-zkp/src/bn254/zk_login.rs @@ -19,7 +19,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::str::FromStr; -type ParsedJWKs = Vec<((String, String), OAuthProviderContent)>; +type ParsedJWKs = Vec<((String, String), JWK)>; #[cfg(test)] #[path = "unit_tests/zk_login_tests.rs"] mod zk_login_tests; @@ -32,10 +32,9 @@ const NUM_EXTRACTABLE_STRINGS: u8 = 5; const MAX_EXTRACTABLE_STR_LEN: u16 = 150; const MAX_EXTRACTABLE_STR_LEN_B64: u16 = 4 * (1 + MAX_EXTRACTABLE_STR_LEN / 3); -/// Supported OAuth providers. Must contain "openid" in "scopes_supported" -/// and "public" for "subject_types_supported" instead of "pairwise". +/// Supported OAuth providers. #[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub enum OAuthProvider { +pub enum OIDCProvider { /// See https://accounts.google.com/.well-known/openid-configuration Google, /// See https://id.twitch.tv/oauth2/.well-known/openid-configuration @@ -48,8 +47,8 @@ pub enum OAuthProvider { /// be retrieved from the JWK endpoint (e.g. ) /// and published on the bulletin along with a trusted party's signature. // #[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Hash, Serialize, Deserialize)] -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct OAuthProviderContent { +#[derive(Hash, Debug, Clone, Serialize, Deserialize)] +pub struct JWK { /// Key type parameter, https://datatracker.ietf.org/doc/html/rfc7517#section-4.1 pub kty: String, /// RSA public exponent, https://datatracker.ietf.org/doc/html/rfc7517#section-9.3 @@ -58,14 +57,12 @@ pub struct OAuthProviderContent { pub n: String, /// Algorithm parameter, https://datatracker.ietf.org/doc/html/rfc7517#section-4.4 pub alg: String, - /// Key ID that identifies a JWK in a JWK set, https://datatracker.ietf.org/doc/html/rfc7517#section-4.5 - kid: String, } /// Reader struct to parse all fields. // #[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Hash, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)] -pub struct OAuthProviderContentReader { +pub struct JWKReader { e: String, n: String, #[serde(rename = "use")] @@ -75,33 +72,24 @@ pub struct OAuthProviderContentReader { alg: String, } -impl OAuthProviderContent { - /// Get the kid string. - pub fn kid(&self) -> &str { - &self.kid - } - - /// Parse OAuthProviderContent from the reader struct. - pub fn from_reader(reader: OAuthProviderContentReader) -> FastCryptoResult { - if reader.alg != "RS256" || reader.my_use != "sig" || reader.kty != "RSA" { +impl JWK { + /// Parse JWK from the reader struct. + pub fn from_reader(reader: JWKReader) -> FastCryptoResult { + let trimmed_e = trim(reader.e); + if reader.alg != "RS256" + || reader.my_use != "sig" + || reader.kty != "RSA" + || trimmed_e != "AQAB" + { return Err(FastCryptoError::InvalidInput); } Ok(Self { kty: reader.kty, - kid: reader.kid, - e: trim(reader.e), + e: trimmed_e, n: trim(reader.n), alg: reader.alg, }) } - - /// Parse OAuthProviderContent from the reader struct. - pub fn validate(&self) -> FastCryptoResult<()> { - if self.alg != "RS256" || self.kty != "RSA" || self.e != "AQAB" { - return Err(FastCryptoError::InvalidInput); - } - Ok(()) - } } /// Trim trailing '=' so that it is considered a valid base64 url encoding string by base64ct library. @@ -109,11 +97,35 @@ fn trim(str: String) -> String { str.trim_end_matches('=').to_owned() } +/// Fetch JWKs from all supported OAuth providers and return the list as ((iss, kid), JWK) +pub async fn fetch_jwks() -> Result { + let client = reqwest::Client::new(); + let mut res = Vec::new(); + // We currently support three providers: Google, Facebook, and Twitch. + for provider in [ + OIDCProvider::Google, + OIDCProvider::Facebook, + OIDCProvider::Twitch, + ] { + let response = client + .get(provider.get_config().1) + .send() + .await + .map_err(|_| FastCryptoError::GeneralError("Failed to get JWK".to_string()))?; + let bytes = response + .bytes() + .await + .map_err(|_| FastCryptoError::GeneralError("Failed to get bytes".to_string()))?; + res.append(&mut parse_jwks(&bytes, provider)?) + } + Ok(res) +} + /// Parse the JWK bytes received from the oauth provider keys endpoint into a map from kid to -/// OAuthProviderContent. +/// JWK. pub fn parse_jwks( json_bytes: &[u8], - provider: OAuthProvider, + provider: OIDCProvider, ) -> Result { let json_str = String::from_utf8_lossy(json_bytes); let parsed_list: Result = serde_json::from_str(&json_str); @@ -121,12 +133,12 @@ pub fn parse_jwks( if let Some(keys) = parsed_list["keys"].as_array() { let mut ret = Vec::new(); for k in keys { - let parsed: OAuthProviderContentReader = serde_json::from_value(k.clone()) + let parsed: JWKReader = serde_json::from_value(k.clone()) .map_err(|_| FastCryptoError::GeneralError("Parse error".to_string()))?; ret.push(( (parsed.kid.clone(), provider.get_config().0.to_owned()), - OAuthProviderContent::from_reader(parsed)?, + JWK::from_reader(parsed)?, )); } return Ok(ret); @@ -137,19 +149,19 @@ pub fn parse_jwks( )) } -impl OAuthProvider { +impl OIDCProvider { /// Returns a tuple of iss string and the JWK url string for the given provider. pub fn get_config(&self) -> (&str, &str) { match self { - OAuthProvider::Google => ( + OIDCProvider::Google => ( "https://accounts.google.com", "https://www.googleapis.com/oauth2/v2/certs", ), - OAuthProvider::Twitch => ( + OIDCProvider::Twitch => ( "https://id.twitch.tv/oauth2", "https://id.twitch.tv/oauth2/keys", ), - OAuthProvider::Facebook => ( + OIDCProvider::Facebook => ( "https://www.facebook.com", "https://www.facebook.com/.well-known/oauth/openid/jwks/", ), @@ -192,14 +204,14 @@ impl JWTHeader { /// A structed of all parsed and validated values from the masked content bytes. #[derive(Default, Debug, Clone, PartialEq, Eq)] -pub struct ParsedMaskedContent { +pub struct JWTDetails { kid: String, header: String, iss: String, aud: String, } -impl ParsedMaskedContent { +impl JWTDetails { /// Read a list of Claims and header string and parse them into fields /// header, iss, iss_index, aud, aud_index. pub fn new(header_base64: &str, claims: &[Claim]) -> Result { @@ -224,7 +236,7 @@ impl ParsedMaskedContent { } let ext_aud = decode_base64_url(&claim_2.value_base64, &claim_2.index_mod_4)?; - Ok(ParsedMaskedContent { + Ok(JWTDetails { kid: header.kid, header: header_base64.to_string(), iss: verify_extended_claim(&ext_iss, ISS)?, @@ -241,7 +253,7 @@ pub struct ZkLoginInputs { claims: Vec, header_base64: String, #[serde(skip)] - parsed_masked_content: ParsedMaskedContent, + parsed_masked_content: JWTDetails, #[serde(skip)] all_inputs_hash: Vec, } @@ -254,9 +266,9 @@ impl ZkLoginInputs { Ok(inputs) } - /// Initialize ParsedMaskedContent + /// Initialize JWTDetails pub fn init(&mut self) -> Result { - self.parsed_masked_content = ParsedMaskedContent::new(&self.header_base64, &self.claims)?; + self.parsed_masked_content = JWTDetails::new(&self.header_base64, &self.claims)?; Ok(self.to_owned()) } @@ -348,7 +360,7 @@ impl ZkLoginInputs { .collect::>(), )?; let header_f = map_bytes_to_field(&self.parsed_masked_content.header, MAX_HEADER_LEN)?; - let modulus_f = map_to_field(&[BigUint::from_bytes_be(modulus)], 2048)?; + let modulus_f = hash_to_field(&[BigUint::from_bytes_be(modulus)], 2048)?; Ok(vec![poseidon.hash(vec![ eph_public_key_0, eph_public_key_1, @@ -418,8 +430,7 @@ fn decode_base64_url(s: &str, i: &u8) -> Result { )); } let mut bits = base64_to_bitarray(s); - let first_char_offset = i % 4; - match first_char_offset { + match i { 0 => {} 1 => { bits.drain(..2); @@ -457,7 +468,7 @@ fn decode_base64_url(s: &str, i: &u8) -> Result { } Ok(std::str::from_utf8(&bitarray_to_bytearray(&bits)) - .map_err(|_| FastCryptoError::GeneralError("Invalid masked content".to_string()))? + .map_err(|_| FastCryptoError::GeneralError("Invalid UTF8 string".to_string()))? .to_owned()) } @@ -541,7 +552,7 @@ fn map_bytes_to_field(str: &str, max_size: u16) -> Result, out_count: u16) -> Result, FastCryptoError> { @@ -560,7 +571,7 @@ fn pad_with_zeroes(in_arr: Vec, out_count: u16) -> Result, /// inWidth to packWidth. Then we compute the poseidon hash of the "packed" input. /// input is the input vector containing equal-width big ints. inWidth is the width of /// each input element. -fn map_to_field(input: &[BigUint], in_width: u16) -> Result { +fn hash_to_field(input: &[BigUint], in_width: u16) -> Result { let num_elements = (input.len() * in_width as usize) / PACK_WIDTH as usize + 1; let packed = pack2(input, in_width, PACK_WIDTH, num_elements)?; to_poseidon_hash(packed) diff --git a/fastcrypto-zkp/src/bn254/zk_login_api.rs b/fastcrypto-zkp/src/bn254/zk_login_api.rs index fc95f2231a..a1d96f1d80 100644 --- a/fastcrypto-zkp/src/bn254/zk_login_api.rs +++ b/fastcrypto-zkp/src/bn254/zk_login_api.rs @@ -6,7 +6,7 @@ use fastcrypto::rsa::{Base64UrlUnpadded, Encoding}; use im::hashmap::HashMap as ImHashMap; use super::verifier::process_vk_special; -use super::zk_login::{OAuthProviderContent, ZkLoginInputs}; +use super::zk_login::{ZkLoginInputs, JWK}; use crate::bn254::VerifyingKey as Bn254VerifyingKey; use crate::circom::{g1_affine_from_str_projective, g2_affine_from_str_projective}; pub use ark_bn254::{Bn254, Fr as Bn254Fr}; @@ -122,7 +122,7 @@ pub fn verify_zk_login( input: &ZkLoginInputs, max_epoch: u64, eph_pubkey_bytes: &[u8], - all_jwk: &ImHashMap<(String, String), OAuthProviderContent>, + all_jwk: &ImHashMap<(String, String), JWK>, usage: Environment, ) -> Result<(), FastCryptoError> { // Load the expected JWK based on (kid, iss). From 02256c33b9f9b0f620fb4c98bc142dd296b1c144 Mon Sep 17 00:00:00 2001 From: Joy Wang <108701016+joyqvq@users.noreply.github.com> Date: Fri, 11 Aug 2023 17:19:27 -0400 Subject: [PATCH 07/13] add utils to fastcrypto --- Cargo.lock | 1 + fastcrypto-zkp/Cargo.toml | 1 + fastcrypto-zkp/src/bn254/mod.rs | 2 + .../src/bn254/unit_tests/zk_login_tests.rs | 14 +++++ fastcrypto-zkp/src/bn254/utils.rs | 63 +++++++++++++++++++ 5 files changed, 81 insertions(+) create mode 100644 fastcrypto-zkp/src/bn254/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 752c0f304a..b3557802ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1670,6 +1670,7 @@ dependencies = [ "ark-relations", "ark-serialize", "ark-std", + "bcs", "blake2", "blst", "byte-slice-cast", diff --git a/fastcrypto-zkp/Cargo.toml b/fastcrypto-zkp/Cargo.toml index c42a70695a..ba66c219a5 100644 --- a/fastcrypto-zkp/Cargo.toml +++ b/fastcrypto-zkp/Cargo.toml @@ -34,6 +34,7 @@ once_cell = "1.16" poseidon-ark = { git = "https://github.com/MystenLabs/poseidon-ark.git", rev = "39844046ded0c4aa7a247e8545e131b59a330a9c" } im = "15" reqwest = { version = "0.11.18", default_features= false, features = ["blocking", "json", "rustls-tls"] } +bcs = "0.1.4" [dev-dependencies] ark-bls12-377 = "0.4.0" diff --git a/fastcrypto-zkp/src/bn254/mod.rs b/fastcrypto-zkp/src/bn254/mod.rs index 135821d31f..66f23647be 100644 --- a/fastcrypto-zkp/src/bn254/mod.rs +++ b/fastcrypto-zkp/src/bn254/mod.rs @@ -21,6 +21,8 @@ pub mod zk_login; /// Zk login entrypoints pub mod zk_login_api; +/// Zk login utils +pub mod utils; /// A field element in the BN254 construction. Thin wrapper around `api::Bn254Fr`. #[derive(Debug, From)] pub struct FieldElement(pub(crate) api::Bn254Fr); 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 ba0cceddf9..b3c7c6ec32 100644 --- a/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs +++ b/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs @@ -1,6 +1,7 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use crate::bn254::utils::get_nonce; use crate::bn254::zk_login::{ big_int_str_to_bytes, decode_base64_url, map_bytes_to_field, parse_jwks, trim, verify_extended_claim, Claim, JWTDetails, JWTHeader, @@ -11,6 +12,10 @@ use crate::bn254::{ zk_login::{ZkLoginInputs, JWK}, zk_login_api::verify_zk_login, }; +use fastcrypto::traits::KeyPair; +use ark_std::rand::SeedableRng; +use ark_std::rand::rngs::StdRng; +use fastcrypto::ed25519::Ed25519KeyPair; use fastcrypto::error::FastCryptoError; use fastcrypto::rsa::{Base64UrlUnpadded, Encoding}; use im::hashmap::HashMap as ImHashMap; @@ -384,3 +389,12 @@ async fn test_get_jwks() { assert!(res.is_ok()); assert!(!res.unwrap().is_empty()); } + +#[test] +fn test_get_nonce() { + let kp = Ed25519KeyPair::generate(&mut StdRng::from_seed([0; 32])); + let mut eph_pk_bytes = vec![0x00]; + eph_pk_bytes.extend(kp.public().as_ref()); + let nonce = get_nonce(&eph_pk_bytes, 10, "100681567828351849884072155819400689117"); + assert_eq!(nonce, "hTPpgF7XAKbW37rEUS6pEVZqmoI"); +} diff --git a/fastcrypto-zkp/src/bn254/utils.rs b/fastcrypto-zkp/src/bn254/utils.rs new file mode 100644 index 0000000000..94268aafab --- /dev/null +++ b/fastcrypto-zkp/src/bn254/utils.rs @@ -0,0 +1,63 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +use fastcrypto::hash::{Blake2b256, HashFunction}; + use fastcrypto::rsa::Base64UrlUnpadded; + use fastcrypto::rsa::Encoding; + use crate::bn254::poseidon::PoseidonWrapper; + use crate::bn254::zk_login::big_int_str_to_bytes; + use crate::bn254::zk_login::AddressParams; + use crate::bn254::zk_login::OIDCProvider; + use crate::bn254::zk_login_api::Bn254Fr; + use std::str::FromStr; + use num_bigint::{BigInt, Sign}; + + const ZK_LOGIN_AUTHENTICATOR_FLAG: u8 = 0x05; + + /// Calculate the Sui address based on address seed and address params. + pub fn get_enoki_address(address_seed: String, param: AddressParams) -> [u8; 32] { + let mut hasher = Blake2b256::default(); + hasher.update([ZK_LOGIN_AUTHENTICATOR_FLAG]); + // unwrap is safe here + hasher.update(bcs::to_bytes(&AddressParams::new(param.iss, param.aud)).unwrap()); + hasher.update(big_int_str_to_bytes(&address_seed)); + hasher.finalize().digest + } + + /// Return the OIDC URL for the given parameters. Crucially the nonce is computed. + pub fn get_oidc_url( + provider: OIDCProvider, + eph_pk_bytes: &[u8], + max_epoch: u64, + client_id: &str, + redirect_url: &str, + jwt_randomness: &str, + ) -> String { + let nonce = get_nonce(eph_pk_bytes, max_epoch, jwt_randomness); + match provider { + OIDCProvider::Google => format!("https://accounts.google.com/o/oauth2/v2/auth?client_id={}&response_type=id_token&redirect_uri={}&scope=openid&nonce={}", client_id, redirect_url, nonce), + OIDCProvider::Twitch => format!("https://id.twitch.tv/oauth2/authorize?client_id={}&force_verify=true&lang=en&login_type=login&redirect_uri={}&response_type=id_token&scope=openid&nonce={}", client_id, redirect_url, nonce), + OIDCProvider::Facebook => format!("https://www.facebook.com/v17.0/dialog/oauth?client_id={}&redirect_uri={}&scope=openid&nonce={}&response_type=id_token", client_id, redirect_url, nonce), } + } + + /// Calculate teh nonce for the given parameters. Nonce is defined as the Base64Url encoded of the poseidon hash of 4 inputs: + /// first half of eph_pk_bytes in BigInt, second half of eph_pk_bytes in BigInt, max_epoch and jwt_randomness. + pub fn get_nonce(eph_pk_bytes: &[u8], max_epoch: u64, jwt_randomness: &str) -> String { + let (first_half, second_half) = eph_pk_bytes.split_at(17); + let first_bigint = BigInt::from_bytes_be(Sign::Plus, first_half); + let second_bigint = BigInt::from_bytes_be(Sign::Plus, second_half); + let mut poseidon = PoseidonWrapper::new(); + let first = Bn254Fr::from_str(&first_bigint.to_string()).unwrap(); + let second = Bn254Fr::from_str(&second_bigint.to_string()).unwrap(); + let max_epoch = Bn254Fr::from_str(&max_epoch.to_string()).unwrap(); + let jwt_randomness = Bn254Fr::from_str(jwt_randomness).unwrap(); + + let hash = poseidon + .hash(vec![first, second, max_epoch, jwt_randomness]) + .unwrap(); + let data = big_int_str_to_bytes(&hash.to_string()); + let truncated = &data[data.len() - 20..]; + let mut buf = vec![0; Base64UrlUnpadded::encoded_len(truncated)]; + Base64UrlUnpadded::encode(truncated, &mut buf) + .unwrap() + .to_string() + } \ No newline at end of file From 50085f0b1e79197502c0eec38bc1e67c669068cc Mon Sep 17 00:00:00 2001 From: Joy Wang <108701016+joyqvq@users.noreply.github.com> Date: Mon, 14 Aug 2023 13:12:35 -0400 Subject: [PATCH 08/13] fix split --- .../src/bn254/unit_tests/zk_login_tests.rs | 10 +- fastcrypto-zkp/src/bn254/utils.rs | 108 +++++++++--------- fastcrypto-zkp/src/bn254/zk_login.rs | 2 +- 3 files changed, 63 insertions(+), 57 deletions(-) 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 b3c7c6ec32..15cb8805ca 100644 --- a/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs +++ b/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs @@ -12,13 +12,14 @@ use crate::bn254::{ zk_login::{ZkLoginInputs, JWK}, zk_login_api::verify_zk_login, }; -use fastcrypto::traits::KeyPair; -use ark_std::rand::SeedableRng; use ark_std::rand::rngs::StdRng; +use ark_std::rand::SeedableRng; use fastcrypto::ed25519::Ed25519KeyPair; use fastcrypto::error::FastCryptoError; use fastcrypto::rsa::{Base64UrlUnpadded, Encoding}; +use fastcrypto::traits::KeyPair; use im::hashmap::HashMap as ImHashMap; +use num_bigint::BigInt; const GOOGLE_JWK_BYTES: &[u8] = r#"{ "keys": [ @@ -395,6 +396,11 @@ fn test_get_nonce() { let kp = Ed25519KeyPair::generate(&mut StdRng::from_seed([0; 32])); let mut eph_pk_bytes = vec![0x00]; eph_pk_bytes.extend(kp.public().as_ref()); + let pk_bigint = BigInt::from_bytes_be(num_bigint::Sign::Plus, &eph_pk_bytes); + assert_eq!( + pk_bigint.to_string(), + "84029355920633174015103288781128426107680789454168570548782290541079926444544" + ); let nonce = get_nonce(&eph_pk_bytes, 10, "100681567828351849884072155819400689117"); assert_eq!(nonce, "hTPpgF7XAKbW37rEUS6pEVZqmoI"); } diff --git a/fastcrypto-zkp/src/bn254/utils.rs b/fastcrypto-zkp/src/bn254/utils.rs index 94268aafab..7038e22bfb 100644 --- a/fastcrypto-zkp/src/bn254/utils.rs +++ b/fastcrypto-zkp/src/bn254/utils.rs @@ -1,63 +1,63 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use crate::bn254::poseidon::PoseidonWrapper; +use crate::bn254::zk_login::big_int_str_to_bytes; +use crate::bn254::zk_login::AddressParams; +use crate::bn254::zk_login::OIDCProvider; +use crate::bn254::zk_login_api::Bn254Fr; use fastcrypto::hash::{Blake2b256, HashFunction}; - use fastcrypto::rsa::Base64UrlUnpadded; - use fastcrypto::rsa::Encoding; - use crate::bn254::poseidon::PoseidonWrapper; - use crate::bn254::zk_login::big_int_str_to_bytes; - use crate::bn254::zk_login::AddressParams; - use crate::bn254::zk_login::OIDCProvider; - use crate::bn254::zk_login_api::Bn254Fr; - use std::str::FromStr; - use num_bigint::{BigInt, Sign}; - - const ZK_LOGIN_AUTHENTICATOR_FLAG: u8 = 0x05; +use fastcrypto::rsa::Base64UrlUnpadded; +use fastcrypto::rsa::Encoding; +use num_bigint::{BigInt, Sign}; +use std::str::FromStr; - /// Calculate the Sui address based on address seed and address params. - pub fn get_enoki_address(address_seed: String, param: AddressParams) -> [u8; 32] { - let mut hasher = Blake2b256::default(); - hasher.update([ZK_LOGIN_AUTHENTICATOR_FLAG]); - // unwrap is safe here - hasher.update(bcs::to_bytes(&AddressParams::new(param.iss, param.aud)).unwrap()); - hasher.update(big_int_str_to_bytes(&address_seed)); - hasher.finalize().digest - } +const ZK_LOGIN_AUTHENTICATOR_FLAG: u8 = 0x05; - /// Return the OIDC URL for the given parameters. Crucially the nonce is computed. - pub fn get_oidc_url( - provider: OIDCProvider, - eph_pk_bytes: &[u8], - max_epoch: u64, - client_id: &str, - redirect_url: &str, - jwt_randomness: &str, - ) -> String { - let nonce = get_nonce(eph_pk_bytes, max_epoch, jwt_randomness); - match provider { +/// Calculate the Sui address based on address seed and address params. +pub fn get_enoki_address(address_seed: String, param: AddressParams) -> [u8; 32] { + let mut hasher = Blake2b256::default(); + hasher.update([ZK_LOGIN_AUTHENTICATOR_FLAG]); + // unwrap is safe here + hasher.update(bcs::to_bytes(&AddressParams::new(param.iss, param.aud)).unwrap()); + hasher.update(big_int_str_to_bytes(&address_seed)); + hasher.finalize().digest +} + +/// Return the OIDC URL for the given parameters. Crucially the nonce is computed. +pub fn get_oidc_url( + provider: OIDCProvider, + eph_pk_bytes: &[u8], + max_epoch: u64, + client_id: &str, + redirect_url: &str, + jwt_randomness: &str, +) -> String { + let nonce = get_nonce(eph_pk_bytes, max_epoch, jwt_randomness); + match provider { OIDCProvider::Google => format!("https://accounts.google.com/o/oauth2/v2/auth?client_id={}&response_type=id_token&redirect_uri={}&scope=openid&nonce={}", client_id, redirect_url, nonce), OIDCProvider::Twitch => format!("https://id.twitch.tv/oauth2/authorize?client_id={}&force_verify=true&lang=en&login_type=login&redirect_uri={}&response_type=id_token&scope=openid&nonce={}", client_id, redirect_url, nonce), - OIDCProvider::Facebook => format!("https://www.facebook.com/v17.0/dialog/oauth?client_id={}&redirect_uri={}&scope=openid&nonce={}&response_type=id_token", client_id, redirect_url, nonce), } - } + OIDCProvider::Facebook => format!("https://www.facebook.com/v17.0/dialog/oauth?client_id={}&redirect_uri={}&scope=openid&nonce={}&response_type=id_token", client_id, redirect_url, nonce) } +} - /// Calculate teh nonce for the given parameters. Nonce is defined as the Base64Url encoded of the poseidon hash of 4 inputs: - /// first half of eph_pk_bytes in BigInt, second half of eph_pk_bytes in BigInt, max_epoch and jwt_randomness. - pub fn get_nonce(eph_pk_bytes: &[u8], max_epoch: u64, jwt_randomness: &str) -> String { - let (first_half, second_half) = eph_pk_bytes.split_at(17); - let first_bigint = BigInt::from_bytes_be(Sign::Plus, first_half); - let second_bigint = BigInt::from_bytes_be(Sign::Plus, second_half); - let mut poseidon = PoseidonWrapper::new(); - let first = Bn254Fr::from_str(&first_bigint.to_string()).unwrap(); - let second = Bn254Fr::from_str(&second_bigint.to_string()).unwrap(); - let max_epoch = Bn254Fr::from_str(&max_epoch.to_string()).unwrap(); - let jwt_randomness = Bn254Fr::from_str(jwt_randomness).unwrap(); +/// Calculate the nonce for the given parameters. Nonce is defined as the Base64Url encoded of the poseidon hash of 4 inputs: +/// first half of eph_pk_bytes in BigInt, second half of eph_pk_bytes in BigInt, max_epoch and jwt_randomness. +pub fn get_nonce(eph_pk_bytes: &[u8], max_epoch: u64, jwt_randomness: &str) -> String { + let (first_half, second_half) = eph_pk_bytes.split_at(17); + let first_bigint = BigInt::from_bytes_be(Sign::Plus, first_half); + let second_bigint = BigInt::from_bytes_be(Sign::Plus, second_half); + let mut poseidon = PoseidonWrapper::new(); + let first = Bn254Fr::from_str(&first_bigint.to_string()).unwrap(); + let second = Bn254Fr::from_str(&second_bigint.to_string()).unwrap(); + let max_epoch = Bn254Fr::from_str(&max_epoch.to_string()).unwrap(); + let jwt_randomness = Bn254Fr::from_str(jwt_randomness).unwrap(); - let hash = poseidon - .hash(vec![first, second, max_epoch, jwt_randomness]) - .unwrap(); - let data = big_int_str_to_bytes(&hash.to_string()); - let truncated = &data[data.len() - 20..]; - let mut buf = vec![0; Base64UrlUnpadded::encoded_len(truncated)]; - Base64UrlUnpadded::encode(truncated, &mut buf) - .unwrap() - .to_string() - } \ No newline at end of file + let hash = poseidon + .hash(vec![first, second, max_epoch, jwt_randomness]) + .unwrap(); + let data = big_int_str_to_bytes(&hash.to_string()); + let truncated = &data[data.len() - 20..]; + let mut buf = vec![0; Base64UrlUnpadded::encoded_len(truncated)]; + Base64UrlUnpadded::encode(truncated, &mut buf) + .unwrap() + .to_string() +} diff --git a/fastcrypto-zkp/src/bn254/zk_login.rs b/fastcrypto-zkp/src/bn254/zk_login.rs index 4c5aadfa47..1efffa792a 100644 --- a/fastcrypto-zkp/src/bn254/zk_login.rs +++ b/fastcrypto-zkp/src/bn254/zk_login.rs @@ -321,7 +321,7 @@ impl ZkLoginInputs { let mut poseidon = PoseidonWrapper::new(); let addr_seed = to_field(&self.address_seed)?; - let (first_half, second_half) = eph_pubkey_bytes.split_at(16); + let (first_half, second_half) = eph_pubkey_bytes.split_at(17); let first_bigint = BigInt::from_bytes_be(Sign::Plus, first_half); let second_bigint = BigInt::from_bytes_be(Sign::Plus, second_half); From 6c8cf8e2dd7d38025d778f7d9ca62daff5e8d33a Mon Sep 17 00:00:00 2001 From: Joy Wang <108701016+joyqvq@users.noreply.github.com> Date: Mon, 14 Aug 2023 14:27:13 -0400 Subject: [PATCH 09/13] move poseidon and fix bigin bytes parse --- fastcrypto-zkp/src/bn254/poseidon.rs | 27 ++++++++++++++++-- .../src/bn254/unit_tests/zk_login_tests.rs | 11 ++++++-- fastcrypto-zkp/src/bn254/utils.rs | 3 +- fastcrypto-zkp/src/bn254/zk_login.rs | 28 ++----------------- 4 files changed, 37 insertions(+), 32 deletions(-) diff --git a/fastcrypto-zkp/src/bn254/poseidon.rs b/fastcrypto-zkp/src/bn254/poseidon.rs index 0216d20327..c0cee6bdac 100644 --- a/fastcrypto-zkp/src/bn254/poseidon.rs +++ b/fastcrypto-zkp/src/bn254/poseidon.rs @@ -38,11 +38,34 @@ impl PoseidonWrapper { .map_err(|_| FastCryptoError::InvalidInput) } } + +/// Calculate the poseidon hash of the field element inputs. If the input +/// length is <= 16, calculate H(inputs), if it is <= 32, calculate H(H(inputs[0..16]), H(inputs[16..32])), otherwise return an error. +pub fn to_poseidon_hash(inputs: Vec) -> Result { + if inputs.len() <= 16 { + let mut poseidon1: PoseidonWrapper = PoseidonWrapper::new(); + poseidon1.hash(inputs) + } else if inputs.len() <= 32 { + let mut poseidon1: PoseidonWrapper = PoseidonWrapper::new(); + let hash1 = poseidon1.hash(inputs[0..16].to_vec())?; + + let mut poseidon2 = PoseidonWrapper::new(); + let hash2 = poseidon2.hash(inputs[16..].to_vec())?; + + let mut poseidon3 = PoseidonWrapper::new(); + poseidon3.hash([hash1, hash2].to_vec()) + } else { + Err(FastCryptoError::GeneralError(format!( + "Yet to implement: Unable to hash a vector of length {}", + inputs.len() + ))) + } +} + #[cfg(test)] mod test { use super::PoseidonWrapper; - use crate::bn254::zk_login::to_poseidon_hash; - use crate::bn254::zk_login::Bn254Fr; + use crate::bn254::{poseidon::to_poseidon_hash, zk_login::Bn254Fr}; use ark_bn254::Fr; use std::str::FromStr; 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 15cb8805ca..7dae9653d9 100644 --- a/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs +++ b/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs @@ -141,11 +141,11 @@ fn test_verify_zk_login_google() { } #[test] -fn test_verify_zk_login_facebook() { +fn test_verify_zk_login_twitch() { let eph_pubkey = big_int_str_to_bytes( "84029355920633174015103288781128426107680789454168570548782290541079926444544", ); - let zklogin_inputs = ZkLoginInputs::from_json("{\"proof_points\":{\"pi_a\":[\"1506355985430918561986621304944206628720275836415696432527226322011786292469\",\"613455373800062466065258859046532491698424798267244949352433118740950052820\",\"1\"],\"pi_b\":[[\"13432605760939024875746197498117682238269308826467106768856049869617495266990\",\"1832067857097543048080313900700732174583103404004828871583233404786491016938\"],[\"12774506805709477874931076799272413990755625719529677477582407925650184326419\",\"9920407680353330309465070375503195109002963389725451415597959332611544046474\"],[\"1\",\"0\"]],\"pi_c\":[\"21460871700073370465890462779698742952227192461460518900819221976004900851129\",\"16417973205725808622705510292771877448246769946278068014196442886693189946200\",\"1\"]},\"address_seed\":\"8309251037759855865804524144086603991988043161256515070528758422897128118794\",\"claims\":[{\"name\":\"iss\",\"value_base64\":\"wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw\",\"index_mod_4\":2},{\"name\":\"aud\",\"value_base64\":\"yJhdWQiOiJyczFiaDA2NWk5eWE0eWR2aWZpeGw0a3NzMHVocHQiLC\",\"index_mod_4\":1}],\"header_base64\":\"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ\"}").unwrap().init().unwrap(); + let zklogin_inputs = ZkLoginInputs::from_json("{\"proof_points\":{\"pi_a\":[\"13091564805270242091988535637094928145526962426048248457544204014146120848061\",\"16720184877774297257067413263271993886875650921635894599271213076438028149121\",\"1\"],\"pi_b\":[[\"10315437641058147137376024432028897182269999873576264368551692657896041757048\",\"1508628913652029422583835336497010615341442910432485486157510667573899391209\"],[\"2615204984444202339450727742801445002386326248143849542511762200103437621802\",\"5849486215298305357746845827664545444623657491883886014999288709074461992087\"],[\"1\",\"0\"]],\"pi_c\":[\"14195277018213067909432749083395103683618329379082932858378933760763589481512\",\"15923172920578108290608652430421506204931592172775947506845238271513931833500\",\"1\"]},\"address_seed\":\"8309251037759855865804524144086603991988043161256515070528758422897128118794\",\"claims\":[{\"name\":\"iss\",\"value_base64\":\"wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw\",\"index_mod_4\":2},{\"name\":\"aud\",\"value_base64\":\"yJhdWQiOiJyczFiaDA2NWk5eWE0eWR2aWZpeGw0a3NzMHVocHQiLC\",\"index_mod_4\":1}],\"header_base64\":\"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ\"}").unwrap().init().unwrap(); assert_eq!(zklogin_inputs.get_kid(), "1".to_string()); assert_eq!( zklogin_inputs.get_iss(), @@ -175,10 +175,15 @@ fn test_verify_zk_login_facebook() { n: "6lq9MQ-q6hcxr7kOUp-tHlHtdcDsVLwVIw13iXUCvuDOeCi0VSuxCCUY6UmMjy53dX00ih2E4Y4UvlrmmurK0eG26b-HMNNAvCGsVXHU3RcRhVoHDaOwHwU72j7bpHn9XbP3Q3jebX6KIfNbei2MiR0Wyb8RZHE-aZhRYO8_-k9G2GycTpvc-2GBsP8VHLUKKfAs2B6sW3q3ymU6M0L-cFXkZ9fHkn9ejs-sqZPhMJxtBPBxoUIUQFTgv4VXTSv914f_YkNw-EjuwbgwXMvpyr06EyfImxHoxsZkFYB-qBYHtaMxTnFsZBr6fn8Ha2JqT1hoP7Z5r5wxDu3GQhKkHw".to_string(), alg: "RS256".to_string(), }); - let res = verify_zk_login(&zklogin_inputs, 10000, &eph_pubkey, &map, Environment::Test); + let res = verify_zk_login(&zklogin_inputs, 10, &eph_pubkey, &map, Environment::Test); assert!(res.is_ok()); } +#[test] +fn test_verify_zk_login_facebook() { + // TODO +} + #[test] fn test_parsed_masked_content() { let header = JWTHeader::new("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ").unwrap(); diff --git a/fastcrypto-zkp/src/bn254/utils.rs b/fastcrypto-zkp/src/bn254/utils.rs index 7038e22bfb..948e599131 100644 --- a/fastcrypto-zkp/src/bn254/utils.rs +++ b/fastcrypto-zkp/src/bn254/utils.rs @@ -1,5 +1,6 @@ -// Copyright (c) Mysten Labs, Inc. +// Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 + use crate::bn254::poseidon::PoseidonWrapper; use crate::bn254::zk_login::big_int_str_to_bytes; use crate::bn254::zk_login::AddressParams; diff --git a/fastcrypto-zkp/src/bn254/zk_login.rs b/fastcrypto-zkp/src/bn254/zk_login.rs index 1efffa792a..27bc68ea99 100644 --- a/fastcrypto-zkp/src/bn254/zk_login.rs +++ b/fastcrypto-zkp/src/bn254/zk_login.rs @@ -4,7 +4,7 @@ use fastcrypto::error::FastCryptoResult; use serde_json::Value; -use super::poseidon::PoseidonWrapper; +use super::poseidon::{to_poseidon_hash, PoseidonWrapper}; use crate::circom::{g1_affine_from_str_projective, g2_affine_from_str_projective}; pub use ark_bn254::{Bn254, Fr as Bn254Fr}; pub use ark_ff::ToConstraintField; @@ -320,7 +320,6 @@ impl ZkLoginInputs { let mut poseidon = PoseidonWrapper::new(); let addr_seed = to_field(&self.address_seed)?; - let (first_half, second_half) = eph_pubkey_bytes.split_at(17); let first_bigint = BigInt::from_bytes_be(Sign::Plus, first_half); let second_bigint = BigInt::from_bytes_be(Sign::Plus, second_half); @@ -514,28 +513,6 @@ fn bitarray_to_bytearray(bits: &[u8]) -> Vec { bytes } -/// Calculate the poseidon hash of the field element inputs. -pub fn to_poseidon_hash(inputs: Vec) -> Result { - if inputs.len() <= 16 { - let mut poseidon1: PoseidonWrapper = PoseidonWrapper::new(); - poseidon1.hash(inputs) - } else if inputs.len() <= 32 { - let mut poseidon1: PoseidonWrapper = PoseidonWrapper::new(); - let hash1 = poseidon1.hash(inputs[0..16].to_vec())?; - - let mut poseidon2 = PoseidonWrapper::new(); - let hash2 = poseidon2.hash(inputs[16..].to_vec())?; - - let mut poseidon3 = PoseidonWrapper::new(); - poseidon3.hash([hash1, hash2].to_vec()) - } else { - Err(FastCryptoError::GeneralError(format!( - "Yet to implement: Unable to hash a vector of length {}", - inputs.len() - ))) - } -} - /// Convert a bigint string to a field element. fn to_field(val: &str) -> Result { Bn254Fr::from_str(val).map_err(|_| FastCryptoError::InvalidInput) @@ -646,8 +623,7 @@ fn big_int_array_to_bits(arr: &[BigUint], int_size: usize) -> Vec { pub fn big_int_str_to_bytes(value: &str) -> Vec { BigInt::from_str(value) .expect("Invalid big int string") - .to_bytes_be() - .1 + .to_signed_bytes_be() } /// Parameters for generating an address. From 778bb13eac6d4affd9838a0a2934b73adb8bdb62 Mon Sep 17 00:00:00 2001 From: Joy Wang <108701016+joyqvq@users.noreply.github.com> Date: Mon, 14 Aug 2023 14:48:16 -0400 Subject: [PATCH 10/13] update crates --- Cargo.lock | 358 +++++++++--------- fastcrypto-zkp/Cargo.toml | 3 +- .../src/bn254/unit_tests/zk_login_tests.rs | 7 +- fastcrypto-zkp/src/bn254/zk_login_api.rs | 5 +- 4 files changed, 178 insertions(+), 195 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b3557802ae..e520e60b33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -520,15 +520,6 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bitmaps" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" -dependencies = [ - "typenum", -] - [[package]] name = "blake2" version = "0.10.6" @@ -908,6 +899,16 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -1678,7 +1679,6 @@ dependencies = [ "derive_more", "fastcrypto", "hex", - "im", "num-bigint", "once_cell", "poseidon-ark", @@ -1732,6 +1732,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.0" @@ -1756,23 +1771,6 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" -[[package]] -name = "futures-io" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" - -[[package]] -name = "futures-macro" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.12", -] - [[package]] name = "futures-sink" version = "0.3.28" @@ -1792,13 +1790,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-core", - "futures-io", - "futures-macro", "futures-task", - "memchr", "pin-project-lite", "pin-utils", - "slab", ] [[package]] @@ -2021,9 +2015,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" @@ -2050,17 +2044,16 @@ dependencies = [ ] [[package]] -name = "hyper-rustls" -version = "0.24.1" +name = "hyper-tls" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "futures-util", - "http", + "bytes", "hyper", - "rustls", + "native-tls", "tokio", - "tokio-rustls", + "tokio-native-tls", ] [[package]] @@ -2103,20 +2096,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "im" -version = "15.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" -dependencies = [ - "bitmaps", - "rand_core", - "rand_xoshiro", - "sized-chunks", - "typenum", - "version_check", -] - [[package]] name = "indenter" version = "0.3.3" @@ -2396,6 +2375,24 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "num" version = "0.4.0" @@ -2540,6 +2537,50 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl" +version = "0.10.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.12", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "os_str_bytes" version = "6.4.1" @@ -2638,6 +2679,12 @@ dependencies = [ "spki 0.7.1", ] +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + [[package]] name = "plotters" version = "0.3.4" @@ -2875,15 +2922,6 @@ dependencies = [ "rand_core", ] -[[package]] -name = "rand_xoshiro" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" -dependencies = [ - "rand_core", -] - [[package]] name = "rayon" version = "1.7.0" @@ -3002,27 +3040,25 @@ dependencies = [ "http", "http-body", "hyper", - "hyper-rustls", + "hyper-tls", "ipnet", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", - "rustls", - "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "tokio", - "tokio-rustls", + "tokio-native-tls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", "winreg", ] @@ -3047,21 +3083,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted", - "web-sys", - "winapi", -] - [[package]] name = "rkyv" version = "0.7.40" @@ -3158,37 +3179,6 @@ dependencies = [ "windows-sys 0.45.0", ] -[[package]] -name = "rustls" -version = "0.21.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb" -dependencies = [ - "log", - "ring", - "rustls-webpki", - "sct", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" -dependencies = [ - "base64 0.21.0", -] - -[[package]] -name = "rustls-webpki" -version = "0.101.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261e9e0888cba427c3316e6322805653c9425240b6fd96cee7cb671ab70ab8d0" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "rustversion" version = "1.0.12" @@ -3222,6 +3212,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "schemars" version = "0.8.12" @@ -3258,16 +3257,6 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" -[[package]] -name = "sct" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "seahash" version = "4.1.0" @@ -3322,6 +3311,29 @@ dependencies = [ "cc", ] +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.17" @@ -3530,16 +3542,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" -[[package]] -name = "sized-chunks" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" -dependencies = [ - "bitmaps", - "typenum", -] - [[package]] name = "slab" version = "0.4.8" @@ -3806,7 +3808,6 @@ dependencies = [ "libc", "memchr", "mio", - "num_cpus", "pin-project-lite", "socket2", "tokio-macros", @@ -3825,12 +3826,12 @@ dependencies = [ ] [[package]] -name = "tokio-rustls" -version = "0.24.1" +name = "tokio-native-tls" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ - "rustls", + "native-tls", "tokio", ] @@ -3996,12 +3997,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "url" version = "2.4.0" @@ -4019,6 +4014,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -4402,25 +4403,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "webpki-roots" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" -dependencies = [ - "webpki", -] - [[package]] name = "which" version = "4.4.0" @@ -4506,7 +4488,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.1", + "windows-targets 0.48.2", ] [[package]] @@ -4526,17 +4508,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.1" +version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +checksum = "d1eeca1c172a285ee6c2c84c341ccea837e7c01b12fbb2d0fe3c9e550ce49ec8" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm 0.48.2", + "windows_aarch64_msvc 0.48.2", + "windows_i686_gnu 0.48.2", + "windows_i686_msvc 0.48.2", + "windows_x86_64_gnu 0.48.2", + "windows_x86_64_gnullvm 0.48.2", + "windows_x86_64_msvc 0.48.2", ] [[package]] @@ -4547,9 +4529,9 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "b10d0c968ba7f6166195e13d593af609ec2e3d24f916f081690695cf5eaffb2f" [[package]] name = "windows_aarch64_msvc" @@ -4565,9 +4547,9 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "571d8d4e62f26d4932099a9efe89660e8bd5087775a2ab5cdd8b747b811f1058" [[package]] name = "windows_i686_gnu" @@ -4583,9 +4565,9 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "2229ad223e178db5fbbc8bd8d3835e51e566b8474bfca58d2e6150c48bb723cd" [[package]] name = "windows_i686_msvc" @@ -4601,9 +4583,9 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "600956e2d840c194eedfc5d18f8242bc2e17c7775b6684488af3a9fff6fe3287" [[package]] name = "windows_x86_64_gnu" @@ -4619,9 +4601,9 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "ea99ff3f8b49fb7a8e0d305e5aec485bd068c2ba691b6e277d29eaeac945868a" [[package]] name = "windows_x86_64_gnullvm" @@ -4631,9 +4613,9 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "8f1a05a1ece9a7a0d5a7ccf30ba2c33e3a61a30e042ffd247567d1de1d94120d" [[package]] name = "windows_x86_64_msvc" @@ -4649,9 +4631,9 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "d419259aba16b663966e29e6d7c6ecfa0bb8425818bb96f6f1f3c3eb71a6e7b9" [[package]] name = "winreg" diff --git a/fastcrypto-zkp/Cargo.toml b/fastcrypto-zkp/Cargo.toml index ba66c219a5..ef7f47b52f 100644 --- a/fastcrypto-zkp/Cargo.toml +++ b/fastcrypto-zkp/Cargo.toml @@ -32,8 +32,7 @@ serde = { version = "1.0.152", features = ["derive"] } serde_json = "1.0.93" once_cell = "1.16" poseidon-ark = { git = "https://github.com/MystenLabs/poseidon-ark.git", rev = "39844046ded0c4aa7a247e8545e131b59a330a9c" } -im = "15" -reqwest = { version = "0.11.18", default_features= false, features = ["blocking", "json", "rustls-tls"] } +reqwest = "0.11.18" bcs = "0.1.4" [dev-dependencies] 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 7dae9653d9..c69515f875 100644 --- a/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs +++ b/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs @@ -1,6 +1,8 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use std::collections::HashMap; + use crate::bn254::utils::get_nonce; use crate::bn254::zk_login::{ big_int_str_to_bytes, decode_base64_url, map_bytes_to_field, parse_jwks, trim, @@ -18,7 +20,6 @@ use fastcrypto::ed25519::Ed25519KeyPair; use fastcrypto::error::FastCryptoError; use fastcrypto::rsa::{Base64UrlUnpadded, Encoding}; use fastcrypto::traits::KeyPair; -use im::hashmap::HashMap as ImHashMap; use num_bigint::BigInt; const GOOGLE_JWK_BYTES: &[u8] = r#"{ @@ -111,7 +112,7 @@ fn test_verify_zk_login_google() { OIDCProvider::Google.get_config().0.to_string() ); - let mut map = ImHashMap::new(); + let mut map = HashMap::new(); let content = JWK { kty: "RSA".to_string(), e: "AQAB".to_string(), @@ -168,7 +169,7 @@ fn test_verify_zk_login_twitch() { "8309251037759855865804524144086603991988043161256515070528758422897128118794" ); - let mut map = ImHashMap::new(); + let mut map = HashMap::new(); map.insert(("1".to_string(), OIDCProvider::Twitch.get_config().0.to_string()), JWK { kty: "RSA".to_string(), e: "AQAB".to_string(), diff --git a/fastcrypto-zkp/src/bn254/zk_login_api.rs b/fastcrypto-zkp/src/bn254/zk_login_api.rs index a1d96f1d80..7db881e46f 100644 --- a/fastcrypto-zkp/src/bn254/zk_login_api.rs +++ b/fastcrypto-zkp/src/bn254/zk_login_api.rs @@ -1,9 +1,10 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use std::collections::HashMap; + use ark_crypto_primitives::snark::SNARK; use fastcrypto::rsa::{Base64UrlUnpadded, Encoding}; -use im::hashmap::HashMap as ImHashMap; use super::verifier::process_vk_special; use super::zk_login::{ZkLoginInputs, JWK}; @@ -122,7 +123,7 @@ pub fn verify_zk_login( input: &ZkLoginInputs, max_epoch: u64, eph_pubkey_bytes: &[u8], - all_jwk: &ImHashMap<(String, String), JWK>, + all_jwk: &HashMap<(String, String), JWK>, usage: Environment, ) -> Result<(), FastCryptoError> { // Load the expected JWK based on (kid, iss). From ed22c7a0846ef421c08d0bfcd624dd236c12c47e Mon Sep 17 00:00:00 2001 From: Joy Wang <108701016+joyqvq@users.noreply.github.com> Date: Mon, 14 Aug 2023 18:37:57 -0400 Subject: [PATCH 11/13] use BigUint to convert to Bn254Fr --- .../src/bn254/unit_tests/zk_login_tests.rs | 26 +++++----- fastcrypto-zkp/src/bn254/utils.rs | 49 +++++++++++++------ fastcrypto-zkp/src/bn254/zk_login.rs | 26 ++++------ 3 files changed, 55 insertions(+), 46 deletions(-) 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 c69515f875..30fed30ca3 100644 --- a/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs +++ b/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs @@ -5,8 +5,8 @@ use std::collections::HashMap; use crate::bn254::utils::get_nonce; use crate::bn254::zk_login::{ - big_int_str_to_bytes, decode_base64_url, map_bytes_to_field, parse_jwks, trim, - verify_extended_claim, Claim, JWTDetails, JWTHeader, + decode_base64_url, map_bytes_to_field, parse_jwks, trim, verify_extended_claim, Claim, + JWTDetails, JWTHeader, }; use crate::bn254::zk_login::{fetch_jwks, OIDCProvider}; use crate::bn254::zk_login_api::Environment; @@ -20,7 +20,6 @@ use fastcrypto::ed25519::Ed25519KeyPair; use fastcrypto::error::FastCryptoError; use fastcrypto::rsa::{Base64UrlUnpadded, Encoding}; use fastcrypto::traits::KeyPair; -use num_bigint::BigInt; const GOOGLE_JWK_BYTES: &[u8] = r#"{ "keys": [ @@ -85,9 +84,10 @@ fn test_verify_zk_login_google() { use crate::bn254::zk_login_api::Bn254Fr; use std::str::FromStr; - let eph_pubkey = big_int_str_to_bytes( - "84029355920633174015103288781128426107680789454168570548782290541079926444544", - ); + let kp = Ed25519KeyPair::generate(&mut StdRng::from_seed([0; 32])); + let mut eph_pubkey = vec![0x00]; + eph_pubkey.extend(kp.public().as_ref()); + assert!(ZkLoginInputs::from_json("{\"something\":{\"pi_a\":[\"17906300526443048714387222471528497388165567048979081127218444558531971001212\",\"16347093943573822555530932280098040740968368762067770538848146419225596827968\",\"1\"],\"pi_b\":[[\"604559992637298524596005947885439665413516028337069712707205304781687795569\",\"3442016989288172723305001983346837664894554996521317914830240702746056975984\"],[\"11525538739919950358574045244601652351196410355282682596092151863632911615318\",\"8054528381876103674715157136115660256860302241449545586065224275685056359825\"],[\"1\",\"0\"]],\"pi_c\":[\"12090542001353421590770702288155881067849038975293665701252531703168853963809\",\"8667909164654995486331191860419304610736366583628608454080754129255123340291\",\"1\"]},\"address_seed\":\"7577247629761003321376053963457717029490787816434302620024795358930497565155\",\"claims\":[{\"name\":\"iss\",\"value_base64\":\"yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC\",\"index_mod_4\":1},{\"name\":\"aud\",\"value_base64\":\"CJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLC\",\"index_mod_4\":1}],\"header_base64\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjkxMWUzOWUyNzkyOGFlOWYxZTlkMWUyMTY0NmRlOTJkMTkzNTFiNDQiLCJ0eXAiOiJKV1QifQ\"}").is_err()); let zklogin_inputs = ZkLoginInputs::from_json("{\"proof_points\":{\"pi_a\":[\"17906300526443048714387222471528497388165567048979081127218444558531971001212\",\"16347093943573822555530932280098040740968368762067770538848146419225596827968\",\"1\"],\"pi_b\":[[\"604559992637298524596005947885439665413516028337069712707205304781687795569\",\"3442016989288172723305001983346837664894554996521317914830240702746056975984\"],[\"11525538739919950358574045244601652351196410355282682596092151863632911615318\",\"8054528381876103674715157136115660256860302241449545586065224275685056359825\"],[\"1\",\"0\"]],\"pi_c\":[\"12090542001353421590770702288155881067849038975293665701252531703168853963809\",\"8667909164654995486331191860419304610736366583628608454080754129255123340291\",\"1\"]},\"address_seed\":\"7577247629761003321376053963457717029490787816434302620024795358930497565155\",\"claims\":[{\"name\":\"iss\",\"value_base64\":\"yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC\",\"index_mod_4\":1},{\"name\":\"aud\",\"value_base64\":\"CJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLC\",\"index_mod_4\":1}],\"header_base64\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjkxMWUzOWUyNzkyOGFlOWYxZTlkMWUyMTY0NmRlOTJkMTkzNTFiNDQiLCJ0eXAiOiJKV1QifQ\"}").unwrap().init().unwrap(); @@ -143,9 +143,10 @@ fn test_verify_zk_login_google() { #[test] fn test_verify_zk_login_twitch() { - let eph_pubkey = big_int_str_to_bytes( - "84029355920633174015103288781128426107680789454168570548782290541079926444544", - ); + let kp = Ed25519KeyPair::generate(&mut StdRng::from_seed([0; 32])); + let mut eph_pubkey = vec![0x00]; + eph_pubkey.extend(kp.public().as_ref()); + let zklogin_inputs = ZkLoginInputs::from_json("{\"proof_points\":{\"pi_a\":[\"13091564805270242091988535637094928145526962426048248457544204014146120848061\",\"16720184877774297257067413263271993886875650921635894599271213076438028149121\",\"1\"],\"pi_b\":[[\"10315437641058147137376024432028897182269999873576264368551692657896041757048\",\"1508628913652029422583835336497010615341442910432485486157510667573899391209\"],[\"2615204984444202339450727742801445002386326248143849542511762200103437621802\",\"5849486215298305357746845827664545444623657491883886014999288709074461992087\"],[\"1\",\"0\"]],\"pi_c\":[\"14195277018213067909432749083395103683618329379082932858378933760763589481512\",\"15923172920578108290608652430421506204931592172775947506845238271513931833500\",\"1\"]},\"address_seed\":\"8309251037759855865804524144086603991988043161256515070528758422897128118794\",\"claims\":[{\"name\":\"iss\",\"value_base64\":\"wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw\",\"index_mod_4\":2},{\"name\":\"aud\",\"value_base64\":\"yJhdWQiOiJyczFiaDA2NWk5eWE0eWR2aWZpeGw0a3NzMHVocHQiLC\",\"index_mod_4\":1}],\"header_base64\":\"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ\"}").unwrap().init().unwrap(); assert_eq!(zklogin_inputs.get_kid(), "1".to_string()); assert_eq!( @@ -402,11 +403,6 @@ fn test_get_nonce() { let kp = Ed25519KeyPair::generate(&mut StdRng::from_seed([0; 32])); let mut eph_pk_bytes = vec![0x00]; eph_pk_bytes.extend(kp.public().as_ref()); - let pk_bigint = BigInt::from_bytes_be(num_bigint::Sign::Plus, &eph_pk_bytes); - assert_eq!( - pk_bigint.to_string(), - "84029355920633174015103288781128426107680789454168570548782290541079926444544" - ); - let nonce = get_nonce(&eph_pk_bytes, 10, "100681567828351849884072155819400689117"); + let nonce = get_nonce(&eph_pk_bytes, 10, "100681567828351849884072155819400689117").unwrap(); assert_eq!(nonce, "hTPpgF7XAKbW37rEUS6pEVZqmoI"); } diff --git a/fastcrypto-zkp/src/bn254/utils.rs b/fastcrypto-zkp/src/bn254/utils.rs index 948e599131..954c0ac78e 100644 --- a/fastcrypto-zkp/src/bn254/utils.rs +++ b/fastcrypto-zkp/src/bn254/utils.rs @@ -2,14 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 use crate::bn254::poseidon::PoseidonWrapper; -use crate::bn254::zk_login::big_int_str_to_bytes; use crate::bn254::zk_login::AddressParams; use crate::bn254::zk_login::OIDCProvider; use crate::bn254::zk_login_api::Bn254Fr; +use fastcrypto::error::FastCryptoError; use fastcrypto::hash::{Blake2b256, HashFunction}; use fastcrypto::rsa::Base64UrlUnpadded; use fastcrypto::rsa::Encoding; -use num_bigint::{BigInt, Sign}; +use num_bigint::BigUint; use std::str::FromStr; const ZK_LOGIN_AUTHENTICATOR_FLAG: u8 = 0x05; @@ -32,23 +32,24 @@ pub fn get_oidc_url( client_id: &str, redirect_url: &str, jwt_randomness: &str, -) -> String { - let nonce = get_nonce(eph_pk_bytes, max_epoch, jwt_randomness); - match provider { +) -> Result { + let nonce = get_nonce(eph_pk_bytes, max_epoch, jwt_randomness)?; + Ok(match provider { OIDCProvider::Google => format!("https://accounts.google.com/o/oauth2/v2/auth?client_id={}&response_type=id_token&redirect_uri={}&scope=openid&nonce={}", client_id, redirect_url, nonce), OIDCProvider::Twitch => format!("https://id.twitch.tv/oauth2/authorize?client_id={}&force_verify=true&lang=en&login_type=login&redirect_uri={}&response_type=id_token&scope=openid&nonce={}", client_id, redirect_url, nonce), - OIDCProvider::Facebook => format!("https://www.facebook.com/v17.0/dialog/oauth?client_id={}&redirect_uri={}&scope=openid&nonce={}&response_type=id_token", client_id, redirect_url, nonce) } + OIDCProvider::Facebook => format!("https://www.facebook.com/v17.0/dialog/oauth?client_id={}&redirect_uri={}&scope=openid&nonce={}&response_type=id_token", client_id, redirect_url, nonce) }) } /// Calculate the nonce for the given parameters. Nonce is defined as the Base64Url encoded of the poseidon hash of 4 inputs: /// first half of eph_pk_bytes in BigInt, second half of eph_pk_bytes in BigInt, max_epoch and jwt_randomness. -pub fn get_nonce(eph_pk_bytes: &[u8], max_epoch: u64, jwt_randomness: &str) -> String { - let (first_half, second_half) = eph_pk_bytes.split_at(17); - let first_bigint = BigInt::from_bytes_be(Sign::Plus, first_half); - let second_bigint = BigInt::from_bytes_be(Sign::Plus, second_half); +pub fn get_nonce( + eph_pk_bytes: &[u8], + max_epoch: u64, + jwt_randomness: &str, +) -> Result { let mut poseidon = PoseidonWrapper::new(); - let first = Bn254Fr::from_str(&first_bigint.to_string()).unwrap(); - let second = Bn254Fr::from_str(&second_bigint.to_string()).unwrap(); + let (first, second) = split_to_two_frs(eph_pk_bytes)?; + let max_epoch = Bn254Fr::from_str(&max_epoch.to_string()).unwrap(); let jwt_randomness = Bn254Fr::from_str(jwt_randomness).unwrap(); @@ -58,7 +59,27 @@ pub fn get_nonce(eph_pk_bytes: &[u8], max_epoch: u64, jwt_randomness: &str) -> S let data = big_int_str_to_bytes(&hash.to_string()); let truncated = &data[data.len() - 20..]; let mut buf = vec![0; Base64UrlUnpadded::encoded_len(truncated)]; - Base64UrlUnpadded::encode(truncated, &mut buf) + Ok(Base64UrlUnpadded::encode(truncated, &mut buf) .unwrap() - .to_string() + .to_string()) +} + +/// Given a 33-byte public key bytes (flag || pk_bytes), returns the two Bn254Fr split at the 128 bit index. +pub fn split_to_two_frs(eph_pk_bytes: &[u8]) -> Result<(Bn254Fr, Bn254Fr), FastCryptoError> { + // Split the bytes deterministically such that the first element contains the first 128 + // bits of the hash, and the second element contains the latter ones. + let (first_half, second_half) = eph_pk_bytes.split_at(17); + let first_bigint = BigUint::from_bytes_be(first_half); + let second_bigint = BigUint::from_bytes_be(second_half); + + let eph_public_key_0 = Bn254Fr::from(first_bigint); + 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) -> Vec { + BigUint::from_str(value) + .expect("Invalid big int string") + .to_bytes_be() } diff --git a/fastcrypto-zkp/src/bn254/zk_login.rs b/fastcrypto-zkp/src/bn254/zk_login.rs index 27bc68ea99..eb10a8411a 100644 --- a/fastcrypto-zkp/src/bn254/zk_login.rs +++ b/fastcrypto-zkp/src/bn254/zk_login.rs @@ -4,7 +4,10 @@ use fastcrypto::error::FastCryptoResult; use serde_json::Value; -use super::poseidon::{to_poseidon_hash, PoseidonWrapper}; +use super::{ + poseidon::{to_poseidon_hash, PoseidonWrapper}, + utils::split_to_two_frs, +}; use crate::circom::{g1_affine_from_str_projective, g2_affine_from_str_projective}; pub use ark_bn254::{Bn254, Fr as Bn254Fr}; pub use ark_ff::ToConstraintField; @@ -14,7 +17,7 @@ use fastcrypto::{ error::FastCryptoError, rsa::{Base64UrlUnpadded, Encoding}, }; -use num_bigint::{BigInt, BigUint, Sign}; +use num_bigint::BigUint; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::str::FromStr; @@ -310,7 +313,7 @@ impl ZkLoginInputs { /// Calculate the poseidon hash from selected fields from inputs, along with the ephemeral pubkey. pub fn calculate_all_inputs_hash( &self, - eph_pubkey_bytes: &[u8], + eph_pk_bytes: &[u8], modulus: &[u8], max_epoch: u64, ) -> Result, FastCryptoError> { @@ -320,12 +323,8 @@ impl ZkLoginInputs { let mut poseidon = PoseidonWrapper::new(); let addr_seed = to_field(&self.address_seed)?; - let (first_half, second_half) = eph_pubkey_bytes.split_at(17); - let first_bigint = BigInt::from_bytes_be(Sign::Plus, first_half); - let second_bigint = BigInt::from_bytes_be(Sign::Plus, second_half); + let (first, second) = split_to_two_frs(eph_pk_bytes)?; - let eph_public_key_0 = to_field(&first_bigint.to_string())?; - let eph_public_key_1 = to_field(&second_bigint.to_string())?; let max_epoch = to_field(&max_epoch.to_string())?; let mut padded_claims = self.claims.clone(); for _ in self.claims.len()..NUM_EXTRACTABLE_STRINGS as usize { @@ -361,8 +360,8 @@ impl ZkLoginInputs { let header_f = map_bytes_to_field(&self.parsed_masked_content.header, MAX_HEADER_LEN)?; let modulus_f = hash_to_field(&[BigUint::from_bytes_be(modulus)], 2048)?; Ok(vec![poseidon.hash(vec![ - eph_public_key_0, - eph_public_key_1, + first, + second, addr_seed, max_epoch, extracted_claims_hash, @@ -619,13 +618,6 @@ fn big_int_array_to_bits(arr: &[BigUint], int_size: usize) -> Vec { bitarray } -/// Convert a big int string to a big endian bytearray. -pub fn big_int_str_to_bytes(value: &str) -> Vec { - BigInt::from_str(value) - .expect("Invalid big int string") - .to_signed_bytes_be() -} - /// Parameters for generating an address. #[derive(Debug, Serialize, Deserialize)] pub struct AddressParams { From d09702a87790f4dfff61d6601c7eb854563cef2e Mon Sep 17 00:00:00 2001 From: Joy Wang <108701016+joyqvq@users.noreply.github.com> Date: Tue, 15 Aug 2023 14:04:46 -0400 Subject: [PATCH 12/13] changes from 0814 --- .../src/bn254/unit_tests/zk_login_tests.rs | 59 +++++++++----- fastcrypto-zkp/src/bn254/utils.rs | 2 +- fastcrypto-zkp/src/bn254/zk_login.rs | 81 +++++++------------ fastcrypto-zkp/src/bn254/zk_login_api.rs | 8 +- 4 files changed, 72 insertions(+), 78 deletions(-) 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 30fed30ca3..187587730d 100644 --- a/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs +++ b/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs @@ -2,11 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 use std::collections::HashMap; +use std::str::FromStr; use crate::bn254::utils::get_nonce; use crate::bn254::zk_login::{ - decode_base64_url, map_bytes_to_field, parse_jwks, trim, verify_extended_claim, Claim, - JWTDetails, JWTHeader, + decode_base64_url, hash_ascii_str_to_field, hash_to_field, parse_jwks, trim, + verify_extended_claim, Claim, JWTDetails, JWTHeader, }; use crate::bn254::zk_login::{fetch_jwks, OIDCProvider}; use crate::bn254::zk_login_api::Environment; @@ -20,6 +21,7 @@ use fastcrypto::ed25519::Ed25519KeyPair; use fastcrypto::error::FastCryptoError; use fastcrypto::rsa::{Base64UrlUnpadded, Encoding}; use fastcrypto::traits::KeyPair; +use num_bigint::BigUint; const GOOGLE_JWK_BYTES: &[u8] = r#"{ "keys": [ @@ -90,10 +92,10 @@ fn test_verify_zk_login_google() { assert!(ZkLoginInputs::from_json("{\"something\":{\"pi_a\":[\"17906300526443048714387222471528497388165567048979081127218444558531971001212\",\"16347093943573822555530932280098040740968368762067770538848146419225596827968\",\"1\"],\"pi_b\":[[\"604559992637298524596005947885439665413516028337069712707205304781687795569\",\"3442016989288172723305001983346837664894554996521317914830240702746056975984\"],[\"11525538739919950358574045244601652351196410355282682596092151863632911615318\",\"8054528381876103674715157136115660256860302241449545586065224275685056359825\"],[\"1\",\"0\"]],\"pi_c\":[\"12090542001353421590770702288155881067849038975293665701252531703168853963809\",\"8667909164654995486331191860419304610736366583628608454080754129255123340291\",\"1\"]},\"address_seed\":\"7577247629761003321376053963457717029490787816434302620024795358930497565155\",\"claims\":[{\"name\":\"iss\",\"value_base64\":\"yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC\",\"index_mod_4\":1},{\"name\":\"aud\",\"value_base64\":\"CJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLC\",\"index_mod_4\":1}],\"header_base64\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjkxMWUzOWUyNzkyOGFlOWYxZTlkMWUyMTY0NmRlOTJkMTkzNTFiNDQiLCJ0eXAiOiJKV1QifQ\"}").is_err()); - let zklogin_inputs = ZkLoginInputs::from_json("{\"proof_points\":{\"pi_a\":[\"17906300526443048714387222471528497388165567048979081127218444558531971001212\",\"16347093943573822555530932280098040740968368762067770538848146419225596827968\",\"1\"],\"pi_b\":[[\"604559992637298524596005947885439665413516028337069712707205304781687795569\",\"3442016989288172723305001983346837664894554996521317914830240702746056975984\"],[\"11525538739919950358574045244601652351196410355282682596092151863632911615318\",\"8054528381876103674715157136115660256860302241449545586065224275685056359825\"],[\"1\",\"0\"]],\"pi_c\":[\"12090542001353421590770702288155881067849038975293665701252531703168853963809\",\"8667909164654995486331191860419304610736366583628608454080754129255123340291\",\"1\"]},\"address_seed\":\"7577247629761003321376053963457717029490787816434302620024795358930497565155\",\"claims\":[{\"name\":\"iss\",\"value_base64\":\"yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC\",\"index_mod_4\":1},{\"name\":\"aud\",\"value_base64\":\"CJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLC\",\"index_mod_4\":1}],\"header_base64\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjkxMWUzOWUyNzkyOGFlOWYxZTlkMWUyMTY0NmRlOTJkMTkzNTFiNDQiLCJ0eXAiOiJKV1QifQ\"}").unwrap().init().unwrap(); + let zklogin_inputs = ZkLoginInputs::from_json("{\"proof_points\":{\"pi_a\":[\"19787893228347416264175863455553559620428641614896993159007341506168718628112\",\"9525429549286657723959098685731622393483997723182834572003815901856723580461\",\"1\"],\"pi_b\":[[\"3493347571535213714356485688393536339935225306901617492672730508554674104240\",\"16985532751431776840148620485065973908539904507322892849232044565343489766128\"],[\"13893319072314620992397619679873669034526557780388381368678976150917704243179\",\"640742494801049238290284649145440140989242166770354858908399041461288677398\"],[\"1\",\"0\"]],\"pi_c\":[\"21862480547331092832339784749917351442633352096270524226556828302437894726278\",\"12611981741824395163083120319401608319346495925967864800017660949138781692880\",\"1\"]},\"address_seed\":\"15909817818955140159551330044992054478592339431921061309213971300613403932293\",\"claims\":[{\"name\":\"iss\",\"value_base64\":\"yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC\",\"index_mod_4\":1},{\"name\":\"aud\",\"value_base64\":\"CJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLC\",\"index_mod_4\":1}],\"header_base64\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjdjOWM3OGUzYjAwZTFiYjA5MmQyNDZjODg3YjExMjIwYzg3YjdkMjAiLCJ0eXAiOiJKV1QifQ\"}").unwrap().init().unwrap(); assert_eq!( zklogin_inputs.get_kid(), - "911e39e27928ae9f1e9d1e21646de92d19351b44".to_string() + "7c9c78e3b00e1bb092d246c887b11220c87b7d20".to_string() ); assert_eq!( zklogin_inputs.get_iss(), @@ -111,18 +113,21 @@ fn test_verify_zk_login_google() { zklogin_inputs.get_address_params().iss, OIDCProvider::Google.get_config().0.to_string() ); - + assert_eq!( + zklogin_inputs.get_address_seed(), + "15909817818955140159551330044992054478592339431921061309213971300613403932293" + ); let mut map = HashMap::new(); let content = JWK { kty: "RSA".to_string(), e: "AQAB".to_string(), - n: "4kGxcWQdTW43aszLmftsGswmwDDKdfcse-lKeT_zjZTB2KGw9E6LVY6IThJVxzYF6mcyU-Z5_jDAW_yi7D_gXep2rxchZvoFayXynbhxyfjK6RtJ6_k30j-WpsXCSAiNAkupYHUyDIBNocvUcrDJsC3U65l8jl1I3nW98X6d-IlAfEb2In2f0fR6d-_lhIQZjXLupjymJduPjjA8oXCUZ9bfAYPhGYj3ZELUHkAyDpZNrnSi8hFVMSUSnorAt9F7cKMUJDM4-Uopzaqcl_f-HxeKvxN7NjiLSiIYaHdgtTpCEuNvsch6q6JTsllJNr3c__BxrG4UMlJ3_KsPxbcvXw".to_string(), + n: "pGMz603XOzO71r-LpW555Etbn2dXAtY4xToNE_Upr1EHxkHFnVnGPsbOeWzP8xU1IpAL56S3sTsbpCR_Ci_PYq8s4I3VWQM0u9w1D_e45S1KJTSex_aiMQ_cjTXb3Iekc00JIkMJhUaNnbsEt7PlOmnyFqvN-G3ZXVDfTuL2Wsn4tRMYf7YU3jgTVN2M_p7bcZYHhkEB-jzNeK7ub-6mOMkKdYWnk0jIoRfV63d32bub0pQpWv8sVmflgK2xKUSJVMZ7CM0FvJYJgF7y42KBPYc6Gm_UWE0uHazDgZgAvQQoNyEF_TRjVfGiihjPFYCPqvFcfLK4773JTD2fLZTgOQ".to_string(), alg: "RS256".to_string(), }; map.insert( ( - "911e39e27928ae9f1e9d1e21646de92d19351b44".to_string(), + "7c9c78e3b00e1bb092d246c887b11220c87b7d20".to_string(), OIDCProvider::Google.get_config().0.to_string(), ), content.clone(), @@ -130,14 +135,14 @@ fn test_verify_zk_login_google() { let modulus = Base64UrlUnpadded::decode_vec(&content.n).unwrap(); assert_eq!( zklogin_inputs - .calculate_all_inputs_hash(&eph_pubkey, &modulus, 10000) - .unwrap(), + .calculate_all_inputs_hash(&eph_pubkey, &modulus, 10) + .unwrap(), vec![Bn254Fr::from_str( - "16145454131073363668578485057126520560259092161919376662788533820152738424329" + "9496323448584064558296338231676268184078052204097371080436367115432777673272" ) .unwrap()] ); - let res = verify_zk_login(&zklogin_inputs, 10000, &eph_pubkey, &map, Environment::Test); + let res = verify_zk_login(&zklogin_inputs, 10, &eph_pubkey, &map, Environment::Test); assert!(res.is_ok()); } @@ -147,7 +152,7 @@ fn test_verify_zk_login_twitch() { let mut eph_pubkey = vec![0x00]; eph_pubkey.extend(kp.public().as_ref()); - let zklogin_inputs = ZkLoginInputs::from_json("{\"proof_points\":{\"pi_a\":[\"13091564805270242091988535637094928145526962426048248457544204014146120848061\",\"16720184877774297257067413263271993886875650921635894599271213076438028149121\",\"1\"],\"pi_b\":[[\"10315437641058147137376024432028897182269999873576264368551692657896041757048\",\"1508628913652029422583835336497010615341442910432485486157510667573899391209\"],[\"2615204984444202339450727742801445002386326248143849542511762200103437621802\",\"5849486215298305357746845827664545444623657491883886014999288709074461992087\"],[\"1\",\"0\"]],\"pi_c\":[\"14195277018213067909432749083395103683618329379082932858378933760763589481512\",\"15923172920578108290608652430421506204931592172775947506845238271513931833500\",\"1\"]},\"address_seed\":\"8309251037759855865804524144086603991988043161256515070528758422897128118794\",\"claims\":[{\"name\":\"iss\",\"value_base64\":\"wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw\",\"index_mod_4\":2},{\"name\":\"aud\",\"value_base64\":\"yJhdWQiOiJyczFiaDA2NWk5eWE0eWR2aWZpeGw0a3NzMHVocHQiLC\",\"index_mod_4\":1}],\"header_base64\":\"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ\"}").unwrap().init().unwrap(); + let zklogin_inputs = ZkLoginInputs::from_json("{\"proof_points\":{\"pi_a\":[\"13051843614423432670927014216106415297302637061714876201698143724579485057433\",\"4502868464003469408525038047182455632737577658719986979336848385442638461715\",\"1\"],\"pi_b\":[[\"3766376969677702623227635810414794209012765519614143648451277080679068806865\",\"15274848924865252629859978623879023835428524488301134035498591572116063831502\"],[\"7946138017363758578225489333393267390091071305689481146893911497411367587561\",\"2088365154316551099212086738010689343716611426876233601699514012876088160321\"],[\"1\",\"0\"]],\"pi_c\":[\"5241274211994340733749437701839810664498991243102320350483342489924082173589\",\"15038895168244503564969393433273344359013838973322340723065298413943589759359\",\"1\"]},\"address_seed\":\"15454157267374145582481438333218897413377268576773635507925280300337575904853\",\"claims\":[{\"name\":\"iss\",\"value_base64\":\"wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw\",\"index_mod_4\":2},{\"name\":\"aud\",\"value_base64\":\"yJhdWQiOiJyczFiaDA2NWk5eWE0eWR2aWZpeGw0a3NzMHVocHQiLC\",\"index_mod_4\":1}],\"header_base64\":\"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ\"}").unwrap().init().unwrap(); assert_eq!(zklogin_inputs.get_kid(), "1".to_string()); assert_eq!( zklogin_inputs.get_iss(), @@ -167,7 +172,7 @@ fn test_verify_zk_login_twitch() { ); assert_eq!( zklogin_inputs.get_address_seed(), - "8309251037759855865804524144086603991988043161256515070528758422897128118794" + "15454157267374145582481438333218897413377268576773635507925280300337575904853" ); let mut map = HashMap::new(); @@ -338,17 +343,33 @@ fn test_verify_extended_claim() { } #[test] -fn test_hash_to_field() { +fn test_hash_ascii_str_to_field() { // Test generated against typescript implementation. - assert!(map_bytes_to_field("sub", 2).is_err()); assert_eq!( - map_bytes_to_field("yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC", 133) + hash_ascii_str_to_field("test@gmail.com", 30) .unwrap() .to_string(), - "19198909745930267855439585988170070469004479286780644790990940640914248274464" + "13606676331558803166736332982602687405662978305929711411606106012181987145625" + ); +} + +#[test] +fn test_hash_to_field() { + // Test generated against typescript implementation. + assert_eq!( + hash_to_field( + &[ + BigUint::from_str("32").unwrap(), + BigUint::from_str("25").unwrap(), + BigUint::from_str("73").unwrap() + ], + 8, + 16 + ) + .unwrap() + .to_string(), + "11782828208033177576380997957942702678240059658740659662920410026149313654840".to_string() ); - assert_eq!(map_bytes_to_field("CJhdWQiOiI1NzU1MTkyMDQyMzctbXNvcDllcDQ1dTJ1bzk4aGFwcW1uZ3Y4ZDg0cWRjOGsuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLC", 133).unwrap().to_string(), "6914089902564896687047107167562960781243311797290496295481879371814854678998"); - assert_eq!(map_bytes_to_field("eyJhbGciOiJSUzI1NiIsImtpZCI6ImM5YWZkYTM2ODJlYmYwOWViMzA1NWMxYzRiZDM5Yjc1MWZiZjgxOTUiLCJ0eXAiOiJKV1QifQ", 150).unwrap().to_string(), "11195180390614794854381992733393925748746563026948577817495625199891112836762"); } #[test] diff --git a/fastcrypto-zkp/src/bn254/utils.rs b/fastcrypto-zkp/src/bn254/utils.rs index 954c0ac78e..f4d9bb8a61 100644 --- a/fastcrypto-zkp/src/bn254/utils.rs +++ b/fastcrypto-zkp/src/bn254/utils.rs @@ -68,7 +68,7 @@ pub fn get_nonce( pub fn split_to_two_frs(eph_pk_bytes: &[u8]) -> Result<(Bn254Fr, Bn254Fr), FastCryptoError> { // Split the bytes deterministically such that the first element contains the first 128 // bits of the hash, and the second element contains the latter ones. - let (first_half, second_half) = eph_pk_bytes.split_at(17); + let (first_half, second_half) = eph_pk_bytes.split_at(eph_pk_bytes.len() - 16); let first_bigint = BigUint::from_bytes_be(first_half); let second_bigint = BigUint::from_bytes_be(second_half); diff --git a/fastcrypto-zkp/src/bn254/zk_login.rs b/fastcrypto-zkp/src/bn254/zk_login.rs index eb10a8411a..e5a3f2002b 100644 --- a/fastcrypto-zkp/src/bn254/zk_login.rs +++ b/fastcrypto-zkp/src/bn254/zk_login.rs @@ -342,7 +342,7 @@ impl ZkLoginInputs { "Invalid claim length".to_string(), )); } - claim_f.push(map_bytes_to_field( + claim_f.push(hash_ascii_str_to_field( &padded_claims[i as usize].value_base64, MAX_EXTRACTABLE_STR_LEN_B64, )?); @@ -357,8 +357,8 @@ impl ZkLoginInputs { .map(|c| to_field(&c.index_mod_4.to_string()).unwrap()) .collect::>(), )?; - let header_f = map_bytes_to_field(&self.parsed_masked_content.header, MAX_HEADER_LEN)?; - let modulus_f = hash_to_field(&[BigUint::from_bytes_be(modulus)], 2048)?; + let header_f = hash_ascii_str_to_field(&self.parsed_masked_content.header, MAX_HEADER_LEN)?; + let modulus_f = hash_to_field(&[BigUint::from_bytes_be(modulus)], 2048, PACK_WIDTH)?; Ok(vec![poseidon.hash(vec![ first, second, @@ -518,17 +518,17 @@ fn to_field(val: &str) -> Result { } /// Pads a stream of bytes and maps it to a field element -fn map_bytes_to_field(str: &str, max_size: u16) -> Result { - if str.len() > max_size as usize { - return Err(FastCryptoError::InvalidInput); - } - let in_arr: Vec = str +fn hash_ascii_str_to_field(str: &str, max_size: u16) -> Result { + let str_padded = str_to_padded_char_codes(str, max_size)?; + hash_to_field(&str_padded, 8, PACK_WIDTH) +} + +fn str_to_padded_char_codes(str: &str, max_len: u16) -> Result, FastCryptoError> { + let arr: Vec = str .chars() .map(|c| BigUint::from_slice(&([c as u32]))) .collect(); - - let str_padded = pad_with_zeroes(in_arr, max_size)?; - hash_to_field(&str_padded, 8) + pad_with_zeroes(arr, max_len) } fn pad_with_zeroes(in_arr: Vec, out_count: u16) -> Result, FastCryptoError> { @@ -547,57 +547,30 @@ fn pad_with_zeroes(in_arr: Vec, out_count: u16) -> Result, /// inWidth to packWidth. Then we compute the poseidon hash of the "packed" input. /// input is the input vector containing equal-width big ints. inWidth is the width of /// each input element. -fn hash_to_field(input: &[BigUint], in_width: u16) -> Result { - let num_elements = (input.len() * in_width as usize) / PACK_WIDTH as usize + 1; - let packed = pack2(input, in_width, PACK_WIDTH, num_elements)?; +fn hash_to_field( + input: &[BigUint], + in_width: u16, + pack_width: u16, +) -> Result { + let packed = convert_base(input, in_width, pack_width)?; to_poseidon_hash(packed) } -/// Helper function to pack into exactly outCount chunks of outWidth bits each. -fn pack2( +/// Helper function to pack field elements from big ints. +fn convert_base( in_arr: &[BigUint], in_width: u16, out_width: u16, - out_count: usize, -) -> Result, FastCryptoError> { - let packed = pack(in_arr, in_width as usize, out_width as usize)?; - if packed.len() > out_count as usize { - return Err(FastCryptoError::InvalidInput); - } - let mut padded = packed.clone(); - padded.extend(vec![ - to_field("0")?; - out_count as usize - packed.len() as usize - ]); - Ok(padded) -} - -/// Helper function to pack field elements from big ints. -fn pack( - in_arr: &[BigUint], - in_width: usize, - out_width: usize, ) -> Result, FastCryptoError> { - let bits = big_int_array_to_bits(in_arr, in_width); - let extra_bits = if bits.len() % out_width == 0 { - 0 - } else { - out_width - (bits.len() % out_width) - }; - let mut bits_padded = bits; - bits_padded.extend(vec![0; extra_bits]); - - if bits_padded.len() % out_width != 0 { - return Err(FastCryptoError::InvalidInput); + let bits = big_int_array_to_bits(in_arr, in_width as usize); + let packed: Vec = bits + .chunks(out_width as usize) + .map(|chunk| Bn254Fr::from(BigUint::from_radix_be(chunk, 2).unwrap())) + .collect(); + match packed.len() != in_arr.len() * in_width as usize / out_width as usize + 1 { + true => Err(FastCryptoError::InvalidInput), + false => Ok(packed), } - - Ok(bits_padded - .chunks(out_width) - .map(|chunk| { - let st = BigUint::from_radix_be(chunk, 2).unwrap().to_string(); - Bn254Fr::from_str(&st).unwrap() - }) - .collect()) } /// Convert a big int array to a bit array with 0 paddings. diff --git a/fastcrypto-zkp/src/bn254/zk_login_api.rs b/fastcrypto-zkp/src/bn254/zk_login_api.rs index 7db881e46f..d72134a135 100644 --- a/fastcrypto-zkp/src/bn254/zk_login_api.rs +++ b/fastcrypto-zkp/src/bn254/zk_login_api.rs @@ -88,16 +88,16 @@ fn global_pvk() -> PreparedVerifyingKey { let mut vk_gamma_abc_g1 = Vec::new(); for e in vec![ vec![ - "7601221783497382045435100727010102844416767995017297605284115099608422303035" + "10650235292452276702815258020174876822554680558613093350826598743737711706082" .to_string(), - "8749785198598536603958085261928419291825402152367782685067088145065090991309" + "10904000006666353404839309737175457841172416892262756319513121366464849299934" .to_string(), "1".to_string(), ], vec![ - "2844107402968053321142842260538249836495213364133637503989930436252095154777" + "13523860369377817188474813326919511067573805860184371020956327842962539802962" .to_string(), - "6671443994502368977962577284247390754840595243304510253358092664535353826787" + "15924113522601648253933515938165772453615741568509559656790523323812357588202" .to_string(), "1".to_string(), ], From 832925095f472bbc304795d8addbb74c59b79f01 Mon Sep 17 00:00:00 2001 From: Joy Wang <108701016+joyqvq@users.noreply.github.com> Date: Tue, 15 Aug 2023 14:20:06 -0400 Subject: [PATCH 13/13] lint fix --- fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 187587730d..e40d242591 100644 --- a/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs +++ b/fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs @@ -135,8 +135,8 @@ fn test_verify_zk_login_google() { let modulus = Base64UrlUnpadded::decode_vec(&content.n).unwrap(); assert_eq!( zklogin_inputs - .calculate_all_inputs_hash(&eph_pubkey, &modulus, 10) - .unwrap(), + .calculate_all_inputs_hash(&eph_pubkey, &modulus, 10) + .unwrap(), vec![Bn254Fr::from_str( "9496323448584064558296338231676268184078052204097371080436367115432777673272" )