penumbra_sdk_keys/keys/
diversifier.rs

1use std::convert::{TryFrom, TryInto};
2use std::fmt::Debug;
3use std::str::FromStr;
4
5use aes::cipher::{generic_array::GenericArray, BlockDecrypt, BlockEncrypt, KeyInit};
6use aes::Aes128;
7
8use anyhow::Context;
9use derivative::Derivative;
10use penumbra_sdk_proto::{penumbra::core::keys::v1 as pb, DomainType};
11use rand::{CryptoRng, RngCore};
12use serde::{Deserialize, Serialize};
13
14use decaf377::Fq;
15
16pub const DIVERSIFIER_LEN_BYTES: usize = 16;
17
18#[derive(Copy, Clone, PartialEq, Eq, Derivative, Serialize, Deserialize)]
19#[derivative(Debug)]
20#[serde(try_from = "pb::Diversifier", into = "pb::Diversifier")]
21pub struct Diversifier(
22    #[derivative(Debug(bound = "", format_with = "crate::fmt_hex"))] pub [u8; DIVERSIFIER_LEN_BYTES],
23);
24
25impl Diversifier {
26    /// Generate the diversified basepoint associated to this diversifier.
27    pub fn diversified_generator(&self) -> decaf377::Element {
28        let hash = blake2b_simd::Params::new()
29            .personal(b"Penumbra_Divrsfy")
30            .hash(&self.0);
31
32        decaf377::Element::encode_to_curve(&Fq::from_le_bytes_mod_order(hash.as_bytes()))
33    }
34}
35
36impl AsRef<[u8; DIVERSIFIER_LEN_BYTES]> for Diversifier {
37    fn as_ref(&self) -> &[u8; DIVERSIFIER_LEN_BYTES] {
38        &self.0
39    }
40}
41
42impl TryFrom<&[u8]> for Diversifier {
43    type Error = anyhow::Error;
44
45    fn try_from(slice: &[u8]) -> Result<Diversifier, Self::Error> {
46        if slice.len() != DIVERSIFIER_LEN_BYTES {
47            anyhow::bail!("diversifier must be 16 bytes, got {:?}", slice.len());
48        }
49
50        let mut bytes = [0u8; DIVERSIFIER_LEN_BYTES];
51        bytes.copy_from_slice(&slice[0..16]);
52        Ok(Diversifier(bytes))
53    }
54}
55
56impl DomainType for Diversifier {
57    type Proto = pb::Diversifier;
58}
59
60impl From<Diversifier> for pb::Diversifier {
61    fn from(d: Diversifier) -> pb::Diversifier {
62        pb::Diversifier {
63            inner: d.as_ref().to_vec(),
64        }
65    }
66}
67
68impl TryFrom<pb::Diversifier> for Diversifier {
69    type Error = anyhow::Error;
70
71    fn try_from(d: pb::Diversifier) -> Result<Diversifier, Self::Error> {
72        d.inner.as_slice().try_into()
73    }
74}
75
76#[derive(Clone, Derivative)]
77#[derivative(Debug, PartialEq, Eq)]
78pub struct DiversifierKey(
79    #[derivative(Debug(bound = "", format_with = "crate::fmt_hex"))] pub(super) [u8; 16],
80);
81
82impl DiversifierKey {
83    pub fn diversifier_for_index(&self, index: &AddressIndex) -> Diversifier {
84        let mut key_bytes = [0u8; 16];
85        key_bytes.copy_from_slice(&self.0);
86        let key = GenericArray::from(key_bytes);
87
88        let mut plaintext_bytes = [0u8; 16];
89        plaintext_bytes.copy_from_slice(&index.to_bytes());
90        let mut block = GenericArray::from(plaintext_bytes);
91
92        let cipher = Aes128::new(&key);
93        cipher.encrypt_block(&mut block);
94
95        let mut ciphertext_bytes = [0u8; 16];
96        ciphertext_bytes.copy_from_slice(&block);
97        Diversifier(ciphertext_bytes)
98    }
99
100    pub fn index_for_diversifier(&self, diversifier: &Diversifier) -> AddressIndex {
101        let mut key_bytes = [0u8; 16];
102        key_bytes.copy_from_slice(&self.0);
103        let key = GenericArray::from(key_bytes);
104
105        let mut block = GenericArray::from(diversifier.0);
106
107        let cipher = Aes128::new(&key);
108        cipher.decrypt_block(&mut block);
109
110        // Special case for the null ciphertext. This enables transparent addresses (which have
111        // a null diversifier) to use account 0.
112        if diversifier.0 == [0u8; 16] {
113            return AddressIndex {
114                account: 0,
115                randomizer: [0; 12],
116            };
117        }
118
119        let mut index_bytes = [0; DIVERSIFIER_LEN_BYTES];
120        index_bytes.copy_from_slice(&block);
121
122        AddressIndex {
123            account: u32::from_le_bytes(
124                index_bytes[0..4].try_into().expect("can form 4 byte array"),
125            ),
126            randomizer: index_bytes[4..16]
127                .try_into()
128                .expect("can form 12 byte array"),
129        }
130    }
131}
132
133#[derive(
134    Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Derivative, Serialize, Deserialize,
135)]
136#[serde(try_from = "pb::AddressIndex", into = "pb::AddressIndex")]
137pub struct AddressIndex {
138    pub account: u32,
139    pub randomizer: [u8; 12],
140}
141
142impl Debug for AddressIndex {
143    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144        f.debug_struct("AddressIndex")
145            .field("account", &self.account)
146            .field("randomizer", &hex::encode(self.randomizer))
147            .finish()
148    }
149}
150
151impl AddressIndex {
152    pub fn to_bytes(&self) -> [u8; 16] {
153        let mut bytes = [0; DIVERSIFIER_LEN_BYTES];
154        bytes[0..4].copy_from_slice(&self.account.to_le_bytes());
155        bytes[4..16].copy_from_slice(&self.randomizer);
156        bytes
157    }
158
159    pub fn is_ephemeral(&self) -> bool {
160        self.randomizer != [0; 12]
161    }
162
163    pub fn new(account: u32) -> Self {
164        AddressIndex::from(account)
165    }
166
167    pub fn new_ephemeral<R: RngCore + CryptoRng>(account: u32, mut rng: R) -> Self {
168        let mut bytes = [0u8; 12];
169
170        rng.fill_bytes(&mut bytes);
171
172        Self {
173            account,
174            randomizer: bytes,
175        }
176    }
177}
178
179impl From<u32> for AddressIndex {
180    fn from(x: u32) -> Self {
181        Self {
182            account: x,
183            randomizer: [0; 12],
184        }
185    }
186}
187
188impl<'a> From<&'a u32> for AddressIndex {
189    fn from(x: &'a u32) -> Self {
190        Self {
191            account: *x,
192            randomizer: [0; 12],
193        }
194    }
195}
196
197// TODO: add support for ephemeral addresses to FromStr impl.
198impl FromStr for AddressIndex {
199    type Err = anyhow::Error;
200
201    fn from_str(s: &str) -> anyhow::Result<Self> {
202        let i: u32 = s.parse()?;
203        Ok(Self {
204            account: i,
205            randomizer: [0; 12],
206        })
207    }
208}
209
210impl From<AddressIndex> for u128 {
211    fn from(x: AddressIndex) -> Self {
212        u128::from_le_bytes(x.to_bytes())
213    }
214}
215
216impl TryFrom<AddressIndex> for u64 {
217    type Error = anyhow::Error;
218    fn try_from(address_index: AddressIndex) -> Result<Self, Self::Error> {
219        let mut bytes = [0; 8];
220        bytes[0..4].copy_from_slice(&address_index.account.to_le_bytes());
221        bytes[5..8].copy_from_slice(address_index.randomizer.as_slice());
222
223        Ok(u64::from_le_bytes(bytes))
224    }
225}
226
227impl TryFrom<&[u8]> for AddressIndex {
228    type Error = anyhow::Error;
229
230    fn try_from(slice: &[u8]) -> Result<AddressIndex, Self::Error> {
231        if slice.len() != DIVERSIFIER_LEN_BYTES {
232            anyhow::bail!("address index must be 16 bytes, got {:?}", slice.len());
233        }
234
235        Ok(AddressIndex {
236            account: u32::from_le_bytes(slice[0..4].try_into().expect("can form 4 byte array")),
237            randomizer: slice[4..16].try_into().expect("can form 12 byte array"),
238        })
239    }
240}
241
242impl DomainType for AddressIndex {
243    type Proto = pb::AddressIndex;
244}
245
246impl From<AddressIndex> for pb::AddressIndex {
247    fn from(d: AddressIndex) -> pb::AddressIndex {
248        if d.is_ephemeral() {
249            pb::AddressIndex {
250                account: d.account,
251                randomizer: d.randomizer.to_vec(),
252            }
253        } else {
254            pb::AddressIndex {
255                account: d.account,
256                randomizer: Vec::new(),
257            }
258        }
259    }
260}
261
262impl TryFrom<pb::AddressIndex> for AddressIndex {
263    type Error = anyhow::Error;
264
265    fn try_from(d: pb::AddressIndex) -> Result<AddressIndex, Self::Error> {
266        let randomizer: [u8; 12] = if d.randomizer.is_empty() {
267            [0; 12]
268        } else {
269            d.randomizer
270                .as_slice()
271                .try_into()
272                .context("could not parse 12-byte array")?
273        };
274
275        Ok(Self {
276            account: d.account,
277            randomizer,
278        })
279    }
280}
281
282#[cfg(test)]
283mod tests {
284    use proptest::prelude::*;
285
286    use super::*;
287
288    fn address_index_strategy() -> BoxedStrategy<AddressIndex> {
289        any::<u32>().prop_map(AddressIndex::from).boxed()
290    }
291
292    fn diversifier_key_strategy() -> BoxedStrategy<DiversifierKey> {
293        any::<[u8; 16]>().prop_map(DiversifierKey).boxed()
294    }
295
296    proptest! {
297        #[test]
298        fn diversifier_encryption_roundtrip(
299            key in diversifier_key_strategy(),
300            index in address_index_strategy(),
301        ) {
302            let diversifier = key.diversifier_for_index(&index);
303            let index2 = key.index_for_diversifier(&diversifier);
304            assert_eq!(index2, index);
305        }
306    }
307
308    proptest! {
309        #[test]
310        fn diversifier_encryption_null_ciphertext(
311            key in diversifier_key_strategy()
312        ) {
313            let diversifier = Diversifier([0; 16]);
314            let index = key.index_for_diversifier(&diversifier);
315            assert_eq!(index, AddressIndex::new(0));
316        }
317    }
318}