mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-12 14:09:25 +00:00
Fixes accumulated errors in the Substrate code. Modifies the runtime build to work with a modern clippy. Removes e2e tests from the workspace.
340 lines
10 KiB
Rust
340 lines
10 KiB
Rust
use std_shims::{sync::LazyLock, vec::Vec};
|
|
|
|
use rand_core::{RngCore, CryptoRng};
|
|
|
|
use zeroize::Zeroize;
|
|
|
|
use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, Scalar, EdwardsPoint};
|
|
|
|
use monero_generators::{H as MONERO_H, Generators, MAX_COMMITMENTS, COMMITMENT_BITS};
|
|
use monero_primitives::{Commitment, INV_EIGHT, keccak256_to_scalar};
|
|
use crate::{core::multiexp, scalar_vector::ScalarVector, BulletproofsBatchVerifier};
|
|
|
|
pub(crate) mod inner_product;
|
|
use inner_product::*;
|
|
pub(crate) use inner_product::IpProof;
|
|
|
|
include!(concat!(env!("OUT_DIR"), "/generators.rs"));
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub(crate) struct AggregateRangeStatement<'a> {
|
|
commitments: &'a [EdwardsPoint],
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub(crate) struct AggregateRangeWitness {
|
|
commitments: Vec<Commitment>,
|
|
}
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
|
|
pub struct AggregateRangeProof {
|
|
pub(crate) A: EdwardsPoint,
|
|
pub(crate) S: EdwardsPoint,
|
|
pub(crate) T1: EdwardsPoint,
|
|
pub(crate) T2: EdwardsPoint,
|
|
pub(crate) tau_x: Scalar,
|
|
pub(crate) mu: Scalar,
|
|
pub(crate) t_hat: Scalar,
|
|
pub(crate) ip: IpProof,
|
|
}
|
|
|
|
impl<'a> AggregateRangeStatement<'a> {
|
|
pub(crate) fn new(commitments: &'a [EdwardsPoint]) -> Option<Self> {
|
|
if commitments.is_empty() || (commitments.len() > MAX_COMMITMENTS) {
|
|
None?;
|
|
}
|
|
Some(Self { commitments })
|
|
}
|
|
}
|
|
|
|
impl AggregateRangeWitness {
|
|
pub(crate) fn new(commitments: Vec<Commitment>) -> Option<Self> {
|
|
if commitments.is_empty() || (commitments.len() > MAX_COMMITMENTS) {
|
|
None?;
|
|
}
|
|
Some(Self { commitments })
|
|
}
|
|
}
|
|
|
|
impl AggregateRangeStatement<'_> {
|
|
fn initial_transcript(&self) -> (Scalar, Vec<EdwardsPoint>) {
|
|
let V = self.commitments.iter().map(|c| c * INV_EIGHT()).collect::<Vec<_>>();
|
|
(keccak256_to_scalar(V.iter().flat_map(|V| V.compress().to_bytes()).collect::<Vec<_>>()), V)
|
|
}
|
|
|
|
fn transcript_A_S(transcript: Scalar, A: EdwardsPoint, S: EdwardsPoint) -> (Scalar, Scalar) {
|
|
let mut buf = Vec::with_capacity(96);
|
|
buf.extend(transcript.to_bytes());
|
|
buf.extend(A.compress().to_bytes());
|
|
buf.extend(S.compress().to_bytes());
|
|
let y = keccak256_to_scalar(buf);
|
|
let z = keccak256_to_scalar(y.to_bytes());
|
|
(y, z)
|
|
}
|
|
|
|
fn transcript_T12(transcript: Scalar, T1: EdwardsPoint, T2: EdwardsPoint) -> Scalar {
|
|
let mut buf = Vec::with_capacity(128);
|
|
buf.extend(transcript.to_bytes());
|
|
buf.extend(transcript.to_bytes());
|
|
buf.extend(T1.compress().to_bytes());
|
|
buf.extend(T2.compress().to_bytes());
|
|
keccak256_to_scalar(buf)
|
|
}
|
|
|
|
fn transcript_tau_x_mu_t_hat(
|
|
transcript: Scalar,
|
|
tau_x: Scalar,
|
|
mu: Scalar,
|
|
t_hat: Scalar,
|
|
) -> Scalar {
|
|
let mut buf = Vec::with_capacity(128);
|
|
buf.extend(transcript.to_bytes());
|
|
buf.extend(transcript.to_bytes());
|
|
buf.extend(tau_x.to_bytes());
|
|
buf.extend(mu.to_bytes());
|
|
buf.extend(t_hat.to_bytes());
|
|
keccak256_to_scalar(buf)
|
|
}
|
|
|
|
#[allow(clippy::needless_pass_by_value)]
|
|
pub(crate) fn prove(
|
|
self,
|
|
rng: &mut (impl RngCore + CryptoRng),
|
|
witness: AggregateRangeWitness,
|
|
) -> Option<AggregateRangeProof> {
|
|
if self.commitments != witness.commitments.iter().map(Commitment::calculate).collect::<Vec<_>>()
|
|
{
|
|
None?
|
|
};
|
|
|
|
let generators = &GENERATORS;
|
|
|
|
let (mut transcript, _) = self.initial_transcript();
|
|
|
|
// Find out the padded amount of commitments
|
|
let mut padded_pow_of_2 = 1;
|
|
while padded_pow_of_2 < witness.commitments.len() {
|
|
padded_pow_of_2 <<= 1;
|
|
}
|
|
|
|
let mut aL = ScalarVector::new(padded_pow_of_2 * COMMITMENT_BITS);
|
|
for (i, commitment) in witness.commitments.iter().enumerate() {
|
|
let mut amount = commitment.amount;
|
|
for j in 0 .. COMMITMENT_BITS {
|
|
aL[(i * COMMITMENT_BITS) + j] = Scalar::from(amount & 1);
|
|
amount >>= 1;
|
|
}
|
|
}
|
|
let aR = aL.clone() - Scalar::ONE;
|
|
|
|
let alpha = Scalar::random(&mut *rng);
|
|
|
|
let A = {
|
|
let mut terms = Vec::with_capacity(1 + (2 * aL.len()));
|
|
terms.push((alpha, ED25519_BASEPOINT_POINT));
|
|
for (aL, G) in aL.0.iter().zip(&generators.G) {
|
|
terms.push((*aL, *G));
|
|
}
|
|
for (aR, H) in aR.0.iter().zip(&generators.H) {
|
|
terms.push((*aR, *H));
|
|
}
|
|
let res = multiexp(&terms) * INV_EIGHT();
|
|
terms.zeroize();
|
|
res
|
|
};
|
|
|
|
let mut sL = ScalarVector::new(padded_pow_of_2 * COMMITMENT_BITS);
|
|
let mut sR = ScalarVector::new(padded_pow_of_2 * COMMITMENT_BITS);
|
|
for i in 0 .. (padded_pow_of_2 * COMMITMENT_BITS) {
|
|
sL[i] = Scalar::random(&mut *rng);
|
|
sR[i] = Scalar::random(&mut *rng);
|
|
}
|
|
let rho = Scalar::random(&mut *rng);
|
|
|
|
let S = {
|
|
let mut terms = Vec::with_capacity(1 + (2 * sL.len()));
|
|
terms.push((rho, ED25519_BASEPOINT_POINT));
|
|
for (sL, G) in sL.0.iter().zip(&generators.G) {
|
|
terms.push((*sL, *G));
|
|
}
|
|
for (sR, H) in sR.0.iter().zip(&generators.H) {
|
|
terms.push((*sR, *H));
|
|
}
|
|
let res = multiexp(&terms) * INV_EIGHT();
|
|
terms.zeroize();
|
|
res
|
|
};
|
|
|
|
let (y, z) = Self::transcript_A_S(transcript, A, S);
|
|
transcript = z;
|
|
let z = ScalarVector::powers(z, 3 + padded_pow_of_2);
|
|
|
|
let twos = ScalarVector::powers(Scalar::from(2u8), COMMITMENT_BITS);
|
|
|
|
let l = [aL - z[1], sL];
|
|
let y_pow_n = ScalarVector::powers(y, aR.len());
|
|
let mut r = [((aR + z[1]) * &y_pow_n), sR * &y_pow_n];
|
|
{
|
|
for j in 0 .. padded_pow_of_2 {
|
|
for i in 0 .. COMMITMENT_BITS {
|
|
r[0].0[(j * COMMITMENT_BITS) + i] += z[2 + j] * twos[i];
|
|
}
|
|
}
|
|
}
|
|
let t1 = (l[0].clone().inner_product(&r[1])) + (r[0].clone().inner_product(&l[1]));
|
|
let t2 = l[1].clone().inner_product(&r[1]);
|
|
|
|
let tau_1 = Scalar::random(&mut *rng);
|
|
let T1 = {
|
|
let mut T1_terms = [(t1, *MONERO_H), (tau_1, ED25519_BASEPOINT_POINT)];
|
|
for term in &mut T1_terms {
|
|
term.0 *= INV_EIGHT();
|
|
}
|
|
let T1 = multiexp(&T1_terms);
|
|
T1_terms.zeroize();
|
|
T1
|
|
};
|
|
let tau_2 = Scalar::random(&mut *rng);
|
|
let T2 = {
|
|
let mut T2_terms = [(t2, *MONERO_H), (tau_2, ED25519_BASEPOINT_POINT)];
|
|
for term in &mut T2_terms {
|
|
term.0 *= INV_EIGHT();
|
|
}
|
|
let T2 = multiexp(&T2_terms);
|
|
T2_terms.zeroize();
|
|
T2
|
|
};
|
|
|
|
transcript = Self::transcript_T12(transcript, T1, T2);
|
|
let x = transcript;
|
|
|
|
let [l0, l1] = l;
|
|
let l = l0 + &(l1 * x);
|
|
let [r0, r1] = r;
|
|
let r = r0 + &(r1 * x);
|
|
let t_hat = l.clone().inner_product(&r);
|
|
let mut tau_x = ((tau_2 * x) + tau_1) * x;
|
|
{
|
|
for (i, commitment) in witness.commitments.iter().enumerate() {
|
|
tau_x += z[2 + i] * commitment.mask;
|
|
}
|
|
}
|
|
let mu = alpha + (rho * x);
|
|
|
|
let y_inv_pow_n = ScalarVector::powers(y.invert(), l.len());
|
|
|
|
transcript = Self::transcript_tau_x_mu_t_hat(transcript, tau_x, mu, t_hat);
|
|
let x_ip = transcript;
|
|
|
|
let ip = IpStatement::new_without_P_transcript(y_inv_pow_n, x_ip)
|
|
.prove(transcript, IpWitness::new(l, r).unwrap())
|
|
.unwrap();
|
|
|
|
let res = AggregateRangeProof { A, S, T1, T2, tau_x, mu, t_hat, ip };
|
|
#[cfg(debug_assertions)]
|
|
{
|
|
let mut verifier = BulletproofsBatchVerifier::default();
|
|
debug_assert!(self.verify(rng, &mut verifier, res.clone()));
|
|
debug_assert!(verifier.verify());
|
|
}
|
|
Some(res)
|
|
}
|
|
|
|
#[must_use]
|
|
pub(crate) fn verify(
|
|
self,
|
|
rng: &mut (impl RngCore + CryptoRng),
|
|
verifier: &mut BulletproofsBatchVerifier,
|
|
mut proof: AggregateRangeProof,
|
|
) -> bool {
|
|
let mut padded_pow_of_2 = 1;
|
|
while padded_pow_of_2 < self.commitments.len() {
|
|
padded_pow_of_2 <<= 1;
|
|
}
|
|
let ip_rows = padded_pow_of_2 * COMMITMENT_BITS;
|
|
|
|
while verifier.0.g_bold.len() < ip_rows {
|
|
verifier.0.g_bold.push(Scalar::ZERO);
|
|
verifier.0.h_bold.push(Scalar::ZERO);
|
|
}
|
|
|
|
let (mut transcript, mut commitments) = self.initial_transcript();
|
|
for commitment in &mut commitments {
|
|
*commitment = commitment.mul_by_cofactor();
|
|
}
|
|
|
|
let (y, z) = Self::transcript_A_S(transcript, proof.A, proof.S);
|
|
transcript = z;
|
|
let z = ScalarVector::powers(z, 3 + padded_pow_of_2);
|
|
transcript = Self::transcript_T12(transcript, proof.T1, proof.T2);
|
|
let x = transcript;
|
|
transcript = Self::transcript_tau_x_mu_t_hat(transcript, proof.tau_x, proof.mu, proof.t_hat);
|
|
let x_ip = transcript;
|
|
|
|
proof.A = proof.A.mul_by_cofactor();
|
|
proof.S = proof.S.mul_by_cofactor();
|
|
proof.T1 = proof.T1.mul_by_cofactor();
|
|
proof.T2 = proof.T2.mul_by_cofactor();
|
|
|
|
let y_pow_n = ScalarVector::powers(y, ip_rows);
|
|
let y_inv_pow_n = ScalarVector::powers(y.invert(), ip_rows);
|
|
|
|
let twos = ScalarVector::powers(Scalar::from(2u8), COMMITMENT_BITS);
|
|
|
|
// 65
|
|
{
|
|
let weight = Scalar::random(&mut *rng);
|
|
verifier.0.h += weight * proof.t_hat;
|
|
verifier.0.g += weight * proof.tau_x;
|
|
|
|
// Now that we've accumulated the lhs, negate the weight and accumulate the rhs
|
|
// These will now sum to 0 if equal
|
|
let weight = -weight;
|
|
|
|
verifier.0.h += weight * (z[1] - (z[2])) * y_pow_n.sum();
|
|
|
|
for (i, commitment) in commitments.iter().enumerate() {
|
|
verifier.0.other.push((weight * z[2 + i], *commitment));
|
|
}
|
|
|
|
for i in 0 .. padded_pow_of_2 {
|
|
verifier.0.h -= weight * z[3 + i] * twos.clone().sum();
|
|
}
|
|
verifier.0.other.push((weight * x, proof.T1));
|
|
verifier.0.other.push((weight * (x * x), proof.T2));
|
|
}
|
|
|
|
let ip_weight = Scalar::random(&mut *rng);
|
|
|
|
// 66
|
|
verifier.0.other.push((ip_weight, proof.A));
|
|
verifier.0.other.push((ip_weight * x, proof.S));
|
|
// We can replace these with a g_sum, h_sum scalar in the batch verifier
|
|
// It'd trade `2 * ip_rows` scalar additions (per proof) for one scalar addition and an
|
|
// additional term in the MSM
|
|
let ip_z = ip_weight * z[1];
|
|
for i in 0 .. ip_rows {
|
|
verifier.0.h_bold[i] += ip_z;
|
|
}
|
|
let neg_ip_z = -ip_z;
|
|
for i in 0 .. ip_rows {
|
|
verifier.0.g_bold[i] += neg_ip_z;
|
|
}
|
|
{
|
|
for j in 0 .. padded_pow_of_2 {
|
|
for i in 0 .. COMMITMENT_BITS {
|
|
let full_i = (j * COMMITMENT_BITS) + i;
|
|
verifier.0.h_bold[full_i] += ip_weight * y_inv_pow_n[full_i] * z[2 + j] * twos[i];
|
|
}
|
|
}
|
|
}
|
|
verifier.0.h += ip_weight * x_ip * proof.t_hat;
|
|
|
|
// 67, 68
|
|
verifier.0.g += ip_weight * -proof.mu;
|
|
let res = IpStatement::new_without_P_transcript(y_inv_pow_n, x_ip)
|
|
.verify(verifier, ip_rows, transcript, ip_weight, proof.ip);
|
|
res.is_ok()
|
|
}
|
|
}
|