1use elliptic_curve::Generate;
39use elliptic_curve::{PrimeField, ops::Invert, point::AffineCoordinates, sec1::ToSec1Point};
40use p256::FieldBytes;
41use p256::{AffinePoint, NonZeroScalar, ProjectivePoint, Scalar};
42use sha2::{Digest, Sha256};
43
44use confium_tc::Result;
45use confium_tc::message::Message;
46use confium_tc::registry::{RoundResult, SessionImpl};
47use confium_tc::session::SessionParams;
48
49use crate::error::{Cmp20ErrorCode, scheme_error};
50use crate::lagrange;
51use crate::share::Cmp20Share;
52
53pub struct Cmp20SignP256;
55
56impl Cmp20SignP256 {
57 pub fn build_session(params: &SessionParams) -> Result<Box<dyn SessionImpl>> {
58 let party_id = params.parties.get(params.this_party_idx)?.id.clone();
59 let message = params.message.clone().unwrap_or_default();
60 let share_bytes = params
61 .local_share
62 .as_ref()
63 .map(|s| s.bytes().to_vec())
64 .ok_or_else(|| scheme_error(Cmp20ErrorCode::BAD_SHARE))?;
65 let share = Cmp20Share::from_bytes(&share_bytes)?;
66
67 let k_i = NonZeroScalar::generate();
68 let r_i_point = (ProjectivePoint::GENERATOR * *k_i).to_affine();
69
70 Ok(Box::new(Cmp20SignSession {
71 party_id,
72 message,
73 share,
74 k_i,
75 r_i_point,
76 round1_seen: Vec::new(),
77 round2_seen: Vec::new(),
78 k_inv: None,
79 r_scalar: None,
80 z: None,
81 our_partial: None,
82 round_done: 0,
83 signature: None,
84 }))
85 }
86}
87
88pub struct Cmp20SignSession {
89 party_id: String,
90 message: Vec<u8>,
91 share: Cmp20Share,
92 k_i: NonZeroScalar,
93 r_i_point: AffinePoint,
94 round1_seen: Vec<(String, u64, AffinePoint)>,
95 round2_seen: Vec<(String, u64, Scalar)>,
96 k_inv: Option<Scalar>,
97 r_scalar: Option<Scalar>,
98 z: Option<Scalar>,
99 our_partial: Option<Scalar>,
100 round_done: u8,
101 signature: Option<Vec<u8>>,
102}
103
104const TAG_NONCE_POINT: u8 = 0xD1;
105const TAG_NONCE_REVEAL: u8 = 0xD2;
106const TAG_PARTIAL: u8 = 0xD3;
107
108impl Cmp20SignSession {
109 fn round1_commit(&mut self) -> Result<RoundResult> {
110 let mut payload = Vec::with_capacity(1 + 1 + 33);
111 payload.push(TAG_NONCE_POINT);
112 payload.push(self.share.party_idx as u8);
113 payload.extend_from_slice(self.r_i_point.to_sec1_point(true).as_bytes());
114 Ok(RoundResult::new(
115 vec![Message::broadcast(&self.party_id, 1, payload)],
116 false,
117 ))
118 }
119
120 fn round2_reveal(&mut self, incoming: &[Message]) -> Result<RoundResult> {
124 for msg in incoming {
125 if msg.round != 1 || msg.payload.is_empty() {
126 continue;
127 }
128 if msg.payload[0] != TAG_NONCE_POINT {
129 continue;
130 }
131 if msg.payload.len() != 1 + 1 + 33 {
132 return Err(scheme_error(Cmp20ErrorCode::BAD_ROUND_MESSAGE));
133 }
134 let idx = msg.payload[1] as u64;
135 let pt = decode_affine(&msg.payload[2..35])?;
136 if msg.from_party_id == self.party_id {
137 if idx != self.share.party_idx as u64 {
138 return Err(scheme_error(Cmp20ErrorCode::BAD_ROUND_MESSAGE));
139 }
140 continue;
141 }
142 self.round1_seen.push((msg.from_party_id.clone(), idx, pt));
143 }
144
145 let mut reveal = Vec::with_capacity(1 + 1 + 32);
146 reveal.push(TAG_NONCE_REVEAL);
147 reveal.push(self.share.party_idx as u8);
148 reveal.extend_from_slice(&self.k_i.to_bytes());
149 Ok(RoundResult::new(
150 vec![Message::broadcast(&self.party_id, 2, reveal)],
151 false,
152 ))
153 }
154
155 fn round3_partial(&mut self, incoming: &[Message]) -> Result<RoundResult> {
159 for msg in incoming {
160 if msg.round != 2 || msg.payload.is_empty() {
161 continue;
162 }
163 if msg.payload[0] != TAG_NONCE_REVEAL {
164 continue;
165 }
166 if msg.payload.len() != 1 + 1 + 32 {
167 return Err(scheme_error(Cmp20ErrorCode::BAD_ROUND_MESSAGE));
168 }
169 let idx = msg.payload[1] as u64;
170 let mut kb = [0u8; 32];
171 kb.copy_from_slice(&msg.payload[2..34]);
172 let fb: p256::FieldBytes = kb.into();
173 let k: Scalar = Option::from(Scalar::from_repr(fb))
174 .ok_or_else(|| scheme_error(Cmp20ErrorCode::BAD_ROUND_MESSAGE))?;
175 if msg.from_party_id == self.party_id {
176 continue;
177 }
178 self.round2_seen.push((msg.from_party_id.clone(), idx, k));
179 }
180
181 let mut parts: Vec<(u64, String, Scalar, AffinePoint)> = Vec::new();
182 parts.push((
183 self.share.party_idx as u64,
184 self.party_id.clone(),
185 *self.k_i,
186 self.r_i_point,
187 ));
188 for (pid, idx, k) in &self.round2_seen {
189 let r_j = self
190 .round1_seen
191 .iter()
192 .find(|(p, _, _)| p == pid)
193 .map(|(_, _, pt)| *pt)
194 .ok_or_else(|| scheme_error(Cmp20ErrorCode::BAD_ROUND_MESSAGE))?;
195 parts.push((*idx, pid.clone(), *k, r_j));
196 }
197 parts.sort_by_key(|(idx, _, _, _)| *idx);
198
199 let k_sum: Scalar = parts
200 .iter()
201 .map(|(_, _, k, _)| *k)
202 .fold(Scalar::ZERO, |a, b| a + b);
203 let k_nz: NonZeroScalar = Option::from(NonZeroScalar::new(k_sum))
204 .ok_or_else(|| scheme_error(Cmp20ErrorCode::INTERNAL))?;
205 let k_inv: Scalar = *k_nz.invert();
206
207 let mut r_proj = ProjectivePoint::IDENTITY;
208 for (_, _, _, pt) in &parts {
209 r_proj += ProjectivePoint::from(*pt);
210 }
211 if r_proj == ProjectivePoint::IDENTITY {
212 return Err(scheme_error(Cmp20ErrorCode::INTERNAL));
213 }
214 let r_affine = r_proj.to_affine();
215 let r_scalar = reduce_x_mod_n(r_affine);
216
217 let z = hash_to_scalar(&self.message);
218
219 let xs_scalar: Vec<Scalar> = parts
220 .iter()
221 .map(|(idx, _, _, _)| Scalar::from(*idx))
222 .collect();
223 let our_idx_scalar = Scalar::from(self.share.party_idx as u64);
224 let lambda_i = lagrange::lagrange_basis_scalar(our_idx_scalar, &xs_scalar);
225
226 let our_partial = k_inv * r_scalar * lambda_i * self.share.scalar();
233
234 let mut partial_payload = Vec::with_capacity(1 + 1 + 32);
235 partial_payload.push(TAG_PARTIAL);
236 partial_payload.push(self.share.party_idx as u8);
237 partial_payload.extend_from_slice(&our_partial.to_bytes());
238
239 self.k_inv = Some(k_inv);
240 self.r_scalar = Some(r_scalar);
241 self.z = Some(z);
242 self.our_partial = Some(our_partial);
243
244 Ok(RoundResult::new(
245 vec![Message::broadcast(&self.party_id, 3, partial_payload)],
246 false,
247 ))
248 }
249
250 fn round4_combine(&mut self, incoming: &[Message]) -> Result<RoundResult> {
262 let k_inv = self
263 .k_inv
264 .ok_or_else(|| scheme_error(Cmp20ErrorCode::INTERNAL))?;
265 let r_scalar = self
266 .r_scalar
267 .ok_or_else(|| scheme_error(Cmp20ErrorCode::INTERNAL))?;
268 let z = self
269 .z
270 .ok_or_else(|| scheme_error(Cmp20ErrorCode::INTERNAL))?;
271 let our_partial = self
272 .our_partial
273 .ok_or_else(|| scheme_error(Cmp20ErrorCode::INTERNAL))?;
274
275 let mut partials: Vec<(u64, Scalar)> = Vec::new();
276 for msg in incoming {
277 if msg.round != 3 || msg.payload.is_empty() {
278 continue;
279 }
280 if msg.payload[0] != TAG_PARTIAL {
281 continue;
282 }
283 if msg.payload.len() != 1 + 1 + 32 {
284 return Err(scheme_error(Cmp20ErrorCode::BAD_ROUND_MESSAGE));
285 }
286 let idx = msg.payload[1] as u64;
287 let mut sb = [0u8; 32];
288 sb.copy_from_slice(&msg.payload[2..34]);
289 let fb: p256::FieldBytes = sb.into();
290 let s: Scalar = Option::from(Scalar::from_repr(fb))
291 .ok_or_else(|| scheme_error(Cmp20ErrorCode::BAD_ROUND_MESSAGE))?;
292 partials.push((idx, s));
293 }
294 partials.push((self.share.party_idx as u64, our_partial));
295
296 let mut s = k_inv * z;
297 for (_, si) in &partials {
298 s += si;
299 }
300 let s = normalize_s_low(s);
301
302 let r_nz: NonZeroScalar = Option::from(NonZeroScalar::new(r_scalar))
303 .ok_or_else(|| scheme_error(Cmp20ErrorCode::BAD_PARTIAL_SIGNATURE))?;
304 let s_nz: NonZeroScalar = Option::from(NonZeroScalar::new(s))
305 .ok_or_else(|| scheme_error(Cmp20ErrorCode::BAD_PARTIAL_SIGNATURE))?;
306 let sig = p256::ecdsa::Signature::from_scalars(r_nz, s_nz)
307 .map_err(|_| scheme_error(Cmp20ErrorCode::INTERNAL))?;
308 let vk = p256::ecdsa::VerifyingKey::from_affine(self.share.public_key)
309 .map_err(|_| scheme_error(Cmp20ErrorCode::INTERNAL))?;
310 use p256::ecdsa::signature::Verifier;
311 if vk.verify(&self.message, &sig).is_err() {
312 if let Some(byz_idx) = identify_byzantine(&partials, k_inv, z, r_nz, &vk, &self.message)
317 {
318 let _ = byz_idx;
319 return Err(scheme_error(Cmp20ErrorCode::IDENTIFIED_BYZANTINE));
320 }
321 return Err(scheme_error(Cmp20ErrorCode::BAD_PARTIAL_SIGNATURE));
322 }
323
324 let mut out = Vec::with_capacity(64);
325 out.extend_from_slice(&r_scalar.to_bytes());
326 out.extend_from_slice(&s.to_bytes());
327 self.signature = Some(out);
328
329 Ok(RoundResult::done())
330 }
331}
332
333fn identify_byzantine(
339 partials: &[(u64, Scalar)],
340 k_inv: Scalar,
341 z: Scalar,
342 r_nz: NonZeroScalar,
343 vk: &p256::ecdsa::VerifyingKey,
344 message: &[u8],
345) -> Option<u64> {
346 use p256::ecdsa::signature::Verifier;
347 for (suspect_idx, _) in partials {
348 let mut s = k_inv * z;
349 for (j_idx, sj) in partials {
350 if j_idx == suspect_idx {
351 continue;
352 }
353 s += sj;
354 }
355 let s = normalize_s_low(s);
356 let s_nz_opt: Option<NonZeroScalar> = NonZeroScalar::new(s).into();
357 if let Some(s_nz) = s_nz_opt {
358 if let Ok(sig_minus) = p256::ecdsa::Signature::from_scalars(r_nz, s_nz) {
359 if vk.verify(message, &sig_minus).is_ok() {
360 return Some(*suspect_idx);
361 }
362 }
363 }
364 }
365 None
366}
367
368impl SessionImpl for Cmp20SignSession {
369 fn round(&mut self, incoming: &[Message]) -> Result<RoundResult> {
370 self.round_done = self.round_done.checked_add(1).ok_or_else(|| {
371 confium_tc::error::RoundOverflowSnafu {
372 round: self.round_done,
373 }
374 .build()
375 })?;
376 match self.round_done {
377 1 => self.round1_commit(),
378 2 => self.round2_reveal(incoming),
379 3 => self.round3_partial(incoming),
380 4 => self.round4_combine(incoming),
381 other => Err(confium_tc::error::RoundOverflowSnafu { round: other }.build()),
382 }
383 }
384
385 fn result(&self) -> Result<Vec<u8>> {
386 if self.round_done < 4 {
387 return Err(confium_tc::error::SessionNotCompleteSnafu {}.build());
388 }
389 self.signature
390 .clone()
391 .ok_or_else(|| scheme_error(Cmp20ErrorCode::INTERNAL))
392 }
393
394 fn destroy(&mut self) {
395 self.k_i = Option::from(NonZeroScalar::new(Scalar::ONE))
396 .unwrap_or_else(|| NonZeroScalar::new(Scalar::ONE).unwrap());
397 self.k_inv = None;
398 self.r_scalar = None;
399 self.z = None;
400 self.our_partial = None;
401 self.signature = None;
402 }
403}
404
405fn reduce_x_mod_n(point: AffinePoint) -> Scalar {
406 let x = point.x();
407 let x_bytes: &[u8] = x.as_slice();
408 let mut arr = [0u8; 32];
409 let n = x_bytes.len().min(32);
410 arr[..n].copy_from_slice(&x_bytes[..n]);
411 reduce_to_scalar(arr)
412}
413
414fn reduce_to_scalar(mut bytes: [u8; 32]) -> Scalar {
417 loop {
418 if let Some(s) = Option::<Scalar>::from(Scalar::from_repr(FieldBytes::from(bytes))) {
419 return s;
420 }
421 let mut h = Sha256::new();
422 h.update(b"confium-scalar-reduce-v1");
423 h.update(bytes);
424 bytes = h.finalize().into();
425 }
426}
427
428fn hash_to_scalar(message: &[u8]) -> Scalar {
429 let mut h = Sha256::new();
430 h.update(message);
431 let digest: [u8; 32] = h.finalize().into();
432 reduce_to_scalar(digest)
433}
434
435fn normalize_s_low(s: Scalar) -> Scalar {
436 use crypto_bigint::Limb;
437 use elliptic_curve::Curve;
438 use p256::NistP256;
439 let n_u = <NistP256 as Curve>::ORDER.get();
440 let half = n_u >> 1usize;
441 let s_u = decode_scalar_to_uint(s);
442 if s_u > half {
443 let (s_prime, _) = n_u.borrowing_sub(&s_u, Limb::ZERO);
444 let be = s_prime.to_be_bytes();
445 let src: &[u8] = be.as_slice();
446 let mut bytes = [0u8; 32];
447 let off = src.len().saturating_sub(32);
448 let take = src.len() - off;
449 bytes[..take].copy_from_slice(&src[off..]);
450 let fb: p256::FieldBytes = bytes.into();
451 Option::from(Scalar::from_repr(fb)).unwrap_or(s)
452 } else {
453 s
454 }
455}
456
457fn decode_scalar_to_uint(s: Scalar) -> p256::U256 {
458 use elliptic_curve::Curve;
459 use p256::NistP256;
460 let bytes = s.to_bytes();
461 <NistP256 as Curve>::Uint::from_be_slice(bytes.as_slice())
462}
463
464fn decode_affine(bytes: &[u8]) -> Result<AffinePoint> {
465 use elliptic_curve::sec1::FromSec1Point;
466 if bytes.len() != 33 {
467 return Err(scheme_error(Cmp20ErrorCode::BAD_ROUND_MESSAGE));
468 }
469 let enc = elliptic_curve::sec1::Sec1Point::<p256::NistP256>::from_bytes(bytes)
470 .map_err(|_| scheme_error(Cmp20ErrorCode::BAD_ROUND_MESSAGE))?;
471 let pt: AffinePoint = Option::from(AffinePoint::from_sec1_point(&enc))
472 .ok_or_else(|| scheme_error(Cmp20ErrorCode::BAD_ROUND_MESSAGE))?;
473 let _ = pt.x();
474 Ok(pt)
475}