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