namada_shielded_token 0.251.4

Namada shielded token
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
//! Tools for migrating shielded wallets .
//!
//! Since users store a serialized version of  [`ShieldedWallet`] locally,
//! changes to this type breaks backwards compatability if migrations are not
//! present.

use namada_core::borsh::{BorshDeserialize, BorshSerialize};

use crate::ShieldedWallet;
use crate::masp::ShieldedUtils;

/// An enum that adds version info to the [`ShieldedWallet`]
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub enum VersionedWallet<U: ShieldedUtils> {
    /// Version 0
    V0(v0::ShieldedWallet<U>),
    /// Version 1
    V1(v1::ShieldedWallet<U>),
    /// Version 2
    V2(ShieldedWallet<U>),
}

impl<U: ShieldedUtils> VersionedWallet<U> {
    /// Try to migrate this wallet to the latest version and return
    /// it if successful.
    pub fn migrate(self) -> eyre::Result<ShieldedWallet<U>> {
        match self {
            VersionedWallet::V0(w) => Ok(w.into()),
            VersionedWallet::V1(w) => Ok(w.into()),
            VersionedWallet::V2(w) => Ok(w),
        }
    }
}

/// A borrowed version of [`VersionedWallet`]
#[derive(BorshSerialize, Debug)]
pub enum VersionedWalletRef<'w, U: ShieldedUtils> {
    /// Version 0
    V0(&'w v0::ShieldedWallet<U>),
    /// Version 1
    V1(&'w v1::ShieldedWallet<U>),
    /// Version 2
    V2(&'w ShieldedWallet<U>),
}

mod migrations {
    use masp_primitives::merkle_tree::CommitmentTree;
    use masp_primitives::sapling::{Diversifier, Node, Note};
    use namada_core::collections::HashMap;

    use crate::masp::bridge_tree::BridgeTree;
    use crate::masp::shielded_wallet::CompactNote;
    use crate::masp::{NotePosition, WitnessMap};

    #[allow(missing_docs, dead_code)]
    pub fn migrate_note_map(
        note_map: HashMap<usize, Note>,
        mut div_map: HashMap<usize, Diversifier>,
    ) -> HashMap<NotePosition, CompactNote> {
        let mut migrated = HashMap::new();

        for (pos, note) in note_map {
            let diversifier = div_map
                .swap_remove(&pos)
                .expect("Missing diversifier in shielded wallet");

            let Note {
                asset_type,
                value,
                pk_d,
                rseed,
                ..
            } = note;

            migrated.insert(
                NotePosition(pos.try_into().unwrap()),
                CompactNote {
                    asset_type,
                    value,
                    diversifier,
                    pk_d,
                    rseed,
                },
            );
        }

        migrated
    }

    #[allow(missing_docs, dead_code)]
    pub fn migrate_bridge_tree(
        tree: &CommitmentTree<Node>,
        witness_map: &WitnessMap,
    ) -> BridgeTree {
        BridgeTree::from_tree_and_witness_map(tree.clone(), witness_map.clone())
            .unwrap()
    }

    #[cfg(test)]
    #[test]
    fn test_bridge_tree_migrations() {
        use masp_primitives::merkle_tree::IncrementalWitness;

        use crate::masp::NotePosition;

        let mut tree: CommitmentTree<Node> = CommitmentTree::empty();
        let mut witness_map = WitnessMap::new();

        // build commitment tree and witness map incrementally
        for i in 0u64..10 {
            let node = Node::from_scalar(i.into());

            tree.append(node).unwrap();

            for wit in witness_map.values_mut() {
                wit.append(node).unwrap();
            }

            if i % 2 == 0 {
                witness_map
                    .insert(i.into(), IncrementalWitness::from_tree(&tree));
            }
        }

        // convert to bridge tree
        let mut bridge_tree = migrate_bridge_tree(&tree, &witness_map);

        for i in (0u64..10).filter(|&i| i % 2 == 0) {
            assert_eq!(
                witness_map[&NotePosition::from(i)].path(),
                bridge_tree.witness(i),
                "Position {i} not equal,\n{witness_map:#?}\n{bridge_tree:#?}"
            );
        }

        // add new incremental witnesses
        for i in 10u64..20 {
            let node = Node::from_scalar(i.into());

            tree.append(node).unwrap();
            bridge_tree.as_mut().append(node).unwrap();

            for wit in witness_map.values_mut() {
                wit.append(node).unwrap();
            }

            if i % 2 == 0 {
                witness_map
                    .insert(i.into(), IncrementalWitness::from_tree(&tree));
                bridge_tree.as_mut().mark().unwrap();
            }
        }

        // check if roots and merkle proofs match
        assert_eq!(tree.root(), bridge_tree.as_ref().root());

        for i in (0u64..20).filter(|&i| i % 2 == 0) {
            assert_eq!(
                witness_map[&NotePosition::from(i)].path(),
                bridge_tree.witness(i),
                "Position {i} not equal,\n{witness_map:#?}\n{bridge_tree:#?}"
            );
        }
    }
}

pub mod v0 {
    //! Version 0 of the shielded wallet, which is used for migration purposes.

    use std::collections::{BTreeMap, BTreeSet};

    use masp_primitives::asset_type::AssetType;
    use masp_primitives::memo::MemoBytes;
    use masp_primitives::merkle_tree::CommitmentTree;
    use masp_primitives::sapling::{
        Diversifier, Node, Note, Nullifier, ViewingKey,
    };
    use namada_core::borsh::{BorshDeserialize, BorshSerialize};
    use namada_core::collections::{HashMap, HashSet};
    use namada_core::masp::AssetData;

    use crate::masp::utils::MaspIndexedTx;
    use crate::masp::{
        ContextSyncStatus, NoteIndex, ShieldedUtils, WitnessMap,
    };

    #[derive(BorshSerialize, BorshDeserialize, Debug)]
    #[allow(missing_docs)]
    pub struct ShieldedWallet<U: ShieldedUtils> {
        /// Location where this shielded context is saved
        #[borsh(skip)]
        pub utils: U,
        /// The commitment tree produced by scanning all transactions up to
        /// tx_pos
        pub tree: CommitmentTree<Node>,
        /// Maps viewing keys to the block height to which they are synced.
        /// In particular, the height given by the value *has been scanned*.
        pub vk_heights: BTreeMap<ViewingKey, Option<MaspIndexedTx>>,
        /// Maps viewing keys to applicable note positions
        pub pos_map: HashMap<ViewingKey, BTreeSet<usize>>,
        /// Maps a nullifier to the note position to which it applies
        pub nf_map: HashMap<Nullifier, usize>,
        /// Maps note positions to their corresponding notes
        pub note_map: HashMap<usize, Note>,
        /// Maps note positions to their corresponding memos
        pub memo_map: HashMap<usize, MemoBytes>,
        /// Maps note positions to the diversifier of their payment address
        pub div_map: HashMap<usize, Diversifier>,
        /// Maps note positions to their witness (used to make merkle paths)
        pub witness_map: WitnessMap,
        /// The set of note positions that have been spent
        pub spents: HashSet<usize>,
        /// Maps asset types to their decodings
        pub asset_types: HashMap<AssetType, AssetData>,
        /// Maps note positions to their corresponding viewing keys
        pub vk_map: HashMap<usize, ViewingKey>,
        /// Maps a shielded tx to the index of its first output note.
        pub note_index: NoteIndex,
        /// The sync state of the context
        pub sync_status: ContextSyncStatus,
    }

    impl<U: ShieldedUtils + Default> Default for ShieldedWallet<U> {
        fn default() -> ShieldedWallet<U> {
            ShieldedWallet::<U> {
                utils: U::default(),
                vk_heights: BTreeMap::new(),
                note_index: BTreeMap::default(),
                tree: CommitmentTree::empty(),
                pos_map: HashMap::default(),
                nf_map: HashMap::default(),
                note_map: HashMap::default(),
                memo_map: HashMap::default(),
                div_map: HashMap::default(),
                witness_map: HashMap::default(),
                spents: HashSet::default(),
                asset_types: HashMap::default(),
                vk_map: HashMap::default(),
                sync_status: ContextSyncStatus::Confirmed,
            }
        }
    }

    impl<U: ShieldedUtils> From<ShieldedWallet<U>> for super::ShieldedWallet<U> {
        fn from(wallet: ShieldedWallet<U>) -> Self {
            #[cfg(not(feature = "historic"))]
            {
                use super::migrations;
                use crate::masp::NotePosition;

                Self {
                    utils: wallet.utils,
                    tree: migrations::migrate_bridge_tree(
                        &wallet.tree,
                        &wallet.witness_map,
                    ),
                    synced_height: wallet
                        .vk_heights
                        .into_values()
                        .filter_map(|itx| Some(itx?.indexed_tx.block_height))
                        .max()
                        .unwrap_or_default(),
                    pos_map: wallet
                        .pos_map
                        .into_iter()
                        .map(|(vk, positions)| {
                            (
                                vk,
                                positions
                                    .into_iter()
                                    .map(|pos| {
                                        NotePosition(pos.try_into().unwrap())
                                    })
                                    .collect(),
                            )
                        })
                        .collect(),
                    nf_map: wallet
                        .nf_map
                        .into_iter()
                        .map(|(nf, pos)| {
                            (nf, NotePosition(pos.try_into().unwrap()))
                        })
                        .collect(),
                    note_map: migrations::migrate_note_map(
                        wallet.note_map,
                        wallet.div_map,
                    ),
                    memo_map: wallet
                        .memo_map
                        .into_iter()
                        .map(|(pos, memo)| {
                            (NotePosition(pos.try_into().unwrap()), memo)
                        })
                        .collect(),
                    spents: wallet
                        .spents
                        .into_iter()
                        .map(|pos| NotePosition(pos.try_into().unwrap()))
                        .collect(),
                    asset_types: wallet.asset_types,
                    conversions: Default::default(),
                    note_index: wallet.note_index,
                    sync_status: wallet.sync_status,
                }
            }
            #[cfg(feature = "historic")]
            {
                drop(wallet);

                // NB: Need to return an empty wallet because
                // we can not rebuild the shielded history.
                Default::default()
            }
        }
    }
}

pub mod v1 {
    //! Version 1 of the shielded wallet, which is used for migration purposes.

    #![allow(missing_docs)]

    use std::collections::{BTreeMap, BTreeSet};

    use masp_primitives::asset_type::AssetType;
    use masp_primitives::memo::MemoBytes;
    use masp_primitives::merkle_tree::CommitmentTree;
    use masp_primitives::sapling::{
        Diversifier, Node, Note, Nullifier, ViewingKey,
    };
    use namada_core::borsh::{BorshDeserialize, BorshSerialize};
    use namada_core::collections::{HashMap, HashSet};
    use namada_core::masp::AssetData;

    use crate::masp::shielded_wallet::EpochedConversions;
    use crate::masp::utils::MaspIndexedTx;
    use crate::masp::{
        ContextSyncStatus, NoteIndex, ShieldedUtils, WitnessMap,
    };

    #[derive(BorshSerialize, BorshDeserialize, Debug)]
    pub struct ShieldedWallet<U: ShieldedUtils> {
        #[borsh(skip)]
        pub utils: U,
        pub tree: CommitmentTree<Node>,
        pub vk_heights: BTreeMap<ViewingKey, Option<MaspIndexedTx>>,
        pub pos_map: HashMap<ViewingKey, BTreeSet<usize>>,
        pub nf_map: HashMap<Nullifier, usize>,
        pub note_map: HashMap<usize, Note>,
        pub memo_map: HashMap<usize, MemoBytes>,
        pub div_map: HashMap<usize, Diversifier>,
        pub witness_map: WitnessMap,
        pub spents: HashSet<usize>,
        pub asset_types: HashMap<AssetType, AssetData>,
        pub conversions: EpochedConversions,
        pub vk_map: HashMap<usize, ViewingKey>,
        pub note_index: NoteIndex,
        pub sync_status: ContextSyncStatus,
    }

    impl<U: ShieldedUtils + Default> Default for ShieldedWallet<U> {
        fn default() -> ShieldedWallet<U> {
            ShieldedWallet::<U> {
                utils: U::default(),
                vk_heights: BTreeMap::new(),
                note_index: BTreeMap::default(),
                tree: CommitmentTree::empty(),
                pos_map: HashMap::default(),
                nf_map: HashMap::default(),
                note_map: HashMap::default(),
                memo_map: HashMap::default(),
                div_map: HashMap::default(),
                witness_map: HashMap::default(),
                spents: HashSet::default(),
                conversions: Default::default(),
                asset_types: HashMap::default(),
                vk_map: HashMap::default(),
                sync_status: ContextSyncStatus::Confirmed,
            }
        }
    }

    impl<U: ShieldedUtils> From<ShieldedWallet<U>> for super::ShieldedWallet<U> {
        fn from(wallet: ShieldedWallet<U>) -> Self {
            #[cfg(not(feature = "historic"))]
            {
                use super::migrations;
                use crate::masp::NotePosition;

                Self {
                    utils: wallet.utils,
                    tree: migrations::migrate_bridge_tree(
                        &wallet.tree,
                        &wallet.witness_map,
                    ),
                    synced_height: wallet
                        .vk_heights
                        .into_values()
                        .filter_map(|itx| Some(itx?.indexed_tx.block_height))
                        .max()
                        .unwrap_or_default(),
                    pos_map: wallet
                        .pos_map
                        .into_iter()
                        .map(|(vk, positions)| {
                            (
                                vk,
                                positions
                                    .into_iter()
                                    .map(|pos| {
                                        NotePosition(pos.try_into().unwrap())
                                    })
                                    .collect(),
                            )
                        })
                        .collect(),
                    nf_map: wallet
                        .nf_map
                        .into_iter()
                        .map(|(nf, pos)| {
                            (nf, NotePosition(pos.try_into().unwrap()))
                        })
                        .collect(),
                    note_map: migrations::migrate_note_map(
                        wallet.note_map,
                        wallet.div_map,
                    ),
                    memo_map: wallet
                        .memo_map
                        .into_iter()
                        .map(|(pos, memo)| {
                            (NotePosition(pos.try_into().unwrap()), memo)
                        })
                        .collect(),
                    spents: wallet
                        .spents
                        .into_iter()
                        .map(|pos| NotePosition(pos.try_into().unwrap()))
                        .collect(),
                    asset_types: wallet.asset_types,
                    conversions: wallet.conversions,
                    note_index: wallet.note_index,
                    sync_status: wallet.sync_status,
                }
            }
            #[cfg(feature = "historic")]
            {
                drop(wallet);

                // NB: Need to return an empty wallet because
                // we can not rebuild the shielded history.
                Default::default()
            }
        }
    }
}