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