mirror of
https://github.com/Qortal/pirate-librustzcash.git
synced 2025-01-30 15:32:14 +00:00
Parse compact blocks to find wallet transactions
This commit is contained in:
parent
af7e263bcc
commit
591b1fc28f
13
Cargo.lock
generated
13
Cargo.lock
generated
@ -534,6 +534,15 @@ dependencies = [
|
||||
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_os"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_xorshift"
|
||||
version = "0.2.0"
|
||||
@ -628,10 +637,13 @@ name = "zcash_client_backend"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bech32 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ff 0.5.0",
|
||||
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pairing 0.15.0",
|
||||
"protobuf 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"protobuf-codegen-pure 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_os 0.2.2 (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.1.0",
|
||||
]
|
||||
@ -732,6 +744,7 @@ dependencies = [
|
||||
"checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853"
|
||||
"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
||||
"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
|
||||
"checksum rand_os 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a788ae3edb696cfcba1c19bfd388cc4b8c21f8a408432b199c072825084da58a"
|
||||
"checksum rand_xorshift 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "77d416b86801d23dde1aa643023b775c3a462efc0ed96443add11546cdf1dca8"
|
||||
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||
"checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d"
|
||||
|
@ -13,6 +13,8 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bech32 = "0.7"
|
||||
ff = { version = "0.5.0", path = "../ff" }
|
||||
hex = "0.3"
|
||||
pairing = { version = "0.15.0", path = "../pairing" }
|
||||
protobuf = "2"
|
||||
zcash_primitives = { version = "0.1.0", path = "../zcash_primitives" }
|
||||
@ -22,6 +24,7 @@ protobuf-codegen-pure = "2"
|
||||
|
||||
[dev-dependencies]
|
||||
rand_core = "0.5"
|
||||
rand_os = "0.2"
|
||||
rand_xorshift = "0.2"
|
||||
|
||||
[badges]
|
||||
|
@ -10,3 +10,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