mirror of https://github.com/zcash/orchard.git
Note encryption test vectors
This commit is contained in:
parent
37326df1ab
commit
769be6c080
|
@ -13,7 +13,7 @@ use crate::{
|
||||||
/// let sk = SpendingKey::from_bytes([7; 32]).unwrap();
|
/// let sk = SpendingKey::from_bytes([7; 32]).unwrap();
|
||||||
/// let address = FullViewingKey::from(&sk).default_address();
|
/// let address = FullViewingKey::from(&sk).default_address();
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub struct Address {
|
pub struct Address {
|
||||||
d: Diversifier,
|
d: Diversifier,
|
||||||
pk_d: DiversifiedTransmissionKey,
|
pk_d: DiversifiedTransmissionKey,
|
||||||
|
|
26
src/keys.rs
26
src/keys.rs
|
@ -291,7 +291,7 @@ impl DiversifierKey {
|
||||||
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
|
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
|
||||||
///
|
///
|
||||||
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub struct Diversifier([u8; 11]);
|
pub struct Diversifier([u8; 11]);
|
||||||
|
|
||||||
impl Diversifier {
|
impl Diversifier {
|
||||||
|
@ -376,6 +376,16 @@ impl From<&FullViewingKey> for IncomingViewingKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IncomingViewingKey {
|
impl IncomingViewingKey {
|
||||||
|
/// 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| {
|
||||||
|
IncomingViewingKey {
|
||||||
|
dk: DiversifierKey(bytes[..32].try_into().unwrap()),
|
||||||
|
ivk: KeyAgreementPrivateKey(ivk.into()),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the default payment address for this key.
|
/// Returns the default payment address for this key.
|
||||||
pub fn default_address(&self) -> Address {
|
pub fn default_address(&self) -> Address {
|
||||||
self.address(self.dk.default_diversifier())
|
self.address(self.dk.default_diversifier())
|
||||||
|
@ -410,6 +420,12 @@ impl From<&FullViewingKey> for OutgoingViewingKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<[u8; 32]> for OutgoingViewingKey {
|
||||||
|
fn from(ovk: [u8; 32]) -> Self {
|
||||||
|
OutgoingViewingKey(ovk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl AsRef<[u8; 32]> for OutgoingViewingKey {
|
impl AsRef<[u8; 32]> for OutgoingViewingKey {
|
||||||
fn as_ref(&self) -> &[u8; 32] {
|
fn as_ref(&self) -> &[u8; 32] {
|
||||||
&self.0
|
&self.0
|
||||||
|
@ -421,7 +437,7 @@ impl AsRef<[u8; 32]> for OutgoingViewingKey {
|
||||||
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
|
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
|
||||||
///
|
///
|
||||||
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub struct DiversifiedTransmissionKey(NonIdentityPallasPoint);
|
pub struct DiversifiedTransmissionKey(NonIdentityPallasPoint);
|
||||||
|
|
||||||
impl DiversifiedTransmissionKey {
|
impl DiversifiedTransmissionKey {
|
||||||
|
@ -517,6 +533,12 @@ impl EphemeralPublicKey {
|
||||||
pub struct SharedSecret(NonIdentityPallasPoint);
|
pub struct SharedSecret(NonIdentityPallasPoint);
|
||||||
|
|
||||||
impl SharedSecret {
|
impl SharedSecret {
|
||||||
|
/// For checking test vectors only.
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn to_bytes(&self) -> [u8; 32] {
|
||||||
|
self.0.to_bytes()
|
||||||
|
}
|
||||||
|
|
||||||
/// Defined in [Zcash Protocol Spec § 5.4.5.6: Orchard Key Agreement][concreteorchardkdf].
|
/// Defined in [Zcash Protocol Spec § 5.4.5.6: Orchard Key Agreement][concreteorchardkdf].
|
||||||
///
|
///
|
||||||
/// [concreteorchardkdf]: https://zips.z.cash/protocol/nu5.pdf#concreteorchardkdf
|
/// [concreteorchardkdf]: https://zips.z.cash/protocol/nu5.pdf#concreteorchardkdf
|
||||||
|
|
10
src/note.rs
10
src/note.rs
|
@ -95,6 +95,16 @@ pub struct Note {
|
||||||
rseed: RandomSeed,
|
rseed: RandomSeed,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Note {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
// Notes are canonically defined by their commitments.
|
||||||
|
ExtractedNoteCommitment::from(self.commitment())
|
||||||
|
.eq(&ExtractedNoteCommitment::from(other.commitment()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Note {}
|
||||||
|
|
||||||
impl Note {
|
impl Note {
|
||||||
pub(crate) fn from_parts(
|
pub(crate) fn from_parts(
|
||||||
recipient: Address,
|
recipient: Address,
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::iter;
|
||||||
use bitvec::{array::BitArray, order::Lsb0};
|
use bitvec::{array::BitArray, order::Lsb0};
|
||||||
use ff::PrimeFieldBits;
|
use ff::PrimeFieldBits;
|
||||||
use pasta_curves::{arithmetic::FieldExt, pallas};
|
use pasta_curves::{arithmetic::FieldExt, pallas};
|
||||||
use subtle::CtOption;
|
use subtle::{ConstantTimeEq, CtOption};
|
||||||
|
|
||||||
use crate::{constants::L_ORCHARD_BASE, primitives::sinsemilla, spec::extract_p, value::NoteValue};
|
use crate::{constants::L_ORCHARD_BASE, primitives::sinsemilla, spec::extract_p, value::NoteValue};
|
||||||
|
|
||||||
|
@ -77,3 +77,17 @@ impl From<&ExtractedNoteCommitment> for [u8; 32] {
|
||||||
cmx.to_bytes()
|
cmx.to_bytes()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ConstantTimeEq for ExtractedNoteCommitment {
|
||||||
|
fn ct_eq(&self, other: &Self) -> subtle::Choice {
|
||||||
|
self.0.ct_eq(&other.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for ExtractedNoteCommitment {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.ct_eq(other).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for ExtractedNoteCommitment {}
|
||||||
|
|
|
@ -248,3 +248,163 @@ impl<T> ShieldedOutput<OrchardDomain> for Action<T> {
|
||||||
&self.encrypted_note().enc_ciphertext
|
&self.encrypted_note().enc_ciphertext
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct CompactAction {
|
||||||
|
ephemeral_key: EphemeralKeyBytes,
|
||||||
|
cmx: ExtractedNoteCommitment,
|
||||||
|
enc_ciphertext: [u8; 52],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<&Action<T>> for CompactAction {
|
||||||
|
fn from(action: &Action<T>) -> Self {
|
||||||
|
CompactAction {
|
||||||
|
ephemeral_key: action.ephemeral_key(),
|
||||||
|
cmx: action.cmx().clone(),
|
||||||
|
enc_ciphertext: action.encrypted_note().enc_ciphertext[..52]
|
||||||
|
.try_into()
|
||||||
|
.unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShieldedOutput<OrchardDomain> for CompactAction {
|
||||||
|
fn ephemeral_key(&self) -> EphemeralKeyBytes {
|
||||||
|
EphemeralKeyBytes(self.ephemeral_key.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmstar_bytes(&self) -> [u8; 32] {
|
||||||
|
self.cmx.to_bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enc_ciphertext(&self) -> &[u8] {
|
||||||
|
&self.enc_ciphertext
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
use zcash_note_encryption::{
|
||||||
|
try_compact_note_decryption, try_note_decryption, try_output_recovery_with_ovk,
|
||||||
|
EphemeralKeyBytes,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{prf_ock_orchard, CompactAction, OrchardDomain, OrchardNoteEncryption};
|
||||||
|
use crate::{
|
||||||
|
bundle::Action,
|
||||||
|
keys::{
|
||||||
|
DiversifiedTransmissionKey, Diversifier, EphemeralSecretKey, IncomingViewingKey,
|
||||||
|
OutgoingViewingKey,
|
||||||
|
},
|
||||||
|
note::{ExtractedNoteCommitment, Nullifier, RandomSeed, TransmittedNoteCiphertext},
|
||||||
|
primitives::redpallas,
|
||||||
|
value::{NoteValue, ValueCommitment},
|
||||||
|
Address, Note,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_vectors() {
|
||||||
|
let test_vectors = crate::test_vectors::note_encryption::test_vectors();
|
||||||
|
|
||||||
|
for tv in test_vectors {
|
||||||
|
//
|
||||||
|
// Load the test vector components
|
||||||
|
//
|
||||||
|
|
||||||
|
// Recipient key material
|
||||||
|
let ivk = IncomingViewingKey::from_bytes(&tv.incoming_viewing_key).unwrap();
|
||||||
|
let ovk = OutgoingViewingKey::from(tv.ovk);
|
||||||
|
let d = Diversifier::from_bytes(tv.default_d);
|
||||||
|
let pk_d = DiversifiedTransmissionKey::from_bytes(&tv.default_pk_d).unwrap();
|
||||||
|
|
||||||
|
// Received Action
|
||||||
|
let cv_net = ValueCommitment::from_bytes(&tv.cv_net).unwrap();
|
||||||
|
let rho = Nullifier::from_bytes(&tv.rho).unwrap();
|
||||||
|
let cmx = ExtractedNoteCommitment::from_bytes(&tv.cmx).unwrap();
|
||||||
|
|
||||||
|
let esk = EphemeralSecretKey::from_bytes(&tv.esk).unwrap();
|
||||||
|
let ephemeral_key = EphemeralKeyBytes(tv.ephemeral_key);
|
||||||
|
|
||||||
|
// Details about the expected note
|
||||||
|
let value = NoteValue::from_raw(tv.v);
|
||||||
|
let rseed = RandomSeed::from_bytes(tv.rseed, &rho).unwrap();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Test the individual components
|
||||||
|
//
|
||||||
|
|
||||||
|
let shared_secret = esk.agree(&pk_d);
|
||||||
|
assert_eq!(shared_secret.to_bytes(), tv.shared_secret);
|
||||||
|
|
||||||
|
let k_enc = shared_secret.kdf_orchard(&ephemeral_key);
|
||||||
|
assert_eq!(k_enc.as_bytes(), tv.k_enc);
|
||||||
|
|
||||||
|
let ock = prf_ock_orchard(&ovk, &cv_net, &cmx.to_bytes(), &ephemeral_key);
|
||||||
|
assert_eq!(ock.as_ref(), tv.ock);
|
||||||
|
|
||||||
|
let recipient = Address::from_parts(d, pk_d);
|
||||||
|
let note = Note::from_parts(recipient, value, rho, rseed);
|
||||||
|
assert_eq!(ExtractedNoteCommitment::from(note.commitment()), cmx);
|
||||||
|
|
||||||
|
let action = Action::from_parts(
|
||||||
|
// rho is the nullifier in the receiving Action.
|
||||||
|
rho,
|
||||||
|
// We don't need a valid rk for this test.
|
||||||
|
redpallas::VerificationKey::dummy(),
|
||||||
|
cmx.clone(),
|
||||||
|
TransmittedNoteCiphertext {
|
||||||
|
epk_bytes: ephemeral_key.0,
|
||||||
|
enc_ciphertext: tv.c_enc,
|
||||||
|
out_ciphertext: tv.c_out,
|
||||||
|
},
|
||||||
|
cv_net.clone(),
|
||||||
|
(),
|
||||||
|
);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Test decryption
|
||||||
|
// (Tested first because it only requires immutable references.)
|
||||||
|
//
|
||||||
|
|
||||||
|
let domain = OrchardDomain { rho };
|
||||||
|
|
||||||
|
match try_note_decryption(&domain, &ivk, &action) {
|
||||||
|
Some((decrypted_note, decrypted_to, decrypted_memo)) => {
|
||||||
|
assert_eq!(decrypted_note, note);
|
||||||
|
assert_eq!(decrypted_to, recipient);
|
||||||
|
assert_eq!(&decrypted_memo[..], &tv.memo[..]);
|
||||||
|
}
|
||||||
|
None => panic!("Note decryption failed"),
|
||||||
|
}
|
||||||
|
|
||||||
|
match try_compact_note_decryption(&domain, &ivk, &CompactAction::from(&action)) {
|
||||||
|
Some((decrypted_note, decrypted_to)) => {
|
||||||
|
assert_eq!(decrypted_note, note);
|
||||||
|
assert_eq!(decrypted_to, recipient);
|
||||||
|
}
|
||||||
|
None => panic!("Compact note decryption failed"),
|
||||||
|
}
|
||||||
|
|
||||||
|
match try_output_recovery_with_ovk(&domain, &ovk, &action, &cv_net, &tv.c_out) {
|
||||||
|
Some((decrypted_note, decrypted_to, decrypted_memo)) => {
|
||||||
|
assert_eq!(decrypted_note, note);
|
||||||
|
assert_eq!(decrypted_to, recipient);
|
||||||
|
assert_eq!(&decrypted_memo[..], &tv.memo[..]);
|
||||||
|
}
|
||||||
|
None => panic!("Output recovery failed"),
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Test encryption
|
||||||
|
//
|
||||||
|
|
||||||
|
let ne = OrchardNoteEncryption::new_with_esk(esk, Some(ovk), note, recipient, tv.memo);
|
||||||
|
|
||||||
|
assert_eq!(ne.encrypt_note_plaintext().as_ref(), &tv.c_enc[..]);
|
||||||
|
assert_eq!(
|
||||||
|
&ne.encrypt_outgoing_plaintext(&cv_net, &cmx, &mut OsRng)[..],
|
||||||
|
&tv.c_out[..]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,9 @@ use std::convert::{TryFrom, TryInto};
|
||||||
use pasta_curves::pallas;
|
use pasta_curves::pallas;
|
||||||
use rand::{CryptoRng, RngCore};
|
use rand::{CryptoRng, RngCore};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
|
||||||
/// A RedPallas signature type.
|
/// A RedPallas signature type.
|
||||||
pub trait SigType: reddsa::SigType + private::Sealed {}
|
pub trait SigType: reddsa::SigType + private::Sealed {}
|
||||||
|
|
||||||
|
@ -93,6 +96,12 @@ impl<T: SigType> PartialEq for VerificationKey<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VerificationKey<SpendAuth> {
|
impl VerificationKey<SpendAuth> {
|
||||||
|
/// Used in the note encryption tests.
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn dummy() -> Self {
|
||||||
|
VerificationKey((&reddsa::SigningKey::new(OsRng)).into())
|
||||||
|
}
|
||||||
|
|
||||||
/// Randomizes this verification key with the given `randomizer`.
|
/// Randomizes this verification key with the given `randomizer`.
|
||||||
///
|
///
|
||||||
/// Randomization is only supported for `SpendAuth` keys.
|
/// Randomization is only supported for `SpendAuth` keys.
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
pub(crate) mod commitment_tree;
|
pub(crate) mod commitment_tree;
|
||||||
pub(crate) mod keys;
|
pub(crate) mod keys;
|
||||||
|
pub(crate) mod note_encryption;
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue