From 784a27374776aef61be07811bcf79d5422e1b347 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 13 Jun 2024 18:54:18 -0400 Subject: [PATCH] Begin crate smashing --- Cargo.lock | 49 +++++++ Cargo.toml | 3 + coins/monero/Cargo.toml | 8 +- coins/monero/generators/Cargo.toml | 21 ++- coins/monero/generators/LICENSE | 2 +- coins/monero/generators/README.md | 7 +- coins/monero/generators/src/hash_to_point.rs | 15 +-- coins/monero/generators/src/lib.rs | 24 ++-- coins/monero/generators/src/varint.rs | 16 --- coins/monero/io/Cargo.toml | 24 ++++ coins/monero/io/LICENSE | 21 +++ coins/monero/io/README.md | 6 + .../{src/serialize.rs => io/src/lib.rs} | 63 +++++---- coins/monero/primitives/Cargo.toml | 44 +++++++ coins/monero/primitives/LICENSE | 21 +++ coins/monero/primitives/README.md | 6 + coins/monero/primitives/src/lib.rs | 122 ++++++++++++++++++ coins/monero/ringct/clsag/Cargo.toml | 66 ++++++++++ coins/monero/ringct/clsag/LICENSE | 21 +++ coins/monero/ringct/clsag/README.md | 6 + .../clsag/mod.rs => ringct/clsag/src/lib.rs} | 44 ++++--- .../clsag => ringct/clsag/src}/multisig.rs | 21 ++- coins/monero/src/bin/reserialize_chain.rs | 2 +- coins/monero/src/lib.rs | 69 ++-------- coins/monero/src/ringct/mod.rs | 2 +- coins/monero/src/rpc/mod.rs | 2 +- coins/monero/src/tests/address.rs | 13 +- coins/monero/src/tests/bulletproofs/mod.rs | 6 +- coins/monero/src/tests/clsag.rs | 18 +-- coins/monero/src/wallet/address.rs | 2 +- coins/monero/src/wallet/decoys.rs | 61 +++++---- coins/monero/src/wallet/mod.rs | 11 +- coins/monero/src/wallet/scan.rs | 2 +- coins/monero/src/wallet/seed/classic.rs | 4 +- coins/monero/src/wallet/send/mod.rs | 6 +- coins/monero/src/wallet/send/multisig.rs | 3 +- coins/monero/tests/runner.rs | 23 ++-- coins/monero/tests/send.rs | 12 +- processor/src/networks/monero.rs | 2 +- tests/full-stack/src/tests/mint_and_burn.rs | 2 +- tests/no-std/Cargo.toml | 3 + tests/processor/src/networks.rs | 3 +- 42 files changed, 606 insertions(+), 250 deletions(-) delete mode 100644 coins/monero/generators/src/varint.rs create mode 100644 coins/monero/io/Cargo.toml create mode 100644 coins/monero/io/LICENSE create mode 100644 coins/monero/io/README.md rename coins/monero/{src/serialize.rs => io/src/lib.rs} (63%) create mode 100644 coins/monero/primitives/Cargo.toml create mode 100644 coins/monero/primitives/LICENSE create mode 100644 coins/monero/primitives/README.md create mode 100644 coins/monero/primitives/src/lib.rs create mode 100644 coins/monero/ringct/clsag/Cargo.toml create mode 100644 coins/monero/ringct/clsag/LICENSE create mode 100644 coins/monero/ringct/clsag/README.md rename coins/monero/{src/ringct/clsag/mod.rs => ringct/clsag/src/lib.rs} (90%) rename coins/monero/{src/ringct/clsag => ringct/clsag/src}/multisig.rs (96%) diff --git a/Cargo.lock b/Cargo.lock index d52220aa..401d33a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4751,6 +4751,27 @@ dependencies = [ "zeroize", ] +[[package]] +name = "monero-clsag" +version = "0.1.0" +dependencies = [ + "curve25519-dalek", + "dalek-ff-group", + "flexible-transcript", + "group", + "modular-frost", + "monero-generators", + "monero-io", + "monero-primitives", + "rand_chacha", + "rand_core", + "sha3", + "std-shims", + "subtle", + "thiserror", + "zeroize", +] + [[package]] name = "monero-generators" version = "0.4.0" @@ -4759,11 +4780,33 @@ dependencies = [ "dalek-ff-group", "group", "hex", + "monero-io", "sha3", "std-shims", "subtle", ] +[[package]] +name = "monero-io" +version = "0.1.0" +dependencies = [ + "curve25519-dalek", + "std-shims", +] + +[[package]] +name = "monero-primitives" +version = "0.1.0" +dependencies = [ + "curve25519-dalek", + "monero-generators", + "monero-io", + "rand_core", + "sha3", + "std-shims", + "zeroize", +] + [[package]] name = "monero-serai" version = "0.1.4-alpha" @@ -4778,7 +4821,10 @@ dependencies = [ "hex", "hex-literal", "modular-frost", + "monero-clsag", "monero-generators", + "monero-io", + "monero-primitives", "multiexp", "pbkdf2 0.12.2", "rand", @@ -8035,7 +8081,10 @@ dependencies = [ "dleq", "flexible-transcript", "minimal-ed448", + "monero-clsag", "monero-generators", + "monero-io", + "monero-primitives", "monero-serai", "multiexp", "schnorr-signatures", diff --git a/Cargo.toml b/Cargo.toml index ce0062f0..df76b22a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,10 @@ members = [ "coins/ethereum", "coins/ethereum/relayer", + "coins/monero/io", "coins/monero/generators", + "coins/monero/primitives", + "coins/monero/ringct/clsag", "coins/monero", "message-queue", diff --git a/coins/monero/Cargo.toml b/coins/monero/Cargo.toml index 0b2029cd..fc87df3b 100644 --- a/coins/monero/Cargo.toml +++ b/coins/monero/Cargo.toml @@ -45,7 +45,10 @@ multiexp = { path = "../../crypto/multiexp", version = "0.4", default-features = transcript = { package = "flexible-transcript", path = "../../crypto/transcript", version = "0.3", default-features = false, features = ["recommended"], optional = true } frost = { package = "modular-frost", path = "../../crypto/frost", version = "0.8", default-features = false, features = ["ed25519"], optional = true } +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 } +monero-clsag = { path = "ringct/clsag", version = "0.1", default-features = false } hex-literal = "0.4" hex = { version = "0.4", default-features = false, features = ["alloc"] } @@ -89,7 +92,10 @@ std = [ "transcript/std", + "monero-io/std", "monero-generators/std", + "monero-primitives/std", + "monero-clsag/std", "hex/std", "serde/std", @@ -99,7 +105,7 @@ std = [ ] http-rpc = ["digest_auth", "simple-request", "tokio"] -multisig = ["transcript", "frost", "std"] +multisig = ["transcript", "frost", "monero-clsag/multisig", "std"] binaries = ["tokio/rt-multi-thread", "tokio/macros", "http-rpc"] default = ["std", "http-rpc"] diff --git a/coins/monero/generators/Cargo.toml b/coins/monero/generators/Cargo.toml index 22df2ae3..ec62879a 100644 --- a/coins/monero/generators/Cargo.toml +++ b/coins/monero/generators/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "monero-generators" version = "0.4.0" -description = "Monero's hash_to_point and generators" +description = "Monero's hash to point function and generators" license = "MIT" repository = "https://github.com/serai-dex/serai/tree/develop/coins/monero/generators" authors = ["Luke Parker "] @@ -20,15 +20,28 @@ std-shims = { path = "../../../common/std-shims", version = "^0.1.1", default-fe subtle = { version = "^2.4", default-features = false } sha3 = { version = "0.10", default-features = false } - -curve25519-dalek = { version = "4", default-features = false, features = ["alloc", "zeroize", "precomputed-tables"] } +curve25519-dalek = { version = "4", default-features = false, features = ["alloc", "zeroize"] } group = { version = "0.13", default-features = false } dalek-ff-group = { path = "../../../crypto/dalek-ff-group", version = "0.4", default-features = false } +monero-io = { path = "../io", version = "0.1", default-features = false } + [dev-dependencies] hex = "0.4" [features] -std = ["std-shims/std", "subtle/std", "sha3/std", "dalek-ff-group/std"] +std = [ + "std-shims/std", + + "subtle/std", + + "sha3/std", + "curve25519-dalek/precomputed-tables", + + "group/alloc", + "dalek-ff-group/std", + + "monero-io/std" +] default = ["std"] diff --git a/coins/monero/generators/LICENSE b/coins/monero/generators/LICENSE index 6779f0ec..91d893c1 100644 --- a/coins/monero/generators/LICENSE +++ b/coins/monero/generators/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022-2023 Luke Parker +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 diff --git a/coins/monero/generators/README.md b/coins/monero/generators/README.md index bab293c9..4a6aaf62 100644 --- a/coins/monero/generators/README.md +++ b/coins/monero/generators/README.md @@ -1,7 +1,8 @@ # Monero Generators Generators used by Monero in both its Pedersen commitments and Bulletproofs(+). -An implementation of Monero's `ge_fromfe_frombytes_vartime`, simply called -`hash_to_point` here, is included, as needed to generate generators. +An implementation of Monero's `hash_to_ec` is included, as needed to generate +the generators. -This library is usable under no-std when the `std` feature is disabled. +This library is usable under no-std when the `std` feature (on by default) is +disabled. diff --git a/coins/monero/generators/src/hash_to_point.rs b/coins/monero/generators/src/hash_to_point.rs index 6a76207d..36c0b3a3 100644 --- a/coins/monero/generators/src/hash_to_point.rs +++ b/coins/monero/generators/src/hash_to_point.rs @@ -1,27 +1,20 @@ use subtle::ConditionallySelectable; -use curve25519_dalek::edwards::{EdwardsPoint, CompressedEdwardsY}; +use curve25519_dalek::edwards::EdwardsPoint; use group::ff::{Field, PrimeField}; use dalek_ff_group::FieldElement; -use crate::hash; +use monero_io::decompress_point; -/// Decompress canonically encoded ed25519 point -/// It does not check if the point is in the prime order subgroup -pub fn decompress_point(bytes: [u8; 32]) -> Option { - CompressedEdwardsY(bytes) - .decompress() - // Ban points which are either unreduced or -0 - .filter(|point| point.compress().to_bytes() == bytes) -} +use crate::keccak256; /// Monero's hash to point function, as named `hash_to_ec`. pub fn hash_to_point(bytes: [u8; 32]) -> EdwardsPoint { #[allow(non_snake_case)] let A = FieldElement::from(486662u64); - let v = FieldElement::from_square(hash(&bytes)).double(); + let v = FieldElement::from_square(keccak256(&bytes)).double(); let w = v + FieldElement::ONE; let x = w.square() + (-A.square() * v); diff --git a/coins/monero/generators/src/lib.rs b/coins/monero/generators/src/lib.rs index c52350c2..60f81bfa 100644 --- a/coins/monero/generators/src/lib.rs +++ b/coins/monero/generators/src/lib.rs @@ -1,8 +1,5 @@ -//! Generators used by Monero in both its Pedersen commitments and Bulletproofs(+). -//! -//! An implementation of Monero's `ge_fromfe_frombytes_vartime`, simply called -//! `hash_to_point` here, is included, as needed to generate generators. - +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![doc = include_str!("../README.md")] #![cfg_attr(not(feature = "std"), no_std)] use std_shims::{sync::OnceLock, vec::Vec}; @@ -14,16 +11,15 @@ use curve25519_dalek::edwards::{EdwardsPoint as DalekPoint}; use group::{Group, GroupEncoding}; use dalek_ff_group::EdwardsPoint; -mod varint; -use varint::write_varint; +use monero_io::{write_varint, decompress_point}; mod hash_to_point; -pub use hash_to_point::{hash_to_point, decompress_point}; +pub use hash_to_point::hash_to_point; #[cfg(test)] mod tests; -fn hash(data: &[u8]) -> [u8; 32] { +fn keccak256(data: &[u8]) -> [u8; 32] { Keccak256::digest(data).into() } @@ -32,7 +28,7 @@ static H_CELL: OnceLock = OnceLock::new(); #[allow(non_snake_case)] pub fn H() -> DalekPoint { *H_CELL.get_or_init(|| { - decompress_point(hash(&EdwardsPoint::generator().to_bytes())).unwrap().mul_by_cofactor() + decompress_point(keccak256(&EdwardsPoint::generator().to_bytes())).unwrap().mul_by_cofactor() }) } @@ -70,10 +66,10 @@ pub fn bulletproofs_generators(dst: &'static [u8]) -> Generators { even.extend(dst); let mut odd = even.clone(); - write_varint(&i.try_into().unwrap(), &mut even).unwrap(); - write_varint(&(i + 1).try_into().unwrap(), &mut odd).unwrap(); - res.H.push(EdwardsPoint(hash_to_point(hash(&even)))); - res.G.push(EdwardsPoint(hash_to_point(hash(&odd)))); + write_varint::, u64>(&i.try_into().unwrap(), &mut even).unwrap(); + write_varint::, u64>(&(i + 1).try_into().unwrap(), &mut odd).unwrap(); + res.H.push(EdwardsPoint(hash_to_point(keccak256(&even)))); + res.G.push(EdwardsPoint(hash_to_point(keccak256(&odd)))); } res } diff --git a/coins/monero/generators/src/varint.rs b/coins/monero/generators/src/varint.rs deleted file mode 100644 index 2e82816e..00000000 --- a/coins/monero/generators/src/varint.rs +++ /dev/null @@ -1,16 +0,0 @@ -use std_shims::io::{self, Write}; - -const VARINT_CONTINUATION_MASK: u8 = 0b1000_0000; -pub(crate) fn write_varint(varint: &u64, w: &mut W) -> io::Result<()> { - let mut varint = *varint; - while { - let mut b = u8::try_from(varint & u64::from(!VARINT_CONTINUATION_MASK)).unwrap(); - varint >>= 7; - if varint != 0 { - b |= VARINT_CONTINUATION_MASK; - } - w.write_all(&[b])?; - varint != 0 - } {} - Ok(()) -} diff --git a/coins/monero/io/Cargo.toml b/coins/monero/io/Cargo.toml new file mode 100644 index 00000000..f43f6448 --- /dev/null +++ b/coins/monero/io/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "monero-io" +version = "0.1.0" +description = "Serialization functions, as within the Monero protocol" +license = "MIT" +repository = "https://github.com/serai-dex/serai/tree/develop/coins/monero/io" +authors = ["Luke Parker "] +edition = "2021" + +[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 } + +curve25519-dalek = { version = "4", default-features = false, features = ["alloc"] } + +[features] +std = ["std-shims/std"] +default = ["std"] diff --git a/coins/monero/io/LICENSE b/coins/monero/io/LICENSE new file mode 100644 index 00000000..91d893c1 --- /dev/null +++ b/coins/monero/io/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/io/README.md b/coins/monero/io/README.md new file mode 100644 index 00000000..4ded71fb --- /dev/null +++ b/coins/monero/io/README.md @@ -0,0 +1,6 @@ +# Monero IO + +Serialization functions, as within 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/serialize.rs b/coins/monero/io/src/lib.rs similarity index 63% rename from coins/monero/src/serialize.rs rename to coins/monero/io/src/lib.rs index d2ae5980..0a8880b7 100644 --- a/coins/monero/src/serialize.rs +++ b/coins/monero/io/src/lib.rs @@ -1,12 +1,18 @@ +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![doc = include_str!("../README.md")] +#![cfg_attr(not(feature = "std"), no_std)] + use core::fmt::Debug; use std_shims::{ + vec, vec::Vec, io::{self, Read, Write}, }; -use curve25519_dalek::{scalar::Scalar, edwards::EdwardsPoint}; - -use monero_generators::decompress_point; +use curve25519_dalek::{ + scalar::Scalar, + edwards::{EdwardsPoint, CompressedEdwardsY}, +}; const VARINT_CONTINUATION_MASK: u8 = 0b1000_0000; @@ -29,17 +35,17 @@ mod sealed { } // This will panic if the VarInt exceeds u64::MAX -pub(crate) fn varint_len(varint: U) -> usize { +pub fn varint_len(varint: U) -> usize { let varint_u64: u64 = varint.try_into().map_err(|_| "varint exceeded u64").unwrap(); ((usize::try_from(u64::BITS - varint_u64.leading_zeros()).unwrap().saturating_sub(1)) / 7) + 1 } -pub(crate) fn write_byte(byte: &u8, w: &mut W) -> io::Result<()> { +pub fn write_byte(byte: &u8, w: &mut W) -> io::Result<()> { w.write_all(&[*byte]) } // This will panic if the VarInt exceeds u64::MAX -pub(crate) fn write_varint(varint: &U, w: &mut W) -> io::Result<()> { +pub fn write_varint(varint: &U, w: &mut W) -> io::Result<()> { let mut varint: u64 = (*varint).try_into().map_err(|_| "varint exceeded u64").unwrap(); while { let mut b = u8::try_from(varint & u64::from(!VARINT_CONTINUATION_MASK)).unwrap(); @@ -53,15 +59,15 @@ pub(crate) fn write_varint(varint: &U, w: &mut W) - Ok(()) } -pub(crate) fn write_scalar(scalar: &Scalar, w: &mut W) -> io::Result<()> { +pub fn write_scalar(scalar: &Scalar, w: &mut W) -> io::Result<()> { w.write_all(&scalar.to_bytes()) } -pub(crate) fn write_point(point: &EdwardsPoint, w: &mut W) -> io::Result<()> { +pub fn write_point(point: &EdwardsPoint, w: &mut W) -> io::Result<()> { w.write_all(&point.compress().to_bytes()) } -pub(crate) fn write_raw_vec io::Result<()>>( +pub fn write_raw_vec io::Result<()>>( f: F, values: &[T], w: &mut W, @@ -72,7 +78,7 @@ pub(crate) fn write_raw_vec io::Result<()>>( Ok(()) } -pub(crate) fn write_vec io::Result<()>>( +pub fn write_vec io::Result<()>>( f: F, values: &[T], w: &mut W, @@ -81,29 +87,29 @@ pub(crate) fn write_vec io::Result<()>>( write_raw_vec(f, values, w) } -pub(crate) fn read_bytes(r: &mut R) -> io::Result<[u8; N]> { +pub fn read_bytes(r: &mut R) -> io::Result<[u8; N]> { let mut res = [0; N]; r.read_exact(&mut res)?; Ok(res) } -pub(crate) fn read_byte(r: &mut R) -> io::Result { +pub fn read_byte(r: &mut R) -> io::Result { Ok(read_bytes::<_, 1>(r)?[0]) } -pub(crate) fn read_u16(r: &mut R) -> io::Result { +pub fn read_u16(r: &mut R) -> io::Result { read_bytes(r).map(u16::from_le_bytes) } -pub(crate) fn read_u32(r: &mut R) -> io::Result { +pub fn read_u32(r: &mut R) -> io::Result { read_bytes(r).map(u32::from_le_bytes) } -pub(crate) fn read_u64(r: &mut R) -> io::Result { +pub fn read_u64(r: &mut R) -> io::Result { read_bytes(r).map(u64::from_le_bytes) } -pub(crate) fn read_varint(r: &mut R) -> io::Result { +pub fn read_varint(r: &mut R) -> io::Result { let mut bits = 0; let mut res = 0; while { @@ -128,24 +134,34 @@ pub(crate) fn read_varint(r: &mut R) -> io::Result(r: &mut R) -> io::Result { +pub fn read_scalar(r: &mut R) -> io::Result { Option::from(Scalar::from_canonical_bytes(read_bytes(r)?)) .ok_or_else(|| io::Error::other("unreduced scalar")) } -pub(crate) fn read_point(r: &mut R) -> io::Result { +/// Decompress a canonically encoded ed25519 point. +/// +/// This function does not check if the point is within the prime order subgroup. +pub fn decompress_point(bytes: [u8; 32]) -> Option { + CompressedEdwardsY(bytes) + .decompress() + // Ban points which are either unreduced or -0 + .filter(|point| point.compress().to_bytes() == bytes) +} + +pub fn read_point(r: &mut R) -> io::Result { let bytes = read_bytes(r)?; decompress_point(bytes).ok_or_else(|| io::Error::other("invalid point")) } -pub(crate) fn read_torsion_free_point(r: &mut R) -> io::Result { +pub fn read_torsion_free_point(r: &mut R) -> io::Result { read_point(r) .ok() .filter(EdwardsPoint::is_torsion_free) .ok_or_else(|| io::Error::other("invalid point")) } -pub(crate) fn read_raw_vec io::Result>( +pub fn read_raw_vec io::Result>( f: F, len: usize, r: &mut R, @@ -157,16 +173,13 @@ pub(crate) fn read_raw_vec io::Result>( Ok(res) } -pub(crate) fn read_array io::Result, const N: usize>( +pub fn read_array io::Result, const N: usize>( f: F, r: &mut R, ) -> io::Result<[T; N]> { read_raw_vec(f, N, r).map(|vec| vec.try_into().unwrap()) } -pub(crate) fn read_vec io::Result>( - f: F, - r: &mut R, -) -> io::Result> { +pub fn read_vec io::Result>(f: F, r: &mut R) -> io::Result> { read_raw_vec(f, read_varint(r)?, r) } diff --git a/coins/monero/primitives/Cargo.toml b/coins/monero/primitives/Cargo.toml new file mode 100644 index 00000000..a6aebb60 --- /dev/null +++ b/coins/monero/primitives/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "monero-primitives" +version = "0.1.0" +description = "Primitives for the Monero protocol" +license = "MIT" +repository = "https://github.com/serai-dex/serai/tree/develop/coins/monero/primitives" +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 } + +rand_core = { version = "0.6", default-features = false } +zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] } + +# Cryptographic dependencies +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 } + +[features] +std = [ + "std-shims/std", + + "rand_core/std", + "zeroize/std", + + "sha3/std", + "curve25519-dalek/precomputed-tables", + + "monero-generators/std", +] +default = ["std"] diff --git a/coins/monero/primitives/LICENSE b/coins/monero/primitives/LICENSE new file mode 100644 index 00000000..91d893c1 --- /dev/null +++ b/coins/monero/primitives/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/primitives/README.md b/coins/monero/primitives/README.md new file mode 100644 index 00000000..bf30be57 --- /dev/null +++ b/coins/monero/primitives/README.md @@ -0,0 +1,6 @@ +# Monero Primitives + +Primitive structures and functions for the Monero protocol. + +This library is usable under no-std when the `std` feature (on by default) is +disabled. diff --git a/coins/monero/primitives/src/lib.rs b/coins/monero/primitives/src/lib.rs new file mode 100644 index 00000000..5da1d858 --- /dev/null +++ b/coins/monero/primitives/src/lib.rs @@ -0,0 +1,122 @@ +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![doc = include_str!("../README.md")] +#![cfg_attr(not(feature = "std"), no_std)] + +use std_shims::{vec, vec::Vec}; +#[cfg(feature = "std")] +use std_shims::sync::OnceLock; + +use zeroize::{Zeroize, ZeroizeOnDrop}; + +use sha3::{Digest, Keccak256}; +use curve25519_dalek::{ + constants::ED25519_BASEPOINT_POINT, + traits::VartimePrecomputedMultiscalarMul, + scalar::Scalar, + edwards::{EdwardsPoint, VartimeEdwardsPrecomputation}, +}; + +use monero_io::varint_len; +use monero_generators::H; + +// TODO: Replace this with a const +#[cfg(feature = "std")] +static INV_EIGHT_CELL: OnceLock = OnceLock::new(); +#[cfg(feature = "std")] +#[allow(non_snake_case)] +pub fn INV_EIGHT() -> Scalar { + *INV_EIGHT_CELL.get_or_init(|| Scalar::from(8u8).invert()) +} +#[cfg(not(feature = "std"))] +#[allow(non_snake_case)] +pub fn INV_EIGHT() -> Scalar { + Scalar::from(8u8).invert() +} + +// On std, we cache this in a static +// In no-std environments, we prefer the reduced memory use and calculate it ad-hoc +#[cfg(feature = "std")] +static BASEPOINT_PRECOMP_CELL: OnceLock = OnceLock::new(); +#[cfg(feature = "std")] +#[allow(non_snake_case)] +pub fn BASEPOINT_PRECOMP() -> &'static VartimeEdwardsPrecomputation { + BASEPOINT_PRECOMP_CELL + .get_or_init(|| VartimeEdwardsPrecomputation::new([ED25519_BASEPOINT_POINT])) +} +#[cfg(not(feature = "std"))] +#[allow(non_snake_case)] +pub fn BASEPOINT_PRECOMP() -> VartimeEdwardsPrecomputation { + VartimeEdwardsPrecomputation::new([ED25519_BASEPOINT_POINT]) +} + +pub fn keccak256(data: impl AsRef<[u8]>) -> [u8; 32] { + Keccak256::digest(data.as_ref()).into() +} + +/// Hash the provided data to a scalar via keccak256(data) % l. +pub fn keccak256_to_scalar(data: impl AsRef<[u8]>) -> Scalar { + let scalar = Scalar::from_bytes_mod_order(keccak256(data.as_ref())); + // Monero will explicitly error in this case + // This library acknowledges its practical impossibility of it occurring, and doesn't bother to + // code in logic to handle it. That said, if it ever occurs, something must happen in order to + // not generate/verify a proof we believe to be valid when it isn't + assert!(scalar != Scalar::ZERO, "ZERO HASH: {:?}", data.as_ref()); + scalar +} + +/// Transparent structure representing a Pedersen commitment's contents. +#[allow(non_snake_case)] +#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)] +pub struct Commitment { + pub mask: Scalar, + pub amount: u64, +} + +impl core::fmt::Debug for Commitment { + fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + fmt.debug_struct("Commitment").field("amount", &self.amount).finish_non_exhaustive() + } +} + +impl Commitment { + /// A commitment to zero, defined with a mask of 1 (as to not be the identity). + pub fn zero() -> Commitment { + Commitment { mask: Scalar::ONE, amount: 0 } + } + + pub fn new(mask: Scalar, amount: u64) -> Commitment { + Commitment { mask, amount } + } + + /// Calculate a Pedersen commitment, as a point, from the transparent structure. + pub fn calculate(&self) -> EdwardsPoint { + EdwardsPoint::vartime_double_scalar_mul_basepoint(&Scalar::from(self.amount), &H(), &self.mask) + } +} + +/// Decoy data, containing the actual member as well (at index `i`). +#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)] +pub struct Decoys { + pub i: u8, + pub offsets: Vec, + pub ring: Vec<[EdwardsPoint; 2]>, +} + +#[allow(clippy::len_without_is_empty)] +impl Decoys { + pub fn fee_weight(offsets: &[u64]) -> usize { + varint_len(offsets.len()) + offsets.iter().map(|offset| varint_len(*offset)).sum::() + } + + pub fn len(&self) -> usize { + self.offsets.len() + } + + pub fn indexes(&self) -> Vec { + let mut res = vec![self.offsets[0]; self.len()]; + for m in 1 .. res.len() { + res[m] = res[m - 1] + self.offsets[m]; + } + res + } +} diff --git a/coins/monero/ringct/clsag/Cargo.toml b/coins/monero/ringct/clsag/Cargo.toml new file mode 100644 index 00000000..dfb2fd49 --- /dev/null +++ b/coins/monero/ringct/clsag/Cargo.toml @@ -0,0 +1,66 @@ +[package] +name = "monero-clsag" +version = "0.1.0" +description = "The CLSAG linkable ring signature, as defined by the Monero protocol" +license = "MIT" +repository = "https://github.com/serai-dex/serai/tree/develop/coins/monero/ringct/clsag" +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 } + +thiserror = { version = "1", default-features = false, optional = true } + +rand_core = { version = "0.6", default-features = false } +zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] } +subtle = { version = "^2.4", default-features = false } + +# Cryptographic dependencies +sha3 = { version = "0.10", default-features = false } +curve25519-dalek = { version = "4", default-features = false, features = ["alloc", "zeroize"] } + +# Multisig dependencies +rand_chacha = { version = "0.3", default-features = false, optional = true } +transcript = { package = "flexible-transcript", path = "../../../../crypto/transcript", version = "0.3", default-features = false, features = ["recommended"], optional = true } +group = { version = "0.13", default-features = false, optional = true } +dalek-ff-group = { path = "../../../../crypto/dalek-ff-group", version = "0.4", default-features = false, optional = true } +frost = { package = "modular-frost", path = "../../../../crypto/frost", default-features = false, features = ["ed25519"], optional = true } + +# 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", + + "thiserror", + + "rand_core/std", + "zeroize/std", + "subtle/std", + + "sha3/std", + "curve25519-dalek/precomputed-tables", + + "rand_chacha?/std", + "transcript?/std", + "group?/alloc", + "dalek-ff-group?/std", + + "monero-io/std", + "monero-generators/std", + "monero-primitives/std", +] +multisig = ["rand_chacha", "transcript", "group", "dalek-ff-group", "frost", "std"] +default = ["std"] diff --git a/coins/monero/ringct/clsag/LICENSE b/coins/monero/ringct/clsag/LICENSE new file mode 100644 index 00000000..91d893c1 --- /dev/null +++ b/coins/monero/ringct/clsag/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/clsag/README.md b/coins/monero/ringct/clsag/README.md new file mode 100644 index 00000000..a589d69c --- /dev/null +++ b/coins/monero/ringct/clsag/README.md @@ -0,0 +1,6 @@ +# Monero CLSAG + +The CLSAG linkable ring signature, as defined 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/clsag/mod.rs b/coins/monero/ringct/clsag/src/lib.rs similarity index 90% rename from coins/monero/src/ringct/clsag/mod.rs rename to coins/monero/ringct/clsag/src/lib.rs index c5ba81d8..1abae389 100644 --- a/coins/monero/src/ringct/clsag/mod.rs +++ b/coins/monero/ringct/clsag/src/lib.rs @@ -1,3 +1,6 @@ +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![doc = include_str!("../README.md")] +#![cfg_attr(not(feature = "std"), no_std)] #![allow(non_snake_case)] use core::ops::Deref; @@ -18,15 +21,14 @@ use curve25519_dalek::{ edwards::{EdwardsPoint, VartimeEdwardsPrecomputation}, }; -use crate::{ - INV_EIGHT, BASEPOINT_PRECOMP, Commitment, random_scalar, hash_to_scalar, wallet::decoys::Decoys, - ringct::hash_to_point, serialize::*, -}; +use monero_io::*; +use monero_generators::hash_to_point; +use monero_primitives::{INV_EIGHT, BASEPOINT_PRECOMP, Commitment, Decoys, keccak256_to_scalar}; #[cfg(feature = "multisig")] mod multisig; #[cfg(feature = "multisig")] -pub(crate) use multisig::{ClsagDetails, ClsagAddendum, ClsagMultisig}; +pub use multisig::{ClsagDetails, ClsagAddendum, ClsagMultisig}; /// Errors when working with CLSAGs. #[derive(Clone, Copy, PartialEq, Eq, Debug)] @@ -59,9 +61,9 @@ pub enum ClsagError { #[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)] pub struct ClsagInput { // The actual commitment for the true spend - pub(crate) commitment: Commitment, + pub commitment: Commitment, // True spend index, offsets, and ring - pub(crate) decoys: Decoys, + pub decoys: Decoys, } impl ClsagInput { @@ -139,10 +141,10 @@ fn core( to_hash.extend(D_INV_EIGHT.compress().to_bytes()); to_hash.extend(pseudo_out.compress().to_bytes()); // mu_P with agg_0 - let mu_P = hash_to_scalar(&to_hash); + let mu_P = keccak256_to_scalar(&to_hash); // mu_C with agg_1 to_hash[PREFIX_AGG_0_LEN - 1] = b'1'; - let mu_C = hash_to_scalar(&to_hash); + let mu_C = keccak256_to_scalar(&to_hash); // Truncate it for the round transcript, altering the DST as needed to_hash.truncate(((2 * n) + 1) * 32); @@ -164,7 +166,7 @@ fn core( end = r + n; to_hash.extend(A.compress().to_bytes()); to_hash.extend(AH.compress().to_bytes()); - c = hash_to_scalar(&to_hash); + c = keccak256_to_scalar(&to_hash); } Mode::Verify(c1) => { @@ -190,7 +192,7 @@ fn core( } }; - let PH = hash_to_point(&P[i]); + let PH = hash_to_point(P[i].compress().0); // (c_p * I) + (c_c * D) + (s_i * PH) let R = match A_c1 { @@ -203,7 +205,7 @@ fn core( to_hash.truncate(((2 * n) + 3) * 32); to_hash.extend(L.compress().to_bytes()); to_hash.extend(R.compress().to_bytes()); - c = hash_to_scalar(&to_hash); + c = keccak256_to_scalar(&to_hash); // This will only execute once and shouldn't need to be constant time. Making it constant time // removes the risk of branch prediction creating timing differences depending on ring index @@ -220,7 +222,7 @@ fn core( pub struct Clsag { D: EdwardsPoint, pub(crate) s: Vec, - pub(crate) c1: Scalar, + pub c1: Scalar, } pub(crate) struct ClsagSignCore { @@ -247,11 +249,11 @@ impl Clsag { let pseudo_out = Commitment::new(mask, input.commitment.amount).calculate(); let mask_delta = input.commitment.mask - mask; - let H = hash_to_point(&input.decoys.ring[r][0]); + let H = hash_to_point(input.decoys.ring[r][0].compress().0); let D = H * mask_delta; let mut s = Vec::with_capacity(input.decoys.ring.len()); for _ in 0 .. input.decoys.ring.len() { - s.push(random_scalar(rng)); + s.push(Scalar::random(rng)); } let ((D, c_p, c_c), c1) = core(&input.decoys.ring, I, &pseudo_out, msg, &D, &s, &Mode::Sign(r, A, AH)); @@ -268,7 +270,7 @@ impl Clsag { /// /// inputs is of the form (private key, key image, input). /// sum_outputs is for the sum of the outputs' commitment masks. - pub(crate) fn sign( + pub fn sign( rng: &mut R, mut inputs: Vec<(Zeroizing, EdwardsPoint, ClsagInput)>, sum_outputs: Scalar, @@ -277,14 +279,14 @@ impl Clsag { let mut res = Vec::with_capacity(inputs.len()); let mut sum_pseudo_outs = Scalar::ZERO; for i in 0 .. inputs.len() { - let mut mask = random_scalar(rng); + let mut mask = Scalar::random(rng); if i == (inputs.len() - 1) { mask = sum_outputs - sum_pseudo_outs; } else { sum_pseudo_outs += mask; } - let mut nonce = Zeroizing::new(random_scalar(rng)); + let mut nonce = Zeroizing::new(Scalar::random(rng)); let ClsagSignCore { mut incomplete_clsag, pseudo_out, key_challenge, challenged_mask } = Clsag::sign_core( rng, @@ -294,7 +296,9 @@ impl Clsag { &msg, nonce.deref() * ED25519_BASEPOINT_TABLE, nonce.deref() * - hash_to_point(&inputs[i].2.decoys.ring[usize::from(inputs[i].2.decoys.i)][0]), + hash_to_point( + inputs[i].2.decoys.ring[usize::from(inputs[i].2.decoys.i)][0].compress().0, + ), ); // Effectively r - cx, except cx is (c_p x) + (c_c z), where z is the delta between a ring // member's commitment and our input commitment (which will only have a known discrete log @@ -349,7 +353,7 @@ impl Clsag { Ok(()) } - pub(crate) fn fee_weight(ring_len: usize) -> usize { + pub fn fee_weight(ring_len: usize) -> usize { (ring_len * 32) + 32 + 32 } diff --git a/coins/monero/src/ringct/clsag/multisig.rs b/coins/monero/ringct/clsag/src/multisig.rs similarity index 96% rename from coins/monero/src/ringct/clsag/multisig.rs rename to coins/monero/ringct/clsag/src/multisig.rs index cc531ee0..d8e29475 100644 --- a/coins/monero/src/ringct/clsag/multisig.rs +++ b/coins/monero/ringct/clsag/src/multisig.rs @@ -26,10 +26,9 @@ use frost::{ algorithm::{WriteAddendum, Algorithm}, }; -use crate::ringct::{ - hash_to_point, - clsag::{ClsagInput, Clsag}, -}; +use monero_generators::hash_to_point; + +use crate::{ClsagInput, Clsag}; impl ClsagInput { fn transcript(&self, transcript: &mut T) { @@ -57,13 +56,13 @@ impl ClsagInput { /// CLSAG input and the mask to use for it. #[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)] -pub(crate) struct ClsagDetails { +pub struct ClsagDetails { input: ClsagInput, mask: Scalar, } impl ClsagDetails { - pub(crate) fn new(input: ClsagInput, mask: Scalar) -> ClsagDetails { + pub fn new(input: ClsagInput, mask: Scalar) -> ClsagDetails { ClsagDetails { input, mask } } } @@ -71,7 +70,7 @@ impl ClsagDetails { /// Addendum produced during the FROST signing process with relevant data. #[derive(Clone, PartialEq, Eq, Zeroize, Debug)] pub struct ClsagAddendum { - pub(crate) key_image: dfg::EdwardsPoint, + pub key_image: dfg::EdwardsPoint, } impl WriteAddendum for ClsagAddendum { @@ -93,10 +92,10 @@ struct Interim { /// FROST algorithm for producing a CLSAG signature. #[allow(non_snake_case)] #[derive(Clone, Debug)] -pub(crate) struct ClsagMultisig { +pub struct ClsagMultisig { transcript: RecommendedTranscript, - pub(crate) H: EdwardsPoint, + pub H: EdwardsPoint, key_image_shares: HashMap<[u8; 32], dfg::EdwardsPoint>, image: Option, @@ -107,7 +106,7 @@ pub(crate) struct ClsagMultisig { } impl ClsagMultisig { - pub(crate) fn new( + pub fn new( transcript: RecommendedTranscript, output_key: EdwardsPoint, details: Arc>>, @@ -115,7 +114,7 @@ impl ClsagMultisig { ClsagMultisig { transcript, - H: hash_to_point(&output_key), + H: hash_to_point(output_key.compress().0), key_image_shares: HashMap::new(), image: None, diff --git a/coins/monero/src/bin/reserialize_chain.rs b/coins/monero/src/bin/reserialize_chain.rs index f2ebfcc1..244e2e1f 100644 --- a/coins/monero/src/bin/reserialize_chain.rs +++ b/coins/monero/src/bin/reserialize_chain.rs @@ -17,7 +17,7 @@ mod binaries { rpc::{RpcError, Rpc, HttpRpc}, }; - pub(crate) use monero_generators::decompress_point; + pub(crate) use monero_io::decompress_point; pub(crate) use tokio::task::JoinHandle; diff --git a/coins/monero/src/lib.rs b/coins/monero/src/lib.rs index 4e6b26d1..5bbe60e9 100644 --- a/coins/monero/src/lib.rs +++ b/coins/monero/src/lib.rs @@ -6,26 +6,21 @@ #[macro_use] extern crate alloc; -use std_shims::{sync::OnceLock, io}; +use std_shims::io; -use rand_core::{RngCore, CryptoRng}; - -use zeroize::{Zeroize, ZeroizeOnDrop}; +use zeroize::Zeroize; use sha3::{Digest, Keccak256}; -use curve25519_dalek::{ - constants::{ED25519_BASEPOINT_TABLE, ED25519_BASEPOINT_POINT}, - scalar::Scalar, - edwards::{EdwardsPoint, VartimeEdwardsPrecomputation}, - traits::VartimePrecomputedMultiscalarMul, -}; +use curve25519_dalek::scalar::Scalar; -pub use monero_generators::{H, decompress_point}; +pub use monero_io::decompress_point; +pub use monero_generators::H; +pub use monero_primitives::INV_EIGHT; mod merkle; -mod serialize; +use monero_io as serialize; use serialize::{read_byte, read_u16}; /// UnreducedScalar struct with functionality for recovering incorrectly reduced scalars. @@ -55,19 +50,6 @@ pub const DEFAULT_LOCK_WINDOW: usize = 10; pub const COINBASE_LOCK_WINDOW: usize = 60; pub const BLOCK_TIME: usize = 120; -static INV_EIGHT_CELL: OnceLock = OnceLock::new(); -#[allow(non_snake_case)] -pub(crate) fn INV_EIGHT() -> Scalar { - *INV_EIGHT_CELL.get_or_init(|| Scalar::from(8u8).invert()) -} - -static BASEPOINT_PRECOMP_CELL: OnceLock = OnceLock::new(); -#[allow(non_snake_case)] -pub(crate) fn BASEPOINT_PRECOMP() -> &'static VartimeEdwardsPrecomputation { - BASEPOINT_PRECOMP_CELL - .get_or_init(|| VartimeEdwardsPrecomputation::new([ED25519_BASEPOINT_POINT])) -} - /// Monero protocol version. /// /// v15 is omitted as v15 was simply v14 and v16 being active at the same time, with regards to the @@ -188,42 +170,7 @@ impl Protocol { } } -/// Transparent structure representing a Pedersen commitment's contents. -#[allow(non_snake_case)] -#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)] -pub struct Commitment { - pub mask: Scalar, - pub amount: u64, -} - -impl core::fmt::Debug for Commitment { - fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { - fmt.debug_struct("Commitment").field("amount", &self.amount).finish_non_exhaustive() - } -} - -impl Commitment { - /// A commitment to zero, defined with a mask of 1 (as to not be the identity). - pub fn zero() -> Commitment { - Commitment { mask: Scalar::ONE, amount: 0 } - } - - pub fn new(mask: Scalar, amount: u64) -> Commitment { - Commitment { mask, amount } - } - - /// Calculate a Pedersen commitment, as a point, from the transparent structure. - pub fn calculate(&self) -> EdwardsPoint { - (&self.mask * ED25519_BASEPOINT_TABLE) + (Scalar::from(self.amount) * H()) - } -} - -/// Support generating a random scalar using a modern rand, as dalek's is notoriously dated. -pub fn random_scalar(rng: &mut R) -> Scalar { - let mut r = [0; 64]; - rng.fill_bytes(&mut r); - Scalar::from_bytes_mod_order_wide(&r) -} +pub use monero_primitives::Commitment; pub(crate) fn hash(data: &[u8]) -> [u8; 32] { Keccak256::digest(data).into() diff --git a/coins/monero/src/ringct/mod.rs b/coins/monero/src/ringct/mod.rs index 3780c5da..9413d48c 100644 --- a/coins/monero/src/ringct/mod.rs +++ b/coins/monero/src/ringct/mod.rs @@ -14,7 +14,7 @@ pub use hash_to_point::{raw_hash_to_point, hash_to_point}; /// MLSAG struct, along with verifying functionality. pub mod mlsag; /// CLSAG struct, along with signing and verifying functionality. -pub mod clsag; +pub use monero_clsag as clsag; /// BorromeanRange struct, along with verifying functionality. pub mod borromean; /// Bulletproofs(+) structs, along with proving and verifying functionality. diff --git a/coins/monero/src/rpc/mod.rs b/coins/monero/src/rpc/mod.rs index c0a8eae2..550abb73 100644 --- a/coins/monero/src/rpc/mod.rs +++ b/coins/monero/src/rpc/mod.rs @@ -11,7 +11,7 @@ use async_trait::async_trait; use curve25519_dalek::edwards::EdwardsPoint; -use monero_generators::decompress_point; +use monero_io::decompress_point; use serde::{Serialize, Deserialize, de::DeserializeOwned}; use serde_json::{Value, json}; diff --git a/coins/monero/src/tests/address.rs b/coins/monero/src/tests/address.rs index e26901e4..3e8ddfc1 100644 --- a/coins/monero/src/tests/address.rs +++ b/coins/monero/src/tests/address.rs @@ -2,14 +2,11 @@ use hex_literal::hex; use rand_core::{RngCore, OsRng}; -use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE; +use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar}; -use monero_generators::decompress_point; +use monero_io::decompress_point; -use crate::{ - random_scalar, - wallet::address::{Network, AddressType, AddressMeta, MoneroAddress}, -}; +use crate::wallet::address::{Network, AddressType, AddressMeta, MoneroAddress}; const SPEND: [u8; 32] = hex!("f8631661f6ab4e6fda310c797330d86e23a682f20d5bc8cc27b18051191f16d7"); const VIEW: [u8; 32] = hex!("4a1535063ad1fee2dabbf909d4fd9a873e29541b401f0944754e17c9a41820ce"); @@ -75,8 +72,8 @@ fn featured() { [(Network::Mainnet, 'C'), (Network::Testnet, 'K'), (Network::Stagenet, 'F')] { for _ in 0 .. 100 { - let spend = &random_scalar(&mut OsRng) * ED25519_BASEPOINT_TABLE; - let view = &random_scalar(&mut OsRng) * ED25519_BASEPOINT_TABLE; + let spend = &Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE; + let view = &Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE; for features in 0 .. (1 << 3) { const SUBADDRESS_FEATURE_BIT: u8 = 1; diff --git a/coins/monero/src/tests/bulletproofs/mod.rs b/coins/monero/src/tests/bulletproofs/mod.rs index 9ffb7bc3..51c02a11 100644 --- a/coins/monero/src/tests/bulletproofs/mod.rs +++ b/coins/monero/src/tests/bulletproofs/mod.rs @@ -2,11 +2,11 @@ use hex_literal::hex; use rand_core::OsRng; use curve25519_dalek::scalar::Scalar; -use monero_generators::decompress_point; +use monero_io::decompress_point; use multiexp::BatchVerifier; use crate::{ - Commitment, random_scalar, + Commitment, ringct::bulletproofs::{Bulletproof, original::OriginalStruct}, wallet::TransactionError, }; @@ -68,7 +68,7 @@ macro_rules! bulletproofs_tests { let mut verifier = BatchVerifier::new(16); for i in 1 ..= 16 { let commitments = (1 ..= i) - .map(|i| Commitment::new(random_scalar(&mut OsRng), u64::try_from(i).unwrap())) + .map(|i| Commitment::new(Scalar::random(&mut OsRng), u64::try_from(i).unwrap())) .collect::>(); let bp = if $plus { diff --git a/coins/monero/src/tests/clsag.rs b/coins/monero/src/tests/clsag.rs index a17d7ba2..24444b0f 100644 --- a/coins/monero/src/tests/clsag.rs +++ b/coins/monero/src/tests/clsag.rs @@ -13,7 +13,7 @@ use transcript::{Transcript, RecommendedTranscript}; use frost::curve::Ed25519; use crate::{ - Commitment, random_scalar, + Commitment, wallet::Decoys, ringct::{ generate_key_image, @@ -43,8 +43,8 @@ fn clsag() { let mut secrets = (Zeroizing::new(Scalar::ZERO), Scalar::ZERO); let mut ring = vec![]; for i in 0 .. RING_LEN { - let dest = Zeroizing::new(random_scalar(&mut OsRng)); - let mask = random_scalar(&mut OsRng); + let dest = Zeroizing::new(Scalar::random(&mut OsRng)); + let mask = Scalar::random(&mut OsRng); let amount; if i == real { secrets = (dest.clone(), mask); @@ -72,7 +72,7 @@ fn clsag() { ) .unwrap(), )], - random_scalar(&mut OsRng), + Scalar::random(&mut OsRng), msg, ) .swap_remove(0); @@ -80,7 +80,7 @@ fn clsag() { clsag.verify(&ring, &image, &pseudo_out, &msg).unwrap(); // make sure verification fails if we throw a random `c1` at it. - clsag.c1 = random_scalar(&mut OsRng); + clsag.c1 = Scalar::random(&mut OsRng); assert!(clsag.verify(&ring, &image, &pseudo_out, &msg).is_err()); } } @@ -90,15 +90,15 @@ fn clsag() { fn clsag_multisig() { let keys = key_gen::<_, Ed25519>(&mut OsRng); - let randomness = random_scalar(&mut OsRng); + let randomness = Scalar::random(&mut OsRng); let mut ring = vec![]; for i in 0 .. RING_LEN { let dest; let mask; let amount; if i != u64::from(RING_INDEX) { - dest = &random_scalar(&mut OsRng) * ED25519_BASEPOINT_TABLE; - mask = random_scalar(&mut OsRng); + dest = &Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE; + mask = Scalar::random(&mut OsRng); amount = OsRng.next_u64(); } else { dest = keys[&Participant::new(1).unwrap()].group_key().0; @@ -108,7 +108,7 @@ fn clsag_multisig() { ring.push([dest, Commitment::new(mask, amount).calculate()]); } - let mask_sum = random_scalar(&mut OsRng); + let mask_sum = Scalar::random(&mut OsRng); let algorithm = ClsagMultisig::new( RecommendedTranscript::new(b"Monero Serai CLSAG Test"), keys[&Participant::new(1).unwrap()].group_key().0, diff --git a/coins/monero/src/wallet/address.rs b/coins/monero/src/wallet/address.rs index d080488d..e5cc3dc9 100644 --- a/coins/monero/src/wallet/address.rs +++ b/coins/monero/src/wallet/address.rs @@ -5,7 +5,7 @@ use zeroize::Zeroize; use curve25519_dalek::edwards::EdwardsPoint; -use monero_generators::decompress_point; +use monero_io::decompress_point; use base58_monero::base58::{encode_check, decode_check}; diff --git a/coins/monero/src/wallet/decoys.rs b/coins/monero/src/wallet/decoys.rs index 0cdff89a..effc4887 100644 --- a/coins/monero/src/wallet/decoys.rs +++ b/coins/monero/src/wallet/decoys.rs @@ -1,6 +1,6 @@ use std_shims::{vec::Vec, collections::HashSet}; -use zeroize::{Zeroize, ZeroizeOnDrop}; +use zeroize::Zeroize; use rand_core::{RngCore, CryptoRng}; use rand_distr::{Distribution, Gamma}; @@ -10,7 +10,6 @@ use rand_distr::num_traits::Float; use curve25519_dalek::edwards::EdwardsPoint; use crate::{ - serialize::varint_len, wallet::SpendableOutput, rpc::{RpcError, RpcConnection, Rpc}, DEFAULT_LOCK_WINDOW, COINBASE_LOCK_WINDOW, BLOCK_TIME, @@ -272,35 +271,38 @@ async fn select_decoys( Ok(res) } -/// Decoy data, containing the actual member as well (at index `i`). -#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)] -pub struct Decoys { - pub(crate) i: u8, - pub(crate) offsets: Vec, - pub(crate) ring: Vec<[EdwardsPoint; 2]>, +pub use monero_primitives::Decoys; + +// TODO: Remove this trait +#[cfg(feature = "std")] +#[async_trait::async_trait] +pub trait DecoySelection { + async fn select( + rng: &mut R, + rpc: &Rpc, + ring_len: usize, + height: usize, + inputs: &[SpendableOutput], + ) -> Result, RpcError>; + + async fn fingerprintable_canonical_select< + R: Send + Sync + RngCore + CryptoRng, + RPC: Send + Sync + RpcConnection, + >( + rng: &mut R, + rpc: &Rpc, + ring_len: usize, + height: usize, + inputs: &[SpendableOutput], + ) -> Result, RpcError>; } -#[allow(clippy::len_without_is_empty)] -impl Decoys { - pub fn fee_weight(offsets: &[u64]) -> usize { - varint_len(offsets.len()) + offsets.iter().map(|offset| varint_len(*offset)).sum::() - } - - pub fn len(&self) -> usize { - self.offsets.len() - } - - pub fn indexes(&self) -> Vec { - let mut res = vec![self.offsets[0]; self.len()]; - for m in 1 .. res.len() { - res[m] = res[m - 1] + self.offsets[m]; - } - res - } - +#[cfg(feature = "std")] +#[async_trait::async_trait] +impl DecoySelection for Decoys { /// Select decoys using the same distribution as Monero. Relies on the monerod RPC /// response for an output's unlocked status, minimizing trips to the daemon. - pub async fn select( + async fn select( rng: &mut R, rpc: &Rpc, ring_len: usize, @@ -318,7 +320,10 @@ impl Decoys { /// /// TODO: upstream change to monerod get_outs RPC to accept a height param for checking /// output's unlocked status and remove all usage of fingerprintable_canonical - pub async fn fingerprintable_canonical_select( + async fn fingerprintable_canonical_select< + R: Send + Sync + RngCore + CryptoRng, + RPC: Send + Sync + RpcConnection, + >( rng: &mut R, rpc: &Rpc, ring_len: usize, diff --git a/coins/monero/src/wallet/mod.rs b/coins/monero/src/wallet/mod.rs index 7ccf27a5..7debf3ed 100644 --- a/coins/monero/src/wallet/mod.rs +++ b/coins/monero/src/wallet/mod.rs @@ -10,7 +10,8 @@ use curve25519_dalek::{ }; use crate::{ - hash, hash_to_scalar, serialize::write_varint, Commitment, ringct::EncryptedAmount, transaction::Input, + hash, hash_to_scalar, serialize::write_varint, Commitment, ringct::EncryptedAmount, + transaction::Input, }; pub mod extra; @@ -26,8 +27,14 @@ use address::{Network, AddressType, SubaddressIndex, AddressSpec, AddressMeta, M mod scan; pub use scan::{ReceivedOutput, SpendableOutput, Timelocked}; +#[cfg(feature = "std")] pub mod decoys; -pub use decoys::Decoys; +#[cfg(not(feature = "std"))] +pub mod decoys { + pub use monero_primitives::Decoys; + pub trait DecoySelection {} +} +pub use decoys::{DecoySelection, Decoys}; mod send; pub use send::{FeePriority, Fee, TransactionError, Change, SignableTransaction, Eventuality}; diff --git a/coins/monero/src/wallet/scan.rs b/coins/monero/src/wallet/scan.rs index f9a33d47..46ddceed 100644 --- a/coins/monero/src/wallet/scan.rs +++ b/coins/monero/src/wallet/scan.rs @@ -9,7 +9,7 @@ use zeroize::{Zeroize, ZeroizeOnDrop}; use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar, edwards::EdwardsPoint}; -use monero_generators::decompress_point; +use monero_io::decompress_point; use crate::{ Commitment, diff --git a/coins/monero/src/wallet/seed/classic.rs b/coins/monero/src/wallet/seed/classic.rs index 0605e4bc..4acb8991 100644 --- a/coins/monero/src/wallet/seed/classic.rs +++ b/coins/monero/src/wallet/seed/classic.rs @@ -11,7 +11,7 @@ use rand_core::{RngCore, CryptoRng}; use curve25519_dalek::scalar::Scalar; -use crate::{random_scalar, wallet::seed::SeedError}; +use crate::wallet::seed::SeedError; pub(crate) const CLASSIC_SEED_LENGTH: usize = 24; pub(crate) const CLASSIC_SEED_LENGTH_WITH_CHECKSUM: usize = 25; @@ -276,7 +276,7 @@ pub(crate) fn seed_to_bytes(lang: Language, words: &str) -> Result); impl ClassicSeed { pub(crate) fn new(rng: &mut R, lang: Language) -> ClassicSeed { - key_to_seed(lang, Zeroizing::new(random_scalar(rng))) + key_to_seed(lang, Zeroizing::new(Scalar::random(rng))) } #[allow(clippy::needless_pass_by_value)] diff --git a/coins/monero/src/wallet/send/mod.rs b/coins/monero/src/wallet/send/mod.rs index 565c101b..9f26461a 100644 --- a/coins/monero/src/wallet/send/mod.rs +++ b/coins/monero/src/wallet/send/mod.rs @@ -23,7 +23,7 @@ use dalek_ff_group as dfg; use frost::FrostError; use crate::{ - Protocol, Commitment, hash, random_scalar, + Protocol, Commitment, hash, serialize::{ read_byte, read_bytes, read_u64, read_scalar, read_point, read_vec, write_byte, write_scalar, write_point, write_raw_vec, write_vec, @@ -616,7 +616,7 @@ impl SignableTransaction { payments.shuffle(&mut rng); // Used for all non-subaddress outputs, or if there's only one subaddress output and a change - let tx_key = Zeroizing::new(random_scalar(&mut rng)); + let tx_key = Zeroizing::new(Scalar::random(&mut rng)); let mut tx_public_key = tx_key.deref() * ED25519_BASEPOINT_TABLE; // If any of these outputs are to a subaddress, we need keys distinct to them @@ -660,7 +660,7 @@ impl SignableTransaction { let (output, payment_id) = match payment { InternalPayment::Payment(payment, need_dummy_payment_id) => { // If this is a subaddress, generate a dedicated r. Else, reuse the TX key - let dedicated = Zeroizing::new(random_scalar(&mut rng)); + let dedicated = Zeroizing::new(Scalar::random(&mut rng)); let use_dedicated = additional && payment.0.is_subaddress(); let r = if use_dedicated { &dedicated } else { &tx_key }; diff --git a/coins/monero/src/wallet/send/multisig.rs b/coins/monero/src/wallet/send/multisig.rs index a5be404a..cdff2d68 100644 --- a/coins/monero/src/wallet/send/multisig.rs +++ b/coins/monero/src/wallet/send/multisig.rs @@ -26,7 +26,6 @@ use frost::{ }; use crate::{ - random_scalar, ringct::{ clsag::{ClsagInput, ClsagDetails, ClsagAddendum, ClsagMultisig}, RctPrunable, @@ -348,7 +347,7 @@ impl SignMachine for TransactionSignMachine { while !sorted.is_empty() { let value = sorted.remove(0); - let mut mask = random_scalar(&mut rng); + let mut mask = Scalar::random(&mut rng); if sorted.is_empty() { mask = output_masks - sum_pseudo_outs; } else { diff --git a/coins/monero/tests/runner.rs b/coins/monero/tests/runner.rs index 9cef6c21..fe77f552 100644 --- a/coins/monero/tests/runner.rs +++ b/coins/monero/tests/runner.rs @@ -9,7 +9,6 @@ use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar}; use tokio::sync::Mutex; use monero_serai::{ - random_scalar, rpc::{HttpRpc, Rpc}, wallet::{ ViewPair, Scanner, @@ -21,9 +20,9 @@ use monero_serai::{ }; pub fn random_address() -> (Scalar, ViewPair, MoneroAddress) { - let spend = random_scalar(&mut OsRng); + let spend = Scalar::random(&mut OsRng); let spend_pub = &spend * ED25519_BASEPOINT_TABLE; - let view = Zeroizing::new(random_scalar(&mut OsRng)); + let view = Zeroizing::new(Scalar::random(&mut OsRng)); ( spend, ViewPair::new(spend_pub, view.clone()), @@ -103,8 +102,8 @@ pub async fn rpc() -> Rpc { let addr = MoneroAddress { meta: AddressMeta::new(Network::Mainnet, AddressType::Standard), - spend: &random_scalar(&mut OsRng) * ED25519_BASEPOINT_TABLE, - view: &random_scalar(&mut OsRng) * ED25519_BASEPOINT_TABLE, + spend: &Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE, + view: &Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE, } .to_string(); @@ -161,7 +160,7 @@ macro_rules! test { use zeroize::Zeroizing; use rand_core::OsRng; - use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE; + use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar}; #[cfg(feature = "multisig")] use transcript::{Transcript, RecommendedTranscript}; @@ -173,9 +172,9 @@ macro_rules! test { }; use monero_serai::{ - random_scalar, wallet::{ - address::{Network, AddressSpec}, ViewPair, Scanner, Change, Decoys, FeePriority, + address::{Network, AddressSpec}, + ViewPair, Scanner, Change, DecoySelection, Decoys, FeePriority, SignableTransaction, SignableTransactionBuilder, }, }; @@ -196,7 +195,7 @@ macro_rules! test { continue; } - let spend = Zeroizing::new(random_scalar(&mut OsRng)); + let spend = Zeroizing::new(Scalar::random(&mut OsRng)); #[cfg(feature = "multisig")] let keys = key_gen::<_, Ed25519>(&mut OsRng); @@ -211,7 +210,7 @@ macro_rules! test { let rpc = rpc().await; - let view = ViewPair::new(spend_pub, Zeroizing::new(random_scalar(&mut OsRng))); + let view = ViewPair::new(spend_pub, Zeroizing::new(Scalar::random(&mut OsRng))); let addr = view.address(Network::Mainnet, AddressSpec::Standard); let miner_tx = get_miner_tx_output(&rpc, &view).await; @@ -223,8 +222,8 @@ macro_rules! test { rpc.get_fee(protocol, FeePriority::Unimportant).await.unwrap(), Change::new( &ViewPair::new( - &random_scalar(&mut OsRng) * ED25519_BASEPOINT_TABLE, - Zeroizing::new(random_scalar(&mut OsRng)) + &Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE, + Zeroizing::new(Scalar::random(&mut OsRng)) ), false ), diff --git a/coins/monero/tests/send.rs b/coins/monero/tests/send.rs index 4c338eb6..636912c0 100644 --- a/coins/monero/tests/send.rs +++ b/coins/monero/tests/send.rs @@ -3,8 +3,8 @@ use rand_core::OsRng; use monero_serai::{ transaction::Transaction, wallet::{ - extra::Extra, address::SubaddressIndex, ReceivedOutput, SpendableOutput, Decoys, - SignableTransactionBuilder, + extra::Extra, address::SubaddressIndex, ReceivedOutput, SpendableOutput, DecoySelection, + Decoys, SignableTransactionBuilder, }, rpc::{Rpc, HttpRpc}, Protocol, @@ -104,8 +104,8 @@ test!( use monero_serai::wallet::FeePriority; let change_view = ViewPair::new( - &random_scalar(&mut OsRng) * ED25519_BASEPOINT_TABLE, - Zeroizing::new(random_scalar(&mut OsRng)), + &Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE, + Zeroizing::new(Scalar::random(&mut OsRng)), ); let mut builder = SignableTransactionBuilder::new( @@ -117,8 +117,8 @@ test!( // Send to a subaddress let sub_view = ViewPair::new( - &random_scalar(&mut OsRng) * ED25519_BASEPOINT_TABLE, - Zeroizing::new(random_scalar(&mut OsRng)), + &Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE, + Zeroizing::new(Scalar::random(&mut OsRng)), ); builder.add_payment( sub_view diff --git a/processor/src/networks/monero.rs b/processor/src/networks/monero.rs index 8d4d1760..2369000c 100644 --- a/processor/src/networks/monero.rs +++ b/processor/src/networks/monero.rs @@ -22,7 +22,7 @@ use monero_serai::{ wallet::{ ViewPair, Scanner, address::{Network as MoneroNetwork, SubaddressIndex, AddressSpec}, - Fee, SpendableOutput, Change, Decoys, TransactionError, + Fee, SpendableOutput, Change, DecoySelection, Decoys, TransactionError, SignableTransaction as MSignableTransaction, Eventuality, TransactionMachine, }, }; diff --git a/tests/full-stack/src/tests/mint_and_burn.rs b/tests/full-stack/src/tests/mint_and_burn.rs index 4093e47d..ccfb91da 100644 --- a/tests/full-stack/src/tests/mint_and_burn.rs +++ b/tests/full-stack/src/tests/mint_and_burn.rs @@ -349,7 +349,7 @@ async fn mint_and_burn_test() { Protocol, transaction::Timelock, wallet::{ - ViewPair, Scanner, Decoys, Change, FeePriority, SignableTransaction, + ViewPair, Scanner, DecoySelection, Decoys, Change, FeePriority, SignableTransaction, address::{Network, AddressType, AddressMeta, MoneroAddress}, }, decompress_point, diff --git a/tests/no-std/Cargo.toml b/tests/no-std/Cargo.toml index 756ca880..d224f1e1 100644 --- a/tests/no-std/Cargo.toml +++ b/tests/no-std/Cargo.toml @@ -35,5 +35,8 @@ dkg = { path = "../../crypto/dkg", default-features = false } bitcoin-serai = { path = "../../coins/bitcoin", default-features = false, features = ["hazmat"] } +monero-io = { path = "../../coins/monero/io", default-features = false } monero-generators = { path = "../../coins/monero/generators", default-features = false } +monero-primitives = { path = "../../coins/monero/primitives", default-features = false } +monero-clsag = { path = "../../coins/monero/ringct/clsag", default-features = false } monero-serai = { path = "../../coins/monero", default-features = false } diff --git a/tests/processor/src/networks.rs b/tests/processor/src/networks.rs index 9af339b7..c2d5d9fb 100644 --- a/tests/processor/src/networks.rs +++ b/tests/processor/src/networks.rs @@ -440,7 +440,8 @@ impl Wallet { Protocol, wallet::{ address::{Network, AddressType, AddressMeta, Address}, - SpendableOutput, Decoys, Change, FeePriority, Scanner, SignableTransaction, + SpendableOutput, DecoySelection, Decoys, Change, FeePriority, Scanner, + SignableTransaction, }, rpc::HttpRpc, decompress_point,