From 1c98f15d5b489ae44fa16ff5979147e3a260d0a7 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 1 Jul 2022 15:27:16 -0400 Subject: [PATCH] Make the cross-group DLEqProof prove constant time Instead of having if statements for the bits, it now has constant time ops. While there are still if statements guiding the proof itself, they aren't dependent on the data within. --- crypto/dleq/Cargo.toml | 2 ++ crypto/dleq/src/cross_group/mod.rs | 45 +++++++++++++----------------- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/crypto/dleq/Cargo.toml b/crypto/dleq/Cargo.toml index de5338b7..27a806e8 100644 --- a/crypto/dleq/Cargo.toml +++ b/crypto/dleq/Cargo.toml @@ -10,6 +10,8 @@ edition = "2021" thiserror = "1" rand_core = "0.6" +subtle = "2.4" + transcript = { package = "flexible-transcript", path = "../transcript", version = "0.1" } ff = "0.12" diff --git a/crypto/dleq/src/cross_group/mod.rs b/crypto/dleq/src/cross_group/mod.rs index 012d1138..8026bafe 100644 --- a/crypto/dleq/src/cross_group/mod.rs +++ b/crypto/dleq/src/cross_group/mod.rs @@ -1,6 +1,8 @@ use thiserror::Error; use rand_core::{RngCore, CryptoRng}; +use subtle::{Choice, ConditionallySelectable}; + use transcript::Transcript; use group::{ff::{Field, PrimeField, PrimeFieldBits}, prime::PrimeGroup}; @@ -197,6 +199,9 @@ impl DLEqProof let capacity = usize::try_from(G0::Scalar::CAPACITY.min(G1::Scalar::CAPACITY)).unwrap(); let mut bits = Vec::with_capacity(capacity); for (i, bit) in raw_bits.iter().enumerate() { + let bit = *bit as u8; + debug_assert_eq!(bit | 1, 1); + let last = i == (capacity - 1); let blinding_key = ( Self::blinding_key(&mut *rng, &mut blinding_key_total.0, &mut pow_2.0, last), @@ -211,11 +216,8 @@ impl DLEqProof (generators.0.alt * blinding_key.0), (generators.1.alt * blinding_key.1) ); - // TODO: Not constant time - if *bit { - commitments.0 += generators.0.primary; - commitments.1 += generators.1.primary; - } + commitments.0 += generators.0.primary * G0::Scalar::from(bit.into()); + commitments.1 += generators.1.primary * G1::Scalar::from(bit.into()); Self::transcript_bit(transcript, i, commitments); let nonces = (G0::Scalar::random(&mut *rng), G1::Scalar::random(&mut *rng)); @@ -223,29 +225,22 @@ impl DLEqProof transcript.clone(), ((generators.0.alt * nonces.0), (generators.1.alt * nonces.1)) ); - let s_0 = (G0::Scalar::random(&mut *rng), G1::Scalar::random(&mut *rng)); + let mut s_0 = (G0::Scalar::random(&mut *rng), G1::Scalar::random(&mut *rng)); - let e_1 = Self::R_nonces( - transcript.clone(), - generators, - (s_0.0, s_0.1), - if *bit { - commitments - } else { - ((commitments.0 - generators.0.primary), (commitments.1 - generators.1.primary)) - }, - e_0 - ); - let s_1 = (nonces.0 + (e_1.0 * blinding_key.0), nonces.1 + (e_1.1 * blinding_key.1)); + let mut to_sign = commitments; + let bit = Choice::from(bit); + let inv_bit = (!bit).unwrap_u8(); + to_sign.0 -= generators.0.primary * G0::Scalar::from(inv_bit.into()); + to_sign.1 -= generators.1.primary * G1::Scalar::from(inv_bit.into()); + let e_1 = Self::R_nonces(transcript.clone(), generators, (s_0.0, s_0.1), to_sign, e_0); + let mut s_1 = (nonces.0 + (e_1.0 * blinding_key.0), nonces.1 + (e_1.1 * blinding_key.1)); - bits.push( - if *bit { - Bit { commitments, e: e_0.0, s: [s_1, s_0] } - } else { - Bit { commitments, e: e_1.0, s: [s_0, s_1] } - } - ); + let e = G0::Scalar::conditional_select(&e_1.0, &e_0.0, bit); + G0::Scalar::conditional_swap(&mut s_1.0, &mut s_0.0, bit); + G1::Scalar::conditional_swap(&mut s_1.1, &mut s_0.1, bit); + bits.push(Bit { commitments, e, s: [s_0, s_1] }); + // Break in order to not generate commitments for unused bits if last { break; }