1use std::convert::TryFrom;
2use std::sync::{Arc, Mutex};
3use bitcoin::{
4 Transaction,
5 Address,
6 VarInt,
7 PublicKey,
8 EcdsaSighashType,
9 TxIn,
10 consensus::{serialize},
11 blockdata::{
12 script::Builder,
13 witness::Witness,
14 opcodes
15 },
16 bip32::{ChainCode},
17 ScriptBuf,
18 CompressedPublicKey,
19 WPubkeyHash,
20 NetworkKind,
21 KnownHrp,
22 ecdsa::Signature
23};
24use byteorder::{WriteBytesExt, LittleEndian};
25use hdpath::{StandardHDPath, HDPath};
26use sha2::{Sha256, Digest};
27use ripemd::Ripemd160;
28use crate::{
29 ledger::{
30 comm::LedgerTransport,
31 commons::as_compact,
32 app::{AsPubkey, AsChainCode, PubkeyAddressApp, AsExtendedKey, LedgerApp}
33 }
34};
35use crate::errors::HWKeyError;
36use crate::ledger::apdu::ApduBuilder;
37use crate::ledger::comm::sendrecv;
38use crate::ledger::connect::direct::CHUNK_SIZE;
39
40const COMMAND_GET_ADDRESS: u8 = 0x40;
41const COMMAND_COIN_VERSION: u8 = 0x16;
42#[allow(dead_code)]
43const COMMAND_GET_UNTRUSTED_INPUT: u8 = 0x42;
44const COMMAND_UNTRUSTED_HASH_TX: u8 = 0x44;
45const COMMAND_UNTRUSTED_HASH_SIGN: u8 = 0x48;
46const COMMAND_HASH_INPUT_FINALIZE_FULL: u8 = 0x4A;
47
48#[derive(Copy, Clone, Eq, PartialEq, Debug)]
49#[repr(u8)]
50pub enum AddressType {
51 Legacy = 0,
53 SegwitCompat = 1,
55 Bench32 = 2
57}
58
59pub struct BitcoinApp {
60 ledger: Arc<Mutex<dyn LedgerTransport>>
61}
62
63#[derive(Debug, Clone, Eq, PartialEq)]
64pub struct AddressResponse {
65 pub pubkey: PublicKey,
66 pub address: Address,
67 pub chaincode: ChainCode
68}
69
70impl AsPubkey for AddressResponse {
71 fn as_pubkey(&self) -> &bitcoin::secp256k1::PublicKey {
72 &self.pubkey.inner
73 }
74}
75
76impl AsChainCode for AddressResponse {
77 fn as_chaincode(&self) -> &ChainCode {
78 &self.chaincode
79 }
80}
81
82impl AsExtendedKey for AddressResponse {}
83
84#[derive(Debug, Clone, Copy, Eq, PartialEq)]
85pub struct GetAddressOpts {
86 pub address_type: AddressType,
87 pub confirmation: bool,
88 pub network: NetworkKind,
89}
90
91impl Default for GetAddressOpts {
92 fn default() -> Self {
93 GetAddressOpts {
94 address_type: AddressType::Bench32,
95 confirmation: false,
96 network: NetworkKind::Main
97 }
98 }
99}
100
101impl GetAddressOpts {
102 pub fn confirm() -> GetAddressOpts {
103 GetAddressOpts {
104 confirmation: true,
105 ..GetAddressOpts::default()
106 }
107 }
108
109 pub fn compat_address() -> GetAddressOpts {
110 GetAddressOpts {
111 address_type: AddressType::SegwitCompat,
112 ..GetAddressOpts::default()
113 }
114 }
115}
116
117pub fn hash160(value: &[u8]) -> [u8; 20] {
118 let mut hash256 = Sha256::new();
119 hash256.update(value);
120 let mut hash160 = Ripemd160::new();
121 hash160.update(hash256.finalize());
122 let bytes = hash160.finalize().to_vec();
123 let mut result = [0u8; 20];
124 result.copy_from_slice(bytes.as_slice());
125 result
126}
127
128impl TryFrom<(Vec<u8>, GetAddressOpts)> for AddressResponse {
129 type Error = HWKeyError;
130
131 fn try_from(full: (Vec<u8>, GetAddressOpts)) -> Result<Self, Self::Error> {
132 let value = full.0;
133 let opts = full.1;
134 if value.is_empty() {
135 return Err(HWKeyError::EncodingError("Empty data".to_string()))
136 }
137 let pubkey_len = value[0] as usize;
138 if 1 + pubkey_len > value.len() {
139 return Err(HWKeyError::EncodingError(
140 format!("Pubkey cutoff. {:?} > {:?} for {:?}", 1 + pubkey_len, value.len(), pubkey_len)
141 ))
142 }
143 let pubkey = &value[1..pubkey_len+1];
144 let pubkey = PublicKey::from_slice(pubkey)
145 .map_err(|_| HWKeyError::CryptoError("Invalid public key".to_string()))?;
146 let pubkey_comp = PublicKey::new(as_compact(&pubkey.inner)?);
147
148 let address_len = value[pubkey_len + 1] as usize;
149 let address_start = 1 + pubkey_len + 1;
150 let address_end = address_start + address_len;
151 if address_end > value.len() {
152 return Err(HWKeyError::EncodingError(
153 format!("Address cutoff. {:?} > {:?} as {:?}..{:?} for {:?}", address_end, value.len(), address_start, address_end, address_len)
154 ))
155 }
156
157 let address = match opts.address_type {
158 AddressType::Bench32 => {
159 let compressed = CompressedPublicKey::try_from(pubkey_comp)
160 .map_err(|_| HWKeyError::CryptoError("Invalid public key".to_string()))?;
161 let hrp = match opts.network {
162 NetworkKind::Main => KnownHrp::Mainnet,
163 NetworkKind::Test => KnownHrp::Testnets
164 };
165 Address::p2wpkh(&compressed, hrp)
166 },
167 AddressType::SegwitCompat => {
168 let script = Builder::new()
169 .push_opcode(opcodes::all::OP_PUSHBYTES_0)
170 .push_slice(hash160(pubkey_comp.to_bytes().as_slice()))
171 .into_script();
172 Address::p2sh(&script, opts.network)
173 .map_err(|_| HWKeyError::CryptoError("Invalid Pubkey".to_string()))?
174 },
175 AddressType::Legacy => Address::p2pkh(pubkey_comp, opts.network)
176 };
177
178 let chaincode_len = 32usize;
179 let chaincode_start = address_end;
180 let chaincode_end = chaincode_start + chaincode_len;
181 if chaincode_end > value.len() {
182 return Err(HWKeyError::EncodingError(
183 format!("Chaincode cutoff. {:?} > {:?}", chaincode_end, value.len())
184 ))
185 }
186 let chaincode = value[chaincode_start..chaincode_end].to_vec();
187 let chaincode = ChainCode::try_from(chaincode.as_slice()).unwrap();
188
189 Ok(AddressResponse {
190 pubkey: pubkey_comp, address, chaincode
191 })
192 }
193}
194
195#[derive(Clone)]
196pub struct SignTx {
197 pub network: NetworkKind,
198 pub inputs: Vec<UnsignedInput>,
199}
200
201#[derive(Clone)]
202pub struct UnsignedInput {
203 pub index: usize,
204 pub amount: u64,
205 pub hd_path: StandardHDPath
206}
207
208#[derive(Clone)]
209struct InputDetails {
210 prev_tx: TxIn,
211 amount: u64,
212 from_address: AddressResponse,
213 redeem: ScriptBuf,
214 hd_path: StandardHDPath
215}
216
217impl BitcoinApp {
218
219 pub fn get_address(&self, hd_path: &dyn HDPath, opts: GetAddressOpts) -> Result<AddressResponse, HWKeyError> {
225 let mut handle = self.ledger.lock().unwrap();
226 BitcoinApp::get_address_internal(&mut *handle, hd_path, opts)
227 }
228
229 fn get_address_internal(device: &mut dyn LedgerTransport, hd_path: &dyn HDPath, opts: GetAddressOpts) -> Result<AddressResponse, HWKeyError> {
230 log::trace!("Get address for {:?}", hd_path.as_custom());
231 let apdu = ApduBuilder::new(COMMAND_GET_ADDRESS)
232 .with_data(hd_path.to_bytes().as_slice())
233 .with_p1(if opts.confirmation {1} else {0})
234 .with_p2(opts.address_type as u8)
235 .build();
236 sendrecv(device, &apdu)
237 .and_then(|res| AddressResponse::try_from((res, opts)))
238 }
239
240 fn witness_redeem(pubkey: &PublicKey) -> ScriptBuf {
241 let compressed = CompressedPublicKey::try_from(*pubkey)
242 .map_err(|_| HWKeyError::CryptoError("Invalid public key".to_string())).unwrap();
243 let key_hash = WPubkeyHash::from(&compressed);
244 ScriptBuf::p2wpkh_script_code(key_hash)
245 }
246
247 pub fn sign_tx(&self, tx: &mut Transaction, config: &SignTx) -> Result<Vec<Signature>, HWKeyError> {
250 let mut device = self.ledger.lock().unwrap();
251 let mut inputs: Vec<InputDetails> = Vec::with_capacity(tx.input.len());
258
259 for ui in config.inputs.iter() {
260 let address = BitcoinApp::get_address_internal(&mut *device, &ui.hd_path, GetAddressOpts {
261 network: config.network,
262 ..GetAddressOpts::default()
263 })?;
264
265 inputs.push(InputDetails {
266 prev_tx: tx.input[ui.index].clone(),
267 amount: ui.amount,
268 from_address: address.clone(),
269 hd_path: ui.hd_path.clone(),
270 redeem: BitcoinApp::witness_redeem(&address.pubkey)
271 });
272 }
273
274 self.start_untrusted_hash_tx(&mut *device, true, &inputs, tx, false)?;
275
276 self.finalize_outputs(&mut *device, tx)?;
278
279 let mut signatures = Vec::with_capacity(inputs.len());
281 for (i, input) in inputs.iter().enumerate() {
282 let ic = input.clone();
283 self.start_untrusted_hash_tx(&mut *device, false,&vec![ic], tx, true)?;
284 let signature = self.untrusted_hash_sign(&mut *device, input, tx.lock_time.to_consensus_u32())?;
285 tx.input[i].witness = Witness::p2wpkh(&signature, &input.from_address.pubkey.inner);
286 signatures.push(signature);
287 }
288
289 Ok(signatures)
290 }
291
292 fn start_untrusted_hash_tx(&self, device: &mut dyn LedgerTransport, is_new_tx: bool, inputs: &[InputDetails], tx: &Transaction, second_pass: bool) -> Result<(), HWKeyError> {
294 let mut data: Vec<u8> = Vec::new();
295 data.write_u32::<LittleEndian>(tx.version.0 as u32)
297 .map_err(|_| HWKeyError::EncodingError("Failed to encode version".to_string()))?;
298 data.extend_from_slice(serialize(&VarInt(inputs.len() as u64)).as_slice());
299 for ti in inputs.iter() {
300 data.push(0x02);
302 data.extend_from_slice(serialize(&ti.prev_tx.previous_output).as_slice());
304 data.write_u64::<LittleEndian>(ti.amount)
306 .map_err(|_| HWKeyError::EncodingError("Failed to encode amount".to_string()))?;
307
308 if second_pass {
312 data.extend_from_slice(serialize(&ti.redeem).as_slice());
315 } else {
316 data.extend_from_slice(serialize(&VarInt(0u64)).as_slice());
318 };
319 data.extend_from_slice(serialize(&ti.prev_tx.sequence).as_slice());
321 }
322
323
324 let outputs_count: u64 = if second_pass {
325 0
327 } else {
328 tx.output.len() as u64
329 };
330 data.extend_from_slice(serialize(&VarInt(outputs_count)).as_slice());
331
332
333 let data = data.chunks(CHUNK_SIZE - 28);
334 for (i, chunk) in data.enumerate() {
335 let first = i == 0;
336 let p1 = if first {
339 0x00
340 } else {
341 0x80
342 };
343 let p2 = if first {
348 if is_new_tx {
349 0x02
350 } else {
351 0x80
352 }
353 } else {
354 0x00
355 };
356 let apdu = ApduBuilder::new(COMMAND_UNTRUSTED_HASH_TX)
357 .with_p1(p1)
358 .with_p2(p2)
359 .with_data(chunk)
360 .build();
361 sendrecv(device, &apdu)?;
362 }
363 Ok(())
364 }
365
366 fn untrusted_hash_sign(&self, device: &mut dyn LedgerTransport, input: &InputDetails, locktime: u32) -> Result<Signature, HWKeyError> {
367 let mut data: Vec<u8> = Vec::new();
368 data.extend_from_slice(input.hd_path.to_bytes().as_slice());
369 data.push(0x00); data.write_u32::<LittleEndian>(locktime) .map_err(|_| HWKeyError::EncodingError("Failed to encode locktime".to_string()))?;
372 data.push(EcdsaSighashType::All as u8); let apdu = ApduBuilder::new(COMMAND_UNTRUSTED_HASH_SIGN)
374 .with_p1(0x00)
375 .with_p2(0x00)
376 .with_data(data.as_slice())
377 .build();
378 let mut signature = sendrecv(device, &apdu)?;
379 if !signature.is_empty() {
382 signature[0] &= 0xFE;
383 }
384 Signature::from_slice(signature.as_slice())
385 .map_err(|_| HWKeyError::CryptoError("Received invalid signature from Ledger".to_string()))
386 }
387
388 fn finalize_outputs(&self, device: &mut dyn LedgerTransport, tx: &Transaction) -> Result<(), HWKeyError> {
389 let mut data: Vec<u8> = Vec::new();
390 data.extend_from_slice(serialize(&VarInt(tx.output.len() as u64)).as_slice());
391 for output in &tx.output {
392 data.write_u64::<LittleEndian>(output.value.to_sat())
393 .map_err(|_| HWKeyError::EncodingError("Failed to encode amount".to_string()))?;
394 let script = &output.script_pubkey;
395 data.extend_from_slice(serialize(script).as_slice());
397 }
398
399 let data = data.chunks(CHUNK_SIZE - 28);
400 let data_len = data.len();
401 for (i, chunk) in data.enumerate() {
402 let last = i == data_len - 1;
403 let apdu = ApduBuilder::new(COMMAND_HASH_INPUT_FINALIZE_FULL)
404 .with_p1(if !last {0x00} else {0x80})
408 .with_p2(0x00)
409 .with_data(chunk)
410 .build();
411 let result = sendrecv(device, &apdu)?;
412 if last && result.ne(&vec![0x00u8, 0x00u8]) {
413 return Err(HWKeyError::CryptoError("Validation required".to_string()))
414 }
415 }
416 Ok(())
417 }
418
419 fn get_version(&self) -> Option<AppVersion> {
420 let apdu = ApduBuilder::new(COMMAND_COIN_VERSION)
421 .build();
422 let device = self.ledger.lock().unwrap();
423 let resp = sendrecv(&*device, &apdu);
424 if resp.is_err() {
425 return None
426 }
427 AppVersion::try_from(resp.unwrap()).ok()
428 }
429}
430
431impl PubkeyAddressApp for BitcoinApp {
432 fn get_extkey_at(&self, hd_path: &dyn HDPath) -> Result<Box<dyn AsExtendedKey>, HWKeyError> {
433 let address = self.get_address(hd_path, GetAddressOpts {
434 confirmation: false,
435 ..GetAddressOpts::default()
436 })?;
437 Ok(Box::new(address))
438 }
439}
440
441#[derive(Copy, Clone, Eq, PartialEq, Debug)]
442pub enum BitcoinApps {
443 Mainnet,
444 Testnet
445}
446
447#[derive(Clone, Eq, PartialEq, Debug)]
448pub struct AppVersion {
449 p2pkh: [u8; 2],
450 p2sh: [u8; 2],
451 family: u8,
452 name: String,
453 ticker: String
454}
455
456impl TryFrom<Vec<u8>> for AppVersion {
457 type Error = ();
458
459 fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
460 let mut expected_len = 2 + 2 + 1 + 1;
461 if value.len() < expected_len {
462 return Err(())
463 }
464 let p2pkh: [u8; 2] = [value[0], value[1]];
465 let p2sh: [u8; 2] = [value[2], value[3]];
466 let family = value[4];
467 let name_len = value[5] as usize;
468 expected_len += name_len;
469 if value.len() < expected_len {
470 return Err(())
471 }
472 let name = String::from_utf8(value[6..6+name_len].to_vec()).map_err(|_| ())?;
473 let ticker_len = value[6 + name_len] as usize;
474 let ticker_start = 6 + name_len + 1;
475 expected_len += ticker_len;
476 if value.len() < expected_len {
477 return Err(())
478 }
479 let ticker = String::from_utf8(value[ticker_start..ticker_start+ticker_len].to_vec()).map_err(|_| ())?;
480 Ok(AppVersion {
481 p2pkh, p2sh,
482 family,
483 name, ticker
484 })
485 }
486}
487
488impl LedgerApp for BitcoinApp {
489 type Networks = BitcoinApps;
490
491 fn new(manager: Arc<Mutex<dyn LedgerTransport>>) -> Self {
492 BitcoinApp { ledger: manager }
493 }
494
495 fn is_open(&self) -> Option<Self::Networks> {
496 self.get_version().and_then(|ver| {
497 if ver.family == 1 && ver.name == "Bitcoin" {
498 match ver.ticker.as_str() {
499 "BTC" => Some(BitcoinApps::Mainnet),
500 "TEST" => Some(BitcoinApps::Testnet),
501 _ => None
502 }
503 } else {
504 None
505 }
506 })
507 }
508}
509
510#[cfg(test)]
511mod tests {
512 use crate::ledger::app::bitcoin::{AddressResponse, GetAddressOpts, AppVersion};
513 use std::convert::TryFrom;
514 use bitcoin::Address;
515 use std::str::FromStr;
516
517 #[test]
518 fn decode_btc_app() {
519 let resp = hex::decode("000000050107426974636f696e03425443").unwrap();
520 let app_ver = AppVersion::try_from(resp);
521 assert!(app_ver.is_ok());
522 assert_eq!(
523 AppVersion {
524 p2pkh: [0, 0],
525 p2sh: [0, 5],
526 family: 1,
527 name: "Bitcoin".to_string(),
528 ticker: "BTC".to_string()
529 },
530 app_ver.unwrap()
531 )
532 }
533
534 #[test]
535 fn decode_btctest_app() {
536 let resp = hex::decode("000000050107426974636f696e0454455354").unwrap();
537 let app_ver = AppVersion::try_from(resp);
538 assert!(app_ver.is_ok());
539 assert_eq!(
540 AppVersion {
541 p2pkh: [0, 0],
542 p2sh: [0, 5],
543 family: 1,
544 name: "Bitcoin".to_string(),
545 ticker: "TEST".to_string()
546 },
547 app_ver.unwrap()
548 )
549 }
550
551 #[test]
552 fn decode_segwit_address_1() {
553 let resp = hex::decode("410465fa75cc427606b99d9aaa326fdc7d0d30add37c545c5795eab1112839ccb406198798942cc6ccac5cc1933b584b23a82f66278513f38a4765e0cdf44b11d5eb2a6263317161616179796b7272783834636c676e706366717530306e6d663267336d6637663533706b336ee115bac4f8c9019b63a1dbec0edf5c22ed14bf94508ff082926964c123c0906c9000000000000000000000000000000000000000000000000000000000000000c901").unwrap();
554 let parsed = AddressResponse::try_from((resp, GetAddressOpts::default()));
555 assert!(parsed.is_ok(), "{:?}", parsed);
556 let parsed = parsed.unwrap();
557 assert_eq!(Address::from_str("bc1qaaayykrrx84clgnpcfqu00nmf2g3mf7f53pk3n").unwrap().assume_checked(), parsed.address);
558 assert_eq!(
559 "0365fa75cc427606b99d9aaa326fdc7d0d30add37c545c5795eab1112839ccb406",
560 hex::encode(parsed.pubkey.to_bytes()));
561 assert_eq!(
562 "e115bac4f8c9019b63a1dbec0edf5c22ed14bf94508ff082926964c123c0906c",
563 hex::encode(parsed.chaincode.as_bytes())
564 )
565 }
566
567 #[test]
568 fn decode_segwit_address_2() {
569 let resp = hex::decode("410423e3b63f8bfec04e968b6b413242006e59e74972617543325116d836521fadb548bac4825b5175c971a4bcae42d75ba622f130048860099a2548980e6e9c06402a6263317175746e616c63776a6561397a6633387667637a6b6e6377387376646339677a79736c6176776e40b2f931e05f7d88850de2ca6f3a5cb68a95740139944d8e5fb91f7b6e23772090000000000000000000000000000000000000000000000000000000000000005f7d").unwrap();
570 let parsed = AddressResponse::try_from((resp, GetAddressOpts::default()));
571 assert!(parsed.is_ok(), "{:?}", parsed);
572 let parsed = parsed.unwrap();
573 assert_eq!(Address::from_str("bc1qutnalcwjea9zf38vgczkncw8svdc9gzyslavwn").unwrap().assume_checked(), parsed.address);
574 assert_eq!(
575 "0223e3b63f8bfec04e968b6b413242006e59e74972617543325116d836521fadb5",
576 hex::encode(parsed.pubkey.to_bytes()));
577 assert_eq!(
578 "40b2f931e05f7d88850de2ca6f3a5cb68a95740139944d8e5fb91f7b6e237720",
579 hex::encode(parsed.chaincode.as_bytes())
580 )
581 }
582
583 #[test]
584 fn decode_segwit_address_3() {
585 let resp = hex::decode("4104cbf9b7ef45036927be859f4d0125f404ef1247878fb97c2b11c05726df0f2323833595ea361631ffeef009b8fa760073a7943a904e04b5dca373fdfd91b1d8342a626331717472346d37776d33336334777a79776833746774706b6b706430776e64326c6d79797166396d8ea6ceaac3341fd23f07c23702ab4303683cce2ddb9d8a4bdb080d4c27b53cae9000000000000000000000000000000000000000000000000000000000000000341f").unwrap();
586 let parsed = AddressResponse::try_from((resp, GetAddressOpts::default()));
587 assert!(parsed.is_ok(), "{:?}", parsed);
588 let parsed = parsed.unwrap();
589 assert_eq!(Address::from_str("bc1qtr4m7wm33c4wzywh3tgtpkkpd0wnd2lmyyqf9m").unwrap().assume_checked(), parsed.address);
590 assert_eq!(
591 "02cbf9b7ef45036927be859f4d0125f404ef1247878fb97c2b11c05726df0f2323",
592 hex::encode(parsed.pubkey.to_bytes()));
593 assert_eq!(
594 "8ea6ceaac3341fd23f07c23702ab4303683cce2ddb9d8a4bdb080d4c27b53cae",
595 hex::encode(parsed.chaincode.as_bytes())
596 )
597 }
598
599 #[test]
600 fn decode_compat_address_1() {
601 let resp = hex::decode("41047311bac2b7908931e73f5b8d02ca9cf8ff294bfad6d2e1e5bba707757d97be3591b954c37b9db706700667d9c15ec31d11053bcc644102fee05f2331c4f28b82223336725948586a72517035754a56665a666457355933467671474446445668746d73dae818a01fbfce0d8bf2deaae7d462a6a79a3be90ec011a79c65ec7251ffab2c90000000000000000000000000000000000000000000000000000000000000000000000000000000d462").unwrap();
602 let parsed = AddressResponse::try_from((resp, GetAddressOpts::compat_address()));
603 assert!(parsed.is_ok(), "{:?}", parsed);
604 let parsed = parsed.unwrap();
605 assert_eq!(Address::from_str("36rYHXjrQp5uJVfZfdW5Y3FvqGDFDVhtms").unwrap().assume_checked(), parsed.address);
606 assert_eq!(
607 "027311bac2b7908931e73f5b8d02ca9cf8ff294bfad6d2e1e5bba707757d97be35",
608 hex::encode(parsed.pubkey.to_bytes()));
609 assert_eq!(
610 "dae818a01fbfce0d8bf2deaae7d462a6a79a3be90ec011a79c65ec7251ffab2c",
611 hex::encode(parsed.chaincode.as_bytes())
612 )
613 }
614}