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 borsh::{BorshDeserialize, BorshSerialize};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use ff::{Field, PrimeField};
use group::{Curve, Group, GroupEncoding, cofactor::CofactorGroup};
use incrementalmerkletree::{self, Level};
use lazy_static::lazy_static;
use rand_core::{CryptoRng, RngCore};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "serde")]
use serde_hex::{SerHex, Strict};
use std::{
array::TryFromSliceError,
cmp::Ordering,
convert::TryFrom,
fmt::{Display, Formatter},
hash::{Hash, Hasher},
io::{self, Read, Write},
str::FromStr,
};
use subtle::{Choice, ConstantTimeEq, CtOption};
use crate::{
asset_type::AssetType,
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::{Personalization, pedersen_hash},
redjubjub::{PrivateKey, PublicKey, Signature},
};
use borsh::BorshSchema;
use borsh::schema::Declaration;
use borsh::schema::Definition;
use borsh::schema::Fields;
use borsh::schema::add_definition;
use std::collections::BTreeMap;
pub const SAPLING_COMMITMENT_TREE_DEPTH: usize = 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, Eq, BorshSerialize, BorshDeserialize, Default)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(transparent)]
pub struct Node {
#[cfg_attr(feature = "serde", serde(with = "SerHex::<Strict>"))]
repr: [u8; 32],
}
impl Node {
pub fn new(repr: [u8; 32]) -> Self {
Node { repr }
}
pub const fn into_repr(self) -> [u8; 32] {
self.repr
}
pub fn from_scalar(cmu: bls12_381::Scalar) -> Self {
Self {
repr: cmu.to_repr(),
}
}
}
impl AsRef<[u8; 32]> for Node {
fn as_ref(&self) -> &[u8; 32] {
&self.repr
}
}
impl incrementalmerkletree::Hashable for Node {
fn empty_leaf() -> Self {
Node {
repr: Note::uncommitted().to_repr(),
}
}
fn combine(level: Level, lhs: &Self, rhs: &Self) -> Self {
Node {
repr: merkle_hash(level.into(), &lhs.repr, &rhs.repr),
}
}
fn empty_root(level: Level) -> Self {
EMPTY_ROOTS[<usize>::from(level)]
}
}
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 { 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());
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 asset_generator: jubjub::ExtendedPoint,
pub value: u64,
pub randomness: jubjub::Fr,
}
impl ValueCommitment {
pub fn commitment(&self) -> jubjub::SubgroupPoint {
(CofactorGroup::clear_cofactor(&self.asset_generator) * jubjub::Fr::from(self.value))
+ (constants::value_commitment_randomness_generator() * self.randomness)
}
}
#[derive(Clone, Debug)]
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: NullifierDerivingKey(constants::proof_generation_key_generator() * self.nsk),
}
}
}
impl BorshSerialize for ProofGenerationKey {
fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
writer.write_all(&self.ak.to_bytes())?;
writer.write_all(&self.nsk.to_repr())?;
Ok(())
}
}
impl BorshDeserialize for ProofGenerationKey {
fn deserialize_reader<R: Read>(reader: &mut R) -> io::Result<Self> {
let ak = {
let mut buf = [0u8; 32];
reader.read_exact(&mut buf)?;
jubjub::SubgroupPoint::from_bytes(&buf)
};
if ak.is_none().into() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"ak not in prime-order subgroup",
));
}
let nsk_bytes = <[u8; 32]>::deserialize_reader(reader)?;
let nsk = Option::from(jubjub::Fr::from_bytes(&nsk_bytes))
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "nsk not in field"))?;
Ok(Self {
ak: ak.unwrap(),
nsk,
})
}
}
impl BorshSchema for ProofGenerationKey {
fn add_definitions_recursively(definitions: &mut BTreeMap<Declaration, Definition>) {
let definition = Definition::Struct {
fields: Fields::NamedFields(vec![
("ak".into(), <[u8; 32]>::declaration()),
("nsk".into(), <[u8; 32]>::declaration()),
]),
};
add_definition(Self::declaration(), definition, definitions);
<[u8; 32]>::add_definitions_recursively(definitions);
}
fn declaration() -> Declaration {
"ProofGenerationKey".into()
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct NullifierDerivingKey(pub jubjub::SubgroupPoint);
impl BorshSerialize for NullifierDerivingKey {
fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
writer.write_all(&self.0.to_bytes())
}
}
impl BorshDeserialize for NullifierDerivingKey {
fn deserialize_reader<R: Read>(reader: &mut R) -> io::Result<Self> {
let nk = {
let mut buf = [0u8; 32];
reader.read_exact(&mut buf)?;
jubjub::SubgroupPoint::from_bytes(&buf)
};
if nk.is_none().into() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"nk not in prime-order subgroup",
));
}
Ok(Self(nk.unwrap()))
}
}
impl BorshSchema for NullifierDerivingKey {
fn add_definitions_recursively(definitions: &mut BTreeMap<Declaration, Definition>) {
let definition = Definition::Struct {
fields: Fields::UnnamedFields(vec![<[u8; 32]>::declaration()]),
};
add_definition(Self::declaration(), definition, definitions);
<[u8; 32]>::add_definitions_recursively(definitions);
}
fn declaration() -> Declaration {
"NullifierDerivingKey".into()
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct ViewingKey {
pub ak: jubjub::SubgroupPoint,
pub nk: NullifierDerivingKey,
}
impl Hash for ViewingKey {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
self.ak.to_bytes().hash(state);
self.nk.0.to_bytes().hash(state);
}
}
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.0.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)
}
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
let ak = {
let mut buf = [0u8; 32];
reader.read_exact(&mut buf)?;
jubjub::SubgroupPoint::from_bytes(&buf).and_then(|p| CtOption::new(p, !p.is_identity()))
};
let nk = {
let mut buf = [0u8; 32];
reader.read_exact(&mut buf)?;
jubjub::SubgroupPoint::from_bytes(&buf)
};
if ak.is_none().into() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"ak not of prime order",
));
}
if nk.is_none().into() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"nk not in prime-order subgroup",
));
}
let ak = ak.unwrap();
let nk = nk.unwrap();
Ok(ViewingKey {
ak,
nk: NullifierDerivingKey(nk),
})
}
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
writer.write_all(&self.ak.to_bytes())?;
writer.write_all(&self.nk.0.to_bytes())?;
Ok(())
}
pub fn to_bytes(&self) -> [u8; 64] {
let mut result = [0u8; 64];
self.write(&mut result[..])
.expect("should be able to serialize a ViewingKey");
result
}
}
impl BorshSerialize for ViewingKey {
fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
self.write(writer)
}
}
impl BorshDeserialize for ViewingKey {
fn deserialize_reader<R: Read>(reader: &mut R) -> io::Result<Self> {
Self::read(reader)
}
}
impl BorshSchema for ViewingKey {
fn add_definitions_recursively(definitions: &mut BTreeMap<Declaration, Definition>) {
let definition = Definition::Struct {
fields: Fields::NamedFields(vec![
("ak".into(), <[u8; 32]>::declaration()),
("nk".into(), NullifierDerivingKey::declaration()),
]),
};
add_definition(Self::declaration(), definition, definitions);
<[u8; 32]>::add_definitions_recursively(definitions);
NullifierDerivingKey::add_definitions_recursively(definitions);
}
fn declaration() -> Declaration {
"ViewingKey".into()
}
}
impl PartialOrd for ViewingKey {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ViewingKey {
fn cmp(&self, other: &Self) -> Ordering {
self.to_bytes().cmp(&other.to_bytes())
}
}
#[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()
}
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(
Copy, Clone, Debug, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize, BorshSchema,
)]
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, Copy, 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 Eq for PaymentAddress {}
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)]
#[allow(dead_code)]
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, asset_type: AssetType, value: u64, rseed: Rseed) -> Option<Note> {
self.g_d().map(|g_d| Note {
asset_type,
value,
rseed,
g_d,
pk_d: self.pk_d,
})
}
}
impl Display for PaymentAddress {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "{}", hex::encode(self.to_bytes()))
}
}
impl FromStr for PaymentAddress {
type Err = io::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let vec = hex::decode(s).map_err(|x| io::Error::new(io::ErrorKind::InvalidData, x))?;
BorshDeserialize::try_from_slice(&vec)
}
}
impl PartialOrd for PaymentAddress {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for PaymentAddress {
fn cmp(&self, other: &Self) -> Ordering {
self.to_bytes().cmp(&other.to_bytes())
}
}
impl Hash for PaymentAddress {
fn hash<H: Hasher>(&self, state: &mut H) {
self.to_bytes().hash(state)
}
}
impl BorshSerialize for PaymentAddress {
fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
writer.write(self.to_bytes().as_ref()).and(Ok(()))
}
}
impl BorshDeserialize for PaymentAddress {
fn deserialize_reader<R: Read>(reader: &mut R) -> io::Result<Self> {
let mut data = [0u8; 43];
reader.read_exact(&mut data)?;
let res = Self::from_bytes(&data);
let pa = res.ok_or_else(|| io::Error::from(io::ErrorKind::InvalidData))?;
Ok(pa)
}
}
impl BorshSchema for PaymentAddress {
fn add_definitions_recursively(definitions: &mut BTreeMap<Declaration, Definition>) {
let definition = Definition::Struct {
fields: Fields::NamedFields(vec![
("diversifier".into(), Diversifier::declaration()),
("pk_d".into(), <[u8; 32]>::declaration()),
]),
};
add_definition(Self::declaration(), definition, definitions);
Diversifier::add_definitions_recursively(definitions);
<[u8; 32]>::add_definitions_recursively(definitions);
}
fn declaration() -> Declaration {
"PaymentAddress".into()
}
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Copy, Clone, Debug)]
pub enum Rseed {
BeforeZip212(jubjub::Fr),
AfterZip212([u8; 32]),
}
impl BorshSchema for Rseed {
fn add_definitions_recursively(definitions: &mut BTreeMap<Declaration, Definition>) {
let definition = Definition::Enum {
tag_width: 1,
variants: vec![
(1, "BeforeZip212".into(), <[u8; 32]>::declaration()),
(2, "AfterZip212".into(), <[u8; 32]>::declaration()),
],
};
add_definition(Self::declaration(), definition, definitions);
<[u8; 32]>::add_definitions_recursively(definitions);
}
fn declaration() -> Declaration {
"Rseed".into()
}
}
impl BorshSerialize for Rseed {
fn serialize<W: std::io::Write>(&self, writer: &mut W) -> io::Result<()> {
match self {
Rseed::BeforeZip212(rcm) => {
writer.write_u8(1)?;
writer.write_all(&rcm.to_repr())
}
Rseed::AfterZip212(rseed) => {
writer.write_u8(2)?;
writer.write_all(rseed)
}
}?;
Ok(())
}
}
impl BorshDeserialize for Rseed {
fn deserialize_reader<R: Read>(reader: &mut R) -> io::Result<Self> {
let rseed_type = reader.read_u8()?;
let rseed_bytes = <[u8; 32]>::deserialize_reader(reader)?;
let rseed = if rseed_type == 0x01 {
let data = Option::from(jubjub::Fr::from_bytes(&rseed_bytes))
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "rseed not in field"))?;
Rseed::BeforeZip212(data)
} else {
Rseed::AfterZip212(rseed_bytes)
};
Ok(rseed)
}
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(
Copy,
Clone,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
BorshSerialize,
BorshDeserialize,
BorshSchema,
)]
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, Eq)]
pub struct NoteValue(u64);
impl TryFrom<u64> for NoteValue {
type Error = ();
fn try_from(value: u64) -> Result<Self, Self::Error> {
if value <= MAX_MONEY {
Ok(NoteValue(value))
} else {
Err(())
}
}
}
impl From<NoteValue> for u64 {
fn from(value: NoteValue) -> u64 {
value.0
}
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, Copy)]
pub struct Note<R = Rseed> {
pub asset_type: AssetType,
pub value: u64,
pub g_d: jubjub::SubgroupPoint,
pub pk_d: jubjub::SubgroupPoint,
pub rseed: R,
}
impl PartialEq for Note {
fn eq(&self, other: &Self) -> bool {
self.value == other.value
&& self.asset_type == other.asset_type
&& 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![];
note_contents.extend_from_slice(&self.asset_type.asset_generator().to_bytes());
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 + 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, nk: &NullifierDerivingKey, 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(&nk.0.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(),
)),
}
}
pub fn commitment(&self) -> Node {
Node {
repr: self.cmu().to_repr(),
}
}
}
impl<T: BorshSchema> BorshSchema for Note<T> {
fn add_definitions_recursively(definitions: &mut BTreeMap<Declaration, Definition>) {
let definition = Definition::Struct {
fields: Fields::NamedFields(vec![
("asset_type".into(), AssetType::declaration()),
("value".into(), u64::declaration()),
("g_d".into(), <[u8; 32]>::declaration()),
("pk_d".into(), <[u8; 32]>::declaration()),
("rseed".into(), T::declaration()),
]),
};
add_definition(Self::declaration(), definition, definitions);
AssetType::add_definitions_recursively(definitions);
u64::add_definitions_recursively(definitions);
<[u8; 32]>::add_definitions_recursively(definitions);
T::add_definitions_recursively(definitions);
}
fn declaration() -> Declaration {
format!("Note<{}>", T::declaration())
}
}
impl<T: BorshSerialize> BorshSerialize for Note<T> {
fn serialize<W: std::io::Write>(&self, writer: &mut W) -> io::Result<()> {
self.asset_type.serialize(writer)?;
writer.write_u64::<LittleEndian>(self.value)?;
writer.write_all(&self.g_d.to_bytes())?;
writer.write_all(&self.pk_d.to_bytes())?;
self.rseed.serialize(writer)?;
Ok(())
}
}
impl<T: BorshDeserialize> BorshDeserialize for Note<T> {
fn deserialize_reader<R: Read>(reader: &mut R) -> io::Result<Self> {
let asset_type = AssetType::deserialize_reader(reader)?;
let value = reader.read_u64::<LittleEndian>()?;
let g_d_bytes = <[u8; 32]>::deserialize_reader(reader)?;
let g_d = Option::from(jubjub::SubgroupPoint::from_bytes(&g_d_bytes))
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "g_d not in field"))?;
let pk_d_bytes = <[u8; 32]>::deserialize_reader(reader)?;
let pk_d = Option::from(jubjub::SubgroupPoint::from_bytes(&pk_d_bytes))
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "pk_d not in field"))?;
let rseed = T::deserialize_reader(reader)?;
Ok(Note {
asset_type,
value,
g_d,
pk_d,
rseed,
})
}
}
#[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {
use proptest::prelude::*;
use std::cmp::min;
use crate::transaction::components::amount::MAX_MONEY;
use super::{
Diversifier, Node, Note, NoteValue, PaymentAddress, Rseed, SaplingIvk,
keys::testing::arb_full_viewing_key,
};
prop_compose! {
pub fn arb_note_value()(value in 0u64..=MAX_MONEY) -> NoteValue {
NoteValue::try_from(value).unwrap()
}
}
prop_compose! {
pub fn arb_positive_note_value(bound: u64)(
value in 1u64..=(min(bound, MAX_MONEY))
) -> NoteValue {
NoteValue::try_from(value).unwrap()
}
}
prop_compose! {
pub fn arb_incoming_viewing_key()(fvk in arb_full_viewing_key()) -> SaplingIvk {
fvk.vk.ivk()
}
}
pub fn arb_payment_address() -> impl Strategy<Value = PaymentAddress> {
arb_incoming_viewing_key().prop_flat_map(|ivk: SaplingIvk| {
any::<[u8; 11]>().prop_filter_map(
"Sampled diversifier must generate a valid Sapling payment address.",
move |d| ivk.to_payment_address(Diversifier(d)),
)
})
}
prop_compose! {
pub fn arb_node()(value in prop::array::uniform32(prop::num::u8::ANY)) -> Node {
Node {
repr: value
}
}
}
prop_compose! {
pub fn arb_note(value: NoteValue)(
asset_type in crate::asset_type::testing::arb_asset_type(),
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,
asset_type
}
}
}
}
#[cfg(test)]
mod tests {
use crate::{
sapling::Note,
sapling::testing::{arb_note, arb_positive_note_value},
transaction::components::amount::MAX_MONEY,
};
use borsh::BorshDeserialize;
use proptest::prelude::*;
proptest! {
#![proptest_config(ProptestConfig::with_cases(10))]
#[test]
fn note_serialization(note in arb_positive_note_value(MAX_MONEY).prop_flat_map(arb_note)) {
let borsh = borsh::to_vec(¬e).unwrap();
let de_note: Note = BorshDeserialize::deserialize(&mut borsh.as_ref()).unwrap();
prop_assert_eq!(note, de_note);
}
}
}