Migrate to published `zip32` crate outside this repository
This commit is contained in:
parent
5aa0e6c6ee
commit
3dfd478141
|
@ -3181,9 +3181,10 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zip32"
|
||||
version = "0.0.0"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d724a63be4dfb50b7f3617e542984e22e4b4a5b8ca5de91f55613152885e6b22"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"blake2b_simd",
|
||||
"memuse",
|
||||
"subtle",
|
||||
|
|
|
@ -10,7 +10,6 @@ members = [
|
|||
"zcash_history",
|
||||
"zcash_primitives",
|
||||
"zcash_proofs",
|
||||
"zip32",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
|
@ -34,7 +33,6 @@ zcash_encoding = { version = "0.2", path = "components/zcash_encoding" }
|
|||
zcash_note_encryption = "0.4"
|
||||
zcash_primitives = { version = "0.13", path = "zcash_primitives", default-features = false }
|
||||
zcash_proofs = { version = "0.13", path = "zcash_proofs", default-features = false }
|
||||
zip32 = { version = "0.0", path = "zip32" }
|
||||
|
||||
# Shielded protocols
|
||||
ff = "0.13"
|
||||
|
@ -104,6 +102,7 @@ rand_xorshift = "0.3"
|
|||
# ZIP 32
|
||||
aes = "0.8"
|
||||
fpe = "0.6"
|
||||
zip32 = "0.1"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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)
|
||||
};
|
||||
|
|
|
@ -1193,7 +1193,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(|_| ())
|
||||
|
|
|
@ -185,7 +185,7 @@ 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::sapling::zip32::ExtendedSpendingKey
|
||||
|
@ -218,7 +218,7 @@ pub fn decode_extended_spending_key(
|
|||
/// 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 extfvk = extsk.to_extended_full_viewing_key();
|
||||
/// let encoded = encode_extended_full_viewing_key(HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, &extfvk);
|
||||
/// ```
|
||||
|
|
|
@ -51,7 +51,7 @@ 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::sapling::zip32::ExtendedSpendingKey
|
||||
pub fn spending_key(seed: &[u8], coin_type: u32, account: AccountId) -> ExtendedSpendingKey {
|
||||
|
@ -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(¶ms, &seed, AccountId::from(account))
|
||||
UnifiedSpendingKey::from_seed(
|
||||
¶ms,
|
||||
&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");
|
||||
|
||||
|
|
|
@ -464,10 +464,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() {
|
||||
|
@ -804,7 +803,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();
|
||||
|
||||
|
@ -889,7 +888,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();
|
||||
|
||||
|
@ -924,7 +923,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(),
|
||||
|
@ -938,7 +937,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!(
|
||||
|
@ -967,7 +966,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(),
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -384,12 +384,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_else(|| AccountId::ZERO);
|
||||
|
||||
let usk = UnifiedSpendingKey::from_seed(&wdb.params, seed.expose_secret(), account)
|
||||
.map_err(|_| SqliteClientError::KeyDerivationError(account))?;
|
||||
|
@ -1130,7 +1127,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());
|
||||
|
||||
|
@ -1155,7 +1152,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()));
|
||||
|
@ -1174,7 +1174,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(
|
||||
|
|
|
@ -152,11 +152,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>(
|
||||
|
@ -283,7 +285,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()
|
||||
}
|
||||
|
@ -295,7 +297,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(
|
||||
|
@ -314,10 +316,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)),
|
||||
])?;
|
||||
|
@ -363,7 +366,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)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -422,7 +425,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);
|
||||
|
@ -433,7 +436,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)
|
||||
|
@ -451,11 +454,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 {
|
||||
|
@ -598,9 +602,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>, _>>()?;
|
||||
|
||||
|
@ -622,7 +627,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(|_| {
|
||||
|
@ -699,7 +705,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))
|
||||
|
@ -1601,18 +1608,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()
|
||||
|
@ -2003,13 +2015,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)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -719,7 +719,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();
|
||||
|
||||
|
@ -890,7 +890,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();
|
||||
|
||||
|
@ -1043,7 +1043,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(
|
||||
|
@ -1076,7 +1076,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)) =
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -59,18 +59,21 @@ 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))
|
||||
.map_err(|e| match e {
|
||||
SqliteClientError::DbError(e) => WalletMigrationError::DbError(e),
|
||||
SqliteClientError::CorruptedData(s) => {
|
||||
WalletMigrationError::CorruptedData(s)
|
||||
}
|
||||
other => WalletMigrationError::CorruptedData(format!(
|
||||
"Unexpected error in migration: {}",
|
||||
other
|
||||
)),
|
||||
})?;
|
||||
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),
|
||||
other => WalletMigrationError::CorruptedData(format!(
|
||||
"Unexpected error in migration: {}",
|
||||
other
|
||||
)),
|
||||
})?;
|
||||
|
||||
for (taddr, _) in taddrs {
|
||||
stmt_update_utxo_account.execute(named_params![
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -242,9 +242,8 @@ 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))
|
||||
.unwrap();
|
||||
let usk0 = UnifiedSpendingKey::from_seed(&db_data.params, &[0u8; 32][..], AccountId::ZERO)
|
||||
.unwrap();
|
||||
let ufvk0 = usk0.to_unified_full_viewing_key();
|
||||
db_data
|
||||
.conn
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -73,7 +73,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();
|
||||
|
|
|
@ -222,9 +222,8 @@ mod tests {
|
|||
.unwrap();
|
||||
|
||||
// Create two accounts in the wallet.
|
||||
let usk0 =
|
||||
UnifiedSpendingKey::from_seed(&db_data.params, &[0u8; 32][..], AccountId::from(0))
|
||||
.unwrap();
|
||||
let usk0 = UnifiedSpendingKey::from_seed(&db_data.params, &[0u8; 32][..], AccountId::ZERO)
|
||||
.unwrap();
|
||||
let ufvk0 = usk0.to_unified_full_viewing_key();
|
||||
db_data
|
||||
.conn
|
||||
|
@ -234,9 +233,12 @@ mod tests {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let usk1 =
|
||||
UnifiedSpendingKey::from_seed(&db_data.params, &[1u8; 32][..], AccountId::from(1))
|
||||
.unwrap();
|
||||
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
|
||||
.conn
|
||||
|
|
|
@ -178,9 +178,8 @@ 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))
|
||||
.unwrap();
|
||||
let usk0 = UnifiedSpendingKey::from_seed(&db_data.params, &[0u8; 32][..], AccountId::ZERO)
|
||||
.unwrap();
|
||||
let ufvk0 = usk0.to_unified_full_viewing_key();
|
||||
db_data
|
||||
.conn
|
||||
|
|
|
@ -293,13 +293,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<_, _>>()?;
|
||||
|
@ -316,13 +315,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<_, _>>()?;
|
||||
|
@ -637,7 +635,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
|
||||
|
@ -1203,11 +1201,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();
|
||||
|
@ -1257,15 +1255,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();
|
||||
|
||||
|
@ -1293,8 +1294,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]
|
||||
|
@ -1514,7 +1518,7 @@ pub(crate) mod tests {
|
|||
// Verify that the received note is not considered spendable
|
||||
let spendable = select_spendable_sapling_notes(
|
||||
&st.wallet().conn,
|
||||
AccountId::from(0),
|
||||
AccountId::ZERO,
|
||||
Amount::const_from_i64(300000),
|
||||
received_tx_height + 10,
|
||||
&[],
|
||||
|
@ -1529,7 +1533,7 @@ pub(crate) mod tests {
|
|||
// Verify that the received note is now considered spendable
|
||||
let spendable = select_spendable_sapling_notes(
|
||||
&st.wallet().conn,
|
||||
AccountId::from(0),
|
||||
AccountId::ZERO,
|
||||
Amount::const_from_i64(300000),
|
||||
received_tx_height + 10,
|
||||
&[],
|
||||
|
|
|
@ -179,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());
|
||||
|
@ -206,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
|
||||
|
@ -855,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];
|
||||
|
@ -884,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])
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -897,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];
|
||||
|
@ -959,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);
|
||||
}
|
||||
|
||||
|
@ -968,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
|
||||
|
@ -1690,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!(),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
|
@ -1,14 +0,0 @@
|
|||
Copyrights in the "zip32" library are retained by their contributors. No
|
||||
copyright assignment is required to contribute to the "zip32" library.
|
||||
|
||||
The "zip32" library is licensed under either of
|
||||
|
||||
* Apache License, Version 2.0, (see ./LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
|
||||
* MIT license (see ./LICENSE-MIT or http://opensource.org/licenses/MIT)
|
||||
|
||||
at your option.
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally
|
||||
submitted for inclusion in the work by you, as defined in the Apache-2.0
|
||||
license, shall be dual licensed as above, without any additional terms or
|
||||
conditions.
|
|
@ -1,22 +0,0 @@
|
|||
[package]
|
||||
name = "zip32"
|
||||
version = "0.0.0"
|
||||
authors = [
|
||||
"Jack Grigg <jack@electriccoin.co>",
|
||||
"Kris Nuttycombe <kris@electriccoin.co>",
|
||||
]
|
||||
description = "Library for implementing shielded hierarchical deterministic wallets"
|
||||
documentation = "https://docs.rs/zip32/"
|
||||
homepage = "https://github.com/zcash-hackworks/zip32"
|
||||
repository = "https://github.com/zcash-hackworks/zip32"
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
blake2b_simd.workspace = true
|
||||
memuse.workspace = true
|
||||
subtle.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
assert_matches.workspace = true
|
|
@ -1,201 +0,0 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -1,23 +0,0 @@
|
|||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
|
@ -1,17 +0,0 @@
|
|||
# zip32 [![Crates.io](https://img.shields.io/crates/v/zip32.svg)](https://crates.io/crates/zip32) #
|
||||
|
||||
## License
|
||||
|
||||
Licensed under either of
|
||||
|
||||
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
||||
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||
|
||||
at your option.
|
||||
|
||||
### Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally
|
||||
submitted for inclusion in the work by you, as defined in the Apache-2.0
|
||||
license, shall be dual licensed as above, without any additional terms or
|
||||
conditions.
|
|
@ -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`"
|
||||
);
|
||||
}
|
190
zip32/src/lib.rs
190
zip32/src/lib.rs
|
@ -1,190 +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;
|
||||
|
||||
/// 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(_));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue