Skip to main content

aptos_crypto_link/
test_utils.rs

1// Copyright (c) Aptos
2// SPDX-License-Identifier: Apache-2.0
3
4//! Internal module containing convenience utility functions mainly for testing
5
6use crate::traits::Uniform;
7use rand::distributions;
8use serde::{Deserialize, Serialize};
9
10/// A deterministic seed for PRNGs related to keys
11pub const TEST_SEED: [u8; 32] = [0u8; 32];
12
13/// A keypair consisting of a private and public key
14#[cfg_attr(feature = "cloneable-private-keys", derive(Clone))]
15#[derive(Serialize, Deserialize, PartialEq, Eq)]
16pub struct KeyPair<S, P>
17where
18    for<'a> P: From<&'a S>,
19{
20    /// the private key component
21    pub private_key: S,
22    /// the public key component
23    pub public_key: P,
24}
25
26impl<S, P> From<S> for KeyPair<S, P>
27where
28    for<'a> P: From<&'a S>,
29{
30    fn from(private_key: S) -> Self {
31        KeyPair {
32            public_key: (&private_key).into(),
33            private_key,
34        }
35    }
36}
37
38impl<S, P> Uniform for KeyPair<S, P>
39where
40    S: Uniform,
41    for<'a> P: From<&'a S>,
42{
43    fn generate<R>(rng: &mut R) -> Self
44    where
45        R: ::rand::RngCore + ::rand::CryptoRng,
46    {
47        let private_key = S::generate(rng);
48        private_key.into()
49    }
50}
51
52/// A pair consisting of a private and public key
53impl<S, P> Uniform for (S, P)
54where
55    S: Uniform,
56    for<'a> P: From<&'a S>,
57{
58    fn generate<R>(rng: &mut R) -> Self
59    where
60        R: ::rand::RngCore + ::rand::CryptoRng,
61    {
62        let private_key = S::generate(rng);
63        let public_key = (&private_key).into();
64        (private_key, public_key)
65    }
66}
67
68impl<Priv, Pub> std::fmt::Debug for KeyPair<Priv, Pub>
69where
70    Priv: Serialize,
71    Pub: Serialize + for<'a> From<&'a Priv>,
72{
73    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        let mut v = bcs::to_bytes(&self.private_key).unwrap();
75        v.extend(&bcs::to_bytes(&self.public_key).unwrap());
76        write!(f, "{}", hex::encode(&v[..]))
77    }
78}
79
80#[cfg(any(test, feature = "fuzzing"))]
81use crate::signing_message;
82#[cfg(any(test, feature = "fuzzing"))]
83use curve25519_dalek::constants::EIGHT_TORSION;
84#[cfg(any(test, feature = "fuzzing"))]
85use curve25519_dalek::edwards::EdwardsPoint;
86#[cfg(any(test, feature = "fuzzing"))]
87use curve25519_dalek::scalar::Scalar;
88#[cfg(any(test, feature = "fuzzing"))]
89use curve25519_dalek::traits::Identity;
90#[cfg(any(test, feature = "fuzzing"))]
91use digest::Digest;
92#[cfg(any(test, feature = "fuzzing"))]
93use proptest::prelude::*;
94use rand::prelude::IteratorRandom;
95#[cfg(any(test, feature = "fuzzing"))]
96use rand::{rngs::StdRng, SeedableRng};
97#[cfg(any(test, feature = "fuzzing"))]
98use sha2::Sha512;
99
100/// Produces a uniformly random keypair from a seed
101#[cfg(any(test, feature = "fuzzing"))]
102pub fn uniform_keypair_strategy<Priv, Pub>() -> impl Strategy<Value = KeyPair<Priv, Pub>>
103where
104    Pub: Serialize + for<'a> From<&'a Priv>,
105    Priv: Serialize + Uniform,
106{
107    // The no_shrink is because keypairs should be fixed -- shrinking would cause a different
108    // keypair to be generated, which appears to not be very useful.
109    any::<[u8; 32]>()
110        .prop_map(|seed| {
111            let mut rng = StdRng::from_seed(seed);
112            KeyPair::<Priv, Pub>::generate(&mut rng)
113        })
114        .no_shrink()
115}
116
117/// Produces a small order group element
118#[cfg(any(test, feature = "fuzzing"))]
119pub fn small_order_strategy() -> impl Strategy<Value = EdwardsPoint> {
120    (0..EIGHT_TORSION.len())
121        .prop_map(|exp| {
122            let generator = EIGHT_TORSION[1]; // generator of size-8 subgroup is at index 1
123            Scalar::from(exp as u64) * generator
124        })
125        .no_shrink()
126}
127
128/// Produces a small order R, public key A and a hash h = H(R, A, m) such that sB - hA = R when s is
129/// zero.
130#[allow(non_snake_case)]
131#[cfg(any(test, feature = "fuzzing"))]
132pub fn small_order_pk_with_adversarial_message(
133) -> impl Strategy<Value = (EdwardsPoint, EdwardsPoint, TestAptosCrypto)> {
134    (
135        small_order_strategy(),
136        small_order_strategy(),
137        random_serializable_struct(),
138    )
139        .prop_filter(
140            "Filtering messages by hash * pk == R",
141            |(R, pk_point, msg)| {
142                let pk_bytes = pk_point.compress().to_bytes();
143
144                let msg_bytes = signing_message(msg).unwrap();
145
146                let mut h: Sha512 = Sha512::new();
147                h.update(R.compress().as_bytes());
148                h.update(pk_bytes);
149                h.update(&msg_bytes);
150
151                let k = Scalar::from_hash(h);
152
153                k * pk_point + (*R) == EdwardsPoint::identity()
154            },
155        )
156}
157
158/// Produces a uniformly random keypair from a seed and the user can alter this sleed slightly.
159/// Useful for circumstances where you want two disjoint keypair generations that may interact with
160/// each other.
161#[cfg(any(test, feature = "fuzzing"))]
162pub fn uniform_keypair_strategy_with_perturbation<Priv, Pub>(
163    perturbation: u8,
164) -> impl Strategy<Value = KeyPair<Priv, Pub>>
165where
166    Pub: Serialize + for<'a> From<&'a Priv>,
167    Priv: Serialize + Uniform,
168{
169    // The no_shrink is because keypairs should be fixed -- shrinking would cause a different
170    // keypair to be generated, which appears to not be very useful.
171    any::<[u8; 32]>()
172        .prop_map(move |mut seed| {
173            for elem in seed.iter_mut() {
174                *elem = elem.saturating_add(perturbation);
175            }
176            let mut rng = StdRng::from_seed(seed);
177            KeyPair::<Priv, Pub>::generate(&mut rng)
178        })
179        .no_shrink()
180}
181
182/// Returns `subset_size` numbers picked uniformly at random from 0 to `max_set_size - 1` (inclusive).
183pub fn random_subset<R>(mut rng: &mut R, max_set_size: usize, subset_size: usize) -> Vec<usize>
184where
185    R: ::rand::Rng + ?Sized,
186{
187    let mut vec = (0..max_set_size)
188        .choose_multiple(&mut rng, subset_size)
189        .into_iter()
190        .collect::<Vec<usize>>();
191
192    vec.sort_unstable();
193
194    vec
195}
196
197/// Returns n random bytes.
198pub fn random_bytes<R>(rng: &mut R, n: usize) -> Vec<u8>
199where
200    R: ::rand::Rng + Copy,
201{
202    let range = distributions::Uniform::from(0u8..u8::MAX);
203    rng.sample_iter(&range).take(n).collect()
204}
205
206/// Generates `num_signers` random key-pairs.
207pub fn random_keypairs<R, PrivKey, PubKey>(
208    mut rng: &mut R,
209    num_signers: usize,
210) -> Vec<KeyPair<PrivKey, PubKey>>
211where
212    R: ::rand::RngCore + ::rand::CryptoRng,
213    PubKey: for<'a> std::convert::From<&'a PrivKey>,
214    PrivKey: Uniform,
215{
216    let mut key_pairs = vec![];
217    for _ in 0..num_signers {
218        key_pairs.push(KeyPair::<PrivKey, PubKey>::generate(&mut rng));
219    }
220    key_pairs
221}
222
223/// This struct provides a means of testing signing and verification through
224/// BCS serialization and domain separation
225//#[cfg(any(test, feature = "fuzzing"))]
226#[derive(Debug, Serialize, Deserialize)]
227pub struct TestAptosCrypto(pub String);
228
229// the following block is macro expanded from derive(CryptoHasher, BCSCryptoHash)
230
231/// Cryptographic hasher for an BCS-serializable #item
232// #[cfg(any(test, feature = "fuzzing"))]
233pub struct TestAptosCryptoHasher(crate::hash::DefaultHasher);
234// #[cfg(any(test, feature = "fuzzing"))]
235impl ::core::clone::Clone for TestAptosCryptoHasher {
236    #[inline]
237    fn clone(&self) -> TestAptosCryptoHasher {
238        match *self {
239            TestAptosCryptoHasher(ref __self_0_0) => {
240                TestAptosCryptoHasher(::core::clone::Clone::clone(__self_0_0))
241            }
242        }
243    }
244}
245// #[cfg(any(test, feature = "fuzzing"))]
246static TEST_CRYPTO_SEED: crate::_once_cell::sync::OnceCell<[u8; 32]> =
247    crate::_once_cell::sync::OnceCell::new();
248// #[cfg(any(test, feature = "fuzzing"))]
249impl TestAptosCryptoHasher {
250    fn new() -> Self {
251        let name = crate::_serde_name::trace_name::<TestAptosCrypto>()
252            .expect("The `CryptoHasher` macro only applies to structs and enums");
253        TestAptosCryptoHasher(crate::hash::DefaultHasher::new(name.as_bytes()))
254    }
255}
256// #[cfg(any(test, feature = "fuzzing"))]
257static TEST_CRYPTO_HASHER: crate::_once_cell::sync::Lazy<TestAptosCryptoHasher> =
258    crate::_once_cell::sync::Lazy::new(TestAptosCryptoHasher::new);
259// #[cfg(any(test, feature = "fuzzing"))]
260impl std::default::Default for TestAptosCryptoHasher {
261    fn default() -> Self {
262        TEST_CRYPTO_HASHER.clone()
263    }
264}
265// #[cfg(any(test, feature = "fuzzing"))]
266impl crate::hash::CryptoHasher for TestAptosCryptoHasher {
267    fn seed() -> &'static [u8; 32] {
268        TEST_CRYPTO_SEED.get_or_init(|| {
269            let name = crate::_serde_name::trace_name::<TestAptosCrypto>()
270                .expect("The `CryptoHasher` macro only applies to structs and enums.")
271                .as_bytes();
272            crate::hash::DefaultHasher::prefixed_hash(name)
273        })
274    }
275    fn update(&mut self, bytes: &[u8]) {
276        self.0.update(bytes);
277    }
278    fn finish(self) -> crate::hash::HashValue {
279        self.0.finish()
280    }
281}
282// #[cfg(any(test, feature = "fuzzing"))]
283impl std::io::Write for TestAptosCryptoHasher {
284    fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
285        self.0.update(bytes);
286        Ok(bytes.len())
287    }
288    fn flush(&mut self) -> std::io::Result<()> {
289        Ok(())
290    }
291}
292// #[cfg(any(test, feature = "fuzzing"))]
293impl crate::hash::CryptoHash for TestAptosCrypto {
294    type Hasher = TestAptosCryptoHasher;
295    fn hash(&self) -> crate::hash::HashValue {
296        use crate::hash::CryptoHasher;
297        let mut state = Self::Hasher::default();
298        bcs::serialize_into(&mut state, &self)
299            .expect("BCS serialization of TestAptosCrypto should not fail");
300        state.finish()
301    }
302}
303
304/// Produces a random TestAptosCrypto signable / verifiable struct.
305#[cfg(any(test, feature = "fuzzing"))]
306pub fn random_serializable_struct() -> impl Strategy<Value = TestAptosCrypto> {
307    (String::arbitrary()).prop_map(TestAptosCrypto).no_shrink()
308}