Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9b455a1
Move Sapling key structs from zip32 to zcash_primitives
str4d Mar 7, 2019
65bbe7d
Implement Sapling note encryption
gtank Oct 19, 2018
70caa7d
Sapling note encryption test vectors
str4d Nov 14, 2018
757316d
Test prf_ock
str4d Nov 14, 2018
6996853
Trial Sapling note decryption
str4d Nov 14, 2018
8e098d4
Trial Sapling output recovery
str4d Nov 14, 2018
484330e
Trial Sapling compact note decryption
str4d Nov 15, 2018
247f3fb
Impl traits and functions for Memo
str4d Jan 24, 2019
2b1583d
Deduplicate Sapling key agreement logic
str4d Mar 23, 2019
566db65
Use a slice instead of a vector in prf_expand()
str4d Mar 23, 2019
899d852
Inline empty nonces
str4d Mar 23, 2019
9086dd9
Enforce consistent plaintext and ciphertext lengths
str4d Mar 23, 2019
e17e4b1
Test invalid decryption edge cases
str4d Mar 23, 2019
6dcb404
Switch to crypto_api_chachapoly crate
str4d Apr 11, 2019
34658c4
Raise minimum Rust version to 1.32
str4d Apr 11, 2019
edf7bc1
Document note_encryption module
str4d Apr 11, 2019
6d03b5c
Replace AeadCipher::seal with AeadCipher::seal_to
str4d Jun 5, 2019
23aa869
Add comments with specification references
str4d Jun 5, 2019
d4fce58
Tweaks to debug output and function names
str4d Jun 5, 2019
75bede4
Use fixed-length arrays instead of Vec
str4d Jun 5, 2019
6846ac5
Require that ak in FullViewingKey is prime order
str4d Jun 5, 2019
9d80be6
Simplify Memo::to_utf8 implementation
str4d Jun 6, 2019
060977f
Return edwards::Point from sapling_ka_agree
str4d Jun 6, 2019
fdb6e20
Check note plaintext version byte when decrypting
str4d Jun 6, 2019
b65aae9
Test both invalid and incorrect diversifiers
str4d Jun 6, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
language: rust
rust:
- 1.31.0
- 1.32.0

cache: cargo

Expand Down
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 4 additions & 7 deletions librustzcash/src/rustzcash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;

use sapling_crypto::primitives::{ProofGenerationKey, ViewingKey};
use zcash_primitives::{sapling::spend_sig, JUBJUB};
use zcash_primitives::{note_encryption::sapling_ka_agree, sapling::spend_sig, JUBJUB};
use zcash_proofs::{
load_parameters,
sapling::{CommitmentTreeWitness, SaplingProvingContext, SaplingVerificationContext},
Expand Down Expand Up @@ -536,15 +536,12 @@ pub extern "system" fn librustzcash_sapling_ka_agree(
Err(_) => return false,
};

// Multiply by 8
let p = p.mul_by_cofactor(&JUBJUB);

// Multiply by sk
let p = p.mul(sk, &JUBJUB);
// Compute key agreement
let ka = sapling_ka_agree(&sk, &p);

// Produce result
let result = unsafe { &mut *result };
p.write(&mut result[..]).expect("length is not 32 bytes");
ka.write(&mut result[..]).expect("length is not 32 bytes");

true
}
Expand Down
16 changes: 16 additions & 0 deletions sapling-crypto/src/jubjub/edwards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ fn convert_subgroup<E: JubjubEngine, S1, S2>(from: &Point<E, S1>) -> Point<E, S2
}
}

impl<E: JubjubEngine> From<&Point<E, Unknown>> for Point<E, Unknown>
{
fn from(p: &Point<E, Unknown>) -> Point<E, Unknown>
{
p.clone()
}
}

impl<E: JubjubEngine> From<Point<E, PrimeOrder>> for Point<E, Unknown>
{
fn from(p: Point<E, PrimeOrder>) -> Point<E, Unknown>
Expand All @@ -53,6 +61,14 @@ impl<E: JubjubEngine> From<Point<E, PrimeOrder>> for Point<E, Unknown>
}
}

impl<E: JubjubEngine> From<&Point<E, PrimeOrder>> for Point<E, Unknown>
{
fn from(p: &Point<E, PrimeOrder>) -> Point<E, Unknown>
{
convert_subgroup(p)
}
}

impl<E: JubjubEngine, Subgroup> Clone for Point<E, Subgroup>
{
fn clone(&self) -> Self {
Expand Down
1 change: 1 addition & 0 deletions sapling-crypto/src/jubjub/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ pub mod tests;
pub enum Unknown { }

/// Point of prime order.
#[derive(Debug)]
pub enum PrimeOrder { }

/// Fixed generators of the Jubjub curve of unknown
Expand Down
21 changes: 19 additions & 2 deletions sapling-crypto/src/primitives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ impl<E: JubjubEngine> ProofGenerationKey<E> {
}
}

#[derive(Debug)]
pub struct ViewingKey<E: JubjubEngine> {
pub ak: edwards::Point<E, PrimeOrder>,
pub nk: edwards::Point<E, PrimeOrder>
Expand Down Expand Up @@ -116,7 +117,7 @@ impl<E: JubjubEngine> ViewingKey<E> {
}
}

#[derive(Copy, Clone)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Diversifier(pub [u8; 11]);

impl Diversifier {
Expand All @@ -129,12 +130,18 @@ impl Diversifier {
}
}

#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct PaymentAddress<E: JubjubEngine> {
pub pk_d: edwards::Point<E, PrimeOrder>,
pub diversifier: Diversifier
}

impl<E: JubjubEngine> PartialEq for PaymentAddress<E> {
fn eq(&self, other: &Self) -> bool {
self.pk_d == other.pk_d && self.diversifier == other.diversifier
}
}

impl<E: JubjubEngine> PaymentAddress<E> {
pub fn g_d(
&self,
Expand Down Expand Up @@ -162,6 +169,7 @@ impl<E: JubjubEngine> PaymentAddress<E> {
}
}

#[derive(Clone, Debug)]
pub struct Note<E: JubjubEngine> {
/// The value of the note
pub value: u64,
Expand All @@ -173,6 +181,15 @@ pub struct Note<E: JubjubEngine> {
pub r: E::Fs
}

impl<E: JubjubEngine> PartialEq for Note<E> {
fn eq(&self, other: &Self) -> bool {
self.value == other.value
&& self.g_d == other.g_d
&& self.pk_d == other.pk_d
&& self.r == other.r
}
}

impl<E: JubjubEngine> Note<E> {
pub fn uncommitted() -> E::Fr {
// The smallest u-coordinate that is not on the curve
Expand Down
1 change: 1 addition & 0 deletions zcash_primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ authors = [

[dependencies]
byteorder = "1"
crypto_api_chachapoly = "0.1"
ff = { path = "../ff" }
hex = "0.3"
lazy_static = "1"
Expand Down
218 changes: 218 additions & 0 deletions zcash_primitives/src/keys.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
//! Sapling key components.
//!
//! Implements section 4.2.2 of the Zcash Protocol Specification.

use blake2_rfc::blake2b::{Blake2b, Blake2bResult};
use ff::{PrimeField, PrimeFieldRepr};
use sapling_crypto::{
jubjub::{edwards, FixedGenerators, JubjubEngine, JubjubParams, ToUniform, Unknown},
primitives::{ProofGenerationKey, ViewingKey},
};
use std::io::{self, Read, Write};

pub const PRF_EXPAND_PERSONALIZATION: &'static [u8; 16] = b"Zcash_ExpandSeed";

/// PRF^expand(sk, t) := BLAKE2b-512("Zcash_ExpandSeed", sk || t)
pub fn prf_expand(sk: &[u8], t: &[u8]) -> Blake2bResult {
prf_expand_vec(sk, &[t])
}

pub fn prf_expand_vec(sk: &[u8], ts: &[&[u8]]) -> Blake2bResult {
let mut h = Blake2b::with_params(64, &[], &[], PRF_EXPAND_PERSONALIZATION);
h.update(sk);
for t in ts {
h.update(t);
}
h.finalize()
}

/// An outgoing viewing key
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct OutgoingViewingKey(pub [u8; 32]);

/// A Sapling expanded spending key
#[derive(Clone)]
pub struct ExpandedSpendingKey<E: JubjubEngine> {
pub ask: E::Fs,
pub nsk: E::Fs,
pub ovk: OutgoingViewingKey,
}

/// A Sapling full viewing key
#[derive(Debug)]
pub struct FullViewingKey<E: JubjubEngine> {
pub vk: ViewingKey<E>,
pub ovk: OutgoingViewingKey,
}

impl<E: JubjubEngine> ExpandedSpendingKey<E> {
Comment thread
daira marked this conversation as resolved.
pub fn from_spending_key(sk: &[u8]) -> Self {
let ask = E::Fs::to_uniform(prf_expand(sk, &[0x00]).as_bytes());
let nsk = E::Fs::to_uniform(prf_expand(sk, &[0x01]).as_bytes());
let mut ovk = OutgoingViewingKey([0u8; 32]);
ovk.0
.copy_from_slice(&prf_expand(sk, &[0x02]).as_bytes()[..32]);
ExpandedSpendingKey { ask, nsk, ovk }
}

pub fn proof_generation_key(&self, params: &E::Params) -> ProofGenerationKey<E> {
ProofGenerationKey {
ak: params
.generator(FixedGenerators::SpendingKeyGenerator)
.mul(self.ask, params),
nsk: self.nsk,
}
}

pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
let mut ask_repr = <E::Fs as PrimeField>::Repr::default();
ask_repr.read_le(&mut reader)?;
let ask = E::Fs::from_repr(ask_repr)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;

let mut nsk_repr = <E::Fs as PrimeField>::Repr::default();
nsk_repr.read_le(&mut reader)?;
let nsk = E::Fs::from_repr(nsk_repr)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;

let mut ovk = [0; 32];
reader.read_exact(&mut ovk)?;

Ok(ExpandedSpendingKey {
ask,
nsk,
ovk: OutgoingViewingKey(ovk),
})
}

pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
self.ask.into_repr().write_le(&mut writer)?;
self.nsk.into_repr().write_le(&mut writer)?;
writer.write_all(&self.ovk.0)?;

Ok(())
}

pub fn to_bytes(&self) -> [u8; 96] {
let mut result = [0u8; 96];
self.write(&mut result[..])
.expect("should be able to serialize an ExpandedSpendingKey");
result
}
}

impl<E: JubjubEngine> Clone for FullViewingKey<E> {
fn clone(&self) -> Self {
FullViewingKey {
vk: ViewingKey {
ak: self.vk.ak.clone(),
nk: self.vk.nk.clone(),
},
ovk: self.ovk.clone(),
}
}
}

impl<E: JubjubEngine> FullViewingKey<E> {
pub fn from_expanded_spending_key(expsk: &ExpandedSpendingKey<E>, params: &E::Params) -> Self {
FullViewingKey {
vk: ViewingKey {
ak: params
.generator(FixedGenerators::SpendingKeyGenerator)
.mul(expsk.ask, params),
nk: params
.generator(FixedGenerators::ProofGenerationKey)
.mul(expsk.nsk, params),
},
ovk: expsk.ovk,
}
}

pub fn read<R: Read>(mut reader: R, params: &E::Params) -> io::Result<Self> {
let ak = edwards::Point::<E, Unknown>::read(&mut reader, params)?;
let ak = match ak.as_prime_order(params) {
Some(p) => p,
None => {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"ak not in prime-order subgroup",
));
}
};
if ak == edwards::Point::zero() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"ak not of prime order",
));
}

let nk = edwards::Point::<E, Unknown>::read(&mut reader, params)?;
let nk = match nk.as_prime_order(params) {
Comment thread
daira marked this conversation as resolved.
Some(p) => p,
None => {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"nk not in prime-order subgroup",
));
}
};

let mut ovk = [0; 32];
reader.read_exact(&mut ovk)?;

Ok(FullViewingKey {
vk: ViewingKey { ak, nk },
ovk: OutgoingViewingKey(ovk),
})
}

pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
self.vk.ak.write(&mut writer)?;
self.vk.nk.write(&mut writer)?;
writer.write_all(&self.ovk.0)?;

Ok(())
}

pub fn to_bytes(&self) -> [u8; 96] {
let mut result = [0u8; 96];
self.write(&mut result[..])
.expect("should be able to serialize a FullViewingKey");
result
}
}

#[cfg(test)]
mod tests {
use pairing::bls12_381::Bls12;
use sapling_crypto::jubjub::{edwards, FixedGenerators, JubjubParams, PrimeOrder};
use std::error::Error;

use super::FullViewingKey;
use crate::JUBJUB;

#[test]
fn ak_must_be_prime_order() {
let mut buf = [0; 96];
let identity = edwards::Point::<Bls12, PrimeOrder>::zero();

// Set both ak and nk to the identity.
identity.write(&mut buf[0..32]).unwrap();
identity.write(&mut buf[32..64]).unwrap();

// ak is not allowed to be the identity.
assert_eq!(
FullViewingKey::<Bls12>::read(&buf[..], &JUBJUB)
.unwrap_err()
.description(),
"ak not of prime order"
);

// Set ak to a basepoint.
let basepoint = JUBJUB.generator(FixedGenerators::SpendingKeyGenerator);
basepoint.write(&mut buf[0..32]).unwrap();

// nk is allowed to be the identity.
assert!(FullViewingKey::<Bls12>::read(&buf[..], &JUBJUB).is_ok());
}
}
Loading