Merge branch 'zsa-note-encryption' into zsa-value-and-encryption

This commit is contained in:
Aurélien Nicolas 2022-05-29 17:17:30 +02:00
commit b04468cabc
4 changed files with 88 additions and 28 deletions

View File

@ -34,6 +34,9 @@ pub const VALUE_COMMITMENT_R_BYTES: [u8; 1] = *b"r";
/// SWU hash-to-curve personalization for the note commitment generator
pub const NOTE_COMMITMENT_PERSONALIZATION: &str = "z.cash:Orchard-NoteCommit";
/// SWU hash-to-curve personalization for the ZSA note commitment generator
pub const NOTE_ZSA_COMMITMENT_PERSONALIZATION: &str = "z.cash:ZSA-NoteCommit";
/// SWU hash-to-curve personalization for the IVK commitment generator
pub const COMMIT_IVK_PERSONALIZATION: &str = "z.cash:Orchard-CommitIvk";

View File

@ -189,7 +189,7 @@ impl Note {
self.value
}
/// Returns the note type
/// Returns the note type of this note.
pub fn note_type(&self) -> NoteType {
self.note_type
}
@ -235,6 +235,7 @@ impl Note {
g_d.to_bytes(),
self.recipient.pk_d().to_bytes(),
self.value,
self.note_type,
self.rho.0,
self.rseed.psi(&self.rho),
self.rseed.rcm(&self.rho),

View File

@ -7,7 +7,11 @@ use pasta_curves::pallas;
use subtle::{ConstantTimeEq, CtOption};
use crate::{
constants::{fixed_bases::NOTE_COMMITMENT_PERSONALIZATION, L_ORCHARD_BASE},
constants::{
fixed_bases::{NOTE_COMMITMENT_PERSONALIZATION, NOTE_ZSA_COMMITMENT_PERSONALIZATION},
L_ORCHARD_BASE,
},
note::note_type::NoteType,
spec::extract_p,
value::NoteValue,
};
@ -41,22 +45,46 @@ impl NoteCommitment {
g_d: [u8; 32],
pk_d: [u8; 32],
v: NoteValue,
note_type: NoteType,
rho: pallas::Base,
psi: pallas::Base,
rcm: NoteCommitTrapdoor,
) -> CtOption<Self> {
let domain = sinsemilla::CommitDomain::new(NOTE_COMMITMENT_PERSONALIZATION);
domain
.commit(
iter::empty()
.chain(BitArray::<_, Lsb0>::new(g_d).iter().by_vals())
.chain(BitArray::<_, Lsb0>::new(pk_d).iter().by_vals())
.chain(v.to_le_bits().iter().by_vals())
.chain(rho.to_le_bits().iter().by_vals().take(L_ORCHARD_BASE))
.chain(psi.to_le_bits().iter().by_vals().take(L_ORCHARD_BASE)),
&rcm.0,
)
.map(NoteCommitment)
let g_d_bits = BitArray::<_, Lsb0>::new(g_d);
let pk_d_bits = BitArray::<_, Lsb0>::new(pk_d);
let v_bits = v.to_le_bits();
let rho_bits = rho.to_le_bits();
let psi_bits = psi.to_le_bits();
let zec_note_bits = iter::empty()
.chain(g_d_bits.iter().by_vals())
.chain(pk_d_bits.iter().by_vals())
.chain(v_bits.iter().by_vals())
.chain(rho_bits.iter().by_vals().take(L_ORCHARD_BASE))
.chain(psi_bits.iter().by_vals().take(L_ORCHARD_BASE));
// TODO: make this constant-time.
if note_type.is_native().into() {
// Commit to ZEC notes as per the Orchard protocol.
Self::commit(NOTE_COMMITMENT_PERSONALIZATION, zec_note_bits, rcm)
} else {
// Commit to non-ZEC notes as per the ZSA protocol.
// Append the note type to the Orchard note encoding.
let type_bits = BitArray::<_, Lsb0>::new(note_type.to_bytes());
let zsa_note_bits = zec_note_bits.chain(type_bits.iter().by_vals());
// Commit in a different domain than Orchard notes.
Self::commit(NOTE_ZSA_COMMITMENT_PERSONALIZATION, zsa_note_bits, rcm)
}
}
fn commit(
personalization: &str,
bits: impl Iterator<Item = bool>,
rcm: NoteCommitTrapdoor,
) -> CtOption<Self> {
let domain = sinsemilla::CommitDomain::new(personalization);
domain.commit(bits, &rcm.0).map(NoteCommitment)
}
}

View File

@ -4,10 +4,11 @@ use core::fmt;
use blake2b_simd::{Hash, Params};
use group::ff::PrimeField;
use zcash_note_encryption::{
BatchDomain, Domain, EphemeralKeyBytes, NotePlaintextBytes, OutPlaintextBytes,
OutgoingCipherKey, ShieldedOutput, COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, NOTE_PLAINTEXT_SIZE,
OUT_PLAINTEXT_SIZE,
OutgoingCipherKey, ShieldedOutput, COMPACT_NOTE_SIZE, COMPACT_ZSA_NOTE_SIZE,
ENC_CIPHERTEXT_SIZE, MEMO_SIZE, NOTE_PLAINTEXT_SIZE, OUT_PLAINTEXT_SIZE,
};
use crate::note::NoteType;
@ -50,6 +51,8 @@ pub(crate) fn prf_ock_orchard(
)
}
/// Domain-specific requirements:
/// - If the note version is 3, the `plaintext` must contain a valid encoding of a ZSA asset type.
fn orchard_parse_note_plaintext_without_memo<F>(
domain: &OrchardDomain,
plaintext: &[u8],
@ -61,9 +64,8 @@ where
assert!(plaintext.len() >= COMPACT_NOTE_SIZE);
// Check note plaintext version
if plaintext[0] != 0x02 {
return None;
}
// and parse the asset type accordingly.
let note_type = parse_version_and_asset_type(plaintext)?;
// The unwraps below are guaranteed to succeed by the assertion above
let diversifier = Diversifier::from_bytes(plaintext[1..12].try_into().unwrap());
@ -76,11 +78,25 @@ where
let pk_d = get_validated_pk_d(&diversifier)?;
let recipient = Address::from_parts(diversifier, pk_d);
// TODO: add note_type
let note = Note::from_parts(recipient, value, NoteType::native(), domain.rho, rseed);
let note = Note::from_parts(recipient, value, note_type, domain.rho, rseed);
Some((note, recipient))
}
fn parse_version_and_asset_type(plaintext: &[u8]) -> Option<NoteType> {
// TODO: make this constant-time?
match plaintext[0] {
0x02 => Some(NoteType::native()),
0x03 if plaintext.len() >= COMPACT_ZSA_NOTE_SIZE => {
let bytes = &plaintext[COMPACT_NOTE_SIZE..COMPACT_ZSA_NOTE_SIZE]
.try_into()
.unwrap();
NoteType::from_bytes(bytes).into()
}
_ => None,
}
}
/// Orchard-specific note encryption logic.
#[derive(Debug)]
pub struct OrchardDomain {
@ -109,7 +125,7 @@ impl Domain for OrchardDomain {
type ValueCommitment = ValueCommitment;
type ExtractedCommitment = ExtractedNoteCommitment;
type ExtractedCommitmentBytes = [u8; 32];
type Memo = [u8; 512]; // TODO use a more interesting type
type Memo = [u8; MEMO_SIZE]; // TODO use a more interesting type
fn derive_esk(note: &Self::Note) -> Option<Self::EphemeralSecretKey> {
Some(note.esk())
@ -149,13 +165,22 @@ impl Domain for OrchardDomain {
_: &Self::Recipient,
memo: &Self::Memo,
) -> NotePlaintextBytes {
let is_native: bool = note.note_type().is_native().into();
let mut np = [0; NOTE_PLAINTEXT_SIZE];
np[0] = 0x02;
np[0] = if is_native { 0x02 } else { 0x03 };
np[1..12].copy_from_slice(note.recipient().diversifier().as_array());
np[12..20].copy_from_slice(&note.value().to_bytes());
// todo: add note_type
np[20..52].copy_from_slice(note.rseed().as_bytes());
np[52..].copy_from_slice(memo);
if is_native {
np[52..].copy_from_slice(memo);
} else {
let zsa_type = note.note_type().to_bytes();
np[52..84].copy_from_slice(&zsa_type);
let short_memo = &memo[0..memo.len() - 32];
np[84..].copy_from_slice(short_memo);
// TODO: handle full-size memo or make short_memo explicit.
};
NotePlaintextBytes(np)
}
@ -222,6 +247,7 @@ impl Domain for OrchardDomain {
}
fn extract_memo(&self, plaintext: &NotePlaintextBytes) -> Self::Memo {
// TODO: support ZSA note plaintext with short_memo.
plaintext.0[COMPACT_NOTE_SIZE..NOTE_PLAINTEXT_SIZE]
.try_into()
.unwrap()
@ -274,7 +300,7 @@ pub struct CompactAction {
nullifier: Nullifier,
cmx: ExtractedNoteCommitment,
ephemeral_key: EphemeralKeyBytes,
enc_ciphertext: [u8; 52],
enc_ciphertext: [u8; COMPACT_NOTE_SIZE],
}
impl fmt::Debug for CompactAction {
@ -289,7 +315,7 @@ impl<T> From<&Action<T>> for CompactAction {
nullifier: *action.nullifier(),
cmx: *action.cmx(),
ephemeral_key: action.ephemeral_key(),
enc_ciphertext: action.encrypted_note().enc_ciphertext[..52]
enc_ciphertext: action.encrypted_note().enc_ciphertext[..COMPACT_NOTE_SIZE]
.try_into()
.unwrap(),
}
@ -313,12 +339,12 @@ impl ShieldedOutput<OrchardDomain, COMPACT_NOTE_SIZE> for CompactAction {
#[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::note::NoteType;
use crate::{
action::Action,
@ -332,6 +358,8 @@ mod tests {
Address, Note,
};
use super::{prf_ock_orchard, CompactAction, OrchardDomain, OrchardNoteEncryption};
#[test]
fn test_vectors() {
let test_vectors = crate::test_vectors::note_encryption::test_vectors();