penumbra_sdk_proof_setup/single/group.rs
1//! This module serves as a small abstraction layer over the group operations we need.
2//!
3//! This simplifies logic in other parts of this crate, since we don't need to rely on
4//! arkworks directly.
5use ark_ec::pairing::{Pairing, PairingOutput};
6use ark_ec::scalar_mul::{variable_base::VariableBaseMSM, ScalarMul};
7use ark_serialize::CanonicalSerialize;
8use decaf377::Bls12_377;
9use rand_core::CryptoRngCore;
10
11/// The group used for the left side of pairings.
12pub type G1 = <Bls12_377 as Pairing>::G1;
13
14/// A prepared version of G1 for more efficient pairings.
15pub type G1Prepared = <Bls12_377 as Pairing>::G1Prepared;
16
17/// The group used for the right side of pairings.
18pub type G2 = <Bls12_377 as Pairing>::G2;
19
20/// A prepared version of G2 for more efficient pairings.
21pub type G2Prepared = <Bls12_377 as Pairing>::G2Prepared;
22
23/// The group used for the output of pairings.
24pub type GT = PairingOutput<Bls12_377>;
25
26/// The field of scalars over which these groups form modules.
27pub type F = <Bls12_377 as Pairing>::ScalarField;
28
29/// The pairing operation between the two groups.
30pub fn pairing(a: impl Into<G1Prepared>, b: impl Into<G2Prepared>) -> GT {
31 <Bls12_377 as Pairing>::pairing(a, b)
32}
33
34// # Batched Pairing Checks
35
36/// Sample a random field that's "small" but still big enough for pairing checks.
37fn rand_small_f<R: CryptoRngCore>(rng: &mut R) -> F {
38 // 128 bits of security
39 let mut bytes = [0u8; 16];
40 rng.fill_bytes(&mut bytes);
41 F::from_le_bytes_mod_order(&bytes)
42}
43
44// This is just a little trick to avoid code duplication in the variants of this pairing checker
45// for different base groups.
46macro_rules! make_batched_pairing_checker {
47 ($name:ident, $gl:ident, $gr:ident, $glp:ident, $grp:ident, $pairing:ident) => {
48 /// A tool for efficiently making many pairing checks.
49 ///
50 /// This version is for pairing checks where the varying parts
51 /// of each side of the pairing equality are in $gl and $gr, respectively.
52 pub struct $name {
53 // Invariant: both vecs have the same length.
54 vary_l: Vec<$gl>,
55 base_l: $glp,
56 vary_r: Vec<$gr>,
57 base_r: $grp,
58 }
59
60 impl $name {
61 pub fn new(base_l: impl Into<$glp>, base_r: impl Into<$grp>) -> Self {
62 Self {
63 vary_l: Vec::new(),
64 base_l: base_l.into(),
65 vary_r: Vec::new(),
66 base_r: base_r.into(),
67 }
68 }
69
70 pub fn add(&mut self, l: $gl, r: $gr) {
71 self.vary_l.push(l);
72 self.vary_r.push(r);
73 }
74
75 #[must_use]
76 pub fn check<R: CryptoRngCore>(self, rng: &mut R) -> bool {
77 let n = self.vary_l.len();
78 let scalars = (0..n).map(|_| rand_small_f(rng)).collect::<Vec<_>>();
79
80 let ready_to_msm_l = <$gl as ScalarMul>::batch_convert_to_mul_base(&self.vary_l);
81 let l = <$gl as VariableBaseMSM>::msm_unchecked(&ready_to_msm_l, &scalars);
82 let ready_to_msm_r = <$gr as ScalarMul>::batch_convert_to_mul_base(&self.vary_r);
83 let r = <$gr as VariableBaseMSM>::msm_unchecked(&ready_to_msm_r, &scalars);
84
85 pairing(l, self.base_l) == $pairing(self.base_r, r)
86 }
87 }
88 };
89}
90
91// This is just a gimmick to support our macro shenanigans
92fn swapped_pairing(a: impl Into<G2Prepared>, b: impl Into<G1Prepared>) -> GT {
93 pairing(b, a)
94}
95
96make_batched_pairing_checker!(
97 BatchedPairingChecker11,
98 G1,
99 G1,
100 G2Prepared,
101 G2Prepared,
102 swapped_pairing
103);
104make_batched_pairing_checker!(
105 BatchedPairingChecker12,
106 G1,
107 G2,
108 G2Prepared,
109 G1Prepared,
110 pairing
111);
112
113/// The size of the hash we use.
114pub(crate) const HASH_SIZE: usize = 32;
115
116/// The hash output we use when we need bytes.
117pub(crate) type Hash = [u8; 32];
118
119/// A utility struct for hashing group elements and producing fields.
120///
121/// This avoids having to deal with some serialization and reduction code from arkworks.
122///
123/// All methods of this struct will handle separation between elements correctly.
124/// This means that feeding in two elements is distinct from feeding in the "concatenation"
125/// of this elements. One place where you still need manual effort on the user's end
126/// is when you're hashing a variable number of elements.
127#[derive(Clone)]
128pub(crate) struct GroupHasher {
129 state: blake2b_simd::State,
130}
131
132impl GroupHasher {
133 /// Create a new hasher with a personalization string.
134 ///
135 /// Because of BLAKE2's limitations, this has to be 16 bytes at most.
136 /// This function will panic if that isn't the case.
137 pub fn new(personalization: &'static [u8]) -> Self {
138 let state = blake2b_simd::Params::new()
139 .personal(personalization)
140 .to_state();
141 Self { state }
142 }
143
144 // Separate methods because the semantics of what this is trying to do are different,
145 // even if eating a usize happens to do the right thing.
146 fn write_len(&mut self, len: usize) {
147 self.eat_usize(len);
148 }
149
150 /// Consume some bytes, adding it to the state of the hash.
151 ///
152 /// These bytes will be length prefixed, and so calling this function
153 /// multiple times is not the same as calling it with the concatenation
154 /// of those bytes.
155 pub fn eat_bytes(&mut self, x: &[u8]) {
156 self.write_len(x.len());
157 self.state.update(x);
158 }
159
160 /// Eat anything that's canonically serializable (yummy!).
161 ///
162 /// This will handle padding between elements, using the declared length.
163 ///
164 /// We keep this internal, to make a simpler public API, since we only have
165 /// a handful of types we actually need to use this for.
166 fn eat_canonical<T: CanonicalSerialize>(&mut self, x: &T) {
167 self.write_len(x.compressed_size());
168 x.serialize_compressed(&mut self.state)
169 .expect("failed to serialize element");
170 }
171
172 /// Consume a usize value, adding it into the state of this hash.
173 ///
174 /// This is useful for (i.e. intended for) encoding metadata.
175 pub fn eat_usize(&mut self, x: usize) {
176 // On basically any platform this should fit in a u64
177 self.state.update(&(x as u64).to_le_bytes());
178 }
179
180 /// Consume a G1 group element, adding it into the state of this hash.
181 pub fn eat_g1(&mut self, x: &G1) {
182 self.eat_canonical(x);
183 }
184
185 /// Consume a G2 group element, adding it into the state of this hash.
186 pub fn eat_g2(&mut self, x: &G2) {
187 self.eat_canonical(x);
188 }
189
190 /// Consume a scalar, adding it into the state of this hash.
191 pub fn eat_f(&mut self, x: &F) {
192 self.eat_canonical(x);
193 }
194
195 /// Finalize this hash function, producing a scalar.
196 pub fn finalize(self) -> F {
197 F::from_le_bytes_mod_order(self.state.finalize().as_bytes())
198 }
199
200 /// Finalize this hash function, producing bytes.
201 pub fn finalize_bytes(self) -> Hash {
202 let mut out = [0u8; HASH_SIZE];
203 out.copy_from_slice(&self.state.finalize().as_bytes()[..HASH_SIZE]);
204 out
205 }
206}