Skip to content

Commit

Permalink
fix: move supported providers outside fastcrypto
Browse files Browse the repository at this point in the history
  • Loading branch information
joyqvq committed Aug 16, 2023
1 parent 928b565 commit d7c5de4
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 48 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion fastcrypto-zkp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ schemars ="0.8.10"
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 = "17028e86f42d6423304212a35b4f63e75e85c052" }
poseidon-ark = { git = "https://github.com/arnaucube/poseidon-ark.git", rev = "ff7f5e05d55667b4ffba129b837da780c4c5c849" }
reqwest = "0.11.18"
bcs = "0.1.4"

Expand Down
96 changes: 84 additions & 12 deletions fastcrypto-zkp/src/bn254/unit_tests/zk_login_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use std::collections::HashMap;
use std::str::FromStr;

use crate::bn254::utils::get_nonce;
use crate::bn254::utils::{get_enoki_address, get_nonce};
use crate::bn254::zk_login::{
decode_base64_url, hash_ascii_str_to_field, hash_to_field, parse_jwks, trim,
verify_extended_claim, Claim, JWTDetails, JWTHeader,
Expand All @@ -18,8 +18,9 @@ use crate::bn254::{
use ark_std::rand::rngs::StdRng;
use ark_std::rand::SeedableRng;
use fastcrypto::ed25519::Ed25519KeyPair;
use fastcrypto::encoding::{Encoding, Hex};
use fastcrypto::error::FastCryptoError;
use fastcrypto::rsa::{Base64UrlUnpadded, Encoding};
use fastcrypto::rsa::{Base64UrlUnpadded, Encoding as OtherEncoding};
use fastcrypto::traits::KeyPair;
use num_bigint::BigUint;

Expand Down Expand Up @@ -117,6 +118,15 @@ fn test_verify_zk_login_google() {
zklogin_inputs.get_address_seed(),
"15909817818955140159551330044992054478592339431921061309213971300613403932293"
);
assert_eq!(
get_enoki_address(
zklogin_inputs.get_address_seed(),
zklogin_inputs.get_address_params()
)
.to_vec(),
Hex::decode("0x504aade5d02308b1b7e58775adde9e1316f71898e2996c94ddd668fd559cdf32").unwrap()
);

let mut map = HashMap::new();
let content = JWK {
kty: "RSA".to_string(),
Expand Down Expand Up @@ -172,7 +182,15 @@ fn test_verify_zk_login_twitch() {
);
assert_eq!(
zklogin_inputs.get_address_seed(),
"15454157267374145582481438333218897413377268576773635507925280300337575904853"
"1064534367616880708177461579252402929321057733203290795002254681379647205957"
);
assert_eq!(
get_enoki_address(
zklogin_inputs.get_address_seed(),
zklogin_inputs.get_address_params()
)
.to_vec(),
Hex::decode("0xbec7f14a8016c8cf8a144e200a48249780fdd62aeca4f917c76f8cad9f11db28").unwrap()
);

let mut map = HashMap::new();
Expand All @@ -188,7 +206,51 @@ fn test_verify_zk_login_twitch() {

#[test]
fn test_verify_zk_login_facebook() {
// TODO
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\":[\"12804639313047315229787728550743383055078739729474025185171491479002145483171\",\"12830011673789922061300240800397034741817891641854768173828021227521530843448\",\"1\"],\"pi_b\":[[\"12625644431344210454514914338194425801967485404149473212699164171827922193834\",\"1376335697052080146785724838188372313238898234957938765859569854030561125069\"],[\"1399303357894043099804381830908396300387243736777873837862325665862145529031\",\"21567422565189038682340366949968745550198992006203892724710357962026885135547\"],[\"1\",\"0\"]],\"pi_c\":[\"10137234384702751217930346226450479491602558029071930705038616408451583910940\",\"356067124413987422716339940579154050383939937649006203945143134848901339386\",\"1\"]},\"address_seed\":\"9170870217795363726833321704645580846260479365166849913550847438937458025900\",\"claims\":[{\"name\":\"iss\",\"value_base64\":\"yJpc3MiOiJodHRwczpcL1wvd3d3LmZhY2Vib29rLmNvbSIs\",\"index_mod_4\":1},{\"name\":\"aud\",\"value_base64\":\"ImF1ZCI6IjIzMzMwNzE1NjM1MjkxNyIs\",\"index_mod_4\":0}],\"header_base64\":\"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjU5MzE3MDEzMzExNjVmMDdmNTUwYWM1ZjAxOTQ5NDJkNTRmOWMyNDkifQ\"}").unwrap().init().unwrap();
assert_eq!(
zklogin_inputs.get_kid(),
"5931701331165f07f550ac5f0194942d54f9c249".to_string()
);
assert_eq!(
zklogin_inputs.get_iss(),
OIDCProvider::Facebook.get_config().0.to_string()
);
assert_eq!(zklogin_inputs.get_aud(), "233307156352917".to_string());
assert_eq!(
zklogin_inputs.get_address_params().aud,
"233307156352917".to_string()
);
assert_eq!(
zklogin_inputs.get_address_params().iss,
zklogin_inputs.get_iss()
);
assert_eq!(
zklogin_inputs.get_address_seed(),
"9170870217795363726833321704645580846260479365166849913550847438937458025900"
);

assert_eq!(
get_enoki_address(
zklogin_inputs.get_address_seed(),
zklogin_inputs.get_address_params()
)
.to_vec(),
Hex::decode("5b10433166b4c4a32fcac2b3d073d90d4e0ad6c4bd33f79f982cc46d5b963e5c").unwrap()
);

let mut map = HashMap::new();
map.insert(("5931701331165f07f550ac5f0194942d54f9c249".to_string(), OIDCProvider::Facebook.get_config().0.to_string()), JWK {
kty: "RSA".to_string(),
e: "AQAB".to_string(),
n: "-GuAIboTsRYNprJQOkdmuKXRx8ARnKXOC9Pajg4KxHHPt3OY8rXRmVeDxTj1-m9TfW6V-wJa_8ncBbbFE-aV-eBi_XeuIToBBvLZp1-UPIjitS8WCDrUhHiJnbvkIZf1B1YBIq_Ua81fzxhtjQ0jDftV2m5aavmJG4_94VG3Md7noQjjUKzxJyUNl4v_joMA6pIRCeeamvfIZorjcR4wVf-wR8NiZjjRbcjKBpc7ztc7Gm778h34RSe9-DLH6uicTROSYNa99pUwhn3XVfAv4hTFpLIcgHYadLZjsHfUvivr76uiYbxDZx6UTkK5jmi51b87u1b6iYmijDIMztzrIQ".to_string(),
alg: "RS256".to_string(),
});
let res = verify_zk_login(&zklogin_inputs, 10, &eph_pubkey, &map, Environment::Test);
assert!(res.is_ok());
}

#[test]
Expand Down Expand Up @@ -379,44 +441,54 @@ fn test_jwk_parse() {
"wYvSKSQYKnGNV72_uVc9jbyUeTMsMbUgZPP0uVQX900To7A8a0XA3O17wuImgOG_BwGkpZrIRXF_RRYSK8IOH8N_ViTWh1vyEYSYwr_jfCpDoedJT0O6TZpBhBSmimtmO8ZBCkhZJ4w0AFNIMDPhMokbxwkEapjMA5zio_06dKfb3OBNmrwedZY86W1204-Pfma9Ih15Dm4o8SNFo5Sl0NNO4Ithvj2bbg1Bz1ydE4lMrXdSQL5C2uM9JYRJLnIjaYopBENwgf2Egc9CdVY8tr8jED-WQB6bcUBhDV6lJLZbpBlTHLkF1RlEMnIV2bDo02CryjThnz8l_-6G_7pJww"
);

parse_jwks(GOOGLE_JWK_BYTES, OIDCProvider::Google)
parse_jwks(GOOGLE_JWK_BYTES, &OIDCProvider::Google)
.unwrap()
.iter()
.for_each(|content| {
assert_eq!(content.0 .1, OIDCProvider::Google.get_config().0);
});

parse_jwks(TWITCH_JWK_BYTES, OIDCProvider::Twitch)
parse_jwks(TWITCH_JWK_BYTES, &OIDCProvider::Twitch)
.unwrap()
.iter()
.for_each(|content| {
assert_eq!(content.0 .1, OIDCProvider::Twitch.get_config().0);
});

parse_jwks(FACEBOOK_JWK_BYTES, OIDCProvider::Facebook)
parse_jwks(FACEBOOK_JWK_BYTES, &OIDCProvider::Facebook)
.unwrap()
.iter()
.for_each(|content| {
assert_eq!(content.0 .1, OIDCProvider::Facebook.get_config().0);
});

assert!(parse_jwks(BAD_JWK_BYTES, OIDCProvider::Twitch).is_err());
assert!(parse_jwks(BAD_JWK_BYTES, &OIDCProvider::Twitch).is_err());

assert!(parse_jwks(
r#"{
"something":[]
}"#
.as_bytes(),
OIDCProvider::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());
let client = reqwest::Client::new();
for p in [
OIDCProvider::Facebook,
OIDCProvider::Google,
OIDCProvider::Twitch,
] {
let res = fetch_jwks(&p, &client).await;
assert!(res.is_ok());
res.unwrap().iter().for_each(|e| {
assert_eq!(e.0 .1, p.get_config().0);
assert_eq!(e.1.alg, "RS256".to_string());
});
}
}

#[test]
Expand Down
4 changes: 2 additions & 2 deletions fastcrypto-zkp/src/bn254/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ use std::str::FromStr;
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] {
pub fn get_enoki_address(address_seed: &str, 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.update(big_int_str_to_bytes(address_seed));
hasher.finalize().digest
}

Expand Down
48 changes: 20 additions & 28 deletions fastcrypto-zkp/src/bn254/zk_login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

use fastcrypto::error::FastCryptoResult;
use reqwest::Client;
use serde_json::Value;

use super::{
Expand Down Expand Up @@ -36,7 +37,7 @@ 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.
/// Supported OIDC providers.
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum OIDCProvider {
/// See https://accounts.google.com/.well-known/openid-configuration
Expand All @@ -47,10 +48,9 @@ pub enum OIDCProvider {
Facebook,
}

/// Struct that contains all the OAuth provider information. A list of them can
/// Struct that contains all the OIDC provider's JWK. A list of them can
/// be retrieved from the JWK endpoint (e.g. <https://www.googleapis.com/oauth2/v3/certs>)
/// and published on the bulletin along with a trusted party's signature.
// #[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Hash, Serialize, Deserialize)]
#[derive(Hash, Debug, Clone, Serialize, Deserialize)]
pub struct JWK {
/// Key type parameter, https://datatracker.ietf.org/doc/html/rfc7517#section-4.1
Expand All @@ -63,8 +63,7 @@ pub struct JWK {
pub alg: String,
}

/// Reader struct to parse all fields.
// #[derive(Debug, Clone, PartialEq, Eq, JsonSchema, Hash, Serialize, Deserialize)]
/// Reader struct to parse all fields in a JWK from JSON.
#[derive(Debug, Serialize, Deserialize)]
pub struct JWKReader {
e: String,
Expand Down Expand Up @@ -101,35 +100,28 @@ 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<ParsedJWKs, FastCryptoError> {
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)
/// Fetch JWKs from the given provider and return the list as ((iss, kid), JWK)
pub async fn fetch_jwks(
provider: &OIDCProvider,
client: &Client,
) -> Result<ParsedJWKs, FastCryptoError> {
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()))?;
parse_jwks(&bytes, provider)
}

/// Parse the JWK bytes received from the oauth provider keys endpoint into a map from kid to
/// JWK.
pub fn parse_jwks(
json_bytes: &[u8],
provider: OIDCProvider,
provider: &OIDCProvider,
) -> Result<ParsedJWKs, FastCryptoError> {
let json_str = String::from_utf8_lossy(json_bytes);
let parsed_list: Result<serde_json::Value, serde_json::Error> = serde_json::from_str(&json_str);
Expand Down
7 changes: 4 additions & 3 deletions fastcrypto-zkp/src/bn254/zk_login_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,10 @@ pub fn verify_zk_login(
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()))?;
let (kid, iss) = (input.get_kid().to_string(), input.get_iss().to_string());
let jwk = all_jwk.get(&(kid.clone(), iss.clone())).ok_or_else(|| {
FastCryptoError::GeneralError(format!("JWK not found ({} - {})", kid, iss))
})?;

// Decode modulus to bytes.
let modulus = Base64UrlUnpadded::decode_vec(&jwk.n).map_err(|_| {
Expand Down

0 comments on commit d7c5de4

Please sign in to comment.