pedersen hash

This commit is contained in:
Hanh 2021-07-11 23:42:52 +08:00
parent 48ab1c9102
commit 1c4b756a38
7 changed files with 286 additions and 25 deletions

View File

@ -42,6 +42,7 @@ secp256k1 = "0.20.2"
tiny-hderive = "0.3.0"
ripemd160 = "0.9.1"
sha2 = "0.9.5"
lazy_static = "1.4.0"
# librustzcash synced to 35023ed8ca2fb1061e78fd740b640d4eefcc5edd

View File

@ -1,8 +1,13 @@
use crate::commitment::{CTree, Witness};
use rayon::prelude::IntoParallelIterator;
use rayon::prelude::*;
use zcash_primitives::merkle_tree::Hashable;
use zcash_primitives::sapling::Node;
use crate::hash::pedersen_hash;
#[inline(always)]
fn node_combine(depth: usize, left: &Node, right: &Node) -> Node {
Node::new(pedersen_hash(depth as u8, &left.repr, &right.repr))
}
trait Builder<T, C> {
fn collect(&mut self, commitments: &[Node], context: &C) -> usize;
@ -70,7 +75,7 @@ impl Builder<CTree, ()> for CTreeBuilder {
fn up(&mut self) {
let h = if self.left.is_some() && self.right.is_some() {
Some(Node::combine(
Some(node_combine(
self.depth,
&self.left.unwrap(),
&self.right.unwrap(),
@ -160,7 +165,7 @@ fn combine_level(commitments: &mut [Node], offset: Option<Node>, n: usize, depth
let next_level: Vec<Node> = (0..nn)
.into_par_iter()
.map(|i| {
Node::combine(
node_combine(
depth,
CTreeBuilder::get(commitments, 2 * i, &offset),
CTreeBuilder::get(commitments, 2 * i + 1, &offset),

View File

@ -234,8 +234,8 @@ impl DbAdapter {
params![account, txid, height, timestamp, tx_index],
)?;
let id_tx: u32 = self.connection.query_row(
"SELECT id_tx FROM transactions WHERE txid = ?1",
params![txid],
"SELECT id_tx FROM transactions WHERE account = ?1 AND txid = ?2",
params![account, txid],
|row| row.get(0),
)?;
log::debug!("-transaction {}", id_tx);
@ -434,6 +434,21 @@ impl DbAdapter {
Ok(nfs)
}
pub fn get_nullifiers_raw(&self) -> anyhow::Result<Vec<(u32, u64, Vec<u8>)>> {
let mut statement = self.connection.prepare("SELECT account, value, nf FROM received_notes")?;
let res = statement.query_map(NO_PARAMS, |row| {
let account: u32 = row.get(0)?;
let amount: i64 = row.get(1)?;
let nf: Vec<u8> = row.get(2)?;
Ok((account, amount as u64, nf))
})?;
let mut v: Vec<(u32, u64, Vec<u8>)> = vec![];
for r in res {
v.push(r?);
}
Ok(v)
}
pub fn get_spendable_notes(
&self,
account: u32,

172
src/hash.rs Normal file
View File

@ -0,0 +1,172 @@
use zcash_primitives::constants::PEDERSEN_HASH_CHUNKS_PER_GENERATOR;
use group::{GroupEncoding, Group, Curve};
use std::io::Read;
use jubjub::{Fr, SubgroupPoint};
use std::ops::AddAssign;
use ff::PrimeField;
use zcash_params::GENERATORS;
use lazy_static::lazy_static;
lazy_static! {
static ref GENERATORS_EXP: Vec<SubgroupPoint> = read_generators_bin();
}
fn read_generators_bin() -> Vec<SubgroupPoint> {
let mut generators_bin = GENERATORS;
let mut gens: Vec<SubgroupPoint> = vec![];
gens.reserve_exact(3 * 32 * 256);
for _i in 0..3 {
for _j in 0..32 {
for _k in 0..256 {
let mut bb = [0u8; 32];
generators_bin.read(&mut bb).unwrap();
let p = SubgroupPoint::from_bytes_unchecked(&bb).unwrap();
gens.push(p);
}
}
}
gens
}
macro_rules! accumulate_scalar {
($acc: ident, $cur: ident, $x: expr) => {
let mut tmp = $cur;
if $x & 1 != 0 {
tmp.add_assign(&$cur);
}
$cur = $cur.double();
if $x & 2 != 0 {
tmp.add_assign(&$cur);
}
if $x & 4 != 0 {
tmp = tmp.neg();
}
$acc.add_assign(&tmp);
}
}
pub fn pedersen_hash(depth: u8, left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] {
let mut result = SubgroupPoint::identity();
let mut bitoffset = 0;
let mut byteoffset = 0;
let mut r_byteoffset = 0;
let mut acc = Fr::zero();
let mut cur = Fr::one();
let a = depth & 7;
let b = depth >> 3;
accumulate_scalar!(acc, cur, a);
cur = cur.double().double().double();
// println!("{}", hex::encode(acc.to_bytes()));
accumulate_scalar!(acc, cur, b);
cur = cur.double().double().double();
// println!("{}", hex::encode(acc.to_bytes()));
let mut i_generator = 0;
let mut chunks_remaining = PEDERSEN_HASH_CHUNKS_PER_GENERATOR - 3;
let mut r = (left[0] as u16) | (left[1] as u16) << 8;
let x = (r >> bitoffset) & 7;
accumulate_scalar!(acc, cur, x);
cur = cur.double().double().double();
for _c in 0..169 {
bitoffset += 3;
let x = (r >> bitoffset) & 7;
accumulate_scalar!(acc, cur, x);
if bitoffset >= 8 {
bitoffset -= 8;
byteoffset += 1;
if byteoffset < 31 {
r = (r >> 8) | (left[byteoffset + 1] as u16) << 8;
}
else if byteoffset == 31 {
r = ((r >> 7) & 0xFF) | (right[0] as u16) << 8;
bitoffset += 1;
}
else if byteoffset < 63 {
r = (r >> 8) | (right[r_byteoffset + 1] as u16) << 8;
r_byteoffset += 1;
}
else if byteoffset == 63 {
r = r >> 8;
}
}
chunks_remaining -= 1;
if chunks_remaining == 0 {
// println!("++ {}", hex::encode(acc.to_bytes()));
result += generator_multiplication(&acc, &GENERATORS_EXP, i_generator);
i_generator += 1;
acc = Fr::zero();
cur = Fr::one();
chunks_remaining = PEDERSEN_HASH_CHUNKS_PER_GENERATOR
} else {
cur = cur.double().double().double(); // 2^4 * cur
}
}
result += generator_multiplication(&acc, &GENERATORS_EXP, i_generator);
jubjub::ExtendedPoint::from(result)
.to_affine()
.get_u()
.to_repr()
}
fn generator_multiplication(acc: &Fr, gens: &[SubgroupPoint], i_generator: u32) -> SubgroupPoint {
let acc = acc.to_repr();
let mut tmp = jubjub::SubgroupPoint::identity();
for (i, &j) in acc.iter().enumerate() {
let offset = (i_generator*32+i as u32)*256 + j as u32;
let x = gens[offset as usize];
tmp += x;
}
tmp
}
#[cfg(test)]
mod tests {
use zcash_primitives::sapling::Node;
use zcash_primitives::merkle_tree::Hashable;
use crate::hash::pedersen_hash;
use rand::{thread_rng, RngCore};
#[test]
fn test_hash() {
let mut r = thread_rng();
for _ in 0..100 {
let mut a = [0u8; 32];
r.fill_bytes(&mut a);
let mut b = [0u8; 32];
r.fill_bytes(&mut b);
let depth = (r.next_u32() % 64) as u8;
let depth = depth.min(62);
// let sa = "767a9a7e989289efdfa69c4c8e985c31f3c2c0353f20a80f572854206f077f86";
// let sb = "944c46945a9e7a0a753850bd90f69d44ac884b60244a9f8eacf3a2aeddd08d6e";
// a.copy_from_slice(&hex::decode(sa).unwrap());
// b.copy_from_slice(&hex::decode(sb).unwrap());
// println!("A: {}", hex::encode(a));
// println!("B: {}", hex::encode(b));
let node1 = Node::new(a);
let node2 = Node::new(b);
let hash = Node::combine(
depth as usize,
&node1,
&node2);
let hash2 = pedersen_hash(depth, &a, &b);
// println!("Reference Hash: {}", hex::encode(hash.repr));
// println!("This Hash: {}", hex::encode(hash2));
// need to expose repr for this check
assert_eq!(hash.repr, hash2);
}
}
}

View File

@ -15,6 +15,7 @@ pub const LWD_URL: &str = "http://lwd.hanh.me:9067";
// pub const LWD_URL: &str = "http://lwd.hanh.me:9067";
// pub const LWD_URL: &str = "http://127.0.0.1:9067";
mod hash;
mod builder;
mod chain;
mod commitment;
@ -41,3 +42,4 @@ pub use crate::scan::{latest_height, scan_all, sync_async};
pub use crate::wallet::{Wallet, WalletBalance};
pub use crate::print::*;
pub use crate::key::is_valid_key;
pub use crate::hash::pedersen_hash;

View File

@ -1,9 +1,11 @@
use bip39::{Language, Mnemonic};
use rand::rngs::OsRng;
use rand::RngCore;
use sync::{DbAdapter, Wallet, ChainError, Witness, print_witness2, LWD_URL};
use rand::{RngCore, thread_rng};
use sync::{DbAdapter, Wallet, ChainError, Witness, print_witness2, LWD_URL, pedersen_hash};
use rusqlite::NO_PARAMS;
use zcash_client_backend::data_api::wallet::ANCHOR_OFFSET;
use zcash_primitives::sapling::Node;
use zcash_primitives::merkle_tree::Hashable;
const DB_NAME: &str = "zec.db";
@ -19,12 +21,15 @@ async fn test() -> anyhow::Result<()> {
env_logger::init();
let seed = dotenv::var("SEED").unwrap();
let seed2 = dotenv::var("SEED2").unwrap();
let address = dotenv::var("ADDRESS").unwrap();
let progress = |height| {
log::info!("Height = {}", height);
};
let wallet = Wallet::new(DB_NAME, LWD_URL);
wallet.new_account_with_key("test", &seed).unwrap();
wallet.new_account_with_key("main", &seed).unwrap();
wallet.new_account_with_key("test", &seed2).unwrap();
let res = wallet.sync(true, ANCHOR_OFFSET, progress).await;
if let Err(err) = res {
if let Some(_) = err.downcast_ref::<ChainError>() {
@ -34,15 +39,25 @@ async fn test() -> anyhow::Result<()> {
panic!(err);
}
}
// let tx_id = wallet
// .send_payment(1, &address, 50000, u64::max_value(), 2, move |progress| { println!("{}", progress.cur()); })
// .await
// .unwrap();
// println!("TXID = {}", tx_id);
let tx_id = wallet
.send_payment(1, &address, 50000, "test memo", u64::max_value(), 2, move |progress| { println!("{}", progress.cur()); })
.await
.unwrap();
println!("TXID = {}", tx_id);
Ok(())
}
#[allow(dead_code)]
async fn test_sync() {
let progress = |height| {
log::info!("Height = {}", height);
};
let wallet = Wallet::new(DB_NAME, LWD_URL);
wallet.sync(true, ANCHOR_OFFSET, progress).await.unwrap();
}
#[allow(dead_code)]
fn test_make_wallet() {
let mut entropy = [0u8; 32];
@ -55,7 +70,7 @@ fn test_make_wallet() {
#[allow(dead_code)]
fn test_rewind() {
let mut db = DbAdapter::new(DB_NAME).unwrap();
db.trim_to_height(1466520).unwrap();
db.trim_to_height(1314000).unwrap();
}
#[allow(dead_code)]
@ -98,10 +113,45 @@ fn w() {
print_witness2(&w);
}
fn test_hash() {
let mut r = thread_rng();
for _ in 0..100 {
let mut a = [0u8; 32];
r.fill_bytes(&mut a);
let mut b = [0u8; 32];
r.fill_bytes(&mut b);
let depth = (r.next_u32() % 64) as u8;
// let sa = "767a9a7e989289efdfa69c4c8e985c31f3c2c0353f20a80f572854206f077f86";
// let sb = "944c46945a9e7a0a753850bd90f69d44ac884b60244a9f8eacf3a2aeddd08d6e";
// a.copy_from_slice(&hex::decode(sa).unwrap());
// b.copy_from_slice(&hex::decode(sb).unwrap());
// println!("A: {}", hex::encode(a));
// println!("B: {}", hex::encode(b));
let node1 = Node::new(a);
let node2 = Node::new(b);
let hash = Node::combine(
depth as usize,
&node1,
&node2);
let hash2 = pedersen_hash(depth, &a, &b);
// println!("Reference Hash: {}", hex::encode(hash.repr));
// println!("This Hash: {}", hex::encode(hash2));
// need to expose repr for this check
assert_eq!(hash.repr, hash2);
}
}
fn main() {
test_hash();
init();
// test_invalid_witness()
// test_rewind();
// test_sync().await;
test().unwrap();
// test_get_balance();
// w();

View File

@ -7,7 +7,7 @@ use zcash_primitives::consensus::{BlockHeight, Parameters};
use zcash_client_backend::encoding::{decode_extended_full_viewing_key, encode_payment_address};
use zcash_primitives::memo::{MemoBytes, Memo};
use std::convert::TryFrom;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
#[derive(Debug)]
pub struct TransactionInfo {
@ -18,7 +18,8 @@ pub struct TransactionInfo {
}
pub async fn decode_transaction(client: &mut CompactTxStreamerClient<Channel>,
nfs: &HashMap<Vec<u8>, u64>,
nfs: &HashMap<(u32, Vec<u8>), u64>,
account: u32,
fvk: &str,
tx_hash: &[u8],
height: u32) -> anyhow::Result<TransactionInfo> {
@ -39,7 +40,7 @@ pub async fn decode_transaction(client: &mut CompactTxStreamerClient<Channel>,
let mut address = String::new();
for spend in tx.shielded_spends.iter() {
let nf = spend.nullifier.to_vec();
if let Some(&v) = nfs.get(&nf) {
if let Some(&v) = nfs.get(&(account, nf)) {
amount -= v as i64;
}
}
@ -52,8 +53,7 @@ pub async fn decode_transaction(client: &mut CompactTxStreamerClient<Channel>,
address = encode_payment_address(NETWORK.hrp_sapling_payment_address(), &pa);
tx_memo = memo;
}
}
else if let Some((_note, pa, memo)) = try_sapling_output_recovery(&NETWORK, height, &ovk, &output) {
} else if let Some((_note, pa, memo)) = try_sapling_output_recovery(&NETWORK, height, &ovk, &output) {
address = encode_payment_address(NETWORK.hrp_sapling_payment_address(), &pa);
tx_memo = memo;
}
@ -65,20 +65,29 @@ pub async fn decode_transaction(client: &mut CompactTxStreamerClient<Channel>,
address,
memo: Memo::try_from(tx_memo)?,
amount,
fee
fee,
};
Ok(tx_info)
}
pub async fn retrieve_tx_info(tx_ids: &[u32], ld_url: &str, db_path: &str) -> anyhow::Result<()> {
let mut tx_ids_set: HashSet<u32> = HashSet::new();
for &tx_id in tx_ids.iter() {
tx_ids_set.insert(tx_id);
}
let mut client = connect_lightwalletd(ld_url).await?;
let db = DbAdapter::new(db_path)?;
for &id_tx in tx_ids.iter() {
let nfs = db.get_nullifiers_raw()?;
let mut nf_map: HashMap<(u32, Vec<u8>), u64> = HashMap::new();
for nf in nfs.iter() {
nf_map.insert((nf.0, nf.2.clone()), nf.1);
}
for &id_tx in tx_ids_set.iter() {
println!("{}", id_tx);
let (account, height, tx_hash) = db.get_txhash(id_tx)?;
let nfs = db.get_nullifier_amounts(account, false)?;
let fvk = db.get_ivk(account)?;
let tx_info = decode_transaction(&mut client, &nfs, &fvk, &tx_hash, height).await?;
let tx_info = decode_transaction(&mut client, &nf_map, account, &fvk, &tx_hash, height).await?;
db.store_tx_metadata(id_tx, &tx_info)?;
}
@ -89,6 +98,7 @@ pub async fn retrieve_tx_info(tx_ids: &[u32], ld_url: &str, db_path: &str) -> an
mod tests {
use crate::{connect_lightwalletd, LWD_URL, DbAdapter};
use crate::transaction::decode_transaction;
use std::collections::HashMap;
#[tokio::test]
async fn test_decode_transaction() {
@ -96,9 +106,15 @@ mod tests {
let mut client = connect_lightwalletd(LWD_URL).await.unwrap();
let db = DbAdapter::new("./zec.db").unwrap();
let account = 1;
let nfs = db.get_nullifier_amounts(account, false).unwrap();
let nfs = db.get_nullifiers_raw().unwrap();
let mut nf_map: HashMap<(u32, Vec<u8>), u64> = HashMap::new();
for nf in nfs.iter() {
if nf.0 == account {
nf_map.insert((nf.0, nf.2.clone()), nf.1);
}
}
let fvk = db.get_ivk(account).unwrap();
let tx_info = decode_transaction(&mut client, &nfs, &fvk, &tx_hash, 1313212).await.unwrap();
let tx_info = decode_transaction(&mut client, &nf_map, account, &fvk, &tx_hash, 1313212).await.unwrap();
println!("{:?}", tx_info);
}
}