Implement a CLSAG algorithm extension which also does key images

Practically, this should be mergeable. There's little reason to do a 
CLSAG and not also a key image. Keeps them isolated for now.
This commit is contained in:
Luke Parker 2022-04-29 22:03:34 -04:00
parent 45559e14ee
commit 27396a6291
No known key found for this signature in database
GPG key ID: F9F1386DB1E119B6
10 changed files with 213 additions and 103 deletions

View file

@ -24,7 +24,7 @@ use crate::{
#[cfg(feature = "multisig")] #[cfg(feature = "multisig")]
mod multisig; mod multisig;
#[cfg(feature = "multisig")] #[cfg(feature = "multisig")]
pub use multisig::Multisig; pub use multisig::{Msg, Multisig, InputMultisig};
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum Error { pub enum Error {
@ -38,16 +38,14 @@ pub enum Error {
#[derive(Clone, PartialEq, Eq, Debug)] #[derive(Clone, PartialEq, Eq, Debug)]
pub struct Input { pub struct Input {
pub image: EdwardsPoint,
// Ring, the index we're signing for, and the actual commitment behind it // Ring, the index we're signing for, and the actual commitment behind it
pub ring: Vec<[EdwardsPoint; 2]>, pub ring: Vec<[EdwardsPoint; 2]>,
pub i: usize, pub i: usize,
pub commitment: Commitment pub commitment: Commitment,
} }
impl Input { impl Input {
pub fn new( pub fn new(
image: EdwardsPoint,
ring: Vec<[EdwardsPoint; 2]>, ring: Vec<[EdwardsPoint; 2]>,
i: u8, i: u8,
commitment: Commitment commitment: Commitment
@ -66,16 +64,13 @@ impl Input {
Err(Error::InvalidCommitment)?; Err(Error::InvalidCommitment)?;
} }
Ok(Input { image, ring, i, commitment }) Ok(Input { ring, i, commitment })
} }
#[cfg(feature = "multisig")] #[cfg(feature = "multisig")]
pub fn context(&self) -> Vec<u8> { pub fn context(&self) -> Vec<u8> {
// image is extraneous in practice as the image should be in the msg AND the addendum when TX
// signing. This just ensures CLSAG guarantees its integrity, even when others won't
let mut context = self.image.compress().to_bytes().to_vec();
// Ring index // Ring index
context.extend(&u8::try_from(self.i).unwrap().to_le_bytes()); let mut context = u8::try_from(self.i).unwrap().to_le_bytes().to_vec();
// Ring // Ring
for pair in &self.ring { for pair in &self.ring {
// Doesn't include key offsets as CLSAG doesn't care and won't be affected by it // Doesn't include key offsets as CLSAG doesn't care and won't be affected by it
@ -92,6 +87,7 @@ pub(crate) fn sign_core<R: RngCore + CryptoRng>(
rng: &mut R, rng: &mut R,
msg: &[u8; 32], msg: &[u8; 32],
input: &Input, input: &Input,
image: &EdwardsPoint,
mask: Scalar, mask: Scalar,
A: EdwardsPoint, A: EdwardsPoint,
AH: EdwardsPoint AH: EdwardsPoint
@ -126,7 +122,7 @@ pub(crate) fn sign_core<R: RngCore + CryptoRng>(
let mut D = H * z; let mut D = H * z;
// Doesn't use a constant time table as dalek takes longer to generate those then they save // Doesn't use a constant time table as dalek takes longer to generate those then they save
let images_precomp = VartimeEdwardsPrecomputation::new(&[input.image, D]); let images_precomp = VartimeEdwardsPrecomputation::new([image, &D]);
D = Scalar::from(8 as u8).invert() * D; D = Scalar::from(8 as u8).invert() * D;
let mut to_hash = vec![]; let mut to_hash = vec![];
@ -145,7 +141,7 @@ pub(crate) fn sign_core<R: RngCore + CryptoRng>(
to_hash.extend(C_non_zero[i].compress().to_bytes()); to_hash.extend(C_non_zero[i].compress().to_bytes());
} }
to_hash.extend(input.image.compress().to_bytes()); to_hash.extend(image.compress().to_bytes());
let D_bytes = D.compress().to_bytes(); let D_bytes = D.compress().to_bytes();
to_hash.extend(D_bytes); to_hash.extend(D_bytes);
to_hash.extend(C_out.compress().to_bytes()); to_hash.extend(C_out.compress().to_bytes());
@ -208,7 +204,7 @@ pub(crate) fn sign_core<R: RngCore + CryptoRng>(
pub fn sign<R: RngCore + CryptoRng>( pub fn sign<R: RngCore + CryptoRng>(
rng: &mut R, rng: &mut R,
msg: [u8; 32], msg: [u8; 32],
inputs: &[(Scalar, Input)], inputs: &[(Scalar, Input, EdwardsPoint)],
sum_outputs: Scalar sum_outputs: Scalar
) -> Option<Vec<(Clsag, EdwardsPoint)>> { ) -> Option<Vec<(Clsag, EdwardsPoint)>> {
if inputs.len() == 0 { if inputs.len() == 0 {
@ -235,6 +231,7 @@ pub fn sign<R: RngCore + CryptoRng>(
rng, rng,
&msg, &msg,
&inputs[i].1, &inputs[i].1,
&inputs[i].2,
mask, mask,
&nonce * &ED25519_BASEPOINT_TABLE, nonce * hash_to_point(&inputs[i].1.ring[inputs[i].1.i][0]) &nonce * &ED25519_BASEPOINT_TABLE, nonce * hash_to_point(&inputs[i].1.ring[inputs[i].1.i][0])
); );

View file

@ -1,3 +1,5 @@
use core::fmt::Debug;
use rand_core::{RngCore, CryptoRng, SeedableRng}; use rand_core::{RngCore, CryptoRng, SeedableRng};
use rand_chacha::ChaCha12Rng; use rand_chacha::ChaCha12Rng;
@ -5,6 +7,7 @@ use blake2::{Digest, Blake2b512};
use curve25519_dalek::{ use curve25519_dalek::{
constants::ED25519_BASEPOINT_TABLE, constants::ED25519_BASEPOINT_TABLE,
traits::Identity,
scalar::Scalar, scalar::Scalar,
edwards::EdwardsPoint edwards::EdwardsPoint
}; };
@ -19,9 +22,14 @@ use crate::{
random_scalar, random_scalar,
hash_to_point, hash_to_point,
frost::{MultisigError, Ed25519, DLEqProof}, frost::{MultisigError, Ed25519, DLEqProof},
key_image,
clsag::{Input, sign_core, verify} clsag::{Input, sign_core, verify}
}; };
pub trait Msg: Clone + Debug {
fn msg(&self, image: EdwardsPoint) -> [u8; 32];
}
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct ClsagSignInterim { struct ClsagSignInterim {
@ -34,44 +42,43 @@ struct ClsagSignInterim {
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Multisig { pub struct Multisig<M: Msg> {
b: Vec<u8>, b: Vec<u8>,
AH0: dfg::EdwardsPoint, AH: (dfg::EdwardsPoint, dfg::EdwardsPoint),
AH1: dfg::EdwardsPoint,
input: Input, input: Input,
msg: Option<[u8; 32]>, image: Option<EdwardsPoint>,
msg: M,
interim: Option<ClsagSignInterim> interim: Option<ClsagSignInterim>
} }
impl Multisig { impl<M: Msg> Multisig<M> {
pub fn new( pub fn new(
input: Input input: Input,
) -> Result<Multisig, MultisigError> { msg: M
) -> Result<Multisig<M>, MultisigError> {
Ok( Ok(
Multisig { Multisig {
b: vec![], b: vec![],
AH0: dfg::EdwardsPoint::identity(), AH: (dfg::EdwardsPoint::identity(), dfg::EdwardsPoint::identity()),
AH1: dfg::EdwardsPoint::identity(),
input, input,
msg: None, image: None,
msg,
interim: None interim: None
} }
) )
} }
pub fn set_msg( pub fn set_image(&mut self, image: EdwardsPoint) {
&mut self, self.image = Some(image);
msg: [u8; 32]
) {
self.msg = Some(msg);
} }
} }
impl Algorithm<Ed25519> for Multisig { impl<M: Msg> Algorithm<Ed25519> for Multisig<M> {
type Signature = (Clsag, EdwardsPoint); type Signature = (Clsag, EdwardsPoint);
// We arguably don't have to commit to at all thanks to xG and yG being committed to, both of // We arguably don't have to commit to at all thanks to xG and yG being committed to, both of
@ -113,7 +120,6 @@ impl Algorithm<Ed25519> for Multisig {
let h0 = <Ed25519 as Curve>::G_from_slice(&serialized[0 .. 32]).map_err(|_| FrostError::InvalidCommitment(l))?; let h0 = <Ed25519 as Curve>::G_from_slice(&serialized[0 .. 32]).map_err(|_| FrostError::InvalidCommitment(l))?;
DLEqProof::deserialize(&serialized[64 .. 128]).ok_or(FrostError::InvalidCommitment(l))?.verify( DLEqProof::deserialize(&serialized[64 .. 128]).ok_or(FrostError::InvalidCommitment(l))?.verify(
l,
&alt, &alt,
&commitments[0], &commitments[0],
&h0 &h0
@ -121,7 +127,6 @@ impl Algorithm<Ed25519> for Multisig {
let h1 = <Ed25519 as Curve>::G_from_slice(&serialized[32 .. 64]).map_err(|_| FrostError::InvalidCommitment(l))?; let h1 = <Ed25519 as Curve>::G_from_slice(&serialized[32 .. 64]).map_err(|_| FrostError::InvalidCommitment(l))?;
DLEqProof::deserialize(&serialized[128 .. 192]).ok_or(FrostError::InvalidCommitment(l))?.verify( DLEqProof::deserialize(&serialized[128 .. 192]).ok_or(FrostError::InvalidCommitment(l))?.verify(
l,
&alt, &alt,
&commitments[1], &commitments[1],
&h1 &h1
@ -129,33 +134,34 @@ impl Algorithm<Ed25519> for Multisig {
self.b.extend(&l.to_le_bytes()); self.b.extend(&l.to_le_bytes());
self.b.extend(&serialized[0 .. 64]); self.b.extend(&serialized[0 .. 64]);
self.AH0 += h0; self.AH.0 += h0;
self.AH1 += h1; self.AH.1 += h1;
Ok(()) Ok(())
} }
fn context(&self) -> Vec<u8> { fn context(&self) -> Vec<u8> {
let mut context = vec![]; let mut context = vec![];
context.extend(&self.msg.unwrap()); // This should be redundant as the image should be in the addendum if using InputMultisig and
// in msg if signing a Transaction, yet this ensures CLSAG takes responsibility for its own
// security boundaries
context.extend(&self.image.unwrap().compress().to_bytes());
context.extend(&self.msg.msg(self.image.unwrap()));
context.extend(&self.input.context()); context.extend(&self.input.context());
context context
} }
fn process_binding(
&mut self,
p: &dfg::Scalar,
) {
self.AH0 += self.AH1 * p;
}
fn sign_share( fn sign_share(
&mut self, &mut self,
view: &ParamsView<Ed25519>, view: &ParamsView<Ed25519>,
nonce_sum: dfg::EdwardsPoint, nonce_sum: dfg::EdwardsPoint,
b: dfg::Scalar,
nonce: dfg::Scalar, nonce: dfg::Scalar,
_: &[u8] _: &[u8]
) -> dfg::Scalar { ) -> dfg::Scalar {
// Apply the binding factor to the H variant of the nonce
self.AH.0 += self.AH.1 * b;
// Use everyone's commitments to derive a random source all signers can agree upon // Use everyone's commitments to derive a random source all signers can agree upon
// Cannot be manipulated to effect and all signers must, and will, know this // Cannot be manipulated to effect and all signers must, and will, know this
// Uses the context as well to prevent passive observers of messages from being able to break // Uses the context as well to prevent passive observers of messages from being able to break
@ -170,11 +176,12 @@ impl Algorithm<Ed25519> for Multisig {
#[allow(non_snake_case)] #[allow(non_snake_case)]
let (clsag, c, mu_C, z, mu_P, C_out) = sign_core( let (clsag, c, mu_C, z, mu_P, C_out) = sign_core(
&mut rng, &mut rng,
&self.msg.unwrap(), &self.msg.msg(self.image.unwrap()),
&self.input, &self.input,
&self.image.unwrap(),
mask, mask,
nonce_sum.0, nonce_sum.0,
self.AH0.0 self.AH.0.0
); );
self.interim = Some(ClsagSignInterim { c: c * mu_P, s: c * mu_C * z, clsag, C_out }); self.interim = Some(ClsagSignInterim { c: c * mu_P, s: c * mu_C * z, clsag, C_out });
@ -193,7 +200,7 @@ impl Algorithm<Ed25519> for Multisig {
let mut clsag = interim.clsag.clone(); let mut clsag = interim.clsag.clone();
clsag.s[self.input.i] = Key { key: (sum.0 - interim.s).to_bytes() }; clsag.s[self.input.i] = Key { key: (sum.0 - interim.s).to_bytes() };
if verify(&clsag, &self.msg.unwrap(), self.input.image, &self.input.ring, interim.C_out) { if verify(&clsag, &self.msg.msg(self.image.unwrap()), self.image.unwrap(), &self.input.ring, interim.C_out) {
return Some((clsag, interim.C_out)); return Some((clsag, interim.C_out));
} }
return None; return None;
@ -211,3 +218,87 @@ impl Algorithm<Ed25519> for Multisig {
); );
} }
} }
#[allow(non_snake_case)]
#[derive(Clone, Debug)]
pub struct InputMultisig<M: Msg>(EdwardsPoint, Multisig<M>);
impl<M: Msg> InputMultisig<M> {
pub fn new(
input: Input,
msg: M
) -> Result<InputMultisig<M>, MultisigError> {
Ok(InputMultisig(EdwardsPoint::identity(), Multisig::new(input, msg)?))
}
pub fn image(&self) -> EdwardsPoint {
self.0
}
}
impl<M: Msg> Algorithm<Ed25519> for InputMultisig<M> {
type Signature = (Clsag, EdwardsPoint);
fn addendum_commit_len() -> usize {
32 + Multisig::<M>::addendum_commit_len()
}
fn preprocess_addendum<R: RngCore + CryptoRng>(
rng: &mut R,
view: &ParamsView<Ed25519>,
nonces: &[dfg::Scalar; 2]
) -> Vec<u8> {
let (mut serialized, end) = key_image::generate_share(rng, view);
serialized.extend(Multisig::<M>::preprocess_addendum(rng, view, nonces));
serialized.extend(end);
serialized
}
fn process_addendum(
&mut self,
view: &ParamsView<Ed25519>,
l: usize,
commitments: &[dfg::EdwardsPoint; 2],
serialized: &[u8]
) -> Result<(), FrostError> {
let (image, serialized) = key_image::verify_share(view, l, serialized).map_err(|_| FrostError::InvalidShare(l))?;
self.0 += image;
if l == *view.included().last().unwrap() {
self.1.set_image(self.0);
}
self.1.process_addendum(view, l, commitments, &serialized)
}
fn context(&self) -> Vec<u8> {
self.1.context()
}
fn sign_share(
&mut self,
view: &ParamsView<Ed25519>,
nonce_sum: dfg::EdwardsPoint,
b: dfg::Scalar,
nonce: dfg::Scalar,
msg: &[u8]
) -> dfg::Scalar {
self.1.sign_share(view, nonce_sum, b, nonce, msg)
}
fn verify(
&self,
group_key: dfg::EdwardsPoint,
nonce: dfg::EdwardsPoint,
sum: dfg::Scalar
) -> Option<Self::Signature> {
self.1.verify(group_key, nonce, sum)
}
fn verify_share(
&self,
verification_share: dfg::EdwardsPoint,
nonce: dfg::EdwardsPoint,
share: dfg::Scalar,
) -> bool {
self.1.verify_share(verification_share, nonce, share)
}
}

View file

@ -26,8 +26,8 @@ use crate::random_scalar;
pub enum MultisigError { pub enum MultisigError {
#[error("internal error ({0})")] #[error("internal error ({0})")]
InternalError(String), InternalError(String),
#[error("invalid discrete log equality proof {0}")] #[error("invalid discrete log equality proof")]
InvalidDLEqProof(usize), InvalidDLEqProof,
#[error("invalid key image {0}")] #[error("invalid key image {0}")]
InvalidKeyImage(usize) InvalidKeyImage(usize)
} }
@ -145,7 +145,6 @@ impl DLEqProof {
pub fn verify( pub fn verify(
&self, &self,
l: usize,
H: &DPoint, H: &DPoint,
primary: &DPoint, primary: &DPoint,
alt: &DPoint alt: &DPoint
@ -166,7 +165,7 @@ impl DLEqProof {
// Take the opportunity to ensure a lack of torsion in key images/randomness commitments // Take the opportunity to ensure a lack of torsion in key images/randomness commitments
if (!primary.is_torsion_free()) || (!alt.is_torsion_free()) || (c != expected_c) { if (!primary.is_torsion_free()) || (!alt.is_torsion_free()) || (c != expected_c) {
Err(MultisigError::InvalidDLEqProof(l))?; Err(MultisigError::InvalidDLEqProof)?;
} }
Ok(()) Ok(())

View file

@ -32,16 +32,15 @@ pub fn verify_share(
share: &[u8] share: &[u8]
) -> Result<(EdwardsPoint, Vec<u8>), MultisigError> { ) -> Result<(EdwardsPoint, Vec<u8>), MultisigError> {
if share.len() < 96 { if share.len() < 96 {
Err(MultisigError::InvalidDLEqProof(l))?; Err(MultisigError::InvalidDLEqProof)?;
} }
let image = CompressedEdwardsY( let image = CompressedEdwardsY(
share[0 .. 32].try_into().unwrap() share[0 .. 32].try_into().unwrap()
).decompress().ok_or(MultisigError::InvalidKeyImage(l))?; ).decompress().ok_or(MultisigError::InvalidKeyImage(l))?;
let proof = DLEqProof::deserialize( let proof = DLEqProof::deserialize(
&share[(share.len() - 64) .. share.len()] &share[(share.len() - 64) .. share.len()]
).ok_or(MultisigError::InvalidDLEqProof(l))?; ).ok_or(MultisigError::InvalidDLEqProof)?;
proof.verify( proof.verify(
l,
&hash_to_point(&view.group_key().0), &hash_to_point(&view.group_key().0),
&view.verification_share(l), &view.verification_share(l),
&image &image

View file

@ -327,7 +327,7 @@ async fn prepare_inputs(
spend: &Scalar, spend: &Scalar,
inputs: &[SpendableOutput], inputs: &[SpendableOutput],
tx: &mut Transaction tx: &mut Transaction
) -> Result<Vec<(Scalar, clsag::Input)>, TransactionError> { ) -> Result<Vec<(Scalar, clsag::Input, EdwardsPoint)>, TransactionError> {
let mut mixins = Vec::with_capacity(inputs.len()); let mut mixins = Vec::with_capacity(inputs.len());
let mut signable = Vec::with_capacity(inputs.len()); let mut signable = Vec::with_capacity(inputs.len());
for (i, input) in inputs.iter().enumerate() { for (i, input) in inputs.iter().enumerate() {
@ -340,51 +340,71 @@ async fn prepare_inputs(
signable.push(( signable.push((
spend + input.key_offset, spend + input.key_offset,
clsag::Input::new( clsag::Input::new(
key_image::generate(&(spend + input.key_offset)),
rpc.get_ring(&mixins[i]).await.map_err(|e| TransactionError::RpcError(e))?, rpc.get_ring(&mixins[i]).await.map_err(|e| TransactionError::RpcError(e))?,
m, m,
input.commitment input.commitment
).map_err(|e| TransactionError::ClsagError(e))? ).map_err(|e| TransactionError::ClsagError(e))?,
key_image::generate(&(spend + input.key_offset))
)); ));
tx.prefix.inputs.push(TxIn::ToKey { tx.prefix.inputs.push(TxIn::ToKey {
amount: VarInt(0), amount: VarInt(0),
key_offsets: mixins::offset(&mixins[i]).iter().map(|x| VarInt(*x)).collect(), key_offsets: mixins::offset(&mixins[i]).iter().map(|x| VarInt(*x)).collect(),
k_image: KeyImage { image: Hash(signable[i].1.image.compress().to_bytes()) } k_image: KeyImage { image: Hash(signable[i].2.compress().to_bytes()) }
}); });
} }
Ok(signable) Ok(signable)
} }
pub async fn send<R: RngCore + CryptoRng>( pub struct SignableTransaction {
rng: &mut R, inputs: Vec<SpendableOutput>,
rpc: &Rpc, payments: Vec<(Address, u64)>,
spend: &Scalar,
inputs: &[SpendableOutput],
payments: &[(Address, u64)],
change: Address, change: Address,
fee_per_byte: u64 fee_per_byte: u64
) -> Result<Transaction, TransactionError> { }
let (_, mask_sum, mut tx) = prepare_outputs(
&mut Preparation::Leader(rng), impl SignableTransaction {
inputs, pub fn new(
payments, inputs: Vec<SpendableOutput>,
change, payments: Vec<(Address, u64)>,
fee_per_byte change: Address,
)?; fee_per_byte: u64
) -> SignableTransaction {
let signable = prepare_inputs(rpc, spend, inputs, &mut tx).await?; SignableTransaction {
inputs,
let clsags = clsag::sign( payments,
rng, change,
tx.signature_hash().expect("Couldn't get the signature hash").0, fee_per_byte
&signable, }
mask_sum }
).ok_or(TransactionError::NoInputs)?;
let mut prunable = tx.rct_signatures.p.unwrap(); pub async fn sign<R: RngCore + CryptoRng>(
prunable.Clsags = clsags.iter().map(|clsag| clsag.0.clone()).collect(); &self,
prunable.pseudo_outs = clsags.iter().map(|clsag| Key { key: clsag.1.compress().to_bytes() }).collect(); rng: &mut R,
tx.rct_signatures.p = Some(prunable); rpc: &Rpc,
Ok(tx) spend: &Scalar
) -> Result<Transaction, TransactionError> {
let (_, mask_sum, mut tx) = prepare_outputs(
&mut Preparation::Leader(rng),
&self.inputs,
&self.payments,
self.change,
self.fee_per_byte
)?;
let signable = prepare_inputs(rpc, spend, &self.inputs, &mut tx).await?;
let clsags = clsag::sign(
rng,
tx.signature_hash().expect("Couldn't get the signature hash").0,
&signable,
mask_sum
).ok_or(TransactionError::NoInputs)?;
let mut prunable = tx.rct_signatures.p.unwrap();
prunable.Clsags = clsags.iter().map(|clsag| clsag.0.clone()).collect();
prunable.pseudo_outs = clsags.iter().map(|clsag| Key { key: clsag.1.compress().to_bytes() }).collect();
tx.rct_signatures.p = Some(prunable);
Ok(tx)
}
} }

View file

@ -1,6 +1,6 @@
use rand::{RngCore, rngs::OsRng}; use rand::{RngCore, rngs::OsRng};
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar}; use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar, edwards::EdwardsPoint};
use monero_serai::{random_scalar, Commitment, frost::MultisigError, key_image, clsag}; use monero_serai::{random_scalar, Commitment, frost::MultisigError, key_image, clsag};
@ -39,17 +39,27 @@ fn test_single() {
&vec![( &vec![(
secrets[0], secrets[0],
clsag::Input::new( clsag::Input::new(
image,
ring.clone(), ring.clone(),
RING_INDEX, RING_INDEX,
Commitment::new(secrets[1], AMOUNT) Commitment::new(secrets[1], AMOUNT)
).unwrap() ).unwrap(),
image
)], )],
Scalar::zero() Scalar::zero()
).unwrap().swap_remove(0); ).unwrap().swap_remove(0);
assert!(clsag::verify(&clsag, &msg, image, &ring, pseudo_out)); assert!(clsag::verify(&clsag, &msg, image, &ring, pseudo_out));
} }
#[cfg(feature = "multisig")]
#[derive(Clone, Debug)]
struct Msg([u8; 32]);
#[cfg(feature = "multisig")]
impl clsag::Msg for Msg {
fn msg(&self, _: EdwardsPoint) -> [u8; 32] {
self.0
}
}
#[cfg(feature = "multisig")] #[cfg(feature = "multisig")]
#[test] #[test]
fn test_multisig() -> Result<(), MultisigError> { fn test_multisig() -> Result<(), MultisigError> {
@ -58,8 +68,6 @@ fn test_multisig() -> Result<(), MultisigError> {
let msg = [1; 32]; let msg = [1; 32];
let image = key_image::generate(&group_private.0);
let randomness = random_scalar(&mut OsRng); let randomness = random_scalar(&mut OsRng);
let mut ring = vec![]; let mut ring = vec![];
for i in 0 .. RING_LEN { for i in 0 .. RING_LEN {
@ -79,13 +87,13 @@ fn test_multisig() -> Result<(), MultisigError> {
} }
let mut algorithms = Vec::with_capacity(t); let mut algorithms = Vec::with_capacity(t);
for i in 1 ..= t { for _ in 1 ..= t {
algorithms.push( algorithms.push(
clsag::Multisig::new( clsag::InputMultisig::new(
clsag::Input::new(image, ring.clone(), RING_INDEX, Commitment::new(randomness, AMOUNT)).unwrap() clsag::Input::new(ring.clone(), RING_INDEX, Commitment::new(randomness, AMOUNT)).unwrap(),
Msg(msg)
).unwrap() ).unwrap()
); );
algorithms[i - 1].set_msg(msg);
} }
let mut signatures = sign(algorithms, keys); let mut signatures = sign(algorithms, keys);

View file

@ -41,13 +41,12 @@ impl Algorithm<Ed25519> for DummyAlgorithm {
fn context(&self) -> Vec<u8> { unimplemented!() } fn context(&self) -> Vec<u8> { unimplemented!() }
fn process_binding(&mut self, _: &Scalar) { unimplemented!() }
fn sign_share( fn sign_share(
&mut self, &mut self,
_: &sign::ParamsView<Ed25519>, _: &sign::ParamsView<Ed25519>,
_: EdwardsPoint, _: EdwardsPoint,
_: Scalar, _: Scalar,
_: Scalar,
_: &[u8], _: &[u8],
) -> Scalar { unimplemented!() } ) -> Scalar { unimplemented!() }

View file

@ -9,7 +9,7 @@ use monero::{
use monero_serai::{ use monero_serai::{
random_scalar, random_scalar,
transaction, transaction::{self, SignableTransaction},
rpc::Rpc rpc::Rpc
}; };
@ -48,9 +48,9 @@ pub async fn send() {
output = transaction::scan(&tx, view, spend_pub).swap_remove(0); output = transaction::scan(&tx, view, spend_pub).swap_remove(0);
// Test creating a zero change output and a non-zero change output // Test creating a zero change output and a non-zero change output
amount = output.commitment.amount - fee - u64::try_from(i).unwrap(); amount = output.commitment.amount - fee - u64::try_from(i).unwrap();
let tx = transaction::send( let tx = SignableTransaction::new(
&mut OsRng, &rpc, &spend, &vec![output], &vec![(addr, amount)], addr, fee_per_byte vec![output], vec![(addr, amount)], addr, fee_per_byte
).await.unwrap(); ).sign(&mut OsRng, &rpc, &spend).await.unwrap();
rpc.publish_transaction(&tx).await.unwrap(); rpc.publish_transaction(&tx).await.unwrap();
} }
} }

View file

@ -33,9 +33,6 @@ pub trait Algorithm<C: Curve>: Clone {
/// Context for this algorithm to be hashed into b, and therefore committed to /// Context for this algorithm to be hashed into b, and therefore committed to
fn context(&self) -> Vec<u8>; fn context(&self) -> Vec<u8>;
/// Process the binding factor generated from all the committed to data
fn process_binding(&mut self, p: &C::F);
/// Sign a share with the given secret/nonce /// Sign a share with the given secret/nonce
/// The secret will already have been its lagrange coefficient applied so it is the necessary /// The secret will already have been its lagrange coefficient applied so it is the necessary
/// key share /// key share
@ -44,6 +41,7 @@ pub trait Algorithm<C: Curve>: Clone {
&mut self, &mut self,
params: &sign::ParamsView<C>, params: &sign::ParamsView<C>,
nonce_sum: C::G, nonce_sum: C::G,
b: C::F,
nonce: C::F, nonce: C::F,
msg: &[u8], msg: &[u8],
) -> C::F; ) -> C::F;
@ -120,12 +118,11 @@ impl<C: Curve, H: Hram<C>> Algorithm<C> for Schnorr<C, H> {
vec![] vec![]
} }
fn process_binding(&mut self, _: &C::F) {}
fn sign_share( fn sign_share(
&mut self, &mut self,
params: &sign::ParamsView<C>, params: &sign::ParamsView<C>,
nonce_sum: C::G, nonce_sum: C::G,
_: C::F,
nonce: C::F, nonce: C::F,
msg: &[u8], msg: &[u8],
) -> C::F { ) -> C::F {

View file

@ -287,7 +287,6 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
} }
let b = C::hash_to_F(&b); let b = C::hash_to_F(&b);
params.algorithm.process_binding(&b);
#[allow(non_snake_case)] #[allow(non_snake_case)]
let mut Ris = vec![]; let mut Ris = vec![];
@ -305,6 +304,7 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
let share = params.algorithm.sign_share( let share = params.algorithm.sign_share(
view, view,
R, R,
b,
our_preprocess.nonces[0] + (our_preprocess.nonces[1] * b), our_preprocess.nonces[0] + (our_preprocess.nonces[1] * b),
msg msg
); );