ledger_bitcoin_client/
command.rs

1/// APDU commands  for the Bitcoin application.
2///
3use bitcoin::{
4    bip32::{ChildNumber, DerivationPath},
5    consensus::encode::{self, VarInt},
6};
7use core::default::Default;
8
9use super::{
10    apdu::{self, APDUCommand},
11    wallet::WalletPolicy,
12};
13
14/// Creates the APDU Command to retrieve the app's name, version and state flags.
15pub fn get_version() -> APDUCommand {
16    APDUCommand {
17        ins: apdu::BitcoinCommandCode::GetVersion as u8,
18        p2: 0x00,
19        ..Default::default()
20    }
21}
22
23/// Creates the APDU Command to retrieve the master fingerprint.
24pub fn get_master_fingerprint() -> APDUCommand {
25    APDUCommand {
26        cla: apdu::Cla::Bitcoin as u8,
27        ins: apdu::BitcoinCommandCode::GetMasterFingerprint as u8,
28        ..Default::default()
29    }
30}
31
32/// Creates the APDU command required to get the extended pubkey with the given derivation path.
33pub fn get_extended_pubkey(path: &DerivationPath, display: bool) -> APDUCommand {
34    let child_numbers: &[ChildNumber] = path.as_ref();
35    let data: Vec<u8> = child_numbers.iter().fold(
36        vec![
37            if display { 1_u8 } else { b'\0' },
38            child_numbers.len() as u8,
39        ],
40        |mut acc, &x| {
41            acc.extend_from_slice(&u32::from(x).to_be_bytes());
42            acc
43        },
44    );
45
46    APDUCommand {
47        cla: apdu::Cla::Bitcoin as u8,
48        ins: apdu::BitcoinCommandCode::GetExtendedPubkey as u8,
49        data,
50        ..Default::default()
51    }
52}
53
54/// Creates the APDU command required to register the given wallet policy.
55pub fn register_wallet(policy: &WalletPolicy) -> APDUCommand {
56    let bytes = policy.serialize();
57    let mut data = encode::serialize(&VarInt(bytes.len() as u64));
58    data.extend(bytes);
59    APDUCommand {
60        cla: apdu::Cla::Bitcoin as u8,
61        ins: apdu::BitcoinCommandCode::RegisterWallet as u8,
62        data,
63        ..Default::default()
64    }
65}
66
67/// Creates the APDU command required to retrieve an address for the given wallet.
68pub fn get_wallet_address(
69    policy: &WalletPolicy,
70    hmac: Option<&[u8; 32]>,
71    change: bool,
72    address_index: u32,
73    display: bool,
74) -> APDUCommand {
75    let mut data: Vec<u8> = Vec::with_capacity(70);
76    data.push(if display { 1_u8 } else { b'\0' });
77    data.extend_from_slice(&policy.id());
78    data.extend_from_slice(hmac.unwrap_or(&[b'\0'; 32]));
79    data.push(if change { 1_u8 } else { b'\0' });
80    data.extend_from_slice(&address_index.to_be_bytes());
81    APDUCommand {
82        cla: apdu::Cla::Bitcoin as u8,
83        ins: apdu::BitcoinCommandCode::GetWalletAddress as u8,
84        data,
85        ..Default::default()
86    }
87}
88
89/// Creates the APDU command required to sign a psbt.
90pub fn sign_psbt(
91    global_mapping_commitment: &[u8],
92    inputs_number: usize,
93    input_commitments_root: &[u8; 32],
94    outputs_number: usize,
95    output_commitments_root: &[u8; 32],
96    policy: &WalletPolicy,
97    hmac: Option<&[u8; 32]>,
98) -> APDUCommand {
99    let mut data: Vec<u8> = Vec::new();
100    data.extend_from_slice(global_mapping_commitment);
101    data.extend(encode::serialize(&VarInt(inputs_number as u64)));
102    data.extend_from_slice(input_commitments_root);
103    data.extend(encode::serialize(&VarInt(outputs_number as u64)));
104    data.extend_from_slice(output_commitments_root);
105    data.extend_from_slice(&policy.id());
106    data.extend_from_slice(hmac.unwrap_or(&[b'\0'; 32]));
107    APDUCommand {
108        cla: apdu::Cla::Bitcoin as u8,
109        ins: apdu::BitcoinCommandCode::SignPSBT as u8,
110        data,
111        ..Default::default()
112    }
113}
114
115/// Creates the APDU Command to sign a message.
116pub fn sign_message(
117    message_length: usize,
118    message_commitment_root: &[u8; 32],
119    path: &DerivationPath,
120) -> APDUCommand {
121    let child_numbers: &[ChildNumber] = path.as_ref();
122    let mut data: Vec<u8> =
123        child_numbers
124            .iter()
125            .fold(vec![child_numbers.len() as u8], |mut acc, &x| {
126                acc.extend_from_slice(&u32::from(x).to_be_bytes());
127                acc
128            });
129    data.extend(encode::serialize(&VarInt(message_length as u64)));
130    data.extend_from_slice(message_commitment_root);
131
132    APDUCommand {
133        cla: apdu::Cla::Bitcoin as u8,
134        ins: apdu::BitcoinCommandCode::SignMessage as u8,
135        data,
136        ..Default::default()
137    }
138}
139
140/// Creates the APDU command to CONTINUE.
141pub fn continue_interrupted(data: Vec<u8>) -> APDUCommand {
142    APDUCommand {
143        cla: apdu::Cla::Framework as u8,
144        ins: apdu::FrameworkCommandCode::ContinueInterrupted as u8,
145        data,
146        ..Default::default()
147    }
148}