One Round DKG (#589)

* Upstream GBP, divisor, circuit abstraction, and EC gadgets from FCMP++

* Initial eVRF implementation

Not quite done yet. It needs to communicate the resulting points and proofs to
extract them from the Pedersen Commitments in order to return those, and then
be tested.

* Add the openings of the PCs to the eVRF as necessary

* Add implementation of secq256k1

* Make DKG Encryption a bit more flexible

No longer requires the use of an EncryptionKeyMessage, and allows pre-defined
keys for encryption.

* Make NUM_BITS an argument for the field macro

* Have the eVRF take a Zeroizing private key

* Initial eVRF-based DKG

* Add embedwards25519 curve

* Inline the eVRF into the DKG library

Due to how we're handling share encryption, we'd either need two circuits or to
dedicate this circuit to the DKG. The latter makes sense at this time.

* Add documentation to the eVRF-based DKG

* Add paragraph claiming robustness

* Update to the new eVRF proof

* Finish routing the eVRF functionality

Still needs errors and serialization, along with a few other TODOs.

* Add initial eVRF DKG test

* Improve eVRF DKG

Updates how we calculcate verification shares, improves performance when
extracting multiple sets of keys, and adds more to the test for it.

* Start using a proper error for the eVRF DKG

* Resolve various TODOs

Supports recovering multiple key shares from the eVRF DKG.

Inlines two loops to save 2**16 iterations.

Adds support for creating a constant time representation of scalars < NUM_BITS.

* Ban zero ECDH keys, document non-zero requirements

* Implement eVRF traits, all the way up to the DKG, for secp256k1/ed25519

* Add Ristretto eVRF trait impls

* Support participating multiple times in the eVRF DKG

* Only participate once per key, not once per key share

* Rewrite processor key-gen around the eVRF DKG

Still a WIP.

* Finish routing the new key gen in the processor

Doesn't touch the tests, coordinator, nor Substrate yet.
`cargo +nightly fmt && cargo +nightly-2024-07-01 clippy --all-features -p serai-processor`
does pass.

* Deduplicate and better document in processor key_gen

* Update serai-processor tests to the new key gen

* Correct amount of yx coefficients, get processor key gen test to pass

* Add embedded elliptic curve keys to Substrate

* Update processor key gen tests to the eVRF DKG

* Have set_keys take signature_participants, not removed_participants

Now no one is removed from the DKG. Only `t` people publish the key however.

Uses a BitVec for an efficient encoding of the participants.

* Update the coordinator binary for the new DKG

This does not yet update any tests.

* Add sensible Debug to key_gen::[Processor, Coordinator]Message

* Have the DKG explicitly declare how to interpolate its shares

Removes the hack for MuSig where we multiply keys by the inverse of their
lagrange interpolation factor.

* Replace Interpolation::None with Interpolation::Constant

Allows the MuSig DKG to keep the secret share as the original private key,
enabling deriving FROST nonces consistently regardless of the MuSig context.

* Get coordinator tests to pass

* Update spec to the new DKG

* Get clippy to pass across the repo

* cargo machete

* Add an extra sleep to ensure expected ordering of `Participation`s

* Update orchestration

* Remove bad panic in coordinator

It expected ConfirmationShare to be n-of-n, not t-of-n.

* Improve documentation on  functions

* Update TX size limit

We now no longer have to support the ridiculous case of having 49 DKG
participations within a 101-of-150 DKG. It does remain quite high due to
needing to _sign_ so many times. It'd may be optimal for parties with multiple
key shares to independently send their preprocesses/shares (despite the
overhead that'll cause with signatures and the transaction structure).

* Correct error in the Processor spec document

* Update a few comments in the validator-sets pallet

* Send/Recv Participation one at a time

Sending all, then attempting to receive all in an expected order, wasn't working
even with notable delays between sending messages. This points to the mempool
not working as expected...

* Correct ThresholdKeys serialization in modular-frost test

* Updating existing TX size limit test for the new DKG parameters

* Increase time allowed for the DKG on the GH CI

* Correct construction of signature_participants in serai-client tests

Fault identified by akil.

* Further contextualize DkgConfirmer by ValidatorSet

Caught by a safety check we wouldn't reuse preprocesses across messages. That
raises the question of we were prior reusing preprocesses (reusing keys)?
Except that'd have caused a variety of signing failures (suggesting we had some
staggered timing avoiding it in practice but yes, this was possible in theory).

* Add necessary calls to set_embedded_elliptic_curve_key in coordinator set rotation tests

* Correct shimmed setting of a secq256k1 key

* cargo fmt

* Don't use `[0; 32]` for the embedded keys in the coordinator rotation test

The key_gen function expects the random values already decided.

* Big-endian secq256k1 scalars

Also restores the prior, safer, Encryption::register function.
This commit is contained in:
Luke Parker
2024-08-16 11:26:07 -07:00
parent 669b2fef72
commit e4e4245ee3
121 changed files with 10388 additions and 2480 deletions

View File

@@ -0,0 +1,20 @@
[package]
name = "generalized-bulletproofs-ec-gadgets"
version = "0.1.0"
description = "Gadgets for working with an embedded Elliptic Curve in a Generalized Bulletproofs circuit"
license = "MIT"
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/evrf/ec-gadgets"
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = ["bulletproofs", "circuit", "divisors"]
edition = "2021"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
generic-array = { version = "1", default-features = false, features = ["alloc"] }
ciphersuite = { path = "../../ciphersuite", version = "0.4", default-features = false, features = ["std"] }
generalized-bulletproofs-circuit-abstraction = { path = "../circuit-abstraction" }

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Luke Parker
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,3 @@
# Generalized Bulletproofs Circuit Abstraction
A circuit abstraction around `generalized-bulletproofs`.

View File

@@ -0,0 +1,529 @@
use core::fmt;
use ciphersuite::{
group::ff::{Field, PrimeField, BatchInverter},
Ciphersuite,
};
use generalized_bulletproofs_circuit_abstraction::*;
use crate::*;
/// Parameters for a discrete logarithm proof.
///
/// This isn't required to be implemented by the Field/Group/Ciphersuite, solely a struct, to
/// enable parameterization of discrete log proofs to the bitlength of the discrete logarithm.
/// While that may be F::NUM_BITS, a discrete log proof a for a full scalar, it could also be 64,
/// a discrete log proof for a u64 (such as if opening a Pedersen commitment in-circuit).
pub trait DiscreteLogParameters {
/// The amount of bits used to represent a scalar.
type ScalarBits: ArrayLength;
/// The amount of x**i coefficients in a divisor.
///
/// This is the amount of points in a divisor (the amount of bits in a scalar, plus one) divided
/// by two.
type XCoefficients: ArrayLength;
/// The amount of x**i coefficients in a divisor, minus one.
type XCoefficientsMinusOne: ArrayLength;
/// The amount of y x**i coefficients in a divisor.
///
/// This is the amount of points in a divisor (the amount of bits in a scalar, plus one) plus
/// one, divided by two, minus two.
type YxCoefficients: ArrayLength;
}
/// A tabled generator for proving/verifying discrete logarithm claims.
#[derive(Clone)]
pub struct GeneratorTable<F: PrimeField, Parameters: DiscreteLogParameters>(
GenericArray<(F, F), Parameters::ScalarBits>,
);
impl<F: PrimeField, Parameters: DiscreteLogParameters> fmt::Debug
for GeneratorTable<F, Parameters>
{
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt
.debug_struct("GeneratorTable")
.field("x", &self.0[0].0)
.field("y", &self.0[0].1)
.finish_non_exhaustive()
}
}
impl<F: PrimeField, Parameters: DiscreteLogParameters> GeneratorTable<F, Parameters> {
/// Create a new table for this generator.
///
/// The generator is assumed to be well-formed and on-curve. This function may panic if it's not.
pub fn new(curve: &CurveSpec<F>, generator_x: F, generator_y: F) -> Self {
// mdbl-2007-bl
fn dbl<F: PrimeField>(a: F, x1: F, y1: F) -> (F, F) {
let xx = x1 * x1;
let w = a + (xx + xx.double());
let y1y1 = y1 * y1;
let r = y1y1 + y1y1;
let sss = (y1 * r).double().double();
let rr = r * r;
let b = x1 + r;
let b = (b * b) - xx - rr;
let h = (w * w) - b.double();
let x3 = h.double() * y1;
let y3 = (w * (b - h)) - rr.double();
let z3 = sss;
// Normalize from XYZ to XY
let z3_inv = z3.invert().unwrap();
let x3 = x3 * z3_inv;
let y3 = y3 * z3_inv;
(x3, y3)
}
let mut res = Self(GenericArray::default());
res.0[0] = (generator_x, generator_y);
for i in 1 .. Parameters::ScalarBits::USIZE {
let last = res.0[i - 1];
res.0[i] = dbl(curve.a, last.0, last.1);
}
res
}
}
/// A representation of the divisor.
///
/// The coefficient for x**1 is explicitly excluded as it's expected to be normalized to 1.
#[derive(Clone)]
pub struct Divisor<Parameters: DiscreteLogParameters> {
/// The coefficient for the `y` term of the divisor.
///
/// There is never more than one `y**i x**0` coefficient as the leading term of the modulus is
/// `y**2`. It's assumed the coefficient is non-zero (and present) as it will be for any divisor
/// exceeding trivial complexity.
pub y: Variable,
/// The coefficients for the `y**1 x**i` terms of the polynomial.
// This subtraction enforces the divisor to have at least 4 points which is acceptable.
// TODO: Double check these constants
pub yx: GenericArray<Variable, Parameters::YxCoefficients>,
/// The coefficients for the `x**i` terms of the polynomial, skipping x**1.
///
/// x**1 is skipped as it's expected to be normalized to 1, and therefore constant, in order to
/// ensure the divisor is non-zero (as necessary for the proof to be complete).
// Subtract 1 from the length due to skipping the coefficient for x**1
pub x_from_power_of_2: GenericArray<Variable, Parameters::XCoefficientsMinusOne>,
/// The constant term in the polynomial (alternatively, the coefficient for y**0 x**0).
pub zero: Variable,
}
/// A point, its discrete logarithm, and the divisor to prove it.
#[derive(Clone)]
pub struct PointWithDlog<Parameters: DiscreteLogParameters> {
/// The point which is supposedly the result of scaling the generator by the discrete logarithm.
pub point: (Variable, Variable),
/// The discrete logarithm, represented as coefficients of a polynomial of 2**i.
pub dlog: GenericArray<Variable, Parameters::ScalarBits>,
/// The divisor interpolating the relevant doublings of generator with the inverse of the point.
pub divisor: Divisor<Parameters>,
}
/// A struct containing a point used for the evaluation of a divisor.
///
/// Preprocesses and caches as much of the calculation as possible to minimize work upon reuse of
/// challenge points.
struct ChallengePoint<F: PrimeField, Parameters: DiscreteLogParameters> {
y: F,
yx: GenericArray<F, Parameters::YxCoefficients>,
x: GenericArray<F, Parameters::XCoefficients>,
p_0_n_0: F,
x_p_0_n_0: GenericArray<F, Parameters::YxCoefficients>,
p_1_n: F,
p_1_d: F,
}
impl<F: PrimeField, Parameters: DiscreteLogParameters> ChallengePoint<F, Parameters> {
fn new(
curve: &CurveSpec<F>,
// The slope between all of the challenge points
slope: F,
// The x and y coordinates
x: F,
y: F,
// The inversion of twice the y coordinate
// We accept this as an argument so that the caller can calculcate these with a batch inversion
inv_two_y: F,
) -> Self {
// Powers of x, skipping x**0
let divisor_x_len = Parameters::XCoefficients::USIZE;
let mut x_pows = GenericArray::default();
x_pows[0] = x;
for i in 1 .. divisor_x_len {
let last = x_pows[i - 1];
x_pows[i] = last * x;
}
// Powers of x multiplied by y
let divisor_yx_len = Parameters::YxCoefficients::USIZE;
let mut yx = GenericArray::default();
// Skips x**0
yx[0] = y * x;
for i in 1 .. divisor_yx_len {
let last = yx[i - 1];
yx[i] = last * x;
}
let x_sq = x.square();
let three_x_sq = x_sq.double() + x_sq;
let three_x_sq_plus_a = three_x_sq + curve.a;
let two_y = y.double();
// p_0_n_0 from `DivisorChallenge`
let p_0_n_0 = three_x_sq_plus_a * inv_two_y;
let mut x_p_0_n_0 = GenericArray::default();
// Since this iterates over x, which skips x**0, this also skips p_0_n_0 x**0
for (i, x) in x_pows.iter().take(divisor_yx_len).enumerate() {
x_p_0_n_0[i] = p_0_n_0 * x;
}
// p_1_n from `DivisorChallenge`
let p_1_n = two_y;
// p_1_d from `DivisorChallenge`
let p_1_d = (-slope * p_1_n) + three_x_sq_plus_a;
ChallengePoint { x: x_pows, y, yx, p_0_n_0, x_p_0_n_0, p_1_n, p_1_d }
}
}
// `DivisorChallenge` from the section `Discrete Log Proof`
fn divisor_challenge_eval<C: Ciphersuite, Parameters: DiscreteLogParameters>(
circuit: &mut Circuit<C>,
divisor: &Divisor<Parameters>,
challenge: &ChallengePoint<C::F, Parameters>,
) -> Variable {
// The evaluation of the divisor differentiated by y, further multiplied by p_0_n_0
// Differentation drops everything without a y coefficient, and drops what remains by a power
// of y
// (y**1 -> y**0, yx**i -> x**i)
// This aligns with p_0_n_1 from `DivisorChallenge`
let p_0_n_1 = {
let mut p_0_n_1 = LinComb::empty().term(challenge.p_0_n_0, divisor.y);
for (j, var) in divisor.yx.iter().enumerate() {
// This does not raise by `j + 1` as x_p_0_n_0 omits x**0
p_0_n_1 = p_0_n_1.term(challenge.x_p_0_n_0[j], *var);
}
p_0_n_1
};
// The evaluation of the divisor differentiated by x
// This aligns with p_0_n_2 from `DivisorChallenge`
let p_0_n_2 = {
// The coefficient for x**1 is 1, so 1 becomes the new zero coefficient
let mut p_0_n_2 = LinComb::empty().constant(C::F::ONE);
// Handle the new y coefficient
p_0_n_2 = p_0_n_2.term(challenge.y, divisor.yx[0]);
// Handle the new yx coefficients
for (j, yx) in divisor.yx.iter().enumerate().skip(1) {
// For the power which was shifted down, we multiply this coefficient
// 3 x**2 -> 2 * 3 x**1
let original_power_of_x = C::F::from(u64::try_from(j + 1).unwrap());
// `j - 1` so `j = 1` indexes yx[0] as yx[0] is the y x**1
// (yx omits y x**0)
let this_weight = original_power_of_x * challenge.yx[j - 1];
p_0_n_2 = p_0_n_2.term(this_weight, *yx);
}
// Handle the x coefficients
// We don't skip the first one as `x_from_power_of_2` already omits x**1
for (i, x) in divisor.x_from_power_of_2.iter().enumerate() {
// i + 2 as the paper expects i to start from 1 and be + 1, yet we start from 0
let original_power_of_x = C::F::from(u64::try_from(i + 2).unwrap());
// Still x[i] as x[0] is x**1
let this_weight = original_power_of_x * challenge.x[i];
p_0_n_2 = p_0_n_2.term(this_weight, *x);
}
p_0_n_2
};
// p_0_n from `DivisorChallenge`
let p_0_n = p_0_n_1 + &p_0_n_2;
// Evaluation of the divisor
// p_0_d from `DivisorChallenge`
let p_0_d = {
let mut p_0_d = LinComb::empty().term(challenge.y, divisor.y);
for (var, c_yx) in divisor.yx.iter().zip(&challenge.yx) {
p_0_d = p_0_d.term(*c_yx, *var);
}
for (i, var) in divisor.x_from_power_of_2.iter().enumerate() {
// This `i+1` is preserved, despite most not being as x omits x**0, as this assumes we
// start with `i=1`
p_0_d = p_0_d.term(challenge.x[i + 1], *var);
}
// Adding x effectively adds a `1 x` term, ensuring the divisor isn't 0
p_0_d.term(C::F::ONE, divisor.zero).constant(challenge.x[0])
};
// Calculate the joint numerator
// p_n from `DivisorChallenge`
let p_n = p_0_n * challenge.p_1_n;
// Calculate the joint denominator
// p_d from `DivisorChallenge`
let p_d = p_0_d * challenge.p_1_d;
// We want `n / d = o`
// `n / d = o` == `n = d * o`
// These are safe unwraps as they're solely done by the prover and should always be non-zero
let witness =
circuit.eval(&p_d).map(|p_d| (p_d, circuit.eval(&p_n).unwrap() * p_d.invert().unwrap()));
let (_l, o, n_claim) = circuit.mul(Some(p_d), None, witness);
circuit.equality(p_n, &n_claim.into());
o
}
/// A challenge to evaluate divisors with.
///
/// This challenge must be sampled after writing the commitments to the transcript. This challenge
/// is reusable across various divisors.
pub struct DiscreteLogChallenge<F: PrimeField, Parameters: DiscreteLogParameters> {
c0: ChallengePoint<F, Parameters>,
c1: ChallengePoint<F, Parameters>,
c2: ChallengePoint<F, Parameters>,
slope: F,
intercept: F,
}
/// A generator which has been challenged and is ready for use in evaluating discrete logarithm
/// claims.
pub struct ChallengedGenerator<F: PrimeField, Parameters: DiscreteLogParameters>(
GenericArray<F, Parameters::ScalarBits>,
);
/// Gadgets for proving the discrete logarithm of points on an elliptic curve defined over the
/// scalar field of the curve of the Bulletproof.
pub trait EcDlogGadgets<C: Ciphersuite> {
/// Sample a challenge for a series of discrete logarithm claims.
///
/// This must be called after writing the commitments to the transcript.
///
/// The generators are assumed to be non-empty. They are not transcripted. If your generators are
/// dynamic, they must be properly transcripted into the context.
///
/// May panic/have undefined behavior if an assumption is broken.
#[allow(clippy::type_complexity)]
fn discrete_log_challenge<T: Transcript, Parameters: DiscreteLogParameters>(
&self,
transcript: &mut T,
curve: &CurveSpec<C::F>,
generators: &[GeneratorTable<C::F, Parameters>],
) -> (DiscreteLogChallenge<C::F, Parameters>, Vec<ChallengedGenerator<C::F, Parameters>>);
/// Prove this point has the specified discrete logarithm over the specified generator.
///
/// The discrete logarithm is not validated to be in a canonical form. The only guarantee made on
/// it is that it's a consistent representation of _a_ discrete logarithm (reuse won't enable
/// re-interpretation as a distinct discrete logarithm).
///
/// This does ensure the point is on-curve.
///
/// This MUST only be called with `Variable`s present within commitments.
///
/// May panic/have undefined behavior if an assumption is broken, or if passed an invalid
/// witness.
fn discrete_log<Parameters: DiscreteLogParameters>(
&mut self,
curve: &CurveSpec<C::F>,
point: PointWithDlog<Parameters>,
challenge: &DiscreteLogChallenge<C::F, Parameters>,
challenged_generator: &ChallengedGenerator<C::F, Parameters>,
) -> OnCurve;
}
impl<C: Ciphersuite> EcDlogGadgets<C> for Circuit<C> {
// This is part of `DiscreteLog` from `Discrete Log Proof`, specifically, the challenges and
// the calculations dependent solely on them
fn discrete_log_challenge<T: Transcript, Parameters: DiscreteLogParameters>(
&self,
transcript: &mut T,
curve: &CurveSpec<C::F>,
generators: &[GeneratorTable<C::F, Parameters>],
) -> (DiscreteLogChallenge<C::F, Parameters>, Vec<ChallengedGenerator<C::F, Parameters>>) {
// Get the challenge points
// TODO: Implement a proper hash to curve
let (c0_x, c0_y) = loop {
let c0_x: C::F = transcript.challenge();
let Some(c0_y) =
Option::<C::F>::from(((c0_x.square() * c0_x) + (curve.a * c0_x) + curve.b).sqrt())
else {
continue;
};
// Takes the even y coordinate as to not be dependent on whatever root the above sqrt
// happens to returns
// TODO: Randomly select which to take
break (c0_x, if bool::from(c0_y.is_odd()) { -c0_y } else { c0_y });
};
let (c1_x, c1_y) = loop {
let c1_x: C::F = transcript.challenge();
let Some(c1_y) =
Option::<C::F>::from(((c1_x.square() * c1_x) + (curve.a * c1_x) + curve.b).sqrt())
else {
continue;
};
break (c1_x, if bool::from(c1_y.is_odd()) { -c1_y } else { c1_y });
};
// mmadd-1998-cmo
fn incomplete_add<F: PrimeField>(x1: F, y1: F, x2: F, y2: F) -> Option<(F, F)> {
if x1 == x2 {
None?
}
let u = y2 - y1;
let uu = u * u;
let v = x2 - x1;
let vv = v * v;
let vvv = v * vv;
let r = vv * x1;
let a = uu - vvv - r.double();
let x3 = v * a;
let y3 = (u * (r - a)) - (vvv * y1);
let z3 = vvv;
// Normalize from XYZ to XY
let z3_inv = Option::<F>::from(z3.invert())?;
let x3 = x3 * z3_inv;
let y3 = y3 * z3_inv;
Some((x3, y3))
}
let (c2_x, c2_y) = incomplete_add::<C::F>(c0_x, c0_y, c1_x, c1_y)
.expect("randomly selected points shared an x coordinate");
// We want C0, C1, C2 = -(C0 + C1)
let c2_y = -c2_y;
// Calculate the slope and intercept
// Safe invert as these x coordinates must be distinct due to passing the above incomplete_add
let slope = (c1_y - c0_y) * (c1_x - c0_x).invert().unwrap();
let intercept = c0_y - (slope * c0_x);
// Calculate the inversions for 2 c_y (for each c) and all of the challenged generators
let mut inversions = vec![C::F::ZERO; 3 + (generators.len() * Parameters::ScalarBits::USIZE)];
// Needed for the left-hand side eval
{
inversions[0] = c0_y.double();
inversions[1] = c1_y.double();
inversions[2] = c2_y.double();
}
// Perform the inversions for the generators
for (i, generator) in generators.iter().enumerate() {
// Needed for the right-hand side eval
for (j, generator) in generator.0.iter().enumerate() {
// `DiscreteLog` has weights of `(mu - (G_i.y + (slope * G_i.x)))**-1` in its last line
inversions[3 + (i * Parameters::ScalarBits::USIZE) + j] =
intercept - (generator.1 - (slope * generator.0));
}
}
for challenge_inversion in &inversions {
// This should be unreachable barring negligible probability
if challenge_inversion.is_zero().into() {
panic!("trying to invert 0");
}
}
let mut scratch = vec![C::F::ZERO; inversions.len()];
let _ = BatchInverter::invert_with_external_scratch(&mut inversions, &mut scratch);
let mut inversions = inversions.into_iter();
let inv_c0_two_y = inversions.next().unwrap();
let inv_c1_two_y = inversions.next().unwrap();
let inv_c2_two_y = inversions.next().unwrap();
let c0 = ChallengePoint::new(curve, slope, c0_x, c0_y, inv_c0_two_y);
let c1 = ChallengePoint::new(curve, slope, c1_x, c1_y, inv_c1_two_y);
let c2 = ChallengePoint::new(curve, slope, c2_x, c2_y, inv_c2_two_y);
// Fill in the inverted values
let mut challenged_generators = Vec::with_capacity(generators.len());
for _ in 0 .. generators.len() {
let mut challenged_generator = GenericArray::default();
for i in 0 .. Parameters::ScalarBits::USIZE {
challenged_generator[i] = inversions.next().unwrap();
}
challenged_generators.push(ChallengedGenerator(challenged_generator));
}
(DiscreteLogChallenge { c0, c1, c2, slope, intercept }, challenged_generators)
}
// `DiscreteLog` from `Discrete Log Proof`
fn discrete_log<Parameters: DiscreteLogParameters>(
&mut self,
curve: &CurveSpec<C::F>,
point: PointWithDlog<Parameters>,
challenge: &DiscreteLogChallenge<C::F, Parameters>,
challenged_generator: &ChallengedGenerator<C::F, Parameters>,
) -> OnCurve {
let PointWithDlog { divisor, dlog, point } = point;
// Ensure this is being safely called
let arg_iter = [point.0, point.1, divisor.y, divisor.zero];
let arg_iter = arg_iter.iter().chain(divisor.yx.iter());
let arg_iter = arg_iter.chain(divisor.x_from_power_of_2.iter());
let arg_iter = arg_iter.chain(dlog.iter());
for variable in arg_iter {
debug_assert!(
matches!(variable, Variable::CG { .. } | Variable::CH { .. } | Variable::V(_)),
"discrete log proofs requires all arguments belong to commitments",
);
}
// Check the point is on curve
let point = self.on_curve(curve, point);
// The challenge has already been sampled so those lines aren't necessary
// lhs from the paper, evaluating the divisor
let lhs_eval = LinComb::from(divisor_challenge_eval(self, &divisor, &challenge.c0)) +
&LinComb::from(divisor_challenge_eval(self, &divisor, &challenge.c1)) +
&LinComb::from(divisor_challenge_eval(self, &divisor, &challenge.c2));
// Interpolate the doublings of the generator
let mut rhs_eval = LinComb::empty();
// We call this `bit` yet it's not constrained to being a bit
// It's presumed to be yet may be malleated
for (bit, weight) in dlog.into_iter().zip(&challenged_generator.0) {
rhs_eval = rhs_eval.term(*weight, bit);
}
// Interpolate the output point
// intercept - (y - (slope * x))
// intercept - y + (slope * x)
// -y + (slope * x) + intercept
// EXCEPT the output point we're proving the discrete log for isn't the one interpolated
// Its negative is, so -y becomes y
// y + (slope * x) + intercept
let output_interpolation = LinComb::empty()
.constant(challenge.intercept)
.term(C::F::ONE, point.y)
.term(challenge.slope, point.x);
let output_interpolation_eval = self.eval(&output_interpolation);
let (_output_interpolation, inverse) =
self.inverse(Some(output_interpolation), output_interpolation_eval);
rhs_eval = rhs_eval.term(C::F::ONE, inverse);
self.equality(lhs_eval, &rhs_eval);
point
}
}

View File

@@ -0,0 +1,130 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
#![deny(missing_docs)]
#![allow(non_snake_case)]
use generic_array::{typenum::Unsigned, ArrayLength, GenericArray};
use ciphersuite::{group::ff::Field, Ciphersuite};
use generalized_bulletproofs_circuit_abstraction::*;
mod dlog;
pub use dlog::*;
/// The specification of a short Weierstrass curve over the field `F`.
///
/// The short Weierstrass curve is defined via the formula `y**2 = x**3 + a*x + b`.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct CurveSpec<F> {
/// The `a` constant in the curve formula.
pub a: F,
/// The `b` constant in the curve formula.
pub b: F,
}
/// A struct for a point on a towered curve which has been confirmed to be on-curve.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct OnCurve {
pub(crate) x: Variable,
pub(crate) y: Variable,
}
impl OnCurve {
/// The variable for the x-coordinate.
pub fn x(&self) -> Variable {
self.x
}
/// The variable for the y-coordinate.
pub fn y(&self) -> Variable {
self.y
}
}
/// Gadgets for working with points on an elliptic curve defined over the scalar field of the curve
/// of the Bulletproof.
pub trait EcGadgets<C: Ciphersuite> {
/// Constrain an x and y coordinate as being on the specified curve.
///
/// The specified curve is defined over the scalar field of the curve this proof is performed
/// over, offering efficient arithmetic.
///
/// May panic if the prover and the point is not actually on-curve.
fn on_curve(&mut self, curve: &CurveSpec<C::F>, point: (Variable, Variable)) -> OnCurve;
/// Perform incomplete addition for a fixed point and an on-curve point.
///
/// `a` is the x and y coordinates of the fixed point, assumed to be on-curve.
///
/// `b` is a point prior checked to be on-curve.
///
/// `c` is a point prior checked to be on-curve, constrained to be the sum of `a` and `b`.
///
/// `a` and `b` are checked to have distinct x coordinates.
///
/// This function may panic if `a` is malformed or if the prover and `c` is not actually the sum
/// of `a` and `b`.
fn incomplete_add_fixed(&mut self, a: (C::F, C::F), b: OnCurve, c: OnCurve) -> OnCurve;
}
impl<C: Ciphersuite> EcGadgets<C> for Circuit<C> {
fn on_curve(&mut self, curve: &CurveSpec<C::F>, (x, y): (Variable, Variable)) -> OnCurve {
let x_eval = self.eval(&LinComb::from(x));
let (_x, _x_2, x2) =
self.mul(Some(LinComb::from(x)), Some(LinComb::from(x)), x_eval.map(|x| (x, x)));
let (_x, _x_2, x3) =
self.mul(Some(LinComb::from(x2)), Some(LinComb::from(x)), x_eval.map(|x| (x * x, x)));
let expected_y2 = LinComb::from(x3).term(curve.a, x).constant(curve.b);
let y_eval = self.eval(&LinComb::from(y));
let (_y, _y_2, y2) =
self.mul(Some(LinComb::from(y)), Some(LinComb::from(y)), y_eval.map(|y| (y, y)));
self.equality(y2.into(), &expected_y2);
OnCurve { x, y }
}
fn incomplete_add_fixed(&mut self, a: (C::F, C::F), b: OnCurve, c: OnCurve) -> OnCurve {
// Check b.x != a.0
{
let bx_lincomb = LinComb::from(b.x);
let bx_eval = self.eval(&bx_lincomb);
self.inequality(bx_lincomb, &LinComb::empty().constant(a.0), bx_eval.map(|bx| (bx, a.0)));
}
let (x0, y0) = (a.0, a.1);
let (x1, y1) = (b.x, b.y);
let (x2, y2) = (c.x, c.y);
let slope_eval = self.eval(&LinComb::from(x1)).map(|x1| {
let y1 = self.eval(&LinComb::from(b.y)).unwrap();
(y1 - y0) * (x1 - x0).invert().unwrap()
});
// slope * (x1 - x0) = y1 - y0
let x1_minus_x0 = LinComb::from(x1).constant(-x0);
let x1_minus_x0_eval = self.eval(&x1_minus_x0);
let (slope, _r, o) =
self.mul(None, Some(x1_minus_x0), slope_eval.map(|slope| (slope, x1_minus_x0_eval.unwrap())));
self.equality(LinComb::from(o), &LinComb::from(y1).constant(-y0));
// slope * (x2 - x0) = -y2 - y0
let x2_minus_x0 = LinComb::from(x2).constant(-x0);
let x2_minus_x0_eval = self.eval(&x2_minus_x0);
let (_slope, _x2_minus_x0, o) = self.mul(
Some(slope.into()),
Some(x2_minus_x0),
slope_eval.map(|slope| (slope, x2_minus_x0_eval.unwrap())),
);
self.equality(o.into(), &LinComb::empty().term(-C::F::ONE, y2).constant(-y0));
// slope * slope = x0 + x1 + x2
let (_slope, _slope_2, o) =
self.mul(Some(slope.into()), Some(slope.into()), slope_eval.map(|slope| (slope, slope)));
self.equality(o.into(), &LinComb::from(x1).term(C::F::ONE, x2).constant(x0));
OnCurve { x: x2, y: y2 }
}
}