2022-05-14 00:26:29 +00:00
|
|
|
#![allow(non_snake_case)]
|
|
|
|
|
|
|
|
use lazy_static::lazy_static;
|
2022-04-28 21:29:56 +00:00
|
|
|
use thiserror::Error;
|
2022-05-03 11:20:24 +00:00
|
|
|
use rand_core::{RngCore, CryptoRng};
|
2022-04-28 21:29:56 +00:00
|
|
|
|
2022-04-22 01:36:18 +00:00
|
|
|
use curve25519_dalek::{
|
|
|
|
constants::ED25519_BASEPOINT_TABLE,
|
|
|
|
scalar::Scalar,
|
|
|
|
traits::VartimePrecomputedMultiscalarMul,
|
|
|
|
edwards::{EdwardsPoint, VartimeEdwardsPrecomputation}
|
|
|
|
};
|
|
|
|
|
2022-04-28 07:31:09 +00:00
|
|
|
use crate::{
|
|
|
|
Commitment,
|
2022-05-21 19:33:35 +00:00
|
|
|
wallet::decoys::Decoys,
|
|
|
|
random_scalar, hash_to_scalar, hash_to_point,
|
|
|
|
serialize::*
|
2022-04-28 07:31:09 +00:00
|
|
|
};
|
2022-04-22 01:36:18 +00:00
|
|
|
|
|
|
|
#[cfg(feature = "multisig")]
|
|
|
|
mod multisig;
|
|
|
|
#[cfg(feature = "multisig")]
|
2022-05-21 19:33:35 +00:00
|
|
|
pub use multisig::{ClsagDetails, ClsagMultisig};
|
|
|
|
|
|
|
|
lazy_static! {
|
|
|
|
static ref INV_EIGHT: Scalar = Scalar::from(8 as u8).invert();
|
|
|
|
}
|
2022-04-22 01:36:18 +00:00
|
|
|
|
2022-05-28 09:08:37 +00:00
|
|
|
#[derive(Clone, Error, Debug)]
|
2022-05-21 19:33:35 +00:00
|
|
|
pub enum ClsagError {
|
2022-04-28 21:29:56 +00:00
|
|
|
#[error("internal error ({0})")]
|
|
|
|
InternalError(String),
|
|
|
|
#[error("invalid ring member (member {0}, ring size {1})")]
|
|
|
|
InvalidRingMember(u8, u8),
|
|
|
|
#[error("invalid commitment")]
|
2022-05-14 00:26:29 +00:00
|
|
|
InvalidCommitment,
|
|
|
|
#[error("invalid D")]
|
|
|
|
InvalidD,
|
|
|
|
#[error("invalid s")]
|
|
|
|
InvalidS,
|
|
|
|
#[error("invalid c1")]
|
|
|
|
InvalidC1
|
2022-04-28 21:29:56 +00:00
|
|
|
}
|
|
|
|
|
2022-05-25 04:21:01 +00:00
|
|
|
#[derive(Clone, PartialEq, Debug)]
|
2022-05-21 19:33:35 +00:00
|
|
|
pub struct ClsagInput {
|
2022-05-06 23:07:37 +00:00
|
|
|
// The actual commitment for the true spend
|
|
|
|
pub commitment: Commitment,
|
|
|
|
// True spend index, offsets, and ring
|
|
|
|
pub decoys: Decoys
|
2022-04-28 21:29:56 +00:00
|
|
|
}
|
|
|
|
|
2022-05-21 19:33:35 +00:00
|
|
|
impl ClsagInput {
|
2022-04-28 21:29:56 +00:00
|
|
|
pub fn new(
|
2022-05-06 23:07:37 +00:00
|
|
|
commitment: Commitment,
|
|
|
|
decoys: Decoys
|
2022-05-21 19:33:35 +00:00
|
|
|
) -> Result<ClsagInput, ClsagError> {
|
2022-05-06 23:07:37 +00:00
|
|
|
let n = decoys.len();
|
2022-04-28 21:29:56 +00:00
|
|
|
if n > u8::MAX.into() {
|
2022-05-21 19:33:35 +00:00
|
|
|
Err(ClsagError::InternalError("max ring size in this library is u8 max".to_string()))?;
|
2022-04-28 21:29:56 +00:00
|
|
|
}
|
2022-05-06 23:07:37 +00:00
|
|
|
if decoys.i >= (n as u8) {
|
2022-05-21 19:33:35 +00:00
|
|
|
Err(ClsagError::InvalidRingMember(decoys.i, n as u8))?;
|
2022-04-28 21:29:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Validate the commitment matches
|
2022-05-06 23:07:37 +00:00
|
|
|
if decoys.ring[usize::from(decoys.i)][1] != commitment.calculate() {
|
2022-05-21 19:33:35 +00:00
|
|
|
Err(ClsagError::InvalidCommitment)?;
|
2022-04-28 21:29:56 +00:00
|
|
|
}
|
|
|
|
|
2022-05-21 19:33:35 +00:00
|
|
|
Ok(ClsagInput { commitment, decoys })
|
2022-04-28 21:29:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-14 00:26:29 +00:00
|
|
|
enum Mode {
|
|
|
|
Sign(usize, EdwardsPoint, EdwardsPoint),
|
|
|
|
#[cfg(feature = "experimental")]
|
|
|
|
Verify(Scalar)
|
|
|
|
}
|
2022-04-22 01:36:18 +00:00
|
|
|
|
2022-05-21 19:33:35 +00:00
|
|
|
// Core of the CLSAG algorithm, applicable to both sign and verify with minimal differences
|
|
|
|
// Said differences are covered via the above Mode
|
2022-05-14 00:26:29 +00:00
|
|
|
fn core(
|
|
|
|
ring: &[[EdwardsPoint; 2]],
|
|
|
|
I: &EdwardsPoint,
|
|
|
|
pseudo_out: &EdwardsPoint,
|
|
|
|
msg: &[u8; 32],
|
|
|
|
D: &EdwardsPoint,
|
|
|
|
s: &[Scalar],
|
|
|
|
A_c1: Mode
|
2022-05-21 19:33:35 +00:00
|
|
|
) -> ((EdwardsPoint, Scalar, Scalar), Scalar) {
|
2022-05-14 00:26:29 +00:00
|
|
|
let n = ring.len();
|
2022-04-22 01:36:18 +00:00
|
|
|
|
2022-05-14 00:26:29 +00:00
|
|
|
let images_precomp = VartimeEdwardsPrecomputation::new([I, D]);
|
|
|
|
let D = D * *INV_EIGHT;
|
2022-04-22 01:36:18 +00:00
|
|
|
|
2022-05-21 19:33:35 +00:00
|
|
|
// Generate the transcript
|
|
|
|
// Instead of generating multiple, a single transcript is created and then edited as needed
|
2022-04-22 01:36:18 +00:00
|
|
|
let mut to_hash = vec![];
|
2022-05-14 00:26:29 +00:00
|
|
|
to_hash.reserve_exact(((2 * n) + 5) * 32);
|
2022-05-21 19:33:35 +00:00
|
|
|
const PREFIX: &[u8] = "CLSAG_".as_bytes();
|
|
|
|
const AGG_0: &[u8] = "CLSAG_agg_0".as_bytes();
|
|
|
|
const ROUND: &[u8] = "round".as_bytes();
|
|
|
|
to_hash.extend(AGG_0);
|
2022-04-22 01:36:18 +00:00
|
|
|
to_hash.extend([0; 32 - AGG_0.len()]);
|
|
|
|
|
2022-05-21 19:33:35 +00:00
|
|
|
let mut P = Vec::with_capacity(n);
|
2022-05-14 00:26:29 +00:00
|
|
|
for member in ring {
|
|
|
|
P.push(member[0]);
|
|
|
|
to_hash.extend(member[0].compress().to_bytes());
|
2022-04-22 01:36:18 +00:00
|
|
|
}
|
|
|
|
|
2022-05-21 19:33:35 +00:00
|
|
|
let mut C = Vec::with_capacity(n);
|
2022-05-14 00:26:29 +00:00
|
|
|
for member in ring {
|
2022-05-21 19:33:35 +00:00
|
|
|
C.push(member[1] - pseudo_out);
|
2022-05-14 00:26:29 +00:00
|
|
|
to_hash.extend(member[1].compress().to_bytes());
|
|
|
|
}
|
|
|
|
|
|
|
|
to_hash.extend(I.compress().to_bytes());
|
2022-05-21 19:33:35 +00:00
|
|
|
to_hash.extend(D.compress().to_bytes());
|
2022-05-14 00:26:29 +00:00
|
|
|
to_hash.extend(pseudo_out.compress().to_bytes());
|
2022-05-21 19:33:35 +00:00
|
|
|
// mu_P with agg_0
|
2022-04-22 01:36:18 +00:00
|
|
|
let mu_P = hash_to_scalar(&to_hash);
|
2022-05-21 19:33:35 +00:00
|
|
|
// mu_C with agg_1
|
2022-04-22 01:36:18 +00:00
|
|
|
to_hash[AGG_0.len() - 1] = '1' as u8;
|
|
|
|
let mu_C = hash_to_scalar(&to_hash);
|
|
|
|
|
2022-05-21 19:33:35 +00:00
|
|
|
// Truncate it for the round transcript, altering the DST as needed
|
2022-04-22 01:36:18 +00:00
|
|
|
to_hash.truncate(((2 * n) + 1) * 32);
|
2022-04-28 07:31:09 +00:00
|
|
|
for i in 0 .. ROUND.len() {
|
2022-05-21 19:33:35 +00:00
|
|
|
to_hash[PREFIX.len() + i] = ROUND[i] as u8;
|
2022-04-22 01:36:18 +00:00
|
|
|
}
|
2022-05-21 19:33:35 +00:00
|
|
|
// Unfortunately, it's I D pseudo_out instead of pseudo_out I D, meaning this needs to be
|
|
|
|
// truncated just to add it back
|
2022-05-14 00:26:29 +00:00
|
|
|
to_hash.extend(pseudo_out.compress().to_bytes());
|
2022-04-22 01:36:18 +00:00
|
|
|
to_hash.extend(msg);
|
|
|
|
|
2022-05-21 19:33:35 +00:00
|
|
|
// Configure the loop based on if we're signing or verifying
|
2022-05-14 04:45:13 +00:00
|
|
|
let start;
|
2022-05-14 00:26:29 +00:00
|
|
|
let end;
|
2022-05-14 04:45:13 +00:00
|
|
|
let mut c;
|
2022-05-14 00:26:29 +00:00
|
|
|
match A_c1 {
|
|
|
|
Mode::Sign(r, A, AH) => {
|
2022-05-14 04:45:13 +00:00
|
|
|
start = r + 1;
|
|
|
|
end = r + n;
|
2022-05-14 00:26:29 +00:00
|
|
|
to_hash.extend(A.compress().to_bytes());
|
|
|
|
to_hash.extend(AH.compress().to_bytes());
|
|
|
|
c = hash_to_scalar(&to_hash);
|
|
|
|
},
|
|
|
|
|
|
|
|
#[cfg(feature = "experimental")]
|
|
|
|
Mode::Verify(c1) => {
|
2022-05-14 04:45:13 +00:00
|
|
|
start = 0;
|
|
|
|
end = n;
|
2022-05-14 00:26:29 +00:00
|
|
|
c = c1;
|
|
|
|
}
|
2022-04-22 01:36:18 +00:00
|
|
|
}
|
|
|
|
|
2022-05-21 19:33:35 +00:00
|
|
|
// Perform the core loop
|
2022-05-14 04:45:13 +00:00
|
|
|
let mut c1 = None;
|
|
|
|
for i in (start .. end).map(|i| i % n) {
|
|
|
|
if i == 0 {
|
|
|
|
c1 = Some(c);
|
|
|
|
}
|
|
|
|
|
2022-04-22 01:36:18 +00:00
|
|
|
let c_p = mu_P * c;
|
|
|
|
let c_c = mu_C * c;
|
|
|
|
|
2022-04-28 07:31:09 +00:00
|
|
|
let L = (&s[i] * &ED25519_BASEPOINT_TABLE) + (c_p * P[i]) + (c_c * C[i]);
|
|
|
|
let PH = hash_to_point(&P[i]);
|
2022-04-22 01:36:18 +00:00
|
|
|
// Shouldn't be an issue as all of the variables in this vartime statement are public
|
2022-04-28 07:31:09 +00:00
|
|
|
let R = (s[i] * PH) + images_precomp.vartime_multiscalar_mul(&[c_p, c_c]);
|
2022-04-22 01:36:18 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-05-21 19:33:35 +00:00
|
|
|
// This first tuple is needed to continue signing, the latter is the c to be tested/worked with
|
|
|
|
((D, c * mu_P, c * mu_C), c1.unwrap_or(c))
|
2022-05-14 00:26:29 +00:00
|
|
|
}
|
|
|
|
|
2022-05-25 04:21:01 +00:00
|
|
|
#[derive(Clone, PartialEq, Debug)]
|
2022-05-21 19:33:35 +00:00
|
|
|
pub struct Clsag {
|
|
|
|
pub D: EdwardsPoint,
|
|
|
|
pub s: Vec<Scalar>,
|
|
|
|
pub c1: Scalar
|
2022-04-22 01:36:18 +00:00
|
|
|
}
|
|
|
|
|
2022-05-21 19:33:35 +00:00
|
|
|
impl Clsag {
|
|
|
|
// Sign core is the extension of core as needed for signing, yet is shared between single signer
|
|
|
|
// and multisig, hence why it's still core
|
|
|
|
pub(crate) fn sign_core<R: RngCore + CryptoRng>(
|
|
|
|
rng: &mut R,
|
|
|
|
I: &EdwardsPoint,
|
|
|
|
input: &ClsagInput,
|
|
|
|
mask: Scalar,
|
|
|
|
msg: &[u8; 32],
|
|
|
|
A: EdwardsPoint,
|
|
|
|
AH: EdwardsPoint
|
|
|
|
) -> (Clsag, EdwardsPoint, Scalar, Scalar) {
|
|
|
|
let r: usize = input.decoys.i.into();
|
|
|
|
|
|
|
|
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 D = H * z;
|
|
|
|
let mut s = Vec::with_capacity(input.decoys.ring.len());
|
|
|
|
for _ in 0 .. input.decoys.ring.len() {
|
|
|
|
s.push(random_scalar(rng));
|
2022-04-28 07:31:09 +00:00
|
|
|
}
|
2022-05-21 19:33:35 +00:00
|
|
|
let ((D, p, c), c1) = core(&input.decoys.ring, I, &pseudo_out, msg, &D, &s, Mode::Sign(r, A, AH));
|
|
|
|
|
|
|
|
(
|
|
|
|
Clsag { D, s, c1 },
|
|
|
|
pseudo_out,
|
|
|
|
p,
|
|
|
|
c * z
|
|
|
|
)
|
|
|
|
}
|
2022-04-28 07:31:09 +00:00
|
|
|
|
2022-05-21 19:33:35 +00:00
|
|
|
// Single signer CLSAG
|
|
|
|
pub fn sign<R: RngCore + CryptoRng>(
|
|
|
|
rng: &mut R,
|
|
|
|
inputs: &[(Scalar, EdwardsPoint, ClsagInput)],
|
|
|
|
sum_outputs: Scalar,
|
|
|
|
msg: [u8; 32]
|
|
|
|
) -> Vec<(Clsag, EdwardsPoint)> {
|
|
|
|
let nonce = random_scalar(rng);
|
2022-04-28 07:31:09 +00:00
|
|
|
let mut rand_source = [0; 64];
|
|
|
|
rng.fill_bytes(&mut rand_source);
|
|
|
|
|
2022-05-21 19:33:35 +00:00
|
|
|
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);
|
|
|
|
if i == (inputs.len() - 1) {
|
|
|
|
mask = sum_outputs - sum_pseudo_outs;
|
|
|
|
} else {
|
|
|
|
sum_pseudo_outs += mask;
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut rand_source = [0; 64];
|
|
|
|
rng.fill_bytes(&mut rand_source);
|
|
|
|
let (mut clsag, pseudo_out, p, c) = Clsag::sign_core(
|
|
|
|
rng,
|
|
|
|
&inputs[i].1,
|
|
|
|
&inputs[i].2,
|
|
|
|
mask,
|
|
|
|
&msg,
|
|
|
|
&nonce * &ED25519_BASEPOINT_TABLE,
|
|
|
|
nonce * hash_to_point(&inputs[i].2.decoys.ring[usize::from(inputs[i].2.decoys.i)][0])
|
|
|
|
);
|
|
|
|
clsag.s[inputs[i].2.decoys.i as usize] = nonce - ((p * inputs[i].0) + c);
|
|
|
|
|
|
|
|
res.push((clsag, pseudo_out));
|
|
|
|
}
|
2022-04-22 01:36:18 +00:00
|
|
|
|
2022-05-21 19:33:35 +00:00
|
|
|
res
|
2022-05-14 00:26:29 +00:00
|
|
|
}
|
|
|
|
|
2022-05-21 19:33:35 +00:00
|
|
|
// Not extensively tested nor guaranteed to have expected parity with Monero
|
|
|
|
#[cfg(feature = "experimental")]
|
|
|
|
pub fn rust_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)
|
|
|
|
);
|
|
|
|
if c1 != self.c1 {
|
|
|
|
Err(ClsagError::InvalidC1)?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
2022-04-22 01:36:18 +00:00
|
|
|
|
2022-05-21 19:33:35 +00:00
|
|
|
pub fn serialize<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
|
|
|
|
write_raw_vec(write_scalar, &self.s, w)?;
|
|
|
|
w.write_all(&self.c1.to_bytes())?;
|
|
|
|
write_point(&self.D, w)
|
2022-04-22 01:36:18 +00:00
|
|
|
}
|
|
|
|
|
2022-05-22 00:27:21 +00:00
|
|
|
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)?
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-05-21 19:33:35 +00:00
|
|
|
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(),
|
|
|
|
ring.len() as u8, ring_bytes.as_ptr(),
|
|
|
|
I_bytes.as_ptr(), pseudo_out_bytes.as_ptr(), msg.as_ptr()
|
|
|
|
) {
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(ClsagError::InvalidC1)
|
|
|
|
}
|
2022-05-14 00:26:29 +00:00
|
|
|
}
|
2022-04-22 01:36:18 +00:00
|
|
|
}
|
|
|
|
}
|