orchard builder
This commit is contained in:
parent
6e33e38986
commit
c10a074fb6
|
@ -1,4 +1,5 @@
|
|||
use blake2b_simd::Params;
|
||||
use blake2b_simd::State;
|
||||
use byteorder::WriteBytesExt;
|
||||
use byteorder::LE;
|
||||
use ff::{Field, PrimeField};
|
||||
|
@ -8,6 +9,7 @@ use jubjub::{Fq, Fr};
|
|||
|
||||
use orchard::keys::Scope;
|
||||
|
||||
use crate::ledger::builder::transparent_bundle::{TransparentBuilder, TransparentInputUnAuthorized};
|
||||
use crate::ledger::transport::*;
|
||||
use crate::taddr::derive_from_pubkey;
|
||||
use crate::{CompactTxStreamerClient, Destination, RawTransaction, Source, TransactionPlan};
|
||||
|
@ -48,10 +50,8 @@ use zcash_primitives::{
|
|||
};
|
||||
use zcash_proofs::{prover::LocalTxProver, sapling::SaplingProvingContext};
|
||||
|
||||
struct TransparentInputUnAuthorized {
|
||||
utxo: OutPoint,
|
||||
coin: TxOut,
|
||||
}
|
||||
mod transparent_bundle;
|
||||
mod orchard_bundle;
|
||||
|
||||
struct SpendDescriptionUnAuthorized {
|
||||
cv: ValueCommitment,
|
||||
|
@ -87,6 +87,14 @@ pub async fn show_public_keys() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create_hasher(perso: &[u8]) -> State {
|
||||
let h = Params::new()
|
||||
.hash_length(32)
|
||||
.personal(perso)
|
||||
.to_state();
|
||||
h
|
||||
}
|
||||
|
||||
pub async fn build_broadcast_tx(
|
||||
network: &Network,
|
||||
client: &mut CompactTxStreamerClient<Channel>,
|
||||
|
@ -95,31 +103,17 @@ pub async fn build_broadcast_tx(
|
|||
) -> Result<String> {
|
||||
ledger_init().await?;
|
||||
let pubkey = ledger_get_pubkey().await?;
|
||||
let ledger_taddr = derive_from_pubkey(network, &pubkey)?;
|
||||
let mut transparent_builder = TransparentBuilder::new(network, &pubkey);
|
||||
|
||||
if ledger_taddr != tx_plan.taddr {
|
||||
if transparent_builder.taddr != tx_plan.taddr {
|
||||
anyhow::bail!("This ledger wallet has a different address");
|
||||
}
|
||||
|
||||
let taddr = &tx_plan.taddr;
|
||||
|
||||
let taddr = decode_transparent_address(
|
||||
&network.b58_pubkey_address_prefix(),
|
||||
&network.b58_script_address_prefix(),
|
||||
taddr,
|
||||
)?
|
||||
.ok_or(anyhow!("Invalid taddr"))?;
|
||||
let pkh = match taddr {
|
||||
TransparentAddress::PublicKey(pkh) => pkh,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let tin_pubscript = taddr.script();
|
||||
|
||||
// Compute header digest
|
||||
let mut h = Params::new()
|
||||
.hash_length(32)
|
||||
.personal(b"ZTxIdHeadersHash")
|
||||
.to_state();
|
||||
let mut h = create_hasher(b"ZTxIdHeadersHash");
|
||||
h.update(&hex!("050000800a27a726b4d0d6c200000000"));
|
||||
|
||||
h.write_u32::<LE>(tx_plan.expiry_height)?;
|
||||
|
@ -169,21 +163,6 @@ pub async fn build_broadcast_tx(
|
|||
let alpha = h.finalize();
|
||||
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")
|
||||
|
@ -197,27 +176,11 @@ pub async fn build_broadcast_tx(
|
|||
let mut sapling_context = SaplingProvingContext::new();
|
||||
let mut value_balance = ValueSum::zero();
|
||||
|
||||
let mut vin = vec![];
|
||||
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(&pkh);
|
||||
trscripts_hasher.update(&hex!("88ac"));
|
||||
sequences_hasher.update(&hex!("FFFFFFFF"));
|
||||
|
||||
vin.push(TransparentInputUnAuthorized {
|
||||
utxo: OutPoint::new(txid, index),
|
||||
coin: TxOut {
|
||||
value: Amount::from_u64(sp.amount).unwrap(),
|
||||
script_pubkey: tin_pubscript.clone(), // will always use the h/w address
|
||||
},
|
||||
});
|
||||
|
||||
ledger_add_t_input(sp.amount).await?;
|
||||
transparent_builder.add_input(txid, index, sp.amount).await?;
|
||||
}
|
||||
Source::Sapling {
|
||||
diversifier,
|
||||
|
@ -276,12 +239,7 @@ pub async fn build_broadcast_tx(
|
|||
}
|
||||
ledger_set_stage(2).await?;
|
||||
|
||||
let prevouts_digest = prevouts_hasher.finalize();
|
||||
log::info!("PREVOUTS {}", hex::encode(prevouts_digest));
|
||||
let pubscripts_digest = trscripts_hasher.finalize();
|
||||
log::info!("PUBSCRIPTS {}", hex::encode(pubscripts_digest));
|
||||
let sequences_digest = sequences_hasher.finalize();
|
||||
log::info!("SEQUENCES {}", hex::encode(sequences_digest));
|
||||
transparent_builder.finalize_hash()?;
|
||||
|
||||
let spends_compact_digest = spends_compact_hasher.finalize();
|
||||
log::info!("C SPENDS {}", hex::encode(spends_compact_digest));
|
||||
|
@ -309,23 +267,13 @@ pub async fn build_broadcast_tx(
|
|||
.personal(b"ZTxIdSOutN__Hash")
|
||||
.to_state();
|
||||
|
||||
let mut vout = vec![];
|
||||
let mut shielded_outputs = vec![];
|
||||
for output in tx_plan.outputs.iter() {
|
||||
if let Destination::Transparent(raw_address) = output.destination {
|
||||
if raw_address[0] != 0 {
|
||||
anyhow::bail!("Only t1 addresses are supported");
|
||||
}
|
||||
ledger_add_t_output(output.amount, &raw_address).await?;
|
||||
let ta = TransparentAddress::PublicKey(raw_address[1..21].try_into().unwrap());
|
||||
vout.push(TxOut {
|
||||
value: Amount::from_u64(output.amount).unwrap(),
|
||||
script_pubkey: ta.script(),
|
||||
});
|
||||
transparent_builder.add_output(raw_address, output.amount).await?;
|
||||
}
|
||||
}
|
||||
ledger_set_stage(3).await?;
|
||||
let has_transparent = !vin.is_empty() || !vout.is_empty();
|
||||
|
||||
for output in tx_plan.outputs.iter() {
|
||||
if let Destination::Sapling(raw_address) = output.destination {
|
||||
|
@ -397,12 +345,7 @@ pub async fn build_broadcast_tx(
|
|||
let outputs_nc_digest = output_non_compact_hasher.finalize();
|
||||
log::info!("NC OUTPUTS {}", hex::encode(outputs_nc_digest));
|
||||
|
||||
ledger_set_transparent_merkle_proof(
|
||||
prevouts_digest.as_bytes(),
|
||||
pubscripts_digest.as_bytes(),
|
||||
sequences_digest.as_bytes(),
|
||||
)
|
||||
.await?;
|
||||
transparent_builder.set_merkle_proof().await?;
|
||||
ledger_set_sapling_merkle_proof(
|
||||
spends_digest.as_bytes(),
|
||||
memos_digest.as_bytes(),
|
||||
|
@ -414,157 +357,7 @@ pub async fn build_broadcast_tx(
|
|||
|
||||
ledger_set_stage(5).await?;
|
||||
|
||||
let orchard_spends: Vec<_> = tx_plan
|
||||
.spends
|
||||
.iter()
|
||||
.filter(|&s| {
|
||||
if let Source::Orchard { .. } = s.source {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.cloned()
|
||||
.collect();
|
||||
let orchard_outputs: Vec<_> = tx_plan
|
||||
.outputs
|
||||
.iter()
|
||||
.filter(|&o| {
|
||||
if let Destination::Orchard(_) = o.destination {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
let num_orchard_spends = orchard_spends.len();
|
||||
let num_orchard_outputs = orchard_outputs.len();
|
||||
let num_actions = num_orchard_spends.max(num_orchard_outputs);
|
||||
|
||||
let orchard_address = o_fvk.address_at(0u64, Scope::External);
|
||||
let mut empty_memo = [0u8; 512];
|
||||
empty_memo[0] = 0xF6;
|
||||
|
||||
// let mut actions = vec![];
|
||||
for i in 0..num_actions {
|
||||
let rcv = orchard::value::ValueCommitTrapdoor::random(OsRng);
|
||||
|
||||
let (_sk, dummy_fvk, dummy_note) = orchard::Note::dummy(&mut OsRng, None);
|
||||
let _dummy_recipient = dummy_fvk.address_at(0u64, Scope::External);
|
||||
|
||||
let alpha = pasta_curves::pallas::Scalar::random(&mut alpha_rng);
|
||||
|
||||
let (fvk, spend_note) = if i < num_orchard_spends {
|
||||
let sp = &tx_plan.spends[i];
|
||||
let note = match &sp.source {
|
||||
Source::Orchard { rseed, rho, .. } => {
|
||||
let rho = orchard::note::Nullifier::from_bytes(rho).unwrap();
|
||||
let note = orchard::Note::from_parts(
|
||||
orchard_address.clone(),
|
||||
orchard::value::NoteValue::from_raw(sp.amount),
|
||||
rho,
|
||||
orchard::note::RandomSeed::from_bytes(rseed.clone(), &rho).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
note
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
(o_fvk.clone(), note)
|
||||
} else {
|
||||
(dummy_fvk, dummy_note)
|
||||
};
|
||||
let nf = spend_note.nullifier(&fvk);
|
||||
|
||||
let mut rseed = [0; 32];
|
||||
rseed_rng.fill_bytes(&mut rseed);
|
||||
let (output_note, memo) = if i < num_orchard_outputs {
|
||||
let output = &orchard_outputs[i];
|
||||
let address = match output.destination {
|
||||
Destination::Orchard(address) => address,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let rseed = orchard::note::RandomSeed::from_bytes(rseed, &nf).unwrap();
|
||||
let note = orchard::Note::from_parts(
|
||||
orchard::Address::from_raw_address_bytes(&address).unwrap(),
|
||||
orchard::value::NoteValue::from_raw(output.amount),
|
||||
nf.clone(),
|
||||
rseed,
|
||||
)
|
||||
.unwrap();
|
||||
let memo = output.memo.as_array().clone();
|
||||
(note, memo)
|
||||
} else {
|
||||
(dummy_note.clone(), empty_memo)
|
||||
};
|
||||
|
||||
let _rk = fvk.ak.randomize(&alpha);
|
||||
let cm = output_note.commitment();
|
||||
let cmx = cm.into();
|
||||
|
||||
let encryptor = orchard::note_encryption::OrchardNoteEncryption::new(
|
||||
Some(o_fvk.to_ovk(Scope::External)),
|
||||
output_note,
|
||||
output_note.recipient(),
|
||||
memo.clone(),
|
||||
);
|
||||
let v_net = orchard::value::ValueSum::default();
|
||||
let cv_net = orchard::value::ValueCommitment::derive(v_net, rcv.clone());
|
||||
let _encrypted_note = orchard::note::TransmittedNoteCiphertext {
|
||||
epk_bytes: encryptor.epk().to_bytes().0,
|
||||
enc_ciphertext: encryptor.encrypt_note_plaintext(),
|
||||
out_ciphertext: encryptor.encrypt_outgoing_plaintext(&cv_net, &cmx, &mut OsRng),
|
||||
};
|
||||
|
||||
// compact outputs ZTxIdOrcActCHash
|
||||
// nf
|
||||
// cmx
|
||||
// epk_bytes
|
||||
// enc_ciphertext[..52]
|
||||
|
||||
// memo ZTxIdOrcActMHash
|
||||
// enc_ciphertext[52..564]
|
||||
|
||||
// non compact ZTxIdOrcActNHash
|
||||
// cv_net
|
||||
// rk
|
||||
// enc_ciphertext[564..]
|
||||
// out_ciphertext
|
||||
}
|
||||
|
||||
let mut vins = vec![];
|
||||
for tin in vin.iter() {
|
||||
let mut txin_hasher = Params::new()
|
||||
.hash_length(32)
|
||||
.personal(b"Zcash___TxInHash")
|
||||
.to_state();
|
||||
|
||||
txin_hasher.update(tin.utxo.hash());
|
||||
txin_hasher.update(&tin.utxo.n().to_le_bytes());
|
||||
txin_hasher.update(&tin.coin.value.to_i64_le_bytes());
|
||||
txin_hasher.update(&[0x19]); // add the script length
|
||||
txin_hasher.update(&tin.coin.script_pubkey.0);
|
||||
txin_hasher.update(&0xFFFFFFFFu32.to_le_bytes());
|
||||
let txin_hash = txin_hasher.finalize();
|
||||
log::info!("TXIN {}", hex::encode(txin_hash));
|
||||
|
||||
let signature = ledger_sign_transparent(txin_hash.as_bytes()).await?;
|
||||
let signature = secp256k1::ecdsa::Signature::from_der(&signature)?;
|
||||
let mut signature = signature.serialize_der().to_vec();
|
||||
signature.extend(&[0x01]); // add SIG_HASH_ALL
|
||||
|
||||
// witness is PUSH(signature) PUSH(pk)
|
||||
let script_sig = Script::default() << &*signature << &*pubkey;
|
||||
|
||||
let txin = TxIn::<transparent::Authorized> {
|
||||
prevout: tin.utxo.clone(),
|
||||
script_sig,
|
||||
sequence: 0xFFFFFFFFu32,
|
||||
};
|
||||
vins.push(txin);
|
||||
}
|
||||
transparent_builder.sign().await?;
|
||||
|
||||
let mut signatures = vec![];
|
||||
for _sp in shielded_spends.iter() {
|
||||
|
@ -581,11 +374,7 @@ pub async fn build_broadcast_tx(
|
|||
signatures.push(signature);
|
||||
}
|
||||
|
||||
let transparent_bundle = transparent::Bundle::<transparent::Authorized> {
|
||||
vin: vins,
|
||||
vout,
|
||||
authorization: transparent::Authorized,
|
||||
};
|
||||
let transparent_bundle = transparent_builder.build();
|
||||
|
||||
let shielded_spends: Vec<_> = shielded_spends
|
||||
.into_iter()
|
||||
|
@ -621,11 +410,7 @@ pub async fn build_broadcast_tx(
|
|||
consensus_branch_id: BranchId::Nu5,
|
||||
lock_time: 0,
|
||||
expiry_height: BlockHeight::from_u32(tx_plan.expiry_height),
|
||||
transparent_bundle: if has_transparent {
|
||||
Some(transparent_bundle)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
transparent_bundle,
|
||||
sprout_bundle: None,
|
||||
sapling_bundle: if has_sapling {
|
||||
Some(sapling_bundle)
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
use std::{fs::File, io::Read};
|
||||
|
||||
use blake2b_simd::Params;
|
||||
use byteorder::{LE, WriteBytesExt};
|
||||
use group::{Group, GroupEncoding};
|
||||
use orchard::{
|
||||
builder::SpendInfo,
|
||||
bundle::{Authorized, Flags},
|
||||
builder::{SpendInfo, InProgress, Unproven, Unauthorized as OrchardUnauthorized, SigningMetadata, SigningParts},
|
||||
bundle::{Authorized, Flags, Authorization},
|
||||
circuit::{Circuit, Instance, ProvingKey},
|
||||
keys::{Diversifier, FullViewingKey, Scope, SpendValidatingKey},
|
||||
keys::{Diversifier, FullViewingKey, Scope, SpendValidatingKey, SpendingKey, SpendAuthorizingKey},
|
||||
note::{ExtractedNoteCommitment, Nullifier, RandomSeed, TransmittedNoteCiphertext},
|
||||
note_encryption::OrchardNoteEncryption,
|
||||
primitives::redpallas::{Signature, SpendAuth},
|
||||
|
@ -18,27 +20,50 @@ use rand_chacha::ChaCha20Rng;
|
|||
use ripemd::Digest;
|
||||
|
||||
use anyhow::Result;
|
||||
use warp_api_ffi::{decode_orchard_merkle_path, TransactionPlan};
|
||||
|
||||
use zcash_primitives::transaction::components::Amount;
|
||||
use tonic::Request;
|
||||
use crate::{decode_orchard_merkle_path, TransactionPlan, RawTransaction, connect_lightwalletd, ledger::*};
|
||||
|
||||
use zcash_primitives::{transaction::{components::Amount, TransactionData, TxVersion, Transaction, sighash_v5, sighash::SignableInput,
|
||||
txid::TxIdDigester, Unauthorized, Authorized as TxAuthorized},
|
||||
consensus::{BlockHeight, BranchId}};
|
||||
use hex_literal::hex;
|
||||
use group::ff::Field;
|
||||
use nonempty::NonEmpty;
|
||||
|
||||
use warp_api_ffi::{Destination, Source};
|
||||
use crate::{Destination, Source};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NoAuth;
|
||||
|
||||
impl Authorization for NoAuth {
|
||||
type SpendAuth = ();
|
||||
}
|
||||
|
||||
pub async fn build_orchard() -> Result<()> {
|
||||
dotenv::dotenv()?;
|
||||
let mut prng = ChaCha20Rng::from_seed([0; 32]);
|
||||
let mut rseed_rng = ChaCha20Rng::from_seed([1; 32]);
|
||||
let mut alpha_rng = ChaCha20Rng::from_seed([2; 32]);
|
||||
let mut sig_rng = ChaCha20Rng::from_seed([3; 32]);
|
||||
|
||||
let spending_key = hex::decode(dotenv::var("SPENDING_KEY").unwrap()).unwrap();
|
||||
let spk = SpendingKey::from_bytes(spending_key.try_into().unwrap()).unwrap();
|
||||
let ask = SpendAuthorizingKey::from(&spk);
|
||||
println!("ASK {:?}", ask);
|
||||
|
||||
let _spending_key = hex::decode(dotenv::var("SPENDING_KEY").unwrap()).unwrap();
|
||||
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 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 orchard_fvk: [u8; 96] = hex::decode(tx_plan.orchard_fvk)
|
||||
.unwrap()
|
||||
.try_into()
|
||||
|
@ -92,12 +117,25 @@ pub async fn build_orchard() -> Result<()> {
|
|||
})
|
||||
.collect();
|
||||
|
||||
let zero_bsk = ValueCommitTrapdoor::zero().into_bsk();
|
||||
|
||||
let mut orchard_memos_hasher = Params::new()
|
||||
.hash_length(32)
|
||||
.personal(b"ZTxIdOrcActMHash")
|
||||
.to_state();
|
||||
let mut orchard_nc_hasher = Params::new()
|
||||
.hash_length(32)
|
||||
.personal(b"ZTxIdOrcActNHash")
|
||||
.to_state();
|
||||
|
||||
let num_actions = spends.len().max(outputs.len());
|
||||
let mut actions = vec![];
|
||||
let mut circuits = vec![];
|
||||
let mut instances = vec![];
|
||||
let mut net_value = ValueSum::default();
|
||||
let mut sum_rcv = ValueCommitTrapdoor::zero();
|
||||
|
||||
let mut padded_outputs = vec![];
|
||||
for i in 0..num_actions {
|
||||
// pad with dummy spends/outputs
|
||||
let spend = if i < spends.len() {
|
||||
|
@ -111,6 +149,7 @@ pub async fn build_orchard() -> Result<()> {
|
|||
} else {
|
||||
OrchardOutput::dummy(&mut prng)
|
||||
};
|
||||
padded_outputs.push(output.clone());
|
||||
|
||||
let rcv = ValueCommitTrapdoor::random(&mut prng);
|
||||
sum_rcv = sum_rcv + &rcv;
|
||||
|
@ -118,7 +157,7 @@ pub async fn build_orchard() -> Result<()> {
|
|||
let ak: SpendValidatingKey = orchard_fvk.clone().into();
|
||||
let rk = ak.randomize(&alpha);
|
||||
|
||||
let rho = spend.note.rho();
|
||||
let rho = spend.note.nullifier(&orchard_fvk);
|
||||
let mut rseed = [0u8; 32];
|
||||
rseed_rng.fill_bytes(&mut rseed);
|
||||
let rseed = RandomSeed::from_bytes(rseed, &rho).unwrap();
|
||||
|
@ -158,13 +197,28 @@ pub async fn build_orchard() -> Result<()> {
|
|||
out_ciphertext: out.clone(),
|
||||
};
|
||||
|
||||
let action: Action<Signature<SpendAuth>> = Action::from_parts(
|
||||
let rk_bytes: [u8; 32] = rk.clone().0.into();
|
||||
orchard_memos_hasher.update(&enc[52..564]);
|
||||
orchard_nc_hasher.update(&cv_net.to_bytes());
|
||||
orchard_nc_hasher.update(&rk_bytes);
|
||||
orchard_nc_hasher.update(&enc[564..]);
|
||||
orchard_nc_hasher.update(&out);
|
||||
|
||||
println!("d/pkd {}", hex::encode(&output.recipient.to_raw_address_bytes()));
|
||||
println!("rho {}", hex::encode(&rho.to_bytes()));
|
||||
println!("amount {}", hex::encode(&output.amount.inner().to_le_bytes()));
|
||||
println!("rseed {}", hex::encode(&rseed.as_bytes()));
|
||||
println!("cmx {}", hex::encode(&cmx.to_bytes()));
|
||||
|
||||
let action: Action<SigningMetadata> = Action::from_parts(
|
||||
rho.clone(),
|
||||
rk.clone(),
|
||||
cmx.clone(),
|
||||
encrypted_note,
|
||||
cv_net.clone(),
|
||||
[0; 64].into(),
|
||||
SigningMetadata {
|
||||
dummy_ask: None,
|
||||
parts: SigningParts { ak, alpha } },
|
||||
);
|
||||
actions.push(action);
|
||||
|
||||
|
@ -174,27 +228,130 @@ pub async fn build_orchard() -> Result<()> {
|
|||
let instance = Instance::from_parts(anchor, cv_net, rho.clone(), rk, cmx, true, true);
|
||||
instances.push(instance);
|
||||
}
|
||||
let actions = NonEmpty::from_slice(&actions).unwrap();
|
||||
|
||||
let pk = ProvingKey::build();
|
||||
let proof = Proof::create(&pk, &circuits, &instances, &mut prng).unwrap();
|
||||
let nv = i64::try_from(net_value).unwrap();
|
||||
let amount = Amount::from_i64(nv).unwrap();
|
||||
|
||||
let sig_hash = [0u8; 32];
|
||||
|
||||
let flags = Flags::from_parts(true, true);
|
||||
let bsk = sum_rcv.into_bsk();
|
||||
let bundle: Bundle<_, Amount> = Bundle::from_parts(
|
||||
actions,
|
||||
flags,
|
||||
amount,
|
||||
anchor,
|
||||
InProgress::<Unproven, OrchardUnauthorized> {
|
||||
proof: Unproven { circuits: vec![] },
|
||||
sigs: OrchardUnauthorized { bsk: bsk.clone() } });
|
||||
|
||||
let tx_data: TransactionData<Unauthorized> = 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: None,
|
||||
orchard_bundle: Some(bundle.clone()),
|
||||
};
|
||||
|
||||
let txid_parts = tx_data.digest(TxIdDigester);
|
||||
let sig_hash = sighash_v5::v5_signature_hash(&tx_data, &SignableInput::Shielded, &txid_parts);
|
||||
let sig_hash = sig_hash.as_bytes();
|
||||
let binding_signature = bsk.sign(&mut prng, &sig_hash);
|
||||
|
||||
let actions = NonEmpty::from_slice(&actions).unwrap();
|
||||
ledger_init().await.unwrap();
|
||||
ledger_init_tx(header_digest.as_bytes()).await.unwrap();
|
||||
ledger_set_orchard_merkle_proof(
|
||||
&anchor.to_bytes(),
|
||||
orchard_memos_hasher.finalize().as_bytes(),
|
||||
orchard_nc_hasher.finalize().as_bytes(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let _bundle: Bundle<_, Amount> = Bundle::from_parts(
|
||||
actions,
|
||||
// no t-in
|
||||
ledger_set_stage(2).await.unwrap();
|
||||
// no t-out
|
||||
ledger_set_stage(3).await.unwrap();
|
||||
// no s-out
|
||||
ledger_set_stage(4).await.unwrap();
|
||||
|
||||
for (a, o) in bundle.actions().iter().zip(padded_outputs.iter()) {
|
||||
let nf = a.nullifier().to_bytes();
|
||||
let epk = a.encrypted_note().epk_bytes;
|
||||
let address =
|
||||
ledger_add_o_action(
|
||||
&nf,
|
||||
o.amount.inner(),
|
||||
&epk,
|
||||
&o.recipient.to_raw_address_bytes(),
|
||||
&a.encrypted_note().enc_ciphertext[0..52],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
ledger_set_stage(5).await.unwrap();
|
||||
ledger_set_net_orchard(-tx_plan.net_chg[1]).await.unwrap();
|
||||
ledger_confirm_fee().await.unwrap();
|
||||
|
||||
let mut auth_actions = vec![];
|
||||
for a in bundle.actions() {
|
||||
println!("ask {:?}", ask);
|
||||
println!("alpha {:?}", a.authorization().parts.alpha);
|
||||
let rsk = ask.randomize(&a.authorization().parts.alpha);
|
||||
println!("rsk {:?}", rsk);
|
||||
// let signature: Signature<SpendAuth> = [0; 64].into();
|
||||
// let signature = rsk.sign(&mut sig_rng, sig_hash);
|
||||
let sig_bytes: [u8; 64] = ledger_sign_orchard().await.unwrap().try_into().unwrap();
|
||||
let signature: Signature<SpendAuth> = sig_bytes.into();
|
||||
let auth_action = Action::from_parts(
|
||||
a.nullifier().clone(), a.rk().clone(), a.cmx().clone(),
|
||||
a.encrypted_note().clone(), a.cv_net().clone(),
|
||||
signature);
|
||||
auth_actions.push(auth_action);
|
||||
}
|
||||
let auth_actions = NonEmpty::from_slice(&auth_actions).unwrap();
|
||||
|
||||
let bundle: Bundle<_, Amount> = Bundle::from_parts(
|
||||
auth_actions,
|
||||
Flags::from_parts(true, true),
|
||||
amount,
|
||||
anchor.clone(),
|
||||
Authorized::from_parts(proof, binding_signature),
|
||||
);
|
||||
|
||||
let tx_data: TransactionData<TxAuthorized> = 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: None,
|
||||
orchard_bundle: Some(bundle),
|
||||
};
|
||||
let tx = Transaction::from_data(tx_data).unwrap();
|
||||
|
||||
let mut tx_bytes = vec![];
|
||||
tx.write(&mut tx_bytes).unwrap();
|
||||
|
||||
let orchard_memos_hash = orchard_memos_hasher.finalize();
|
||||
let orchard_nc_hash = orchard_nc_hasher.finalize();
|
||||
|
||||
let mut client = connect_lightwalletd("https://lwdv3.zecwallet.co").await?;
|
||||
let response = client
|
||||
.send_transaction(Request::new(RawTransaction {
|
||||
data: tx_bytes,
|
||||
height: 0,
|
||||
}))
|
||||
.await?
|
||||
.into_inner();
|
||||
|
||||
println!("LWD send transaction {:?}", response);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
use blake2b_simd::Params;
|
||||
use blake2b_simd::State;
|
||||
use byteorder::WriteBytesExt;
|
||||
use byteorder::LE;
|
||||
use ff::{Field, PrimeField};
|
||||
use group::GroupEncoding;
|
||||
use hex_literal::hex;
|
||||
use jubjub::{Fq, Fr};
|
||||
|
||||
use orchard::keys::Scope;
|
||||
|
||||
use crate::ledger::transport::*;
|
||||
use crate::taddr::derive_from_pubkey;
|
||||
use crate::{CompactTxStreamerClient, Destination, RawTransaction, Source, TransactionPlan};
|
||||
use anyhow::{anyhow, Result};
|
||||
use rand::{rngs::OsRng, RngCore, SeedableRng};
|
||||
use rand_chacha::ChaChaRng;
|
||||
use ripemd::{Digest, Ripemd160};
|
||||
use secp256k1::PublicKey;
|
||||
use sha2::Sha256;
|
||||
use tonic::{transport::Channel, Request};
|
||||
use zcash_client_backend::encoding::{
|
||||
decode_transparent_address, encode_extended_full_viewing_key, encode_transparent_address,
|
||||
};
|
||||
use zcash_primitives::consensus::Network;
|
||||
use zcash_primitives::consensus::Parameters;
|
||||
use zcash_primitives::legacy::{Script, TransparentAddress};
|
||||
use zcash_primitives::transaction::components::{transparent, OutPoint, TxIn, TxOut};
|
||||
use zcash_primitives::zip32::ExtendedFullViewingKey;
|
||||
|
||||
use zcash_primitives::{
|
||||
consensus::{BlockHeight, BranchId, MainNetwork},
|
||||
constants::PROOF_GENERATION_KEY_GENERATOR,
|
||||
merkle_tree::IncrementalWitness,
|
||||
sapling::{
|
||||
note_encryption::sapling_note_encryption,
|
||||
prover::TxProver,
|
||||
redjubjub::Signature,
|
||||
value::{NoteValue, ValueCommitment, ValueSum},
|
||||
Diversifier, Node, Note, Nullifier, PaymentAddress, Rseed,
|
||||
},
|
||||
transaction::{
|
||||
components::{
|
||||
sapling::{Authorized as SapAuthorized, Bundle},
|
||||
Amount, OutputDescription, SpendDescription, GROTH_PROOF_SIZE,
|
||||
},
|
||||
Authorized, TransactionData, TxVersion,
|
||||
},
|
||||
};
|
||||
use zcash_proofs::{prover::LocalTxProver, sapling::SaplingProvingContext};
|
||||
|
||||
use super::create_hasher;
|
||||
|
||||
pub struct TransparentInputUnAuthorized {
|
||||
utxo: OutPoint,
|
||||
coin: TxOut,
|
||||
}
|
||||
|
||||
pub struct TransparentBuilder {
|
||||
pub taddr: String,
|
||||
pubkey: Vec<u8>,
|
||||
pkh: [u8; 20],
|
||||
tin_pubscript: Script,
|
||||
prevouts_hasher: State,
|
||||
trscripts_hasher: State,
|
||||
sequences_hasher: State,
|
||||
vin: Vec<TransparentInputUnAuthorized>,
|
||||
vins: Vec<TxIn<transparent::Authorized>>,
|
||||
vout: Vec<TxOut>,
|
||||
}
|
||||
|
||||
impl TransparentBuilder {
|
||||
pub fn new(network: &Network, pubkey: &[u8]) -> Self {
|
||||
let taddr_str = derive_from_pubkey(network, &pubkey).unwrap();
|
||||
let taddr = decode_transparent_address(
|
||||
&network.b58_pubkey_address_prefix(),
|
||||
&network.b58_script_address_prefix(),
|
||||
&taddr_str,
|
||||
).unwrap().unwrap();
|
||||
let pkh = match taddr {
|
||||
TransparentAddress::PublicKey(pkh) => pkh,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let tin_pubscript = taddr.script();
|
||||
TransparentBuilder {
|
||||
taddr: taddr_str,
|
||||
pubkey: pubkey.to_vec(),
|
||||
pkh: pkh.clone(),
|
||||
tin_pubscript,
|
||||
prevouts_hasher: create_hasher(b"ZTxIdPrevoutHash"),
|
||||
trscripts_hasher: create_hasher(b"ZTxTrScriptsHash"),
|
||||
sequences_hasher: create_hasher(b"ZTxIdSequencHash"),
|
||||
vin: vec![],
|
||||
vins: vec![],
|
||||
vout: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn add_input(&mut self, txid: [u8; 32], index: u32, amount: u64) -> Result<()> {
|
||||
self.prevouts_hasher.update(&txid);
|
||||
self.prevouts_hasher.write_u32::<LE>(index)?;
|
||||
self.trscripts_hasher.update(&hex!("1976a914"));
|
||||
self.trscripts_hasher.update(&self.pkh);
|
||||
self.trscripts_hasher.update(&hex!("88ac"));
|
||||
self.sequences_hasher.update(&hex!("FFFFFFFF"));
|
||||
|
||||
self.vin.push(TransparentInputUnAuthorized {
|
||||
utxo: OutPoint::new(txid, index),
|
||||
coin: TxOut {
|
||||
value: Amount::from_u64(amount).unwrap(),
|
||||
script_pubkey: self.tin_pubscript.clone(), // will always use the h/w address
|
||||
},
|
||||
});
|
||||
|
||||
ledger_add_t_input(amount).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn add_output(&mut self, raw_address: [u8; 21], amount: u64) -> Result<()> {
|
||||
if raw_address[0] != 0 {
|
||||
anyhow::bail!("Only t1 addresses are supported");
|
||||
}
|
||||
ledger_add_t_output(amount, &raw_address).await?;
|
||||
let ta = TransparentAddress::PublicKey(raw_address[1..21].try_into().unwrap());
|
||||
self.vout.push(TxOut {
|
||||
value: Amount::from_u64(amount).unwrap(),
|
||||
script_pubkey: ta.script(),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn finalize_hash(&mut self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn set_merkle_proof(&self) -> Result<()> {
|
||||
let prevouts_digest = self.prevouts_hasher.finalize();
|
||||
log::info!("PREVOUTS {}", hex::encode(prevouts_digest));
|
||||
let pubscripts_digest = self.trscripts_hasher.finalize();
|
||||
log::info!("PUBSCRIPTS {}", hex::encode(pubscripts_digest));
|
||||
let sequences_digest = self.sequences_hasher.finalize();
|
||||
log::info!("SEQUENCES {}", hex::encode(sequences_digest));
|
||||
|
||||
ledger_set_transparent_merkle_proof(
|
||||
prevouts_digest.as_bytes(),
|
||||
pubscripts_digest.as_bytes(),
|
||||
sequences_digest.as_bytes(),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn sign(&mut self) -> Result<()> {
|
||||
let mut vins: Vec<TxIn<transparent::Authorized>> = vec![];
|
||||
for tin in self.vin.iter() {
|
||||
let mut txin_hasher = create_hasher(b"Zcash___TxInHash");
|
||||
|
||||
txin_hasher.update(tin.utxo.hash());
|
||||
txin_hasher.update(&tin.utxo.n().to_le_bytes());
|
||||
txin_hasher.update(&tin.coin.value.to_i64_le_bytes());
|
||||
txin_hasher.update(&[0x19]); // add the script length
|
||||
txin_hasher.update(&tin.coin.script_pubkey.0);
|
||||
txin_hasher.update(&0xFFFFFFFFu32.to_le_bytes());
|
||||
let txin_hash = txin_hasher.finalize();
|
||||
log::info!("TXIN {}", hex::encode(txin_hash));
|
||||
|
||||
let signature = ledger_sign_transparent(txin_hash.as_bytes()).await?;
|
||||
let signature = secp256k1::ecdsa::Signature::from_der(&signature)?;
|
||||
let mut signature = signature.serialize_der().to_vec();
|
||||
signature.extend(&[0x01]); // add SIG_HASH_ALL
|
||||
|
||||
// witness is PUSH(signature) PUSH(pk)
|
||||
let script_sig = Script::default() << &*signature << &*self.pubkey;
|
||||
|
||||
let txin = TxIn::<transparent::Authorized> {
|
||||
prevout: tin.utxo.clone(),
|
||||
script_sig,
|
||||
sequence: 0xFFFFFFFFu32,
|
||||
};
|
||||
self.vins.push(txin);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn build(self) -> Option<transparent::Bundle<transparent::Authorized>> {
|
||||
if !self.vin.is_empty() || !self.vout.is_empty() {
|
||||
let transparent_bundle = transparent::Bundle::<transparent::Authorized> {
|
||||
vin: self.vins,
|
||||
vout: self.vout,
|
||||
authorization: transparent::Authorized,
|
||||
};
|
||||
Some(transparent_bundle)
|
||||
}
|
||||
else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
|
@ -260,6 +260,13 @@ pub async fn ledger_sign_sapling() -> Result<Vec<u8>> {
|
|||
Ok(signature)
|
||||
}
|
||||
|
||||
pub async fn ledger_sign_orchard() -> Result<Vec<u8>> {
|
||||
let mut bb: Vec<u8> = vec![];
|
||||
bb.write_all(&hex!("E024000000"))?;
|
||||
let signature = apdu(&bb).await?;
|
||||
Ok(signature)
|
||||
}
|
||||
|
||||
pub async fn ledger_end_tx() -> Result<()> {
|
||||
let mut bb: Vec<u8> = vec![];
|
||||
bb.write_all(&hex!("E030000000"))?;
|
||||
|
|
|
@ -43,11 +43,9 @@ use nonempty::NonEmpty;
|
|||
|
||||
use hex_literal::hex;
|
||||
|
||||
mod orchard_bundle;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
orchard_bundle::build_orchard().await.unwrap();
|
||||
// orchard_bundle::build_orchard().await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
|
|
Loading…
Reference in New Issue