ciphersuite_kp256/
lib.rs

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