Merge pull request #1059 from zcash/1044-extract-zip32

Extract `zip32` crate again
This commit is contained in:
str4d 2023-12-06 18:46:35 +00:00 committed by GitHub
commit a9d6505148
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 199 additions and 443 deletions

12
Cargo.lock generated
View File

@ -3114,6 +3114,7 @@ dependencies = [
"zcash_address",
"zcash_encoding",
"zcash_note_encryption",
"zip32",
]
[[package]]
@ -3177,3 +3178,14 @@ dependencies = [
"quote",
"syn 2.0.39",
]
[[package]]
name = "zip32"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d724a63be4dfb50b7f3617e542984e22e4b4a5b8ca5de91f55613152885e6b22"
dependencies = [
"blake2b_simd",
"memuse",
"subtle",
]

View File

@ -102,6 +102,7 @@ rand_xorshift = "0.3"
# ZIP 32
aes = "0.8"
fpe = "0.6"
zip32 = "0.1"
[profile.release]
lto = true

View File

@ -18,16 +18,11 @@ fn parse_viewing_key(s: &str) -> Result<(ExtendedFullViewingKey, bool), &'static
fn parse_diversifier_index(s: &str) -> Result<DiversifierIndex, &'static str> {
let i: u128 = s.parse().map_err(|_| "Diversifier index is not a number")?;
if i >= (1 << 88) {
return Err("Diversifier index too large");
}
Ok(DiversifierIndex(i.to_le_bytes()[..11].try_into().unwrap()))
DiversifierIndex::try_from(i).map_err(|_| "Diversifier index too large")
}
fn encode_diversifier_index(di: &DiversifierIndex) -> u128 {
let mut bytes = [0; 16];
bytes[..11].copy_from_slice(&di.0);
u128::from_le_bytes(bytes)
(*di).into()
}
#[derive(Debug, Options)]

View File

@ -253,7 +253,7 @@ impl RecipientAddress {
#[cfg(test)]
mod tests {
use zcash_address::test_vectors;
use zcash_primitives::consensus::MAIN_NETWORK;
use zcash_primitives::{consensus::MAIN_NETWORK, zip32::AccountId};
use super::{RecipientAddress, UnifiedAddress};
use crate::keys::sapling;
@ -267,7 +267,7 @@ mod tests {
};
let sapling = {
let extsk = sapling::spending_key(&[0; 32], 0, 0.into());
let extsk = sapling::spending_key(&[0; 32], 0, AccountId::ZERO);
let dfvk = extsk.to_diversifiable_full_viewing_key();
Some(dfvk.default_address().1)
};

View File

@ -1274,7 +1274,7 @@ pub mod testing {
seed: &SecretVec<u8>,
_birthday: AccountBirthday,
) -> Result<(AccountId, UnifiedSpendingKey), Self::Error> {
let account = AccountId::from(0);
let account = AccountId::ZERO;
UnifiedSpendingKey::from_seed(&self.network, seed.expose_secret(), account)
.map(|k| (account, k))
.map_err(|_| ())

View File

@ -185,17 +185,17 @@ impl<P: consensus::Parameters> AddressCodec<P> for UnifiedAddress {
/// keys::sapling,
/// };
///
/// let extsk = sapling::spending_key(&[0; 32][..], COIN_TYPE, AccountId::from(0));
/// let extsk = sapling::spending_key(&[0; 32][..], COIN_TYPE, AccountId::ZERO);
/// let encoded = encode_extended_spending_key(HRP_SAPLING_EXTENDED_SPENDING_KEY, &extsk);
/// ```
/// [`ExtendedSpendingKey`]: zcash_primitives::zip32::ExtendedSpendingKey
/// [`ExtendedSpendingKey`]: zcash_primitives::sapling::zip32::ExtendedSpendingKey
pub fn encode_extended_spending_key(hrp: &str, extsk: &ExtendedSpendingKey) -> String {
bech32_encode(hrp, |w| extsk.write(w))
}
/// Decodes an [`ExtendedSpendingKey`] from a Bech32-encoded string.
///
/// [`ExtendedSpendingKey`]: zcash_primitives::zip32::ExtendedSpendingKey
/// [`ExtendedSpendingKey`]: zcash_primitives::sapling::zip32::ExtendedSpendingKey
pub fn decode_extended_spending_key(
hrp: &str,
s: &str,
@ -210,26 +210,26 @@ pub fn decode_extended_spending_key(
/// ```
/// use zcash_primitives::{
/// constants::testnet::{COIN_TYPE, HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY},
/// sapling::zip32::ExtendedFullViewingKey,
/// zip32::AccountId,
/// };
/// use zcash_client_backend::{
/// encoding::encode_extended_full_viewing_key,
/// keys::sapling,
/// };
/// use zcash_primitives::zip32::ExtendedFullViewingKey;
///
/// let extsk = sapling::spending_key(&[0; 32][..], COIN_TYPE, AccountId::from(0));
/// let extsk = sapling::spending_key(&[0; 32][..], COIN_TYPE, AccountId::ZERO);
/// let extfvk = extsk.to_extended_full_viewing_key();
/// let encoded = encode_extended_full_viewing_key(HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, &extfvk);
/// ```
/// [`ExtendedFullViewingKey`]: zcash_primitives::zip32::ExtendedFullViewingKey
/// [`ExtendedFullViewingKey`]: zcash_primitives::sapling::zip32::ExtendedFullViewingKey
pub fn encode_extended_full_viewing_key(hrp: &str, extfvk: &ExtendedFullViewingKey) -> String {
bech32_encode(hrp, |w| extfvk.write(w))
}
/// Decodes an [`ExtendedFullViewingKey`] from a Bech32-encoded string.
///
/// [`ExtendedFullViewingKey`]: zcash_primitives::zip32::ExtendedFullViewingKey
/// [`ExtendedFullViewingKey`]: zcash_primitives::sapling::zip32::ExtendedFullViewingKey
pub fn decode_extended_full_viewing_key(
hrp: &str,
s: &str,

View File

@ -51,9 +51,9 @@ pub mod sapling {
/// keys::sapling,
/// };
///
/// let extsk = sapling::spending_key(&[0; 32][..], COIN_TYPE, AccountId::from(0));
/// let extsk = sapling::spending_key(&[0; 32][..], COIN_TYPE, AccountId::ZERO);
/// ```
/// [`ExtendedSpendingKey`]: zcash_primitives::zip32::ExtendedSpendingKey
/// [`ExtendedSpendingKey`]: zcash_primitives::sapling::zip32::ExtendedSpendingKey
pub fn spending_key(seed: &[u8], coin_type: u32, account: AccountId) -> ExtendedSpendingKey {
if seed.len() < 32 {
panic!("ZIP 32 seeds MUST be at least 32 bytes");
@ -72,7 +72,7 @@ pub mod sapling {
#[cfg(feature = "transparent-inputs")]
fn to_transparent_child_index(j: DiversifierIndex) -> Option<u32> {
let (low_4_bytes, rest) = j.0.split_at(4);
let (low_4_bytes, rest) = j.as_bytes().split_at(4);
let transparent_j = u32::from_le_bytes(low_4_bytes.try_into().unwrap());
if transparent_j > (0x7FFFFFFF) || rest.iter().any(|b| b != &0) {
None
@ -588,7 +588,11 @@ pub mod testing {
prop::array::uniform32(prop::num::u8::ANY).prop_flat_map(move |seed| {
prop::num::u32::ANY
.prop_map(move |account| {
UnifiedSpendingKey::from_seed(&params, &seed, AccountId::from(account))
UnifiedSpendingKey::from_seed(
&params,
&seed,
AccountId::try_from(account & ((1 << 31) - 1)).unwrap(),
)
})
.prop_filter("seeds must generate valid USKs", |v| v.is_ok())
.prop_map(|v| v.unwrap())
@ -632,14 +636,14 @@ mod tests {
#[test]
#[should_panic]
fn spending_key_panics_on_short_seed() {
let _ = sapling::spending_key(&[0; 31][..], 0, AccountId::from(0));
let _ = sapling::spending_key(&[0; 31][..], 0, AccountId::ZERO);
}
#[cfg(feature = "transparent-inputs")]
#[test]
fn pk_to_taddr() {
let taddr =
legacy::keys::AccountPrivKey::from_seed(&MAIN_NETWORK, &seed(), AccountId::from(0))
legacy::keys::AccountPrivKey::from_seed(&MAIN_NETWORK, &seed(), AccountId::ZERO)
.unwrap()
.to_account_pubkey()
.derive_external_ivk()
@ -652,7 +656,7 @@ mod tests {
#[test]
fn ufvk_round_trip() {
let account = 0.into();
let account = AccountId::ZERO;
let orchard = {
let sk = orchard::keys::SpendingKey::from_zip32_seed(&[0; 32], 0, 0).unwrap();
@ -666,8 +670,7 @@ mod tests {
#[cfg(feature = "transparent-inputs")]
let transparent = {
let privkey =
AccountPrivKey::from_seed(&MAIN_NETWORK, &[0; 32], AccountId::from(0)).unwrap();
let privkey = AccountPrivKey::from_seed(&MAIN_NETWORK, &[0; 32], account).unwrap();
Some(privkey.to_account_pubkey())
};
@ -726,7 +729,7 @@ mod tests {
let usk = UnifiedSpendingKey::from_seed(
&MAIN_NETWORK,
&tv.root_seed,
AccountId::from(tv.account),
AccountId::try_from(tv.account).unwrap(),
)
.expect("seed produced a valid unified spending key");

View File

@ -244,7 +244,7 @@ impl fmt::Display for ScanError {
/// [`WalletSaplingOutput`]s, whereas the implementation for [`SaplingIvk`] cannot
/// do so and will return the unit value in those outputs instead.
///
/// [`ExtendedFullViewingKey`]: zcash_primitives::zip32::ExtendedFullViewingKey
/// [`ExtendedFullViewingKey`]: zcash_primitives::sapling::zip32::ExtendedFullViewingKey
/// [`SaplingIvk`]: zcash_primitives::sapling::SaplingIvk
/// [`CompactBlock`]: crate::proto::compact_formats::CompactBlock
/// [`ScanningKey`]: crate::scanning::ScanningKey
@ -465,10 +465,9 @@ pub(crate) fn scan_block_with_runner<
let spend = nullifiers
.iter()
.map(|&(account, nf)| CtOption::new(account, nf.ct_eq(&spend_nf)))
.fold(
CtOption::new(AccountId::from(0), 0.into()),
|first, next| CtOption::conditional_select(&next, &first, first.is_some()),
)
.fold(CtOption::new(AccountId::ZERO, 0.into()), |first, next| {
CtOption::conditional_select(&next, &first, first.is_some())
})
.map(|account| WalletSaplingSpend::from_parts(index, spend_nf, account));
if spend.is_some().into() {
@ -808,7 +807,7 @@ mod tests {
#[test]
fn scan_block_with_my_tx() {
fn go(scan_multithreaded: bool) {
let account = AccountId::from(0);
let account = AccountId::ZERO;
let extsk = ExtendedSpendingKey::master(&[]);
let dfvk = extsk.to_diversifiable_full_viewing_key();
@ -893,7 +892,7 @@ mod tests {
#[test]
fn scan_block_with_txs_after_my_tx() {
fn go(scan_multithreaded: bool) {
let account = AccountId::from(0);
let account = AccountId::ZERO;
let extsk = ExtendedSpendingKey::master(&[]);
let dfvk = extsk.to_diversifiable_full_viewing_key();
@ -928,7 +927,7 @@ mod tests {
let scanned_block = scan_block_with_runner(
&Network::TestNetwork,
cb,
&[(&AccountId::from(0), &dfvk)],
&[(&AccountId::ZERO, &dfvk)],
&[],
None,
batch_runner.as_mut(),
@ -942,7 +941,7 @@ mod tests {
assert_eq!(tx.sapling_spends.len(), 0);
assert_eq!(tx.sapling_outputs.len(), 1);
assert_eq!(tx.sapling_outputs[0].index(), 0);
assert_eq!(tx.sapling_outputs[0].account(), AccountId::from(0));
assert_eq!(tx.sapling_outputs[0].account(), AccountId::ZERO);
assert_eq!(tx.sapling_outputs[0].note().value().inner(), 5);
assert_eq!(
@ -971,7 +970,7 @@ mod tests {
let extsk = ExtendedSpendingKey::master(&[]);
let dfvk = extsk.to_diversifiable_full_viewing_key();
let nf = Nullifier([7; 32]);
let account = AccountId::from(12);
let account = AccountId::try_from(12).unwrap();
let cb = fake_compact_block(
1u32.into(),

View File

@ -333,7 +333,7 @@ pub enum OvkPolicy {
/// Transaction outputs will be decryptable by the sender, in addition to the
/// recipients.
///
/// [`ExtendedFullViewingKey`]: zcash_primitives::zip32::ExtendedFullViewingKey
/// [`ExtendedFullViewingKey`]: zcash_primitives::sapling::zip32::ExtendedFullViewingKey
Sender,
/// Use a custom outgoing viewing key. This might for instance be derived from a

View File

@ -455,7 +455,7 @@ mod tests {
// Account balance should reflect both received notes
assert_eq!(
st.get_total_balance(AccountId::from(0)),
st.get_total_balance(AccountId::ZERO),
(value + value2).unwrap()
);
@ -466,7 +466,7 @@ mod tests {
// Account balance should be unaltered
assert_eq!(
st.get_total_balance(AccountId::from(0)),
st.get_total_balance(AccountId::ZERO),
(value + value2).unwrap()
);
@ -476,14 +476,14 @@ mod tests {
.unwrap();
// Account balance should only contain the first received note
assert_eq!(st.get_total_balance(AccountId::from(0)), value);
assert_eq!(st.get_total_balance(AccountId::ZERO), value);
// Scan the cache again
st.scan_cached_blocks(h, 2);
// Account balance should again reflect both received notes
assert_eq!(
st.get_total_balance(AccountId::from(0)),
st.get_total_balance(AccountId::ZERO),
(value + value2).unwrap()
);
}
@ -502,7 +502,7 @@ mod tests {
let value = NonNegativeAmount::const_from_u64(50000);
let (h1, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
st.scan_cached_blocks(h1, 1);
assert_eq!(st.get_total_balance(AccountId::from(0)), value);
assert_eq!(st.get_total_balance(AccountId::ZERO), value);
// Create blocks to reach SAPLING_ACTIVATION_HEIGHT + 2
let (h2, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
@ -514,7 +514,7 @@ mod tests {
// Now scan the block of height SAPLING_ACTIVATION_HEIGHT + 1
st.scan_cached_blocks(h2, 1);
assert_eq!(
st.get_total_balance(AccountId::from(0)),
st.get_total_balance(AccountId::ZERO),
NonNegativeAmount::const_from_u64(150_000)
);
@ -567,7 +567,7 @@ mod tests {
assert_eq!(summary.received_sapling_note_count(), 1);
// Account balance should reflect the received note
assert_eq!(st.get_total_balance(AccountId::from(0)), value);
assert_eq!(st.get_total_balance(AccountId::ZERO), value);
// Create a second fake CompactBlock sending more value to the address
let value2 = NonNegativeAmount::const_from_u64(7);
@ -581,7 +581,7 @@ mod tests {
// Account balance should reflect both received notes
assert_eq!(
st.get_total_balance(AccountId::from(0)),
st.get_total_balance(AccountId::ZERO),
(value + value2).unwrap()
);
}
@ -606,7 +606,7 @@ mod tests {
st.scan_cached_blocks(received_height, 1);
// Account balance should reflect the received note
assert_eq!(st.get_total_balance(AccountId::from(0)), value);
assert_eq!(st.get_total_balance(AccountId::ZERO), value);
// Create a second fake CompactBlock spending value from the address
let extsk2 = ExtendedSpendingKey::master(&[0]);
@ -619,7 +619,7 @@ mod tests {
// Account balance should equal the change
assert_eq!(
st.get_total_balance(AccountId::from(0)),
st.get_total_balance(AccountId::ZERO),
(value - value2).unwrap()
);
}
@ -652,7 +652,7 @@ mod tests {
// Account balance should equal the change
assert_eq!(
st.get_total_balance(AccountId::from(0)),
st.get_total_balance(AccountId::ZERO),
(value - value2).unwrap()
);
@ -661,7 +661,7 @@ mod tests {
// Account balance should be the same.
assert_eq!(
st.get_total_balance(AccountId::from(0)),
st.get_total_balance(AccountId::ZERO),
(value - value2).unwrap()
);
}

View File

@ -385,12 +385,9 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
) -> Result<(AccountId, UnifiedSpendingKey), Self::Error> {
self.transactionally(|wdb| {
let account = wallet::get_max_account_id(wdb.conn.0)?
.map(|a| AccountId::from(u32::from(a) + 1))
.unwrap_or_else(|| AccountId::from(0));
if u32::from(account) >= 0x7FFFFFFF {
return Err(SqliteClientError::AccountIdOutOfRange);
}
.map(|a| a.next().ok_or(SqliteClientError::AccountIdOutOfRange))
.transpose()?
.unwrap_or(AccountId::ZERO);
let usk = UnifiedSpendingKey::from_seed(&wdb.params, seed.expose_secret(), account)
.map_err(|_| SqliteClientError::KeyDerivationError(account))?;
@ -1132,7 +1129,7 @@ mod tests {
.with_test_account(AccountBirthday::from_sapling_activation)
.build();
let account = AccountId::from(0);
let account = AccountId::ZERO;
let current_addr = st.wallet().get_current_address(account).unwrap();
assert!(current_addr.is_some());
@ -1157,7 +1154,10 @@ mod tests {
let ufvk = usk.to_unified_full_viewing_key();
let (taddr, _) = usk.default_transparent_address();
let receivers = st.wallet().get_transparent_receivers(0.into()).unwrap();
let receivers = st
.wallet()
.get_transparent_receivers(AccountId::ZERO)
.unwrap();
// The receiver for the default UA should be in the set.
assert!(receivers.contains_key(ufvk.default_address().0.transparent().unwrap()));
@ -1176,7 +1176,7 @@ mod tests {
// Generate some fake CompactBlocks.
let seed = [0u8; 32];
let account = AccountId::from(0);
let account = AccountId::ZERO;
let extsk = sapling::spending_key(&seed, st.wallet().params.coin_type(), account);
let dfvk = extsk.to_diversifiable_full_viewing_key();
let (h1, meta1, _) = st.generate_next_block(

View File

@ -166,11 +166,13 @@ pub(crate) fn get_max_account_id(
conn: &rusqlite::Connection,
) -> Result<Option<AccountId>, SqliteClientError> {
// This returns the most recently generated address.
conn.query_row("SELECT MAX(account) FROM accounts", [], |row| {
conn.query_row_and_then("SELECT MAX(account) FROM accounts", [], |row| {
let account_id: Option<u32> = row.get(0)?;
Ok(account_id.map(AccountId::from))
account_id
.map(AccountId::try_from)
.transpose()
.map_err(|_| SqliteClientError::AccountIdOutOfRange)
})
.map_err(SqliteClientError::from)
}
pub(crate) fn add_account<P: consensus::Parameters>(
@ -297,7 +299,7 @@ pub(crate) fn get_current_address<P: consensus::Parameters>(
addr_str,
))),
})
.map(|addr| (addr, DiversifierIndex(di_be)))
.map(|addr| (addr, DiversifierIndex::from(di_be)))
})
.transpose()
}
@ -309,7 +311,7 @@ pub(crate) fn insert_address<P: consensus::Parameters>(
conn: &rusqlite::Connection,
params: &P,
account: AccountId,
mut diversifier_index: DiversifierIndex,
diversifier_index: DiversifierIndex,
address: &UnifiedAddress,
) -> Result<(), rusqlite::Error> {
let mut stmt = conn.prepare_cached(
@ -328,10 +330,11 @@ pub(crate) fn insert_address<P: consensus::Parameters>(
)?;
// the diversifier index is stored in big-endian order to allow sorting
diversifier_index.0.reverse();
let mut di_be = *diversifier_index.as_bytes();
di_be.reverse();
stmt.execute(named_params![
":account": &u32::from(account),
":diversifier_index_be": &&diversifier_index.0[..],
":diversifier_index_be": &di_be[..],
":address": &address.encode(params),
":cached_transparent_receiver_address": &address.transparent().map(|r| r.encode(params)),
])?;
@ -377,7 +380,7 @@ pub(crate) fn get_transparent_receivers<P: consensus::Parameters>(
if let Some(taddr) = ua.transparent() {
ret.insert(
*taddr,
AddressMetadata::new(account, DiversifierIndex(di_be)),
AddressMetadata::new(account, DiversifierIndex::from(di_be)),
);
}
}
@ -436,7 +439,7 @@ pub(crate) fn get_unified_full_viewing_keys<P: consensus::Parameters>(
let rows = stmt_fetch_accounts.query_map([], |row| {
let acct: u32 = row.get(0)?;
let account = AccountId::from(acct);
let account = AccountId::try_from(acct).map_err(|_| SqliteClientError::AccountIdOutOfRange);
let ufvk_str: String = row.get(1)?;
let ufvk = UnifiedFullViewingKey::decode(params, &ufvk_str)
.map_err(SqliteClientError::CorruptedData);
@ -447,7 +450,7 @@ pub(crate) fn get_unified_full_viewing_keys<P: consensus::Parameters>(
let mut res: HashMap<AccountId, UnifiedFullViewingKey> = HashMap::new();
for row in rows {
let (account_id, ufvkr) = row?;
res.insert(account_id, ufvkr?);
res.insert(account_id?, ufvkr?);
}
Ok(res)
@ -465,11 +468,12 @@ pub(crate) fn get_account_for_ufvk<P: consensus::Parameters>(
[&ufvk.encode(params)],
|row| {
let acct: u32 = row.get(0)?;
Ok(AccountId::from(acct))
Ok(AccountId::try_from(acct).map_err(|_| SqliteClientError::AccountIdOutOfRange))
},
)
.optional()
.map_err(SqliteClientError::from)
.map_err(SqliteClientError::from)?
.transpose()
}
pub(crate) trait ScanProgress {
@ -612,9 +616,10 @@ pub(crate) fn get_wallet_summary<P: consensus::Parameters>(
let mut stmt_accounts = conn.prepare_cached("SELECT account FROM accounts")?;
let mut account_balances = stmt_accounts
.query([])?
.mapped(|row| {
row.get::<_, u32>(0)
.map(|a| (AccountId::from(a), AccountBalance::ZERO))
.and_then(|row| {
AccountId::try_from(row.get::<_, u32>(0)?)
.map_err(|_| SqliteClientError::AccountIdOutOfRange)
.map(|a| (a, AccountBalance::ZERO))
})
.collect::<Result<BTreeMap<AccountId, AccountBalance>, _>>()?;
@ -636,7 +641,8 @@ pub(crate) fn get_wallet_summary<P: consensus::Parameters>(
let mut rows =
stmt_select_notes.query(named_params![":summary_height": u32::from(summary_height)])?;
while let Some(row) = rows.next()? {
let account = row.get::<_, u32>(0).map(AccountId::from)?;
let account = AccountId::try_from(row.get::<_, u32>(0)?)
.map_err(|_| SqliteClientError::AccountIdOutOfRange)?;
let value_raw = row.get::<_, i64>(1)?;
let value = NonNegativeAmount::from_nonnegative_i64(value_raw).map_err(|_| {
@ -710,7 +716,8 @@ pub(crate) fn get_wallet_summary<P: consensus::Parameters>(
])?;
while let Some(row) = rows.next()? {
let account = AccountId::from(row.get::<_, u32>(0)?);
let account = AccountId::try_from(row.get::<_, u32>(0)?)
.map_err(|_| SqliteClientError::AccountIdOutOfRange)?;
let raw_value = row.get(1)?;
let value = NonNegativeAmount::from_nonnegative_i64(raw_value).map_err(|_| {
SqliteClientError::CorruptedData(format!("Negative UTXO value {:?}", raw_value))
@ -1612,18 +1619,23 @@ pub(crate) fn put_received_transparent_utxo<P: consensus::Parameters>(
.query_row(
"SELECT account FROM addresses WHERE cached_transparent_receiver_address = :address",
named_params![":address": &address_str],
|row| row.get::<_, u32>(0).map(AccountId::from),
|row| row.get::<_, u32>(0),
)
.optional()?;
let utxoid = if let Some(account) = account_id {
put_legacy_transparent_utxo(conn, params, output, account)?
put_legacy_transparent_utxo(
conn,
params,
output,
AccountId::try_from(account).map_err(|_| SqliteClientError::AccountIdOutOfRange)?,
)?
} else {
// If the UTXO is received at the legacy transparent address, there may be no entry in the
// addresses table that can be used to tie the address to a particular account. In this
// case, we should look up the legacy address for account 0 and check whether it matches
// the address for the received UTXO, and if so then insert/update it directly.
let account = AccountId::from(0u32);
let account = AccountId::ZERO;
get_legacy_transparent_address(params, conn, account).and_then(|legacy_taddr| {
if legacy_taddr
.iter()
@ -2014,13 +2026,14 @@ mod tests {
// The default address is set for the test account
assert_matches!(
st.wallet().get_current_address(AccountId::from(0)),
st.wallet().get_current_address(AccountId::ZERO),
Ok(Some(_))
);
// No default address is set for an un-initialized account
assert_matches!(
st.wallet().get_current_address(AccountId::from(1)),
st.wallet()
.get_current_address(AccountId::try_from(1).unwrap()),
Ok(None)
);
}

View File

@ -720,7 +720,7 @@ mod tests {
let mut db_data = WalletDb::for_path(data_file.path(), Network::TestNetwork).unwrap();
let seed = [0xab; 32];
let account = AccountId::from(0);
let account = AccountId::ZERO;
let secret_key = sapling::spending_key(&seed, db_data.params.coin_type(), account);
let extfvk = secret_key.to_extended_full_viewing_key();
@ -891,7 +891,7 @@ mod tests {
let mut db_data = WalletDb::for_path(data_file.path(), Network::TestNetwork).unwrap();
let seed = [0xab; 32];
let account = AccountId::from(0);
let account = AccountId::ZERO;
let secret_key = sapling::spending_key(&seed, db_data.params.coin_type(), account);
let extfvk = secret_key.to_extended_full_viewing_key();
@ -1044,7 +1044,7 @@ mod tests {
let mut db_data = WalletDb::for_path(data_file.path(), Network::TestNetwork).unwrap();
let seed = [0xab; 32];
let account = AccountId::from(0);
let account = AccountId::ZERO;
let secret_key = UnifiedSpendingKey::from_seed(&db_data.params, &seed, account).unwrap();
init_main(
@ -1077,7 +1077,7 @@ mod tests {
let (account, _usk) = db_data
.create_account(&Secret::new(seed.to_vec()), birthday)
.unwrap();
assert_eq!(account, AccountId::from(0u32));
assert_eq!(account, AccountId::ZERO);
for tv in &test_vectors::UNIFIED[..3] {
if let Some(RecipientAddress::Unified(tvua)) =

View File

@ -308,8 +308,7 @@ mod tests {
let data_file = NamedTempFile::new().unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), network).unwrap();
init_wallet_db_internal(&mut db_data, None, &[addresses_table::MIGRATION_ID]).unwrap();
let usk =
UnifiedSpendingKey::from_seed(&network, &[0u8; 32][..], AccountId::from(0)).unwrap();
let usk = UnifiedSpendingKey::from_seed(&network, &[0u8; 32][..], AccountId::ZERO).unwrap();
let ufvk = usk.to_unified_full_viewing_key();
db_data
@ -436,8 +435,7 @@ mod tests {
let mut tx_bytes = vec![];
tx.write(&mut tx_bytes).unwrap();
let usk =
UnifiedSpendingKey::from_seed(&network, &[0u8; 32][..], AccountId::from(0)).unwrap();
let usk = UnifiedSpendingKey::from_seed(&network, &[0u8; 32][..], AccountId::ZERO).unwrap();
let ufvk = usk.to_unified_full_viewing_key();
let (ua, _) = ufvk.default_address();
let taddr = ufvk

View File

@ -59,13 +59,16 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
let mut rows = stmt_fetch_accounts.query([])?;
while let Some(row) = rows.next()? {
let account: u32 = row.get(0)?;
let taddrs =
get_transparent_receivers(transaction, &self._params, AccountId::from(account))
let taddrs = get_transparent_receivers(
transaction,
&self._params,
AccountId::try_from(account).map_err(|_| {
WalletMigrationError::CorruptedData("Account ID is invalid".to_owned())
})?,
)
.map_err(|e| match e {
SqliteClientError::DbError(e) => WalletMigrationError::DbError(e),
SqliteClientError::CorruptedData(s) => {
WalletMigrationError::CorruptedData(s)
}
SqliteClientError::CorruptedData(s) => WalletMigrationError::CorruptedData(s),
other => WalletMigrationError::CorruptedData(format!(
"Unexpected error in migration: {}",
other

View File

@ -61,7 +61,9 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
let mut rows = stmt_fetch_accounts.query([])?;
while let Some(row) = rows.next()? {
let account: u32 = row.get(0)?;
let account = AccountId::from(account);
let account = AccountId::try_from(account).map_err(|_| {
WalletMigrationError::CorruptedData("Account ID is invalid".to_owned())
})?;
let ufvk_str: String = row.get(1)?;
let ufvk = UnifiedFullViewingKey::decode(&self.params, &ufvk_str)

View File

@ -242,8 +242,7 @@ mod tests {
init_wallet_db_internal(&mut db_data, None, &[v_transactions_net::MIGRATION_ID]).unwrap();
// Create an account in the wallet
let usk0 =
UnifiedSpendingKey::from_seed(&db_data.params, &[0u8; 32][..], AccountId::from(0))
let usk0 = UnifiedSpendingKey::from_seed(&db_data.params, &[0u8; 32][..], AccountId::ZERO)
.unwrap();
let ufvk0 = usk0.to_unified_full_viewing_key();
db_data

View File

@ -71,10 +71,12 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
))
})?;
tx_sent_notes
.entry((id_tx, txid))
.or_default()
.insert(AccountId::from(account), ufvk);
tx_sent_notes.entry((id_tx, txid)).or_default().insert(
AccountId::try_from(account).map_err(|_| {
WalletMigrationError::CorruptedData("Account ID is invalid".to_owned())
})?,
ufvk,
);
}
let mut stmt_update_sent_memo = transaction.prepare(

View File

@ -71,7 +71,9 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
// migration is being used to initialize an empty database.
if let Some(seed) = &self.seed {
let account: u32 = row.get(0)?;
let account = AccountId::from(account);
let account = AccountId::try_from(account).map_err(|_| {
WalletMigrationError::CorruptedData("Account ID is invalid".to_owned())
})?;
let usk =
UnifiedSpendingKey::from_seed(&self.params, seed.expose_secret(), account)
.unwrap();

View File

@ -222,8 +222,7 @@ mod tests {
.unwrap();
// Create two accounts in the wallet.
let usk0 =
UnifiedSpendingKey::from_seed(&db_data.params, &[0u8; 32][..], AccountId::from(0))
let usk0 = UnifiedSpendingKey::from_seed(&db_data.params, &[0u8; 32][..], AccountId::ZERO)
.unwrap();
let ufvk0 = usk0.to_unified_full_viewing_key();
db_data
@ -234,8 +233,11 @@ mod tests {
)
.unwrap();
let usk1 =
UnifiedSpendingKey::from_seed(&db_data.params, &[1u8; 32][..], AccountId::from(1))
let usk1 = UnifiedSpendingKey::from_seed(
&db_data.params,
&[1u8; 32][..],
AccountId::try_from(1).unwrap(),
)
.unwrap();
let ufvk1 = usk1.to_unified_full_viewing_key();
db_data

View File

@ -178,8 +178,7 @@ mod tests {
init_wallet_db_internal(&mut db_data, None, &[v_transactions_net::MIGRATION_ID]).unwrap();
// Create an account in the wallet
let usk0 =
UnifiedSpendingKey::from_seed(&db_data.params, &[0u8; 32][..], AccountId::from(0))
let usk0 = UnifiedSpendingKey::from_seed(&db_data.params, &[0u8; 32][..], AccountId::ZERO)
.unwrap();
let ufvk0 = usk0.to_unified_full_viewing_key();
db_data

View File

@ -338,13 +338,12 @@ pub(crate) fn get_sapling_nullifiers(
WHERE block IS NULL
AND nf IS NOT NULL",
)?;
let nullifiers = stmt_fetch_nullifiers.query_map([], |row| {
let nullifiers = stmt_fetch_nullifiers.query_and_then([], |row| {
let account: u32 = row.get(1)?;
let nf_bytes: Vec<u8> = row.get(2)?;
Ok((
AccountId::from(account),
sapling::Nullifier::from_slice(&nf_bytes).unwrap(),
))
AccountId::try_from(account)
.map_err(|_| SqliteClientError::AccountIdOutOfRange)
.map(|a| (a, sapling::Nullifier::from_slice(&nf_bytes).unwrap()))
})?;
let res: Vec<_> = nullifiers.collect::<Result<_, _>>()?;
@ -361,13 +360,12 @@ pub(crate) fn get_all_sapling_nullifiers(
FROM sapling_received_notes rn
WHERE nf IS NOT NULL",
)?;
let nullifiers = stmt_fetch_nullifiers.query_map([], |row| {
let nullifiers = stmt_fetch_nullifiers.query_and_then([], |row| {
let account: u32 = row.get(1)?;
let nf_bytes: Vec<u8> = row.get(2)?;
Ok((
AccountId::from(account),
sapling::Nullifier::from_slice(&nf_bytes).unwrap(),
))
AccountId::try_from(account)
.map_err(|_| SqliteClientError::AccountIdOutOfRange)
.map(|a| (a, sapling::Nullifier::from_slice(&nf_bytes).unwrap()))
})?;
let res: Vec<_> = nullifiers.collect::<Result<_, _>>()?;
@ -688,7 +686,7 @@ pub(crate) mod tests {
let to = dfvk.default_address().1.into();
// Create a USK that doesn't exist in the wallet
let acct1 = AccountId::from(1);
let acct1 = AccountId::try_from(1).unwrap();
let usk1 = UnifiedSpendingKey::from_seed(&st.network(), &[1u8; 32], acct1).unwrap();
// Attempting to spend with a USK that is not in the wallet results in an error
@ -1263,11 +1261,11 @@ pub(crate) mod tests {
st.scan_cached_blocks(h, 1);
// Spendable balance matches total balance
assert_eq!(st.get_total_balance(AccountId::from(0)), value);
assert_eq!(st.get_spendable_balance(AccountId::from(0), 1), value);
assert_eq!(st.get_total_balance(AccountId::ZERO), value);
assert_eq!(st.get_spendable_balance(AccountId::ZERO, 1), value);
assert_eq!(
st.get_total_balance(AccountId::from(1)),
NonNegativeAmount::ZERO
st.get_total_balance(AccountId::try_from(1).unwrap()),
NonNegativeAmount::ZERO,
);
let amount_sent = NonNegativeAmount::from_u64(20000).unwrap();
@ -1317,15 +1315,18 @@ pub(crate) mod tests {
let pending_change = (amount_left - amount_legacy_change).unwrap();
// The "legacy change" is not counted by get_pending_change().
assert_eq!(st.get_pending_change(AccountId::from(0), 1), pending_change);
assert_eq!(st.get_pending_change(AccountId::ZERO, 1), pending_change);
// We spent the only note so we only have pending change.
assert_eq!(st.get_total_balance(AccountId::from(0)), pending_change);
assert_eq!(st.get_total_balance(AccountId::ZERO), pending_change);
let (h, _) = st.generate_next_block_including(txid);
st.scan_cached_blocks(h, 1);
assert_eq!(st.get_total_balance(AccountId::from(1)), amount_sent);
assert_eq!(st.get_total_balance(AccountId::from(0)), amount_left);
assert_eq!(
st.get_total_balance(AccountId::try_from(1).unwrap()),
amount_sent,
);
assert_eq!(st.get_total_balance(AccountId::ZERO), amount_left);
st.reset();
@ -1353,8 +1354,11 @@ pub(crate) mod tests {
st.scan_cached_blocks(st.sapling_activation_height(), 2);
assert_eq!(st.get_total_balance(AccountId::from(1)), amount_sent);
assert_eq!(st.get_total_balance(AccountId::from(0)), amount_left);
assert_eq!(
st.get_total_balance(AccountId::try_from(1).unwrap()),
amount_sent,
);
assert_eq!(st.get_total_balance(AccountId::ZERO), amount_left);
}
#[test]
@ -1575,7 +1579,7 @@ pub(crate) mod tests {
let spendable = select_spendable_sapling_notes(
&st.wallet().conn,
&st.wallet().params,
AccountId::from(0),
AccountId::ZERO,
Amount::const_from_i64(300000),
received_tx_height + 10,
&[],
@ -1591,7 +1595,7 @@ pub(crate) mod tests {
let spendable = select_spendable_sapling_notes(
&st.wallet().conn,
&st.wallet().params,
AccountId::from(0),
AccountId::ZERO,
Amount::const_from_i64(300000),
received_tx_height + 10,
&[],

View File

@ -21,6 +21,7 @@ all-features = true
equihash.workspace = true
zcash_address.workspace = true
zcash_encoding.workspace = true
zip32.workspace = true
# Dependencies exposed in a public API:
# (Breaking upgrades to these require a breaking upgrade to this crate.)

View File

@ -9,7 +9,7 @@ pub const COIN_TYPE: u32 = 133;
///
/// Defined in [ZIP 32].
///
/// [`ExtendedSpendingKey`]: crate::zip32::ExtendedSpendingKey
/// [`ExtendedSpendingKey`]: crate::sapling::zip32::ExtendedSpendingKey
/// [ZIP 32]: https://github.com/zcash/zips/blob/master/zip-0032.rst
pub const HRP_SAPLING_EXTENDED_SPENDING_KEY: &str = "secret-extended-key-main";
@ -17,7 +17,7 @@ pub const HRP_SAPLING_EXTENDED_SPENDING_KEY: &str = "secret-extended-key-main";
///
/// Defined in [ZIP 32].
///
/// [`ExtendedFullViewingKey`]: crate::zip32::ExtendedFullViewingKey
/// [`ExtendedFullViewingKey`]: crate::sapling::zip32::ExtendedFullViewingKey
/// [ZIP 32]: https://github.com/zcash/zips/blob/master/zip-0032.rst
pub const HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY: &str = "zxviews";

View File

@ -13,7 +13,7 @@ pub const COIN_TYPE: u32 = 1;
///
/// It is defined in [the `zcashd` codebase].
///
/// [`ExtendedSpendingKey`]: crate::zip32::ExtendedSpendingKey
/// [`ExtendedSpendingKey`]: crate::sapling::zip32::ExtendedSpendingKey
/// [the `zcashd` codebase]: <https://github.com/zcash/zcash/blob/128d863fb8be39ee294fda397c1ce3ba3b889cb2/src/chainparams.cpp#L496>
pub const HRP_SAPLING_EXTENDED_SPENDING_KEY: &str = "secret-extended-key-regtest";
@ -21,7 +21,7 @@ pub const HRP_SAPLING_EXTENDED_SPENDING_KEY: &str = "secret-extended-key-regtest
///
/// It is defined in [the `zcashd` codebase].
///
/// [`ExtendedFullViewingKey`]: crate::zip32::ExtendedFullViewingKey
/// [`ExtendedFullViewingKey`]: crate::sapling::zip32::ExtendedFullViewingKey
/// [the `zcashd` codebase]: <https://github.com/zcash/zcash/blob/128d863fb8be39ee294fda397c1ce3ba3b889cb2/src/chainparams.cpp#L494>
pub const HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY: &str = "zxviewregtestsapling";

View File

@ -9,7 +9,7 @@ pub const COIN_TYPE: u32 = 1;
///
/// Defined in [ZIP 32].
///
/// [`ExtendedSpendingKey`]: crate::zip32::ExtendedSpendingKey
/// [`ExtendedSpendingKey`]: crate::sapling::zip32::ExtendedSpendingKey
/// [ZIP 32]: https://github.com/zcash/zips/blob/master/zip-0032.rst
pub const HRP_SAPLING_EXTENDED_SPENDING_KEY: &str = "secret-extended-key-test";
@ -17,7 +17,7 @@ pub const HRP_SAPLING_EXTENDED_SPENDING_KEY: &str = "secret-extended-key-test";
///
/// Defined in [ZIP 32].
///
/// [`ExtendedFullViewingKey`]: crate::zip32::ExtendedFullViewingKey
/// [`ExtendedFullViewingKey`]: crate::sapling::zip32::ExtendedFullViewingKey
/// [ZIP 32]: https://github.com/zcash/zips/blob/master/zip-0032.rst
pub const HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY: &str = "zxviewtestsapling";

View File

@ -18,7 +18,7 @@ pub mod memo;
pub mod merkle_tree;
pub mod sapling;
pub mod transaction;
pub mod zip32;
pub use zip32;
pub mod zip339;
#[cfg(feature = "zfuture")]

View File

@ -8,6 +8,7 @@ use aes::Aes256;
use blake2b_simd::Params as Blake2bParams;
use byteorder::{ByteOrder, LittleEndian, ReadBytesExt, WriteBytesExt};
use fpe::ff1::{BinaryNumeralString, FF1};
use std::io::{self, Read, Write};
use std::ops::AddAssign;
@ -178,7 +179,7 @@ impl DiversifierKey {
fn try_diversifier_internal(ff: &FF1<Aes256>, j: DiversifierIndex) -> Option<Diversifier> {
// Generate d_j
let enc = ff
.encrypt(&[], &BinaryNumeralString::from_bytes_le(&j.0[..]))
.encrypt(&[], &BinaryNumeralString::from_bytes_le(j.as_bytes()))
.unwrap();
let mut d_j = [0; 11];
d_j.copy_from_slice(&enc.to_bytes_le());
@ -205,9 +206,7 @@ impl DiversifierKey {
let dec = ff
.decrypt(&[], &BinaryNumeralString::from_bytes_le(&d.0[..]))
.unwrap();
let mut j = DiversifierIndex::new();
j.0.copy_from_slice(&dec.to_bytes_le());
j
DiversifierIndex::from(<[u8; 11]>::try_from(&dec.to_bytes_le()[..]).unwrap())
}
/// Returns the first index starting from j that generates a valid
@ -854,9 +853,9 @@ mod tests {
fn diversifier() {
let dk = DiversifierKey([0; 32]);
let j_0 = DiversifierIndex::new();
let j_1 = DiversifierIndex([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
let j_2 = DiversifierIndex([2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
let j_3 = DiversifierIndex([3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
let j_1 = DiversifierIndex::from([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
let j_2 = DiversifierIndex::from([2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
let j_3 = DiversifierIndex::from([3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
// Computed using this Rust implementation
let d_0 = [220, 231, 126, 188, 236, 10, 38, 175, 214, 153, 140];
let d_3 = [60, 253, 170, 8, 171, 147, 220, 31, 3, 144, 34];
@ -883,12 +882,12 @@ mod tests {
let di32: u32 = 0xa0b0c0d0;
assert_eq!(
DiversifierIndex::from(di32),
DiversifierIndex([0xd0, 0xc0, 0xb0, 0xa0, 0, 0, 0, 0, 0, 0, 0])
DiversifierIndex::from([0xd0, 0xc0, 0xb0, 0xa0, 0, 0, 0, 0, 0, 0, 0])
);
let di64: u64 = 0x0102030405060708;
assert_eq!(
DiversifierIndex::from(di64),
DiversifierIndex([8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0])
DiversifierIndex::from([8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0])
);
}
@ -896,9 +895,9 @@ mod tests {
fn find_diversifier() {
let dk = DiversifierKey([0; 32]);
let j_0 = DiversifierIndex::new();
let j_1 = DiversifierIndex([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
let j_2 = DiversifierIndex([2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
let j_3 = DiversifierIndex([3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
let j_1 = DiversifierIndex::from([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
let j_2 = DiversifierIndex::from([2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
let j_3 = DiversifierIndex::from([3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
// Computed using this Rust implementation
let d_0 = [220, 231, 126, 188, 236, 10, 38, 175, 214, 153, 140];
let d_3 = [60, 253, 170, 8, 171, 147, 220, 31, 3, 144, 34];
@ -958,7 +957,7 @@ mod tests {
[59, 246, 250, 31, 131, 191, 69, 99, 200, 167, 19]
);
let j_1 = DiversifierIndex([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
let j_1 = DiversifierIndex::from([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(xfvk_m.address(j_1), None);
}
@ -967,7 +966,7 @@ mod tests {
let seed = [0; 32];
let xsk_m = ExtendedSpendingKey::master(&seed);
let (j_m, addr_m) = xsk_m.default_address();
assert_eq!(j_m.0, [0; 11]);
assert_eq!(j_m.as_bytes(), &[0; 11]);
assert_eq!(
addr_m.diversifier().0,
// Computed using this Rust implementation
@ -1689,7 +1688,7 @@ mod tests {
}
// dmax
let dmax = DiversifierIndex([0xff; 11]);
let dmax = DiversifierIndex::from([0xff; 11]);
match xfvk.dk.find_diversifier(dmax) {
Some((l, d)) if l == dmax => assert_eq!(d.0, tv.dmax.unwrap()),
Some((_, _)) => panic!(),

View File

@ -784,7 +784,7 @@ mod tests {
orchard_saks: Vec::new(),
};
let tsk = AccountPrivKey::from_seed(&TEST_NETWORK, &[0u8; 32], AccountId::from(0)).unwrap();
let tsk = AccountPrivKey::from_seed(&TEST_NETWORK, &[0u8; 32], AccountId::ZERO).unwrap();
let prev_coin = TxOut {
value: NonNegativeAmount::const_from_u64(50000),
script_pubkey: tsk

View File

@ -1,198 +0,0 @@
//! Implementation of [ZIP 32] for hierarchical deterministic key management.
//!
//! [ZIP 32]: https://zips.z.cash/zip-0032
use memuse::{self, DynamicUsage};
use subtle::{Choice, ConditionallySelectable};
pub mod fingerprint;
#[deprecated(note = "Please use the types exported from the `zip32::sapling` module instead.")]
pub use crate::sapling::zip32::{
sapling_address, sapling_default_address, sapling_derive_internal_fvk, sapling_find_address,
DiversifiableFullViewingKey, ExtendedFullViewingKey, ExtendedSpendingKey,
ZIP32_SAPLING_FVFP_PERSONALIZATION, ZIP32_SAPLING_INT_PERSONALIZATION,
ZIP32_SAPLING_MASTER_PERSONALIZATION,
};
/// A type-safe wrapper for account identifiers.
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AccountId(u32);
memuse::impl_no_dynamic_usage!(AccountId);
impl From<u32> for AccountId {
fn from(id: u32) -> Self {
Self(id)
}
}
impl From<AccountId> for u32 {
fn from(id: AccountId) -> Self {
id.0
}
}
impl From<AccountId> for ChildIndex {
fn from(id: AccountId) -> Self {
// Account IDs are always hardened in derivation paths.
ChildIndex::hardened(id.0)
}
}
impl ConditionallySelectable for AccountId {
fn conditional_select(a0: &Self, a1: &Self, c: Choice) -> Self {
AccountId(u32::conditional_select(&a0.0, &a1.0, c))
}
}
// ZIP 32 structures
/// A child index for a derived key.
///
/// Only hardened derivation is supported.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ChildIndex(u32);
impl ChildIndex {
/// Parses the given ZIP 32 child index.
///
/// Returns `None` if the hardened bit is not set.
pub fn from_index(i: u32) -> Option<Self> {
if i >= (1 << 31) {
Some(ChildIndex(i))
} else {
None
}
}
/// Constructs a hardened `ChildIndex` from the given value.
///
/// # Panics
///
/// Panics if `value >= (1 << 31)`.
pub const fn hardened(value: u32) -> Self {
assert!(value < (1 << 31));
Self(value + (1 << 31))
}
/// Returns the index as a 32-bit integer, including the hardened bit.
pub fn index(&self) -> u32 {
self.0
}
}
/// A value that is needed, in addition to a spending key, in order to derive descendant
/// keys and addresses of that key.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ChainCode([u8; 32]);
impl ChainCode {
/// Constructs a `ChainCode` from the given array.
pub fn new(c: [u8; 32]) -> Self {
Self(c)
}
/// Returns the byte representation of the chain code, as required for
/// [ZIP 32](https://zips.z.cash/zip-0032) encoding.
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DiversifierIndex(pub [u8; 11]);
impl Default for DiversifierIndex {
fn default() -> Self {
DiversifierIndex::new()
}
}
impl From<u32> for DiversifierIndex {
fn from(i: u32) -> Self {
u64::from(i).into()
}
}
impl From<u64> for DiversifierIndex {
fn from(i: u64) -> Self {
let mut result = DiversifierIndex([0; 11]);
result.0[..8].copy_from_slice(&i.to_le_bytes());
result
}
}
impl TryFrom<DiversifierIndex> for u32 {
type Error = std::num::TryFromIntError;
fn try_from(di: DiversifierIndex) -> Result<u32, Self::Error> {
let mut u128_bytes = [0u8; 16];
u128_bytes[0..11].copy_from_slice(&di.0[..]);
u128::from_le_bytes(u128_bytes).try_into()
}
}
impl DiversifierIndex {
pub fn new() -> Self {
DiversifierIndex([0; 11])
}
pub fn increment(&mut self) -> Result<(), ()> {
for k in 0..11 {
self.0[k] = self.0[k].wrapping_add(1);
if self.0[k] != 0 {
// No overflow
return Ok(());
}
}
// Overflow
Err(())
}
}
/// The scope of a viewing key or address.
///
/// A "scope" narrows the visibility or usage to a level below "full".
///
/// Consistent usage of `Scope` enables the user to provide consistent views over a wallet
/// to other people. For example, a user can give an external [SaplingIvk] to a merchant
/// terminal, enabling it to only detect "real" transactions from customers and not
/// internal transactions from the wallet.
///
/// [SaplingIvk]: crate::sapling::SaplingIvk
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Scope {
/// A scope used for wallet-external operations, namely deriving addresses to give to
/// other users in order to receive funds.
External,
/// A scope used for wallet-internal operations, such as creating change notes,
/// auto-shielding, and note management.
Internal,
}
memuse::impl_no_dynamic_usage!(Scope);
#[cfg(test)]
mod tests {
use super::DiversifierIndex;
use assert_matches::assert_matches;
#[test]
fn diversifier_index_to_u32() {
let two = DiversifierIndex([
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
assert_eq!(u32::try_from(two), Ok(2));
let max_u32 = DiversifierIndex([
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
assert_eq!(u32::try_from(max_u32), Ok(u32::MAX));
let too_big = DiversifierIndex([
0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
assert_matches!(u32::try_from(too_big), Err(_));
}
}

View File

@ -1,80 +0,0 @@
//! Seed Fingerprints according to ZIP 32
//!
//! Implements section `Seed Fingerprints` of Shielded Hierarchical Deterministic Wallets (ZIP 32)
//!
//! [Section Seed Fingerprints]: https://zips.z.cash/zip-0032#seed-fingerprints
use blake2b_simd::Params as Blake2bParams;
pub const ZIP32_SEED_FP_PERSONALIZATION: &[u8; 16] = b"Zcash_HD_Seed_FP";
pub struct SeedFingerprint([u8; 32]);
impl SeedFingerprint {
/// Return the seed fingerprint of the wallet as defined in
/// <https://zips.z.cash/zip-0032#seed-fingerprints> or None
/// if the length of `seed_bytes` is less than 32 or
/// greater than 252.
pub fn from_seed(seed_bytes: &[u8]) -> Option<SeedFingerprint> {
let seed_len = seed_bytes.len();
if (32..=252).contains(&seed_len) {
let seed_len: u8 = seed_len.try_into().unwrap();
Some(SeedFingerprint(
Blake2bParams::new()
.hash_length(32)
.personal(ZIP32_SEED_FP_PERSONALIZATION)
.to_state()
.update(&[seed_len])
.update(seed_bytes)
.finalize()
.as_bytes()
.try_into()
.expect("hash length should be 32 bytes"),
))
} else {
None
}
}
/// Returns the fingerprint as a byte array.
pub fn to_bytes(&self) -> [u8; 32] {
self.0
}
}
#[test]
fn test_seed_fingerprint() {
struct TestVector {
root_seed: Vec<u8>,
fingerprint: Vec<u8>,
}
let test_vectors = vec![TestVector {
root_seed: vec![
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
0x1c, 0x1d, 0x1e, 0x1f,
],
fingerprint: vec![
0xde, 0xff, 0x60, 0x4c, 0x24, 0x67, 0x10, 0xf7, 0x17, 0x6d, 0xea, 0xd0, 0x2a, 0xa7,
0x46, 0xf2, 0xfd, 0x8d, 0x53, 0x89, 0xf7, 0x7, 0x25, 0x56, 0xdc, 0xb5, 0x55, 0xfd,
0xbe, 0x5e, 0x3a, 0xe3,
],
}];
for tv in test_vectors {
let fp = SeedFingerprint::from_seed(&tv.root_seed).expect("root_seed has valid length");
assert_eq!(&fp.to_bytes(), &tv.fingerprint[..]);
}
}
#[test]
fn test_seed_fingerprint_is_none() {
let odd_seed = vec![
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f,
];
assert!(
SeedFingerprint::from_seed(&odd_seed).is_none(),
"fingerprint from short seed should be `None`"
);
}