penumbra_sdk_keys/keys/
fvk.rs

1use 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/// The root viewing capability for all data related to a given spend authority.
29#[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    /// Derive a shielded payment address with the given [`AddressIndex`].
40    pub fn payment_address(&self, index: AddressIndex) -> (Address, fmd::DetectionKey) {
41        self.incoming().payment_address(index)
42    }
43
44    /// Derive a random ephemeral address.
45    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    /// Views the structure of the supplied address with this viewing key.
54    pub fn view_address(&self, address: Address) -> AddressView {
55        // WART: this can't cleanly forward to a method on the IVK,
56        // because the IVK doesn't know the WalletId.
57        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    /// Returns the index of the given address, if the address is viewed by this
69    /// viewing key; otherwise, returns `None`.
70    pub fn address_index(&self, address: &Address) -> Option<AddressIndex> {
71        self.incoming().address_index(address)
72    }
73
74    /// Construct a full viewing key from its components.
75    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    /// Returns the incoming viewing key for this full viewing key.
105    pub fn incoming(&self) -> &IncomingViewingKey {
106        &self.ivk
107    }
108
109    /// Returns the outgoing viewing key for this full viewing key.
110    pub fn outgoing(&self) -> &OutgoingViewingKey {
111        &self.ovk
112    }
113
114    pub fn nullifier_key(&self) -> &NullifierKey {
115        &self.nk
116    }
117
118    /// Returns the spend verification key contained in this full viewing key.
119    pub fn spend_verification_key(&self) -> &VerificationKey<SpendAuth> {
120        &self.ak
121    }
122
123    /// Construct the backreference key for this full viewing key.
124    pub fn backref_key(&self) -> BackreferenceKey {
125        BackreferenceKey::derive(self.outgoing()).clone()
126    }
127
128    /// Hashes the full viewing key into an [`WalletId`].
129    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}