Merge pull request #438 from nuttycom/sapling_try_diversifier
Make it possible for callers to search for valid Sapling diversifiers.
This commit is contained in:
commit
3d7b9dc9aa
|
@ -65,7 +65,7 @@ fn main() {
|
|||
return;
|
||||
};
|
||||
|
||||
let (diversifier_index, address) = extfvk.address(opts.diversifier_index).unwrap();
|
||||
let (diversifier_index, address) = extfvk.find_address(opts.diversifier_index).unwrap();
|
||||
println!(
|
||||
"# Diversifier index: {}",
|
||||
encode_diversifier_index(&diversifier_index)
|
||||
|
|
|
@ -124,7 +124,7 @@ where
|
|||
///
|
||||
/// let account = AccountId(0);
|
||||
/// let extsk = spending_key(&[0; 32][..], COIN_TYPE, account.0);
|
||||
/// let to = extsk.default_address().unwrap().1.into();
|
||||
/// let to = extsk.default_address().1.into();
|
||||
///
|
||||
/// let data_file = NamedTempFile::new().unwrap();
|
||||
/// let db_read = WalletDb::for_path(data_file, Network::TestNetwork).unwrap();
|
||||
|
|
|
@ -357,7 +357,7 @@ mod tests {
|
|||
value: Amount,
|
||||
tx_after: bool,
|
||||
) -> CompactBlock {
|
||||
let to = extfvk.default_address().unwrap().1;
|
||||
let to = extfvk.default_address().1;
|
||||
|
||||
// Create a fake Note for the account
|
||||
let mut rng = OsRng;
|
||||
|
|
|
@ -537,7 +537,7 @@ mod tests {
|
|||
|
||||
// Create a second fake CompactBlock spending value from the address
|
||||
let extsk2 = ExtendedSpendingKey::master(&[0]);
|
||||
let to2 = extsk2.default_address().unwrap().1;
|
||||
let to2 = extsk2.default_address().1;
|
||||
let value2 = Amount::from_u64(2).unwrap();
|
||||
insert_into_cache(
|
||||
&db_cache,
|
||||
|
|
|
@ -545,7 +545,7 @@ fn address_from_extfvk<P: consensus::Parameters>(
|
|||
params: &P,
|
||||
extfvk: &ExtendedFullViewingKey,
|
||||
) -> String {
|
||||
let addr = extfvk.default_address().unwrap().1;
|
||||
let addr = extfvk.default_address().1;
|
||||
encode_payment_address(params.hrp_sapling_payment_address(), &addr)
|
||||
}
|
||||
|
||||
|
@ -607,7 +607,7 @@ mod tests {
|
|||
extfvk: ExtendedFullViewingKey,
|
||||
value: Amount,
|
||||
) -> (CompactBlock, Nullifier) {
|
||||
let to = extfvk.default_address().unwrap().1;
|
||||
let to = extfvk.default_address().1;
|
||||
|
||||
// Create a fake Note for the account
|
||||
let mut rng = OsRng;
|
||||
|
@ -698,7 +698,7 @@ mod tests {
|
|||
|
||||
// Create a fake Note for the change
|
||||
ctx.outputs.push({
|
||||
let change_addr = extfvk.default_address().unwrap().1;
|
||||
let change_addr = extfvk.default_address().1;
|
||||
let rseed = generate_random_rseed(&network(), height, &mut rng);
|
||||
let note = Note {
|
||||
g_d: change_addr.diversifier().g_d().unwrap(),
|
||||
|
|
|
@ -310,6 +310,6 @@ mod tests {
|
|||
|
||||
// The account's address should be in the data DB
|
||||
let pa = get_address(&db_data, AccountId(0)).unwrap();
|
||||
assert_eq!(pa.unwrap(), extsk.default_address().unwrap().1);
|
||||
assert_eq!(pa.unwrap(), extsk.default_address().1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -200,7 +200,7 @@ mod tests {
|
|||
ExtendedFullViewingKey::from(&extsk1),
|
||||
];
|
||||
init_accounts_table(&db_data, &extfvks).unwrap();
|
||||
let to = extsk0.default_address().unwrap().1.into();
|
||||
let to = extsk0.default_address().1.into();
|
||||
|
||||
// Invalid extsk for the given account should cause an error
|
||||
let mut db_write = db_data.get_update_ops().unwrap();
|
||||
|
@ -245,7 +245,7 @@ mod tests {
|
|||
let extsk = ExtendedSpendingKey::master(&[]);
|
||||
let extfvks = [ExtendedFullViewingKey::from(&extsk)];
|
||||
init_accounts_table(&db_data, &extfvks).unwrap();
|
||||
let to = extsk.default_address().unwrap().1.into();
|
||||
let to = extsk.default_address().1.into();
|
||||
|
||||
// We cannot do anything if we aren't synchronised
|
||||
let mut db_write = db_data.get_update_ops().unwrap();
|
||||
|
@ -283,7 +283,7 @@ mod tests {
|
|||
let extsk = ExtendedSpendingKey::master(&[]);
|
||||
let extfvks = [ExtendedFullViewingKey::from(&extsk)];
|
||||
init_accounts_table(&db_data, &extfvks).unwrap();
|
||||
let to = extsk.default_address().unwrap().1.into();
|
||||
let to = extsk.default_address().1.into();
|
||||
|
||||
// Account balance should be zero
|
||||
assert_eq!(get_balance(&db_data, AccountId(0)).unwrap(), Amount::zero());
|
||||
|
@ -367,7 +367,7 @@ mod tests {
|
|||
|
||||
// Spend fails because there are insufficient verified notes
|
||||
let extsk2 = ExtendedSpendingKey::master(&[]);
|
||||
let to = extsk2.default_address().unwrap().1.into();
|
||||
let to = extsk2.default_address().1.into();
|
||||
match create_spend_to_address(
|
||||
&mut db_write,
|
||||
&tests::network(),
|
||||
|
@ -469,7 +469,7 @@ mod tests {
|
|||
|
||||
// Send some of the funds to another address
|
||||
let extsk2 = ExtendedSpendingKey::master(&[]);
|
||||
let to = extsk2.default_address().unwrap().1.into();
|
||||
let to = extsk2.default_address().1.into();
|
||||
create_spend_to_address(
|
||||
&mut db_write,
|
||||
&tests::network(),
|
||||
|
@ -589,7 +589,7 @@ mod tests {
|
|||
assert_eq!(get_balance(&db_data, AccountId(0)).unwrap(), value);
|
||||
|
||||
let extsk2 = ExtendedSpendingKey::master(&[]);
|
||||
let addr2 = extsk2.default_address().unwrap().1;
|
||||
let addr2 = extsk2.default_address().1;
|
||||
let to = addr2.clone().into();
|
||||
|
||||
let send_and_recover_with_policy = |db_write: &mut DataConnStmtCache<'_, _>, ovk_policy| {
|
||||
|
|
|
@ -809,7 +809,7 @@ mod tests {
|
|||
|
||||
// create some inputs to spend
|
||||
let extsk = ExtendedSpendingKey::master(&[]);
|
||||
let to = extsk.default_address().unwrap().1;
|
||||
let to = extsk.default_address().1;
|
||||
let note1 = to
|
||||
.create_note(110000, Rseed::BeforeZip212(jubjub::Fr::random(&mut rng)))
|
||||
.unwrap();
|
||||
|
|
|
@ -53,6 +53,24 @@ and this library adheres to Rust's notion of
|
|||
crate, which can be found in the `components` directory.
|
||||
- `zcash_primitives::transaction::components::Amount` now implements
|
||||
`memuse::DynamicUsage`, to enable `orchard::Bundle<_, Amount>::dynamic_usage`.
|
||||
- `zcash_primitives::zip32::diversifier` has been renamed to `find_sapling_diversifier`
|
||||
and `sapling_diversifier` has been added. `find_sapling_diversifier` searches the
|
||||
diversifier index space, whereas `sapling_diversifier` just attempts to use the
|
||||
provided diversifier index and returns `None` if it does not produce a valid
|
||||
diversifier.
|
||||
- `zcash_primitives::zip32::DiversifierKey::diversifier` has been renamed to
|
||||
`find_diversifier` and the `diversifier` method has new semantics.
|
||||
`find_diversifier` searches the diversifier index space to find a diversifier
|
||||
index which produces a valid diversifier, whereas `diversifier` just attempts
|
||||
to use the provided diversifier index and returns `None` if it does not
|
||||
produce a valid diversifier.
|
||||
- `zcash_primitives::zip32::ExtendedFullViewingKey::address` has been renamed
|
||||
to `find_address` and the `address` method has new semantics. `find_address`
|
||||
searches the diversifier index space until it obtains a valid diversifier,
|
||||
and returns the address corresponding to that diversifier, whereas `address`
|
||||
just attempts to create an address corresponding to the diversifier derived
|
||||
from the provided diversifier index and returns `None` if the provided index
|
||||
does not produce a valid diversifier.
|
||||
|
||||
### Changed
|
||||
- MSRV is now 1.51.0.
|
||||
|
|
|
@ -546,10 +546,7 @@ pub mod testing {
|
|||
}
|
||||
|
||||
pub fn arb_payment_address() -> impl Strategy<Value = PaymentAddress> {
|
||||
arb_extended_spending_key()
|
||||
.prop_map(|sk| sk.default_address().map(|(_, a)| a))
|
||||
.prop_filter("A valid payment address is required.", |r| r.is_ok())
|
||||
.prop_map(|r| r.unwrap())
|
||||
arb_extended_spending_key().prop_map(|sk| sk.default_address().1)
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
|
|
|
@ -199,7 +199,7 @@ pub mod testing {
|
|||
prop_compose! {
|
||||
pub fn arb_shielded_addr()(extsk in arb_extended_spending_key()) -> PaymentAddress {
|
||||
let extfvk = ExtendedFullViewingKey::from(&extsk);
|
||||
extfvk.default_address().unwrap().1
|
||||
extfvk.default_address().1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -496,7 +496,7 @@ mod tests {
|
|||
let extsk = ExtendedSpendingKey::master(&[]);
|
||||
let extfvk = ExtendedFullViewingKey::from(&extsk);
|
||||
let ovk = extfvk.fvk.ovk;
|
||||
let to = extfvk.default_address().unwrap().1;
|
||||
let to = extfvk.default_address().1;
|
||||
|
||||
let sapling_activation_height = TEST_NETWORK
|
||||
.activation_height(NetworkUpgrade::Sapling)
|
||||
|
@ -549,7 +549,7 @@ mod tests {
|
|||
fn binding_sig_present_if_shielded_spend() {
|
||||
let extsk = ExtendedSpendingKey::master(&[]);
|
||||
let extfvk = ExtendedFullViewingKey::from(&extsk);
|
||||
let to = extfvk.default_address().unwrap().1;
|
||||
let to = extfvk.default_address().1;
|
||||
|
||||
let mut rng = OsRng;
|
||||
|
||||
|
@ -622,7 +622,7 @@ mod tests {
|
|||
|
||||
let extfvk = ExtendedFullViewingKey::from(&extsk);
|
||||
let ovk = Some(extfvk.fvk.ovk);
|
||||
let to = extfvk.default_address().unwrap().1;
|
||||
let to = extfvk.default_address().1;
|
||||
|
||||
// Fail if there is only a Sapling output
|
||||
// 0.0005 z-ZEC out, 0.00001 t-ZEC fee
|
||||
|
|
|
@ -140,29 +140,40 @@ impl DiversifierKey {
|
|||
DiversifierKey(dk)
|
||||
}
|
||||
|
||||
fn try_diversifier_internal(ff: &FF1<Aes256>, j: DiversifierIndex) -> Option<Diversifier> {
|
||||
// Generate d_j
|
||||
let enc = ff
|
||||
.encrypt(&[], &BinaryNumeralString::from_bytes_le(&j.0[..]))
|
||||
.unwrap();
|
||||
let mut d_j = [0; 11];
|
||||
d_j.copy_from_slice(&enc.to_bytes_le());
|
||||
let diversifier = Diversifier(d_j);
|
||||
|
||||
diversifier.g_d().map(|_| diversifier)
|
||||
}
|
||||
|
||||
/// Attempts to produce a diversifier at the given index. Returns None
|
||||
/// if the index does not produce a valid diversifier.
|
||||
pub fn diversifier(&self, j: DiversifierIndex) -> Option<Diversifier> {
|
||||
let ff = FF1::<Aes256>::new(&self.0, 2).unwrap();
|
||||
Self::try_diversifier_internal(&ff, j)
|
||||
}
|
||||
|
||||
/// Returns the first index starting from j that generates a valid
|
||||
/// diversifier, along with the corresponding diversifier. Returns
|
||||
/// an error if the diversifier space is exhausted.
|
||||
pub fn diversifier(
|
||||
/// `None` if the diversifier space contains no valid diversifiers
|
||||
/// at or above the specified diversifier index.
|
||||
pub fn find_diversifier(
|
||||
&self,
|
||||
mut j: DiversifierIndex,
|
||||
) -> Result<(DiversifierIndex, Diversifier), ()> {
|
||||
) -> Option<(DiversifierIndex, Diversifier)> {
|
||||
let ff = FF1::<Aes256>::new(&self.0, 2).unwrap();
|
||||
loop {
|
||||
// Generate d_j
|
||||
let enc = ff
|
||||
.encrypt(&[], &BinaryNumeralString::from_bytes_le(&j.0[..]))
|
||||
.unwrap();
|
||||
let mut d_j = [0; 11];
|
||||
d_j.copy_from_slice(&enc.to_bytes_le());
|
||||
let d_j = Diversifier(d_j);
|
||||
|
||||
// Return (j, d_j) if valid, else increment j and try again
|
||||
match d_j.g_d() {
|
||||
Some(_) => return Ok((j, d_j)),
|
||||
match Self::try_diversifier_internal(&ff, j) {
|
||||
Some(d_j) => return Some((j, d_j)),
|
||||
None => {
|
||||
if j.increment().is_err() {
|
||||
return Err(());
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -341,7 +352,9 @@ impl ExtendedSpendingKey {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn default_address(&self) -> Result<(DiversifierIndex, PaymentAddress), ()> {
|
||||
/// Returns the address with the lowest valid diversifier index, along with
|
||||
/// the diversifier index that generated that address.
|
||||
pub fn default_address(&self) -> (DiversifierIndex, PaymentAddress) {
|
||||
ExtendedFullViewingKey::from(self).default_address()
|
||||
}
|
||||
}
|
||||
|
@ -428,16 +441,30 @@ impl ExtendedFullViewingKey {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn address(&self, j: DiversifierIndex) -> Result<(DiversifierIndex, PaymentAddress), ()> {
|
||||
let (j, d_j) = self.dk.diversifier(j)?;
|
||||
match self.fvk.vk.to_payment_address(d_j) {
|
||||
Some(addr) => Ok((j, addr)),
|
||||
None => Err(()),
|
||||
}
|
||||
/// Attempt to produce a payment address given the specified diversifier
|
||||
/// index, and return None if the specified index does not produce a valid
|
||||
/// diversifier.
|
||||
pub fn address(&self, j: DiversifierIndex) -> Option<PaymentAddress> {
|
||||
self.dk
|
||||
.diversifier(j)
|
||||
.and_then(|d_j| self.fvk.vk.to_payment_address(d_j))
|
||||
}
|
||||
|
||||
pub fn default_address(&self) -> Result<(DiversifierIndex, PaymentAddress), ()> {
|
||||
self.address(DiversifierIndex::new())
|
||||
/// Search the diversifier space starting at diversifier index `j` for
|
||||
/// one which will produce a valid diversifier, and return the payment address
|
||||
/// constructed using that diversifier along with the index at which the
|
||||
/// valid diversifier was found.
|
||||
pub fn find_address(&self, j: DiversifierIndex) -> Option<(DiversifierIndex, PaymentAddress)> {
|
||||
let (j, d_j) = self.dk.find_diversifier(j)?;
|
||||
self.fvk.vk.to_payment_address(d_j).map(|addr| (j, addr))
|
||||
}
|
||||
|
||||
/// Returns the payment address corresponding to the smallest valid diversifier
|
||||
/// index, along with that index.
|
||||
pub fn default_address(&self) -> (DiversifierIndex, PaymentAddress) {
|
||||
// This unwrap is safe, if you have to search the 2^88 space of
|
||||
// diversifiers it'll never return anyway.
|
||||
self.find_address(DiversifierIndex::new()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -518,31 +545,74 @@ mod tests {
|
|||
let d_3 = [60, 253, 170, 8, 171, 147, 220, 31, 3, 144, 34];
|
||||
|
||||
// j = 0
|
||||
let (j, d_j) = dk.diversifier(j_0).unwrap();
|
||||
let d_j = dk.diversifier(j_0).unwrap();
|
||||
assert_eq!(d_j.0, d_0);
|
||||
|
||||
// j = 1
|
||||
assert_eq!(dk.diversifier(j_1), None);
|
||||
|
||||
// j = 2
|
||||
assert_eq!(dk.diversifier(j_2), None);
|
||||
|
||||
// j = 3
|
||||
let d_j = dk.diversifier(j_3).unwrap();
|
||||
assert_eq!(d_j.0, d_3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
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]);
|
||||
// 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];
|
||||
|
||||
// j = 0
|
||||
let (j, d_j) = dk.find_diversifier(j_0).unwrap();
|
||||
assert_eq!(j, j_0);
|
||||
assert_eq!(d_j.0, d_0);
|
||||
|
||||
// j = 1
|
||||
let (j, d_j) = dk.diversifier(j_1).unwrap();
|
||||
let (j, d_j) = dk.find_diversifier(j_1).unwrap();
|
||||
assert_eq!(j, j_3);
|
||||
assert_eq!(d_j.0, d_3);
|
||||
|
||||
// j = 2
|
||||
let (j, d_j) = dk.diversifier(j_2).unwrap();
|
||||
let (j, d_j) = dk.find_diversifier(j_2).unwrap();
|
||||
assert_eq!(j, j_3);
|
||||
assert_eq!(d_j.0, d_3);
|
||||
|
||||
// j = 3
|
||||
let (j, d_j) = dk.diversifier(j_3).unwrap();
|
||||
let (j, d_j) = dk.find_diversifier(j_3).unwrap();
|
||||
assert_eq!(j, j_3);
|
||||
assert_eq!(d_j.0, d_3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn address() {
|
||||
let seed = [0; 32];
|
||||
let xsk_m = ExtendedSpendingKey::master(&seed);
|
||||
let xfvk_m = ExtendedFullViewingKey::from(&xsk_m);
|
||||
let j_0 = DiversifierIndex::new();
|
||||
let addr_m = xfvk_m.address(j_0).unwrap();
|
||||
assert_eq!(
|
||||
addr_m.diversifier().0,
|
||||
// Computed using this Rust implementation
|
||||
[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]);
|
||||
assert_eq!(xfvk_m.address(j_1), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_address() {
|
||||
let seed = [0; 32];
|
||||
let xsk_m = ExtendedSpendingKey::master(&seed);
|
||||
let (j_m, addr_m) = xsk_m.default_address().unwrap();
|
||||
let (j_m, addr_m) = xsk_m.default_address();
|
||||
assert_eq!(j_m.0, [0; 11]);
|
||||
assert_eq!(
|
||||
addr_m.diversifier().0,
|
||||
|
@ -1039,34 +1109,31 @@ mod tests {
|
|||
|
||||
// d0
|
||||
let mut di = DiversifierIndex::new();
|
||||
match xfvk.dk.diversifier(di) {
|
||||
Ok((l, d)) if l == di => assert_eq!(d.0, tv.d0.unwrap()),
|
||||
Ok((_, _)) => assert!(tv.d0.is_none()),
|
||||
Err(()) => panic!(),
|
||||
match xfvk.dk.find_diversifier(di).unwrap() {
|
||||
(l, d) if l == di => assert_eq!(d.0, tv.d0.unwrap()),
|
||||
(_, _) => assert!(tv.d0.is_none()),
|
||||
}
|
||||
|
||||
// d1
|
||||
di.increment().unwrap();
|
||||
match xfvk.dk.diversifier(di) {
|
||||
Ok((l, d)) if l == di => assert_eq!(d.0, tv.d1.unwrap()),
|
||||
Ok((_, _)) => assert!(tv.d1.is_none()),
|
||||
Err(()) => panic!(),
|
||||
match xfvk.dk.find_diversifier(di).unwrap() {
|
||||
(l, d) if l == di => assert_eq!(d.0, tv.d1.unwrap()),
|
||||
(_, _) => assert!(tv.d1.is_none()),
|
||||
}
|
||||
|
||||
// d2
|
||||
di.increment().unwrap();
|
||||
match xfvk.dk.diversifier(di) {
|
||||
Ok((l, d)) if l == di => assert_eq!(d.0, tv.d2.unwrap()),
|
||||
Ok((_, _)) => assert!(tv.d2.is_none()),
|
||||
Err(()) => panic!(),
|
||||
match xfvk.dk.find_diversifier(di).unwrap() {
|
||||
(l, d) if l == di => assert_eq!(d.0, tv.d2.unwrap()),
|
||||
(_, _) => assert!(tv.d2.is_none()),
|
||||
}
|
||||
|
||||
// dmax
|
||||
let dmax = DiversifierIndex([0xff; 11]);
|
||||
match xfvk.dk.diversifier(dmax) {
|
||||
Ok((l, d)) if l == dmax => assert_eq!(d.0, tv.dmax.unwrap()),
|
||||
Ok((_, _)) => panic!(),
|
||||
Err(_) => assert!(tv.dmax.is_none()),
|
||||
match xfvk.dk.find_diversifier(dmax) {
|
||||
Some((l, d)) if l == dmax => assert_eq!(d.0, tv.dmax.unwrap()),
|
||||
Some((_, _)) => panic!(),
|
||||
None => assert!(tv.dmax.is_none()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue