pub mod group_hash;
pub mod keys;
pub mod note_encryption;
pub mod pedersen_hash;
pub mod prover;
pub mod redjubjub;
pub mod util;
use bitvec::{order::Lsb0, view::AsBits};
use blake2s_simd::Params as Blake2sParams;
use byteorder::{LittleEndian, WriteBytesExt};
use ff::{Field, PrimeField};
use group::{Curve, Group, GroupEncoding};
use incrementalmerkletree::{self, Altitude};
use lazy_static::lazy_static;
use rand_core::{CryptoRng, RngCore};
use std::array::TryFromSliceError;
use std::convert::{TryFrom, TryInto};
use std::io::{self, Read, Write};
use subtle::{Choice, ConstantTimeEq};
use crate::{
constants::{self, SPENDING_KEY_GENERATOR},
keys::prf_expand,
merkle_tree::{HashSer, Hashable},
transaction::components::amount::MAX_MONEY,
};
use self::{
group_hash::group_hash,
pedersen_hash::{pedersen_hash, Personalization},
redjubjub::{PrivateKey, PublicKey, Signature},
};
pub const SAPLING_COMMITMENT_TREE_DEPTH: usize = 32;
pub const SAPLING_COMMITMENT_TREE_DEPTH_U8: u8 = 32;
pub fn merkle_hash(depth: usize, lhs: &[u8; 32], rhs: &[u8; 32]) -> [u8; 32] {
let lhs = {
let mut tmp = [false; 256];
for (a, b) in tmp.iter_mut().zip(lhs.as_bits::<Lsb0>()) {
*a = *b;
}
tmp
};
let rhs = {
let mut tmp = [false; 256];
for (a, b) in tmp.iter_mut().zip(rhs.as_bits::<Lsb0>()) {
*a = *b;
}
tmp
};
jubjub::ExtendedPoint::from(pedersen_hash(
Personalization::MerkleTree(depth),
lhs.iter()
.copied()
.take(bls12_381::Scalar::NUM_BITS as usize)
.chain(
rhs.iter()
.copied()
.take(bls12_381::Scalar::NUM_BITS as usize),
),
))
.to_affine()
.get_u()
.to_repr()
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Node {
repr: [u8; 32],
}
impl Node {
pub fn new(repr: [u8; 32]) -> Self {
Node { repr }
}
}
impl incrementalmerkletree::Hashable for Node {
fn empty_leaf() -> Self {
Node {
repr: Note::uncommitted().to_repr(),
}
}
fn combine(altitude: Altitude, lhs: &Self, rhs: &Self) -> Self {
Node {
repr: merkle_hash(altitude.into(), &lhs.repr, &rhs.repr),
}
}
fn empty_root(altitude: Altitude) -> Self {
EMPTY_ROOTS[<usize>::from(altitude)]
}
}
impl HashSer for Node {
fn read<R: Read>(mut reader: R) -> io::Result<Self> {
let mut repr = [0u8; 32];
reader.read_exact(&mut repr)?;
Ok(Node::new(repr))
}
fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
writer.write_all(self.repr.as_ref())
}
}
impl From<Node> for bls12_381::Scalar {
fn from(node: Node) -> Self {
bls12_381::Scalar::from_repr(node.repr).unwrap()
}
}
lazy_static! {
static ref EMPTY_ROOTS: Vec<Node> = {
let mut v = vec![Node::blank()];
for d in 0..SAPLING_COMMITMENT_TREE_DEPTH {
let next = Node::combine(d, &v[d], &v[d]);
v.push(next);
}
v
};
}
pub fn spend_sig<R: RngCore + CryptoRng>(
ask: PrivateKey,
ar: jubjub::Fr,
sighash: &[u8; 32],
rng: &mut R,
) -> Signature {
spend_sig_internal(ask, ar, sighash, rng)
}
pub(crate) fn spend_sig_internal<R: RngCore>(
ask: PrivateKey,
ar: jubjub::Fr,
sighash: &[u8; 32],
rng: &mut R,
) -> Signature {
let rsk = ask.randomize(ar);
let rk = PublicKey::from_private(&rsk, SPENDING_KEY_GENERATOR);
let mut data_to_be_signed = [0u8; 64];
data_to_be_signed[0..32].copy_from_slice(&rk.0.to_bytes());
(&mut data_to_be_signed[32..64]).copy_from_slice(&sighash[..]);
rsk.sign(&data_to_be_signed, rng, SPENDING_KEY_GENERATOR)
}
#[derive(Clone)]
pub struct ValueCommitment {
pub value: u64,
pub randomness: jubjub::Fr,
}
impl ValueCommitment {
pub fn commitment(&self) -> jubjub::SubgroupPoint {
(constants::VALUE_COMMITMENT_VALUE_GENERATOR * jubjub::Fr::from(self.value))
+ (constants::VALUE_COMMITMENT_RANDOMNESS_GENERATOR * self.randomness)
}
}
#[derive(Clone)]
pub struct ProofGenerationKey {
pub ak: jubjub::SubgroupPoint,
pub nsk: jubjub::Fr,
}
impl ProofGenerationKey {
pub fn to_viewing_key(&self) -> ViewingKey {
ViewingKey {
ak: self.ak,
nk: constants::PROOF_GENERATION_KEY_GENERATOR * self.nsk,
}
}
}
#[derive(Debug, Clone)]
pub struct ViewingKey {
pub ak: jubjub::SubgroupPoint,
pub nk: jubjub::SubgroupPoint,
}
impl ViewingKey {
pub fn rk(&self, ar: jubjub::Fr) -> jubjub::SubgroupPoint {
self.ak + constants::SPENDING_KEY_GENERATOR * ar
}
pub fn ivk(&self) -> SaplingIvk {
let mut h = [0; 32];
h.copy_from_slice(
Blake2sParams::new()
.hash_length(32)
.personal(constants::CRH_IVK_PERSONALIZATION)
.to_state()
.update(&self.ak.to_bytes())
.update(&self.nk.to_bytes())
.finalize()
.as_bytes(),
);
h[31] &= 0b0000_0111;
SaplingIvk(jubjub::Fr::from_repr(h).unwrap())
}
pub fn to_payment_address(&self, diversifier: Diversifier) -> Option<PaymentAddress> {
self.ivk().to_payment_address(diversifier)
}
}
#[derive(Debug, Clone)]
pub struct SaplingIvk(pub jubjub::Fr);
impl SaplingIvk {
pub fn to_payment_address(&self, diversifier: Diversifier) -> Option<PaymentAddress> {
diversifier.g_d().and_then(|g_d| {
let pk_d = g_d * self.0;
PaymentAddress::from_parts(diversifier, pk_d)
})
}
pub fn to_repr(&self) -> [u8; 32] {
self.0.to_repr()
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Diversifier(pub [u8; 11]);
impl Diversifier {
pub fn g_d(&self) -> Option<jubjub::SubgroupPoint> {
group_hash(&self.0, constants::KEY_DIVERSIFICATION_PERSONALIZATION)
}
}
#[derive(Clone, Debug)]
pub struct PaymentAddress {
pk_d: jubjub::SubgroupPoint,
diversifier: Diversifier,
}
impl PartialEq for PaymentAddress {
fn eq(&self, other: &Self) -> bool {
self.pk_d == other.pk_d && self.diversifier == other.diversifier
}
}
impl PaymentAddress {
pub fn from_parts(diversifier: Diversifier, pk_d: jubjub::SubgroupPoint) -> Option<Self> {
if pk_d.is_identity().into() {
None
} else {
Some(PaymentAddress { pk_d, diversifier })
}
}
#[cfg(test)]
pub(crate) fn from_parts_unchecked(
diversifier: Diversifier,
pk_d: jubjub::SubgroupPoint,
) -> Self {
PaymentAddress { pk_d, diversifier }
}
pub fn from_bytes(bytes: &[u8; 43]) -> Option<Self> {
let diversifier = {
let mut tmp = [0; 11];
tmp.copy_from_slice(&bytes[0..11]);
Diversifier(tmp)
};
diversifier.g_d()?;
let pk_d = jubjub::SubgroupPoint::from_bytes(bytes[11..43].try_into().unwrap());
if pk_d.is_some().into() {
PaymentAddress::from_parts(diversifier, pk_d.unwrap())
} else {
None
}
}
pub fn to_bytes(&self) -> [u8; 43] {
let mut bytes = [0; 43];
bytes[0..11].copy_from_slice(&self.diversifier.0);
bytes[11..].copy_from_slice(&self.pk_d.to_bytes());
bytes
}
pub fn diversifier(&self) -> &Diversifier {
&self.diversifier
}
pub fn pk_d(&self) -> &jubjub::SubgroupPoint {
&self.pk_d
}
pub fn g_d(&self) -> Option<jubjub::SubgroupPoint> {
self.diversifier.g_d()
}
pub fn create_note(&self, value: u64, rseed: Rseed) -> Option<Note> {
self.g_d().map(|g_d| Note {
value,
rseed,
g_d,
pk_d: self.pk_d,
})
}
}
#[derive(Copy, Clone, Debug)]
pub enum Rseed {
BeforeZip212(jubjub::Fr),
AfterZip212([u8; 32]),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Nullifier(pub [u8; 32]);
impl Nullifier {
pub fn from_slice(bytes: &[u8]) -> Result<Nullifier, TryFromSliceError> {
bytes.try_into().map(Nullifier)
}
pub fn to_vec(&self) -> Vec<u8> {
self.0.to_vec()
}
}
impl AsRef<[u8]> for Nullifier {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl ConstantTimeEq for Nullifier {
fn ct_eq(&self, other: &Self) -> Choice {
self.0.ct_eq(&other.0)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct NoteValue(u64);
impl TryFrom<u64> for NoteValue {
type Error = ();
fn try_from(value: u64) -> Result<Self, Self::Error> {
if value <= MAX_MONEY as u64 {
Ok(NoteValue(value))
} else {
Err(())
}
}
}
impl From<NoteValue> for u64 {
fn from(value: NoteValue) -> u64 {
value.0
}
}
#[derive(Clone, Debug)]
pub struct Note {
pub value: u64,
pub g_d: jubjub::SubgroupPoint,
pub pk_d: jubjub::SubgroupPoint,
pub rseed: Rseed,
}
impl PartialEq for Note {
fn eq(&self, other: &Self) -> bool {
self.value == other.value
&& self.g_d == other.g_d
&& self.pk_d == other.pk_d
&& self.rcm() == other.rcm()
}
}
impl Note {
pub fn uncommitted() -> bls12_381::Scalar {
bls12_381::Scalar::one()
}
fn cm_full_point(&self) -> jubjub::SubgroupPoint {
let mut note_contents = vec![];
(&mut note_contents)
.write_u64::<LittleEndian>(self.value)
.unwrap();
note_contents.extend_from_slice(&self.g_d.to_bytes());
note_contents.extend_from_slice(&self.pk_d.to_bytes());
assert_eq!(note_contents.len(), 32 + 32 + 8);
let hash_of_contents = pedersen_hash(
Personalization::NoteCommitment,
note_contents
.into_iter()
.flat_map(|byte| (0..8).map(move |i| ((byte >> i) & 1) == 1)),
);
(constants::NOTE_COMMITMENT_RANDOMNESS_GENERATOR * self.rcm()) + hash_of_contents
}
pub fn nf(&self, viewing_key: &ViewingKey, position: u64) -> Nullifier {
let rho = self.cm_full_point()
+ (constants::NULLIFIER_POSITION_GENERATOR * jubjub::Fr::from(position));
Nullifier::from_slice(
Blake2sParams::new()
.hash_length(32)
.personal(constants::PRF_NF_PERSONALIZATION)
.to_state()
.update(&viewing_key.nk.to_bytes())
.update(&rho.to_bytes())
.finalize()
.as_bytes(),
)
.unwrap()
}
pub fn cmu(&self) -> bls12_381::Scalar {
jubjub::ExtendedPoint::from(self.cm_full_point())
.to_affine()
.get_u()
}
pub fn rcm(&self) -> jubjub::Fr {
match self.rseed {
Rseed::BeforeZip212(rcm) => rcm,
Rseed::AfterZip212(rseed) => {
jubjub::Fr::from_bytes_wide(prf_expand(&rseed, &[0x04]).as_array())
}
}
}
pub fn generate_or_derive_esk<R: RngCore + CryptoRng>(&self, rng: &mut R) -> jubjub::Fr {
self.generate_or_derive_esk_internal(rng)
}
pub(crate) fn generate_or_derive_esk_internal<R: RngCore>(&self, rng: &mut R) -> jubjub::Fr {
match self.derive_esk() {
None => jubjub::Fr::random(rng),
Some(esk) => esk,
}
}
pub fn derive_esk(&self) -> Option<jubjub::Fr> {
match self.rseed {
Rseed::BeforeZip212(_) => None,
Rseed::AfterZip212(rseed) => Some(jubjub::Fr::from_bytes_wide(
prf_expand(&rseed, &[0x05]).as_array(),
)),
}
}
}
#[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {
use proptest::prelude::*;
use std::cmp::min;
use std::convert::TryFrom;
use crate::{
transaction::components::amount::MAX_MONEY, zip32::testing::arb_extended_spending_key,
};
use super::{Node, Note, NoteValue, PaymentAddress, Rseed};
prop_compose! {
pub fn arb_note_value()(value in 0u64..=MAX_MONEY as u64) -> NoteValue {
NoteValue::try_from(value).unwrap()
}
}
prop_compose! {
pub fn arb_positive_note_value(bound: u64)(
value in 1u64..=(min(bound, MAX_MONEY as u64))
) -> NoteValue {
NoteValue::try_from(value).unwrap()
}
}
pub fn arb_payment_address() -> impl Strategy<Value = PaymentAddress> {
arb_extended_spending_key().prop_map(|sk| sk.default_address().1)
}
prop_compose! {
pub fn arb_node()(value in prop::array::uniform32(prop::num::u8::ANY)) -> Node {
Node::new(value)
}
}
prop_compose! {
pub fn arb_note(value: NoteValue)(
addr in arb_payment_address(),
rseed in prop::array::uniform32(prop::num::u8::ANY).prop_map(Rseed::AfterZip212)
) -> Note {
Note {
value: value.into(),
g_d: addr.g_d().unwrap(), pk_d: *addr.pk_d(),
rseed
}
}
}
}