penumbra_sdk_keys/address/
view.rs1use std::cmp::Ordering;
2
3use serde::{Deserialize, Serialize};
4
5use penumbra_sdk_proto::{penumbra::core::keys::v1 as pb, DomainType};
6
7use crate::keys::{AddressIndex, WalletId};
8
9use super::Address;
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17#[serde(try_from = "pb::AddressView", into = "pb::AddressView")]
18pub enum AddressView {
19 Opaque {
20 address: Address,
21 },
22 Decoded {
23 address: Address,
24 index: AddressIndex,
25 wallet_id: WalletId,
26 },
27}
28
29impl AddressView {
30 pub fn address(&self) -> Address {
31 match self {
32 AddressView::Opaque { address } => address.clone(),
33 AddressView::Decoded { address, .. } => address.clone(),
34 }
35 }
36}
37
38impl DomainType for AddressView {
39 type Proto = pb::AddressView;
40}
41
42impl From<AddressView> for pb::AddressView {
43 fn from(view: AddressView) -> Self {
44 match view {
45 AddressView::Opaque { address } => Self {
46 address_view: Some(pb::address_view::AddressView::Opaque(
47 pb::address_view::Opaque {
48 address: Some(address.into()),
49 },
50 )),
51 },
52 AddressView::Decoded {
53 address,
54 index,
55 wallet_id,
56 } => Self {
57 address_view: Some(pb::address_view::AddressView::Decoded(
58 pb::address_view::Decoded {
59 address: Some(address.into()),
60 index: Some(index.into()),
61 wallet_id: Some(wallet_id.into()),
62 },
63 )),
64 },
65 }
66 }
67}
68
69impl TryFrom<pb::AddressView> for AddressView {
70 type Error = anyhow::Error;
71 fn try_from(value: pb::AddressView) -> Result<Self, Self::Error> {
72 match value.address_view {
73 Some(pb::address_view::AddressView::Opaque(opaque)) => {
74 let address = opaque
75 .address
76 .ok_or_else(|| anyhow::anyhow!("AddressView::Opaque missing address field"))?
77 .try_into()?;
78 Ok(AddressView::Opaque { address })
79 }
80 Some(pb::address_view::AddressView::Decoded(visible)) => {
81 let address = visible
82 .address
83 .ok_or_else(|| anyhow::anyhow!("AddressView::Visible missing address field"))?
84 .try_into()?;
85 let index = visible
86 .index
87 .ok_or_else(|| anyhow::anyhow!("AddressView::Visible missing index field"))?
88 .try_into()?;
89 let wallet_id = visible
90 .wallet_id
91 .ok_or_else(|| anyhow::anyhow!("AddressView::Visible missing wallet_id field"))?
92 .try_into()?;
93 Ok(AddressView::Decoded {
94 address,
95 index,
96 wallet_id,
97 })
98 }
99 None => Err(anyhow::anyhow!("AddressView missing address_view field")),
100 }
101 }
102}
103
104impl PartialOrd for AddressView {
106 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
107 use AddressView::*;
108 match (self, other) {
110 (Opaque { .. }, Decoded { .. }) => Some(Ordering::Less),
111 (Decoded { .. }, Opaque { .. }) => Some(Ordering::Greater),
112 (Opaque { address: a1 }, Opaque { address: a2 }) => a1.partial_cmp(a2),
113 (
114 Decoded {
115 address: a1,
116 index: i1,
117 wallet_id: w1,
118 },
119 Decoded {
120 address: a2,
121 index: i2,
122 wallet_id: w2,
123 },
124 ) => (a1, i1, w1).partial_cmp(&(a2, i2, w2)),
125 }
126 }
127}
128
129impl Ord for AddressView {
130 fn cmp(&self, other: &Self) -> Ordering {
131 match (self, other) {
133 (AddressView::Opaque { address: a1 }, AddressView::Opaque { address: a2 }) => {
134 a1.cmp(a2)
135 }
136 (
137 AddressView::Decoded {
138 address: a1,
139 index: i1,
140 wallet_id: w1,
141 },
142 AddressView::Decoded {
143 address: a2,
144 index: i2,
145 wallet_id: w2,
146 },
147 ) => match a1.cmp(a2) {
148 Ordering::Equal => match i1.cmp(i2) {
149 Ordering::Equal => w1.cmp(w2),
150 ord => ord,
151 },
152 ord => ord,
153 },
154 (
155 AddressView::Opaque { address: _ },
156 AddressView::Decoded {
157 address: _,
158 index: _,
159 wallet_id: _,
160 },
161 ) => Ordering::Less,
162 (
163 AddressView::Decoded {
164 address: _,
165 index: _,
166 wallet_id: _,
167 },
168 AddressView::Opaque { address: _ },
169 ) => Ordering::Greater,
170 }
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use rand_core::OsRng;
177
178 use crate::keys::{Bip44Path, SeedPhrase, SpendKey};
179
180 use super::*;
181
182 #[test]
183 fn address_view_basic() {
184 let sk1 = SpendKey::from_seed_phrase_bip44(SeedPhrase::generate(OsRng), &Bip44Path::new(0));
185 let sk2 = SpendKey::from_seed_phrase_bip44(SeedPhrase::generate(OsRng), &Bip44Path::new(0));
186
187 let fvk1 = sk1.full_viewing_key();
188 let fvk2 = sk2.full_viewing_key();
189
190 let addr1_0 = fvk1.payment_address(0.into()).0;
191 let addr1_1 = fvk1.payment_address(1.into()).0;
192 let addr2_0 = fvk2.payment_address(0.into()).0;
193 let addr2_1 = fvk2.payment_address(1.into()).0;
194
195 assert_eq!(
196 fvk1.view_address(addr1_0.clone()),
197 AddressView::Decoded {
198 address: addr1_0.clone(),
199 index: 0.into(),
200 wallet_id: fvk1.wallet_id(),
201 }
202 );
203 assert_eq!(
204 fvk2.view_address(addr1_0.clone()),
205 AddressView::Opaque {
206 address: addr1_0.clone()
207 }
208 );
209 assert_eq!(
210 fvk1.view_address(addr1_1.clone()),
211 AddressView::Decoded {
212 address: addr1_1.clone(),
213 index: 1.into(),
214 wallet_id: fvk1.wallet_id(),
215 }
216 );
217 assert_eq!(
218 fvk2.view_address(addr1_1.clone()),
219 AddressView::Opaque {
220 address: addr1_1.clone()
221 }
222 );
223 assert_eq!(
224 fvk1.view_address(addr2_0.clone()),
225 AddressView::Opaque {
226 address: addr2_0.clone()
227 }
228 );
229 assert_eq!(
230 fvk2.view_address(addr2_0.clone()),
231 AddressView::Decoded {
232 address: addr2_0.clone(),
233 index: 0.into(),
234 wallet_id: fvk2.wallet_id(),
235 }
236 );
237 assert_eq!(
238 fvk1.view_address(addr2_1.clone()),
239 AddressView::Opaque {
240 address: addr2_1.clone()
241 }
242 );
243 assert_eq!(
244 fvk2.view_address(addr2_1.clone()),
245 AddressView::Decoded {
246 address: addr2_1.clone(),
247 index: 1.into(),
248 wallet_id: fvk2.wallet_id(),
249 }
250 );
251 }
252}