Skip to main content

runar_lang/
ec.rs

1//! Real secp256k1 elliptic curve operations for testing.
2//!
3//! Uses the `k256` crate for real EC arithmetic. Point encoding is
4//! 64 bytes: `x[32] || y[32]` (big-endian, no prefix byte).
5
6use k256::elliptic_curve::group::{Group, GroupEncoding};
7use k256::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint};
8use k256::{AffinePoint, ProjectivePoint, Scalar};
9
10use crate::prelude::{Bigint, ByteString, Point};
11
12/// Parse a 64-byte Point (x[32] || y[32]) into a ProjectivePoint.
13fn point_to_projective(p: &[u8]) -> ProjectivePoint {
14    assert_eq!(p.len(), 64, "Point must be exactly 64 bytes");
15
16    // Check for point at infinity (all zeros)
17    if p.iter().all(|&b| b == 0) {
18        return ProjectivePoint::IDENTITY;
19    }
20
21    // Build uncompressed SEC1 encoding: 0x04 || x || y
22    let mut sec1 = vec![0x04u8];
23    sec1.extend_from_slice(p);
24    let encoded = k256::EncodedPoint::from_bytes(&sec1)
25        .expect("invalid SEC1 encoding");
26    let affine = AffinePoint::from_encoded_point(&encoded)
27        .expect("point not on curve");
28    ProjectivePoint::from(affine)
29}
30
31/// Serialize a ProjectivePoint to a 64-byte Point (x[32] || y[32]).
32fn projective_to_point(p: &ProjectivePoint) -> Point {
33    if p.is_identity().into() {
34        return vec![0u8; 64];
35    }
36    let affine = p.to_affine();
37    let encoded = affine.to_encoded_point(false); // uncompressed
38    let bytes = encoded.as_bytes(); // 0x04 || x[32] || y[32]
39    bytes[1..65].to_vec()
40}
41
42/// Convert an i64 scalar to a k256::Scalar (mod N).
43fn i64_to_scalar(k: Bigint) -> Scalar {
44    if k >= 0 {
45        Scalar::from(k as u64)
46    } else {
47        // Negative: compute N - |k|
48        Scalar::ZERO - Scalar::from((-k) as u64)
49    }
50}
51
52/// Point addition on secp256k1.
53pub fn ec_add(a: &[u8], b: &[u8]) -> Point {
54    let pa = point_to_projective(a);
55    let pb = point_to_projective(b);
56    projective_to_point(&(pa + pb))
57}
58
59/// Scalar multiplication: k * P.
60pub fn ec_mul(p: &[u8], k: Bigint) -> Point {
61    let pp = point_to_projective(p);
62    let s = i64_to_scalar(k);
63    projective_to_point(&(pp * s))
64}
65
66/// Scalar multiplication with the generator: k * G.
67pub fn ec_mul_gen(k: Bigint) -> Point {
68    let s = i64_to_scalar(k);
69    projective_to_point(&(ProjectivePoint::GENERATOR * s))
70}
71
72/// Point negation: returns (x, p - y).
73pub fn ec_negate(p: &[u8]) -> Point {
74    let pp = point_to_projective(p);
75    projective_to_point(&(-pp))
76}
77
78/// Check if a point is on the secp256k1 curve.
79pub fn ec_on_curve(p: &[u8]) -> bool {
80    if p.len() != 64 {
81        return false;
82    }
83    // All zeros = point at infinity, consider it "on curve"
84    if p.iter().all(|&b| b == 0) {
85        return true;
86    }
87    let mut sec1 = vec![0x04u8];
88    sec1.extend_from_slice(p);
89    let Ok(enc) = k256::EncodedPoint::from_bytes(&sec1) else { return false };
90    let ct = AffinePoint::from_encoded_point(&enc);
91    ct.is_some().into()
92}
93
94/// Non-negative modular reduction: ((value % m) + m) % m.
95pub fn ec_mod_reduce(value: Bigint, m: Bigint) -> Bigint {
96    let r = value % m;
97    if r < 0 { r + m } else { r }
98}
99
100/// Encode a point as a 33-byte compressed public key.
101pub fn ec_encode_compressed(p: &[u8]) -> ByteString {
102    let pp = point_to_projective(p);
103    let affine = pp.to_affine();
104    affine.to_bytes().to_vec()
105}
106
107/// Construct a Point from two coordinate integers.
108pub fn ec_make_point(x: Bigint, y: Bigint) -> Point {
109    let mut buf = vec![0u8; 64];
110    let xb = (x as u64).to_be_bytes();
111    let yb = (y as u64).to_be_bytes();
112    buf[24..32].copy_from_slice(&xb);
113    buf[56..64].copy_from_slice(&yb);
114    buf
115}
116
117/// Extract the x-coordinate from a Point as an i64.
118/// Note: only meaningful for small test values; real coordinates are 256-bit.
119pub fn ec_point_x(p: &[u8]) -> Bigint {
120    assert_eq!(p.len(), 64, "Point must be exactly 64 bytes");
121    // Return as i64 — will only work for small coordinates
122    let mut bytes = [0u8; 8];
123    bytes.copy_from_slice(&p[24..32]);
124    u64::from_be_bytes(bytes) as i64
125}
126
127/// Extract the y-coordinate from a Point as an i64.
128/// Note: only meaningful for small test values; real coordinates are 256-bit.
129pub fn ec_point_y(p: &[u8]) -> Bigint {
130    assert_eq!(p.len(), 64, "Point must be exactly 64 bytes");
131    let mut bytes = [0u8; 8];
132    bytes.copy_from_slice(&p[56..64]);
133    u64::from_be_bytes(bytes) as i64
134}
135
136// ---------------------------------------------------------------------------
137// Tests
138// ---------------------------------------------------------------------------
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    /// Helper: the secp256k1 generator point as a 64-byte Point.
145    fn ec_g() -> Point {
146        ec_mul_gen(1)
147    }
148
149    #[test]
150    fn ec_g_is_64_bytes() {
151        assert_eq!(ec_g().len(), 64);
152    }
153
154    #[test]
155    fn ec_g_is_on_curve() {
156        assert!(ec_on_curve(&ec_g()));
157    }
158
159    #[test]
160    fn ec_add_g_g_equals_ec_mul_g_2() {
161        let g = ec_g();
162        let sum = ec_add(&g, &g);
163        let doubled = ec_mul(&g, 2);
164        assert_eq!(sum, doubled);
165    }
166
167    #[test]
168    fn ec_add_g_g_equals_ec_mul_gen_2() {
169        let g = ec_g();
170        let sum = ec_add(&g, &g);
171        let gen2 = ec_mul_gen(2);
172        assert_eq!(sum, gen2);
173    }
174
175    #[test]
176    fn ec_mul_gen_1_equals_g() {
177        let g = ec_g();
178        let gen1 = ec_mul_gen(1);
179        assert_eq!(gen1, g);
180    }
181
182    #[test]
183    fn ec_negate_produces_on_curve_point() {
184        let g = ec_g();
185        let neg = ec_negate(&g);
186        assert_eq!(neg.len(), 64);
187        assert!(ec_on_curve(&neg));
188        // Negated point should differ from original (y coordinate differs)
189        assert_ne!(neg, g);
190    }
191
192    #[test]
193    fn ec_negate_double_negate_is_identity() {
194        let g = ec_g();
195        let double_neg = ec_negate(&ec_negate(&g));
196        assert_eq!(double_neg, g);
197    }
198
199    #[test]
200    fn ec_add_point_and_negation_is_identity() {
201        let g = ec_g();
202        let neg = ec_negate(&g);
203        let sum = ec_add(&g, &neg);
204        // Point at infinity = 64 zero bytes
205        assert_eq!(sum, vec![0u8; 64]);
206    }
207
208    #[test]
209    fn ec_make_point_round_trip() {
210        let x: Bigint = 12345;
211        let y: Bigint = 67890;
212        let p = ec_make_point(x, y);
213        assert_eq!(p.len(), 64);
214        assert_eq!(ec_point_x(&p), x);
215        assert_eq!(ec_point_y(&p), y);
216    }
217
218    #[test]
219    fn ec_encode_compressed_produces_33_bytes() {
220        let g = ec_g();
221        let compressed = ec_encode_compressed(&g);
222        assert_eq!(compressed.len(), 33);
223        // First byte must be 0x02 or 0x03 (compressed prefix)
224        assert!(compressed[0] == 0x02 || compressed[0] == 0x03);
225    }
226
227    #[test]
228    fn ec_on_curve_rejects_invalid_point() {
229        // Random 64 bytes very unlikely to be on the curve
230        let bad_point = vec![0xffu8; 64];
231        assert!(!ec_on_curve(&bad_point));
232    }
233
234    #[test]
235    fn ec_on_curve_rejects_wrong_length() {
236        assert!(!ec_on_curve(&[0u8; 32]));
237    }
238
239    #[test]
240    fn ec_on_curve_accepts_identity() {
241        assert!(ec_on_curve(&vec![0u8; 64]));
242    }
243
244    #[test]
245    fn ec_mod_reduce_basic() {
246        assert_eq!(ec_mod_reduce(10, 3), 1);
247        assert_eq!(ec_mod_reduce(-1, 5), 4);
248        assert_eq!(ec_mod_reduce(0, 7), 0);
249    }
250
251    #[test]
252    fn ec_mul_associative() {
253        // (G * 3) * 2 should equal G * 6
254        let g = ec_g();
255        let g3 = ec_mul(&g, 3);
256        let g3x2 = ec_mul(&g3, 2);
257        let g6 = ec_mul_gen(6);
258        assert_eq!(g3x2, g6);
259    }
260}