mithril_stm/circuits/halo2/
circuit.rs1use ff::Field;
2use group::Group;
3use midnight_circuits::ecc::curves::CircuitCurve;
4use midnight_circuits::instructions::{
5 AssignmentInstructions, ConversionInstructions, PublicInputInstructions,
6};
7use midnight_circuits::types::{
8 AssignedBit, AssignedNative, AssignedNativePoint, AssignedScalarOfNativeCurve,
9};
10use midnight_proofs::circuit::{Layouter, Value};
11use midnight_proofs::plonk::Error;
12use midnight_zk_stdlib::{Relation, ZkStdLib, ZkStdLibArch};
13
14use crate::circuits::halo2::constants::{DST_LOTTERY, DST_SIGNATURE};
15use crate::circuits::halo2::gadgets::{
16 verify_lottery, verify_merkle_path, verify_unique_signature,
17};
18use crate::circuits::halo2::types::{
19 Jubjub, JubjubBase, LotteryIndex, MTLeaf, MerklePath, MerkleRoot, SignedMessageWithoutPrefix,
20};
21use crate::signature_scheme::{PrimeOrderProjectivePoint, UniqueSchnorrSignature};
22
23type F = JubjubBase;
24type C = Jubjub;
25
26#[derive(Clone, Default, Debug)]
27pub struct StmCircuit {
28 quorum: u32,
30 num_lotteries: u32,
32 merkle_tree_depth: u32,
33}
34
35impl StmCircuit {
36 pub fn new(quorum: u32, num_lotteries: u32, merkle_tree_depth: u32) -> Self {
37 Self {
38 quorum,
39 num_lotteries,
40 merkle_tree_depth,
41 }
42 }
43}
44
45impl Relation for StmCircuit {
46 type Instance = (MerkleRoot, SignedMessageWithoutPrefix);
47 type Witness = Vec<(MTLeaf, MerklePath, UniqueSchnorrSignature, LotteryIndex)>;
48
49 fn format_instance(instance: &Self::Instance) -> Result<Vec<F>, Error> {
50 Ok(vec![instance.0, instance.1])
51 }
52
53 fn circuit(
54 &self,
55 std_lib: &ZkStdLib,
56 layouter: &mut impl Layouter<F>,
57 instance: Value<Self::Instance>,
58 witness: Value<Self::Witness>,
59 ) -> Result<(), Error> {
60 assert!(self.quorum < self.num_lotteries);
61
62 let merkle_root: AssignedNative<F> =
63 std_lib.assign_as_public_input(layouter, instance.map(|(x, _)| x))?;
64 let msg: AssignedNative<F> =
65 std_lib.assign_as_public_input(layouter, instance.map(|(_, x)| x))?;
66
67 let hash = std_lib.hash_to_curve(layouter, &[merkle_root.clone(), msg.clone()])?;
69
70 let generator: AssignedNativePoint<C> = std_lib.jubjub().assign_fixed(
71 layouter,
72 <C as CircuitCurve>::CryptographicGroup::generator(),
73 )?;
74
75 let dst_signature: AssignedNative<_> = std_lib.assign_fixed(layouter, DST_SIGNATURE)?;
76 let dst_lottery: AssignedNative<_> = std_lib.assign_fixed(layouter, DST_LOTTERY)?;
77 let lottery_prefix = std_lib.poseidon(
78 layouter,
79 &[dst_lottery.clone(), merkle_root.clone(), msg.clone()],
80 )?;
81
82 let witness = witness.transpose_vec(self.quorum as usize);
83
84 let mut pre_index: AssignedNative<_> = std_lib.assign(layouter, Value::known(F::ZERO))?;
85 for (i, wit) in witness.into_iter().enumerate() {
86 let index: AssignedNative<F> =
87 std_lib.assign(layouter, wit.clone().map(|(_, _, _, i)| F::from(i as u64)))?;
88
89 if i > 0 {
91 let is_less = std_lib.lower_than(layouter, &pre_index, &index, 32)?;
92 std_lib.assert_true(layouter, &is_less)?;
93 }
94
95 pre_index = index.clone();
96
97 let vk = std_lib
98 .jubjub()
99 .assign(layouter, wit.clone().map(|(x, _, _, _)| x.0.0.0))?;
100
101 let target: AssignedNative<F> =
102 std_lib.assign(layouter, wit.clone().map(|(x, _, _, _)| x.1))?;
103
104 let assigned_merkle_siblings = std_lib.assign_many(
106 layouter,
107 wit.clone()
108 .map(|(_, x, _, _)| x.siblings.iter().map(|x| x.1).collect::<Vec<_>>())
109 .transpose_vec(self.merkle_tree_depth as usize)
110 .as_slice(),
111 )?;
112
113 let assigned_merkle_positions = std_lib.assign_many(
115 layouter,
116 wit.clone()
117 .map(|(_, x, _, _)| x.siblings.iter().map(|x| x.0.into()).collect::<Vec<_>>())
118 .transpose_vec(self.merkle_tree_depth as usize)
119 .as_slice(),
120 )?;
121
122 let assigned_merkle_positions = assigned_merkle_positions
124 .iter()
125 .map(|pos| std_lib.convert(layouter, pos))
126 .collect::<Result<Vec<AssignedBit<F>>, Error>>()?;
127
128 let sigma_value = wit
129 .clone()
130 .map_with_result(|(_, _, sig, _)| {
131 let (u, v) = sig.commitment_point.get_coordinates();
132 PrimeOrderProjectivePoint::from_coordinates(u, v).map(|point| point.0)
133 })
134 .map_err(|e| {
135 Error::Synthesis(format!(
136 "invalid commitment point: not on curve or not prime order: {e:?}"
137 ))
138 })?;
139 let sigma: AssignedNativePoint<_> = std_lib.jubjub().assign(layouter, sigma_value)?;
140 let s: AssignedScalarOfNativeCurve<C> = std_lib
141 .jubjub()
142 .assign(layouter, wit.clone().map(|(_, _, sig, _)| sig.response.0))?;
143 let c_native = std_lib.assign(layouter, wit.map(|(_, _, sig, _)| sig.challenge.0))?;
144 let c: AssignedScalarOfNativeCurve<C> =
145 std_lib.jubjub().convert(layouter, &c_native)?;
146
147 verify_merkle_path(
148 std_lib,
149 layouter,
150 &vk,
151 &target,
152 &merkle_root,
153 &assigned_merkle_siblings,
154 &assigned_merkle_positions,
155 )?;
156
157 verify_unique_signature(
158 std_lib,
159 layouter,
160 &dst_signature,
161 &generator,
162 &vk,
163 &s,
164 &c,
165 &c_native,
166 &hash,
167 &sigma,
168 )?;
169
170 verify_lottery(std_lib, layouter, &lottery_prefix, &sigma, &index, &target)?;
171 }
172
173 let m = std_lib.assign_fixed(layouter, F::from(self.num_lotteries as u64))?;
175 let is_less = std_lib.lower_than(layouter, &pre_index, &m, 32)?;
176
177 std_lib.assert_true(layouter, &is_less)
178 }
179
180 fn used_chips(&self) -> ZkStdLibArch {
181 ZkStdLibArch {
182 jubjub: true,
183 poseidon: true,
184 sha2_256: false,
185 sha2_512: false,
186 keccak_256: false,
187 sha3_256: false,
188 secp256k1: false,
189 bls12_381: false,
190 base64: false,
191 nr_pow2range_cols: 2,
192 automaton: false,
193 blake2b: false,
194 }
195 }
196
197 fn write_relation<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
198 writer.write_all(&self.quorum.to_le_bytes())?;
199 writer.write_all(&self.num_lotteries.to_le_bytes())?;
200 writer.write_all(&self.merkle_tree_depth.to_le_bytes())
201 }
202
203 fn read_relation<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
204 let mut quorum_bytes = [0u8; 4];
206 let mut num_lotteries_bytes = [0u8; 4];
207 let mut merkle_tree_depth_bytes = [0u8; 4];
208
209 reader.read_exact(&mut quorum_bytes)?;
211 reader.read_exact(&mut num_lotteries_bytes)?;
212 reader.read_exact(&mut merkle_tree_depth_bytes)?;
213
214 let quorum = u32::from_le_bytes(quorum_bytes);
216 let num_lotteries = u32::from_le_bytes(num_lotteries_bytes);
217 let merkle_tree_depth = u32::from_le_bytes(merkle_tree_depth_bytes);
218
219 Ok(Self {
221 quorum,
222 num_lotteries,
223 merkle_tree_depth,
224 })
225 }
226}