mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-11 21:49:26 +00:00
This commit replaces all usage of `unwrap` with `expect` within `networks/monero`, clarifying why the panic risked is unreachable. This commit also replaces some uses of `unwrap` with solutions which are guaranteed not to fail. Notably, compilation on 128-bit systems is prevented, ensuring `u64::try_from(usize::MAX)` will never panic at runtime. Slight breaking changes are additionally included as necessary to massage out some avoidable panics.
343 lines
10 KiB
Rust
343 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<'a> AggregateRangeStatement<'a> {
|
|
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).expect("Bulletproofs::Original created an invalid IpWitness"),
|
|
)
|
|
.expect("Bulletproofs::Original failed to prove the inner-product");
|
|
|
|
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()
|
|
}
|
|
}
|