penumbra_sdk_keys/keys/
diversifier.rs1use 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 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 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
197impl 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}