Merge
This commit is contained in:
46
lib/src/lightwallet/address.rs
Normal file
46
lib/src/lightwallet/address.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
//! Structs for handling supported address types.
|
||||
|
||||
use pairing::bls12_381::Bls12;
|
||||
use zcash_primitives::primitives::PaymentAddress;
|
||||
use zcash_client_backend::encoding::{decode_payment_address, decode_transparent_address};
|
||||
use zcash_primitives::legacy::TransparentAddress;
|
||||
|
||||
/// An address that funds can be sent to.
|
||||
pub enum RecipientAddress {
|
||||
Shielded(PaymentAddress<Bls12>),
|
||||
Transparent(TransparentAddress),
|
||||
}
|
||||
|
||||
impl From<PaymentAddress<Bls12>> for RecipientAddress {
|
||||
fn from(addr: PaymentAddress<Bls12>) -> Self {
|
||||
RecipientAddress::Shielded(addr)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TransparentAddress> for RecipientAddress {
|
||||
fn from(addr: TransparentAddress) -> Self {
|
||||
RecipientAddress::Transparent(addr)
|
||||
}
|
||||
}
|
||||
|
||||
impl RecipientAddress {
|
||||
pub fn from_str(s: &str, hrp_sapling_address: &str, b58_pubkey_address: [u8; 2], b58_script_address: [u8; 2]) -> Option<Self> {
|
||||
// Try to match a sapling z address
|
||||
if let Some(pa) = match decode_payment_address(hrp_sapling_address, s) {
|
||||
Ok(ret) => ret,
|
||||
Err(_) => None
|
||||
}
|
||||
{
|
||||
Some(RecipientAddress::Shielded(pa)) // Matched a shielded address
|
||||
} else if let Some(addr) = match decode_transparent_address(
|
||||
&b58_pubkey_address, &b58_script_address, s) {
|
||||
Ok(ret) => ret,
|
||||
Err(_) => None
|
||||
}
|
||||
{
|
||||
Some(RecipientAddress::Transparent(addr)) // Matched a transparent address
|
||||
} else {
|
||||
None // Didn't match anything
|
||||
}
|
||||
}
|
||||
}
|
||||
493
lib/src/lightwallet/data.rs
Normal file
493
lib/src/lightwallet/data.rs
Normal file
@@ -0,0 +1,493 @@
|
||||
use std::io::{self, Read, Write};
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
use pairing::bls12_381::{Bls12};
|
||||
use ff::{PrimeField, PrimeFieldRepr};
|
||||
|
||||
use zcash_primitives::{
|
||||
block::BlockHash,
|
||||
merkle_tree::{CommitmentTree, IncrementalWitness},
|
||||
sapling::Node,
|
||||
serialize::{Vector, Optional},
|
||||
transaction::{
|
||||
components::{OutPoint},
|
||||
TxId,
|
||||
},
|
||||
note_encryption::{Memo,},
|
||||
zip32::{ExtendedFullViewingKey,},
|
||||
JUBJUB,
|
||||
primitives::{Diversifier, Note,},
|
||||
jubjub::{
|
||||
JubjubEngine,
|
||||
fs::{Fs, FsRepr},
|
||||
}
|
||||
};
|
||||
use zcash_primitives::zip32::ExtendedSpendingKey;
|
||||
|
||||
|
||||
pub struct BlockData {
|
||||
pub height: i32,
|
||||
pub hash: BlockHash,
|
||||
pub tree: CommitmentTree<Node>,
|
||||
}
|
||||
|
||||
impl BlockData {
|
||||
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
|
||||
let height = reader.read_i32::<LittleEndian>()?;
|
||||
|
||||
let mut hash_bytes = [0; 32];
|
||||
reader.read_exact(&mut hash_bytes)?;
|
||||
|
||||
let tree = CommitmentTree::<Node>::read(&mut reader)?;
|
||||
|
||||
let endtag = reader.read_u64::<LittleEndian>()?;
|
||||
if endtag != 11 {
|
||||
println!("End tag for blockdata {}", endtag);
|
||||
}
|
||||
|
||||
|
||||
Ok(BlockData{
|
||||
height,
|
||||
hash: BlockHash{ 0: hash_bytes },
|
||||
tree
|
||||
})
|
||||
}
|
||||
|
||||
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
writer.write_i32::<LittleEndian>(self.height)?;
|
||||
writer.write_all(&self.hash.0)?;
|
||||
self.tree.write(&mut writer)?;
|
||||
writer.write_u64::<LittleEndian>(11)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SaplingNoteData {
|
||||
pub(super) account: usize,
|
||||
pub(super) extfvk: ExtendedFullViewingKey, // Technically, this should be recoverable from the account number, but we're going to refactor this in the future, so I'll write it again here.
|
||||
pub diversifier: Diversifier,
|
||||
pub note: Note<Bls12>,
|
||||
pub(super) witnesses: Vec<IncrementalWitness<Node>>,
|
||||
pub(super) nullifier: [u8; 32],
|
||||
pub spent: Option<TxId>, // If this note was confirmed spent
|
||||
pub unconfirmed_spent: Option<TxId>, // If this note was spent in a send, but has not yet been confirmed.
|
||||
pub memo: Option<Memo>,
|
||||
pub is_change: bool,
|
||||
// TODO: We need to remove the unconfirmed_spent (i.e., set it to None) if the Tx has expired
|
||||
}
|
||||
|
||||
|
||||
/// Reads an FsRepr from [u8] of length 32
|
||||
/// This will panic (abort) if length provided is
|
||||
/// not correct
|
||||
/// TODO: This is duplicate from rustzcash.rs
|
||||
fn read_fs(from: &[u8]) -> FsRepr {
|
||||
assert_eq!(from.len(), 32);
|
||||
|
||||
let mut f = <<Bls12 as JubjubEngine>::Fs as PrimeField>::Repr::default();
|
||||
f.read_le(from).expect("length is 32 bytes");
|
||||
|
||||
f
|
||||
}
|
||||
|
||||
// Reading a note also needs the corresponding address to read from.
|
||||
pub fn read_note<R: Read>(mut reader: R) -> io::Result<(u64, Fs)> {
|
||||
let value = reader.read_u64::<LittleEndian>()?;
|
||||
|
||||
let mut r_bytes: [u8; 32] = [0; 32];
|
||||
reader.read_exact(&mut r_bytes)?;
|
||||
|
||||
let r = match Fs::from_repr(read_fs(&r_bytes)) {
|
||||
Ok(r) => r,
|
||||
Err(_) => return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput, "Couldn't parse randomness"))
|
||||
};
|
||||
|
||||
Ok((value, r))
|
||||
}
|
||||
|
||||
impl SaplingNoteData {
|
||||
fn serialized_version() -> u64 {
|
||||
1
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
extfvk: &ExtendedFullViewingKey,
|
||||
output: zcash_client_backend::wallet::WalletShieldedOutput
|
||||
) -> Self {
|
||||
let witness = output.witness;
|
||||
let nf = {
|
||||
let mut nf = [0; 32];
|
||||
nf.copy_from_slice(
|
||||
&output
|
||||
.note
|
||||
.nf(&extfvk.fvk.vk, witness.position() as u64, &JUBJUB),
|
||||
);
|
||||
nf
|
||||
};
|
||||
|
||||
SaplingNoteData {
|
||||
account: output.account,
|
||||
extfvk: extfvk.clone(),
|
||||
diversifier: output.to.diversifier,
|
||||
note: output.note,
|
||||
witnesses: vec![witness],
|
||||
nullifier: nf,
|
||||
spent: None,
|
||||
unconfirmed_spent: None,
|
||||
memo: None,
|
||||
is_change: output.is_change,
|
||||
}
|
||||
}
|
||||
|
||||
// Reading a note also needs the corresponding address to read from.
|
||||
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
|
||||
let version = reader.read_u64::<LittleEndian>()?;
|
||||
assert_eq!(version, SaplingNoteData::serialized_version());
|
||||
|
||||
let account = reader.read_u64::<LittleEndian>()? as usize;
|
||||
|
||||
let extfvk = ExtendedFullViewingKey::read(&mut reader)?;
|
||||
|
||||
let mut diversifier_bytes = [0u8; 11];
|
||||
reader.read_exact(&mut diversifier_bytes)?;
|
||||
let diversifier = Diversifier{0: diversifier_bytes};
|
||||
|
||||
// To recover the note, read the value and r, and then use the payment address
|
||||
// to recreate the note
|
||||
let (value, r) = read_note(&mut reader)?; // TODO: This method is in a different package, because of some fields that are private
|
||||
|
||||
let maybe_note = extfvk.fvk.vk.into_payment_address(diversifier, &JUBJUB).unwrap().create_note(value, r, &JUBJUB);
|
||||
|
||||
let note = match maybe_note {
|
||||
Some(n) => Ok(n),
|
||||
None => Err(io::Error::new(io::ErrorKind::InvalidInput, "Couldn't create the note for the address"))
|
||||
}?;
|
||||
|
||||
let witnesses = Vector::read(&mut reader, |r| IncrementalWitness::<Node>::read(r))?;
|
||||
|
||||
let mut nullifier = [0u8; 32];
|
||||
reader.read_exact(&mut nullifier)?;
|
||||
|
||||
// Note that this is only the spent field, we ignore the unconfirmed_spent field.
|
||||
// The reason is that unconfirmed spents are only in memory, and we need to get the actual value of spent
|
||||
// from the blockchain anyway.
|
||||
let spent = Optional::read(&mut reader, |r| {
|
||||
let mut txid_bytes = [0u8; 32];
|
||||
r.read_exact(&mut txid_bytes)?;
|
||||
Ok(TxId{0: txid_bytes})
|
||||
})?;
|
||||
|
||||
let memo = Optional::read(&mut reader, |r| {
|
||||
let mut memo_bytes = [0u8; 512];
|
||||
r.read_exact(&mut memo_bytes)?;
|
||||
match Memo::from_bytes(&memo_bytes) {
|
||||
Some(m) => Ok(m),
|
||||
None => Err(io::Error::new(io::ErrorKind::InvalidInput, "Couldn't create the memo"))
|
||||
}
|
||||
})?;
|
||||
|
||||
let is_change: bool = reader.read_u8()? > 0;
|
||||
|
||||
Ok(SaplingNoteData {
|
||||
account,
|
||||
extfvk,
|
||||
diversifier,
|
||||
note,
|
||||
witnesses,
|
||||
nullifier,
|
||||
spent,
|
||||
unconfirmed_spent: None,
|
||||
memo,
|
||||
is_change,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
// Write a version number first, so we can later upgrade this if needed.
|
||||
writer.write_u64::<LittleEndian>(SaplingNoteData::serialized_version())?;
|
||||
|
||||
writer.write_u64::<LittleEndian>(self.account as u64)?;
|
||||
|
||||
self.extfvk.write(&mut writer)?;
|
||||
|
||||
writer.write_all(&self.diversifier.0)?;
|
||||
|
||||
// Writing the note means writing the note.value and note.r. The Note is recoverable
|
||||
// from these 2 values and the Payment address.
|
||||
writer.write_u64::<LittleEndian>(self.note.value)?;
|
||||
|
||||
let mut rcm = [0; 32];
|
||||
self.note.r.into_repr().write_le(&mut rcm[..])?;
|
||||
writer.write_all(&rcm)?;
|
||||
|
||||
Vector::write(&mut writer, &self.witnesses, |wr, wi| wi.write(wr) )?;
|
||||
|
||||
writer.write_all(&self.nullifier)?;
|
||||
Optional::write(&mut writer, &self.spent, |w, t| w.write_all(&t.0))?;
|
||||
|
||||
Optional::write(&mut writer, &self.memo, |w, m| w.write_all(m.as_bytes()))?;
|
||||
|
||||
writer.write_u8(if self.is_change {1} else {0})?;
|
||||
|
||||
// Note that we don't write the unconfirmed_spent field, because if the wallet is restarted,
|
||||
// we don't want to be beholden to any expired txns
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Utxo {
|
||||
pub address: String,
|
||||
pub txid: TxId,
|
||||
pub output_index: u64,
|
||||
pub script: Vec<u8>,
|
||||
pub value: u64,
|
||||
pub height: i32,
|
||||
|
||||
pub spent: Option<TxId>, // If this utxo was confirmed spent
|
||||
pub unconfirmed_spent: Option<TxId>, // If this utxo was spent in a send, but has not yet been confirmed.
|
||||
}
|
||||
|
||||
impl Utxo {
|
||||
pub fn serialized_version() -> u64 {
|
||||
return 1;
|
||||
}
|
||||
|
||||
pub fn to_outpoint(&self) -> OutPoint {
|
||||
OutPoint { hash: self.txid.0, n: self.output_index as u32 }
|
||||
}
|
||||
|
||||
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
|
||||
let version = reader.read_u64::<LittleEndian>()?;
|
||||
assert_eq!(version, Utxo::serialized_version());
|
||||
|
||||
let address_len = reader.read_i32::<LittleEndian>()?;
|
||||
let mut address_bytes = vec![0; address_len as usize];
|
||||
reader.read_exact(&mut address_bytes)?;
|
||||
let address = String::from_utf8(address_bytes).unwrap();
|
||||
assert_eq!(address.chars().take(1).collect::<Vec<char>>()[0], 't');
|
||||
|
||||
let mut txid_bytes = [0; 32];
|
||||
reader.read_exact(&mut txid_bytes)?;
|
||||
let txid = TxId { 0: txid_bytes };
|
||||
|
||||
let output_index = reader.read_u64::<LittleEndian>()?;
|
||||
let value = reader.read_u64::<LittleEndian>()?;
|
||||
let height = reader.read_i32::<LittleEndian>()?;
|
||||
|
||||
let script = Vector::read(&mut reader, |r| {
|
||||
let mut byte = [0; 1];
|
||||
r.read_exact(&mut byte)?;
|
||||
Ok(byte[0])
|
||||
})?;
|
||||
|
||||
let spent = Optional::read(&mut reader, |r| {
|
||||
let mut txbytes = [0u8; 32];
|
||||
r.read_exact(&mut txbytes)?;
|
||||
Ok(TxId{0: txbytes})
|
||||
})?;
|
||||
|
||||
// Note that we don't write the unconfirmed spent field, because if the wallet is restarted, we'll reset any unconfirmed stuff.
|
||||
|
||||
Ok(Utxo {
|
||||
address,
|
||||
txid,
|
||||
output_index,
|
||||
script,
|
||||
value,
|
||||
height,
|
||||
spent,
|
||||
unconfirmed_spent: None::<TxId>,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
writer.write_u64::<LittleEndian>(Utxo::serialized_version())?;
|
||||
|
||||
writer.write_u32::<LittleEndian>(self.address.as_bytes().len() as u32)?;
|
||||
writer.write_all(self.address.as_bytes())?;
|
||||
|
||||
writer.write_all(&self.txid.0)?;
|
||||
|
||||
writer.write_u64::<LittleEndian>(self.output_index)?;
|
||||
writer.write_u64::<LittleEndian>(self.value)?;
|
||||
writer.write_i32::<LittleEndian>(self.height)?;
|
||||
|
||||
Vector::write(&mut writer, &self.script, |w, b| w.write_all(&[*b]))?;
|
||||
|
||||
Optional::write(&mut writer, &self.spent, |w, txid| w.write_all(&txid.0))?;
|
||||
|
||||
// Note that we don't write the unconfirmed spent field, because if the wallet is restarted, we'll reset any unconfirmed stuff.
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OutgoingTxMetadata {
|
||||
pub address: String,
|
||||
pub value : u64,
|
||||
pub memo : Memo,
|
||||
}
|
||||
|
||||
impl OutgoingTxMetadata {
|
||||
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
|
||||
let address_len = reader.read_u64::<LittleEndian>()?;
|
||||
let mut address_bytes = vec![0; address_len as usize];
|
||||
reader.read_exact(&mut address_bytes)?;
|
||||
let address = String::from_utf8(address_bytes).unwrap();
|
||||
|
||||
let value = reader.read_u64::<LittleEndian>()?;
|
||||
|
||||
let mut memo_bytes = [0u8; 512];
|
||||
reader.read_exact(&mut memo_bytes)?;
|
||||
let memo = Memo::from_bytes(&memo_bytes).unwrap();
|
||||
|
||||
Ok(OutgoingTxMetadata{
|
||||
address,
|
||||
value,
|
||||
memo,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
// Strings are written as len + utf8
|
||||
writer.write_u64::<LittleEndian>(self.address.as_bytes().len() as u64)?;
|
||||
writer.write_all(self.address.as_bytes())?;
|
||||
|
||||
writer.write_u64::<LittleEndian>(self.value)?;
|
||||
writer.write_all(self.memo.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WalletTx {
|
||||
pub block: i32,
|
||||
|
||||
// Txid of this transaction. It's duplicated here (It is also the Key in the HashMap that points to this
|
||||
// WalletTx in LightWallet::txs)
|
||||
pub txid: TxId,
|
||||
|
||||
// List of all notes received in this tx. Some of these might be change notes.
|
||||
pub notes: Vec<SaplingNoteData>,
|
||||
|
||||
// List of all Utxos received in this Tx. Some of these might be change notes
|
||||
pub utxos: Vec<Utxo>,
|
||||
|
||||
// Total shielded value spent in this Tx. Note that this is the value of the wallet's notes spent.
|
||||
// Some change may be returned in one of the notes above. Subtract the two to get the actual value spent.
|
||||
// Also note that even after subtraction, you might need to account for transparent inputs and outputs
|
||||
// to make sure the value is accurate.
|
||||
pub total_shielded_value_spent: u64,
|
||||
|
||||
// Total amount of transparent funds that belong to us that were spent in this Tx.
|
||||
pub total_transparent_value_spent : u64,
|
||||
|
||||
// All outgoing sapling sends to addresses outside this wallet
|
||||
pub outgoing_metadata: Vec<OutgoingTxMetadata>,
|
||||
|
||||
pub full_tx_scanned: bool,
|
||||
}
|
||||
|
||||
impl WalletTx {
|
||||
pub fn serialized_version() -> u64 {
|
||||
return 3;
|
||||
}
|
||||
|
||||
pub fn new(height: i32, txid: &TxId) -> Self {
|
||||
WalletTx {
|
||||
block: height,
|
||||
txid: txid.clone(),
|
||||
notes: vec![],
|
||||
utxos: vec![],
|
||||
total_shielded_value_spent: 0,
|
||||
total_transparent_value_spent: 0,
|
||||
outgoing_metadata: vec![],
|
||||
full_tx_scanned: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
|
||||
let version = reader.read_u64::<LittleEndian>()?;
|
||||
assert!(version <= WalletTx::serialized_version());
|
||||
|
||||
let block = reader.read_i32::<LittleEndian>()?;
|
||||
|
||||
let mut txid_bytes = [0u8; 32];
|
||||
reader.read_exact(&mut txid_bytes)?;
|
||||
|
||||
let txid = TxId{0: txid_bytes};
|
||||
|
||||
let notes = Vector::read(&mut reader, |r| SaplingNoteData::read(r))?;
|
||||
let utxos = Vector::read(&mut reader, |r| Utxo::read(r))?;
|
||||
|
||||
let total_shielded_value_spent = reader.read_u64::<LittleEndian>()?;
|
||||
let total_transparent_value_spent = reader.read_u64::<LittleEndian>()?;
|
||||
|
||||
// Outgoing metadata was only added in version 2
|
||||
let outgoing_metadata = Vector::read(&mut reader, |r| OutgoingTxMetadata::read(r))?;
|
||||
|
||||
let full_tx_scanned = reader.read_u8()? > 0;
|
||||
|
||||
Ok(WalletTx{
|
||||
block,
|
||||
txid,
|
||||
notes,
|
||||
utxos,
|
||||
total_shielded_value_spent,
|
||||
total_transparent_value_spent,
|
||||
outgoing_metadata,
|
||||
full_tx_scanned
|
||||
})
|
||||
}
|
||||
|
||||
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
writer.write_u64::<LittleEndian>(WalletTx::serialized_version())?;
|
||||
|
||||
writer.write_i32::<LittleEndian>(self.block)?;
|
||||
|
||||
writer.write_all(&self.txid.0)?;
|
||||
|
||||
Vector::write(&mut writer, &self.notes, |w, nd| nd.write(w))?;
|
||||
Vector::write(&mut writer, &self.utxos, |w, u| u.write(w))?;
|
||||
|
||||
writer.write_u64::<LittleEndian>(self.total_shielded_value_spent)?;
|
||||
writer.write_u64::<LittleEndian>(self.total_transparent_value_spent)?;
|
||||
|
||||
// Write the outgoing metadata
|
||||
Vector::write(&mut writer, &self.outgoing_metadata, |w, om| om.write(w))?;
|
||||
|
||||
writer.write_u8(if self.full_tx_scanned {1} else {0})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SpendableNote {
|
||||
pub txid: TxId,
|
||||
pub nullifier: [u8; 32],
|
||||
pub diversifier: Diversifier,
|
||||
pub note: Note<Bls12>,
|
||||
pub witness: IncrementalWitness<Node>,
|
||||
pub extsk: ExtendedSpendingKey,
|
||||
}
|
||||
|
||||
impl SpendableNote {
|
||||
pub fn from(txid: TxId, nd: &SaplingNoteData, anchor_offset: usize, extsk: &ExtendedSpendingKey) -> Option<Self> {
|
||||
// Include only notes that haven't been spent, or haven't been included in an unconfirmed spend yet.
|
||||
if nd.spent.is_none() && nd.unconfirmed_spent.is_none() {
|
||||
let witness = nd.witnesses.get(nd.witnesses.len() - anchor_offset - 1);
|
||||
|
||||
witness.map(|w| SpendableNote {
|
||||
txid,
|
||||
nullifier: nd.nullifier,
|
||||
diversifier: nd.diversifier,
|
||||
note: nd.note.clone(),
|
||||
witness: w.clone(),
|
||||
extsk: extsk.clone(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
127
lib/src/lightwallet/extended_key.rs
Normal file
127
lib/src/lightwallet/extended_key.rs
Normal file
@@ -0,0 +1,127 @@
|
||||
use ring::{
|
||||
digest,
|
||||
hmac::{SigningContext, SigningKey},
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use secp256k1::{PublicKey, Secp256k1, SecretKey, SignOnly, VerifyOnly, Error};
|
||||
|
||||
lazy_static! {
|
||||
static ref SECP256K1_SIGN_ONLY: Secp256k1<SignOnly> = Secp256k1::signing_only();
|
||||
static ref SECP256K1_VERIFY_ONLY: Secp256k1<VerifyOnly> = Secp256k1::verification_only();
|
||||
}
|
||||
/// Random entropy, part of extended key.
|
||||
type ChainCode = Vec<u8>;
|
||||
|
||||
|
||||
const HARDENED_KEY_START_INDEX: u32 = 2_147_483_648; // 2 ** 31
|
||||
|
||||
/// KeyIndex indicates the key type and index of a child key.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum KeyIndex {
|
||||
/// Normal key, index range is from 0 to 2 ** 31 - 1
|
||||
Normal(u32),
|
||||
/// Hardened key, index range is from 2 ** 31 to 2 ** 32 - 1
|
||||
Hardened(u32),
|
||||
}
|
||||
|
||||
impl KeyIndex {
|
||||
|
||||
/// Check index range.
|
||||
pub fn is_valid(self) -> bool {
|
||||
match self {
|
||||
KeyIndex::Normal(i) => i < HARDENED_KEY_START_INDEX,
|
||||
KeyIndex::Hardened(i) => i >= HARDENED_KEY_START_INDEX,
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate Hardened KeyIndex from normalize index value.
|
||||
pub fn hardened_from_normalize_index(i: u32) -> Result<KeyIndex, Error> {
|
||||
if i < HARDENED_KEY_START_INDEX {
|
||||
Ok(KeyIndex::Hardened(HARDENED_KEY_START_INDEX + i))
|
||||
} else {
|
||||
Ok(KeyIndex::Hardened(i))
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate KeyIndex from raw index value.
|
||||
pub fn from_index(i: u32) -> Result<Self, Error> {
|
||||
if i < HARDENED_KEY_START_INDEX {
|
||||
Ok(KeyIndex::Normal(i))
|
||||
} else {
|
||||
Ok(KeyIndex::Hardened(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for KeyIndex {
|
||||
fn from(index: u32) -> Self {
|
||||
KeyIndex::from_index(index).expect("KeyIndex")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// ExtendedPrivKey is used for child key derivation.
|
||||
/// See [secp256k1 crate documentation](https://docs.rs/secp256k1) for SecretKey signatures usage.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ExtendedPrivKey {
|
||||
pub private_key: SecretKey,
|
||||
pub chain_code: ChainCode,
|
||||
}
|
||||
|
||||
|
||||
impl ExtendedPrivKey {
|
||||
|
||||
/// Generate an ExtendedPrivKey from seed
|
||||
pub fn with_seed(seed: &[u8]) -> Result<ExtendedPrivKey, Error> {
|
||||
let signature = {
|
||||
let signing_key = SigningKey::new(&digest::SHA512, b"Bitcoin seed");
|
||||
let mut h = SigningContext::with_key(&signing_key);
|
||||
h.update(&seed);
|
||||
h.sign()
|
||||
};
|
||||
let sig_bytes = signature.as_ref();
|
||||
let (key, chain_code) = sig_bytes.split_at(sig_bytes.len() / 2);
|
||||
let private_key = SecretKey::from_slice(key)?;
|
||||
Ok(ExtendedPrivKey {
|
||||
private_key,
|
||||
chain_code: chain_code.to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
fn sign_hardended_key(&self, index: u32) -> ring::hmac::Signature {
|
||||
let signing_key = SigningKey::new(&digest::SHA512, &self.chain_code);
|
||||
let mut h = SigningContext::with_key(&signing_key);
|
||||
h.update(&[0x00]);
|
||||
h.update(&self.private_key[..]);
|
||||
h.update(&index.to_be_bytes());
|
||||
h.sign()
|
||||
}
|
||||
|
||||
fn sign_normal_key(&self, index: u32) -> ring::hmac::Signature {
|
||||
let signing_key = SigningKey::new(&digest::SHA512, &self.chain_code);
|
||||
let mut h = SigningContext::with_key(&signing_key);
|
||||
let public_key = PublicKey::from_secret_key(&SECP256K1_SIGN_ONLY, &self.private_key);
|
||||
h.update(&public_key.serialize());
|
||||
h.update(&index.to_be_bytes());
|
||||
h.sign()
|
||||
}
|
||||
|
||||
/// Derive a child key from ExtendedPrivKey.
|
||||
pub fn derive_private_key(&self, key_index: KeyIndex) -> Result<ExtendedPrivKey, Error> {
|
||||
if !key_index.is_valid() {
|
||||
return Err(Error::InvalidTweak);
|
||||
}
|
||||
let signature = match key_index {
|
||||
KeyIndex::Hardened(index) => self.sign_hardended_key(index),
|
||||
KeyIndex::Normal(index) => self.sign_normal_key(index),
|
||||
};
|
||||
let sig_bytes = signature.as_ref();
|
||||
let (key, chain_code) = sig_bytes.split_at(sig_bytes.len() / 2);
|
||||
let mut private_key = SecretKey::from_slice(key)?;
|
||||
private_key.add_assign(&self.private_key[..])?;
|
||||
Ok(ExtendedPrivKey {
|
||||
private_key,
|
||||
chain_code: chain_code.to_vec(),
|
||||
})
|
||||
}
|
||||
}
|
||||
123
lib/src/lightwallet/prover.rs
Normal file
123
lib/src/lightwallet/prover.rs
Normal file
@@ -0,0 +1,123 @@
|
||||
//! Abstractions over the proving system and parameters for ease of use.
|
||||
|
||||
use bellman::groth16::{prepare_verifying_key, Parameters, PreparedVerifyingKey};
|
||||
use pairing::bls12_381::{Bls12, Fr};
|
||||
use zcash_primitives::{
|
||||
jubjub::{edwards, fs::Fs, Unknown},
|
||||
primitives::{Diversifier, PaymentAddress, ProofGenerationKey},
|
||||
redjubjub::{PublicKey, Signature},
|
||||
transaction::components::Amount
|
||||
};
|
||||
use zcash_primitives::{
|
||||
merkle_tree::CommitmentTreeWitness, prover::TxProver, sapling::Node,
|
||||
transaction::components::GROTH_PROOF_SIZE, JUBJUB,
|
||||
};
|
||||
use zcash_proofs::sapling::SaplingProvingContext;
|
||||
|
||||
/// An implementation of [`TxProver`] using Sapling Spend and Output parameters provided
|
||||
/// in-memory.
|
||||
pub struct InMemTxProver {
|
||||
spend_params: Parameters<Bls12>,
|
||||
spend_vk: PreparedVerifyingKey<Bls12>,
|
||||
output_params: Parameters<Bls12>,
|
||||
}
|
||||
|
||||
impl InMemTxProver {
|
||||
pub fn new(spend_params: &[u8], output_params: &[u8]) -> Self {
|
||||
// Deserialize params
|
||||
let spend_params = Parameters::<Bls12>::read(spend_params, false)
|
||||
.expect("couldn't deserialize Sapling spend parameters file");
|
||||
let output_params = Parameters::<Bls12>::read(output_params, false)
|
||||
.expect("couldn't deserialize Sapling spend parameters file");
|
||||
|
||||
// Prepare verifying keys
|
||||
let spend_vk = prepare_verifying_key(&spend_params.vk);
|
||||
|
||||
InMemTxProver {
|
||||
spend_params,
|
||||
spend_vk,
|
||||
output_params,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TxProver for InMemTxProver {
|
||||
type SaplingProvingContext = SaplingProvingContext;
|
||||
|
||||
fn new_sapling_proving_context(&self) -> Self::SaplingProvingContext {
|
||||
SaplingProvingContext::new()
|
||||
}
|
||||
|
||||
fn spend_proof(
|
||||
&self,
|
||||
ctx: &mut Self::SaplingProvingContext,
|
||||
proof_generation_key: ProofGenerationKey<Bls12>,
|
||||
diversifier: Diversifier,
|
||||
rcm: Fs,
|
||||
ar: Fs,
|
||||
value: u64,
|
||||
anchor: Fr,
|
||||
witness: CommitmentTreeWitness<Node>,
|
||||
) -> Result<
|
||||
(
|
||||
[u8; GROTH_PROOF_SIZE],
|
||||
edwards::Point<Bls12, Unknown>,
|
||||
PublicKey<Bls12>,
|
||||
),
|
||||
(),
|
||||
> {
|
||||
let (proof, cv, rk) = ctx.spend_proof(
|
||||
proof_generation_key,
|
||||
diversifier,
|
||||
rcm,
|
||||
ar,
|
||||
value,
|
||||
anchor,
|
||||
witness,
|
||||
&self.spend_params,
|
||||
&self.spend_vk,
|
||||
&JUBJUB,
|
||||
)?;
|
||||
|
||||
let mut zkproof = [0u8; GROTH_PROOF_SIZE];
|
||||
proof
|
||||
.write(&mut zkproof[..])
|
||||
.expect("should be able to serialize a proof");
|
||||
|
||||
Ok((zkproof, cv, rk))
|
||||
}
|
||||
|
||||
fn output_proof(
|
||||
&self,
|
||||
ctx: &mut Self::SaplingProvingContext,
|
||||
esk: Fs,
|
||||
payment_address: PaymentAddress<Bls12>,
|
||||
rcm: Fs,
|
||||
value: u64,
|
||||
) -> ([u8; GROTH_PROOF_SIZE], edwards::Point<Bls12, Unknown>) {
|
||||
let (proof, cv) = ctx.output_proof(
|
||||
esk,
|
||||
payment_address,
|
||||
rcm,
|
||||
value,
|
||||
&self.output_params,
|
||||
&JUBJUB,
|
||||
);
|
||||
|
||||
let mut zkproof = [0u8; GROTH_PROOF_SIZE];
|
||||
proof
|
||||
.write(&mut zkproof[..])
|
||||
.expect("should be able to serialize a proof");
|
||||
|
||||
(zkproof, cv)
|
||||
}
|
||||
|
||||
fn binding_sig(
|
||||
&self,
|
||||
ctx: &mut Self::SaplingProvingContext,
|
||||
value_balance: Amount,
|
||||
sighash: &[u8; 32],
|
||||
) -> Result<Signature, ()> {
|
||||
ctx.binding_sig(value_balance, sighash, &JUBJUB)
|
||||
}
|
||||
}
|
||||
21
lib/src/lightwallet/utils.rs
Normal file
21
lib/src/lightwallet/utils.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use std::io::{self, Read, Write};
|
||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
pub fn read_string<R: Read>(mut reader: R) -> io::Result<String> {
|
||||
// Strings are written as <littleendian> len + bytes
|
||||
let str_len = reader.read_u64::<LittleEndian>()?;
|
||||
let mut str_bytes = vec![0; str_len as usize];
|
||||
reader.read_exact(&mut str_bytes)?;
|
||||
|
||||
let str = String::from_utf8(str_bytes).map_err(|e| {
|
||||
io::Error::new(io::ErrorKind::InvalidData, e.to_string())
|
||||
})?;
|
||||
|
||||
Ok(str)
|
||||
}
|
||||
|
||||
pub fn write_string<W: Write>(mut writer: W, s: &String) -> io::Result<()> {
|
||||
// Strings are written as len + utf8
|
||||
writer.write_u64::<LittleEndian>(s.as_bytes().len() as u64)?;
|
||||
writer.write_all(s.as_bytes())
|
||||
}
|
||||
Reference in New Issue
Block a user