Refactor
This commit is contained in:
parent
945cf8941e
commit
9b94a6a2b3
60
Cargo.toml
60
Cargo.toml
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(¬e.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(())
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
21
src/lib.rs
21
src/lib.rs
|
@ -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;
|
||||
|
|
|
@ -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(
|
||||
¶ms_dir.join("sapling-spend.params"),
|
||||
¶ms_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(())
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()),
|
||||
|
|
|
@ -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>")]
|
||||
|
|
23
src/pay.rs
23
src/pay.rs
|
@ -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> {
|
||||
|
|
Loading…
Reference in New Issue