Update the CLSAG multisig API for TX signing

This commit is contained in:
Luke Parker 2022-04-30 01:41:05 -04:00
parent d0506e2e9b
commit 22ac5ce3b6
No known key found for this signature in database
GPG key ID: F9F1386DB1E119B6
5 changed files with 51 additions and 39 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::{Msg, Multisig, InputMultisig}; pub use multisig::{TransactionData, Multisig, InputMultisig};
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum Error { pub enum Error {

View file

@ -19,15 +19,15 @@ use frost::{Curve, FrostError, algorithm::Algorithm, sign::ParamsView};
use monero::util::ringct::{Key, Clsag}; use monero::util::ringct::{Key, Clsag};
use crate::{ use crate::{
random_scalar,
hash_to_point, hash_to_point,
frost::{MultisigError, Ed25519, DLEqProof}, frost::{MultisigError, Ed25519, DLEqProof},
key_image, key_image,
clsag::{Input, sign_core, verify} clsag::{Input, sign_core, verify}
}; };
pub trait Msg: Clone + Debug { pub trait TransactionData: Clone + Debug {
fn msg(&self, image: EdwardsPoint) -> [u8; 32]; fn msg(&self) -> [u8; 32];
fn mask_sum(&self) -> Scalar;
} }
#[allow(non_snake_case)] #[allow(non_snake_case)]
@ -42,23 +42,23 @@ struct ClsagSignInterim {
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Multisig<M: Msg> { pub struct Multisig<D: TransactionData> {
b: Vec<u8>, b: Vec<u8>,
AH: (dfg::EdwardsPoint, dfg::EdwardsPoint), AH: (dfg::EdwardsPoint, dfg::EdwardsPoint),
input: Input, input: Input,
image: Option<EdwardsPoint>, image: Option<EdwardsPoint>,
msg: M, data: D,
interim: Option<ClsagSignInterim> interim: Option<ClsagSignInterim>
} }
impl<M: Msg> Multisig<M> { impl<D: TransactionData> Multisig<D> {
pub fn new( pub fn new(
input: Input, input: Input,
msg: M data: D
) -> Result<Multisig<M>, MultisigError> { ) -> Result<Multisig<D>, MultisigError> {
Ok( Ok(
Multisig { Multisig {
b: vec![], b: vec![],
@ -67,7 +67,7 @@ impl<M: Msg> Multisig<M> {
input, input,
image: None, image: None,
msg, data,
interim: None interim: None
} }
) )
@ -78,7 +78,7 @@ impl<M: Msg> Multisig<M> {
} }
} }
impl<M: Msg> Algorithm<Ed25519> for Multisig<M> { impl<D: TransactionData> Algorithm<Ed25519> for Multisig<D> {
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
@ -146,7 +146,7 @@ impl<M: Msg> Algorithm<Ed25519> for Multisig<M> {
// in msg if signing a Transaction, yet this ensures CLSAG takes responsibility for its own // in msg if signing a Transaction, yet this ensures CLSAG takes responsibility for its own
// security boundaries // security boundaries
context.extend(&self.image.unwrap().compress().to_bytes()); context.extend(&self.image.unwrap().compress().to_bytes());
context.extend(&self.msg.msg(self.image.unwrap())); context.extend(&self.data.msg());
context.extend(&self.input.context()); context.extend(&self.input.context());
context context
} }
@ -171,15 +171,14 @@ impl<M: Msg> Algorithm<Ed25519> for Multisig<M> {
seed.extend(&self.context()); seed.extend(&self.context());
seed.extend(&self.b); seed.extend(&self.b);
let mut rng = ChaCha12Rng::from_seed(Blake2b512::digest(seed)[0 .. 32].try_into().unwrap()); let mut rng = ChaCha12Rng::from_seed(Blake2b512::digest(seed)[0 .. 32].try_into().unwrap());
let mask = random_scalar(&mut rng);
#[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.msg(self.image.unwrap()), &self.data.msg(),
&self.input, &self.input,
&self.image.unwrap(), &self.image.unwrap(),
mask, self.data.mask_sum(),
nonce_sum.0, nonce_sum.0,
self.AH.0.0 self.AH.0.0
); );
@ -200,7 +199,7 @@ impl<M: Msg> Algorithm<Ed25519> for Multisig<M> {
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.msg(self.image.unwrap()), self.image.unwrap(), &self.input.ring, interim.C_out) { if verify(&clsag, &self.data.msg(), 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;
@ -221,13 +220,13 @@ impl<M: Msg> Algorithm<Ed25519> for Multisig<M> {
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct InputMultisig<M: Msg>(EdwardsPoint, Multisig<M>); pub struct InputMultisig<D: TransactionData>(EdwardsPoint, Multisig<D>);
impl<M: Msg> InputMultisig<M> { impl<D: TransactionData> InputMultisig<D> {
pub fn new( pub fn new(
input: Input, input: Input,
msg: M msg: D
) -> Result<InputMultisig<M>, MultisigError> { ) -> Result<InputMultisig<D>, MultisigError> {
Ok(InputMultisig(EdwardsPoint::identity(), Multisig::new(input, msg)?)) Ok(InputMultisig(EdwardsPoint::identity(), Multisig::new(input, msg)?))
} }
@ -236,11 +235,11 @@ impl<M: Msg> InputMultisig<M> {
} }
} }
impl<M: Msg> Algorithm<Ed25519> for InputMultisig<M> { impl<D: TransactionData> Algorithm<Ed25519> for InputMultisig<D> {
type Signature = (Clsag, EdwardsPoint); type Signature = (Clsag, EdwardsPoint);
fn addendum_commit_len() -> usize { fn addendum_commit_len() -> usize {
32 + Multisig::<M>::addendum_commit_len() 32 + Multisig::<D>::addendum_commit_len()
} }
fn preprocess_addendum<R: RngCore + CryptoRng>( fn preprocess_addendum<R: RngCore + CryptoRng>(
@ -249,7 +248,7 @@ impl<M: Msg> Algorithm<Ed25519> for InputMultisig<M> {
nonces: &[dfg::Scalar; 2] nonces: &[dfg::Scalar; 2]
) -> Vec<u8> { ) -> Vec<u8> {
let (mut serialized, end) = key_image::generate_share(rng, view); let (mut serialized, end) = key_image::generate_share(rng, view);
serialized.extend(Multisig::<M>::preprocess_addendum(rng, view, nonces)); serialized.extend(Multisig::<D>::preprocess_addendum(rng, view, nonces));
serialized.extend(end); serialized.extend(end);
serialized serialized
} }

View file

@ -47,6 +47,8 @@ pub enum TransactionError {
InvalidPreparation(String), InvalidPreparation(String),
#[error("no inputs")] #[error("no inputs")]
NoInputs, NoInputs,
#[error("no outputs")]
NoOutputs,
#[error("too many outputs")] #[error("too many outputs")]
TooManyOutputs, TooManyOutputs,
#[error("not enough funds (in {0}, out {1})")] #[error("not enough funds (in {0}, out {1})")]
@ -370,13 +372,22 @@ impl SignableTransaction {
payments: Vec<(Address, u64)>, payments: Vec<(Address, u64)>,
change: Address, change: Address,
fee_per_byte: u64 fee_per_byte: u64
) -> SignableTransaction { ) -> Result<SignableTransaction, TransactionError> {
SignableTransaction { if inputs.len() == 0 {
inputs, Err(TransactionError::NoInputs)?;
payments,
change,
fee_per_byte
} }
if payments.len() == 0 {
Err(TransactionError::NoOutputs)?;
}
Ok(
SignableTransaction {
inputs,
payments,
change,
fee_per_byte
}
)
} }
pub async fn sign<R: RngCore + CryptoRng>( pub async fn sign<R: RngCore + CryptoRng>(
@ -400,7 +411,7 @@ impl SignableTransaction {
tx.signature_hash().expect("Couldn't get the signature hash").0, tx.signature_hash().expect("Couldn't get the signature hash").0,
&signable, &signable,
mask_sum mask_sum
).ok_or(TransactionError::NoInputs)?; ).unwrap(); // None if no inputs which new checks for
let mut prunable = tx.rct_signatures.p.unwrap(); let mut prunable = tx.rct_signatures.p.unwrap();
prunable.Clsags = clsags.iter().map(|clsag| clsag.0.clone()).collect(); 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(); prunable.pseudo_outs = clsags.iter().map(|clsag| Key { key: clsag.1.compress().to_bytes() }).collect();

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, edwards::EdwardsPoint}; use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar};
use monero_serai::{random_scalar, Commitment, frost::MultisigError, key_image, clsag}; use monero_serai::{random_scalar, Commitment, frost::MultisigError, key_image, clsag};
@ -52,11 +52,15 @@ fn test_single() {
#[cfg(feature = "multisig")] #[cfg(feature = "multisig")]
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct Msg([u8; 32]); struct TransactionData;
#[cfg(feature = "multisig")] #[cfg(feature = "multisig")]
impl clsag::Msg for Msg { impl clsag::TransactionData for TransactionData {
fn msg(&self, _: EdwardsPoint) -> [u8; 32] { fn msg(&self) -> [u8; 32] {
self.0 [1; 32]
}
fn mask_sum(&self) -> Scalar {
Scalar::from(21u64)
} }
} }
@ -66,8 +70,6 @@ fn test_multisig() -> Result<(), MultisigError> {
let (keys, group_private) = generate_keys(); let (keys, group_private) = generate_keys();
let t = keys[0].params().t(); let t = keys[0].params().t();
let msg = [1; 32];
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 {
@ -92,7 +94,7 @@ fn test_multisig() -> Result<(), MultisigError> {
sign::AlgorithmMachine::new( sign::AlgorithmMachine::new(
clsag::InputMultisig::new( clsag::InputMultisig::new(
clsag::Input::new(ring.clone(), RING_INDEX, Commitment::new(randomness, AMOUNT)).unwrap(), clsag::Input::new(ring.clone(), RING_INDEX, Commitment::new(randomness, AMOUNT)).unwrap(),
Msg(msg) TransactionData
).unwrap(), ).unwrap(),
keys[i - 1].clone(), keys[i - 1].clone(),
&(1 ..= THRESHOLD).collect::<Vec<usize>>() &(1 ..= THRESHOLD).collect::<Vec<usize>>()

View file

@ -50,7 +50,7 @@ pub async fn send() {
amount = output.commitment.amount - fee - u64::try_from(i).unwrap(); amount = output.commitment.amount - fee - u64::try_from(i).unwrap();
let tx = SignableTransaction::new( let tx = SignableTransaction::new(
vec![output], vec![(addr, amount)], addr, fee_per_byte vec![output], vec![(addr, amount)], addr, fee_per_byte
).sign(&mut OsRng, &rpc, &spend).await.unwrap(); ).unwrap().sign(&mut OsRng, &rpc, &spend).await.unwrap();
rpc.publish_transaction(&tx).await.unwrap(); rpc.publish_transaction(&tx).await.unwrap();
} }
} }