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..30766fd40 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)); + .chain(psi_bits_left.iter().by_vals()); - // 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) - } + 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)) + .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 boolean `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) } } @@ -140,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 + ); + } +}