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}