1use ark_ff::{PrimeField, Zero};
2use rand_core::{CryptoRng, RngCore};
3
4use ark_r1cs_std::prelude::*;
5use ark_relations::r1cs::SynthesisError;
6use decaf377::{
7 r1cs::{ElementVar, FqVar},
8 Fq, Fr,
9};
10
11use super::{AddressIndex, Diversifier, DiversifierKey};
12use crate::{
13 fmd, ka,
14 keys::{AuthorizationKeyVar, NullifierKeyVar, IVK_DOMAIN_SEP},
15 prf, Address,
16};
17
18pub const IVK_LEN_BYTES: usize = 64;
19const MOD_R_QUOTIENT: usize = 4;
20
21#[derive(Clone, Debug, PartialEq, Eq)]
24pub struct IncomingViewingKey {
25 pub(super) ivk: ka::Secret,
26 pub(super) dk: DiversifierKey,
27}
28
29impl IncomingViewingKey {
30 pub fn payment_address(&self, index: AddressIndex) -> (Address, fmd::DetectionKey) {
32 let d = self.dk.diversifier_for_index(&index);
33 let g_d = d.diversified_generator();
34 let pk_d = self.ivk.diversified_public(&g_d);
35
36 let dtk_d = fmd::DetectionKey::from_field(Fr::from_le_bytes_mod_order(
37 prf::expand(b"PenumbraExpndFMD", &self.ivk.to_bytes(), d.as_ref()).as_bytes(),
38 ));
39 let ck_d = dtk_d.clue_key();
40
41 (
42 Address::from_components(d, pk_d, ck_d).expect("pk_d is valid"),
43 dtk_d,
44 )
45 }
46
47 pub fn transparent_address(&self) -> String {
52 let dzero = Diversifier([0u8; 16]);
54 let g_dzero = dzero.diversified_generator();
55 let pk_dzero = self.ivk.diversified_public(&g_dzero);
56 let ck_id = fmd::ClueKey([0u8; 32]);
57
58 let address = Address::from_components(dzero, pk_dzero, ck_id).expect("valid address");
59
60 address
62 .encode_as_transparent_address()
63 .expect("address meets transparent requirements")
64 }
65
66 pub fn ephemeral_address<R: RngCore + CryptoRng>(
68 &self,
69 mut rng: R,
70 mut address_index: AddressIndex,
71 ) -> (Address, fmd::DetectionKey) {
72 let mut random_index = [0u8; 12];
73
74 rng.fill_bytes(&mut random_index);
75
76 address_index.randomizer = random_index;
77
78 self.payment_address(address_index)
79 }
80
81 pub fn key_agreement_with(&self, pk: &ka::Public) -> Result<ka::SharedSecret, ka::Error> {
83 self.ivk.key_agreement_with(pk)
84 }
85
86 pub fn diversified_public(&self, diversified_generator: &decaf377::Element) -> ka::Public {
88 self.ivk.diversified_public(diversified_generator)
89 }
90
91 pub fn index_for_diversifier(&self, diversifier: &Diversifier) -> AddressIndex {
94 self.dk.index_for_diversifier(diversifier)
95 }
96
97 pub fn views_address(&self, address: &Address) -> bool {
99 self.ivk.diversified_public(address.diversified_generator()) == *address.transmission_key()
100 }
101
102 pub(super) fn address_index(&self, address: &Address) -> Option<AddressIndex> {
106 if self.views_address(address) {
107 Some(self.index_for_diversifier(address.diversifier()))
108 } else {
109 None
110 }
111 }
112}
113
114pub struct IncomingViewingKeyVar {
115 inner: FqVar,
116}
117
118impl IncomingViewingKeyVar {
119 pub fn derive(nk: &NullifierKeyVar, ak: &AuthorizationKeyVar) -> Result<Self, SynthesisError> {
121 let cs = nk.inner.cs();
122 let ivk_domain_sep = FqVar::new_constant(cs.clone(), *IVK_DOMAIN_SEP)?;
123 let ivk_mod_q = poseidon377::r1cs::hash_2(
124 cs.clone(),
125 &ivk_domain_sep,
126 (nk.inner.clone(), ak.inner.compress_to_field()?),
127 )?;
128
129 let r_modulus: Fq = Fq::from(Fr::MODULUS);
131 let ivk_mod_q_ooc: Fq = ivk_mod_q.value().unwrap_or_default();
132 let ivk_mod_r_ooc = Fr::from_le_bytes_mod_order(&ivk_mod_q_ooc.to_bytes());
133
134 let ivk_mod_r_ooc_q = Fq::from_le_bytes_mod_order(&ivk_mod_r_ooc.to_bytes());
136 let ivk_mod_r = FqVar::new_witness(cs.clone(), || Ok(ivk_mod_r_ooc_q))?;
137
138 let mut temp_ivk_mod_q = ivk_mod_q_ooc;
140 let mut a = 0;
141 while temp_ivk_mod_q > r_modulus {
142 temp_ivk_mod_q -= r_modulus;
143 a += 1;
144 }
145
146 let mod_r_var = FqVar::new_constant(cs.clone(), r_modulus)?;
151 let a_var = FqVar::new_witness(cs.clone(), || Ok(Fq::from(a as u64)))?;
152 let rhs = &mod_r_var * &a_var + &ivk_mod_r;
153 ivk_mod_q.enforce_equal(&rhs)?;
154
155 let mut mul = a_var.clone();
160 for i in 1..=MOD_R_QUOTIENT {
161 mul *= a_var.clone() - FqVar::new_constant(cs.clone(), Fq::from(i as u64))?;
162 }
163 let zero = FqVar::new_constant(cs.clone(), Fq::zero())?;
164 mul.enforce_equal(&zero)?;
165
166 ivk_mod_r.enforce_cmp(&mod_r_var, core::cmp::Ordering::Less, false)?;
169
170 let is_less_than_q_minus_4_mod_r = ivk_mod_r.is_cmp(
172 &FqVar::new_constant(
173 cs.clone(),
174 -Fq::from(MOD_R_QUOTIENT as u64) * Fq::from(r_modulus),
175 )?,
176 core::cmp::Ordering::Less,
177 false,
178 )?;
179 let overflows = a_var
180 .is_eq(&FqVar::new_constant(
181 cs.clone(),
182 &Fq::from(MOD_R_QUOTIENT as u64),
183 )?)?
184 .and(&is_less_than_q_minus_4_mod_r.not())?;
185 overflows.enforce_equal(&Boolean::FALSE)?;
186
187 Ok(IncomingViewingKeyVar { inner: ivk_mod_r })
188 }
189
190 pub fn diversified_public(
192 &self,
193 diversified_generator: &ElementVar,
194 ) -> Result<ElementVar, SynthesisError> {
195 let ivk_vars = self.inner.to_bits_le()?;
196 diversified_generator.scalar_mul_le(ivk_vars.to_bits_le()?.iter())
197 }
198}
199
200#[cfg(test)]
201mod test {
202 use crate::{
203 keys::{Bip44Path, SeedPhrase, SpendKey},
204 test_keys,
205 };
206 use proptest::prelude::*;
207 use std::str::FromStr;
208
209 use super::*;
210
211 #[test]
212 fn transparent_address_generation_and_parsing() {
213 let seed_phrase = SeedPhrase::from_str(test_keys::SEED_PHRASE).expect("valid seed phrase");
215 let spend_key = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0));
216 let ivk = spend_key.full_viewing_key().incoming();
217
218 let transparent_address_str = ivk.transparent_address();
219
220 let reconstructed: Address = transparent_address_str
221 .parse()
222 .expect("can parse transparent address");
223
224 assert!(ivk.views_address(&reconstructed));
225
226 let address_index = ivk.address_index(&reconstructed).expect("views address");
227
228 let actual_address = ivk.payment_address(address_index).0;
229
230 assert_ne!(reconstructed.diversifier(), actual_address.diversifier());
234 assert_ne!(
237 reconstructed.transmission_key(),
238 actual_address.transmission_key()
239 );
240 assert_ne!(reconstructed.clue_key(), actual_address.clue_key());
242
243 println!("Transparent address: {}", transparent_address_str);
244 println!("Reconstructed address: {}", reconstructed);
245 println!("Address index: {:?}", address_index);
246 println!("Actual address for index: {}", actual_address);
247 }
248
249 #[test]
250 fn views_address_succeeds_on_own_address() {
251 let rng = rand::rngs::OsRng;
252 let spend_key =
253 SpendKey::from_seed_phrase_bip44(SeedPhrase::generate(rng), &Bip44Path::new(0));
254 let ivk = spend_key.full_viewing_key().incoming();
255 let own_address = ivk.payment_address(AddressIndex::from(0u32)).0;
256 assert!(ivk.views_address(&own_address));
257 }
258
259 proptest! {
260 #[test]
261 fn views_address_succeeds_on_own_ephemeral_address(address_index in any::<u32>()) {
262 let rng = rand::rngs::OsRng;
263 let spend_key = SpendKey::from_seed_phrase_bip44(SeedPhrase::generate(rng), &Bip44Path::new(0));
264 let fvk = spend_key.full_viewing_key();
265 let (own_address, _) = fvk.ephemeral_address(rng, AddressIndex::from(address_index));
266 let ivk = fvk.incoming();
267 assert!(ivk.views_address(&own_address));
268
269 let derived_address_index = fvk.address_index(&own_address);
270 assert_eq!(derived_address_index.expect("index exists").account, AddressIndex::from(address_index).account);
271 }
272 }
273
274 #[test]
275 fn views_address_fails_on_other_address() {
276 let rng = rand::rngs::OsRng;
277 let spend_key =
278 SpendKey::from_seed_phrase_bip44(SeedPhrase::generate(rng), &Bip44Path::new(0));
279 let ivk = spend_key.full_viewing_key().incoming();
280
281 let other_address =
282 SpendKey::from_seed_phrase_bip44(SeedPhrase::generate(rng), &Bip44Path::new(0))
283 .full_viewing_key()
284 .incoming()
285 .payment_address(AddressIndex::from(0u32))
286 .0;
287
288 assert!(!ivk.views_address(&other_address));
289 }
290
291 #[test]
292 fn enforce_field_assumptions() {
293 use num_bigint::BigUint;
294 use num_traits::ops::checked::CheckedSub;
295
296 let fq_modulus: BigUint = Fq::MODULUS.into();
297 let max_q: BigUint = &fq_modulus - 1u32;
298 let fr_modulus: BigUint = Fr::MODULUS.into();
299 assert!(
300 fr_modulus < fq_modulus,
301 "we assume that our scalar field is smaller than our base field"
302 );
303
304 let mut multiple = 0;
305 let mut res = max_q;
306 loop {
307 res = if let Some(x) = res.checked_sub(&fr_modulus) {
308 multiple += 1;
309 x
310 } else {
311 break;
312 };
313 }
314
315 assert_eq!(
316 MOD_R_QUOTIENT, multiple,
317 "`a = fr_modulus * 4 + r mod q` only works on specific curve parameters"
318 );
319 }
320}