use std::str::FromStr;
pub mod error;
use std::{
collections::{BTreeMap, BTreeSet},
sync::mpsc,
};
use error::Error;
use serde::{Deserialize, Serialize};
use {
bip39,
miniscript::{
bitcoin::{
self,
bip32::{self, DerivationPath},
ecdsa,
psbt::Input,
secp256k1::{self, All, Message},
sighash, EcdsaSighashType, NetworkKind, Psbt,
},
descriptor::DescriptorMultiXKey,
Descriptor, DescriptorPublicKey, ForEachKey,
},
};
use crate::account::AddrAccount;
use crate::{derivator::Derivator, signing_manager};
#[derive(Debug)]
pub enum SignerNotif {
Info(bip32::Fingerprint, serde_json::Value),
Xpub(bip32::Fingerprint, OXpub),
Descriptor(bip32::Fingerprint, DescriptorMultiXKey<bip32::Xpub>),
DescriptorRegistered(bip32::Fingerprint, Descriptor<DescriptorPublicKey>, bool),
Signed(bip32::Fingerprint, Psbt),
Error(bip32::Fingerprint, Error),
Manager(signing_manager::Error),
}
pub trait Signer {
fn init(&mut self, channel: mpsc::Sender<SignerNotif>);
fn info(&self);
fn get_xpub(&self, deriv: DerivationPath, display: bool);
fn is_descriptor_registered(&self, descriptor: Descriptor<DescriptorPublicKey>);
fn register_descriptor(&mut self, descriptor: Descriptor<DescriptorPublicKey>);
fn sign(&self, psbt: Psbt, descriptor: Descriptor<DescriptorPublicKey>);
fn display_address(&self, _deriv: (AddrAccount, u32)) {}
}
macro_rules! send {
($s:ident, $notif:ident($val1:expr)) => {
if let Some(sender) = &$s.sender {
if let Err(e) = sender.send(SignerNotif::$notif($s.fingerprint(), $val1)) {
log::error!("Signer fail to send notification: {e:?}");
}
}
};
($s:ident, $notif:ident($val1:expr, $val2:expr)) => {
if let Some(sender) = &$s.sender {
if let Err(e) = sender.send(SignerNotif::$notif($s.fingerprint(), $val1, $val2)) {
log::error!("Signer fail to send notification: {e:?}");
}
}
};
}
impl Signer for HotSigner {
fn init(&mut self, channel: mpsc::Sender<SignerNotif>) {
self.sender = Some(channel);
self.info();
}
fn info(&self) {
send!(self, Info(serde_json::Value::Null));
}
fn get_xpub(&self, deriv: DerivationPath, _display: bool) {
let xpub = self.xpub(&deriv);
if let Some(sender) = &self.sender {
let _ = sender.send(SignerNotif::Xpub(self.fingerprint(), xpub));
}
}
fn is_descriptor_registered(&self, descriptor: Descriptor<DescriptorPublicKey>) {
let registered = self.descriptors.contains(&descriptor);
if let Some(sender) = &self.sender {
let _ = sender.send(SignerNotif::DescriptorRegistered(
self.fingerprint(),
descriptor,
registered,
));
}
}
fn register_descriptor(&mut self, descriptor: Descriptor<DescriptorPublicKey>) {
let wrong_network = descriptor.for_any_key(|k| match k {
DescriptorPublicKey::Single(_) => true,
DescriptorPublicKey::XPub(key) => match (self.network, key.xkey.network) {
(bitcoin::Network::Bitcoin, NetworkKind::Main) => false,
(bitcoin::Network::Bitcoin, NetworkKind::Test) => true,
(_, NetworkKind::Main) => true,
_ => false,
},
DescriptorPublicKey::MultiXPub(key) => match (self.network, key.xkey.network) {
(bitcoin::Network::Bitcoin, NetworkKind::Main) => false,
(bitcoin::Network::Bitcoin, NetworkKind::Test) => true,
(_, NetworkKind::Main) => true,
_ => false,
},
});
if !wrong_network {
self.descriptors.insert(descriptor.clone());
}
if let Some(sender) = &self.sender {
let response = if wrong_network {
SignerNotif::Error(self.fingerprint(), Error::DescriptorNetwork)
} else {
SignerNotif::DescriptorRegistered(self.fingerprint(), descriptor, true)
};
let _ = sender.send(response);
}
}
fn sign(&self, mut psbt: Psbt, descriptor: Descriptor<DescriptorPublicKey>) {
let response = if self.descriptors.contains(&descriptor) {
if let Err(e) = self.inner_sign(&mut psbt, &descriptor) {
SignerNotif::Error(self.fingerprint(), e)
} else {
SignerNotif::Signed(self.fingerprint(), psbt)
}
} else {
SignerNotif::Error(self.fingerprint(), Error::UnregisteredDescriptor)
};
if let Some(sender) = &self.sender {
let _ = sender.send(response);
}
}
}
#[derive(Debug, Clone)]
pub struct HotSigner {
master_xpriv: bip32::Xpriv,
fingerprint: bip32::Fingerprint,
secp: secp256k1::Secp256k1<All>,
mnemonic: Option<bip39::Mnemonic>,
descriptors: BTreeSet<Descriptor<DescriptorPublicKey>>,
network: bitcoin::Network,
sender: Option<mpsc::Sender<SignerNotif>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonSigner {
mnemonic: bip39::Mnemonic,
descriptors: BTreeSet<String>,
network: bitcoin::Network,
}
pub struct OXpriv {
pub origin: (bip32::Fingerprint, bip32::DerivationPath),
pub xkey: bip32::Xpriv,
}
#[derive(Debug)]
pub struct OXpub {
pub origin: (bip32::Fingerprint, bip32::DerivationPath),
pub xkey: bip32::Xpub,
}
impl HotSigner {
pub fn to_json(&self) -> Option<JsonSigner> {
let descriptors = self.descriptors.iter().map(|d| d.to_string()).collect();
self.mnemonic.as_ref().map(|mnemonic| JsonSigner {
mnemonic: mnemonic.clone(),
descriptors,
network: self.network,
})
}
pub fn from_json(json: JsonSigner) -> Self {
let mut signer = HotSigner::new_from_mnemonics(json.network, &json.mnemonic.to_string())
.expect("valid signer");
#[allow(clippy::mutable_key_type)]
let descriptors = json
.descriptors
.into_iter()
.filter_map(|d| Descriptor::from_str(&d).ok())
.collect();
signer.descriptors = descriptors;
signer
}
pub fn new_from_xpriv(network: bitcoin::Network, xpriv: bip32::Xpriv) -> Self {
let secp = secp256k1::Secp256k1::new();
let fingerprint = xpriv.fingerprint(&secp);
let master_xpriv = xpriv;
HotSigner {
master_xpriv,
fingerprint,
secp,
mnemonic: None,
descriptors: BTreeSet::new(),
network,
sender: None,
}
}
pub fn new_from_mnemonics(network: bitcoin::Network, mnemonic: &str) -> Result<Self, Error> {
let mnemonic = bip39::Mnemonic::from_str(mnemonic)?;
let seed = mnemonic.to_seed("");
let key = bip32::Xpriv::new_master(network, &seed).map_err(|_| Error::XPrivFromSeed)?;
let mut signer = Self::new_from_xpriv(network, key);
signer.mnemonic = Some(mnemonic);
Ok(signer)
}
pub fn new(network: bitcoin::Network) -> Result<Self, Error> {
assert_ne!(network, bitcoin::Network::Bitcoin);
let mnemonic = bip39::Mnemonic::generate(12).expect("12 words must not fail");
let mut signer = Self::new_from_mnemonics(network, &mnemonic.to_string())?;
signer.mnemonic = Some(mnemonic);
Ok(signer)
}
pub fn inner_register_descriptor(&mut self, descriptor: Descriptor<DescriptorPublicKey>) {
if !self.descriptors.contains(&descriptor) {
self.descriptors.insert(descriptor);
}
}
pub fn xpriv(&self, path: &DerivationPath) -> OXpriv {
let xkey = self
.master_xpriv
.derive_priv(&self.secp, path)
.expect("cannot fail");
OXpriv {
origin: (self.fingerprint, path.clone()),
xkey,
}
}
pub fn xpub(&self, path: &DerivationPath) -> OXpub {
let xpriv = self.xpriv(path);
let xkey = bip32::Xpub::from_priv(&self.secp, &xpriv.xkey);
OXpub {
origin: xpriv.origin,
xkey,
}
}
fn private_key_at(&self, path: &DerivationPath) -> secp256k1::SecretKey {
self.master_xpriv
.derive_priv(self.secp(), path)
.expect("deriveable")
.private_key
}
pub fn public_key_at(&self, path: &DerivationPath) -> secp256k1::PublicKey {
self.private_key_at(path).public_key(self.secp())
}
pub fn inner_sign(
&self,
psbt: &mut Psbt,
descriptor: &Descriptor<DescriptorPublicKey>,
) -> Result<(), Error> {
let mut cache = sighash::SighashCache::new(psbt.unsigned_tx.clone());
let derivator = Derivator::new(descriptor.clone(), self.network).unwrap();
let mut inputs_to_sign = BTreeMap::new();
for (index, input) in psbt.inputs.iter().enumerate() {
let mut derivation_paths = vec![];
input.bip32_derivation.iter().for_each(|(_, (fg, deriv))| {
if *fg == self.fingerprint() {
derivation_paths.push(deriv.clone());
}
});
if !derivation_paths.is_empty() {
if input.witness_utxo.is_none() {
Err(Error::MissingWitnessUtxo)?
}
let (hash, sighash_type) = psbt.sighash_ecdsa(0, &mut cache).map_err(|e| {
log::error!("Fail to generate sig hash: {e}");
Error::SighashFail
})?;
if sighash_type != EcdsaSighashType::All {
return Err(Error::SighashFail);
}
inputs_to_sign.insert(index, (hash, psbt.inputs[index].clone(), derivation_paths));
}
}
for (index, (hash, mut input, deriv)) in inputs_to_sign {
self.sign_input(hash, &mut input, deriv, &derivator)?;
psbt.inputs[index] = input;
}
Ok(())
}
pub fn sign_input(
&self,
hash: Message,
input: &mut Input,
deriv: Vec<DerivationPath>,
derivator: &Derivator,
) -> Result<(), Error> {
for d in &deriv {
let signing_key = self.private_key_at(d);
let pubkey = self.public_key_at(d);
if !input.bip32_derivation.contains_key(&pubkey) {
continue;
}
if let Some(wit) = &input.witness_utxo {
let ap = account_path(d)?;
let expected_spk = match ap.0 {
AddrAccount::Receive => derivator.receive_at(ap.1),
AddrAccount::Change => derivator.change_at(ap.1),
}
.script_pubkey();
if wit.script_pubkey != expected_spk {
Err(Error::SpkNotMatch)
} else {
Ok(())
}
} else {
Err(Error::MissingWitnessUtxo)
}?;
let signature = self.secp.sign_ecdsa_low_r(&hash, &signing_key);
self.secp()
.verify_ecdsa(&hash, &signature, &pubkey)
.map_err(|_| Error::InvalidSignature)?;
let signature = ecdsa::Signature {
signature,
sighash_type: EcdsaSighashType::All,
};
input.partial_sigs.insert(pubkey.into(), signature);
}
Ok(())
}
pub fn fingerprint(&self) -> bip32::Fingerprint {
self.fingerprint
}
fn secp(&self) -> &secp256k1::Secp256k1<All> {
&self.secp
}
#[allow(unused)]
fn mnemonic(&self) -> Option<bip39::Mnemonic> {
self.mnemonic.clone()
}
}
pub fn deriv_path(path: &(AddrAccount, u32)) -> Result<DerivationPath, Error> {
let account_u32: u32 = path.0.into();
DerivationPath::from_str(&format!("m/{}/{}", account_u32, path.1))
.map_err(|_| Error::DerivationPath)
}
pub fn account_path(path: &DerivationPath) -> Result<(AddrAccount, u32), Error> {
if path.len() != 2 {
Err(Error::DerivationPath)?
}
let path = path.to_u32_vec();
Ok((path[0].into(), path[1]))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
descriptor::wpkh,
test_utils::{random_output, setup_logger, txid},
};
use bitcoin::Network;
use miniscript::bitcoin::{absolute::Height, Amount, ScriptBuf, TxIn, Witness};
use std::sync::mpsc;
#[test]
fn test_create_hot_signer_from_xpriv() {
let network = Network::Testnet;
let xpriv =
bip32::Xpriv::new_master(network, &bip39::Mnemonic::generate(12).unwrap().to_seed(""))
.unwrap();
let signer = HotSigner::new_from_xpriv(network, xpriv);
assert_eq!(signer.network, network);
}
#[test]
fn test_create_hot_signer_from_mnemonic() {
let network = Network::Testnet;
let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
let signer = HotSigner::new_from_mnemonics(network, mnemonic).unwrap();
assert_eq!(signer.network, network);
}
#[test]
fn test_sign_transaction() {
setup_logger();
let network = Network::Testnet;
let xpriv =
bip32::Xpriv::new_master(network, &bip39::Mnemonic::generate(12).unwrap().to_seed(""))
.unwrap();
let signer = HotSigner::new_from_xpriv(network, xpriv);
let xpub = signer.xpub(&DerivationPath::from_str("m/84'/0'/0'/1'").unwrap());
let descriptor = wpkh(xpub);
let txin = TxIn {
previous_output: bitcoin::OutPoint {
txid: txid(0),
vout: 1,
},
script_sig: ScriptBuf::new(),
sequence: bitcoin::Sequence::ZERO,
witness: Witness::new(),
};
let txout = random_output();
let tx = bitcoin::Transaction {
version: bitcoin::transaction::Version(2),
lock_time: bitcoin::absolute::LockTime::Blocks(Height::ZERO),
input: vec![txin],
output: vec![txout],
};
let mut psbt = Psbt::from_unsigned_tx(tx).unwrap();
let deriv = &(AddrAccount::Receive, 0);
let deriv_p = deriv_path(deriv).unwrap();
let pubkey = signer.public_key_at(&deriv_p);
assert!(psbt.inputs[0].partial_sigs.is_empty());
signer.inner_sign(&mut psbt, &descriptor).unwrap();
assert!(psbt.inputs[0].partial_sigs.is_empty());
let w_deriv = &(AddrAccount::Change, 0);
let w_deriv_path = deriv_path(w_deriv).unwrap();
psbt.inputs
.get_mut(0)
.unwrap()
.bip32_derivation
.insert(pubkey, (signer.fingerprint(), w_deriv_path));
let res = signer.inner_sign(&mut psbt, &descriptor);
assert_eq!(res, Err(Error::MissingWitnessUtxo));
assert!(psbt.inputs[0].partial_sigs.is_empty());
let derivator = Derivator::new(descriptor.clone(), bitcoin::Network::Regtest).unwrap();
psbt.inputs.get_mut(0).unwrap().witness_utxo = Some(bitcoin::TxOut {
value: Amount::from_sat(100_000),
script_pubkey: derivator.receive_spk_at(deriv.1),
});
signer.inner_sign(&mut psbt, &descriptor).unwrap();
assert!(psbt.inputs[0].partial_sigs.is_empty());
psbt.inputs[0].bip32_derivation.clear();
psbt.inputs
.get_mut(0)
.unwrap()
.bip32_derivation
.insert(pubkey, (signer.fingerprint(), deriv_p));
signer.inner_sign(&mut psbt, &descriptor).unwrap();
assert!(!psbt.inputs[0].partial_sigs.is_empty());
}
struct MockSender {
receiver: mpsc::Receiver<SignerNotif>,
}
impl MockSender {
fn new() -> (mpsc::Sender<SignerNotif>, Self) {
let (sender, receiver) = mpsc::channel();
(sender, MockSender { receiver })
}
}
#[test]
fn test_signer_init() {
let (sender, mock) = MockSender::new();
let mut signer = HotSigner::new_from_xpriv(
Network::Regtest,
bip32::Xpriv::new_master(
Network::Regtest,
&bip39::Mnemonic::generate(12).unwrap().to_seed(""),
)
.unwrap(),
);
signer.init(sender);
let notif = mock.receiver.recv().unwrap();
match notif {
SignerNotif::Info(fg, _) => {
assert_eq!(signer.fingerprint(), fg);
}
_ => panic!("Expected Info notification"),
}
}
#[test]
fn test_signer_info() {
let (sender, mock) = MockSender::new();
let mut signer = HotSigner::new_from_xpriv(
Network::Regtest,
bip32::Xpriv::new_master(
Network::Regtest,
&bip39::Mnemonic::generate(12).unwrap().to_seed(""),
)
.unwrap(),
);
signer.init(sender);
signer.info();
let notif = mock.receiver.recv().unwrap();
match notif {
SignerNotif::Info(fg, _) => {
assert_eq!(signer.fingerprint(), fg);
}
_ => panic!("Expected Info notification"),
}
}
#[test]
fn test_signer_get_xpub() {
let (sender, mock) = MockSender::new();
let mut signer = HotSigner::new_from_xpriv(
Network::Regtest,
bip32::Xpriv::new_master(
Network::Regtest,
&bip39::Mnemonic::generate(12).unwrap().to_seed(""),
)
.unwrap(),
);
signer.init(sender);
let derivation_path = DerivationPath::from_str("m/84'/0'/0'/0").unwrap();
signer.get_xpub(derivation_path, false);
let _ = mock.receiver.recv().unwrap();
let notif = mock.receiver.recv().unwrap();
match notif {
SignerNotif::Xpub(fg, _) => {
assert_eq!(signer.fingerprint(), fg);
}
_ => panic!("Expected Xpub notification"),
}
}
#[test]
fn test_signer_is_descriptor_registered() {
let (sender, mock) = MockSender::new();
let mut signer = HotSigner::new_from_xpriv(
Network::Regtest,
bip32::Xpriv::new_master(
Network::Regtest,
&bip39::Mnemonic::generate(12).unwrap().to_seed(""),
)
.unwrap(),
);
signer.init(sender);
let _ = mock.receiver.recv();
let descriptor = wpkh(signer.xpub(&DerivationPath::from_str("m/84'/0'/0'/0").unwrap()));
signer.is_descriptor_registered(descriptor.clone());
let notif = mock.receiver.recv().unwrap();
match notif {
SignerNotif::DescriptorRegistered(fg, desc, false) => {
assert_eq!(signer.fingerprint(), fg);
assert_eq!(desc, descriptor);
}
_ => panic!("Expected DescriptorRegistered notification"),
}
signer.register_descriptor(descriptor.clone());
let notif = mock.receiver.recv().unwrap();
match notif {
SignerNotif::DescriptorRegistered(fg, desc, true) => {
assert_eq!(signer.fingerprint(), fg);
assert_eq!(desc, descriptor);
}
_ => panic!("Expected DescriptorRegistered notification"),
}
signer.is_descriptor_registered(descriptor.clone());
let notif = mock.receiver.recv().unwrap();
match notif {
SignerNotif::DescriptorRegistered(fg, desc, true) => {
assert_eq!(signer.fingerprint(), fg);
assert_eq!(desc, descriptor);
}
_ => panic!("Expected DescriptorRegistered notification"),
}
}
#[test]
fn test_signer_sign() {
let (sender, mock) = MockSender::new();
let mut signer = HotSigner::new_from_xpriv(
Network::Regtest,
bip32::Xpriv::new_master(
Network::Regtest,
&bip39::Mnemonic::generate(12).unwrap().to_seed(""),
)
.unwrap(),
);
let derivation_path = DerivationPath::from_str("m/84'/0'/0'/0").unwrap();
let descriptor = wpkh(signer.xpub(&derivation_path));
signer.init(sender);
let notif = mock.receiver.recv().unwrap();
match notif {
SignerNotif::Info(fg, _) => {
assert_eq!(signer.fingerprint(), fg);
}
_ => panic!("Expected DescriptorRegistered notification"),
}
signer.register_descriptor(descriptor.clone());
let notif = mock.receiver.recv().unwrap();
match notif {
SignerNotif::DescriptorRegistered(fg, desc, true) => {
assert_eq!(signer.fingerprint(), fg);
assert_eq!(desc, descriptor);
}
_ => panic!("Expected DescriptorRegistered notification"),
}
let txin = TxIn {
previous_output: bitcoin::OutPoint {
txid: txid(0),
vout: 1,
},
script_sig: ScriptBuf::new(),
sequence: bitcoin::Sequence::ZERO,
witness: Witness::new(),
};
let txout = random_output();
let tx = bitcoin::Transaction {
version: bitcoin::transaction::Version(2),
lock_time: bitcoin::absolute::LockTime::Blocks(Height::ZERO),
input: vec![txin],
output: vec![txout],
};
let mut psbt = Psbt::from_unsigned_tx(tx).unwrap();
let deriv = &(AddrAccount::Receive, 0);
let deriv_p = deriv_path(deriv).unwrap();
let pubkey = signer.public_key_at(&deriv_p);
let derivator = Derivator::new(descriptor.clone(), bitcoin::Network::Regtest).unwrap();
psbt.inputs.get_mut(0).unwrap().witness_utxo = Some(bitcoin::TxOut {
value: Amount::from_sat(100_000),
script_pubkey: derivator.receive_spk_at(deriv.1),
});
psbt.inputs
.get_mut(0)
.unwrap()
.bip32_derivation
.insert(pubkey, (signer.fingerprint(), deriv_p));
signer.sign(psbt, descriptor);
let notif = mock.receiver.recv().unwrap();
match notif {
SignerNotif::Signed(fg, psbt) => {
assert_eq!(signer.fingerprint(), fg);
assert!(!psbt.inputs[0].partial_sigs.is_empty());
}
_ => panic!("Expected DescriptorRegistered notification"),
}
}
}