confium_tc_frost_ed25519/
group.rs1use curve25519_dalek::constants::ED25519_BASEPOINT_POINT;
20use curve25519_dalek::edwards::CompressedEdwardsY;
21use curve25519_dalek::edwards::EdwardsPoint;
22use curve25519_dalek::scalar::Scalar;
23
24use crate::error::{CODE_INVALID_COMMITMENT, CODE_MALFORMED_SHARE, FrostError, Result};
25
26pub const SCALAR_BYTES: usize = 32;
28
29pub const ELEMENT_BYTES: usize = 32;
31
32#[inline]
34pub fn base_point() -> EdwardsPoint {
35 ED25519_BASEPOINT_POINT
36}
37
38#[inline]
40pub fn mul_base(s: &Scalar) -> EdwardsPoint {
41 EdwardsPoint::mul_base(s)
42}
43
44#[inline]
46pub fn scalar_to_bytes(s: &Scalar) -> [u8; SCALAR_BYTES] {
47 s.to_bytes()
48}
49
50pub fn scalar_from_bytes_mod_order(bytes: &[u8; SCALAR_BYTES]) -> Scalar {
53 Scalar::from_bytes_mod_order(*bytes)
54}
55
56pub fn scalar_from_slice(bytes: &[u8]) -> Result<Scalar> {
58 if bytes.len() != SCALAR_BYTES {
59 return Err(FrostError::MalformedShare {
60 reason: "scalar must be exactly 32 bytes",
61 code: CODE_MALFORMED_SHARE,
62 });
63 }
64 let mut arr = [0u8; SCALAR_BYTES];
65 arr.copy_from_slice(bytes);
66 Ok(scalar_from_bytes_mod_order(&arr))
67}
68
69#[inline]
71pub fn point_to_bytes(p: &EdwardsPoint) -> [u8; ELEMENT_BYTES] {
72 p.compress().to_bytes()
73}
74
75pub fn point_from_bytes(bytes: &[u8; ELEMENT_BYTES]) -> Option<EdwardsPoint> {
78 CompressedEdwardsY::from_slice(bytes).ok()?.decompress()
79}
80
81pub fn point_from_slice(bytes: &[u8], party: &str) -> Result<EdwardsPoint> {
84 if bytes.len() != ELEMENT_BYTES {
85 return Err(FrostError::InvalidCommitment {
86 party: party.to_string(),
87 reason: "commitment must be exactly 32 bytes",
88 code: CODE_INVALID_COMMITMENT,
89 });
90 }
91 let mut arr = [0u8; ELEMENT_BYTES];
92 arr.copy_from_slice(bytes);
93 point_from_bytes(&arr).ok_or(FrostError::InvalidCommitment {
94 party: party.to_string(),
95 reason: "encoded point is not a valid curve point",
96 code: CODE_INVALID_COMMITMENT,
97 })
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn scalar_round_trip() {
106 let s = Scalar::from_bytes_mod_order([42u8; 32]);
107 let bytes = scalar_to_bytes(&s);
108 let back = scalar_from_bytes_mod_order(&bytes);
109 assert_eq!(s, back);
110 }
111
112 #[test]
113 fn base_point_times_one_is_base() {
114 let one = Scalar::ONE;
115 assert_eq!(mul_base(&one), base_point());
116 }
117
118 #[test]
119 fn point_round_trip_via_base() {
120 let s = Scalar::from(123u64);
121 let p = mul_base(&s);
122 let bytes = point_to_bytes(&p);
123 let back = point_from_bytes(&bytes).expect("round trips");
124 assert_eq!(p, back);
125 }
126
127 #[test]
128 fn point_from_slice_rejects_bad_length() {
129 let err = point_from_slice(&[0u8; 31], "x").unwrap_err();
130 match err {
131 FrostError::InvalidCommitment { .. } => {}
132 other => panic!("expected InvalidCommitment, got {other:?}"),
133 }
134 }
135
136 #[test]
137 fn point_from_slice_rejects_invalid_encoding() {
138 let err = point_from_slice(&[2u8; 32], "x").unwrap_err();
141 match err {
142 FrostError::InvalidCommitment { reason, .. } => {
143 assert!(reason.contains("valid curve point"));
144 }
145 other => panic!("expected InvalidCommitment, got {other:?}"),
146 }
147 }
148}