1use std::iter;
18
19use binius_core::Word;
20use binius_frontend::{CircuitBuilder, CircuitM4, Wire, WitnessFiller};
21
22use super::{
23 DIGEST_LEN, DIGEST_WIRES, MESSAGE_WIRES, Message, PUBLIC_PARAM_LEN, PUBLIC_PARAM_WIRES,
24 xmss::{XmssPublicKey, XmssSignature, XmssSignatureWires, circuit_xmss_verify},
25};
26use crate::{
27 bignum::{BigUint, biguint_lt},
28 blake3::{Blake3Compress2x, blake3_fixed},
29 util::split_u32_words,
30};
31
32#[derive(Debug, Clone)]
34pub struct SignerWires {
35 pub public_param: [Wire; PUBLIC_PARAM_WIRES],
36 pub merkle_root: [Wire; DIGEST_WIRES],
37 pub signature: XmssSignatureWires,
38}
39
40impl SignerWires {
41 pub fn new_inout(builder: &CircuitBuilder) -> Self {
43 Self {
44 public_param: std::array::from_fn(|_| builder.add_inout()),
45 merkle_root: std::array::from_fn(|_| builder.add_inout()),
46 signature: XmssSignatureWires::new_witness(builder),
47 }
48 }
49
50 pub fn new_witness(builder: &CircuitBuilder) -> Self {
54 Self {
55 public_param: std::array::from_fn(|_| builder.add_witness()),
56 merkle_root: std::array::from_fn(|_| builder.add_witness()),
57 signature: XmssSignatureWires::new_witness(builder),
58 }
59 }
60
61 fn populate(
63 &self,
64 w: &mut WitnessFiller<'_>,
65 public_key: &XmssPublicKey,
66 signature: &XmssSignature,
67 ) {
68 w.pack_bytes_le(&self.public_param, &public_key.public_param);
69 w.pack_bytes_le(&self.merkle_root, &public_key.merkle_root);
70 self.signature.populate(w, signature);
71 }
72
73 fn key_as_integer(&self) -> BigUint {
78 BigUint {
79 limbs: self
80 .public_param
81 .iter()
82 .chain(&self.merkle_root)
83 .copied()
84 .collect(),
85 }
86 }
87}
88
89#[derive(Debug, Clone)]
94pub struct MultiSigWires {
95 pub message: [Wire; MESSAGE_WIRES],
96 pub epoch: Wire,
97 pub signers: Vec<SignerWires>,
98}
99
100impl MultiSigWires {
101 pub fn new(builder: &CircuitBuilder, num_signers: usize) -> Self {
103 Self {
104 message: std::array::from_fn(|_| builder.add_inout()),
105 epoch: builder.add_inout(),
106 signers: (0..num_signers)
107 .map(|_| SignerWires::new_inout(builder))
108 .collect(),
109 }
110 }
111
112 pub fn populate(
118 &self,
119 w: &mut WitnessFiller<'_>,
120 message: &Message,
121 epoch: u32,
122 signatures: &[(XmssPublicKey, XmssSignature)],
123 ) {
124 assert_eq!(
125 signatures.len(),
126 self.signers.len(),
127 "expected {} signatures, got {}",
128 self.signers.len(),
129 signatures.len()
130 );
131
132 w.pack_bytes_le(&self.message, message);
133 w[self.epoch] = Word::from_u64(epoch as u64);
134 for (wires, (public_key, signature)) in iter::zip(&self.signers, signatures) {
135 wires.populate(w, public_key, signature);
136 }
137 }
138}
139
140pub fn circuit_xmss_multisig(builder: &CircuitBuilder, wires: &MultiSigWires) {
145 for (i, signer) in wires.signers.iter().enumerate() {
146 let builder = builder.subcircuit(format!("signer[{i}]"));
147 circuit_xmss_verify(
148 &builder,
149 &signer.public_param,
150 &signer.merkle_root,
151 &wires.message,
152 wires.epoch,
153 &signer.signature,
154 );
155 }
156}
157
158pub const SIGNER_BYTES: usize = PUBLIC_PARAM_LEN + DIGEST_LEN;
160
161pub const SIGNER_SET_WIRES: usize = 8;
166
167pub fn signer_order_key(public_key: &XmssPublicKey) -> [u8; SIGNER_BYTES] {
177 let mut bytes = [0u8; SIGNER_BYTES];
178 bytes[..PUBLIC_PARAM_LEN].copy_from_slice(&public_key.public_param);
179 bytes[PUBLIC_PARAM_LEN..].copy_from_slice(&public_key.merkle_root);
180 bytes.reverse();
181 bytes
182}
183
184pub fn sort_by_signer(signatures: &mut [(XmssPublicKey, XmssSignature)]) {
186 signatures.sort_unstable_by_key(|(public_key, _)| signer_order_key(public_key));
187}
188
189pub fn signer_set_digest(public_keys: &[XmssPublicKey]) -> [u8; 32] {
202 let mut bytes = Vec::with_capacity(public_keys.len() * SIGNER_BYTES);
204 for public_key in public_keys {
205 bytes.extend_from_slice(&public_key.public_param);
206 bytes.extend_from_slice(&public_key.merkle_root);
207 }
208 *blake3::hash(&bytes).as_bytes()
209}
210
211#[derive(Debug, Clone)]
218pub struct CommittedMultiSigWires {
219 pub message: [Wire; MESSAGE_WIRES],
220 pub epoch: Wire,
221 pub signer_set: [Wire; SIGNER_SET_WIRES],
222 pub signers: Vec<SignerWires>,
223}
224
225impl CommittedMultiSigWires {
226 pub fn new(builder: &CircuitBuilder, num_signers: usize) -> Self {
234 assert!(num_signers > 0, "an aggregate needs at least one signer");
235 Self {
236 message: std::array::from_fn(|_| builder.add_inout()),
237 epoch: builder.add_inout(),
238 signer_set: std::array::from_fn(|_| builder.add_inout()),
239 signers: (0..num_signers)
240 .map(|_| SignerWires::new_witness(builder))
241 .collect(),
242 }
243 }
244
245 pub fn populate(
257 &self,
258 w: &mut WitnessFiller<'_>,
259 message: &Message,
260 epoch: u32,
261 signatures: &[(XmssPublicKey, XmssSignature)],
262 ) {
263 assert_eq!(
264 signatures.len(),
265 self.signers.len(),
266 "expected {} signatures, got {}",
267 self.signers.len(),
268 signatures.len()
269 );
270
271 w.pack_bytes_le(&self.message, message);
272 w[self.epoch] = Word::from_u64(epoch as u64);
273 for (wires, (public_key, signature)) in iter::zip(&self.signers, signatures) {
274 wires.populate(w, public_key, signature);
275 }
276
277 let public_keys: Vec<_> = signatures
280 .iter()
281 .map(|&(public_key, _)| public_key)
282 .collect();
283 let digest = signer_set_digest(&public_keys);
284 for (&wire, chunk) in iter::zip(&self.signer_set, digest.chunks_exact(4)) {
285 let word = u32::from_le_bytes(chunk.try_into().expect("a chunk is four bytes"));
286 w[wire] = Word::from_u64(word as u64);
287 }
288 }
289}
290
291pub fn circuit_xmss_multisig_committed(builder: &CircuitBuilder, wires: &CommittedMultiSigWires) {
312 let mut payload = Vec::with_capacity(wires.signers.len() * (PUBLIC_PARAM_WIRES + DIGEST_WIRES));
314
315 for (i, signer) in wires.signers.iter().enumerate() {
316 let signer_builder = builder.subcircuit(format!("signer[{i}]"));
317 circuit_xmss_verify(
318 &signer_builder,
319 &signer.public_param,
320 &signer.merkle_root,
321 &wires.message,
322 wires.epoch,
323 &signer.signature,
324 );
325
326 if i > 0 {
329 let previous = &wires.signers[i - 1];
330 let increases =
331 biguint_lt(&signer_builder, &previous.key_as_integer(), &signer.key_as_integer());
332 signer_builder.assert_true("signer keys strictly increase", increases);
333 }
334
335 payload.extend_from_slice(&signer.public_param);
336 payload.extend_from_slice(&signer.merkle_root);
337 }
338
339 let len_bytes = wires.signers.len() * SIGNER_BYTES;
342 let message = split_u32_words(builder, &payload, len_bytes / 4);
343 let digest = blake3_fixed(builder, &message, len_bytes);
344
345 builder.assert_eq_v("signer set commitment", digest, wires.signer_set);
346}
347
348fn chip_call_words(
353 signer: &SignerWires,
354 message: &[Wire; MESSAGE_WIRES],
355 epoch: Wire,
356) -> Vec<Wire> {
357 let XmssSignatureWires {
358 randomness,
359 chain_tips,
360 merkle_path,
361 } = &signer.signature;
362 signer
363 .public_param
364 .iter()
365 .chain(&signer.merkle_root)
366 .chain(message)
367 .chain(iter::once(&epoch))
368 .chain(randomness)
369 .chain(chain_tips.iter().flatten())
370 .chain(merkle_path.iter().flatten())
371 .copied()
372 .collect()
373}
374
375fn xmss_verify_chip() -> CircuitM4 {
386 let builder = CircuitBuilder::new();
387 builder.register_chip(Blake3Compress2x, &[]);
388
389 let public_param = std::array::from_fn(|_| builder.add_inout());
392 let merkle_root = std::array::from_fn(|_| builder.add_inout());
393 let message: [Wire; MESSAGE_WIRES] = std::array::from_fn(|_| builder.add_inout());
394 let epoch = builder.add_inout();
395 let signature = XmssSignatureWires {
396 randomness: std::array::from_fn(|_| builder.add_inout()),
397 chain_tips: std::array::from_fn(|_| std::array::from_fn(|_| builder.add_inout())),
398 merkle_path: std::array::from_fn(|_| std::array::from_fn(|_| builder.add_inout())),
399 };
400 let signer = SignerWires {
401 public_param,
402 merkle_root,
403 signature,
404 };
405
406 circuit_xmss_verify(
407 &builder,
408 &signer.public_param,
409 &signer.merkle_root,
410 &message,
411 epoch,
412 &signer.signature,
413 );
414
415 let system = builder.build_m4();
416 assert_eq!(
417 system.main.circuit.inout(),
418 chip_call_words(&signer, &message, epoch),
419 "the chip's inout segment must be the order its call sites pass"
420 );
421 system
422}
423
424pub fn circuit_xmss_multisig_chip(builder: &CircuitBuilder, wires: &MultiSigWires) {
435 let chip = builder.add_chip(xmss_verify_chip());
436 for signer in &wires.signers {
437 builder.call_chip(chip, &chip_call_words(signer, &wires.message, wires.epoch));
438 }
439}
440
441#[cfg(test)]
442mod tests {
443 use rand::{Rng, SeedableRng, rngs::StdRng};
444
445 use super::*;
446 use crate::hash_based_sig::{MESSAGE_LEN, xmss::generate_signature};
447
448 fn generate(
450 seed: u64,
451 num_signers: usize,
452 epoch: u32,
453 ) -> (Message, Vec<(XmssPublicKey, XmssSignature)>) {
454 let mut rng = StdRng::seed_from_u64(seed);
455 let mut message = [0u8; MESSAGE_LEN];
456 rng.fill_bytes(&mut message);
457 let signatures = (0..num_signers)
458 .map(|_| generate_signature(&mut rng, &message, epoch))
459 .collect();
460 (message, signatures)
461 }
462
463 fn run(
464 message: &Message,
465 epoch: u32,
466 signatures: &[(XmssPublicKey, XmssSignature)],
467 ) -> Result<(), String> {
468 let b = CircuitBuilder::new();
469 let wires = MultiSigWires::new(&b, signatures.len());
470 circuit_xmss_multisig(&b, &wires);
471
472 let circuit = b.build();
473 let mut w = circuit.new_witness_filler();
474 wires.populate(&mut w, message, epoch, signatures);
475
476 circuit
477 .populate_wire_witness(&mut w)
478 .map_err(|e| format!("populate: {e:?}"))?;
479 circuit
480 .constraint_system()
481 .verify(&w.into_value_vec())
482 .map_err(|e| format!("verify: {e:?}"))
483 }
484
485 fn run_chip(
491 message: &Message,
492 epoch: u32,
493 signatures: &[(XmssPublicKey, XmssSignature)],
494 ) -> Result<(), String> {
495 let b = CircuitBuilder::new();
496 let wires = MultiSigWires::new(&b, signatures.len());
497 circuit_xmss_multisig_chip(&b, &wires);
498
499 let circuit = b.build_m4();
500 circuit.validate().map_err(|e| format!("validate: {e:?}"))?;
501 let cs = circuit.to_constraint_system();
502 cs.validate().map_err(|e| format!("validate cs: {e:?}"))?;
503
504 let witness = circuit
505 .generate_witness(|w| wires.populate(w, message, epoch, signatures))
506 .map_err(|e| format!("populate: {e:?}"))?;
507 witness.verify(&cs).map_err(|e| format!("verify: {e:?}"))
508 }
509
510 fn run_committed(
515 message: &Message,
516 epoch: u32,
517 signatures: &[(XmssPublicKey, XmssSignature)],
518 disturb: impl FnOnce(&mut WitnessFiller<'_>, &CommittedMultiSigWires),
519 ) -> Result<(), String> {
520 let b = CircuitBuilder::new();
521 let wires = CommittedMultiSigWires::new(&b, signatures.len());
522 circuit_xmss_multisig_committed(&b, &wires);
523
524 let circuit = b.build();
525 let mut w = circuit.new_witness_filler();
526 wires.populate(&mut w, message, epoch, signatures);
527 disturb(&mut w, &wires);
528
529 circuit
530 .populate_wire_witness(&mut w)
531 .map_err(|e| format!("populate: {e:?}"))?;
532 circuit
533 .constraint_system()
534 .verify(&w.into_value_vec())
535 .map_err(|e| format!("verify: {e:?}"))
536 }
537
538 fn generate_sorted(
540 seed: u64,
541 num_signers: usize,
542 epoch: u32,
543 ) -> (Message, Vec<(XmssPublicKey, XmssSignature)>) {
544 let (message, mut signatures) = generate(seed, num_signers, epoch);
545 sort_by_signer(&mut signatures);
546 (message, signatures)
547 }
548
549 #[test]
550 fn a_committed_aggregate_verifies() {
551 let (message, signatures) = generate_sorted(1, 3, 42);
552 run_committed(&message, 42, &signatures, |_, _| ()).unwrap();
553 }
554
555 #[test]
556 fn the_statement_is_the_same_size_whatever_the_number_of_signers() {
557 let published = |num_signers| {
563 let b = CircuitBuilder::new();
564 let wires = CommittedMultiSigWires::new(&b, num_signers);
565 circuit_xmss_multisig_committed(&b, &wires);
566 b.build().constraint_system().n_inout
567 };
568 assert_eq!(published(1), published(8));
569 }
570
571 #[test]
572 fn the_published_form_grows_with_the_signers_and_this_one_does_not() {
573 let published_inout = |num_signers| {
575 let b = CircuitBuilder::new();
576 let wires = MultiSigWires::new(&b, num_signers);
577 circuit_xmss_multisig(&b, &wires);
578 b.build().constraint_system().n_inout
579 };
580 assert!(published_inout(8) > published_inout(1));
581 }
582
583 #[test]
584 fn a_repeated_signer_fails_the_committed_aggregate() {
585 let (message, mut signatures) = generate_sorted(5, 3, 42);
595 signatures[1] = signatures[0].clone();
596 let err = run_committed(&message, 42, &signatures, |_, _| ())
597 .expect_err("a repeated signer must be rejected");
598 assert!(err.contains("strictly increase"), "unexpected failure: {err}");
599 }
600
601 #[test]
602 fn signers_out_of_order_fail_the_committed_aggregate() {
603 let (message, mut signatures) = generate_sorted(6, 3, 42);
610 signatures.swap(0, 1);
611 let err = run_committed(&message, 42, &signatures, |_, _| ())
612 .expect_err("an unsorted signer list must be rejected");
613 assert!(err.contains("strictly increase"), "unexpected failure: {err}");
614 }
615
616 #[test]
617 fn a_wrong_commitment_fails_the_committed_aggregate() {
618 let (message, signatures) = generate_sorted(7, 2, 42);
622 let err = run_committed(&message, 42, &signatures, |w, wires| {
623 w[wires.signer_set[0]] = Word(w[wires.signer_set[0]].0 ^ 1);
624 })
625 .expect_err("a commitment the keys do not hash to must be rejected");
626 assert!(err.contains("commitment"), "unexpected failure: {err}");
627 }
628
629 #[test]
630 fn a_bad_signature_fails_the_committed_aggregate() {
631 let (message, mut signatures) = generate_sorted(8, 3, 42);
633 signatures[1].1.chain_tips[0][0] ^= 0xFF;
634 assert!(run_committed(&message, 42, &signatures, |_, _| ()).is_err());
635 }
636
637 #[test]
638 fn the_commitment_is_over_the_keys_and_nothing_else() {
639 let (_, signatures) = generate_sorted(9, 3, 42);
645 let keys: Vec<_> = signatures.iter().map(|&(k, _)| k).collect();
646
647 let mut other = keys.clone();
648 other[2].merkle_root[0] ^= 1;
649
650 assert_eq!(signer_set_digest(&keys), signer_set_digest(&keys));
651 assert_ne!(signer_set_digest(&keys), signer_set_digest(&other));
652 }
653
654 #[test]
655 fn the_declared_order_is_the_order_the_circuit_compares_in() {
656 let (message, signatures) = generate_sorted(10, 4, 42);
662 let keys: Vec<_> = signatures.iter().map(|&(k, _)| k).collect();
663
664 for pair in keys.windows(2) {
666 assert!(signer_order_key(&pair[0]) < signer_order_key(&pair[1]));
667 }
668
669 run_committed(&message, 42, &signatures, |_, _| ()).unwrap();
670 }
671
672 #[test]
673 fn independent_signers_verify_together() {
674 let (message, signatures) = generate(1, 3, 42);
675 run(&message, 42, &signatures).unwrap();
676 }
677
678 #[test]
679 fn one_bad_signature_fails_the_aggregate() {
680 let (message, mut signatures) = generate(2, 3, 42);
681 signatures[1].1.chain_tips[0][0] ^= 0xFF;
682 assert!(run(&message, 42, &signatures).is_err());
683 }
684
685 #[test]
686 fn a_signature_on_another_message_fails_the_aggregate() {
687 let (message, mut signatures) = generate(3, 2, 42);
689 let mut rng = StdRng::seed_from_u64(4);
690 let mut other = [0u8; MESSAGE_LEN];
691 rng.fill_bytes(&mut other);
692 signatures[0] = generate_signature(&mut rng, &other, 42);
693 assert!(run(&message, 42, &signatures).is_err());
694 }
695
696 #[test]
697 fn independent_signers_verify_together_through_the_chip() {
698 let (message, signatures) = generate(1, 2, 42);
699 run_chip(&message, 42, &signatures).unwrap();
700 }
701
702 #[test]
703 fn one_bad_signature_fails_the_chip_aggregate() {
704 let (message, mut signatures) = generate(2, 2, 42);
705 signatures[1].1.chain_tips[0][0] ^= 0xFF;
706 assert!(run_chip(&message, 42, &signatures).is_err());
707 }
708}