use std::str::FromStr;
use bdk_wallet::miniscript::descriptor::{Descriptor, DescriptorPublicKey, DescriptorSecretKey};
use bitcoin::bip32::{ChildNumber, DerivationPath, Fingerprint, Xpriv};
use bitcoin::hashes::{sha256, Hash};
use bitcoin::secp256k1::{All, PublicKey, Secp256k1, SecretKey};
use bitcoin::Network;
use ddk_manager::SimpleSigner;
use super::error::ContractError;
use super::types::DlcInputSigningKey;
const CHILD_NUMBER_RANGE: u32 = 3_400;
const DLC_BASE_PATH: &str = "m/420'/0'/0'";
const KEYS_ID_TAG: &[u8] = b"CONTRACT_SIGNER_KEY_ID_V0";
#[derive(Clone)]
pub struct ContractKeyProvider {
xprv: Xpriv,
fingerprint: Fingerprint,
secp: Secp256k1<All>,
dlc_path: DerivationPath,
}
impl ContractKeyProvider {
pub fn from_xprv(xprv: Xpriv) -> Self {
let secp = Secp256k1::new();
let fingerprint = xprv.fingerprint(&secp);
let dlc_path = DerivationPath::from_str(DLC_BASE_PATH).expect("valid base path");
Self {
xprv,
fingerprint,
secp,
dlc_path,
}
}
pub fn from_seed(seed: &[u8], network: Network) -> Result<Self, ContractError> {
let xprv = Xpriv::new_master(network, seed)
.map_err(|e| ContractError::Key(format!("invalid seed: {e}")))?;
Ok(Self::from_xprv(xprv))
}
pub fn from_mnemonic(
mnemonic: &str,
passphrase: Option<&str>,
network: Network,
) -> Result<Self, ContractError> {
let mnemonic = bip39::Mnemonic::from_str(mnemonic)
.map_err(|e| ContractError::Key(format!("invalid mnemonic: {e}")))?;
let seed = mnemonic.to_seed(passphrase.unwrap_or(""));
Self::from_seed(&seed, network)
}
pub fn from_descriptor(descriptor: &str) -> Result<Self, ContractError> {
let secp = Secp256k1::new();
let (_, key_map) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, descriptor)
.map_err(|e| ContractError::Descriptor(e.to_string()))?;
let xprv = key_map
.values()
.find_map(|secret| match secret {
DescriptorSecretKey::XPrv(xkey) => Some(xkey.xkey),
_ => None,
})
.ok_or_else(|| {
ContractError::Descriptor(
"descriptor does not contain an extended private key".to_string(),
)
})?;
Ok(Self::from_xprv(xprv))
}
pub fn keys_id(&self, temporary_contract_id: [u8; 32]) -> [u8; 32] {
let mut input = Vec::with_capacity(4 + 32 + KEYS_ID_TAG.len());
input.extend_from_slice(self.fingerprint.as_bytes());
input.extend_from_slice(&temporary_contract_id);
input.extend_from_slice(KEYS_ID_TAG);
sha256::Hash::hash(&input).to_byte_array()
}
pub fn funding_secret_key_for_keys_id(
&self,
keys_id: [u8; 32],
) -> Result<SecretKey, ContractError> {
let (level_1, level_2, level_3) = hierarchical_indices(keys_id);
let path = self.hierarchical_derivation_path(level_1, level_2, level_3)?;
let base_key = self
.xprv
.derive_priv(&self.secp, &path)
.map_err(|e| ContractError::Bip32(e.to_string()))?
.private_key;
self.harden(&base_key, level_1, level_2, level_3)
}
pub fn funding_secret_key(
&self,
temporary_contract_id: [u8; 32],
) -> Result<SecretKey, ContractError> {
self.funding_secret_key_for_keys_id(self.keys_id(temporary_contract_id))
}
pub fn funding_pubkey(
&self,
temporary_contract_id: [u8; 32],
) -> Result<PublicKey, ContractError> {
Ok(self
.funding_secret_key(temporary_contract_id)?
.public_key(&self.secp))
}
pub fn dlc_input_signing_key(
&self,
prior_temporary_contract_id: [u8; 32],
input_serial_id: u64,
) -> Result<DlcInputSigningKey, ContractError> {
Ok(DlcInputSigningKey {
input_serial_id,
prior_funding_secret_key: self.funding_secret_key(prior_temporary_contract_id)?,
})
}
fn hierarchical_derivation_path(
&self,
level_1: u32,
level_2: u32,
level_3: u32,
) -> Result<DerivationPath, ContractError> {
let child = |index: u32| {
ChildNumber::from_normal_idx(index)
.map_err(|e| ContractError::Key(format!("invalid derivation index: {e}")))
};
Ok(self
.dlc_path
.extend([child(level_1)?, child(level_2)?, child(level_3)?]))
}
fn harden(
&self,
base_key: &SecretKey,
level_1: u32,
level_2: u32,
level_3: u32,
) -> Result<SecretKey, ContractError> {
let mut input = Vec::new();
input.extend_from_slice(self.fingerprint.as_bytes());
input.extend_from_slice(&base_key.secret_bytes());
input.extend_from_slice(&level_1.to_be_bytes());
input.extend_from_slice(&level_2.to_be_bytes());
input.extend_from_slice(&level_3.to_be_bytes());
SecretKey::from_slice(sha256::Hash::hash(&input).as_ref())
.map_err(|e| ContractError::Key(format!("invalid derived key: {e}")))
}
}
fn hierarchical_indices(keys_id: [u8; 32]) -> (u32, u32, u32) {
let level = |offset: usize| {
u32::from_be_bytes([
keys_id[offset],
keys_id[offset + 1],
keys_id[offset + 2],
keys_id[offset + 3],
]) % CHILD_NUMBER_RANGE
};
(level(0), level(4), level(8))
}
impl ddk_manager::ContractSignerProvider for ContractKeyProvider {
type Signer = SimpleSigner;
fn derive_signer_key_id(&self, _is_offer_party: bool, temp_id: [u8; 32]) -> [u8; 32] {
self.keys_id(temp_id)
}
fn derive_contract_signer(
&self,
key_id: [u8; 32],
) -> Result<Self::Signer, ddk_manager::error::Error> {
let secret_key = self
.funding_secret_key_for_keys_id(key_id)
.map_err(|e| ddk_manager::error::Error::InvalidParameters(e.to_string()))?;
Ok(SimpleSigner::new(secret_key))
}
fn get_secret_key_for_pubkey(
&self,
_pubkey: &PublicKey,
) -> Result<SecretKey, ddk_manager::error::Error> {
unreachable!("get_secret_key_for_pubkey is only used for channels")
}
fn get_new_secret_key(&self) -> Result<SecretKey, ddk_manager::error::Error> {
unreachable!("get_new_secret_key is only used for channels")
}
}
#[cfg(test)]
mod tests {
use super::*;
const TEMP_A: [u8; 32] = [0xA1; 32];
const TEMP_B: [u8; 32] = [0xB2; 32];
const MNEMONIC: &str =
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
fn provider() -> ContractKeyProvider {
ContractKeyProvider::from_mnemonic(MNEMONIC, None, Network::Regtest).unwrap()
}
#[test]
fn derivation_is_deterministic_and_recoverable() {
let a = provider().funding_secret_key(TEMP_A).unwrap();
let b = provider().funding_secret_key(TEMP_A).unwrap();
assert_eq!(a, b);
assert_ne!(a, provider().funding_secret_key(TEMP_B).unwrap());
}
#[test]
fn funding_pubkey_matches_secret_key() {
let keys = provider();
let secp = Secp256k1::new();
let sk = keys.funding_secret_key(TEMP_A).unwrap();
assert_eq!(keys.funding_pubkey(TEMP_A).unwrap(), sk.public_key(&secp));
}
#[test]
fn dlc_input_signing_key_carries_the_recovered_prior_key() {
let keys = provider();
let signing_key = keys.dlc_input_signing_key(TEMP_A, 900).unwrap();
assert_eq!(signing_key.input_serial_id, 900);
assert_eq!(
signing_key.prior_funding_secret_key,
keys.funding_secret_key(TEMP_A).unwrap()
);
}
#[test]
fn constructors_agree() {
let mnemonic = bip39::Mnemonic::from_str(MNEMONIC).unwrap();
let seed = mnemonic.to_seed("");
let from_seed = ContractKeyProvider::from_seed(&seed, Network::Regtest).unwrap();
let from_xprv =
ContractKeyProvider::from_xprv(Xpriv::new_master(Network::Regtest, &seed).unwrap());
assert_eq!(
provider().funding_secret_key(TEMP_A).unwrap(),
from_seed.funding_secret_key(TEMP_A).unwrap()
);
assert_eq!(
from_seed.funding_secret_key(TEMP_A).unwrap(),
from_xprv.funding_secret_key(TEMP_A).unwrap()
);
}
#[test]
fn hierarchical_indices_are_deterministic_and_bounded() {
let key_id = [
0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04,
0x05, 0x06, 0x07, 0x08,
];
assert_eq!(hierarchical_indices(key_id), hierarchical_indices(key_id));
let (l1, l2, l3) = hierarchical_indices(key_id);
assert_eq!(
l1,
u32::from_be_bytes([0x12, 0x34, 0x56, 0x78]) % CHILD_NUMBER_RANGE
);
assert_eq!(
l2,
u32::from_be_bytes([0x9A, 0xBC, 0xDE, 0xF0]) % CHILD_NUMBER_RANGE
);
assert_eq!(
l3,
u32::from_be_bytes([0x11, 0x22, 0x33, 0x44]) % CHILD_NUMBER_RANGE
);
assert!(l1 < CHILD_NUMBER_RANGE && l2 < CHILD_NUMBER_RANGE && l3 < CHILD_NUMBER_RANGE);
assert_eq!(hierarchical_indices([0u8; 32]), (0, 0, 0));
}
#[test]
fn hierarchical_indices_distribute_across_the_range() {
use std::collections::HashSet;
let mut seen = HashSet::new();
for i in 0..1000u32 {
let mut key_id = [0u8; 32];
key_id[0..4].copy_from_slice(&i.to_be_bytes());
key_id[4..8].copy_from_slice(&i.wrapping_mul(7919).to_be_bytes());
key_id[8..12].copy_from_slice(&i.wrapping_mul(104729).to_be_bytes());
seen.insert(hierarchical_indices(key_id));
}
assert!(seen.len() > 900, "distribution too poor: {}", seen.len());
}
#[test]
fn derivation_path_is_base_plus_three_levels() {
use bitcoin::bip32::ChildNumber;
let keys = provider();
let (l1, l2, l3) = hierarchical_indices([1u8; 32]);
let path = keys.hierarchical_derivation_path(l1, l2, l3).unwrap();
assert_eq!(path.len(), 6);
assert_eq!(path[0], ChildNumber::from_hardened_idx(420).unwrap());
assert_eq!(path[1], ChildNumber::from_hardened_idx(0).unwrap());
assert_eq!(path[2], ChildNumber::from_hardened_idx(0).unwrap());
assert_eq!(path[3], ChildNumber::from_normal_idx(l1).unwrap());
assert_eq!(path[4], ChildNumber::from_normal_idx(l2).unwrap());
assert_eq!(path[5], ChildNumber::from_normal_idx(l3).unwrap());
}
#[test]
fn hardening_is_deterministic_and_sensitive() {
let keys = provider();
let base = SecretKey::from_slice(&[0x42; 32]).unwrap();
assert_eq!(
keys.harden(&base, 100, 200, 300).unwrap(),
keys.harden(&base, 100, 200, 300).unwrap()
);
assert_ne!(keys.harden(&base, 100, 200, 300).unwrap(), base);
assert_ne!(
keys.harden(&base, 100, 200, 300).unwrap(),
keys.harden(&base, 100, 200, 301).unwrap()
);
assert_ne!(
keys.harden(&base, 100, 200, 300).unwrap(),
keys.harden(&base, 100, 201, 300).unwrap()
);
assert_ne!(
keys.harden(&base, 100, 200, 300).unwrap(),
keys.harden(&base, 101, 200, 300).unwrap()
);
}
#[test]
fn from_descriptor_requires_a_private_key() {
let secp = Secp256k1::new();
let xpub = bitcoin::bip32::Xpub::from_priv(
&secp,
&Xpriv::new_master(Network::Regtest, &[7u8; 32]).unwrap(),
);
let watch_only = format!("wpkh({xpub}/0/*)");
assert!(matches!(
ContractKeyProvider::from_descriptor(&watch_only),
Err(ContractError::Descriptor(_))
));
}
}