Smash out Borromean

This commit is contained in:
Luke Parker
2024-06-16 11:55:41 -04:00
parent 303e72c844
commit 3e82ee60b3
14 changed files with 147 additions and 36 deletions

15
Cargo.lock generated
View File

@@ -4751,6 +4751,18 @@ dependencies = [
"zeroize",
]
[[package]]
name = "monero-borromean"
version = "0.1.0"
dependencies = [
"curve25519-dalek",
"monero-generators",
"monero-io",
"monero-primitives",
"std-shims",
"zeroize",
]
[[package]]
name = "monero-bulletproofs"
version = "0.1.0"
@@ -4827,7 +4839,9 @@ name = "monero-primitives"
version = "0.1.0"
dependencies = [
"curve25519-dalek",
"hex",
"monero-generators",
"monero-io",
"sha3",
"std-shims",
"zeroize",
@@ -4847,6 +4861,7 @@ dependencies = [
"hex",
"hex-literal",
"modular-frost",
"monero-borromean",
"monero-bulletproofs",
"monero-clsag",
"monero-generators",

View File

@@ -48,6 +48,7 @@ members = [
"coins/monero/primitives",
"coins/monero/ringct/mlsag",
"coins/monero/ringct/clsag",
"coins/monero/ringct/borromean",
"coins/monero/ringct/bulletproofs",
"coins/monero",

View File

@@ -49,6 +49,7 @@ monero-generators = { path = "generators", version = "0.4", default-features = f
monero-primitives = { path = "primitives", version = "0.1", default-features = false }
monero-mlsag = { path = "ringct/mlsag", version = "0.1", default-features = false }
monero-clsag = { path = "ringct/clsag", version = "0.1", default-features = false }
monero-borromean = { path = "ringct/borromean", version = "0.1", default-features = false }
monero-bulletproofs = { path = "ringct/bulletproofs", version = "0.1", default-features = false }
hex-literal = "0.4"

View File

@@ -25,8 +25,12 @@ sha3 = { version = "0.10", default-features = false }
curve25519-dalek = { version = "4", default-features = false, features = ["alloc", "zeroize"] }
# Other Monero dependencies
monero-io = { path = "../io", version = "0.1", default-features = false }
monero-generators = { path = "../generators", version = "0.4", default-features = false }
[dev-dependencies]
hex = { version = "0.4", default-features = false, features = ["alloc"] }
[features]
std = [
"std-shims/std",

View File

@@ -19,6 +19,12 @@ use curve25519_dalek::{
use monero_generators::H;
mod unreduced_scalar;
pub use unreduced_scalar::UnreducedScalar;
#[cfg(test)]
mod tests;
// On std, we cache some variables in statics.
#[cfg(feature = "std")]
static INV_EIGHT_CELL: OnceLock<Scalar> = OnceLock::new();

View File

@@ -1,6 +1,6 @@
use curve25519_dalek::scalar::Scalar;
use crate::unreduced_scalar::*;
use crate::UnreducedScalar;
#[test]
fn recover_scalars() {

View File

@@ -1,18 +1,19 @@
use core::cmp::Ordering;
use std_shims::{
sync::OnceLock,
io::{self, *},
};
use zeroize::Zeroize;
use curve25519_dalek::scalar::Scalar;
use crate::serialize::*;
use monero_io::*;
static PRECOMPUTED_SCALARS_CELL: OnceLock<[Scalar; 8]> = OnceLock::new();
/// Precomputed scalars used to recover an incorrectly reduced scalar.
// Precomputed scalars used to recover an incorrectly reduced scalar.
#[allow(non_snake_case)]
pub(crate) fn PRECOMPUTED_SCALARS() -> [Scalar; 8] {
fn PRECOMPUTED_SCALARS() -> [Scalar; 8] {
*PRECOMPUTED_SCALARS_CELL.get_or_init(|| {
let mut precomputed_scalars = [Scalar::ONE; 8];
for (i, scalar) in precomputed_scalars.iter_mut().enumerate().skip(1) {
@@ -22,14 +23,23 @@ pub(crate) fn PRECOMPUTED_SCALARS() -> [Scalar; 8] {
})
}
#[derive(Clone, PartialEq, Eq, Debug)]
/// An unreduced scalar.
///
/// While most of modern Monero enforces scalars be reduced, certain legacy parts of the code did
/// not. These section can generally simply be read as a scalar/reduced into a scalar when the time
/// comes, yet a couple have non-standard reductions performed.
///
/// This struct delays scalar conversions and offers the non-standard reduction.
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
pub struct UnreducedScalar(pub [u8; 32]);
impl UnreducedScalar {
/// Write an UnreducedScalar.
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
w.write_all(&self.0)
}
/// Read an UnreducedScalar.
pub fn read<R: Read>(r: &mut R) -> io::Result<UnreducedScalar> {
Ok(UnreducedScalar(read_bytes(r)?))
}
@@ -43,12 +53,12 @@ impl UnreducedScalar {
bits
}
/// Computes the non-adjacent form of this scalar with width 5.
///
/// This matches Monero's `slide` function and intentionally gives incorrect outputs under
/// certain conditions in order to match Monero.
///
/// This function does not execute in constant time.
// Computes the non-adjacent form of this scalar with width 5.
//
// This matches Monero's `slide` function and intentionally gives incorrect outputs under
// certain conditions in order to match Monero.
//
// This function does not execute in constant time.
fn non_adjacent_form(&self) -> [i8; 256] {
let bits = self.as_bits();
let mut naf = [0i8; 256];
@@ -104,11 +114,11 @@ impl UnreducedScalar {
/// Recover the scalar that an array of bytes was incorrectly interpreted as by Monero's `slide`
/// function.
///
/// In Borromean range proofs Monero was not checking that the scalars used were
/// reduced. This lead to the scalar stored being interpreted as a different scalar,
/// this function recovers that scalar.
/// In Borromean range proofs, Monero was not checking that the scalars used were
/// reduced. This lead to the scalar stored being interpreted as a different scalar.
/// This function recovers that scalar.
///
/// See: https://github.com/monero-project/monero/issues/8438
/// See https://github.com/monero-project/monero/issues/8438 for more info.
pub fn recover_monero_slide_scalar(&self) -> Scalar {
if self.0[31] & 128 == 0 {
// Computing the w-NAF of a number can only give an output with 1 more bit than

View File

@@ -0,0 +1,41 @@
[package]
name = "monero-borromean"
version = "0.1.0"
description = "Borromean ring signatures arranged into a range proof, as done by the Monero protocol"
license = "MIT"
repository = "https://github.com/serai-dex/serai/tree/develop/coins/monero/ringct/borromean"
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
edition = "2021"
rust-version = "1.79"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[lints]
workspace = true
[dependencies]
std-shims = { path = "../../../../common/std-shims", version = "^0.1.1", default-features = false }
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
# Cryptographic dependencies
curve25519-dalek = { version = "4", default-features = false, features = ["alloc", "zeroize"] }
# Other Monero dependencies
monero-io = { path = "../../io", version = "0.1", default-features = false }
monero-generators = { path = "../../generators", version = "0.4", default-features = false }
monero-primitives = { path = "../../primitives", version = "0.1", default-features = false }
[features]
std = [
"std-shims/std",
"zeroize/std",
"monero-io/std",
"monero-generators/std",
"monero-primitives/std",
]
default = ["std"]

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022-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,7 @@
# Monero Borromean
Borromean ring signatures arranged into a range proof, as done by the Monero
protocol.
This library is usable under no-std when the `std` feature (on by default) is
disabled.

View File

@@ -1,18 +1,26 @@
#![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 core::fmt::Debug;
use std_shims::io::{self, Read, Write};
use zeroize::Zeroize;
use curve25519_dalek::{traits::Identity, Scalar, EdwardsPoint};
use monero_io::*;
use monero_generators::H_pow_2;
use monero_primitives::{keccak256_to_scalar, UnreducedScalar};
use crate::{hash_to_scalar, unreduced_scalar::UnreducedScalar, serialize::*};
/// 64 Borromean ring signatures, as needed for a 64-bit range proof.
///
/// s0 and s1 are stored as `UnreducedScalar`s due to Monero not requiring they were reduced.
/// `UnreducedScalar` preserves their original byte encoding and implements a custom reduction
/// algorithm which was in use.
#[derive(Clone, PartialEq, Eq, Debug)]
// 64 Borromean ring signatures, as needed for a 64-bit range proof.
//
// s0 and s1 are stored as `UnreducedScalar`s due to Monero not requiring they were reduced.
// `UnreducedScalar` preserves their original byte encoding and implements a custom reduction
// algorithm which was in use.
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
struct BorromeanSignatures {
s0: [UnreducedScalar; 64],
s1: [UnreducedScalar; 64],
@@ -20,7 +28,7 @@ struct BorromeanSignatures {
}
impl BorromeanSignatures {
/// Read a set of BorromeanSignatures from a reader.
// Read a set of BorromeanSignatures.
fn read<R: Read>(r: &mut R) -> io::Result<BorromeanSignatures> {
Ok(BorromeanSignatures {
s0: read_array(UnreducedScalar::read, r)?,
@@ -29,7 +37,7 @@ impl BorromeanSignatures {
})
}
/// Write the set of BorromeanSignatures to a writer.
// Write the set of BorromeanSignatures.
fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
for s0 in &self.s0 {
s0.write(w)?;
@@ -52,26 +60,26 @@ impl BorromeanSignatures {
);
#[allow(non_snake_case)]
let LV = EdwardsPoint::vartime_double_scalar_mul_basepoint(
&hash_to_scalar(LL.compress().as_bytes()),
&keccak256_to_scalar(LL.compress().as_bytes()),
&keys_b[i],
&self.s1[i].recover_monero_slide_scalar(),
);
transcript[(i * 32) .. ((i + 1) * 32)].copy_from_slice(LV.compress().as_bytes());
}
hash_to_scalar(&transcript) == self.ee
keccak256_to_scalar(transcript) == self.ee
}
}
/// A range proof premised on Borromean ring signatures.
#[derive(Clone, PartialEq, Eq, Debug)]
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
pub struct BorromeanRange {
sigs: BorromeanSignatures,
bit_commitments: [EdwardsPoint; 64],
}
impl BorromeanRange {
/// Read a BorromeanRange proof from a reader.
/// Read a BorromeanRange proof.
pub fn read<R: Read>(r: &mut R) -> io::Result<BorromeanRange> {
Ok(BorromeanRange {
sigs: BorromeanSignatures::read(r)?,
@@ -79,13 +87,14 @@ impl BorromeanRange {
})
}
/// Write the BorromeanRange proof to a reader.
/// Write the BorromeanRange proof.
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
self.sigs.write(w)?;
write_raw_vec(write_point, &self.bit_commitments, w)
}
/// Verify the commitment contains a 64-bit value.
#[must_use]
pub fn verify(&self, commitment: &EdwardsPoint) -> bool {
if &self.bit_commitments.iter().sum::<EdwardsPoint>() != commitment {
return false;

View File

@@ -23,9 +23,6 @@ mod merkle;
use monero_io as serialize;
use serialize::{read_byte, read_u16};
/// UnreducedScalar struct with functionality for recovering incorrectly reduced scalars.
mod unreduced_scalar;
/// Ring Signature structs and functionality.
pub mod ring_signatures;

View File

@@ -16,7 +16,7 @@ pub use monero_mlsag as mlsag;
/// CLSAG struct, along with signing and verifying functionality.
pub use monero_clsag as clsag;
/// BorromeanRange struct, along with verifying functionality.
pub mod borromean;
pub use monero_borromean as borromean;
/// Bulletproofs(+) structs, along with proving and verifying functionality.
pub use monero_bulletproofs as bulletproofs;

View File

@@ -1,4 +1,3 @@
mod unreduced_scalar;
mod address;
mod seed;
mod extra;