mirror of
https://github.com/serai-dex/serai.git
synced 2025-01-03 17:40:34 +00:00
Implement hash_to_point in Rust
Closes https://github.com/serai-dex/serai/issues/32.
This commit is contained in:
parent
6ce506a79d
commit
953a873338
10 changed files with 108 additions and 38 deletions
|
@ -26,9 +26,9 @@ blake2 = { version = "0.10", optional = true }
|
||||||
|
|
||||||
curve25519-dalek = { version = "3", features = ["std"] }
|
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 }
|
transcript = { package = "flexible-transcript", path = "../../crypto/transcript", features = ["recommended"], optional = true }
|
||||||
frost = { package = "modular-frost", path = "../../crypto/frost", features = ["ed25519"], optional = true }
|
frost = { package = "modular-frost", path = "../../crypto/frost", features = ["ed25519"], optional = true }
|
||||||
dleq = { path = "../../crypto/dleq", features = ["serialize"], optional = true }
|
dleq = { path = "../../crypto/dleq", features = ["serialize"], optional = true }
|
||||||
|
@ -45,7 +45,7 @@ reqwest = { version = "0.11", features = ["json"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
experimental = []
|
experimental = []
|
||||||
multisig = ["rand_chacha", "blake2", "group", "dalek-ff-group", "transcript", "frost", "dleq"]
|
multisig = ["rand_chacha", "blake2", "transcript", "frost", "dleq"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
|
|
|
@ -98,20 +98,3 @@ pub fn hash(data: &[u8]) -> [u8; 32] {
|
||||||
pub fn hash_to_scalar(data: &[u8]) -> Scalar {
|
pub fn hash_to_scalar(data: &[u8]) -> Scalar {
|
||||||
Scalar::from_bytes_mod_order(hash(&data))
|
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))
|
|
||||||
}
|
|
||||||
|
|
|
@ -12,11 +12,11 @@ use curve25519_dalek::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Commitment,
|
Commitment, random_scalar, hash_to_scalar,
|
||||||
|
transaction::RING_LEN,
|
||||||
wallet::decoys::Decoys,
|
wallet::decoys::Decoys,
|
||||||
random_scalar, hash_to_scalar, hash_to_point,
|
ringct::hash_to_point,
|
||||||
serialize::*,
|
serialize::*
|
||||||
transaction::RING_LEN
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
|
@ -170,7 +170,7 @@ fn core(
|
||||||
let c_c = mu_C * c;
|
let c_c = mu_C * c;
|
||||||
|
|
||||||
let L = (&s[i] * &ED25519_BASEPOINT_TABLE) + (c_p * P[i]) + (c_c * C[i]);
|
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
|
// 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]);
|
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 pseudo_out = Commitment::new(mask, input.commitment.amount).calculate();
|
||||||
let z = input.commitment.mask - mask;
|
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 D = H * z;
|
||||||
let mut s = Vec::with_capacity(input.decoys.ring.len());
|
let mut s = Vec::with_capacity(input.decoys.ring.len());
|
||||||
for _ in 0 .. input.decoys.ring.len() {
|
for _ in 0 .. input.decoys.ring.len() {
|
||||||
|
@ -254,7 +254,7 @@ impl Clsag {
|
||||||
mask,
|
mask,
|
||||||
&msg,
|
&msg,
|
||||||
&nonce * &ED25519_BASEPOINT_TABLE,
|
&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);
|
clsag.s[usize::from(inputs[i].2.decoys.i)] = nonce - ((p * inputs[i].0) + c);
|
||||||
|
|
||||||
|
|
|
@ -18,9 +18,8 @@ use frost::{curve::Ed25519, FrostError, FrostView, algorithm::Algorithm};
|
||||||
use dalek_ff_group as dfg;
|
use dalek_ff_group as dfg;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
hash_to_point,
|
|
||||||
frost::{MultisigError, write_dleq, read_dleq},
|
frost::{MultisigError, write_dleq, read_dleq},
|
||||||
ringct::clsag::{ClsagInput, Clsag}
|
ringct::{hash_to_point, clsag::{ClsagInput, Clsag}}
|
||||||
};
|
};
|
||||||
|
|
||||||
impl ClsagInput {
|
impl ClsagInput {
|
||||||
|
@ -129,7 +128,7 @@ impl Algorithm<Ed25519> for ClsagMultisig {
|
||||||
view: &FrostView<Ed25519>,
|
view: &FrostView<Ed25519>,
|
||||||
nonces: &[dfg::Scalar; 2]
|
nonces: &[dfg::Scalar; 2]
|
||||||
) -> Vec<u8> {
|
) -> Vec<u8> {
|
||||||
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());
|
let mut serialized = Vec::with_capacity(ClsagMultisig::serialized_len());
|
||||||
serialized.extend((view.secret_share().0 * self.H).compress().to_bytes());
|
serialized.extend((view.secret_share().0 * self.H).compress().to_bytes());
|
||||||
|
|
67
coins/monero/src/ringct/hash_to_point.rs
Normal file
67
coins/monero/src/ringct/hash_to_point.rs
Normal file
|
@ -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()
|
||||||
|
}
|
|
@ -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 clsag;
|
||||||
|
pub mod bulletproofs;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
serialize::*,
|
serialize::*,
|
||||||
ringct::{clsag::Clsag, bulletproofs::Bulletproofs}
|
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)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
pub struct RctBase {
|
pub struct RctBase {
|
||||||
pub fee: u64,
|
pub fee: u64,
|
||||||
|
|
|
@ -12,9 +12,9 @@ use frost::curve::Ed25519;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Commitment,
|
Commitment,
|
||||||
random_scalar, generate_key_image,
|
random_scalar,
|
||||||
wallet::Decoys,
|
wallet::Decoys,
|
||||||
ringct::clsag::{ClsagInput, Clsag}
|
ringct::{generate_key_image, clsag::{ClsagInput, Clsag}}
|
||||||
};
|
};
|
||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
use crate::{frost::MultisigError, ringct::clsag::{ClsagDetails, ClsagMultisig}};
|
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()]);
|
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(
|
let (clsag, pseudo_out) = Clsag::sign(
|
||||||
&mut OsRng,
|
&mut OsRng,
|
||||||
&vec![(
|
&vec![(
|
||||||
|
|
13
coins/monero/src/tests/hash_to_point.rs
Normal file
13
coins/monero/src/tests/hash_to_point.rs
Normal file
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,2 +1,3 @@
|
||||||
|
mod hash_to_point;
|
||||||
mod clsag;
|
mod clsag;
|
||||||
mod address;
|
mod address;
|
||||||
|
|
|
@ -17,8 +17,8 @@ use frost::FrostError;
|
||||||
use crate::{
|
use crate::{
|
||||||
Commitment,
|
Commitment,
|
||||||
random_scalar,
|
random_scalar,
|
||||||
generate_key_image,
|
|
||||||
ringct::{
|
ringct::{
|
||||||
|
generate_key_image,
|
||||||
clsag::{ClsagError, ClsagInput, Clsag},
|
clsag::{ClsagError, ClsagInput, Clsag},
|
||||||
bulletproofs::{MAX_OUTPUTS, Bulletproofs},
|
bulletproofs::{MAX_OUTPUTS, Bulletproofs},
|
||||||
RctBase, RctPrunable, RctSignatures
|
RctBase, RctPrunable, RctSignatures
|
||||||
|
@ -126,7 +126,7 @@ async fn prepare_inputs<R: RngCore + CryptoRng>(
|
||||||
for (i, input) in inputs.iter().enumerate() {
|
for (i, input) in inputs.iter().enumerate() {
|
||||||
signable.push((
|
signable.push((
|
||||||
spend + input.key_offset,
|
spend + input.key_offset,
|
||||||
generate_key_image(&(spend + input.key_offset)),
|
generate_key_image(spend + input.key_offset),
|
||||||
ClsagInput::new(
|
ClsagInput::new(
|
||||||
input.commitment,
|
input.commitment,
|
||||||
decoys[i].clone()
|
decoys[i].clone()
|
||||||
|
@ -337,7 +337,7 @@ impl SignableTransaction {
|
||||||
Err(TransactionError::WrongPrivateKey)?;
|
Err(TransactionError::WrongPrivateKey)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
images.push(generate_key_image(&offset));
|
images.push(generate_key_image(offset));
|
||||||
}
|
}
|
||||||
images.sort_by(key_image_sort);
|
images.sort_by(key_image_sort);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue