mirror of
https://github.com/serai-dex/serai.git
synced 2024-11-17 01:17:36 +00:00
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:
parent
45559e14ee
commit
27396a6291
10 changed files with 213 additions and 103 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::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])
|
||||||
);
|
);
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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(())
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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!() }
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue