Skip to content

Commit

Permalink
ZK proof cleanup (#99)
Browse files Browse the repository at this point in the history
  • Loading branch information
fjarri authored Jan 20, 2024
2 parents 1e8f908 + a851fff commit afe35e9
Show file tree
Hide file tree
Showing 28 changed files with 976 additions and 500 deletions.
112 changes: 97 additions & 15 deletions synedrion/src/cggmp21/params.rs
Original file line number Diff line number Diff line change
@@ -1,45 +1,57 @@
use crate::curve::Scalar;
use crate::curve::ORDER;
use crate::paillier::PaillierParams;
use crate::uint::{
upcast_uint, U1024Mod, U2048Mod, U4096Mod, U512Mod, U1024, U2048, U4096, U512, U8192,
subtle::ConditionallySelectable, upcast_uint, Bounded, Encoding, NonZero, Signed, U1024Mod,
U2048Mod, U4096Mod, U512Mod, Zero, U1024, U2048, U4096, U512, U8192,
};

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct PaillierTest;

impl PaillierParams for PaillierTest {
// We need 257-bit primes because we need MODULUS_BITS to accommodate all the possible
// values of curve scalar squared, which is 512 bits.

/*
The prime size is chosen to be minimal for which the `TestSchemeParams` still work.
In the presigning, we are effectively constructing a ciphertext of
d = x * sum(j=1..P) y_i + sum(j=1..2*(P-1)) z_j
where
0 < x, y_i < q < 2^L, and
-2^LP < z < 2^LP
(`q` is the curve order, `L` and `LP` are constants in `TestSchemeParams`,
`P` is the number of parties).
This is `delta_i`, an additive share of the product of two secret values.
This is `delta_i` or `chi_i`.
During signing `chi_i` gets additionally multiplied by `r` (nonce, a scalar).
We need the final result to be `-N/2 < d < N/2`
(that is, it may be negative, and it cannot wrap around modulo N).
(that is, it may be negative, and it cannot wrap around modulo N),
so that it could fit in a Paillier ciphertext without wrapping around.
This is needed for ZK proofs to work.
`N` is a product of two primes of the size `PRIME_BITS`, so `N > 2^(2 * PRIME_BITS - 2)`.
The upper bound on `log2(d)` is
max(2 * L, LP + 2) + ceil(log2(P))
The upper bound on `log2(d * r)` is
max(2 * L, LP + 2) + ceil(log2(CURVE_ORDER)) + ceil(log2(P))
(note that in reality, due to numbers being random, the distribution will have a distinct peak,
and the upper bound will have a low probability of being reached)
Therefore we require `max(2 * L, LP + 2) + ceil(log2(P)) < 2 * PRIME_BITS - 2`.
Therefore we require
max(2 * L, LP + 2) + ceil(log2(CURVE_ORDER)) + ceil(log2(P)) < 2 * PRIME_BITS - 2`
For tests we assume `ceil(log2(P)) = 5` (we won't run tests with more than 32 nodes),
and since in `TestSchemeParams` `L = LP = 256`, this leads to `PRIME_BITS >= L + 4`.
and since in `TestSchemeParams` `L = LP = 256`, this leads to `PRIME_BITS >= 397`.
For production it does not matter since both 2*L and LP are much smaller than 2*PRIME_BITS.
For production it does not matter since 2*L, LP, and log2(CURVE_ORDER)
are much smaller than 2*PRIME_BITS.
*/

const PRIME_BITS: usize = 260;
const PRIME_BITS: usize = 397;
type HalfUint = U512;
type HalfUintMod = U512Mod;
type Uint = U1024;
Expand Down Expand Up @@ -68,7 +80,9 @@ impl PaillierParams for PaillierProduction {
// but for now they are hardcoded to `k256`.
pub trait SchemeParams: Clone + Send + PartialEq + Eq + core::fmt::Debug + 'static {
/// The order of the curve.
const CURVE_ORDER: <Self::Paillier as PaillierParams>::Uint; // $q$
const CURVE_ORDER: NonZero<<Self::Paillier as PaillierParams>::Uint>; // $q$
/// The order of the curve as a wide integer.
const CURVE_ORDER_WIDE: NonZero<<Self::Paillier as PaillierParams>::WideUint>;
/// The sheme's statistical security parameter.
const SECURITY_PARAMETER: usize; // $\kappa$
/// The bound for secret values.
Expand All @@ -79,6 +93,68 @@ pub trait SchemeParams: Clone + Send + PartialEq + Eq + core::fmt::Debug + 'stat
const EPS_BOUND: usize; // $\eps$, in paper $= 2 \ell$ (see Table 2)
/// The parameters of the Paillier encryption.
type Paillier: PaillierParams;

/// Converts a curve scalar to the associated integer type.
fn uint_from_scalar(value: &Scalar) -> <Self::Paillier as PaillierParams>::Uint {
let scalar_bytes = value.to_bytes();
let mut repr = <Self::Paillier as PaillierParams>::Uint::ZERO.to_be_bytes();

let uint_len = repr.as_ref().len();
let scalar_len = scalar_bytes.len();

debug_assert!(uint_len >= scalar_len);
repr.as_mut()[uint_len - scalar_len..].copy_from_slice(&scalar_bytes);
<Self::Paillier as PaillierParams>::Uint::from_be_bytes(repr)
}

/// Converts a curve scalar to the associated integer type, wrapped in `Bounded`.
fn bounded_from_scalar(value: &Scalar) -> Bounded<<Self::Paillier as PaillierParams>::Uint> {
const ORDER_BITS: usize = ORDER.bits_vartime();
Bounded::new(Self::uint_from_scalar(value), ORDER_BITS as u32).unwrap()
}

/// Converts a curve scalar to the associated integer type, wrapped in `Signed`.
fn signed_from_scalar(value: &Scalar) -> Signed<<Self::Paillier as PaillierParams>::Uint> {
Self::bounded_from_scalar(value).into_signed().unwrap()
}

/// Converts an integer to the associated curve scalar type.
fn scalar_from_uint(value: &<Self::Paillier as PaillierParams>::Uint) -> Scalar {
let r = *value % Self::CURVE_ORDER;

let repr = r.to_be_bytes();
let uint_len = repr.as_ref().len();
let scalar_len = Scalar::repr_len();

// Can unwrap here since the value is within the Scalar range
Scalar::try_from_bytes(&repr.as_ref()[uint_len - scalar_len..]).unwrap()
}

/// Converts a `Signed`-wrapped integer to the associated curve scalar type.
fn scalar_from_signed(value: &Signed<<Self::Paillier as PaillierParams>::Uint>) -> Scalar {
let abs_value = Self::scalar_from_uint(&value.abs());
Scalar::conditional_select(&abs_value, &-abs_value, value.is_negative())
}

/// Converts a wide integer to the associated curve scalar type.
fn scalar_from_wide_uint(value: &<Self::Paillier as PaillierParams>::WideUint) -> Scalar {
let r = *value % Self::CURVE_ORDER_WIDE;

let repr = r.to_be_bytes();
let uint_len = repr.as_ref().len();
let scalar_len = Scalar::repr_len();

// Can unwrap here since the value is within the Scalar range
Scalar::try_from_bytes(&repr.as_ref()[uint_len - scalar_len..]).unwrap()
}

/// Converts a `Signed`-wrapped wide integer to the associated curve scalar type.
fn scalar_from_wide_signed(
value: &Signed<<Self::Paillier as PaillierParams>::WideUint>,
) -> Scalar {
let abs_value = Self::scalar_from_wide_uint(&value.abs());
Scalar::conditional_select(&abs_value, &-abs_value, value.is_negative())
}
}

/// Scheme parameters **for testing purposes only**.
Expand All @@ -100,7 +176,10 @@ impl SchemeParams for TestParams {
const LP_BOUND: usize = 256;
const EPS_BOUND: usize = 320;
type Paillier = PaillierTest;
const CURVE_ORDER: <Self::Paillier as PaillierParams>::Uint = upcast_uint(ORDER);
const CURVE_ORDER: NonZero<<Self::Paillier as PaillierParams>::Uint> =
NonZero::<<Self::Paillier as PaillierParams>::Uint>::const_new(upcast_uint(ORDER)).0;
const CURVE_ORDER_WIDE: NonZero<<Self::Paillier as PaillierParams>::WideUint> =
NonZero::<<Self::Paillier as PaillierParams>::WideUint>::const_new(upcast_uint(ORDER)).0;
}

/// Production strength parameters.
Expand All @@ -113,5 +192,8 @@ impl SchemeParams for ProductionParams {
const LP_BOUND: usize = Self::L_BOUND * 5;
const EPS_BOUND: usize = Self::L_BOUND * 2;
type Paillier = PaillierProduction;
const CURVE_ORDER: <Self::Paillier as PaillierParams>::Uint = upcast_uint(ORDER);
const CURVE_ORDER: NonZero<<Self::Paillier as PaillierParams>::Uint> =
NonZero::<<Self::Paillier as PaillierParams>::Uint>::const_new(upcast_uint(ORDER)).0;
const CURVE_ORDER_WIDE: NonZero<<Self::Paillier as PaillierParams>::WideUint> =
NonZero::<<Self::Paillier as PaillierParams>::WideUint>::const_new(upcast_uint(ORDER)).0;
}
64 changes: 64 additions & 0 deletions synedrion/src/cggmp21/protocols/interactive_signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,3 +259,67 @@ impl<P: SchemeParams> FinalizableToResult for Round4<P> {
.map_err(wrap_finalize_error)
}
}

#[cfg(test)]
mod tests {
use k256::ecdsa::{signature::hazmat::PrehashVerifier, VerifyingKey};
use rand_core::{OsRng, RngCore};

use super::{Context, Round1};
use crate::cggmp21::TestParams;
use crate::common::KeyShare;
use crate::curve::Scalar;
use crate::rounds::{
test_utils::{step_next_round, step_result, step_round},
FirstRound, PartyIdx,
};

#[test]
fn execute_interactive_signing() {
let mut shared_randomness = [0u8; 32];
OsRng.fill_bytes(&mut shared_randomness);

let message = Scalar::random(&mut OsRng);

let num_parties = 3;
let key_shares = KeyShare::new_centralized(&mut OsRng, num_parties, None);
let r1 = (0..num_parties)
.map(|idx| {
Round1::<TestParams>::new(
&mut OsRng,
&shared_randomness,
num_parties,
PartyIdx::from_usize(idx),
Context {
message,
key_share: key_shares[idx].clone(),
},
)
.unwrap()
})
.collect();

let r1a = step_round(&mut OsRng, r1).unwrap();
let r2 = step_next_round(&mut OsRng, r1a).unwrap();
let r2a = step_round(&mut OsRng, r2).unwrap();
let r3 = step_next_round(&mut OsRng, r2a).unwrap();
let r3a = step_round(&mut OsRng, r3).unwrap();
let r4 = step_next_round(&mut OsRng, r3a).unwrap();
let r4a = step_round(&mut OsRng, r4).unwrap();
let signatures = step_result(&mut OsRng, r4a).unwrap();

for signature in signatures {
let (sig, rec_id) = signature.to_backend();

let vkey = key_shares[0].verifying_key();

// Check that the signature can be verified
vkey.verify_prehash(&message.to_bytes(), &sig).unwrap();

// Check that the key can be recovered
let recovered_key =
VerifyingKey::recover_from_prehash(&message.to_bytes(), &sig, rec_id).unwrap();
assert_eq!(recovered_key, vkey);
}
}
}
21 changes: 6 additions & 15 deletions synedrion/src/cggmp21/protocols/key_refresh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ use crate::cggmp21::{
use crate::common::{KeyShareChange, PublicAuxInfo, SecretAuxInfo};
use crate::curve::{Point, Scalar};
use crate::paillier::{
Ciphertext, PaillierParams, PublicKeyPaillier, PublicKeyPaillierPrecomputed, RPParams,
RPParamsMod, RPSecret, Randomizer, SecretKeyPaillier, SecretKeyPaillierPrecomputed,
Ciphertext, PublicKeyPaillier, PublicKeyPaillierPrecomputed, RPParams, RPParamsMod, RPSecret,
Randomizer, SecretKeyPaillier, SecretKeyPaillierPrecomputed,
};
use crate::rounds::{
all_parties_except, try_to_holevec, BaseRound, BroadcastRound, DirectRound, Finalizable,
Expand All @@ -30,13 +30,7 @@ use crate::tools::collections::HoleVec;
use crate::tools::hashing::{Chain, Hash, HashOutput, Hashable};
use crate::tools::random::random_bits;
use crate::tools::serde_bytes;
use crate::uint::{FromScalar, UintLike};

fn uint_from_scalar<P: SchemeParams>(
x: &Scalar,
) -> <<P as SchemeParams>::Paillier as PaillierParams>::Uint {
<<P as SchemeParams>::Paillier as PaillierParams>::Uint::from_scalar(x)
}
use crate::uint::UintLike;

/// Possible results of the KeyRefresh protocol.
#[derive(Debug, Clone, Copy)]
Expand Down Expand Up @@ -528,7 +522,7 @@ impl<P: SchemeParams> DirectRound for Round3<P> {

let x_secret = self.context.xs_secret[idx];
let x_public = self.context.data_precomp.data.xs_public[idx];
let ciphertext = Ciphertext::new(rng, &data.paillier_pk, &uint_from_scalar::<P>(&x_secret));
let ciphertext = Ciphertext::new(rng, &data.paillier_pk, &P::uint_from_scalar(&x_secret));

let sch_proof_x = SchProof::new(
&self.context.sch_secrets_x[idx],
Expand Down Expand Up @@ -556,11 +550,8 @@ impl<P: SchemeParams> DirectRound for Round3<P> {
) -> Result<Self::Payload, ReceiveError<Self::Result>> {
let sender_data = &self.datas.get(from.as_usize()).unwrap();

let x_secret = msg
.data2
.paillier_enc_x
.decrypt(&self.context.paillier_sk)
.to_scalar();
let x_secret =
P::scalar_from_uint(&msg.data2.paillier_enc_x.decrypt(&self.context.paillier_sk));

if x_secret.mul_by_generator()
!= sender_data.data.xs_public[self.context.party_idx.as_usize()]
Expand Down
Loading

0 comments on commit afe35e9

Please sign in to comment.