zcash-sync/src/note_selection/builder.rs

322 lines
12 KiB
Rust

use super::types::*;
use crate::coinconfig::get_prover;
use crate::orchard::{get_proving_key, OrchardHasher, ORCHARD_ROOTS};
use crate::sapling::{SaplingHasher, SAPLING_ROOTS};
use crate::sync::tree::TreeCheckpoint;
use crate::sync::Witness;
use crate::{AccountData, CoinConfig};
use anyhow::anyhow;
use jubjub::Fr;
use orchard::builder::Builder as OrchardBuilder;
use orchard::bundle::Flags;
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 ripemd::{Digest, Ripemd160};
use secp256k1::{All, PublicKey, Secp256k1, SecretKey};
use sha2::Sha256;
use std::str::FromStr;
use zcash_client_backend::encoding::decode_extended_spending_key;
use zcash_primitives::consensus::{BlockHeight, BranchId, Network, Parameters};
use zcash_primitives::legacy::TransparentAddress;
use zcash_primitives::merkle_tree::IncrementalWitness;
use zcash_primitives::sapling::prover::TxProver;
use zcash_primitives::sapling::{Diversifier, Node, PaymentAddress, Rseed};
use zcash_primitives::transaction::builder::Builder;
use zcash_primitives::transaction::components::{Amount, OutPoint, TxOut};
use zcash_primitives::transaction::sighash::{signature_hash, SignableInput};
use zcash_primitives::transaction::txid::TxIdDigester;
use zcash_primitives::transaction::{Transaction, TransactionData, TxVersion};
use zcash_primitives::zip32::ExtendedSpendingKey;
pub struct SecretKeys {
pub transparent: Option<SecretKey>,
pub sapling: Option<ExtendedSpendingKey>,
pub orchard: Option<SpendingKey>,
}
pub struct TxBuilderContext {
pub height: u32,
pub sapling_anchor: [u8; 32],
pub orchard_anchor: Option<[u8; 32]>,
}
impl TxBuilderContext {
pub fn from_height(coin: u8, height: u32) -> anyhow::Result<Self> {
let c = CoinConfig::get(coin);
let db = c.db.as_ref().unwrap();
let db = db.lock().unwrap();
let TreeCheckpoint { tree, .. } = db.get_tree_by_name(height, "sapling")?;
let hasher = SaplingHasher {};
let sapling_anchor = tree.root(32, &SAPLING_ROOTS, &hasher);
let orchard_anchor = if c.chain.has_unified() {
let TreeCheckpoint { tree, .. } = db.get_tree_by_name(height, "orchard")?;
let hasher = OrchardHasher::new();
Some(tree.root(32, &ORCHARD_ROOTS, &hasher))
} else {
None
};
let context = TxBuilderContext {
height,
sapling_anchor,
orchard_anchor,
};
Ok(context)
}
}
pub fn build_tx(
network: &Network,
skeys: &SecretKeys,
plan: &TransactionPlan,
mut rng: impl RngCore + CryptoRng + Clone,
) -> anyhow::Result<Vec<u8>> {
let secp = Secp256k1::<All>::new();
let transparent_address = skeys.transparent.map(|tkey| {
let pub_key = PublicKey::from_secret_key(&secp, &tkey);
let pub_key = pub_key.serialize();
let pub_key = Ripemd160::digest(&Sha256::digest(&pub_key));
TransparentAddress::PublicKey(pub_key.into())
});
let sapling_fvk = skeys
.sapling
.as_ref()
.map(|sk| sk.to_extended_full_viewing_key());
let sapling_ovk = sapling_fvk.as_ref().map(|efvk| efvk.fvk.ovk.clone());
let okeys = skeys.orchard.map(|sk| {
let orchard_fvk = FullViewingKey::from(&sk);
let orchard_ovk = orchard_fvk.clone().to_ovk(Scope::External);
(orchard_fvk, orchard_ovk)
});
let (orchard_fvk, orchard_ovk) = match okeys {
Some((a, b)) => (Some(a), Some(b)),
_ => (None, None),
};
let mut has_orchard = false;
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();
let mut orchard_builder = OrchardBuilder::new(Flags::from_parts(true, true), anchor);
for spend in plan.spends.iter() {
match &spend.source {
Source::Transparent { txid, index } => {
let utxo = OutPoint::new(*txid, *index);
let coin = TxOut {
value: Amount::from_u64(spend.amount).unwrap(),
script_pubkey: transparent_address
.ok_or(anyhow!("No transparent key"))
.map(|ta| ta.script())?,
};
builder
.add_transparent_input(skeys.transparent.unwrap(), utxo, coin)
.map_err(|e| anyhow!(e.to_string()))?;
}
Source::Sapling {
diversifier,
rseed,
witness,
..
} => {
let diversifier = Diversifier(*diversifier);
let sapling_address = sapling_fvk
.as_ref()
.ok_or(anyhow!("No sapling key"))?
.fvk
.vk
.to_payment_address(diversifier)
.unwrap();
let rseed = Rseed::BeforeZip212(Fr::from_bytes(rseed).unwrap());
let note = sapling_address.create_note(spend.amount, rseed);
let witness = IncrementalWitness::<Node>::read(witness.as_slice())?;
let merkle_path = witness.path().unwrap();
builder
.add_sapling_spend(
skeys.sapling.clone().ok_or(anyhow!("No Sapling Key"))?,
diversifier,
note,
merkle_path,
)
.map_err(|e| anyhow!(e.to_string()))?;
}
Source::Orchard {
id_note,
diversifier,
rho,
rseed,
witness,
} => {
has_orchard = true;
let diversifier = orchard::keys::Diversifier::from_bytes(*diversifier);
let sender_address = orchard_fvk
.as_ref()
.ok_or(anyhow!("No Orchard key"))
.map(|fvk| fvk.address(diversifier, Scope::External))?;
let value = NoteValue::from_raw(spend.amount);
let rho = Nullifier::from_bytes(&rho).unwrap();
let rseed = orchard::note::RandomSeed::from_bytes(*rseed, &rho).unwrap();
let note = orchard::Note::from_parts(sender_address, value, rho, rseed).unwrap();
let witness = Witness::from_bytes(*id_note, &witness)?;
let auth_path: Vec<_> = witness
.auth_path(32, &ORCHARD_ROOTS, &OrchardHasher::new())
.iter()
.map(|n| orchard::tree::MerkleHashOrchard::from_bytes(n).unwrap())
.collect();
let merkle_path = orchard::tree::MerklePath::from_parts(
witness.position as u32,
auth_path.try_into().unwrap(),
);
orchard_builder
.add_spend(orchard_fvk.clone().unwrap(), note, merkle_path)
.map_err(|e| anyhow!(e.to_string()))?;
}
}
}
for output in plan.outputs.iter() {
let value = Amount::from_u64(output.amount).unwrap();
match &output.destination {
Destination::Transparent(_addr) => {
let transparent_address = output.destination.transparent();
builder
.add_transparent_output(&transparent_address, value)
.map_err(|e| anyhow!(e.to_string()))?;
}
Destination::Sapling(addr) => {
let sapling_address = PaymentAddress::from_bytes(addr).unwrap();
builder
.add_sapling_output(sapling_ovk, sapling_address, value, output.memo.clone())
.map_err(|e| anyhow!(e.to_string()))?;
}
Destination::Orchard(addr) => {
has_orchard = true;
let orchard_address = Address::from_raw_address_bytes(addr).unwrap();
orchard_builder
.add_recipient(
orchard_ovk.clone(),
orchard_address,
NoteValue::from_raw(output.amount),
Some(*output.memo.as_array()),
)
.map_err(|_| anyhow!("Orchard::add_recipient"))?;
}
}
}
let transparent_bundle = builder.transparent_builder.build();
let mut ctx = get_prover().new_sapling_proving_context();
let sapling_bundle = builder
.sapling_builder
.build(
get_prover(),
&mut ctx,
&mut rng,
BlockHeight::from_u32(plan.anchor_height),
None,
)
.unwrap();
let mut orchard_bundle: Option<Bundle<_, Amount>> = None;
if has_orchard {
orchard_bundle = Some(orchard_builder.build(&mut rng).unwrap());
}
let consensus_branch_id =
BranchId::for_height(network, BlockHeight::from_u32(plan.anchor_height));
let version = TxVersion::suggested_for_branch(consensus_branch_id);
let unauthed_tx: TransactionData<zcash_primitives::transaction::Unauthorized> =
TransactionData::from_parts(
version,
consensus_branch_id,
0,
BlockHeight::from_u32(plan.expiry_height),
transparent_bundle,
None,
sapling_bundle,
orchard_bundle,
);
let txid_parts = unauthed_tx.digest(TxIdDigester);
let sig_hash = signature_hash(&unauthed_tx, &SignableInput::Shielded, &txid_parts);
let sig_hash: [u8; 32] = sig_hash.as_ref().clone();
let transparent_bundle = unauthed_tx
.transparent_bundle()
.map(|tb| tb.clone().apply_signatures(&unauthed_tx, &txid_parts));
let sapling_bundle = unauthed_tx.sapling_bundle().map(|sb| {
sb.clone()
.apply_signatures(get_prover(), &mut ctx, &mut rng, &sig_hash)
.unwrap()
.0
});
let mut orchard_signing_keys = vec![];
if let Some(sk) = skeys.orchard {
orchard_signing_keys.push(SpendAuthorizingKey::from(&sk));
}
let orchard_bundle = unauthed_tx.orchard_bundle().map(|ob| {
let proven = ob
.clone()
.create_proof(get_proving_key(), &mut rng)
.unwrap();
proven
.apply_signatures(&mut rng, sig_hash, &orchard_signing_keys)
.unwrap()
});
let tx_data: TransactionData<zcash_primitives::transaction::Authorized> =
TransactionData::from_parts(
version,
consensus_branch_id,
0,
BlockHeight::from_u32(plan.expiry_height),
transparent_bundle,
None,
sapling_bundle,
orchard_bundle,
);
let tx = Transaction::from_data(tx_data).unwrap();
let mut tx_bytes = vec![];
tx.write(&mut tx_bytes).unwrap();
Ok(tx_bytes)
}
pub fn get_secret_keys(coin: u8, account: u32) -> anyhow::Result<SecretKeys> {
let c = CoinConfig::get(coin);
let db = c.db()?;
let transparent_sk = db
.get_tsk(account)?
.map(|tsk| SecretKey::from_str(&tsk).unwrap());
let AccountData { sk, .. } = db.get_account_info(account)?;
let sapling_sk = sk.ok_or(anyhow!("No secret key"))?;
let sapling_sk = decode_extended_spending_key(
c.chain.network().hrp_sapling_extended_spending_key(),
&sapling_sk,
)
.unwrap();
let orchard_sk = db
.get_orchard(account)?
.and_then(|ob| ob.sk.map(|sk| SpendingKey::from_bytes(sk).unwrap()));
let sk = SecretKeys {
transparent: transparent_sk,
sapling: Some(sapling_sk),
orchard: orchard_sk,
};
Ok(sk)
}