Begin crate smashing

This commit is contained in:
Luke Parker
2024-06-13 18:54:18 -04:00
parent 5cdae6eeb8
commit 784a273747
42 changed files with 606 additions and 250 deletions

49
Cargo.lock generated
View File

@@ -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",

View File

@@ -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",

View File

@@ -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"]

View File

@@ -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 <lukeparker5132@gmail.com>"]
@@ -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"]

View File

@@ -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

View File

@@ -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.

View File

@@ -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<EdwardsPoint> {
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);

View File

@@ -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<DalekPoint> = 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::<Vec<u8>, u64>(&i.try_into().unwrap(), &mut even).unwrap();
write_varint::<Vec<u8>, 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
}

View File

@@ -1,16 +0,0 @@
use std_shims::io::{self, Write};
const VARINT_CONTINUATION_MASK: u8 = 0b1000_0000;
pub(crate) fn write_varint<W: Write>(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(())
}

View File

@@ -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 <lukeparker5132@gmail.com>"]
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"]

21
coins/monero/io/LICENSE Normal file
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,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.

View File

@@ -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<U: sealed::VarInt>(varint: U) -> usize {
pub fn varint_len<U: sealed::VarInt>(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<W: Write>(byte: &u8, w: &mut W) -> io::Result<()> {
pub fn write_byte<W: Write>(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<W: Write, U: sealed::VarInt>(varint: &U, w: &mut W) -> io::Result<()> {
pub fn write_varint<W: Write, U: sealed::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<W: Write, U: sealed::VarInt>(varint: &U, w: &mut W) -
Ok(())
}
pub(crate) fn write_scalar<W: Write>(scalar: &Scalar, w: &mut W) -> io::Result<()> {
pub fn write_scalar<W: Write>(scalar: &Scalar, w: &mut W) -> io::Result<()> {
w.write_all(&scalar.to_bytes())
}
pub(crate) fn write_point<W: Write>(point: &EdwardsPoint, w: &mut W) -> io::Result<()> {
pub fn write_point<W: Write>(point: &EdwardsPoint, w: &mut W) -> io::Result<()> {
w.write_all(&point.compress().to_bytes())
}
pub(crate) fn write_raw_vec<T, W: Write, F: Fn(&T, &mut W) -> io::Result<()>>(
pub fn write_raw_vec<T, W: Write, F: Fn(&T, &mut W) -> io::Result<()>>(
f: F,
values: &[T],
w: &mut W,
@@ -72,7 +78,7 @@ pub(crate) fn write_raw_vec<T, W: Write, F: Fn(&T, &mut W) -> io::Result<()>>(
Ok(())
}
pub(crate) fn write_vec<T, W: Write, F: Fn(&T, &mut W) -> io::Result<()>>(
pub fn write_vec<T, W: Write, F: Fn(&T, &mut W) -> io::Result<()>>(
f: F,
values: &[T],
w: &mut W,
@@ -81,29 +87,29 @@ pub(crate) fn write_vec<T, W: Write, F: Fn(&T, &mut W) -> io::Result<()>>(
write_raw_vec(f, values, w)
}
pub(crate) fn read_bytes<R: Read, const N: usize>(r: &mut R) -> io::Result<[u8; N]> {
pub fn read_bytes<R: Read, const N: usize>(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: Read>(r: &mut R) -> io::Result<u8> {
pub fn read_byte<R: Read>(r: &mut R) -> io::Result<u8> {
Ok(read_bytes::<_, 1>(r)?[0])
}
pub(crate) fn read_u16<R: Read>(r: &mut R) -> io::Result<u16> {
pub fn read_u16<R: Read>(r: &mut R) -> io::Result<u16> {
read_bytes(r).map(u16::from_le_bytes)
}
pub(crate) fn read_u32<R: Read>(r: &mut R) -> io::Result<u32> {
pub fn read_u32<R: Read>(r: &mut R) -> io::Result<u32> {
read_bytes(r).map(u32::from_le_bytes)
}
pub(crate) fn read_u64<R: Read>(r: &mut R) -> io::Result<u64> {
pub fn read_u64<R: Read>(r: &mut R) -> io::Result<u64> {
read_bytes(r).map(u64::from_le_bytes)
}
pub(crate) fn read_varint<R: Read, U: sealed::VarInt>(r: &mut R) -> io::Result<U> {
pub fn read_varint<R: Read, U: sealed::VarInt>(r: &mut R) -> io::Result<U> {
let mut bits = 0;
let mut res = 0;
while {
@@ -128,24 +134,34 @@ pub(crate) fn read_varint<R: Read, U: sealed::VarInt>(r: &mut R) -> io::Result<U
// for now. There's also further edge cases as noted by
// https://github.com/monero-project/monero/issues/8438, where some scalars had an archaic
// reduction applied
pub(crate) fn read_scalar<R: Read>(r: &mut R) -> io::Result<Scalar> {
pub fn read_scalar<R: Read>(r: &mut R) -> io::Result<Scalar> {
Option::from(Scalar::from_canonical_bytes(read_bytes(r)?))
.ok_or_else(|| io::Error::other("unreduced scalar"))
}
pub(crate) fn read_point<R: Read>(r: &mut R) -> io::Result<EdwardsPoint> {
/// 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<EdwardsPoint> {
CompressedEdwardsY(bytes)
.decompress()
// Ban points which are either unreduced or -0
.filter(|point| point.compress().to_bytes() == bytes)
}
pub fn read_point<R: Read>(r: &mut R) -> io::Result<EdwardsPoint> {
let bytes = read_bytes(r)?;
decompress_point(bytes).ok_or_else(|| io::Error::other("invalid point"))
}
pub(crate) fn read_torsion_free_point<R: Read>(r: &mut R) -> io::Result<EdwardsPoint> {
pub fn read_torsion_free_point<R: Read>(r: &mut R) -> io::Result<EdwardsPoint> {
read_point(r)
.ok()
.filter(EdwardsPoint::is_torsion_free)
.ok_or_else(|| io::Error::other("invalid point"))
}
pub(crate) fn read_raw_vec<R: Read, T, F: Fn(&mut R) -> io::Result<T>>(
pub fn read_raw_vec<R: Read, T, F: Fn(&mut R) -> io::Result<T>>(
f: F,
len: usize,
r: &mut R,
@@ -157,16 +173,13 @@ pub(crate) fn read_raw_vec<R: Read, T, F: Fn(&mut R) -> io::Result<T>>(
Ok(res)
}
pub(crate) fn read_array<R: Read, T: Debug, F: Fn(&mut R) -> io::Result<T>, const N: usize>(
pub fn read_array<R: Read, T: Debug, F: Fn(&mut R) -> io::Result<T>, 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<R: Read, T, F: Fn(&mut R) -> io::Result<T>>(
f: F,
r: &mut R,
) -> io::Result<Vec<T>> {
pub fn read_vec<R: Read, T, F: Fn(&mut R) -> io::Result<T>>(f: F, r: &mut R) -> io::Result<Vec<T>> {
read_raw_vec(f, read_varint(r)?, r)
}

View File

@@ -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 <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 }
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"]

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,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.

View File

@@ -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<Scalar> = 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<VartimeEdwardsPrecomputation> = 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<u64>,
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::<usize>()
}
pub fn len(&self) -> usize {
self.offsets.len()
}
pub fn indexes(&self) -> Vec<u64> {
let mut res = vec![self.offsets[0]; self.len()];
for m in 1 .. res.len() {
res[m] = res[m - 1] + self.offsets[m];
}
res
}
}

View File

@@ -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 <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 }
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"]

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,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.

View File

@@ -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<Scalar>,
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<R: RngCore + CryptoRng>(
pub fn sign<R: RngCore + CryptoRng>(
rng: &mut R,
mut inputs: Vec<(Zeroizing<Scalar>, 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
}

View File

@@ -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<T: 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<dfg::EdwardsPoint>,
@@ -107,7 +106,7 @@ pub(crate) struct ClsagMultisig {
}
impl ClsagMultisig {
pub(crate) fn new(
pub fn new(
transcript: RecommendedTranscript,
output_key: EdwardsPoint,
details: Arc<RwLock<Option<ClsagDetails>>>,
@@ -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,

View File

@@ -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;

View File

@@ -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<Scalar> = 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<VartimeEdwardsPrecomputation> = 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<R: RngCore + CryptoRng>(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()

View File

@@ -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.

View File

@@ -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};

View File

@@ -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;

View File

@@ -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::<Vec<_>>();
let bp = if $plus {

View File

@@ -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,

View File

@@ -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};

View File

@@ -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<R: RngCore + CryptoRng, RPC: RpcConnection>(
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<u64>,
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<R: Send + Sync + RngCore + CryptoRng, RPC: Send + Sync + RpcConnection>(
rng: &mut R,
rpc: &Rpc<RPC>,
ring_len: usize,
height: usize,
inputs: &[SpendableOutput],
) -> Result<Vec<Decoys>, RpcError>;
async fn fingerprintable_canonical_select<
R: Send + Sync + RngCore + CryptoRng,
RPC: Send + Sync + RpcConnection,
>(
rng: &mut R,
rpc: &Rpc<RPC>,
ring_len: usize,
height: usize,
inputs: &[SpendableOutput],
) -> Result<Vec<Decoys>, 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::<usize>()
}
pub fn len(&self) -> usize {
self.offsets.len()
}
pub fn indexes(&self) -> Vec<u64> {
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<R: RngCore + CryptoRng, RPC: RpcConnection>(
async fn select<R: Send + Sync + RngCore + CryptoRng, RPC: Send + Sync + RpcConnection>(
rng: &mut R,
rpc: &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<R: RngCore + CryptoRng, RPC: RpcConnection>(
async fn fingerprintable_canonical_select<
R: Send + Sync + RngCore + CryptoRng,
RPC: Send + Sync + RpcConnection,
>(
rng: &mut R,
rpc: &Rpc<RPC>,
ring_len: usize,

View File

@@ -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};

View File

@@ -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,

View File

@@ -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<Zeroizing<[u8
pub struct ClassicSeed(Language, Zeroizing<String>);
impl ClassicSeed {
pub(crate) fn new<R: RngCore + CryptoRng>(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)]

View File

@@ -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 };

View File

@@ -26,7 +26,6 @@ use frost::{
};
use crate::{
random_scalar,
ringct::{
clsag::{ClsagInput, ClsagDetails, ClsagAddendum, ClsagMultisig},
RctPrunable,
@@ -348,7 +347,7 @@ impl SignMachine<Transaction> 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 {

View File

@@ -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<HttpRpc> {
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
),

View File

@@ -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

View File

@@ -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,
},
};

View File

@@ -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,

View File

@@ -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 }

View File

@@ -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,