binius_examples/circuits/
ethsign.rs1use std::{array, iter};
3
4use anyhow::Result;
5use binius_circuits::{
6 bignum::BigUint, bytes::swap_bytes, ecdsa::ecrecover, fixed_byte_vec::ByteVec,
7 keccak::keccak256_varlen,
8};
9use binius_core::word::Word;
10use binius_frontend::{CircuitBuilder, Wire, WitnessFiller};
11use clap::Args;
12use ethsign::SecretKey;
13use rand::prelude::*;
14use sha3::{Digest, Keccak256 as Sha3Keccak256};
15
16use crate::ExampleCircuit;
17
18struct Signature {
19 r: [Wire; 4],
20 s: [Wire; 4],
21 recid_odd: Wire,
22 address: [Wire; 3],
23 msg: ByteVec,
25}
26
27pub struct EthSignExample {
29 max_msg_len_bytes: usize,
30 signatures: Vec<Signature>,
31}
32
33#[derive(Args, Debug, Clone)]
34pub struct Params {
35 #[arg(short = 'n', long, default_value_t = 1)]
37 pub n_signatures: usize,
38 #[arg(short = 'm', long, default_value_t = 128, value_parser = clap::value_parser!(u16).range(1..))]
40 pub max_msg_len_bytes: u16,
41}
42
43#[derive(Args, Debug, Clone)]
44pub struct Instance {}
45
46impl ExampleCircuit for EthSignExample {
47 type Params = Params;
48 type Instance = Instance;
49
50 fn build(params: Params, builder: &mut CircuitBuilder) -> Result<Self> {
51 let max_msg_len_bytes = params.max_msg_len_bytes as usize;
52 let signatures = (0..params.n_signatures)
53 .map(|_| {
54 let msg_len = builder.add_inout();
55 let message = (0..params.max_msg_len_bytes.div_ceil(8))
56 .map(|_| builder.add_inout())
57 .collect::<Vec<_>>();
58 let r = array::from_fn(|_| builder.add_inout());
59 let s = array::from_fn(|_| builder.add_inout());
60 let recid_odd = builder.add_inout();
61 let address = array::from_fn(|_| builder.add_inout());
62
63 let msg = ByteVec::new(message, msg_len);
64 let msg_digest = keccak256_varlen(builder, &msg);
65
66 let z = BigUint {
69 limbs: msg_digest
70 .iter()
71 .rev()
72 .map(|&word| swap_bytes(builder, word))
73 .collect(),
74 };
75
76 let public_key = ecrecover(
78 builder,
79 &z,
80 &BigUint { limbs: r.to_vec() },
81 &BigUint { limbs: s.to_vec() },
82 recid_odd,
83 );
84
85 builder.assert_false("recovered_pk_not_pai", public_key.is_point_at_infinity);
87
88 let mut public_key_limbs = Vec::with_capacity(8);
90 public_key_limbs.extend(&public_key.y.limbs);
91 public_key_limbs.extend(&public_key.x.limbs);
92 public_key_limbs.reverse();
93
94 let public_key_message = public_key_limbs
95 .into_iter()
96 .map(|word| swap_bytes(builder, word))
97 .collect::<Vec<_>>();
98
99 let address_msg = ByteVec::new_const_len(builder, public_key_message, 64);
103 let address_digest = keccak256_varlen(builder, &address_msg);
104
105 assert_address_eq(builder, &address_digest, &address);
107
108 Signature {
109 r,
110 s,
111 recid_odd,
112 address,
113 msg,
114 }
115 })
116 .collect();
117
118 Ok(Self {
119 signatures,
120 max_msg_len_bytes,
121 })
122 }
123
124 fn populate_witness(&self, _instance: Instance, w: &mut WitnessFiller<'_>) -> Result<()> {
125 let mut rng = StdRng::seed_from_u64(42);
127
128 for Signature {
129 r,
130 s,
131 recid_odd,
132 address,
133 msg,
134 } in &self.signatures
135 {
136 let sk_bytes: [u8; 32] = rng.random();
138 let secret_key = SecretKey::from_raw(&sk_bytes)?;
139
140 let msg_len = rng.random_range(1..=self.max_msg_len_bytes);
142 let msg_bytes = (0..msg_len).map(|_| rng.random()).collect::<Vec<u8>>();
143 let msg_hash = keccak256(&msg_bytes);
144
145 let mut signature = secret_key.sign(&msg_hash)?;
147 let public = secret_key.public();
148
149 msg.populate_data(w, &msg_bytes);
151 msg.populate_len_bytes(w, msg_len);
152
153 w[*recid_odd] = if signature.v != 0 {
155 Word::ALL_ONE
156 } else {
157 Word::ZERO
158 };
159
160 signature.r.reverse();
162 w.pack_bytes_le(r, &signature.r);
163
164 signature.s.reverse();
165 w.pack_bytes_le(s, &signature.s);
166
167 w.pack_bytes_le(address, public.address());
170 }
171
172 Ok(())
173 }
174
175 fn param_summary(params: &Self::Params) -> Option<String> {
176 Some(format!("{}s-{}b", params.n_signatures, params.max_msg_len_bytes))
177 }
178}
179
180fn assert_address_eq(b: &CircuitBuilder, digest: &[Wire], address: &[Wire]) {
181 assert_eq!(digest.len(), 4);
182 assert_eq!(address.len(), 3);
183
184 let digest_len = b.add_constant_64(32);
185 let digest_byte_vec = ByteVec::new(digest.to_vec(), digest_len);
186 let digest_sliced = digest_byte_vec.slice_const_range(b, 12..32);
187
188 for (i, (&lhs_i, rhs_i)) in iter::zip(address, digest_sliced.data).enumerate() {
189 b.assert_eq(format!("address_word_{i}"), lhs_i, rhs_i);
190 }
191}
192
193fn keccak256(bytes: &[u8]) -> [u8; 32] {
194 let mut hasher = Sha3Keccak256::new();
195 hasher.update(bytes);
196 hasher.finalize().into()
197}