diff --git a/coins/monero/Cargo.toml b/coins/monero/Cargo.toml index f262a3ae..10001ea5 100644 --- a/coins/monero/Cargo.toml +++ b/coins/monero/Cargo.toml @@ -26,9 +26,9 @@ blake2 = { version = "0.10", optional = true } curve25519-dalek = { version = "3", features = ["std"] } -group = { version = "0.12", optional = true } +group = { version = "0.12" } +dalek-ff-group = { path = "../../crypto/dalek-ff-group" } -dalek-ff-group = { path = "../../crypto/dalek-ff-group", optional = true } transcript = { package = "flexible-transcript", path = "../../crypto/transcript", features = ["recommended"], optional = true } frost = { package = "modular-frost", path = "../../crypto/frost", features = ["ed25519"], optional = true } dleq = { path = "../../crypto/dleq", features = ["serialize"], optional = true } @@ -45,7 +45,7 @@ reqwest = { version = "0.11", features = ["json"] } [features] experimental = [] -multisig = ["rand_chacha", "blake2", "group", "dalek-ff-group", "transcript", "frost", "dleq"] +multisig = ["rand_chacha", "blake2", "transcript", "frost", "dleq"] [dev-dependencies] sha2 = "0.10" diff --git a/coins/monero/src/lib.rs b/coins/monero/src/lib.rs index 8237b4f6..f92da959 100644 --- a/coins/monero/src/lib.rs +++ b/coins/monero/src/lib.rs @@ -98,20 +98,3 @@ pub fn hash(data: &[u8]) -> [u8; 32] { pub fn hash_to_scalar(data: &[u8]) -> Scalar { Scalar::from_bytes_mod_order(hash(&data)) } - -pub fn hash_to_point(point: &EdwardsPoint) -> EdwardsPoint { - let mut bytes = point.compress().to_bytes(); - unsafe { - #[link(name = "wrapper")] - extern "C" { - fn c_hash_to_point(point: *const u8); - } - - c_hash_to_point(bytes.as_mut_ptr()); - } - CompressedEdwardsY::from_slice(&bytes).decompress().unwrap() -} - -pub fn generate_key_image(secret: &Scalar) -> EdwardsPoint { - secret * hash_to_point(&(secret * &ED25519_BASEPOINT_TABLE)) -} diff --git a/coins/monero/src/ringct/clsag/mod.rs b/coins/monero/src/ringct/clsag/mod.rs index 80a50300..41f68c96 100644 --- a/coins/monero/src/ringct/clsag/mod.rs +++ b/coins/monero/src/ringct/clsag/mod.rs @@ -12,11 +12,11 @@ use curve25519_dalek::{ }; use crate::{ - Commitment, + Commitment, random_scalar, hash_to_scalar, + transaction::RING_LEN, wallet::decoys::Decoys, - random_scalar, hash_to_scalar, hash_to_point, - serialize::*, - transaction::RING_LEN + ringct::hash_to_point, + serialize::* }; #[cfg(feature = "multisig")] @@ -170,7 +170,7 @@ fn core( let c_c = mu_C * c; let L = (&s[i] * &ED25519_BASEPOINT_TABLE) + (c_p * P[i]) + (c_c * C[i]); - let PH = hash_to_point(&P[i]); + let PH = hash_to_point(P[i]); // Shouldn't be an issue as all of the variables in this vartime statement are public let R = (s[i] * PH) + images_precomp.vartime_multiscalar_mul(&[c_p, c_c]); @@ -208,7 +208,7 @@ impl Clsag { let pseudo_out = Commitment::new(mask, input.commitment.amount).calculate(); let z = input.commitment.mask - mask; - let H = hash_to_point(&input.decoys.ring[r][0]); + let H = hash_to_point(input.decoys.ring[r][0]); let D = H * z; let mut s = Vec::with_capacity(input.decoys.ring.len()); for _ in 0 .. input.decoys.ring.len() { @@ -254,7 +254,7 @@ impl Clsag { mask, &msg, &nonce * &ED25519_BASEPOINT_TABLE, - nonce * hash_to_point(&inputs[i].2.decoys.ring[usize::from(inputs[i].2.decoys.i)][0]) + nonce * hash_to_point(inputs[i].2.decoys.ring[usize::from(inputs[i].2.decoys.i)][0]) ); clsag.s[usize::from(inputs[i].2.decoys.i)] = nonce - ((p * inputs[i].0) + c); diff --git a/coins/monero/src/ringct/clsag/multisig.rs b/coins/monero/src/ringct/clsag/multisig.rs index 19fff6a7..8365ff6d 100644 --- a/coins/monero/src/ringct/clsag/multisig.rs +++ b/coins/monero/src/ringct/clsag/multisig.rs @@ -18,9 +18,8 @@ use frost::{curve::Ed25519, FrostError, FrostView, algorithm::Algorithm}; use dalek_ff_group as dfg; use crate::{ - hash_to_point, frost::{MultisigError, write_dleq, read_dleq}, - ringct::clsag::{ClsagInput, Clsag} + ringct::{hash_to_point, clsag::{ClsagInput, Clsag}} }; impl ClsagInput { @@ -129,7 +128,7 @@ impl Algorithm for ClsagMultisig { view: &FrostView, nonces: &[dfg::Scalar; 2] ) -> Vec { - self.H = hash_to_point(&view.group_key().0); + self.H = hash_to_point(view.group_key().0); let mut serialized = Vec::with_capacity(ClsagMultisig::serialized_len()); serialized.extend((view.secret_share().0 * self.H).compress().to_bytes()); diff --git a/coins/monero/src/ringct/hash_to_point.rs b/coins/monero/src/ringct/hash_to_point.rs new file mode 100644 index 00000000..946307c4 --- /dev/null +++ b/coins/monero/src/ringct/hash_to_point.rs @@ -0,0 +1,67 @@ +use subtle::ConditionallySelectable; + +use curve25519_dalek::edwards::{CompressedEdwardsY, EdwardsPoint}; + +use group::ff::{Field, PrimeField}; +use dalek_ff_group::field::FieldElement; + +use crate::hash; + +pub fn hash_to_point(point: EdwardsPoint) -> EdwardsPoint { + let mut bytes = point.compress().to_bytes(); + unsafe { + #[link(name = "wrapper")] + extern "C" { + fn c_hash_to_point(point: *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(key: EdwardsPoint) -> EdwardsPoint { + #[allow(non_snake_case)] + let A = FieldElement::from(486662u64); + + let v = FieldElement::from_square(hash(&key.compress().to_bytes())).double(); + let w = v + FieldElement::one(); + let x = w.square() + (-A.square() * v); + + // This isn't the complete X, yet its initial value + // We don't calculate the full X, and instead solely calculate Y, letting dalek reconstruct X + // While inefficient, it solves API boundaries and reduces the amount of work done here + #[allow(non_snake_case)] + let X = { + let u = w; + let v = x; + let v3 = v * v * v; + let uv3 = u * v3; + let v7 = v3 * v3 * v; + let uv7 = u * v7; + uv3 * uv7.pow((-FieldElement::from(5u8)) * FieldElement::from(8u8).invert().unwrap()) + }; + let x = X.square() * x; + + let y = w - x; + let non_zero_0 = !y.is_zero(); + let y_if_non_zero_0 = w + x; + let sign = non_zero_0 & (!y_if_non_zero_0.is_zero()); + + let mut z = -A; + z *= FieldElement::conditional_select(&v, &FieldElement::from(1u8), sign); + #[allow(non_snake_case)] + let Z = z + w; + #[allow(non_snake_case)] + let mut Y = z - w; + + Y = Y * Z.invert().unwrap(); + let mut bytes = Y.to_repr(); + bytes[31] |= sign.unwrap_u8() << 7; + + CompressedEdwardsY(bytes).decompress().unwrap().mul_by_cofactor() +} diff --git a/coins/monero/src/ringct/mod.rs b/coins/monero/src/ringct/mod.rs index dbfc0fad..4935265e 100644 --- a/coins/monero/src/ringct/mod.rs +++ b/coins/monero/src/ringct/mod.rs @@ -1,13 +1,20 @@ -use curve25519_dalek::edwards::EdwardsPoint; +use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar, edwards::EdwardsPoint}; + +pub(crate) mod hash_to_point; +pub use hash_to_point::hash_to_point; -pub mod bulletproofs; pub mod clsag; +pub mod bulletproofs; use crate::{ serialize::*, ringct::{clsag::Clsag, bulletproofs::Bulletproofs} }; +pub fn generate_key_image(secret: Scalar) -> EdwardsPoint { + secret * hash_to_point(&secret * &ED25519_BASEPOINT_TABLE) +} + #[derive(Clone, PartialEq, Debug)] pub struct RctBase { pub fee: u64, diff --git a/coins/monero/src/tests/clsag.rs b/coins/monero/src/tests/clsag.rs index 66644a50..17cb9940 100644 --- a/coins/monero/src/tests/clsag.rs +++ b/coins/monero/src/tests/clsag.rs @@ -12,9 +12,9 @@ use frost::curve::Ed25519; use crate::{ Commitment, - random_scalar, generate_key_image, + random_scalar, wallet::Decoys, - ringct::clsag::{ClsagInput, Clsag} + ringct::{generate_key_image, clsag::{ClsagInput, Clsag}} }; #[cfg(feature = "multisig")] use crate::{frost::MultisigError, ringct::clsag::{ClsagDetails, ClsagMultisig}}; @@ -48,7 +48,7 @@ fn clsag() { ring.push([&dest * &ED25519_BASEPOINT_TABLE, Commitment::new(mask, amount).calculate()]); } - let image = generate_key_image(&secrets[0]); + let image = generate_key_image(secrets[0]); let (clsag, pseudo_out) = Clsag::sign( &mut OsRng, &vec![( diff --git a/coins/monero/src/tests/hash_to_point.rs b/coins/monero/src/tests/hash_to_point.rs new file mode 100644 index 00000000..b04bbb9f --- /dev/null +++ b/coins/monero/src/tests/hash_to_point.rs @@ -0,0 +1,13 @@ +use rand::rngs::OsRng; + +use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE; + +use crate::{random_scalar, ringct::hash_to_point::{hash_to_point, rust_hash_to_point}}; + +#[test] +fn test_hash_to_point() { + for _ in 0 .. 200 { + let point = &random_scalar(&mut OsRng) * &ED25519_BASEPOINT_TABLE; + assert_eq!(rust_hash_to_point(point), hash_to_point(point)); + } +} diff --git a/coins/monero/src/tests/mod.rs b/coins/monero/src/tests/mod.rs index d9b85f0c..0ef934c8 100644 --- a/coins/monero/src/tests/mod.rs +++ b/coins/monero/src/tests/mod.rs @@ -1,2 +1,3 @@ +mod hash_to_point; mod clsag; mod address; diff --git a/coins/monero/src/wallet/send/mod.rs b/coins/monero/src/wallet/send/mod.rs index cf9ab33f..16cdca6c 100644 --- a/coins/monero/src/wallet/send/mod.rs +++ b/coins/monero/src/wallet/send/mod.rs @@ -17,8 +17,8 @@ use frost::FrostError; use crate::{ Commitment, random_scalar, - generate_key_image, ringct::{ + generate_key_image, clsag::{ClsagError, ClsagInput, Clsag}, bulletproofs::{MAX_OUTPUTS, Bulletproofs}, RctBase, RctPrunable, RctSignatures @@ -126,7 +126,7 @@ async fn prepare_inputs( for (i, input) in inputs.iter().enumerate() { signable.push(( spend + input.key_offset, - generate_key_image(&(spend + input.key_offset)), + generate_key_image(spend + input.key_offset), ClsagInput::new( input.commitment, decoys[i].clone() @@ -337,7 +337,7 @@ impl SignableTransaction { Err(TransactionError::WrongPrivateKey)?; } - images.push(generate_key_image(&offset)); + images.push(generate_key_image(offset)); } images.sort_by(key_image_sort);