owned this note
owned this note
Published
Linked with GitHub
# ZeWIF/zmigrate Working Group
March 14, 2025
- This document is intended to be modified by all meeting participants in real time: if you see something that should be better, then change it!
- The meeting will be recorded for those who cannot attend.
## Present
- Wolf McNally, Blockchain Commons
- Zancas "Za" Wil, Coordinator, Zingo Labs
- Dorianvp, Zingo Labs
- Kris Nuttycombe, core engineer, ECC (Zashi, zcashd)
- Daira-Emma Hopwood, R&D Engineering Manager, ECC (Zashi, zcashd) [joined late]
- Pacu, Developer Relations Engineer (ZCG Resource)
## Wallets
- zcashd: Kris Nuttycombe
- zcash_client_{backend/sqlite} (Zashi, Edge, Nighthawk, Unstoppable): Kris Nuttycombe
- reference database structure (shielded-primary mobile wallet): https://github.com/zcash/librustzcash/blob/main/zcash_client_sqlite/src/wallet/db.rs
- Diagram: https://github.com/zingolabs/zcash-wallet-formats/blob/master/zashi/assets/images/zashi-erd-dbvis.png
- zingo: Zancas & Dorian
- zecwallet/zecwallet-lite: Zancas & Dorian
## Goals of the Meeting
- Get input from a variety of Zcash wallet format experts
- Refine the proposed Rust Abstractions below, which will help define an MVP for target deliverable
- Identify any outstanding issues for research
## Outline of Abstractions
This is work in progress a set of in-memory abstractions in Rust covering the space of typical information stored in Zcash wallet files. The goal is interchange, not creating an operable wallet.
Note: In the following code `Blob<N>` is a fixed-sized binary object and `Data` is a variable-sized binary object.
Structures that can hold wallet-specific metadata have `attachments: HashMap<Digest, Envelope>`. This is a set of Gordian Envelope attachments, keyed by their digest. This allows for arbitrary metadata to be attached to any object in the wallet interchange format.
- [Introduction to Gordian Envelope](https://www.blockchaincommons.com/introduction/Envelope-Intro/)
- [Gordian Envelope: Attachments](https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2023-006-envelope-attachment.md)
Structures that have unique identifiers within the wallet interchange format have `id: ARID`. This provides a unique identifier for the object that becomes the subject of a Gordian Envelope, while the other fields of the struct become predicate-object assertions. Such identifiers are desirable when no existing fields can be relied on to uniquely identify the object (a counterexample is the `txid` field of a transaction).
- [ARID: Apparently Random Identifier](https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2022-002-arid.md)
```rust
#![allow(dead_code)]
use std::collections::{HashMap, HashSet};
use bc_components::{Digest, ARID};
use bc_envelope::prelude::*;
use crate::{Amount, Blob, BlockHeight, Data, TxId};
/// A trait for objects that can hold one or more Gordian Envelope attachments.
///
/// QUESTION: Should all possible wallet objects implement this trait, or only
/// some? What criteria should be used to determine this?
pub trait Attachable {
fn attachments(&self) -> &HashMap<Digest, Envelope>;
fn attachments_mut(&mut self) -> &mut HashMap<Digest, Envelope>;
// Duplicate attachments are not allowed.
fn add_attachment(&mut self, attachment: Envelope) {
self.attachments_mut().insert(attachment.digest().into_owned(), attachment);
}
}
/// A trait for objects that have a unique identifier within the wallet
/// interchange format.
///
/// QUESTION: Should all possible wallet objects implement this trait, or only
/// some? What criteria should be used to determine this?
pub trait Identifiable {
fn id(&self) -> &ARID;
}
/// Represents a wallet database, the top level of the interchange format
/// hierarchy, which can contain multiple wallets and a global transaction
/// history.
#[derive(Debug, Clone)]
pub struct WalletDB {
wallets: HashMap<ARID, Wallet>,
transactions: HashMap<TxId, Transaction>,
attachments: HashMap<Digest, Envelope>,
}
/// Further impls of this omitted for brevity.
impl Attachable for WalletDB {
fn attachments(&self) -> &HashMap<Digest, Envelope> {
&self.attachments
}
fn attachments_mut(&mut self) -> &mut HashMap<Digest, Envelope> {
&mut self.attachments
}
}
/// Represents an entire wallet, including multiple accounts, a wallet-specific
/// subset of the global transaction history, and optionally a form of seed
/// material for generating cryptographic keys.
#[derive(Debug, Clone)]
pub struct Wallet {
id: ARID,
seed_material: Option<SeedMaterial>,
accounts: HashMap<ARID, Account>,
attachments: HashMap<Digest, Envelope>,
}
/// Further impls of this omitted for brevity.
impl Identifiable for Wallet {
fn id(&self) -> &ARID {
&self.id
}
}
/// Seed material used to generate the keys in the wallet.
/// Proposal as minimal set of sources of truth
#[derive(Debug, Clone)]
pub enum SeedMaterial {
Bip39Mnemonic(String),
PreBIP39Seed(Blob<32>),
}
/// Logical grouping within a wallet. Each account can have its own set of
/// addresses, transactions, and other metadata.
#[derive(Debug, Clone)]
pub struct Account {
id: ARID,
name: String, // May not be unique.
zip32_account_id: Option<u32>,
addresses: HashMap<String, Address>,
relevant_transactions: HashSet<TxId>, // Subset of the global transaction history.
// The following are intended for storage of information that may not be
// recoverable from the chain.
sapling_sent_outputs: Vec<SaplingSentOutput>,
orchard_sent_outputs: Vec<OrchardSentOutput>,
attachments: HashMap<Digest, Envelope>,
}
use crate::{Amount, Blob};
/// Represents a sent output in a Sapling shielded transaction within a Zcash wallet.
///
/// This structure stores the plaintext details of a Sapling note that was sent by the wallet,
/// which are not recoverable from the blockchain. It is used for selective disclosure,
/// allowing the sender to prove a payment was made to a specific shielded address without
/// revealing the full transaction details. This is particularly useful for auditing or
/// compliance with regulations. The Sapling protocol, activated in June 2018, enhances
/// privacy using zk-SNARKs over the earlier Sprout protocol.
#[derive(Debug, Clone)]
pub struct SaplingSentOutput {
/// The diversifier used in deriving the recipient's shielded address.
///
/// This 11-byte value is part of the Sapling address construction, allowing multiple
/// unique addresses to be generated from a single key pair. It is critical for
/// identifying the recipient and reconstructing the note for proof generation.
pub diversifier: Blob<11>,
/// The recipient's public key, serialized in compressed form.
///
/// This 32-byte value represents a point on the Jubjub curve, used in Sapling's
/// cryptographic operations. It is part of the note plaintext and is needed to
/// verify the recipient of the sent funds during selective disclosure.
pub receipient_public_key: Blob<32>,
/// The value of ZEC sent in this output, in zatoshis (1 ZEC = 10^8 zatoshis).
///
/// This 64-bit unsigned integer specifies the amount transferred. It is constrained
/// by the protocol to a maximum value (2^63 - 1 zatoshis), ensuring it fits within
/// the note's value field for Sapling transactions.
pub value: Amount,
/// The random commitment material used in the note commitment.
///
/// This 32-byte value (256-bit scalar) is a randomly generated element used to
/// construct the note commitment on the blockchain, ensuring privacy by masking
/// the note's contents. It is stored here to allow reconstruction of the commitment
/// for proving purposes.
pub rcm: Blob<32>,
}
/// Represents a sent output in an Orchard shielded transaction within a Zcash wallet.
///
/// This structure stores the plaintext details of an Orchard note that was sent by the
/// wallet, which are not recoverable from the blockchain. It supports selective disclosure
/// for proving payments to shielded addresses, enhancing privacy and compliance features.
/// Introduced in Network Upgrade 5 (NU5), the Orchard protocol builds on Sapling with
/// additional privacy enhancements, using the Pallas curve and new cryptographic primitives.
#[derive(Debug, Clone)]
pub struct OrchardSentOutput {
/// The diversifier used in deriving the recipient's shielded address.
///
/// This 11-byte value serves the same purpose as in Sapling, enabling address
/// diversity for privacy. It is part of the note plaintext and essential for
/// identifying the recipient during selective disclosure.
pub diversifier: Blob<11>,
/// The recipient's public key, serialized in compressed form.
///
/// This 32-byte value represents a point on the Pallas curve, distinct from Sapling's
/// Jubjub curve. It is included in the note plaintext and necessary for verifying
/// the recipient in proofs or audits.
pub receipient_public_key: Blob<32>,
/// The value of ZEC sent in this output, in zatoshis (1 ZEC = 10^8 zatoshis).
///
/// This 64-bit unsigned integer denotes the amount sent, with the same maximum value
/// constraint as Sapling (2^63 - 1 zatoshis). It is a core component of the note
/// for tracking and proving the transaction amount.
pub value: Amount,
/// A randomness element used in Orchard's note encryption and commitment.
///
/// This 32-byte value (an element of the Pallas curve's field F_q) is unique to Orchard,
/// enhancing privacy by contributing to the note's uniqueness. It is stored for
/// reconstructing the note during selective disclosure.
pub rho: Blob<32>,
/// Another randomness element used in Orchard's note construction.
///
/// This 32-byte value (also an element of F_q) further strengthens privacy in Orchard
/// transactions. It is part of the note plaintext and required for generating proofs
/// that validate the sent output.
pub psi: Blob<32>,
/// The random commitment material used in the note commitment.
///
/// This 32-byte value (256-bit scalar) serves a similar role to Sapling's rcm, masking
/// the note's contents on the blockchain. It is stored to enable the wallet to
/// regenerate the commitment for proving payment details.
pub rcm: Blob<32>,
}
/// A wallet address can be either an exposed transparent address or one of several shielded types.
#[derive(Debug, Clone)]
pub enum Address {
/// An exposed transparent (T-address) similar to Bitcoin's.
Transparent(TransparentAddress),
/// A shielded address (Z-address). This can include Sapling, Sprout, or Orchard formats.
Shielded(ShieldedAddress),
}
#[derive(Debug, Clone)]
pub struct TransparentAddress {
address: String, // Unique
spend_authority: Option<TransparentSpendAuthority>,
derivation_info: Option<DerivationInfo>,
attachments: HashMap<Digest, Envelope>,
}
/// Details specific to shielded addresses.
#[derive(Debug, Clone)]
pub struct ShieldedAddress {
/// The actual address string (could encode Sapling, Orchard, etc.).
address: String, // Unique
/// Optional diversifier or other Zcash-specific metadata.
diversifier: Option<Data>,
attachments: HashMap<Digest, Envelope>,
}
/// The authority to spend from a transparent address.
#[derive(Debug, Clone)]
pub enum TransparentSpendAuthority {
SpendingKey(SpendingKey),
Derived,
}
#[derive(Debug, Clone)]
pub struct SpendingKey(Blob<32>);
#[derive(Debug, Clone)]
pub struct DerivationInfo {
change: NonHardenedChildIndex,
address_index: NonHardenedChildIndex,
}
#[derive(Debug, Clone)]
pub struct NonHardenedChildIndex(u32);
/// A transaction that can combine both transparent and shielded components.
#[derive(Debug, Clone)]
pub struct Transaction {
/// The transaction id.
txid: TxId,
/// The raw transaction data, if known.
raw: Option<Data>,
/// The height at which the transaction was mined, if known.
/// It is possible that if a rollback occurred just after the zeWIF
/// export, the transaction could have been unmined, and possibly
/// remined at a different height.
mined_height: Option<BlockHeight>,
// Design issue: do we want to parse out all of this? All wallets will
// necessarily have code to parse a transaction. The only information
// that is not redundant with the raw transaction encoding is the
// *decrypted* note plaintexts (and it might be sufficient to just
// indicate which output indices are expected to be decryptable with
// which keys). I don't see the point of duplicating the raw data in a
// different format (that still needs to be parsed!)
// -- Daira-Emma
/// Optional data for transparent inputs
inputs: Option<Vec<TxIn>>,
/// Optional data for transparent outputs
outputs: Option<Vec<TxOut>>,
/// Optional data for Sapling spends
sapling_spends: Option<Vec<SaplingSpendDescription>>,
/// Optional data for Sapling outputs
sapling_outputs: Option<Vec<SaplingOutputDescription>>,
/// Optional data for Orchard actions
orchard_actions: Option<Vec<OrchardActionDescription>>,
/// Optional data for Sprout JoinSplit descriptions
sprout_joinsplits: Option<Vec<JoinSplitDescription>>,
// Additional metadata such as confirmations or timestamp may be added here.
attachments: HashMap<Digest, Envelope>,
}
/// A reference to a previous transaction output.
#[derive(Debug, Clone)]
pub struct TxOutPoint {
txid: TxId,
index: u32,
}
/// A transparent transaction input.
#[derive(Debug, Clone)]
pub struct TxIn {
previous_output: TxOutPoint,
/// Script signature for unlocking the previous output.
script_sig: Data,
sequence: u32,
}
/// A transparent transaction output.
#[derive(Debug, Clone)]
pub struct TxOut {
value: Amount,
script_pubkey: Data,
}
/// Data specific to Sapling spends.
#[derive(Debug, Clone)]
pub struct SaplingSpendDescription {
spend_index: u32,
/// The value of the input note, if known.
value: Option<Amount>,
/// The height that the anchor corresponds to, if known.
anchor_height: Option<BlockHeight>,
/// A nullifier to ensure the note is spent only once.
nullifier: Blob<32>,
/// A zero-knowledge proof that the spend is valid.
zkproof: Data,
// Additional fields (e.g., spending key components) may be required.
attachments: HashMap<Digest, Envelope>,
}
/// Data specific to Sapling outputs.
/// *this is currently not sufficient to make all outputs recoverable*
/// Additional things that are needed:
/// * recipient address (unified address that was specified as the recipient by the user)
/// * value of the output
/// * other information in https://github.com/zcash/librustzcash/blob/54fd075449218e27e4cbf5cb7108cd89f458acc0/zcash_client_sqlite/src/wallet/db.rs#L455 may not be recoverable from the chain
#[derive(Debug, Clone)]
pub struct SaplingOutputDescription {
output_index: u32,
/// The note commitment.
commitment: Blob<32>,
/// Ephemeral key for the encrypted note.
ephemeral_key: Blob<32>,
/// Encrypted ciphertext containing the note details.
enc_ciphertext: Data,
/// An optional memo field.
memo: Option<Data>,
/// This and the witness are recorded at export as of
/// an anchor depth 20 blocks back from the chain tip, or the oldest possible witness at a lesser depth.
note_commitment_tree_position: Position,
/// Witness
witness: Option<(Anchor, IncrementalWitness)>,
attachments: HashMap<Digest, Envelope>,
}
/// Data specific to Orchard actions.
/// TODO CHECK
#[derive(Debug, Clone)]
pub struct OrchardActionDescription {
action_index: u32,
/// The anchor of the current commitment tree.
anchor: Blob<32>,
/// A nullifier to ensure the note is spent only once.
nullifier: Blob<32>,
/// A zero-knowledge proof that the spend is valid.
zkproof: Data,
/// Additional fields (e.g., spending key components) may be required.
/// The note commitment.
commitment: Blob<32>,
/// Ephemeral key for the encrypted note.
ephemeral_key: Blob<32>,
/// Encrypted ciphertext containing the note details.
enc_ciphertext: Data,
/// An optional memo field.
memo: Option<Data>,
/// This and the witness are recorded at export as of
/// an anchor depth 20 blocks back from the chain tip, or the oldest possible witness at a lesser depth.
note_commitment_tree_position: Position,
/// Witness
witness: Option<(Anchor, IncrementalWitness)>,
attachments: HashMap<Digest, Envelope>,
}
/// For legacy Sprout transactions: JoinSplit descriptions that mix transparent and shielded values.
#[derive(Debug, Clone)]
pub struct JoinSplitDescription {
anchor: Anchor,
nullifiers: [Blob<32>; 2],
commitments: [Blob<32>; 2],
/// A zero-knowledge proof to validate the JoinSplit operation.
zkproof: Data,
// Further fields may be added as necessary.
attachments: HashMap<Digest, Envelope>,
}
/// A position in a note commitment tree.
#[derive(Debug, Clone)]
pub struct Position(u32);
#[derive(Debug, Clone)]
pub struct Anchor(Blob<32>);
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct IncrementalWitness<const DEPTH: usize, Hash> {
pub tree: IncrementalMerkleTree,
pub filled: Vec<Hash>,
pub cursor: Option<IncrementalMerkleTree>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct IncrementalMerkleTree {
pub left: Option<u256>,
pub right: Option<u256>,
pub parents: Vec<Option<u256>>,
}
```