1use std::collections::HashMap;
2use std::str::FromStr;
3use std::sync::LazyLock;
4use std::sync::RwLock;
5
6use crate::SignatureError;
7use ark_bn254::Fq;
8use ark_bn254::Fq2;
9use ark_bn254::Fr;
10use ark_bn254::G1Affine;
11use ark_bn254::G1Projective;
12use ark_bn254::G2Affine;
13use ark_bn254::G2Projective;
14use ark_ff::PrimeField;
15use ark_groth16::PreparedVerifyingKey;
16use ark_groth16::Proof;
17use sui_sdk_types::Bn254FieldElement;
18use sui_sdk_types::CircomG1;
19use sui_sdk_types::CircomG2;
20use sui_sdk_types::Ed25519PublicKey;
21use sui_sdk_types::Jwk;
22use sui_sdk_types::Secp256k1PublicKey;
23use sui_sdk_types::Secp256r1PublicKey;
24use sui_sdk_types::SimpleSignature;
25use sui_sdk_types::U256;
26use sui_sdk_types::ZkLoginInputs;
27use sui_sdk_types::ZkLoginProof;
28
29use super::POSEIDON;
30
31#[derive(Clone, Debug, PartialEq, Default)]
32pub struct VerifyingKey {
33 inner: PreparedVerifyingKey<ark_bn254::Bn254>,
34}
35
36const fn str_to_bn254(s: &str) -> Bn254FieldElement {
37 match Bn254FieldElement::from_str_radix_10(s) {
38 Ok(e) => e,
39 Err(_) => panic!("unable to convert bn254"),
40 }
41}
42
43const fn build_circom_g1([e0, e1, e2]: [&str; 3]) -> CircomG1 {
44 CircomG1([str_to_bn254(e0), str_to_bn254(e1), str_to_bn254(e2)])
45}
46
47const fn build_circom_g2([[e00, e01], [e10, e11], [e20, e21]]: [[&str; 2]; 3]) -> CircomG2 {
48 CircomG2([
49 [str_to_bn254(e00), str_to_bn254(e01)],
50 [str_to_bn254(e10), str_to_bn254(e11)],
51 [str_to_bn254(e20), str_to_bn254(e21)],
52 ])
53}
54
55fn circom_to_arkworks_g1(g1: &CircomG1) -> Result<G1Affine, SignatureError> {
56 let CircomG1([f0, f1, f2]) = g1;
57
58 let g1: G1Affine =
59 G1Projective::new_unchecked(bn254_to_fq(f0), bn254_to_fq(f1), bn254_to_fq(f2)).into();
60
61 if !g1.is_on_curve() || !g1.is_in_correct_subgroup_assuming_on_curve() {
62 return Err(SignatureError::from_source("invalid G1 input"));
63 }
64
65 Ok(g1)
66}
67
68fn circom_to_arkworks_g2(g2: &CircomG2) -> Result<G2Affine, SignatureError> {
69 let CircomG2([[f00, f01], [f10, f11], [f20, f21]]) = g2;
70
71 let g2: G2Affine = G2Projective::new_unchecked(
72 Fq2::new(bn254_to_fq(f00), bn254_to_fq(f01)),
73 Fq2::new(bn254_to_fq(f10), bn254_to_fq(f11)),
74 Fq2::new(bn254_to_fq(f20), bn254_to_fq(f21)),
75 )
76 .into();
77
78 if !g2.is_on_curve() || !g2.is_in_correct_subgroup_assuming_on_curve() {
79 return Err(SignatureError::from_source("invalid G2 input"));
80 }
81
82 Ok(g2)
83}
84
85fn bn254_to_fq(f: &Bn254FieldElement) -> Fq {
86 Fq::from_be_bytes_mod_order(f.padded())
87}
88
89fn bn254_to_fr(f: &Bn254FieldElement) -> Fr {
90 Fr::from_be_bytes_mod_order(f.padded())
91}
92
93fn mainnet_verifying_key() -> VerifyingKey {
94 const CIRCOM_ALPHA_G1: CircomG1 = build_circom_g1([
95 "21529901943976716921335152104180790524318946701278905588288070441048877064089",
96 "7775817982019986089115946956794180159548389285968353014325286374017358010641",
97 "1",
98 ]);
99
100 const CIRCOM_BETA_G2: CircomG2 = build_circom_g2([
101 [
102 "6600437987682835329040464538375790690815756241121776438004683031791078085074",
103 "16207344858883952201936462217289725998755030546200154201671892670464461194903",
104 ],
105 [
106 "17943105074568074607580970189766801116106680981075272363121544016828311544390",
107 "18339640667362802607939727433487930605412455701857832124655129852540230493587",
108 ],
109 ["1", "0"],
110 ]);
111
112 const CIRCOM_GAMMA_G2: CircomG2 = build_circom_g2([
113 [
114 "10857046999023057135944570762232829481370756359578518086990519993285655852781",
115 "11559732032986387107991004021392285783925812861821192530917403151452391805634",
116 ],
117 [
118 "8495653923123431417604973247489272438418190587263600148770280649306958101930",
119 "4082367875863433681332203403145435568316851327593401208105741076214120093531",
120 ],
121 ["1", "0"],
122 ]);
123
124 const CIRCOM_DELTA_G2: CircomG2 = build_circom_g2([
125 [
126 "19260309516619721648285279557078789954438346514188902804737557357941293711874",
127 "2480422554560175324649200374556411861037961022026590718777465211464278308900",
128 ],
129 [
130 "14489104692423540990601374549557603533921811847080812036788172274404299703364",
131 "12564378633583954025611992187142343628816140907276948128970903673042690269191",
132 ],
133 ["1", "0"],
134 ]);
135
136 const CIRCOM_GAMMA_ABC_G1: [CircomG1; 2] = [
137 build_circom_g1([
138 "1607694606386445293170795095076356565829000940041894770459712091642365695804",
139 "18066827569413962196795937356879694709963206118612267170825707780758040578649",
140 "1",
141 ]),
142 build_circom_g1([
143 "20653794344898475822834426774542692225449366952113790098812854265588083247207",
144 "3296759704176575765409730962060698204792513807296274014163938591826372646699",
145 "1",
146 ]),
147 ];
148
149 let vk = ark_groth16::VerifyingKey {
150 alpha_g1: circom_to_arkworks_g1(&CIRCOM_ALPHA_G1).unwrap(),
151 beta_g2: circom_to_arkworks_g2(&CIRCOM_BETA_G2).unwrap(),
152 gamma_g2: circom_to_arkworks_g2(&CIRCOM_GAMMA_G2).unwrap(),
153 delta_g2: circom_to_arkworks_g2(&CIRCOM_DELTA_G2).unwrap(),
154 gamma_abc_g1: CIRCOM_GAMMA_ABC_G1
155 .iter()
156 .map(circom_to_arkworks_g1)
157 .collect::<Result<_, _>>()
158 .unwrap(),
159 };
160
161 VerifyingKey {
162 inner: PreparedVerifyingKey::from(vk),
163 }
164}
165
166fn dev_verifying_key() -> VerifyingKey {
168 const CIRCOM_ALPHA_G1: CircomG1 = build_circom_g1([
169 "20491192805390485299153009773594534940189261866228447918068658471970481763042",
170 "9383485363053290200918347156157836566562967994039712273449902621266178545958",
171 "1",
172 ]);
173
174 const CIRCOM_BETA_G2: CircomG2 = build_circom_g2([
175 [
176 "6375614351688725206403948262868962793625744043794305715222011528459656738731",
177 "4252822878758300859123897981450591353533073413197771768651442665752259397132",
178 ],
179 [
180 "10505242626370262277552901082094356697409835680220590971873171140371331206856",
181 "21847035105528745403288232691147584728191162732299865338377159692350059136679",
182 ],
183 ["1", "0"],
184 ]);
185
186 const CIRCOM_GAMMA_G2: CircomG2 = build_circom_g2([
187 [
188 "10857046999023057135944570762232829481370756359578518086990519993285655852781",
189 "11559732032986387107991004021392285783925812861821192530917403151452391805634",
190 ],
191 [
192 "8495653923123431417604973247489272438418190587263600148770280649306958101930",
193 "4082367875863433681332203403145435568316851327593401208105741076214120093531",
194 ],
195 ["1", "0"],
196 ]);
197
198 const CIRCOM_DELTA_G2: CircomG2 = build_circom_g2([
199 [
200 "10857046999023057135944570762232829481370756359578518086990519993285655852781",
201 "11559732032986387107991004021392285783925812861821192530917403151452391805634",
202 ],
203 [
204 "8495653923123431417604973247489272438418190587263600148770280649306958101930",
205 "4082367875863433681332203403145435568316851327593401208105741076214120093531",
206 ],
207 ["1", "0"],
208 ]);
209
210 const CIRCOM_GAMMA_ABC_G1: [CircomG1; 2] = [
211 build_circom_g1([
212 "20701306374481714853949730154526815782802808896228594855451770849676897643964",
213 "2766989084754673216772682210231588284954002353414778477810174100808747060165",
214 "1",
215 ]),
216 build_circom_g1([
217 "501195541410525737371980194958674422793469475773065719916327137354779402600",
218 "13527631693157515024233848630878973193664410306029731429350155106228769355415",
219 "1",
220 ]),
221 ];
222
223 let vk = ark_groth16::VerifyingKey {
224 alpha_g1: circom_to_arkworks_g1(&CIRCOM_ALPHA_G1).unwrap(),
225 beta_g2: circom_to_arkworks_g2(&CIRCOM_BETA_G2).unwrap(),
226 gamma_g2: circom_to_arkworks_g2(&CIRCOM_GAMMA_G2).unwrap(),
227 delta_g2: circom_to_arkworks_g2(&CIRCOM_DELTA_G2).unwrap(),
228 gamma_abc_g1: CIRCOM_GAMMA_ABC_G1
229 .iter()
230 .map(circom_to_arkworks_g1)
231 .collect::<Result<_, _>>()
232 .unwrap(),
233 };
234
235 VerifyingKey::new(PreparedVerifyingKey::from(vk))
236}
237
238fn dev_verifying_key_v2() -> VerifyingKey {
242 const CIRCOM_ALPHA_G1: CircomG1 = build_circom_g1([
243 "20491192805390485299153009773594534940189261866228447918068658471970481763042",
244 "9383485363053290200918347156157836566562967994039712273449902621266178545958",
245 "1",
246 ]);
247
248 const CIRCOM_BETA_G2: CircomG2 = build_circom_g2([
249 [
250 "6375614351688725206403948262868962793625744043794305715222011528459656738731",
251 "4252822878758300859123897981450591353533073413197771768651442665752259397132",
252 ],
253 [
254 "10505242626370262277552901082094356697409835680220590971873171140371331206856",
255 "21847035105528745403288232691147584728191162732299865338377159692350059136679",
256 ],
257 ["1", "0"],
258 ]);
259
260 const CIRCOM_GAMMA_G2: CircomG2 = build_circom_g2([
261 [
262 "10857046999023057135944570762232829481370756359578518086990519993285655852781",
263 "11559732032986387107991004021392285783925812861821192530917403151452391805634",
264 ],
265 [
266 "8495653923123431417604973247489272438418190587263600148770280649306958101930",
267 "4082367875863433681332203403145435568316851327593401208105741076214120093531",
268 ],
269 ["1", "0"],
270 ]);
271
272 const CIRCOM_DELTA_G2: CircomG2 = build_circom_g2([
273 [
274 "10857046999023057135944570762232829481370756359578518086990519993285655852781",
275 "11559732032986387107991004021392285783925812861821192530917403151452391805634",
276 ],
277 [
278 "8495653923123431417604973247489272438418190587263600148770280649306958101930",
279 "4082367875863433681332203403145435568316851327593401208105741076214120093531",
280 ],
281 ["1", "0"],
282 ]);
283
284 const CIRCOM_GAMMA_ABC_G1: [CircomG1; 2] = [
285 build_circom_g1([
286 "7494019946711010262111829149650251402606293243816947408234427261113297488209",
287 "15277037309547978131462479061393755632702482465205499508808087107719455935457",
288 "1",
289 ]),
290 build_circom_g1([
291 "11337299590256247592846534480065240790944417381563410255189124427241414159992",
292 "10513214342948426742825684049934611922239382370302051503558098869981509341761",
293 "1",
294 ]),
295 ];
296
297 let vk = ark_groth16::VerifyingKey {
298 alpha_g1: circom_to_arkworks_g1(&CIRCOM_ALPHA_G1).unwrap(),
299 beta_g2: circom_to_arkworks_g2(&CIRCOM_BETA_G2).unwrap(),
300 gamma_g2: circom_to_arkworks_g2(&CIRCOM_GAMMA_G2).unwrap(),
301 delta_g2: circom_to_arkworks_g2(&CIRCOM_DELTA_G2).unwrap(),
302 gamma_abc_g1: CIRCOM_GAMMA_ABC_G1
303 .iter()
304 .map(circom_to_arkworks_g1)
305 .collect::<Result<_, _>>()
306 .unwrap(),
307 };
308
309 VerifyingKey::new(PreparedVerifyingKey::from(vk))
310}
311
312impl VerifyingKey {
313 fn new(inner: PreparedVerifyingKey<ark_bn254::Bn254>) -> Self {
314 Self { inner }
315 }
316
317 pub fn new_mainnet() -> Self {
318 mainnet_verifying_key()
319 }
320
321 pub fn new_dev_for(version: CircuitVersion) -> Self {
322 match version {
323 CircuitVersion::V1 => dev_verifying_key(),
324 CircuitVersion::V2 => dev_verifying_key_v2(),
325 }
326 }
327
328 pub fn verify_zklogin(
329 &self,
330 jwk: &Jwk,
331 inputs: &ZkLoginInputs,
332 signature: &SimpleSignature,
333 max_epoch: u64,
334 circuit_version: CircuitVersion,
335 ) -> Result<(), SignatureError> {
336 use base64ct::Base64UrlUnpadded;
337 use base64ct::Encoding;
338 let modulus = Base64UrlUnpadded::decode_vec(&jwk.n)
340 .map_err(|e| SignatureError::from_source(e.to_string()))?;
341
342 let proof = zklogin_proof_to_arkworks(inputs.proof_points())?;
343 let input_hash =
344 calculate_all_inputs_hash(inputs, signature, &modulus, max_epoch, circuit_version)?;
345
346 self.verify_proof(&proof, &[input_hash])
347 }
348
349 fn verify_proof(
350 &self,
351 proof: &Proof<ark_bn254::Bn254>,
352 public_inputs: &[ark_bn254::Fr],
353 ) -> Result<(), SignatureError> {
354 use ark_snark::SNARK;
355
356 if ark_groth16::Groth16::<ark_bn254::Bn254>::verify_with_processed_vk(
357 &self.inner,
358 public_inputs,
359 proof,
360 )
361 .map_err(|e| SignatureError::from_source(e.to_string()))?
362 {
363 Ok(())
364 } else {
365 Err(SignatureError::from_source("Groth16 proof verify failed"))
366 }
367 }
368}
369
370fn zklogin_proof_to_arkworks(
371 proof: &ZkLoginProof,
372) -> Result<Proof<ark_bn254::Bn254>, SignatureError> {
373 Ok(Proof {
374 a: circom_to_arkworks_g1(&proof.a)?,
375 b: circom_to_arkworks_g2(&proof.b)?,
376 c: circom_to_arkworks_g1(&proof.c)?,
377 })
378}
379
380fn public_key_to_frs(signature: &SimpleSignature) -> Result<(Fr, Fr), SignatureError> {
383 let mut buf = [0u8; 34];
386
387 buf[0] = signature.scheme().to_u8();
388
389 let buf = match signature {
390 SimpleSignature::Ed25519 { public_key, .. } => {
391 buf[1..Ed25519PublicKey::LENGTH + 1].copy_from_slice(public_key.inner());
392 &buf[..Ed25519PublicKey::LENGTH + 1]
393 }
394 SimpleSignature::Secp256k1 { public_key, .. } => {
395 buf[1..Secp256k1PublicKey::LENGTH + 1].copy_from_slice(public_key.inner());
396 &buf[..Secp256k1PublicKey::LENGTH + 1]
397 }
398 SimpleSignature::Secp256r1 { public_key, .. } => {
399 buf[1..Secp256r1PublicKey::LENGTH + 1].copy_from_slice(public_key.inner());
400 &buf[..Secp256r1PublicKey::LENGTH + 1]
401 }
402 _ => return Err(SignatureError::from_source("unknown signature scheme")),
403 };
404
405 let (first_half, second_half) = buf.split_at(buf.len() - 16);
409
410 let eph_public_key_0 = Fr::from_be_bytes_mod_order(first_half);
411 let eph_public_key_1 = Fr::from_be_bytes_mod_order(second_half);
412 Ok((eph_public_key_0, eph_public_key_1))
413}
414
415pub(crate) type U8192 = bnum::BUintD8<1024>;
416
417const PACK_WIDTH: u8 = 248;
418const V1_MAX_EXT_ISS_LEN: u16 = 165;
419const V2_MAX_EXT_ISS_LEN: u16 = 186;
420
421#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
423pub enum CircuitVersion {
424 V1,
427 V2,
430}
431
432struct CircuitConfig {
434 max_header_len_b64: u16,
436 max_iss_len: u16,
440 max_rsa_bits: u16,
442}
443
444impl CircuitVersion {
445 fn config(&self) -> CircuitConfig {
446 match self {
447 CircuitVersion::V1 => CircuitConfig {
448 max_header_len_b64: 248,
449 max_iss_len: 4 * (1 + V1_MAX_EXT_ISS_LEN / 3),
450 max_rsa_bits: 2048,
451 },
452 CircuitVersion::V2 => CircuitConfig {
453 max_header_len_b64: 279,
454 max_iss_len: V2_MAX_EXT_ISS_LEN,
455 max_rsa_bits: 8192,
456 },
457 }
458 }
459}
460
461pub fn hash_ascii_str_to_field(s: &str, max_size: u16) -> Result<Fr, SignatureError> {
463 let str_padded = str_to_padded_char_codes(s, max_size)?;
464 hash_to_field(&str_padded, 8, PACK_WIDTH)
465}
466
467fn str_to_padded_char_codes(s: &str, max_len: u16) -> Result<Vec<U256>, SignatureError> {
468 let arr: Vec<U256> = s.bytes().map(U256::from).collect();
469 pad_with_zeroes(arr, max_len)
470}
471
472fn pad_with_zeroes(in_arr: Vec<U256>, out_count: u16) -> Result<Vec<U256>, SignatureError> {
473 if in_arr.len() > out_count as usize {
474 return Err(SignatureError::from_source("in_arr too long"));
475 }
476 let mut padded = in_arr;
477 padded.resize(out_count as usize, U256::ZERO);
478 Ok(padded)
479}
480
481fn hash_to_field<T: ToBits>(
486 input: &[T],
487 in_width: u16,
488 pack_width: u8,
489) -> Result<Fr, SignatureError> {
490 let packed = convert_base(input, in_width, pack_width)?;
491
492 POSEIDON.hash(&packed).map_err(SignatureError::from_source)
493}
494
495fn convert_base<T: ToBits>(
497 in_arr: &[T],
498 in_width: u16,
499 out_width: u8,
500) -> Result<Vec<Fr>, SignatureError> {
501 if out_width == 0 {
502 return Err(SignatureError::from_source("invalid input"));
503 }
504 let bits = big_int_array_to_bits(in_arr, in_width as usize)?;
505 let mut packed: Vec<Fr> = bits
506 .rchunks(out_width as usize)
507 .map(|chunk| {
508 U256::from_radix_be(chunk, 2)
509 .map(|v| Fr::from_be_bytes_mod_order(v.to_be().digits()))
510 .ok_or_else(|| SignatureError::from_source("invalid radix-2 conversion"))
511 })
512 .collect::<Result<_, _>>()?;
513 packed.reverse();
514 match packed.len() != (in_arr.len() * in_width as usize).div_ceil(out_width as usize) {
515 true => Err(SignatureError::from_source("invalid input")),
516 false => Ok(packed),
517 }
518}
519
520fn big_int_array_to_bits<T: ToBits>(
522 integers: &[T],
523 intended_size: usize,
524) -> Result<Vec<u8>, SignatureError> {
525 use itertools::Itertools;
526 use std::cmp::Ordering::Equal;
527 use std::cmp::Ordering::Greater;
528 use std::cmp::Ordering::Less;
529
530 integers
531 .iter()
532 .map(|integer| {
533 let bits = integer.to_bits();
534 match bits.len().cmp(&intended_size) {
535 Less => {
536 let extra_bits = intended_size - bits.len();
537 let mut padded = vec![0; extra_bits];
538 padded.extend(bits);
539 Ok(padded)
540 }
541 Equal => Ok(bits),
542 Greater => Err(SignatureError::from_source("invalid input")),
543 }
544 })
545 .flatten_ok()
546 .collect()
547}
548
549trait ToBits {
550 fn to_bits(&self) -> Vec<u8>;
551}
552
553impl ToBits for U256 {
554 fn to_bits(&self) -> Vec<u8> {
555 self.to_radix_be(2)
556 }
557}
558
559impl ToBits for U8192 {
560 fn to_bits(&self) -> Vec<u8> {
561 self.to_radix_be(2)
562 }
563}
564
565pub fn calculate_all_inputs_hash(
567 inputs: &ZkLoginInputs,
568 signature: &SimpleSignature,
569 modulus: &[u8],
570 max_epoch: u64,
571 version: CircuitVersion,
572) -> Result<Fr, SignatureError> {
573 let config = version.config();
574 if inputs.header_base64().len() > config.max_header_len_b64 as usize {
575 return Err(SignatureError::from_source("header too long"));
576 }
577
578 let (first, second) = public_key_to_frs(signature)?;
579
580 let address_seed = bn254_to_fr(inputs.address_seed());
581 let max_epoch_f = Fr::from_be_bytes_mod_order(U256::from(max_epoch).to_be().digits());
582 let header_f = hash_ascii_str_to_field(inputs.header_base64(), config.max_header_len_b64)?;
583 let modulus_f = modulus_to_field(modulus, config.max_rsa_bits)?;
584
585 match version {
586 CircuitVersion::V1 => {
587 let index_mod_4_f = Fr::from_be_bytes_mod_order(
588 U256::from(inputs.iss_base64_details().index_mod_4)
589 .to_be()
590 .digits(),
591 );
592 let iss_base64_f =
593 hash_ascii_str_to_field(&inputs.iss_base64_details().value, config.max_iss_len)?;
594
595 POSEIDON
596 .hash(&[
597 first,
598 second,
599 address_seed,
600 max_epoch_f,
601 iss_base64_f,
602 index_mod_4_f,
603 header_f,
604 modulus_f,
605 ])
606 .map_err(SignatureError::from_source)
607 }
608 CircuitVersion::V2 => {
609 let iss_details = inputs.iss_base64_details();
610 let ext_iss = iss_details
611 .decoded_extended_claim()
612 .map_err(SignatureError::from_source)?;
613 let iss_f = hash_ascii_str_to_field(&ext_iss, config.max_iss_len)?;
614 let rsa_num_bits_f = Fr::from(rsa_num_bits(modulus));
615
616 POSEIDON
617 .hash(&[
618 first,
619 second,
620 address_seed,
621 max_epoch_f,
622 iss_f,
623 header_f,
624 modulus_f,
625 rsa_num_bits_f,
626 ])
627 .map_err(SignatureError::from_source)
628 }
629 }
630}
631
632type ModulusHashKey = (Vec<u8>, u16);
636
637static MODULUS_HASH_CACHE: LazyLock<RwLock<HashMap<ModulusHashKey, Fr>>> =
638 LazyLock::new(|| RwLock::new(HashMap::new()));
639
640const MODULUS_HASH_CACHE_MAX_SIZE: usize = 100;
642
643fn modulus_to_field(modulus: &[u8], max_rsa_bits: u16) -> Result<Fr, SignatureError> {
649 if let Some(f) = MODULUS_HASH_CACHE
650 .read()
651 .ok()
652 .and_then(|cache| cache.get(&(modulus.to_vec(), max_rsa_bits)).copied())
653 {
654 return Ok(f);
655 }
656 let f = hash_to_field(
657 &[U8192::from_be_slice(modulus)
658 .ok_or_else(|| SignatureError::from_source("JWK modulus too large for U8192"))?],
659 max_rsa_bits,
660 PACK_WIDTH,
661 )?;
662 if let Ok(mut cache) = MODULUS_HASH_CACHE.write() {
663 if cache.len() >= MODULUS_HASH_CACHE_MAX_SIZE {
664 cache.clear();
665 }
666 cache.insert((modulus.to_vec(), max_rsa_bits), f);
667 }
668 Ok(f)
669}
670
671fn rsa_num_bits(modulus: &[u8]) -> u64 {
676 let mut iter = modulus.iter().skip_while(|b| **b == 0);
677 match iter.next() {
678 Some(first) => (u8::BITS - first.leading_zeros()) as u64 + iter.count() as u64 * 8,
679 None => 0,
680 }
681}
682
683#[allow(unused)]
685fn gen_address_seed(
686 salt: &str,
687 name: &str, value: &str, aud: &str, ) -> Result<String, SignatureError> {
691 let salt_hash = POSEIDON
692 .hash(&[bn254_to_fr(
693 &Bn254FieldElement::from_str(salt).map_err(SignatureError::from_source)?,
694 )])
695 .map_err(SignatureError::from_source)?;
696 gen_address_seed_with_salt_hash(salt_hash, name, value, aud)
697}
698
699const MAX_KEY_CLAIM_NAME_LENGTH: u16 = 32;
700const MAX_KEY_CLAIM_VALUE_LENGTH: u16 = 115;
701const MAX_AUD_VALUE_LENGTH: u16 = 145;
702
703pub(crate) fn gen_address_seed_with_salt_hash(
705 salt_hash: Fr,
706 name: &str, value: &str, aud: &str, ) -> Result<String, SignatureError> {
710 Ok(POSEIDON
711 .hash(&[
712 hash_ascii_str_to_field(name, MAX_KEY_CLAIM_NAME_LENGTH)?,
713 hash_ascii_str_to_field(value, MAX_KEY_CLAIM_VALUE_LENGTH)?,
714 hash_ascii_str_to_field(aud, MAX_AUD_VALUE_LENGTH)?,
715 salt_hash,
716 ])
717 .map_err(SignatureError::from_source)?
718 .to_string())
719}
720
721#[cfg(test)]
722mod test {
723 use super::*;
724
725 #[cfg(test)]
726 #[cfg(target_arch = "wasm32")]
727 use wasm_bindgen_test::wasm_bindgen_test as test;
728
729 #[test]
730 fn test_verify_zklogin_google() {
731 let user_salt = "206703048842351542647799591018316385612";
732
733 let signature = crate::zklogin::tests::test_eph_simple_signature();
734
735 let address_seed = gen_address_seed(
737 user_salt,
738 "sub",
739 "106294049240999307923",
740 "25769832374-famecqrhe2gkebt5fvqms2263046lj96.apps.googleusercontent.com",
741 )
742 .unwrap();
743
744 let inputs = serde_json::json!({
745 "proof_points": {
746 "a": [
747 "8247215875293406890829839156897863742504615191361518281091302475904551111016",
748 "6872980335748205979379321982220498484242209225765686471076081944034292159666",
749 "1"
750 ],
751 "b": [
752 [
753 "21419680064642047510915171723230639588631899775315750803416713283740137406807",
754 "21566716915562037737681888858382287035712341650647439119820808127161946325890"
755 ],
756 [
757 "17867714710686394159919998503724240212517838710399045289784307078087926404555",
758 "21812769875502013113255155836896615164559280911997219958031852239645061854221"
759 ],
760 ["1","0"]
761 ],
762 "c": [
763 "7530826803702928198368421787278524256623871560746240215547076095911132653214",
764 "16244547936249959771862454850485726883972969173921727256151991751860694123976",
765 "1"
766 ]
767 },
768 "iss_base64_details": {
769 "value": "yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC",
770 "index_mod_4": 1
771 },
772 "header_base64": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjZmNzI1NDEwMWY1NmU0MWNmMzVjOTkyNmRlODRhMmQ1NTJiNGM2ZjEiLCJ0eXAiOiJKV1QifQ",
773 "address_seed": address_seed
774 });
775
776 let zklogin_inputs: ZkLoginInputs = serde_json::from_value(inputs).unwrap();
777
778 let jwk = Jwk {
779 kty: "RSA".to_string(),
780 e: "AQAB".to_string(),
781 n: "oUriU8GqbRw-avcMn95DGW1cpZR1IoM6L7krfrWvLSSCcSX6Ig117o25Yk7QWBiJpaPV0FbP7Y5-DmThZ3SaF0AXW-3BsKPEXfFfeKVc6vBqk3t5mKlNEowjdvNTSzoOXO5UIHwsXaxiJlbMRalaFEUm-2CKgmXl1ss_yGh1OHkfnBiGsfQUndKoHiZuDzBMGw8Sf67am_Ok-4FShK0NuR3-q33aB_3Z7obC71dejSLWFOEcKUVCaw6DGVuLog3x506h1QQ1r0FXKOQxnmqrRgpoHqGSouuG35oZve1vgCU4vLZ6EAgBAbC0KL35I7_0wUDSMpiAvf7iZxzJVbspkQ".to_string(),
782 alg: "RS256".to_string(),
783 };
784
785 VerifyingKey::new_mainnet()
786 .verify_zklogin(&jwk, &zklogin_inputs, &signature, 10, CircuitVersion::V1)
787 .unwrap();
788 }
789
790 #[test]
791 fn test_public_key_to_frs() {
792 let signature = crate::zklogin::tests::test_eph_simple_signature();
793 let (actual_0, actual_1) = public_key_to_frs(&signature).unwrap();
794 let expect_0 = Fr::from(ark_ff::BigInt([
795 1244302228903607218,
796 13386648721483054705,
797 0,
798 0,
799 ]));
800
801 let expect_1 = Fr::from(ark_ff::BigInt([
802 18225592963892023808,
803 2904666130704426303,
804 0,
805 0,
806 ]));
807 assert_eq!(actual_0, expect_0);
808 assert_eq!(actual_1, expect_1);
809 }
810
811 #[test]
812 fn test_hash_ascii_str_to_field() {
813 let actual = hash_ascii_str_to_field("sub", 32).unwrap();
814 let expect = Fr::from(ark_ff::BigInt([
815 9420274050661827129,
816 9736100402995345242,
817 10431892319505233812,
818 1450152190758097105,
819 ]));
820 assert_eq!(actual, expect);
821
822 let actual = hash_ascii_str_to_field("106294049240999307923", 115).unwrap();
823 let expect = Fr::from(ark_ff::BigInt([
824 1616959301818912987,
825 17318965991705091209,
826 15303466056770245354,
827 1596136658728187659,
828 ]));
829 assert_eq!(actual, expect);
830
831 let actual = hash_ascii_str_to_field(
832 "25769832374-famecqrhe2gkebt5fvqms2263046lj96.apps.googleusercontent.com",
833 145,
834 )
835 .unwrap();
836 let expect = Fr::from(ark_ff::BigInt([
837 5030944271044826582,
838 8577618269522081956,
839 6962871209781429610,
840 2149811477348923117,
841 ]));
842 assert_eq!(actual, expect);
843
844 let actual =
845 hash_ascii_str_to_field("yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC", 224)
846 .unwrap();
847 let expect = Fr::from(ark_ff::BigInt([
848 6021918591354572765,
849 14069258108381575504,
850 1736509627917450843,
851 2767185135515367512,
852 ]));
853 assert_eq!(actual, expect);
854
855 let actual = hash_ascii_str_to_field(
856 "eyJhbGciOiJSUzI1NiIsImtpZCI6IjZmNzI1NDEwMWY1NmU0MWNmMzVjOTkyNmRlODRhMmQ1NTJiNGM2ZjEiLCJ0eXAiOiJKV1QifQ",
857 248,
858 ).unwrap();
859 let expect = Fr::from(ark_ff::BigInt([
860 4239129243150064016,
861 15469804315138207306,
862 17534492051703966556,
863 2100329545252322607,
864 ]));
865 assert_eq!(actual, expect);
866 }
867}