mirror of
https://github.com/Qortal/pirate-librustzcash.git
synced 2025-02-07 06:44:11 +00:00
Parse compact blocks to find wallet transactions
This commit is contained in:
parent
2dd2fc620e
commit
5ec94b5db5
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -537,10 +537,13 @@ name = "zcash_client_backend"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bech32 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ff 0.4.0",
|
||||
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pairing 0.14.2",
|
||||
"protobuf 2.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"protobuf-codegen-pure 2.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_os 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_xorshift 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"zcash_primitives 0.0.0",
|
||||
]
|
||||
|
@ -8,6 +8,8 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bech32 = "0.7"
|
||||
ff = { path = "../ff" }
|
||||
hex = "0.3"
|
||||
pairing = { path = "../pairing" }
|
||||
protobuf = "2"
|
||||
zcash_primitives = { path = "../zcash_primitives" }
|
||||
@ -17,4 +19,5 @@ protobuf-codegen-pure = "2"
|
||||
|
||||
[dev-dependencies]
|
||||
rand_core = "0.5"
|
||||
rand_os = "0.2"
|
||||
rand_xorshift = "0.2"
|
||||
|
@ -7,3 +7,5 @@ pub mod constants;
|
||||
pub mod encoding;
|
||||
pub mod keys;
|
||||
pub mod proto;
|
||||
pub mod wallet;
|
||||
pub mod welding_rig;
|
||||
|
32
zcash_client_backend/src/wallet.rs
Normal file
32
zcash_client_backend/src/wallet.rs
Normal file
@ -0,0 +1,32 @@
|
||||
//! Structs representing transaction data scanned from the block chain by a wallet or
|
||||
//! light client.
|
||||
|
||||
use pairing::bls12_381::{Bls12, Fr};
|
||||
use zcash_primitives::{
|
||||
jubjub::{edwards, PrimeOrder},
|
||||
transaction::TxId,
|
||||
};
|
||||
|
||||
pub struct EncCiphertextFrag(pub [u8; 52]);
|
||||
|
||||
/// A subset of a [`Transaction`] relevant to wallets and light clients.
|
||||
///
|
||||
/// [`Transaction`]: zcash_primitives::transaction::Transaction
|
||||
pub struct WalletTx {
|
||||
pub txid: TxId,
|
||||
pub num_spends: usize,
|
||||
pub num_outputs: usize,
|
||||
pub shielded_outputs: Vec<WalletShieldedOutput>,
|
||||
}
|
||||
|
||||
/// A subset of an [`OutputDescription`] relevant to wallets and light clients.
|
||||
///
|
||||
/// [`OutputDescription`]: zcash_primitives::transaction::components::OutputDescription
|
||||
pub struct WalletShieldedOutput {
|
||||
pub index: usize,
|
||||
pub cmu: Fr,
|
||||
pub epk: edwards::Point<Bls12, PrimeOrder>,
|
||||
pub enc_ct: EncCiphertextFrag,
|
||||
pub account: usize,
|
||||
pub value: u64,
|
||||
}
|
194
zcash_client_backend/src/welding_rig.rs
Normal file
194
zcash_client_backend/src/welding_rig.rs
Normal file
@ -0,0 +1,194 @@
|
||||
//! Tools for scanning a compact representation of the Zcash block chain.
|
||||
|
||||
use ff::{PrimeField, PrimeFieldRepr};
|
||||
use pairing::bls12_381::{Bls12, Fr, FrRepr};
|
||||
use zcash_primitives::{
|
||||
jubjub::{edwards, fs::Fs},
|
||||
note_encryption::try_sapling_compact_note_decryption,
|
||||
transaction::TxId,
|
||||
zip32::ExtendedFullViewingKey,
|
||||
JUBJUB,
|
||||
};
|
||||
|
||||
use crate::proto::compact_formats::{CompactBlock, CompactOutput, CompactTx};
|
||||
use crate::wallet::{EncCiphertextFrag, WalletShieldedOutput, WalletTx};
|
||||
|
||||
/// Scans a [`CompactOutput`] with a set of [`ExtendedFullViewingKey`]s.
|
||||
///
|
||||
/// Returns a [`WalletShieldedOutput`] if this output belongs to any of the given
|
||||
/// [`ExtendedFullViewingKey`]s.
|
||||
fn scan_output(
|
||||
(index, output): (usize, CompactOutput),
|
||||
ivks: &[Fs],
|
||||
) -> Option<WalletShieldedOutput> {
|
||||
let mut repr = FrRepr::default();
|
||||
if repr.read_le(&output.cmu[..]).is_err() {
|
||||
return None;
|
||||
}
|
||||
let cmu = match Fr::from_repr(repr) {
|
||||
Ok(cmu) => cmu,
|
||||
Err(_) => return None,
|
||||
};
|
||||
|
||||
let epk = match edwards::Point::<Bls12, _>::read(&output.epk[..], &JUBJUB) {
|
||||
Ok(p) => match p.as_prime_order(&JUBJUB) {
|
||||
Some(epk) => epk,
|
||||
None => return None,
|
||||
},
|
||||
Err(_) => return None,
|
||||
};
|
||||
|
||||
let ct = output.ciphertext;
|
||||
|
||||
for (account, ivk) in ivks.iter().enumerate() {
|
||||
let value = match try_sapling_compact_note_decryption(ivk, &epk, &cmu, &ct) {
|
||||
Some((note, _)) => note.value,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
// It's ours, so let's copy the ciphertext fragment and return
|
||||
let mut enc_ct = EncCiphertextFrag([0u8; 52]);
|
||||
enc_ct.0.copy_from_slice(&ct);
|
||||
|
||||
return Some(WalletShieldedOutput {
|
||||
index,
|
||||
cmu,
|
||||
epk,
|
||||
enc_ct,
|
||||
account,
|
||||
value,
|
||||
});
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Scans a [`CompactTx`] with a set of [`ExtendedFullViewingKey`]s.
|
||||
///
|
||||
/// Returns a [`WalletTx`] if this transaction belongs to any of the given
|
||||
/// [`ExtendedFullViewingKey`]s.
|
||||
fn scan_tx(tx: CompactTx, extfvks: &[ExtendedFullViewingKey]) -> Option<WalletTx> {
|
||||
let num_spends = tx.spends.len();
|
||||
let num_outputs = tx.outputs.len();
|
||||
|
||||
// Check for incoming notes
|
||||
let shielded_outputs: Vec<WalletShieldedOutput> = {
|
||||
let ivks: Vec<_> = extfvks.iter().map(|extfvk| extfvk.fvk.vk.ivk()).collect();
|
||||
tx.outputs
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter_map(|(index, output)| scan_output((index, output), &ivks))
|
||||
.collect()
|
||||
};
|
||||
|
||||
if shielded_outputs.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let mut txid = TxId([0u8; 32]);
|
||||
txid.0.copy_from_slice(&tx.hash);
|
||||
Some(WalletTx {
|
||||
txid,
|
||||
num_spends,
|
||||
num_outputs,
|
||||
shielded_outputs,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Scans a [`CompactBlock`] for transactions belonging to a set of
|
||||
/// [`ExtendedFullViewingKey`]s.
|
||||
///
|
||||
/// Returns a vector of [`WalletTx`]s belonging to any of the given
|
||||
/// [`ExtendedFullViewingKey`]s.
|
||||
pub fn scan_block(block: CompactBlock, extfvks: &[ExtendedFullViewingKey]) -> Vec<WalletTx> {
|
||||
block
|
||||
.vtx
|
||||
.into_iter()
|
||||
.filter_map(|tx| scan_tx(tx, extfvks))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ff::{Field, PrimeField, PrimeFieldRepr};
|
||||
use pairing::bls12_381::Bls12;
|
||||
use rand_core::RngCore;
|
||||
use rand_os::OsRng;
|
||||
use zcash_primitives::{
|
||||
jubjub::fs::Fs,
|
||||
note_encryption::{Memo, SaplingNoteEncryption},
|
||||
primitives::Note,
|
||||
transaction::components::Amount,
|
||||
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
|
||||
JUBJUB,
|
||||
};
|
||||
|
||||
use super::scan_block;
|
||||
use crate::proto::compact_formats::{CompactBlock, CompactOutput, CompactTx};
|
||||
|
||||
/// Create a fake CompactBlock at the given height, containing a single output paying
|
||||
/// the given address. Returns the CompactBlock and the nullifier for the new note.
|
||||
fn fake_compact_block(
|
||||
height: i32,
|
||||
extfvk: ExtendedFullViewingKey,
|
||||
value: Amount,
|
||||
) -> CompactBlock {
|
||||
let to = extfvk.default_address().unwrap().1;
|
||||
|
||||
// Create a fake Note for the account
|
||||
let mut rng = OsRng;
|
||||
let note = Note {
|
||||
g_d: to.diversifier.g_d::<Bls12>(&JUBJUB).unwrap(),
|
||||
pk_d: to.pk_d.clone(),
|
||||
value: value.into(),
|
||||
r: Fs::random(&mut rng),
|
||||
};
|
||||
let encryptor = SaplingNoteEncryption::new(
|
||||
extfvk.fvk.ovk,
|
||||
note.clone(),
|
||||
to.clone(),
|
||||
Memo::default(),
|
||||
&mut rng,
|
||||
);
|
||||
let mut cmu = vec![];
|
||||
note.cm(&JUBJUB).into_repr().write_le(&mut cmu).unwrap();
|
||||
let mut epk = vec![];
|
||||
encryptor.epk().write(&mut epk).unwrap();
|
||||
let enc_ciphertext = encryptor.encrypt_note_plaintext();
|
||||
|
||||
// Create a fake CompactBlock containing the note
|
||||
let mut cb = CompactBlock::new();
|
||||
cb.set_height(height as u64);
|
||||
|
||||
let mut cout = CompactOutput::new();
|
||||
cout.set_cmu(cmu);
|
||||
cout.set_epk(epk);
|
||||
cout.set_ciphertext(enc_ciphertext[..52].to_vec());
|
||||
let mut ctx = CompactTx::new();
|
||||
let mut txid = vec![0; 32];
|
||||
rng.fill_bytes(&mut txid);
|
||||
ctx.set_hash(txid);
|
||||
ctx.outputs.push(cout);
|
||||
cb.vtx.push(ctx);
|
||||
|
||||
cb
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scan_block_with_my_tx() {
|
||||
let extsk = ExtendedSpendingKey::master(&[]);
|
||||
let extfvk = ExtendedFullViewingKey::from(&extsk);
|
||||
|
||||
let cb = fake_compact_block(1, extfvk.clone(), Amount::from_u64(5).unwrap());
|
||||
|
||||
let txs = scan_block(cb, &[extfvk]);
|
||||
assert_eq!(txs.len(), 1);
|
||||
|
||||
let tx = &txs[0];
|
||||
assert_eq!(tx.num_spends, 0);
|
||||
assert_eq!(tx.num_outputs, 1);
|
||||
assert_eq!(tx.shielded_outputs.len(), 1);
|
||||
assert_eq!(tx.shielded_outputs[0].index, 0);
|
||||
assert_eq!(tx.shielded_outputs[0].account, 0);
|
||||
assert_eq!(tx.shielded_outputs[0].value, 5);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user