mirror of
https://github.com/serai-dex/serai.git
synced 2024-11-17 01:17:36 +00:00
Update the CLSAG multisig API for TX signing
This commit is contained in:
parent
d0506e2e9b
commit
22ac5ce3b6
5 changed files with 51 additions and 39 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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>>()
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue