penumbra_sdk_keys/
address.rs

1//! [Payment address][Address] facilities.
2
3use std::{
4    fmt::Display,
5    io::{Cursor, Read, Write},
6    sync::OnceLock,
7};
8
9use anyhow::Context;
10use ark_serialize::CanonicalDeserialize;
11use decaf377::Fq;
12use f4jumble::{f4jumble, f4jumble_inv};
13use penumbra_sdk_proto::{penumbra::core::keys::v1 as pb, serializers::bech32str, DomainType};
14use rand::{CryptoRng, Rng};
15use serde::{Deserialize, Serialize};
16use sha2::{Digest, Sha256};
17
18mod r1cs;
19pub use r1cs::AddressVar;
20
21mod view;
22pub use view::AddressView;
23
24use crate::{fmd, ka, keys::Diversifier};
25
26pub const TRANSPARENT_ADDRESS_BECH32_PREFIX: &str = "tpenumbra";
27
28/// The length of an [`Address`] in bytes.
29pub const ADDRESS_LEN_BYTES: usize = 80;
30
31/// Number of bits in the address short form divided by the number of bits per Bech32m character
32pub const ADDRESS_NUM_CHARS_SHORT_FORM: usize = 24;
33
34/// A valid payment address.
35#[derive(Clone, Eq, Serialize, Deserialize)]
36#[serde(try_from = "pb::Address", into = "pb::Address")]
37pub struct Address {
38    /// The address diversifier.
39    d: Diversifier,
40    /// A cached copy of the diversified base.
41    g_d: OnceLock<decaf377::Element>,
42
43    /// The public key for this payment address.
44    ///
45    /// extra invariant: the bytes in pk_d should be the canonical encoding of an
46    /// s value (whether or not it is a valid decaf377 encoding)
47    /// this ensures we can use a PaymentAddress to form a note commitment,
48    /// which involves hashing s as a field element.
49    pk_d: ka::Public,
50    /// The transmission key s value.
51    transmission_key_s: Fq,
52
53    /// The clue key for this payment address.
54    ck_d: fmd::ClueKey,
55}
56
57impl std::cmp::PartialEq for Address {
58    fn eq(
59        &self,
60        rhs @ Self {
61            d: rhs_d,
62            g_d: rhs_g_d,
63            pk_d: rhs_pk_d,
64            transmission_key_s: rhs_transmission_key_s,
65            ck_d: rhs_ck_d,
66        }: &Self,
67    ) -> bool {
68        let lhs @ Self {
69            d: lhs_d,
70            g_d: lhs_g_d,
71            pk_d: lhs_pk_d,
72            transmission_key_s: lhs_transmission_key_s,
73            ck_d: lhs_ck_d,
74        } = self;
75
76        // When a `OnceLock<T>` value is compared, it will only call `get()`, refraining from
77        // initializing the value. To make sure that an address that *hasn't* yet accessed its
78        // diversified base is considered equal to an address that *has*, compute the base points
79        // if they have not already been generated.
80        lhs.diversified_generator();
81        rhs.diversified_generator();
82
83        // Compare all of the fields.
84        lhs_d.eq(rhs_d)
85            && lhs_g_d.eq(rhs_g_d)
86            && lhs_pk_d.eq(rhs_pk_d)
87            && lhs_transmission_key_s.eq(rhs_transmission_key_s)
88            && lhs_ck_d.eq(rhs_ck_d)
89    }
90}
91
92impl std::cmp::PartialOrd for Address {
93    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
94        Some(self.to_vec().cmp(&other.to_vec()))
95    }
96}
97
98impl std::cmp::Ord for Address {
99    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
100        self.to_vec().cmp(&other.to_vec())
101    }
102}
103
104impl std::hash::Hash for Address {
105    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
106        self.to_vec().hash(state)
107    }
108}
109
110impl Address {
111    /// Constructs a payment address from its components.
112    ///
113    /// Returns `None` if the bytes in pk_d are a non-canonical representation
114    /// of an [`Fq`] `s` value.
115    pub fn from_components(d: Diversifier, pk_d: ka::Public, ck_d: fmd::ClueKey) -> Option<Self> {
116        // XXX ugly -- better way to get our hands on the s value?
117        // add to decaf377::Encoding? there's compress_to_field already...
118        if let Ok(transmission_key_s) = Fq::deserialize_compressed(&pk_d.0[..]) {
119            // don't need an error type here, caller will probably .expect anyways
120            Some(Self {
121                d,
122                g_d: OnceLock::new(),
123                pk_d,
124                ck_d,
125                transmission_key_s,
126            })
127        } else {
128            None
129        }
130    }
131
132    /// Returns a reference to the address diversifier.
133    pub fn diversifier(&self) -> &Diversifier {
134        &self.d
135    }
136
137    /// Returns a reference to the diversified base.
138    ///
139    /// This method computes the diversified base if it has not been computed yet. This value is
140    /// cached after it has been computed once.
141    pub fn diversified_generator(&self) -> &decaf377::Element {
142        self.g_d
143            .get_or_init(|| self.diversifier().diversified_generator())
144    }
145
146    /// Returns a reference to the transmission key.
147    pub fn transmission_key(&self) -> &ka::Public {
148        &self.pk_d
149    }
150
151    /// Returns a reference to the clue key.
152    pub fn clue_key(&self) -> &fmd::ClueKey {
153        &self.ck_d
154    }
155
156    /// Returns a reference to the transmission key `s` value.
157    pub fn transmission_key_s(&self) -> &Fq {
158        &self.transmission_key_s
159    }
160
161    /// Converts this address to a vector of bytes.
162    pub fn to_vec(&self) -> Vec<u8> {
163        let mut bytes = std::io::Cursor::new(Vec::new());
164        bytes
165            .write_all(&self.diversifier().0)
166            .expect("can write diversifier into vec");
167        bytes
168            .write_all(&self.transmission_key().0)
169            .expect("can write transmission key into vec");
170        bytes
171            .write_all(&self.clue_key().0)
172            .expect("can write clue key into vec");
173
174        f4jumble(bytes.get_ref()).expect("can jumble")
175    }
176
177    /// Generates a randomized dummy address.
178    pub fn dummy<R: CryptoRng + Rng>(rng: &mut R) -> Self {
179        loop {
180            let mut diversifier_bytes = [0u8; 16];
181            rng.fill_bytes(&mut diversifier_bytes);
182
183            let mut pk_d_bytes = [0u8; 32];
184            rng.fill_bytes(&mut pk_d_bytes);
185
186            let mut clue_key_bytes = [0; 32];
187            rng.fill_bytes(&mut clue_key_bytes);
188
189            let diversifier = Diversifier(diversifier_bytes);
190            let addr = Address::from_components(
191                diversifier,
192                ka::Public(pk_d_bytes),
193                fmd::ClueKey(clue_key_bytes),
194            );
195
196            if let Some(addr) = addr {
197                return addr;
198            }
199        }
200    }
201
202    /// Short form suitable for displaying in a UI.
203    pub fn display_short_form(&self) -> String {
204        let full_address = format!("{self}");
205        // Fixed prefix is `penumbrav2t` plus the Bech32m separator `1`.
206        let fixed_prefix = format!("{}{}", bech32str::address::BECH32_PREFIX, '1');
207        let num_chars_to_display = fixed_prefix.len() + ADDRESS_NUM_CHARS_SHORT_FORM;
208
209        format!("{}…", &full_address[0..num_chars_to_display])
210    }
211
212    /// Compat (bech32 non-m) address format
213    pub fn compat_encoding(&self) -> String {
214        let proto_address = pb::Address::from(self);
215        bech32str::encode(
216            &proto_address.inner,
217            bech32str::compat_address::BECH32_PREFIX,
218            bech32str::Bech32,
219        )
220    }
221
222    /// Generate a Noble forwarding address.
223    pub fn noble_forwarding_address(&self, channel: &str) -> NobleForwardingAddress {
224        NobleForwardingAddress {
225            channel: channel.to_string(),
226            recipient: format!("{}", self),
227        }
228    }
229
230    /// Encodes the address as a transparent address if it has zero diversifier and clue key.
231    /// Returns `None` if the address doesn't meet the requirements for a transparent address.
232    pub fn encode_as_transparent_address(&self) -> Option<String> {
233        // Check if diversifier is zero
234        if self.diversifier().0 != [0u8; 16] {
235            return None;
236        }
237
238        // Check if clue key is identity
239        if self.clue_key().0 != [0u8; 32] {
240            return None;
241        }
242
243        // If both are zero, encode the transmission key
244        Some(bech32str::encode(
245            &self.transmission_key().0,
246            TRANSPARENT_ADDRESS_BECH32_PREFIX,
247            bech32str::Bech32,
248        ))
249    }
250}
251
252#[derive(Clone, Debug, Serialize, Deserialize)]
253pub struct NobleForwardingAddress {
254    pub channel: String,
255    pub recipient: String,
256}
257
258impl NobleForwardingAddress {
259    pub fn bytes(&self) -> Vec<u8> {
260        // Based on https://github.com/noble-assets/forwarding/blob/9d7657a89a5d4c68a082f28629e759b60b39b0fd/types/account.go#L17
261        let channel = self.channel.clone();
262        let recipient = self.recipient.clone();
263        let bz = format!("{channel}{recipient}").as_bytes().to_owned();
264        let th = Sha256::digest("forwarding".as_bytes());
265        let mut hasher = Sha256::new();
266        hasher.update(th);
267        hasher.update(bz);
268
269        // This constructs the account bytes for the Noble forwarding address
270        // Only use bytes 12 and on:
271        hasher.finalize()[12..].to_vec()
272    }
273}
274
275impl Display for NobleForwardingAddress {
276    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
277        let addr_bytes = &self.bytes();
278
279        write!(
280            f,
281            "{}",
282            bech32str::encode(&addr_bytes, "noble", bech32str::Bech32)
283        )
284    }
285}
286
287impl DomainType for Address {
288    type Proto = pb::Address;
289}
290
291impl From<Address> for pb::Address {
292    fn from(a: Address) -> Self {
293        Self::from(&a)
294    }
295}
296
297impl From<&Address> for pb::Address {
298    fn from(a: &Address) -> Self {
299        pb::Address {
300            inner: a.to_vec(),
301            // Always produce encodings without the alt format.
302            alt_bech32m: String::new(),
303        }
304    }
305}
306
307impl TryFrom<pb::Address> for Address {
308    type Error = anyhow::Error;
309
310    fn try_from(value: pb::Address) -> Result<Self, Self::Error> {
311        match (value.inner.is_empty(), value.alt_bech32m.is_empty()) {
312            (false, true) => value.inner.try_into(),
313            (true, false) => value.alt_bech32m.parse(),
314            (false, false) => Err(anyhow::anyhow!(
315                "Address proto has both inner and alt_bech32m fields set"
316            )),
317            (true, true) => Err(anyhow::anyhow!(
318                "Address proto has neither inner nor alt_bech32m fields set"
319            )),
320        }
321    }
322}
323
324impl std::fmt::Display for Address {
325    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
326        let proto_address = pb::Address::from(self);
327        f.write_str(&bech32str::encode(
328            &proto_address.inner,
329            bech32str::address::BECH32_PREFIX,
330            bech32str::Bech32m,
331        ))
332    }
333}
334
335impl std::fmt::Debug for Address {
336    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
337        <Self as std::fmt::Display>::fmt(self, f)
338    }
339}
340
341impl std::str::FromStr for Address {
342    type Err = anyhow::Error;
343
344    fn from_str(s: &str) -> Result<Self, Self::Err> {
345        if s.starts_with(TRANSPARENT_ADDRESS_BECH32_PREFIX) {
346            let dzero = Diversifier([0u8; 16]);
347
348            let pk_dzero_bytes: [u8; 32] =
349                bech32str::decode(s, TRANSPARENT_ADDRESS_BECH32_PREFIX, bech32str::Bech32)?
350                    .try_into()
351                    .map_err(|bytes: Vec<u8>| {
352                        anyhow::anyhow!("wrong length {}, expected 32", bytes.len())
353                    })?;
354            let pk_dzero = ka::Public(pk_dzero_bytes);
355
356            let ck_id = fmd::ClueKey([0u8; 32]);
357
358            let address = Self::from_components(dzero, pk_dzero, ck_id)
359                .ok_or_else(|| anyhow::anyhow!("could not reconstruct transparent address"))?;
360
361            // Verify this is a valid transparent address, bailing if not
362            if address.encode_as_transparent_address().is_none() {
363                return Err(anyhow::anyhow!("invalid transparent address components"));
364            }
365
366            Ok(address)
367        } else if s.starts_with(bech32str::compat_address::BECH32_PREFIX) {
368            pb::Address {
369                inner: bech32str::decode(
370                    s,
371                    bech32str::compat_address::BECH32_PREFIX,
372                    bech32str::Bech32,
373                )?,
374                alt_bech32m: String::new(),
375            }
376            .try_into()
377        } else {
378            pb::Address {
379                inner: bech32str::decode(s, bech32str::address::BECH32_PREFIX, bech32str::Bech32m)?,
380                alt_bech32m: String::new(),
381            }
382            .try_into()
383        }
384    }
385}
386
387impl TryFrom<Vec<u8>> for Address {
388    type Error = anyhow::Error;
389
390    fn try_from(jumbled_vec: Vec<u8>) -> Result<Self, Self::Error> {
391        (&jumbled_vec[..]).try_into()
392    }
393}
394
395impl TryFrom<&Vec<u8>> for Address {
396    type Error = anyhow::Error;
397
398    fn try_from(jumbled_vec: &Vec<u8>) -> Result<Self, Self::Error> {
399        (jumbled_vec[..]).try_into()
400    }
401}
402
403impl TryFrom<&[u8]> for Address {
404    type Error = anyhow::Error;
405
406    fn try_from(jumbled_bytes: &[u8]) -> Result<Self, Self::Error> {
407        if jumbled_bytes.len() != ADDRESS_LEN_BYTES {
408            anyhow::bail!("address malformed");
409        }
410
411        let unjumbled_bytes = f4jumble_inv(jumbled_bytes).context("invalid address")?;
412        let mut bytes = Cursor::new(unjumbled_bytes);
413
414        let mut diversifier_bytes = [0u8; 16];
415        bytes
416            .read_exact(&mut diversifier_bytes)
417            .context("could not read diversifier bytes")?;
418
419        let mut pk_d_bytes = [0u8; 32];
420        bytes
421            .read_exact(&mut pk_d_bytes)
422            .context("could not read transmission key bytes")?;
423
424        let mut clue_key_bytes = [0; 32];
425        bytes
426            .read_exact(&mut clue_key_bytes)
427            .context("could not read clue key bytes")?;
428
429        let diversifier = Diversifier(diversifier_bytes);
430
431        Address::from_components(
432            diversifier,
433            ka::Public(pk_d_bytes),
434            fmd::ClueKey(clue_key_bytes),
435        )
436        .ok_or_else(|| anyhow::anyhow!("could not create address from components"))
437    }
438}
439
440/// Assert the addresses are both [`Send`] and [`Sync`].
441//  NB: allow dead code, because this block only contains compile-time assertions.
442#[allow(dead_code)]
443mod assert_address_is_send_and_sync {
444    fn is_send<T: Send>() {}
445    fn is_sync<T: Sync>() {}
446    fn f() {
447        is_send::<super::Address>();
448        is_sync::<super::Address>();
449    }
450}
451
452#[cfg(test)]
453mod tests {
454    use std::str::FromStr;
455
456    use rand_core::OsRng;
457
458    use super::*;
459    use crate::keys::{Bip44Path, SeedPhrase, SpendKey};
460
461    #[test]
462    fn test_address_encoding() {
463        let rng = OsRng;
464        let seed_phrase = SeedPhrase::generate(rng);
465        let sk = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0));
466        let fvk = sk.full_viewing_key();
467        let ivk = fvk.incoming();
468        let (dest, _dtk_d) = ivk.payment_address(0u32.into());
469
470        let bech32m_addr = format!("{dest}");
471
472        let addr = Address::from_str(&bech32m_addr).expect("can decode valid address");
473
474        use penumbra_sdk_proto::Message;
475
476        let proto_addr = dest.encode_to_vec();
477        let proto_addr_bech32m = pb::Address {
478            inner: Vec::new(),
479            alt_bech32m: bech32m_addr,
480        }
481        .encode_to_vec();
482        let proto_addr_direct: pb::Address = dest.clone().into();
483        let addr_from_proto: Address = proto_addr_direct
484            .try_into()
485            .expect("can convert from proto back to address");
486
487        let addr2 = Address::decode(proto_addr.as_ref()).expect("can decode valid address");
488        let addr3 = Address::decode(proto_addr_bech32m.as_ref()).expect("can decode valid address");
489
490        assert_eq!(addr, dest);
491        assert_eq!(addr2, dest);
492        assert_eq!(addr3, dest);
493        assert_eq!(addr_from_proto, dest);
494    }
495
496    #[test]
497    fn test_compat_encoding() {
498        let rng = OsRng;
499        let seed_phrase = SeedPhrase::generate(rng);
500        let sk = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0));
501        let fvk = sk.full_viewing_key();
502        let ivk = fvk.incoming();
503        let (dest, _dtk_d) = ivk.payment_address(0u32.into());
504
505        let bech32_addr = dest.compat_encoding();
506
507        let addr = Address::from_str(&bech32_addr).expect("can decode valid address");
508
509        let proto_addr = dest.encode_to_vec();
510
511        let addr2 = Address::decode(proto_addr.as_ref()).expect("can decode valid address");
512
513        assert_eq!(addr, dest);
514        assert_eq!(addr2, dest);
515    }
516
517    #[test]
518    fn test_bytes_roundtrip() {
519        let rng = OsRng;
520        let seed_phrase = SeedPhrase::generate(rng);
521        let sk = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0));
522        let fvk = sk.full_viewing_key();
523        let ivk = fvk.incoming();
524        let (dest, _dtk_d) = ivk.payment_address(0u32.into());
525
526        let bytes = dest.to_vec();
527        let addr: Address = bytes.try_into().expect("can decode valid address");
528
529        assert_eq!(addr, dest);
530    }
531
532    #[test]
533    fn test_address_keys_are_diversified() {
534        let rng = OsRng;
535        let seed_phrase = SeedPhrase::generate(rng);
536        let sk = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0));
537        let fvk = sk.full_viewing_key();
538        let ivk = fvk.incoming();
539        let (dest1, dtk_d1) = ivk.payment_address(0u32.into());
540        let (dest2, dtk_d2) = ivk.payment_address(1u32.into());
541
542        assert!(dest1.transmission_key() != dest2.transmission_key());
543        assert!(dest1.clue_key() != dest2.clue_key());
544        assert!(dtk_d1.to_bytes() != dtk_d2.to_bytes());
545    }
546}