mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-12 14:09:25 +00:00
Remove Monero as a dependency
Introduces missing CLSAG checks. The only difference now should be the additional rejection of torsioned points, which is relevant to https://github.com/serai-dex/serai/issues/25. Considering this is only currently used for FROST verification, this should be fine. Closes https://github.com/serai-dex/serai/issues/19 by making it irrelevant. Increases priority of https://github.com/serai-dex/serai/issues/68, as now it's used for the BP generators which are done at first-proof. Also merges BP's stricter hash_to_point with the library's, since CLSAG has the same bound.
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user