Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion key-wallet/src/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
use async_trait::async_trait;
use secp256k1::{ecdsa, PublicKey};

use crate::bip32::DerivationPath;
use crate::bip32::{DerivationPath, ExtendedPubKey};

/// A signing method a [`Signer`] can perform.
///
Expand Down Expand Up @@ -125,4 +125,19 @@ pub trait Signer: Send + Sync {
/// keys) that the caller later references when signing Platform state
/// transitions.
async fn public_key(&self, path: &DerivationPath) -> Result<PublicKey, Self::Error>;

/// Return the BIP-32 extended public key at `path` — the public point
/// plus the chain code and parent fingerprint, so the caller can
/// non-hardened-derive a whole range of descendants from a single
/// (possibly hardened) request without further signer round-trips
/// (e.g. DashPay contact payment addresses under
/// `m/9'/coin'/15'/account'/sender/recipient`).
///
/// Distinct from [`Self::public_key`], which returns only the leaf point
/// (no chain code). A signer that cannot export an extended public key at
/// a hardened path should return an error rather than panic.
async fn extended_public_key(
&self,
path: &DerivationPath,
) -> Result<ExtendedPubKey, Self::Error>;
}
19 changes: 19 additions & 0 deletions key-wallet/src/wallet/managed_wallet_info/asset_lock_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,19 @@ mod tests {
.map_err(|e| e.to_string())?;
Ok(secp256k1::PublicKey::from_secret_key(&secp, &xpriv.private_key))
}

async fn extended_public_key(
&self,
path: &DerivationPath,
) -> Result<crate::bip32::ExtendedPubKey, Self::Error> {
let secp = secp256k1::Secp256k1::new();
let xpriv = self
.root
.to_extended_priv_key(self.network)
.derive_priv(&secp, path)
.map_err(|e| e.to_string())?;
Ok(crate::bip32::ExtendedPubKey::from_priv(&secp, &xpriv))
}
}

#[tokio::test]
Expand Down Expand Up @@ -537,6 +550,12 @@ mod tests {
async fn public_key(&self, _: &DerivationPath) -> Result<PublicKey, Self::Error> {
unreachable!()
}
async fn extended_public_key(
&self,
_: &DerivationPath,
) -> Result<crate::bip32::ExtendedPubKey, Self::Error> {
unreachable!()
}
}

let (wallet, mut info) = test_wallet_and_info();
Expand Down
40 changes: 40 additions & 0 deletions key-wallet/src/wallet/managed_wallet_info/transaction_building.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,19 @@ mod tests {
.map_err(|e| e.to_string())?;
Ok(secp256k1::PublicKey::from_secret_key(&secp, &xpriv.private_key))
}

async fn extended_public_key(
&self,
path: &DerivationPath,
) -> Result<crate::bip32::ExtendedPubKey, Self::Error> {
let secp = secp256k1::Secp256k1::new();
let xpriv = self
.root
.to_extended_priv_key(self.network)
.derive_priv(&secp, path)
.map_err(|e| e.to_string())?;
Ok(crate::bip32::ExtendedPubKey::from_priv(&secp, &xpriv))
}
}

fn root_from(wallet: &Wallet) -> crate::wallet::root_extended_keys::RootExtendedPrivKey {
Expand All @@ -396,6 +409,27 @@ mod tests {
}
}

#[tokio::test]
async fn in_memory_signer_extended_public_key_matches_wallet_derivation() {
use crate::bip32::DerivationPath;
use std::str::FromStr;
let (wallet, _info) = test_wallet_and_info();
let signer = InMemorySigner {
root: root_from(&wallet),
network: Network::Testnet,
};
// A hardened path — only derivable with the private key, which is the
// whole point of exposing extended-pubkey export on the signer.
let path = DerivationPath::from_str("m/9'/1'/15'/0'").expect("valid path");
let from_signer =
signer.extended_public_key(&path).await.expect("signer extended_public_key");
let from_wallet = wallet.derive_extended_public_key(&path).expect("wallet extended pubkey");
assert_eq!(
from_signer, from_wallet,
"signer xpub at a hardened path must equal the wallet's own derivation"
);
}

fn dest_outputs(amount: u64) -> Vec<(Address<NetworkUnchecked>, u64)> {
let dest = Address::from_str("yTb47qEBpNmgXvYYsHEN4nh8yJwa5iC4Cs").unwrap();
vec![(dest, amount)]
Expand Down Expand Up @@ -443,6 +477,12 @@ mod tests {
async fn public_key(&self, _: &DerivationPath) -> Result<PublicKey, Self::Error> {
unreachable!()
}
async fn extended_public_key(
&self,
_: &DerivationPath,
) -> Result<crate::bip32::ExtendedPubKey, Self::Error> {
unreachable!()
}
}

let (wallet, mut info) = test_wallet_and_info();
Expand Down
Loading