penumbra_sdk_keys/keys/
fvk.rs1use anyhow::Context;
2use ark_serialize::CanonicalDeserialize;
3use decaf377::{Fq, Fr};
4use once_cell::sync::Lazy;
5use poseidon377::hash_2;
6use rand_core::{CryptoRng, RngCore};
7use serde::{Deserialize, Serialize};
8
9use penumbra_sdk_proto::{penumbra::core::keys::v1 as pb, serializers::bech32str, DomainType};
10
11use crate::keys::wallet_id::WalletId;
12use crate::{
13 fmd, ka, prf,
14 rdsa::{SpendAuth, VerificationKey},
15 Address, AddressView, BackreferenceKey,
16};
17
18use super::{AddressIndex, DiversifierKey, IncomingViewingKey, NullifierKey, OutgoingViewingKey};
19
20pub mod r1cs;
21
22pub(crate) static IVK_DOMAIN_SEP: Lazy<Fq> =
23 Lazy::new(|| Fq::from_le_bytes_mod_order(b"penumbra.derive.ivk"));
24
25static ACCOUNT_ID_DOMAIN_SEP: Lazy<Fq> =
26 Lazy::new(|| Fq::from_le_bytes_mod_order(b"Penumbra_HashFVK"));
27
28#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
30#[serde(try_from = "pb::FullViewingKey", into = "pb::FullViewingKey")]
31pub struct FullViewingKey {
32 ak: VerificationKey<SpendAuth>,
33 nk: NullifierKey,
34 ovk: OutgoingViewingKey,
35 ivk: IncomingViewingKey,
36}
37
38impl FullViewingKey {
39 pub fn payment_address(&self, index: AddressIndex) -> (Address, fmd::DetectionKey) {
41 self.incoming().payment_address(index)
42 }
43
44 pub fn ephemeral_address<R: RngCore + CryptoRng>(
46 &self,
47 rng: R,
48 address_index: AddressIndex,
49 ) -> (Address, fmd::DetectionKey) {
50 self.incoming().ephemeral_address(rng, address_index)
51 }
52
53 pub fn view_address(&self, address: Address) -> AddressView {
55 if self.incoming().views_address(&address) {
58 AddressView::Decoded {
59 index: self.incoming().index_for_diversifier(address.diversifier()),
60 wallet_id: self.wallet_id(),
61 address,
62 }
63 } else {
64 AddressView::Opaque { address }
65 }
66 }
67
68 pub fn address_index(&self, address: &Address) -> Option<AddressIndex> {
71 self.incoming().address_index(address)
72 }
73
74 pub fn from_components(ak: VerificationKey<SpendAuth>, nk: NullifierKey) -> Self {
76 let ovk = {
77 let hash_result = prf::expand(b"Penumbra_DeriOVK", &nk.0.to_bytes(), ak.as_ref());
78 let mut ovk = [0; 32];
79 ovk.copy_from_slice(&hash_result.as_bytes()[0..32]);
80 ovk
81 };
82
83 let dk = {
84 let hash_result = prf::expand(b"Penumbra_DerivDK", &nk.0.to_bytes(), ak.as_ref());
85 let mut dk = [0; 16];
86 dk.copy_from_slice(&hash_result.as_bytes()[0..16]);
87 dk
88 };
89
90 let ivk = {
91 let ak_s = Fq::from_bytes_checked(ak.as_ref())
92 .expect("verification key is valid, so its byte encoding is a decaf377 s value");
93 let ivk_mod_q = poseidon377::hash_2(&IVK_DOMAIN_SEP, (nk.0, ak_s));
94 ka::Secret::new_from_field(Fr::from_le_bytes_mod_order(&ivk_mod_q.to_bytes()))
95 };
96
97 let dk = DiversifierKey(dk);
98 let ovk = OutgoingViewingKey(ovk);
99 let ivk = IncomingViewingKey { ivk, dk };
100
101 Self { ak, nk, ovk, ivk }
102 }
103
104 pub fn incoming(&self) -> &IncomingViewingKey {
106 &self.ivk
107 }
108
109 pub fn outgoing(&self) -> &OutgoingViewingKey {
111 &self.ovk
112 }
113
114 pub fn nullifier_key(&self) -> &NullifierKey {
115 &self.nk
116 }
117
118 pub fn spend_verification_key(&self) -> &VerificationKey<SpendAuth> {
120 &self.ak
121 }
122
123 pub fn backref_key(&self) -> BackreferenceKey {
125 BackreferenceKey::derive(self.outgoing()).clone()
126 }
127
128 pub fn wallet_id(&self) -> WalletId {
130 let hash_result = hash_2(
131 &ACCOUNT_ID_DOMAIN_SEP,
132 (
133 self.nk.0,
134 Fq::from_le_bytes_mod_order(&self.ak.to_bytes()[..]),
135 ),
136 );
137 let hash = hash_result.to_bytes()[..32]
138 .try_into()
139 .expect("hash is 32 bytes");
140 WalletId(hash)
141 }
142}
143
144impl DomainType for FullViewingKey {
145 type Proto = pb::FullViewingKey;
146}
147
148impl TryFrom<pb::FullViewingKey> for FullViewingKey {
149 type Error = anyhow::Error;
150
151 fn try_from(value: pb::FullViewingKey) -> Result<Self, Self::Error> {
152 if value.inner.len() != 64 {
153 anyhow::bail!(
154 "Wrong byte length, expected 64 but found {}",
155 value.inner.len()
156 );
157 }
158
159 let ak_bytes: [u8; 32] = value.inner[0..32].try_into().context("fvk wrong length")?;
160 let nk_bytes: [u8; 32] = value.inner[32..64].try_into().context("fvk wrong length")?;
161
162 let ak = ak_bytes.try_into()?;
163 let nk = NullifierKey(
164 Fq::deserialize_compressed(&nk_bytes[..])
165 .context("could not deserialize nullifier key")?,
166 );
167
168 Ok(FullViewingKey::from_components(ak, nk))
169 }
170}
171
172impl From<FullViewingKey> for pb::FullViewingKey {
173 fn from(value: FullViewingKey) -> pb::FullViewingKey {
174 let mut inner = Vec::with_capacity(64);
175 inner.extend_from_slice(&value.ak.to_bytes());
176 inner.extend_from_slice(&value.nk.0.to_bytes());
177 pb::FullViewingKey { inner }
178 }
179}
180
181impl std::fmt::Display for FullViewingKey {
182 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
183 let proto = pb::FullViewingKey::from(self.clone());
184 f.write_str(&bech32str::encode(
185 &proto.inner,
186 bech32str::full_viewing_key::BECH32_PREFIX,
187 bech32str::Bech32m,
188 ))
189 }
190}
191
192impl std::fmt::Debug for FullViewingKey {
193 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
194 <Self as std::fmt::Display>::fmt(self, f)
195 }
196}
197
198impl std::str::FromStr for FullViewingKey {
199 type Err = anyhow::Error;
200
201 fn from_str(s: &str) -> Result<Self, Self::Err> {
202 pb::FullViewingKey {
203 inner: bech32str::decode(
204 s,
205 bech32str::full_viewing_key::BECH32_PREFIX,
206 bech32str::Bech32m,
207 )?,
208 }
209 .try_into()
210 }
211}