diff --git a/Cargo.toml b/Cargo.toml index 2b873d0..1002012 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ name = "warp_api_ffi" crate-type = ["rlib"] [dependencies] +nonempty = "0.7" env_logger = "0.9.0" anyhow = "1.0.40" thiserror = "1.0.25" @@ -75,6 +76,8 @@ chrono = "0.4.19" lazycell = "1.3.0" reqwest = { version = "0.11.4", features = ["json", "rustls-tls"], default-features = false } hex-literal = "0.4" +pasta_curves = "0.5" +f4jumble = { path = "../../librustzcash/components/f4jumble" } # Halo orchard = "0.3.0" @@ -108,7 +111,7 @@ objc = { version = "0.2", features = [ "objc_exception" ], optional = true } block = { version = "0.1.6", optional = true } [features] -ledger = ["ledger-apdu", "ledger-transport-hid"] +ledger = ["ledger-apdu", "ledger-transport-hid", "dotenv"] dart_ffi = ["allo-isolate", "once_cell", "android_logger"] rpc = ["rocket", "dotenv"] nodejs = ["node-bindgen"] @@ -118,38 +121,38 @@ sqlcipher = ["rusqlite/bundled-sqlcipher-vendored-openssl"] [dependencies.zcash_params] #git = "https://github.com/hhanh00/zcash-params.git" -#rev = "154f3544781500c27f58923ccc6c7e779eecf9df" +#rev = "2e118feceeaa31ef68fb83d1fc94a1a46db4569c" path = "../zcash-params" [dependencies.zcash_client_backend] #git = "https://github.com/hhanh00/librustzcash.git" -#rev = "f39dd6c2af20662fd1aaa9e00172efebd3435ff4" +#rev = "e2fe0b8d386fad99e00d6135c5caf3cc04045646" path = "../../librustzcash/zcash_client_backend" [dependencies.zcash_primitives] #git = "https://github.com/hhanh00/librustzcash.git" -#rev = "f39dd6c2af20662fd1aaa9e00172efebd3435ff4" +#rev = "e2fe0b8d386fad99e00d6135c5caf3cc04045646" path = "../../librustzcash/zcash_primitives" features = [ "transparent-inputs" ] [dependencies.zcash_proofs] #git = "https://github.com/hhanh00/librustzcash.git" -#rev = "f39dd6c2af20662fd1aaa9e00172efebd3435ff4" +#rev = "e2fe0b8d386fad99e00d6135c5caf3cc04045646" path = "../../librustzcash/zcash_proofs" [dependencies.zcash_address] #git = "https://github.com/hhanh00/librustzcash.git" -#rev = "f39dd6c2af20662fd1aaa9e00172efebd3435ff4" +#rev = "e2fe0b8d386fad99e00d6135c5caf3cc04045646" path = "../../librustzcash/components/zcash_address" [dependencies.zcash_encoding] #git = "https://github.com/hhanh00/librustzcash.git" -#rev = "f39dd6c2af20662fd1aaa9e00172efebd3435ff4" +#rev = "e2fe0b8d386fad99e00d6135c5caf3cc04045646" path = "../../librustzcash/components/zcash_encoding" [dependencies.zcash_note_encryption] #git = "https://github.com/hhanh00/librustzcash.git" -#rev = "f39dd6c2af20662fd1aaa9e00172efebd3435ff4" +#rev = "e2fe0b8d386fad99e00d6135c5caf3cc04045646" path = "../../librustzcash/components/zcash_note_encryption" [build-dependencies] @@ -161,5 +164,4 @@ criterion = "0.3.4" #These patch overrides must be included in your workspace root Cargo.toml #[patch.crates-io] -#zcash_note_encryption = { git = "https://github.com/hhanh00/librustzcash.git", rev = "ad4a1c61fdaf04ac4fb884976ad175196e695264" } -#jubjub = { git = "https://github.com/hhanh00/jubjub.git", rev = "4a3edf3d242f368b5aa418ec659d01f191127cf3" } +#zcash_note_encryption = { git = "https://github.com/hhanh00/librustzcash.git", rev = "e2fe0b8d386fad99e00d6135c5caf3cc04045646" } diff --git a/binding.h b/binding.h index 7bdcb6e..81ee8a7 100644 --- a/binding.h +++ b/binding.h @@ -415,6 +415,10 @@ struct CResult_u8 set_property(uint8_t coin, char *name, char *value); struct CResult_____c_char ledger_send(uint8_t coin, char *tx_plan); +struct CResult_u32 ledger_import_account(uint8_t coin, char *name); + +struct CResult_bool ledger_has_account(uint8_t coin, uint32_t account); + bool has_cuda(void); bool has_metal(void); diff --git a/src/api/dart_ffi.rs b/src/api/dart_ffi.rs index c0f46b6..24073f0 100644 --- a/src/api/dart_ffi.rs +++ b/src/api/dart_ffi.rs @@ -2,7 +2,7 @@ use crate::coinconfig::{init_coin, CoinConfig, MEMPOOL, MEMPOOL_RUNNER, self}; use crate::db::data_generated::fb::{ProgressT, Recipients, SendTemplate, Servers}; use crate::db::FullEncryptedBackup; use crate::note_selection::TransactionReport; -use crate::{ChainError, TransactionPlan, Tx, build_broadcast_tx}; +use crate::{ChainError, TransactionPlan, Tx}; use allo_isolate::{ffi, IntoDart}; use android_logger::Config; use flatbuffers::FlatBufferBuilder; @@ -1219,6 +1219,7 @@ pub unsafe extern "C" fn set_property( to_cresult(with_coin(coin, res)) } +#[cfg(feature = "ledger")] #[no_mangle] #[tokio::main] pub async unsafe extern "C" fn ledger_send(coin: u8, tx_plan: *mut c_char) -> CResult<*mut c_char> { @@ -1228,12 +1229,30 @@ pub async unsafe extern "C" fn ledger_send(coin: u8, tx_plan: *mut c_char) -> CR let c = CoinConfig::get(coin); let mut client = c.connect_lwd().await?; let prover = coinconfig::get_prover(); - let response = build_broadcast_tx(&mut client, &tx_plan, prover).await?; + let response = crate::ledger::build_broadcast_tx(c.chain.network(), &mut client, &tx_plan, prover).await?; Ok::<_, anyhow::Error>(response) }; to_cresult_str(res.await) } +#[cfg(feature = "ledger")] +#[no_mangle] +#[tokio::main] +pub async unsafe extern "C" fn ledger_import_account(coin: u8, name: *mut c_char) -> CResult { + from_c_str!(name); + let account = crate::ledger::import_account(coin, &name).await; + to_cresult(account) +} + +#[cfg(feature = "ledger")] +#[no_mangle] +pub unsafe extern "C" fn ledger_has_account(coin: u8, account: u32) -> CResult { + let res = |connection: &Connection| { + crate::ledger::is_external(connection, account) + }; + to_cresult(with_coin(coin, res)) +} + #[no_mangle] pub unsafe extern "C" fn has_cuda() -> bool { crate::gpu::has_cuda() diff --git a/src/api/payment_v2.rs b/src/api/payment_v2.rs index 5daadf6..2f67b10 100644 --- a/src/api/payment_v2.rs +++ b/src/api/payment_v2.rs @@ -42,11 +42,12 @@ pub async fn build_tx_plan_with_utxos( } } - let (fvk, taddr) = { + let (fvk, taddr, orchard_fvk) = { let db = c.db()?; let AccountData { fvk, .. } = db.get_account_info(account)?; let taddr = db.get_taddr(account)?.unwrap_or_default(); - (fvk, taddr) + let orchard_fvk = db.get_orchard(account)?.map(|o| hex::encode(&o.fvk)).unwrap_or_default(); + (fvk, taddr, orchard_fvk) }; let change_address = get_unified_address(coin, account, 7)?; let context = TxBuilderContext::from_height(coin, checkpoint_height)?; @@ -79,6 +80,7 @@ pub async fn build_tx_plan_with_utxos( network, &fvk, &taddr, + &orchard_fvk, checkpoint_height, expiry_height, &context.orchard_anchor, diff --git a/src/db/migration.rs b/src/db/migration.rs index 8a3ed8b..6c0387b 100644 --- a/src/db/migration.rs +++ b/src/db/migration.rs @@ -313,6 +313,12 @@ pub fn init_db(connection: &Connection, network: &Network, has_ua: bool) -> anyh [], )?; + connection.execute( + "CREATE TABLE IF NOT EXISTS hw_wallets( + account INTEGER PRIMARY KEY NOT NULL, + ledger BOOL NOT NULL)", + [], + )?; } if version != LATEST_VERSION { diff --git a/src/db/read.rs b/src/db/read.rs index 2bc00ac..e89084d 100644 --- a/src/db/read.rs +++ b/src/db/read.rs @@ -708,3 +708,10 @@ pub fn get_available_addrs(connection: &Connection, account: u32) -> anyhow::Res | if has_orchard { 4 } else { 0 }; Ok(res) } + +pub fn get_account_by_address(connection: &Connection, address: &str) -> Result> { + let id = connection.query_row("SELECT id_account FROM accounts WHERE address = ?1", [address], |row| { + row.get::<_, u32>(0) + }).optional()?; + Ok(id) +} diff --git a/src/ledger.rs b/src/ledger.rs index b769190..2d74f8e 100644 --- a/src/ledger.rs +++ b/src/ledger.rs @@ -1,7 +1,11 @@ mod transport; mod builder; +mod account; // #[cfg(test)] mod tests; pub use builder::build_broadcast_tx; +pub use transport::*; +pub use account::{import as import_account, is_external}; + diff --git a/src/ledger/account.rs b/src/ledger/account.rs new file mode 100644 index 0000000..60e7f0d --- /dev/null +++ b/src/ledger/account.rs @@ -0,0 +1,51 @@ +use anyhow::Result; +use rusqlite::{params, Connection, OptionalExtension}; +use zcash_client_backend::encoding::{encode_payment_address, encode_extended_full_viewing_key}; +use zcash_primitives::{consensus::Parameters, zip32::ExtendedFullViewingKey}; +use crate::{db::read::get_account_by_address, taddr::derive_from_pubkey, CoinConfig}; + +use super::transport::*; + +pub async fn import(coin: u8, name: &str) -> Result { + let c = CoinConfig::get(coin); + let network = c.chain.network(); + ledger_init().await?; + let dfvk = ledger_get_dfvk().await?; + let fvk = ExtendedFullViewingKey::from_diversifiable_full_viewing_key(&dfvk); + let fvk = encode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), &fvk); + let (_, pa) = dfvk.default_address(); + let address = encode_payment_address(network.hrp_sapling_payment_address(), &pa); + let mut db = c.db()?; + if let Some(account) = get_account_by_address(&db.connection, &address)? { + return Ok(account) + } + let pub_key = ledger_get_pubkey().await?; + let t_address = derive_from_pubkey(network, &pub_key)?; + + println!("OFVK"); + let o_fvk = ledger_get_o_fvk().await?; + println!("fvk {}", hex::encode(&o_fvk)); + println!("OFVK2"); + + let db_transaction = db.begin_transaction()?; + db_transaction.execute("INSERT INTO accounts(name, seed, aindex, sk, ivk, address) VALUES + (?1, NULL, 0, NULL, ?2, ?3)", params![name, fvk, address])?; + let id_account = db_transaction.last_insert_rowid() as u32; + db_transaction.execute("INSERT INTO taddrs(account, sk, address) VALUES + (?1, NULL, ?2)", params![id_account, t_address])?; + db_transaction.execute("INSERT INTO orchard_addrs(account, sk, fvk) VALUES + (?1, NULL, ?2)", params![id_account, o_fvk])?; + db_transaction.execute("INSERT INTO hw_wallets(account, ledger) VALUES + (?1, 1)", [id_account])?; + db_transaction.commit()?; + + Ok(id_account) +} + +pub fn is_external(connection: &Connection, account: u32) -> Result { + let res = connection.query_row("SELECT ledger FROM hw_wallets WHERE account = ?1", [account], |row| { + row.get::<_, bool>(0) + }).optional()?; + Ok(res.unwrap_or(false)) +} + diff --git a/src/ledger/builder.rs b/src/ledger/builder.rs index 1e02f5f..e6b4174 100644 --- a/src/ledger/builder.rs +++ b/src/ledger/builder.rs @@ -1,7 +1,3 @@ -use std::{ - io::Read, vec, -}; -use std::io::Write; use blake2b_simd::Params; use byteorder::WriteBytesExt; use byteorder::LE; @@ -9,26 +5,30 @@ use ff::{PrimeField, Field}; use group::GroupEncoding; use hex_literal::hex; use jubjub::{Fr, Fq}; + + +use orchard::keys::Scope; + use rand::{rngs::OsRng, RngCore, SeedableRng}; use rand_chacha::ChaChaRng; use ripemd::{Digest, Ripemd160}; -use secp256k1::{All, PublicKey, Secp256k1, SecretKey}; +use secp256k1::PublicKey; use sha2::Sha256; use tonic::{Request, transport::Channel}; -use zcash_client_backend::address::RecipientAddress; -use zcash_client_backend::encoding::decode_transparent_address; +use zcash_client_backend::encoding::{decode_transparent_address, encode_extended_full_viewing_key, encode_transparent_address}; +use zcash_primitives::consensus::Network; 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 zcash_primitives::zip32::ExtendedFullViewingKey; +use crate::taddr::derive_from_pubkey; use crate::{Destination, Source, TransactionPlan, RawTransaction, CompactTxStreamerClient}; use crate::ledger::transport::*; use anyhow::{anyhow, Result}; use zcash_primitives::{ consensus::{BlockHeight, BranchId, MainNetwork}, - merkle_tree::{Hashable, IncrementalWitness}, + merkle_tree::IncrementalWitness, sapling::{ note_encryption::sapling_note_encryption, value::{NoteValue, ValueCommitment, ValueSum}, Diversifier, Node, Note, PaymentAddress, Rseed, Nullifier, prover::TxProver, redjubjub::Signature, @@ -53,14 +53,40 @@ struct SpendDescriptionUnAuthorized { zkproof: [u8; GROTH_PROOF_SIZE], } -pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient, tx_plan: &TransactionPlan, prover: &LocalTxProver) -> Result { +#[allow(dead_code)] +pub async fn show_public_keys() -> Result<()> { + let network = MainNetwork; + + ledger_init().await?; + let pub_key = ledger_get_pubkey().await?; + let pub_key = PublicKey::from_slice(&pub_key)?; + let pub_key = pub_key.serialize(); + let pub_key = Ripemd160::digest(&Sha256::digest(&pub_key)); + let address = TransparentAddress::PublicKey(pub_key.into()); + let address = encode_transparent_address( + &network.b58_pubkey_address_prefix(), + &network.b58_script_address_prefix(), + &address, + ); + println!("address {}", address); + let dfvk = ledger_get_dfvk().await?; + let efvk = ExtendedFullViewingKey::from_diversifiable_full_viewing_key(&dfvk); + let efvk = encode_extended_full_viewing_key(MainNetwork.hrp_sapling_extended_full_viewing_key(), &efvk); + println!("efvk {}", efvk); + Ok(()) +} + +pub async fn build_broadcast_tx(network: &Network, client: &mut CompactTxStreamerClient, tx_plan: &TransactionPlan, prover: &LocalTxProver) -> Result { ledger_init().await?; let pubkey = ledger_get_pubkey().await?; + let ledger_taddr = derive_from_pubkey(network, &pubkey)?; - let network = MainNetwork; // TODO: Pass network as param - let secp = Secp256k1::::new(); + if ledger_taddr != tx_plan.taddr { + anyhow::bail!("This ledger wallet has a different address"); + } let taddr = &tx_plan.taddr; + let taddr = decode_transparent_address( &network.b58_pubkey_address_prefix(), &network.b58_script_address_prefix(), @@ -99,7 +125,9 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient, t let fvk = dfvk.fvk; let nf_key = proofgen_key.to_viewing_key().nk; - println!("NSK {}", hex::encode(proofgen_key.nsk.to_bytes())); + let o_fvk: [u8; 96] = ledger_get_o_fvk().await?.try_into().unwrap(); + let o_fvk = orchard::keys::FullViewingKey::from_bytes(&o_fvk).ok_or(anyhow!("Invalid Orchard FVK"))?; + assert_eq!(PROOF_GENERATION_KEY_GENERATOR * proofgen_key.nsk, fvk.vk.nk.0); // Derive rseed PRNG @@ -118,7 +146,6 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient, t .to_state(); h.update(&master_seed); let alpha = h.finalize(); - println!("ALPHA SEED {}", hex::encode(&alpha.as_bytes())); let mut alpha_rng = ChaChaRng::from_seed(alpha.as_bytes().try_into().unwrap()); let mut prevouts_hasher = Params::new() @@ -197,22 +224,22 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient, t shielded_spends.push(SpendDescriptionUnAuthorized { cv, anchor, nullifier, rk, zkproof }); } - Source::Orchard { .. } => { unimplemented!() }, + Source::Orchard { .. } => {} } } - ledger_set_stage(1).await?; + ledger_set_stage(2).await?; let prevouts_digest = prevouts_hasher.finalize(); - println!("PREVOUTS {}", hex::encode(prevouts_digest)); + log::info!("PREVOUTS {}", hex::encode(prevouts_digest)); let pubscripts_digest = trscripts_hasher.finalize(); - println!("PUBSCRIPTS {}", hex::encode(pubscripts_digest)); + log::info!("PUBSCRIPTS {}", hex::encode(pubscripts_digest)); let sequences_digest = sequences_hasher.finalize(); - println!("SEQUENCES {}", hex::encode(sequences_digest)); + log::info!("SEQUENCES {}", hex::encode(sequences_digest)); let spends_compact_digest = spends_compact_hasher.finalize(); - println!("C SPENDS {}", hex::encode(spends_compact_digest)); + log::info!("C SPENDS {}", hex::encode(spends_compact_digest)); let spends_non_compact_digest = spends_non_compact_hasher.finalize(); - println!("NC SPENDS {}", hex::encode(spends_non_compact_digest)); + log::info!("NC SPENDS {}", hex::encode(spends_non_compact_digest)); let mut spends_hasher = Params::new() .hash_length(32) @@ -223,7 +250,7 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient, t spends_hasher.update(spends_non_compact_digest.as_bytes()); } let spends_digest = spends_hasher.finalize(); - println!("SPENDS {}", hex::encode(spends_digest)); + log::info!("SPENDS {}", hex::encode(spends_digest)); let mut output_memos_hasher = Params::new() .hash_length(32) @@ -250,7 +277,7 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient, t }); } } - ledger_set_stage(2).await?; + ledger_set_stage(3).await?; let has_transparent = !vin.is_empty() || !vout.is_empty(); for output in tx_plan.outputs.iter() { @@ -266,7 +293,7 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient, t let note = Note::from_parts(recipient, value, rseed); let rcm = note.rcm(); let cmu = note.cmu(); - println!("cmu {}", hex::encode(cmu.to_bytes())); + log::info!("cmu {}", hex::encode(cmu.to_bytes())); let encryptor = sapling_note_encryption::<_, MainNetwork>(Some(ovk.clone()), note, recipient, output.memo.clone(), &mut OsRng); @@ -298,12 +325,12 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient, t shielded_outputs.push(OutputDescription { cv, cmu, ephemeral_key, enc_ciphertext, out_ciphertext, zkproof }); } } - ledger_set_stage(3).await?; + ledger_set_stage(4).await?; let memos_digest = output_memos_hasher.finalize(); - println!("MEMOS {}", hex::encode(memos_digest)); + log::info!("MEMOS {}", hex::encode(memos_digest)); let outputs_nc_digest = output_non_compact_hasher.finalize(); - println!("NC OUTPUTS {}", hex::encode(outputs_nc_digest)); + log::info!("NC OUTPUTS {}", hex::encode(outputs_nc_digest)); ledger_set_transparent_merkle_proof(prevouts_digest.as_bytes(), pubscripts_digest.as_bytes(), sequences_digest.as_bytes()).await?; @@ -311,6 +338,113 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient, t ledger_set_net_sapling(-tx_plan.net_chg[0]).await?; + ledger_set_stage(5).await?; + + + let orchard_spends: Vec<_> = tx_plan.spends.iter().filter(|&s| + if let Source::Orchard { .. } = s.source { true } else { false } + ).cloned().collect(); + let orchard_outputs: Vec<_> = tx_plan.outputs.iter().filter(|&o| + if let Destination::Orchard(_) = o.destination { true } else { false } + ).cloned().collect(); + + let num_orchard_spends = orchard_spends.len(); + let num_orchard_outputs = orchard_outputs.len(); + let num_actions = num_orchard_spends.max(num_orchard_outputs); + + let orchard_address = o_fvk.address_at(0u64, Scope::External); + let mut empty_memo = [0u8; 512]; + empty_memo[0] = 0xF6; + + // let mut actions = vec![]; + for i in 0..num_actions { + let rcv = orchard::value::ValueCommitTrapdoor::random(OsRng); + + let (_sk, dummy_fvk, dummy_note) = orchard::Note::dummy(&mut OsRng, None); + let _dummy_recipient = dummy_fvk.address_at(0u64, Scope::External); + + let alpha = pasta_curves::pallas::Scalar::random(&mut alpha_rng); + + let (fvk, spend_note) = if i < num_orchard_spends { + let sp = &tx_plan.spends[i]; + let note = match &sp.source { + Source::Orchard { rseed, rho, .. } => { + let rho = orchard::note::Nullifier::from_bytes(rho).unwrap(); + let note = orchard::Note::from_parts( + orchard_address.clone(), + orchard::value::NoteValue::from_raw(sp.amount), + rho, + orchard::note::RandomSeed::from_bytes(rseed.clone(), &rho).unwrap()).unwrap(); + note + } + _ => unreachable!() + }; + (o_fvk.clone(), note) + } + else { + (dummy_fvk, dummy_note) + }; + let nf = spend_note.nullifier(&fvk); + + let mut rseed = [0; 32]; + rseed_rng.fill_bytes(&mut rseed); + let (output_note, memo) = if i < num_orchard_outputs { + let output = &orchard_outputs[i]; + let address = match output.destination { + Destination::Orchard(address) => address, + _ => unreachable!() + }; + let rseed = orchard::note::RandomSeed::from_bytes(rseed, &nf).unwrap(); + let note = orchard::Note::from_parts( + orchard::Address::from_raw_address_bytes(&address).unwrap(), + orchard::value::NoteValue::from_raw(output.amount), + nf.clone(), + rseed).unwrap(); + let memo = output.memo.as_array().clone(); + (note, memo) + } + else { + (dummy_note.clone(), empty_memo) + }; + + let _rk = fvk.ak.randomize(&alpha); + let cm = output_note.commitment(); + let cmx = cm.into(); + + let encryptor = orchard::note_encryption::OrchardNoteEncryption::new( + Some(o_fvk.to_ovk(Scope::External)), + output_note, + output_note.recipient(), + memo.clone() + ); + let v_net = orchard::value::ValueSum::default(); + let cv_net = orchard::value::ValueCommitment::derive(v_net, rcv.clone()); + let _encrypted_note = orchard::note::TransmittedNoteCiphertext { + epk_bytes: encryptor.epk().to_bytes().0, + enc_ciphertext: encryptor.encrypt_note_plaintext(), + out_ciphertext: encryptor.encrypt_outgoing_plaintext(&cv_net, &cmx, &mut OsRng), + }; + + // compact outputs ZTxIdOrcActCHash + // nf + // cmx + // epk_bytes + // enc_ciphertext[..52] + + // memo ZTxIdOrcActMHash + // enc_ciphertext[52..564] + + // non compact ZTxIdOrcActNHash + // cv_net + // rk + // enc_ciphertext[564..] + // out_ciphertext + } + + + + + let mut vins = vec![]; for tin in vin.iter() { let mut txin_hasher = Params::new() @@ -325,7 +459,7 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient, t 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)); + log::info!("TXIN {}", hex::encode(txin_hash)); let signature = ledger_sign_transparent(txin_hash.as_bytes()).await?; let signature = secp256k1::ecdsa::Signature::from_der(&signature)?; @@ -340,14 +474,12 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient, t 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?; - println!("SIGNATURE {}", hex::encode(&signature)); let signature = Signature::read(&*signature)?; // Signature verification // let rk = sp.rk(); @@ -360,37 +492,6 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient, t signatures.push(signature); } - // 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, @@ -405,12 +506,9 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient, t 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)); + log::info!("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 } ); @@ -430,11 +528,13 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient, t let mut raw_tx = vec![]; tx.write_v5(&mut raw_tx)?; + ledger_end_tx().await?; + let response = client.send_transaction(Request::new(RawTransaction { data: raw_tx, height: 0, })).await?.into_inner(); - println!("{}", response.error_message); + log::info!("{}", response.error_message); Ok(response.error_message) } diff --git a/src/ledger/tests.rs b/src/ledger/tests.rs index 93eb2fe..873538b 100644 --- a/src/ledger/tests.rs +++ b/src/ledger/tests.rs @@ -1,7 +1,8 @@ use super::transport::*; use anyhow::Result; -async fn unit_tests() -> Result<()> { +#[allow(dead_code)] +pub async fn unit_tests() -> Result<()> { let hash = ledger_pedersen_hash( &hex::decode("B315693B486D4D3CD8E4256E8C37CA4E8EC367E4D95D5C314625DC7B44B57EA2CA18FDCFF5871906F4238FB315693B486D4D3CD8E4256E8C37CA4E8EC367E4D95D5C314625DC7B44B57EA2")? ).await?; diff --git a/src/ledger/transport.rs b/src/ledger/transport.rs index a308dcc..d195eb8 100644 --- a/src/ledger/transport.rs +++ b/src/ledger/transport.rs @@ -13,9 +13,19 @@ use zcash_primitives::zip32::DiversifiableFullViewingKey; use std::io::Write; use hex_literal::hex; -async fn apdu(data: &[u8]) -> Vec { - let api = HidApi::new().unwrap(); - let transport = TransportNativeHID::new(&api).unwrap(); +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], @@ -23,10 +33,12 @@ async fn apdu(data: &[u8]) -> Vec { p2: data[3], data: &data[5..], }; - println!("ins {}", data[1]); - let response = transport.exchange(&command).unwrap(); - println!("ret {}", response.retcode()); - response.data().to_vec() + 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"); @@ -36,19 +48,20 @@ async fn apdu_http(data: &[u8]) -> Vec { .post(&format!("http://{}:5000/apdu", TEST_SERVER_IP.unwrap())) .body(format!("{{\"data\": \"{}\"}}", hex::encode(data))) .send() - .await - .unwrap(); - let response_body: Value = response.json().await.unwrap(); - let data = response_body["data"].as_str().unwrap(); - let data = hex::decode(data).unwrap(); - data[..data.len()-2].to_vec() + .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; + apdu(&bb).await?; Ok(()) } @@ -56,7 +69,7 @@ pub async fn ledger_init() -> Result<()> { pub async fn ledger_get_dfvk() -> Result { let mut bb: Vec = vec![]; bb.write_all(&hex!("E006000000"))?; - let dfvk_vec = apdu(&bb).await; + let dfvk_vec = apdu(&bb).await?; let mut dfvk = [0; 128]; dfvk.copy_from_slice(&dfvk_vec); @@ -64,18 +77,141 @@ pub async fn ledger_get_dfvk() -> Result { Ok(dfvk) } -pub async fn ledger_get_address() -> Result { +pub async fn ledger_get_pubkey() -> Result> { let mut bb: Vec = vec![]; bb.write_all(&hex!("E007000000"))?; - let address = apdu(&bb).await; - let address = String::from_utf8_lossy(&address); - Ok(address.to_string()) + 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!("E015000008"))?; + 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!("E01B000000"))?; + 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!("E011000000"))?; - let proofgen_key = apdu(&bb).await; + 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(), @@ -83,106 +219,26 @@ pub async fn ledger_get_proofgen_key() -> Result { Ok(proofgen_key) } -pub async fn ledger_init_tx(header_digest: &[u8]) -> Result> { +pub async fn ledger_sign_transparent(txin_digest: &[u8]) -> Result> { let mut bb: Vec = vec![]; - bb.write_all(&hex!("E008000020"))?; - bb.write_all(header_digest)?; - let main_seed = apdu(&bb).await; - Ok(main_seed) -} - -pub async fn ledger_add_t_input(amount: u64) -> Result<()> { - let mut bb: Vec = vec![]; - bb.write_all(&hex!("E009000008"))?; - 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!("E00A00001D"))?; - 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!("E00B000087"))?; - 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_transparent_merkle_proof(prevouts_digest: &[u8], pubscripts_digest: &[u8], sequences_digest: &[u8]) -> Result<()> { - let mut bb: Vec = vec![]; - bb.write_all(&hex!("E00D000060"))?; - 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!("E00E000060"))?; - 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_net_sapling(net: i64) -> Result<()> { - let mut bb: Vec = vec![]; - bb.write_all(&hex!("E00C000008"))?; - bb.write_i64::(net)?; - apdu(&bb).await; - Ok(()) -} - -pub async fn ledger_set_stage(stage: u8) -> Result<()> { - let mut bb: Vec = vec![]; - bb.write_all(&hex!("E00F"))?; - bb.write_u8(stage)?; - bb.write_all(&hex!("0000"))?; - apdu(&bb).await; - Ok(()) -} - -pub async fn ledger_get_sighash() -> Result> { - let mut bb: Vec = vec![]; - bb.write_all(&hex!("E010000000"))?; - let sighash = apdu(&bb).await; - Ok(sighash) + 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!("E012000000"))?; - let signature = apdu(&bb).await; + bb.write_all(&hex!("E023000000"))?; + let signature = apdu(&bb).await?; Ok(signature) } -pub async fn ledger_sign_transparent(txin_digest: &[u8]) -> Result> { +pub async fn ledger_end_tx() -> 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) + bb.write_all(&hex!("E030000000"))?; + apdu(&bb).await?; + Ok(()) } pub async fn ledger_cmu(data: &[u8]) -> Result> { @@ -190,7 +246,7 @@ pub async fn ledger_cmu(data: &[u8]) -> Result> { bb.write_all(&hex!("E0800000"))?; bb.write_u8(data.len() as u8)?; bb.write_all(data)?; - let cmu = apdu(&bb).await; + let cmu = apdu(&bb).await?; Ok(cmu) } @@ -199,7 +255,7 @@ pub async fn ledger_jubjub_hash(data: &[u8]) -> Result> { bb.write_all(&hex!("E0810000"))?; bb.write_u8(data.len() as u8)?; bb.write_all(data)?; - let cmu = apdu(&bb).await; + let cmu = apdu(&bb).await?; Ok(cmu) } @@ -208,6 +264,15 @@ pub async fn ledger_pedersen_hash(data: &[u8]) -> Result> { bb.write_all(&hex!("E0820000"))?; bb.write_u8(data.len() as u8)?; bb.write_all(data)?; - let cmu = apdu(&bb).await; + 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) +} diff --git a/src/lib.rs b/src/lib.rs index b01e16a..4146a4a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -102,7 +102,7 @@ mod zip32; pub mod api; #[cfg(feature = "ledger")] -mod ledger; +pub mod ledger; pub use crate::chain::{connect_lightwalletd, get_best_server, ChainError}; pub use crate::coinconfig::{ @@ -121,7 +121,8 @@ pub use note_selection::{ build_tx, build_tx_plan, fetch_utxos, get_secret_keys, TransactionBuilderConfig, TransactionBuilderError, TransactionPlan, TxBuilderContext, MAX_ATTEMPTS, }; -pub use unified::{decode_unified_address, get_unified_address}; +pub use crate::orchard::decode_merkle_path as decode_orchard_merkle_path; +pub use crate::unified::{decode_unified_address, get_unified_address}; #[cfg(feature = "nodejs")] pub mod nodejs; @@ -134,7 +135,4 @@ pub fn init_test() { set_coin_lwd_url(0, "http://127.0.0.1:9067"); } -#[cfg(feature = "ledger")] -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 c534012..8de9a1a 100644 --- a/src/main/ledger.rs +++ b/src/main/ledger.rs @@ -1,20 +1,39 @@ use std::{ fs::File, - io::Read, + io::{Read, Write}, path::Path, }; +use blake2b_simd::Params; +use byteorder::{WriteBytesExt, LE}; +use group::{Group, GroupEncoding}; + +use halo2_proofs::{pasta::pallas::{self}}; +use orchard::{keys::{Scope, SpendValidatingKey}, note::{RandomSeed, Nullifier, ExtractedNoteCommitment}, bundle::{Flags, Authorized}, tree::MerklePath, value::{ValueCommitTrapdoor, ValueCommitment, ValueSum}, circuit::{Circuit, ProvingKey}, primitives::redpallas::{Signature, SpendAuth}, Action, builder::{SpendInfo}, Proof}; +use rand::{SeedableRng, RngCore}; +use rand_chacha::ChaCha20Rng; use ripemd::Digest; -use secp256k1::SecretKey; -use warp_api_ffi::{TransactionPlan, connect_lightwalletd, build_broadcast_tx, derive_from_secretkey}; + +use warp_api_ffi::{TransactionPlan, connect_lightwalletd, ledger::{build_broadcast_tx, ledger_init, ledger_set_stage, ledger_init_tx, ledger_add_o_action, ledger_set_orchard_merkle_proof, ledger_confirm_fee, ledger_set_net_orchard}, decode_orchard_merkle_path}; use anyhow::Result; -use zcash_client_backend::{encoding::encode_transparent_address, address::RecipientAddress}; -use zcash_primitives::{legacy::TransparentAddress, consensus::{Parameters, Network, Network::MainNetwork}}; + +use zcash_primitives::{consensus::{Network::MainNetwork, BlockHeight, BranchId}, transaction::{TransactionData, TxVersion, txid::TxIdDigester, sighash_v5, sighash::SignableInput, components::Amount}}; use zcash_proofs::prover::LocalTxProver; -#[tokio::main] -async fn main() -> Result<()> { - let network: &Network = &MainNetwork; +use group::ff::Field; +use nonempty::NonEmpty; +use hex_literal::hex; +use warp_api_ffi::{Source, Destination}; + +mod orchard_bundle; + +#[tokio::main] +async fn main() { + orchard_bundle::build_orchard().await.unwrap(); +} + +#[tokio::main] +async fn main2() -> Result<()> { let params_dir = Path::new(&std::env::var("HOME").unwrap()).join(".zcash-params"); let prover = LocalTxProver::new( ¶ms_dir.join("sapling-spend.params"), @@ -27,7 +46,243 @@ async fn main() -> Result<()> { let mut client = connect_lightwalletd("https://lwdv3.zecwallet.co").await?; - build_broadcast_tx(&mut client, &tx_plan, &prover).await?; + build_broadcast_tx(&MainNetwork, &mut client, &tx_plan, &prover).await?; Ok(()) } + +const ORCHARD_SPENDAUTHSIG_BASEPOINT_BYTES: [u8; 32] = [ + 99, 201, 117, 184, 132, 114, 26, 141, 12, 161, 112, 123, 227, 12, 127, 12, 95, 68, 95, 62, 124, + 24, 141, 59, 6, 214, 241, 40, 179, 35, 85, 183, +]; + +async fn main3() -> Result<()> { + dotenv::dotenv()?; + let spending_key = hex::decode(dotenv::var("SPENDING_KEY").unwrap()).unwrap(); + + let x = hex::decode("063b1e0d8b7f64bb2a9465903b46c9019b552951afa5d735817d449d1334c510").unwrap(); + + let expiry_height = 2_500_000; + let mut h = Params::new() + .hash_length(32) + .personal(b"ZTxIdHeadersHash") + .to_state(); + h.update(&hex!("050000800a27a726b4d0d6c200000000")); + h.write_u32::(expiry_height)?; + let header_digest = h.finalize(); + + // let Q: Point = Point::hash_to_curve(Q_PERSONALIZATION)(MERKLE_CRH_PERSONALIZATION.as_bytes()); + // let p = ::from_bytes(&ORCHARD_SPENDAUTHSIG_BASEPOINT_BYTES).unwrap(); + // println!("p {:?}", p); + // let q = Ep::identity() + p; + // println!("q {:?}", q); + // let p = p.double(); + // println!("{:?}", p); + // println!("{}", hex::encode(ORCHARD_SPENDAUTHSIG_BASEPOINT_BYTES)); + let sk = orchard::keys::SpendingKey::from_bytes(spending_key.try_into().unwrap()).unwrap(); + let fvk = orchard::keys::FullViewingKey::from(&sk); + println!("FVK {:?}", fvk); + println!("FVK {}", hex::encode(fvk.to_bytes())); + // let ivk = fvk.to_ivk(Scope::External); + // println!("IVK {:?}", ivk); + // println!("DK {:?}", hex::encode(ivk.dk.to_bytes())); + // let rho = Nullifier::dummy(&mut OsRng); + // println!("rho {}", hex::encode(rho.to_bytes())); + + let rho = Nullifier::from_bytes(&x.clone().try_into().unwrap()).unwrap(); + + let mut prng = ChaCha20Rng::from_seed([0; 32]); + let rcv = ValueCommitTrapdoor::random(&mut prng); + + let mut alpha_rng = ChaCha20Rng::from_seed([1; 32]); + let mut rseed_rng = ChaCha20Rng::from_seed([2; 32]); + + let alpha = pallas::Scalar::random(&mut alpha_rng); + let ak: SpendValidatingKey = fvk.clone().into(); + let rk = ak.randomize(&alpha); + let rk_bytes: [u8; 32] = rk.clone().0.into(); + + let v_net = orchard::value::ValueSum::from_raw(-1000); + let cv_net = orchard::value::ValueCommitment::derive(v_net, rcv.clone()); + + let mut rseed = [0u8; 32]; + rseed_rng.fill_bytes(&mut rseed); + println!("rseed {}", hex::encode(&rseed)); + + let rseed = RandomSeed::from_bytes(rseed, &rho).unwrap(); + println!("esk {:?}", &rseed.esk(&rho)); + println!("psi {:?}", &rseed.psi(&rho)); + println!("rcm {:?}", &rseed.rcm(&rho)); + + let address = fvk.address_at(0u64, Scope::External); + let mut buf = vec![]; + buf.write_all(&rho.to_bytes())?; + buf.write_all(&address.to_raw_address_bytes())?; + buf.write_u64::(400_000)?; + + let note = orchard::Note::from_parts(address, + orchard::value::NoteValue::from_raw(400_000), rho, rseed).unwrap(); + println!("note {:?}", note); + let cmx: ExtractedNoteCommitment = note.commitment().into(); + println!("cmx {:?}", cmx); + // ledger_test_math(&buf).await?; + + let mut memo = [0u8; 512]; + memo[0] = 0xF6; + + let encryptor = orchard::note_encryption::OrchardNoteEncryption::new( + None, + note.clone(), + address.clone(), + memo); + + let epk = encryptor.epk().to_bytes().0; + let enc = encryptor.encrypt_note_plaintext(); + let out = encryptor.encrypt_outgoing_plaintext(&cv_net.clone(), &cmx, &mut prng); + let encrypted_note = orchard::note::TransmittedNoteCiphertext { + epk_bytes: epk.clone(), + enc_ciphertext: enc.clone(), + out_ciphertext: out.clone(), + }; + + let merkle_path = MerklePath::dummy(&mut prng); + let anchor = merkle_path.root(cmx); + + let _authorization = zcash_primitives::transaction::components::orchard::Unauthorized {}; + + let action = + orchard::Action::from_parts(rho.clone(), rk, cmx, encrypted_note, cv_net.clone(), + Signature::::from([0; 64])); + let _actions = NonEmpty::new(action); + let _circuit = Circuit::from_action_context_unchecked(orchard::builder::SpendInfo::new( + fvk.clone(), note.clone(), merkle_path).unwrap(), note, alpha, rcv.clone()); + + let mut orchard_memos_hasher = Params::new() + .hash_length(32) + .personal(b"ZTxIdOrcActMHash") + .to_state(); + orchard_memos_hasher.update(&enc[52..564]); + let orchard_memos_hash = orchard_memos_hasher.finalize(); + + let mut orchard_nc_hasher = Params::new() + .hash_length(32) + .personal(b"ZTxIdOrcActNHash") + .to_state(); + orchard_nc_hasher.update(&cv_net.to_bytes()); + orchard_nc_hasher.update(&rk_bytes); + orchard_nc_hasher.update(&enc[564..]); + orchard_nc_hasher.update(&out); + let orchard_nc_hash = orchard_nc_hasher.finalize(); + + // let mut orchard_builder = orchard::builder::Builder::new(Flags::from_byte(3).unwrap(), + // anchor); + // orchard_builder.add_spend(fvk.clone(), note, merkle_path).unwrap(); + // orchard_builder.add_recipient(None, address.clone(), + // orchard::value::NoteValue::from_raw(399_000), None).unwrap(); + // let bundle: orchard::Bundle, _> = orchard_builder.build(&mut prng).unwrap(); + + let _bsk = rcv.into_bsk(); + + let _pk = ProvingKey::build(); + // let instance = action.to_instance(Flags::from_parts(true, true), anchor.clone()); + // let bundle = orchard::Bundle::from_parts(actions, + // Flags::from_byte(3).unwrap(), + // Amount::from_i64(-1000).unwrap(), + // anchor, + // InProgress:: { + // proof: Unproven { circuits: vec![circuit] }, + // sigs: OrchardUnauthorized { bsk } + // } + // ); + // let proof = bundle.create_proof(&pk, &mut prng).unwrap(); + // let proof = proof.authorization(); + // let proof = &proof.proof; + + // let bundle = orchard::Bundle::from_parts(actions, + // Flags::from_byte(3).unwrap(), + // Amount::from_i64(-1000).unwrap(), + // anchor, + // orchard::bundle::Authorized { + // proof: todo!(), + // binding_signature: todo!(), + // } + // ); + + + let tx_data: TransactionData = TransactionData { + version: TxVersion::Zip225, + consensus_branch_id: BranchId::Nu5, + lock_time: 0, + expiry_height: BlockHeight::from_u32(expiry_height), + transparent_bundle: None, + sprout_bundle: None, + sapling_bundle: None, + orchard_bundle: None, + }; + + let txid_parts = tx_data.digest(TxIdDigester); + let sig_hash = sighash_v5::v5_signature_hash(&tx_data, &SignableInput::Shielded, &txid_parts); + + println!("ORCHARD memos {:?}", orchard_memos_hash); + println!("ORCHARD nc {:?}", orchard_nc_hash); + + println!("SIGHASH PARTS {:?}", txid_parts); + println!("SIGHASH {:?}", sig_hash); + + ledger_init().await.unwrap(); + ledger_init_tx(header_digest.as_bytes()).await.unwrap(); + ledger_set_orchard_merkle_proof(&anchor.to_bytes(), + orchard_memos_hash.as_bytes(), orchard_nc_hash.as_bytes()).await.unwrap(); + + // no t-in + ledger_set_stage(2).await.unwrap(); + // no t-out + ledger_set_stage(3).await.unwrap(); + // no s-out + ledger_set_stage(4).await.unwrap(); + ledger_add_o_action(&x, 400_000, &epk, + &address.to_raw_address_bytes(), &enc[0..52]).await.unwrap(); + ledger_set_stage(5).await.unwrap(); + + ledger_set_net_orchard(-1000).await.unwrap(); + + ledger_confirm_fee().await.unwrap(); + + // println!("address {}", hex::encode(address.to_raw_address_bytes())); + + // let esk = ExtendedSpendingKey::master(&[1; 32]); + // let (_, pa) = esk.default_address(); + // let ta = TransparentAddress::PublicKey([1; 20]); + + // let ua = UnifiedAddress::from_receivers( + // Some(address), + // Some(pa), + // Some(ta), + // ).unwrap(); + // let ua = ua.encode(&MainNetwork); + // println!("UA {}", ua); + + // let mut message = [1u8; 128]; + // f4jumble::f4jumble_mut(&mut message[..]).unwrap(); + // println!("f4 {}", hex::encode(message)); + + // println!("{}", hex::encode(address.to_raw_address_bytes())); + // let a = zcash_address::ZcashAddress::try_from_encoded("u1hlpt22xwe3sy034cdjhtnlp4l39zwa7xsj6tsxq0d2p9mv0gzsxnzkpc3wxpv6nh9s2kyxe54qnnxujxc8wqjemvlelzsdxlm7feqdjuksgpx45w3we563apmtmxhql6aa584u9569pdaq3q8h9p8gma67z5td3sckhamh99aqkgf3cg76rykn2e2pwxdjm8wdya0w39355rgvhxvpw").unwrap(); + // if let zcash_address::AddressKind::Unified(r) = a.kind { + // let Address(rs) = r; + // let r = &rs[0]; + // if let Receiver::Orchard(address) = r { + // println!("address {}", hex::encode(address)); + // } + // } + // println!("a {:?}", a); + + + // ledger_init().await?; + // ledger_test_math().await?; + // let fvk = ledger_get_o_fvk().await?; + // println!("FVK {}", hex::encode(&fvk)); + + Ok(()) +} + diff --git a/src/note_selection/builder.rs b/src/note_selection/builder.rs index 91f5bb1..4148f9d 100644 --- a/src/note_selection/builder.rs +++ b/src/note_selection/builder.rs @@ -13,8 +13,7 @@ use orchard::keys::{FullViewingKey, Scope, SpendAuthorizingKey, SpendingKey}; use orchard::note::Nullifier; use orchard::value::NoteValue; use orchard::{Address, Anchor, Bundle}; -use rand::{CryptoRng, RngCore, SeedableRng}; -use rand_chacha::ChaChaRng; +use rand::{CryptoRng, RngCore}; use ripemd::{Digest, Ripemd160}; use secp256k1::{All, PublicKey, Secp256k1, SecretKey}; use sha2::Sha256; @@ -100,7 +99,6 @@ pub fn build_tx( }; let mut has_orchard = false; - let mut rng = ChaChaRng::from_seed([0; 32]); let mut builder = Builder::new_with_rng(*network, BlockHeight::from_u32(plan.anchor_height), &mut rng); let anchor: Anchor = orchard::tree::MerkleHashOrchard::from_bytes(&plan.orchard_anchor) .unwrap() diff --git a/src/note_selection/optimize.rs b/src/note_selection/optimize.rs index 89de8f4..bd94e4b 100644 --- a/src/note_selection/optimize.rs +++ b/src/note_selection/optimize.rs @@ -290,6 +290,7 @@ pub fn build_tx_plan( network: &Network, fvk: &str, taddr: &str, + orchard_fvk: &str, anchor_height: u32, expiry_height: u32, orchard_anchor: &Option, @@ -323,6 +324,7 @@ pub fn build_tx_plan( let tx_plan = TransactionPlan { fvk: fvk.to_string(), taddr: taddr.to_string(), + orchard_fvk: orchard_fvk.to_string(), anchor_height, expiry_height, orchard_anchor: orchard_anchor.unwrap_or(Hash::default()), diff --git a/src/note_selection/types.rs b/src/note_selection/types.rs index 034f766..2b9a8a8 100644 --- a/src/note_selection/types.rs +++ b/src/note_selection/types.rs @@ -157,8 +157,9 @@ pub struct RecipientShort { #[derive(Serialize, Deserialize, Default)] #[serde_as] pub struct TransactionPlan { - pub fvk: String, pub taddr: String, + pub fvk: String, + pub orchard_fvk: String, pub anchor_height: u32, pub expiry_height: u32, #[serde(with = "SerHex::")] diff --git a/src/orchard.rs b/src/orchard.rs index facf191..d816fae 100644 --- a/src/orchard.rs +++ b/src/orchard.rs @@ -12,7 +12,7 @@ mod note; pub use hash::{OrchardHasher, ORCHARD_ROOTS}; pub use key::{derive_orchard_keys, OrchardKeyBytes}; -pub use note::{DecryptedOrchardNote, OrchardDecrypter, OrchardViewKey}; +pub use note::{DecryptedOrchardNote, OrchardDecrypter, OrchardViewKey, decode_merkle_path}; pub fn get_proving_key() -> &'static ProvingKey { if !PROVING_KEY.filled() { diff --git a/src/orchard/note.rs b/src/orchard/note.rs index cf15efb..dc81808 100644 --- a/src/orchard/note.rs +++ b/src/orchard/note.rs @@ -1,7 +1,7 @@ use crate::chain::Nf; use crate::db::ReceivedNote; use crate::sync::{ - CompactOutputBytes, DecryptedNote, Node, OutputPosition, TrialDecrypter, ViewKey, + CompactOutputBytes, DecryptedNote, Node, OutputPosition, TrialDecrypter, ViewKey, Witness, }; use crate::CompactTx; use orchard::keys::PreparedIncomingViewingKey; @@ -110,3 +110,17 @@ impl TrialDecrypter anyhow::Result { + let witness = Witness::from_bytes(id_note, witness)?; + let auth_path: Vec<_> = witness + .auth_path(32, &super::ORCHARD_ROOTS, &super::OrchardHasher::new()) + .iter() + .map(|n| orchard::tree::MerkleHashOrchard::from_bytes(n).unwrap()) + .collect(); + let merkle_path = orchard::tree::MerklePath::from_parts( + witness.position as u32, + auth_path.try_into().unwrap(), + ); + Ok(merkle_path) +} diff --git a/src/pay.rs b/src/pay.rs index eef4b67..249c43a 100644 --- a/src/pay.rs +++ b/src/pay.rs @@ -423,18 +423,16 @@ pub async fn broadcast_tx(tx: &[u8]) -> anyhow::Result { height: latest_height as u64, }; - return Err(anyhow!("Broadcasting is disabled")); - - // let rep = client - // .send_transaction(Request::new(raw_tx)) - // .await? - // .into_inner(); - // let code = rep.error_code; - // if code == 0 { - // Ok(rep.error_message) - // } else { - // Err(anyhow::anyhow!(rep.error_message)) - // } + let rep = client + .send_transaction(Request::new(raw_tx)) + .await? + .into_inner(); + let code = rep.error_code; + if code == 0 { + Ok(rep.error_message) + } else { + Err(anyhow::anyhow!(rep.error_message)) + } } pub fn get_tx_summary(tx: &Tx) -> anyhow::Result { diff --git a/src/taddr.rs b/src/taddr.rs index a5658ec..3f8105a 100644 --- a/src/taddr.rs +++ b/src/taddr.rs @@ -268,6 +268,19 @@ pub fn derive_from_secretkey( Ok((sk, address)) } +pub fn derive_from_pubkey(network: &Network, pub_key: &[u8]) -> anyhow::Result { + let pub_key = PublicKey::from_slice(pub_key)?; + let pub_key = pub_key.serialize(); + let pub_key = Ripemd160::digest(&Sha256::digest(&pub_key)); + let address = TransparentAddress::PublicKey(pub_key.into()); + let address = encode_transparent_address( + &network.b58_pubkey_address_prefix(), + &network.b58_script_address_prefix(), + &address, + ); + Ok(address) +} + pub async fn sweep_tkey( last_height: u32, sk: &str, diff --git a/src/unified.rs b/src/unified.rs index a3bbd96..3e67049 100644 --- a/src/unified.rs +++ b/src/unified.rs @@ -42,6 +42,9 @@ impl std::fmt::Display for DecodedUA { } } +/* + * It can also return a t-addr if there is no other selection + */ pub fn get_unified_address( network: &Network, db: &DbAdapter, @@ -59,8 +62,8 @@ pub fn get_unified_address( } if !tpe.sapling && !tpe.orchard { // UA cannot be t-only - tpe.sapling = true; - tpe.orchard = true; + let address = db.get_taddr(account)?.ok_or(anyhow!("No taddr"))?; + return Ok(address) } let address = match (tpe.transparent, tpe.sapling, tpe.orchard) {