use crate::{utils::*, LedgerBTCError};
use bitcoins::{
prelude::Transaction,
types::{BitcoinTxIn, Utxo, WitnessTx},
};
use coins_bip32::{path::DerivationPath, prelude::*};
use coins_ledger::{
common::{APDUAnswer, APDUCommand},
transports::{Ledger, LedgerAsync},
};
use futures::lock::Mutex;
#[derive(Clone, Debug)]
pub struct SigningInfo {
pub input_idx: usize,
pub prevout: Utxo,
pub deriv: Option<KeyDerivation>,
}
#[derive(Clone, Debug)]
pub struct SigInfo {
pub input_idx: usize,
pub sig: Signature,
pub deriv: KeyDerivation,
}
pub struct LedgerBTC {
transport: Mutex<Ledger>,
}
impl LedgerBTC {
pub async fn init() -> Result<LedgerBTC, LedgerBTCError> {
Ok(LedgerBTC {
transport: Mutex::new(Ledger::init().await?),
})
}
pub fn close(self) {}
}
impl LedgerBTC {
async fn get_key_info(
&self,
transport: &Ledger,
deriv: &DerivationPath,
) -> Result<InternalKeyInfo, LedgerBTCError> {
if deriv.len() > 10 {
return Err(LedgerBTCError::DerivationTooLong);
}
let data = derivation_path_to_apdu_data(deriv);
let command = APDUCommand {
ins: Commands::GetWalletPublicKey as u8,
p1: 0x00,
p2: 0x02, data,
response_len: None,
};
let answer = transport.exchange(&command).await?;
let data = answer
.data()
.ok_or(LedgerBTCError::UnexpectedNullResponse)?;
Ok(parse_pubkey_response(deriv, data))
}
pub async fn get_xpub(&self, deriv: &DerivationPath) -> Result<DerivedXPub, LedgerBTCError> {
let transport = self.transport.lock().await;
let child = self.get_key_info(&transport, deriv).await?;
if !deriv.is_empty() {
let parent = self
.get_key_info(&transport, &deriv.resized(deriv.len() - 1, 0))
.await?;
let master = self.get_key_info(&transport, &deriv.resized(0, 0)).await?;
Ok(DerivedXPub::new(
XPub::new(
child.pubkey,
XKeyInfo {
depth: deriv.len() as u8,
parent: fingerprint_of(&parent.pubkey),
index: *deriv.last().unwrap(),
chain_code: child.chain_code,
hint: Hint::SegWit,
},
),
KeyDerivation {
root: fingerprint_of(&master.pubkey),
path: deriv.clone(),
},
))
} else {
let root = fingerprint_of(&child.pubkey);
Ok(DerivedXPub::new(
XPub::new(
child.pubkey,
XKeyInfo {
depth: 0,
parent: KeyFingerprint([0u8; 4]),
index: 0,
chain_code: child.chain_code,
hint: Hint::SegWit,
},
),
KeyDerivation {
root,
path: child.path,
},
))
}
}
pub async fn get_master_xpub<'a>(&self) -> Result<DerivedXPub, LedgerBTCError> {
self.get_xpub(&Default::default()).await
}
}
impl LedgerBTC {
async fn signature_exchange(
&self,
transport: &Ledger,
first_packet: &APDUCommand,
locktime: u32,
utxo: &Utxo,
txin: &BitcoinTxIn,
deriv: &DerivationPath,
) -> Result<APDUAnswer, LedgerBTCError> {
let mut packets = vec![modify_tx_start_packet(first_packet)];
packets.extend(packetize_input_for_signing(utxo, txin));
for packet in packets.iter() {
transport.exchange(packet).await?;
}
let last_packet = transaction_final_packet(locktime, deriv);
Ok(transport.exchange(&last_packet).await?)
}
async fn get_sig(
&self,
transport: &Ledger,
first_packet: &APDUCommand,
locktime: u32,
utxo: &Utxo,
txin: &BitcoinTxIn,
deriv: &DerivationPath,
) -> Result<Signature, LedgerBTCError> {
parse_sig(
&self
.signature_exchange(transport, first_packet, locktime, utxo, txin, deriv)
.await?,
)
}
pub async fn get_tx_signatures(
&self,
tx: &WitnessTx,
signing_info: &[SigningInfo],
) -> Result<Vec<SigInfo>, LedgerBTCError> {
if signing_info.len() != tx.inputs().len() {
return Err(LedgerBTCError::SigningInfoLengthMismatch);
}
let master = self.get_xpub(&Default::default()).await?;
if !should_sign(&master, signing_info) {
return Ok(vec![]);
}
let transport = self.transport.lock().await;
let first_packet = packetize_version_and_vin_length(tx.version(), tx.inputs().len() as u64);
let mut packets = vec![first_packet.clone()];
packets.extend(
signing_info
.iter()
.map(|s| &s.prevout)
.zip(tx.inputs())
.flat_map(|(u, i)| packetize_input(u, i))
.collect::<Vec<_>>(),
);
packets.extend(packetize_vout(tx.outputs()));
for packet in packets.iter() {
transport.exchange(packet).await?;
}
let mut sigs = vec![];
for (i, info) in signing_info.iter().enumerate() {
if let Some(deriv) = &info.deriv {
let sig = self
.get_sig(
&transport,
&first_packet,
tx.locktime(),
&info.prevout,
&tx.inputs()[i],
&deriv.path,
)
.await?;
sigs.push(SigInfo {
input_idx: info.input_idx,
sig,
deriv: deriv.clone(),
});
}
}
Ok(sigs)
}
}