ciphersuite_mirror/
kp256.rs

1use zeroize::Zeroize;
2
3use sha2::Sha256;
4
5use group::ff::PrimeField;
6
7use elliptic_curve::{
8  generic_array::GenericArray,
9  bigint::{NonZero, CheckedAdd, Encoding, U384},
10  hash2curve::{Expander, ExpandMsg, ExpandMsgXmd},
11};
12
13use crate::Ciphersuite;
14
15macro_rules! kp_curve {
16  (
17    $feature: literal,
18    $lib:     ident,
19
20    $Ciphersuite: ident,
21    $ID:          literal
22  ) => {
23    impl Ciphersuite for $Ciphersuite {
24      type F = $lib::Scalar;
25      type G = $lib::ProjectivePoint;
26      type H = Sha256;
27
28      const ID: &'static [u8] = $ID;
29
30      fn generator() -> Self::G {
31        $lib::ProjectivePoint::GENERATOR
32      }
33
34      fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F {
35        // While one of these two libraries does support directly hashing to the Scalar field, the
36        // other doesn't. While that's probably an oversight, this is a universally working method
37
38        // This method is from
39        // https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html
40        // Specifically, Section 5
41
42        // While that draft, overall, is intended for hashing to curves, that necessitates
43        // detailing how to hash to a finite field. The draft comments that its mechanism for
44        // doing so, which it uses to derive field elements, is also applicable to the scalar field
45
46        // The hash_to_field function is intended to provide unbiased values
47        // In order to do so, a wide reduction from an extra k bits is applied, minimizing bias to
48        // 2^-k
49        // k is intended to be the bits of security of the suite, which is 128 for secp256k1 and
50        // P-256
51        const K: usize = 128;
52
53        // L is the amount of bytes of material which should be used in the wide reduction
54        // The 256 is for the bit-length of the primes, rounded up to the nearest byte threshold
55        // This is a simplification of the formula from the end of section 5
56        const L: usize = (256 + K) / 8; // 48
57
58        // In order to perform this reduction, we need to use 48-byte numbers
59        // First, convert the modulus to a 48-byte number
60        // This is done by getting -1 as bytes, parsing it into a U384, and then adding back one
61        let mut modulus = [0; L];
62        // The byte repr of scalars will be 32 big-endian bytes
63        // Set the lower 32 bytes of our 48-byte array accordingly
64        modulus[16 ..].copy_from_slice(&(Self::F::ZERO - Self::F::ONE).to_bytes());
65        // Use a checked_add + unwrap since this addition cannot fail (being a 32-byte value with
66        // 48-bytes of space)
67        // While a non-panicking saturating_add/wrapping_add could be used, they'd likely be less
68        // performant
69        let modulus = U384::from_be_slice(&modulus).checked_add(&U384::ONE).unwrap();
70
71        // The defined P-256 and secp256k1 ciphersuites both use expand_message_xmd
72        let mut wide = U384::from_be_bytes({
73          let mut bytes = [0; 48];
74          ExpandMsgXmd::<Sha256>::expand_message(&[msg], &[dst], 48)
75            .unwrap()
76            .fill_bytes(&mut bytes);
77          bytes
78        })
79        .rem(&NonZero::new(modulus).unwrap())
80        .to_be_bytes();
81
82        // Now that this has been reduced back to a 32-byte value, grab the lower 32-bytes
83        let mut array = *GenericArray::from_slice(&wide[16 ..]);
84        let res = $lib::Scalar::from_repr(array).unwrap();
85
86        // Zeroize the temp values we can due to the possibility hash_to_F is being used for nonces
87        wide.zeroize();
88        array.zeroize();
89        res
90      }
91    }
92  };
93}
94
95#[cfg(test)]
96fn test_oversize_dst<C: Ciphersuite>() {
97  use sha2::Digest;
98
99  // The draft specifies DSTs >255 bytes should be hashed into a 32-byte DST
100  let oversize_dst = [0x00; 256];
101  let actual_dst = Sha256::digest([b"H2C-OVERSIZE-DST-".as_ref(), &oversize_dst].concat());
102  // Test the hash_to_F function handles this
103  // If it didn't, these would return different values
104  assert_eq!(C::hash_to_F(&oversize_dst, &[]), C::hash_to_F(&actual_dst, &[]));
105}
106
107/// Ciphersuite for Secp256k1.
108///
109/// hash_to_F is implemented via the IETF draft for hash to curve's hash_to_field (v16).
110#[cfg(feature = "secp256k1")]
111#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
112pub struct Secp256k1;
113#[cfg(feature = "secp256k1")]
114kp_curve!("secp256k1", k256, Secp256k1, b"secp256k1");
115#[cfg(feature = "secp256k1")]
116#[test]
117fn test_secp256k1() {
118  ff_group_tests::group::test_prime_group_bits::<_, k256::ProjectivePoint>(&mut rand_core::OsRng);
119
120  // Ideally, a test vector from hash_to_field (not FROST) would be here
121  // Unfortunately, the IETF draft only provides vectors for field elements, not scalars
122  // Vectors have been requested in
123  // https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/issues/343
124
125  assert_eq!(
126    Secp256k1::hash_to_F(
127      b"FROST-secp256k1-SHA256-v11nonce",
128      &hex::decode(
129        "\
13080cbea5e405d169999d8c4b30b755fedb26ab07ec8198cda4873ed8ce5e16773\
13108f89ffe80ac94dcb920c26f3f46140bfc7f95b493f8310f5fc1ea2b01f4254c"
132      )
133      .unwrap()
134    )
135    .to_repr()
136    .iter()
137    .copied()
138    .collect::<Vec<_>>(),
139    hex::decode("acc83278035223c1ba464e2d11bfacfc872b2b23e1041cf5f6130da21e4d8068").unwrap()
140  );
141
142  test_oversize_dst::<Secp256k1>();
143}
144
145/// Ciphersuite for P-256.
146///
147/// hash_to_F is implemented via the IETF draft for hash to curve's hash_to_field (v16).
148#[cfg(feature = "p256")]
149#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
150pub struct P256;
151#[cfg(feature = "p256")]
152kp_curve!("p256", p256, P256, b"P-256");
153#[cfg(feature = "p256")]
154#[test]
155fn test_p256() {
156  ff_group_tests::group::test_prime_group_bits::<_, p256::ProjectivePoint>(&mut rand_core::OsRng);
157
158  assert_eq!(
159    P256::hash_to_F(
160      b"FROST-P256-SHA256-v11nonce",
161      &hex::decode(
162        "\
163f4e8cf80aec3f888d997900ac7e3e349944b5a6b47649fc32186d2f1238103c6\
1640c9c1a0fe806c184add50bbdcac913dda73e482daf95dcb9f35dbb0d8a9f7731"
165      )
166      .unwrap()
167    )
168    .to_repr()
169    .iter()
170    .copied()
171    .collect::<Vec<_>>(),
172    hex::decode("f871dfcf6bcd199342651adc361b92c941cb6a0d8c8c1a3b91d79e2c1bf3722d").unwrap()
173  );
174
175  test_oversize_dst::<P256>();
176}