From 3e82ee60b36692354ecfe3f28c9313178170e6fe Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 16 Jun 2024 11:55:41 -0400 Subject: [PATCH] Smash out Borromean --- Cargo.lock | 15 +++++++ Cargo.toml | 1 + coins/monero/Cargo.toml | 1 + coins/monero/primitives/Cargo.toml | 4 ++ coins/monero/primitives/src/lib.rs | 6 +++ .../src/tests.rs} | 2 +- .../{ => primitives}/src/unreduced_scalar.rs | 40 +++++++++++------- coins/monero/ringct/borromean/Cargo.toml | 41 +++++++++++++++++++ coins/monero/ringct/borromean/LICENSE | 21 ++++++++++ coins/monero/ringct/borromean/README.md | 7 ++++ .../borromean/src/lib.rs} | 39 +++++++++++------- coins/monero/src/lib.rs | 3 -- coins/monero/src/ringct/mod.rs | 2 +- coins/monero/src/tests/mod.rs | 1 - 14 files changed, 147 insertions(+), 36 deletions(-) rename coins/monero/{src/tests/unreduced_scalar.rs => primitives/src/tests.rs} (97%) rename coins/monero/{ => primitives}/src/unreduced_scalar.rs (76%) create mode 100644 coins/monero/ringct/borromean/Cargo.toml create mode 100644 coins/monero/ringct/borromean/LICENSE create mode 100644 coins/monero/ringct/borromean/README.md rename coins/monero/{src/ringct/borromean.rs => ringct/borromean/src/lib.rs} (71%) diff --git a/Cargo.lock b/Cargo.lock index 7e3700fd..d4551749 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index b6973c4b..8f4a142f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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", diff --git a/coins/monero/Cargo.toml b/coins/monero/Cargo.toml index d2a9c7c7..f1712a16 100644 --- a/coins/monero/Cargo.toml +++ b/coins/monero/Cargo.toml @@ -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" diff --git a/coins/monero/primitives/Cargo.toml b/coins/monero/primitives/Cargo.toml index 6184bf5c..9477d641 100644 --- a/coins/monero/primitives/Cargo.toml +++ b/coins/monero/primitives/Cargo.toml @@ -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", diff --git a/coins/monero/primitives/src/lib.rs b/coins/monero/primitives/src/lib.rs index 7f9ebae4..993cb8b2 100644 --- a/coins/monero/primitives/src/lib.rs +++ b/coins/monero/primitives/src/lib.rs @@ -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 = OnceLock::new(); diff --git a/coins/monero/src/tests/unreduced_scalar.rs b/coins/monero/primitives/src/tests.rs similarity index 97% rename from coins/monero/src/tests/unreduced_scalar.rs rename to coins/monero/primitives/src/tests.rs index 1816991d..a14d1cd5 100644 --- a/coins/monero/src/tests/unreduced_scalar.rs +++ b/coins/monero/primitives/src/tests.rs @@ -1,6 +1,6 @@ use curve25519_dalek::scalar::Scalar; -use crate::unreduced_scalar::*; +use crate::UnreducedScalar; #[test] fn recover_scalars() { diff --git a/coins/monero/src/unreduced_scalar.rs b/coins/monero/primitives/src/unreduced_scalar.rs similarity index 76% rename from coins/monero/src/unreduced_scalar.rs rename to coins/monero/primitives/src/unreduced_scalar.rs index 4008a262..53621a77 100644 --- a/coins/monero/src/unreduced_scalar.rs +++ b/coins/monero/primitives/src/unreduced_scalar.rs @@ -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(&self, w: &mut W) -> io::Result<()> { w.write_all(&self.0) } + /// Read an UnreducedScalar. pub fn read(r: &mut R) -> io::Result { 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 diff --git a/coins/monero/ringct/borromean/Cargo.toml b/coins/monero/ringct/borromean/Cargo.toml new file mode 100644 index 00000000..b239a8c3 --- /dev/null +++ b/coins/monero/ringct/borromean/Cargo.toml @@ -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 "] +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"] diff --git a/coins/monero/ringct/borromean/LICENSE b/coins/monero/ringct/borromean/LICENSE new file mode 100644 index 00000000..91d893c1 --- /dev/null +++ b/coins/monero/ringct/borromean/LICENSE @@ -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. diff --git a/coins/monero/ringct/borromean/README.md b/coins/monero/ringct/borromean/README.md new file mode 100644 index 00000000..6d3071c8 --- /dev/null +++ b/coins/monero/ringct/borromean/README.md @@ -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. diff --git a/coins/monero/src/ringct/borromean.rs b/coins/monero/ringct/borromean/src/lib.rs similarity index 71% rename from coins/monero/src/ringct/borromean.rs rename to coins/monero/ringct/borromean/src/lib.rs index 886baa41..5e105142 100644 --- a/coins/monero/src/ringct/borromean.rs +++ b/coins/monero/ringct/borromean/src/lib.rs @@ -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: &mut R) -> io::Result { 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(&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: &mut R) -> io::Result { 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(&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::() != commitment { return false; diff --git a/coins/monero/src/lib.rs b/coins/monero/src/lib.rs index 5bbe60e9..c03d4af4 100644 --- a/coins/monero/src/lib.rs +++ b/coins/monero/src/lib.rs @@ -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; diff --git a/coins/monero/src/ringct/mod.rs b/coins/monero/src/ringct/mod.rs index 6192ba6b..70cefc53 100644 --- a/coins/monero/src/ringct/mod.rs +++ b/coins/monero/src/ringct/mod.rs @@ -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; diff --git a/coins/monero/src/tests/mod.rs b/coins/monero/src/tests/mod.rs index 99da24ac..2662bbdd 100644 --- a/coins/monero/src/tests/mod.rs +++ b/coins/monero/src/tests/mod.rs @@ -1,4 +1,3 @@ -mod unreduced_scalar; mod address; mod seed; mod extra;