H/W ledger support
This commit is contained in:
parent
a2607930ac
commit
d4ba8f7cfc
19
Cargo.toml
19
Cargo.toml
|
@ -65,35 +65,42 @@ blake2b_simd = "0.5.11"
|
|||
chacha20poly1305 = "0.9.0"
|
||||
base64 = "^0.13"
|
||||
|
||||
ledger-apdu = { version = "0.8.0", optional = true }
|
||||
hmac = { version = "0.12.1", optional = true }
|
||||
ed25519-bip32 = { version = "0.4.1", optional = true }
|
||||
|
||||
[features]
|
||||
ledger = ["ledger-apdu", "hmac", "ed25519-bip32"]
|
||||
|
||||
# librustzcash synced to 35023ed8ca2fb1061e78fd740b640d4eefcc5edd
|
||||
|
||||
[dependencies.zcash_client_backend]
|
||||
git = "https://github.com/hhanh00/librustzcash.git"
|
||||
rev = "67689473e4edbfbd6d2ff33f6ddd5dd2a402bb20"
|
||||
rev = "c708380ef59b254f71a7d0b417e03f44fd267c2d"
|
||||
|
||||
[dependencies.zcash_primitives]
|
||||
git = "https://github.com/hhanh00/librustzcash.git"
|
||||
rev = "67689473e4edbfbd6d2ff33f6ddd5dd2a402bb20"
|
||||
rev = "c708380ef59b254f71a7d0b417e03f44fd267c2d"
|
||||
features = [ "transparent-inputs" ]
|
||||
|
||||
[dependencies.zcash_proofs]
|
||||
git = "https://github.com/hhanh00/librustzcash.git"
|
||||
rev = "67689473e4edbfbd6d2ff33f6ddd5dd2a402bb20"
|
||||
rev = "c708380ef59b254f71a7d0b417e03f44fd267c2d"
|
||||
|
||||
[dependencies.zcash_params]
|
||||
path = "../zcash-params"
|
||||
|
||||
[dependencies.zcash_address]
|
||||
git = "https://github.com/hhanh00/librustzcash.git"
|
||||
rev = "67689473e4edbfbd6d2ff33f6ddd5dd2a402bb20"
|
||||
rev = "c708380ef59b254f71a7d0b417e03f44fd267c2d"
|
||||
|
||||
[dependencies.zcash_encoding]
|
||||
git = "https://github.com/hhanh00/librustzcash.git"
|
||||
rev = "67689473e4edbfbd6d2ff33f6ddd5dd2a402bb20"
|
||||
rev = "c708380ef59b254f71a7d0b417e03f44fd267c2d"
|
||||
|
||||
[dependencies.zcash_note_encryption]
|
||||
git = "https://github.com/hhanh00/librustzcash.git"
|
||||
rev = "67689473e4edbfbd6d2ff33f6ddd5dd2a402bb20"
|
||||
rev = "c708380ef59b254f71a7d0b417e03f44fd267c2d"
|
||||
|
||||
[build-dependencies]
|
||||
tonic-build = "0.4.2"
|
||||
|
|
|
@ -0,0 +1,696 @@
|
|||
#![allow(unused_imports)]
|
||||
|
||||
use anyhow::anyhow;
|
||||
use bip39::{Mnemonic, Language, Seed};
|
||||
use byteorder::{ByteOrder, LE, LittleEndian, WriteBytesExt};
|
||||
use ed25519_bip32::{DerivationScheme, XPrv};
|
||||
use sha2::{Sha256, Sha512};
|
||||
use hmac::{Hmac, Mac};
|
||||
use hmac::digest::{crypto_common, FixedOutput, MacMarker, Update};
|
||||
use blake2b_simd::Params;
|
||||
use jubjub::{ExtendedPoint, Fr, SubgroupPoint};
|
||||
use group::GroupEncoding;
|
||||
use ledger_apdu::{APDUAnswer, APDUCommand};
|
||||
use rand::rngs::OsRng;
|
||||
use zcash_primitives::zip32::{ExtendedSpendingKey, ChildIndex, ExtendedFullViewingKey, ChainCode, DiversifierKey};
|
||||
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::sapling::keys::{ExpandedSpendingKey, FullViewingKey};
|
||||
use zcash_primitives::sapling::{Diversifier, Node, Note, PaymentAddress, ProofGenerationKey, Rseed, ViewingKey};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde::__private::de::Content::ByteBuf;
|
||||
use zcash_primitives::memo::Memo;
|
||||
use zcash_primitives::merkle_tree::IncrementalWitness;
|
||||
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::transaction::builder::Builder;
|
||||
use zcash_primitives::transaction::components::amount::DEFAULT_FEE;
|
||||
use zcash_primitives::transaction::components::OutputDescription;
|
||||
use zcash_primitives::transaction::components::sapling::GrothProofBytes;
|
||||
use zcash_proofs::sapling::SaplingProvingContext;
|
||||
use crate::{Tx, TxIn, TxOut};
|
||||
|
||||
const HARDENED: u32 = 0x8000_0000;
|
||||
const NETWORK: &Network = &MainNetwork;
|
||||
const EXPIRY: u32 = 50;
|
||||
const LEDGER_IP: &str = "192.168.0.101";
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[allow(non_snake_case)]
|
||||
struct APDURequest {
|
||||
apduHex: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct APDUReply {
|
||||
data: String,
|
||||
error: Option<String>,
|
||||
}
|
||||
|
||||
// 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.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(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!(address, "zs1m8d7506t4rpcgaag392xae698gx8j5at63qpg54ssprg6eqej0grmkfu76tq6p495z3w6s8qlll");
|
||||
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() {
|
||||
println!("{} {}", buffer.len(), sout.addr);
|
||||
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());
|
||||
println!("{}", buffer.len());
|
||||
buffer.write_u64::<LE>(sout.amount)?;
|
||||
println!("{}", buffer.len());
|
||||
buffer.push(0xF6); // no memo
|
||||
println!("{}", buffer.len());
|
||||
buffer.push(0x01); // ovk present
|
||||
buffer.extend_from_slice(&hex::decode(&sout.ovk)?);
|
||||
println!("{}", buffer.len());
|
||||
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);
|
||||
println!("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,
|
||||
};
|
||||
println!("data {}", hex::encode(c));
|
||||
let command = APDUCommand {
|
||||
cla: 0x85,
|
||||
ins: 0xA0,
|
||||
p1,
|
||||
p2: 0,
|
||||
data: c.to_vec(),
|
||||
};
|
||||
let rep = send_request(&command).await;
|
||||
println!("{}", 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_request(&command).await;
|
||||
println!("{}", rep.retcode);
|
||||
let ak = &rep.data[0..32];
|
||||
let nsk = &rep.data[32..64];
|
||||
let rcv = &rep.data[64..96];
|
||||
let ar = &rep.data[96..128];
|
||||
println!("ak {}", hex::encode(ak));
|
||||
println!("nsk {}", hex::encode(nsk));
|
||||
println!("rcv {}", hex::encode(rcv));
|
||||
println!("ar {}", hex::encode(ar));
|
||||
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());
|
||||
println!("rk {}", hex::encode(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_request(&command).await;
|
||||
println!("{}", rep.retcode);
|
||||
let rcv = &rep.data[0..32];
|
||||
let rseed = &rep.data[32..64];
|
||||
println!("rcv {}", hex::encode(rcv));
|
||||
println!("rseed {}", hex::encode(rseed));
|
||||
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,
|
||||
};
|
||||
println!("data {}", hex::encode(c));
|
||||
let command = APDUCommand {
|
||||
cla: 0x85,
|
||||
ins: 0xA3,
|
||||
p1,
|
||||
p2: 0,
|
||||
data: c.to_vec(),
|
||||
};
|
||||
let rep = send_request(&command).await;
|
||||
println!("{}", rep.retcode);
|
||||
if p1 == 2 {
|
||||
println!("{}", rep.data.len());
|
||||
tx_hash.copy_from_slice(&rep.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_request(&command).await;
|
||||
println!("{}", rep.retcode);
|
||||
let signature = &rep.data[0..64];
|
||||
println!("Signature: {}", hex::encode(signature));
|
||||
signatures.push(rep.data[0..64].to_vec())
|
||||
}
|
||||
|
||||
println!("tx hash: {}", hex::encode(tx_hash));
|
||||
println!("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();
|
||||
println!("binding_signature: {}", hex::encode(&sig_buffer));
|
||||
|
||||
let tx = get_tx_data(tx.height, u64::from(DEFAULT_FEE), &spend_datas, &output_descriptions,
|
||||
&signatures, &binding_signature);
|
||||
println!("{}", hex::encode(&tx));
|
||||
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
|
||||
{
|
||||
// get spend data
|
||||
|
||||
let txin = &tx.inputs[i];
|
||||
// let ak = hex::decode("36989b418770b44d5f2b1f02dab1ca09e8b3269bbc7c25c274da7d1452e55850").unwrap();
|
||||
// let nsk = hex::decode("6806f129399fa44851e3a1280682bb7a3d771d99aa328fae9501aea833826705").unwrap();
|
||||
// let rcv = hex::decode("6b0bd5a312e5ea1272aa3a7317d2e9b8eb534c229c046f076680b07dd8b1e509").unwrap();
|
||||
// let alpha = hex::decode("ebc82f3378d5b25004a3b798166ed1e0ca9a006befb31e5d5ae42b9234bbc509").unwrap();
|
||||
|
||||
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_request(command: &APDUCommand) -> APDUAnswer {
|
||||
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
|
||||
}
|
||||
|
||||
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 std::fs::File;
|
||||
use std::io::Read;
|
||||
use blake2b_simd::Params;
|
||||
use group::GroupEncoding;
|
||||
use jubjub::{Fr, ExtendedPoint, SubgroupPoint};
|
||||
use ledger_apdu::*;
|
||||
use zcash_primitives::constants::SPENDING_KEY_GENERATOR;
|
||||
use zcash_primitives::sapling::redjubjub::{PublicKey, Signature};
|
||||
use zcash_proofs::prover::LocalTxProver;
|
||||
use crate::ledger::{build_tx_ledger, send_request, slice_to_hash};
|
||||
use crate::Tx;
|
||||
|
||||
#[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.data[1], answer.data[2]);
|
||||
assert_eq!(answer.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.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();
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn load_tx2() {
|
||||
// let file = File::open("tx.json").unwrap();
|
||||
// let tx: Tx = serde_json::from_reader(&file).unwrap();
|
||||
// let prover = LocalTxProver::with_default_location().unwrap();
|
||||
// get_spendinfo(&tx, &prover);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn test_ask() {
|
||||
let ask = slice_to_hash(&hex::decode("3cfc5d99015978bf893e9e04ded88dee2fd10b9db84e500b300d81713d9b860c").unwrap());
|
||||
let alpha = slice_to_hash(&hex::decode("857a2184f5bfa1eb7dc22bdcae35ade9ebf96839a95c43e2b2bdd03aab37d809").unwrap());
|
||||
let ask = Fr::from_bytes(&ask).unwrap();
|
||||
let ak = SPENDING_KEY_GENERATOR * ask;
|
||||
let alpha = Fr::from_bytes(&alpha).unwrap();
|
||||
let rsk = ask + alpha;
|
||||
let rk = SPENDING_KEY_GENERATOR * rsk;
|
||||
println!("ak: {}", hex::encode(ak.to_bytes()));
|
||||
println!("rk: {}", hex::encode(rk.to_bytes()));
|
||||
let rk = PublicKey(ak.into()).randomize(alpha, SPENDING_KEY_GENERATOR);
|
||||
println!("rk: {}", hex::encode(rk.0.to_bytes()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sighash() {
|
||||
let data = hex::decode("0400008085202f89d53a633bbecf82fe9e9484d8a0e727c73bb9e68c96e72dec30144f6a84afa136a5f25f01959361ee6eb56a7401210ee268226f6ce764a4f10b7f29e54db37272869eda84eecf7257f9979a4848bbf52f4969a5736594ab7ba41452e7bb9068240000000000000000000000000000000000000000000000000000000000000000598b1eba3382c85d968e28c943d315ebd53a38d3fa5bb7fa40762cf5e753b20635b991949f54e9e17577d0a4cc1662e2c201d9d4d91bb641c93bc2799e46460800000000dba61800e80300000000000001000000").unwrap();
|
||||
let hash: [u8; 32] = slice_to_hash(Params::new()
|
||||
.hash_length(32)
|
||||
.personal(&hex::decode("5a6361736853696748617368a675ffe9").unwrap())
|
||||
.hash(&data)
|
||||
.as_bytes());
|
||||
|
||||
println!("{}", hex::encode(&hash));
|
||||
}
|
||||
}
|
|
@ -36,6 +36,14 @@ mod transaction;
|
|||
mod ua;
|
||||
mod wallet;
|
||||
|
||||
#[cfg(feature = "ledger")]
|
||||
mod ledger;
|
||||
|
||||
#[cfg(not(feature = "ledger"))]
|
||||
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 fn hex_to_hash(hex: &str) -> anyhow::Result<[u8; 32]> {
|
||||
let mut hash = [0u8; 32];
|
||||
hex::decode_to_slice(hex, &mut hash)?;
|
||||
|
@ -59,3 +67,4 @@ pub use crate::print::*;
|
|||
pub use crate::scan::{latest_height, scan_all, sync_async};
|
||||
pub use crate::ua::{get_sapling, get_ua};
|
||||
pub use crate::wallet::{RecipientMemo, Wallet, WalletBalance, encrypt_backup, decrypt_backup};
|
||||
pub use crate::ledger::build_tx_ledger;
|
||||
|
|
|
@ -5,10 +5,7 @@ use crate::pay::TxBuilder;
|
|||
use crate::prices::fetch_historical_prices;
|
||||
use crate::scan::ProgressCallback;
|
||||
use crate::taddr::{get_taddr_balance, get_utxos};
|
||||
use crate::{
|
||||
broadcast_tx, connect_lightwalletd, get_latest_height, BlockId, CTree,
|
||||
DbAdapter,
|
||||
};
|
||||
use crate::{broadcast_tx, connect_lightwalletd, get_latest_height, BlockId, CTree, DbAdapter, build_tx_ledger};
|
||||
use bip39::{Language, Mnemonic};
|
||||
use lazycell::AtomicLazyCell;
|
||||
use rand::rngs::OsRng;
|
||||
|
@ -17,6 +14,7 @@ use secp256k1::SecretKey;
|
|||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::convert::TryFrom;
|
||||
use std::fs::File;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use anyhow::anyhow;
|
||||
|
@ -584,6 +582,15 @@ impl Wallet {
|
|||
Ok(recipient_memos)
|
||||
}
|
||||
|
||||
pub async fn ledger_sign(&mut self, tx_filename: &str) -> anyhow::Result<String> {
|
||||
self._ensure_prover()?;
|
||||
let file = File::open(tx_filename)?;
|
||||
let mut tx: Tx = serde_json::from_reader(&file)?;
|
||||
let raw_tx = build_tx_ledger(&mut tx, self.prover.borrow().unwrap()).await?;
|
||||
let tx_id = broadcast_tx(&raw_tx, &self.ld_url).await?;
|
||||
Ok(tx_id)
|
||||
}
|
||||
|
||||
fn chain(&self) -> &dyn CoinChain { get_coin_chain(self.coin_type) }
|
||||
fn network(&self) -> &Network { self.chain().network() }
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue