Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disallow Orchard ivk = 0 on IncomingViewingKey::from & SpendingKey generation #3962

Merged
merged 13 commits into from
Aug 1, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
4 changes: 3 additions & 1 deletion zebra-chain/src/orchard/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ mod tests {
// Default diversifier, where index = 0.
let diversifier_key = keys::DiversifierKey::from(full_viewing_key);

let incoming_viewing_key = keys::IncomingViewingKey::from(full_viewing_key);
// This should fail with negligible probability.
let incoming_viewing_key = keys::IncomingViewingKey::try_from(full_viewing_key)
.expect("a valid incoming viewing key");

let diversifier = keys::Diversifier::from(diversifier_key);
let transmission_key = keys::TransmissionKey::from((incoming_viewing_key, diversifier));
Expand Down
3 changes: 2 additions & 1 deletion zebra-chain/src/orchard/arbitrary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ impl Arbitrary for keys::TransmissionKey {
let diversifier_key = keys::DiversifierKey::from(full_viewing_key);

let diversifier = Diversifier::from(diversifier_key);
let incoming_viewing_key = keys::IncomingViewingKey::from(full_viewing_key);
let incoming_viewing_key = keys::IncomingViewingKey::try_from(full_viewing_key)
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
.expect("a valid incoming viewing key");

Self::from((incoming_viewing_key, diversifier))
})
Expand Down
79 changes: 69 additions & 10 deletions zebra-chain/src/orchard/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use bitvec::prelude::*;
use fpe::ff1::{BinaryNumeralString, FF1};
use group::{ff::PrimeField, prime::PrimeCurveAffine, Group, GroupEncoding};
use halo2::{
arithmetic::{Coordinates, CurveAffine, FieldExt},
arithmetic::{Coordinates, CurveAffine, Field, FieldExt},
pasta::pallas,
};
use rand_core::{CryptoRng, RngCore};
Expand Down Expand Up @@ -203,7 +203,12 @@ impl SpendingKey {
let sk = Self::from_bytes(bytes, network);

// "if ask = 0, discard this key and repeat with a new sk"
if SpendAuthorizingKey::from(sk).0 == pallas::Scalar::zero() {
if SpendAuthorizingKey::from(sk).0.is_zero().into() {
continue;
}

// "if ivk ∈ {0, ⊥}, discard this key and repeat with a new sk"
if IncomingViewingKey::try_from(FullViewingKey::from(sk)).is_err() {
continue;
}

Expand Down Expand Up @@ -595,7 +600,7 @@ impl PartialEq for FullViewingKey {
#[derive(Copy, Clone)]
pub struct IncomingViewingKey {
dk: DiversifierKey,
// TODO: refine type
// TODO: refine type, so that IncomingViewingkey.ivk cannot be 0
ivk: pallas::Scalar,
}

Expand Down Expand Up @@ -642,7 +647,46 @@ impl From<IncomingViewingKey> for [u8; 64] {
}
}

impl From<FullViewingKey> for IncomingViewingKey {
impl TryFrom<[u8; 64]> for IncomingViewingKey {
type Error = &'static str;

/// Convert an array of bytes into a [`IncomingViewingKey`].
///
/// Returns an error if the encoding is malformed or if it [encodes the scalar additive
/// identity, 0][1].
///
/// > ivk MUST be in the range {1 .. 𝑞P - 1}
///
/// [1]: https://zips.z.cash/protocol/protocol.pdf#orchardinviewingkeyencoding
fn try_from(bytes: [u8; 64]) -> Result<Self, Self::Error> {
let mut dk_bytes = [0u8; 32];
dk_bytes.copy_from_slice(&bytes[..32]);
let dk = DiversifierKey::from(dk_bytes);

let mut ivk_bytes = [0u8; 32];
ivk_bytes[..].copy_from_slice(&bytes[32..]);
dconnolly marked this conversation as resolved.
Show resolved Hide resolved

let possible_scalar = pallas::Scalar::from_repr(ivk_bytes);

if possible_scalar.is_some().into() {
let scalar = possible_scalar.unwrap();
if scalar.is_zero().into() {
Err("pallas::Scalar value for Orchard IncomingViewingKey is 0")
} else {
Ok(Self {
dk,
ivk: possible_scalar.unwrap(),
})
}
} else {
Err("Invalid pallas::Scalar value for Orchard IncomingViewingKey")
}
}
}

impl TryFrom<FullViewingKey> for IncomingViewingKey {
type Error = &'static str;

/// Commit^ivk_rivk(ak, nk) :=
/// SinsemillaShortCommit_rcm(︁
/// "z.cash:Orchard-CommitIvk",
Expand All @@ -652,7 +696,7 @@ impl From<FullViewingKey> for IncomingViewingKey {
/// <https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents>
/// <https://zips.z.cash/protocol/nu5.pdf#concreteprfs>
#[allow(non_snake_case)]
fn from(fvk: FullViewingKey) -> Self {
fn try_from(fvk: FullViewingKey) -> Result<Self, Self::Error> {
let mut M: BitVec<u8, Lsb0> = BitVec::new();

// I2LEBSP_l^Orchard_base(ak)︁
Expand All @@ -674,10 +718,18 @@ impl From<FullViewingKey> for IncomingViewingKey {
)
.expect("deriving orchard commit^ivk should not output ⊥ ");

Self {
dk: fvk.into(),
// mod r_P
ivk: pallas::Scalar::from_repr(commit_x.into()).unwrap(),
let ivk_ctoption = pallas::Scalar::from_repr(commit_x.into());

// if ivk ∈ {0, ⊥}, discard this key

// [`Scalar::is_zero()`] is constant-time under the hood, and ivk is mod r_P
if ivk_ctoption.is_some().into() && !<bool>::from(ivk_ctoption.unwrap().is_zero()) {
Ok(Self {
dk: fvk.into(),
ivk: ivk_ctoption.unwrap(),
})
} else {
Err("generated ivk is the additive identity 0, invalid")
}
}
}
Expand Down Expand Up @@ -804,6 +856,12 @@ impl From<FullViewingKey> for DiversifierKey {
}
}

impl From<[u8; 32]> for DiversifierKey {
fn from(bytes: [u8; 32]) -> DiversifierKey {
DiversifierKey(bytes)
}
}

impl From<DiversifierKey> for [u8; 32] {
fn from(dk: DiversifierKey) -> [u8; 32] {
dk.0
Expand Down Expand Up @@ -1002,10 +1060,11 @@ impl From<&OutgoingCipherKey> for [u8; 32] {

// TODO: implement PrivateKey: #2192

/// An ephemeral private key for Orchard key agreement.
/// An _ephemeral private key_ for Orchard key agreement.
///
/// <https://zips.z.cash/protocol/nu5.pdf#concreteorchardkeyagreement>
/// <https://zips.z.cash/protocol/nu5.pdf#saplingandorchardencrypt>
// TODO: refine so that the inner `Scalar` != 0
#[derive(Copy, Clone, Debug)]
pub struct EphemeralPrivateKey(pub(crate) pallas::Scalar);

Expand Down
5 changes: 3 additions & 2 deletions zebra-chain/src/orchard/keys/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ fn generate_keys_from_test_vectors() {
let diversifier_key = DiversifierKey::from(full_viewing_key);
assert_eq!(diversifier_key, test_vector.dk);

let incoming_viewing_key = IncomingViewingKey::from(full_viewing_key);
let incoming_viewing_key =
IncomingViewingKey::try_from(full_viewing_key).expect("a valid incoming viewing key");
assert_eq!(<[u8; 32]>::from(incoming_viewing_key.ivk), test_vector.ivk);

let outgoing_viewing_key = OutgoingViewingKey::from(full_viewing_key);
Expand Down Expand Up @@ -84,7 +85,7 @@ proptest! {
// Test ConstantTimeEq, Eq, PartialEq
assert_eq!(diversifier_key, diversifier_key.clone());

let incoming_viewing_key = IncomingViewingKey::from(full_viewing_key);
let incoming_viewing_key = IncomingViewingKey::try_from(full_viewing_key).expect("a valid incoming viewing key");
// Test ConstantTimeEq, Eq, PartialEq
assert_eq!(incoming_viewing_key, incoming_viewing_key.clone());

Expand Down