Ledger App integration

This commit is contained in:
Hanh 2023-04-19 09:55:10 +10:00
parent d3d8d27538
commit f855d7ca81
21 changed files with 774 additions and 231 deletions

View File

@ -32,6 +32,7 @@ name = "warp_api_ffi"
crate-type = ["rlib"] crate-type = ["rlib"]
[dependencies] [dependencies]
nonempty = "0.7"
env_logger = "0.9.0" env_logger = "0.9.0"
anyhow = "1.0.40" anyhow = "1.0.40"
thiserror = "1.0.25" thiserror = "1.0.25"
@ -75,6 +76,8 @@ chrono = "0.4.19"
lazycell = "1.3.0" lazycell = "1.3.0"
reqwest = { version = "0.11.4", features = ["json", "rustls-tls"], default-features = false } reqwest = { version = "0.11.4", features = ["json", "rustls-tls"], default-features = false }
hex-literal = "0.4" hex-literal = "0.4"
pasta_curves = "0.5"
f4jumble = { path = "../../librustzcash/components/f4jumble" }
# Halo # Halo
orchard = "0.3.0" orchard = "0.3.0"
@ -108,7 +111,7 @@ objc = { version = "0.2", features = [ "objc_exception" ], optional = true }
block = { version = "0.1.6", optional = true } block = { version = "0.1.6", optional = true }
[features] [features]
ledger = ["ledger-apdu", "ledger-transport-hid"] ledger = ["ledger-apdu", "ledger-transport-hid", "dotenv"]
dart_ffi = ["allo-isolate", "once_cell", "android_logger"] dart_ffi = ["allo-isolate", "once_cell", "android_logger"]
rpc = ["rocket", "dotenv"] rpc = ["rocket", "dotenv"]
nodejs = ["node-bindgen"] nodejs = ["node-bindgen"]
@ -118,38 +121,38 @@ sqlcipher = ["rusqlite/bundled-sqlcipher-vendored-openssl"]
[dependencies.zcash_params] [dependencies.zcash_params]
#git = "https://github.com/hhanh00/zcash-params.git" #git = "https://github.com/hhanh00/zcash-params.git"
#rev = "154f3544781500c27f58923ccc6c7e779eecf9df" #rev = "2e118feceeaa31ef68fb83d1fc94a1a46db4569c"
path = "../zcash-params" path = "../zcash-params"
[dependencies.zcash_client_backend] [dependencies.zcash_client_backend]
#git = "https://github.com/hhanh00/librustzcash.git" #git = "https://github.com/hhanh00/librustzcash.git"
#rev = "f39dd6c2af20662fd1aaa9e00172efebd3435ff4" #rev = "e2fe0b8d386fad99e00d6135c5caf3cc04045646"
path = "../../librustzcash/zcash_client_backend" path = "../../librustzcash/zcash_client_backend"
[dependencies.zcash_primitives] [dependencies.zcash_primitives]
#git = "https://github.com/hhanh00/librustzcash.git" #git = "https://github.com/hhanh00/librustzcash.git"
#rev = "f39dd6c2af20662fd1aaa9e00172efebd3435ff4" #rev = "e2fe0b8d386fad99e00d6135c5caf3cc04045646"
path = "../../librustzcash/zcash_primitives" path = "../../librustzcash/zcash_primitives"
features = [ "transparent-inputs" ] features = [ "transparent-inputs" ]
[dependencies.zcash_proofs] [dependencies.zcash_proofs]
#git = "https://github.com/hhanh00/librustzcash.git" #git = "https://github.com/hhanh00/librustzcash.git"
#rev = "f39dd6c2af20662fd1aaa9e00172efebd3435ff4" #rev = "e2fe0b8d386fad99e00d6135c5caf3cc04045646"
path = "../../librustzcash/zcash_proofs" path = "../../librustzcash/zcash_proofs"
[dependencies.zcash_address] [dependencies.zcash_address]
#git = "https://github.com/hhanh00/librustzcash.git" #git = "https://github.com/hhanh00/librustzcash.git"
#rev = "f39dd6c2af20662fd1aaa9e00172efebd3435ff4" #rev = "e2fe0b8d386fad99e00d6135c5caf3cc04045646"
path = "../../librustzcash/components/zcash_address" path = "../../librustzcash/components/zcash_address"
[dependencies.zcash_encoding] [dependencies.zcash_encoding]
#git = "https://github.com/hhanh00/librustzcash.git" #git = "https://github.com/hhanh00/librustzcash.git"
#rev = "f39dd6c2af20662fd1aaa9e00172efebd3435ff4" #rev = "e2fe0b8d386fad99e00d6135c5caf3cc04045646"
path = "../../librustzcash/components/zcash_encoding" path = "../../librustzcash/components/zcash_encoding"
[dependencies.zcash_note_encryption] [dependencies.zcash_note_encryption]
#git = "https://github.com/hhanh00/librustzcash.git" #git = "https://github.com/hhanh00/librustzcash.git"
#rev = "f39dd6c2af20662fd1aaa9e00172efebd3435ff4" #rev = "e2fe0b8d386fad99e00d6135c5caf3cc04045646"
path = "../../librustzcash/components/zcash_note_encryption" path = "../../librustzcash/components/zcash_note_encryption"
[build-dependencies] [build-dependencies]
@ -161,5 +164,4 @@ criterion = "0.3.4"
#These patch overrides must be included in your workspace root Cargo.toml #These patch overrides must be included in your workspace root Cargo.toml
#[patch.crates-io] #[patch.crates-io]
#zcash_note_encryption = { git = "https://github.com/hhanh00/librustzcash.git", rev = "ad4a1c61fdaf04ac4fb884976ad175196e695264" } #zcash_note_encryption = { git = "https://github.com/hhanh00/librustzcash.git", rev = "e2fe0b8d386fad99e00d6135c5caf3cc04045646" }
#jubjub = { git = "https://github.com/hhanh00/jubjub.git", rev = "4a3edf3d242f368b5aa418ec659d01f191127cf3" }

View File

@ -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_____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_cuda(void);
bool has_metal(void); bool has_metal(void);

View File

@ -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::data_generated::fb::{ProgressT, Recipients, SendTemplate, Servers};
use crate::db::FullEncryptedBackup; use crate::db::FullEncryptedBackup;
use crate::note_selection::TransactionReport; use crate::note_selection::TransactionReport;
use crate::{ChainError, TransactionPlan, Tx, build_broadcast_tx}; use crate::{ChainError, TransactionPlan, Tx};
use allo_isolate::{ffi, IntoDart}; use allo_isolate::{ffi, IntoDart};
use android_logger::Config; use android_logger::Config;
use flatbuffers::FlatBufferBuilder; use flatbuffers::FlatBufferBuilder;
@ -1219,6 +1219,7 @@ pub unsafe extern "C" fn set_property(
to_cresult(with_coin(coin, res)) to_cresult(with_coin(coin, res))
} }
#[cfg(feature = "ledger")]
#[no_mangle] #[no_mangle]
#[tokio::main] #[tokio::main]
pub async unsafe extern "C" fn ledger_send(coin: u8, tx_plan: *mut c_char) -> CResult<*mut c_char> { 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 c = CoinConfig::get(coin);
let mut client = c.connect_lwd().await?; let mut client = c.connect_lwd().await?;
let prover = coinconfig::get_prover(); 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) Ok::<_, anyhow::Error>(response)
}; };
to_cresult_str(res.await) 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<u32> {
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<bool> {
let res = |connection: &Connection| {
crate::ledger::is_external(connection, account)
};
to_cresult(with_coin(coin, res))
}
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn has_cuda() -> bool { pub unsafe extern "C" fn has_cuda() -> bool {
crate::gpu::has_cuda() crate::gpu::has_cuda()

View File

@ -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 db = c.db()?;
let AccountData { fvk, .. } = db.get_account_info(account)?; let AccountData { fvk, .. } = db.get_account_info(account)?;
let taddr = db.get_taddr(account)?.unwrap_or_default(); 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 change_address = get_unified_address(coin, account, 7)?;
let context = TxBuilderContext::from_height(coin, checkpoint_height)?; let context = TxBuilderContext::from_height(coin, checkpoint_height)?;
@ -79,6 +80,7 @@ pub async fn build_tx_plan_with_utxos(
network, network,
&fvk, &fvk,
&taddr, &taddr,
&orchard_fvk,
checkpoint_height, checkpoint_height,
expiry_height, expiry_height,
&context.orchard_anchor, &context.orchard_anchor,

View File

@ -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 { if version != LATEST_VERSION {

View File

@ -708,3 +708,10 @@ pub fn get_available_addrs(connection: &Connection, account: u32) -> anyhow::Res
| if has_orchard { 4 } else { 0 }; | if has_orchard { 4 } else { 0 };
Ok(res) Ok(res)
} }
pub fn get_account_by_address(connection: &Connection, address: &str) -> Result<Option<u32>> {
let id = connection.query_row("SELECT id_account FROM accounts WHERE address = ?1", [address], |row| {
row.get::<_, u32>(0)
}).optional()?;
Ok(id)
}

View File

@ -1,7 +1,11 @@
mod transport; mod transport;
mod builder; mod builder;
mod account;
// #[cfg(test)] // #[cfg(test)]
mod tests; mod tests;
pub use builder::build_broadcast_tx; pub use builder::build_broadcast_tx;
pub use transport::*;
pub use account::{import as import_account, is_external};

51
src/ledger/account.rs Normal file
View File

@ -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<u32> {
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<bool> {
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))
}

View File

@ -1,7 +1,3 @@
use std::{
io::Read, vec,
};
use std::io::Write;
use blake2b_simd::Params; use blake2b_simd::Params;
use byteorder::WriteBytesExt; use byteorder::WriteBytesExt;
use byteorder::LE; use byteorder::LE;
@ -9,26 +5,30 @@ use ff::{PrimeField, Field};
use group::GroupEncoding; use group::GroupEncoding;
use hex_literal::hex; use hex_literal::hex;
use jubjub::{Fr, Fq}; use jubjub::{Fr, Fq};
use orchard::keys::Scope;
use rand::{rngs::OsRng, RngCore, SeedableRng}; use rand::{rngs::OsRng, RngCore, SeedableRng};
use rand_chacha::ChaChaRng; use rand_chacha::ChaChaRng;
use ripemd::{Digest, Ripemd160}; use ripemd::{Digest, Ripemd160};
use secp256k1::{All, PublicKey, Secp256k1, SecretKey}; use secp256k1::PublicKey;
use sha2::Sha256; use sha2::Sha256;
use tonic::{Request, transport::Channel}; use tonic::{Request, transport::Channel};
use zcash_client_backend::address::RecipientAddress; use zcash_client_backend::encoding::{decode_transparent_address, encode_extended_full_viewing_key, encode_transparent_address};
use zcash_client_backend::encoding::decode_transparent_address; use zcash_primitives::consensus::Network;
use zcash_primitives::consensus::Parameters; use zcash_primitives::consensus::Parameters;
use zcash_primitives::legacy::{TransparentAddress, Script}; 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::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::{Destination, Source, TransactionPlan, RawTransaction, CompactTxStreamerClient};
use crate::ledger::transport::*; use crate::ledger::transport::*;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use zcash_primitives::{ use zcash_primitives::{
consensus::{BlockHeight, BranchId, MainNetwork}, consensus::{BlockHeight, BranchId, MainNetwork},
merkle_tree::{Hashable, IncrementalWitness}, merkle_tree::IncrementalWitness,
sapling::{ sapling::{
note_encryption::sapling_note_encryption, value::{NoteValue, ValueCommitment, ValueSum}, Diversifier, Node, Note, note_encryption::sapling_note_encryption, value::{NoteValue, ValueCommitment, ValueSum}, Diversifier, Node, Note,
PaymentAddress, Rseed, Nullifier, prover::TxProver, redjubjub::Signature, PaymentAddress, Rseed, Nullifier, prover::TxProver, redjubjub::Signature,
@ -53,14 +53,40 @@ struct SpendDescriptionUnAuthorized {
zkproof: [u8; GROTH_PROOF_SIZE], zkproof: [u8; GROTH_PROOF_SIZE],
} }
pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient<Channel>, tx_plan: &TransactionPlan, prover: &LocalTxProver) -> Result<String> { #[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<Channel>, tx_plan: &TransactionPlan, prover: &LocalTxProver) -> Result<String> {
ledger_init().await?; ledger_init().await?;
let pubkey = ledger_get_pubkey().await?; let pubkey = ledger_get_pubkey().await?;
let ledger_taddr = derive_from_pubkey(network, &pubkey)?;
let network = MainNetwork; // TODO: Pass network as param if ledger_taddr != tx_plan.taddr {
let secp = Secp256k1::<All>::new(); anyhow::bail!("This ledger wallet has a different address");
}
let taddr = &tx_plan.taddr; let taddr = &tx_plan.taddr;
let taddr = decode_transparent_address( let taddr = decode_transparent_address(
&network.b58_pubkey_address_prefix(), &network.b58_pubkey_address_prefix(),
&network.b58_script_address_prefix(), &network.b58_script_address_prefix(),
@ -99,7 +125,9 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient<Channel>, t
let fvk = dfvk.fvk; let fvk = dfvk.fvk;
let nf_key = proofgen_key.to_viewing_key().nk; 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); assert_eq!(PROOF_GENERATION_KEY_GENERATOR * proofgen_key.nsk, fvk.vk.nk.0);
// Derive rseed PRNG // Derive rseed PRNG
@ -118,7 +146,6 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient<Channel>, t
.to_state(); .to_state();
h.update(&master_seed); h.update(&master_seed);
let alpha = h.finalize(); 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 alpha_rng = ChaChaRng::from_seed(alpha.as_bytes().try_into().unwrap());
let mut prevouts_hasher = Params::new() let mut prevouts_hasher = Params::new()
@ -197,22 +224,22 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient<Channel>, t
shielded_spends.push(SpendDescriptionUnAuthorized { cv, anchor, nullifier, rk, zkproof }); 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(); 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(); 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(); 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(); 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(); 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() let mut spends_hasher = Params::new()
.hash_length(32) .hash_length(32)
@ -223,7 +250,7 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient<Channel>, t
spends_hasher.update(spends_non_compact_digest.as_bytes()); spends_hasher.update(spends_non_compact_digest.as_bytes());
} }
let spends_digest = spends_hasher.finalize(); 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() let mut output_memos_hasher = Params::new()
.hash_length(32) .hash_length(32)
@ -250,7 +277,7 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient<Channel>, t
}); });
} }
} }
ledger_set_stage(2).await?; ledger_set_stage(3).await?;
let has_transparent = !vin.is_empty() || !vout.is_empty(); let has_transparent = !vin.is_empty() || !vout.is_empty();
for output in tx_plan.outputs.iter() { for output in tx_plan.outputs.iter() {
@ -266,7 +293,7 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient<Channel>, t
let note = Note::from_parts(recipient, value, rseed); let note = Note::from_parts(recipient, value, rseed);
let rcm = note.rcm(); let rcm = note.rcm();
let cmu = note.cmu(); let cmu = note.cmu();
println!("cmu {}", hex::encode(cmu.to_bytes())); log::info!("cmu {}", hex::encode(cmu.to_bytes()));
let encryptor = let encryptor =
sapling_note_encryption::<_, MainNetwork>(Some(ovk.clone()), note, recipient, output.memo.clone(), &mut OsRng); 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<Channel>, t
shielded_outputs.push(OutputDescription { cv, cmu, ephemeral_key, enc_ciphertext, out_ciphertext, zkproof }); 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(); 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(); 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(), ledger_set_transparent_merkle_proof(prevouts_digest.as_bytes(),
pubscripts_digest.as_bytes(), sequences_digest.as_bytes()).await?; pubscripts_digest.as_bytes(), sequences_digest.as_bytes()).await?;
@ -311,6 +338,113 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient<Channel>, t
ledger_set_net_sapling(-tx_plan.net_chg[0]).await?; 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![]; let mut vins = vec![];
for tin in vin.iter() { for tin in vin.iter() {
let mut txin_hasher = Params::new() let mut txin_hasher = Params::new()
@ -325,7 +459,7 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient<Channel>, t
txin_hasher.update(&tin.coin.script_pubkey.0); txin_hasher.update(&tin.coin.script_pubkey.0);
txin_hasher.update(&0xFFFFFFFFu32.to_le_bytes()); txin_hasher.update(&0xFFFFFFFFu32.to_le_bytes());
let txin_hash = txin_hasher.finalize(); 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 = ledger_sign_transparent(txin_hash.as_bytes()).await?;
let signature = secp256k1::ecdsa::Signature::from_der(&signature)?; let signature = secp256k1::ecdsa::Signature::from_der(&signature)?;
@ -340,14 +474,12 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient<Channel>, t
script_sig, script_sig,
sequence: 0xFFFFFFFFu32, sequence: 0xFFFFFFFFu32,
}; };
println!("TXIN {:?}", txin);
vins.push(txin); vins.push(txin);
} }
let mut signatures = vec![]; let mut signatures = vec![];
for _sp in shielded_spends.iter() { for _sp in shielded_spends.iter() {
let signature = ledger_sign_sapling().await?; let signature = ledger_sign_sapling().await?;
println!("SIGNATURE {}", hex::encode(&signature));
let signature = Signature::read(&*signature)?; let signature = Signature::read(&*signature)?;
// Signature verification // Signature verification
// let rk = sp.rk(); // let rk = sp.rk();
@ -360,37 +492,6 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient<Channel>, t
signatures.push(signature); 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<zcash_primitives::transaction::Unauthorized> =
// 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::<transparent::Authorized> { let transparent_bundle = transparent::Bundle::<transparent::Authorized> {
vin: vins, vin: vins,
vout, vout,
@ -405,12 +506,9 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient<Channel>, t
let value: i64 = value_balance.try_into().unwrap(); let value: i64 = value_balance.try_into().unwrap();
let value = Amount::from_i64(value).unwrap(); let value = Amount::from_i64(value).unwrap();
let sighash = ledger_get_sighash().await?; 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(); 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( let sapling_bundle = Bundle::<_>::from_parts(
shielded_spends, shielded_outputs, value, shielded_spends, shielded_outputs, value,
SapAuthorized { binding_sig } ); SapAuthorized { binding_sig } );
@ -430,11 +528,13 @@ pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient<Channel>, t
let mut raw_tx = vec![]; let mut raw_tx = vec![];
tx.write_v5(&mut raw_tx)?; tx.write_v5(&mut raw_tx)?;
ledger_end_tx().await?;
let response = client.send_transaction(Request::new(RawTransaction { let response = client.send_transaction(Request::new(RawTransaction {
data: raw_tx, data: raw_tx,
height: 0, height: 0,
})).await?.into_inner(); })).await?.into_inner();
println!("{}", response.error_message); log::info!("{}", response.error_message);
Ok(response.error_message) Ok(response.error_message)
} }

View File

@ -1,7 +1,8 @@
use super::transport::*; use super::transport::*;
use anyhow::Result; use anyhow::Result;
async fn unit_tests() -> Result<()> { #[allow(dead_code)]
pub async fn unit_tests() -> Result<()> {
let hash = ledger_pedersen_hash( let hash = ledger_pedersen_hash(
&hex::decode("B315693B486D4D3CD8E4256E8C37CA4E8EC367E4D95D5C314625DC7B44B57EA2CA18FDCFF5871906F4238FB315693B486D4D3CD8E4256E8C37CA4E8EC367E4D95D5C314625DC7B44B57EA2")? &hex::decode("B315693B486D4D3CD8E4256E8C37CA4E8EC367E4D95D5C314625DC7B44B57EA2CA18FDCFF5871906F4238FB315693B486D4D3CD8E4256E8C37CA4E8EC367E4D95D5C314625DC7B44B57EA2")?
).await?; ).await?;

View File

@ -13,9 +13,19 @@ use zcash_primitives::zip32::DiversifiableFullViewingKey;
use std::io::Write; use std::io::Write;
use hex_literal::hex; use hex_literal::hex;
async fn apdu(data: &[u8]) -> Vec<u8> { fn handle_error_code(code: u16) -> Result<()> {
let api = HidApi::new().unwrap(); match code {
let transport = TransportNativeHID::new(&api).unwrap(); 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<Vec<u8>> {
let api = HidApi::new()?;
let transport = TransportNativeHID::new(&api)?;
let command = APDUCommand { let command = APDUCommand {
cla: data[0], cla: data[0],
ins: data[1], ins: data[1],
@ -23,10 +33,12 @@ async fn apdu(data: &[u8]) -> Vec<u8> {
p2: data[3], p2: data[3],
data: &data[5..], data: &data[5..],
}; };
println!("ins {}", data[1]); log::info!("ins {}", data[1]);
let response = transport.exchange(&command).unwrap(); let response = transport.exchange(&command)?;
println!("ret {}", response.retcode()); let error_code = response.retcode();
response.data().to_vec() 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"); const TEST_SERVER_IP: Option<&'static str> = option_env!("LEDGER_IP");
@ -36,19 +48,20 @@ async fn apdu_http(data: &[u8]) -> Vec<u8> {
.post(&format!("http://{}:5000/apdu", TEST_SERVER_IP.unwrap())) .post(&format!("http://{}:5000/apdu", TEST_SERVER_IP.unwrap()))
.body(format!("{{\"data\": \"{}\"}}", hex::encode(data))) .body(format!("{{\"data\": \"{}\"}}", hex::encode(data)))
.send() .send()
.await .await?;
.unwrap(); let response_body: Value = response.json().await?;
let response_body: Value = response.json().await.unwrap(); let data = response_body["data"].as_str().ok_or(anyhow!("No data field"))?;
let data = response_body["data"].as_str().unwrap(); let data = hex::decode(data)?;
let data = hex::decode(data).unwrap(); let error_code = u16::from_be_bytes(data[data.len()-2..].try_into().unwrap());
data[..data.len()-2].to_vec() handle_error_code(error_code)?;
Ok(data[..data.len()-2].to_vec())
} }
pub async fn ledger_init() -> Result<()> { pub async fn ledger_init() -> Result<()> {
let mut bb: Vec<u8> = vec![]; let mut bb: Vec<u8> = vec![];
bb.clear(); bb.clear();
bb.write_all(&hex!("E005000000"))?; bb.write_all(&hex!("E005000000"))?;
apdu(&bb).await; apdu(&bb).await?;
Ok(()) Ok(())
} }
@ -56,7 +69,7 @@ pub async fn ledger_init() -> Result<()> {
pub async fn ledger_get_dfvk() -> Result<DiversifiableFullViewingKey> { pub async fn ledger_get_dfvk() -> Result<DiversifiableFullViewingKey> {
let mut bb: Vec<u8> = vec![]; let mut bb: Vec<u8> = vec![];
bb.write_all(&hex!("E006000000"))?; bb.write_all(&hex!("E006000000"))?;
let dfvk_vec = apdu(&bb).await; let dfvk_vec = apdu(&bb).await?;
let mut dfvk = [0; 128]; let mut dfvk = [0; 128];
dfvk.copy_from_slice(&dfvk_vec); dfvk.copy_from_slice(&dfvk_vec);
@ -64,18 +77,141 @@ pub async fn ledger_get_dfvk() -> Result<DiversifiableFullViewingKey> {
Ok(dfvk) Ok(dfvk)
} }
pub async fn ledger_get_address() -> Result<String> { pub async fn ledger_get_pubkey() -> Result<Vec<u8>> {
let mut bb: Vec<u8> = vec![]; let mut bb: Vec<u8> = vec![];
bb.write_all(&hex!("E007000000"))?; bb.write_all(&hex!("E007000000"))?;
let address = apdu(&bb).await; let pk = apdu(&bb).await?;
let address = String::from_utf8_lossy(&address); Ok(pk)
Ok(address.to_string()) }
pub async fn ledger_get_o_fvk() -> Result<Vec<u8>> {
let mut bb: Vec<u8> = vec![];
bb.write_all(&hex!("E008000000"))?;
let pk = apdu(&bb).await?;
Ok(pk)
}
pub async fn ledger_init_tx(header_digest: &[u8]) -> Result<Vec<u8>> {
let mut bb: Vec<u8> = 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<u8> = 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<u8> = 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<u8> = 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<u8> = 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<u8> = vec![];
bb.write_all(&hex!("E015000008"))?;
bb.write_u64::<LE>(amount)?;
apdu(&bb).await?;
Ok(())
}
pub async fn ledger_add_t_output(amount: u64, address: &[u8]) -> Result<()> {
let mut bb: Vec<u8> = vec![];
bb.write_all(&hex!("E01601001D"))?;
bb.write_u64::<LE>(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<u8> = vec![];
bb.write_all(&hex!("E017010087"))?;
bb.write_all(address)?;
bb.write_u64::<LE>(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<u8> = vec![];
bb.write_all(&hex!("E0180100A7"))?;
bb.write_all(nf)?;
bb.write_all(address)?;
bb.write_u64::<LE>(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<u8> = vec![];
bb.write_all(&hex!("E019000008"))?;
bb.write_i64::<LE>(net)?;
apdu(&bb).await?;
Ok(())
}
pub async fn ledger_set_net_orchard(net: i64) -> Result<()> {
let mut bb: Vec<u8> = vec![];
bb.write_all(&hex!("E01A000008"))?;
bb.write_i64::<LE>(net)?;
apdu(&bb).await?;
Ok(())
}
pub async fn ledger_confirm_fee() -> Result<()> {
let mut bb: Vec<u8> = vec![];
bb.write_all(&hex!("E01B000000"))?;
apdu(&bb).await?;
Ok(())
}
pub async fn ledger_get_sighash() -> Result<Vec<u8>> {
let mut bb: Vec<u8> = vec![];
bb.write_all(&hex!("E020000000"))?;
let sighash = apdu(&bb).await?;
Ok(sighash)
} }
pub async fn ledger_get_proofgen_key() -> Result<ProofGenerationKey> { pub async fn ledger_get_proofgen_key() -> Result<ProofGenerationKey> {
let mut bb: Vec<u8> = vec![]; let mut bb: Vec<u8> = vec![];
bb.write_all(&hex!("E011000000"))?; bb.write_all(&hex!("E021000000"))?;
let proofgen_key = apdu(&bb).await; let proofgen_key = apdu(&bb).await?;
let proofgen_key = ProofGenerationKey { let proofgen_key = ProofGenerationKey {
ak: SubgroupPoint::from_bytes(proofgen_key[0..32].try_into().unwrap()).unwrap(), ak: SubgroupPoint::from_bytes(proofgen_key[0..32].try_into().unwrap()).unwrap(),
nsk: Fr::from_bytes(proofgen_key[32..64].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<ProofGenerationKey> {
Ok(proofgen_key) Ok(proofgen_key)
} }
pub async fn ledger_init_tx(header_digest: &[u8]) -> Result<Vec<u8>> { pub async fn ledger_sign_transparent(txin_digest: &[u8]) -> Result<Vec<u8>> {
let mut bb: Vec<u8> = vec![]; let mut bb: Vec<u8> = vec![];
bb.write_all(&hex!("E008000020"))?; bb.write_all(&hex!("E022000020"))?;
bb.write_all(header_digest)?; bb.write_all(txin_digest)?;
let main_seed = apdu(&bb).await; let signature = apdu(&bb).await?;
Ok(main_seed) Ok(signature)
}
pub async fn ledger_add_t_input(amount: u64) -> Result<()> {
let mut bb: Vec<u8> = vec![];
bb.write_all(&hex!("E009000008"))?;
bb.write_u64::<LE>(amount)?;
apdu(&bb).await;
Ok(())
}
pub async fn ledger_add_t_output(amount: u64, address: &[u8]) -> Result<()> {
let mut bb: Vec<u8> = vec![];
bb.write_all(&hex!("E00A00001D"))?;
bb.write_u64::<LE>(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<u8> = vec![];
bb.write_all(&hex!("E00B000087"))?;
bb.write_all(address)?;
bb.write_u64::<LE>(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<u8> = 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<u8> = 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<u8> = vec![];
bb.write_all(&hex!("E00C000008"))?;
bb.write_i64::<LE>(net)?;
apdu(&bb).await;
Ok(())
}
pub async fn ledger_set_stage(stage: u8) -> Result<()> {
let mut bb: Vec<u8> = 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<Vec<u8>> {
let mut bb: Vec<u8> = vec![];
bb.write_all(&hex!("E010000000"))?;
let sighash = apdu(&bb).await;
Ok(sighash)
} }
pub async fn ledger_sign_sapling() -> Result<Vec<u8>> { pub async fn ledger_sign_sapling() -> Result<Vec<u8>> {
let mut bb: Vec<u8> = vec![]; let mut bb: Vec<u8> = vec![];
bb.write_all(&hex!("E012000000"))?; bb.write_all(&hex!("E023000000"))?;
let signature = apdu(&bb).await; let signature = apdu(&bb).await?;
Ok(signature) Ok(signature)
} }
pub async fn ledger_sign_transparent(txin_digest: &[u8]) -> Result<Vec<u8>> { pub async fn ledger_end_tx() -> Result<()> {
let mut bb: Vec<u8> = vec![]; let mut bb: Vec<u8> = vec![];
bb.write_all(&hex!("E014000020"))?; bb.write_all(&hex!("E030000000"))?;
bb.write_all(txin_digest)?; apdu(&bb).await?;
let signature = apdu(&bb).await; Ok(())
Ok(signature)
}
pub async fn ledger_get_pubkey() -> Result<Vec<u8>> {
let mut bb: Vec<u8> = vec![];
bb.write_all(&hex!("E013000000"))?;
let pk = apdu(&bb).await;
Ok(pk)
} }
pub async fn ledger_cmu(data: &[u8]) -> Result<Vec<u8>> { pub async fn ledger_cmu(data: &[u8]) -> Result<Vec<u8>> {
@ -190,7 +246,7 @@ pub async fn ledger_cmu(data: &[u8]) -> Result<Vec<u8>> {
bb.write_all(&hex!("E0800000"))?; bb.write_all(&hex!("E0800000"))?;
bb.write_u8(data.len() as u8)?; bb.write_u8(data.len() as u8)?;
bb.write_all(data)?; bb.write_all(data)?;
let cmu = apdu(&bb).await; let cmu = apdu(&bb).await?;
Ok(cmu) Ok(cmu)
} }
@ -199,7 +255,7 @@ pub async fn ledger_jubjub_hash(data: &[u8]) -> Result<Vec<u8>> {
bb.write_all(&hex!("E0810000"))?; bb.write_all(&hex!("E0810000"))?;
bb.write_u8(data.len() as u8)?; bb.write_u8(data.len() as u8)?;
bb.write_all(data)?; bb.write_all(data)?;
let cmu = apdu(&bb).await; let cmu = apdu(&bb).await?;
Ok(cmu) Ok(cmu)
} }
@ -208,6 +264,15 @@ pub async fn ledger_pedersen_hash(data: &[u8]) -> Result<Vec<u8>> {
bb.write_all(&hex!("E0820000"))?; bb.write_all(&hex!("E0820000"))?;
bb.write_u8(data.len() as u8)?; bb.write_u8(data.len() as u8)?;
bb.write_all(data)?; bb.write_all(data)?;
let cmu = apdu(&bb).await; let cmu = apdu(&bb).await?;
Ok(cmu) Ok(cmu)
} }
pub async fn ledger_test_math(data: &[u8]) -> Result<Vec<u8>> {
let mut bb: Vec<u8> = vec![];
bb.write_all(&hex!("E0FF0000"))?;
bb.write_u8(data.len() as u8)?;
bb.write_all(data)?;
let res = apdu(&bb).await?;
Ok(res)
}

View File

@ -102,7 +102,7 @@ mod zip32;
pub mod api; pub mod api;
#[cfg(feature = "ledger")] #[cfg(feature = "ledger")]
mod ledger; pub mod ledger;
pub use crate::chain::{connect_lightwalletd, get_best_server, ChainError}; pub use crate::chain::{connect_lightwalletd, get_best_server, ChainError};
pub use crate::coinconfig::{ pub use crate::coinconfig::{
@ -121,7 +121,8 @@ pub use note_selection::{
build_tx, build_tx_plan, fetch_utxos, get_secret_keys, TransactionBuilderConfig, build_tx, build_tx_plan, fetch_utxos, get_secret_keys, TransactionBuilderConfig,
TransactionBuilderError, TransactionPlan, TxBuilderContext, MAX_ATTEMPTS, 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")] #[cfg(feature = "nodejs")]
pub mod nodejs; pub mod nodejs;
@ -134,7 +135,4 @@ pub fn init_test() {
set_coin_lwd_url(0, "http://127.0.0.1:9067"); 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; pub use taddr::derive_from_secretkey;

View File

@ -1,20 +1,39 @@
use std::{ use std::{
fs::File, fs::File,
io::Read, io::{Read, Write},
path::Path, 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 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 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; use zcash_proofs::prover::LocalTxProver;
#[tokio::main] use group::ff::Field;
async fn main() -> Result<()> { use nonempty::NonEmpty;
let network: &Network = &MainNetwork;
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 params_dir = Path::new(&std::env::var("HOME").unwrap()).join(".zcash-params");
let prover = LocalTxProver::new( let prover = LocalTxProver::new(
&params_dir.join("sapling-spend.params"), &params_dir.join("sapling-spend.params"),
@ -27,7 +46,243 @@ async fn main() -> Result<()> {
let mut client = connect_lightwalletd("https://lwdv3.zecwallet.co").await?; 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(()) 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::<LE>(expiry_height)?;
let header_digest = h.finalize();
// let Q: Point = Point::hash_to_curve(Q_PERSONALIZATION)(MERKLE_CRH_PERSONALIZATION.as_bytes());
// let p = <Ep as GroupEncoding>::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::<LE>(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::<SpendAuth>::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::InProgress<orchard::builder::Unproven, orchard::builder::Unauthorized>, _> = 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::<Unproven, OrchardUnauthorized> {
// 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<zcash_primitives::transaction::Unauthorized> = 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(())
}

View File

@ -13,8 +13,7 @@ use orchard::keys::{FullViewingKey, Scope, SpendAuthorizingKey, SpendingKey};
use orchard::note::Nullifier; use orchard::note::Nullifier;
use orchard::value::NoteValue; use orchard::value::NoteValue;
use orchard::{Address, Anchor, Bundle}; use orchard::{Address, Anchor, Bundle};
use rand::{CryptoRng, RngCore, SeedableRng}; use rand::{CryptoRng, RngCore};
use rand_chacha::ChaChaRng;
use ripemd::{Digest, Ripemd160}; use ripemd::{Digest, Ripemd160};
use secp256k1::{All, PublicKey, Secp256k1, SecretKey}; use secp256k1::{All, PublicKey, Secp256k1, SecretKey};
use sha2::Sha256; use sha2::Sha256;
@ -100,7 +99,6 @@ pub fn build_tx(
}; };
let mut has_orchard = false; 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 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) let anchor: Anchor = orchard::tree::MerkleHashOrchard::from_bytes(&plan.orchard_anchor)
.unwrap() .unwrap()

View File

@ -290,6 +290,7 @@ pub fn build_tx_plan<F: FeeCalculator>(
network: &Network, network: &Network,
fvk: &str, fvk: &str,
taddr: &str, taddr: &str,
orchard_fvk: &str,
anchor_height: u32, anchor_height: u32,
expiry_height: u32, expiry_height: u32,
orchard_anchor: &Option<Hash>, orchard_anchor: &Option<Hash>,
@ -323,6 +324,7 @@ pub fn build_tx_plan<F: FeeCalculator>(
let tx_plan = TransactionPlan { let tx_plan = TransactionPlan {
fvk: fvk.to_string(), fvk: fvk.to_string(),
taddr: taddr.to_string(), taddr: taddr.to_string(),
orchard_fvk: orchard_fvk.to_string(),
anchor_height, anchor_height,
expiry_height, expiry_height,
orchard_anchor: orchard_anchor.unwrap_or(Hash::default()), orchard_anchor: orchard_anchor.unwrap_or(Hash::default()),

View File

@ -157,8 +157,9 @@ pub struct RecipientShort {
#[derive(Serialize, Deserialize, Default)] #[derive(Serialize, Deserialize, Default)]
#[serde_as] #[serde_as]
pub struct TransactionPlan { pub struct TransactionPlan {
pub fvk: String,
pub taddr: String, pub taddr: String,
pub fvk: String,
pub orchard_fvk: String,
pub anchor_height: u32, pub anchor_height: u32,
pub expiry_height: u32, pub expiry_height: u32,
#[serde(with = "SerHex::<Strict>")] #[serde(with = "SerHex::<Strict>")]

View File

@ -12,7 +12,7 @@ mod note;
pub use hash::{OrchardHasher, ORCHARD_ROOTS}; pub use hash::{OrchardHasher, ORCHARD_ROOTS};
pub use key::{derive_orchard_keys, OrchardKeyBytes}; 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 { pub fn get_proving_key() -> &'static ProvingKey {
if !PROVING_KEY.filled() { if !PROVING_KEY.filled() {

View File

@ -1,7 +1,7 @@
use crate::chain::Nf; use crate::chain::Nf;
use crate::db::ReceivedNote; use crate::db::ReceivedNote;
use crate::sync::{ use crate::sync::{
CompactOutputBytes, DecryptedNote, Node, OutputPosition, TrialDecrypter, ViewKey, CompactOutputBytes, DecryptedNote, Node, OutputPosition, TrialDecrypter, ViewKey, Witness,
}; };
use crate::CompactTx; use crate::CompactTx;
use orchard::keys::PreparedIncomingViewingKey; use orchard::keys::PreparedIncomingViewingKey;
@ -110,3 +110,17 @@ impl<N: Parameters> TrialDecrypter<N, OrchardDomain, OrchardViewKey, DecryptedOr
vtx.actions.iter().map(|co| co.into()).collect() vtx.actions.iter().map(|co| co.into()).collect()
} }
} }
pub fn decode_merkle_path(id_note: u32, witness: &[u8]) -> anyhow::Result<orchard::tree::MerklePath> {
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)
}

View File

@ -423,18 +423,16 @@ pub async fn broadcast_tx(tx: &[u8]) -> anyhow::Result<String> {
height: latest_height as u64, height: latest_height as u64,
}; };
return Err(anyhow!("Broadcasting is disabled")); let rep = client
.send_transaction(Request::new(raw_tx))
// let rep = client .await?
// .send_transaction(Request::new(raw_tx)) .into_inner();
// .await? let code = rep.error_code;
// .into_inner(); if code == 0 {
// let code = rep.error_code; Ok(rep.error_message)
// if code == 0 { } else {
// Ok(rep.error_message) Err(anyhow::anyhow!(rep.error_message))
// } else { }
// Err(anyhow::anyhow!(rep.error_message))
// }
} }
pub fn get_tx_summary(tx: &Tx) -> anyhow::Result<TxSummary> { pub fn get_tx_summary(tx: &Tx) -> anyhow::Result<TxSummary> {

View File

@ -268,6 +268,19 @@ pub fn derive_from_secretkey(
Ok((sk, address)) Ok((sk, address))
} }
pub fn derive_from_pubkey(network: &Network, pub_key: &[u8]) -> anyhow::Result<String> {
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( pub async fn sweep_tkey(
last_height: u32, last_height: u32,
sk: &str, sk: &str,

View File

@ -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( pub fn get_unified_address(
network: &Network, network: &Network,
db: &DbAdapter, db: &DbAdapter,
@ -59,8 +62,8 @@ pub fn get_unified_address(
} }
if !tpe.sapling && !tpe.orchard { if !tpe.sapling && !tpe.orchard {
// UA cannot be t-only // UA cannot be t-only
tpe.sapling = true; let address = db.get_taddr(account)?.ok_or(anyhow!("No taddr"))?;
tpe.orchard = true; return Ok(address)
} }
let address = match (tpe.transparent, tpe.sapling, tpe.orchard) { let address = match (tpe.transparent, tpe.sapling, tpe.orchard) {