From 252b860e5cfed2df3d3ff6b876e2ba31516e6310 Mon Sep 17 00:00:00 2001 From: Joy Wang <108701016+joyqvq@users.noreply.github.com> Date: Thu, 12 Oct 2023 16:01:50 -0400 Subject: [PATCH] zklogin: add new provider --- .../bn254/unit_tests/zk_login_e2e_tests.rs | 94 ++++++++++++++-- fastcrypto-zkp/src/bn254/utils.rs | 28 ++++- fastcrypto-zkp/src/bn254/zk_login.rs | 26 +++++ fastcrypto-zkp/src/bn254/zk_login_api.rs | 105 +++++++++++++++++- 4 files changed, 236 insertions(+), 17 deletions(-) diff --git a/fastcrypto-zkp/src/bn254/unit_tests/zk_login_e2e_tests.rs b/fastcrypto-zkp/src/bn254/unit_tests/zk_login_e2e_tests.rs index f4304ebdbb..03de0a123e 100644 --- a/fastcrypto-zkp/src/bn254/unit_tests/zk_login_e2e_tests.rs +++ b/fastcrypto-zkp/src/bn254/unit_tests/zk_login_e2e_tests.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::bn254::{ - utils::{gen_address_seed, get_proof, get_salt}, + utils::{gen_address_seed, get_proof}, zk_login::{JwkId, OIDCProvider, ZkLoginInputs, JWK}, zk_login_api::{verify_zk_login, ZkLoginEnv}, }; @@ -11,17 +11,17 @@ use fastcrypto::{ed25519::Ed25519KeyPair, jwt_utils::parse_and_validate_jwt, tra use im::HashMap as ImHashMap; use num_bigint::BigUint; +const PROVER_DEV_SERVER_URL: &str = "https://prover-dev.mystenlabs.com/v1"; + #[tokio::test] async fn test_end_to_end_twitch() { // Use a fixed Twitch token obtained with nonce hTPpgF7XAKbW37rEUS6pEVZqmoI // Derived based on max_epoch = 10, kp generated from seed = [0; 32], and jwt_randomness 100681567828351849884072155819400689117. - // let parsed_token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ.eyJhdWQiOiJyczFiaDA2NWk5eWE0eWR2aWZpeGw0a3NzMHVocHQiLCJleHAiOjE2OTIyODQzMzQsImlhdCI6MTY5MjI4MzQzNCwiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiwic3ViIjoiOTA0NDQ4NjkyIiwiYXpwIjoicnMxYmgwNjVpOXlhNHlkdmlmaXhsNGtzczB1aHB0Iiwibm9uY2UiOiJoVFBwZ0Y3WEFLYlczN3JFVVM2cEVWWnFtb0kiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJqb3lxdnEifQ.M54Sgs6aDu5Mprs_CgXeRbgiErC7oehj-h9oEcBqZFDADwd09zs9hbfDPqUjaNBB-_I6G7kn9e-zwPov8PUecI68kr3oyiCMWhKD-3h1FEu13MZv71B6jhIDMu1_UgI-RSrOQMRvdI8eL3qqD-KsvJuJH1Sz0w56PnB0xupUg-eSvgnMBAo6iTa0t1grX9qGy7U00i_oqn9J4jVGVVEbMhUWROJMjowWdOogJ4_VNqm67JHd_rMZ3xtjLabP6Nk1Gx-VjUbYceNADWUr5xpJveRtvb1FJvd0HSN4mab51zuSUnavCQw2OXbyoH8j6uuQAAKVhG-_Ht1hCvReycGXKw"; let parsed_token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ.eyJhdWQiOiJyczFiaDA2NWk5eWE0eWR2aWZpeGw0a3NzMHVocHQiLCJleHAiOjE2OTIyODQzMzQsImlhdCI6MTY5MjI4MzQzNCwiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiwic3ViIjoiOTA0NDQ4NjkyIiwiYXpwIjoicnMxYmgwNjVpOXlhNHlkdmlmaXhsNGtzczB1aHB0Iiwibm9uY2UiOiJoVFBwZ0Y3WEFLYlczN3JFVVM2cEVWWnFtb0kiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJqb3lxdnEifQ.M54Sgs6aDu5Mprs_CgXeRbgiErC7oehj-h9oEcBqZFDADwd09zs9hbfDPqUjaNBB-_I6G7kn9e-zwPov8PUecI68kr3oyiCMWhKD-3h1FEu13MZv71B6jhIDMu1_UgI-RSrOQMRvdI8eL3qqD-KsvJuJH1Sz0w56PnB0xupUg-eSvgnMBAo6iTa0t1grX9qGy7U00i_oqn9J4jVGVVEbMhUWROJMjowWdOogJ4_VNqm67JHd_rMZ3xtjLabP6Nk1Gx-VjUbYceNADWUr5xpJveRtvb1FJvd0HSN4mab51zuSUnavCQw2OXbyoH8j6uuQAAKVhG-_Ht1hCvReycGXKw"; let max_epoch = 10; let jwt_randomness = "100681567828351849884072155819400689117"; - - // Get salt based on the Twitch token. - let user_salt = get_salt(parsed_token).await.unwrap(); + // A dummy salt + let user_salt = "129390038577185583942388216820280642146"; // Generate an ephermeral key pair. let kp = Ed25519KeyPair::generate(&mut StdRng::from_seed([0; 32])); @@ -35,13 +35,14 @@ async fn test_end_to_end_twitch() { max_epoch, jwt_randomness, &kp_bigint, - &user_salt, + user_salt, + PROVER_DEV_SERVER_URL, ) .await .unwrap(); let (sub, aud) = parse_and_validate_jwt(parsed_token).unwrap(); // Get the address seed. - let address_seed = gen_address_seed(&user_salt, "sub", &sub, &aud).unwrap(); + let address_seed = gen_address_seed(user_salt, "sub", &sub, &aud).unwrap(); let zk_login_inputs = ZkLoginInputs::from_reader(reader, &address_seed).unwrap(); // Make a map of jwk ids to jwks just for Twitch. let mut map = ImHashMap::new(); @@ -58,13 +59,90 @@ async fn test_end_to_end_twitch() { }, ); - // Verify it against final vk. + // Verify it against test vk ok. let res = verify_zk_login( + &zk_login_inputs, + max_epoch, + &eph_pubkey, + &map, + &ZkLoginEnv::Test, + ); + assert!(res.is_ok()); + + // Verify it against prod vk fails. + let res_prod = verify_zk_login( &zk_login_inputs, max_epoch, &eph_pubkey, &map, &ZkLoginEnv::Prod, ); + assert!(res_prod.is_err()); +} + +#[tokio::test] +async fn test_end_to_end_kakao() { + // Use a fixed Kako token obtained with nonce hTPpgF7XAKbW37rEUS6pEVZqmoI + // Derived based on max_epoch = 10, kp generated from seed = [0; 32], and jwt_randomness 100681567828351849884072155819400689117. + let parsed_token = "eyJraWQiOiI5ZjI1MmRhZGQ1ZjIzM2Y5M2QyZmE1MjhkMTJmZWEiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJhYTZiZGRmMzkzYjU0ZDRlMGQ0MmFlMDAxNGVkZmQyZiIsInN1YiI6IjMwOTUxMzQzODkiLCJhdXRoX3RpbWUiOjE2OTcxNDYwMjIsImlzcyI6Imh0dHBzOi8va2F1dGgua2FrYW8uY29tIiwiZXhwIjoxNjk3MTY3NjIyLCJpYXQiOjE2OTcxNDYwMjIsIm5vbmNlIjoiaFRQcGdGN1hBS2JXMzdyRVVTNnBFVlpxbW9JIn0.ICP5Fz4Ves7HoFOixwvBeQSYBLWxFPtN6QTnMIv9d9zYnfkaXJ9VyqnaEE3BzY3dzHeWgKFps5Dmrm8Vn4WLmeRAvxDz7831g8Ln8-krTHIUcLzi91NGUPPyx6bIkCzxTqhIB4omatvXD7vAf_AlsqJJYMOIvLQxdpRq8-d_JyAfELE_aWVatXSwGIBYIi_91CEZ64nsHV1J4Wz_tVFc5vbPT4wZabBzepMPXcNHVtrtkuW96nWNygbpap1mSz4fEP9mdlTD2Oi2FHD2cX3rebqiEYTeZI5HySzo4NcN_4TcIgf5cFSapyglqCuulFBXCkIkF9lKN3Il6yJ9MD_N4w"; + let max_epoch = 10; + let jwt_randomness = "100681567828351849884072155819400689117"; + // A dummy salt + let user_salt = "129390038577185583942388216820280642146"; + + // Generate an ephermeral key pair. + let kp = Ed25519KeyPair::generate(&mut StdRng::from_seed([0; 32])); + let mut eph_pubkey = vec![0x00]; + eph_pubkey.extend(kp.public().as_ref()); + let kp_bigint = BigUint::from_bytes_be(&eph_pubkey).to_string(); + + // Get a proof from endpoint and serialize it. + let reader = get_proof( + parsed_token, + max_epoch, + jwt_randomness, + &kp_bigint, + user_salt, + PROVER_DEV_SERVER_URL, + ) + .await + .unwrap(); + let (sub, aud) = parse_and_validate_jwt(parsed_token).unwrap(); + // Get the address seed. + let address_seed = gen_address_seed(user_salt, "sub", &sub, &aud).unwrap(); + let zk_login_inputs = ZkLoginInputs::from_reader(reader, &address_seed).unwrap(); + // Make a map of jwk ids to jwks just for Twitch. + let mut map = ImHashMap::new(); + map.insert( + JwkId::new( + OIDCProvider::Kakao.get_config().iss, + "9f252dadd5f233f93d2fa528d12fea".to_string(), + ), + JWK { + kty: "RSA".to_string(), + e: "AQAB".to_string(), + n: "qGWf6RVzV2pM8YqJ6by5exoixIlTvdXDfYj2v7E6xkoYmesAjp_1IYL7rzhpUYqIkWX0P4wOwAsg-Ud8PcMHggfwUNPOcqgSk1hAIHr63zSlG8xatQb17q9LrWny2HWkUVEU30PxxHsLcuzmfhbRx8kOrNfJEirIuqSyWF_OBHeEgBgYjydd_c8vPo7IiH-pijZn4ZouPsEg7wtdIX3-0ZcXXDbFkaDaqClfqmVCLNBhg3DKYDQOoyWXrpFKUXUFuk2FTCqWaQJ0GniO4p_ppkYIf4zhlwUYfXZEhm8cBo6H2EgukntDbTgnoha8kNunTPekxWTDhE5wGAt6YpT4Yw".to_string(), + alg: "RS256".to_string(), + }, + ); + + // Verify it against test vk ok. + let res = verify_zk_login( + &zk_login_inputs, + max_epoch, + &eph_pubkey, + &map, + &ZkLoginEnv::Test, + ); assert!(res.is_ok()); + + // Verify it against prod vk fails. + let res_prod = verify_zk_login( + &zk_login_inputs, + max_epoch, + &eph_pubkey, + &map, + &ZkLoginEnv::Prod, + ); + assert!(res_prod.is_err()); } diff --git a/fastcrypto-zkp/src/bn254/utils.rs b/fastcrypto-zkp/src/bn254/utils.rs index 4c97e94dbd..d15d44d230 100644 --- a/fastcrypto-zkp/src/bn254/utils.rs +++ b/fastcrypto-zkp/src/bn254/utils.rs @@ -17,8 +17,6 @@ use std::str::FromStr; use super::zk_login::{hash_ascii_str_to_field, to_field}; const ZK_LOGIN_AUTHENTICATOR_FLAG: u8 = 0x05; -const SALT_SERVER_URL: &str = "https://salt.api.mystenlabs.com/get_salt"; -const PROVER_SERVER_URL: &str = "https://prover.mystenlabs.com/v1"; const MAX_KEY_CLAIM_NAME_LENGTH: u8 = 32; const MAX_KEY_CLAIM_VALUE_LENGTH: u8 = 115; const MAX_AUD_VALUE_LENGTH: u8 = 145; @@ -77,7 +75,24 @@ pub fn get_oidc_url( 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), + OIDCProvider::Kakao => format!("https://kauth.kakao.com/oauth/authorize?response_type=code&client_id={}&redirect_uri={}&nonce={}", client_id, redirect_url, nonce), + OIDCProvider::Apple => format!("https://appleid.apple.com/auth/authorize?response_type=code&client_id={}&redirect_uri={}&nonce={}", client_id, redirect_url, nonce), + OIDCProvider::Slack => format!("https://slack.com/openid/connect/authorize?response_type=code&client_id={}&redirect_uri={}&nonce={}", client_id, redirect_url, nonce) + }) +} + +/// Return the token exchange URL for the given auth code. +pub fn get_token_exchange_url( + provider: OIDCProvider, + client_id: &str, + redirect_url: &str, + auth_code: &str, +) -> Result { + match provider { + OIDCProvider::Kakao => Ok(format!("https://kauth.kakao.com/oauth/token?grant_type=authorization_code&client_id={}&redirect_uri={}&code={}", client_id, redirect_url, auth_code)), + _ => Err(FastCryptoError::InvalidInput) + } } /// Calculate the nonce for the given parameters. Nonce is defined as the Base64Url encoded of the poseidon hash of 4 inputs: @@ -114,11 +129,11 @@ pub struct GetSaltResponse { } /// Call the salt server for the given jwt_token and return the salt. -pub async fn get_salt(jwt_token: &str) -> Result { +pub async fn get_salt(jwt_token: &str, salt_url: &str) -> Result { let client = Client::new(); let body = json!({ "token": jwt_token }); let response = client - .post(SALT_SERVER_URL) + .post(salt_url) .json(&body) .header("Content-Type", "application/json") .send() @@ -140,6 +155,7 @@ pub async fn get_proof( jwt_randomness: &str, eph_pubkey: &str, salt: &str, + prover_url: &str, ) -> Result { let body = json!({ "jwt": jwt_token, @@ -151,7 +167,7 @@ pub async fn get_proof( }); let client = Client::new(); let response = client - .post(PROVER_SERVER_URL.to_string()) + .post(prover_url.to_string()) .header("Content-Type", "application/json") .json(&body) .send() diff --git a/fastcrypto-zkp/src/bn254/zk_login.rs b/fastcrypto-zkp/src/bn254/zk_login.rs index f16a9fc665..ce0257dba4 100644 --- a/fastcrypto-zkp/src/bn254/zk_login.rs +++ b/fastcrypto-zkp/src/bn254/zk_login.rs @@ -87,6 +87,12 @@ pub enum OIDCProvider { Twitch, /// See https://www.facebook.com/.well-known/openid-configuration/ Facebook, + /// See https://kauth.kakao.com/.well-known/openid-configuration + Kakao, + /// See https://appleid.apple.com/.well-known/openid-configuration + Apple, + /// See https://slack.com/.well-known/openid-configuration + Slack, } impl FromStr for OIDCProvider { @@ -97,6 +103,9 @@ impl FromStr for OIDCProvider { "Google" => Ok(Self::Google), "Twitch" => Ok(Self::Twitch), "Facebook" => Ok(Self::Facebook), + "Kakao" => Ok(Self::Kakao), + "Apple" => Ok(Self::Apple), + "Slack" => Ok(Self::Slack), _ => Err(FastCryptoError::InvalidInput), } } @@ -108,6 +117,9 @@ impl ToString for OIDCProvider { Self::Google => "Google".to_string(), Self::Twitch => "Twitch".to_string(), Self::Facebook => "Facebook".to_string(), + Self::Kakao => "Kakao".to_string(), + Self::Apple => "Apple".to_string(), + Self::Slack => "Slack".to_string(), } } } @@ -128,6 +140,17 @@ impl OIDCProvider { "https://www.facebook.com", "https://www.facebook.com/.well-known/oauth/openid/jwks/", ), + OIDCProvider::Kakao => ProviderConfig::new( + "https://kauth.kakao.com", + "https://kauth.kakao.com/.well-known/jwks.json", + ), + OIDCProvider::Apple => ProviderConfig::new( + "https://appleid.apple.com", + "https://appleid.apple.com/auth/keys", + ), + OIDCProvider::Slack => { + ProviderConfig::new("https://slack.com", "https://slack.com/openid/connect/keys") + } } } @@ -137,6 +160,9 @@ impl OIDCProvider { "https://accounts.google.com" => Ok(Self::Google), "https://id.twitch.tv/oauth2" => Ok(Self::Twitch), "https://www.facebook.com" => Ok(Self::Facebook), + "https://kauth.kakao.com" => Ok(Self::Kakao), + "https://appleid.apple.com" => Ok(Self::Apple), + "https://slack.com" => Ok(Self::Slack), _ => Err(FastCryptoError::InvalidInput), } } diff --git a/fastcrypto-zkp/src/bn254/zk_login_api.rs b/fastcrypto-zkp/src/bn254/zk_login_api.rs index f89ddedfeb..abb8e2be0c 100644 --- a/fastcrypto-zkp/src/bn254/zk_login_api.rs +++ b/fastcrypto-zkp/src/bn254/zk_login_api.rs @@ -32,9 +32,104 @@ impl Default for ZkLoginEnv { } } -/// Produced from ceremony. Secure to use for mainnet. +/// Corresponding to proofs generated from prover (prod). Produced from ceremony. Secure to use for mainnet. static GLOBAL_VERIFYING_KEY: Lazy> = Lazy::new(global_pvk); +/// Corresponding to proofs generated from prover-dev. Used in devnet/testnet. +static INSECURE_VERIFYING_KEY: Lazy> = Lazy::new(insecure_pvk); + +/// Load a fixed verifying key from zkLogin.vkey output. This is based on a local setup and should not use in production. +fn insecure_pvk() -> PreparedVerifyingKey { + // Convert the Circom G1/G2/GT to arkworks G1/G2/GT + let vk_alpha_1 = g1_affine_from_str_projective(&vec![ + "20491192805390485299153009773594534940189261866228447918068658471970481763042".to_string(), + "9383485363053290200918347156157836566562967994039712273449902621266178545958".to_string(), + "1".to_string(), + ]) + .unwrap(); + 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()], + ]) + .unwrap(); + 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()], + ]) + .unwrap(); + 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()], + ]) + .unwrap(); + + // Create a vector of G1Affine elements from the IC + let mut vk_gamma_abc_g1 = Vec::new(); + for e in vec![ + vec![ + "20701306374481714853949730154526815782802808896228594855451770849676897643964" + .to_string(), + "2766989084754673216772682210231588284954002353414778477810174100808747060165" + .to_string(), + "1".to_string(), + ], + vec![ + "501195541410525737371980194958674422793469475773065719916327137354779402600" + .to_string(), + "13527631693157515024233848630878973193664410306029731429350155106228769355415" + .to_string(), + "1".to_string(), + ], + ] { + let g1 = g1_affine_from_str_projective(&e).unwrap(); + 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, + }; + + // Convert the verifying key into the prepared form. + process_vk_special(&Bn254VerifyingKey(vk)).as_arkworks_pvk() +} + /// 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 @@ -163,11 +258,15 @@ pub fn verify_zk_login( /// Verify a proof against its public inputs using the fixed verifying key. pub fn verify_zk_login_proof_with_fixed_vk( - _usage: &ZkLoginEnv, + usage: &ZkLoginEnv, proof: &Proof, public_inputs: &[Bn254Fr], ) -> Result { - Groth16::::verify_with_processed_vk(&GLOBAL_VERIFYING_KEY, public_inputs, proof) + let vk = match usage { + ZkLoginEnv::Prod => &GLOBAL_VERIFYING_KEY, + ZkLoginEnv::Test => &INSECURE_VERIFYING_KEY, + }; + Groth16::::verify_with_processed_vk(vk, public_inputs, proof) .map_err(|e| FastCryptoError::GeneralError(e.to_string())) }