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}
|
|
|
|
};
|
|
|
|
|
|
|
|
use monero::{
|
|
|
|
consensus::Encodable,
|
|
|
|
util::ringct::{Key, Clsag}
|
|
|
|
};
|
|
|
|
|
2022-04-28 07:31:09 +00:00
|
|
|
use crate::{
|
|
|
|
Commitment,
|
|
|
|
c_verify_clsag,
|
|
|
|
random_scalar,
|
|
|
|
hash_to_scalar,
|
|
|
|
hash_to_point
|
|
|
|
};
|
2022-04-22 01:36:18 +00:00
|
|
|
|
|
|
|
#[cfg(feature = "multisig")]
|
|
|
|
mod multisig;
|
|
|
|
#[cfg(feature = "multisig")]
|
2022-04-30 08:32:19 +00:00
|
|
|
pub use multisig::Multisig;
|
2022-04-22 01:36:18 +00:00
|
|
|
|
2022-04-28 21:29:56 +00:00
|
|
|
#[derive(Error, Debug)]
|
|
|
|
pub enum Error {
|
|
|
|
#[error("internal error ({0})")]
|
|
|
|
InternalError(String),
|
|
|
|
#[error("invalid ring member (member {0}, ring size {1})")]
|
|
|
|
InvalidRingMember(u8, u8),
|
|
|
|
#[error("invalid commitment")]
|
|
|
|
InvalidCommitment
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
|
|
|
pub struct Input {
|
|
|
|
// Ring, the index we're signing for, and the actual commitment behind it
|
|
|
|
pub ring: Vec<[EdwardsPoint; 2]>,
|
2022-05-03 11:20:24 +00:00
|
|
|
pub i: u8,
|
|
|
|
pub commitment: Commitment
|
2022-04-28 21:29:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Input {
|
|
|
|
pub fn new(
|
|
|
|
ring: Vec<[EdwardsPoint; 2]>,
|
|
|
|
i: u8,
|
|
|
|
commitment: Commitment
|
2022-05-03 11:20:24 +00:00
|
|
|
) -> Result<Input, Error> {
|
2022-04-28 21:29:56 +00:00
|
|
|
let n = ring.len();
|
|
|
|
if n > u8::MAX.into() {
|
|
|
|
Err(Error::InternalError("max ring size in this library is u8 max".to_string()))?;
|
|
|
|
}
|
|
|
|
if i >= (n as u8) {
|
|
|
|
Err(Error::InvalidRingMember(i, n as u8))?;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate the commitment matches
|
2022-05-03 11:20:24 +00:00
|
|
|
if ring[usize::from(i)][1] != commitment.calculate() {
|
2022-04-28 21:29:56 +00:00
|
|
|
Err(Error::InvalidCommitment)?;
|
|
|
|
}
|
|
|
|
|
2022-04-30 02:03:34 +00:00
|
|
|
Ok(Input { ring, i, commitment })
|
2022-04-28 21:29:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-22 01:36:18 +00:00
|
|
|
#[allow(non_snake_case)]
|
2022-04-28 21:12:54 +00:00
|
|
|
pub(crate) fn sign_core<R: RngCore + CryptoRng>(
|
|
|
|
rng: &mut R,
|
2022-04-23 07:59:21 +00:00
|
|
|
msg: &[u8; 32],
|
2022-04-28 21:29:56 +00:00
|
|
|
input: &Input,
|
2022-04-30 02:03:34 +00:00
|
|
|
image: &EdwardsPoint,
|
2022-04-28 07:31:09 +00:00
|
|
|
mask: Scalar,
|
2022-04-22 01:36:18 +00:00
|
|
|
A: EdwardsPoint,
|
|
|
|
AH: EdwardsPoint
|
|
|
|
) -> (Clsag, Scalar, Scalar, Scalar, Scalar, EdwardsPoint) {
|
2022-04-28 07:31:09 +00:00
|
|
|
let n = input.ring.len();
|
|
|
|
let r: usize = input.i.into();
|
2022-04-22 01:36:18 +00:00
|
|
|
|
|
|
|
let C_out;
|
|
|
|
|
|
|
|
let mut P = vec![];
|
|
|
|
P.reserve_exact(n);
|
|
|
|
let mut C = vec![];
|
|
|
|
C.reserve_exact(n);
|
|
|
|
let mut C_non_zero = vec![];
|
|
|
|
C_non_zero.reserve_exact(n);
|
|
|
|
|
|
|
|
let z;
|
|
|
|
|
|
|
|
{
|
2022-04-28 07:31:09 +00:00
|
|
|
C_out = Commitment::new(mask, input.commitment.amount).calculate();
|
2022-04-22 01:36:18 +00:00
|
|
|
|
2022-04-28 07:31:09 +00:00
|
|
|
for member in &input.ring {
|
2022-04-22 01:36:18 +00:00
|
|
|
P.push(member[0]);
|
|
|
|
C_non_zero.push(member[1]);
|
|
|
|
C.push(C_non_zero[C_non_zero.len() - 1] - C_out);
|
|
|
|
}
|
|
|
|
|
2022-04-28 07:31:09 +00:00
|
|
|
z = input.commitment.mask - mask;
|
2022-04-22 01:36:18 +00:00
|
|
|
}
|
|
|
|
|
2022-04-28 07:31:09 +00:00
|
|
|
let H = hash_to_point(&P[r]);
|
2022-04-22 01:36:18 +00:00
|
|
|
let mut D = H * z;
|
|
|
|
|
|
|
|
// Doesn't use a constant time table as dalek takes longer to generate those then they save
|
2022-04-30 02:03:34 +00:00
|
|
|
let images_precomp = VartimeEdwardsPrecomputation::new([image, &D]);
|
2022-04-22 01:36:18 +00:00
|
|
|
D = Scalar::from(8 as u8).invert() * D;
|
|
|
|
|
|
|
|
let mut to_hash = vec![];
|
|
|
|
to_hash.reserve_exact(((2 * n) + 4) * 32);
|
|
|
|
const PREFIX: &str = "CLSAG_";
|
|
|
|
const AGG_0: &str = "CLSAG_agg_0";
|
|
|
|
const ROUND: &str = "round";
|
|
|
|
to_hash.extend(AGG_0.bytes());
|
|
|
|
to_hash.extend([0; 32 - AGG_0.len()]);
|
|
|
|
|
2022-04-28 07:31:09 +00:00
|
|
|
for i in 0 .. n {
|
|
|
|
to_hash.extend(P[i].compress().to_bytes());
|
2022-04-22 01:36:18 +00:00
|
|
|
}
|
|
|
|
|
2022-04-28 07:31:09 +00:00
|
|
|
for i in 0 .. n {
|
|
|
|
to_hash.extend(C_non_zero[i].compress().to_bytes());
|
2022-04-22 01:36:18 +00:00
|
|
|
}
|
|
|
|
|
2022-04-30 02:03:34 +00:00
|
|
|
to_hash.extend(image.compress().to_bytes());
|
2022-04-22 01:36:18 +00:00
|
|
|
let D_bytes = D.compress().to_bytes();
|
|
|
|
to_hash.extend(D_bytes);
|
|
|
|
to_hash.extend(C_out.compress().to_bytes());
|
|
|
|
let mu_P = hash_to_scalar(&to_hash);
|
|
|
|
to_hash[AGG_0.len() - 1] = '1' as u8;
|
|
|
|
let mu_C = hash_to_scalar(&to_hash);
|
|
|
|
|
|
|
|
to_hash.truncate(((2 * n) + 1) * 32);
|
|
|
|
to_hash.reserve_exact(((2 * n) + 5) * 32);
|
2022-04-28 07:31:09 +00:00
|
|
|
for i in 0 .. ROUND.len() {
|
|
|
|
to_hash[PREFIX.len() + i] = ROUND.as_bytes()[i] as u8;
|
2022-04-22 01:36:18 +00:00
|
|
|
}
|
|
|
|
to_hash.extend(C_out.compress().to_bytes());
|
|
|
|
to_hash.extend(msg);
|
|
|
|
to_hash.extend(A.compress().to_bytes());
|
|
|
|
to_hash.extend(AH.compress().to_bytes());
|
|
|
|
let mut c = hash_to_scalar(&to_hash);
|
|
|
|
|
|
|
|
let mut c1 = Scalar::zero();
|
2022-04-28 07:31:09 +00:00
|
|
|
let mut i = (r + 1) % n;
|
|
|
|
if i == 0 {
|
2022-04-22 01:36:18 +00:00
|
|
|
c1 = c;
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut s = vec![];
|
|
|
|
s.resize(n, Scalar::zero());
|
2022-04-28 07:31:09 +00:00
|
|
|
while i != r {
|
2022-04-29 00:09:31 +00:00
|
|
|
s[i] = random_scalar(&mut *rng);
|
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-04-28 07:31:09 +00:00
|
|
|
i = (i + 1) % n;
|
|
|
|
if i == 0 {
|
2022-04-22 01:36:18 +00:00
|
|
|
c1 = c;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
(
|
|
|
|
Clsag {
|
|
|
|
s: s.iter().map(|s| Key { key: s.to_bytes() }).collect(),
|
|
|
|
c1: Key { key: c1.to_bytes() },
|
|
|
|
D: Key { key: D_bytes }
|
|
|
|
},
|
|
|
|
c, mu_C, z, mu_P,
|
|
|
|
C_out
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
pub fn sign<R: RngCore + CryptoRng>(
|
|
|
|
rng: &mut R,
|
|
|
|
msg: [u8; 32],
|
2022-04-30 02:03:34 +00:00
|
|
|
inputs: &[(Scalar, Input, EdwardsPoint)],
|
2022-04-28 07:31:09 +00:00
|
|
|
sum_outputs: Scalar
|
|
|
|
) -> Option<Vec<(Clsag, EdwardsPoint)>> {
|
|
|
|
if inputs.len() == 0 {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
|
|
|
let nonce = random_scalar(rng);
|
2022-04-22 01:36:18 +00:00
|
|
|
let mut rand_source = [0; 64];
|
|
|
|
rng.fill_bytes(&mut rand_source);
|
2022-04-28 07:31:09 +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, c, mu_C, z, mu_P, C_out) = sign_core(
|
2022-04-28 21:12:54 +00:00
|
|
|
rng,
|
2022-04-28 07:31:09 +00:00
|
|
|
&msg,
|
|
|
|
&inputs[i].1,
|
2022-04-30 02:03:34 +00:00
|
|
|
&inputs[i].2,
|
2022-04-28 07:31:09 +00:00
|
|
|
mask,
|
2022-05-03 11:20:24 +00:00
|
|
|
&nonce * &ED25519_BASEPOINT_TABLE, nonce * hash_to_point(&inputs[i].1.ring[usize::from(inputs[i].1.i)][0])
|
2022-04-28 07:31:09 +00:00
|
|
|
);
|
|
|
|
clsag.s[inputs[i].1.i as usize] = Key {
|
|
|
|
key: (nonce - (c * ((mu_C * z) + (mu_P * inputs[i].0)))).to_bytes()
|
|
|
|
};
|
|
|
|
|
|
|
|
res.push((clsag, C_out));
|
|
|
|
}
|
|
|
|
|
|
|
|
Some(res)
|
2022-04-22 01:36:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Uses Monero's C verification function to ensure compatibility with Monero
|
|
|
|
pub fn verify(
|
|
|
|
clsag: &Clsag,
|
|
|
|
msg: &[u8; 32],
|
2022-04-28 16:01:20 +00:00
|
|
|
image: EdwardsPoint,
|
2022-04-22 01:36:18 +00:00
|
|
|
ring: &[[EdwardsPoint; 2]],
|
|
|
|
pseudo_out: EdwardsPoint
|
2022-04-28 07:31:09 +00:00
|
|
|
) -> bool {
|
2022-04-22 01:36:18 +00:00
|
|
|
// Workaround for the fact monero-rs doesn't include the length of clsag.s in clsag encoding
|
|
|
|
// despite it being part of clsag encoding. Reason for the patch version pin
|
|
|
|
let mut serialized = vec![clsag.s.len() as u8];
|
|
|
|
clsag.consensus_encode(&mut serialized).unwrap();
|
|
|
|
|
|
|
|
let image_bytes = image.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 {
|
2022-04-28 07:31:09 +00:00
|
|
|
c_verify_clsag(
|
2022-04-22 01:36:18 +00:00
|
|
|
serialized.len(), serialized.as_ptr(), image_bytes.as_ptr(),
|
2022-04-23 07:59:21 +00:00
|
|
|
ring.len() as u8, ring_bytes.as_ptr(), msg.as_ptr(), pseudo_out_bytes.as_ptr()
|
2022-04-28 07:31:09 +00:00
|
|
|
)
|
2022-04-22 01:36:18 +00:00
|
|
|
}
|
|
|
|
}
|