Unified address encode/decode & accounts
This commit is contained in:
parent
f8a692e3e5
commit
544fb25f9a
|
@ -294,3 +294,25 @@ pub async fn import_sync_data(coin: u8, file: &str) -> anyhow::Result<()> {
|
||||||
retrieve_tx_info(c.coin_type, &mut client, c.db_path.as_ref().unwrap(), &ids).await?;
|
retrieve_tx_info(c.coin_type, &mut client, c.db_path.as_ref().unwrap(), &ids).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the Unified address
|
||||||
|
/// # Arguments
|
||||||
|
/// * `coin`: 0 for zcash, 1 for ycash
|
||||||
|
/// * `id_account`: account id as returned from [new_account]
|
||||||
|
///
|
||||||
|
/// The address depends on the UA settings and may include transparent, sapling & orchard receivers
|
||||||
|
pub fn get_unified_address(coin: u8, id_account: u32) -> anyhow::Result<String> {
|
||||||
|
let c = CoinConfig::get(coin);
|
||||||
|
let db = c.db()?;
|
||||||
|
let address = crate::get_unified_address(c.chain.network(), &db, id_account)?;
|
||||||
|
Ok(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode a unified address into its receivers
|
||||||
|
///
|
||||||
|
/// For testing only. The format of the returned value is subject to change
|
||||||
|
pub fn decode_unified_address(coin: u8, address: &str) -> anyhow::Result<String> {
|
||||||
|
let c = CoinConfig::get(coin);
|
||||||
|
let res = crate::decode_unified_address(c.chain.network(), address)?;
|
||||||
|
Ok(res.to_string())
|
||||||
|
}
|
||||||
|
|
79
src/db.rs
79
src/db.rs
|
@ -10,14 +10,17 @@ 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 std::convert::TryInto;
|
||||||
|
use orchard::keys::FullViewingKey;
|
||||||
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, get_coin_chain, get_coin_id};
|
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::orchard::derive_orchard_keys;
|
use crate::orchard::{derive_orchard_keys, OrchardKeyBytes, OrchardViewKey};
|
||||||
|
use crate::sapling::SaplingViewKey;
|
||||||
use crate::sync;
|
use crate::sync;
|
||||||
|
use crate::unified::UnifiedAddressType;
|
||||||
|
|
||||||
mod migration;
|
mod migration;
|
||||||
|
|
||||||
|
@ -36,6 +39,7 @@ pub struct DbAdapter {
|
||||||
pub db_path: String,
|
pub db_path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct ReceivedNote {
|
pub struct ReceivedNote {
|
||||||
pub account: u32,
|
pub account: u32,
|
||||||
pub height: u32,
|
pub height: u32,
|
||||||
|
@ -207,33 +211,51 @@ impl DbAdapter {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_fvks(&self) -> anyhow::Result<HashMap<u32, AccountViewKey>> {
|
pub fn get_sapling_fvks(&self) -> anyhow::Result<Vec<SaplingViewKey>> {
|
||||||
let mut statement = self
|
let mut statement = self
|
||||||
.connection
|
.connection
|
||||||
.prepare("SELECT id_account, ivk, sk FROM accounts")?;
|
.prepare("SELECT id_account, ivk FROM accounts")?;
|
||||||
let rows = statement.query_map([], |row| {
|
let rows = statement.query_map([], |row| {
|
||||||
let account: u32 = row.get(0)?;
|
let account: u32 = row.get(0)?;
|
||||||
let ivk: String = row.get(1)?;
|
let ivk: String = row.get(1)?;
|
||||||
let sk: Option<String> = row.get(2)?;
|
|
||||||
let fvk = decode_extended_full_viewing_key(
|
let fvk = decode_extended_full_viewing_key(
|
||||||
self.network().hrp_sapling_extended_full_viewing_key(),
|
self.network().hrp_sapling_extended_full_viewing_key(),
|
||||||
&ivk,
|
&ivk,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let ivk = fvk.fvk.vk.ivk();
|
let ivk = fvk.fvk.vk.ivk();
|
||||||
Ok((
|
Ok(SaplingViewKey {
|
||||||
account,
|
account,
|
||||||
AccountViewKey {
|
|
||||||
fvk,
|
fvk,
|
||||||
ivk,
|
ivk
|
||||||
viewonly: sk.is_none(),
|
})
|
||||||
},
|
|
||||||
))
|
|
||||||
})?;
|
})?;
|
||||||
let mut fvks: HashMap<u32, AccountViewKey> = HashMap::new();
|
let mut fvks = vec![];
|
||||||
for r in rows {
|
for r in rows {
|
||||||
let row = r?;
|
let row = r?;
|
||||||
fvks.insert(row.0, row.1);
|
fvks.push(row);
|
||||||
|
}
|
||||||
|
Ok(fvks)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_orchard_fvks(&self) -> anyhow::Result<Vec<OrchardViewKey>> {
|
||||||
|
let mut statement = self.connection.prepare("SELECT account, fvk FROM orchard_addrs")?;
|
||||||
|
let rows = statement.query_map([], |row| {
|
||||||
|
let account: u32 = row.get(0)?;
|
||||||
|
let fvk: Vec<u8> = row.get(1)?;
|
||||||
|
let fvk: [u8; 96] = fvk.try_into().unwrap();
|
||||||
|
let fvk = FullViewingKey::from_bytes(&fvk).unwrap();
|
||||||
|
let vk =
|
||||||
|
OrchardViewKey {
|
||||||
|
account,
|
||||||
|
fvk,
|
||||||
|
};
|
||||||
|
Ok(vk)
|
||||||
|
})?;
|
||||||
|
let mut fvks = vec![];
|
||||||
|
for r in rows {
|
||||||
|
let row = r?;
|
||||||
|
fvks.push(row);
|
||||||
}
|
}
|
||||||
Ok(fvks)
|
Ok(fvks)
|
||||||
}
|
}
|
||||||
|
@ -271,6 +293,8 @@ impl DbAdapter {
|
||||||
|
|
||||||
let tx = self.connection.transaction()?;
|
let tx = self.connection.transaction()?;
|
||||||
tx.execute("DELETE FROM blocks WHERE height > ?1", params![height])?;
|
tx.execute("DELETE FROM blocks WHERE height > ?1", params![height])?;
|
||||||
|
tx.execute("DELETE FROM sapling_tree WHERE height > ?1", params![height])?;
|
||||||
|
tx.execute("DELETE FROM orchard_tree WHERE height > ?1", params![height])?;
|
||||||
tx.execute(
|
tx.execute(
|
||||||
"DELETE FROM sapling_witnesses WHERE height > ?1",
|
"DELETE FROM sapling_witnesses WHERE height > ?1",
|
||||||
params![height],
|
params![height],
|
||||||
|
@ -397,10 +421,9 @@ impl DbAdapter {
|
||||||
position: usize,
|
position: usize,
|
||||||
db_tx: &Transaction,
|
db_tx: &Transaction,
|
||||||
) -> anyhow::Result<u32> {
|
) -> anyhow::Result<u32> {
|
||||||
log::debug!("+received_note {}", id_tx);
|
log::info!("+received_note {} {:?}", id_tx, note);
|
||||||
db_tx.execute("INSERT INTO received_notes(account, tx, height, position, output_index, diversifier, value, rcm, rho, 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, ?11)
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)", 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])?;
|
||||||
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",
|
||||||
|
@ -913,6 +936,18 @@ impl DbAdapter {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_orchard(&self, account: u32) -> anyhow::Result<Option<OrchardKeyBytes>> {
|
||||||
|
let key = self.connection.query_row("SELECT sk, fvk FROM orchard_addrs WHERE account = ?1", params![account], |row| {
|
||||||
|
let sk: Vec<u8> = row.get(0)?;
|
||||||
|
let fvk: Vec<u8> = row.get(1)?;
|
||||||
|
Ok(OrchardKeyBytes {
|
||||||
|
sk: sk.try_into().unwrap(),
|
||||||
|
fvk: fvk.try_into().unwrap(),
|
||||||
|
})
|
||||||
|
}).optional()?;
|
||||||
|
Ok(key)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn store_ua_settings(&self, account: u32, transparent: bool, sapling: bool, orchard: bool) -> anyhow::Result<()> {
|
pub fn store_ua_settings(&self, account: u32, transparent: bool, sapling: bool, orchard: bool) -> anyhow::Result<()> {
|
||||||
self.connection.execute(
|
self.connection.execute(
|
||||||
"INSERT INTO ua_settings(account, transparent, sapling, orchard) VALUES (?1, ?2, ?3, ?4) ON CONFLICT DO NOTHING",
|
"INSERT INTO ua_settings(account, transparent, sapling, orchard) VALUES (?1, ?2, ?3, ?4) ON CONFLICT DO NOTHING",
|
||||||
|
@ -921,6 +956,20 @@ impl DbAdapter {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_ua_settings(&self, account: u32) -> anyhow::Result<UnifiedAddressType> {
|
||||||
|
let tpe = self.connection.query_row("SELECT transparent, sapling, orchard FROM ua_settings WHERE account = ?1", params![account], |row| {
|
||||||
|
let transparent: bool = row.get(0)?;
|
||||||
|
let sapling: bool = row.get(1)?;
|
||||||
|
let orchard: bool = row.get(2)?;
|
||||||
|
Ok(UnifiedAddressType {
|
||||||
|
transparent,
|
||||||
|
sapling,
|
||||||
|
orchard
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
Ok(tpe)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn store_historical_prices(
|
pub fn store_historical_prices(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &[Quote],
|
prices: &[Quote],
|
||||||
|
|
|
@ -95,6 +95,7 @@ mod prices;
|
||||||
mod scan;
|
mod scan;
|
||||||
mod taddr;
|
mod taddr;
|
||||||
mod transaction;
|
mod transaction;
|
||||||
|
mod unified;
|
||||||
// mod ua;
|
// mod ua;
|
||||||
mod zip32;
|
mod zip32;
|
||||||
// mod wallet;
|
// mod wallet;
|
||||||
|
@ -132,6 +133,8 @@ pub use crate::pay::{broadcast_tx, Tx, TxIn, TxOut};
|
||||||
pub use zip32::KeyPack;
|
pub use zip32::KeyPack;
|
||||||
// pub use crate::wallet::{decrypt_backup, encrypt_backup, RecipientMemo, Wallet, WalletBalance};
|
// pub use crate::wallet::{decrypt_backup, encrypt_backup, RecipientMemo, Wallet, WalletBalance};
|
||||||
|
|
||||||
|
pub use unified::{get_unified_address, decode_unified_address};
|
||||||
|
|
||||||
#[cfg(feature = "ledger_sapling")]
|
#[cfg(feature = "ledger_sapling")]
|
||||||
pub use crate::ledger::sapling::build_tx_ledger;
|
pub use crate::ledger::sapling::build_tx_ledger;
|
||||||
|
|
||||||
|
|
|
@ -86,6 +86,8 @@ async fn main() -> anyhow::Result<()> {
|
||||||
get_backup,
|
get_backup,
|
||||||
get_balance,
|
get_balance,
|
||||||
get_address,
|
get_address,
|
||||||
|
get_unified_address,
|
||||||
|
decode_unified_address,
|
||||||
get_tx_history,
|
get_tx_history,
|
||||||
pay,
|
pay,
|
||||||
mark_synced,
|
mark_synced,
|
||||||
|
@ -177,6 +179,20 @@ pub fn get_address() -> Result<String, Error> {
|
||||||
Ok(address)
|
Ok(address)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/unified_address")]
|
||||||
|
pub fn get_unified_address() -> Result<String, Error> {
|
||||||
|
let c = CoinConfig::get_active();
|
||||||
|
let address = warp_api_ffi::api::account::get_unified_address(c.coin, c.id_account)?;
|
||||||
|
Ok(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/decode_unified_address?<address>")]
|
||||||
|
pub fn decode_unified_address(address: String) -> Result<String, Error> {
|
||||||
|
let c = CoinConfig::get_active();
|
||||||
|
let result = warp_api_ffi::api::account::decode_unified_address(c.coin, &address)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/backup")]
|
#[get("/backup")]
|
||||||
pub fn get_backup(config: &State<Config>) -> Result<Json<Backup>, Error> {
|
pub fn get_backup(config: &State<Config>) -> Result<Json<Backup>, Error> {
|
||||||
if !config.allow_backup {
|
if !config.allow_backup {
|
||||||
|
|
|
@ -4,4 +4,4 @@ mod key;
|
||||||
|
|
||||||
pub use note::{OrchardDecrypter, OrchardViewKey, DecryptedOrchardNote};
|
pub use note::{OrchardDecrypter, OrchardViewKey, DecryptedOrchardNote};
|
||||||
pub use hash::OrchardHasher;
|
pub use hash::OrchardHasher;
|
||||||
pub use key::derive_orchard_keys;
|
pub use key::{derive_orchard_keys, OrchardKeyBytes};
|
||||||
|
|
|
@ -1,15 +1,25 @@
|
||||||
use bip39::{Language, Mnemonic};
|
use bip39::{Language, Mnemonic, Seed};
|
||||||
use orchard::keys::{FullViewingKey, SpendingKey};
|
use orchard::Address;
|
||||||
|
use orchard::keys::{FullViewingKey, Scope, SpendingKey};
|
||||||
|
|
||||||
pub struct OrchardKeyBytes {
|
pub struct OrchardKeyBytes {
|
||||||
pub sk: [u8; 32],
|
pub sk: [u8; 32],
|
||||||
pub fvk: [u8; 96],
|
pub fvk: [u8; 96],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl OrchardKeyBytes {
|
||||||
|
pub fn get_address(&self, index: usize) -> Address {
|
||||||
|
let fvk = FullViewingKey::from_bytes(&self.fvk).unwrap();
|
||||||
|
let address = fvk.address_at(index, Scope::External);
|
||||||
|
address
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn derive_orchard_keys(coin_type: u32, seed: &str, account_index: u32) -> OrchardKeyBytes {
|
pub fn derive_orchard_keys(coin_type: u32, seed: &str, account_index: u32) -> OrchardKeyBytes {
|
||||||
let mnemonic = Mnemonic::from_phrase(seed, Language::English).unwrap();
|
let mnemonic = Mnemonic::from_phrase(seed, Language::English).unwrap();
|
||||||
|
let seed = Seed::new(&mnemonic, "");
|
||||||
let sk = SpendingKey::from_zip32_seed(
|
let sk = SpendingKey::from_zip32_seed(
|
||||||
mnemonic.entropy(),
|
seed.as_bytes(),
|
||||||
coin_type,
|
coin_type,
|
||||||
account_index,
|
account_index,
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
|
use orchard::keys::Scope;
|
||||||
use orchard::note_encryption::OrchardDomain;
|
use orchard::note_encryption::OrchardDomain;
|
||||||
use zcash_primitives::consensus::{BlockHeight, Parameters};
|
use zcash_primitives::consensus::{BlockHeight, Parameters};
|
||||||
use crate::chain::Nf;
|
use crate::chain::Nf;
|
||||||
use crate::CompactTx;
|
use crate::{CompactTx, DbAdapterBuilder};
|
||||||
use crate::db::ReceivedNote;
|
use crate::db::ReceivedNote;
|
||||||
use crate::sync::{CompactOutputBytes, DecryptedNote, Node, OutputPosition, TrialDecrypter, ViewKey};
|
use crate::sync::{CompactOutputBytes, DecryptedNote, Node, OutputPosition, TrialDecrypter, ViewKey};
|
||||||
use zcash_note_encryption;
|
use zcash_note_encryption;
|
||||||
use zcash_primitives::sapling::Nullifier;
|
use zcash_primitives::sapling::Nullifier;
|
||||||
|
use zcash_params::coin::CoinType;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct OrchardViewKey {
|
pub struct OrchardViewKey {
|
||||||
pub account: u32,
|
pub account: u32,
|
||||||
pub fvk: orchard::keys::FullViewingKey,
|
pub fvk: orchard::keys::FullViewingKey,
|
||||||
|
@ -50,7 +52,7 @@ impl DecryptedNote<OrchardDomain, OrchardViewKey> for DecryptedOrchardNote {
|
||||||
self.cmx
|
self.cmx
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_received_note(&self, position: u64) -> ReceivedNote {
|
fn to_received_note(&self, _position: u64) -> ReceivedNote {
|
||||||
ReceivedNote {
|
ReceivedNote {
|
||||||
account: self.vk.account,
|
account: self.vk.account,
|
||||||
height: self.output_position.height,
|
height: self.output_position.height,
|
||||||
|
@ -94,3 +96,36 @@ impl <N: Parameters> TrialDecrypter<N, OrchardDomain, OrchardViewKey, DecryptedO
|
||||||
vtx.actions.iter().map(|co| co.into()).collect()
|
vtx.actions.iter().map(|co| co.into()).collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_decrypt() -> anyhow::Result<()> {
|
||||||
|
// let mut nullifier = hex::decode("951ab285b0f4df3ff24f24470dbb8bafa3b5caeeb204fc4465f7ea9c3d5a980a").unwrap();
|
||||||
|
// let mut epk = hex::decode("182d698c3bb8b168d5f9420f1c2e32d94b4dbc0826181c1783ea47fedd31b710").unwrap();
|
||||||
|
// let mut cmx = hex::decode("df45e00eb39e4c281e2804a366d3010b7f663724472d12637e0a749e6ce22719").unwrap();
|
||||||
|
// let ciphertext = hex::decode("d9bc6ee09b0afde5dd69bfdf4b667a38da3e1084e84eb6752d54800b9f5110203b60496ab5313dba3f2acb9ef30bcaf68fbfcc59").unwrap();
|
||||||
|
|
||||||
|
let mut nullifier = hex::decode("ea1b97cc83d326db4130433022f68dd32a0bc707448b19b0980e4e6404412b29").unwrap();
|
||||||
|
let mut epk = hex::decode("e2f666e905666f29bb678c694602b2768bea655c0f2b18f9c342ad8b64b18c0c").unwrap();
|
||||||
|
let mut cmx = hex::decode("4a95dbf0d1d0cac1376a0b8fb0fc2ed2843d0e2670dd976a63386b293f30de25").unwrap();
|
||||||
|
let ciphertext = hex::decode("73640095a90bb03d14f687d6acf4822618a3def1da3b71a588da1c68e25042f7c9aa759778e73aa2bb39d1061e51c1e8cf5e0bce").unwrap();
|
||||||
|
|
||||||
|
let db_builder = DbAdapterBuilder {
|
||||||
|
coin_type: CoinType::Zcash,
|
||||||
|
db_path: "./zec.db".to_string()
|
||||||
|
};
|
||||||
|
let db = db_builder.build()?;
|
||||||
|
let keys = db.get_orchard_fvks()?.first().unwrap().clone();
|
||||||
|
let fvk = keys.fvk;
|
||||||
|
|
||||||
|
let output = CompactOutputBytes {
|
||||||
|
nullifier: nullifier.clone().try_into().unwrap(),
|
||||||
|
epk: epk.try_into().unwrap(),
|
||||||
|
cmx: cmx.try_into().unwrap(),
|
||||||
|
ciphertext: ciphertext.try_into().unwrap()
|
||||||
|
};
|
||||||
|
let domain = OrchardDomain::for_nullifier(orchard::note::Nullifier::from_bytes(&nullifier.try_into().unwrap()).unwrap());
|
||||||
|
let r = zcash_note_encryption::try_compact_note_decryption(&domain, &fvk.to_ivk(Scope::External), &output);
|
||||||
|
println!("{:?}", r);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
27
src/scan.rs
27
src/scan.rs
|
@ -91,29 +91,8 @@ pub async fn sync_async<'a>(
|
||||||
let db = DbAdapter::new(coin_type, &db_path)?;
|
let db = DbAdapter::new(coin_type, &db_path)?;
|
||||||
let height = db.get_db_height()?;
|
let height = db.get_db_height()?;
|
||||||
let hash = db.get_db_hash(height)?;
|
let hash = db.get_db_hash(height)?;
|
||||||
let vks = db.get_fvks()?;
|
let sapling_vks = db.get_sapling_fvks()?;
|
||||||
let sapling_vks: Vec<_> = vks.iter().map(|(&account, ak)| {
|
let orchard_vks = db.get_orchard_fvks()?;
|
||||||
SaplingViewKey {
|
|
||||||
account,
|
|
||||||
fvk: ak.fvk.clone(),
|
|
||||||
ivk: ak.ivk.clone()
|
|
||||||
}
|
|
||||||
}).collect();
|
|
||||||
let orchard_vks: Vec<_> = db.get_seeds()?.iter().map(|a| {
|
|
||||||
let mnemonic = Mnemonic::from_phrase(&a.seed, Language::English).unwrap();
|
|
||||||
let sk = SpendingKey::from_zip32_seed(
|
|
||||||
mnemonic.entropy(),
|
|
||||||
network.coin_type(),
|
|
||||||
a.id_account,
|
|
||||||
).unwrap();
|
|
||||||
let fvk = FullViewingKey::from(&sk);
|
|
||||||
let vk =
|
|
||||||
OrchardViewKey {
|
|
||||||
account: a.id_account,
|
|
||||||
fvk,
|
|
||||||
};
|
|
||||||
vk
|
|
||||||
}).collect();
|
|
||||||
(height, hash, sapling_vks, orchard_vks)
|
(height, hash, sapling_vks, orchard_vks)
|
||||||
};
|
};
|
||||||
let end_height = get_latest_height(&mut client).await?;
|
let end_height = get_latest_height(&mut client).await?;
|
||||||
|
@ -138,6 +117,7 @@ pub async fn sync_async<'a>(
|
||||||
let last_timestamp = last_block.time;
|
let last_timestamp = last_block.time;
|
||||||
|
|
||||||
// Sapling
|
// Sapling
|
||||||
|
log::info!("Sapling");
|
||||||
{
|
{
|
||||||
let decrypter = SaplingDecrypter::new(network);
|
let decrypter = SaplingDecrypter::new(network);
|
||||||
let warper = WarpProcessor::new(SaplingHasher::default());
|
let warper = WarpProcessor::new(SaplingHasher::default());
|
||||||
|
@ -153,6 +133,7 @@ pub async fn sync_async<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Orchard
|
// Orchard
|
||||||
|
log::info!("Orchard");
|
||||||
{
|
{
|
||||||
let decrypter = OrchardDecrypter::new(network);
|
let decrypter = OrchardDecrypter::new(network);
|
||||||
let warper = WarpProcessor::new(OrchardHasher::new());
|
let warper = WarpProcessor::new(OrchardHasher::new());
|
||||||
|
|
|
@ -152,6 +152,7 @@ mod tests {
|
||||||
use crate::db::DbAdapterBuilder;
|
use crate::db::DbAdapterBuilder;
|
||||||
use crate::init_coin;
|
use crate::init_coin;
|
||||||
use crate::sapling::{DecryptedSaplingNote, SaplingDecrypter, SaplingHasher, SaplingViewKey};
|
use crate::sapling::{DecryptedSaplingNote, SaplingDecrypter, SaplingHasher, SaplingViewKey};
|
||||||
|
use crate::scan::Blocks;
|
||||||
use crate::sync::CTree;
|
use crate::sync::CTree;
|
||||||
use crate::sync::tree::WarpProcessor;
|
use crate::sync::tree::WarpProcessor;
|
||||||
use super::Synchronizer;
|
use super::Synchronizer;
|
||||||
|
@ -179,7 +180,7 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
synchronizer.initialize().unwrap();
|
synchronizer.initialize().unwrap();
|
||||||
synchronizer.process(vec![]).unwrap();
|
synchronizer.process(&vec![]).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ use crate::chain::Nf;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
use orchard::note_encryption::OrchardDomain;
|
||||||
use zcash_note_encryption::batch::try_compact_note_decryption;
|
use zcash_note_encryption::batch::try_compact_note_decryption;
|
||||||
use zcash_note_encryption::{BatchDomain, COMPACT_NOTE_SIZE, EphemeralKeyBytes, ShieldedOutput};
|
use zcash_note_encryption::{BatchDomain, COMPACT_NOTE_SIZE, EphemeralKeyBytes, ShieldedOutput};
|
||||||
use zcash_primitives::consensus::{BlockHeight, Parameters};
|
use zcash_primitives::consensus::{BlockHeight, Parameters};
|
||||||
|
@ -57,6 +58,29 @@ pub struct CompactOutputBytes {
|
||||||
pub ciphertext: [u8; 52],
|
pub ciphertext: [u8; 52],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for CompactOutputBytes {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
writeln!(f, "nullifier: {}", hex::encode(self.nullifier))?;
|
||||||
|
writeln!(f, "epk: {}", hex::encode(self.epk))?;
|
||||||
|
writeln!(f, "cmx: {}", hex::encode(self.cmx))?;
|
||||||
|
writeln!(f, "ciphertext: {}", hex::encode(self.ciphertext))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShieldedOutput<OrchardDomain, COMPACT_NOTE_SIZE> for CompactOutputBytes {
|
||||||
|
fn ephemeral_key(&self) -> EphemeralKeyBytes {
|
||||||
|
EphemeralKeyBytes(self.epk)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmstar_bytes(&self) -> [u8; 32] {
|
||||||
|
self.cmx
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enc_ciphertext(&self) -> &[u8; COMPACT_NOTE_SIZE] {
|
||||||
|
&self.ciphertext
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<&CompactSaplingOutput> for CompactOutputBytes {
|
impl From<&CompactSaplingOutput> for CompactOutputBytes {
|
||||||
fn from(co: &CompactSaplingOutput) -> Self {
|
fn from(co: &CompactSaplingOutput) -> Self {
|
||||||
CompactOutputBytes {
|
CompactOutputBytes {
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use orchard::Address;
|
||||||
|
use orchard::keys::{FullViewingKey, Scope};
|
||||||
|
use rusqlite::Connection;
|
||||||
|
use zcash_address::{ToAddress, unified, ZcashAddress};
|
||||||
|
use zcash_address::unified::{Container, Encoding, Receiver};
|
||||||
|
use zcash_client_backend::address::RecipientAddress;
|
||||||
|
use zcash_client_backend::encoding::{AddressCodec, decode_payment_address, encode_payment_address};
|
||||||
|
use zcash_primitives::consensus::{Network, Parameters};
|
||||||
|
use zcash_primitives::legacy::TransparentAddress;
|
||||||
|
use zcash_primitives::sapling::PaymentAddress;
|
||||||
|
use crate::{AccountData, DbAdapter};
|
||||||
|
|
||||||
|
pub struct UnifiedAddressType {
|
||||||
|
pub transparent: bool,
|
||||||
|
pub sapling: bool,
|
||||||
|
pub orchard: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DecodedUA {
|
||||||
|
pub network: Network,
|
||||||
|
pub transparent: Option<TransparentAddress>,
|
||||||
|
pub sapling: Option<PaymentAddress>,
|
||||||
|
pub orchard: Option<Address>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for DecodedUA {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "DecodedUA: {:?} {:?} {:?}",
|
||||||
|
self.transparent.as_ref().map(|a| a.encode(&self.network)),
|
||||||
|
self.sapling.as_ref().map(|a| encode_payment_address(self.network.hrp_sapling_payment_address(), a)),
|
||||||
|
self.orchard.as_ref().map(|a| {
|
||||||
|
let ua = unified::Address(vec![Receiver::Orchard(a.to_raw_address_bytes())]);
|
||||||
|
ua.encode(&network2network(&self.network))
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_unified_address(network: &Network, db: &DbAdapter, account: u32) -> anyhow::Result<String> {
|
||||||
|
let tpe = db.get_ua_settings(account)?;
|
||||||
|
let mut rcvs = vec![];
|
||||||
|
if tpe.transparent {
|
||||||
|
let address = db.get_taddr(account)?;
|
||||||
|
if let Some(address) = address {
|
||||||
|
let address = TransparentAddress::decode(network, &address)?;
|
||||||
|
if let TransparentAddress::PublicKey(pkh) = address {
|
||||||
|
let rcv = Receiver::P2pkh(pkh);
|
||||||
|
rcvs.push(rcv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tpe.sapling {
|
||||||
|
let AccountData { address , .. } = db.get_account_info(account)?;
|
||||||
|
let pa = decode_payment_address(network.hrp_sapling_payment_address(), &address).unwrap();
|
||||||
|
let rcv = Receiver::Sapling(pa.to_bytes());
|
||||||
|
rcvs.push(rcv);
|
||||||
|
}
|
||||||
|
if tpe.orchard {
|
||||||
|
let AccountData { address: zaddr, .. } = db.get_account_info(account)?;
|
||||||
|
let okey = db.get_orchard(account)?;
|
||||||
|
if let Some(okey) = okey {
|
||||||
|
let fvk = FullViewingKey::from_bytes(&okey.fvk).unwrap();
|
||||||
|
let address = fvk.address_at(0usize, Scope::External);
|
||||||
|
let rcv = Receiver::Orchard(address.to_raw_address_bytes());
|
||||||
|
rcvs.push(rcv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let addresses = unified::Address(rcvs);
|
||||||
|
let unified_address = ZcashAddress::from_unified(network2network(network), addresses);
|
||||||
|
Ok(unified_address.encode())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode_unified_address(network: &Network, ua: &str) -> anyhow::Result<DecodedUA> {
|
||||||
|
let mut decoded_ua = DecodedUA {
|
||||||
|
network: network.clone(),
|
||||||
|
transparent: None,
|
||||||
|
sapling: None,
|
||||||
|
orchard: None
|
||||||
|
};
|
||||||
|
let network = network2network(network);
|
||||||
|
let (a_network, ua) = unified::Address::decode(ua)?;
|
||||||
|
if network != a_network {
|
||||||
|
anyhow::bail!("Invalid network")
|
||||||
|
}
|
||||||
|
|
||||||
|
for recv in ua.items_as_parsed() {
|
||||||
|
match recv {
|
||||||
|
Receiver::Orchard(addr) => {
|
||||||
|
decoded_ua.orchard = Address::from_raw_address_bytes(addr).into();
|
||||||
|
}
|
||||||
|
Receiver::Sapling(addr) => {
|
||||||
|
decoded_ua.sapling = PaymentAddress::from_bytes(addr);
|
||||||
|
}
|
||||||
|
Receiver::P2pkh(addr) => {
|
||||||
|
decoded_ua.transparent = Some(TransparentAddress::PublicKey(*addr));
|
||||||
|
}
|
||||||
|
Receiver::P2sh(_) => {}
|
||||||
|
Receiver::Unknown { .. } => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(decoded_ua)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn network2network(n: &Network) -> zcash_address::Network {
|
||||||
|
match n {
|
||||||
|
Network::MainNetwork => zcash_address::Network::Main,
|
||||||
|
Network::TestNetwork => zcash_address::Network::Test,
|
||||||
|
Network::YCashMainNetwork => zcash_address::Network::Main,
|
||||||
|
Network::YCashTestNetwork => zcash_address::Network::Test,
|
||||||
|
Network::PirateChainMainNetwork => zcash_address::Network::Main,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// u1pncsxa8jt7aq37r8uvhjrgt7sv8a665hdw44rqa28cd9t6qqmktzwktw772nlle6skkkxwmtzxaan3slntqev03g70tzpky3c58hfgvfjkcky255cwqgfuzdjcktfl7pjalt5sl33se75pmga09etn9dplr98eq2g8cgmvgvx6jx2a2xhy39x96c6rumvlyt35whml87r064qdzw30e
|
Loading…
Reference in New Issue