Squashed commit of the following:
commit 3dca82d62e10252555fcfe498e63c41a5ca967af Author: Hanh <hanh425@gmail.com> Date: Sun Sep 18 01:30:56 2022 +0800 WIP commit 32013d4eea0bff3321e4bb82a4c878aa4feaea7b Author: Hanh <hanh425@gmail.com> Date: Sat Sep 17 19:48:32 2022 +0800 WIP commit 0f4b16d1b3874f9377b7144591a602aa97e6747d Author: Hanh <hanh425@gmail.com> Date: Sat Sep 17 12:45:41 2022 +0800 WIP commit 90cf116c230b2845d43bdc9b81057f8b761b6773 Author: Hanh <hanh425@gmail.com> Date: Fri Sep 16 21:05:52 2022 +0800 WIP commit d8a8db0a29564c98b3f7dc331d5d37f4b7a87c18 Author: Hanh <hanh425@gmail.com> Date: Fri Sep 16 18:03:56 2022 +0800 WIP commit cb467ea2cd7bada9a9cbf9fbc59b265bb3be4968 Author: Hanh <hanh425@gmail.com> Date: Fri Sep 16 17:17:51 2022 +0800 WIP commit ba3b4de96e19329a317cc4164cf69442e9b1aa8a Author: Hanh <hanh425@gmail.com> Date: Fri Sep 16 14:41:07 2022 +0800 Sapling Pedersen hash commit 3e9be116a68342c22da147dba011e2d6a9e68cbc Author: Hanh <hanh425@gmail.com> Date: Thu Sep 15 10:16:54 2022 +0800 WIP commit 94e0e8b0d5601ed48227bae89ea3a6c1bb093abc Author: Hanh <hanh425@gmail.com> Date: Wed Sep 14 01:22:22 2022 +0800 WIP commit 95708029ab4b94d85f9d565a16505d767bb4598b Author: Hanh <hanh425@gmail.com> Date: Tue Sep 13 21:27:51 2022 +0800 Db API for Orchard commit 170a31fd7cf79fba74f710fcd1bf7404235c8e51 Author: Hanh <hanh425@gmail.com> Date: Tue Sep 13 20:57:38 2022 +0800 Add support for orchard to db schema
This commit is contained in:
parent
267192c42c
commit
d5f06cc7eb
|
@ -1,5 +1,5 @@
|
||||||
/target
|
/target
|
||||||
.env
|
../../.env
|
||||||
.idea/
|
.idea/
|
||||||
docs/_site/
|
docs/_site/
|
||||||
*.db
|
*.db
|
||||||
|
|
|
@ -7,6 +7,7 @@ edition = "2021"
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "scan_all"
|
name = "scan_all"
|
||||||
harness = false
|
harness = false
|
||||||
|
required-features = ["dotenv"]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "warp-rpc"
|
name = "warp-rpc"
|
||||||
|
@ -18,6 +19,11 @@ name = "wallet"
|
||||||
path = "src/main/wallet.rs"
|
path = "src/main/wallet.rs"
|
||||||
required-features = ["dotenv"]
|
required-features = ["dotenv"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "tests"
|
||||||
|
path = "src/main/tests.rs"
|
||||||
|
required-features = ["dotenv"]
|
||||||
|
|
||||||
#[[bin]]
|
#[[bin]]
|
||||||
#name = "ledger"
|
#name = "ledger"
|
||||||
#path = "src/main/ledger.rs"
|
#path = "src/main/ledger.rs"
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use criterion::{criterion_group, criterion_main, Criterion};
|
use criterion::{criterion_group, criterion_main, Criterion};
|
||||||
use warp_api_ffi::scan_all;
|
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
|
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
|
||||||
use zcash_primitives::consensus::{Network, Parameters};
|
use zcash_primitives::consensus::{Network, Parameters};
|
||||||
|
|
|
@ -581,7 +581,7 @@ pub unsafe extern "C" fn split_data(id: u32, data: *mut c_char) -> CResult<*mut
|
||||||
pub unsafe extern "C" fn merge_data(drop: *mut c_char) -> CResult<*mut c_char> {
|
pub unsafe extern "C" fn merge_data(drop: *mut c_char) -> CResult<*mut c_char> {
|
||||||
from_c_str!(drop);
|
from_c_str!(drop);
|
||||||
let res = || {
|
let res = || {
|
||||||
let res = crate::fountain::put_drop(&*drop)?
|
let res = crate::fountain::RaptorQDrops::put_drop(&*drop)?
|
||||||
.map(|d| base64::encode(&d))
|
.map(|d| base64::encode(&d))
|
||||||
.unwrap_or(String::new());
|
.unwrap_or(String::new());
|
||||||
Ok::<_, anyhow::Error>(res)
|
Ok::<_, anyhow::Error>(res)
|
||||||
|
|
|
@ -141,7 +141,7 @@ async fn fetch_and_store_tree_state(
|
||||||
.into_inner();
|
.into_inner();
|
||||||
let tree = CTree::read(&*hex::decode(&tree_state.sapling_tree)?)?;
|
let tree = CTree::read(&*hex::decode(&tree_state.sapling_tree)?)?;
|
||||||
let db = c.db()?;
|
let db = c.db()?;
|
||||||
DbAdapter::store_block(&db.connection, height, &block.hash, block.time, &tree)?;
|
DbAdapter::store_block(&db.connection, height, &block.hash, block.time, &tree, None)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,20 @@ use rayon::prelude::IntoParallelIterator;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use zcash_primitives::sapling::Node;
|
use zcash_primitives::sapling::Node;
|
||||||
|
|
||||||
|
pub enum ProvingSystem {
|
||||||
|
Sapling,
|
||||||
|
Orchard
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ProvingSystemHasher {
|
||||||
|
type ExtendedPoint;
|
||||||
|
type AffinePoint;
|
||||||
|
|
||||||
|
fn combine_pair(depth: usize, left: &Node, right: &Node) -> Node;
|
||||||
|
fn batch_combine(depth: usize, left: &Node, right: &Node) -> Node;
|
||||||
|
fn batch_normalize(hash_extended: &[Self::ExtendedPoint], hash_affine: &mut [Self::AffinePoint]);
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn batch_node_combine1(depth: usize, left: &Node, right: &Node) -> ExtendedPoint {
|
fn batch_node_combine1(depth: usize, left: &Node, right: &Node) -> ExtendedPoint {
|
||||||
// Node::new(pedersen_hash(depth as u8, &left.repr, &right.repr))
|
// Node::new(pedersen_hash(depth as u8, &left.repr, &right.repr))
|
||||||
|
|
184
src/db.rs
184
src/db.rs
|
@ -9,18 +9,26 @@ use rusqlite::{params, Connection, OptionalExtension, Transaction};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::serde_as;
|
use serde_with::serde_as;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::convert::TryInto;
|
||||||
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
|
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
|
||||||
use zcash_params::coin::{get_coin_chain, get_coin_id, CoinType};
|
use zcash_params::coin::{CoinType, get_coin_chain, get_coin_id};
|
||||||
use zcash_primitives::consensus::{Network, NetworkUpgrade, Parameters};
|
use zcash_primitives::consensus::{Network, NetworkUpgrade, Parameters};
|
||||||
use zcash_primitives::merkle_tree::IncrementalWitness;
|
use zcash_primitives::merkle_tree::IncrementalWitness;
|
||||||
use zcash_primitives::sapling::{Diversifier, Node, Note, Rseed, SaplingIvk};
|
use zcash_primitives::sapling::{Diversifier, Node, Note, Rseed, SaplingIvk};
|
||||||
use zcash_primitives::zip32::{DiversifierIndex, ExtendedFullViewingKey};
|
use zcash_primitives::zip32::{DiversifierIndex, ExtendedFullViewingKey};
|
||||||
|
use crate::sync;
|
||||||
|
|
||||||
mod migration;
|
mod migration;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub const DEFAULT_DB_PATH: &str = "zec.db";
|
pub const DEFAULT_DB_PATH: &str = "zec.db";
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DbAdapterBuilder {
|
||||||
|
pub coin_type: CoinType,
|
||||||
|
pub db_path: String,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct DbAdapter {
|
pub struct DbAdapter {
|
||||||
pub coin_type: CoinType,
|
pub coin_type: CoinType,
|
||||||
pub connection: Connection,
|
pub connection: Connection,
|
||||||
|
@ -35,9 +43,17 @@ pub struct ReceivedNote {
|
||||||
pub value: u64,
|
pub value: u64,
|
||||||
pub rcm: Vec<u8>,
|
pub rcm: Vec<u8>,
|
||||||
pub nf: Vec<u8>,
|
pub nf: Vec<u8>,
|
||||||
|
pub rho: Option<Vec<u8>>,
|
||||||
pub spent: Option<u32>,
|
pub spent: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ReceivedNoteShort {
|
||||||
|
pub id: u32,
|
||||||
|
pub account: u32,
|
||||||
|
pub nf: Nf,
|
||||||
|
pub value: u64,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SpendableNote {
|
pub struct SpendableNote {
|
||||||
pub id: u32,
|
pub id: u32,
|
||||||
|
@ -82,6 +98,15 @@ pub fn wrap_query_no_rows(name: &'static str) -> impl Fn(rusqlite::Error) -> any
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DbAdapterBuilder {
|
||||||
|
pub fn build(&self) -> anyhow::Result<DbAdapter> {
|
||||||
|
DbAdapter::new(
|
||||||
|
self.coin_type,
|
||||||
|
&self.db_path,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl DbAdapter {
|
impl DbAdapter {
|
||||||
pub fn new(coin_type: CoinType, db_path: &str) -> anyhow::Result<DbAdapter> {
|
pub fn new(coin_type: CoinType, db_path: &str) -> anyhow::Result<DbAdapter> {
|
||||||
let connection = Connection::open(db_path)?;
|
let connection = Connection::open(db_path)?;
|
||||||
|
@ -226,6 +251,10 @@ impl DbAdapter {
|
||||||
"DELETE FROM sapling_witnesses WHERE height > ?1",
|
"DELETE FROM sapling_witnesses WHERE height > ?1",
|
||||||
params![height],
|
params![height],
|
||||||
)?;
|
)?;
|
||||||
|
tx.execute(
|
||||||
|
"DELETE FROM orchard_witnesses WHERE height > ?1",
|
||||||
|
params![height],
|
||||||
|
)?;
|
||||||
tx.execute(
|
tx.execute(
|
||||||
"DELETE FROM received_notes WHERE height > ?1",
|
"DELETE FROM received_notes WHERE height > ?1",
|
||||||
params![height],
|
params![height],
|
||||||
|
@ -265,16 +294,48 @@ impl DbAdapter {
|
||||||
height: u32,
|
height: u32,
|
||||||
hash: &[u8],
|
hash: &[u8],
|
||||||
timestamp: u32,
|
timestamp: u32,
|
||||||
tree: &CTree,
|
sapling_tree: &CTree,
|
||||||
|
orchard_tree: Option<&CTree>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
log::debug!("+block");
|
log::debug!("+block");
|
||||||
|
let mut sapling_bb: Vec<u8> = vec![];
|
||||||
|
sapling_tree.write(&mut sapling_bb)?;
|
||||||
|
let orchard_bb = orchard_tree.map(|tree| {
|
||||||
let mut bb: Vec<u8> = vec![];
|
let mut bb: Vec<u8> = vec![];
|
||||||
tree.write(&mut bb)?;
|
tree.write(&mut bb).unwrap();
|
||||||
|
bb
|
||||||
|
});
|
||||||
connection.execute(
|
connection.execute(
|
||||||
"INSERT INTO blocks(height, hash, timestamp, sapling_tree)
|
"INSERT INTO blocks(height, hash, timestamp, sapling_tree, orchard_tree)
|
||||||
VALUES (?1, ?2, ?3, ?4)
|
VALUES (?1, ?2, ?3, ?4, ?5)
|
||||||
ON CONFLICT DO NOTHING",
|
ON CONFLICT DO NOTHING",
|
||||||
params![height, hash, timestamp, &bb],
|
params![height, hash, timestamp, &sapling_bb, orchard_bb],
|
||||||
|
)?;
|
||||||
|
log::debug!("-block");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn store_block2(
|
||||||
|
height: u32,
|
||||||
|
hash: &[u8],
|
||||||
|
timestamp: u32,
|
||||||
|
sapling_tree: &sync::CTree,
|
||||||
|
orchard_tree: Option<&sync::CTree>,
|
||||||
|
connection: &Connection,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
log::debug!("+block");
|
||||||
|
let mut sapling_bb: Vec<u8> = vec![];
|
||||||
|
sapling_tree.write(&mut sapling_bb)?;
|
||||||
|
let orchard_bb = orchard_tree.map(|tree| {
|
||||||
|
let mut bb: Vec<u8> = vec![];
|
||||||
|
tree.write(&mut bb).unwrap();
|
||||||
|
bb
|
||||||
|
});
|
||||||
|
connection.execute(
|
||||||
|
"INSERT INTO blocks(height, hash, timestamp, sapling_tree, orchard_tree)
|
||||||
|
VALUES (?1, ?2, ?3, ?4, ?5)
|
||||||
|
ON CONFLICT DO NOTHING",
|
||||||
|
params![height, hash, timestamp, &sapling_bb, orchard_bb],
|
||||||
)?;
|
)?;
|
||||||
log::debug!("-block");
|
log::debug!("-block");
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -313,9 +374,9 @@ impl DbAdapter {
|
||||||
db_tx: &Transaction,
|
db_tx: &Transaction,
|
||||||
) -> anyhow::Result<u32> {
|
) -> anyhow::Result<u32> {
|
||||||
log::debug!("+received_note {}", id_tx);
|
log::debug!("+received_note {}", id_tx);
|
||||||
db_tx.execute("INSERT INTO received_notes(account, tx, height, position, output_index, diversifier, value, rcm, nf, spent)
|
db_tx.execute("INSERT INTO received_notes(account, tx, height, position, output_index, diversifier, value, rcm, rho, nf, spent)
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)
|
||||||
ON CONFLICT DO NOTHING", params![note.account, id_tx, note.height, position as u32, note.output_index, note.diversifier, note.value as i64, note.rcm, note.nf, note.spent])?;
|
ON CONFLICT DO NOTHING", params![note.account, id_tx, note.height, position as u32, note.output_index, note.diversifier, note.value as i64, note.rcm, note.rho, note.nf, note.spent])?;
|
||||||
let id_note: u32 = db_tx
|
let id_note: u32 = db_tx
|
||||||
.query_row(
|
.query_row(
|
||||||
"SELECT id_note FROM received_notes WHERE tx = ?1 AND output_index = ?2",
|
"SELECT id_note FROM received_notes WHERE tx = ?1 AND output_index = ?2",
|
||||||
|
@ -327,6 +388,7 @@ impl DbAdapter {
|
||||||
Ok(id_note)
|
Ok(id_note)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Depends on the type of witness
|
||||||
pub fn store_witnesses(
|
pub fn store_witnesses(
|
||||||
connection: &Connection,
|
connection: &Connection,
|
||||||
witness: &Witness,
|
witness: &Witness,
|
||||||
|
@ -345,6 +407,25 @@ impl DbAdapter {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn store_witness(
|
||||||
|
witness: &sync::Witness,
|
||||||
|
height: u32,
|
||||||
|
id_note: u32,
|
||||||
|
connection: &Connection,
|
||||||
|
shielded_pool: &str
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
log::debug!("+store_witness");
|
||||||
|
let mut bb: Vec<u8> = vec![];
|
||||||
|
witness.write(&mut bb)?;
|
||||||
|
connection.execute(
|
||||||
|
&format!("INSERT INTO {}_witnesses(note, height, witness) VALUES (?1, ?2, ?3)
|
||||||
|
ON CONFLICT DO NOTHING", shielded_pool),
|
||||||
|
params![id_note, height, bb],
|
||||||
|
)?;
|
||||||
|
log::debug!("-store_witness");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn store_tx_metadata(&self, id_tx: u32, tx_info: &TransactionInfo) -> anyhow::Result<()> {
|
pub fn store_tx_metadata(&self, id_tx: u32, tx_info: &TransactionInfo) -> anyhow::Result<()> {
|
||||||
self.connection.execute(
|
self.connection.execute(
|
||||||
"UPDATE transactions SET address = ?1, memo = ?2 WHERE id_tx = ?3",
|
"UPDATE transactions SET address = ?1, memo = ?2 WHERE id_tx = ?3",
|
||||||
|
@ -420,8 +501,12 @@ impl DbAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_tree(&self) -> anyhow::Result<(CTree, Vec<Witness>)> {
|
pub fn get_tree(&self) -> anyhow::Result<(CTree, Vec<Witness>)> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_tree_by_name(&self, shielded_pool: &str) -> anyhow::Result<(sync::CTree, Vec<sync::Witness>)> {
|
||||||
let res = self.connection.query_row(
|
let res = self.connection.query_row(
|
||||||
"SELECT height, sapling_tree FROM blocks WHERE height = (SELECT MAX(height) FROM blocks)",
|
&format!("SELECT height, {}_tree FROM blocks WHERE height = (SELECT MAX(height) FROM blocks)", shielded_pool),
|
||||||
[], |row| {
|
[], |row| {
|
||||||
let height: u32 = row.get(0)?;
|
let height: u32 = row.get(0)?;
|
||||||
let tree: Vec<u8> = row.get(1)?;
|
let tree: Vec<u8> = row.get(1)?;
|
||||||
|
@ -429,21 +514,21 @@ impl DbAdapter {
|
||||||
}).optional()?;
|
}).optional()?;
|
||||||
Ok(match res {
|
Ok(match res {
|
||||||
Some((height, tree)) => {
|
Some((height, tree)) => {
|
||||||
let tree = CTree::read(&*tree)?;
|
let tree = sync::CTree::read(&*tree)?;
|
||||||
let mut statement = self.connection.prepare(
|
let mut statement = self.connection.prepare(
|
||||||
"SELECT id_note, witness FROM sapling_witnesses w, received_notes n WHERE w.height = ?1 AND w.note = n.id_note AND (n.spent IS NULL OR n.spent = 0)")?;
|
&format!("SELECT id_note, witness FROM {}_witnesses w, received_notes n WHERE w.height = ?1 AND w.note = n.id_note AND (n.spent IS NULL OR n.spent = 0)", shielded_pool))?;
|
||||||
let ws = statement.query_map(params![height], |row| {
|
let ws = statement.query_map(params![height], |row| {
|
||||||
let id_note: u32 = row.get(0)?;
|
let id_note: u32 = row.get(0)?;
|
||||||
let witness: Vec<u8> = row.get(1)?;
|
let witness: Vec<u8> = row.get(1)?;
|
||||||
Ok(Witness::read(id_note, &*witness).unwrap())
|
Ok(sync::Witness::read(id_note, &*witness).unwrap())
|
||||||
})?;
|
})?;
|
||||||
let mut witnesses: Vec<Witness> = vec![];
|
let mut witnesses = vec![];
|
||||||
for w in ws {
|
for w in ws {
|
||||||
witnesses.push(w?);
|
witnesses.push(w?);
|
||||||
}
|
}
|
||||||
(tree, witnesses)
|
(tree, witnesses)
|
||||||
}
|
}
|
||||||
None => (CTree::new(), vec![]),
|
None => (sync::CTree::new(), vec![]),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -493,6 +578,33 @@ impl DbAdapter {
|
||||||
Ok(nfs)
|
Ok(nfs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_unspent_nullifiers(
|
||||||
|
&self,
|
||||||
|
account: u32,
|
||||||
|
) -> anyhow::Result<Vec<ReceivedNoteShort>> {
|
||||||
|
let sql = "SELECT id_note, nf, value FROM received_notes WHERE account = ?1 AND (spent IS NULL OR spent = 0)";
|
||||||
|
let mut statement = self.connection.prepare(sql)?;
|
||||||
|
let nfs_res = statement.query_map(params![account], |row| {
|
||||||
|
let id: u32 = row.get(0)?;
|
||||||
|
let nf: Vec<u8> = row.get(1)?;
|
||||||
|
let value: i64 = row.get(2)?;
|
||||||
|
let nf: [u8; 32] = nf.try_into().unwrap();
|
||||||
|
let nf = Nf(nf);
|
||||||
|
Ok(ReceivedNoteShort {
|
||||||
|
id,
|
||||||
|
account,
|
||||||
|
nf,
|
||||||
|
value: value as u64,
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
let mut nfs = vec![];
|
||||||
|
for n in nfs_res {
|
||||||
|
let n = n?;
|
||||||
|
nfs.push(n);
|
||||||
|
}
|
||||||
|
Ok(nfs)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_nullifiers_raw(&self) -> anyhow::Result<Vec<(u32, u64, Vec<u8>)>> {
|
pub fn get_nullifiers_raw(&self) -> anyhow::Result<Vec<(u32, u64, Vec<u8>)>> {
|
||||||
let mut statement = self
|
let mut statement = self
|
||||||
.connection
|
.connection
|
||||||
|
@ -510,6 +622,7 @@ impl DbAdapter {
|
||||||
Ok(v)
|
Ok(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Depends on the type of witness - Should it returned any spendable note? sapling or orchard
|
||||||
pub fn get_spendable_notes(
|
pub fn get_spendable_notes(
|
||||||
&self,
|
&self,
|
||||||
account: u32,
|
account: u32,
|
||||||
|
@ -590,6 +703,10 @@ impl DbAdapter {
|
||||||
"DELETE FROM sapling_witnesses WHERE height < ?1",
|
"DELETE FROM sapling_witnesses WHERE height < ?1",
|
||||||
params![min_height],
|
params![min_height],
|
||||||
)?;
|
)?;
|
||||||
|
transaction.execute(
|
||||||
|
"DELETE FROM orchard_witnesses WHERE height < ?1",
|
||||||
|
params![min_height],
|
||||||
|
)?;
|
||||||
transaction.execute("DELETE FROM blocks WHERE height < ?1", params![min_height])?;
|
transaction.execute("DELETE FROM blocks WHERE height < ?1", params![min_height])?;
|
||||||
transaction.commit()?;
|
transaction.commit()?;
|
||||||
}
|
}
|
||||||
|
@ -634,6 +751,7 @@ impl DbAdapter {
|
||||||
Ok(contacts)
|
Ok(contacts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Orchard diversifiers have a different space
|
||||||
pub fn get_diversifier(&self, account: u32) -> anyhow::Result<DiversifierIndex> {
|
pub fn get_diversifier(&self, account: u32) -> anyhow::Result<DiversifierIndex> {
|
||||||
let diversifier_index = self
|
let diversifier_index = self
|
||||||
.connection
|
.connection
|
||||||
|
@ -652,6 +770,21 @@ impl DbAdapter {
|
||||||
Ok(DiversifierIndex(diversifier_index))
|
Ok(DiversifierIndex(diversifier_index))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: See get_diversifier
|
||||||
|
pub fn store_diversifier(
|
||||||
|
&self,
|
||||||
|
account: u32,
|
||||||
|
diversifier_index: &DiversifierIndex,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let diversifier_bytes = diversifier_index.0.to_vec();
|
||||||
|
self.connection.execute(
|
||||||
|
"INSERT INTO diversifiers(account, diversifier_index) VALUES (?1, ?2) ON CONFLICT \
|
||||||
|
(account) DO UPDATE SET diversifier_index = excluded.diversifier_index",
|
||||||
|
params![account, diversifier_bytes],
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_account_info(&self, account: u32) -> anyhow::Result<AccountData> {
|
pub fn get_account_info(&self, account: u32) -> anyhow::Result<AccountData> {
|
||||||
let account_data = self
|
let account_data = self
|
||||||
.connection
|
.connection
|
||||||
|
@ -679,20 +812,6 @@ impl DbAdapter {
|
||||||
Ok(account_data)
|
Ok(account_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn store_diversifier(
|
|
||||||
&self,
|
|
||||||
account: u32,
|
|
||||||
diversifier_index: &DiversifierIndex,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let diversifier_bytes = diversifier_index.0.to_vec();
|
|
||||||
self.connection.execute(
|
|
||||||
"INSERT INTO diversifiers(account, diversifier_index) VALUES (?1, ?2) ON CONFLICT \
|
|
||||||
(account) DO UPDATE SET diversifier_index = excluded.diversifier_index",
|
|
||||||
params![account, diversifier_bytes],
|
|
||||||
)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_taddr(&self, account: u32) -> anyhow::Result<Option<String>> {
|
pub fn get_taddr(&self, account: u32) -> anyhow::Result<Option<String>> {
|
||||||
let address = self
|
let address = self
|
||||||
.connection
|
.connection
|
||||||
|
@ -813,6 +932,8 @@ impl DbAdapter {
|
||||||
self.connection.execute("DELETE FROM received_notes", [])?;
|
self.connection.execute("DELETE FROM received_notes", [])?;
|
||||||
self.connection
|
self.connection
|
||||||
.execute("DELETE FROM sapling_witnesses", [])?;
|
.execute("DELETE FROM sapling_witnesses", [])?;
|
||||||
|
self.connection
|
||||||
|
.execute("DELETE FROM orchard_witnesses", [])?;
|
||||||
self.connection.execute("DELETE FROM transactions", [])?;
|
self.connection.execute("DELETE FROM transactions", [])?;
|
||||||
self.connection.execute("DELETE FROM messages", [])?;
|
self.connection.execute("DELETE FROM messages", [])?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1147,7 +1268,7 @@ pub struct AccountData {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::db::{DbAdapter, ReceivedNote, DEFAULT_DB_PATH};
|
use crate::db::{DbAdapter, DEFAULT_DB_PATH, ReceivedNote};
|
||||||
use crate::commitment::{CTree, Witness};
|
use crate::commitment::{CTree, Witness};
|
||||||
use zcash_params::coin::CoinType;
|
use zcash_params::coin::CoinType;
|
||||||
|
|
||||||
|
@ -1158,7 +1279,7 @@ mod tests {
|
||||||
db.trim_to_height(0).unwrap();
|
db.trim_to_height(0).unwrap();
|
||||||
|
|
||||||
let db_tx = db.begin_transaction().unwrap();
|
let db_tx = db.begin_transaction().unwrap();
|
||||||
DbAdapter::store_block(&db_tx, 1, &[0u8; 32], 0, &CTree::new()).unwrap();
|
DbAdapter::store_block(&db_tx, 1, &[0u8; 32], 0, &CTree::new(), None).unwrap();
|
||||||
let id_tx = DbAdapter::store_transaction(&[0; 32], 1, 1, 0, 20, &db_tx).unwrap();
|
let id_tx = DbAdapter::store_transaction(&[0; 32], 1, 1, 0, 20, &db_tx).unwrap();
|
||||||
DbAdapter::store_received_note(
|
DbAdapter::store_received_note(
|
||||||
&ReceivedNote {
|
&ReceivedNote {
|
||||||
|
@ -1169,6 +1290,7 @@ mod tests {
|
||||||
value: 0,
|
value: 0,
|
||||||
rcm: vec![],
|
rcm: vec![],
|
||||||
nf: vec![],
|
nf: vec![],
|
||||||
|
rho: None,
|
||||||
spent: None,
|
spent: None,
|
||||||
},
|
},
|
||||||
id_tx,
|
id_tx,
|
||||||
|
|
|
@ -177,8 +177,26 @@ pub fn init_db(connection: &Connection) -> anyhow::Result<()> {
|
||||||
connection.execute("ALTER TABLE messages ADD id_tx INTEGER", [])?;
|
connection.execute("ALTER TABLE messages ADD id_tx INTEGER", [])?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if version != 4 {
|
if version < 5 {
|
||||||
update_schema_version(connection, 4)?;
|
connection.execute("ALTER TABLE blocks ADD orchard_tree BLOB", [])?;
|
||||||
|
connection.execute("ALTER TABLE received_notes ADD rho BLOB", [])?;
|
||||||
|
connection.execute(
|
||||||
|
"CREATE TABLE IF NOT EXISTS orchard_witnesses (
|
||||||
|
id_witness INTEGER PRIMARY KEY,
|
||||||
|
note INTEGER NOT NULL,
|
||||||
|
height INTEGER NOT NULL,
|
||||||
|
witness BLOB NOT NULL,
|
||||||
|
CONSTRAINT witness_height UNIQUE (note, height))",
|
||||||
|
[],
|
||||||
|
)?;
|
||||||
|
connection.execute(
|
||||||
|
"CREATE INDEX IF NOT EXISTS i_orchard_witness ON orchard_witnesses(height)",
|
||||||
|
[],
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if version != 5 {
|
||||||
|
update_schema_version(connection, 5)?;
|
||||||
log::info!("Database migrated");
|
log::info!("Database migrated");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -96,7 +96,9 @@ impl FountainCodes {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn put_drop(drop: &str) -> anyhow::Result<Option<Vec<u8>>> {
|
impl RaptorQDrops {
|
||||||
|
pub fn put_drop(drop: &str) -> anyhow::Result<Option<Vec<u8>>> {
|
||||||
let mut fc = RAPTORQ.lock().unwrap();
|
let mut fc = RAPTORQ.lock().unwrap();
|
||||||
fc.put_drop(drop)
|
fc.put_drop(drop)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ use std::io::Read;
|
||||||
use std::ops::AddAssign;
|
use std::ops::AddAssign;
|
||||||
use zcash_params::GENERATORS;
|
use zcash_params::GENERATORS;
|
||||||
use zcash_primitives::constants::PEDERSEN_HASH_CHUNKS_PER_GENERATOR;
|
use zcash_primitives::constants::PEDERSEN_HASH_CHUNKS_PER_GENERATOR;
|
||||||
|
use crate::Hash;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref GENERATORS_EXP: Vec<ExtendedNielsPoint> = read_generators_bin();
|
pub static ref GENERATORS_EXP: Vec<ExtendedNielsPoint> = read_generators_bin();
|
||||||
|
@ -31,6 +32,7 @@ fn read_generators_bin() -> Vec<ExtendedNielsPoint> {
|
||||||
|
|
||||||
macro_rules! accumulate_scalar {
|
macro_rules! accumulate_scalar {
|
||||||
($acc: ident, $cur: ident, $x: expr) => {
|
($acc: ident, $cur: ident, $x: expr) => {
|
||||||
|
// println!("accumulate_scalar {}", $x);
|
||||||
let mut tmp = $cur;
|
let mut tmp = $cur;
|
||||||
if $x & 1 != 0 {
|
if $x & 1 != 0 {
|
||||||
tmp.add_assign(&$cur);
|
tmp.add_assign(&$cur);
|
||||||
|
@ -47,8 +49,6 @@ macro_rules! accumulate_scalar {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Hash = [u8; 32];
|
|
||||||
|
|
||||||
pub fn pedersen_hash(depth: u8, left: &Hash, right: &Hash) -> Hash {
|
pub fn pedersen_hash(depth: u8, left: &Hash, right: &Hash) -> Hash {
|
||||||
let p = pedersen_hash_inner(depth, left, right);
|
let p = pedersen_hash_inner(depth, left, right);
|
||||||
|
|
||||||
|
@ -68,11 +68,9 @@ pub fn pedersen_hash_inner(depth: u8, left: &Hash, right: &Hash) -> ExtendedPoin
|
||||||
let b = depth >> 3;
|
let b = depth >> 3;
|
||||||
accumulate_scalar!(acc, cur, a);
|
accumulate_scalar!(acc, cur, a);
|
||||||
cur = cur.double().double().double();
|
cur = cur.double().double().double();
|
||||||
// println!("{}", hex::encode(acc.to_bytes()));
|
|
||||||
|
|
||||||
accumulate_scalar!(acc, cur, b);
|
accumulate_scalar!(acc, cur, b);
|
||||||
cur = cur.double().double().double();
|
cur = cur.double().double().double();
|
||||||
// println!("{}", hex::encode(acc.to_bytes()));
|
|
||||||
|
|
||||||
let mut i_generator = 0;
|
let mut i_generator = 0;
|
||||||
let mut chunks_remaining = PEDERSEN_HASH_CHUNKS_PER_GENERATOR - 3;
|
let mut chunks_remaining = PEDERSEN_HASH_CHUNKS_PER_GENERATOR - 3;
|
||||||
|
@ -104,8 +102,6 @@ pub fn pedersen_hash_inner(depth: u8, left: &Hash, right: &Hash) -> ExtendedPoin
|
||||||
|
|
||||||
chunks_remaining -= 1;
|
chunks_remaining -= 1;
|
||||||
if chunks_remaining == 0 {
|
if chunks_remaining == 0 {
|
||||||
// println!("++ {}", hex::encode(acc.to_bytes()));
|
|
||||||
|
|
||||||
result += generator_multiplication(&acc, &GENERATORS_EXP, i_generator);
|
result += generator_multiplication(&acc, &GENERATORS_EXP, i_generator);
|
||||||
|
|
||||||
i_generator += 1;
|
i_generator += 1;
|
||||||
|
|
12
src/lib.rs
12
src/lib.rs
|
@ -72,6 +72,8 @@ const LWD_URL: &str = "https://mainnet.lightwalletd.com:9067";
|
||||||
// YCash
|
// YCash
|
||||||
// pub const LWD_URL: &str = "https://lite.ycash.xyz:9067";
|
// pub const LWD_URL: &str = "https://lite.ycash.xyz:9067";
|
||||||
|
|
||||||
|
pub type Hash = [u8; 32];
|
||||||
|
|
||||||
mod builder;
|
mod builder;
|
||||||
mod chain;
|
mod chain;
|
||||||
mod coinconfig;
|
mod coinconfig;
|
||||||
|
@ -80,6 +82,8 @@ mod contact;
|
||||||
mod db;
|
mod db;
|
||||||
mod fountain;
|
mod fountain;
|
||||||
mod hash;
|
mod hash;
|
||||||
|
pub mod sync;
|
||||||
|
pub mod sapling;
|
||||||
mod key;
|
mod key;
|
||||||
mod key2;
|
mod key2;
|
||||||
mod mempool;
|
mod mempool;
|
||||||
|
@ -115,11 +119,11 @@ pub use crate::chain::{
|
||||||
ChainError,
|
ChainError,
|
||||||
};
|
};
|
||||||
pub use crate::coinconfig::{
|
pub use crate::coinconfig::{
|
||||||
init_coin, set_active, set_active_account, set_coin_lwd_url, CoinConfig,
|
CoinConfig, init_coin, set_active, set_active_account, set_coin_lwd_url,
|
||||||
|
COIN_CONFIG,
|
||||||
};
|
};
|
||||||
pub use crate::db::{AccountData, AccountInfo, AccountRec, DbAdapter, TxRec};
|
pub use crate::db::{AccountData, AccountInfo, AccountRec, DbAdapter, TxRec, DbAdapterBuilder};
|
||||||
// pub use crate::fountain::FountainCodes;
|
pub use crate::fountain::{FountainCodes, RaptorQDrops};
|
||||||
pub use crate::hash::Hash;
|
|
||||||
pub use crate::key::KeyHelpers;
|
pub use crate::key::KeyHelpers;
|
||||||
pub use crate::lw_rpc::compact_tx_streamer_client::CompactTxStreamerClient;
|
pub use crate::lw_rpc::compact_tx_streamer_client::CompactTxStreamerClient;
|
||||||
pub use crate::lw_rpc::*;
|
pub use crate::lw_rpc::*;
|
||||||
|
|
|
@ -15,10 +15,7 @@ use std::sync::Mutex;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use warp_api_ffi::api::payment::{Recipient, RecipientMemo};
|
use warp_api_ffi::api::payment::{Recipient, RecipientMemo};
|
||||||
use warp_api_ffi::api::payment_uri::PaymentURI;
|
use warp_api_ffi::api::payment_uri::PaymentURI;
|
||||||
use warp_api_ffi::{
|
use warp_api_ffi::{get_best_server, AccountData, AccountInfo, AccountRec, CoinConfig, KeyPack, Tx, TxRec, RaptorQDrops};
|
||||||
derive_zip32, get_best_server, AccountData, AccountInfo, AccountRec, CoinConfig, KeyPack,
|
|
||||||
RaptorQDrops, Tx, TxRec,
|
|
||||||
};
|
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref SYNC_CANCELED: Mutex<bool> = Mutex::new(false);
|
static ref SYNC_CANCELED: Mutex<bool> = Mutex::new(false);
|
||||||
|
@ -102,7 +99,6 @@ async fn main() -> anyhow::Result<()> {
|
||||||
merge_data,
|
merge_data,
|
||||||
derive_keys,
|
derive_keys,
|
||||||
instant_sync,
|
instant_sync,
|
||||||
trial_decrypt,
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.attach(AdHoc::config::<Config>())
|
.attach(AdHoc::config::<Config>())
|
||||||
|
@ -310,7 +306,7 @@ pub fn split_data(id: u32, data: String) -> Result<Json<RaptorQDrops>, Error> {
|
||||||
|
|
||||||
#[post("/merge?<data>")]
|
#[post("/merge?<data>")]
|
||||||
pub fn merge_data(data: String) -> Result<String, Error> {
|
pub fn merge_data(data: String) -> Result<String, Error> {
|
||||||
let result = warp_api_ffi::put_drop(&data)?
|
let result = warp_api_ffi::RaptorQDrops::put_drop(&data)?
|
||||||
.map(|data| hex::encode(&data))
|
.map(|data| hex::encode(&data))
|
||||||
.unwrap_or(String::new());
|
.unwrap_or(String::new());
|
||||||
Ok(result)
|
Ok(result)
|
||||||
|
@ -333,21 +329,6 @@ pub fn derive_keys(
|
||||||
Ok(Json(result))
|
Ok(Json(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/trial_decrypt?<height>&<cmu>&<epk>&<ciphertext>")]
|
|
||||||
pub async fn trial_decrypt(
|
|
||||||
height: u32,
|
|
||||||
cmu: String,
|
|
||||||
epk: String,
|
|
||||||
ciphertext: String,
|
|
||||||
) -> Result<String, Error> {
|
|
||||||
let epk = hex::decode(&epk)?;
|
|
||||||
let cmu = hex::decode(&cmu)?;
|
|
||||||
let ciphertext = hex::decode(&ciphertext)?;
|
|
||||||
let note = warp_api_ffi::api::sync::trial_decrypt(height, &cmu, &epk, &ciphertext)?;
|
|
||||||
log::info!("{:?}", note);
|
|
||||||
Ok(note.is_some().to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[post("/instant_sync")]
|
#[post("/instant_sync")]
|
||||||
pub async fn instant_sync() -> Result<(), Error> {
|
pub async fn instant_sync() -> Result<(), Error> {
|
||||||
let c = CoinConfig::get_active();
|
let c = CoinConfig::get_active();
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{BufReader, BufWriter, Read, Write};
|
||||||
|
use byteorder::{LE, ReadBytesExt, WriteBytesExt};
|
||||||
|
use tonic::Request;
|
||||||
|
use prost::Message;
|
||||||
|
use zcash_client_backend::encoding::{decode_extended_full_viewing_key, decode_extended_spending_key, encode_extended_full_viewing_key, encode_payment_address};
|
||||||
|
use zcash_primitives::consensus::{Network, NetworkUpgrade, Parameters};
|
||||||
|
use zcash_primitives::sapling::note_encryption::SaplingDomain;
|
||||||
|
use zcash_primitives::zip32::ExtendedFullViewingKey;
|
||||||
|
use warp_api_ffi::{BlockId, BlockRange, ChainSpec, COIN_CONFIG, CoinConfig, CompactBlock, connect_lightwalletd, DbAdapter, DbAdapterBuilder, derive_zip32, init_coin};
|
||||||
|
use warp_api_ffi::sapling::{DecryptedSaplingNote, SaplingDecrypter, SaplingHasher, SaplingViewKey};
|
||||||
|
use warp_api_ffi::sync::{WarpProcessor, Synchronizer, CTree};
|
||||||
|
|
||||||
|
type SaplingSynchronizer = Synchronizer<Network, SaplingDomain<Network>, SaplingViewKey, DecryptedSaplingNote,
|
||||||
|
SaplingDecrypter<Network>, SaplingHasher>;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
async fn write_block_file() {
|
||||||
|
init_coin(1, "yec-new.db").unwrap();
|
||||||
|
let coin = COIN_CONFIG[1].lock().unwrap();
|
||||||
|
let mut client = connect_lightwalletd("https://lite.ycash.xyz:9067").await.unwrap();
|
||||||
|
let network = coin.chain.network();
|
||||||
|
let start = u32::from(network.activation_height(NetworkUpgrade::Sapling).unwrap()) + 1;
|
||||||
|
let end = client.get_latest_block(Request::new(ChainSpec {})).await.unwrap().into_inner();
|
||||||
|
let end = end.height as u32;
|
||||||
|
|
||||||
|
let mut blocks = client.get_block_range(Request::new(BlockRange {
|
||||||
|
start: Some(BlockId { height: start as u64, hash: vec![] }),
|
||||||
|
end: Some(BlockId { height: end as u64, hash: vec![] }),
|
||||||
|
spam_filter_threshold: 0
|
||||||
|
})).await.unwrap().into_inner();
|
||||||
|
|
||||||
|
let file = File::create("ycash.bin").unwrap();
|
||||||
|
let mut writer = BufWriter::new(file);
|
||||||
|
while let Some(block) = blocks.message().await.unwrap() {
|
||||||
|
println!("{}", block.height);
|
||||||
|
let mut buf = prost::bytes::BytesMut::new();
|
||||||
|
block.encode(&mut buf).unwrap();
|
||||||
|
writer.write_u32::<LE>(buf.len() as u32).unwrap();
|
||||||
|
writer.write_all(&buf).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_block_file(coin: &CoinConfig, fvk: ExtendedFullViewingKey) {
|
||||||
|
let network = coin.chain.network();
|
||||||
|
let file = File::open("/home/hanh/ycash.bin").unwrap();
|
||||||
|
let mut reader = BufReader::new(file);
|
||||||
|
|
||||||
|
let db_builder = DbAdapterBuilder { coin_type: coin.coin_type, db_path: coin.db_path.as_ref().unwrap().to_owned() };
|
||||||
|
let mut synchronizer = SaplingSynchronizer {
|
||||||
|
decrypter: SaplingDecrypter::new(*network),
|
||||||
|
warper: WarpProcessor::new(SaplingHasher::default()),
|
||||||
|
vks: vec![SaplingViewKey {
|
||||||
|
account: 1,
|
||||||
|
fvk: fvk.clone(),
|
||||||
|
ivk: fvk.fvk.vk.ivk()
|
||||||
|
}],
|
||||||
|
tree: CTree::new(),
|
||||||
|
witnesses: vec![],
|
||||||
|
|
||||||
|
db: db_builder.clone(),
|
||||||
|
shielded_pool: "sapling".to_string(),
|
||||||
|
|
||||||
|
note_position: 0,
|
||||||
|
nullifiers: HashMap::new(),
|
||||||
|
_phantom: Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
synchronizer.initialize().unwrap();
|
||||||
|
|
||||||
|
let mut blocks = vec![];
|
||||||
|
let mut height = 0;
|
||||||
|
let mut hash = [0u8; 32];
|
||||||
|
let mut time = 0;
|
||||||
|
while let Ok(len) = reader.read_u32::<LE>() {
|
||||||
|
let mut buf = vec![0u8; len as usize];
|
||||||
|
reader.read_exact(&mut buf).unwrap();
|
||||||
|
let cb: CompactBlock = CompactBlock::decode(&*buf).unwrap();
|
||||||
|
height = cb.height;
|
||||||
|
hash.copy_from_slice(&cb.hash);
|
||||||
|
time = cb.time;
|
||||||
|
blocks.push(cb);
|
||||||
|
if height % 100_000 == 0 {
|
||||||
|
synchronizer.process(blocks).unwrap();
|
||||||
|
blocks = vec![];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
synchronizer.process(blocks).unwrap();
|
||||||
|
let db = db_builder.build().unwrap();
|
||||||
|
DbAdapter::store_block2(height as u32, &hash, time, &synchronizer.tree, None, &db.connection).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
env_logger::init();
|
||||||
|
init_coin(1, "yec-new.db").unwrap();
|
||||||
|
let coin = COIN_CONFIG[1].lock().unwrap();
|
||||||
|
let network = coin.chain.network();
|
||||||
|
let _ = dotenv::dotenv();
|
||||||
|
let seed_str = dotenv::var("SEED").unwrap();
|
||||||
|
let kp = derive_zip32(&network, &seed_str, 0, 0, None).unwrap();
|
||||||
|
let zk = kp.z_key.clone();
|
||||||
|
let sk = decode_extended_spending_key(network.hrp_sapling_extended_spending_key(), &zk).unwrap().unwrap();
|
||||||
|
|
||||||
|
let fvk = ExtendedFullViewingKey::from(&sk);
|
||||||
|
let fvk_str = encode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), &fvk);
|
||||||
|
let (_, pa) = fvk.default_address();
|
||||||
|
let address = encode_payment_address(network.hrp_sapling_payment_address(), &pa);
|
||||||
|
let db_builder = DbAdapterBuilder { coin_type: coin.coin_type, db_path: coin.db_path.as_ref().unwrap().to_owned() };
|
||||||
|
let db = db_builder.build().unwrap();
|
||||||
|
db.store_account("test", Some(&seed_str), 0, Some(&zk), &fvk_str, &address).unwrap();
|
||||||
|
|
||||||
|
// write_block_file().await;
|
||||||
|
read_block_file(&coin, fvk);
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
use std::io::Read;
|
||||||
|
use group::GroupEncoding;
|
||||||
|
use jubjub::{ExtendedNielsPoint, ExtendedPoint, SubgroupPoint};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use zcash_params::GENERATORS;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref GENERATORS_EXP: Vec<ExtendedNielsPoint> = read_generators_bin();
|
||||||
|
}
|
||||||
|
|
||||||
|
mod hash;
|
||||||
|
mod note;
|
||||||
|
|
||||||
|
pub use note::{SaplingDecrypter, SaplingViewKey, DecryptedSaplingNote};
|
||||||
|
pub use hash::SaplingHasher;
|
||||||
|
|
||||||
|
fn read_generators_bin() -> Vec<ExtendedNielsPoint> {
|
||||||
|
let mut generators_bin = GENERATORS;
|
||||||
|
let mut gens: Vec<ExtendedNielsPoint> = 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_exact(&mut bb).unwrap();
|
||||||
|
let p = ExtendedPoint::from(SubgroupPoint::from_bytes_unchecked(&bb).unwrap())
|
||||||
|
.to_niels();
|
||||||
|
gens.push(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gens
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
use ff::PrimeField;
|
||||||
|
use group::Curve;
|
||||||
|
use jubjub::{ExtendedPoint, Fr};
|
||||||
|
use zcash_primitives::constants::PEDERSEN_HASH_CHUNKS_PER_GENERATOR;
|
||||||
|
use crate::sync::{Hasher, Node};
|
||||||
|
use super::GENERATORS_EXP;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn accumulate_scalar(acc: &mut Fr, cur: &mut Fr, x: u8) {
|
||||||
|
let mut tmp = *cur;
|
||||||
|
if x & 1 != 0 {
|
||||||
|
tmp += *cur;
|
||||||
|
}
|
||||||
|
*cur = cur.double();
|
||||||
|
if x & 2 != 0 {
|
||||||
|
tmp += *cur;
|
||||||
|
}
|
||||||
|
if x & 4 != 0 {
|
||||||
|
tmp = tmp.neg();
|
||||||
|
}
|
||||||
|
|
||||||
|
*acc += tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accumulate_generator(acc: &Fr, idx_generator: u32) -> ExtendedPoint {
|
||||||
|
let acc_bytes = acc.to_repr();
|
||||||
|
|
||||||
|
let mut tmp = ExtendedPoint::identity();
|
||||||
|
for (i, &j) in acc_bytes.iter().enumerate() {
|
||||||
|
let offset = (idx_generator * 32 + i as u32) * 256 + j as u32;
|
||||||
|
let x = GENERATORS_EXP[offset as usize];
|
||||||
|
tmp += x;
|
||||||
|
}
|
||||||
|
tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hash_combine(depth: u8, left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] {
|
||||||
|
let mut hash = ExtendedPoint::identity();
|
||||||
|
let mut acc = Fr::zero();
|
||||||
|
let mut cur = Fr::one();
|
||||||
|
|
||||||
|
let a = depth & 7;
|
||||||
|
let b = depth >> 3;
|
||||||
|
|
||||||
|
accumulate_scalar(&mut acc, &mut cur, a);
|
||||||
|
cur = cur.double().double().double();
|
||||||
|
accumulate_scalar(&mut acc, &mut cur, b);
|
||||||
|
cur = cur.double().double().double();
|
||||||
|
|
||||||
|
// Shift right by 1 bit and overwrite the 256th bit of left
|
||||||
|
let mut left = *left;
|
||||||
|
let mut right = *right;
|
||||||
|
|
||||||
|
// move by 1 bit to fill the missing 256th bit of left
|
||||||
|
let mut carry = 0;
|
||||||
|
for i in (0..32).rev() {
|
||||||
|
let c = right[i] & 1;
|
||||||
|
right[i] = right[i] >> 1 | carry << 7;
|
||||||
|
carry = c;
|
||||||
|
}
|
||||||
|
left[31] &= 0x7F;
|
||||||
|
left[31] |= carry << 7; // move the first bit of right into 256th of left
|
||||||
|
|
||||||
|
// we have 255*2/3 = 170 chunks
|
||||||
|
let mut bit_offset = 0;
|
||||||
|
let mut byte_offset = 0;
|
||||||
|
let mut idx_generator = 0;
|
||||||
|
for i in 0..170 {
|
||||||
|
let mut v = if byte_offset < 31 {
|
||||||
|
left[byte_offset] as u16 | (left[byte_offset + 1] as u16) << 8
|
||||||
|
} else if byte_offset == 31 {
|
||||||
|
left[31] as u16 | (right[0] as u16) << 8
|
||||||
|
} else if byte_offset < 63 {
|
||||||
|
right[byte_offset - 32] as u16 | (right[byte_offset - 31] as u16) << 8
|
||||||
|
} else {
|
||||||
|
right[byte_offset - 32] as u16
|
||||||
|
};
|
||||||
|
v = v >> bit_offset & 0x07; // keep 3 bits
|
||||||
|
accumulate_scalar(&mut acc, &mut cur, v as u8);
|
||||||
|
|
||||||
|
if (i+3) % PEDERSEN_HASH_CHUNKS_PER_GENERATOR as u32 == 0 {
|
||||||
|
hash += accumulate_generator(&acc, idx_generator);
|
||||||
|
idx_generator += 1;
|
||||||
|
acc = Fr::zero();
|
||||||
|
cur = Fr::one();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cur = cur.double().double().double(); // 2^4 * cur
|
||||||
|
}
|
||||||
|
bit_offset += 3;
|
||||||
|
if bit_offset >= 8 {
|
||||||
|
byte_offset += bit_offset / 8;
|
||||||
|
bit_offset %= 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hash += accumulate_generator(&acc, idx_generator);
|
||||||
|
|
||||||
|
let hash = hash
|
||||||
|
.to_affine()
|
||||||
|
.get_u()
|
||||||
|
.to_repr();
|
||||||
|
hash
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct SaplingHasher {}
|
||||||
|
|
||||||
|
impl Hasher for SaplingHasher {
|
||||||
|
fn uncommited_node() -> Node {
|
||||||
|
[0u8; 32]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn node_combine(&self, depth: u8, left: &Node, right: &Node) -> Node {
|
||||||
|
hash_combine(depth, left, right)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use rand::RngCore;
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
use crate::pedersen_hash;
|
||||||
|
use crate::sapling::hash::hash_combine;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hash1() {
|
||||||
|
let depth = 8;
|
||||||
|
let sa = "767a9a7e989289efdfa69c4c8e985c31f3c2c0353f20a80f572854206f077f86";
|
||||||
|
let sb = "944c46945a9e7a0a753850bd90f69d44ac884b60244a9f8eacf3a2aeddd08d6e";
|
||||||
|
let a: [u8; 32] = hex::decode(sa).unwrap().try_into().unwrap();
|
||||||
|
let b: [u8; 32] = hex::decode(sb).unwrap().try_into().unwrap();
|
||||||
|
println!("A: {}", hex::encode(a));
|
||||||
|
println!("B: {}", hex::encode(b));
|
||||||
|
|
||||||
|
let hash = pedersen_hash(depth, &a, &b);
|
||||||
|
let hash2 = hash_combine(depth, &a, &b);
|
||||||
|
println!("Reference Hash: {}", hex::encode(hash));
|
||||||
|
println!("This Hash: {}", hex::encode(hash2));
|
||||||
|
// need to expose repr for this check
|
||||||
|
assert_eq!(hash, hash2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hash_random() {
|
||||||
|
let mut rng = OsRng;
|
||||||
|
for _ in 0..1000 {
|
||||||
|
let depth = (rng.next_u32() % 50) as u8;
|
||||||
|
let mut a = [0u8; 32];
|
||||||
|
let mut b = [0u8; 32];
|
||||||
|
rng.fill_bytes(&mut a);
|
||||||
|
rng.fill_bytes(&mut b);
|
||||||
|
println!("A: {}", hex::encode(a));
|
||||||
|
println!("B: {}", hex::encode(b));
|
||||||
|
|
||||||
|
let hash = pedersen_hash(depth, &a, &b);
|
||||||
|
let hash2 = hash_combine(depth, &a, &b);
|
||||||
|
println!("Reference Hash: {}", hex::encode(hash));
|
||||||
|
println!("This Hash: {}", hex::encode(hash2));
|
||||||
|
// need to expose repr for this check
|
||||||
|
assert_eq!(hash, hash2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use ff::PrimeField;
|
||||||
|
use zcash_note_encryption::Domain;
|
||||||
|
use zcash_primitives::consensus::{BlockHeight, Parameters};
|
||||||
|
use zcash_primitives::sapling::note_encryption::SaplingDomain;
|
||||||
|
use zcash_primitives::sapling::{PaymentAddress, SaplingIvk};
|
||||||
|
use zcash_primitives::zip32::ExtendedFullViewingKey;
|
||||||
|
use crate::chain::Nf;
|
||||||
|
use crate::CompactTx;
|
||||||
|
use crate::db::ReceivedNote;
|
||||||
|
use crate::sync::Node;
|
||||||
|
use crate::sync::{CompactOutputBytes, DecryptedNote, OutputPosition, TrialDecrypter, ViewKey};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SaplingViewKey {
|
||||||
|
pub account: u32,
|
||||||
|
pub fvk: ExtendedFullViewingKey,
|
||||||
|
pub ivk: SaplingIvk,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <P: Parameters> ViewKey<SaplingDomain<P>> for SaplingViewKey {
|
||||||
|
fn account(&self) -> u32 { self.account }
|
||||||
|
fn ivk(&self) -> <SaplingDomain<P> as Domain>::IncomingViewingKey {
|
||||||
|
self.ivk.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DecryptedSaplingNote {
|
||||||
|
pub vk: SaplingViewKey,
|
||||||
|
pub note: zcash_primitives::sapling::Note,
|
||||||
|
pub pa: PaymentAddress,
|
||||||
|
pub output_position: OutputPosition,
|
||||||
|
pub cmx: Node,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <P: Parameters> DecryptedNote<SaplingDomain<P>, SaplingViewKey> for DecryptedSaplingNote {
|
||||||
|
fn from_parts(vk: SaplingViewKey, note: zcash_primitives::sapling::Note, pa: PaymentAddress, output_position: OutputPosition, cmx: Node) -> Self {
|
||||||
|
DecryptedSaplingNote {
|
||||||
|
vk,
|
||||||
|
note,
|
||||||
|
pa,
|
||||||
|
output_position,
|
||||||
|
cmx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn position(&self, block_offset: usize) -> usize {
|
||||||
|
block_offset + self.output_position.position_in_block
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmx(&self) -> Node {
|
||||||
|
self.cmx
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_received_note(&self, position: u64) -> ReceivedNote {
|
||||||
|
let viewing_key = &self.vk.fvk.fvk.vk;
|
||||||
|
ReceivedNote {
|
||||||
|
account: self.vk.account,
|
||||||
|
height: self.output_position.height,
|
||||||
|
output_index: self.output_position.output_index as u32,
|
||||||
|
diversifier: self.pa.diversifier().0.to_vec(),
|
||||||
|
value: self.note.value,
|
||||||
|
rcm: self.note.rcm().to_repr().to_vec(),
|
||||||
|
nf: self.note.nf(viewing_key, position).to_vec(),
|
||||||
|
rho: None,
|
||||||
|
spent: None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SaplingDecrypter<N> {
|
||||||
|
pub network: N,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <N> SaplingDecrypter<N> {
|
||||||
|
pub fn new(network: N) -> Self {
|
||||||
|
SaplingDecrypter {
|
||||||
|
network,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <N: Parameters> TrialDecrypter<N, SaplingDomain<N>, SaplingViewKey, DecryptedSaplingNote> for SaplingDecrypter<N> {
|
||||||
|
fn domain(&self, height: BlockHeight) -> SaplingDomain<N> {
|
||||||
|
SaplingDomain::<N>::for_height(self.network.clone(), height)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spends(&self, vtx: &CompactTx) -> Vec<Nf> {
|
||||||
|
vtx.spends.iter().map(|co| {
|
||||||
|
let nf: [u8; 32] = co.nf.clone().try_into().unwrap();
|
||||||
|
Nf(nf)
|
||||||
|
}).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn outputs(&self, vtx: &CompactTx) -> Vec<CompactOutputBytes> {
|
||||||
|
vtx.outputs.iter().map(|co| co.into()).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -210,6 +210,7 @@ pub async fn sync_async(
|
||||||
diversifier: n.pa.diversifier().0.to_vec(),
|
diversifier: n.pa.diversifier().0.to_vec(),
|
||||||
value: note.value,
|
value: note.value,
|
||||||
rcm: rcm.to_vec(),
|
rcm: rcm.to_vec(),
|
||||||
|
rho: None,
|
||||||
nf: nf.0.to_vec(),
|
nf: nf.0.to_vec(),
|
||||||
spent: None,
|
spent: None,
|
||||||
},
|
},
|
||||||
|
@ -324,6 +325,7 @@ pub async fn sync_async(
|
||||||
&block.hash,
|
&block.hash,
|
||||||
block.time,
|
block.time,
|
||||||
&tree,
|
&tree,
|
||||||
|
None,
|
||||||
)?;
|
)?;
|
||||||
db_transaction.commit()?;
|
db_transaction.commit()?;
|
||||||
// db_transaction is dropped here
|
// db_transaction is dropped here
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use anyhow::Result;
|
||||||
|
use rayon::prelude::*;
|
||||||
|
use zcash_note_encryption::BatchDomain;
|
||||||
|
use zcash_primitives::consensus::Parameters;
|
||||||
|
use crate::{CompactBlock, DbAdapter};
|
||||||
|
use crate::chain::Nf;
|
||||||
|
use crate::db::{DbAdapterBuilder, ReceivedNote, ReceivedNoteShort};
|
||||||
|
|
||||||
|
mod tree;
|
||||||
|
mod trial_decrypt;
|
||||||
|
|
||||||
|
pub use trial_decrypt::{ViewKey, DecryptedNote, TrialDecrypter, CompactOutputBytes, OutputPosition};
|
||||||
|
pub use tree::{Hasher, Node, WarpProcessor, Witness, CTree};
|
||||||
|
|
||||||
|
pub struct Synchronizer<N: Parameters, D: BatchDomain<ExtractedCommitmentBytes = [u8; 32]>, VK: ViewKey<D>, DN: DecryptedNote<D, VK>,
|
||||||
|
TD: TrialDecrypter<N, D, VK, DN>, H: Hasher> {
|
||||||
|
pub decrypter: TD,
|
||||||
|
pub warper: WarpProcessor<H>,
|
||||||
|
pub vks: Vec<VK>,
|
||||||
|
pub db: DbAdapterBuilder,
|
||||||
|
pub shielded_pool: String,
|
||||||
|
|
||||||
|
pub note_position: usize,
|
||||||
|
pub nullifiers: HashMap<Nf, ReceivedNoteShort>,
|
||||||
|
pub tree: CTree,
|
||||||
|
pub witnesses: Vec<Witness>,
|
||||||
|
pub _phantom: PhantomData<(N, D, DN)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <N: Parameters + Sync,
|
||||||
|
D: BatchDomain<ExtractedCommitmentBytes = [u8; 32]> + Sync + Send,
|
||||||
|
VK: ViewKey<D> + Sync + Send,
|
||||||
|
DN: DecryptedNote<D, VK> + Sync,
|
||||||
|
TD: TrialDecrypter<N, D, VK, DN> + Sync,
|
||||||
|
H: Hasher> Synchronizer<N, D, VK, DN, TD, H> {
|
||||||
|
pub fn initialize(&mut self) -> Result<()> {
|
||||||
|
let db = self.db.build()?;
|
||||||
|
let (tree, witnesses) = db.get_tree_by_name(&self.shielded_pool)?;
|
||||||
|
self.tree = tree;
|
||||||
|
self.witnesses = witnesses;
|
||||||
|
for vk in self.vks.iter() {
|
||||||
|
let account = vk.account();
|
||||||
|
let nfs = db.get_unspent_nullifiers(account)?;
|
||||||
|
for rn in nfs.into_iter() {
|
||||||
|
self.nullifiers.insert(rn.nf.clone(), rn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process(&mut self, blocks: Vec<CompactBlock>) -> Result<()> {
|
||||||
|
if blocks.is_empty() { return Ok(()) }
|
||||||
|
let decrypter = self.decrypter.clone();
|
||||||
|
let decrypted_blocks: Vec<_> = blocks
|
||||||
|
.par_iter()
|
||||||
|
.map(|b| decrypter.decrypt_notes(b, &self.vks))
|
||||||
|
.collect();
|
||||||
|
let mut db = self.db.build()?;
|
||||||
|
self.warper.initialize(&self.tree, &self.witnesses);
|
||||||
|
let db_tx = db.begin_transaction()?;
|
||||||
|
|
||||||
|
// Detect new received notes
|
||||||
|
let mut new_witnesses = vec![];
|
||||||
|
for decb in decrypted_blocks.iter() {
|
||||||
|
for dectx in decb.txs.iter() {
|
||||||
|
let id_tx = DbAdapter::store_transaction(&dectx.tx_id, dectx.account, dectx.height, dectx.timestamp, dectx.tx_index as u32, &db_tx)?;
|
||||||
|
let mut balance: i64 = 0;
|
||||||
|
for decn in dectx.notes.iter() {
|
||||||
|
let position = decn.position(self.note_position);
|
||||||
|
let rn: ReceivedNote = decn.to_received_note(position as u64);
|
||||||
|
let id_note = DbAdapter::store_received_note(&rn, id_tx, position, &db_tx)?;
|
||||||
|
let nf = Nf(rn.nf.try_into().unwrap());
|
||||||
|
self.nullifiers.insert(nf, ReceivedNoteShort {
|
||||||
|
id: id_note,
|
||||||
|
account: rn.account,
|
||||||
|
nf,
|
||||||
|
value: rn.value
|
||||||
|
});
|
||||||
|
let witness = Witness::new(position, id_note, &decn.cmx());
|
||||||
|
log::info!("Witness {} {} {}", witness.position, witness.id_note, hex::encode(witness.cmx));
|
||||||
|
new_witnesses.push(witness);
|
||||||
|
balance += rn.value as i64;
|
||||||
|
}
|
||||||
|
DbAdapter::add_value(id_tx, balance, &db_tx)?;
|
||||||
|
}
|
||||||
|
self.note_position += decb.count_outputs as usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect spends and collect note commitments
|
||||||
|
let mut new_cmx = vec![];
|
||||||
|
let mut height = 0;
|
||||||
|
for b in blocks.iter() {
|
||||||
|
for (tx_index, tx) in b.vtx.iter().enumerate() {
|
||||||
|
for sp in self.decrypter.spends(tx).iter() {
|
||||||
|
if let Some(rn) = self.nullifiers.get(sp) {
|
||||||
|
let id_tx = DbAdapter::store_transaction(&tx.hash, rn.account, b.height as u32, b.time, tx_index as u32, &db_tx)?;
|
||||||
|
DbAdapter::add_value(id_tx, -(rn.value as i64), &db_tx)?;
|
||||||
|
DbAdapter::mark_spent(rn.id, b.height as u32, &db_tx)?;
|
||||||
|
self.nullifiers.remove(sp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new_cmx.extend(self.decrypter.outputs(tx).into_iter().map(|cob| cob.cmx));
|
||||||
|
}
|
||||||
|
height = b.height as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run blocks through warp sync
|
||||||
|
self.warper.add_nodes(&mut new_cmx, &new_witnesses);
|
||||||
|
let (updated_tree, updated_witnesses) = self.warper.finalize();
|
||||||
|
|
||||||
|
// Store witnesses
|
||||||
|
for w in updated_witnesses.iter() {
|
||||||
|
DbAdapter::store_witness(w, height, w.id_note, &db_tx, &self.shielded_pool)?;
|
||||||
|
}
|
||||||
|
self.tree = updated_tree;
|
||||||
|
self.witnesses = updated_witnesses;
|
||||||
|
|
||||||
|
db_tx.commit()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use zcash_primitives::consensus::Network;
|
||||||
|
use zcash_primitives::sapling::note_encryption::SaplingDomain;
|
||||||
|
use crate::coinconfig::COIN_CONFIG;
|
||||||
|
use crate::db::DbAdapterBuilder;
|
||||||
|
use crate::init_coin;
|
||||||
|
use crate::sapling::{DecryptedSaplingNote, SaplingDecrypter, SaplingHasher, SaplingViewKey};
|
||||||
|
use crate::sync::CTree;
|
||||||
|
use crate::sync::tree::WarpProcessor;
|
||||||
|
use super::Synchronizer;
|
||||||
|
|
||||||
|
type SaplingSynchronizer = Synchronizer<Network, SaplingDomain<Network>, SaplingViewKey, DecryptedSaplingNote,
|
||||||
|
SaplingDecrypter<Network>, SaplingHasher>;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
init_coin(0, "zec.db").unwrap();
|
||||||
|
let coin = COIN_CONFIG[0].lock().unwrap();
|
||||||
|
let network = coin.chain.network();
|
||||||
|
let mut synchronizer = SaplingSynchronizer {
|
||||||
|
decrypter: SaplingDecrypter::new(*network),
|
||||||
|
warper: WarpProcessor::new(SaplingHasher::default()),
|
||||||
|
vks: vec![],
|
||||||
|
db: DbAdapterBuilder { coin_type: coin.coin_type, db_path: coin.db_path.as_ref().unwrap().to_owned() },
|
||||||
|
shielded_pool: "sapling".to_string(),
|
||||||
|
tree: CTree::new(),
|
||||||
|
witnesses: vec![],
|
||||||
|
|
||||||
|
note_position: 0,
|
||||||
|
nullifiers: HashMap::new(),
|
||||||
|
_phantom: Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
synchronizer.initialize().unwrap();
|
||||||
|
synchronizer.process(vec![]).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,603 @@
|
||||||
|
use rayon::prelude::*;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use byteorder::WriteBytesExt;
|
||||||
|
use zcash_encoding::{Optional, Vector};
|
||||||
|
|
||||||
|
pub type Node = [u8; 32];
|
||||||
|
|
||||||
|
pub trait Hasher: Clone + Sync {
|
||||||
|
fn uncommited_node() -> Node;
|
||||||
|
fn node_combine(&self, depth: u8, left: &Node, right: &Node) -> Node;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CTree {
|
||||||
|
pub left: Option<Node>,
|
||||||
|
pub right: Option<Node>,
|
||||||
|
pub parents: Vec<Option<Node>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CTree {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
CTree {
|
||||||
|
left: None,
|
||||||
|
right: None,
|
||||||
|
parents: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_position(&self) -> usize {
|
||||||
|
let mut p = 0usize;
|
||||||
|
for parent in self.parents.iter().rev() {
|
||||||
|
if parent.is_some() {
|
||||||
|
p += 1;
|
||||||
|
}
|
||||||
|
p *= 2;
|
||||||
|
}
|
||||||
|
if self.left.is_some() {
|
||||||
|
p += 1;
|
||||||
|
}
|
||||||
|
if self.right.is_some() {
|
||||||
|
p += 1;
|
||||||
|
}
|
||||||
|
p
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clone_trimmed(&self, depth: usize) -> Self {
|
||||||
|
let mut tree = self.clone();
|
||||||
|
tree.parents.truncate(depth);
|
||||||
|
if let Some(None) = tree.parents.last() {
|
||||||
|
// Remove trailing None
|
||||||
|
tree.parents.truncate(depth - 1);
|
||||||
|
}
|
||||||
|
tree
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn root<H: Hasher>(&self, height: usize, empty_roots: &[Node], hasher: &H) -> Node {
|
||||||
|
if self.left.is_none() {
|
||||||
|
return empty_roots[height];
|
||||||
|
}
|
||||||
|
// merge the leaves
|
||||||
|
let left = self.left.unwrap_or(H::uncommited_node());
|
||||||
|
let right = self.right.unwrap_or(H::uncommited_node());
|
||||||
|
let mut cur = hasher.node_combine(0, &left, &right);
|
||||||
|
// merge the parents
|
||||||
|
let mut depth = 1u8;
|
||||||
|
for p in self.parents.iter() {
|
||||||
|
if let Some(ref left) = p {
|
||||||
|
cur = hasher.node_combine(depth, left, &cur);
|
||||||
|
} else {
|
||||||
|
cur = hasher.node_combine(depth, &cur, &empty_roots[depth as usize]);
|
||||||
|
}
|
||||||
|
depth += 1;
|
||||||
|
}
|
||||||
|
// fill in the missing levels
|
||||||
|
for d in depth as usize..height {
|
||||||
|
cur = hasher.node_combine(d as u8, &cur, &empty_roots[d]);
|
||||||
|
}
|
||||||
|
cur
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read<R: Read>(mut reader: R) -> std::io::Result<Self> {
|
||||||
|
let left = Optional::read(&mut reader, node_read)?;
|
||||||
|
let right = Optional::read(&mut reader, node_read)?;
|
||||||
|
let parents = Vector::read(&mut reader, |r| Optional::read(r, node_read))?;
|
||||||
|
|
||||||
|
Ok(CTree {
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
parents,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write<W: Write>(&self, mut writer: W) -> std::io::Result<()> {
|
||||||
|
Optional::write(&mut writer, self.left, |w, n| node_write(&n, w))?;
|
||||||
|
Optional::write(&mut writer, self.right, |w, n| node_write(&n, w))?;
|
||||||
|
Vector::write(&mut writer, &self.parents, |w, e| {
|
||||||
|
Optional::write(w, *e, |w, n| node_write(&n, w))
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_bytes(bytes: &[u8]) -> std::io::Result<Self> {
|
||||||
|
Self::read(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn node_read<R: Read>(mut r: R) -> std::io::Result<Node> {
|
||||||
|
let mut hash = [0u8; 32];
|
||||||
|
r.read(&mut hash)?;
|
||||||
|
Ok(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn node_write<W: Write>(node: &Node, mut w: W) -> std::io::Result<()> {
|
||||||
|
w.write_all(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Witness {
|
||||||
|
pub position: usize,
|
||||||
|
pub tree: CTree, // commitment tree at the moment the witness is created: immutable
|
||||||
|
pub filled: Vec<Node>, // as more nodes are added, levels get filled up: won't change anymore
|
||||||
|
pub cursor: CTree, // partial tree which still updates when nodes are added
|
||||||
|
|
||||||
|
// not used for decryption but identifies the witness
|
||||||
|
pub id_note: u32,
|
||||||
|
pub cmx: [u8; 32],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Witness {
|
||||||
|
pub fn new(position: usize, id_note: u32, cmx: &[u8; 32]) -> Witness {
|
||||||
|
Witness {
|
||||||
|
position,
|
||||||
|
id_note,
|
||||||
|
tree: CTree::new(),
|
||||||
|
filled: vec![],
|
||||||
|
cursor: CTree::new(),
|
||||||
|
cmx: cmx.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn auth_path<H: Hasher>(&self, height: usize, empty_roots: &[Node], hasher: H) -> Vec<Node> {
|
||||||
|
let mut filled_iter = self.filled.iter();
|
||||||
|
let mut cursor_used = false;
|
||||||
|
let mut next_filler = move |depth: usize| {
|
||||||
|
if let Some(f) = filled_iter.next() {
|
||||||
|
*f
|
||||||
|
} else if !cursor_used {
|
||||||
|
cursor_used = true;
|
||||||
|
self.cursor.root(depth, empty_roots, &hasher)
|
||||||
|
} else {
|
||||||
|
empty_roots[depth]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut auth_path = vec![];
|
||||||
|
if let Some(left) = self.tree.left {
|
||||||
|
if self.tree.right.is_some() {
|
||||||
|
auth_path.push(left);
|
||||||
|
} else {
|
||||||
|
auth_path.push(next_filler(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i in 1..height {
|
||||||
|
let p = if i - 1 < self.tree.parents.len() {
|
||||||
|
self.tree.parents[i - 1]
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(node) = p {
|
||||||
|
auth_path.push(node);
|
||||||
|
} else {
|
||||||
|
auth_path.push(next_filler(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auth_path
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read<R: Read>(id_note: u32, mut reader: R) -> std::io::Result<Self> {
|
||||||
|
let tree = CTree::read(&mut reader)?;
|
||||||
|
let filled = Vector::read(&mut reader, |r| node_read(r))?;
|
||||||
|
let cursor = Optional::read(&mut reader, |r| CTree::read(r))?;
|
||||||
|
let mut cmx = [0u8; 32];
|
||||||
|
reader.read(&mut cmx)?;
|
||||||
|
|
||||||
|
let mut witness = Witness {
|
||||||
|
position: 0,
|
||||||
|
id_note,
|
||||||
|
tree,
|
||||||
|
filled,
|
||||||
|
cursor: cursor.unwrap_or_else(CTree::new),
|
||||||
|
cmx,
|
||||||
|
};
|
||||||
|
witness.position = witness.tree.get_position() - 1;
|
||||||
|
|
||||||
|
Ok(witness)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write<W: Write>(&self, mut writer: W) -> std::io::Result<()> {
|
||||||
|
self.tree.write(&mut writer)?;
|
||||||
|
Vector::write(&mut writer, &self.filled, |w, n| node_write(&n, w))?;
|
||||||
|
if self.cursor.left == None && self.cursor.right == None {
|
||||||
|
writer.write_u8(0)?;
|
||||||
|
} else {
|
||||||
|
writer.write_u8(1)?;
|
||||||
|
self.cursor.write(&mut writer)?;
|
||||||
|
};
|
||||||
|
writer.write_all(&self.cmx)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_bytes(id_note: u32, bytes: &[u8]) -> std::io::Result<Self> {
|
||||||
|
Self::read(id_note, bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Builder {
|
||||||
|
type Context;
|
||||||
|
type Output;
|
||||||
|
|
||||||
|
fn collect(&mut self, commitments: &[Node], context: &Self::Context) -> usize;
|
||||||
|
fn up(&mut self);
|
||||||
|
fn finished(&self) -> bool;
|
||||||
|
fn finalize(self, context: &Self::Context) -> Self::Output;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CTreeBuilder<H: Hasher> {
|
||||||
|
left: Option<Node>,
|
||||||
|
right: Option<Node>,
|
||||||
|
prev_tree: CTree,
|
||||||
|
next_tree: CTree,
|
||||||
|
start: usize,
|
||||||
|
total_len: usize,
|
||||||
|
depth: u8,
|
||||||
|
offset: Option<Node>,
|
||||||
|
first_block: bool,
|
||||||
|
hasher: H,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <H: Hasher> Builder for CTreeBuilder<H> {
|
||||||
|
type Context = ();
|
||||||
|
type Output = CTree;
|
||||||
|
|
||||||
|
fn collect(&mut self, commitments: &[Node], _context: &()) -> usize {
|
||||||
|
assert!(self.right.is_none() || self.left.is_some()); // R can't be set without L
|
||||||
|
|
||||||
|
let offset: Option<Node>;
|
||||||
|
let m: usize;
|
||||||
|
|
||||||
|
if self.left.is_some() && self.right.is_none() {
|
||||||
|
offset = self.left;
|
||||||
|
m = commitments.len() + 1;
|
||||||
|
} else {
|
||||||
|
offset = None;
|
||||||
|
m = commitments.len();
|
||||||
|
};
|
||||||
|
|
||||||
|
let n = if self.total_len > 0 {
|
||||||
|
if self.depth == 0 {
|
||||||
|
if m % 2 == 0 {
|
||||||
|
self.next_tree.left = Some(*Self::get(commitments, m - 2, &offset));
|
||||||
|
self.next_tree.right = Some(*Self::get(commitments, m - 1, &offset));
|
||||||
|
m - 2
|
||||||
|
} else {
|
||||||
|
self.next_tree.left = Some(*Self::get(commitments, m - 1, &offset));
|
||||||
|
self.next_tree.right = None;
|
||||||
|
m - 1
|
||||||
|
}
|
||||||
|
} else if m % 2 == 0 {
|
||||||
|
self.next_tree.parents.push(None);
|
||||||
|
m
|
||||||
|
} else {
|
||||||
|
let last_node = Self::get(commitments, m - 1, &offset);
|
||||||
|
self.next_tree.parents.push(Some(*last_node));
|
||||||
|
m - 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
assert_eq!(n % 2, 0);
|
||||||
|
|
||||||
|
self.offset = offset;
|
||||||
|
n
|
||||||
|
}
|
||||||
|
|
||||||
|
fn up(&mut self) {
|
||||||
|
let h = if self.left.is_some() && self.right.is_some() {
|
||||||
|
Some(self.hasher.node_combine(
|
||||||
|
self.depth,
|
||||||
|
&self.left.unwrap(),
|
||||||
|
&self.right.unwrap(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let (l, r) = match self.prev_tree.parents.get(self.depth as usize) {
|
||||||
|
Some(Some(p)) => (Some(*p), h),
|
||||||
|
Some(None) => (h, None),
|
||||||
|
None => (h, None),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.left = l;
|
||||||
|
self.right = r;
|
||||||
|
|
||||||
|
assert!(self.start % 2 == 0 || self.offset.is_some());
|
||||||
|
self.start /= 2;
|
||||||
|
|
||||||
|
self.depth += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finished(&self) -> bool {
|
||||||
|
self.depth as usize >= self.prev_tree.parents.len() && self.left.is_none() && self.right.is_none()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finalize(self, _context: &()) -> CTree {
|
||||||
|
if self.total_len > 0 {
|
||||||
|
self.next_tree
|
||||||
|
} else {
|
||||||
|
self.prev_tree
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <H: Hasher> CTreeBuilder<H> {
|
||||||
|
fn new(prev_tree: &CTree, len: usize, first_block: bool, hasher: H) -> Self {
|
||||||
|
let start = prev_tree.get_position();
|
||||||
|
CTreeBuilder {
|
||||||
|
left: prev_tree.left,
|
||||||
|
right: prev_tree.right,
|
||||||
|
prev_tree: prev_tree.clone(),
|
||||||
|
next_tree: CTree::new(),
|
||||||
|
start,
|
||||||
|
total_len: len,
|
||||||
|
depth: 0,
|
||||||
|
offset: None,
|
||||||
|
first_block,
|
||||||
|
hasher,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn get_opt<'a>(
|
||||||
|
commitments: &'a [Node],
|
||||||
|
index: usize,
|
||||||
|
offset: &'a Option<Node>,
|
||||||
|
) -> Option<&'a Node> {
|
||||||
|
if offset.is_some() {
|
||||||
|
if index > 0 {
|
||||||
|
commitments.get(index - 1)
|
||||||
|
} else {
|
||||||
|
offset.as_ref()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
commitments.get(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn get<'a>(
|
||||||
|
commitments: &'a [Node],
|
||||||
|
index: usize,
|
||||||
|
offset: &'a Option<Node>,
|
||||||
|
) -> &'a Node {
|
||||||
|
Self::get_opt(commitments, index, offset).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn adjusted_start(&self, prev: &Option<Node>) -> usize {
|
||||||
|
if prev.is_some() {
|
||||||
|
self.start - 1
|
||||||
|
} else {
|
||||||
|
self.start
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn combine_level<H: Hasher>(
|
||||||
|
commitments: &mut [Node],
|
||||||
|
offset: Option<Node>,
|
||||||
|
n: usize,
|
||||||
|
depth: u8,
|
||||||
|
hasher: &H,
|
||||||
|
) -> usize {
|
||||||
|
assert_eq!(n % 2, 0);
|
||||||
|
|
||||||
|
// TODO: Support batch hash combine
|
||||||
|
let nn = n / 2;
|
||||||
|
let next_level: Vec<_> = (0..nn)
|
||||||
|
.into_par_iter()
|
||||||
|
.map(|i| {
|
||||||
|
hasher.node_combine(
|
||||||
|
depth,
|
||||||
|
CTreeBuilder::<H>::get(commitments, 2 * i, &offset),
|
||||||
|
CTreeBuilder::<H>::get(commitments, 2 * i + 1, &offset),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
commitments[0..nn].copy_from_slice(&next_level);
|
||||||
|
nn
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WitnessBuilder<H: Hasher> {
|
||||||
|
witness: Witness,
|
||||||
|
p: usize,
|
||||||
|
inside: bool,
|
||||||
|
_phantom: PhantomData<H>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <H: Hasher> WitnessBuilder<H> {
|
||||||
|
fn new(tree_builder: &CTreeBuilder<H>, prev_witness: &Witness, count: usize) -> Self {
|
||||||
|
let position = prev_witness.position;
|
||||||
|
let inside = position >= tree_builder.start && position < tree_builder.start + count;
|
||||||
|
WitnessBuilder {
|
||||||
|
witness: prev_witness.clone(),
|
||||||
|
p: position,
|
||||||
|
inside,
|
||||||
|
_phantom: PhantomData::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <H: Hasher> Builder for WitnessBuilder<H> {
|
||||||
|
type Context = CTreeBuilder<H>;
|
||||||
|
type Output = Witness;
|
||||||
|
|
||||||
|
fn collect(&mut self, commitments: &[Node], context: &CTreeBuilder<H>) -> usize {
|
||||||
|
let offset = context.offset;
|
||||||
|
let depth = context.depth;
|
||||||
|
|
||||||
|
let tree = &mut self.witness.tree;
|
||||||
|
|
||||||
|
if self.inside {
|
||||||
|
let rp = self.p - context.adjusted_start(&offset);
|
||||||
|
if depth == 0 {
|
||||||
|
if self.p % 2 == 1 {
|
||||||
|
tree.left = Some(*CTreeBuilder::<H>::get(commitments, rp - 1, &offset));
|
||||||
|
tree.right = Some(*CTreeBuilder::<H>::get(commitments, rp, &offset));
|
||||||
|
} else {
|
||||||
|
tree.left = Some(*CTreeBuilder::<H>::get(commitments, rp, &offset));
|
||||||
|
tree.right = None;
|
||||||
|
}
|
||||||
|
} else if self.p % 2 == 1 {
|
||||||
|
tree.parents
|
||||||
|
.push(Some(*CTreeBuilder::<H>::get(commitments, rp - 1, &offset)));
|
||||||
|
} else if self.p != 0 {
|
||||||
|
tree.parents.push(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let right = if depth != 0 && !context.first_block {
|
||||||
|
context.right
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
// println!("D {}", depth);
|
||||||
|
// println!("O {:?}", offset.map(|r| hex::encode(r.repr)));
|
||||||
|
// println!("R {:?}", right.map(|r| hex::encode(r.repr)));
|
||||||
|
// for c in commitments.iter() {
|
||||||
|
// println!("{}", hex::encode(c.repr));
|
||||||
|
// }
|
||||||
|
let p1 = self.p + 1;
|
||||||
|
// println!("P {} P1 {} S {} AS {}", self.p, p1, context.start, context.adjusted_start(&right));
|
||||||
|
let has_p1 = p1 >= context.adjusted_start(&right) && p1 < context.start + commitments.len();
|
||||||
|
if has_p1 {
|
||||||
|
let p1 =
|
||||||
|
CTreeBuilder::<H>::get(commitments, p1 - context.adjusted_start(&right), &right);
|
||||||
|
if depth == 0 {
|
||||||
|
if tree.right.is_none() {
|
||||||
|
self.witness.filled.push(*p1);
|
||||||
|
}
|
||||||
|
} else if depth as usize > tree.parents.len() || tree.parents[depth as usize - 1].is_none() {
|
||||||
|
self.witness.filled.push(*p1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn up(&mut self) {
|
||||||
|
self.p /= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finished(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finalize(mut self, context: &CTreeBuilder<H>) -> Witness {
|
||||||
|
if context.total_len == 0 {
|
||||||
|
self.witness.cursor = CTree::new();
|
||||||
|
|
||||||
|
let mut final_position = context.prev_tree.get_position() as u32;
|
||||||
|
let mut witness_position = self.witness.tree.get_position() as u32;
|
||||||
|
assert_ne!(witness_position, 0);
|
||||||
|
witness_position -= 1;
|
||||||
|
|
||||||
|
// look for first not equal bit in MSB order
|
||||||
|
final_position = final_position.reverse_bits();
|
||||||
|
witness_position = witness_position.reverse_bits();
|
||||||
|
let mut bit: i32 = 31;
|
||||||
|
// reverse bits because it is easier to do in LSB
|
||||||
|
// it should not underflow because these numbers are not equal
|
||||||
|
while bit >= 0 {
|
||||||
|
if final_position & 1 != witness_position & 1 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
final_position >>= 1;
|
||||||
|
witness_position >>= 1;
|
||||||
|
bit -= 1;
|
||||||
|
}
|
||||||
|
// look for the first bit set in final_position after
|
||||||
|
final_position >>= 1;
|
||||||
|
bit -= 1;
|
||||||
|
while bit >= 0 {
|
||||||
|
if final_position & 1 == 1 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
final_position >>= 1;
|
||||||
|
bit -= 1;
|
||||||
|
}
|
||||||
|
if bit >= 0 {
|
||||||
|
self.witness.cursor = context.prev_tree.clone_trimmed(bit as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.witness
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WarpProcessor<H> {
|
||||||
|
prev_tree: CTree,
|
||||||
|
prev_witnesses: Vec<Witness>,
|
||||||
|
first_block: bool,
|
||||||
|
hasher: H,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <H: Hasher> WarpProcessor<H> {
|
||||||
|
pub fn new(hasher: H) -> WarpProcessor<H> {
|
||||||
|
WarpProcessor {
|
||||||
|
prev_tree: CTree::new(),
|
||||||
|
prev_witnesses: vec![],
|
||||||
|
first_block: true,
|
||||||
|
hasher,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initialize(&mut self, prev_tree: &CTree, prev_witnesses: &[Witness]) {
|
||||||
|
self.first_block = true;
|
||||||
|
self.prev_tree = prev_tree.clone();
|
||||||
|
self.prev_witnesses = prev_witnesses.to_vec();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_nodes(&mut self, nodes: &mut [Node], new_witnesses: &[Witness]) {
|
||||||
|
log::info!("Adding {} cmx", nodes.len());
|
||||||
|
if nodes.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.prev_witnesses.extend_from_slice(new_witnesses);
|
||||||
|
let (t, ws) = self.advance_tree(
|
||||||
|
nodes,
|
||||||
|
);
|
||||||
|
self.first_block = false;
|
||||||
|
self.prev_tree = t;
|
||||||
|
self.prev_witnesses = ws;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finalize(&mut self) -> (CTree, Vec<Witness>) {
|
||||||
|
if self.first_block {
|
||||||
|
(self.prev_tree.clone(), self.prev_witnesses.clone())
|
||||||
|
} else {
|
||||||
|
let (t, ws) = self.advance_tree(&mut []);
|
||||||
|
(t, ws)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn advance_tree(
|
||||||
|
&self,
|
||||||
|
mut commitments: &mut [Node],
|
||||||
|
) -> (CTree, Vec<Witness>) {
|
||||||
|
let mut builder = CTreeBuilder::<H>::new(&self.prev_tree, commitments.len(), self.first_block, self.hasher.clone());
|
||||||
|
let mut witness_builders: Vec<_> = self.prev_witnesses
|
||||||
|
.iter()
|
||||||
|
.map(|witness| WitnessBuilder::new(&builder, witness, commitments.len()))
|
||||||
|
.collect();
|
||||||
|
while !commitments.is_empty() || !builder.finished() {
|
||||||
|
let n = builder.collect(commitments, &());
|
||||||
|
for b in witness_builders.iter_mut() {
|
||||||
|
b.collect(commitments, &builder);
|
||||||
|
}
|
||||||
|
let nn = combine_level(commitments, builder.offset, n, builder.depth, &self.hasher);
|
||||||
|
builder.up();
|
||||||
|
for b in witness_builders.iter_mut() {
|
||||||
|
b.up();
|
||||||
|
}
|
||||||
|
commitments = &mut commitments[0..nn];
|
||||||
|
}
|
||||||
|
|
||||||
|
let witnesses = witness_builders
|
||||||
|
.into_iter()
|
||||||
|
.map(|b| b.finalize(&builder))
|
||||||
|
.collect();
|
||||||
|
let tree = builder.finalize(&());
|
||||||
|
(tree, witnesses)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,174 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use crate::chain::Nf;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::time::Instant;
|
||||||
|
use zcash_note_encryption::batch::try_compact_note_decryption;
|
||||||
|
use zcash_note_encryption::{BatchDomain, COMPACT_NOTE_SIZE, EphemeralKeyBytes, ShieldedOutput};
|
||||||
|
use zcash_primitives::consensus::{BlockHeight, Parameters};
|
||||||
|
use crate::{CompactBlock, CompactSaplingOutput, CompactTx};
|
||||||
|
use crate::db::ReceivedNote;
|
||||||
|
use crate::sync::tree::Node;
|
||||||
|
|
||||||
|
pub struct DecryptedBlock<D: BatchDomain, VK, DN: DecryptedNote<D, VK>> {
|
||||||
|
pub height: u32,
|
||||||
|
pub spends: Vec<Nf>,
|
||||||
|
pub txs: Vec<DecryptedTx<D, VK, DN>>,
|
||||||
|
pub count_outputs: u32,
|
||||||
|
pub elapsed: usize,
|
||||||
|
_phantom: PhantomData<(D, VK)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DecryptedTx<D: BatchDomain, VK, DN: DecryptedNote<D, VK>> {
|
||||||
|
pub account: u32,
|
||||||
|
pub height: u32,
|
||||||
|
pub timestamp: u32,
|
||||||
|
pub tx_index: usize,
|
||||||
|
pub tx_id: Vec<u8>,
|
||||||
|
pub notes: Vec<DN>,
|
||||||
|
_phantom: PhantomData<(D, VK)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ViewKey<D: BatchDomain>: Clone {
|
||||||
|
fn account(&self) -> u32;
|
||||||
|
fn ivk(&self) -> D::IncomingViewingKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct OutputPosition {
|
||||||
|
pub height: u32,
|
||||||
|
pub tx_index: usize,
|
||||||
|
pub output_index: usize,
|
||||||
|
pub position_in_block: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DecryptedNote<D: BatchDomain, VK>: Send + Sync {
|
||||||
|
fn from_parts(vk: VK, note: D::Note, pa: D::Recipient, output_position: OutputPosition, cmx: Node) -> Self;
|
||||||
|
fn position(&self, block_offset: usize) -> usize;
|
||||||
|
fn cmx(&self) -> Node;
|
||||||
|
fn to_received_note(&self, position: u64) -> ReceivedNote;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deep copy from protobuf message
|
||||||
|
pub struct CompactOutputBytes {
|
||||||
|
pub epk: [u8; 32],
|
||||||
|
pub cmx: [u8; 32],
|
||||||
|
pub ciphertext: [u8; 52],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&CompactSaplingOutput> for CompactOutputBytes {
|
||||||
|
fn from(co: &CompactSaplingOutput) -> Self {
|
||||||
|
CompactOutputBytes {
|
||||||
|
epk: co.epk.clone().try_into().unwrap(),
|
||||||
|
cmx: co.cmu.clone().try_into().unwrap(),
|
||||||
|
ciphertext: co.ciphertext.clone().try_into().unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CompactShieldedOutput(CompactOutputBytes, OutputPosition);
|
||||||
|
|
||||||
|
impl<D: BatchDomain<ExtractedCommitmentBytes = [u8; 32]>> ShieldedOutput<D, COMPACT_NOTE_SIZE>
|
||||||
|
for CompactShieldedOutput
|
||||||
|
{
|
||||||
|
fn ephemeral_key(&self) -> EphemeralKeyBytes {
|
||||||
|
EphemeralKeyBytes(self.0.epk)
|
||||||
|
}
|
||||||
|
fn cmstar_bytes(&self) -> D::ExtractedCommitmentBytes {
|
||||||
|
self.0.cmx
|
||||||
|
}
|
||||||
|
fn enc_ciphertext(&self) -> &[u8; COMPACT_NOTE_SIZE] {
|
||||||
|
&self.0.ciphertext
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait TrialDecrypter<N: Parameters, D: BatchDomain<ExtractedCommitmentBytes = [u8; 32]>, VK: ViewKey<D>, DN: DecryptedNote<D, VK>>: Clone {
|
||||||
|
fn decrypt_notes(
|
||||||
|
&self,
|
||||||
|
block: &CompactBlock,
|
||||||
|
vks: &[VK],
|
||||||
|
) -> DecryptedBlock<D, VK, DN> {
|
||||||
|
let height = BlockHeight::from_u32(block.height as u32);
|
||||||
|
let mut count_outputs = 0u32;
|
||||||
|
let mut spends: Vec<Nf> = vec![];
|
||||||
|
let vvks: Vec<_> = vks.iter().map(|vk| vk.ivk().clone()).collect();
|
||||||
|
let mut outputs = vec![];
|
||||||
|
let mut txs = HashMap::new();
|
||||||
|
for (tx_index, vtx) in block.vtx.iter().enumerate() {
|
||||||
|
for cs in vtx.spends.iter() {
|
||||||
|
let mut nf = [0u8; 32];
|
||||||
|
nf.copy_from_slice(&cs.nf);
|
||||||
|
spends.push(Nf(nf));
|
||||||
|
}
|
||||||
|
|
||||||
|
let tx_outputs = self.outputs(vtx);
|
||||||
|
if let Some(fco) = tx_outputs.first() {
|
||||||
|
if !fco.epk.is_empty() {
|
||||||
|
for (output_index, cob) in tx_outputs.into_iter().enumerate() {
|
||||||
|
let domain = self.domain(height);
|
||||||
|
let pos = OutputPosition {
|
||||||
|
height: block.height as u32,
|
||||||
|
tx_index,
|
||||||
|
output_index,
|
||||||
|
position_in_block: count_outputs as usize,
|
||||||
|
};
|
||||||
|
let output = CompactShieldedOutput(cob, pos);
|
||||||
|
outputs.push((domain, output));
|
||||||
|
|
||||||
|
count_outputs += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// we filter by transaction, therefore if one epk is empty, every epk is empty
|
||||||
|
// log::info!("Spam Filter tx {}", hex::encode(&vtx.hash));
|
||||||
|
count_outputs += vtx.outputs.len() as u32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let start = Instant::now();
|
||||||
|
let notes_decrypted =
|
||||||
|
try_compact_note_decryption(&vvks, &outputs);
|
||||||
|
let elapsed = start.elapsed().as_millis() as usize;
|
||||||
|
|
||||||
|
for (pos, opt_note) in notes_decrypted.iter().enumerate() {
|
||||||
|
if let Some((note, pa)) = opt_note {
|
||||||
|
let vk = &vks[pos / outputs.len()];
|
||||||
|
let account = vk.account();
|
||||||
|
let output = &outputs[pos % outputs.len()];
|
||||||
|
let tx_index = output.1.1.tx_index;
|
||||||
|
let tx_key = (account, tx_index);
|
||||||
|
let tx = txs.entry(tx_key).or_insert_with(||
|
||||||
|
DecryptedTx {
|
||||||
|
account,
|
||||||
|
height: block.height as u32,
|
||||||
|
timestamp: block.time,
|
||||||
|
tx_index,
|
||||||
|
tx_id: block.vtx[tx_index].hash.clone(),
|
||||||
|
notes: vec![],
|
||||||
|
_phantom: PhantomData::default(),
|
||||||
|
});
|
||||||
|
tx.notes.push(DN::from_parts(
|
||||||
|
vk.clone(),
|
||||||
|
note.clone(),
|
||||||
|
pa.clone(),
|
||||||
|
output.1.1.clone(),
|
||||||
|
output.1.0.cmx,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DecryptedBlock {
|
||||||
|
height: block.height as u32,
|
||||||
|
spends,
|
||||||
|
txs: txs.into_values().collect(),
|
||||||
|
count_outputs,
|
||||||
|
elapsed,
|
||||||
|
_phantom: PhantomData::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn domain(&self, height: BlockHeight) -> D;
|
||||||
|
fn spends(&self, vtx: &CompactTx) -> Vec<Nf>;
|
||||||
|
fn outputs(&self, vtx: &CompactTx) -> Vec<CompactOutputBytes>;
|
||||||
|
}
|
||||||
|
|
|
@ -271,7 +271,7 @@ pub async fn retrieve_tx_info(
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::transaction::decode_transaction;
|
use crate::transaction::decode_transaction;
|
||||||
use crate::{connect_lightwalletd, DbAdapter, LWD_URL};
|
use crate::{AccountData, connect_lightwalletd, DbAdapter, LWD_URL};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
|
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
|
||||||
use zcash_params::coin::CoinType;
|
use zcash_params::coin::CoinType;
|
||||||
|
@ -292,8 +292,7 @@ mod tests {
|
||||||
nf_map.insert((nf.0, nf.2.clone()), nf.1);
|
nf_map.insert((nf.0, nf.2.clone()), nf.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let account = db.get_account_info(account).unwrap();
|
let AccountData { fvk, .. } = db.get_account_info(account).unwrap();
|
||||||
let fvk = account.fvk.clone();
|
|
||||||
let fvk = decode_extended_full_viewing_key(
|
let fvk = decode_extended_full_viewing_key(
|
||||||
Network::MainNetwork.hrp_sapling_extended_full_viewing_key(),
|
Network::MainNetwork.hrp_sapling_extended_full_viewing_key(),
|
||||||
&fvk,
|
&fvk,
|
||||||
|
|
Loading…
Reference in New Issue