1use elliptic_curve::sec1::ToSec1Point;
4use p256::{AffinePoint, FieldBytes, NonZeroScalar, Scalar};
5use zeroize::Zeroize;
6
7use crate::error::{Gg18ErrorCode, Result, scheme_error};
8
9const SHARE_MAGIC: [u8; 4] = *b"GG18";
10const SHARE_VERSION: u8 = 1;
11pub const SHARE_BYTES: usize = 4 + 1 + 32 + 33 + 1;
13
14#[derive(Clone)]
16pub struct Gg18Share {
17 pub x_i: NonZeroScalar,
19 pub public_key: AffinePoint,
21 pub party_idx: u32,
23}
24
25impl std::fmt::Debug for Gg18Share {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 f.debug_struct("Gg18Share")
28 .field("x_i", &"<redacted>")
29 .field("party_idx", &self.party_idx)
30 .finish_non_exhaustive()
31 }
32}
33
34impl Drop for Gg18Share {
35 fn drop(&mut self) {
36 let mut bytes = self.x_i.to_bytes();
37 bytes.zeroize();
38 }
39}
40
41impl Gg18Share {
42 pub fn to_bytes(&self) -> Vec<u8> {
43 let mut out = Vec::with_capacity(SHARE_BYTES);
44 out.extend_from_slice(&SHARE_MAGIC);
45 out.push(SHARE_VERSION);
46 out.extend_from_slice(&self.x_i.to_bytes());
47 out.extend_from_slice(self.public_key.to_sec1_point(true).as_bytes());
48 out.push(self.party_idx as u8);
49 out
50 }
51
52 pub fn from_bytes(data: &[u8]) -> Result<Self> {
53 if data.len() != SHARE_BYTES {
54 return Err(scheme_error(Gg18ErrorCode::BAD_SHARE));
55 }
56 if data[0..4] != SHARE_MAGIC {
57 return Err(scheme_error(Gg18ErrorCode::BAD_SHARE));
58 }
59 if data[4] != SHARE_VERSION {
60 return Err(scheme_error(Gg18ErrorCode::BAD_SHARE));
61 }
62 let mut x_i_bytes = [0u8; 32];
63 x_i_bytes.copy_from_slice(&data[5..37]);
64 let fb: FieldBytes = x_i_bytes.into();
65 let x_i: NonZeroScalar = Option::from(NonZeroScalar::from_repr(fb))
66 .ok_or_else(|| scheme_error(Gg18ErrorCode::BAD_SHARE))?;
67 let pk_bytes = &data[37..70];
68 let public_key = decode_affine(pk_bytes)?;
69 let party_idx = data[70] as u32;
70 Ok(Gg18Share {
71 x_i,
72 public_key,
73 party_idx,
74 })
75 }
76
77 pub fn from_parts(x_i: NonZeroScalar, public_key: AffinePoint, party_idx: u32) -> Self {
78 Gg18Share {
79 x_i,
80 public_key,
81 party_idx,
82 }
83 }
84
85 pub fn scalar(&self) -> Scalar {
86 *self.x_i
87 }
88}
89
90pub(crate) fn decode_affine(bytes: &[u8]) -> Result<AffinePoint> {
92 use elliptic_curve::point::AffineCoordinates;
93 use elliptic_curve::sec1::FromSec1Point;
94 if bytes.len() != 33 {
95 return Err(scheme_error(Gg18ErrorCode::BAD_SHARE));
96 }
97 let enc = elliptic_curve::sec1::Sec1Point::<p256::NistP256>::from_bytes(bytes)
98 .map_err(|_| scheme_error(Gg18ErrorCode::BAD_SHARE))?;
99 let pt: AffinePoint = Option::from(AffinePoint::from_sec1_point(&enc))
100 .ok_or_else(|| scheme_error(Gg18ErrorCode::BAD_SHARE))?;
101 let _ = pt.x();
102 Ok(pt)
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use elliptic_curve::Generate;
109
110 fn random_share(idx: u32) -> Gg18Share {
111 let x_i = NonZeroScalar::generate();
112 let g = p256::ProjectivePoint::GENERATOR;
113 let pk = (g * *x_i).to_affine();
114 Gg18Share::from_parts(x_i, pk, idx)
115 }
116
117 #[test]
118 fn share_round_trip() {
119 let s = random_share(1);
120 let bytes = s.to_bytes();
121 assert_eq!(bytes.len(), SHARE_BYTES);
122 let s2 = Gg18Share::from_bytes(&bytes).expect("decode");
123 assert_eq!(s2.party_idx, 1);
124 assert_eq!(s2.x_i.to_bytes(), s.x_i.to_bytes());
125 }
126
127 #[test]
128 fn share_rejects_bad_magic() {
129 let mut bytes = random_share(0).to_bytes();
130 bytes[0] = b'X';
131 assert!(Gg18Share::from_bytes(&bytes).is_err());
132 }
133
134 #[test]
135 fn share_rejects_truncated() {
136 let bytes = random_share(0).to_bytes();
137 assert!(Gg18Share::from_bytes(&bytes[..10]).is_err());
138 }
139
140 #[test]
141 fn share_rejects_zero_scalar() {
142 let mut bytes = random_share(0).to_bytes();
143 for b in &mut bytes[5..37] {
144 *b = 0;
145 }
146 assert!(Gg18Share::from_bytes(&bytes).is_err());
147 }
148
149 #[test]
150 fn debug_redacts_secret() {
151 let s = random_share(0);
152 let dbg = format!("{:?}", s);
153 assert!(dbg.contains("<redacted>"));
154 }
155}