mirror of
https://github.com/serai-dex/serai.git
synced 2025-01-27 13:06:01 +00:00
7d2d739042
* Rename the coins folder to networks Ethereum isn't a coin. It's a network. Resolves #357. * More renames of coins -> networks in orchestration * Correct paths in tests/ * cargo fmt
292 lines
8.8 KiB
Rust
292 lines
8.8 KiB
Rust
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
|
#![doc = include_str!("../README.md")]
|
|
#![deny(missing_docs)]
|
|
#![cfg_attr(not(feature = "std"), no_std)]
|
|
#![allow(non_snake_case)]
|
|
|
|
use std_shims::{
|
|
vec,
|
|
vec::Vec,
|
|
io::{self, Read, Write},
|
|
};
|
|
|
|
use rand_core::{RngCore, CryptoRng};
|
|
use zeroize::Zeroizing;
|
|
|
|
use curve25519_dalek::edwards::EdwardsPoint;
|
|
|
|
use monero_io::*;
|
|
pub use monero_generators::MAX_COMMITMENTS;
|
|
use monero_primitives::Commitment;
|
|
|
|
pub(crate) mod scalar_vector;
|
|
pub(crate) mod point_vector;
|
|
|
|
pub(crate) mod core;
|
|
use crate::core::LOG_COMMITMENT_BITS;
|
|
|
|
pub(crate) mod batch_verifier;
|
|
use batch_verifier::{BulletproofsBatchVerifier, BulletproofsPlusBatchVerifier};
|
|
pub use batch_verifier::BatchVerifier;
|
|
|
|
pub(crate) mod original;
|
|
use crate::original::{
|
|
IpProof, AggregateRangeStatement as OriginalStatement, AggregateRangeWitness as OriginalWitness,
|
|
AggregateRangeProof as OriginalProof,
|
|
};
|
|
|
|
pub(crate) mod plus;
|
|
use crate::plus::{
|
|
WipProof, AggregateRangeStatement as PlusStatement, AggregateRangeWitness as PlusWitness,
|
|
AggregateRangeProof as PlusProof,
|
|
};
|
|
|
|
#[cfg(test)]
|
|
mod tests;
|
|
|
|
/// An error from proving/verifying Bulletproofs(+).
|
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
|
#[cfg_attr(feature = "std", derive(thiserror::Error))]
|
|
pub enum BulletproofError {
|
|
/// Proving/verifying a Bulletproof(+) range proof with no commitments.
|
|
#[cfg_attr(feature = "std", error("no commitments to prove the range for"))]
|
|
NoCommitments,
|
|
/// Proving/verifying a Bulletproof(+) range proof with more commitments than supported.
|
|
#[cfg_attr(feature = "std", error("too many commitments to prove the range for"))]
|
|
TooManyCommitments,
|
|
}
|
|
|
|
/// A Bulletproof(+).
|
|
///
|
|
/// This encapsulates either a Bulletproof or a Bulletproof+.
|
|
#[allow(clippy::large_enum_variant)]
|
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
|
pub enum Bulletproof {
|
|
/// A Bulletproof.
|
|
Original(OriginalProof),
|
|
/// A Bulletproof+.
|
|
Plus(PlusProof),
|
|
}
|
|
|
|
impl Bulletproof {
|
|
fn bp_fields(plus: bool) -> usize {
|
|
if plus {
|
|
6
|
|
} else {
|
|
9
|
|
}
|
|
}
|
|
|
|
/// Calculate the weight penalty for the Bulletproof(+).
|
|
///
|
|
/// Bulletproofs(+) are logarithmically sized yet linearly timed. Evaluating by their size alone
|
|
/// accordingly doesn't properly represent the burden of the proof. Monero 'claws back' some of
|
|
/// the weight lost by using a proof smaller than it is fast to compensate for this.
|
|
// https://github.com/monero-project/monero/blob/94e67bf96bbc010241f29ada6abc89f49a81759c/
|
|
// src/cryptonote_basic/cryptonote_format_utils.cpp#L106-L124
|
|
pub fn calculate_bp_clawback(plus: bool, n_outputs: usize) -> (usize, usize) {
|
|
#[allow(non_snake_case)]
|
|
let mut LR_len = 0;
|
|
let mut n_padded_outputs = 1;
|
|
while n_padded_outputs < n_outputs {
|
|
LR_len += 1;
|
|
n_padded_outputs = 1 << LR_len;
|
|
}
|
|
LR_len += LOG_COMMITMENT_BITS;
|
|
|
|
let mut bp_clawback = 0;
|
|
if n_padded_outputs > 2 {
|
|
let fields = Bulletproof::bp_fields(plus);
|
|
let base = ((fields + (2 * (LOG_COMMITMENT_BITS + 1))) * 32) / 2;
|
|
let size = (fields + (2 * LR_len)) * 32;
|
|
bp_clawback = ((base * n_padded_outputs) - size) * 4 / 5;
|
|
}
|
|
|
|
(bp_clawback, LR_len)
|
|
}
|
|
|
|
/// Prove the list of commitments are within [0 .. 2^64) with an aggregate Bulletproof.
|
|
pub fn prove<R: RngCore + CryptoRng>(
|
|
rng: &mut R,
|
|
outputs: Vec<Commitment>,
|
|
) -> Result<Bulletproof, BulletproofError> {
|
|
if outputs.is_empty() {
|
|
Err(BulletproofError::NoCommitments)?;
|
|
}
|
|
if outputs.len() > MAX_COMMITMENTS {
|
|
Err(BulletproofError::TooManyCommitments)?;
|
|
}
|
|
let commitments = outputs.iter().map(Commitment::calculate).collect::<Vec<_>>();
|
|
Ok(Bulletproof::Original(
|
|
OriginalStatement::new(&commitments)
|
|
.unwrap()
|
|
.prove(rng, OriginalWitness::new(outputs).unwrap())
|
|
.unwrap(),
|
|
))
|
|
}
|
|
|
|
/// Prove the list of commitments are within [0 .. 2^64) with an aggregate Bulletproof+.
|
|
pub fn prove_plus<R: RngCore + CryptoRng>(
|
|
rng: &mut R,
|
|
outputs: Vec<Commitment>,
|
|
) -> Result<Bulletproof, BulletproofError> {
|
|
if outputs.is_empty() {
|
|
Err(BulletproofError::NoCommitments)?;
|
|
}
|
|
if outputs.len() > MAX_COMMITMENTS {
|
|
Err(BulletproofError::TooManyCommitments)?;
|
|
}
|
|
let commitments = outputs.iter().map(Commitment::calculate).collect::<Vec<_>>();
|
|
Ok(Bulletproof::Plus(
|
|
PlusStatement::new(&commitments)
|
|
.unwrap()
|
|
.prove(rng, &Zeroizing::new(PlusWitness::new(outputs).unwrap()))
|
|
.unwrap(),
|
|
))
|
|
}
|
|
|
|
/// Verify the given Bulletproof(+).
|
|
#[must_use]
|
|
pub fn verify<R: RngCore + CryptoRng>(&self, rng: &mut R, commitments: &[EdwardsPoint]) -> bool {
|
|
match self {
|
|
Bulletproof::Original(bp) => {
|
|
let mut verifier = BulletproofsBatchVerifier::default();
|
|
let Some(statement) = OriginalStatement::new(commitments) else {
|
|
return false;
|
|
};
|
|
if !statement.verify(rng, &mut verifier, bp.clone()) {
|
|
return false;
|
|
}
|
|
verifier.verify()
|
|
}
|
|
Bulletproof::Plus(bp) => {
|
|
let mut verifier = BulletproofsPlusBatchVerifier::default();
|
|
let Some(statement) = PlusStatement::new(commitments) else {
|
|
return false;
|
|
};
|
|
if !statement.verify(rng, &mut verifier, bp.clone()) {
|
|
return false;
|
|
}
|
|
verifier.verify()
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Accumulate the verification for the given Bulletproof(+) into the specified BatchVerifier.
|
|
///
|
|
/// Returns false if the Bulletproof(+) isn't sane, leaving the BatchVerifier in an undefined
|
|
/// state.
|
|
///
|
|
/// Returns true if the Bulletproof(+) is sane, regardless of its validity.
|
|
///
|
|
/// The BatchVerifier must have its verification function executed to actually verify this proof.
|
|
#[must_use]
|
|
pub fn batch_verify<R: RngCore + CryptoRng>(
|
|
&self,
|
|
rng: &mut R,
|
|
verifier: &mut BatchVerifier,
|
|
commitments: &[EdwardsPoint],
|
|
) -> bool {
|
|
match self {
|
|
Bulletproof::Original(bp) => {
|
|
let Some(statement) = OriginalStatement::new(commitments) else {
|
|
return false;
|
|
};
|
|
statement.verify(rng, &mut verifier.original, bp.clone())
|
|
}
|
|
Bulletproof::Plus(bp) => {
|
|
let Some(statement) = PlusStatement::new(commitments) else {
|
|
return false;
|
|
};
|
|
statement.verify(rng, &mut verifier.plus, bp.clone())
|
|
}
|
|
}
|
|
}
|
|
|
|
fn write_core<W: Write, F: Fn(&[EdwardsPoint], &mut W) -> io::Result<()>>(
|
|
&self,
|
|
w: &mut W,
|
|
specific_write_vec: F,
|
|
) -> io::Result<()> {
|
|
match self {
|
|
Bulletproof::Original(bp) => {
|
|
write_point(&bp.A, w)?;
|
|
write_point(&bp.S, w)?;
|
|
write_point(&bp.T1, w)?;
|
|
write_point(&bp.T2, w)?;
|
|
write_scalar(&bp.tau_x, w)?;
|
|
write_scalar(&bp.mu, w)?;
|
|
specific_write_vec(&bp.ip.L, w)?;
|
|
specific_write_vec(&bp.ip.R, w)?;
|
|
write_scalar(&bp.ip.a, w)?;
|
|
write_scalar(&bp.ip.b, w)?;
|
|
write_scalar(&bp.t_hat, w)
|
|
}
|
|
|
|
Bulletproof::Plus(bp) => {
|
|
write_point(&bp.A, w)?;
|
|
write_point(&bp.wip.A, w)?;
|
|
write_point(&bp.wip.B, w)?;
|
|
write_scalar(&bp.wip.r_answer, w)?;
|
|
write_scalar(&bp.wip.s_answer, w)?;
|
|
write_scalar(&bp.wip.delta_answer, w)?;
|
|
specific_write_vec(&bp.wip.L, w)?;
|
|
specific_write_vec(&bp.wip.R, w)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Write a Bulletproof(+) for the message signed by a transaction's signature.
|
|
///
|
|
/// This has a distinct encoding from the standard encoding.
|
|
pub fn signature_write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
|
self.write_core(w, |points, w| write_raw_vec(write_point, points, w))
|
|
}
|
|
|
|
/// Write a Bulletproof(+).
|
|
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
|
self.write_core(w, |points, w| write_vec(write_point, points, w))
|
|
}
|
|
|
|
/// Serialize a Bulletproof(+) to a `Vec<u8>`.
|
|
pub fn serialize(&self) -> Vec<u8> {
|
|
let mut serialized = vec![];
|
|
self.write(&mut serialized).unwrap();
|
|
serialized
|
|
}
|
|
|
|
/// Read a Bulletproof.
|
|
pub fn read<R: Read>(r: &mut R) -> io::Result<Bulletproof> {
|
|
Ok(Bulletproof::Original(OriginalProof {
|
|
A: read_point(r)?,
|
|
S: read_point(r)?,
|
|
T1: read_point(r)?,
|
|
T2: read_point(r)?,
|
|
tau_x: read_scalar(r)?,
|
|
mu: read_scalar(r)?,
|
|
ip: IpProof {
|
|
L: read_vec(read_point, r)?,
|
|
R: read_vec(read_point, r)?,
|
|
a: read_scalar(r)?,
|
|
b: read_scalar(r)?,
|
|
},
|
|
t_hat: read_scalar(r)?,
|
|
}))
|
|
}
|
|
|
|
/// Read a Bulletproof+.
|
|
pub fn read_plus<R: Read>(r: &mut R) -> io::Result<Bulletproof> {
|
|
Ok(Bulletproof::Plus(PlusProof {
|
|
A: read_point(r)?,
|
|
wip: WipProof {
|
|
A: read_point(r)?,
|
|
B: read_point(r)?,
|
|
r_answer: read_scalar(r)?,
|
|
s_answer: read_scalar(r)?,
|
|
delta_answer: read_scalar(r)?,
|
|
L: read_vec(read_point, r)?.into_iter().collect(),
|
|
R: read_vec(read_point, r)?.into_iter().collect(),
|
|
},
|
|
}))
|
|
}
|
|
}
|