-
Notifications
You must be signed in to change notification settings - Fork 2
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
Feature: note plaintext size generalization #2
base: main
Are you sure you want to change the base?
Conversation
* Copy updated src folder from librustzcash/zsa1 * Cherry-pick the latest update of zcash_note_encryption from zcash/librustzcash (it was missed in QED-it/librustzcash) * Upgrade Rust version * Upgrade Rust version to 1.65 * Add --features=alloc to command line for build-nodefault jon in ci.yml * Downgrade Rust version back to 1.61.0 --------- Co-authored-by: Dmitry Demin <[email protected]>
Co-authored-by: Dmitry Demin <[email protected]>
Regarding the compile time vs runtime discussion, we currently set the array size during compile time in the implementation of the trait: https://github.com/QED-it/orchard/blob/a9af64c146bef38d7ac957983bc78c8a3f54f76e/src/note_encryption/note_encryption_zsa.rs#L12 impl OrchardDomain for OrchardZSA {
const COMPACT_NOTE_SIZE: usize = COMPACT_NOTE_SIZE_ZSA;
type NotePlaintextBytes = NoteBytes<{ Self::NOTE_PLAINTEXT_SIZE }>;
type NoteCiphertextBytes = NoteBytes<{ Self::ENC_CIPHERTEXT_SIZE }>;
type CompactNotePlaintextBytes = NoteBytes<{ Self::COMPACT_NOTE_SIZE }>;
type CompactNoteCiphertextBytes = NoteBytes<{ Self::COMPACT_NOTE_SIZE }>;
... |
…on (#6) * Update CHANGELOG.md with changes for note plaintext size generalization * Improve the description in CHANGELOG.md --------- Co-authored-by: Dmitry Demin <[email protected]>
Co-authored-by: Dmitry Demin <[email protected]>
src/lib.rs
Outdated
type NotePlaintextBytes: AsMut<[u8]> + for<'a> From<&'a [u8]>; | ||
type NoteCiphertextBytes: AsMut<[u8]> + for<'a> From<(&'a [u8], &'a [u8])>; | ||
type CompactNotePlaintextBytes: AsMut<[u8]> + for<'a> From<&'a [u8]>; | ||
type CompactNoteCiphertextBytes: AsRef<[u8]>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is incorrect to have From
bounds on these types, as the conversions are necessarily fallible (the slices can be any length, and the types cannot) and this would force panics in the public API (not just in the internal trait API that we control both sides of). Additionally, NoteCiphertextBytes
should not take the tag as a slice (we know its length).
Replace all of these with trait methods that "parse" the slices to get these, e.g. fn parse_note_plaintext_bytes(plaintext: &[u8]) -> Option<Self::NotePlaintextBytes>
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done: added three new methods to Domain
trait to parse note bytes.
fn parse_note_plaintext_bytes(plaintext: &[u8]) -> Option<Self::NotePlaintextBytes>
fn parse_note_ciphertext_bytes(output: &[u8], tag: [u8; AEAD_TAG_SIZE]) -> Option<Self::NoteCiphertextBytes>
fn parse_compact_note_plaintext_bytes(plaintext: &[u8]) -> Option<Self::CompactNotePlaintextBytes>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewed b8bd2a1.
src/lib.rs
Outdated
/// Exposes the note ciphertext of the output. Returns `None` if the output is compact. | ||
fn enc_ciphertext(&self) -> Option<D::NoteCiphertextBytes>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This no longer "exposes" the output, and instead forcibly clones it, which is undesirable in usages of ShieldedOutput
outside of zcash_note_encryption
. We should instead return a reference that the caller can clone if desired (by adding a Clone
bound on `Domain::NoteCiphertextBytes):
/// Exposes the note ciphertext of the output. Returns `None` if the output is compact. | |
fn enc_ciphertext(&self) -> Option<D::NoteCiphertextBytes>; | |
/// Exposes the note ciphertext of the output. | |
/// | |
/// Returns `None` if the output is only compact. | |
fn enc_ciphertext(&self) -> Option<&D::NoteCiphertextBytes>; |
Also, document this change to the trait in the changelog.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We tried to change the code to return references but encountered a few technical challenges due to how NoteCiphertextBytes
and CompactNoteCiphertextBytes
are implemented in both the orchard
and sapling-crypto
crates. These types are defined and implemented as newtypes over fixed-size byte arrays:
type NoteCiphertextBytes = NoteBytesData<{ ENC_CIPHERTEXT_SIZE }>;
type CompactNoteCiphertextBytes = NoteBytesData<{ COMPACT_NOTE_SIZE }>;
pub struct NoteBytesData<const N: usize>(pub [u8; N]);
The issue is that these newtypes wrap arrays that are not directly stored as such in the structs implementing ShieldedOutput
. Therefore, we cannot return a reference to them directly because they are constructed on-the-fly from these arrays.
A possible workaround is to change NoteCiphertextBytes
and CompactNoteCiphertextBytes
type implementations in orchard
and sapling-crypto
to wrap references instead of arrays, but we would need to adjust the Domain
trait to allow lifetimes and define associated types as generic with lifetimes using Rust's Generic Associated Types (GAT):
trait Domain {
type NoteCiphertextBytes<'a>;
type CompactNoteCiphertextBytes<'a>;
}
This approach would involve additional changes, impacting all Domain
and ShieldedOutput
implementations and adding complexity to the codebase. If using references is crucial, we can proceed with this strategy though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After discussion in the ZSA meeting, we will make my suggested change. If an action is non-compact, we know it is possible to get a reference to the appropriate type, and the data is large enough that we don't want to force clones.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done: now enc_ciphertext
returns Option
of a reference.
/// Exposes the compact note ciphertext of the output. | ||
fn enc_ciphertext_compact(&self) -> D::CompactNoteCiphertextBytes; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise, we should avoid forcibly cloning here, and instead have a Clone
bound on Domain::CompactNoteCiphertextBytes
:
/// Exposes the compact note ciphertext of the output. | |
fn enc_ciphertext_compact(&self) -> D::CompactNoteCiphertextBytes; | |
/// Exposes the compact view of the output's note ciphertext. | |
fn enc_ciphertext_compact(&self) -> &D::CompactNoteCiphertextBytes; |
Also, document this change to the trait in the changelog.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After discussion in the ZSA meeting, we'll leave this method as-is; the issue is that for non-compact actions, we can't get a reference to a fixed-length sub-array. The alternative would be to have a separate CompactShieldedOutput
trait (which is effectively what we had in the past), but we can consider that refactor later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it: left enc_ciphertext_compact
as-is.
src/lib.rs
Outdated
output[NOTE_PLAINTEXT_SIZE..].copy_from_slice(&tag); | ||
|
||
output | ||
D::NoteCiphertextBytes::from((output, tag.as_ref())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once the From
s are replaced, this becomes
D::NoteCiphertextBytes::from((output, tag.as_ref())) | |
D::parse_note_ciphertext_bytes(output, tag).expect("output is correct length") |
the unwrap here being fine because we know that output
is derived from D::NotePlaintextBytes
, which we can assume is the correct size relative to D::NoteCiphertextBytes
.
Non-blocking: I have ideas on how to make this infallible in a type-safe way, but I'll look at that refactor after this PR lands.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done (added and used parse..
method).
src/lib.rs
Outdated
fn extract_memo( | ||
&self, | ||
plaintext: &Self::NotePlaintextBytes, | ||
) -> (Self::CompactNotePlaintextBytes, Self::Memo); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I dislike this change, in part because we want to move away from the notion of compact memo representations in future NUs. But looking at how it is used in this crate, I think it's fine for now if we rename it:
fn extract_memo( | |
&self, | |
plaintext: &Self::NotePlaintextBytes, | |
) -> (Self::CompactNotePlaintextBytes, Self::Memo); | |
fn split_plaintext_at_memo( | |
&self, | |
plaintext: &Self::NotePlaintextBytes, | |
) -> (Self::CompactNotePlaintextBytes, Self::Memo); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done: renamed to split_plaintext_at_memo
.
src/lib.rs
Outdated
) -> Option<(Self::Note, Self::Recipient)>; | ||
|
||
/// Extracts the memo field from the given note plaintext. | ||
/// Splits the memo field from the given note plaintext. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Paired with the method rename:
/// Splits the memo field from the given note plaintext. | |
/// Splits the given note plaintext into the compact part (containing the note) and | |
/// the memo field. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
/// The size of a compact note. | ||
pub const COMPACT_NOTE_SIZE: usize = 1 + // version | ||
11 + // diversifier | ||
8 + // value | ||
32; // rseed (or rcm prior to ZIP 212) | ||
/// The size of [`NotePlaintextBytes`]. | ||
pub const NOTE_PLAINTEXT_SIZE: usize = COMPACT_NOTE_SIZE + 512; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either document these removals in the changelog, or (my preference) undo these removals (the constants are still relevant in downstream crates) and alter their docstrings to say that these are pre-ZSA.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done: documented in the changelog.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These constants applicable to Sapling and Orchard but not OrchardZSA.
We believe keeping the constants here will create a confusion in the future and propose to remove them.
Each implementation should provide these.
/// The size of an encrypted note plaintext. | ||
pub const ENC_CIPHERTEXT_SIZE: usize = NOTE_PLAINTEXT_SIZE + AEAD_TAG_SIZE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise, either document this removal in the changelog, or (my preference) undo this removal and alter its docstring to say that this is pre-ZSA.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done: documented in the changelog.
@@ -114,8 +109,6 @@ impl ConstantTimeEq for EphemeralKeyBytes { | |||
} | |||
} | |||
|
|||
/// Newtype representing the byte encoding of a note plaintext. | |||
pub struct NotePlaintextBytes(pub [u8; NOTE_PLAINTEXT_SIZE]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Document this removal in the changelog.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done: documented in the changelog.
@@ -192,7 +190,7 @@ pub trait Domain { | |||
fn kdf(secret: Self::SharedSecret, ephemeral_key: &EphemeralKeyBytes) -> Self::SymmetricKey; | |||
|
|||
/// Encodes the given `Note` and `Memo` as a note plaintext. | |||
fn note_plaintext_bytes(note: &Self::Note, memo: &Self::Memo) -> NotePlaintextBytes; | |||
fn note_plaintext_bytes(note: &Self::Note, memo: &Self::Memo) -> Self::NotePlaintextBytes; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Document the trait method changes like this in the changelog.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done: documented in the changelog.
@@ -403,24 +399,18 @@ impl<D: Domain> NoteEncryption<D> { | |||
} | |||
|
|||
/// Generates `encCiphertext` for this note. | |||
pub fn encrypt_note_plaintext(&self) -> [u8; ENC_CIPHERTEXT_SIZE] { | |||
pub fn encrypt_note_plaintext(&self) -> D::NoteCiphertextBytes { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Document this change in the changelog.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done: documented in the changelog.
…rom ShieldedOutput methods)
) * Attempt to resolve review issues for zcash/pull/2 * NoteBytes moved to zcash_note_encryption (copy of #8) (#9) * Add NoteBytes * Implement concat manually for wasm * Fix slice bounds in concat * Fmt * Split interface and implementation of NoteBytes * Add new method to NoteBytes --------- Co-authored-by: alexeykoren <[email protected]> * Resolve review issues for zcash/pull/2 (except returning references from ShieldedOutput methods) * Fix cargo doc issue * Fix based on feedback from PR #10 review * Make split_ciphertext_at_tag a method of ShieldedOutput with minor refactoring --------- Co-authored-by: Dmitry Demin <[email protected]> Co-authored-by: alexeykoren <[email protected]>
Co-authored-by: Dmitry Demin <[email protected]>
* Refactor enc_ciphertext to return reference instead of copy These changes were discussed and suggested in PR zcash_note_encryption#2 * Remove extra spaces in rust-toolchain.toml * Restore the original order of const definition to reduce PR diff * Fix the comment for split_plaintext_at_memo * Fix docstring for NOTE_PLAINTEXT_SIZE * Update CHANGELOG * Remove unused constants COMPACT_NOTE_SIZE, NOTE_PLAINTEXT_SIZE, ENC_CIPHERTEXT_SIZE, and update CHANGELOG accordingly * Minor improvement in CHANGELOG.md --------- Co-authored-by: Dmitry Demin <[email protected]>
Co-authored-by: Dmitry Demin <[email protected]>
…hertext (#112) This PR updates the `ShieldedOutput` implementation for the `Action`/`CompactAction` struct to align with the recent changes in the `zcash_note_encryption` crate. Specifically, the `enc_ciphertext` method now returns a reference instead of a copy. This change was discussed and suggested in PR zcash/zcash_note_encryption#2 review. --------- Co-authored-by: Dmitry Demin <[email protected]>
(updated description 20.08.2024)
This PR continues the discussion from: zcash/librustzcash#746. Some things have changed, and we need an updated review to continue.
In order to support note encryption for ZSA, we suggest extending the current
zcash_note_encryption
implementation. Currently, theCOMPACT_NOTE_SIZE
is a constant; however, we need to support variable note sizes to include theAssetId
field for ZSA notes.Current state in
zcash_note_encryption
:and
Proposed changes:
We suggest converting these constants into new abstract types within the Domain trait:
NotePlaintextBytes
,NoteCiphertextBytes
,CompactNotePlaintextBytes
, andCompactNoteCiphertextBytes
. These types would then be implemented in theorchard
andsapling-crypto
crates.After the discussion of the first version of this PR, the following methods are also proposed to be added to the
Domain
trait to safely convert byte slices into these new types:parse_note_plaintext_bytes
,parse_note_ciphertext_bytes
, andparse_compact_note_plaintext_bytes
.Updated
Domain
trait:Here,
NoteBytes
is a helper trait designed to simplify and unify the definition and implementation of these new associated types.Additionally, constants will be removed from function signatures since they are not known at compilation time. For example:
will be replaced with:
Implementation:
We have provided our initial implementation, complemented by the appropriate changes in the
orchard
andsapling-crypto
crates. See the following modules for details:https://github.com/QED-it/orchard/blob/zsa1/src/note_encryption/domain.rs
https://github.com/QED-it/sapling-crypto/blob/zsa1/src/note_encryption.rs
Additionally, we made several minor updates in the
zcash_primitives
crate of QED-it's fork of thelibrustzcash
repository to align it with the described changes inzcash_note_encryption
,orchard
, andsapling-crypto crates
:https://github.com/QED-it/librustzcash/tree/zsa1/zcash_primitives