1use binius_core::word::Word;
4use binius_frontend::{CircuitBuilder, Wire, WitnessFiller};
5
6use crate::slice;
7
8pub struct Attribute {
10 pub name: &'static str,
11 pub len_bytes: Wire,
13 pub value: Vec<Wire>,
14}
15
16impl Attribute {
17 pub fn populate_len_bytes(&self, w: &mut WitnessFiller<'_>, len_bytes: usize) {
19 w[self.len_bytes] = Word(len_bytes as u64);
20 }
21
22 pub fn populate_value(&self, w: &mut WitnessFiller<'_>, value: &[u8]) {
27 w.pack_bytes_le(&self.value, value);
28 }
29}
30
31pub struct JwtClaims {
48 pub len_bytes: Wire,
50 pub json: Vec<Wire>,
51 pub attributes: Vec<Attribute>,
52}
53
54impl JwtClaims {
55 pub fn new(
63 b: &CircuitBuilder,
64 len_bytes: Wire,
65 json: Vec<Wire>,
66 attributes: Vec<Attribute>,
67 ) -> Self {
68 let max_len_bytes = json.len() << 3;
73 let too_long = b.icmp_ult(b.add_constant_64(max_len_bytes as u64), len_bytes);
74 b.assert_false("length check", too_long);
75
76 for (attr_idx, attr) in attributes.iter().enumerate() {
77 let b = b.subcircuit(format!("attr[ix={}, name={}]", attr_idx, attr.name));
78
79 let pattern = format!("\"{}\":\"", attr.name);
81 let pattern_bytes = pattern.as_bytes();
82 let pattern_len = pattern_bytes.len();
83
84 let zero = b.add_constant(Word::ZERO);
94 let mut value_start = zero;
95 let mut found_start = zero;
96
97 for start_pos in 0..max_len_bytes.saturating_sub(pattern_len) {
99 let b = b.subcircuit(format!("start_pos[{start_pos}]"));
100
101 let end_wire = b.add_constant(Word((start_pos + pattern_len) as u64));
103
104 let within_bounds = b.icmp_ult(end_wire, len_bytes);
106 let mut matches_here = within_bounds;
108
109 for (i, &expected_byte) in pattern_bytes.iter().enumerate() {
111 let byte_pos = start_pos + i;
112 let word_idx = byte_pos / 8;
113 let byte_offset = byte_pos % 8;
114
115 let actual_byte = b.extract_byte(json[word_idx], byte_offset as u32);
116 let expected = b.add_constant(Word(expected_byte as u64));
117 let byte_matches = b.icmp_eq(actual_byte, expected);
118 matches_here = b.band(matches_here, byte_matches);
119 }
120
121 value_start = b.select(matches_here, end_wire, value_start);
123 found_start = b.bor(found_start, matches_here);
124 }
125
126 b.assert_true("attr_found", found_start);
128
129 let mut value_end = zero;
136 let mut found_end = zero;
137 let quote = b.add_constant_zx_8(b'"');
138 let comma = b.add_constant_zx_8(b',');
139 let close_brace = b.add_constant_zx_8(b'}');
140 for pos in 0..max_len_bytes {
143 let b = b.subcircuit(format!("find_terminator[{pos}]"));
144
145 let pos_wire = b.add_constant(Word(pos as u64));
146 let within_bounds = b.icmp_ult(pos_wire, len_bytes);
147
148 let at_or_after_start = b.bnot(b.icmp_ult(pos_wire, value_start));
151 let not_found_yet = b.bnot(found_end);
152 let should_check = b.band(b.band(at_or_after_start, within_bounds), not_found_yet);
153
154 let word_idx = pos / 8;
156 let byte_offset = pos % 8;
157
158 let byte_at_pos = b.extract_byte(json[word_idx], byte_offset as u32);
159
160 let is_quote = b.icmp_eq(byte_at_pos, quote);
162 let is_comma = b.icmp_eq(byte_at_pos, comma);
163 let is_close_brace = b.icmp_eq(byte_at_pos, close_brace);
164
165 let is_terminator = b.bxor(b.bxor(is_quote, is_comma), is_close_brace);
169
170 let found_here = b.band(should_check, is_terminator);
171
172 value_end = b.select(found_here, pos_wire, value_end);
176 found_end = b.bxor(found_end, found_here);
180 }
181
182 b.assert_true("attr_terminator_found", found_end);
184
185 let (value_length, _borrow) = b.isub_bin_bout(value_end, value_start, zero);
189
190 b.assert_eq("attr_length", value_length, attr.len_bytes);
192
193 let extracted =
195 slice::slice(&b, len_bytes, value_length, &json, value_start, attr.value.len());
196 slice::assert_slice_eq(&b, "attr_value", value_length, &extracted, &attr.value);
197 }
198
199 JwtClaims {
200 len_bytes,
201 json,
202 attributes,
203 }
204 }
205
206 pub fn populate_len_bytes(&self, w: &mut WitnessFiller<'_>, len_bytes: usize) {
208 w[self.len_bytes] = Word(len_bytes as u64);
209 }
210
211 pub fn populate_json(&self, w: &mut WitnessFiller<'_>, json: &[u8]) {
216 w.pack_bytes_le(&self.json, json);
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use binius_frontend::CircuitBuilder;
223
224 use super::{Attribute, JwtClaims, Wire};
225
226 #[test]
227 fn test_single_attribute() {
228 let b = CircuitBuilder::new();
229
230 let len_json = b.add_witness();
231 let json: Vec<Wire> = (0..32).map(|_| b.add_witness()).collect();
232
233 let attributes = vec![Attribute {
234 name: "sub",
235 len_bytes: b.add_inout(),
236 value: (0..2).map(|_| b.add_inout()).collect(),
237 }];
238
239 let jwt_claims = JwtClaims::new(&b, len_json, json, attributes);
240
241 let circuit = b.build();
242 let mut filler = circuit.new_witness_filler();
243
244 let json_str = r#"{"sub":"1234567890","iss":"google.com"}"#;
245
246 jwt_claims.populate_len_bytes(&mut filler, json_str.len());
248 jwt_claims.populate_json(&mut filler, json_str.as_bytes());
249
250 jwt_claims.attributes[0].populate_len_bytes(&mut filler, 10);
252 jwt_claims.attributes[0].populate_value(&mut filler, b"1234567890");
253
254 circuit.populate_wire_witness(&mut filler).unwrap();
255
256 let cs = circuit.constraint_system();
258 cs.verify(&filler.into_value_vec()).unwrap();
259 }
260
261 #[test]
262 fn test_multiple_attributes() {
263 let b = CircuitBuilder::new();
264
265 let len_bytes = b.add_witness();
266 let json: Vec<Wire> = (0..32).map(|_| b.add_witness()).collect();
267
268 let attributes = vec![
269 Attribute {
270 name: "sub",
271 len_bytes: b.add_inout(),
272 value: (0..2).map(|_| b.add_inout()).collect(),
273 },
274 Attribute {
275 name: "iss",
276 len_bytes: b.add_inout(),
277 value: (0..4).map(|_| b.add_inout()).collect(),
278 },
279 Attribute {
280 name: "aud",
281 len_bytes: b.add_inout(),
282 value: (0..2).map(|_| b.add_inout()).collect(),
283 },
284 ];
285
286 let jwt_claims = JwtClaims::new(&b, len_bytes, json, attributes);
287
288 let circuit = b.build();
289 let mut filler = circuit.new_witness_filler();
290
291 let json_str =
293 r#"{"sub":"1234567890","iss":"google.com","aud":"4074087","iat":1676415809}"#;
294
295 jwt_claims.populate_len_bytes(&mut filler, json_str.len());
297 jwt_claims.populate_json(&mut filler, json_str.as_bytes());
298
299 jwt_claims.attributes[0].populate_len_bytes(&mut filler, 10);
301 jwt_claims.attributes[0].populate_value(&mut filler, b"1234567890");
302
303 jwt_claims.attributes[1].populate_len_bytes(&mut filler, 10);
304 jwt_claims.attributes[1].populate_value(&mut filler, b"google.com");
305
306 jwt_claims.attributes[2].populate_len_bytes(&mut filler, 7);
307 jwt_claims.attributes[2].populate_value(&mut filler, b"4074087");
308
309 circuit.populate_wire_witness(&mut filler).unwrap();
310
311 let cs = circuit.constraint_system();
313 cs.verify(&filler.into_value_vec()).unwrap();
314 }
315
316 #[test]
317 fn test_attribute_not_found() {
318 let b = CircuitBuilder::new();
319
320 let len_bytes = b.add_witness();
321 let json: Vec<Wire> = (0..16).map(|_| b.add_witness()).collect();
322
323 let attributes = vec![Attribute {
324 name: "missing",
325 len_bytes: b.add_inout(),
326 value: (0..2).map(|_| b.add_inout()).collect(),
327 }];
328
329 let jwt_claims = JwtClaims::new(&b, len_bytes, json, attributes);
330
331 let circuit = b.build();
332 let mut filler = circuit.new_witness_filler();
333
334 let json_str = r#"{"sub":"1234567890","iss":"google.com"}"#;
336
337 jwt_claims.populate_len_bytes(&mut filler, json_str.len());
339 jwt_claims.populate_json(&mut filler, json_str.as_bytes());
340
341 jwt_claims.attributes[0].populate_len_bytes(&mut filler, 5);
343 jwt_claims.attributes[0].populate_value(&mut filler, b"value");
344
345 let result = circuit.populate_wire_witness(&mut filler);
347 assert!(result.is_err());
348 }
349
350 #[test]
351 fn test_wrong_value() {
352 let b = CircuitBuilder::new();
353
354 let len_bytes = b.add_witness();
355 let json: Vec<Wire> = (0..16).map(|_| b.add_witness()).collect();
356
357 let attributes = vec![Attribute {
358 name: "sub",
359 len_bytes: b.add_inout(),
360 value: (0..2).map(|_| b.add_inout()).collect(),
361 }];
362
363 let jwt_claims = JwtClaims::new(&b, len_bytes, json, attributes);
364
365 let circuit = b.build();
366 let mut filler = circuit.new_witness_filler();
367
368 let json_str = r#"{"sub":"1234567890","iss":"google.com"}"#;
370
371 jwt_claims.populate_len_bytes(&mut filler, json_str.len());
373 jwt_claims.populate_json(&mut filler, json_str.as_bytes());
374
375 jwt_claims.attributes[0].populate_len_bytes(&mut filler, 10);
377 jwt_claims.attributes[0].populate_value(&mut filler, b"9876543210");
378
379 let result = circuit.populate_wire_witness(&mut filler);
381 assert!(result.is_err());
382 }
383
384 #[test]
385 fn test_attributes_in_different_order() {
386 let b = CircuitBuilder::new();
387
388 let len_bytes = b.add_witness();
389 let json: Vec<Wire> = (0..32).map(|_| b.add_witness()).collect();
390
391 let attributes = vec![
392 Attribute {
393 name: "aud",
394 len_bytes: b.add_inout(),
395 value: (0..16 / 8).map(|_| b.add_inout()).collect(),
396 },
397 Attribute {
398 name: "sub",
399 len_bytes: b.add_inout(),
400 value: (0..16 / 8).map(|_| b.add_inout()).collect(),
401 },
402 ];
403
404 let jwt_claims = JwtClaims::new(&b, len_bytes, json, attributes);
405
406 let circuit = b.build();
407 let mut filler = circuit.new_witness_filler();
408
409 let json_str =
411 r#"{"iss":"google.com","sub":"1234567890","email":"test@example.com","aud":"4074087"}"#;
412
413 jwt_claims.populate_len_bytes(&mut filler, json_str.len());
415 jwt_claims.populate_json(&mut filler, json_str.as_bytes());
416
417 jwt_claims.attributes[0].populate_len_bytes(&mut filler, 7);
419 jwt_claims.attributes[0].populate_value(&mut filler, b"4074087");
420
421 jwt_claims.attributes[1].populate_len_bytes(&mut filler, 10);
422 jwt_claims.attributes[1].populate_value(&mut filler, b"1234567890");
423
424 circuit.populate_wire_witness(&mut filler).unwrap();
425
426 let cs = circuit.constraint_system();
428 cs.verify(&filler.into_value_vec()).unwrap();
429 }
430
431 #[test]
432 fn test_empty_string_value() {
433 let b = CircuitBuilder::new();
434
435 let len_bytes = b.add_witness();
436 let json: Vec<Wire> = (0..16).map(|_| b.add_witness()).collect();
437
438 let attributes = vec![Attribute {
439 name: "empty",
440 len_bytes: b.add_inout(),
441 value: (0..1).map(|_| b.add_inout()).collect(),
442 }];
443
444 let jwt_claims = JwtClaims::new(&b, len_bytes, json, attributes);
445
446 let circuit = b.build();
447 let mut filler = circuit.new_witness_filler();
448
449 let json_str = r#"{"empty":"","sub":"123"}"#;
451
452 jwt_claims.populate_len_bytes(&mut filler, json_str.len());
454 jwt_claims.populate_json(&mut filler, json_str.as_bytes());
455
456 jwt_claims.attributes[0].populate_len_bytes(&mut filler, 0);
458 jwt_claims.attributes[0].populate_value(&mut filler, b"");
459
460 circuit.populate_wire_witness(&mut filler).unwrap();
461
462 let cs = circuit.constraint_system();
464 cs.verify(&filler.into_value_vec()).unwrap();
465 }
466
467 #[test]
468 fn test_special_characters() {
469 let b = CircuitBuilder::new();
470
471 let len_bytes = b.add_witness();
472 let json: Vec<Wire> = (0..32).map(|_| b.add_witness()).collect();
473
474 let attributes = vec![
475 Attribute {
476 name: "email",
477 len_bytes: b.add_inout(),
478 value: (0..4).map(|_| b.add_inout()).collect(),
479 },
480 Attribute {
481 name: "nonce",
482 len_bytes: b.add_inout(),
483 value: (0..4).map(|_| b.add_inout()).collect(),
484 },
485 ];
486
487 let jwt_claims = JwtClaims::new(&b, len_bytes, json, attributes);
488
489 let circuit = b.build();
490 let mut filler = circuit.new_witness_filler();
491
492 let json_str = r#"{"email":"john.doe@gmail.com","nonce":"7-VU9fuWeWtgDLHmVJ2UtRrine8"}"#;
494
495 jwt_claims.populate_len_bytes(&mut filler, json_str.len());
497 jwt_claims.populate_json(&mut filler, json_str.as_bytes());
498
499 jwt_claims.attributes[0].populate_len_bytes(&mut filler, 18);
501 jwt_claims.attributes[0].populate_value(&mut filler, b"john.doe@gmail.com");
502
503 jwt_claims.attributes[1].populate_len_bytes(&mut filler, 27);
504 jwt_claims.attributes[1].populate_value(&mut filler, b"7-VU9fuWeWtgDLHmVJ2UtRrine8");
505
506 circuit.populate_wire_witness(&mut filler).unwrap();
507
508 let cs = circuit.constraint_system();
510 cs.verify(&filler.into_value_vec()).unwrap();
511 }
512
513 #[test]
514 fn test_last_attribute_no_comma() {
515 let b = CircuitBuilder::new();
516
517 let len_bytes = b.add_witness();
518 let json: Vec<Wire> = (0..16).map(|_| b.add_witness()).collect();
519
520 let attributes = vec![
521 Attribute {
522 name: "iss",
523 len_bytes: b.add_inout(),
524 value: (0..16 / 8).map(|_| b.add_inout()).collect(),
525 },
526 Attribute {
527 name: "last",
528 len_bytes: b.add_inout(),
529 value: (0..16 / 8).map(|_| b.add_inout()).collect(),
530 },
531 ];
532
533 let jwt_claims = JwtClaims::new(&b, len_bytes, json, attributes);
534
535 let circuit = b.build();
536 let mut filler = circuit.new_witness_filler();
537
538 let json_str = r#"{"iss":"example.com","last":"value123"}"#;
540
541 jwt_claims.populate_len_bytes(&mut filler, json_str.len());
543 jwt_claims.populate_json(&mut filler, json_str.as_bytes());
544
545 jwt_claims.attributes[0].populate_len_bytes(&mut filler, 11);
547 jwt_claims.attributes[0].populate_value(&mut filler, b"example.com");
548
549 jwt_claims.attributes[1].populate_len_bytes(&mut filler, 8);
550 jwt_claims.attributes[1].populate_value(&mut filler, b"value123");
551
552 circuit.populate_wire_witness(&mut filler).unwrap();
553
554 let cs = circuit.constraint_system();
556 cs.verify(&filler.into_value_vec()).unwrap();
557 }
558}