Skip to content

Commit

Permalink
zklogin: add new provider
Browse files Browse the repository at this point in the history
  • Loading branch information
joyqvq committed Oct 19, 2023
1 parent 05fadba commit 24ac4d8
Show file tree
Hide file tree
Showing 6 changed files with 337 additions and 91 deletions.
163 changes: 134 additions & 29 deletions fastcrypto-zkp/src/bn254/unit_tests/zk_login_e2e_tests.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use std::env;

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},
};
Expand All @@ -11,38 +13,14 @@ 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();

// 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,
)
.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();
let (max_epoch, eph_pubkey, zk_login_inputs) = get_test_inputs(parsed_token).await;
// Make a map of jwk ids to jwks just for Twitch.
let mut map = ImHashMap::new();
map.insert(
Expand All @@ -58,13 +36,140 @@ 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 Kakao 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, eph_pubkey, zk_login_inputs) = get_test_inputs(parsed_token).await;

// 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());
}

#[tokio::test]
async fn test_end_to_end_apple() {
// Use a fixed Apple token obtained with nonce hTPpgF7XAKbW37rEUS6pEVZqmoI
// Derived based on max_epoch = 10, kp generated from seed = [0; 32], and jwt_randomness 100681567828351849884072155819400689117.
let parsed_token = "eyJraWQiOiJXNldjT0tCIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoibmwuZGlna2FzLndhbGxldC5jbGllbnQiLCJleHAiOjE2OTc4MjEwNzQsImlhdCI6MTY5NzczNDY3NCwic3ViIjoiMDAxMzkzLjc0YTEzNTRlZjc0YjRiOGViMWQyMDdkMzRkNzE2OGQ2LjE2MjkiLCJub25jZSI6ImhUUHBnRjdYQUtiVzM3ckVVUzZwRVZacW1vSSIsImNfaGFzaCI6Inl4dlh3Y1VXaHFUa1dpazQtQWh1UXciLCJhdXRoX3RpbWUiOjE2OTc3MzQ2NzQsIm5vbmNlX3N1cHBvcnRlZCI6dHJ1ZX0.LmGVSJY8rOpvsNob4fEqUecm_Y1ZitbW3lIK64f2QjgNUqnIpkO5sV0wXlVzlRWwGI4k3qURbwtTQO7Dw7kORaQIhlLzA1cZNHU22aXdQyQ9FIHPFgQecuudk-_0dvHB1IqhGsmvLv_qLJBQiuB7MGztVeZsgDYtXFs4dw04LCht0DNTEh_ihBRcJZkxHR9K13ItDiVUH5fLIRlfT70VgZWNuaGkKYfxeWg9nMD6medJU7VawWvXPt48YGtxIYcZqv6hlZwW14qGx-F2qg64NWjCSqwdBk5wqyhzpJdnErP79ESgGxpskNIZNn1JEzspJtgAS7Pmc0peV0hyg9FHtg";
// Make a map of jwk ids to jwks just for Apple.
let (max_epoch, eph_pubkey, zk_login_inputs) = get_test_inputs(parsed_token).await;
let mut map = ImHashMap::new();
map.insert(
JwkId::new(
OIDCProvider::Apple.get_config().iss,
"W6WcOKB".to_string(),
),
JWK {
kty: "RSA".to_string(),
e: "AQAB".to_string(),
n: "2Zc5d0-zkZ5AKmtYTvxHc3vRc41YfbklflxG9SWsg5qXUxvfgpktGAcxXLFAd9Uglzow9ezvmTGce5d3DhAYKwHAEPT9hbaMDj7DfmEwuNO8UahfnBkBXsCoUaL3QITF5_DAPsZroTqs7tkQQZ7qPkQXCSu2aosgOJmaoKQgwcOdjD0D49ne2B_dkxBcNCcJT9pTSWJ8NfGycjWAQsvC8CGstH8oKwhC5raDcc2IGXMOQC7Qr75d6J5Q24CePHj_JD7zjbwYy9KNH8wyr829eO_G4OEUW50FAN6HKtvjhJIguMl_1BLZ93z2KJyxExiNTZBUBQbbgCNBfzTv7JrxMw".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());
}

async fn get_test_inputs(parsed_token: &str) -> (u64, Vec<u8>, ZkLoginInputs) {
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();
let url = &env::var("URL").unwrap_or_else(|_| PROVER_DEV_SERVER_URL.to_owned());
println!("using URL: {:?}", url);

// Get a proof from endpoint and serialize it.
let reader = get_proof(
parsed_token,
max_epoch,
jwt_randomness,
&kp_bigint,
user_salt,
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();
(max_epoch, eph_pubkey, zk_login_inputs)
}
6 changes: 3 additions & 3 deletions fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::bn254::utils::{
};
use crate::bn254::zk_login::{
convert_base, decode_base64_url, hash_ascii_str_to_field, hash_to_field, parse_jwks, to_field,
trim, verify_extended_claim, Claim, JWTDetails, JWTHeader, JwkId,
trim, verify_extended_claim, Claim, JWTDetails, JwkId,
};
use crate::bn254::zk_login::{fetch_jwks, OIDCProvider};
use crate::bn254::zk_login_api::ZkLoginEnv;
Expand All @@ -23,6 +23,7 @@ use ark_std::rand::SeedableRng;
use fastcrypto::ed25519::Ed25519KeyPair;
use fastcrypto::encoding::{Encoding, Hex};
use fastcrypto::error::FastCryptoError;
use fastcrypto::jwt_utils::JWTHeader;
use fastcrypto::traits::KeyPair;
use im::hashmap::HashMap as ImHashMap;
use num_bigint::BigUint;
Expand Down Expand Up @@ -146,8 +147,7 @@ async fn test_verify_zk_login_google() {
#[test]
fn test_parse_jwt_details() {
let header = JWTHeader::new("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ").unwrap();
assert_eq!(header.alg, "RS256");
assert_eq!(header.typ, "JWT");
assert_eq!(header.kid, "1");

// Invalid base64
assert_eq!(
Expand Down
28 changes: 22 additions & 6 deletions fastcrypto-zkp/src/bn254/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)

Check warning on line 81 in fastcrypto-zkp/src/bn254/utils.rs

View check run for this annotation

Codecov / codecov/patch

fastcrypto-zkp/src/bn254/utils.rs#L78-L81

Added lines #L78 - L81 were not covered by tests
})
}

Check warning on line 83 in fastcrypto-zkp/src/bn254/utils.rs

View check run for this annotation

Codecov / codecov/patch

fastcrypto-zkp/src/bn254/utils.rs#L83

Added line #L83 was not covered by tests

/// 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<String, FastCryptoError> {
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)

Check warning on line 94 in fastcrypto-zkp/src/bn254/utils.rs

View check run for this annotation

Codecov / codecov/patch

fastcrypto-zkp/src/bn254/utils.rs#L86-L94

Added lines #L86 - L94 were not covered by tests
}
}

/// Calculate the nonce for the given parameters. Nonce is defined as the Base64Url encoded of the poseidon hash of 4 inputs:
Expand Down Expand Up @@ -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<String, FastCryptoError> {
pub async fn get_salt(jwt_token: &str, salt_url: &str) -> Result<String, FastCryptoError> {

Check warning on line 132 in fastcrypto-zkp/src/bn254/utils.rs

View check run for this annotation

Codecov / codecov/patch

fastcrypto-zkp/src/bn254/utils.rs#L132

Added line #L132 was not covered by tests
let client = Client::new();
let body = json!({ "token": jwt_token });
let response = client
.post(SALT_SERVER_URL)
.post(salt_url)

Check warning on line 136 in fastcrypto-zkp/src/bn254/utils.rs

View check run for this annotation

Codecov / codecov/patch

fastcrypto-zkp/src/bn254/utils.rs#L136

Added line #L136 was not covered by tests
.json(&body)
.header("Content-Type", "application/json")
.send()
Expand All @@ -140,6 +155,7 @@ pub async fn get_proof(
jwt_randomness: &str,
eph_pubkey: &str,
salt: &str,
prover_url: &str,
) -> Result<ZkLoginInputsReader, FastCryptoError> {
let body = json!({
"jwt": jwt_token,
Expand All @@ -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()
Expand Down
Loading

0 comments on commit 24ac4d8

Please sign in to comment.