@@ -58,6 +58,32 @@ pub struct ZkLoginInputs {
58
58
pub address_seed : Bn254FieldElement ,
59
59
}
60
60
61
+ impl ZkLoginInputs {
62
+ #[ cfg( feature = "serde" ) ]
63
+ #[ cfg_attr( doc_cfg, doc( cfg( feature = "serde" ) ) ) ]
64
+ pub fn iss ( & self ) -> Result < String , InvalidZkLoginClaimError > {
65
+ const ISS : & str = "iss" ;
66
+
67
+ let iss = self . iss_base64_details . verify_extended_claim ( ISS ) ?;
68
+
69
+ if iss. len ( ) > 255 {
70
+ Err ( InvalidZkLoginClaimError :: new ( "invalid iss: too long" ) )
71
+ } else {
72
+ Ok ( iss)
73
+ }
74
+ }
75
+
76
+ #[ cfg( feature = "serde" ) ]
77
+ #[ cfg_attr( doc_cfg, doc( cfg( feature = "serde" ) ) ) ]
78
+ pub fn public_identifier ( & self ) -> Result < ZkLoginPublicIdentifier , InvalidZkLoginClaimError > {
79
+ let iss = self . iss ( ) ?;
80
+ Ok ( ZkLoginPublicIdentifier {
81
+ iss,
82
+ address_seed : self . address_seed . clone ( ) ,
83
+ } )
84
+ }
85
+ }
86
+
61
87
/// A claim of the iss in a zklogin proof
62
88
///
63
89
/// # BCS
@@ -78,6 +104,144 @@ pub struct ZkLoginClaim {
78
104
pub index_mod_4 : u8 ,
79
105
}
80
106
107
+ #[ derive( Debug ) ]
108
+ pub struct InvalidZkLoginClaimError ( String ) ;
109
+
110
+ #[ cfg( feature = "serde" ) ]
111
+ #[ cfg_attr( doc_cfg, doc( cfg( feature = "serde" ) ) ) ]
112
+ impl InvalidZkLoginClaimError {
113
+ fn new < T : Into < String > > ( err : T ) -> Self {
114
+ Self ( err. into ( ) )
115
+ }
116
+ }
117
+
118
+ impl std:: fmt:: Display for InvalidZkLoginClaimError {
119
+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
120
+ write ! ( f, "invalid zklogin claim: {}" , self . 0 )
121
+ }
122
+ }
123
+
124
+ impl std:: error:: Error for InvalidZkLoginClaimError { }
125
+
126
+ #[ cfg( feature = "serde" ) ]
127
+ #[ cfg_attr( doc_cfg, doc( cfg( feature = "serde" ) ) ) ]
128
+ impl ZkLoginClaim {
129
+ fn verify_extended_claim (
130
+ & self ,
131
+ expected_key : & str ,
132
+ ) -> Result < String , InvalidZkLoginClaimError > {
133
+ /// 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
134
+ /// element in the output. Returns InvalidZkLoginClaimError if one of the characters is not in the base64 charset.
135
+ fn base64_to_bitarray ( input : & str ) -> Result < Vec < u8 > , InvalidZkLoginClaimError > {
136
+ use itertools:: Itertools ;
137
+
138
+ const BASE64_URL_CHARSET : & str =
139
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" ;
140
+
141
+ input
142
+ . chars ( )
143
+ . map ( |c| {
144
+ BASE64_URL_CHARSET
145
+ . find ( c)
146
+ . map ( |index| index as u8 )
147
+ . map ( |index| ( 0 ..6 ) . rev ( ) . map ( move |i| ( index >> i) & 1 ) )
148
+ . ok_or_else ( || {
149
+ InvalidZkLoginClaimError :: new ( "base64_to_bitarry invalid input" )
150
+ } )
151
+ } )
152
+ . flatten_ok ( )
153
+ . collect ( )
154
+ }
155
+
156
+ /// Convert a bitarray (each bit is represented by a u8) to a byte array by taking each 8 bits as a
157
+ /// byte in big-endian format.
158
+ fn bitarray_to_bytearray ( bits : & [ u8 ] ) -> Result < Vec < u8 > , InvalidZkLoginClaimError > {
159
+ if bits. len ( ) % 8 != 0 {
160
+ return Err ( InvalidZkLoginClaimError :: new (
161
+ "bitarray_to_bytearray invalid input" ,
162
+ ) ) ;
163
+ }
164
+ Ok ( bits
165
+ . chunks ( 8 )
166
+ . map ( |chunk| {
167
+ let mut byte = 0u8 ;
168
+ for ( i, bit) in chunk. iter ( ) . rev ( ) . enumerate ( ) {
169
+ byte |= bit << i;
170
+ }
171
+ byte
172
+ } )
173
+ . collect ( ) )
174
+ }
175
+
176
+ /// Parse the base64 string, add paddings based on offset, and convert to a bytearray.
177
+ fn decode_base64_url (
178
+ s : & str ,
179
+ index_mod_4 : & u8 ,
180
+ ) -> Result < String , InvalidZkLoginClaimError > {
181
+ if s. len ( ) < 2 {
182
+ return Err ( InvalidZkLoginClaimError :: new (
183
+ "Base64 string smaller than 2" ,
184
+ ) ) ;
185
+ }
186
+ let mut bits = base64_to_bitarray ( s) ?;
187
+ match index_mod_4 {
188
+ 0 => { }
189
+ 1 => {
190
+ bits. drain ( ..2 ) ;
191
+ }
192
+ 2 => {
193
+ bits. drain ( ..4 ) ;
194
+ }
195
+ _ => {
196
+ return Err ( InvalidZkLoginClaimError :: new ( "Invalid first_char_offset" ) ) ;
197
+ }
198
+ }
199
+
200
+ let last_char_offset = ( index_mod_4 + s. len ( ) as u8 - 1 ) % 4 ;
201
+ match last_char_offset {
202
+ 3 => { }
203
+ 2 => {
204
+ bits. drain ( bits. len ( ) - 2 ..) ;
205
+ }
206
+ 1 => {
207
+ bits. drain ( bits. len ( ) - 4 ..) ;
208
+ }
209
+ _ => {
210
+ return Err ( InvalidZkLoginClaimError :: new ( "Invalid last_char_offset" ) ) ;
211
+ }
212
+ }
213
+
214
+ if bits. len ( ) % 8 != 0 {
215
+ return Err ( InvalidZkLoginClaimError :: new ( "Invalid bits length" ) ) ;
216
+ }
217
+
218
+ Ok ( std:: str:: from_utf8 ( & bitarray_to_bytearray ( & bits) ?)
219
+ . map_err ( |_| InvalidZkLoginClaimError :: new ( "Invalid UTF8 string" ) ) ?
220
+ . to_owned ( ) )
221
+ }
222
+
223
+ let extended_claim = decode_base64_url ( & self . value , & self . index_mod_4 ) ?;
224
+
225
+ // Last character of each extracted_claim must be '}' or ','
226
+ if !( extended_claim. ends_with ( '}' ) || extended_claim. ends_with ( ',' ) ) {
227
+ return Err ( InvalidZkLoginClaimError :: new ( "Invalid extended claim" ) ) ;
228
+ }
229
+
230
+ let json_str = format ! ( "{{{}}}" , & extended_claim[ ..extended_claim. len( ) - 1 ] ) ;
231
+
232
+ serde_json:: from_str :: < serde_json:: Value > ( & json_str)
233
+ . map_err ( |e| InvalidZkLoginClaimError :: new ( e. to_string ( ) ) ) ?
234
+ . as_object_mut ( )
235
+ . and_then ( |o| o. get_mut ( expected_key) )
236
+ . map ( serde_json:: Value :: take)
237
+ . and_then ( |v| match v {
238
+ serde_json:: Value :: String ( s) => Some ( s) ,
239
+ _ => None ,
240
+ } )
241
+ . ok_or_else ( || InvalidZkLoginClaimError :: new ( "invalid extended claim" ) )
242
+ }
243
+ }
244
+
81
245
/// A zklogin groth16 proof
82
246
///
83
247
/// # BCS
0 commit comments