mirror of https://github.com/zcash/orchard.git
Orchard note encryption
This commit is contained in:
parent
11350339f5
commit
99665572a2
|
@ -28,6 +28,10 @@ impl Address {
|
|||
Address { d, pk_d }
|
||||
}
|
||||
|
||||
pub(crate) fn diversifer(&self) -> Diversifier {
|
||||
self.d
|
||||
}
|
||||
|
||||
pub(crate) fn g_d(&self) -> NonIdentityPallasPoint {
|
||||
diversify_hash(self.d.as_array())
|
||||
}
|
||||
|
|
25
src/keys.rs
25
src/keys.rs
|
@ -295,6 +295,10 @@ impl DiversifierKey {
|
|||
pub struct Diversifier([u8; 11]);
|
||||
|
||||
impl Diversifier {
|
||||
pub(crate) fn from_bytes(d: [u8; 11]) -> Self {
|
||||
Diversifier(d)
|
||||
}
|
||||
|
||||
/// Returns the byte array corresponding to this diversifier.
|
||||
pub fn as_array(&self) -> &[u8; 11] {
|
||||
&self.0
|
||||
|
@ -339,7 +343,7 @@ impl KeyAgreementPrivateKey {
|
|||
|
||||
/// Returns the payment address for this key corresponding to the given diversifier.
|
||||
fn address(&self, d: Diversifier) -> Address {
|
||||
let pk_d = DiversifiedTransmissionKey::derive(self, &d);
|
||||
let pk_d = DiversifiedTransmissionKey::derive_inner(self, &d);
|
||||
Address::from_parts(d, pk_d)
|
||||
}
|
||||
}
|
||||
|
@ -406,23 +410,38 @@ impl From<&FullViewingKey> for OutgoingViewingKey {
|
|||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8; 32]> for OutgoingViewingKey {
|
||||
fn as_ref(&self) -> &[u8; 32] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// The diversified transmission key for a given payment address.
|
||||
///
|
||||
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
|
||||
///
|
||||
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) struct DiversifiedTransmissionKey(NonIdentityPallasPoint);
|
||||
pub struct DiversifiedTransmissionKey(NonIdentityPallasPoint);
|
||||
|
||||
impl DiversifiedTransmissionKey {
|
||||
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
|
||||
///
|
||||
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
||||
fn derive(ivk: &KeyAgreementPrivateKey, d: &Diversifier) -> Self {
|
||||
pub(crate) fn derive(ivk: &IncomingViewingKey, d: &Diversifier) -> Self {
|
||||
Self::derive_inner(&ivk.ivk, d)
|
||||
}
|
||||
|
||||
fn derive_inner(ivk: &KeyAgreementPrivateKey, d: &Diversifier) -> Self {
|
||||
let g_d = diversify_hash(&d.as_array());
|
||||
DiversifiedTransmissionKey(ka_orchard(&ivk.0, &g_d))
|
||||
}
|
||||
|
||||
/// $abst_P(bytes)$
|
||||
pub(crate) fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
|
||||
NonIdentityPallasPoint::from_bytes(bytes).map(DiversifiedTransmissionKey)
|
||||
}
|
||||
|
||||
/// $repr_P(self)$
|
||||
pub(crate) fn to_bytes(self) -> [u8; 32] {
|
||||
self.0.to_bytes()
|
||||
|
|
|
@ -23,6 +23,7 @@ mod circuit;
|
|||
mod constants;
|
||||
pub mod keys;
|
||||
pub mod note;
|
||||
mod note_encryption;
|
||||
pub mod primitives;
|
||||
mod spec;
|
||||
mod tree;
|
||||
|
|
22
src/note.rs
22
src/note.rs
|
@ -5,7 +5,7 @@ use rand::RngCore;
|
|||
use subtle::CtOption;
|
||||
|
||||
use crate::{
|
||||
keys::{FullViewingKey, SpendingKey},
|
||||
keys::{EphemeralSecretKey, FullViewingKey, SpendingKey},
|
||||
spec::{to_base, to_scalar, NonZeroPallasScalar, PrfExpand},
|
||||
value::NoteValue,
|
||||
Address,
|
||||
|
@ -39,6 +39,10 @@ impl RandomSeed {
|
|||
CtOption::new(rseed, esk.is_some())
|
||||
}
|
||||
|
||||
pub(crate) fn to_bytes(&self) -> &[u8; 32] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend].
|
||||
///
|
||||
/// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend
|
||||
|
@ -92,7 +96,6 @@ pub struct Note {
|
|||
}
|
||||
|
||||
impl Note {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn from_parts(
|
||||
recipient: Address,
|
||||
value: NoteValue,
|
||||
|
@ -154,11 +157,26 @@ impl Note {
|
|||
(sk, fvk, note)
|
||||
}
|
||||
|
||||
/// Returns the recipient of this note.
|
||||
pub fn recipient(&self) -> Address {
|
||||
self.recipient
|
||||
}
|
||||
|
||||
/// Returns the value of this note.
|
||||
pub fn value(&self) -> NoteValue {
|
||||
self.value
|
||||
}
|
||||
|
||||
/// Derives the ephemeral secret key for this note.
|
||||
pub(crate) fn rseed(&self) -> &RandomSeed {
|
||||
&self.rseed
|
||||
}
|
||||
|
||||
/// Derives the ephemeral secret key for this note.
|
||||
pub(crate) fn esk(&self) -> EphemeralSecretKey {
|
||||
EphemeralSecretKey(self.rseed.esk(&self.rho))
|
||||
}
|
||||
|
||||
/// Derives the commitment to this note.
|
||||
///
|
||||
/// Defined in [Zcash Protocol Spec § 3.2: Notes][notes].
|
||||
|
|
|
@ -71,3 +71,9 @@ impl std::ops::Deref for ExtractedNoteCommitment {
|
|||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ExtractedNoteCommitment> for [u8; 32] {
|
||||
fn from(cmx: &ExtractedNoteCommitment) -> Self {
|
||||
cmx.to_bytes()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,250 @@
|
|||
//! In-band secret distribution for Orchard bundles.
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
use blake2b_simd::{Hash, Params};
|
||||
use halo2::arithmetic::FieldExt;
|
||||
use zcash_note_encryption::{
|
||||
Domain, EphemeralKeyBytes, NotePlaintextBytes, NoteValidity, OutPlaintextBytes,
|
||||
OutgoingCipherKey, ShieldedOutput, COMPACT_NOTE_SIZE, NOTE_PLAINTEXT_SIZE, OUT_PLAINTEXT_SIZE,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
bundle::Action,
|
||||
keys::{
|
||||
DiversifiedTransmissionKey, Diversifier, EphemeralPublicKey, EphemeralSecretKey,
|
||||
IncomingViewingKey, OutgoingViewingKey, SharedSecret,
|
||||
},
|
||||
note::{ExtractedNoteCommitment, Nullifier, RandomSeed},
|
||||
spec::diversify_hash,
|
||||
value::{NoteValue, ValueCommitment},
|
||||
Address, Note,
|
||||
};
|
||||
|
||||
const PRF_OCK_ORCHARD_PERSONALIZATION: &[u8; 16] = b"Zcash_Orchardock";
|
||||
|
||||
/// Defined in [Zcash Protocol Spec § 5.4.2: Pseudo Random Functions][concreteprfs].
|
||||
///
|
||||
/// [concreteprfs]: https://zips.z.cash/protocol/nu5.pdf#concreteprfs
|
||||
pub(crate) fn prf_ock_orchard(
|
||||
ovk: &OutgoingViewingKey,
|
||||
cv: &ValueCommitment,
|
||||
cmx_bytes: &[u8; 32],
|
||||
ephemeral_key: &EphemeralKeyBytes,
|
||||
) -> OutgoingCipherKey {
|
||||
OutgoingCipherKey(
|
||||
Params::new()
|
||||
.hash_length(32)
|
||||
.personal(PRF_OCK_ORCHARD_PERSONALIZATION)
|
||||
.to_state()
|
||||
.update(ovk.as_ref())
|
||||
.update(&cv.to_bytes())
|
||||
.update(cmx_bytes)
|
||||
.update(ephemeral_key.as_ref())
|
||||
.finalize()
|
||||
.as_bytes()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
fn orchard_parse_note_plaintext_without_memo<F>(
|
||||
domain: &OrchardDomain,
|
||||
plaintext: &[u8],
|
||||
get_validated_pk_d: F,
|
||||
) -> Option<(Note, Address)>
|
||||
where
|
||||
F: FnOnce(&Diversifier) -> Option<DiversifiedTransmissionKey>,
|
||||
{
|
||||
assert!(plaintext.len() >= COMPACT_NOTE_SIZE);
|
||||
|
||||
// Check note plaintext version
|
||||
if plaintext[0] != 0x02 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// The unwraps below are guaranteed to succeed by the assertion above
|
||||
let diversifier = Diversifier::from_bytes(plaintext[1..12].try_into().unwrap());
|
||||
let value = NoteValue::from_bytes(plaintext[12..20].try_into().unwrap());
|
||||
let rseed = Option::from(RandomSeed::from_bytes(
|
||||
plaintext[20..COMPACT_NOTE_SIZE].try_into().unwrap(),
|
||||
&domain.rho,
|
||||
))?;
|
||||
|
||||
let pk_d = get_validated_pk_d(&diversifier)?;
|
||||
|
||||
let recipient = Address::from_parts(diversifier, pk_d);
|
||||
let note = Note::from_parts(recipient, value, domain.rho, rseed);
|
||||
Some((note, recipient))
|
||||
}
|
||||
|
||||
/// Orchard-specific note encryption logic.
|
||||
#[derive(Debug)]
|
||||
pub struct OrchardDomain {
|
||||
rho: Nullifier,
|
||||
}
|
||||
|
||||
impl Domain for OrchardDomain {
|
||||
type EphemeralSecretKey = EphemeralSecretKey;
|
||||
type EphemeralPublicKey = EphemeralPublicKey;
|
||||
type SharedSecret = SharedSecret;
|
||||
type SymmetricKey = Hash;
|
||||
type Note = Note;
|
||||
type Recipient = Address;
|
||||
type DiversifiedTransmissionKey = DiversifiedTransmissionKey;
|
||||
type IncomingViewingKey = IncomingViewingKey;
|
||||
type OutgoingViewingKey = OutgoingViewingKey;
|
||||
type ValueCommitment = ValueCommitment;
|
||||
type ExtractedCommitment = ExtractedNoteCommitment;
|
||||
type ExtractedCommitmentBytes = [u8; 32];
|
||||
type Memo = [u8; 512]; // TODO use a more interesting type
|
||||
|
||||
fn derive_esk(note: &Self::Note) -> Option<Self::EphemeralSecretKey> {
|
||||
Some(note.esk())
|
||||
}
|
||||
|
||||
fn get_pk_d(note: &Self::Note) -> Self::DiversifiedTransmissionKey {
|
||||
*note.recipient().pk_d()
|
||||
}
|
||||
|
||||
fn ka_derive_public(
|
||||
note: &Self::Note,
|
||||
esk: &Self::EphemeralSecretKey,
|
||||
) -> Self::EphemeralPublicKey {
|
||||
esk.derive_public(note.recipient().g_d())
|
||||
}
|
||||
|
||||
fn ka_agree_enc(
|
||||
esk: &Self::EphemeralSecretKey,
|
||||
pk_d: &Self::DiversifiedTransmissionKey,
|
||||
) -> Self::SharedSecret {
|
||||
esk.agree(pk_d)
|
||||
}
|
||||
|
||||
fn ka_agree_dec(
|
||||
ivk: &Self::IncomingViewingKey,
|
||||
epk: &Self::EphemeralPublicKey,
|
||||
) -> Self::SharedSecret {
|
||||
epk.agree(ivk)
|
||||
}
|
||||
|
||||
fn kdf(secret: Self::SharedSecret, ephemeral_key: &EphemeralKeyBytes) -> Self::SymmetricKey {
|
||||
secret.kdf_orchard(&ephemeral_key)
|
||||
}
|
||||
|
||||
fn note_plaintext_bytes(
|
||||
note: &Self::Note,
|
||||
_: &Self::Recipient,
|
||||
memo: &Self::Memo,
|
||||
) -> NotePlaintextBytes {
|
||||
let mut np = [0; NOTE_PLAINTEXT_SIZE];
|
||||
np[0] = 0x02;
|
||||
np[1..12].copy_from_slice(note.recipient().diversifer().as_array());
|
||||
np[12..20].copy_from_slice(¬e.value().to_bytes());
|
||||
np[20..52].copy_from_slice(note.rseed().to_bytes());
|
||||
np[52..].copy_from_slice(memo);
|
||||
NotePlaintextBytes(np)
|
||||
}
|
||||
|
||||
fn derive_ock(
|
||||
ovk: &Self::OutgoingViewingKey,
|
||||
cv: &Self::ValueCommitment,
|
||||
cmstar_bytes: &Self::ExtractedCommitmentBytes,
|
||||
ephemeral_key: &EphemeralKeyBytes,
|
||||
) -> OutgoingCipherKey {
|
||||
prf_ock_orchard(ovk, cv, cmstar_bytes, ephemeral_key)
|
||||
}
|
||||
|
||||
fn outgoing_plaintext_bytes(
|
||||
note: &Self::Note,
|
||||
esk: &Self::EphemeralSecretKey,
|
||||
) -> OutPlaintextBytes {
|
||||
let mut op = [0; OUT_PLAINTEXT_SIZE];
|
||||
op[..32].copy_from_slice(¬e.recipient().pk_d().to_bytes());
|
||||
op[32..].copy_from_slice(&esk.0.to_bytes());
|
||||
OutPlaintextBytes(op)
|
||||
}
|
||||
|
||||
fn epk_bytes(epk: &Self::EphemeralPublicKey) -> EphemeralKeyBytes {
|
||||
epk.to_bytes()
|
||||
}
|
||||
|
||||
fn epk(ephemeral_key: &EphemeralKeyBytes) -> Option<Self::EphemeralPublicKey> {
|
||||
EphemeralPublicKey::from_bytes(&ephemeral_key.0).into()
|
||||
}
|
||||
|
||||
fn check_epk_bytes<F: Fn(&Self::EphemeralSecretKey) -> NoteValidity>(
|
||||
note: &Self::Note,
|
||||
check: F,
|
||||
) -> NoteValidity {
|
||||
check(¬e.esk())
|
||||
}
|
||||
|
||||
fn cmstar(note: &Self::Note) -> Self::ExtractedCommitment {
|
||||
note.commitment().into()
|
||||
}
|
||||
|
||||
fn parse_note_plaintext_without_memo_ivk(
|
||||
&self,
|
||||
ivk: &Self::IncomingViewingKey,
|
||||
plaintext: &[u8],
|
||||
) -> Option<(Self::Note, Self::Recipient)> {
|
||||
orchard_parse_note_plaintext_without_memo(&self, plaintext, |diversifier| {
|
||||
Some(DiversifiedTransmissionKey::derive(ivk, diversifier))
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_note_plaintext_without_memo_ovk(
|
||||
&self,
|
||||
pk_d: &Self::DiversifiedTransmissionKey,
|
||||
esk: &Self::EphemeralSecretKey,
|
||||
ephemeral_key: &EphemeralKeyBytes,
|
||||
plaintext: &[u8],
|
||||
) -> Option<(Self::Note, Self::Recipient)> {
|
||||
orchard_parse_note_plaintext_without_memo(&self, plaintext, |diversifier| {
|
||||
if esk
|
||||
.derive_public(diversify_hash(diversifier.as_array()))
|
||||
.to_bytes()
|
||||
.0
|
||||
== ephemeral_key.0
|
||||
{
|
||||
Some(*pk_d)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_memo(&self, plaintext: &[u8]) -> Self::Memo {
|
||||
plaintext[COMPACT_NOTE_SIZE..NOTE_PLAINTEXT_SIZE]
|
||||
.try_into()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn extract_pk_d(
|
||||
out_plaintext: &[u8; OUT_PLAINTEXT_SIZE],
|
||||
) -> Option<Self::DiversifiedTransmissionKey> {
|
||||
DiversifiedTransmissionKey::from_bytes(out_plaintext[0..32].try_into().unwrap()).into()
|
||||
}
|
||||
|
||||
fn extract_esk(out_plaintext: &[u8; OUT_PLAINTEXT_SIZE]) -> Option<Self::EphemeralSecretKey> {
|
||||
EphemeralSecretKey::from_bytes(out_plaintext[32..OUT_PLAINTEXT_SIZE].try_into().unwrap())
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub type OrchardNoteEncryption = zcash_note_encryption::NoteEncryption<OrchardDomain>;
|
||||
|
||||
impl<T> ShieldedOutput<OrchardDomain> for Action<T> {
|
||||
fn ephemeral_key(&self) -> EphemeralKeyBytes {
|
||||
EphemeralKeyBytes(self.encrypted_note().epk_bytes)
|
||||
}
|
||||
|
||||
fn cmstar_bytes(&self) -> [u8; 32] {
|
||||
self.cmx().to_bytes()
|
||||
}
|
||||
|
||||
fn enc_ciphertext(&self) -> &[u8] {
|
||||
&self.encrypted_note().enc_ciphertext
|
||||
}
|
||||
}
|
|
@ -74,6 +74,14 @@ impl NoteValue {
|
|||
NoteValue(value)
|
||||
}
|
||||
|
||||
pub(crate) fn from_bytes(bytes: [u8; 8]) -> Self {
|
||||
NoteValue(u64::from_le_bytes(bytes))
|
||||
}
|
||||
|
||||
pub(crate) fn to_bytes(self) -> [u8; 8] {
|
||||
self.0.to_le_bytes()
|
||||
}
|
||||
|
||||
pub(crate) fn to_le_bits(self) -> BitArray<Lsb0, [u8; 8]> {
|
||||
BitArray::<Lsb0, _>::new(self.0.to_le_bytes())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue