From a7e70ad919a9c6883c2930b56fb8d00f8fdbe197 Mon Sep 17 00:00:00 2001 From: Constance Date: Mon, 3 Apr 2023 10:26:43 +0200 Subject: [PATCH 1/3] Constant-time note commitment --- Cargo.toml | 6 ++-- src/note/commitment.rs | 79 +++++++++++++++++++++++++++++++----------- 2 files changed, 61 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5a09720a7..445212e41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,8 +29,8 @@ blake2b_simd = "1" ff = "0.12" fpe = "0.5" group = { version = "0.12.1", features = ["wnaf-memuse"] } -halo2_gadgets = "0.2" -halo2_proofs = "0.2" +halo2_gadgets = { git = "https://github.com/QED-it/halo2", branch = "append_sinsemilla_commit" } +halo2_proofs = { git = "https://github.com/QED-it/halo2", branch = "append_sinsemilla_commit" } hex = "0.4" lazy_static = "1" memuse = { version = "0.2.1", features = ["nonempty"] } @@ -52,7 +52,7 @@ plotters = { version = "0.3.0", optional = true } [dev-dependencies] criterion = "0.3" -halo2_gadgets = { version = "0.2", features = ["test-dependencies"] } +halo2_gadgets = { git = "https://github.com/QED-it/halo2", branch = "append_sinsemilla_commit", features = ["test-dependencies"] } hex = "0.4" proptest = "1.0.0" zcash_note_encryption = { version = "0.2", features = ["pre-zip-212"] } diff --git a/src/note/commitment.rs b/src/note/commitment.rs index f95d8be82..22cca5045 100644 --- a/src/note/commitment.rs +++ b/src/note/commitment.rs @@ -3,12 +3,14 @@ use core::iter; use bitvec::{array::BitArray, order::Lsb0}; use group::ff::{PrimeField, PrimeFieldBits}; use halo2_gadgets::sinsemilla::primitives as sinsemilla; +use halo2_gadgets::sinsemilla::primitives::append_hash_to_point; use pasta_curves::pallas; use subtle::{ConstantTimeEq, CtOption}; use crate::{ constants::{ fixed_bases::{NOTE_COMMITMENT_PERSONALIZATION, NOTE_ZSA_COMMITMENT_PERSONALIZATION}, + sinsemilla::K, L_ORCHARD_BASE, }, note::asset_base::AssetBase, @@ -56,35 +58,70 @@ impl NoteCommitment { let rho_bits = rho.to_le_bits(); let psi_bits = psi.to_le_bits(); - let zec_note_bits = iter::empty() + let remaining_bits = + (g_d_bits.len() + pk_d_bits.len() + v_bits.len() + 2 * L_ORCHARD_BASE) % K; + + let (psi_bits_left, psi_bits_right) = psi_bits.split_at(L_ORCHARD_BASE - remaining_bits); + + let common_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 asset.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(asset.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) - } + .chain(psi_bits_left.iter().by_vals()); + + let zec_suffix = psi_bits_right.clone().iter().by_vals().take(remaining_bits); + let type_bits = BitArray::<_, Lsb0>::new(asset.to_bytes()); + let zsa_suffix = iter::empty() + .chain(psi_bits_right.iter().by_vals().take(remaining_bits)) + .chain(type_bits.iter().by_vals()); + + Self::double_constant_time_commit( + NOTE_COMMITMENT_PERSONALIZATION, + NOTE_ZSA_COMMITMENT_PERSONALIZATION, + common_bits, + zec_suffix, + zsa_suffix, + rcm, + asset.is_native().into(), + ) } - fn commit( - personalization: &str, - bits: impl Iterator, + /// Evaluates `SinsemillaCommit_{rcm}(personalization1, common_bits||suffix1)` and + /// `SinsemillaCommit_{rcm}(personalization2, common_bits||suffix2)` and returns the commit + /// corresponding to the choice. + /// + /// We would like to have a constant time implementation even if suffix1 and suffix2 have not + /// the same length. + /// `common_bits` must be a multiple of K bits + fn double_constant_time_commit( + personalization1: &str, + personalization2: &str, + common_bits: impl Iterator, + suffix1: impl Iterator, + suffix2: impl Iterator, rcm: NoteCommitTrapdoor, + choice: bool, ) -> CtOption { - let domain = sinsemilla::CommitDomain::new(personalization); - domain.commit(bits, &rcm.0).map(NoteCommitment) + // Select the desired personalization + let domain = if choice { + sinsemilla::CommitDomain::new(personalization1) + } else { + sinsemilla::CommitDomain::new(personalization2) + }; + // Evaluate the hash on the `common_bits` + let common_hash = domain.hash_to_point_inner(common_bits); + // Continue to evaluate the hash from the previous hash with each possible suffix + // We would like to have a constant time implementation. Hence, we have to evaluate the + // hash for the both suffixes + let hash1 = append_hash_to_point(common_hash, suffix1); + let hash2 = append_hash_to_point(common_hash, suffix2); + // Select the desired hash + let note_hash = if choice { hash1 } else { hash2 }; + // Evaluate the commitment from this hash point + domain + .commit_from_hash_point(note_hash, &rcm.0) + .map(NoteCommitment) } } From fbb987535be867402463cb9ecd6cf3540a61a77f Mon Sep 17 00:00:00 2001 From: Constance Date: Mon, 3 Apr 2023 10:34:14 +0200 Subject: [PATCH 2/3] Fix clippy warnings --- src/note/commitment.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/note/commitment.rs b/src/note/commitment.rs index 22cca5045..dadd7b6a2 100644 --- a/src/note/commitment.rs +++ b/src/note/commitment.rs @@ -70,7 +70,7 @@ impl NoteCommitment { .chain(rho_bits.iter().by_vals().take(L_ORCHARD_BASE)) .chain(psi_bits_left.iter().by_vals()); - let zec_suffix = psi_bits_right.clone().iter().by_vals().take(remaining_bits); + let zec_suffix = psi_bits_right.iter().by_vals().take(remaining_bits); let type_bits = BitArray::<_, Lsb0>::new(asset.to_bytes()); let zsa_suffix = iter::empty() .chain(psi_bits_right.iter().by_vals().take(remaining_bits)) From 5c4bb54048788b408c24f001a562ac61c9622d6e Mon Sep 17 00:00:00 2001 From: Constance Date: Thu, 6 Apr 2023 11:07:41 +0200 Subject: [PATCH 3/3] Add a test --- src/note/commitment.rs | 81 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/src/note/commitment.rs b/src/note/commitment.rs index dadd7b6a2..30766fd40 100644 --- a/src/note/commitment.rs +++ b/src/note/commitment.rs @@ -89,7 +89,7 @@ impl NoteCommitment { /// Evaluates `SinsemillaCommit_{rcm}(personalization1, common_bits||suffix1)` and /// `SinsemillaCommit_{rcm}(personalization2, common_bits||suffix2)` and returns the commit - /// corresponding to the choice. + /// corresponding to the boolean `choice`. /// /// We would like to have a constant time implementation even if suffix1 and suffix2 have not /// the same length. @@ -177,3 +177,82 @@ impl PartialEq for ExtractedNoteCommitment { } impl Eq for ExtractedNoteCommitment {} + +#[cfg(test)] +mod tests { + use crate::constants::fixed_bases::{ + NOTE_COMMITMENT_PERSONALIZATION, NOTE_ZSA_COMMITMENT_PERSONALIZATION, + }; + use crate::note::commitment::NoteCommitTrapdoor; + use crate::note::NoteCommitment; + use ff::Field; + use halo2_gadgets::sinsemilla::primitives as sinsemilla; + use pasta_curves::pallas; + use rand::{rngs::OsRng, Rng}; + + #[test] + fn test_double_constant_time_commit() { + let mut os_rng = OsRng::default(); + let prefix: Vec = (0..30).map(|_| os_rng.gen::()).collect(); + let suffix_zec: Vec = (0..6).map(|_| os_rng.gen::()).collect(); + let suffix_zsa: Vec = (0..25).map(|_| os_rng.gen::()).collect(); + + let rcm = NoteCommitTrapdoor(pallas::Scalar::random(&mut os_rng)); + + let commit_zec = { + let domain = sinsemilla::CommitDomain::new(NOTE_COMMITMENT_PERSONALIZATION); + domain + .commit( + prefix + .clone() + .into_iter() + .chain(suffix_zec.clone().into_iter()), + &rcm.0, + ) + .unwrap() + }; + + assert_eq!( + commit_zec, + NoteCommitment::double_constant_time_commit( + NOTE_COMMITMENT_PERSONALIZATION, + NOTE_ZSA_COMMITMENT_PERSONALIZATION, + prefix.clone().into_iter(), + suffix_zec.clone().into_iter(), + suffix_zsa.clone().into_iter(), + rcm.clone(), + true + ) + .unwrap() + .0 + ); + + let commit_zsa = { + let domain = sinsemilla::CommitDomain::new(NOTE_ZSA_COMMITMENT_PERSONALIZATION); + domain + .commit( + prefix + .clone() + .into_iter() + .chain(suffix_zsa.clone().into_iter()), + &rcm.0, + ) + .unwrap() + }; + + assert_eq!( + commit_zsa, + NoteCommitment::double_constant_time_commit( + NOTE_COMMITMENT_PERSONALIZATION, + NOTE_ZSA_COMMITMENT_PERSONALIZATION, + prefix.into_iter(), + suffix_zec.into_iter(), + suffix_zsa.into_iter(), + rcm, + false + ) + .unwrap() + .0 + ); + } +}