Implement TX creation

Updates CLSAG signing as needed. Moves around Error types.

CLSAG multisig and the multisig feature is currently completely borked 
because of this. The created TXs are accepted by Monero nodes.
This commit is contained in:
Luke Parker
2022-04-28 03:31:09 -04:00
parent b10b531311
commit f3a5e3c27e
13 changed files with 802 additions and 145 deletions

View File

@@ -14,60 +14,31 @@ use monero::{
util::ringct::{Key, Clsag}
};
use crate::{SignError, c_verify_clsag, random_scalar, commitment, hash_to_scalar, hash_to_point};
use crate::{
Commitment,
transaction::SignableInput,
c_verify_clsag,
random_scalar,
hash_to_scalar,
hash_to_point
};
#[cfg(feature = "multisig")]
mod multisig;
#[cfg(feature = "multisig")]
pub use multisig::Multisig;
// Ring with both the index we're signing for and the data needed to rebuild its commitment
#[derive(Clone, PartialEq, Eq, Debug)]
pub(crate) struct SemiSignableRing {
ring: Vec<[EdwardsPoint; 2]>,
i: usize,
randomness: Scalar,
amount: u64
}
pub(crate) fn validate_sign_args(
ring: Vec<[EdwardsPoint; 2]>,
i: u8,
private_key: Option<&Scalar>, // Option as multisig won't have access to this
randomness: &Scalar,
amount: u64
) -> Result<SemiSignableRing, SignError> {
let n = ring.len();
if n > u8::MAX.into() {
Err(SignError::InternalError("max ring size in this library is u8 max".to_string()))?;
}
if i >= (n as u8) {
Err(SignError::InvalidRingMember(i, n as u8))?;
}
let i: usize = i.into();
// Validate the secrets match these ring members
if private_key.is_some() && (ring[i][0] != (private_key.unwrap() * &ED25519_BASEPOINT_TABLE)) {
Err(SignError::InvalidSecret(0))?;
}
if ring[i][1] != commitment(&randomness, amount) {
Err(SignError::InvalidSecret(1))?;
}
Ok(SemiSignableRing { ring, i, randomness: *randomness, amount })
}
#[allow(non_snake_case)]
pub(crate) fn sign_core(
rand_source: [u8; 64],
image: EdwardsPoint,
ssr: &SemiSignableRing,
msg: &[u8; 32],
input: &SignableInput,
mask: Scalar,
A: EdwardsPoint,
AH: EdwardsPoint
) -> (Clsag, Scalar, Scalar, Scalar, Scalar, EdwardsPoint) {
let n = ssr.ring.len();
let i: usize = ssr.i.into();
let n = input.ring.len();
let r: usize = input.i.into();
let C_out;
@@ -83,24 +54,22 @@ pub(crate) fn sign_core(
let mut next_rand = rand_source;
next_rand = Blake2b512::digest(&next_rand).as_slice().try_into().unwrap();
{
let a = Scalar::from_bytes_mod_order_wide(&next_rand);
next_rand = Blake2b512::digest(&next_rand).as_slice().try_into().unwrap();
C_out = commitment(&a, ssr.amount);
C_out = Commitment::new(mask, input.commitment.amount).calculate();
for member in &ssr.ring {
for member in &input.ring {
P.push(member[0]);
C_non_zero.push(member[1]);
C.push(C_non_zero[C_non_zero.len() - 1] - C_out);
}
z = ssr.randomness - a;
z = input.commitment.mask - mask;
}
let H = hash_to_point(&P[i]);
let H = hash_to_point(&P[r]);
let mut D = H * z;
// Doesn't use a constant time table as dalek takes longer to generate those then they save
let images_precomp = VartimeEdwardsPrecomputation::new(&[image, D]);
let images_precomp = VartimeEdwardsPrecomputation::new(&[input.image, D]);
D = Scalar::from(8 as u8).invert() * D;
let mut to_hash = vec![];
@@ -111,15 +80,15 @@ pub(crate) fn sign_core(
to_hash.extend(AGG_0.bytes());
to_hash.extend([0; 32 - AGG_0.len()]);
for j in 0 .. n {
to_hash.extend(P[j].compress().to_bytes());
for i in 0 .. n {
to_hash.extend(P[i].compress().to_bytes());
}
for j in 0 .. n {
to_hash.extend(C_non_zero[j].compress().to_bytes());
for i in 0 .. n {
to_hash.extend(C_non_zero[i].compress().to_bytes());
}
to_hash.extend(image.compress().to_bytes());
to_hash.extend(input.image.compress().to_bytes());
let D_bytes = D.compress().to_bytes();
to_hash.extend(D_bytes);
to_hash.extend(C_out.compress().to_bytes());
@@ -129,8 +98,8 @@ pub(crate) fn sign_core(
to_hash.truncate(((2 * n) + 1) * 32);
to_hash.reserve_exact(((2 * n) + 5) * 32);
for j in 0 .. ROUND.len() {
to_hash[PREFIX.len() + j] = ROUND.as_bytes()[j] as u8;
for i in 0 .. ROUND.len() {
to_hash[PREFIX.len() + i] = ROUND.as_bytes()[i] as u8;
}
to_hash.extend(C_out.compress().to_bytes());
to_hash.extend(msg);
@@ -139,31 +108,31 @@ pub(crate) fn sign_core(
let mut c = hash_to_scalar(&to_hash);
let mut c1 = Scalar::zero();
let mut j = (i + 1) % n;
if j == 0 {
let mut i = (r + 1) % n;
if i == 0 {
c1 = c;
}
let mut s = vec![];
s.resize(n, Scalar::zero());
while j != i {
s[j] = Scalar::from_bytes_mod_order_wide(&next_rand);
while i != r {
s[i] = Scalar::from_bytes_mod_order_wide(&next_rand);
next_rand = Blake2b512::digest(&next_rand).as_slice().try_into().unwrap();
let c_p = mu_P * c;
let c_c = mu_C * c;
let L = (&s[j] * &ED25519_BASEPOINT_TABLE) + (c_p * P[j]) + (c_c * C[j]);
let PH = hash_to_point(&P[j]);
let L = (&s[i] * &ED25519_BASEPOINT_TABLE) + (c_p * P[i]) + (c_c * C[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[j] * PH) + images_precomp.vartime_multiscalar_mul(&[c_p, c_c]);
let R = (s[i] * PH) + images_precomp.vartime_multiscalar_mul(&[c_p, c_c]);
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);
j = (j + 1) % n;
if j == 0 {
i = (i + 1) % n;
if i == 0 {
c1 = c;
}
}
@@ -182,28 +151,45 @@ pub(crate) fn sign_core(
#[allow(non_snake_case)]
pub fn sign<R: RngCore + CryptoRng>(
rng: &mut R,
image: EdwardsPoint,
msg: [u8; 32],
ring: Vec<[EdwardsPoint; 2]>,
i: u8,
private_key: &Scalar,
randomness: &Scalar,
amount: u64
) -> Result<(Clsag, EdwardsPoint), SignError> {
let ssr = validate_sign_args(ring, i, Some(private_key), randomness, amount)?;
let a = random_scalar(rng);
inputs: &[(Scalar, SignableInput)],
sum_outputs: Scalar
) -> Option<Vec<(Clsag, EdwardsPoint)>> {
if inputs.len() == 0 {
return None;
}
let nonce = random_scalar(rng);
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(
rand_source,
image,
&ssr,
&msg,
&a * &ED25519_BASEPOINT_TABLE, a * hash_to_point(&ssr.ring[ssr.i][0])
);
clsag.s[i as usize] = Key { key: (a - (c * ((mu_C * z) + (mu_P * private_key)))).to_bytes() };
Ok((clsag, C_out))
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(
rand_source,
&msg,
&inputs[i].1,
mask,
&nonce * &ED25519_BASEPOINT_TABLE, nonce * hash_to_point(&inputs[i].1.ring[inputs[i].1.i][0])
);
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)
}
// Uses Monero's C verification function to ensure compatibility with Monero
@@ -213,7 +199,7 @@ pub fn verify(
msg: &[u8; 32],
ring: &[[EdwardsPoint; 2]],
pseudo_out: EdwardsPoint
) -> Result<(), SignError> {
) -> bool {
// 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];
@@ -229,13 +215,10 @@ pub fn verify(
let pseudo_out_bytes = pseudo_out.compress().to_bytes();
let success;
unsafe {
success = c_verify_clsag(
c_verify_clsag(
serialized.len(), serialized.as_ptr(), image_bytes.as_ptr(),
ring.len() as u8, ring_bytes.as_ptr(), msg.as_ptr(), pseudo_out_bytes.as_ptr()
);
)
}
if success { Ok(()) } else { Err(SignError::InvalidSignature) }
}