diff --git a/Cargo.lock b/Cargo.lock index 827469f6..7709b8e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4569,7 +4569,6 @@ version = "0.1.0" dependencies = [ "base58-monero 1.0.0", "blake2", - "cc", "curve25519-dalek 3.2.0", "dalek-ff-group", "dleq-serai", diff --git a/coins/monero/Cargo.toml b/coins/monero/Cargo.toml index 04c7a82d..0f8268ca 100644 --- a/coins/monero/Cargo.toml +++ b/coins/monero/Cargo.toml @@ -6,9 +6,6 @@ license = "MIT" authors = ["Luke Parker <lukeparker5132@gmail.com>"] edition = "2021" -[build-dependencies] -cc = "1.0" - [dependencies] hex-literal = "0.3" lazy_static = "1" @@ -45,7 +42,6 @@ monero = "0.16" reqwest = { version = "0.11", features = ["json"] } [features] -experimental = [] multisig = ["rand_chacha", "blake2", "transcript", "frost", "dleq"] [dev-dependencies] diff --git a/coins/monero/build.rs b/coins/monero/build.rs deleted file mode 100644 index 98b25044..00000000 --- a/coins/monero/build.rs +++ /dev/null @@ -1,52 +0,0 @@ -use std::process::Command; - -fn main() { - if !Command::new("git") - .args(&["submodule", "update", "--init", "--recursive"]) - .status() - .unwrap() - .success() - { - panic!("git failed to init submodules"); - } - - println!("cargo:rerun-if-changed=c/wrapper.cpp"); - #[rustfmt::skip] - cc::Build::new() - .static_flag(true) - .warnings(false) - .extra_warnings(false) - .flag("-Wno-deprecated-declarations") - - .include("c/monero/external/supercop/include") - .include("c/monero/contrib/epee/include") - .include("c/monero/src") - .include("c/monero/build/release/generated_include") - - .define("AUTO_INITIALIZE_EASYLOGGINGPP", None) - .include("c/monero/external/easylogging++") - .file("c/monero/external/easylogging++/easylogging++.cc") - - .file("c/monero/src/common/aligned.c") - .file("c/monero/src/common/perf_timer.cpp") - - .include("c/monero/src/crypto") - .file("c/monero/src/crypto/crypto-ops-data.c") - .file("c/monero/src/crypto/crypto-ops.c") - .file("c/monero/src/crypto/keccak.c") - .file("c/monero/src/crypto/hash.c") - - .include("c/monero/src/ringct") - .file("c/monero/src/ringct/rctCryptoOps.c") - .file("c/monero/src/ringct/rctTypes.cpp") - .file("c/monero/src/ringct/rctOps.cpp") - .file("c/monero/src/ringct/multiexp.cc") - .file("c/monero/src/ringct/bulletproofs.cc") - .file("c/monero/src/ringct/rctSigs.cpp") - - .file("c/wrapper.cpp") - .compile("wrapper"); - - println!("cargo:rustc-link-lib=wrapper"); - println!("cargo:rustc-link-lib=stdc++"); -} diff --git a/coins/monero/c/monero b/coins/monero/c/monero deleted file mode 160000 index 424e4de1..00000000 --- a/coins/monero/c/monero +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 424e4de16b98506170db7b0d7d87a79ccf541744 diff --git a/coins/monero/c/wrapper.cpp b/coins/monero/c/wrapper.cpp deleted file mode 100644 index d9436579..00000000 --- a/coins/monero/c/wrapper.cpp +++ /dev/null @@ -1,156 +0,0 @@ -#include <mutex> - -#include "ringct/bulletproofs.h" -#include "ringct/rctSigs.h" - -typedef std::lock_guard<std::mutex> lock; - -std::mutex rng_mutex; -uint8_t rng_entropy[64]; - -extern "C" { - void rng(uint8_t* seed) { - // Set the first half to the seed - memcpy(rng_entropy, seed, 32); - // Set the second half to the hash of a DST to ensure a lack of collisions - crypto::cn_fast_hash("RNG_entropy_seed", 16, (char*) &rng_entropy[32]); - } -} - -extern "C" void monero_wide_reduce(uint8_t* value); -namespace crypto { - void generate_random_bytes_not_thread_safe(size_t n, void* value) { - size_t written = 0; - while (written != n) { - uint8_t hash[32]; - crypto::cn_fast_hash(rng_entropy, 64, (char*) hash); - // Step the RNG by setting the latter half to the most recent result - // Does not leak the RNG, even if the values are leaked (which they are - // expected to be) due to the first half remaining constant and - // undisclosed - memcpy(&rng_entropy[32], hash, 32); - - size_t next = n - written; - if (next > 32) { - next = 32; - } - memcpy(&((uint8_t*) value)[written], hash, next); - written += next; - } - } - - void random32_unbiased(unsigned char *bytes) { - uint8_t value[64]; - generate_random_bytes_not_thread_safe(64, value); - monero_wide_reduce(value); - memcpy(bytes, value, 32); - } -} - -extern "C" { - void c_hash_to_point(uint8_t* point) { - rct::key key_point; - ge_p3 e_p3; - memcpy(key_point.bytes, point, 32); - rct::hash_to_p3(e_p3, key_point); - ge_p3_tobytes(point, &e_p3); - } - - uint8_t* c_generate_bp(uint8_t* seed, uint8_t len, uint64_t* a, uint8_t* m) { - lock guard(rng_mutex); - rng(seed); - - rct::keyV masks; - std::vector<uint64_t> amounts; - masks.resize(len); - amounts.resize(len); - for (uint8_t i = 0; i < len; i++) { - memcpy(masks[i].bytes, m + (i * 32), 32); - amounts[i] = a[i]; - } - - rct::Bulletproof bp = rct::bulletproof_PROVE(amounts, masks); - - std::stringstream ss; - binary_archive<true> ba(ss); - ::serialization::serialize(ba, bp); - uint8_t* res = (uint8_t*) calloc(ss.str().size(), 1); - memcpy(res, ss.str().data(), ss.str().size()); - return res; - } - - bool c_verify_bp( - uint8_t* seed, - uint s_len, - uint8_t* s, - uint8_t c_len, - uint8_t* c - ) { - // BPs are batch verified which use RNG based weights to ensure individual - // integrity - // That's why this must also have control over RNG, to prevent interrupting - // multisig signing while not using known seeds. Considering this doesn't - // actually define a batch, and it's only verifying a single BP, - // it'd probably be fine, but... - lock guard(rng_mutex); - rng(seed); - - rct::Bulletproof bp; - std::stringstream ss; - std::string str; - str.assign((char*) s, (size_t) s_len); - ss << str; - binary_archive<false> ba(ss); - ::serialization::serialize(ba, bp); - if (!ss.good()) { - return false; - } - - bp.V.resize(c_len); - for (uint8_t i = 0; i < c_len; i++) { - memcpy(bp.V[i].bytes, &c[i * 32], 32); - } - - try { return rct::bulletproof_VERIFY(bp); } catch(...) { return false; } - } - - bool c_verify_clsag( - uint s_len, - uint8_t* s, - uint8_t k_len, - uint8_t* k, - uint8_t* I, - uint8_t* p, - uint8_t* m - ) { - rct::clsag clsag; - std::stringstream ss; - std::string str; - str.assign((char*) s, (size_t) s_len); - ss << str; - binary_archive<false> ba(ss); - ::serialization::serialize(ba, clsag); - if (!ss.good()) { - return false; - } - - rct::ctkeyV keys; - keys.resize(k_len); - for (uint8_t i = 0; i < k_len; i++) { - memcpy(keys[i].dest.bytes, &k[(i * 2) * 32], 32); - memcpy(keys[i].mask.bytes, &k[((i * 2) + 1) * 32], 32); - } - - memcpy(clsag.I.bytes, I, 32); - - rct::key pseudo_out; - memcpy(pseudo_out.bytes, p, 32); - - rct::key msg; - memcpy(msg.bytes, m, 32); - - try { - return verRctCLSAGSimple(msg, clsag, keys, pseudo_out); - } catch(...) { return false; } - } -} diff --git a/coins/monero/src/lib.rs b/coins/monero/src/lib.rs index 33601f0f..18990dad 100644 --- a/coins/monero/src/lib.rs +++ b/coins/monero/src/lib.rs @@ -1,10 +1,6 @@ -use std::slice; - use lazy_static::lazy_static; use rand_core::{RngCore, CryptoRng}; -use subtle::ConstantTimeEq; - use tiny_keccak::{Hasher, Keccak}; use curve25519_dalek::{ @@ -38,26 +34,6 @@ lazy_static! { static ref H_TABLE: EdwardsBasepointTable = EdwardsBasepointTable::create(&H); } -// Function from libsodium our subsection of Monero relies on. Implementing it here means we don't -// need to link against libsodium -#[no_mangle] -unsafe extern "C" fn crypto_verify_32(a: *const u8, b: *const u8) -> isize { - isize::from(slice::from_raw_parts(a, 32).ct_eq(slice::from_raw_parts(b, 32)).unwrap_u8()) - 1 -} - -// Offer a wide reduction to C. Our seeded RNG prevented Monero from defining an unbiased scalar -// generation function, and in order to not use Monero code (which would require propagating its -// license), the function was rewritten. It was rewritten with wide reduction, instead of rejection -// sampling however, hence the need for this function -#[no_mangle] -unsafe extern "C" fn monero_wide_reduce(value: *mut u8) { - let res = - Scalar::from_bytes_mod_order_wide(std::slice::from_raw_parts(value, 64).try_into().unwrap()); - for (i, b) in res.to_bytes().iter().enumerate() { - value.add(i).write(*b); - } -} - #[allow(non_snake_case)] #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Commitment { @@ -95,5 +71,11 @@ pub fn hash(data: &[u8]) -> [u8; 32] { } pub fn hash_to_scalar(data: &[u8]) -> Scalar { - Scalar::from_bytes_mod_order(hash(data)) + let scalar = Scalar::from_bytes_mod_order(hash(data)); + // 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); + scalar } diff --git a/coins/monero/src/ringct/bulletproofs/core.rs b/coins/monero/src/ringct/bulletproofs/core.rs index e083dda0..8988c595 100644 --- a/coins/monero/src/ringct/bulletproofs/core.rs +++ b/coins/monero/src/ringct/bulletproofs/core.rs @@ -28,13 +28,7 @@ fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Scalar { } fn hash_to_scalar(data: &[u8]) -> Scalar { - let scalar = Scalar(dalek_hash(data)); - // Monero will explicitly retry on these cases, as them occurring breaks the proof - // This library acknowledges their practical impossibility of them occurring, and doesn't bother - // to code in logic to handle it. That said, if they ever occur, something must happen in order - // to not generate a proof we believe to be valid when it isn't - assert!(!bool::from(scalar.is_zero()), "ZERO HASH: {:?}", data); - scalar + Scalar(dalek_hash(data)) } fn generator(i: usize) -> EdwardsPoint { diff --git a/coins/monero/src/ringct/bulletproofs/mod.rs b/coins/monero/src/ringct/bulletproofs/mod.rs index 2599b2fc..388fdfd7 100644 --- a/coins/monero/src/ringct/bulletproofs/mod.rs +++ b/coins/monero/src/ringct/bulletproofs/mod.rs @@ -54,44 +54,6 @@ impl Bulletproofs { Ok(prove(rng, outputs)) } - #[must_use] - pub fn verify<R: RngCore + CryptoRng>(&self, rng: &mut R, commitments: &[EdwardsPoint]) -> bool { - if commitments.len() > 16 { - return false; - } - - let mut seed = [0; 32]; - rng.fill_bytes(&mut seed); - - let mut serialized = Vec::with_capacity((9 + (2 * self.L.len())) * 32); - self.serialize(&mut serialized).unwrap(); - let commitments: Vec<[u8; 32]> = commitments - .iter() - .map(|commitment| (commitment * Scalar::from(8u8).invert()).compress().to_bytes()) - .collect(); - - unsafe { - #[link(name = "wrapper")] - extern "C" { - fn c_verify_bp( - seed: *const u8, - serialized_len: usize, - serialized: *const u8, - commitments_len: u8, - commitments: *const [u8; 32], - ) -> bool; - } - - c_verify_bp( - seed.as_ptr(), - serialized.len(), - serialized.as_ptr(), - u8::try_from(commitments.len()).unwrap(), - commitments.as_ptr(), - ) - } - } - fn serialize_core<W: std::io::Write, F: Fn(&[EdwardsPoint], &mut W) -> std::io::Result<()>>( &self, w: &mut W, diff --git a/coins/monero/src/ringct/clsag/mod.rs b/coins/monero/src/ringct/clsag/mod.rs index b9f480e1..755d09c2 100644 --- a/coins/monero/src/ringct/clsag/mod.rs +++ b/coins/monero/src/ringct/clsag/mod.rs @@ -7,7 +7,7 @@ use rand_core::{RngCore, CryptoRng}; use curve25519_dalek::{ constants::ED25519_BASEPOINT_TABLE, scalar::Scalar, - traits::VartimePrecomputedMultiscalarMul, + traits::{IsIdentity, VartimePrecomputedMultiscalarMul}, edwards::{EdwardsPoint, VartimeEdwardsPrecomputation}, }; @@ -29,10 +29,14 @@ lazy_static! { pub enum ClsagError { #[error("internal error ({0})")] InternalError(String), + #[error("invalid ring")] + InvalidRing, #[error("invalid ring member (member {0}, ring size {1})")] InvalidRingMember(u8, u8), #[error("invalid commitment")] InvalidCommitment, + #[error("invalid key image")] + InvalidImage, #[error("invalid D")] InvalidD, #[error("invalid s")] @@ -72,7 +76,6 @@ impl ClsagInput { #[allow(clippy::large_enum_variant)] enum Mode { Sign(usize, EdwardsPoint, EdwardsPoint), - #[cfg(feature = "experimental")] Verify(Scalar), } @@ -150,7 +153,6 @@ fn core( c = hash_to_scalar(&to_hash); } - #[cfg(feature = "experimental")] Mode::Verify(c1) => { start = 0; end = n; @@ -259,17 +261,31 @@ impl Clsag { res } - // Not extensively tested nor guaranteed to have expected parity with Monero - #[cfg(feature = "experimental")] - pub fn rust_verify( + pub fn verify( &self, ring: &[[EdwardsPoint; 2]], I: &EdwardsPoint, pseudo_out: &EdwardsPoint, msg: &[u8; 32], ) -> Result<(), ClsagError> { - let (_, c1) = - core(ring, I, pseudo_out, msg, &self.D.mul_by_cofactor(), &self.s, Mode::Verify(self.c1)); + // Preliminary checks. s, c1, and points must also be encoded canonically, which isn't checked + // here + if ring.len() == 0 { + Err(ClsagError::InvalidRing)?; + } + if ring.len() != self.s.len() { + Err(ClsagError::InvalidS)?; + } + if I.is_identity() { + Err(ClsagError::InvalidImage)?; + } + + let D = self.D.mul_by_cofactor(); + if D.is_identity() { + Err(ClsagError::InvalidD)?; + } + + let (_, c1) = core(ring, I, pseudo_out, msg, &D, &self.s, Mode::Verify(self.c1)); if c1 != self.c1 { Err(ClsagError::InvalidC1)?; } @@ -289,58 +305,4 @@ impl Clsag { pub fn deserialize<R: std::io::Read>(decoys: usize, r: &mut R) -> std::io::Result<Clsag> { Ok(Clsag { s: read_raw_vec(read_scalar, decoys, r)?, c1: read_scalar(r)?, D: read_point(r)? }) } - - pub fn verify( - &self, - ring: &[[EdwardsPoint; 2]], - I: &EdwardsPoint, - pseudo_out: &EdwardsPoint, - msg: &[u8; 32], - ) -> Result<(), ClsagError> { - // Serialize it to pass the struct to Monero without extensive FFI - let mut serialized = Vec::with_capacity(1 + ((self.s.len() + 2) * 32)); - write_varint(&self.s.len().try_into().unwrap(), &mut serialized).unwrap(); - self.serialize(&mut serialized).unwrap(); - - let I_bytes = I.compress().to_bytes(); - - let mut ring_bytes = vec![]; - for member in ring { - ring_bytes.extend(&member[0].compress().to_bytes()); - ring_bytes.extend(&member[1].compress().to_bytes()); - } - - let pseudo_out_bytes = pseudo_out.compress().to_bytes(); - - unsafe { - // Uses Monero's C verification function to ensure compatibility with Monero - #[link(name = "wrapper")] - extern "C" { - pub(crate) fn c_verify_clsag( - serialized_len: usize, - serialized: *const u8, - ring_size: u8, - ring: *const u8, - I: *const u8, - pseudo_out: *const u8, - msg: *const u8, - ) -> bool; - } - - if c_verify_clsag( - serialized.len(), - serialized.as_ptr(), - u8::try_from(ring.len()) - .map_err(|_| ClsagError::InternalError("too large ring".to_string()))?, - ring_bytes.as_ptr(), - I_bytes.as_ptr(), - pseudo_out_bytes.as_ptr(), - msg.as_ptr(), - ) { - Ok(()) - } else { - Err(ClsagError::InvalidC1) - } - } - } } diff --git a/coins/monero/src/ringct/hash_to_point.rs b/coins/monero/src/ringct/hash_to_point.rs index a28c8b95..779030de 100644 --- a/coins/monero/src/ringct/hash_to_point.rs +++ b/coins/monero/src/ringct/hash_to_point.rs @@ -7,23 +7,8 @@ use dalek_ff_group::field::FieldElement; use crate::hash; -pub(crate) fn raw_hash_to_point(mut bytes: [u8; 32]) -> EdwardsPoint { - unsafe { - #[link(name = "wrapper")] - extern "C" { - fn c_hash_to_point(key: *const u8); - } - - c_hash_to_point(bytes.as_mut_ptr()); - } - CompressedEdwardsY::from_slice(&bytes).decompress().unwrap() -} - -// This works without issue. It's also 140 times slower (@ 3.5ms), and despite checking it passes -// for all branches, there still could be *some* discrepancy somewhere. There's no reason to use it -// unless we're trying to purge that section of the C static library, which we aren't right now #[allow(dead_code)] -pub(crate) fn rust_hash_to_point(bytes: [u8; 32]) -> EdwardsPoint { +pub(crate) fn raw_hash_to_point(bytes: [u8; 32]) -> EdwardsPoint { #[allow(non_snake_case)] let A = FieldElement::from(486662u64); diff --git a/coins/monero/src/tests/bulletproofs.rs b/coins/monero/src/tests/bulletproofs.rs deleted file mode 100644 index 98ae1546..00000000 --- a/coins/monero/src/tests/bulletproofs.rs +++ /dev/null @@ -1,21 +0,0 @@ -use rand::rngs::OsRng; - -use crate::{Commitment, random_scalar, ringct::bulletproofs::Bulletproofs}; - -#[test] -fn bulletproofs() { - // Create Bulletproofs for all possible output quantities - for i in 1 .. 17 { - let commitments = - (1 ..= i).map(|i| Commitment::new(random_scalar(&mut OsRng), i)).collect::<Vec<_>>(); - - assert!(Bulletproofs::new(&mut OsRng, &commitments) - .unwrap() - .verify(&mut OsRng, &commitments.iter().map(Commitment::calculate).collect::<Vec<_>>())); - } - - // Check it errors if we try to create too many - assert!( - Bulletproofs::new(&mut OsRng, &[Commitment::new(random_scalar(&mut OsRng), 1); 17]).is_err() - ); -} diff --git a/coins/monero/src/tests/clsag.rs b/coins/monero/src/tests/clsag.rs index 73f5b7cd..fe9ecc9b 100644 --- a/coins/monero/src/tests/clsag.rs +++ b/coins/monero/src/tests/clsag.rs @@ -74,8 +74,6 @@ fn clsag() { ) .swap_remove(0); clsag.verify(&ring, &image, &pseudo_out, &msg).unwrap(); - #[cfg(feature = "experimental")] - clsag.rust_verify(&ring, &image, &pseudo_out, &msg).unwrap(); } } diff --git a/coins/monero/src/tests/hash_to_point.rs b/coins/monero/src/tests/hash_to_point.rs deleted file mode 100644 index 983539e5..00000000 --- a/coins/monero/src/tests/hash_to_point.rs +++ /dev/null @@ -1,16 +0,0 @@ -use rand::rngs::OsRng; - -use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE; - -use crate::{ - random_scalar, - ringct::hash_to_point::{hash_to_point as c_hash_to_point, rust_hash_to_point}, -}; - -#[test] -fn hash_to_point() { - for _ in 0 .. 50 { - let point = &random_scalar(&mut OsRng) * &ED25519_BASEPOINT_TABLE; - assert_eq!(rust_hash_to_point(point.compress().to_bytes()), c_hash_to_point(point)); - } -} diff --git a/coins/monero/src/tests/mod.rs b/coins/monero/src/tests/mod.rs index 5af30bad..d9b85f0c 100644 --- a/coins/monero/src/tests/mod.rs +++ b/coins/monero/src/tests/mod.rs @@ -1,4 +1,2 @@ -mod hash_to_point; mod clsag; -mod bulletproofs; mod address;