use anyhow::{anyhow, Result}; use byteorder::WriteBytesExt; use byteorder::LE; use group::GroupEncoding; use hex_literal::hex; use jubjub::Fr; use jubjub::SubgroupPoint; use ledger_apdu::APDUCommand; use ledger_transport_hid::{hidapi::HidApi, TransportNativeHID}; use reqwest::Client; use serde_json::Value; use std::io::Write; use zcash_primitives::sapling::ProofGenerationKey; use zcash_primitives::zip32::DiversifiableFullViewingKey; fn handle_error_code(code: u16) -> Result<()> { match code { 0x9000 => Ok(()), 0x6D02 => Err(anyhow!("Zcash Application NOT OPEN")), 0x6985 => Err(anyhow!("Tx REJECTED by User")), 0x5515 => Err(anyhow!("Ledger is LOCKED")), _ => Err(anyhow!("Ledger device returned error code {:#06x}", code)), } } async fn apdu_hid(data: &[u8]) -> Result> { let api = HidApi::new()?; let transport = TransportNativeHID::new(&api)?; let command = APDUCommand { cla: data[0], ins: data[1], p1: data[2], p2: data[3], data: &data[5..], }; log::info!("ins {}", data[1]); let response = transport.exchange(&command)?; let error_code = response.retcode(); log::info!("error_code {}", error_code); handle_error_code(error_code)?; Ok(response.data().to_vec()) } const TEST_SERVER_IP: Option<&'static str> = option_env!("LEDGER_IP"); async fn apdu_http(data: &[u8]) -> Vec { let client = Client::new(); .post(&format!("http://{}:5000/apdu", TEST_SERVER_IP.unwrap())) .body(format!("{{\"data\": \"{}\"}}", hex::encode(data))) .send() .await?; let response_body: Value = response.json().await?; let data = response_body["data"] .as_str() .ok_or(anyhow!("No data field"))?; let data = hex::decode(data)?; let error_code = u16::from_be_bytes(data[data.len() - 2..].try_into().unwrap()); handle_error_code(error_code)?; Ok(data[..data.len() - 2].to_vec()) } pub async fn ledger_init() -> Result<()> { let mut bb: Vec = vec![]; bb.clear(); bb.write_all(&hex!("E005000000"))?; apdu(&bb).await?; Ok(()) } pub async fn ledger_get_dfvk() -> Result { let mut bb: Vec = vec![]; bb.write_all(&hex!("E006000000"))?; let dfvk_vec = apdu(&bb).await?; let mut dfvk = [0; 128]; dfvk.copy_from_slice(&dfvk_vec); let dfvk = DiversifiableFullViewingKey::from_bytes(&dfvk) .ok_or(anyhow!("Invalid diversifiable fvk"))?; Ok(dfvk) } pub async fn ledger_get_pubkey() -> Result> { let mut bb: Vec = vec![]; bb.write_all(&hex!("E007000000"))?; let pk = apdu(&bb).await?; Ok(pk) } pub async fn ledger_get_o_fvk() -> Result> { let mut bb: Vec = vec![]; bb.write_all(&hex!("E008000000"))?; let pk = apdu(&bb).await?; Ok(pk) } pub async fn ledger_init_tx(header_digest: &[u8]) -> Result> { let mut bb: Vec = vec![]; bb.write_all(&hex!("E010000020"))?; bb.write_all(header_digest)?; let main_seed = apdu(&bb).await?; Ok(main_seed) } pub async fn ledger_set_stage(stage: u8) -> Result<()> { let mut bb: Vec = vec![]; bb.write_all(&hex!("E011"))?; bb.write_u8(stage)?; bb.write_all(&hex!("0000"))?; apdu(&bb).await?; Ok(()) } pub async fn ledger_set_transparent_merkle_proof( prevouts_digest: &[u8], pubscripts_digest: &[u8], sequences_digest: &[u8], ) -> Result<()> { let mut bb: Vec = vec![]; bb.write_all(&hex!("E012000060"))?; bb.write_all(prevouts_digest)?; bb.write_all(pubscripts_digest)?; bb.write_all(sequences_digest)?; apdu(&bb).await?; Ok(()) } pub async fn ledger_set_sapling_merkle_proof( spends_digest: &[u8], memos_digest: &[u8], outputs_nc_digest: &[u8], ) -> Result<()> { let mut bb: Vec = vec![]; bb.write_all(&hex!("E013000060"))?; bb.write_all(spends_digest)?; bb.write_all(memos_digest)?; bb.write_all(outputs_nc_digest)?; apdu(&bb).await?; Ok(()) } pub async fn ledger_set_orchard_merkle_proof( anchor: &[u8], memos_digest: &[u8], outputs_nc_digest: &[u8], ) -> Result<()> { let mut bb: Vec = vec![]; bb.write_all(&hex!("E014000060"))?; bb.write_all(anchor)?; bb.write_all(memos_digest)?; bb.write_all(outputs_nc_digest)?; apdu(&bb).await?; Ok(()) } pub async fn ledger_add_t_input(amount: u64) -> Result<()> { let mut bb: Vec = vec![]; bb.write_all(&hex!("E015010008"))?; bb.write_u64::(amount)?; apdu(&bb).await?; Ok(()) } pub async fn ledger_add_t_output(amount: u64, address: &[u8]) -> Result<()> { let mut bb: Vec = vec![]; bb.write_all(&hex!("E01601001D"))?; bb.write_u64::(amount)?; bb.write_all(address)?; apdu(&bb).await?; Ok(()) } pub async fn ledger_add_s_output( amount: u64, epk: &[u8], address: &[u8], enc_compact: &[u8], ) -> Result<()> { let mut bb: Vec = vec![]; bb.write_all(&hex!("E017010087"))?; bb.write_all(address)?; bb.write_u64::(amount)?; bb.write_all(epk)?; bb.write_all(enc_compact)?; apdu(&bb).await?; Ok(()) } pub async fn ledger_add_o_action( nf: &[u8], amount: u64, epk: &[u8], address: &[u8], enc_compact: &[u8], ) -> Result<()> { let mut bb: Vec = vec![]; bb.write_all(&hex!("E0180100A7"))?; bb.write_all(nf)?; bb.write_all(address)?; bb.write_u64::(amount)?; bb.write_all(epk)?; bb.write_all(enc_compact)?; apdu(&bb).await?; Ok(()) } pub async fn ledger_set_net_sapling(net: i64) -> Result<()> { let mut bb: Vec = vec![]; bb.write_all(&hex!("E019000008"))?; bb.write_i64::(net)?; apdu(&bb).await?; Ok(()) } pub async fn ledger_set_net_orchard(net: i64) -> Result<()> { let mut bb: Vec = vec![]; bb.write_all(&hex!("E01A000008"))?; bb.write_i64::(net)?; apdu(&bb).await?; Ok(()) } pub async fn ledger_confirm_fee() -> Result<()> { let mut bb: Vec = vec![]; bb.write_all(&hex!("E01B010000"))?; apdu(&bb).await?; Ok(()) } pub async fn ledger_get_sighash() -> Result> { let mut bb: Vec = vec![]; bb.write_all(&hex!("E020000000"))?; let sighash = apdu(&bb).await?; Ok(sighash) } pub async fn ledger_get_proofgen_key() -> Result { let mut bb: Vec = vec![]; bb.write_all(&hex!("E021000000"))?; let proofgen_key = apdu(&bb).await?; let proofgen_key = ProofGenerationKey { ak: SubgroupPoint::from_bytes(proofgen_key[0..32].try_into().unwrap()).unwrap(), nsk: Fr::from_bytes(proofgen_key[32..64].try_into().unwrap()).unwrap(), }; Ok(proofgen_key) } pub async fn ledger_sign_transparent(txin_digest: &[u8]) -> Result> { let mut bb: Vec = vec![]; bb.write_all(&hex!("E022000020"))?; bb.write_all(txin_digest)?; let signature = apdu(&bb).await?; Ok(signature) } pub async fn ledger_sign_sapling() -> Result> { let mut bb: Vec = vec![]; bb.write_all(&hex!("E023000000"))?; let signature = apdu(&bb).await?; Ok(signature) } pub async fn ledger_sign_orchard() -> Result> { let mut bb: Vec = vec![]; bb.write_all(&hex!("E024000000"))?; let signature = apdu(&bb).await?; Ok(signature) } pub async fn ledger_end_tx() -> Result<()> { let mut bb: Vec = vec![]; bb.write_all(&hex!("E030000000"))?; apdu(&bb).await?; Ok(()) } pub async fn ledger_cmu(data: &[u8]) -> Result> { let mut bb: Vec = vec![]; bb.write_all(&hex!("E0800000"))?; bb.write_u8(data.len() as u8)?; bb.write_all(data)?; let cmu = apdu(&bb).await?; Ok(cmu) } pub async fn ledger_jubjub_hash(data: &[u8]) -> Result> { let mut bb: Vec = vec![]; bb.write_all(&hex!("E0810000"))?; bb.write_u8(data.len() as u8)?; bb.write_all(data)?; let cmu = apdu(&bb).await?; Ok(cmu) } pub async fn ledger_pedersen_hash(data: &[u8]) -> Result> { let mut bb: Vec = vec![]; bb.write_all(&hex!("E0820000"))?; bb.write_u8(data.len() as u8)?; bb.write_all(data)?; let cmu = apdu(&bb).await?; Ok(cmu) } pub async fn ledger_test_math(data: &[u8]) -> Result> { let mut bb: Vec = vec![]; bb.write_all(&hex!("E0FF0000"))?; bb.write_u8(data.len() as u8)?; bb.write_all(data)?; let res = apdu(&bb).await?; Ok(res) }