Skip to main content

zebra_chain/orchard/
arbitrary.rs

1//! Randomised data generation for Orchard types.
2
3use group::{
4    ff::{FromUniformBytes, PrimeField},
5    prime::PrimeCurveAffine,
6};
7use halo2::pasta::pallas;
8use reddsa::{orchard::SpendAuth, Signature, SigningKey, VerificationKey, VerificationKeyBytes};
9
10use proptest::{array, collection::vec, prelude::*};
11
12use super::{
13    keys::*, note, tree, Action, AuthorizedAction, Flags, NoteCommitment, ValueCommitment,
14};
15
16impl Arbitrary for Action {
17    type Parameters = ();
18
19    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
20        (
21            any::<note::Nullifier>(),
22            any::<SpendAuthVerificationKeyBytes>(),
23            any::<note::EncryptedNote>(),
24            any::<note::WrappedNoteKey>(),
25        )
26            .prop_map(|(nullifier, rk, enc_ciphertext, out_ciphertext)| Self {
27                cv: ValueCommitment(pallas::Affine::identity()),
28                nullifier,
29                rk: rk.0,
30                cm_x: NoteCommitment(pallas::Affine::identity()).extract_x(),
31                ephemeral_key: EphemeralPublicKey(pallas::Affine::generator()),
32                enc_ciphertext,
33                out_ciphertext,
34            })
35            .boxed()
36    }
37
38    type Strategy = BoxedStrategy<Self>;
39}
40
41impl Arbitrary for note::Nullifier {
42    type Parameters = ();
43
44    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
45        (vec(any::<u8>(), 64))
46            .prop_map(|bytes| {
47                let bytes = bytes.try_into().expect("vec is the correct length");
48                Self::try_from(pallas::Scalar::from_uniform_bytes(&bytes).to_repr())
49                    .expect("a valid generated nullifier")
50            })
51            .boxed()
52    }
53
54    type Strategy = BoxedStrategy<Self>;
55}
56
57impl Arbitrary for AuthorizedAction {
58    type Parameters = ();
59
60    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
61        (any::<Action>(), any::<SpendAuthSignature>())
62            .prop_map(|(action, spend_auth_sig)| Self {
63                action,
64                spend_auth_sig: spend_auth_sig.0,
65            })
66            .boxed()
67    }
68
69    type Strategy = BoxedStrategy<Self>;
70}
71
72#[derive(Copy, Clone, Debug, Eq, PartialEq)]
73struct SpendAuthSignature(pub(crate) Signature<SpendAuth>);
74
75impl Arbitrary for SpendAuthSignature {
76    type Parameters = ();
77
78    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
79        (array::uniform32(any::<u8>()), array::uniform32(any::<u8>()))
80            .prop_map(|(r_bytes, s_bytes)| {
81                let mut bytes = [0; 64];
82                bytes[0..32].copy_from_slice(&r_bytes[..]);
83                bytes[32..64].copy_from_slice(&s_bytes[..]);
84                SpendAuthSignature(Signature::<SpendAuth>::from(bytes))
85            })
86            .boxed()
87    }
88
89    type Strategy = BoxedStrategy<Self>;
90}
91
92#[derive(Copy, Clone, PartialEq, Eq, Debug)]
93struct SpendAuthVerificationKeyBytes(pub(crate) VerificationKeyBytes<SpendAuth>);
94
95impl Arbitrary for SpendAuthVerificationKeyBytes {
96    type Parameters = ();
97
98    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
99        // Generate a random signing key from a "seed".
100        (vec(any::<u8>(), 64))
101            .prop_map(|bytes| {
102                let bytes = bytes.try_into().expect("vec is the correct length");
103                // Convert to a scalar
104                let sk_scalar = pallas::Scalar::from_uniform_bytes(&bytes);
105                // Convert that back to a (canonical) encoding
106                let sk_bytes = sk_scalar.to_repr();
107                // Decode it into a signing key
108                let sk = SigningKey::try_from(sk_bytes).unwrap();
109                let pk = VerificationKey::<SpendAuth>::from(&sk);
110                SpendAuthVerificationKeyBytes(pk.into())
111            })
112            .boxed()
113    }
114
115    type Strategy = BoxedStrategy<Self>;
116}
117
118impl Arbitrary for Flags {
119    type Parameters = ();
120
121    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
122        // Only generate flags valid in the pre-NU6.3 (v5 Orchard) format: bit 2
123        // (`ENABLE_CROSS_ADDRESS`) is reserved before NU6.3, so generating it would produce v5
124        // Orchard bundles that do not round-trip. The arbitrary transaction strategies generate
125        // v4/v5 transactions, whose Orchard bundles use this format.
126        let pre_nu6_3 = Self::ENABLE_SPENDS.bits() | Self::ENABLE_OUTPUTS.bits();
127        (any::<u8>())
128            .prop_map(move |bits| Self::from_bits_truncate(bits & pre_nu6_3))
129            .boxed()
130    }
131
132    type Strategy = BoxedStrategy<Self>;
133}
134
135fn pallas_base_strat() -> BoxedStrategy<pallas::Base> {
136    (vec(any::<u8>(), 64))
137        .prop_map(|bytes| {
138            let bytes = bytes.try_into().expect("vec is the correct length");
139            pallas::Base::from_uniform_bytes(&bytes)
140        })
141        .boxed()
142}
143
144impl Arbitrary for tree::Root {
145    type Parameters = ();
146
147    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
148        pallas_base_strat()
149            .prop_map(|base| {
150                Self::try_from(base.to_repr())
151                    .expect("a valid generated Orchard note commitment tree root")
152            })
153            .boxed()
154    }
155
156    type Strategy = BoxedStrategy<Self>;
157}
158
159impl Arbitrary for tree::Node {
160    type Parameters = ();
161
162    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
163        pallas_base_strat()
164            .prop_map(|base| {
165                Self::try_from(base.to_repr())
166                    .expect("a valid generated Orchard note commitment tree root")
167            })
168            .boxed()
169    }
170
171    type Strategy = BoxedStrategy<Self>;
172}