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

Add low degree exponent interpolation proof #119

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
28 changes: 20 additions & 8 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
language: rust
cache: cargo
rust:
- stable
rust: stable
virt: lxd

before_script:
- rustup component add rustfmt-preview clippy
- cargo fmt --all -- --check
- cargo clippy -- -D clippy::all
env:
- BIGINT_BACKEND=rust-gmp-kzen
- BIGINT_BACKEND=num-bigint

before_install:
- rustup component add rustfmt clippy

script:
- cargo build --verbose
- cargo test --verbose
- cargo build --verbose --no-default-features --features $BIGINT_BACKEND
- cargo test --verbose --no-default-features --features $BIGINT_BACKEND
- if [[ "$BIGINT_BACKEND" = "rust-gmp-kzen" ]]; then cargo fmt --all -- --check; fi
- if [[ "$BIGINT_BACKEND" = "rust-gmp-kzen" ]]; then cargo clippy -- -D clippy::all; fi

deploy:
provider: cargo
token:
secure: "FE6A1XRyJtTK92rV3y5e0go+FDKn1HpJbYkHOacDqabTkUluXrCTw3ERFcQQ13QZdc9xkxoAs7roKp8ivl0Xg1IJCzI+yLb0ZR6YcYebKEqd06YFbBmejjvMsyyZHKPTQmroe+tBwcA1IqLLcAY8vmY5EGhJTsGUhovIomw1RvqM6gu9yUwII/sF0a5gqY761cJd4QoLlWTb1Er7DqZxoU9drhWAJQP7sLsspjLu6dOyWzb0A2mTmnek+iuVnt9mGPtjGk4FcNPGbEmNu3UPOVuXcyibFPIALEWrH0ouZB7E9k312g45LucSeKSimgQYQBNAzdsnkKyBwyTpGuaosGnMbI7mhoi3visV21RTbw61N05dmZTYb4VAMcx+93TslKMDv5nmIlUmKxULNRBSTPPtrg0/X7KuKaoHVstrxx0ohd8GFwGYQBB64mQaOxFBhoy//prpHjhFl+1cti4JHyaHFSV/PfaryvUfRg4q2Dlq1HP+ey5cPRPbwfpSO1RmXlIDWe21ncRnKSpgMHTPBzYNtil+gZyzHl5X4ZLvLCaHsZwZQPMFB+otlabFaS1caqkk1F1fHMrj8NMak/snb2IyUJqXgQivqzEn38G3k9/QHeQXhNVwyGDtdWV51P9XfXFpxrEuuWlXF56ABiWcF7bY7Y3DeCbnFNLkVkGZYvY="
on:
tags: true
condition: '"$TRAVIS_TAG" =~ ^v[0-9.]+$ && "$BIGINT_BACKEND" = "rust-gmp-kzen"'
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "curv"
version = "0.7.0"
version = "0.7.1"
edition = "2018"
authors = ["Omer Shlomovits"]
license = "MIT"
Expand Down Expand Up @@ -33,6 +33,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_derive = "1.0"
sha2 = "0.8.0"
sha3 = "0.8.2"
thiserror = "1"
zeroize = "1"

rust-gmp-kzen = { version = "0.5", features = ["serde_support"], optional = true }
Expand Down
6 changes: 6 additions & 0 deletions src/arithmetic/big_gmp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,12 @@ impl ring_algorithm::RingNormalize for BigInt {

crate::__bigint_impl_from! { u32, i32, u64 }

impl From<u16> for BigInt {
fn from(n: u16) -> Self {
BigInt::from(u64::from(n))
}
}

/// Internal helper trait. Creates short-hand for wrapping Mpz into BigInt.
trait Wrap {
fn wrap(self) -> BigInt;
Expand Down
6 changes: 6 additions & 0 deletions src/arithmetic/big_native.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ impl num_traits::Num for BigInt {

crate::__bigint_impl_from! { u32, i32, u64 }

impl From<u16> for BigInt {
fn from(n: u16) -> Self {
BigInt::from(u64::from(n))
}
}

impl BasicOps for BigInt {
fn pow(&self, exponent: u32) -> Self {
self.num.pow(exponent).wrap()
Expand Down
2 changes: 1 addition & 1 deletion src/arithmetic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ mod test {
// Conversion traits
for<'a> u64: std::convert::TryFrom<&'a BigInt>,
for<'a> i64: std::convert::TryFrom<&'a BigInt>,
BigInt: From<u32> + From<i32> + From<u64>,
BigInt: From<u16> + From<u32> + From<i32> + From<u64>,
// STD Operators
BigInt: Add<Output = BigInt>
+ Sub<Output = BigInt>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
use std::fmt;

use derivative::Derivative;
use thiserror::Error;

use crate::cryptographic_primitives::hashing::traits::Hash;
use crate::cryptographic_primitives::proofs::ProofError;
use crate::cryptographic_primitives::secret_sharing::Polynomial;
use crate::elliptic::curves::traits::ECPoint;

/// The prover private polynomial
#[derive(Derivative)]
#[derivative(Clone(bound = "P::Scalar: Clone"))]
#[derivative(Debug(bound = "P::Scalar: fmt::Debug"))]
pub struct LdeiWitness<P: ECPoint> {
pub w: Polynomial<P>,
}

/// Claims that there's polynomial `w(x)` of degree `deg(w) <= degree`, and
/// `forall i. x[i] = g[i] * alpha[i]` (and the prover knows `w(x)`)
#[derive(Derivative)]
#[derivative(Clone(bound = "P: Clone, P::Scalar: Clone"))]
#[derivative(Debug(bound = "P: fmt::Debug, P::Scalar: fmt::Debug"))]
pub struct LdeiStatement<P: ECPoint> {
pub alpha: Vec<P::Scalar>,
pub g: Vec<P>,
pub x: Vec<P>,
pub d: u16,
}

impl<P> LdeiStatement<P>
where
P: ECPoint + Clone,
P::Scalar: Clone + PartialEq,
{
/// Takes [witness](LdeiWitness) (ie. secret polynomial `w(x)`), list of scalars `alpha`,
/// list of generators `g`, number `d`. Produces LdeiStatement consisting of `alpha`, `g`, `d`,
/// and list `x` such as `x_i = g_i * w(alpha_i)`
pub fn new(
witness: &LdeiWitness<P>,
alpha: Vec<P::Scalar>,
g: Vec<P>,
d: u16,
) -> Result<Self, InvalidLdeiStatement> {
if g.len() != alpha.len() {
return Err(InvalidLdeiStatement::AlphaLengthDoesntMatchG);
}
if witness.w.degree() > d {
return Err(InvalidLdeiStatement::PolynomialDegreeMoreThanD);
}
if !ensure_list_is_pairwise_distinct(&alpha) {
return Err(InvalidLdeiStatement::AlphaNotPairwiseDistinct);
}
Ok(Self {
x: g.iter()
.zip(&alpha)
.map(|(g, a)| g.clone() * witness.w.evaluate(a))
.collect(),
alpha,
g,
d,
})
}
}

#[derive(Derivative)]
#[derivative(Clone(bound = "P: Clone, P::Scalar: Clone"))]
#[derivative(Debug(bound = "P: fmt::Debug, P::Scalar: fmt::Debug"))]
pub struct LdeiProof<P: ECPoint> {
pub a: Vec<P>,
pub e: P::Scalar,
pub z: Polynomial<P>,
}

impl<P> LdeiProof<P>
where
P: ECPoint + Clone + PartialEq,
P::Scalar: Clone + PartialEq,
{
/// Constructs [LdeiStatement] and proves it correctness
///
/// ## Protocol
///
/// The prover samples `u(X) ← Z_q[X]` with `deg(u) ≤ d` and computes `a_i = g_i^u(alpha_i)`
/// for all `i ∈ [m]`, in addition to `e = H(g_1,...,g_m,x_1,...,x_m,a_1,...,a_m)`, and
/// `z(X) = u(X) − e · w(X)`. The proof is `(a_1,...,a_m,e,z)`.
#[allow(clippy::many_single_char_names)]
pub fn prove<H>(
witness: &LdeiWitness<P>,
statement: &LdeiStatement<P>,
) -> Result<LdeiProof<P>, InvalidLdeiStatement>
where
H: Hash,
{
if statement.alpha.len() != statement.g.len() {
return Err(InvalidLdeiStatement::AlphaLengthDoesntMatchG);
}
if witness.w.degree() > statement.d {
return Err(InvalidLdeiStatement::PolynomialDegreeMoreThanD);
}
if !ensure_list_is_pairwise_distinct(&statement.alpha) {
return Err(InvalidLdeiStatement::AlphaNotPairwiseDistinct);
}

let x_expected: Vec<P> = statement
.g
.iter()
.zip(&statement.alpha)
.map(|(g, a)| g.clone() * witness.w.evaluate(a))
.collect();
if statement.x != x_expected {
return Err(InvalidLdeiStatement::ListOfXDoesntMatchExpectedValue);
}

let u = Polynomial::<P>::sample(statement.d);
let a: Vec<P> = statement
.g
.iter()
.zip(&statement.alpha)
.map(|(g, a)| g.clone() * u.evaluate(a))
.collect();

let hash_input: Vec<&P> = statement.g.iter().chain(&statement.x).chain(&a).collect();
let e = H::create_hash_from_ge::<P>(hash_input.as_slice());

let z = &u - &(&witness.w * &e);

Ok(LdeiProof { a, e, z })
}

/// Verifies correctness of a statement
///
/// ## Protocol
///
/// The verifier checks that `e = H(g1,...,gm,x1,...,xm,a1,...,am)`, that
/// `deg(z) ≤ d`, and that `a_i = g_i^z(αlpha_i) * x_i^e` for all i, and accepts if all of this is
/// true, otherwise rejects.
pub fn verify<H>(&self, statement: &LdeiStatement<P>) -> Result<(), ProofError>
where
H: Hash,
{
let hash_input: Vec<&P> = statement
.g
.iter()
.chain(&statement.x)
.chain(&self.a)
.collect();
let e = H::create_hash_from_ge::<P>(hash_input.as_slice());
if e != self.e {
return Err(ProofError);
}
if self.z.degree() > statement.d {
return Err(ProofError);
}

let expected_a: Vec<_> = statement
.g
.iter()
.zip(&statement.alpha)
.zip(&statement.x)
.map(|((g, a), x)| g.clone() * self.z.evaluate(&a) + x.clone() * e.clone())
.collect();

if self.a == expected_a {
Ok(())
} else {
Err(ProofError)
}
}
}

/// Indicates that statement is not valid or doesn't match a witness
#[derive(Debug, Error)]
pub enum InvalidLdeiStatement {
#[error("`alpha`s are not pairwise distinct")]
AlphaNotPairwiseDistinct,
#[error("alpha.len() != g.len()")]
AlphaLengthDoesntMatchG,
#[error("deg(w) > d")]
PolynomialDegreeMoreThanD,
#[error("`statement.x` doesn't match expected value")]
ListOfXDoesntMatchExpectedValue,
}

fn ensure_list_is_pairwise_distinct<S: PartialEq>(list: &[S]) -> bool {
for (i, x1) in list.iter().enumerate() {
for (j, x2) in list.iter().enumerate() {
if i != j && x1 == x2 {
return false;
}
}
}
true
}

#[cfg(test)]
mod tests {
use std::iter;

use crate::arithmetic::BigInt;
use crate::cryptographic_primitives::hashing::hash_sha256::HSha256;
use crate::elliptic::curves::traits::ECScalar;
use crate::test_for_all_curves;

use super::*;

test_for_all_curves!(correctly_proofs);
fn correctly_proofs<P>()
where
P: ECPoint + Clone + PartialEq,
P::Scalar: ECScalar + Clone + PartialEq,
{
let d = 5;
let poly = Polynomial::<P>::sample_exact(5);
let witness = LdeiWitness { w: poly };

let alpha: Vec<P::Scalar> = (1..=10).map(|i| ECScalar::from(&BigInt::from(i))).collect();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you sure the generators are not supposed to be picked at random such that there is no way to compute x such that g_i = x g_j ?
for example see how we generate the generator vector in bulletproofs

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The line you commented doesn't choose generators — it chooses list of α (a list of pairwise distinct scalar from Setup phase of the proof). Line below chooses generators completely at random. I agree that correlation like g_i = x g_j could potentially lead to vulnerabilities

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no idea why I picked that line :)
so will you change it ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I found what code you're referring to — generate_random_point. Are you ok with me moving this function to curv::elliptic::curves:: generate_random_point ?

let g: Vec<P> = iter::repeat_with(ECScalar::new_random)
.map(|x| P::generator() * x)
.take(10)
.collect();

let statement = LdeiStatement::new(&witness, alpha, g, d).unwrap();

let proof = LdeiProof::prove::<HSha256>(&witness, &statement).expect("failed to prove");
proof
.verify::<HSha256>(&statement)
.expect("failed to validate proof");
}
}
1 change: 1 addition & 0 deletions src/cryptographic_primitives/proofs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use std::error::Error;
use std::fmt;

pub mod low_degree_exponent_interpolation;
pub mod sigma_correct_homomorphic_elgamal_enc;
pub mod sigma_correct_homomorphic_elgamal_encryption_of_dlog;
pub mod sigma_dlog;
Expand Down
Loading