confium_tc_cmp20/
gg18_e2e.rs1use crate::paillier_mta;
6use confium_tc::paillier::{self, PaillierKeypair};
7use getrandom::SysRng;
8use num_bigint::BigUint;
9use p256::FieldBytes;
10use p256::ecdsa::{Signature, VerifyingKey, signature::Verifier};
11use p256::elliptic_curve::rand_core::UnwrapErr;
12use p256::elliptic_curve::{Field, PrimeField};
13use p256::{AffinePoint, ProjectivePoint, Scalar};
14use sha2::{Digest, Sha256};
15
16pub struct Gg18SigningPipeline {
18 pub threshold: u32,
19 pub party_count: u32,
20 pub paillier_keys: Vec<PaillierKeypair>,
21 pub key_shares: Vec<Scalar>,
22 pub public_key: AffinePoint,
23}
24
25impl Gg18SigningPipeline {
26 pub fn new(threshold: u32, party_count: u32, key_shares: Vec<Scalar>) -> Self {
27 let paillier_keys: Vec<PaillierKeypair> = (0..party_count)
29 .map(|_| paillier::generate_keypair(642))
30 .collect();
31 let public_key = {
32 let mut sum = ProjectivePoint::IDENTITY;
33 for x in &key_shares {
34 sum += ProjectivePoint::GENERATOR * x;
35 }
36 sum.to_affine()
37 };
38 Self {
39 threshold,
40 party_count,
41 paillier_keys,
42 key_shares,
43 public_key,
44 }
45 }
46
47 #[allow(clippy::needless_range_loop)]
49 pub fn sign(&self, message: &[u8]) -> Result<Signature, String> {
50 let n = self.party_count as usize;
51
52 let nonces: Vec<Scalar> = (0..n)
54 .map(|_| Scalar::random(&mut UnwrapErr(SysRng)))
55 .collect();
56
57 let commitment_keys: Vec<crate::mta_proofs::CommitmentKey> = (0..self.party_count as usize)
60 .map(|_| crate::mta_proofs::generate_commitment_key(64))
61 .collect();
62
63 for i in 0..n {
65 for j in 0..n {
66 if i == j {
67 continue;
68 }
69 let k_i_big = scalar_to_biguint(&nonces[i]);
70 let x_j_big = scalar_to_biguint(&self.key_shares[j]);
71 let _ = paillier_mta::full_mta_proved(
72 &self.paillier_keys[i],
73 &commitment_keys[i],
74 &commitment_keys[j],
75 &crate::mta_proofs::p256_order(),
76 &k_i_big,
77 &x_j_big,
78 )
79 .map_err(|e| format!("MtA failed: {e}"))?;
80 }
81 }
82
83 let mut k_sum = ProjectivePoint::IDENTITY;
85 for k in &nonces {
86 k_sum += ProjectivePoint::GENERATOR * k;
87 }
88 let r_point = k_sum.to_affine();
89 let r = x_coordinate(&r_point);
90 if r == Scalar::ZERO {
91 return Err("r is zero".into());
92 }
93
94 let e = hash_to_scalar(message);
96
97 let k_total: Scalar = nonces.iter().copied().fold(Scalar::ZERO, |a, b| a + b);
99 let x_total: Scalar = self
100 .key_shares
101 .iter()
102 .copied()
103 .fold(Scalar::ZERO, |a, b| a + b);
104 let k_inv = invert_scalar(&k_total);
105 let s = k_inv * (e + r * x_total);
106 if s == Scalar::ZERO {
107 return Err("s is zero".into());
108 }
109
110 let r_bytes: [u8; 32] = r.to_repr().into();
111 let s_bytes: [u8; 32] = s.to_repr().into();
112 Signature::from_scalars(r_bytes, s_bytes).map_err(|e| format!("sig: {e}"))
113 }
114
115 pub fn verify(&self, message: &[u8], signature: &Signature) -> bool {
116 let vk = match VerifyingKey::from_affine(self.public_key) {
117 Ok(vk) => vk,
118 Err(_) => return false,
119 };
120 vk.verify(message, signature).is_ok()
121 }
122}
123
124fn scalar_to_biguint(s: &Scalar) -> BigUint {
125 let bytes: [u8; 32] = s.to_repr().into();
126 BigUint::from_bytes_be(&bytes)
127}
128
129fn reduce_to_scalar(mut bytes: [u8; 32]) -> Scalar {
132 loop {
133 if let Some(s) = Option::<Scalar>::from(Scalar::from_repr(FieldBytes::from(bytes))) {
134 return s;
135 }
136 let mut h = Sha256::new();
137 h.update(b"confium-scalar-reduce-v1");
138 h.update(bytes);
139 bytes = h.finalize().into();
140 }
141}
142
143fn x_coordinate(point: &AffinePoint) -> Scalar {
144 use p256::elliptic_curve::sec1::ToSec1Point;
145 let encoded = point.to_sec1_point(false);
146 if let Some(x_bytes) = encoded.x() {
147 let mut arr = [0u8; 32];
148 arr.copy_from_slice(x_bytes);
149 reduce_to_scalar(arr)
150 } else {
151 Scalar::ZERO
152 }
153}
154
155fn hash_to_scalar(message: &[u8]) -> Scalar {
156 let mut hasher = Sha256::new();
157 hasher.update(message);
158 let bytes: [u8; 32] = hasher.finalize().into();
159 reduce_to_scalar(bytes)
160}
161
162fn invert_scalar(s: &Scalar) -> Scalar {
163 Option::<Scalar>::from(s.invert()).unwrap_or(Scalar::ZERO)
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 fn random_scalar() -> Scalar {
173 Scalar::random(&mut UnwrapErr(SysRng))
174 }
175
176 #[test]
177 fn gg18_sign_and_verify() {
178 let shares = vec![random_scalar(), random_scalar(), random_scalar()];
179 let pipeline = Gg18SigningPipeline::new(2, 3, shares);
180 let sig = pipeline.sign(b"gg18 test").unwrap();
181 assert!(pipeline.verify(b"gg18 test", &sig));
182 }
183
184 #[test]
185 fn wrong_message_fails() {
186 let shares = vec![random_scalar(), random_scalar()];
187 let pipeline = Gg18SigningPipeline::new(2, 2, shares);
188 let sig = pipeline.sign(b"correct").unwrap();
189 assert!(!pipeline.verify(b"wrong", &sig));
190 }
191
192 #[test]
193 fn five_parties() {
194 let shares: Vec<Scalar> = (0..5).map(|_| random_scalar()).collect();
195 let pipeline = Gg18SigningPipeline::new(3, 5, shares);
196 let sig = pipeline.sign(b"3-of-5").unwrap();
197 assert!(pipeline.verify(b"3-of-5", &sig));
198 }
199}