This commit is contained in:
Hanh 2023-04-14 23:00:42 +10:00
parent 945cf8941e
commit 9b94a6a2b3
18 changed files with 817 additions and 942 deletions

View File

@ -19,9 +19,9 @@ name = "wallet"
path = "src/main/wallet.rs"
required-features = ["dotenv"]
#[[bin]]
#name = "ledger"
#path = "src/main/ledger.rs"
[[bin]]
name = "ledger"
path = "src/main/ledger.rs"
#[[bin]]
#name = "sign"
@ -74,6 +74,7 @@ clap = "3.1.18"
chrono = "0.4.19"
lazycell = "1.3.0"
reqwest = { version = "0.11.4", features = ["json", "rustls-tls"], default-features = false }
hex-literal = "0.4"
# Halo
orchard = "0.3.0"
@ -89,15 +90,13 @@ base58check = "0.1.0"
raptorq = "1.7.0"
sysinfo = "0.25"
ledger-apdu = { version = "0.9.0", optional = true }
hmac = { version = "0.12.1", optional = true }
ed25519-bip32 = { version = "0.4.1", optional = true }
ledger-transport-hid = { version = "0.9", optional = true }
ledger-transport-hid = { version = "0.10", optional = true }
ledger-apdu = { version = "0.10", optional = true }
allo-isolate = { version = "0.1", optional = true }
once_cell = { version = "1.8.0", optional = true }
android_logger = { version = "0.10.0", optional = true }
rocket = { version = "0.5.0-rc.2", features = ["json"], optional = true }
rocket = { version = "0.5.0-rc.3", features = ["json"], optional = true }
dotenv = { version = "0.15.0", optional = true }
node-bindgen = { version = "4.0", optional = true }
@ -109,8 +108,7 @@ objc = { version = "0.2", features = [ "objc_exception" ], optional = true }
block = { version = "0.1.6", optional = true }
[features]
ledger = ["ledger-apdu", "hmac", "ed25519-bip32", "ledger-transport-hid"]
ledger_sapling = ["ledger"]
ledger = ["ledger-apdu", "ledger-transport-hid"]
dart_ffi = ["allo-isolate", "once_cell", "android_logger"]
rpc = ["rocket", "dotenv"]
nodejs = ["node-bindgen"]
@ -119,40 +117,40 @@ apple_metal = ["metal", "objc", "block"]
sqlcipher = ["rusqlite/bundled-sqlcipher-vendored-openssl"]
[dependencies.zcash_params]
git = "https://github.com/hhanh00/zcash-params.git"
rev = "154f3544781500c27f58923ccc6c7e779eecf9df"
#path = "../zcash-params"
#git = "https://github.com/hhanh00/zcash-params.git"
#rev = "154f3544781500c27f58923ccc6c7e779eecf9df"
path = "../zcash-params"
[dependencies.zcash_client_backend]
git = "https://github.com/hhanh00/librustzcash.git"
rev = "f39dd6c2af20662fd1aaa9e00172efebd3435ff4"
#path = "../../librustzcash/zcash_client_backend"
#git = "https://github.com/hhanh00/librustzcash.git"
#rev = "f39dd6c2af20662fd1aaa9e00172efebd3435ff4"
path = "../../librustzcash/zcash_client_backend"
[dependencies.zcash_primitives]
git = "https://github.com/hhanh00/librustzcash.git"
rev = "f39dd6c2af20662fd1aaa9e00172efebd3435ff4"
#path = "../../librustzcash/zcash_primitives"
#git = "https://github.com/hhanh00/librustzcash.git"
#rev = "f39dd6c2af20662fd1aaa9e00172efebd3435ff4"
path = "../../librustzcash/zcash_primitives"
features = [ "transparent-inputs" ]
[dependencies.zcash_proofs]
git = "https://github.com/hhanh00/librustzcash.git"
rev = "f39dd6c2af20662fd1aaa9e00172efebd3435ff4"
#path = "../../librustzcash/zcash_proofs"
#git = "https://github.com/hhanh00/librustzcash.git"
#rev = "f39dd6c2af20662fd1aaa9e00172efebd3435ff4"
path = "../../librustzcash/zcash_proofs"
[dependencies.zcash_address]
git = "https://github.com/hhanh00/librustzcash.git"
rev = "f39dd6c2af20662fd1aaa9e00172efebd3435ff4"
#path = "../../librustzcash/components/zcash_address"
#git = "https://github.com/hhanh00/librustzcash.git"
#rev = "f39dd6c2af20662fd1aaa9e00172efebd3435ff4"
path = "../../librustzcash/components/zcash_address"
[dependencies.zcash_encoding]
git = "https://github.com/hhanh00/librustzcash.git"
rev = "f39dd6c2af20662fd1aaa9e00172efebd3435ff4"
#path = "../../librustzcash/components/zcash_encoding"
#git = "https://github.com/hhanh00/librustzcash.git"
#rev = "f39dd6c2af20662fd1aaa9e00172efebd3435ff4"
path = "../../librustzcash/components/zcash_encoding"
[dependencies.zcash_note_encryption]
git = "https://github.com/hhanh00/librustzcash.git"
rev = "f39dd6c2af20662fd1aaa9e00172efebd3435ff4"
#path = "../../librustzcash/components/zcash_note_encryption"
#git = "https://github.com/hhanh00/librustzcash.git"
#rev = "f39dd6c2af20662fd1aaa9e00172efebd3435ff4"
path = "../../librustzcash/components/zcash_note_encryption"
[build-dependencies]
tonic-build = "0.7.2"

View File

@ -250,6 +250,12 @@ struct CResult_____c_char get_diversified_address(uint8_t ua_type, uint32_t time
struct CResult_u32 get_latest_height(void);
struct CResult_u8 ledger_build_keys(void);
struct CResult_____c_char ledger_get_fvk(uint8_t coin);
struct CResult_____c_char ledger_get_address(void);
void skip_to_last_height(uint8_t coin);
struct CResult_u32 rewind_to(uint32_t height);

View File

@ -15,6 +15,8 @@ use std::path::Path;
use std::sync::Mutex;
use tokio::sync::Semaphore;
use zcash_primitives::transaction::builder::Progress;
#[cfg(feature="ledger2")]
use crate::ledger2;
static mut POST_COBJ: Option<ffi::DartPostCObjectFnType> = None;
@ -394,6 +396,42 @@ pub async unsafe extern "C" fn get_latest_height() -> CResult<u32> {
to_cresult(height)
}
#[cfg(feature = "ledger2")]
#[no_mangle]
pub unsafe extern "C" fn ledger_build_keys() -> CResult<u8> {
use crate::ledger2::{LedgerClient, build_keys};
let res = || {
let client = ledger2::LedgerClient::new()?;
ledger2::build_keys(&client)?;
Ok(0u8)
};
to_cresult(res())
}
#[cfg(feature="ledger2")]
#[no_mangle]
pub unsafe extern "C" fn ledger_get_fvk(coin: u8) -> CResult<*mut c_char> {
let res = || {
let c = CoinConfig::get(coin);
let client = ledger2::LedgerClient::new()?;
let fvk = ledger2::get_fvk(c.chain.network(), &client)?;
Ok(fvk)
};
to_cresult_str(res())
}
#[cfg(feature = "ledger2")]
#[no_mangle]
pub unsafe extern "C" fn ledger_get_address() -> CResult<*mut c_char> {
let res = || {
let client = ledger2::LedgerClient::new()?;
let address = ledger2::get_address(&client)?;
Ok(address)
};
to_cresult_str(res())
}
#[allow(dead_code)]
fn report_progress(progress: Progress, port: i64) {
if port != 0 {

View File

@ -42,10 +42,11 @@ pub async fn build_tx_plan_with_utxos(
}
}
let fvk = {
let (fvk, taddr) = {
let db = c.db()?;
let AccountData { fvk, .. } = db.get_account_info(account)?;
fvk
let taddr = db.get_taddr(account)?.unwrap_or_default();
(fvk, taddr)
};
let change_address = get_unified_address(coin, account, 7)?;
let context = TxBuilderContext::from_height(coin, checkpoint_height)?;
@ -77,6 +78,7 @@ pub async fn build_tx_plan_with_utxos(
let tx_plan = note_selection::build_tx_plan::<FeeFlat>(
network,
&fvk,
&taddr,
checkpoint_height,
expiry_height,
&context.orchard_anchor,

View File

@ -1,20 +1,7 @@
use serde::{Deserialize, Serialize};
mod transport;
mod builder;
#[cfg(feature = "ledger_sapling")]
pub mod sapling;
// #[cfg(test)]
mod tests;
mod transparent;
#[derive(Serialize, Deserialize)]
#[allow(non_snake_case)]
struct APDURequest {
apduHex: String,
}
#[derive(Serialize, Deserialize)]
struct APDUReply {
data: String,
error: Option<String>,
}
pub use transparent::sweep_ledger;
pub use builder::build_broadcast_tx;

346
src/ledger/builder.rs Normal file
View File

@ -0,0 +1,346 @@
use std::{
fs::File,
io::Read,
path::{Path, PathBuf}, vec,
};
use std::io::Write;
use blake2b_simd::Params;
use bls12_381::Scalar;
use byteorder::WriteBytesExt;
use byteorder::LE;
use ff::{PrimeField, Field};
use group::GroupEncoding;
use hex_literal::hex;
use jubjub::{Fr, SubgroupPoint, Fq};
use ledger_apdu::APDUCommand;
use ledger_transport_hid::{TransportNativeHID, hidapi::HidApi};
use orchard::keys::FullViewingKey;
use rand::{rngs::OsRng, RngCore, SeedableRng};
use rand_chacha::ChaChaRng;
use reqwest::Client;
use ripemd::{Digest, Ripemd160};
use secp256k1::{All, PublicKey, Secp256k1, SecretKey};
use serde_json::Value;
use sha2::Sha256;
use tonic::{Request, transport::Channel};
use crate::{Destination, Source, TransactionPlan, connect_lightwalletd, RawTransaction, CompactTxStreamerClient};
use zcash_client_backend::encoding::{decode_extended_spending_key, encode_extended_full_viewing_key, encode_payment_address};
use zcash_note_encryption::EphemeralKeyBytes;
use zcash_params::tx;
use crate::ledger::transport::*;
use anyhow::{anyhow, Result};
use zcash_primitives::{
consensus::{BlockHeight, BranchId, MainNetwork, Parameters},
merkle_tree::IncrementalWitness,
sapling::{
note_encryption::sapling_note_encryption, value::{NoteValue, ValueCommitment, ValueSum}, Diversifier, Node, Note,
PaymentAddress, Rseed, ProofGenerationKey, Nullifier, prover::TxProver, redjubjub::Signature,
},
transaction::{
components::{sapling::{OutputDescriptionV5, Bundle, Authorized as SapAuthorized}, Amount, OutputDescription, SpendDescription},
TransactionData, TxVersion, Authorized,
},
zip32::{DiversifiableFullViewingKey, ExtendedFullViewingKey, ExtendedSpendingKey}, constants::PROOF_GENERATION_KEY_GENERATOR,
};
use zcash_primitives::merkle_tree::Hashable;
use zcash_primitives::transaction::components::GROTH_PROOF_SIZE;
use zcash_proofs::{prover::LocalTxProver, sapling::SaplingProvingContext};
struct SpendDescriptionUnAuthorized {
cv: ValueCommitment,
anchor: Fq,
pub nullifier: Nullifier,
rk: zcash_primitives::sapling::redjubjub::PublicKey,
zkproof: [u8; GROTH_PROOF_SIZE],
}
pub async fn build_broadcast_tx(client: &mut CompactTxStreamerClient<Channel>, tx_plan: &TransactionPlan, prover: &LocalTxProver) -> Result<()> {
ledger_init().await?;
let address = ledger_get_address().await?;
println!("address {}", address);
let secp = Secp256k1::<All>::new();
// TODO: Not used atm
let sk: SecretKey = "ee729122985b068958c6c62afe6ee70927d3e7f9405c0b9a09e822cd2f5ce2ae"
.parse()
.unwrap();
let pub_key = PublicKey::from_secret_key(&secp, &sk);
let pub_key = pub_key.serialize();
let pub_key = Ripemd160::digest(&Sha256::digest(&pub_key));
let tpub_key: [u8; 20] = pub_key.into();
// Compute header digest
let mut h = Params::new()
.hash_length(32)
.personal(b"ZTxIdHeadersHash")
.to_state();
h.update(&hex!("050000800a27a726b4d0d6c200000000"));
h.write_u32::<LE>(tx_plan.expiry_height)?;
let header_digest = h.finalize();
let master_seed = ledger_init_tx(header_digest.as_bytes()).await?;
// For testing only
// let esk = "secret-extended-key-main1qwy5cttzqqqqpq8ksfmzqgz90r73yevcw6mvwuv5zuddak9zgl9epp6x308pczzez3hse753heepdk886yf7dmse5qvyl5jsuk5w4ejhtm30cpa862kq0pfu0z4zxxvyd523zeta3rr6lj0vg30mshf6wrlfucg47jv3ldspe0sv464uewwlglr0dzakssj8tdx27vq3dnerfa5z5fgf8vjutlcey3lwn4m6ncg8y4n2cgl64rd768uqg0yfvshljqt3g4x83kngv4guq06xx";
// let extsk = decode_extended_spending_key(MainNetwork.hrp_sapling_extended_spending_key(), &esk)
// .unwrap();
// let ovk = extsk.expsk.ovk;
// let proofgen_key = extsk.expsk.proof_generation_key();
// let dfvk = extsk.to_diversifiable_full_viewing_key();
let dfvk = ledger_get_dfvk().await?;
let ovk = dfvk.fvk.ovk;
let proofgen_key = ledger_get_proofgen_key().await?;
let fvk = dfvk.fvk;
let nf_key = proofgen_key.to_viewing_key().nk;
println!("NSK {}", hex::encode(proofgen_key.nsk.to_bytes()));
assert_eq!(PROOF_GENERATION_KEY_GENERATOR * proofgen_key.nsk, fvk.vk.nk.0);
// Derive rseed PRNG
let mut h = Params::new()
.hash_length(32)
.personal(b"ZRSeedPRNG__Hash")
.to_state();
h.update(&master_seed);
let main_rseed = h.finalize();
let mut rseed_rng = ChaChaRng::from_seed(main_rseed.as_bytes().try_into().unwrap());
// Derive alpha PRNG
let mut h = Params::new()
.hash_length(32)
.personal(b"ZAlphaPRNG__Hash")
.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()
.hash_length(32)
.personal(b"ZTxIdPrevoutHash")
.to_state();
let mut trscripts_hasher = Params::new()
.hash_length(32)
.personal(b"ZTxTrScriptsHash")
.to_state();
let mut sequences_hasher = Params::new()
.hash_length(32)
.personal(b"ZTxIdSequencHash")
.to_state();
let mut spends_compact_hasher = Params::new()
.hash_length(32)
.personal(b"ZTxIdSSpendCHash")
.to_state();
let mut spends_non_compact_hasher = Params::new()
.hash_length(32)
.personal(b"ZTxIdSSpendNHash")
.to_state();
let mut sapling_context = SaplingProvingContext::new();
let mut value_balance = ValueSum::zero();
let mut shielded_spends = vec![];
for sp in tx_plan.spends.iter() {
match sp.source {
Source::Transparent { txid, index } => {
prevouts_hasher.update(&txid);
prevouts_hasher.write_u32::<LE>(index)?;
trscripts_hasher.update(&hex!("1976a914"));
trscripts_hasher.update(&tpub_key);
trscripts_hasher.update(&hex!("88ac"));
sequences_hasher.update(&hex!("FFFFFFFF"));
ledger_add_t_input(sp.amount).await?;
}
Source::Sapling { diversifier, rseed, ref witness, .. } => {
let diversifier = Diversifier(diversifier);
let z_address = fvk.vk.to_payment_address(diversifier).ok_or(anyhow!("Invalid diversifier"))?;
let rseed = Rseed::BeforeZip212(Fr::from_bytes(&rseed).unwrap());
let note = Note::from_parts(z_address, NoteValue::from_raw(sp.amount), rseed);
let witness = IncrementalWitness::<Node>::read(&witness[..])?;
let merkle_path = witness.path().ok_or(anyhow!("Invalid merkle path"))?;
let node = Node::from_cmu(&note.cmu());
let anchor = Fq::from_bytes(&merkle_path.root(node).repr).unwrap();
let nullifier = note.nf(&nf_key, merkle_path.position);
let alpha = Fr::random(&mut alpha_rng);
println!("ALPHA {}", hex::encode(&alpha.to_bytes()));
let (zkproof, cv, rk) = prover.spend_proof(&mut sapling_context, proofgen_key.clone(), diversifier, rseed, alpha,
sp.amount, anchor, merkle_path.clone(), OsRng).map_err(|_| anyhow!("Error generating spend"))?;
value_balance = (value_balance + note.value()).ok_or(anyhow!("Invalid amount"))?;
spends_compact_hasher.update(nullifier.as_ref());
spends_non_compact_hasher.update(&cv.to_bytes());
spends_non_compact_hasher.update(&anchor.to_repr());
rk.write(&mut spends_non_compact_hasher)?;
shielded_spends.push(SpendDescriptionUnAuthorized { cv, anchor, nullifier, rk, zkproof });
}
Source::Orchard { .. } => { unimplemented!() },
}
}
ledger_set_stage(1).await?;
let prevouts_digest = prevouts_hasher.finalize();
println!("PREVOUTS {}", hex::encode(prevouts_digest));
let pubscripts_digest = trscripts_hasher.finalize();
println!("PUBSCRIPTS {}", hex::encode(pubscripts_digest));
let sequences_digest = sequences_hasher.finalize();
println!("SEQUENCES {}", hex::encode(sequences_digest));
let spends_compact_digest = spends_compact_hasher.finalize();
println!("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));
let mut spends_hasher = Params::new()
.hash_length(32)
.personal(b"ZTxIdSSpendsHash")
.to_state();
spends_hasher.update(spends_compact_digest.as_bytes());
spends_hasher.update(spends_non_compact_digest.as_bytes());
let spends_digest = spends_hasher.finalize();
println!("SPENDS {}", hex::encode(spends_digest));
let mut output_memos_hasher = Params::new()
.hash_length(32)
.personal(b"ZTxIdSOutM__Hash")
.to_state();
let mut output_non_compact_hasher = Params::new()
.hash_length(32)
.personal(b"ZTxIdSOutN__Hash")
.to_state();
let mut shielded_outputs = vec![];
for output in tx_plan.outputs.iter() {
if let Destination::Transparent(raw_address) = output.destination {
ledger_add_t_output(output.amount, &raw_address[1..21]).await?;
}
}
ledger_set_stage(2).await?;
for output in tx_plan.outputs.iter() {
if let Destination::Sapling(raw_address) = output.destination {
let recipient = PaymentAddress::from_bytes(&raw_address).unwrap();
let mut rseed = [0u8; 32];
rseed_rng.fill_bytes(&mut rseed);
let rseed = Rseed::AfterZip212(rseed);
let value = NoteValue::from_raw(output.amount);
value_balance = (value_balance - value).ok_or(anyhow!("Invalid amount"))?;
let note = Note::from_parts(recipient, value, rseed);
let rcm = note.rcm();
let cmu = note.cmu();
println!("cmu {}", hex::encode(cmu.to_bytes()));
let encryptor =
sapling_note_encryption::<_, MainNetwork>(Some(ovk.clone()), note, recipient, output.memo.clone(), &mut OsRng);
let (zkproof, cv) = prover.output_proof(
&mut sapling_context,
encryptor.esk().0,
recipient,
rcm,
output.amount,
&mut OsRng,
);
let enc_ciphertext = encryptor.encrypt_note_plaintext();
let out_ciphertext = encryptor.encrypt_outgoing_plaintext(&cv, &cmu, &mut OsRng);
let epk = encryptor.epk();
ledger_add_s_output(output.amount, &epk.to_bytes().0, &raw_address, &enc_ciphertext[0..52]).await?;
let memo = &enc_ciphertext[52..564];
output_memos_hasher.update(memo);
output_non_compact_hasher.update(&cv.as_inner().to_bytes());
output_non_compact_hasher.update(&enc_ciphertext[564..]);
output_non_compact_hasher.update(&out_ciphertext);
let ephemeral_key = epk.to_bytes();
shielded_outputs.push(OutputDescription { cv, cmu, ephemeral_key, enc_ciphertext, out_ciphertext, zkproof });
}
}
ledger_set_stage(3).await?;
let memos_digest = output_memos_hasher.finalize();
println!("MEMOS {}", hex::encode(memos_digest));
let outputs_nc_digest = output_non_compact_hasher.finalize();
println!("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?;
ledger_set_sapling_merkle_proof(spends_digest.as_bytes(), memos_digest.as_bytes(), outputs_nc_digest.as_bytes()).await?;
ledger_set_net_sapling(-tx_plan.net_chg[0]).await?;
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();
// let mut message: Vec<u8> = vec![];
// message.write_all(&rk.0.to_bytes())?;
// message.write_all(sig_hash.as_ref())?;
// println!("MSG {}", hex::encode(&message));
// let verified = rk.verify_with_zip216(&message, &signature, SPENDING_KEY_GENERATOR, true);
// assert!(verified);
signatures.push(signature);
}
let shielded_spends = shielded_spends.into_iter().zip(signatures.into_iter()).map(|(sp, spend_auth_sig)|
SpendDescription::<_> { cv: sp.cv, anchor: sp.anchor, nullifier: sp.nullifier, rk: sp.rk, zkproof: sp.zkproof,
spend_auth_sig }).collect();
let value: i64 = value_balance.try_into().unwrap();
let value = Amount::from_i64(value).unwrap();
let sighash = ledger_get_sighash().await?;
let binding_sig = sapling_context.binding_sig(value, &sighash.try_into().unwrap()).unwrap();
let sapling_bundle = Bundle::<_>::from_parts(
shielded_spends, shielded_outputs, value,
SapAuthorized { binding_sig } );
let authed_tx: TransactionData<Authorized> = TransactionData {
version: TxVersion::Zip225,
consensus_branch_id: BranchId::Nu5,
lock_time: 0,
expiry_height: BlockHeight::from_u32(tx_plan.expiry_height),
transparent_bundle: None,
sprout_bundle: None,
sapling_bundle: Some(sapling_bundle),
orchard_bundle: None,
};
let tx = authed_tx.freeze().unwrap();
let mut raw_tx = vec![];
tx.write_v5(&mut raw_tx)?;
let response = client.send_transaction(Request::new(RawTransaction {
data: raw_tx,
height: 0,
})).await?.into_inner();
println!("{}", response.error_message);
Ok(())
}

View File

@ -1,725 +0,0 @@
#![allow(unused_imports)]
use crate::ledger::{APDUReply, APDURequest};
use crate::{Tx, TxIn, TxOut};
use anyhow::anyhow;
use bip39::{Language, Mnemonic, Seed};
use blake2b_simd::Params;
use byteorder::{ByteOrder, LittleEndian, WriteBytesExt, LE};
use ed25519_bip32::{DerivationScheme, XPrv};
use group::GroupEncoding;
use hmac::digest::{crypto_common, FixedOutput, MacMarker, Update};
use hmac::{Hmac, Mac};
use jubjub::{ExtendedPoint, Fr, SubgroupPoint};
use ledger_apdu::{APDUAnswer, APDUCommand};
use rand::rngs::OsRng;
use serde::{Deserialize, Serialize};
use sha2::{Sha256, Sha512};
use zcash_client_backend::encoding::{
decode_extended_full_viewing_key, decode_payment_address, encode_extended_spending_key,
encode_payment_address,
};
use zcash_primitives::consensus::Network::MainNetwork;
use zcash_primitives::consensus::{BlockHeight, Network, Parameters};
use zcash_primitives::constants::{PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR};
use zcash_primitives::keys::OutgoingViewingKey;
use zcash_primitives::memo::Memo;
use zcash_primitives::merkle_tree::IncrementalWitness;
use zcash_primitives::sapling::keys::{ExpandedSpendingKey, FullViewingKey};
use zcash_primitives::sapling::note_encryption::sapling_note_encryption;
use zcash_primitives::sapling::prover::TxProver;
use zcash_primitives::sapling::redjubjub::{PublicKey, Signature};
use zcash_primitives::sapling::{
Diversifier, Node, Note, PaymentAddress, ProofGenerationKey, Rseed, ViewingKey,
};
use zcash_primitives::transaction::builder::Builder;
use zcash_primitives::transaction::components::amount::DEFAULT_FEE;
use zcash_primitives::transaction::components::sapling::GrothProofBytes;
use zcash_primitives::transaction::components::OutputDescription;
use zcash_primitives::zip32::{
ChainCode, ChildIndex, DiversifierKey, ExtendedFullViewingKey, ExtendedSpendingKey,
};
use zcash_proofs::sapling::SaplingProvingContext;
const HARDENED: u32 = 0x8000_0000;
const NETWORK: &Network = &MainNetwork;
const EXPIRY: u32 = 50;
const LEDGER_IP: &str = "192.168.0.101";
// fn get_ivk(app: &LedgerApp) -> anyhow::Result<String> {
// let command = ApduCommand {
// cla: 0x85,
// ins: 0xf0,
// p1: 1,
// p2: 0,
// length: 4,
// data: vec![0, 0, 0, 0]
// };
// let res = app.exchange(command)?;
// let mut raw_ivk = [0u8; 32];
// raw_ivk.copy_from_slice(&res.apdu_data());
// let ivk = jubjub::Fr::from_bytes(&raw_ivk).unwrap();
// let ivk = SaplingIvk(ivk);
// let fvk = ExtendedFullViewingKey {
// depth: 0,
// parent_fvk_tag: (),
// child_index: (),
// chain_code: (),
// fvk: FullViewingKey {},
// dk: DiversifierKey()
// };
// println!("{}", address);
//
// Ok(address)
// }
const CURVE_SEEDKEY: &[u8] = b"ed25519 seed";
const ZCASH_PERSO: &[u8] = b"Zcash_ExpandSeed";
type HMAC256 = Hmac<Sha256>;
type HMAC512 = Hmac<Sha512>;
fn hmac_sha2<T: Update + FixedOutput + MacMarker + crypto_common::KeyInit>(data: &mut [u8]) {
let mut hmac = T::new_from_slice(CURVE_SEEDKEY).unwrap();
hmac.update(&data);
data.copy_from_slice(&hmac.finalize().into_bytes());
}
macro_rules! prf_expand {
($($y:expr),*) => (
{
let mut res = [0u8; 64];
let mut hasher = Params::new()
.hash_length(64)
.personal(ZCASH_PERSO)
.to_state();
$(
hasher.update($y);
)*
res.copy_from_slice(&hasher.finalize().as_bytes());
res
})
}
struct ExtSpendingKey {
chain: [u8; 32],
ovk: [u8; 32],
dk: [u8; 32],
ask: Fr,
nsk: Fr,
}
fn derive_child(esk: &mut ExtSpendingKey, path: &[u32]) {
let mut a = [0u8; 32];
let mut n = [0u8; 32];
a.copy_from_slice(&esk.ask.to_bytes());
n.copy_from_slice(&esk.nsk.to_bytes());
for &p in path {
println!("==> ask: {}", hex::encode(esk.ask.to_bytes()));
let hardened = (p & 0x8000_0000) != 0;
let c = p & 0x7FFF_FFFF;
assert!(hardened);
//make index LE
//zip32 child derivation
let mut le_i = [0; 4];
LittleEndian::write_u32(&mut le_i, c + (1 << 31));
println!("==> chain: {}", hex::encode(esk.chain));
println!("==> a: {}", hex::encode(a));
println!("==> n: {}", hex::encode(n));
println!("==> ovk: {}", hex::encode(esk.ovk));
println!("==> dk: {}", hex::encode(esk.dk));
println!("==> i: {}", hex::encode(le_i));
let h = prf_expand!(&esk.chain, &[0x11], &a, &n, &esk.ovk, &esk.dk, &le_i);
println!("==> tmp: {}", hex::encode(h));
let mut key = [0u8; 32];
key.copy_from_slice(&h[..32]);
esk.chain.copy_from_slice(&h[32..]);
let ask_cur = Fr::from_bytes_wide(&prf_expand!(&key, &[0x13]));
let nsk_cur = Fr::from_bytes_wide(&prf_expand!(&key, &[0x14]));
esk.ask += ask_cur;
esk.nsk += nsk_cur;
let t = prf_expand!(&key, &[0x15], &esk.ovk);
esk.ovk.copy_from_slice(&t[..32]);
let t = prf_expand!(&key, &[0x16], &esk.dk);
esk.dk.copy_from_slice(&t[..32]);
a.copy_from_slice(&scalar_to_bytes(&prf_expand!(&key, &[0x00])));
n.copy_from_slice(&scalar_to_bytes(&prf_expand!(&key, &[0x01])));
}
}
fn scalar_to_bytes(k: &[u8; 64]) -> [u8; 32] {
let t = Fr::from_bytes_wide(k);
t.to_bytes()
}
struct SpendData {
position: u64,
cv: ExtendedPoint,
anchor: [u8; 32],
nullifier: [u8; 32],
rk: ExtendedPoint,
zkproof: [u8; 192],
}
pub async fn build_tx_ledger(tx: &mut Tx, prover: &impl TxProver) -> anyhow::Result<Vec<u8>> {
let mut buffer = Vec::<u8>::new();
let tin_count = tx.t_inputs.len();
let s_in_count = tx.inputs.len();
let mut s_out_count = tx.outputs.len();
// TODO: Support t in/outputs
assert_eq!(tin_count, 0);
buffer.push(0u8);
buffer.push(0u8);
buffer.push(s_in_count as u8);
buffer.push((s_out_count + 1) as u8); // +1 for change
let mut change = 0;
for sin in tx.inputs.iter() {
buffer.write_u32::<LE>(HARDENED)?;
let fvk = decode_extended_full_viewing_key(
NETWORK.hrp_sapling_extended_full_viewing_key(),
&sin.fvk,
)
.unwrap()
.unwrap();
let (_, pa) = fvk.default_address();
let address = encode_payment_address(NETWORK.hrp_sapling_payment_address(), &pa);
assert_eq!(pa.to_bytes().len(), 43);
buffer.extend_from_slice(&pa.to_bytes());
buffer.write_u64::<LE>(sin.amount)?;
change += sin.amount as i64;
}
// assert_eq!(buffer.len(), 4+55*s_in_count);
for sout in tx.outputs.iter() {
let pa = decode_payment_address(NETWORK.hrp_sapling_payment_address(), &sout.addr)
.unwrap()
.unwrap();
assert_eq!(pa.to_bytes().len(), 43);
buffer.extend_from_slice(&pa.to_bytes());
buffer.write_u64::<LE>(sout.amount)?;
buffer.push(0xF6); // no memo
buffer.push(0x01); // ovk present
buffer.extend_from_slice(&hex::decode(&sout.ovk)?);
change -= sout.amount as i64;
}
assert_eq!(buffer.len(), 4 + 55 * s_in_count + 85 * (s_out_count));
change -= i64::from(DEFAULT_FEE);
assert!(change >= 0);
let output_change = TxOut {
addr: tx.change.clone(),
amount: change as u64,
ovk: tx.ovk.clone(),
memo: "".to_string(),
};
tx.outputs.push(output_change);
s_out_count += 1;
let pa_change = decode_payment_address(NETWORK.hrp_sapling_payment_address(), &tx.change)
.unwrap()
.unwrap();
buffer.extend_from_slice(&pa_change.to_bytes());
buffer.write_u64::<LE>(change as u64)?;
buffer.push(0xF6); // no memo
buffer.push(0x01); // ovk present
buffer.extend_from_slice(&hex::decode(&tx.ovk)?);
assert_eq!(buffer.len(), 4 + 55 * s_in_count + 85 * s_out_count);
log::debug!("txlen {}", buffer.len());
let mut chunks: Vec<_> = buffer.chunks(250).collect();
chunks.insert(0, &[]); // starts with empty chunk
for (index, c) in chunks.iter().enumerate() {
let p1 = match index {
0 => 0,
_ if index == chunks.len() - 1 => 2,
_ => 1,
};
log::debug!("data {}", hex::encode(c));
let command = APDUCommand {
cla: 0x85,
ins: 0xA0,
p1,
p2: 0,
data: c.to_vec(),
};
let rep = send_http_request(&command).await;
log::debug!("{}", rep.retcode());
}
let mut buffer = Vec::<u8>::new();
let mut context = prover.new_sapling_proving_context();
let mut spend_datas: Vec<SpendData> = vec![];
for i in 0..s_in_count {
let txin = &tx.inputs[i];
let command = APDUCommand {
cla: 0x85,
ins: 0xA1,
p1: 0,
p2: 0,
data: vec![],
};
let rep = send_http_request(&command).await;
log::debug!("{}", rep.retcode());
let ak = &rep.apdu_data()[0..32];
let nsk = &rep.apdu_data()[32..64];
let rcv = &rep.apdu_data()[64..96];
let ar = &rep.apdu_data()[96..128];
let ak = SubgroupPoint::from_bytes(&slice_to_hash(&ak)).unwrap();
let nsk = Fr::from_bytes(&slice_to_hash(&nsk)).unwrap();
let rcv = Fr::from_bytes(&slice_to_hash(&rcv)).unwrap();
let ar = Fr::from_bytes(&slice_to_hash(&ar)).unwrap();
let spend_data = get_spend_proof(&tx, i, ak, nsk, ar, rcv, &mut context, prover);
let rseed = string_to_hash(&txin.rseed);
buffer.extend_from_slice(&rseed);
buffer.write_u64::<LE>(spend_data.position)?;
spend_datas.push(spend_data);
}
for spd in spend_datas.iter() {
buffer.extend_from_slice(&spd.cv.to_bytes());
buffer.extend_from_slice(&spd.anchor);
buffer.extend_from_slice(&spd.nullifier);
buffer.extend_from_slice(&spd.rk.to_bytes());
buffer.extend_from_slice(&spd.zkproof);
}
let mut output_descriptions: Vec<OutputDescription<GrothProofBytes>> = vec![];
for i in 0..s_out_count {
let command = APDUCommand {
cla: 0x85,
ins: 0xA2,
p1: 0,
p2: 0,
data: vec![],
};
let rep = send_http_request(&command).await;
log::debug!("{}", rep.retcode());
let rcv = &rep.apdu_data()[0..32];
let rseed = &rep.apdu_data()[32..64];
let rcv = Fr::from_bytes(&slice_to_hash(&rcv)).unwrap();
let rseed = slice_to_hash(&rseed);
let output_description =
get_output_description(tx, i, change as u64, rcv, rseed, &mut context, prover);
buffer.extend_from_slice(&output_description.cv.to_bytes());
buffer.extend_from_slice(&output_description.cmu.to_bytes());
buffer.extend_from_slice(&output_description.ephemeral_key.0);
buffer.extend_from_slice(&output_description.enc_ciphertext);
buffer.extend_from_slice(&output_description.out_ciphertext);
buffer.extend_from_slice(&output_description.zkproof);
output_descriptions.push(output_description);
}
let hash_data = get_hash_data(
tx.height,
u64::from(DEFAULT_FEE),
&spend_datas,
&output_descriptions,
);
buffer.extend_from_slice(&hash_data);
let sig_hash: [u8; 32] = slice_to_hash(
Params::new()
.hash_length(32)
.personal(&hex::decode("5a6361736853696748617368a675ffe9")?) // consensus branch id = canopy
.hash(&hash_data)
.as_bytes(),
);
let mut tx_hash = [0u8; 32];
let mut chunks: Vec<_> = buffer.chunks(250).collect();
chunks.insert(0, &[]); // starts with empty chunk
for (index, c) in chunks.iter().enumerate() {
let p1 = match index {
0 => 0,
_ if index == chunks.len() - 1 => 2,
_ => 1,
};
log::debug!("data {}", hex::encode(c));
let command = APDUCommand {
cla: 0x85,
ins: 0xA3,
p1,
p2: 0,
data: c.to_vec(),
};
let rep = send_http_request(&command).await;
log::debug!("{}", rep.retcode());
if p1 == 2 {
tx_hash.copy_from_slice(&rep.apdu_data()[0..32]);
}
}
let mut signatures: Vec<Vec<u8>> = vec![];
for _i in 0..s_in_count {
let command = APDUCommand {
cla: 0x85,
ins: 0xA4,
p1: 0,
p2: 0,
data: vec![],
};
let rep = send_http_request(&command).await;
log::debug!("{}", rep.retcode());
let signature = &rep.apdu_data()[0..64];
signatures.push(rep.apdu_data()[0..64].to_vec())
}
log::debug!("tx hash: {}", hex::encode(tx_hash));
log::debug!("sig hash: {}", hex::encode(sig_hash));
let binding_signature = prover
.binding_sig(&mut context, DEFAULT_FEE, &sig_hash)
.map_err(|_| anyhow!("Cannot create binding signature"))?;
let mut sig_buffer: Vec<u8> = vec![];
binding_signature.write(&mut sig_buffer).unwrap();
log::debug!("binding_signature: {}", hex::encode(&sig_buffer));
let tx = get_tx_data(
tx.height,
u64::from(DEFAULT_FEE),
&spend_datas,
&output_descriptions,
&signatures,
&binding_signature,
);
Ok(tx)
}
fn get_spend_proof<T: TxProver>(
tx: &Tx,
i: usize,
ak: SubgroupPoint,
nsk: Fr,
ar: Fr,
rcv: Fr,
context: &mut T::SaplingProvingContext,
prover: &T,
) -> SpendData {
let txin = &tx.inputs[i];
let fvk = decode_extended_full_viewing_key(
NETWORK.hrp_sapling_extended_full_viewing_key(),
&txin.fvk,
)
.unwrap()
.unwrap();
let mut diversifier = [0u8; 11];
hex::decode_to_slice(&txin.diversifier, &mut diversifier).unwrap();
let diversifier = Diversifier(diversifier);
let pa = fvk.fvk.vk.to_payment_address(diversifier).unwrap();
let mut rseed_bytes = [0u8; 32];
hex::decode_to_slice(&txin.rseed, &mut rseed_bytes).unwrap();
let rseed = Fr::from_bytes(&rseed_bytes).unwrap();
let note = pa
.create_note(txin.amount, Rseed::BeforeZip212(rseed))
.unwrap();
let w = hex::decode(&txin.witness).unwrap();
let witness = IncrementalWitness::<Node>::read(&*w).unwrap();
let merkle_path = witness.path().unwrap();
let position = merkle_path.position;
let cmu = Node::new(note.cmu().into());
let anchor = merkle_path.root(cmu).into();
let pgk = ProofGenerationKey { ak, nsk };
let value = txin.amount;
let vk = pgk.to_viewing_key();
let (spend_proof, cv, rk) = prover
.spend_proof_with_rcv(
context,
rcv,
pgk,
diversifier,
Rseed::BeforeZip212(rseed),
ar,
value,
anchor,
merkle_path,
)
.unwrap();
let spend_data = SpendData {
position,
cv,
anchor: anchor.to_bytes(),
nullifier: note.nf(&vk, position).0,
rk: rk.0,
zkproof: spend_proof,
};
spend_data
}
fn get_output_description<T: TxProver>(
tx: &Tx,
i: usize,
change_amount: u64,
rcv: Fr,
rseed: [u8; 32],
context: &mut T::SaplingProvingContext,
prover: &T,
) -> OutputDescription<GrothProofBytes> {
let txout = if i == tx.outputs.len() {
TxOut {
addr: tx.change.clone(),
amount: change_amount,
ovk: tx.ovk.clone(),
memo: "".to_string(),
}
} else {
tx.outputs[i].clone()
};
let ovk = OutgoingViewingKey(string_to_hash(&tx.ovk));
let pa = decode_payment_address(NETWORK.hrp_sapling_payment_address(), &txout.addr)
.unwrap()
.unwrap();
let rseed = Rseed::AfterZip212(rseed);
let note = pa.create_note(txout.amount, rseed).unwrap();
let encryptor = sapling_note_encryption::<_, zcash_primitives::consensus::MainNetwork>(
Some(ovk),
note.clone(),
pa.clone(),
Memo::Empty.encode(),
&mut OsRng,
);
let cmu = note.cmu();
let epk = *encryptor.epk();
let (zkproof, cv) = prover.output_proof_with_rcv(
context,
rcv,
*encryptor.esk(),
pa.clone(),
note.rcm(),
txout.amount,
);
let enc_ciphertext = encryptor.encrypt_note_plaintext();
let out_ciphertext = encryptor.encrypt_outgoing_plaintext(&cv, &cmu, &mut OsRng);
OutputDescription {
cv,
cmu,
ephemeral_key: epk.to_bytes().into(),
enc_ciphertext,
out_ciphertext,
zkproof,
}
}
fn string_to_hash(s: &str) -> [u8; 32] {
slice_to_hash(&hex::decode(s).unwrap())
}
fn slice_to_hash(s: &[u8]) -> [u8; 32] {
let mut b = [0u8; 32];
b.copy_from_slice(s);
b
}
async fn send_http_request(command: &APDUCommand<Vec<u8>>) -> APDUAnswer<Vec<u8>> {
let port = 9000;
let apdu_hex = hex::encode(command.serialize());
let client = reqwest::Client::new();
let rep = client
.post(format!("http://{}:{}", LEDGER_IP, port))
.json(&APDURequest { apduHex: apdu_hex })
.header("Content-Type", "application/json")
.send()
.await
.unwrap();
let rep: APDUReply = rep.json().await.unwrap();
let answer = APDUAnswer::from_answer(hex::decode(rep.data).unwrap());
answer.unwrap()
}
fn get_hash_data(
expiry_height: u32,
sapling_value_balance: u64,
spend_datas: &[SpendData],
output_descriptions: &[OutputDescription<GrothProofBytes>],
) -> Vec<u8> {
let prevout_hash: [u8; 32] = slice_to_hash(
Params::new()
.hash_length(32)
.personal(b"ZcashPrevoutHash")
.hash(&[])
.as_bytes(),
);
let out_hash: [u8; 32] = slice_to_hash(
Params::new()
.hash_length(32)
.personal(b"ZcashOutputsHash")
.hash(&[])
.as_bytes(),
);
let sequence_hash: [u8; 32] = slice_to_hash(
Params::new()
.hash_length(32)
.personal(b"ZcashSequencHash")
.hash(&[])
.as_bytes(),
);
let mut data: Vec<u8> = vec![];
for sp in spend_datas.iter() {
data.extend_from_slice(&sp.cv.to_bytes());
data.extend_from_slice(&sp.anchor);
data.extend_from_slice(&sp.nullifier);
data.extend_from_slice(&sp.rk.to_bytes());
data.extend_from_slice(&sp.zkproof);
}
let shieldedspendhash: [u8; 32] = slice_to_hash(
Params::new()
.hash_length(32)
.personal(b"ZcashSSpendsHash")
.hash(&data)
.as_bytes(),
);
let mut data: Vec<u8> = vec![];
for output_description in output_descriptions.iter() {
data.extend_from_slice(&output_description.cv.to_bytes());
data.extend_from_slice(&output_description.cmu.to_bytes());
data.extend_from_slice(&output_description.ephemeral_key.0);
data.extend_from_slice(&output_description.enc_ciphertext);
data.extend_from_slice(&output_description.out_ciphertext);
data.extend_from_slice(&output_description.zkproof);
}
let shieldedoutputhash: [u8; 32] = slice_to_hash(
Params::new()
.hash_length(32)
.personal(b"ZcashSOutputHash")
.hash(&data)
.as_bytes(),
);
let mut tx_hash_data: Vec<u8> = vec![];
tx_hash_data.write_u32::<LE>(0x80000004).unwrap();
tx_hash_data.write_u32::<LE>(0x892F2085).unwrap();
tx_hash_data.extend_from_slice(&prevout_hash);
tx_hash_data.extend_from_slice(&sequence_hash);
tx_hash_data.extend_from_slice(&out_hash);
tx_hash_data.extend_from_slice(&[0u8; 32]);
tx_hash_data.extend_from_slice(&shieldedspendhash);
tx_hash_data.extend_from_slice(&shieldedoutputhash);
tx_hash_data.write_u32::<LE>(0).unwrap();
tx_hash_data
.write_u32::<LE>(expiry_height + EXPIRY)
.unwrap();
tx_hash_data.write_u64::<LE>(sapling_value_balance).unwrap();
tx_hash_data.write_u32::<LE>(1).unwrap();
assert_eq!(tx_hash_data.len(), 220);
tx_hash_data
}
fn get_tx_data(
expiry_height: u32,
sapling_value_balance: u64,
spend_datas: &[SpendData],
output_descriptions: &[OutputDescription<GrothProofBytes>],
signatures: &[Vec<u8>],
binding_signature: &Signature,
) -> Vec<u8> {
let mut tx_data: Vec<u8> = vec![];
tx_data.write_u32::<LE>(0x80000004).unwrap();
tx_data.write_u32::<LE>(0x892F2085).unwrap();
tx_data.push(0);
tx_data.push(0);
tx_data.write_u32::<LE>(0).unwrap();
tx_data.write_u32::<LE>(expiry_height + EXPIRY).unwrap();
tx_data.write_u64::<LE>(sapling_value_balance).unwrap();
tx_data.push(spend_datas.len() as u8); // TODO Support compactsize
for (sp, sig) in spend_datas.iter().zip(signatures) {
let mut sp_bytes: Vec<u8> = vec![];
sp_bytes.extend_from_slice(&sp.cv.to_bytes());
sp_bytes.extend_from_slice(&sp.anchor);
sp_bytes.extend_from_slice(&sp.nullifier);
sp_bytes.extend_from_slice(&sp.rk.to_bytes());
sp_bytes.extend_from_slice(&sp.zkproof);
sp_bytes.extend_from_slice(&sig);
assert_eq!(sp_bytes.len(), 384);
tx_data.extend_from_slice(&sp_bytes);
}
tx_data.push(output_descriptions.len() as u8); // TODO Support compactsize
for output_description in output_descriptions.iter() {
tx_data.extend_from_slice(&output_description.cv.to_bytes());
tx_data.extend_from_slice(&output_description.cmu.to_bytes());
tx_data.extend_from_slice(&output_description.ephemeral_key.0);
tx_data.extend_from_slice(&output_description.enc_ciphertext);
tx_data.extend_from_slice(&output_description.out_ciphertext);
tx_data.extend_from_slice(&output_description.zkproof);
}
tx_data.push(0);
let mut sig_buffer: Vec<u8> = vec![];
binding_signature.write(&mut sig_buffer).unwrap();
tx_data.extend_from_slice(&sig_buffer);
tx_data
}
#[cfg(test)]
mod tests {
use crate::ledger::{build_tx_ledger, send_request, slice_to_hash};
use crate::Tx;
use blake2b_simd::Params;
use group::GroupEncoding;
use jubjub::{ExtendedPoint, Fr, SubgroupPoint};
use ledger_apdu::*;
use std::fs::File;
use std::io::Read;
use zcash_primitives::constants::SPENDING_KEY_GENERATOR;
use zcash_primitives::sapling::redjubjub::{PublicKey, Signature};
use zcash_proofs::prover::LocalTxProver;
#[tokio::test]
async fn get_version() {
let command = APDUCommand {
cla: 0x85,
ins: 0x00,
p1: 0,
p2: 0,
data: vec![],
};
let answer = send_request(&command).await;
assert_eq!(answer.retcode(), 0x9000);
println!("{}.{}", answer.apdu_data()[1], answer.apdu_data()[2]);
assert_eq!(answer.apdu_data()[1], 3);
}
#[tokio::test]
async fn get_addr() {
let command = APDUCommand {
cla: 0x85,
ins: 0x11,
p1: 0,
p2: 0,
data: vec![0, 0, 0, 0],
};
let answer = send_request(&command).await;
let address = String::from_utf8(answer.apdu_data()[43..].to_ascii_lowercase()).unwrap();
println!("{}", address);
assert_eq!(
address,
"zs1m8d7506t4rpcgaag392xae698gx8j5at63qpg54ssprg6eqej0grmkfu76tq6p495z3w6s8qlll"
);
}
#[tokio::test]
async fn load_tx() {
let file = File::open("tx.json").unwrap();
let mut tx: Tx = serde_json::from_reader(&file).unwrap();
let prover = LocalTxProver::with_default_location().unwrap();
build_tx_ledger(&mut tx, &prover).await.unwrap();
}
}

20
src/ledger/tests.rs Normal file
View File

@ -0,0 +1,20 @@
use super::transport::*;
use anyhow::Result;
async fn unit_tests() -> Result<()> {
let hash = ledger_pedersen_hash(
&hex::decode("B315693B486D4D3CD8E4256E8C37CA4E8EC367E4D95D5C314625DC7B44B57EA2CA18FDCFF5871906F4238FB315693B486D4D3CD8E4256E8C37CA4E8EC367E4D95D5C314625DC7B44B57EA2")?
).await?;
assert_eq!(hex::encode(hash), "155966835f64664e38335990c7ffbf37038375fa6b77e9315f40c136011a5a58");
let hash = ledger_jubjub_hash(
&hex::decode("B315693B486D4D3CD8E4256E8C37CA4E8EC367E4D95D5C314625DC7B44B57EA2CA18FDCFF5871906F4238F")?
).await?;
assert_eq!(hex::encode(hash), "0ce188d187e6e5bd7b4b966bdb0b0cc7737138be5adb60491e51e146a96944da");
let cmu = ledger_cmu(
&hex::decode("20A1070000000000B315693B486D4D3CD8E4256E8C37CA4E8EC367E4D95D5C314625DC7B44B57EA2CA18FDCFF5871906F4238F196D4C779C9A84ECFA9248146DE1C089316947A991AECCB1522DD2A3855B96E8")?
).await?;
assert_eq!(hex::encode(cmu), "d4a5fe02f8ad9056c4191f1a1c9a026641b617eaa0aed0d3b3be5228a998d069");
Ok(())
}

View File

@ -1,132 +0,0 @@
use crate::ledger::{APDUReply, APDURequest};
use crate::taddr::{get_taddr_balance, get_utxos};
use crate::{connect_lightwalletd, GetAddressUtxosArg, LWD_URL};
use anyhow::Result;
use byteorder::{BigEndian as BE, LittleEndian as LE, ReadBytesExt, WriteBytesExt};
use ledger_apdu::{APDUAnswer, APDUCommand};
use ledger_transport_hid::hidapi::HidApi;
use ledger_transport_hid::TransportNativeHID;
use ripemd::Digest;
use ripemd::Ripemd160;
use secp256k1::PublicKey;
use sha2::Sha256;
use std::io::{Read, Write};
use std::str::from_utf8;
use tonic::Request;
use zcash_client_backend::encoding::encode_transparent_address;
use zcash_primitives::consensus::Network::MainNetwork;
use zcash_primitives::consensus::Parameters;
use zcash_primitives::legacy::TransparentAddress;
const HARDENED: u32 = 0x80000000;
pub async fn sweep_ledger() -> Result<()> {
let api = HidApi::new()?;
let device = TransportNativeHID::list_ledgers(&api).next().unwrap();
let transport = TransportNativeHID::open_device(&api, &device).unwrap();
let mut data = vec![];
data.write_u8(5)?;
data.write_u32::<BE>(44 | HARDENED)?;
data.write_u32::<BE>(MainNetwork.coin_type() | HARDENED)?;
data.write_u32::<BE>(HARDENED)?;
data.write_u32::<BE>(0x0)?;
data.write_u32::<BE>(0x0)?;
let res = transport
.exchange(&APDUCommand {
cla: 0xE0,
ins: 0x40,
p1: 0,
p2: 0,
data: data.as_slice(),
})
.unwrap();
println!("{}", res.retcode());
let mut data = res.apdu_data();
println!("{}", hex::encode(&data));
let len = data.read_u8()?;
let mut pk = vec![0u8; len as usize];
data.read_exact(&mut pk).unwrap();
println!("{}", hex::encode(&pk));
let pub_key = PublicKey::from_slice(&pk).unwrap();
let pub_key = pub_key.serialize();
let pub_key = Ripemd160::digest(&Sha256::digest(&pub_key));
let pub_key_hash: [u8; 20] = pub_key.into();
let address = TransparentAddress::PublicKey(pub_key_hash.clone());
let address = encode_transparent_address(
&MainNetwork.b58_pubkey_address_prefix(),
&MainNetwork.b58_script_address_prefix(),
&address,
);
println!("{}", address);
let mut client = connect_lightwalletd(LWD_URL).await?;
let balance = get_taddr_balance(&mut client, &address).await.unwrap();
println!("{}", balance);
let req = GetAddressUtxosArg {
addresses: vec![address.to_string()],
start_height: 0,
max_entries: 0,
};
let utxo_rep = client
.get_address_utxos(Request::new(req))
.await?
.into_inner();
let mut first = true;
for utxo_reply in utxo_rep.address_utxos.iter() {
let mut data = vec![];
data.write_u8(0)?;
data.write_all(&utxo_reply.txid)?;
data.write_u32::<LE>(utxo_reply.index as u32)?;
let res = transport
.exchange(&APDUCommand {
cla: 0xE0,
ins: 0x40,
p1: if first { 0 } else { 0x80 },
p2: 0,
data: data.as_slice(),
})
.unwrap();
first = false;
}
let data = [0u8];
let res = transport
.exchange(&APDUCommand {
cla: 0xE0,
ins: 0x4A,
p1: 0xFF,
p2: 0,
data: data.as_slice(),
})
.unwrap();
println!("{}", res.retcode());
let mut data = vec![];
data.write_u8(1)?;
data.write_u64::<LE>(balance)?;
data.write_u8(25)?;
data.write_u8(0x76)?;
data.write_u8(0xa9)?;
data.write_u8(0x14)?;
data.write_all(&pub_key_hash)?;
data.write_u8(0x88)?;
data.write_u8(0xac)?;
let res = transport
.exchange(&APDUCommand {
cla: 0xE0,
ins: 0x4A,
p1: 0x80,
p2: 0,
data: data.as_slice(),
})
.unwrap();
println!("{}", res.retcode());
Ok(())
}

200
src/ledger/transport.rs Normal file
View File

@ -0,0 +1,200 @@
use anyhow::{anyhow, Result};
use jubjub::Fr;
use jubjub::SubgroupPoint;
use group::GroupEncoding;
use ledger_apdu::APDUCommand;
use ledger_transport_hid::{hidapi::HidApi, TransportNativeHID};
use reqwest::Client;
use serde_json::Value;
use byteorder::WriteBytesExt;
use byteorder::LE;
use zcash_primitives::sapling::ProofGenerationKey;
use zcash_primitives::zip32::DiversifiableFullViewingKey;
use std::io::Write;
use hex_literal::hex;
async fn apdu(data: &[u8]) -> Vec<u8> {
let api = HidApi::new().unwrap();
let transport = TransportNativeHID::new(&api).unwrap();
let command = APDUCommand {
cla: data[0],
ins: data[1],
p1: data[2],
p2: data[3],
data: &data[5..],
};
println!("ins {}", data[1]);
let response = transport.exchange(&command).unwrap();
println!("ret {}", response.retcode());
response.data().to_vec()
}
const TEST_SERVER_IP: &str = "127.0.0.1";
async fn apdu2(data: &[u8]) -> Vec<u8> {
let client = Client::new();
let response = client.post(&format!("http://{}:5000/apdu", TEST_SERVER_IP))
.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()
}
pub async fn ledger_init() -> Result<()> {
let mut bb: Vec<u8> = vec![];
bb.clear();
bb.write_all(&hex!("E005000000"))?;
apdu(&bb).await;
Ok(())
}
pub async fn ledger_get_dfvk() -> Result<DiversifiableFullViewingKey> {
let mut bb: Vec<u8> = vec![];
bb.write_all(&hex!("E006000000"))?;
let dfvk_vec = apdu(&bb).await;
let mut dfvk = [0; 128];
dfvk.copy_from_slice(&dfvk_vec);
let dfvk = DiversifiableFullViewingKey::from_bytes(&dfvk).ok_or(anyhow!("Invalid diversifiable fvk"))?;
Ok(dfvk)
}
pub async fn ledger_get_address() -> Result<String> {
let mut bb: Vec<u8> = vec![];
bb.write_all(&hex!("E007000000"))?;
let address = apdu(&bb).await;
let address = String::from_utf8_lossy(&address);
Ok(address.to_string())
}
pub async fn ledger_get_proofgen_key() -> Result<ProofGenerationKey> {
let mut bb: Vec<u8> = vec![];
bb.write_all(&hex!("E011000000"))?;
let proofgen_key = apdu(&bb).await;
let proofgen_key = ProofGenerationKey {
ak: SubgroupPoint::from_bytes(proofgen_key[0..32].try_into().unwrap()).unwrap(),
nsk: Fr::from_bytes(proofgen_key[32..64].try_into().unwrap()).unwrap(),
};
Ok(proofgen_key)
}
pub async fn ledger_init_tx(header_digest: &[u8]) -> Result<Vec<u8>> {
let mut bb: Vec<u8> = 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<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!("E00A000020"))?;
bb.write_u64::<LE>(amount)?;
bb.write_all(address)?;
bb.write_all(&hex!("000000"))?;
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!("E00B00008C"))?;
bb.write_u64::<LE>(amount)?;
bb.write_all(epk)?;
bb.write_all(address)?;
bb.write_all(&hex!("0000000000"))?;
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>> {
let mut bb: Vec<u8> = vec![];
bb.write_all(&hex!("E012000000"))?;
let signature = apdu(&bb).await;
Ok(signature)
}
pub async fn ledger_cmu(data: &[u8]) -> Result<Vec<u8>> {
let mut bb: Vec<u8> = vec![];
bb.write_all(&hex!("E0800000"))?;
bb.write_u8(data.len() as u8)?;
bb.write_all(data)?;
let cmu = apdu(&bb).await;
Ok(cmu)
}
pub async fn ledger_jubjub_hash(data: &[u8]) -> Result<Vec<u8>> {
let mut bb: Vec<u8> = vec![];
bb.write_all(&hex!("E0810000"))?;
bb.write_u8(data.len() as u8)?;
bb.write_all(data)?;
let cmu = apdu(&bb).await;
Ok(cmu)
}
pub async fn ledger_pedersen_hash(data: &[u8]) -> Result<Vec<u8>> {
let mut bb: Vec<u8> = vec![];
bb.write_all(&hex!("E0820000"))?;
bb.write_u8(data.len() as u8)?;
bb.write_all(data)?;
let cmu = apdu(&bb).await;
Ok(cmu)
}

79
src/ledger/util.rs Normal file
View File

@ -0,0 +1,79 @@
use anyhow::{anyhow, Result};
use ledger_apdu::{APDUCommand, APDUErrorCode};
use ledger_transport_hid::{TransportNativeHID, hidapi::HidApi};
use zcash_client_backend::encoding::encode_extended_full_viewing_key;
use zcash_primitives::{zip32::{DiversifiableFullViewingKey, ExtendedFullViewingKey}, consensus::{Parameters, Network}};
use std::ops::Deref;
pub fn build_keys(client: &LedgerClient) -> Result<()> {
let command = APDUCommand::<Vec<u8>> {
cla: 0xE0,
ins: 0x05,
p1: 0,
p2: 0,
data: vec![],
};
client.exchange(&command)?;
Ok(())
}
pub fn get_address(client: &LedgerClient) -> Result<String> {
let command = APDUCommand::<Vec<u8>> {
cla: 0xE0,
ins: 0x07,
p1: 0,
p2: 0,
data: vec![],
};
let response = client.exchange(&command)?;
let address = String::from_utf8_lossy(&response);
println!("{}", address);
Ok(address.to_string())
}
pub fn get_fvk(network: &Network, client: &LedgerClient) -> Result<String> {
let command = APDUCommand::<Vec<u8>> {
cla: 0xE0,
ins: 0x06,
p1: 0,
p2: 0,
data: vec![],
};
let response = client.exchange(&command)?;
let mut dfvk = [0u8; 128];
dfvk.copy_from_slice(&response);
let dfvk = DiversifiableFullViewingKey::from_bytes(&dfvk).ok_or(anyhow!("Invalid diversifiable fvk"))?;
let fvk = ExtendedFullViewingKey::from_diversifiable_full_viewing_key(&dfvk);
let fvk = encode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), &fvk);
Ok(fvk)
}
pub struct LedgerClient {
transport: TransportNativeHID,
}
impl LedgerClient {
pub fn new() -> Result<Self> {
let api = HidApi::new()?;
for d in api.device_list() {
println!("{:?} {} {} {}", d, d.path().to_string_lossy(), d.manufacturer_string().unwrap(), d.usage_page());
}
let transport = TransportNativeHID::new(&api)?;
Ok(LedgerClient {
transport,
})
}
pub fn exchange<B>(&self, command: &APDUCommand<B>) -> Result<Vec<u8>> where B: Deref<Target = [u8]> {
let answer = self.transport.exchange(command)?;
let code = answer.error_code().map_err(|e| anyhow!("APDU Error {}", e))?;
if code != APDUErrorCode::NoError {
anyhow::bail!(code);
}
Ok(answer.data().to_vec())
}
}

View File

@ -104,17 +104,6 @@ pub mod api;
#[cfg(feature = "ledger")]
mod ledger;
#[cfg(not(feature = "ledger"))]
#[allow(dead_code)]
mod ledger {
pub async fn build_tx_ledger(
_tx: &mut super::pay::Tx,
_prover: &impl zcash_primitives::sapling::prover::TxProver,
) -> anyhow::Result<Vec<u8>> {
unreachable!()
}
}
pub use crate::chain::{connect_lightwalletd, get_best_server, ChainError};
pub use crate::coinconfig::{
init_coin, set_active, set_active_account, set_coin_lwd_url, CoinConfig, COIN_CONFIG,
@ -128,17 +117,12 @@ pub use crate::pay::{broadcast_tx, Tx, TxIn, TxOut};
// pub use crate::wallet::{decrypt_backup, encrypt_backup, RecipientMemo, Wallet, WalletBalance};
pub use note_selection::{
Source, Destination,
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};
#[cfg(feature = "ledger_sapling")]
pub use crate::ledger::sapling::build_tx_ledger;
#[cfg(feature = "ledger")]
pub use crate::ledger::sweep_ledger;
#[cfg(feature = "nodejs")]
pub mod nodejs;
@ -149,3 +133,6 @@ pub fn init_test() {
init_coin(0, "./zec.db").unwrap();
set_coin_lwd_url(0, "http://127.0.0.1:9067");
}
#[cfg(feature = "ledger")]
pub use ledger::build_broadcast_tx;

View File

@ -1,6 +1,67 @@
use sync::sweep_ledger;
use std::{
fs::File,
io::Read,
path::{Path, PathBuf}, vec,
};
use std::io::Write;
use blake2b_simd::Params;
use bls12_381::Scalar;
use byteorder::WriteBytesExt;
use byteorder::LE;
use ff::{PrimeField, Field};
use group::GroupEncoding;
use hex_literal::hex;
use jubjub::{Fr, SubgroupPoint, Fq};
use ledger_apdu::APDUCommand;
use ledger_transport_hid::{TransportNativeHID, hidapi::HidApi};
use orchard::keys::FullViewingKey;
use rand::{rngs::OsRng, RngCore, SeedableRng};
use rand_chacha::ChaChaRng;
use reqwest::Client;
use ripemd::{Digest, Ripemd160};
use secp256k1::{All, PublicKey, Secp256k1, SecretKey};
use serde_json::Value;
use sha2::Sha256;
use tonic::{Request, transport::Channel};
use warp_api_ffi::{Destination, Source, TransactionPlan, connect_lightwalletd, RawTransaction, CompactTxStreamerClient, build_broadcast_tx};
use zcash_client_backend::encoding::{decode_extended_spending_key, encode_extended_full_viewing_key, encode_payment_address};
use zcash_note_encryption::EphemeralKeyBytes;
use zcash_params::tx;
use anyhow::{anyhow, Result};
use zcash_primitives::{
consensus::{BlockHeight, BranchId, MainNetwork, Parameters},
merkle_tree::IncrementalWitness,
sapling::{
note_encryption::sapling_note_encryption, value::{NoteValue, ValueCommitment, ValueSum}, Diversifier, Node, Note,
PaymentAddress, Rseed, ProofGenerationKey, Nullifier, prover::TxProver, redjubjub::Signature,
},
transaction::{
components::{sapling::{OutputDescriptionV5, Bundle, Authorized as SapAuthorized}, Amount, OutputDescription, SpendDescription},
TransactionData, TxVersion, Authorized,
},
zip32::{DiversifiableFullViewingKey, ExtendedFullViewingKey, ExtendedSpendingKey}, constants::PROOF_GENERATION_KEY_GENERATOR,
};
use zcash_primitives::merkle_tree::Hashable;
use zcash_primitives::transaction::components::GROTH_PROOF_SIZE;
use zcash_proofs::{prover::LocalTxProver, sapling::SaplingProvingContext};
#[tokio::main]
async fn main() {
sweep_ledger().await.unwrap();
}
async fn main() -> Result<()> {
let params_dir = Path::new(&std::env::var("HOME").unwrap()).join(".zcash-params");
let prover = LocalTxProver::new(
&params_dir.join("sapling-spend.params"),
&params_dir.join("sapling-output.params"),
);
let mut file = File::open("/tmp/tx.json").unwrap();
let mut data = String::new();
file.read_to_string(&mut data).unwrap();
let tx_plan: TransactionPlan = serde_json::from_str(&data).unwrap();
let mut client = connect_lightwalletd("https://lwdv3.zecwallet.co").await?;
build_broadcast_tx(&mut client, &tx_plan, &prover).await?;
Ok(())
}

View File

@ -1,5 +1,5 @@
use crate::{AccountData, Empty, Hash, RawTransaction};
use orchard::keys::{FullViewingKey, IncomingViewingKey, Scope, PreparedEphemeralPublicKey};
use orchard::keys::{FullViewingKey, IncomingViewingKey, Scope};
use orchard::note_encryption::OrchardDomain;
use std::collections::HashMap;
use tokio::runtime::Runtime;

View File

@ -13,7 +13,8 @@ use orchard::keys::{FullViewingKey, Scope, SpendAuthorizingKey, SpendingKey};
use orchard::note::Nullifier;
use orchard::value::NoteValue;
use orchard::{Address, Anchor, Bundle};
use rand::{CryptoRng, RngCore};
use rand::{CryptoRng, RngCore, SeedableRng};
use rand_chacha::ChaChaRng;
use ripemd::{Digest, Ripemd160};
use secp256k1::{All, PublicKey, Secp256k1, SecretKey};
use sha2::Sha256;
@ -99,7 +100,8 @@ pub fn build_tx(
};
let mut has_orchard = false;
let mut builder = Builder::new(*network, BlockHeight::from_u32(plan.anchor_height));
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()
.into();

View File

@ -289,6 +289,7 @@ pub fn outputs_for_change(
pub fn build_tx_plan<F: FeeCalculator>(
network: &Network,
fvk: &str,
taddr: &str,
anchor_height: u32,
expiry_height: u32,
orchard_anchor: &Option<Hash>,
@ -321,6 +322,7 @@ pub fn build_tx_plan<F: FeeCalculator>(
if updated_fee == fee {
let tx_plan = TransactionPlan {
fvk: fvk.to_string(),
taddr: taddr.to_string(),
anchor_height,
expiry_height,
orchard_anchor: orchard_anchor.unwrap_or(Hash::default()),

View File

@ -158,6 +158,7 @@ pub struct RecipientShort {
#[serde_as]
pub struct TransactionPlan {
pub fvk: String,
pub taddr: String,
pub anchor_height: u32,
pub expiry_height: u32,
#[serde(with = "SerHex::<Strict>")]

View File

@ -422,16 +422,19 @@ pub async fn broadcast_tx(tx: &[u8]) -> anyhow::Result<String> {
data: tx.to_vec(),
height: latest_height as u64,
};
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))
}
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))
// }
}
pub fn get_tx_summary(tx: &Tx) -> anyhow::Result<TxSummary> {