use std::collections::BTreeMap;
use bitcoin::{
bip32::{ChildNumber, DerivationPath, Xpriv},
hashes::{Hash, HashEngine, sha256},
secp256k1::{PublicKey, SecretKey},
};
use thiserror::Error;
use uuid::Uuid;
use crate::{secp, types::SwapNetwork};
const PURPOSE: u32 = 84;
#[derive(Debug, Error)]
pub enum KeyringError {
#[error("BIP32 derivation: {0}")]
Bip32(#[from] bitcoin::bip32::Error),
#[error("invalid secp256k1 scalar: {0}")]
Scalar(#[from] bitcoin::secp256k1::Error),
#[error("branch {0} was not declared at construction")]
UnknownBranch(u32),
}
#[derive(Clone)]
pub struct Keyring {
branches: BTreeMap<u32, Xpriv>,
}
impl Keyring {
pub fn from_seed(
seed: &[u8],
network: SwapNetwork,
branches: &[u32],
) -> Result<Self, KeyringError> {
let mut master = Xpriv::new_master(network.bitcoin_network(), seed)?;
let branches = branches
.iter()
.map(|&branch| Ok((branch, derive_branch(&master, network, branch)?)))
.collect::<Result<_, KeyringError>>();
master.private_key.non_secure_erase();
Ok(Self {
branches: branches?,
})
}
pub fn swap_key(&self, branch: u32, swap_id: &Uuid) -> Result<SecretKey, KeyringError> {
Ok(derive_swap_key(self.branch(branch)?, swap_id)?)
}
pub fn branch_privkey(&self, branch: u32) -> Result<SecretKey, KeyringError> {
Ok(self.branch(branch)?.private_key)
}
pub fn branch_pubkey(&self, branch: u32) -> Result<PublicKey, KeyringError> {
Ok(self.branch_privkey(branch)?.public_key(secp()))
}
fn branch(&self, branch: u32) -> Result<&Xpriv, KeyringError> {
self.branches
.get(&branch)
.ok_or(KeyringError::UnknownBranch(branch))
}
}
impl Drop for Keyring {
fn drop(&mut self) {
for xpriv in self.branches.values_mut() {
xpriv.private_key.non_secure_erase();
}
}
}
fn derive_branch(
master: &Xpriv,
network: SwapNetwork,
branch: u32,
) -> Result<Xpriv, bitcoin::bip32::Error> {
let coin = match network {
SwapNetwork::Mainnet => 0,
_ => 1,
};
let path = DerivationPath::from(vec![
ChildNumber::from_hardened_idx(PURPOSE).expect("fixed purpose"),
ChildNumber::from_hardened_idx(coin).expect("fixed coin"),
ChildNumber::from_hardened_idx(branch).expect("fixed branch"),
]);
master.derive_priv(secp(), &path)
}
fn derive_swap_key(xpriv: &Xpriv, swap_id: &Uuid) -> Result<SecretKey, bitcoin::secp256k1::Error> {
let mut engine = sha256::Hash::engine();
engine.input(&xpriv.private_key.secret_bytes());
engine.input(swap_id.as_bytes());
let hash = sha256::Hash::from_engine(engine);
SecretKey::from_slice(hash.as_ref())
}
#[cfg(test)]
mod tests {
use super::*;
const SEED: [u8; 32] = [0x42; 32];
const SWAP_ID: &str = "00112233-4455-6677-8899-aabbccddeeff";
fn keyring(network: SwapNetwork) -> Keyring {
Keyring::from_seed(&SEED, network, &[0, 1]).unwrap()
}
fn swap_id() -> Uuid {
Uuid::parse_str(SWAP_ID).unwrap()
}
#[test]
fn keyring_derivation_vectors() {
let swap_id = swap_id();
let cases = [
(
SwapNetwork::Mainnet,
"0f2e2ea115f409ee6e6483e3bd2670fcd75d7d40403f427e267889ee1789493e",
"8b376168533a879dc6e2e0fb30a6e6d05e646732df386971504e40b32ace99f8",
),
(
SwapNetwork::Regtest,
"28734f9f0d768466462ec6c6061cff4246bb18e07048861a153929f534b16bb4",
"1f324e1a744da50a1654784536741afa11ba03020175862082ee9e5a3be716a2",
),
];
for (network, branch0, branch1) in cases {
let keyring = keyring(network);
assert_eq!(
hex::encode(keyring.swap_key(0, &swap_id).unwrap().secret_bytes()),
branch0,
"branch 0 on {network:?}"
);
assert_eq!(
hex::encode(keyring.swap_key(1, &swap_id).unwrap().secret_bytes()),
branch1,
"branch 1 on {network:?}"
);
}
}
#[test]
fn keyring_mainnet_differs_from_test_networks() {
let swap_id = swap_id();
assert_ne!(
keyring(SwapNetwork::Mainnet).swap_key(0, &swap_id).unwrap(),
keyring(SwapNetwork::Regtest).swap_key(0, &swap_id).unwrap()
);
}
#[test]
fn keyring_test_networks_share_derivation() {
let swap_id = swap_id();
let testnet = keyring(SwapNetwork::Testnet);
let signet = keyring(SwapNetwork::Signet);
let regtest = keyring(SwapNetwork::Regtest);
assert_eq!(
testnet.swap_key(0, &swap_id).unwrap(),
signet.swap_key(0, &swap_id).unwrap()
);
assert_eq!(
testnet.swap_key(0, &swap_id).unwrap(),
regtest.swap_key(0, &swap_id).unwrap()
);
}
#[test]
fn keyring_derivation_is_deterministic_and_branch_separated() {
let keyring = keyring(SwapNetwork::Regtest);
let swap_id = Uuid::from_u128(0x1234567890abcdef1234567890abcdef);
assert_eq!(
keyring.swap_key(0, &swap_id).unwrap(),
keyring.swap_key(0, &swap_id).unwrap()
);
assert_ne!(
keyring.swap_key(0, &swap_id).unwrap(),
keyring.swap_key(1, &swap_id).unwrap()
);
assert_ne!(
keyring.swap_key(0, &swap_id).unwrap(),
keyring
.swap_key(0, &Uuid::from_u128(swap_id.as_u128() + 1))
.unwrap()
);
}
#[test]
fn branch_pubkey_matches_branch_privkey() {
let keyring = keyring(SwapNetwork::Regtest);
assert_eq!(
keyring.branch_pubkey(0).unwrap(),
keyring.branch_privkey(0).unwrap().public_key(secp())
);
}
#[test]
fn unregistered_branch_is_reported() {
let keyring = keyring(SwapNetwork::Regtest);
assert!(matches!(
keyring.swap_key(9, &swap_id()),
Err(KeyringError::UnknownBranch(9))
));
assert!(matches!(
keyring.branch_privkey(9),
Err(KeyringError::UnknownBranch(9))
));
assert!(matches!(
keyring.branch_pubkey(9),
Err(KeyringError::UnknownBranch(9))
));
}
}