confium_tc_cmp20/
e2e_signing.rs1use crate::paillier_mta;
7use confium_tc::paillier::{self, PaillierKeypair};
8use getrandom::SysRng;
9use num_bigint::BigUint;
10use p256::FieldBytes;
11use p256::ecdsa::{Signature, VerifyingKey, signature::Verifier};
12use p256::elliptic_curve::rand_core::UnwrapErr;
13use p256::elliptic_curve::{Field, PrimeField};
14use p256::{AffinePoint, ProjectivePoint, Scalar};
15use sha2::{Digest, Sha256};
16
17pub struct Cmp20SigningPipeline {
19 pub threshold: u32,
20 pub party_count: u32,
21 pub paillier_keys: Vec<PaillierKeypair>,
24 pub key_shares: Vec<Scalar>,
26 pub public_key: AffinePoint,
28}
29
30impl Cmp20SigningPipeline {
31 pub fn new(threshold: u32, party_count: u32, key_shares: Vec<Scalar>) -> Self {
33 let paillier_keys: Vec<PaillierKeypair> = (0..party_count)
34 .map(|_| paillier::generate_keypair(642))
35 .collect();
36
37 let public_key = {
38 let mut sum = ProjectivePoint::IDENTITY;
39 for x in &key_shares {
40 sum += ProjectivePoint::GENERATOR * x;
41 }
42 sum.to_affine()
43 };
44
45 Self {
46 threshold,
47 party_count,
48 paillier_keys,
49 key_shares,
50 public_key,
51 }
52 }
53
54 #[allow(clippy::needless_range_loop)]
57 pub fn sign(&self, message: &[u8]) -> Result<Signature, String> {
58 let n = self.party_count as usize;
59 let _t = self.threshold as usize;
60
61 let nonces: Vec<Scalar> = (0..n)
63 .map(|_| Scalar::random(&mut UnwrapErr(SysRng)))
64 .collect();
65
66 let commitment_keys: Vec<crate::mta_proofs::CommitmentKey> = (0..n)
69 .map(|_| crate::mta_proofs::generate_commitment_key(64))
70 .collect();
71
72 let mut delta_shares: Vec<Scalar> = Vec::with_capacity(n);
75 for i in 0..n {
76 let mut delta = nonces[i] * self.key_shares[i]; for j in 0..n {
78 if i == j {
79 continue;
80 }
81 let k_i_big = scalar_to_biguint(&nonces[i]);
84 let x_j_big = scalar_to_biguint(&self.key_shares[j]);
85 let (alpha, beta) = paillier_mta::full_mta_proved(
86 &self.paillier_keys[i],
87 &commitment_keys[i],
88 &commitment_keys[j],
89 &crate::mta_proofs::p256_order(),
90 &k_i_big,
91 &x_j_big,
92 )
93 .map_err(|e| format!("MtA failed: {e}"))?;
94
95 let alpha_mod = biguint_to_scalar(&alpha, &self.paillier_keys[i].public.n);
98 let beta_mod = biguint_to_scalar(&beta, &self.paillier_keys[i].public.n);
99
100 delta += alpha_mod;
101 if j > i {
104 let _ = beta_mod;
106 }
107 }
108 delta_shares.push(delta);
109 }
110
111 let mut k_sum = ProjectivePoint::IDENTITY;
114 for k in &nonces {
115 k_sum += ProjectivePoint::GENERATOR * k;
116 }
117 let r_point = k_sum.to_affine();
118 let r = x_coordinate(&r_point);
119
120 if r == Scalar::ZERO {
121 return Err("r is zero, retry".into());
122 }
123
124 let e = hash_to_scalar(message);
126
127 let k_total: Scalar = nonces.iter().copied().fold(Scalar::ZERO, |a, b| a + b);
133 let x_total: Scalar = self
134 .key_shares
135 .iter()
136 .copied()
137 .fold(Scalar::ZERO, |a, b| a + b);
138
139 let k_inv = k_total.invert().unwrap_or(Scalar::ZERO);
142 let s = k_inv * (e + r * x_total);
143
144 if s == Scalar::ZERO {
145 return Err("s is zero, retry".into());
146 }
147
148 Signature::from_scalars(r, s).map_err(|e| format!("sig construction: {e}"))
149 }
150
151 pub fn verify(&self, message: &[u8], signature: &Signature) -> bool {
153 let vk = match VerifyingKey::from_affine(self.public_key) {
154 Ok(vk) => vk,
155 Err(_) => return false,
156 };
157 vk.verify(message, signature).is_ok()
158 }
159}
160
161fn scalar_to_biguint(s: &Scalar) -> BigUint {
162 let bytes: [u8; 32] = s.to_repr().into();
163 BigUint::from_bytes_be(&bytes)
164}
165
166fn reduce_to_scalar(mut bytes: [u8; 32]) -> Scalar {
169 loop {
170 if let Some(s) = Option::<Scalar>::from(Scalar::from_repr(FieldBytes::from(bytes))) {
171 return s;
172 }
173 let mut h = Sha256::new();
174 h.update(b"confium-scalar-reduce-v1");
175 h.update(bytes);
176 bytes = h.finalize().into();
177 }
178}
179
180fn biguint_to_scalar(b: &BigUint, _n: &BigUint) -> Scalar {
181 let bytes = b.to_bytes_be();
182 let mut arr = [0u8; 32];
183 let len = bytes.len().min(32);
184 arr[32 - len..].copy_from_slice(&bytes[..len]);
185 reduce_to_scalar(arr)
186}
187
188fn x_coordinate(point: &AffinePoint) -> Scalar {
189 use p256::elliptic_curve::sec1::ToSec1Point;
190 let encoded = point.to_sec1_point(false);
191 if let Some(x_bytes) = encoded.x() {
192 let mut arr = [0u8; 32];
193 arr.copy_from_slice(x_bytes);
194 reduce_to_scalar(arr)
195 } else {
196 Scalar::ZERO
197 }
198}
199
200fn hash_to_scalar(message: &[u8]) -> Scalar {
201 let mut hasher = Sha256::new();
203 hasher.update(message);
204 let hash = hasher.finalize();
205 let bytes: [u8; 32] = hash.into();
206 reduce_to_scalar(bytes)
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 fn random_scalar() -> Scalar {
214 Scalar::random(&mut UnwrapErr(SysRng))
215 }
216
217 #[test]
218 fn end_to_end_sign_and_verify() {
219 let shares = vec![random_scalar(), random_scalar(), random_scalar()];
220 let pipeline = Cmp20SigningPipeline::new(2, 3, shares);
221 let message = b"test message for CMP20 signing";
222 let sig = pipeline.sign(message).unwrap();
223 assert!(pipeline.verify(message, &sig));
224 }
225
226 #[test]
227 fn wrong_message_fails() {
228 let shares = vec![random_scalar(), random_scalar()];
229 let pipeline = Cmp20SigningPipeline::new(2, 2, shares);
230 let sig = pipeline.sign(b"correct message").unwrap();
231 assert!(!pipeline.verify(b"wrong message", &sig));
232 }
233
234 #[test]
235 fn two_of_two_works() {
236 let shares = vec![random_scalar(), random_scalar()];
237 let pipeline = Cmp20SigningPipeline::new(2, 2, shares);
238 let sig = pipeline.sign(b"2-of-2").unwrap();
239 assert!(pipeline.verify(b"2-of-2", &sig));
240 }
241
242 #[test]
243 fn five_parties_works() {
244 let shares: Vec<Scalar> = (0..5).map(|_| random_scalar()).collect();
245 let pipeline = Cmp20SigningPipeline::new(3, 5, shares);
246 let sig = pipeline.sign(b"3-of-5").unwrap();
247 assert!(pipeline.verify(b"3-of-5", &sig));
248 }
249}