sui_crypto/zklogin/
verify.rs

1use std::str::FromStr;
2
3use crate::SignatureError;
4use ark_bn254::Fq;
5use ark_bn254::Fq2;
6use ark_bn254::Fr;
7use ark_bn254::G1Affine;
8use ark_bn254::G1Projective;
9use ark_bn254::G2Affine;
10use ark_bn254::G2Projective;
11use ark_ff::PrimeField;
12use ark_groth16::PreparedVerifyingKey;
13use ark_groth16::Proof;
14use sui_sdk_types::Bn254FieldElement;
15use sui_sdk_types::CircomG1;
16use sui_sdk_types::CircomG2;
17use sui_sdk_types::Ed25519PublicKey;
18use sui_sdk_types::Jwk;
19use sui_sdk_types::Secp256k1PublicKey;
20use sui_sdk_types::Secp256r1PublicKey;
21use sui_sdk_types::SimpleSignature;
22use sui_sdk_types::ZkLoginInputs;
23use sui_sdk_types::ZkLoginProof;
24
25use super::POSEIDON;
26
27#[derive(Clone, Debug)]
28pub struct VerifyingKey {
29    inner: PreparedVerifyingKey<ark_bn254::Bn254>,
30}
31
32const fn str_to_bn254(s: &str) -> Bn254FieldElement {
33    match Bn254FieldElement::from_str_radix_10(s) {
34        Ok(e) => e,
35        Err(_) => panic!("unable to convert bn254"),
36    }
37}
38
39const fn build_circom_g1([e0, e1, e2]: [&str; 3]) -> CircomG1 {
40    CircomG1([str_to_bn254(e0), str_to_bn254(e1), str_to_bn254(e2)])
41}
42
43const fn build_circom_g2([[e00, e01], [e10, e11], [e20, e21]]: [[&str; 2]; 3]) -> CircomG2 {
44    CircomG2([
45        [str_to_bn254(e00), str_to_bn254(e01)],
46        [str_to_bn254(e10), str_to_bn254(e11)],
47        [str_to_bn254(e20), str_to_bn254(e21)],
48    ])
49}
50
51fn circom_to_arkworks_g1(g1: &CircomG1) -> Result<G1Affine, SignatureError> {
52    let CircomG1([f0, f1, f2]) = g1;
53
54    let g1: G1Affine =
55        G1Projective::new_unchecked(bn254_to_fq(f0), bn254_to_fq(f1), bn254_to_fq(f2)).into();
56
57    if !g1.is_on_curve() || !g1.is_in_correct_subgroup_assuming_on_curve() {
58        return Err(SignatureError::from_source("invalid G1 input"));
59    }
60
61    Ok(g1)
62}
63
64fn circom_to_arkworks_g2(g2: &CircomG2) -> Result<G2Affine, SignatureError> {
65    let CircomG2([[f00, f01], [f10, f11], [f20, f21]]) = g2;
66
67    let g2: G2Affine = G2Projective::new_unchecked(
68        Fq2::new(bn254_to_fq(f00), bn254_to_fq(f01)),
69        Fq2::new(bn254_to_fq(f10), bn254_to_fq(f11)),
70        Fq2::new(bn254_to_fq(f20), bn254_to_fq(f21)),
71    )
72    .into();
73
74    if !g2.is_on_curve() || !g2.is_in_correct_subgroup_assuming_on_curve() {
75        return Err(SignatureError::from_source("invalid G2 input"));
76    }
77
78    Ok(g2)
79}
80
81fn bn254_to_fq(f: &Bn254FieldElement) -> Fq {
82    Fq::from_be_bytes_mod_order(f.padded())
83}
84
85fn bn254_to_fr(f: &Bn254FieldElement) -> Fr {
86    Fr::from_be_bytes_mod_order(f.padded())
87}
88
89fn mainnet_verifying_key() -> VerifyingKey {
90    const CIRCOM_ALPHA_G1: CircomG1 = build_circom_g1([
91        "21529901943976716921335152104180790524318946701278905588288070441048877064089",
92        "7775817982019986089115946956794180159548389285968353014325286374017358010641",
93        "1",
94    ]);
95
96    const CIRCOM_BETA_G2: CircomG2 = build_circom_g2([
97        [
98            "6600437987682835329040464538375790690815756241121776438004683031791078085074",
99            "16207344858883952201936462217289725998755030546200154201671892670464461194903",
100        ],
101        [
102            "17943105074568074607580970189766801116106680981075272363121544016828311544390",
103            "18339640667362802607939727433487930605412455701857832124655129852540230493587",
104        ],
105        ["1", "0"],
106    ]);
107
108    const CIRCOM_GAMMA_G2: CircomG2 = build_circom_g2([
109        [
110            "10857046999023057135944570762232829481370756359578518086990519993285655852781",
111            "11559732032986387107991004021392285783925812861821192530917403151452391805634",
112        ],
113        [
114            "8495653923123431417604973247489272438418190587263600148770280649306958101930",
115            "4082367875863433681332203403145435568316851327593401208105741076214120093531",
116        ],
117        ["1", "0"],
118    ]);
119
120    const CIRCOM_DELTA_G2: CircomG2 = build_circom_g2([
121        [
122            "19260309516619721648285279557078789954438346514188902804737557357941293711874",
123            "2480422554560175324649200374556411861037961022026590718777465211464278308900",
124        ],
125        [
126            "14489104692423540990601374549557603533921811847080812036788172274404299703364",
127            "12564378633583954025611992187142343628816140907276948128970903673042690269191",
128        ],
129        ["1", "0"],
130    ]);
131
132    const CIRCOM_GAMMA_ABC_G1: [CircomG1; 2] = [
133        build_circom_g1([
134            "1607694606386445293170795095076356565829000940041894770459712091642365695804",
135            "18066827569413962196795937356879694709963206118612267170825707780758040578649",
136            "1",
137        ]),
138        build_circom_g1([
139            "20653794344898475822834426774542692225449366952113790098812854265588083247207",
140            "3296759704176575765409730962060698204792513807296274014163938591826372646699",
141            "1",
142        ]),
143    ];
144
145    let vk = ark_groth16::VerifyingKey {
146        alpha_g1: circom_to_arkworks_g1(&CIRCOM_ALPHA_G1).unwrap(),
147        beta_g2: circom_to_arkworks_g2(&CIRCOM_BETA_G2).unwrap(),
148        gamma_g2: circom_to_arkworks_g2(&CIRCOM_GAMMA_G2).unwrap(),
149        delta_g2: circom_to_arkworks_g2(&CIRCOM_DELTA_G2).unwrap(),
150        gamma_abc_g1: CIRCOM_GAMMA_ABC_G1
151            .iter()
152            .map(circom_to_arkworks_g1)
153            .collect::<Result<_, _>>()
154            .unwrap(),
155    };
156
157    VerifyingKey {
158        inner: PreparedVerifyingKey::from(vk),
159    }
160}
161
162/// Load a fixed verifying key from zkLogin.vkey output. This is based on a local setup and should not use in production.
163fn dev_verifying_key() -> VerifyingKey {
164    const CIRCOM_ALPHA_G1: CircomG1 = build_circom_g1([
165        "20491192805390485299153009773594534940189261866228447918068658471970481763042",
166        "9383485363053290200918347156157836566562967994039712273449902621266178545958",
167        "1",
168    ]);
169
170    const CIRCOM_BETA_G2: CircomG2 = build_circom_g2([
171        [
172            "6375614351688725206403948262868962793625744043794305715222011528459656738731",
173            "4252822878758300859123897981450591353533073413197771768651442665752259397132",
174        ],
175        [
176            "10505242626370262277552901082094356697409835680220590971873171140371331206856",
177            "21847035105528745403288232691147584728191162732299865338377159692350059136679",
178        ],
179        ["1", "0"],
180    ]);
181
182    const CIRCOM_GAMMA_G2: CircomG2 = build_circom_g2([
183        [
184            "10857046999023057135944570762232829481370756359578518086990519993285655852781",
185            "11559732032986387107991004021392285783925812861821192530917403151452391805634",
186        ],
187        [
188            "8495653923123431417604973247489272438418190587263600148770280649306958101930",
189            "4082367875863433681332203403145435568316851327593401208105741076214120093531",
190        ],
191        ["1", "0"],
192    ]);
193
194    const CIRCOM_DELTA_G2: CircomG2 = build_circom_g2([
195        [
196            "10857046999023057135944570762232829481370756359578518086990519993285655852781",
197            "11559732032986387107991004021392285783925812861821192530917403151452391805634",
198        ],
199        [
200            "8495653923123431417604973247489272438418190587263600148770280649306958101930",
201            "4082367875863433681332203403145435568316851327593401208105741076214120093531",
202        ],
203        ["1", "0"],
204    ]);
205
206    const CIRCOM_GAMMA_ABC_G1: [CircomG1; 2] = [
207        build_circom_g1([
208            "20701306374481714853949730154526815782802808896228594855451770849676897643964",
209            "2766989084754673216772682210231588284954002353414778477810174100808747060165",
210            "1",
211        ]),
212        build_circom_g1([
213            "501195541410525737371980194958674422793469475773065719916327137354779402600",
214            "13527631693157515024233848630878973193664410306029731429350155106228769355415",
215            "1",
216        ]),
217    ];
218
219    let vk = ark_groth16::VerifyingKey {
220        alpha_g1: circom_to_arkworks_g1(&CIRCOM_ALPHA_G1).unwrap(),
221        beta_g2: circom_to_arkworks_g2(&CIRCOM_BETA_G2).unwrap(),
222        gamma_g2: circom_to_arkworks_g2(&CIRCOM_GAMMA_G2).unwrap(),
223        delta_g2: circom_to_arkworks_g2(&CIRCOM_DELTA_G2).unwrap(),
224        gamma_abc_g1: CIRCOM_GAMMA_ABC_G1
225            .iter()
226            .map(circom_to_arkworks_g1)
227            .collect::<Result<_, _>>()
228            .unwrap(),
229    };
230
231    VerifyingKey::new(PreparedVerifyingKey::from(vk))
232}
233
234impl VerifyingKey {
235    fn new(inner: PreparedVerifyingKey<ark_bn254::Bn254>) -> Self {
236        Self { inner }
237    }
238
239    pub fn new_mainnet() -> Self {
240        mainnet_verifying_key()
241    }
242
243    pub fn new_dev() -> Self {
244        dev_verifying_key()
245    }
246
247    pub fn verify_zklogin(
248        &self,
249        jwk: &Jwk,
250        inputs: &ZkLoginInputs,
251        signature: &SimpleSignature,
252        max_epoch: u64,
253    ) -> Result<(), SignatureError> {
254        use base64ct::Base64UrlUnpadded;
255        use base64ct::Encoding;
256        // Decode modulus to bytes.
257        let modulus = Base64UrlUnpadded::decode_vec(&jwk.n)
258            .map_err(|e| SignatureError::from_source(e.to_string()))?;
259
260        let proof = zklogin_proof_to_arkworks(&inputs.proof_points).unwrap();
261        let input_hash = calculate_all_inputs_hash(inputs, signature, &modulus, max_epoch).unwrap();
262
263        self.verify_proof(&proof, &[input_hash])
264    }
265
266    fn verify_proof(
267        &self,
268        proof: &Proof<ark_bn254::Bn254>,
269        public_inputs: &[ark_bn254::Fr],
270    ) -> Result<(), SignatureError> {
271        use ark_snark::SNARK;
272
273        if ark_groth16::Groth16::<ark_bn254::Bn254>::verify_with_processed_vk(
274            &self.inner,
275            public_inputs,
276            proof,
277        )
278        .map_err(|e| SignatureError::from_source(e.to_string()))?
279        {
280            Ok(())
281        } else {
282            Err(SignatureError::from_source("Groth16 proof verify failed"))
283        }
284    }
285}
286
287fn zklogin_proof_to_arkworks(
288    proof: &ZkLoginProof,
289) -> Result<Proof<ark_bn254::Bn254>, SignatureError> {
290    Ok(Proof {
291        a: circom_to_arkworks_g1(&proof.a)?,
292        b: circom_to_arkworks_g2(&proof.b)?,
293        c: circom_to_arkworks_g1(&proof.c)?,
294    })
295}
296
297/// Given a SimpleSignature convert the corrisponding public key, prefixed with the signature
298/// scheme flag, to two Bn254Frs
299pub fn public_key_to_frs(signature: &SimpleSignature) -> (Fr, Fr) {
300    // buf length of the longest public key secp256r1/secp256k1 of 33 bytes plus 1 byte for the
301    // scheme
302    let mut buf = [0u8; 34];
303
304    buf[0] = signature.scheme().to_u8();
305
306    let buf = match signature {
307        SimpleSignature::Ed25519 { public_key, .. } => {
308            buf[1..Ed25519PublicKey::LENGTH + 1].copy_from_slice(public_key.inner());
309            &buf[..Ed25519PublicKey::LENGTH + 1]
310        }
311        SimpleSignature::Secp256k1 { public_key, .. } => {
312            buf[1..Secp256k1PublicKey::LENGTH + 1].copy_from_slice(public_key.inner());
313            &buf[..Secp256k1PublicKey::LENGTH + 1]
314        }
315        SimpleSignature::Secp256r1 { public_key, .. } => {
316            buf[1..Secp256r1PublicKey::LENGTH + 1].copy_from_slice(public_key.inner());
317            &buf[..Secp256r1PublicKey::LENGTH + 1]
318        }
319    };
320
321    //TODO this comment is wrong...
322    // Split the bytes deterministically such that the first element contains the first 128
323    // bits of the hash, and the second element contains the latter ones.
324    let (first_half, second_half) = buf.split_at(buf.len() - 16);
325
326    let eph_public_key_0 = Fr::from_be_bytes_mod_order(first_half);
327    let eph_public_key_1 = Fr::from_be_bytes_mod_order(second_half);
328    (eph_public_key_0, eph_public_key_1)
329}
330
331pub(crate) type U256 = bnum::BUintD8<32>;
332pub(crate) type U2048 = bnum::BUintD8<256>;
333
334const MAX_HEADER_LEN: u8 = 248;
335const PACK_WIDTH: u8 = 248;
336#[allow(unused)]
337const ISS: &str = "iss";
338#[allow(unused)]
339const BASE64_URL_CHARSET: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
340const MAX_EXT_ISS_LEN: u8 = 165;
341const MAX_ISS_LEN_B64: u8 = 4 * (1 + MAX_EXT_ISS_LEN / 3);
342
343/// Pads a stream of bytes and maps it to a field element
344pub fn hash_ascii_str_to_field(s: &str, max_size: u8) -> Result<Fr, SignatureError> {
345    let str_padded = str_to_padded_char_codes(s, max_size)?;
346    hash_to_field(&str_padded, 8, PACK_WIDTH)
347}
348
349fn str_to_padded_char_codes(s: &str, max_len: u8) -> Result<Vec<U256>, SignatureError> {
350    let arr: Vec<U256> = s.bytes().map(U256::from).collect();
351    pad_with_zeroes(arr, max_len)
352}
353
354fn pad_with_zeroes(in_arr: Vec<U256>, out_count: u8) -> Result<Vec<U256>, SignatureError> {
355    if in_arr.len() > out_count as usize {
356        return Err(SignatureError::from_source("in_arr too long"));
357    }
358    let mut padded = in_arr;
359    padded.resize(out_count as usize, U256::ZERO);
360    Ok(padded)
361}
362
363/// Maps a stream of bigints to a single field element. First we convert the base from
364/// inWidth to packWidth. Then we compute the poseidon hash of the "packed" input.
365/// input is the input vector containing equal-width big ints. inWidth is the width of
366/// each input element.
367fn hash_to_field<T: ToBits>(
368    input: &[T],
369    in_width: u16,
370    pack_width: u8,
371) -> Result<Fr, SignatureError> {
372    let packed = convert_base(input, in_width, pack_width)?;
373
374    POSEIDON.hash(&packed).map_err(SignatureError::from_source)
375}
376
377/// Helper function to pack field elements from big ints.
378fn convert_base<T: ToBits>(
379    in_arr: &[T],
380    in_width: u16,
381    out_width: u8,
382) -> Result<Vec<Fr>, SignatureError> {
383    if out_width == 0 {
384        return Err(SignatureError::from_source("invalid input"));
385    }
386    let bits = big_int_array_to_bits(in_arr, in_width as usize)?;
387    let mut packed: Vec<Fr> = bits
388        .rchunks(out_width as usize)
389        .map(|chunk| {
390            Fr::from_be_bytes_mod_order(U256::from_radix_be(chunk, 2).unwrap().to_be().digits())
391        })
392        .collect();
393    packed.reverse();
394    match packed.len() != (in_arr.len() * in_width as usize).div_ceil(out_width as usize) {
395        true => Err(SignatureError::from_source("invalid input")),
396        false => Ok(packed),
397    }
398}
399
400/// Convert a big int array to a bit array with 0 paddings.
401fn big_int_array_to_bits<T: ToBits>(
402    integers: &[T],
403    intended_size: usize,
404) -> Result<Vec<u8>, SignatureError> {
405    use itertools::Itertools;
406    use std::cmp::Ordering::Equal;
407    use std::cmp::Ordering::Greater;
408    use std::cmp::Ordering::Less;
409
410    integers
411        .iter()
412        .map(|integer| {
413            let bits = integer.to_bits();
414            match bits.len().cmp(&intended_size) {
415                Less => {
416                    let extra_bits = intended_size - bits.len();
417                    let mut padded = vec![0; extra_bits];
418                    padded.extend(bits);
419                    Ok(padded)
420                }
421                Equal => Ok(bits),
422                Greater => Err(SignatureError::from_source("invalid input")),
423            }
424        })
425        .flatten_ok()
426        .collect()
427}
428
429trait ToBits {
430    fn to_bits(&self) -> Vec<u8>;
431}
432
433impl ToBits for U256 {
434    fn to_bits(&self) -> Vec<u8> {
435        self.to_radix_be(2)
436    }
437}
438
439impl ToBits for U2048 {
440    fn to_bits(&self) -> Vec<u8> {
441        self.to_radix_be(2)
442    }
443}
444
445/// Calculate the poseidon hash from selected fields from inputs, along with the ephemeral pubkey.
446pub fn calculate_all_inputs_hash(
447    inputs: &ZkLoginInputs,
448    signature: &SimpleSignature,
449    modulus: &[u8],
450    max_epoch: u64,
451) -> Result<Fr, SignatureError> {
452    if inputs.header_base64.len() > MAX_HEADER_LEN as usize {
453        return Err(SignatureError::from_source("header too long"));
454    }
455
456    let (first, second) = public_key_to_frs(signature);
457
458    let address_seed = bn254_to_fr(&inputs.address_seed);
459    let max_epoch_f = Fr::from_be_bytes_mod_order(U256::from(max_epoch).to_be().digits());
460    let index_mod_4_f = Fr::from_be_bytes_mod_order(
461        U256::from(inputs.iss_base64_details.index_mod_4)
462            .to_be()
463            .digits(),
464    );
465
466    let iss_base64_f = hash_ascii_str_to_field(&inputs.iss_base64_details.value, MAX_ISS_LEN_B64)?;
467    let header_f = hash_ascii_str_to_field(&inputs.header_base64, MAX_HEADER_LEN)?;
468    let modulus_f = hash_to_field(&[U2048::from_be_slice(modulus).unwrap()], 2048, PACK_WIDTH)?;
469
470    POSEIDON
471        .hash(&[
472            first,
473            second,
474            address_seed,
475            max_epoch_f,
476            iss_base64_f,
477            index_mod_4_f,
478            header_f,
479            modulus_f,
480        ])
481        .map_err(SignatureError::from_source)
482}
483
484/// Calculate the Sui address based on address seed and address params.
485#[allow(unused)]
486fn gen_address_seed(
487    salt: &str,
488    name: &str,  // i.e. "sub"
489    value: &str, // i.e. the sub value
490    aud: &str,   // i.e. the client ID
491) -> Result<String, SignatureError> {
492    let salt_hash = POSEIDON
493        .hash(&[bn254_to_fr(
494            &Bn254FieldElement::from_str(salt).map_err(SignatureError::from_source)?,
495        )])
496        .map_err(SignatureError::from_source)?;
497    gen_address_seed_with_salt_hash(salt_hash, name, value, aud)
498}
499
500const MAX_KEY_CLAIM_NAME_LENGTH: u8 = 32;
501const MAX_KEY_CLAIM_VALUE_LENGTH: u8 = 115;
502const MAX_AUD_VALUE_LENGTH: u8 = 145;
503
504/// Same as [`gen_address_seed`] but takes the poseidon hash of the salt as input instead of the salt.
505pub(crate) fn gen_address_seed_with_salt_hash(
506    salt_hash: Fr,
507    name: &str,  // i.e. "sub"
508    value: &str, // i.e. the sub value
509    aud: &str,   // i.e. the client ID
510) -> Result<String, SignatureError> {
511    Ok(POSEIDON
512        .hash(&[
513            hash_ascii_str_to_field(name, MAX_KEY_CLAIM_NAME_LENGTH)?,
514            hash_ascii_str_to_field(value, MAX_KEY_CLAIM_VALUE_LENGTH)?,
515            hash_ascii_str_to_field(aud, MAX_AUD_VALUE_LENGTH)?,
516            salt_hash,
517        ])
518        .map_err(SignatureError::from_source)?
519        .to_string())
520}
521
522#[cfg(test)]
523mod test {
524    use super::*;
525    use sui_sdk_types::Ed25519Signature;
526
527    #[cfg(test)]
528    #[cfg(target_arch = "wasm32")]
529    use wasm_bindgen_test::wasm_bindgen_test as test;
530
531    #[test]
532    fn test_verify_zklogin_google() {
533        let user_salt = "206703048842351542647799591018316385612";
534
535        let pubkey = Ed25519PublicKey::new([
536            185, 198, 238, 22, 48, 239, 62, 113, 17, 68, 166, 72, 219, 6, 187, 178, 40, 79, 114,
537            116, 207, 190, 229, 63, 252, 238, 80, 60, 193, 164, 146, 0,
538        ]);
539        let signature = SimpleSignature::Ed25519 {
540            signature: Ed25519Signature::new([0; 64]),
541            public_key: pubkey,
542        };
543
544        // Get the address seed.
545        let address_seed = gen_address_seed(
546            user_salt,
547            "sub",
548            "106294049240999307923",
549            "25769832374-famecqrhe2gkebt5fvqms2263046lj96.apps.googleusercontent.com",
550        )
551        .unwrap();
552
553        let inputs = serde_json::json!({
554            "proof_points": {
555                "a": [
556                    "8247215875293406890829839156897863742504615191361518281091302475904551111016",
557                    "6872980335748205979379321982220498484242209225765686471076081944034292159666",
558                    "1"
559                ],
560                "b": [
561                    [
562                        "21419680064642047510915171723230639588631899775315750803416713283740137406807",
563                        "21566716915562037737681888858382287035712341650647439119820808127161946325890"
564                    ],
565                    [
566                        "17867714710686394159919998503724240212517838710399045289784307078087926404555",
567                        "21812769875502013113255155836896615164559280911997219958031852239645061854221"
568                    ],
569                    ["1","0"]
570                ],
571                "c": [
572                    "7530826803702928198368421787278524256623871560746240215547076095911132653214",
573                    "16244547936249959771862454850485726883972969173921727256151991751860694123976",
574                    "1"
575                ]
576            },
577            "iss_base64_details": {
578                "value": "yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC",
579                "index_mod_4": 1
580            },
581            "header_base64": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjZmNzI1NDEwMWY1NmU0MWNmMzVjOTkyNmRlODRhMmQ1NTJiNGM2ZjEiLCJ0eXAiOiJKV1QifQ",
582            "address_seed": address_seed
583        });
584
585        let zklogin_inputs: ZkLoginInputs = serde_json::from_value(inputs).unwrap();
586
587        let jwk = Jwk {
588            kty: "RSA".to_string(),
589            e: "AQAB".to_string(),
590            n: "oUriU8GqbRw-avcMn95DGW1cpZR1IoM6L7krfrWvLSSCcSX6Ig117o25Yk7QWBiJpaPV0FbP7Y5-DmThZ3SaF0AXW-3BsKPEXfFfeKVc6vBqk3t5mKlNEowjdvNTSzoOXO5UIHwsXaxiJlbMRalaFEUm-2CKgmXl1ss_yGh1OHkfnBiGsfQUndKoHiZuDzBMGw8Sf67am_Ok-4FShK0NuR3-q33aB_3Z7obC71dejSLWFOEcKUVCaw6DGVuLog3x506h1QQ1r0FXKOQxnmqrRgpoHqGSouuG35oZve1vgCU4vLZ6EAgBAbC0KL35I7_0wUDSMpiAvf7iZxzJVbspkQ".to_string(),
591            alg: "RS256".to_string(),
592        };
593
594        VerifyingKey::new_mainnet()
595            .verify_zklogin(&jwk, &zklogin_inputs, &signature, 10)
596            .unwrap();
597    }
598
599    #[test]
600    fn test_public_key_to_frs() {
601        let pubkey = Ed25519PublicKey::new([
602            185, 198, 238, 22, 48, 239, 62, 113, 17, 68, 166, 72, 219, 6, 187, 178, 40, 79, 114,
603            116, 207, 190, 229, 63, 252, 238, 80, 60, 193, 164, 146, 0,
604        ]);
605        let signature = SimpleSignature::Ed25519 {
606            signature: Ed25519Signature::new([0; 64]),
607            public_key: pubkey,
608        };
609        let (actual_0, actual_1) = public_key_to_frs(&signature);
610        let expect_0 = Fr::from(ark_ff::BigInt([
611            1244302228903607218,
612            13386648721483054705,
613            0,
614            0,
615        ]));
616
617        let expect_1 = Fr::from(ark_ff::BigInt([
618            18225592963892023808,
619            2904666130704426303,
620            0,
621            0,
622        ]));
623        assert_eq!(actual_0, expect_0);
624        assert_eq!(actual_1, expect_1);
625    }
626
627    #[test]
628    fn test_hash_ascii_str_to_field() {
629        let actual = hash_ascii_str_to_field("sub", 32).unwrap();
630        let expect = Fr::from(ark_ff::BigInt([
631            9420274050661827129,
632            9736100402995345242,
633            10431892319505233812,
634            1450152190758097105,
635        ]));
636        assert_eq!(actual, expect);
637
638        let actual = hash_ascii_str_to_field("106294049240999307923", 115).unwrap();
639        let expect = Fr::from(ark_ff::BigInt([
640            1616959301818912987,
641            17318965991705091209,
642            15303466056770245354,
643            1596136658728187659,
644        ]));
645        assert_eq!(actual, expect);
646
647        let actual = hash_ascii_str_to_field(
648            "25769832374-famecqrhe2gkebt5fvqms2263046lj96.apps.googleusercontent.com",
649            145,
650        )
651        .unwrap();
652        let expect = Fr::from(ark_ff::BigInt([
653            5030944271044826582,
654            8577618269522081956,
655            6962871209781429610,
656            2149811477348923117,
657        ]));
658        assert_eq!(actual, expect);
659
660        let actual =
661            hash_ascii_str_to_field("yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC", 224)
662                .unwrap();
663        let expect = Fr::from(ark_ff::BigInt([
664            6021918591354572765,
665            14069258108381575504,
666            1736509627917450843,
667            2767185135515367512,
668        ]));
669        assert_eq!(actual, expect);
670
671        let actual = hash_ascii_str_to_field(
672        "eyJhbGciOiJSUzI1NiIsImtpZCI6IjZmNzI1NDEwMWY1NmU0MWNmMzVjOTkyNmRlODRhMmQ1NTJiNGM2ZjEiLCJ0eXAiOiJKV1QifQ",
673        248,
674    ).unwrap();
675        let expect = Fr::from(ark_ff::BigInt([
676            4239129243150064016,
677            15469804315138207306,
678            17534492051703966556,
679            2100329545252322607,
680        ]));
681        assert_eq!(actual, expect);
682    }
683}