openadp_ocrypt/
crypto.rs

1//! Cryptographic operations for OpenADP.
2//!
3//! This module implements the core cryptographic primitives used by OpenADP:
4//! - Ed25519 elliptic curve operations
5//! - Point compression/decompression 
6//! - Shamir secret sharing
7//! - HKDF key derivation
8//! - Hash-to-point function H
9//!
10//! All operations are designed to be compatible with the Go and Python implementations.
11
12use crate::{OpenADPError, Result};
13use sha2::{Sha256, Digest};
14use hkdf::Hkdf;
15use rand_core::{OsRng, RngCore};
16use rug::{Integer, Complete};
17use num_bigint::BigUint;
18use num_traits::{Zero, One};
19
20// Ed25519 curve parameters (matching Go implementation)
21lazy_static::lazy_static! {
22    /// Base field Z_p where p = 2^255 - 19
23    pub static ref P: BigUint = {
24        let p_str = "57896044618658097711785492504343953926634992332820282019728792003956564819949";
25        BigUint::parse_bytes(p_str.as_bytes(), 10).unwrap()
26    };
27
28    /// Curve constant d = -121665 * inv(121666) mod p
29    pub static ref D: BigUint = {
30        let inv121666 = mod_inverse(&BigUint::from(121666u32), &P);
31        let mut d = &*P - &BigUint::from(121665u32); // -121665 mod p
32        d = (&d * &inv121666) % &*P;
33        d
34    };
35
36    /// Group order q = 2^252 + 27742317777372353535851937790883648493
37    pub static ref Q: BigUint = {
38        let q_str = "7237005577332262213973186563042994240857116359379907606001950938285454250989";
39        BigUint::parse_bytes(q_str.as_bytes(), 10).unwrap()
40    };
41
42    /// Square root of -1 mod p
43    pub static ref MODP_SQRT_M1: BigUint = {
44        let exp = (&*P - 1u32) / 4u32;
45        BigUint::from(2u32).modpow(&exp, &P)
46    };
47
48    /// Base point G
49    pub static ref G: Point4D = {
50        // Base point y-coordinate: 4/5 mod p
51        let gy = (&BigUint::from(4u32) * &mod_inverse(&BigUint::from(5u32), &P)) % &*P;
52        let gx = recover_x(&gy, 0).expect("Failed to recover base point X coordinate");
53        expand(&Point2D { x: gx, y: gy })
54    };
55
56    /// Zero point (neutral element)
57    pub static ref ZERO_POINT: Point4D = Point4D {
58        x: BigUint::zero(),
59        y: BigUint::one(),
60        z: BigUint::one(),
61        t: BigUint::zero(),
62    };
63}
64
65/// 2D point representation for Ed25519
66#[derive(Debug, Clone, PartialEq)]
67pub struct Point2D {
68    pub x: BigUint,
69    pub y: BigUint,
70}
71
72impl Point2D {
73    pub fn new(x: BigUint, y: BigUint) -> Self {
74        Self { x, y }
75    }
76}
77
78/// 4D point representation for Ed25519 (extended coordinates)
79#[derive(Debug, Clone, PartialEq)]
80pub struct Point4D {
81    pub x: BigUint,
82    pub y: BigUint,
83    pub z: BigUint,
84    pub t: BigUint,
85}
86
87impl Point4D {
88    pub fn new(x: BigUint, y: BigUint, z: BigUint, t: BigUint) -> Self {
89        Self { x, y, z, t }
90    }
91
92    pub fn identity() -> Self {
93        ZERO_POINT.clone()
94    }
95}
96
97/// Expand converts a 2D point to extended 4D coordinates
98pub fn expand(point: &Point2D) -> Point4D {
99    let xy = (&point.x * &point.y) % &*P;
100    Point4D {
101        x: point.x.clone(),
102        y: point.y.clone(),
103        z: BigUint::one(),
104        t: xy,
105    }
106}
107
108/// Unexpand converts extended 4D coordinates back to 2D point
109pub fn unexpand(point: &Point4D) -> Result<Point2D> {
110    let z_inv = mod_inverse(&point.z, &P);
111    let x = (&point.x * &z_inv) % &*P;
112    let y = (&point.y * &z_inv) % &*P;
113    Ok(Point2D { x, y })
114}
115
116/// SHA-256 hash function
117pub fn sha256_hash(data: &[u8]) -> Vec<u8> {
118    let mut hasher = Sha256::new();
119    hasher.update(data);
120    hasher.finalize().to_vec()
121}
122
123/// Modular inverse using Fermat's little theorem (for prime moduli)
124pub fn mod_inverse(a: &BigUint, p: &BigUint) -> BigUint {
125    // For prime p, a^(-1) = a^(p-2) mod p
126    let exp = p - 2u32;
127    a.modpow(&exp, p)
128}
129
130/// Add two points in extended coordinates (matching Go PointAdd)
131pub fn point_add(p1: &Point4D, p2: &Point4D) -> Point4D {
132    // A = (Y1 - X1) * (Y2 - X2)
133    let a = ((&p1.y + &*P - &p1.x) * (&p2.y + &*P - &p2.x)) % &*P;
134    
135    // B = (Y1 + X1) * (Y2 + X2)
136    let b = ((&p1.y + &p1.x) * (&p2.y + &p2.x)) % &*P;
137    
138    // C = 2 * T1 * T2 * d
139    let c = (2u32 * &p1.t * &p2.t % &*P * &*D) % &*P;
140    
141    // D = 2 * Z1 * Z2
142    let d = (2u32 * &p1.z * &p2.z) % &*P;
143    
144    // E, F, G, H = B - A, D - C, D + C, B + A
145    let e = (&b + &*P - &a) % &*P;
146    let f = (&d + &*P - &c) % &*P;
147    let g = (&d + &c) % &*P;
148    let h = (&b + &a) % &*P;
149    
150    // Return (E * F, G * H, F * G, E * H)
151    Point4D {
152        x: (&e * &f) % &*P,
153        y: (&g * &h) % &*P,
154        z: (&f * &g) % &*P,
155        t: (&e * &h) % &*P,
156    }
157}
158
159/// Multiply point by scalar using double-and-add (matching Go PointMul)
160pub fn point_mul(s: &BigUint, p: &Point4D) -> Point4D {
161    let mut q = ZERO_POINT.clone();
162    let mut p_copy = p.clone();
163    let mut s_copy = s.clone();
164    
165    while s_copy > BigUint::zero() {
166        if s_copy.bit(0) {
167            q = point_add(&q, &p_copy);
168        }
169        p_copy = point_add(&p_copy, &p_copy);
170        s_copy >>= 1;
171    }
172    
173    q
174}
175
176/// Multiply point by 8 (cofactor clearing, matching Go pointMul8)
177pub fn point_mul8(p: &Point4D) -> Point4D {
178    // Multiply by 8 = 2^3, so we double 3 times
179    let mut result = point_add(p, p);          // 2P
180    result = point_add(&result, &result);      // 4P
181    result = point_add(&result, &result);      // 8P
182    result
183}
184
185/// Check if two points are equal in projective coordinates
186pub fn point_equal(p1: &Point4D, p2: &Point4D) -> bool {
187    // x1 / z1 == x2 / z2  <==>  x1 * z2 == x2 * z1
188    let left = (&p1.x * &p2.z) % &*P;
189    let right = (&p2.x * &p1.z) % &*P;
190    if left != right {
191        return false;
192    }
193    
194    let left = (&p1.y * &p2.z) % &*P;
195    let right = (&p2.y * &p1.z) % &*P;
196    left == right
197}
198
199/// Recover x-coordinate from y and sign bit (matching Go recoverX)
200pub fn recover_x(y: &BigUint, sign: u8) -> Option<BigUint> {
201    if y >= &*P {
202        return None;
203    }
204    
205    // x^2 = (y^2 - 1) / (d * y^2 + 1)
206    let y2 = (y * y) % &*P;
207    
208    let numerator = (&y2 + &*P - 1u32) % &*P;
209    let denominator = ((&*D * &y2) + 1u32) % &*P;
210    
211    let denominator_inv = mod_inverse(&denominator, &P);
212    let x2 = (&numerator * &denominator_inv) % &*P;
213    
214    if x2.is_zero() {
215        return if sign != 0 { None } else { Some(BigUint::zero()) };
216    }
217    
218    // Compute square root of x2
219    let exp = (&*P + 3u32) / 8u32;
220    let mut x = x2.modpow(&exp, &P);
221    
222    // Check if x^2 == x2
223    let x_squared = (&x * &x) % &*P;
224    if x_squared != x2 {
225        x = (&x * &*MODP_SQRT_M1) % &*P;
226    }
227    
228    // Verify again
229    let x_squared = (&x * &x) % &*P;
230    if x_squared != x2 {
231        return None;
232    }
233    
234    // Check sign
235    if x.bit(0) != (sign != 0) {
236        x = &*P - &x;
237    }
238    
239    Some(x)
240}
241
242/// Compress a point to 32 bytes (matching Go PointCompress)
243pub fn point_compress(p: &Point4D) -> Result<Vec<u8>> {
244    let z_inv = mod_inverse(&p.z, &P);
245    let x = (&p.x * &z_inv) % &*P;
246    let mut y = (&p.y * &z_inv) % &*P;
247    
248    // Set sign bit
249    if x.bit(0) {
250        y.set_bit(255, true);
251    }
252    
253    // Convert to little-endian 32 bytes
254    let mut result = vec![0u8; 32];
255    let y_bytes = y.to_bytes_le();
256    let copy_len = std::cmp::min(y_bytes.len(), 32);
257    result[..copy_len].copy_from_slice(&y_bytes[..copy_len]);
258    
259    Ok(result)
260}
261
262/// Decompress 32 bytes to a point (matching Go PointDecompress)
263pub fn point_decompress(data: &[u8]) -> Result<Point4D> {
264    if data.len() != 32 {
265        return Err(OpenADPError::PointOperation("Invalid input length for decompression".to_string()));
266    }
267    
268    // Convert from little-endian
269    let mut y = BigUint::zero();
270    for i in 0..32 {
271        for bit in 0..8 {
272            if (data[i] >> bit) & 1 == 1 {
273                y.set_bit((i * 8 + bit) as u64, true);
274            }
275        }
276    }
277    
278    let sign = if y.bit(255) { 1 } else { 0 };
279    y.set_bit(255, false); // Clear sign bit
280    
281    let x = recover_x(&y, sign)
282        .ok_or_else(|| OpenADPError::PointOperation("Invalid point".to_string()))?;
283    
284    let xy = (&x * &y) % &*P;
285    let point = Point4D {
286        x,
287        y,
288        z: BigUint::one(),
289        t: xy,
290    };
291    
292    // Validate the decompressed point
293    if !is_valid_point(&point) {
294        return Err(OpenADPError::PointOperation("Invalid point: failed validation".to_string()));
295    }
296    
297    Ok(point)
298}
299
300/// Check if a point is valid using Ed25519 cofactor clearing
301pub fn is_valid_point(p: &Point4D) -> bool {
302    // Check that the point is not the zero point
303    if point_equal(p, &ZERO_POINT) {
304        return false;
305    }
306    
307    // Ed25519 point validation using cofactor clearing:
308    // A valid point P should satisfy: 8*P is not the zero point
309    let eight_p = point_mul8(p);
310    !point_equal(&eight_p, &ZERO_POINT)
311}
312
313/// Add 16-bit length prefix to data (matching Go prefixed)
314pub fn prefixed(data: &[u8]) -> Vec<u8> {
315    let l = data.len();
316    if l >= (1 << 16) {
317        panic!("Input string too long");
318    }
319    let mut result = Vec::with_capacity(data.len() + 2);
320    result.push(l as u8);           // Low byte
321    result.push((l >> 8) as u8);    // High byte
322    result.extend_from_slice(data);
323    result
324}
325
326/// Hash-to-point function H (matching Go H function exactly)
327#[allow(non_snake_case)]
328pub fn H(uid: &[u8], did: &[u8], bid: &[u8], pin: &[u8]) -> Result<Point4D> {
329    // Concatenate all inputs with length prefixes (matching Go implementation)
330    let mut data = prefixed(uid);
331    data.extend_from_slice(&prefixed(did));
332    data.extend_from_slice(&prefixed(bid));
333    data.extend_from_slice(pin);
334    
335    // Hash and convert to point
336    let hash = sha256_hash(&data);
337    
338    // Convert hash to big integer and extract sign bit (matching Go)
339    let y_base_full = BigUint::from_bytes_le(&hash);
340    
341    let sign = if y_base_full.bit(255) { 1 } else { 0 };
342    let mut y_base = y_base_full.clone();
343    y_base.set_bit(255, false); // Clear sign bit
344    
345    for counter in 0..1000 {
346        // XOR with counter to find valid point
347        let y = &y_base ^ BigUint::from(counter as u32);
348        
349        if let Some(x) = recover_x(&y, sign) {
350            // Force the point to be in a group of order q (multiply by 8)
351            let p = expand(&Point2D { x, y });
352            let p = point_mul8(&p);
353            
354            if is_valid_point(&p) {
355                return Ok(p);
356            }
357        }
358    }
359    
360    // Fallback to base point if no valid point found
361    Ok(G.clone())
362}
363
364/// Derive encryption key from point using HKDF (matching Go DeriveEncKey)
365pub fn derive_enc_key(point: &Point4D) -> Result<Vec<u8>> {
366    let point_bytes = point_compress(point)?;
367    
368    // Use HKDF with proper salt and info to match Go implementation
369    let salt = b"OpenADP-EncKey-v1";
370    let info = b"AES-256-GCM";
371    
372    let hk = Hkdf::<Sha256>::new(Some(salt), &point_bytes);
373    let mut okm = [0u8; 32];
374    hk.expand(info, &mut okm)
375        .map_err(|e| OpenADPError::Crypto(format!("HKDF expansion failed: {}", e)))?;
376    
377    Ok(okm.to_vec())
378}
379
380// Large prime for Shamir secret sharing - using Go's Q value
381// This is the order of the Ed25519 curve
382const Q_HEX: &str = "1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed";
383
384/// Shamir secret sharing implementation using finite field arithmetic with rug big integers
385pub struct ShamirSecretSharing;
386
387impl ShamirSecretSharing {
388    /// Get the prime modulus Q as a rug Integer
389    pub fn get_q() -> Integer {
390        Integer::from_str_radix(Q_HEX, 16).unwrap()
391    }
392
393    /// Generate a cryptographically secure random number less than Q
394    fn random_mod_q() -> Integer {
395        let q = Self::get_q();
396        let mut rng = OsRng;
397        
398        // Generate a random integer with the same bit length as Q
399        let bit_len = q.significant_bits();
400        let byte_len = (bit_len + 7) / 8;
401        
402        loop {
403            let mut bytes = vec![0u8; byte_len as usize];
404            rng.fill_bytes(&mut bytes);
405            
406            let random_int = Integer::from_digits(&bytes, rug::integer::Order::MsfBe);
407            if random_int < q {
408                return random_int;
409            }
410        }
411    }
412
413    /// Split integer secret into shares (matches Go implementation exactly)
414    pub fn split_secret(secret: &Integer, threshold: usize, num_shares: usize) -> Result<Vec<(usize, Integer)>> {
415        if threshold == 0 || threshold > num_shares {
416            return Err(OpenADPError::SecretSharing("Invalid threshold".to_string()));
417        }
418        
419        let q = Self::get_q();
420        
421        if *secret >= q {
422            return Err(OpenADPError::SecretSharing("Secret too large for field".to_string()));
423        }
424        
425        // Generate random coefficients for polynomial f(x) = a0 + a1*x + a2*x^2 + ...
426        // where a0 = secret
427        let mut coefficients = vec![secret.clone()];
428        
429        for _ in 1..threshold {
430            coefficients.push(Self::random_mod_q());
431        }
432        
433        // Evaluate polynomial at x = 1, 2, ..., num_shares
434        let mut shares = Vec::new();
435        for x in 1..=num_shares {
436            let x_int = Integer::from(x);
437            let mut y = Integer::new();
438            let mut x_power = Integer::from(1);
439            
440            for coeff in &coefficients {
441                // y = (y + coeff * x_power) % q
442                let term = (coeff * &x_power).complete() % &q;
443                y = (&y + &term).complete() % &q;
444                // x_power = (x_power * x) % q
445                x_power = (&x_power * &x_int).complete() % &q;
446            }
447            
448            shares.push((x, y));
449        }
450        
451        Ok(shares)
452    }
453    
454    /// Recover integer secret from shares (matches Go implementation exactly)
455    pub fn recover_secret(shares: Vec<(usize, Integer)>) -> Result<Integer> {
456        if shares.is_empty() {
457            return Err(OpenADPError::SecretSharing("No shares provided".to_string()));
458        }
459        
460        let q = Self::get_q();
461        let mut secret = Integer::new();
462        
463        // Convert x coordinates to integers
464        let int_shares: Vec<(Integer, Integer)> = shares.into_iter()
465            .map(|(x, y)| (Integer::from(x), y))
466            .collect();
467        
468        // Lagrange interpolation to find f(0)
469        for (i, (xi, yi)) in int_shares.iter().enumerate() {
470            // Compute Lagrange basis polynomial Li(0)
471            let mut numerator = Integer::from(1);
472            let mut denominator = Integer::from(1);
473            
474            for (j, (xj, _)) in int_shares.iter().enumerate() {
475                if i != j {
476                    // numerator = numerator * (0 - xj) = numerator * (-xj) = numerator * (q - xj)
477                    let neg_xj = Integer::from(&q - xj);
478                    numerator = Integer::from(&numerator * &neg_xj) % &q;
479                    // denominator = denominator * (xi - xj)
480                    let xi_clone = xi.clone();
481                    let xj_clone = xj.clone();
482                    let diff = if &xi_clone >= &xj_clone {
483                        Integer::from(&xi_clone - &xj_clone)
484                    } else {
485                        let xj_minus_xi = Integer::from(&xj_clone - &xi_clone);
486                        Integer::from(&q - &xj_minus_xi)
487                    };
488                    denominator = Integer::from(&denominator * &diff) % &q;
489                }
490            }
491            
492            // Compute Li(0) = numerator / denominator (mod q)
493            let denominator_inv = denominator.invert(&q)
494                .map_err(|_| OpenADPError::SecretSharing("Cannot invert denominator".to_string()))?;
495            let li_0 = (&numerator * &denominator_inv).complete() % &q;
496            
497            // Add yi * Li(0) to result
498            let term = (yi * &li_0).complete() % &q;
499            secret = (&secret + &term).complete() % &q;
500        }
501        
502        Ok(secret)
503    }
504
505    /// Split secret into shares using polynomial evaluation over finite field
506    pub fn split_secret_bytes(secret: &[u8], threshold: usize, num_shares: usize) -> Result<Vec<(usize, Vec<u8>)>> {
507        if threshold == 0 || threshold > num_shares {
508            return Err(OpenADPError::SecretSharing("Invalid threshold".to_string()));
509        }
510        
511        // Convert bytes to big integer
512        let secret_int = Integer::from_digits(secret, rug::integer::Order::MsfBe);
513        
514        // Split the integer secret
515        let int_shares = Self::split_secret(&secret_int, threshold, num_shares)?;
516        
517        // Convert back to bytes
518        let mut byte_shares = Vec::new();
519        for (x, y) in int_shares {
520            let y_bytes = y.to_digits::<u8>(rug::integer::Order::MsfBe);
521            byte_shares.push((x, y_bytes));
522        }
523        
524        Ok(byte_shares)
525    }
526    
527    /// Recover secret from byte shares
528    pub fn recover_secret_bytes(shares: Vec<(usize, Vec<u8>)>) -> Result<Vec<u8>> {
529        if shares.is_empty() {
530            return Err(OpenADPError::SecretSharing("No shares provided".to_string()));
531        }
532        
533        // Convert bytes to integers
534        let int_shares = shares.into_iter()
535            .map(|(x, y_bytes)| {
536                let y = Integer::from_digits(&y_bytes, rug::integer::Order::MsfBe);
537                (x, y)
538            })
539            .collect();
540        
541        // Recover integer secret
542        let secret_int = Self::recover_secret(int_shares)?;
543        
544        // Convert back to bytes
545        let secret_bytes = secret_int.to_digits::<u8>(rug::integer::Order::MsfBe);
546        
547        Ok(secret_bytes)
548    }
549}
550
551/// Point share for point-based secret sharing
552pub struct PointShare {
553    pub x: usize,
554    pub point: Point4D,
555}
556
557impl PointShare {
558    pub fn new(x: usize, point: Point4D) -> Self {
559        Self { x, point }
560    }
561}
562
563/// Recover point secret from point shares using Lagrange interpolation
564pub fn recover_point_secret(point_shares: Vec<PointShare>) -> Result<Point4D> {
565    if point_shares.is_empty() {
566        return Err(OpenADPError::SecretSharing("No point shares provided".to_string()));
567    }
568    
569    let q = &*Q;
570    let mut secret_point = ZERO_POINT.clone();
571    
572    // Lagrange interpolation in point space
573    for (i, share_i) in point_shares.iter().enumerate() {
574        let xi = BigUint::from(share_i.x);
575        
576        // Compute Lagrange basis polynomial Li(0)
577        let mut numerator = BigUint::one();
578        let mut denominator = BigUint::one();
579        
580        for (j, share_j) in point_shares.iter().enumerate() {
581            if i != j {
582                let xj = BigUint::from(share_j.x);
583                
584                // numerator = numerator * (0 - xj) = numerator * (-xj) = numerator * (q - xj)
585                let neg_xj = (q + q - &xj) % q; // Equivalent to q - xj
586                numerator = (&numerator * &neg_xj) % q;
587                
588                // denominator = denominator * (xi - xj)
589                let diff = if &xi >= &xj {
590                    (&xi - &xj) % q
591                } else {
592                    (q + &xi - &xj) % q
593                };
594                denominator = (&denominator * &diff) % q;
595            }
596        }
597        
598        // Compute Li(0) = numerator / denominator (mod q)
599        let denominator_inv = mod_inverse(&denominator, q);
600        let li_0 = (&numerator * &denominator_inv) % q;
601        
602        // Add Li(0) * Pi to result
603        let term_point = point_mul(&li_0, &share_i.point);
604        secret_point = point_add(&secret_point, &term_point);
605    }
606    
607    Ok(secret_point)
608}
609
610/// Ed25519 operations wrapper
611pub struct Ed25519;
612
613impl Ed25519 {
614    #[allow(non_snake_case)]
615    pub fn H(uid: &[u8], did: &[u8], bid: &[u8], pin: &[u8]) -> Result<Point4D> {
616        H(uid, did, bid, pin)
617    }
618
619    pub fn scalar_mult(scalar: &[u8], point: &Point4D) -> Result<Point4D> {
620        let scalar_bigint = BigUint::from_bytes_le(scalar);
621        Ok(point_mul(&scalar_bigint, point))
622    }
623
624    pub fn point_add(p1: &Point4D, p2: &Point4D) -> Result<Point4D> {
625        Ok(point_add(p1, p2))
626    }
627
628    pub fn compress(point: &Point4D) -> Result<Vec<u8>> {
629        point_compress(point)
630    }
631
632    pub fn decompress(data: &[u8]) -> Result<Point4D> {
633        point_decompress(data)
634    }
635}
636
637#[cfg(test)]
638mod tests {
639    use super::*;
640
641    #[test]
642    fn test_point_operations() {
643        // Test basic point operations
644        let p1 = G.clone();
645        let p2 = point_add(&p1, &p1);
646        
647        // Test that 2*G != G
648        assert!(!point_equal(&p1, &p2));
649        
650        // Test point multiplication
651        let scalar = BigUint::from(2u32);
652        let p3 = point_mul(&scalar, &p1);
653        assert!(point_equal(&p2, &p3));
654    }
655
656    #[test]
657    fn test_hash_functions() {
658        let data = b"test data";
659        let hash = sha256_hash(data);
660        assert_eq!(hash.len(), 32);
661    }
662
663    #[test]
664    fn test_H() {
665        let uid = b"test-user";
666        let did = b"test-device";
667        let bid = b"test-backup";
668        let pin = b"12";
669        
670        let point = H(uid, did, bid, pin).unwrap();
671        assert!(is_valid_point(&point));
672    }
673
674    #[test]
675    fn test_shamir_secret_sharing() {
676        let secret = Integer::from(12345);
677        let threshold = 3;
678        let num_shares = 5;
679        
680        let shares = ShamirSecretSharing::split_secret(&secret, threshold, num_shares).unwrap();
681        assert_eq!(shares.len(), num_shares);
682        
683        // Test recovery with minimum threshold
684        let recovery_shares = shares.into_iter().take(threshold).collect();
685        let recovered = ShamirSecretSharing::recover_secret(recovery_shares).unwrap();
686        assert_eq!(recovered, secret);
687    }
688
689    #[test]
690    fn test_key_derivation() {
691        let point = G.clone();
692        let key = derive_enc_key(&point).unwrap();
693        assert_eq!(key.len(), 32);
694    }
695}