Skip to content
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

ZK proof cleanup #99

Merged
merged 22 commits into from
Jan 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
310552b
Include challenges in all ZK proofs, for uniformity
fjarri Jan 17, 2024
5f6d321
Make method visibility uniform
fjarri Jan 17, 2024
5d39f7f
Adjust parameter names
fjarri Jan 17, 2024
ba2be99
Decouple curve scalars from big integers
fjarri Jan 18, 2024
f1889f3
Add more value-ref combinations for arithmetic operators
fjarri Jan 18, 2024
3abfbe9
Fix a bug in generating a mul proof in Presigning
fjarri Jan 18, 2024
6944b61
Add an interactive signing test
fjarri Jan 18, 2024
3da747f
Fix some bugs in the additional info saved in `Presigning`
fjarri Jan 19, 2024
c8730fe
Cache public data to create the challenge for the aff-g proof
fjarri Jan 19, 2024
a9a8974
Bump the prime bitsize in test parameters for the ZK proofs to work
fjarri Jan 19, 2024
c587c4b
Fix a too lenient test of P_dec, and fix the integer sizes in the proof
fjarri Jan 19, 2024
791bafd
Propagate all necessary information for P_dec proof in Signing
fjarri Jan 20, 2024
db0aef9
Cache public data to create the challenge for the dec proof
fjarri Jan 20, 2024
d23ae5f
Cache public data to create the challenge for the enc proof
fjarri Jan 20, 2024
d3254a3
Cache public data to create the challenge for the log* proof
fjarri Jan 20, 2024
18f329c
Cache public data to create the challenge for the mod proof
fjarri Jan 20, 2024
8a66854
Cache public data to create the challenge for the mul proof
fjarri Jan 20, 2024
d9bf733
Cache public data to create the challenge for the mul* proof
fjarri Jan 20, 2024
1c6f019
Cache public data to create the challenge for the prm proof
fjarri Jan 20, 2024
d02d1fe
Adjust parameter order in sch proof
fjarri Jan 20, 2024
fb7a234
Expand ZK proof docstrings and bring some variable names in sync with…
fjarri Jan 20, 2024
a851fff
Adjust derived traits for proof objects
fjarri Jan 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
// 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 @@
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);

Check warning on line 105 in synedrion/src/cggmp21/params.rs

View check run for this annotation

Codecov / codecov/patch

synedrion/src/cggmp21/params.rs#L105

Added line #L105 was not covered by tests
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 @@
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 @@
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
Loading