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