From f42b55f42bbc2db24a271ce205a525182d1dab64 Mon Sep 17 00:00:00 2001 From: Hanh Date: Sun, 16 Apr 2023 18:27:38 +1000 Subject: [PATCH] Support for transparent in/out --- src/db/migration.rs | 24 ++++++- src/ledger.rs | 2 - src/ledger/builder.rs | 146 +++++++++++++++++++++++++++++++++++----- src/ledger/transport.rs | 22 ++++-- src/lib.rs | 2 +- src/main/ledger.rs | 4 +- 6 files changed, 169 insertions(+), 31 deletions(-) diff --git a/src/db/migration.rs b/src/db/migration.rs index b9cdc95..8a3ed8b 100644 --- a/src/db/migration.rs +++ b/src/db/migration.rs @@ -34,7 +34,7 @@ pub fn reset_db(connection: &Connection) -> anyhow::Result<()> { Ok(()) } -const LATEST_VERSION: u32 = 7; +const LATEST_VERSION: u32 = 8; pub fn init_db(connection: &Connection, network: &Network, has_ua: bool) -> anyhow::Result<()> { connection.execute( @@ -293,6 +293,28 @@ pub fn init_db(connection: &Connection, network: &Network, has_ua: bool) -> anyh )?; } + if version < 8 { + connection.execute( + "CREATE TABLE IF NOT EXISTS new_taddrs ( + account INTEGER PRIMARY KEY NOT NULL, + sk TEXT, + address TEXT NOT NULL)", + [], + )?; + connection.execute( + "INSERT INTO new_taddrs( + account, sk, address + ) SELECT * FROM taddrs", + [], + )?; + connection.execute("DROP TABLE taddrs", [])?; + connection.execute( + "ALTER TABLE new_taddrs RENAME TO taddrs", + [], + )?; + + } + if version != LATEST_VERSION { update_schema_version(connection, LATEST_VERSION)?; connection.cache_flush()?; diff --git a/src/ledger.rs b/src/ledger.rs index be97167..b769190 100644 --- a/src/ledger.rs +++ b/src/ledger.rs @@ -5,5 +5,3 @@ mod builder; mod tests; pub use builder::build_broadcast_tx; - -pub use transport::ledger_get_taddr; diff --git a/src/ledger/builder.rs b/src/ledger/builder.rs index c6e3092..1e02f5f 100644 --- a/src/ledger/builder.rs +++ b/src/ledger/builder.rs @@ -15,6 +15,13 @@ use ripemd::{Digest, Ripemd160}; use secp256k1::{All, PublicKey, Secp256k1, SecretKey}; use sha2::Sha256; use tonic::{Request, transport::Channel}; +use zcash_client_backend::address::RecipientAddress; +use zcash_client_backend::encoding::decode_transparent_address; +use zcash_primitives::consensus::Parameters; +use zcash_primitives::legacy::{TransparentAddress, Script}; +use zcash_primitives::transaction::components::transparent::builder::Unauthorized; +use zcash_primitives::transaction::components::{transparent, TxIn, OutPoint, TxOut}; +use zcash_primitives::transaction::txid::TxIdDigester; use crate::{Destination, Source, TransactionPlan, RawTransaction, CompactTxStreamerClient}; use crate::ledger::transport::*; use anyhow::{anyhow, Result}; @@ -33,6 +40,11 @@ use zcash_primitives::{ }; use zcash_proofs::{prover::LocalTxProver, sapling::SaplingProvingContext}; +struct TransparentInputUnAuthorized { + utxo: OutPoint, + coin: TxOut, +} + struct SpendDescriptionUnAuthorized { cv: ValueCommitment, anchor: Fq, @@ -43,19 +55,22 @@ struct SpendDescriptionUnAuthorized { pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient, tx_plan: &TransactionPlan, prover: &LocalTxProver) -> Result { ledger_init().await?; - let address = ledger_get_address().await?; - println!("address {}", address); + let pubkey = ledger_get_pubkey().await?; + let network = MainNetwork; // TODO: Pass network as param let secp = Secp256k1::::new(); - // TODO: Not used atm - let sk: SecretKey = "ee729122985b068958c6c62afe6ee70927d3e7f9405c0b9a09e822cd2f5ce2ae" - .parse() - .unwrap(); - let pub_key = PublicKey::from_secret_key(&secp, &sk); - let pub_key = pub_key.serialize(); - let pub_key = Ripemd160::digest(&Sha256::digest(&pub_key)); - let tpub_key: [u8; 20] = pub_key.into(); + let taddr = &tx_plan.taddr; + let taddr = decode_transparent_address( + &network.b58_pubkey_address_prefix(), + &network.b58_script_address_prefix(), + taddr + )?.ok_or(anyhow!("Invalid taddr"))?; + let pkh = match taddr { + TransparentAddress::PublicKey(pkh) => pkh, + _ => unreachable!() + }; + let tin_pubscript = taddr.script(); // Compute header digest let mut h = Params::new() @@ -134,6 +149,7 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient, t let mut sapling_context = SaplingProvingContext::new(); let mut value_balance = ValueSum::zero(); + let mut vin = vec![]; let mut shielded_spends = vec![]; for sp in tx_plan.spends.iter() { match sp.source { @@ -141,10 +157,17 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient, t prevouts_hasher.update(&txid); prevouts_hasher.write_u32::(index)?; trscripts_hasher.update(&hex!("1976a914")); - trscripts_hasher.update(&tpub_key); + trscripts_hasher.update(&pkh); trscripts_hasher.update(&hex!("88ac")); sequences_hasher.update(&hex!("FFFFFFFF")); + vin.push(TransparentInputUnAuthorized { + utxo: OutPoint::new(txid, index), + coin: TxOut { value: Amount::from_u64(sp.amount).unwrap(), + script_pubkey: tin_pubscript.clone(), // will always use the h/w address + } + }); + ledger_add_t_input(sp.amount).await?; } Source::Sapling { diversifier, rseed, ref witness, .. } => { @@ -195,8 +218,10 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient, t .hash_length(32) .personal(b"ZTxIdSSpendsHash") .to_state(); - spends_hasher.update(spends_compact_digest.as_bytes()); - spends_hasher.update(spends_non_compact_digest.as_bytes()); + if !shielded_spends.is_empty() { + spends_hasher.update(spends_compact_digest.as_bytes()); + spends_hasher.update(spends_non_compact_digest.as_bytes()); + } let spends_digest = spends_hasher.finalize(); println!("SPENDS {}", hex::encode(spends_digest)); @@ -210,13 +235,23 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient, t .personal(b"ZTxIdSOutN__Hash") .to_state(); + let mut vout = vec![]; let mut shielded_outputs = vec![]; for output in tx_plan.outputs.iter() { if let Destination::Transparent(raw_address) = output.destination { - ledger_add_t_output(output.amount, &raw_address[1..21]).await?; + if raw_address[0] != 0 { + anyhow::bail!("Only t1 addresses are supported"); + } + ledger_add_t_output(output.amount, &raw_address).await?; + let ta = TransparentAddress::PublicKey(raw_address[1..21].try_into().unwrap()); + vout.push(TxOut { + value: Amount::from_u64(output.amount).unwrap(), + script_pubkey: ta.script() + }); } } ledger_set_stage(2).await?; + let has_transparent = !vin.is_empty() || !vout.is_empty(); for output in tx_plan.outputs.iter() { if let Destination::Sapling(raw_address) = output.destination { @@ -276,6 +311,39 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient, t ledger_set_net_sapling(-tx_plan.net_chg[0]).await?; + let mut vins = vec![]; + for tin in vin.iter() { + let mut txin_hasher = Params::new() + .hash_length(32) + .personal(b"Zcash___TxInHash") + .to_state(); + + txin_hasher.update(tin.utxo.hash()); + txin_hasher.update(&tin.utxo.n().to_le_bytes()); + txin_hasher.update(&tin.coin.value.to_i64_le_bytes()); + txin_hasher.update(&[0x19]); // add the script length + txin_hasher.update(&tin.coin.script_pubkey.0); + txin_hasher.update(&0xFFFFFFFFu32.to_le_bytes()); + let txin_hash = txin_hasher.finalize(); + println!("TXIN {}", hex::encode(txin_hash)); + + let signature = ledger_sign_transparent(txin_hash.as_bytes()).await?; + let signature = secp256k1::ecdsa::Signature::from_der(&signature)?; + let mut signature = signature.serialize_der().to_vec(); + signature.extend(&[0x01]); // add SIG_HASH_ALL + + // witness is PUSH(signature) PUSH(pk) + let script_sig = Script::default() << &*signature << &*pubkey; + + let txin = TxIn:: { + prevout: tin.utxo.clone(), + script_sig, + sequence: 0xFFFFFFFFu32, + }; + println!("TXIN {:?}", txin); + vins.push(txin); + } + let mut signatures = vec![]; for _sp in shielded_spends.iter() { let signature = ledger_sign_sapling().await?; @@ -292,15 +360,57 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient, t signatures.push(signature); } - let shielded_spends = shielded_spends.into_iter().zip(signatures.into_iter()).map(|(sp, spend_auth_sig)| - SpendDescription::<_> { cv: sp.cv, anchor: sp.anchor, nullifier: sp.nullifier, rk: sp.rk, zkproof: sp.zkproof, + // let sk = SecretKey::from_slice(&hex::decode("a2a6cef015bbce367e916d83152094d6492c422beb037edcbbd50593aa02b183").unwrap()).unwrap(); + // let mut transparent_builder = transparent::builder::TransparentBuilder::empty(); + // for vin in vin.iter() { + // transparent_builder.add_input(sk.clone(), vin.utxo.clone(), vin.coin.clone()).unwrap(); + // } + // for vout in vout.iter() { + // transparent_builder.add_output(&vout.recipient_address().unwrap(), vout.value).unwrap(); + // } + // let transparent_bundle = transparent_builder.build(); + // let transparent_bundle = { + // let consensus_branch_id = + // BranchId::for_height(&network, BlockHeight::from_u32(tx_plan.anchor_height)); + // let version = TxVersion::suggested_for_branch(consensus_branch_id); + // let unauthed_tx: TransactionData = + // TransactionData::from_parts( + // version, + // consensus_branch_id, + // 0, + // BlockHeight::from_u32(tx_plan.expiry_height), + // transparent_bundle.clone(), + // None, + // None, + // None, + // ); + + // let txid_parts = unauthed_tx.digest(TxIdDigester); + + // transparent_bundle.unwrap().apply_signatures(&unauthed_tx, &txid_parts) + // }; + // println!("VIN 2 {:?}", &transparent_bundle.vin[0]); + + let transparent_bundle = transparent::Bundle:: { + vin: vins, + vout, + authorization: transparent::Authorized + }; + + let shielded_spends: Vec<_> = shielded_spends.into_iter().zip(signatures.into_iter()).map(|(sp, spend_auth_sig)| + SpendDescription:: { cv: sp.cv, anchor: sp.anchor, nullifier: sp.nullifier, rk: sp.rk, zkproof: sp.zkproof, spend_auth_sig }).collect(); + let has_sapling = !shielded_spends.is_empty() || !shielded_outputs.is_empty(); let value: i64 = value_balance.try_into().unwrap(); let value = Amount::from_i64(value).unwrap(); let sighash = ledger_get_sighash().await?; + println!("TXID {}", hex::encode(&sighash)); let binding_sig = sapling_context.binding_sig(value, &sighash.try_into().unwrap()).unwrap(); + println!("{} {}", has_transparent, has_sapling); + println!("{} {} {:?}", shielded_spends.len(), shielded_outputs.len(), value_balance); + let sapling_bundle = Bundle::<_>::from_parts( shielded_spends, shielded_outputs, value, SapAuthorized { binding_sig } ); @@ -310,9 +420,9 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient, t consensus_branch_id: BranchId::Nu5, lock_time: 0, expiry_height: BlockHeight::from_u32(tx_plan.expiry_height), - transparent_bundle: None, + transparent_bundle: if has_transparent { Some(transparent_bundle) } else { None }, sprout_bundle: None, - sapling_bundle: Some(sapling_bundle), + sapling_bundle: if has_sapling { Some(sapling_bundle) } else { None }, orchard_bundle: None, }; diff --git a/src/ledger/transport.rs b/src/ledger/transport.rs index b8c7601..ec9cd3b 100644 --- a/src/ledger/transport.rs +++ b/src/ledger/transport.rs @@ -172,6 +172,21 @@ pub async fn ledger_sign_sapling() -> Result> { Ok(signature) } +pub async fn ledger_sign_transparent(txin_digest: &[u8]) -> Result> { + let mut bb: Vec = vec![]; + bb.write_all(&hex!("E014000020"))?; + bb.write_all(txin_digest)?; + let signature = apdu(&bb).await; + Ok(signature) +} + +pub async fn ledger_get_pubkey() -> Result> { + let mut bb: Vec = vec![]; + bb.write_all(&hex!("E013000000"))?; + let pk = apdu(&bb).await; + Ok(pk) +} + pub async fn ledger_cmu(data: &[u8]) -> Result> { let mut bb: Vec = vec![]; bb.write_all(&hex!("E0800000"))?; @@ -198,10 +213,3 @@ pub async fn ledger_pedersen_hash(data: &[u8]) -> Result> { let cmu = apdu(&bb).await; Ok(cmu) } - -pub async fn ledger_get_taddr() -> Result> { - let mut bb: Vec = vec![]; - bb.write_all(&hex!("E013000000"))?; - let pkh = apdu(&bb).await; - Ok(pkh) -} diff --git a/src/lib.rs b/src/lib.rs index 1249685..b01e16a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -135,6 +135,6 @@ pub fn init_test() { } #[cfg(feature = "ledger")] -pub use ledger::{build_broadcast_tx, ledger_get_taddr}; +pub use ledger::{build_broadcast_tx}; pub use taddr::derive_from_secretkey; diff --git a/src/main/ledger.rs b/src/main/ledger.rs index fbff5b7..c534012 100644 --- a/src/main/ledger.rs +++ b/src/main/ledger.rs @@ -5,9 +5,9 @@ use std::{ }; use ripemd::Digest; use secp256k1::SecretKey; -use warp_api_ffi::{TransactionPlan, connect_lightwalletd, build_broadcast_tx, ledger_get_taddr, derive_from_secretkey}; +use warp_api_ffi::{TransactionPlan, connect_lightwalletd, build_broadcast_tx, derive_from_secretkey}; use anyhow::Result; -use zcash_client_backend::encoding::encode_transparent_address; +use zcash_client_backend::{encoding::encode_transparent_address, address::RecipientAddress}; use zcash_primitives::{legacy::TransparentAddress, consensus::{Parameters, Network, Network::MainNetwork}}; use zcash_proofs::prover::LocalTxProver;