From 4b3d2bbe5e586414ad6da1704919f8939fa749b0 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 17 Dec 2024 17:33:47 +0000 Subject: [PATCH] transparent: Add `Bip32Derivation::extract_bip_44_fields` --- zcash_transparent/CHANGELOG.md | 3 +++ zcash_transparent/src/pczt.rs | 46 +++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/zcash_transparent/CHANGELOG.md b/zcash_transparent/CHANGELOG.md index 35cc8874e..46dc2a245 100644 --- a/zcash_transparent/CHANGELOG.md +++ b/zcash_transparent/CHANGELOG.md @@ -11,6 +11,9 @@ and this library adheres to Rust's notion of - `zcash_transparent::keys::AccountPubKey::derive_pubkey_at_bip32_path` now returns the correct result for valid paths instead of an error or panic. +### Added +- `zcash_transparent::pczt::Bip32Derivation::extract_bip_44_fields` + ## [0.1.0] - 2024-12-16 The entries below are relative to the `zcash_primitives` crate as of the tag diff --git a/zcash_transparent/src/pczt.rs b/zcash_transparent/src/pczt.rs index faf053031..5f6837613 100644 --- a/zcash_transparent/src/pczt.rs +++ b/zcash_transparent/src/pczt.rs @@ -8,7 +8,11 @@ use bip32::ChildNumber; use getset::Getters; use zcash_protocol::{value::Zatoshis, TxId}; -use crate::{address::Script, sighash::SighashType}; +use crate::{ + address::Script, + keys::{NonHardenedChildIndex, TransparentKeyScope}, + sighash::SighashType, +}; mod parse; pub use parse::ParseError; @@ -230,3 +234,43 @@ pub struct Bip32Derivation { /// The sequence of indices corresponding to the HD path. derivation_path: Vec, } + +impl Bip32Derivation { + /// Extracts the BIP 44 account index, scope, and address index from this derivation + /// path. + /// + /// Returns `None` if the seed fingerprints don't match, or if this is a non-standard + /// derivation path. + pub fn extract_bip_44_fields( + &self, + seed_fp: &zip32::fingerprint::SeedFingerprint, + expected_coin_type: ChildNumber, + ) -> Option<(zip32::AccountId, TransparentKeyScope, NonHardenedChildIndex)> { + if self.seed_fingerprint == seed_fp.to_bytes() { + match &self.derivation_path[..] { + [purpose, coin_type, account_index, scope, address_index] + if purpose == &ChildNumber(44 | ChildNumber::HARDENED_FLAG) + && coin_type.is_hardened() + && coin_type == &expected_coin_type + && account_index.is_hardened() + && !scope.is_hardened() + && !address_index.is_hardened() => + { + let account_index = zip32::AccountId::try_from(account_index.index()) + .expect("account_index is hardened"); + + let scope = + TransparentKeyScope::custom(scope.index()).expect("scope is not hardened"); + + let address_index = NonHardenedChildIndex::from_index(address_index.index()) + .expect("address_index is not hardened"); + + Some((account_index, scope, address_index)) + } + _ => None, + } + } else { + None + } + } +}