Merge pull request #170 from nuttycom/keys_ffi_ops

Add serialization and parsing for Orchard keys.
This commit is contained in:
Kris Nuttycombe 2021-08-31 15:40:25 -06:00 committed by GitHub
commit d50844f146
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 211 additions and 15 deletions

View File

@ -1,3 +1,7 @@
use std::convert::TryInto;
use subtle::CtOption;
use crate::{
keys::{DiversifiedTransmissionKey, Diversifier},
spec::{diversify_hash, NonIdentityPallasPoint},
@ -39,6 +43,26 @@ impl Address {
pub(crate) fn pk_d(&self) -> &DiversifiedTransmissionKey {
&self.pk_d
}
/// Serializes this address to its "raw" encoding as specified in [Zcash Protocol Spec § 5.6.4.2: Orchard Raw Payment Addresses][orchardpaymentaddrencoding]
///
/// [orchardpaymentaddrencoding]: https://zips.z.cash/protocol/protocol.pdf#orchardpaymentaddrencoding
pub fn to_raw_address_bytes(&self) -> [u8; 43] {
let mut result = [0u8; 43];
result[..11].copy_from_slice(self.d.as_array());
result[11..].copy_from_slice(&self.pk_d.to_bytes());
result
}
/// Parse an address from its "raw" encoding as specified in [Zcash Protocol Spec § 5.6.4.2: Orchard Raw Payment Addresses][orchardpaymentaddrencoding]
///
/// [orchardpaymentaddrencoding]: https://zips.z.cash/protocol/protocol.pdf#orchardpaymentaddrencoding
pub fn from_raw_address_bytes(bytes: &[u8; 43]) -> CtOption<Self> {
DiversifiedTransmissionKey::from_bytes(bytes[11..].try_into().unwrap()).map(|pk_d| {
let d = Diversifier::from_bytes(bytes[..11].try_into().unwrap());
Self::from_parts(d, pk_d)
})
}
}
/// Generators for property testing.

View File

@ -7,11 +7,15 @@ use std::mem;
use blake2b_simd::Hash as Blake2bHash;
use nonempty::NonEmpty;
use zcash_note_encryption::try_note_decryption;
use crate::{
address::Address,
bundle::commitments::{hash_bundle_auth_data, hash_bundle_txid_data},
circuit::{Instance, Proof, VerifyingKey},
note::{ExtractedNoteCommitment, Nullifier, TransmittedNoteCiphertext},
keys::IncomingViewingKey,
note::{ExtractedNoteCommitment, Note, Nullifier, TransmittedNoteCiphertext},
note_encryption::OrchardDomain,
primitives::redpallas::{self, Binding, SpendAuth},
tree::Anchor,
value::{ValueCommitTrapdoor, ValueCommitment, ValueSum},
@ -354,6 +358,39 @@ impl<T: Authorization, V> Bundle<T, V> {
.map(|a| a.to_instance(self.flags, self.anchor))
.collect()
}
/// Perform trial decryption of each action in the bundle with each of the
/// specified incoming viewing keys, and return the decrypted note contents
/// along with the index of the action from which it was derived.
pub fn decrypt_outputs_for_keys(
&self,
keys: &[IncomingViewingKey],
) -> Vec<(usize, IncomingViewingKey, Note, Address, [u8; 512])> {
self.actions
.iter()
.enumerate()
.filter_map(|(idx, action)| {
let domain = OrchardDomain::for_action(action);
keys.iter().find_map(move |ivk| {
try_note_decryption(&domain, ivk, action)
.map(|(n, a, m)| (idx, ivk.clone(), n, a, m))
})
})
.collect()
}
/// Perform trial decryption of each action at `action_idx` in the bundle with the
/// specified incoming viewing key, and return the decrypted note contents.
pub fn decrypt_output_with_key(
&self,
action_idx: usize,
key: &IncomingViewingKey,
) -> Option<(Note, Address, [u8; 512])> {
self.actions.get(action_idx).and_then(move |action| {
let domain = OrchardDomain::for_action(action);
try_note_decryption(&domain, key, action)
})
}
}
impl<T: Authorization, V: Copy + Into<ValueSum>> Bundle<T, V> {

View File

@ -1,6 +1,6 @@
//! Key structures for Orchard.
use std::convert::TryInto;
use std::convert::{TryFrom, TryInto};
use std::mem;
use aes::Aes256;
@ -10,8 +10,7 @@ use group::{prime::PrimeCurveAffine, Curve, GroupEncoding};
use halo2::arithmetic::FieldExt;
use pasta_curves::pallas;
use rand::RngCore;
use subtle::ConstantTimeEq;
use subtle::CtOption;
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
use zcash_note_encryption::EphemeralKeyBytes;
use crate::{
@ -30,7 +29,7 @@ const KDF_ORCHARD_PERSONALIZATION: &[u8; 16] = b"Zcash_OrchardKDF";
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct SpendingKey([u8; 32]);
impl SpendingKey {
@ -65,9 +64,15 @@ impl SpendingKey {
let ivk = KeyAgreementPrivateKey::derive_inner(&(&sk).into());
CtOption::new(sk, !(ask.ct_is_zero() | ivk.is_none()))
}
/// Returns the raw bytes of the spending key.
pub fn to_bytes(&self) -> &[u8; 32] {
&self.0
}
}
/// A spend authorizing key, used to create spend authorization signatures.
/// This type enforces that the corresponding public point (ak^) has ỹ = 0.
///
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
///
@ -112,7 +117,7 @@ impl From<&SpendingKey> for SpendAuthorizingKey {
/// $\mathsf{ak}$ but stored here as a RedPallas verification key.
///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialOrd, Ord)]
pub struct SpendValidatingKey(redpallas::VerificationKey<SpendAuth>);
impl From<&SpendAuthorizingKey> for SpendValidatingKey {
@ -133,11 +138,35 @@ impl PartialEq for SpendValidatingKey {
}
}
impl Eq for SpendValidatingKey {}
impl SpendValidatingKey {
/// Randomizes this spend validating key with the given `randomizer`.
pub fn randomize(&self, randomizer: &pallas::Scalar) -> redpallas::VerificationKey<SpendAuth> {
self.0.randomize(randomizer)
}
/// Converts this spend validating key to its serialized form,
/// I2LEOSP_256(ak).
pub(crate) fn to_bytes(&self) -> [u8; 32] {
// This is correct because the wrapped point must have ỹ = 0, and
// so the point repr is the same as I2LEOSP of its x-coordinate.
<[u8; 32]>::from(&self.0)
}
pub(crate) fn from_bytes(bytes: &[u8]) -> Option<Self> {
<[u8; 32]>::try_from(bytes)
.ok()
.and_then(|b|
// check that the sign of the y-coordinate is positive
if b[31] & 0x80 == 0 {
<redpallas::VerificationKey<SpendAuth>>::try_from(b).ok()
} else {
None
}
)
.map(SpendValidatingKey)
}
}
/// A key used to derive [`Nullifier`]s from [`Note`]s.
@ -147,7 +176,7 @@ impl SpendValidatingKey {
/// [`Nullifier`]: crate::note::Nullifier
/// [`Note`]: crate::note::Note
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[derive(Copy, Debug, Clone)]
#[derive(Copy, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct NullifierDerivingKey(pallas::Base);
impl NullifierDerivingKey {
@ -166,6 +195,21 @@ impl NullifierDerivingKey {
pub(crate) fn prf_nf(&self, rho: pallas::Base) -> pallas::Base {
prf_nf(self.0, rho)
}
/// Converts this nullifier deriving key to its serialized form.
pub(crate) fn to_bytes(&self) -> [u8; 32] {
<[u8; 32]>::from(self.0)
}
pub(crate) fn from_bytes(bytes: &[u8]) -> Option<Self> {
let nk_bytes = <[u8; 32]>::try_from(bytes).ok()?;
let nk = pallas::Base::from_bytes(&nk_bytes).map(NullifierDerivingKey);
if nk.is_some().into() {
Some(nk.unwrap())
} else {
None
}
}
}
/// The randomness for $\mathsf{Commit}^\mathsf{ivk}$.
@ -173,7 +217,7 @@ impl NullifierDerivingKey {
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[derive(Copy, Debug, Clone)]
#[derive(Copy, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct CommitIvkRandomness(pallas::Scalar);
impl From<&SpendingKey> for CommitIvkRandomness {
@ -186,6 +230,21 @@ impl CommitIvkRandomness {
pub(crate) fn inner(&self) -> pallas::Scalar {
self.0
}
/// Converts this nullifier deriving key to its serialized form.
pub(crate) fn to_bytes(&self) -> [u8; 32] {
<[u8; 32]>::from(self.0)
}
pub(crate) fn from_bytes(bytes: &[u8]) -> Option<Self> {
let rivk_bytes = <[u8; 32]>::try_from(bytes).ok()?;
let rivk = pallas::Scalar::from_bytes(&rivk_bytes).map(CommitIvkRandomness);
if rivk.is_some().into() {
Some(rivk.unwrap())
} else {
None
}
}
}
/// A key that provides the capability to view incoming and outgoing transactions.
@ -196,7 +255,7 @@ impl CommitIvkRandomness {
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct FullViewingKey {
ak: SpendValidatingKey,
nk: NullifierDerivingKey,
@ -256,6 +315,28 @@ impl FullViewingKey {
// Shortcut: we don't need to derive DiversifierKey.
KeyAgreementPrivateKey::from(self).address(d)
}
/// Serializes the full viewing key as specified in [Zcash Protocol Spec § 5.6.4.4: Orchard Raw Full Viewing Keys][orchardrawfullviewingkeys]
///
/// [orchardrawfullviewingkeys]: https://zips.z.cash/protocol/protocol.pdf#orchardfullviewingkeyencoding
pub fn to_raw_fvk_bytes(&self) -> [u8; 96] {
let mut result = [0u8; 96];
result[..32].copy_from_slice(&self.ak.to_bytes());
result[32..64].copy_from_slice(&self.nk.to_bytes());
result[64..].copy_from_slice(&<[u8; 32]>::from(&self.rivk.0));
result
}
/// Parses a full viewing key from its "raw" encoding as specified in [Zcash Protocol Spec § 5.6.4.4: Orchard Raw Full Viewing Keys][orchardrawfullviewingkeys]
///
/// [orchardrawfullviewingkeys]: https://zips.z.cash/protocol/protocol.pdf#orchardfullviewingkeyencoding
pub fn from_raw_fvk_bytes(bytes: &[u8; 96]) -> Option<Self> {
let ak = SpendValidatingKey::from_bytes(&bytes[..32])?;
let nk = NullifierDerivingKey::from_bytes(&bytes[32..64])?;
let rivk = CommitIvkRandomness::from_bytes(&bytes[64..])?;
Some(FullViewingKey { ak, nk, rivk })
}
}
/// A key that provides the capability to derive a sequence of diversifiers.
@ -263,7 +344,7 @@ impl FullViewingKey {
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct DiversifierKey([u8; 32]);
impl From<&FullViewingKey> for DiversifierKey {
@ -273,7 +354,7 @@ impl From<&FullViewingKey> for DiversifierKey {
}
/// The index for a particular diversifier.
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DiversifierIndex([u8; 11]);
macro_rules! di_from {
@ -305,6 +386,16 @@ impl DiversifierKey {
.unwrap();
Diversifier(enc.to_bytes_le().try_into().unwrap())
}
/// Return the raw bytes of the diversifier key
pub fn to_bytes(&self) -> &[u8; 32] {
&self.0
}
/// Construct a diversifier key from bytes
pub fn from_bytes(bytes: [u8; 32]) -> Self {
DiversifierKey(bytes)
}
}
/// A diversifier that can be used to derive a specific [`Address`] from a
@ -345,7 +436,7 @@ impl Diversifier {
/// decryption of notes). When we actually want to serialize ivk, we're guaranteed to get
/// a valid base field element encoding, because we always construct ivk from an integer
/// in the correct range.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
struct KeyAgreementPrivateKey(NonZeroPallasScalar);
impl From<&FullViewingKey> for KeyAgreementPrivateKey {
@ -382,7 +473,7 @@ impl KeyAgreementPrivateKey {
/// Defined in [Zcash Protocol Spec § 5.6.4.3: Orchard Raw Incoming Viewing Keys][orchardinviewingkeyencoding].
///
/// [orchardinviewingkeyencoding]: https://zips.z.cash/protocol/nu5.pdf#orchardinviewingkeyencoding
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct IncomingViewingKey {
dk: DiversifierKey,
ivk: KeyAgreementPrivateKey,
@ -398,6 +489,16 @@ impl From<&FullViewingKey> for IncomingViewingKey {
}
impl IncomingViewingKey {
/// Serializes an Orchard incoming viewing key to its raw encoding as specified in [Zcash Protocol Spec § 5.6.4.3: Orchard Raw Incoming Viewing Keys][orchardrawinviewingkeys]
///
/// [orchardrawinviewingkeys]: https://zips.z.cash/protocol/protocol.pdf#orchardinviewingkeyencoding
pub fn to_bytes(&self) -> [u8; 64] {
let mut result = [0u8; 64];
result.copy_from_slice(self.dk.to_bytes());
result[32..].copy_from_slice(&self.ivk.0.to_bytes());
result
}
/// Parses an Orchard incoming viewing key from its raw encoding.
pub fn from_bytes(bytes: &[u8; 64]) -> CtOption<Self> {
NonZeroPallasBase::from_bytes(bytes[32..].try_into().unwrap()).map(|ivk| {
@ -492,6 +593,20 @@ impl DiversifiedTransmissionKey {
}
}
impl Default for DiversifiedTransmissionKey {
fn default() -> Self {
DiversifiedTransmissionKey(NonIdentityPallasPoint::default())
}
}
impl ConditionallySelectable for DiversifiedTransmissionKey {
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
DiversifiedTransmissionKey(NonIdentityPallasPoint::conditional_select(
&a.0, &b.0, choice,
))
}
}
/// An ephemeral secret key used to encrypt an output note on-chain.
///
/// `esk` is "ephemeral" in the sense that each secret key is only used once. In

View File

@ -1,5 +1,6 @@
//! A minimal RedPallas implementation for use in Zcash.
use std::cmp::{Ord, Ordering, PartialOrd};
use std::convert::{TryFrom, TryInto};
use pasta_curves::pallas;
@ -97,6 +98,20 @@ impl<T: SigType> PartialEq for VerificationKey<T> {
}
}
impl<T: SigType> Eq for VerificationKey<T> {}
impl<T: SigType> PartialOrd for VerificationKey<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
<[u8; 32]>::from(self).partial_cmp(&<[u8; 32]>::from(other))
}
}
impl<T: SigType> Ord for VerificationKey<T> {
fn cmp(&self, other: &Self) -> Ordering {
<[u8; 32]>::from(self).cmp(&<[u8; 32]>::from(other))
}
}
impl VerificationKey<SpendAuth> {
/// Used in the note encryption tests.
#[cfg(test)]

View File

@ -73,6 +73,10 @@ impl NonZeroPallasBase {
pallas::Base::from_bytes(bytes).and_then(NonZeroPallasBase::from_base)
}
pub(crate) fn to_bytes(&self) -> [u8; 32] {
self.0.to_bytes()
}
pub(crate) fn from_base(b: pallas::Base) -> CtOption<Self> {
CtOption::new(NonZeroPallasBase(b), !b.ct_is_zero())
}
@ -89,7 +93,7 @@ impl NonZeroPallasBase {
}
/// An integer in [1..r_P].
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct NonZeroPallasScalar(pallas::Scalar);
impl Default for NonZeroPallasScalar {

View File

@ -71,7 +71,8 @@ impl NoteValue {
Default::default()
}
pub(crate) fn inner(&self) -> u64 {
/// Returns the raw underlying value.
pub fn inner(&self) -> u64 {
self.0
}