This commit is contained in:
Deirdre Connolly 2021-03-11 08:24:45 -05:00 committed by Deirdre Connolly
parent df1ecc72b1
commit bd4e5e1f38
4 changed files with 79 additions and 44 deletions

1
Cargo.lock generated
View File

@ -4448,6 +4448,7 @@ dependencies = [
"fpe", "fpe",
"funty", "funty",
"futures 0.3.14", "futures 0.3.14",
"group 0.9.0",
"halo2", "halo2",
"hex", "hex",
"itertools 0.10.0", "itertools 0.10.0",

View File

@ -27,6 +27,7 @@ displaydoc = "0.2.1"
equihash = "0.1" equihash = "0.1"
fpe = "0.4" fpe = "0.4"
futures = "0.3" futures = "0.3"
group = "0.9"
halo2 = { git = "https://github.com/zcash/halo2.git", branch = "main" } halo2 = { git = "https://github.com/zcash/halo2.git", branch = "main" }
hex = "0.4" hex = "0.4"
jubjub = "0.6.0" jubjub = "0.6.0"

View File

@ -9,7 +9,7 @@
mod tests; mod tests;
use std::{ use std::{
convert::{From, Into, TryFrom}, convert::{From, Into, TryFrom, TryInto},
fmt, fmt,
io::{self, Write}, io::{self, Write},
str::FromStr, str::FromStr,
@ -18,7 +18,8 @@ use std::{
use aes::Aes256; use aes::Aes256;
use bech32::{self, FromBase32, ToBase32, Variant}; use bech32::{self, FromBase32, ToBase32, Variant};
use fpe::ff1::{BinaryNumeralString, FF1}; use fpe::ff1::{BinaryNumeralString, FF1};
use halo2::pasta::pallas; use group::GroupEncoding;
use halo2::{arithmetic::FieldExt, pasta::pallas};
use rand_core::{CryptoRng, RngCore}; use rand_core::{CryptoRng, RngCore};
use crate::{ use crate::{
@ -42,13 +43,15 @@ use super::sinsemilla::*;
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn prp_d(K: [u8; 32], d: [u8; 11]) -> [u8; 11] { fn prp_d(K: [u8; 32], d: [u8; 11]) -> [u8; 11] {
let radix = 2; let radix = 2;
let tweak = ""; let tweak = b"";
let ff = FF1::<Aes256>::new(&K, radix).expect("valid radix"); let ff = FF1::<Aes256>::new(&K, radix).expect("valid radix");
let enc = ff ff.encrypt(tweak, &BinaryNumeralString::from_bytes_le(&d))
.encrypt(tweak.into(), &BinaryNumeralString::from_bytes_le(&d)) .unwrap()
.unwrap(); .to_bytes_le()
.try_into()
.unwrap()
} }
/// Invokes Blake2b-512 as PRF^expand with parameter t. /// Invokes Blake2b-512 as PRF^expand with parameter t.
@ -59,16 +62,17 @@ fn prp_d(K: [u8; 32], d: [u8; 11]) -> [u8; 11] {
// TODO: This is basically a duplicate of the one in our sapling module, its // TODO: This is basically a duplicate of the one in our sapling module, its
// definition in the draft NU5 spec is incomplete so I'm putting it here in case // definition in the draft NU5 spec is incomplete so I'm putting it here in case
// it changes. // it changes.
fn prf_expand(sk: [u8; 32], t: &[u8]) -> [u8; 64] { fn prf_expand(sk: [u8; 32], t: &[&[u8]]) -> [u8; 64] {
let hash = blake2b_simd::Params::new() let state = blake2b_simd::Params::new()
.hash_length(64) .hash_length(64)
.personal(b"Zcash_ExpandSeed") .personal(b"Zcash_ExpandSeed")
.to_state() .to_state();
.update(&sk[..])
.update(t)
.finalize();
*hash.as_array() state.update(&sk[..]);
t.iter().for_each(|t_i| state.update(t_i));
*state.finalize().as_array()
} }
/// Used to derive the outgoing cipher key _ock_ used to encrypt an encrypted /// Used to derive the outgoing cipher key _ock_ used to encrypt an encrypted
@ -77,15 +81,15 @@ fn prf_expand(sk: [u8; 32], t: &[u8]) -> [u8; 64] {
/// PRF^ock(ovk, cv, cm_x, ephemeralKey) := BLAKE2b-256(“Zcash_Orchardock”, ovk || cv || cm_x || ephemeralKey) /// PRF^ock(ovk, cv, cm_x, ephemeralKey) := BLAKE2b-256(“Zcash_Orchardock”, ovk || cv || cm_x || ephemeralKey)
/// ///
/// https://zips.z.cash/protocol/nu5.pdf#concreteprfs /// https://zips.z.cash/protocol/nu5.pdf#concreteprfs
fn prf_ock(ovk: [u8; 32], cv: [u8; 32], cm_x: [u8; 32], ephemeral_key: [u8; 32]) -> [u8; 32] { fn prf_ock(ovk: [u8; 32], cv: [u8; 32], cm_x: [u8; 32], ephemeral_key: [u8; 32]) -> [u8; 64] {
let hash = blake2b_simd::Params::new() let hash = blake2b_simd::Params::new()
.hash_length(32) .hash_length(32)
.personal(b"Zcash_Orchardock") .personal(b"Zcash_Orchardock")
.to_state() .to_state()
.update(ovk) .update(&ovk)
.update(cv) .update(&cv)
.update(cm_x) .update(&cm_x)
.update(ephemeral_key) .update(&ephemeral_key)
.finalize(); .finalize();
*hash.as_array() *hash.as_array()
@ -348,8 +352,8 @@ pub struct NullifierDerivingKey(pub pallas::Base);
impl fmt::Debug for NullifierDerivingKey { impl fmt::Debug for NullifierDerivingKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("NullifierDerivingKey") f.debug_struct("NullifierDerivingKey")
.field("u", &hex::encode(self.0.get_u().to_bytes())) .field("x", &hex::encode(self.0.get_x().to_bytes()))
.field("v", &hex::encode(self.0.get_v().to_bytes())) .field("y", &hex::encode(self.0.get_y().to_bytes()))
.finish() .finish()
} }
} }
@ -406,7 +410,7 @@ impl From<SpendingKey> for IvkCommitRandomness {
/// ///
/// https://zips.z.cash/protocol/protocol.pdf#orchardkeycomponents /// https://zips.z.cash/protocol/protocol.pdf#orchardkeycomponents
fn from(sk: SpendingKey) -> Self { fn from(sk: SpendingKey) -> Self {
Self(pallas::Scalar::from_bytes_wide(prf_expand(sk, [8]))) Self(pallas::Scalar::from_bytes_wide(prf_expand(sk.into(), &[8])))
} }
} }
@ -416,6 +420,20 @@ impl From<IvkCommitRandomness> for [u8; 32] {
} }
} }
impl TryFrom<[u8; 32]> for IvkCommitRandomness {
type Error = &'static str;
fn try_from(bytes: [u8; 32]) -> Result<Self, Self::Error> {
let possible_scalar = pallas::Scalar::from_bytes(&bytes);
if possible_scalar.is_some().into() {
Ok(Self(possible_scalar.unwrap()))
} else {
Err("Invalid pallas::Scalar value")
}
}
}
/// Magic human-readable strings used to identify what networks Orchard incoming /// Magic human-readable strings used to identify what networks Orchard incoming
/// viewing keys are associated with when encoded/decoded with bech32. /// viewing keys are associated with when encoded/decoded with bech32.
/// ///
@ -476,10 +494,18 @@ impl From<FullViewingKey> for IncomingViewingKey {
/// https://zips.z.cash/protocol/protocol.pdf#orchardkeycomponents /// https://zips.z.cash/protocol/protocol.pdf#orchardkeycomponents
/// https://zips.z.cash/protocol/protocol.pdf#concreteprfs /// https://zips.z.cash/protocol/protocol.pdf#concreteprfs
fn from(fvk: FullViewingKey) -> Self { fn from(fvk: FullViewingKey) -> Self {
let M = (fvk.ak.into(), fvk.nk.into()).concat(); let M = (
fvk.spend_validating_key.into(),
fvk.nullifier_deriving_key.into(),
)
.concat();
// Commit^ivk_rivk // Commit^ivk_rivk
let scalar = sinsemilla_short_commit(fvk.ivk.into(), "z.cash:Orchard-CommitIvk", M); let scalar = sinsemilla_short_commit(
fvk.ivk_commit_randomness.into(),
"z.cash:Orchard-CommitIvk",
M,
);
Self { Self {
network: Network::default(), network: Network::default(),
@ -594,7 +620,7 @@ impl FromStr for FullViewingKey {
}, },
spend_validating_key: SpendValidatingKey::from(ak_bytes), spend_validating_key: SpendValidatingKey::from(ak_bytes),
nullifier_deriving_key: NullifierDerivingKey::from(nk_bytes), nullifier_deriving_key: NullifierDerivingKey::from(nk_bytes),
ivk_commit_randomness: IvkCommitRandomness::from(rivk_bytes), ivk_commit_randomness: IvkCommitRandomness::try_from(rivk_bytes).unwrap(),
}) })
} }
_ => Err(SerializationError::Parse("bech32 decoding error")), _ => Err(SerializationError::Parse("bech32 decoding error")),
@ -605,12 +631,19 @@ impl FromStr for FullViewingKey {
impl FullViewingKey { impl FullViewingKey {
/// [4.2.3]: https://zips.z.cash/protocol/protocol.pdf#orchardkeycomponents /// [4.2.3]: https://zips.z.cash/protocol/protocol.pdf#orchardkeycomponents
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn to_R(&self) -> [u8; 32] { pub fn to_R(&self) -> [u8; 64] {
// let K = I2LEBSP_l_sk(rivk) // let K = I2LEBSP_l_sk(rivk)
let K: [u8; 32] = self.ivk_commit_randomness.into(); let K: [u8; 32] = self.ivk_commit_randomness.into();
// let R = PRF^expand_K( [0x82] || I2LEOSP256(ak) || I2LEOSP256(nk) ) // let R = PRF^expand_K( [0x82] || I2LEOSP256(ak) || I2LEOSP256(nk) )
prf_expand(K, ([0x82], self.ak.into(), self.nk.into()).concat()) prf_expand(
K,
[
[0x82u8],
self.spend_validating_key.into(),
self.nullifier_deriving_key.into(),
],
)
} }
} }
@ -688,7 +721,7 @@ impl From<Diversifier> for pallas::Point {
/// ///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
fn from(d: Diversifier) -> Self { fn from(d: Diversifier) -> Self {
diversify_hash(d.0) diversify_hash(&d.0)
} }
} }

View File

@ -4,6 +4,20 @@ use bitvec::prelude::*;
use halo2::pasta::pallas; use halo2::pasta::pallas;
/// [Hash Extractor for Pallas][concreteextractorpallas]
///
/// P → B^[l^Orchard_Merkle]
///
/// [concreteextractorpallas]: https://zips.z.cash/protocol/nu5.pdf#concreteextractorpallas
// TODO: should this return the basefield element type, or the bytes?
pub fn extract_p(point: pallas::Point) -> pallas::Base {
match pallas::Affine::from(point).get_xy().into() {
// If Some, it's not the identity.
Some((x, _)) => x,
_ => pallas::Base::zero(),
}
}
/// GroupHash into Pallas, aka _GroupHash^P_ /// GroupHash into Pallas, aka _GroupHash^P_
/// ///
/// Produces a random point in the Pallas curve. The first input element acts /// Produces a random point in the Pallas curve. The first input element acts
@ -21,7 +35,7 @@ pub fn pallas_group_hash(D: &[u8], M: &[u8]) -> pallas::Point {
/// https://zips.z.cash/protocol/protocol.pdf#concretesinsemillahash /// https://zips.z.cash/protocol/protocol.pdf#concretesinsemillahash
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn Q(D: &[u8]) -> pallas::Point { fn Q(D: &[u8]) -> pallas::Point {
pallas_group_hash("z.cash:SinsemillaQ", D) pallas_group_hash(b"z.cash:SinsemillaQ", D)
} }
/// S(j) := GroupHash^P(“z.cash:SinsemillaS”, LEBS2OSP32(I2LEBSP32(j))) /// S(j) := GroupHash^P(“z.cash:SinsemillaS”, LEBS2OSP32(I2LEBSP32(j)))
@ -32,7 +46,7 @@ fn Q(D: &[u8]) -> pallas::Point {
// XXX: should j be strictly limited to k=10 bits? // XXX: should j be strictly limited to k=10 bits?
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn S(j: [u8; 2]) -> pallas::Point { fn S(j: [u8; 2]) -> pallas::Point {
pallas_group_hash("z.cash:SinsemillaS", &j) pallas_group_hash(b"z.cash:SinsemillaS", &j)
} }
/// "...an algebraic hash function with collision resistance (for fixed input /// "...an algebraic hash function with collision resistance (for fixed input
@ -76,20 +90,6 @@ pub fn sinsemilla_hash_to_point(D: &[u8], M: &BitVec<Lsb0, u8>) -> pallas::Point
acc acc
} }
/// [Hash Extractor for Pallas][concreteextractorpallas]
///
/// P → B^[l^Orchard_Merkle]
///
/// [concreteextractorpallas]: https://zips.z.cash/protocol/nu5.pdf#concreteextractorpallas
// TODO: should this return the basefield element type, or the bytes?
pub fn extract_p(point: pallas::Point) -> pallas::Base {
match pallas::Affine::from(point).get_xy().into() {
// If Some, it's not the identity.
Some((x, _)) => x,
_ => pallas::Base::zero(),
}
}
/// Sinsemilla Hash Function /// Sinsemilla Hash Function
/// ///
/// "SinsemillaHash is an algebraic hash function with collision resistance (for /// "SinsemillaHash is an algebraic hash function with collision resistance (for
@ -117,7 +117,7 @@ pub fn sinsemilla_hash(D: &[u8], M: &BitVec<Lsb0, u8>) -> pallas::Base {
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn sinsemilla_commit(r: pallas::Scalar, D: &[u8], M: &BitVec<Lsb0, u8>) -> pallas::Point { pub fn sinsemilla_commit(r: pallas::Scalar, D: &[u8], M: &BitVec<Lsb0, u8>) -> pallas::Point {
sinsemilla_hash_to_point((D, b"-M").concat(), M) sinsemilla_hash_to_point((D, b"-M").concat(), M)
+ r * pallas_group_hash((D, b"r").concat(), b"") + pallas_group_hash((D, b"r").concat(), b"") * r
} }
/// SinsemillaShortCommit_r(D, M) := Extract_P(SinsemillaCommit_r(D, M)) /// SinsemillaShortCommit_r(D, M) := Extract_P(SinsemillaCommit_r(D, M))