sui_sdk_types/crypto/
zklogin.rs

1use super::SimpleSignature;
2use crate::checkpoint::EpochId;
3use crate::u256::U256;
4
5/// A zklogin authenticator
6///
7/// # BCS
8///
9/// The BCS serialized form for this type is defined by the following ABNF:
10///
11/// ```text
12/// zklogin-bcs = bytes             ; contents are defined by <zklogin-authenticator>
13/// zklogin     = zklogin-flag
14///               zklogin-inputs
15///               u64               ; max epoch
16///               simple-signature    
17/// ```
18///
19/// Note: Due to historical reasons, signatures are serialized slightly different from the majority
20/// of the types in Sui. In particular if a signature is ever embedded in another structure it
21/// generally is serialized as `bytes` meaning it has a length prefix that defines the length of
22/// the completely serialized signature.
23#[derive(Debug, Clone, PartialEq, Eq)]
24#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
25pub struct ZkLoginAuthenticator {
26    /// Zklogin proof and inputs required to perform proof verification.
27    pub inputs: ZkLoginInputs,
28
29    /// Maximum epoch for which the proof is valid.
30    pub max_epoch: EpochId,
31
32    /// User signature with the pubkey attested to by the provided proof.
33    pub signature: SimpleSignature,
34}
35
36/// A zklogin groth16 proof and the required inputs to perform proof verification.
37///
38/// # BCS
39///
40/// The BCS serialized form for this type is defined by the following ABNF:
41///
42/// ```text
43/// zklogin-inputs = zklogin-proof
44///                  zklogin-claim
45///                  string              ; base64url-unpadded encoded JwtHeader
46///                  bn254-field-element ; address_seed
47/// ```
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct ZkLoginInputs {
50    proof_points: ZkLoginProof,
51    iss_base64_details: ZkLoginClaim,
52    header_base64: String,
53
54    jwt_header: JwtHeader,
55    jwk_id: JwkId,
56    public_identifier: ZkLoginPublicIdentifier,
57}
58
59impl ZkLoginInputs {
60    #[cfg(feature = "serde")]
61    #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
62    pub fn new(
63        proof_points: ZkLoginProof,
64        iss_base64_details: ZkLoginClaim,
65        header_base64: String,
66        address_seed: Bn254FieldElement,
67    ) -> Result<Self, InvalidZkLoginAuthenticatorError> {
68        let iss = {
69            const ISS: &str = "iss";
70
71            let iss = iss_base64_details.verify_extended_claim(ISS)?;
72
73            if iss.len() > 255 {
74                return Err(InvalidZkLoginAuthenticatorError::new(
75                    "invalid iss: too long",
76                ));
77            }
78            iss
79        };
80
81        let jwt_header = JwtHeader::from_base64(&header_base64)?;
82        let jwk_id = JwkId {
83            iss: iss.clone(),
84            kid: jwt_header.kid.clone(),
85        };
86
87        let public_identifier = ZkLoginPublicIdentifier { iss, address_seed };
88
89        Ok(Self {
90            proof_points,
91            iss_base64_details,
92            header_base64,
93            jwt_header,
94            jwk_id,
95            public_identifier,
96        })
97    }
98
99    pub fn proof_points(&self) -> &ZkLoginProof {
100        &self.proof_points
101    }
102
103    pub fn iss_base64_details(&self) -> &ZkLoginClaim {
104        &self.iss_base64_details
105    }
106
107    pub fn header_base64(&self) -> &str {
108        &self.header_base64
109    }
110
111    pub fn address_seed(&self) -> &Bn254FieldElement {
112        &self.public_identifier.address_seed
113    }
114
115    pub fn jwk_id(&self) -> &JwkId {
116        &self.jwk_id
117    }
118
119    pub fn iss(&self) -> &str {
120        &self.public_identifier.iss
121    }
122
123    pub fn public_identifier(&self) -> &ZkLoginPublicIdentifier {
124        &self.public_identifier
125    }
126}
127
128#[cfg(feature = "proptest")]
129impl proptest::arbitrary::Arbitrary for ZkLoginInputs {
130    type Parameters = ();
131    type Strategy = proptest::strategy::BoxedStrategy<Self>;
132
133    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
134        use proptest::prelude::*;
135
136        (any::<ZkLoginProof>(), any::<Bn254FieldElement>())
137            .prop_map(|(proof_points, address_seed)| {
138                //TODO implement Arbitrary for real for ZkLoginClaim and header_base64 values
139                let iss_base64_details = ZkLoginClaim {
140                    value: "wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw".to_owned(),
141                    index_mod_4: 2,
142                };
143                let header_base64 = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ".to_owned();
144                Self::new(
145                    proof_points,
146                    iss_base64_details,
147                    header_base64,
148                    address_seed,
149                )
150                .unwrap()
151            })
152            .boxed()
153    }
154}
155
156/// A claim of the iss in a zklogin proof
157///
158/// # BCS
159///
160/// The BCS serialized form for this type is defined by the following ABNF:
161///
162/// ```text
163/// zklogin-claim = string u8
164/// ```
165#[derive(Debug, Clone, PartialEq, Eq)]
166#[cfg_attr(
167    feature = "serde",
168    derive(serde_derive::Serialize, serde_derive::Deserialize)
169)]
170#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
171pub struct ZkLoginClaim {
172    pub value: String,
173    pub index_mod_4: u8,
174}
175
176#[derive(Debug)]
177pub struct InvalidZkLoginAuthenticatorError(String);
178
179#[cfg(feature = "serde")]
180#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
181impl InvalidZkLoginAuthenticatorError {
182    fn new<T: Into<String>>(err: T) -> Self {
183        Self(err.into())
184    }
185}
186
187impl std::fmt::Display for InvalidZkLoginAuthenticatorError {
188    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
189        write!(f, "invalid zklogin claim: {}", self.0)
190    }
191}
192
193impl std::error::Error for InvalidZkLoginAuthenticatorError {}
194
195#[cfg(feature = "serde")]
196#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
197impl ZkLoginClaim {
198    fn verify_extended_claim(
199        &self,
200        expected_key: &str,
201    ) -> Result<String, InvalidZkLoginAuthenticatorError> {
202        /// Map a base64 string to a bit array by taking each char's index and convert it to binary form with one bit per u8
203        /// element in the output. Returns InvalidZkLoginClaimError if one of the characters is not in the base64 charset.
204        fn base64_to_bitarray(input: &str) -> Result<Vec<u8>, InvalidZkLoginAuthenticatorError> {
205            use itertools::Itertools;
206
207            const BASE64_URL_CHARSET: &str =
208                "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
209
210            input
211                .chars()
212                .map(|c| {
213                    BASE64_URL_CHARSET
214                        .find(c)
215                        .map(|index| index as u8)
216                        .map(|index| (0..6).rev().map(move |i| (index >> i) & 1))
217                        .ok_or_else(|| {
218                            InvalidZkLoginAuthenticatorError::new("base64_to_bitarry invalid input")
219                        })
220                })
221                .flatten_ok()
222                .collect()
223        }
224
225        /// Convert a bitarray (each bit is represented by a u8) to a byte array by taking each 8 bits as a
226        /// byte in big-endian format.
227        fn bitarray_to_bytearray(bits: &[u8]) -> Result<Vec<u8>, InvalidZkLoginAuthenticatorError> {
228            #[expect(clippy::manual_is_multiple_of)]
229            if bits.len() % 8 != 0 {
230                return Err(InvalidZkLoginAuthenticatorError::new(
231                    "bitarray_to_bytearray invalid input",
232                ));
233            }
234            Ok(bits
235                .chunks(8)
236                .map(|chunk| {
237                    let mut byte = 0u8;
238                    for (i, bit) in chunk.iter().rev().enumerate() {
239                        byte |= bit << i;
240                    }
241                    byte
242                })
243                .collect())
244        }
245
246        /// Parse the base64 string, add paddings based on offset, and convert to a bytearray.
247        fn decode_base64_url(
248            s: &str,
249            index_mod_4: &u8,
250        ) -> Result<String, InvalidZkLoginAuthenticatorError> {
251            if s.len() < 2 {
252                return Err(InvalidZkLoginAuthenticatorError::new(
253                    "Base64 string smaller than 2",
254                ));
255            }
256            let mut bits = base64_to_bitarray(s)?;
257            match index_mod_4 {
258                0 => {}
259                1 => {
260                    bits.drain(..2);
261                }
262                2 => {
263                    bits.drain(..4);
264                }
265                _ => {
266                    return Err(InvalidZkLoginAuthenticatorError::new(
267                        "Invalid first_char_offset",
268                    ));
269                }
270            }
271
272            let last_char_offset = (index_mod_4 + s.len() as u8 - 1) % 4;
273            match last_char_offset {
274                3 => {}
275                2 => {
276                    bits.drain(bits.len() - 2..);
277                }
278                1 => {
279                    bits.drain(bits.len() - 4..);
280                }
281                _ => {
282                    return Err(InvalidZkLoginAuthenticatorError::new(
283                        "Invalid last_char_offset",
284                    ));
285                }
286            }
287
288            if bits.len() % 8 != 0 {
289                return Err(InvalidZkLoginAuthenticatorError::new("Invalid bits length"));
290            }
291
292            Ok(std::str::from_utf8(&bitarray_to_bytearray(&bits)?)
293                .map_err(|_| InvalidZkLoginAuthenticatorError::new("Invalid UTF8 string"))?
294                .to_owned())
295        }
296
297        let extended_claim = decode_base64_url(&self.value, &self.index_mod_4)?;
298
299        // Last character of each extracted_claim must be '}' or ','
300        if !(extended_claim.ends_with('}') || extended_claim.ends_with(',')) {
301            return Err(InvalidZkLoginAuthenticatorError::new(
302                "Invalid extended claim",
303            ));
304        }
305
306        let json_str = format!("{{{}}}", &extended_claim[..extended_claim.len() - 1]);
307
308        serde_json::from_str::<serde_json::Value>(&json_str)
309            .map_err(|e| InvalidZkLoginAuthenticatorError::new(e.to_string()))?
310            .as_object_mut()
311            .and_then(|o| o.get_mut(expected_key))
312            .map(serde_json::Value::take)
313            .and_then(|v| match v {
314                serde_json::Value::String(s) => Some(s),
315                _ => None,
316            })
317            .ok_or_else(|| InvalidZkLoginAuthenticatorError::new("invalid extended claim"))
318    }
319}
320
321/// Struct that represents a standard JWT header according to
322/// https://openid.net/specs/openid-connect-core-1_0.html
323#[derive(Debug, Clone, PartialEq, Eq)]
324struct JwtHeader {
325    alg: String,
326    kid: String,
327    typ: Option<String>,
328}
329
330impl JwtHeader {
331    #[cfg(feature = "serde")]
332    fn from_base64(s: &str) -> Result<Self, InvalidZkLoginAuthenticatorError> {
333        use base64ct::Base64UrlUnpadded;
334        use base64ct::Encoding;
335
336        #[derive(serde_derive::Serialize, serde_derive::Deserialize)]
337        struct Header {
338            alg: String,
339            kid: String,
340            #[serde(skip_serializing_if = "Option::is_none")]
341            typ: Option<String>,
342        }
343
344        let header_bytes = Base64UrlUnpadded::decode_vec(s)
345            .map_err(|e| InvalidZkLoginAuthenticatorError::new(format!("invalid base64: {e}")))?;
346        let Header { alg, kid, typ } = serde_json::from_slice(&header_bytes)
347            .map_err(|e| InvalidZkLoginAuthenticatorError::new(format!("invalid json: {e}")))?;
348        if alg != "RS256" {
349            return Err(InvalidZkLoginAuthenticatorError::new(
350                "jwt alg must be RS256",
351            ));
352        }
353        Ok(Self { alg, kid, typ })
354    }
355}
356
357/// A zklogin groth16 proof
358///
359/// # BCS
360///
361/// The BCS serialized form for this type is defined by the following ABNF:
362///
363/// ```text
364/// zklogin-proof = circom-g1 circom-g2 circom-g1
365/// ```
366#[derive(Debug, Clone, PartialEq, Eq)]
367#[cfg_attr(
368    feature = "serde",
369    derive(serde_derive::Serialize, serde_derive::Deserialize)
370)]
371#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
372pub struct ZkLoginProof {
373    pub a: CircomG1,
374    pub b: CircomG2,
375    pub c: CircomG1,
376}
377
378/// A G1 point
379///
380/// This represents the canonical decimal representation of the projective coordinates in Fq.
381///
382/// # BCS
383///
384/// The BCS serialized form for this type is defined by the following ABNF:
385///
386/// ```text
387/// circom-g1 = %x03 3(bn254-field-element)
388/// ```
389#[derive(Clone, Debug, PartialEq, Eq)]
390#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
391pub struct CircomG1(pub [Bn254FieldElement; 3]);
392
393/// A G2 point
394///
395/// This represents the canonical decimal representation of the coefficients of the projective
396/// coordinates in Fq2.
397///
398/// # BCS
399///
400/// The BCS serialized form for this type is defined by the following ABNF:
401///
402/// ```text
403/// circom-g2 = %x03 3(%x02 2(bn254-field-element))
404/// ```
405#[derive(Clone, Debug, PartialEq, Eq)]
406#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
407pub struct CircomG2(pub [[Bn254FieldElement; 2]; 3]);
408
409/// Public Key equivalent for Zklogin authenticators
410///
411/// A `ZkLoginPublicIdentifier` is the equivalent of a public key for other account authenticators,
412/// and contains the information required to derive the onchain account [`Address`] for a Zklogin
413/// authenticator.
414///
415/// ## Note
416///
417/// Due to a historical bug that was introduced in the Sui Typescript SDK when the zklogin
418/// authenticator was first introduced, there are now possibly two "valid" addresses for each
419/// zklogin authenticator depending on the bit-pattern of the `address_seed` value.
420///
421/// The original bug incorrectly derived a zklogin's address by stripping any leading
422/// zero-bytes that could have been present in the 32-byte length `address_seed` value prior to
423/// hashing, leading to a different derived address. This incorrectly derived address was
424/// presented to users of various wallets, leading them to sending funds to these addresses
425/// that they couldn't access. Instead of letting these users lose any assets that were sent to
426/// these addresses, the Sui network decided to change the protocol to allow for a zklogin
427/// authenticator who's `address_seed` value had leading zero-bytes be authorized to sign for
428/// both the addresses derived from both the unpadded and padded `address_seed` value.
429///
430/// # BCS
431///
432/// The BCS serialized form for this type is defined by the following ABNF:
433///
434/// ```text
435/// zklogin-public-identifier-bcs = bytes ; where the contents are defined by
436///                                       ; <zklogin-public-identifier>
437///
438/// zklogin-public-identifier = zklogin-public-identifier-iss
439///                             address-seed
440///
441/// zklogin-public-identifier-unpadded = zklogin-public-identifier-iss
442///                                      address-seed-unpadded
443///
444/// ; The iss, or issuer, is a utf8 string that is less than 255 bytes long
445/// ; and is serialized with the iss's length in bytes as a u8 followed by
446/// ; the bytes of the iss
447/// zklogin-public-identifier-iss = u8 *255(OCTET)
448///
449/// ; A Bn254FieldElement serialized as a 32-byte big-endian value
450/// address-seed = 32(OCTET)
451///
452/// ; A Bn254FieldElement serialized as a 32-byte big-endian value
453/// ; with any leading zero bytes stripped
454/// address-seed-unpadded = %x00 / %x01-ff *31(OCTET)
455/// ```
456///
457/// [`Address`]: crate::Address
458#[derive(Clone, Debug, PartialEq, Eq)]
459#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
460pub struct ZkLoginPublicIdentifier {
461    iss: String,
462    address_seed: Bn254FieldElement,
463}
464
465impl ZkLoginPublicIdentifier {
466    pub fn new(iss: String, address_seed: Bn254FieldElement) -> Option<Self> {
467        if iss.len() > 255 {
468            None
469        } else {
470            Some(Self { iss, address_seed })
471        }
472    }
473
474    pub fn iss(&self) -> &str {
475        &self.iss
476    }
477
478    pub fn address_seed(&self) -> &Bn254FieldElement {
479        &self.address_seed
480    }
481}
482
483/// A JSON Web Key
484///
485/// Struct that contains info for a JWK. A list of them for different kids can
486/// be retrieved from the JWK endpoint (e.g. <https://www.googleapis.com/oauth2/v3/certs>).
487/// The JWK is used to verify the JWT token.
488///
489/// # BCS
490///
491/// The BCS serialized form for this type is defined by the following ABNF:
492///
493/// ```text
494/// jwk = string string string string
495/// ```
496#[derive(Clone, Debug, PartialEq, Eq, Hash)]
497#[cfg_attr(
498    feature = "serde",
499    derive(serde_derive::Serialize, serde_derive::Deserialize)
500)]
501#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
502pub struct Jwk {
503    /// Key type parameter, <https://datatracker.ietf.org/doc/html/rfc7517#section-4.1>
504    pub kty: String,
505
506    /// RSA public exponent, <https://datatracker.ietf.org/doc/html/rfc7517#section-9.3>
507    pub e: String,
508
509    /// RSA modulus, <https://datatracker.ietf.org/doc/html/rfc7517#section-9.3>
510    pub n: String,
511
512    /// Algorithm parameter, <https://datatracker.ietf.org/doc/html/rfc7517#section-4.4>
513    pub alg: String,
514}
515
516/// Key to uniquely identify a JWK
517///
518/// # BCS
519///
520/// The BCS serialized form for this type is defined by the following ABNF:
521///
522/// ```text
523/// jwk-id = string string
524/// ```
525#[derive(Clone, Debug, PartialEq, Eq, Hash)]
526#[cfg_attr(
527    feature = "serde",
528    derive(serde_derive::Serialize, serde_derive::Deserialize)
529)]
530#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
531pub struct JwkId {
532    /// The issuer or identity of the OIDC provider.
533    pub iss: String,
534
535    /// A key id use to uniquely identify a key from an OIDC provider.
536    pub kid: String,
537}
538
539/// A point on the BN254 elliptic curve.
540///
541/// This is a 32-byte, or 256-bit, value that is generally represented as radix10 when a
542/// human-readable display format is needed, and is represented as a 32-byte big-endian value while
543/// in memory.
544///
545/// # BCS
546///
547/// The BCS serialized form for this type is defined by the following ABNF:
548///
549/// ```text
550/// bn254-field-element = *DIGIT ; which is then interpreted as a radix10 encoded 32-byte value
551/// ```
552#[derive(Clone, Debug, Default, PartialEq, Eq)]
553#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
554pub struct Bn254FieldElement([u8; 32]);
555
556impl Bn254FieldElement {
557    pub const fn new(bytes: [u8; 32]) -> Self {
558        Self(bytes)
559    }
560
561    pub const fn from_str_radix_10(s: &str) -> Result<Self, Bn254FieldElementParseError> {
562        let u256 = match U256::from_str_radix(s, 10) {
563            Ok(u256) => u256,
564            Err(e) => return Err(Bn254FieldElementParseError(e)),
565        };
566        let be = u256.to_be();
567        Ok(Self(*be.digits()))
568    }
569
570    pub fn unpadded(&self) -> &[u8] {
571        let mut buf = self.0.as_slice();
572
573        while !buf.is_empty() && buf[0] == 0 {
574            buf = &buf[1..];
575        }
576
577        // If the value is '0' then just return a slice of length 1 of the final byte
578        if buf.is_empty() {
579            &self.0[31..]
580        } else {
581            buf
582        }
583    }
584
585    pub fn padded(&self) -> &[u8] {
586        &self.0
587    }
588}
589
590impl std::fmt::Display for Bn254FieldElement {
591    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
592        let u256 = U256::from_be(U256::from_digits(self.0));
593        let radix10 = u256.to_str_radix(10);
594        f.write_str(&radix10)
595    }
596}
597
598#[derive(Debug)]
599pub struct Bn254FieldElementParseError(bnum::errors::ParseIntError);
600
601impl std::fmt::Display for Bn254FieldElementParseError {
602    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
603        write!(f, "unable to parse radix10 encoded value {}", self.0)
604    }
605}
606
607impl std::error::Error for Bn254FieldElementParseError {}
608
609impl std::str::FromStr for Bn254FieldElement {
610    type Err = Bn254FieldElementParseError;
611
612    fn from_str(s: &str) -> Result<Self, Self::Err> {
613        let u256 = U256::from_str_radix(s, 10).map_err(Bn254FieldElementParseError)?;
614        let be = u256.to_be();
615        Ok(Self(*be.digits()))
616    }
617}
618
619#[cfg(test)]
620mod test {
621    use super::Bn254FieldElement;
622    use num_bigint::BigUint;
623    use proptest::prelude::*;
624    use std::str::FromStr;
625    use test_strategy::proptest;
626
627    #[cfg(target_arch = "wasm32")]
628    use wasm_bindgen_test::wasm_bindgen_test as test;
629
630    #[test]
631    fn unpadded_slice() {
632        let seed = Bn254FieldElement([0; 32]);
633        let zero: [u8; 1] = [0];
634        assert_eq!(seed.unpadded(), zero.as_slice());
635
636        let mut seed = Bn254FieldElement([1; 32]);
637        seed.0[0] = 0;
638        assert_eq!(seed.unpadded(), [1; 31].as_slice());
639    }
640
641    #[proptest]
642    fn dont_crash_on_large_inputs(
643        #[strategy(proptest::collection::vec(any::<u8>(), 33..1024))] bytes: Vec<u8>,
644    ) {
645        let big_int = BigUint::from_bytes_be(&bytes);
646        let radix10 = big_int.to_str_radix(10);
647
648        // doesn't crash
649        let _ = Bn254FieldElement::from_str(&radix10);
650    }
651
652    #[proptest]
653    fn valid_address_seeds(
654        #[strategy(proptest::collection::vec(any::<u8>(), 1..=32))] bytes: Vec<u8>,
655    ) {
656        let big_int = BigUint::from_bytes_be(&bytes);
657        let radix10 = big_int.to_str_radix(10);
658
659        let seed = Bn254FieldElement::from_str(&radix10).unwrap();
660        assert_eq!(radix10, seed.to_string());
661        // Ensure unpadded doesn't crash
662        seed.unpadded();
663    }
664}
665
666#[cfg(feature = "serde")]
667#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
668mod serialization {
669    use crate::SignatureScheme;
670
671    use super::*;
672    use serde::Deserialize;
673    use serde::Deserializer;
674    use serde::Serialize;
675    use serde::Serializer;
676    use serde_with::Bytes;
677    use serde_with::DeserializeAs;
678    use serde_with::SerializeAs;
679    use std::borrow::Cow;
680
681    // Serialized format is: iss_bytes_len || iss_bytes || padded_32_byte_address_seed.
682    impl Serialize for ZkLoginPublicIdentifier {
683        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
684        where
685            S: Serializer,
686        {
687            if serializer.is_human_readable() {
688                #[derive(serde_derive::Serialize)]
689                struct Readable<'a> {
690                    iss: &'a str,
691                    address_seed: &'a Bn254FieldElement,
692                }
693                let readable = Readable {
694                    iss: &self.iss,
695                    address_seed: &self.address_seed,
696                };
697                readable.serialize(serializer)
698            } else {
699                let mut buf = Vec::new();
700                let iss_bytes = self.iss.as_bytes();
701                buf.push(iss_bytes.len() as u8);
702                buf.extend(iss_bytes);
703
704                buf.extend(&self.address_seed.0);
705
706                serializer.serialize_bytes(&buf)
707            }
708        }
709    }
710
711    impl<'de> Deserialize<'de> for ZkLoginPublicIdentifier {
712        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
713        where
714            D: Deserializer<'de>,
715        {
716            if deserializer.is_human_readable() {
717                #[derive(serde_derive::Deserialize)]
718                struct Readable {
719                    iss: String,
720                    address_seed: Bn254FieldElement,
721                }
722
723                let Readable { iss, address_seed } = Deserialize::deserialize(deserializer)?;
724                Self::new(iss, address_seed)
725                    .ok_or_else(|| serde::de::Error::custom("invalid zklogin public identifier"))
726            } else {
727                let bytes: Cow<'de, [u8]> = Bytes::deserialize_as(deserializer)?;
728                let iss_len = *bytes
729                    .first()
730                    .ok_or_else(|| serde::de::Error::custom("invalid zklogin public identifier"))?;
731                let iss_bytes = bytes
732                    .get(1..(1 + iss_len as usize))
733                    .ok_or_else(|| serde::de::Error::custom("invalid zklogin public identifier"))?;
734                let iss = std::str::from_utf8(iss_bytes).map_err(serde::de::Error::custom)?;
735                let address_seed_bytes = bytes
736                    .get((1 + iss_len as usize)..)
737                    .ok_or_else(|| serde::de::Error::custom("invalid zklogin public identifier"))?;
738
739                let address_seed = <[u8; 32]>::try_from(address_seed_bytes)
740                    .map_err(serde::de::Error::custom)
741                    .map(Bn254FieldElement)?;
742
743                Self::new(iss.into(), address_seed)
744                    .ok_or_else(|| serde::de::Error::custom("invalid zklogin public identifier"))
745            }
746        }
747    }
748
749    #[derive(serde_derive::Serialize)]
750    struct AuthenticatorRef<'a> {
751        inputs: &'a ZkLoginInputs,
752        max_epoch: EpochId,
753        signature: &'a SimpleSignature,
754    }
755
756    #[derive(serde_derive::Deserialize)]
757    struct Authenticator {
758        inputs: ZkLoginInputs,
759        max_epoch: EpochId,
760        signature: SimpleSignature,
761    }
762
763    impl Serialize for ZkLoginAuthenticator {
764        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
765        where
766            S: Serializer,
767        {
768            if serializer.is_human_readable() {
769                let authenticator_ref = AuthenticatorRef {
770                    inputs: &self.inputs,
771                    max_epoch: self.max_epoch,
772                    signature: &self.signature,
773                };
774
775                authenticator_ref.serialize(serializer)
776            } else {
777                let bytes = self.to_bytes();
778                serializer.serialize_bytes(&bytes)
779            }
780        }
781    }
782
783    impl<'de> Deserialize<'de> for ZkLoginAuthenticator {
784        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
785        where
786            D: Deserializer<'de>,
787        {
788            if deserializer.is_human_readable() {
789                let Authenticator {
790                    inputs,
791                    max_epoch,
792                    signature,
793                } = Authenticator::deserialize(deserializer)?;
794                Ok(Self {
795                    inputs,
796                    max_epoch,
797                    signature,
798                })
799            } else {
800                let bytes: Cow<'de, [u8]> = Bytes::deserialize_as(deserializer)?;
801                Self::from_serialized_bytes(bytes)
802            }
803        }
804    }
805
806    impl ZkLoginAuthenticator {
807        pub(crate) fn to_bytes(&self) -> Vec<u8> {
808            let authenticator_ref = AuthenticatorRef {
809                inputs: &self.inputs,
810                max_epoch: self.max_epoch,
811                signature: &self.signature,
812            };
813
814            let mut buf = Vec::new();
815            buf.push(SignatureScheme::ZkLogin as u8);
816
817            bcs::serialize_into(&mut buf, &authenticator_ref).expect("serialization cannot fail");
818            buf
819        }
820
821        pub(crate) fn from_serialized_bytes<T: AsRef<[u8]>, E: serde::de::Error>(
822            bytes: T,
823        ) -> Result<Self, E> {
824            let bytes = bytes.as_ref();
825            let flag = SignatureScheme::from_byte(
826                *bytes
827                    .first()
828                    .ok_or_else(|| serde::de::Error::custom("missing signature scheme flag"))?,
829            )
830            .map_err(serde::de::Error::custom)?;
831            if flag != SignatureScheme::ZkLogin {
832                return Err(serde::de::Error::custom("invalid zklogin flag"));
833            }
834            let bcs_bytes = &bytes[1..];
835
836            let Authenticator {
837                inputs,
838                max_epoch,
839                signature,
840            } = bcs::from_bytes(bcs_bytes).map_err(serde::de::Error::custom)?;
841            Ok(Self {
842                inputs,
843                max_epoch,
844                signature,
845            })
846        }
847    }
848
849    impl Serialize for ZkLoginInputs {
850        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
851        where
852            S: Serializer,
853        {
854            #[derive(serde_derive::Serialize)]
855            struct Inputs<'a> {
856                proof_points: &'a ZkLoginProof,
857                iss_base64_details: &'a ZkLoginClaim,
858                header_base64: &'a str,
859                address_seed: &'a Bn254FieldElement,
860            }
861
862            Inputs {
863                proof_points: self.proof_points(),
864                iss_base64_details: self.iss_base64_details(),
865                header_base64: self.header_base64(),
866                address_seed: self.address_seed(),
867            }
868            .serialize(serializer)
869        }
870    }
871
872    impl<'de> Deserialize<'de> for ZkLoginInputs {
873        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
874        where
875            D: Deserializer<'de>,
876        {
877            #[derive(serde_derive::Deserialize)]
878            struct Inputs {
879                proof_points: ZkLoginProof,
880                iss_base64_details: ZkLoginClaim,
881                header_base64: String,
882                address_seed: Bn254FieldElement,
883            }
884
885            let Inputs {
886                proof_points,
887                iss_base64_details,
888                header_base64,
889                address_seed,
890            } = Inputs::deserialize(deserializer)?;
891            Self::new(
892                proof_points,
893                iss_base64_details,
894                header_base64,
895                address_seed,
896            )
897            .map_err(serde::de::Error::custom)
898        }
899    }
900
901    // AddressSeed's serialized format is as a radix10 encoded string
902    impl Serialize for Bn254FieldElement {
903        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
904        where
905            S: serde::Serializer,
906        {
907            serde_with::DisplayFromStr::serialize_as(self, serializer)
908        }
909    }
910
911    impl<'de> Deserialize<'de> for Bn254FieldElement {
912        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
913        where
914            D: Deserializer<'de>,
915        {
916            serde_with::DisplayFromStr::deserialize_as(deserializer)
917        }
918    }
919
920    impl Serialize for CircomG1 {
921        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
922        where
923            S: serde::Serializer,
924        {
925            use serde::ser::SerializeSeq;
926            let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
927            for element in &self.0 {
928                seq.serialize_element(element)?;
929            }
930            seq.end()
931        }
932    }
933
934    impl<'de> Deserialize<'de> for CircomG1 {
935        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
936        where
937            D: Deserializer<'de>,
938        {
939            let inner = <Vec<_>>::deserialize(deserializer)?;
940            Ok(Self(inner.try_into().map_err(|_| {
941                serde::de::Error::custom("expected array of length 3")
942            })?))
943        }
944    }
945
946    impl Serialize for CircomG2 {
947        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
948        where
949            S: serde::Serializer,
950        {
951            use serde::ser::SerializeSeq;
952
953            struct Inner<'a>(&'a [Bn254FieldElement; 2]);
954
955            impl Serialize for Inner<'_> {
956                fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
957                where
958                    S: serde::Serializer,
959                {
960                    let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
961                    for element in self.0 {
962                        seq.serialize_element(element)?;
963                    }
964                    seq.end()
965                }
966            }
967
968            let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
969            for element in &self.0 {
970                seq.serialize_element(&Inner(element))?;
971            }
972            seq.end()
973        }
974    }
975
976    impl<'de> Deserialize<'de> for CircomG2 {
977        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
978        where
979            D: Deserializer<'de>,
980        {
981            let vecs = <Vec<Vec<Bn254FieldElement>>>::deserialize(deserializer)?;
982            let mut inner: [[Bn254FieldElement; 2]; 3] = Default::default();
983
984            if vecs.len() != 3 {
985                return Err(serde::de::Error::custom(
986                    "vector of three vectors each being a vector of two strings",
987                ));
988            }
989
990            for (i, v) in vecs.into_iter().enumerate() {
991                if v.len() != 2 {
992                    return Err(serde::de::Error::custom(
993                        "vector of three vectors each being a vector of two strings",
994                    ));
995                }
996
997                for (j, point) in v.into_iter().enumerate() {
998                    inner[i][j] = point;
999                }
1000            }
1001
1002            Ok(Self(inner))
1003        }
1004    }
1005}