dcrypt_algorithms/ec/p521/constants.rs
1//! Shared constants and helper functions for P-521 operations
2
3/// Size of a P-521 scalar in bytes (66 bytes)
4pub const P521_SCALAR_SIZE: usize = 66;
5
6/// Size of a P-521 field element in bytes (66 bytes)
7pub const P521_FIELD_ELEMENT_SIZE: usize = 66;
8
9/// Size of an uncompressed P-521 point in bytes: format byte (0x04) + x-coordinate + y-coordinate
10pub const P521_POINT_UNCOMPRESSED_SIZE: usize = 1 + 2 * P521_FIELD_ELEMENT_SIZE; // 1 + 132 = 133 bytes
11
12/// Size of a compressed P-521 point in bytes: format byte (0x02/0x03) + x-coordinate
13pub const P521_POINT_COMPRESSED_SIZE: usize = 1 + P521_FIELD_ELEMENT_SIZE; // 1 + 66 = 67 bytes
14
15/// Size of the KDF output for P-521 ECDH-KEM shared secret derivation (e.g., for HKDF-SHA512)
16pub const P521_KEM_SHARED_SECRET_KDF_OUTPUT_SIZE: usize = 64;
17
18/// Number of 32-bit limbs for P-521 field elements and scalars
19pub(crate) const P521_LIMBS: usize = 17;
20
21/// Converts 66 big-endian bytes to 17 little-endian u32 limbs for P-521.
22/// The most significant limb (limbs[16]) will only use its lowest 9 bits (521 mod 32 = 9).
23pub(crate) fn p521_bytes_to_limbs(bytes_be: &[u8; P521_FIELD_ELEMENT_SIZE]) -> [u32; P521_LIMBS] {
24 let mut limbs = [0u32; P521_LIMBS];
25 #[allow(clippy::needless_range_loop)]
26 for i in 0..16 {
27 // First 16 limbs are full
28 let offset = P521_FIELD_ELEMENT_SIZE - 4 - (i * 4);
29 limbs[i] = u32::from_be_bytes([
30 bytes_be[offset],
31 bytes_be[offset + 1],
32 bytes_be[offset + 2],
33 bytes_be[offset + 3],
34 ]);
35 }
36 // Last limb (most significant) from the first 2 bytes (16 bits), but only 9 bits are used.
37 // bytes_be[0] (MSB of input), bytes_be[1]
38 limbs[16] = ((bytes_be[0] as u32) << 8) | (bytes_be[1] as u32);
39 limbs[16] &= (1 << 9) - 1; // Mask to 9 bits (2^9 -1) is 0x1FF
40
41 limbs
42}
43
44/// Converts 17 little-endian u32 limbs to 66 big-endian bytes for P-521.
45pub(crate) fn p521_limbs_to_bytes(limbs: &[u32; P521_LIMBS]) -> [u8; P521_FIELD_ELEMENT_SIZE] {
46 let mut bytes_be = [0u8; P521_FIELD_ELEMENT_SIZE];
47 #[allow(clippy::needless_range_loop)]
48 for i in 0..16 {
49 // First 16 limbs
50 let limb_bytes = limbs[i].to_be_bytes();
51 let offset = P521_FIELD_ELEMENT_SIZE - 4 - (i * 4);
52 bytes_be[offset..offset + 4].copy_from_slice(&limb_bytes);
53 }
54 // Last limb (most significant)
55 let ms_limb_val = limbs[16] & 0x1FF; // Ensure only 9 bits are used
56 bytes_be[0] = (ms_limb_val >> 8) as u8; // Top bit of the 9 bits
57 bytes_be[1] = (ms_limb_val & 0xFF) as u8; // Lower 8 bits of the 9 bits
58
59 // Zero out the unused top 7 bits of the first byte if necessary (though ms_limb_val >> 8 already handles it)
60 bytes_be[0] &= 0x01; // Since it's 2^521-1, the MSB of the first byte can only be 0 or 1.
61
62 bytes_be
63}