mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 20:29:23 +00:00
Reorganize CLSAG sign flow
This commit is contained in:
@@ -53,7 +53,7 @@ extern "C" {
|
|||||||
try { return rct::bulletproof_VERIFY(bp); } catch(...) { return false; }
|
try { return rct::bulletproof_VERIFY(bp); } catch(...) { return false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
bool c_verify_clsag(uint s_len, uint8_t* s, uint8_t* I, uint8_t k_len, uint8_t* k, uint8_t* m, uint8_t* p) {
|
bool c_verify_clsag(uint s_len, uint8_t* s, uint8_t* I, uint8_t k_len, uint8_t* k, uint8_t* p, uint8_t* m) {
|
||||||
rct::clsag clsag;
|
rct::clsag clsag;
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
std::string str;
|
std::string str;
|
||||||
@@ -66,9 +66,6 @@ extern "C" {
|
|||||||
}
|
}
|
||||||
memcpy(clsag.I.bytes, I, 32);
|
memcpy(clsag.I.bytes, I, 32);
|
||||||
|
|
||||||
rct::key msg;
|
|
||||||
memcpy(msg.bytes, m, 32);
|
|
||||||
|
|
||||||
rct::ctkeyV keys;
|
rct::ctkeyV keys;
|
||||||
keys.resize(k_len);
|
keys.resize(k_len);
|
||||||
for (uint8_t i = 0; i < k_len; i++) {
|
for (uint8_t i = 0; i < k_len; i++) {
|
||||||
@@ -79,6 +76,9 @@ extern "C" {
|
|||||||
rct::key pseudo_out;
|
rct::key pseudo_out;
|
||||||
memcpy(pseudo_out.bytes, p, 32);
|
memcpy(pseudo_out.bytes, p, 32);
|
||||||
|
|
||||||
|
rct::key msg;
|
||||||
|
memcpy(msg.bytes, m, 32);
|
||||||
|
|
||||||
try { return verRctCLSAGSimple(msg, clsag, keys, pseudo_out); } catch(...) { return false; }
|
try { return verRctCLSAGSimple(msg, clsag, keys, pseudo_out); } catch(...) { return false; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,23 +8,21 @@ use curve25519_dalek::{
|
|||||||
edwards::{EdwardsPoint, VartimeEdwardsPrecomputation}
|
edwards::{EdwardsPoint, VartimeEdwardsPrecomputation}
|
||||||
};
|
};
|
||||||
|
|
||||||
use monero::{
|
use monero::{consensus::Encodable, util::ringct::{Key, Clsag}};
|
||||||
consensus::Encodable,
|
|
||||||
util::ringct::{Key, Clsag}
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Commitment,
|
Commitment,
|
||||||
c_verify_clsag,
|
transaction::decoys::Decoys,
|
||||||
random_scalar,
|
random_scalar,
|
||||||
hash_to_scalar,
|
hash_to_scalar,
|
||||||
hash_to_point
|
hash_to_point,
|
||||||
|
c_verify_clsag
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
mod multisig;
|
mod multisig;
|
||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
pub use multisig::Multisig;
|
pub use multisig::{Details, Multisig};
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
@@ -36,49 +34,48 @@ pub enum Error {
|
|||||||
InvalidCommitment
|
InvalidCommitment
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Input {
|
pub struct Input {
|
||||||
// Ring, the index we're signing for, and the actual commitment behind it
|
// The actual commitment for the true spend
|
||||||
pub ring: Vec<[EdwardsPoint; 2]>,
|
pub commitment: Commitment,
|
||||||
pub i: u8,
|
// True spend index, offsets, and ring
|
||||||
pub commitment: Commitment
|
pub decoys: Decoys
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Input {
|
impl Input {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
ring: Vec<[EdwardsPoint; 2]>,
|
commitment: Commitment,
|
||||||
i: u8,
|
decoys: Decoys
|
||||||
commitment: Commitment
|
|
||||||
) -> Result<Input, Error> {
|
) -> Result<Input, Error> {
|
||||||
let n = ring.len();
|
let n = decoys.len();
|
||||||
if n > u8::MAX.into() {
|
if n > u8::MAX.into() {
|
||||||
Err(Error::InternalError("max ring size in this library is u8 max".to_string()))?;
|
Err(Error::InternalError("max ring size in this library is u8 max".to_string()))?;
|
||||||
}
|
}
|
||||||
if i >= (n as u8) {
|
if decoys.i >= (n as u8) {
|
||||||
Err(Error::InvalidRingMember(i, n as u8))?;
|
Err(Error::InvalidRingMember(decoys.i, n as u8))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the commitment matches
|
// Validate the commitment matches
|
||||||
if ring[usize::from(i)][1] != commitment.calculate() {
|
if decoys.ring[usize::from(decoys.i)][1] != commitment.calculate() {
|
||||||
Err(Error::InvalidCommitment)?;
|
Err(Error::InvalidCommitment)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Input { ring, i, commitment })
|
Ok(Input { commitment, decoys })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub(crate) fn sign_core<R: RngCore + CryptoRng>(
|
pub(crate) fn sign_core<R: RngCore + CryptoRng>(
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
msg: &[u8; 32],
|
|
||||||
input: &Input,
|
|
||||||
image: &EdwardsPoint,
|
image: &EdwardsPoint,
|
||||||
|
input: &Input,
|
||||||
mask: Scalar,
|
mask: Scalar,
|
||||||
|
msg: &[u8; 32],
|
||||||
A: EdwardsPoint,
|
A: EdwardsPoint,
|
||||||
AH: EdwardsPoint
|
AH: EdwardsPoint
|
||||||
) -> (Clsag, Scalar, Scalar, Scalar, Scalar, EdwardsPoint) {
|
) -> (Clsag, Scalar, Scalar, Scalar, Scalar, EdwardsPoint) {
|
||||||
let n = input.ring.len();
|
let n = input.decoys.len();
|
||||||
let r: usize = input.i.into();
|
let r: usize = input.decoys.i.into();
|
||||||
|
|
||||||
let C_out;
|
let C_out;
|
||||||
|
|
||||||
@@ -94,7 +91,7 @@ pub(crate) fn sign_core<R: RngCore + CryptoRng>(
|
|||||||
{
|
{
|
||||||
C_out = Commitment::new(mask, input.commitment.amount).calculate();
|
C_out = Commitment::new(mask, input.commitment.amount).calculate();
|
||||||
|
|
||||||
for member in &input.ring {
|
for member in &input.decoys.ring {
|
||||||
P.push(member[0]);
|
P.push(member[0]);
|
||||||
C_non_zero.push(member[1]);
|
C_non_zero.push(member[1]);
|
||||||
C.push(C_non_zero[C_non_zero.len() - 1] - C_out);
|
C.push(C_non_zero[C_non_zero.len() - 1] - C_out);
|
||||||
@@ -188,9 +185,9 @@ pub(crate) fn sign_core<R: RngCore + CryptoRng>(
|
|||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn sign<R: RngCore + CryptoRng>(
|
pub fn sign<R: RngCore + CryptoRng>(
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
msg: [u8; 32],
|
inputs: &[(Scalar, EdwardsPoint, Input)],
|
||||||
inputs: &[(Scalar, Input, EdwardsPoint)],
|
sum_outputs: Scalar,
|
||||||
sum_outputs: Scalar
|
msg: [u8; 32]
|
||||||
) -> Option<Vec<(Clsag, EdwardsPoint)>> {
|
) -> Option<Vec<(Clsag, EdwardsPoint)>> {
|
||||||
if inputs.len() == 0 {
|
if inputs.len() == 0 {
|
||||||
return None;
|
return None;
|
||||||
@@ -214,13 +211,14 @@ pub fn sign<R: RngCore + CryptoRng>(
|
|||||||
rng.fill_bytes(&mut rand_source);
|
rng.fill_bytes(&mut rand_source);
|
||||||
let (mut clsag, c, mu_C, z, mu_P, C_out) = sign_core(
|
let (mut clsag, c, mu_C, z, mu_P, C_out) = sign_core(
|
||||||
rng,
|
rng,
|
||||||
&msg,
|
|
||||||
&inputs[i].1,
|
&inputs[i].1,
|
||||||
&inputs[i].2,
|
&inputs[i].2,
|
||||||
mask,
|
mask,
|
||||||
&nonce * &ED25519_BASEPOINT_TABLE, nonce * hash_to_point(&inputs[i].1.ring[usize::from(inputs[i].1.i)][0])
|
&msg,
|
||||||
|
&nonce * &ED25519_BASEPOINT_TABLE,
|
||||||
|
nonce * hash_to_point(&inputs[i].2.decoys.ring[usize::from(inputs[i].2.decoys.i)][0])
|
||||||
);
|
);
|
||||||
clsag.s[inputs[i].1.i as usize] = Key {
|
clsag.s[inputs[i].2.decoys.i as usize] = Key {
|
||||||
key: (nonce - (c * ((mu_C * z) + (mu_P * inputs[i].0)))).to_bytes()
|
key: (nonce - (c * ((mu_C * z) + (mu_P * inputs[i].0)))).to_bytes()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -233,10 +231,10 @@ pub fn sign<R: RngCore + CryptoRng>(
|
|||||||
// Uses Monero's C verification function to ensure compatibility with Monero
|
// Uses Monero's C verification function to ensure compatibility with Monero
|
||||||
pub fn verify(
|
pub fn verify(
|
||||||
clsag: &Clsag,
|
clsag: &Clsag,
|
||||||
msg: &[u8; 32],
|
|
||||||
image: EdwardsPoint,
|
image: EdwardsPoint,
|
||||||
ring: &[[EdwardsPoint; 2]],
|
ring: &[[EdwardsPoint; 2]],
|
||||||
pseudo_out: EdwardsPoint
|
pseudo_out: EdwardsPoint,
|
||||||
|
msg: &[u8; 32]
|
||||||
) -> bool {
|
) -> bool {
|
||||||
// Workaround for the fact monero-rs doesn't include the length of clsag.s in clsag encoding
|
// 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
|
// despite it being part of clsag encoding. Reason for the patch version pin
|
||||||
@@ -256,7 +254,7 @@ pub fn verify(
|
|||||||
unsafe {
|
unsafe {
|
||||||
c_verify_clsag(
|
c_verify_clsag(
|
||||||
serialized.len(), serialized.as_ptr(), image_bytes.as_ptr(),
|
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()
|
ring.len() as u8, ring_bytes.as_ptr(), pseudo_out_bytes.as_ptr(), msg.as_ptr()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,13 +31,14 @@ impl Input {
|
|||||||
// Doesn't domain separate as this is considered part of the larger CLSAG proof
|
// Doesn't domain separate as this is considered part of the larger CLSAG proof
|
||||||
|
|
||||||
// Ring index
|
// Ring index
|
||||||
transcript.append_message(b"ring_index", &[self.i]);
|
transcript.append_message(b"ring_index", &[self.decoys.i]);
|
||||||
|
|
||||||
// Ring
|
// Ring
|
||||||
let mut ring = vec![];
|
let mut ring = vec![];
|
||||||
for pair in &self.ring {
|
for pair in &self.decoys.ring {
|
||||||
// Doesn't include global output indexes as CLSAG doesn't care and won't be affected by it
|
// Doesn't include global output indexes as CLSAG doesn't care and won't be affected by it
|
||||||
// They're just a mutable reference to this data
|
// They're just a unreliable reference to this data which will be included in the message
|
||||||
|
// if in use
|
||||||
ring.extend(&pair[0].compress().to_bytes());
|
ring.extend(&pair[0].compress().to_bytes());
|
||||||
ring.extend(&pair[1].compress().to_bytes());
|
ring.extend(&pair[1].compress().to_bytes());
|
||||||
}
|
}
|
||||||
@@ -49,9 +50,24 @@ impl Input {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pub to enable testing
|
||||||
|
// While we could move the CLSAG test inside this crate, that'd require duplicating the FROST test
|
||||||
|
// helper, and isn't worth doing right now when this is harmless enough (semver? TODO)
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Details {
|
||||||
|
input: Input,
|
||||||
|
mask: Scalar
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Details {
|
||||||
|
pub fn new(input: Input, mask: Scalar) -> Details {
|
||||||
|
Details { input, mask }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct ClsagSignInterim {
|
struct Interim {
|
||||||
c: Scalar,
|
c: Scalar,
|
||||||
s: Scalar,
|
s: Scalar,
|
||||||
|
|
||||||
@@ -63,36 +79,33 @@ struct ClsagSignInterim {
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Multisig {
|
pub struct Multisig {
|
||||||
transcript: Transcript,
|
transcript: Transcript,
|
||||||
input: Input,
|
|
||||||
|
|
||||||
image: EdwardsPoint,
|
image: EdwardsPoint,
|
||||||
commitments_H: Vec<u8>,
|
commitments_H: Vec<u8>,
|
||||||
AH: (dfg::EdwardsPoint, dfg::EdwardsPoint),
|
AH: (dfg::EdwardsPoint, dfg::EdwardsPoint),
|
||||||
|
|
||||||
msg: Rc<RefCell<[u8; 32]>>,
|
details: Rc<RefCell<Option<Details>>>,
|
||||||
mask: Rc<RefCell<Scalar>>,
|
msg: Rc<RefCell<Option<[u8; 32]>>>,
|
||||||
|
|
||||||
interim: Option<ClsagSignInterim>
|
interim: Option<Interim>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Multisig {
|
impl Multisig {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
transcript: Transcript,
|
transcript: Transcript,
|
||||||
input: Input,
|
details: Rc<RefCell<Option<Details>>>,
|
||||||
msg: Rc<RefCell<[u8; 32]>>,
|
msg: Rc<RefCell<Option<[u8; 32]>>>,
|
||||||
mask: Rc<RefCell<Scalar>>,
|
|
||||||
) -> Result<Multisig, MultisigError> {
|
) -> Result<Multisig, MultisigError> {
|
||||||
Ok(
|
Ok(
|
||||||
Multisig {
|
Multisig {
|
||||||
transcript,
|
transcript,
|
||||||
input,
|
|
||||||
|
|
||||||
image: EdwardsPoint::identity(),
|
image: EdwardsPoint::identity(),
|
||||||
commitments_H: vec![],
|
commitments_H: vec![],
|
||||||
AH: (dfg::EdwardsPoint::identity(), dfg::EdwardsPoint::identity()),
|
AH: (dfg::EdwardsPoint::identity(), dfg::EdwardsPoint::identity()),
|
||||||
|
|
||||||
|
details,
|
||||||
msg,
|
msg,
|
||||||
mask,
|
|
||||||
|
|
||||||
interim: None
|
interim: None
|
||||||
}
|
}
|
||||||
@@ -102,6 +115,18 @@ impl Multisig {
|
|||||||
pub fn serialized_len() -> usize {
|
pub fn serialized_len() -> usize {
|
||||||
3 * (32 + 64)
|
3 * (32 + 64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn input(&self) -> Input {
|
||||||
|
self.details.borrow().as_ref().unwrap().input.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mask(&self) -> Scalar {
|
||||||
|
self.details.borrow().as_ref().unwrap().mask
|
||||||
|
}
|
||||||
|
|
||||||
|
fn msg(&self) -> [u8; 32] {
|
||||||
|
*self.msg.borrow().as_ref().unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Algorithm<Ed25519> for Multisig {
|
impl Algorithm<Ed25519> for Multisig {
|
||||||
@@ -144,9 +169,9 @@ impl Algorithm<Ed25519> for Multisig {
|
|||||||
|
|
||||||
if self.commitments_H.len() == 0 {
|
if self.commitments_H.len() == 0 {
|
||||||
self.transcript.domain_separate(b"CLSAG");
|
self.transcript.domain_separate(b"CLSAG");
|
||||||
self.input.transcript(&mut self.transcript);
|
self.input().transcript(&mut self.transcript);
|
||||||
self.transcript.append_message(b"message", &*self.msg.borrow());
|
self.transcript.append_message(b"mask", &self.mask().to_bytes());
|
||||||
self.transcript.append_message(b"mask", &self.mask.borrow().to_bytes());
|
self.transcript.append_message(b"message", &self.msg());
|
||||||
}
|
}
|
||||||
|
|
||||||
let (share, serialized) = key_image::verify_share(view, l, serialized).map_err(|_| FrostError::InvalidShare(l))?;
|
let (share, serialized) = key_image::verify_share(view, l, serialized).map_err(|_| FrostError::InvalidShare(l))?;
|
||||||
@@ -156,7 +181,7 @@ impl Algorithm<Ed25519> for Multisig {
|
|||||||
self.transcript.append_message(b"image_share", &share.compress().to_bytes());
|
self.transcript.append_message(b"image_share", &share.compress().to_bytes());
|
||||||
self.image += share;
|
self.image += share;
|
||||||
|
|
||||||
let alt = &hash_to_point(&self.input.ring[usize::from(self.input.i)][0]);
|
let alt = &hash_to_point(&view.group_key().0);
|
||||||
|
|
||||||
// Uses the same format FROST does for the expected commitments (nonce * G where this is nonce * H)
|
// Uses the same format FROST does for the expected commitments (nonce * G where this is nonce * H)
|
||||||
// Given this is guaranteed to match commitments, which FROST commits to, this also technically
|
// Given this is guaranteed to match commitments, which FROST commits to, this also technically
|
||||||
@@ -214,14 +239,14 @@ 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.borrow(),
|
|
||||||
&self.input,
|
|
||||||
&self.image,
|
&self.image,
|
||||||
*self.mask.borrow(),
|
&self.input(),
|
||||||
|
self.mask(),
|
||||||
|
&self.msg(),
|
||||||
nonce_sum.0,
|
nonce_sum.0,
|
||||||
self.AH.0.0
|
self.AH.0.0
|
||||||
);
|
);
|
||||||
self.interim = Some(ClsagSignInterim { c: c * mu_P, s: c * mu_C * z, clsag, C_out });
|
self.interim = Some(Interim { c: c * mu_P, s: c * mu_C * z, clsag, C_out });
|
||||||
|
|
||||||
let share = dfg::Scalar(nonce.0 - (c * mu_P * view.secret_share().0));
|
let share = dfg::Scalar(nonce.0 - (c * mu_P * view.secret_share().0));
|
||||||
|
|
||||||
@@ -237,8 +262,8 @@ impl Algorithm<Ed25519> for Multisig {
|
|||||||
let interim = self.interim.as_ref().unwrap();
|
let interim = self.interim.as_ref().unwrap();
|
||||||
|
|
||||||
let mut clsag = interim.clsag.clone();
|
let mut clsag = interim.clsag.clone();
|
||||||
clsag.s[usize::from(self.input.i)] = Key { key: (sum.0 - interim.s).to_bytes() };
|
clsag.s[usize::from(self.input().decoys.i)] = Key { key: (sum.0 - interim.s).to_bytes() };
|
||||||
if verify(&clsag, &self.msg.borrow(), self.image, &self.input.ring, interim.C_out) {
|
if verify(&clsag, self.image, &self.input().decoys.ring, interim.C_out, &self.msg()) {
|
||||||
return Some((clsag, interim.C_out));
|
return Some((clsag, interim.C_out));
|
||||||
}
|
}
|
||||||
return None;
|
return None;
|
||||||
|
|||||||
@@ -69,12 +69,25 @@ fn offset(decoys: &[u64]) -> Vec<VarInt> {
|
|||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Decoys {
|
||||||
|
pub i: u8,
|
||||||
|
pub offsets: Vec<VarInt>,
|
||||||
|
pub ring: Vec<[EdwardsPoint; 2]>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decoys {
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.offsets.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn select<R: RngCore + CryptoRng>(
|
pub(crate) async fn select<R: RngCore + CryptoRng>(
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
rpc: &Rpc,
|
rpc: &Rpc,
|
||||||
height: usize,
|
height: usize,
|
||||||
inputs: &[SpendableOutput]
|
inputs: &[SpendableOutput]
|
||||||
) -> Result<Vec<(Vec<VarInt>, u8, Vec<[EdwardsPoint; 2]>)>, RpcError> {
|
) -> Result<Vec<Decoys>, RpcError> {
|
||||||
// Convert the inputs in question to the raw output data
|
// Convert the inputs in question to the raw output data
|
||||||
let mut outputs = Vec::with_capacity(inputs.len());
|
let mut outputs = Vec::with_capacity(inputs.len());
|
||||||
for input in inputs {
|
for input in inputs {
|
||||||
@@ -133,11 +146,11 @@ pub(crate) async fn select<R: RngCore + CryptoRng>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
decoys[replace] = outputs[i];
|
decoys[replace] = outputs[i];
|
||||||
res.push((
|
res.push(Decoys {
|
||||||
offset(&decoys.iter().map(|output| output.0).collect::<Vec<_>>()),
|
i: u8::try_from(replace).unwrap(),
|
||||||
u8::try_from(replace).unwrap(),
|
offsets: offset(&decoys.iter().map(|output| output.0).collect::<Vec<_>>()),
|
||||||
decoys.iter().map(|output| output.1).collect()
|
ring: decoys.iter().map(|output| output.1).collect()
|
||||||
));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ use crate::{
|
|||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
use crate::frost::MultisigError;
|
use crate::frost::MultisigError;
|
||||||
|
|
||||||
mod decoys;
|
pub mod decoys;
|
||||||
|
|
||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
mod multisig;
|
mod multisig;
|
||||||
|
|
||||||
@@ -198,7 +199,7 @@ async fn prepare_inputs<R: RngCore + CryptoRng>(
|
|||||||
inputs: &[SpendableOutput],
|
inputs: &[SpendableOutput],
|
||||||
spend: &Scalar,
|
spend: &Scalar,
|
||||||
tx: &mut Transaction
|
tx: &mut Transaction
|
||||||
) -> Result<Vec<(Scalar, clsag::Input, EdwardsPoint)>, TransactionError> {
|
) -> Result<Vec<(Scalar, EdwardsPoint, clsag::Input)>, TransactionError> {
|
||||||
// TODO sort inputs
|
// TODO sort inputs
|
||||||
|
|
||||||
let mut signable = Vec::with_capacity(inputs.len());
|
let mut signable = Vec::with_capacity(inputs.len());
|
||||||
@@ -214,18 +215,17 @@ async fn prepare_inputs<R: RngCore + CryptoRng>(
|
|||||||
for (i, input) in inputs.iter().enumerate() {
|
for (i, input) in inputs.iter().enumerate() {
|
||||||
signable.push((
|
signable.push((
|
||||||
spend + input.key_offset,
|
spend + input.key_offset,
|
||||||
|
key_image::generate(&(spend + input.key_offset)),
|
||||||
clsag::Input::new(
|
clsag::Input::new(
|
||||||
decoys[i].2.clone(),
|
input.commitment,
|
||||||
decoys[i].1,
|
decoys[i].clone()
|
||||||
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: decoys[i].0.clone(),
|
key_offsets: decoys[i].offsets.clone(),
|
||||||
k_image: KeyImage { image: Hash(signable[i].2.compress().to_bytes()) }
|
k_image: KeyImage { image: Hash(signable[i].1.compress().to_bytes()) }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,9 +370,9 @@ impl SignableTransaction {
|
|||||||
|
|
||||||
let clsags = clsag::sign(
|
let clsags = clsag::sign(
|
||||||
rng,
|
rng,
|
||||||
tx.signature_hash().expect("Couldn't get the signature hash").0,
|
|
||||||
&signable,
|
&signable,
|
||||||
mask_sum
|
mask_sum,
|
||||||
|
tx.signature_hash().expect("Couldn't get the signature hash").0
|
||||||
).unwrap(); // None if no inputs which new checks for
|
).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();
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ use frost::{FrostError, MultisigKeys, MultisigParams, sign::{State, StateMachine
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
frost::{Transcript, Ed25519},
|
frost::{Transcript, Ed25519},
|
||||||
key_image, bulletproofs, clsag,
|
random_scalar, key_image, bulletproofs, clsag,
|
||||||
rpc::Rpc,
|
rpc::Rpc,
|
||||||
transaction::{TransactionError, SignableTransaction, decoys}
|
transaction::{TransactionError, SignableTransaction, decoys::{self, Decoys}}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct TransactionMachine {
|
pub struct TransactionMachine {
|
||||||
@@ -27,12 +27,15 @@ pub struct TransactionMachine {
|
|||||||
signable: SignableTransaction,
|
signable: SignableTransaction,
|
||||||
transcript: Transcript,
|
transcript: Transcript,
|
||||||
|
|
||||||
|
decoys: Vec<Decoys>,
|
||||||
|
|
||||||
our_images: Vec<EdwardsPoint>,
|
our_images: Vec<EdwardsPoint>,
|
||||||
mask_sum: Rc<RefCell<Scalar>>,
|
output_masks: Option<Scalar>,
|
||||||
msg: Rc<RefCell<[u8; 32]>>,
|
inputs: Vec<Rc<RefCell<Option<clsag::Details>>>>,
|
||||||
|
msg: Rc<RefCell<Option<[u8; 32]>>>,
|
||||||
clsags: Vec<AlgorithmMachine<Ed25519, clsag::Multisig>>,
|
clsags: Vec<AlgorithmMachine<Ed25519, clsag::Multisig>>,
|
||||||
inputs: Vec<TxIn>,
|
|
||||||
tx: Option<Transaction>,
|
tx: Option<Transaction>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SignableTransaction {
|
impl SignableTransaction {
|
||||||
@@ -41,32 +44,30 @@ impl SignableTransaction {
|
|||||||
label: Vec<u8>,
|
label: Vec<u8>,
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
rpc: &Rpc,
|
rpc: &Rpc,
|
||||||
keys: Rc<MultisigKeys<Ed25519>>,
|
|
||||||
height: usize,
|
height: usize,
|
||||||
|
keys: Rc<MultisigKeys<Ed25519>>,
|
||||||
included: &[usize]
|
included: &[usize]
|
||||||
) -> Result<TransactionMachine, TransactionError> {
|
) -> Result<TransactionMachine, TransactionError> {
|
||||||
let mut our_images = vec![];
|
let mut our_images = vec![];
|
||||||
|
|
||||||
let mask_sum = Rc::new(RefCell::new(Scalar::zero()));
|
|
||||||
let msg = Rc::new(RefCell::new([0; 32]));
|
|
||||||
let mut clsags = vec![];
|
|
||||||
|
|
||||||
let mut inputs = vec![];
|
let mut inputs = vec![];
|
||||||
|
inputs.resize(self.inputs.len(), Rc::new(RefCell::new(None)));
|
||||||
|
let msg = Rc::new(RefCell::new(None));
|
||||||
|
let mut clsags = vec![];
|
||||||
|
|
||||||
// Create a RNG out of the input shared keys, which either requires the view key or being every
|
// Create a RNG out of the input shared keys, which either requires the view key or being every
|
||||||
// sender, and the payments (address and amount), which a passive adversary may be able to know
|
// sender, and the payments (address and amount), which a passive adversary may be able to know
|
||||||
// depending on how these transactions are coordinated
|
// depending on how these transactions are coordinated
|
||||||
|
|
||||||
// The lack of dedicated entropy here is frustrating. We can probably provide entropy inclusion
|
|
||||||
// if we move CLSAG ring to a Rc RefCell like msg and mask? TODO
|
|
||||||
let mut transcript = Transcript::new(label);
|
let mut transcript = Transcript::new(label);
|
||||||
|
// Also include the spend_key as below only the key offset is included, so this confirms the sum product
|
||||||
|
// Useful as confirming the sum product confirms the key image, further guaranteeing the one time
|
||||||
|
// properties noted below
|
||||||
|
transcript.append_message(b"spend_key", &keys.group_key().0.compress().to_bytes());
|
||||||
for input in &self.inputs {
|
for input in &self.inputs {
|
||||||
// These outputs can only be spent once. Therefore, it forces all RNGs derived from this
|
// These outputs can only be spent once. Therefore, it forces all RNGs derived from this
|
||||||
// transcript (such as the one used to create one time keys) to be unique
|
// transcript (such as the one used to create one time keys) to be unique
|
||||||
transcript.append_message(b"input_hash", &input.tx.0);
|
transcript.append_message(b"input_hash", &input.tx.0);
|
||||||
// TODO: Should this be u8, u16, or u32? Right now, outputs are solely up to 16, but what
|
transcript.append_message(b"input_output_index", &u16::try_from(input.o).unwrap().to_le_bytes());
|
||||||
// about the future?
|
|
||||||
transcript.append_message(b"input_output_index", &u64::try_from(input.o).unwrap().to_le_bytes());
|
|
||||||
// Not including this, with a doxxed list of payments, would allow brute forcing the inputs
|
// Not including this, with a doxxed list of payments, would allow brute forcing the inputs
|
||||||
// to determine RNG seeds and therefore the true spends
|
// to determine RNG seeds and therefore the true spends
|
||||||
transcript.append_message(b"input_shared_key", &input.key_offset.to_bytes());
|
transcript.append_message(b"input_shared_key", &input.key_offset.to_bytes());
|
||||||
@@ -75,10 +76,13 @@ impl SignableTransaction {
|
|||||||
transcript.append_message(b"payment_address", &payment.0.as_bytes());
|
transcript.append_message(b"payment_address", &payment.0.as_bytes());
|
||||||
transcript.append_message(b"payment_amount", &payment.1.to_le_bytes());
|
transcript.append_message(b"payment_amount", &payment.1.to_le_bytes());
|
||||||
}
|
}
|
||||||
// Not only is this an output, but this locks to the base keys to be complete with the above key offsets
|
|
||||||
transcript.append_message(b"change", &self.change.as_bytes());
|
transcript.append_message(b"change", &self.change.as_bytes());
|
||||||
|
|
||||||
// Select decoys
|
// Select decoys
|
||||||
|
// Ideally, this would be done post entropy, instead of now, yet doing so would require sign
|
||||||
|
// to be async which isn't feasible. This should be suitably competent though
|
||||||
|
// While this inability means we can immediately create the input, moving it out of the
|
||||||
|
// Rc RefCell, keeping it within an Rc RefCell keeps our options flexible
|
||||||
let decoys = decoys::select(
|
let decoys = decoys::select(
|
||||||
&mut ChaCha12Rng::from_seed(transcript.rng_seed(b"decoys", None)),
|
&mut ChaCha12Rng::from_seed(transcript.rng_seed(b"decoys", None)),
|
||||||
rpc,
|
rpc,
|
||||||
@@ -98,24 +102,13 @@ impl SignableTransaction {
|
|||||||
AlgorithmMachine::new(
|
AlgorithmMachine::new(
|
||||||
clsag::Multisig::new(
|
clsag::Multisig::new(
|
||||||
transcript.clone(),
|
transcript.clone(),
|
||||||
clsag::Input::new(
|
inputs[i].clone(),
|
||||||
decoys[i].2.clone(),
|
msg.clone()
|
||||||
decoys[i].1,
|
|
||||||
input.commitment
|
|
||||||
).map_err(|e| TransactionError::ClsagError(e))?,
|
|
||||||
msg.clone(),
|
|
||||||
mask_sum.clone()
|
|
||||||
).map_err(|e| TransactionError::MultisigError(e))?,
|
).map_err(|e| TransactionError::MultisigError(e))?,
|
||||||
Rc::new(keys),
|
Rc::new(keys),
|
||||||
included
|
included
|
||||||
).map_err(|e| TransactionError::FrostError(e))?
|
).map_err(|e| TransactionError::FrostError(e))?
|
||||||
);
|
);
|
||||||
|
|
||||||
inputs.push(TxIn::ToKey {
|
|
||||||
amount: VarInt(0),
|
|
||||||
key_offsets: decoys[i].0.clone(),
|
|
||||||
k_image: KeyImage { image: Hash([0; 32]) }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify these outputs by a dummy prep
|
// Verify these outputs by a dummy prep
|
||||||
@@ -125,11 +118,15 @@ impl SignableTransaction {
|
|||||||
leader: keys.params().i() == included[0],
|
leader: keys.params().i() == included[0],
|
||||||
signable: self,
|
signable: self,
|
||||||
transcript,
|
transcript,
|
||||||
|
|
||||||
|
decoys,
|
||||||
|
|
||||||
our_images,
|
our_images,
|
||||||
mask_sum,
|
output_masks: None,
|
||||||
|
inputs,
|
||||||
msg,
|
msg,
|
||||||
clsags,
|
clsags,
|
||||||
inputs,
|
|
||||||
tx: None
|
tx: None
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -159,8 +156,8 @@ impl StateMachine for TransactionMachine {
|
|||||||
|
|
||||||
let mut rng = ChaCha12Rng::from_seed(self.transcript.rng_seed(b"tx_keys", Some(entropy)));
|
let mut rng = ChaCha12Rng::from_seed(self.transcript.rng_seed(b"tx_keys", Some(entropy)));
|
||||||
// Safe to unwrap thanks to the dummy prepare
|
// Safe to unwrap thanks to the dummy prepare
|
||||||
let (commitments, mask_sum) = self.signable.prepare_outputs(&mut rng).unwrap();
|
let (commitments, output_masks) = self.signable.prepare_outputs(&mut rng).unwrap();
|
||||||
self.mask_sum.replace(mask_sum);
|
self.output_masks = Some(output_masks);
|
||||||
|
|
||||||
let bp = bulletproofs::generate(&commitments).unwrap();
|
let bp = bulletproofs::generate(&commitments).unwrap();
|
||||||
bp.consensus_encode(&mut serialized).unwrap();
|
bp.consensus_encode(&mut serialized).unwrap();
|
||||||
@@ -186,25 +183,24 @@ impl StateMachine for TransactionMachine {
|
|||||||
let clsag_lens = clsag_len * self.clsags.len();
|
let clsag_lens = clsag_len * self.clsags.len();
|
||||||
|
|
||||||
// Split out the prep and update the TX
|
// Split out the prep and update the TX
|
||||||
let mut tx = None;
|
let mut tx;
|
||||||
if self.leader {
|
if self.leader {
|
||||||
tx = self.tx.take();
|
tx = self.tx.take().unwrap();
|
||||||
} else {
|
} else {
|
||||||
for (l, prep) in commitments.iter().enumerate() {
|
let (l, prep) = commitments.iter().enumerate().filter(|(_, prep)| prep.is_some()).next()
|
||||||
if prep.is_none() {
|
.ok_or(FrostError::InternalError("no participants".to_string()))?;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let prep = prep.as_ref().unwrap();
|
let prep = prep.as_ref().unwrap();
|
||||||
|
|
||||||
let mut rng = ChaCha12Rng::from_seed(
|
// Not invalid outputs due to doing a dummy prep as leader
|
||||||
|
let (commitments, output_masks) = self.signable.prepare_outputs(
|
||||||
|
&mut ChaCha12Rng::from_seed(
|
||||||
self.transcript.rng_seed(
|
self.transcript.rng_seed(
|
||||||
b"tx_keys",
|
b"tx_keys",
|
||||||
Some(prep[clsag_lens .. (clsag_lens + 32)].try_into().map_err(|_| FrostError::InvalidShare(l))?)
|
Some(prep[clsag_lens .. (clsag_lens + 32)].try_into().map_err(|_| FrostError::InvalidShare(l))?)
|
||||||
)
|
)
|
||||||
);
|
)
|
||||||
// Not invalid outputs due to doing a dummy prep as leader
|
).map_err(|_| FrostError::InvalidShare(l))?;
|
||||||
let (commitments, mask_sum) = self.signable.prepare_outputs(&mut rng).map_err(|_| FrostError::InvalidShare(l))?;
|
self.output_masks.replace(output_masks);
|
||||||
self.mask_sum.replace(mask_sum);
|
|
||||||
|
|
||||||
// Verify the provided bulletproofs if not leader
|
// Verify the provided bulletproofs if not leader
|
||||||
let bp = deserialize(&prep[(clsag_lens + 32) .. prep.len()]).map_err(|_| FrostError::InvalidShare(l))?;
|
let bp = deserialize(&prep[(clsag_lens + 32) .. prep.len()]).map_err(|_| FrostError::InvalidShare(l))?;
|
||||||
@@ -212,42 +208,54 @@ impl StateMachine for TransactionMachine {
|
|||||||
Err(FrostError::InvalidShare(l))?;
|
Err(FrostError::InvalidShare(l))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let tx_inner = self.signable.prepare_transaction(&commitments, bp);
|
tx = self.signable.prepare_transaction(&commitments, bp);
|
||||||
tx = Some(tx_inner);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the key images and update the TX
|
let mut rng = ChaCha12Rng::from_seed(self.transcript.rng_seed(b"pseudo_out_masks", None));
|
||||||
|
let mut sum_pseudo_outs = Scalar::zero();
|
||||||
|
for c in 0 .. self.clsags.len() {
|
||||||
|
// Calculate the key images in order to update the TX
|
||||||
// Multisig will parse/calculate/validate this as needed, yet doing so here as well provides
|
// Multisig will parse/calculate/validate this as needed, yet doing so here as well provides
|
||||||
// the easiest API overall
|
// the easiest API overall
|
||||||
for c in 0 .. self.clsags.len() {
|
|
||||||
let mut image = self.our_images[c];
|
let mut image = self.our_images[c];
|
||||||
for (l, serialized) in commitments.iter().enumerate() {
|
for (l, serialized) in commitments.iter().enumerate().filter(|(_, s)| s.is_some()) {
|
||||||
if serialized.is_none() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
image += CompressedEdwardsY(
|
image += CompressedEdwardsY(
|
||||||
serialized.as_ref().unwrap()[((c * clsag_len) + 64) .. ((c * clsag_len) + 96)]
|
serialized.as_ref().unwrap()[((c * clsag_len) + 64) .. ((c * clsag_len) + 96)]
|
||||||
.try_into().map_err(|_| FrostError::InvalidCommitment(l))?
|
.try_into().map_err(|_| FrostError::InvalidCommitment(l))?
|
||||||
).decompress().ok_or(FrostError::InvalidCommitment(l))?;
|
).decompress().ok_or(FrostError::InvalidCommitment(l))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.inputs[c] = match self.inputs[c].clone() {
|
|
||||||
TxIn::ToKey { amount, key_offsets, k_image: _ } => TxIn::ToKey {
|
|
||||||
amount, key_offsets,
|
|
||||||
k_image: KeyImage { image: Hash(image.compress().to_bytes()) }
|
|
||||||
},
|
|
||||||
_ => panic!("Signing for an input which isn't ToKey")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO sort inputs
|
// TODO sort inputs
|
||||||
|
|
||||||
let mut tx = tx.unwrap();
|
let mut mask = random_scalar(&mut rng);
|
||||||
tx.prefix.inputs = self.inputs.clone();
|
if c == (self.clsags.len() - 1) {
|
||||||
self.msg.replace(tx.signature_hash().unwrap().0);
|
mask = self.output_masks.unwrap() - sum_pseudo_outs;
|
||||||
|
} else {
|
||||||
|
sum_pseudo_outs += mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.inputs[c].replace(
|
||||||
|
Some(
|
||||||
|
clsag::Details::new(
|
||||||
|
clsag::Input::new(
|
||||||
|
self.signable.inputs[c].commitment,
|
||||||
|
self.decoys[c].clone()
|
||||||
|
).map_err(|_| panic!("Signing an input which isn't present in the ring we created for it"))?,
|
||||||
|
mask
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
tx.prefix.inputs.push(
|
||||||
|
TxIn::ToKey {
|
||||||
|
amount: VarInt(0),
|
||||||
|
key_offsets: self.decoys[c].offsets.clone(),
|
||||||
|
k_image: KeyImage { image: Hash(image.compress().to_bytes()) }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.msg.replace(Some(tx.signature_hash().unwrap().0));
|
||||||
self.tx = Some(tx);
|
self.tx = Some(tx);
|
||||||
|
|
||||||
// Iterate over each CLSAG calling sign
|
// Iterate over each CLSAG calling sign
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ use rand::{RngCore, rngs::OsRng};
|
|||||||
|
|
||||||
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar};
|
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar};
|
||||||
|
|
||||||
use monero_serai::{random_scalar, Commitment, key_image, clsag};
|
use monero::VarInt;
|
||||||
|
|
||||||
|
use monero_serai::{random_scalar, Commitment, transaction::decoys::Decoys, key_image, clsag};
|
||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
use monero_serai::frost::{MultisigError, Transcript};
|
use monero_serai::frost::{MultisigError, Transcript};
|
||||||
|
|
||||||
@@ -40,19 +42,22 @@ fn test_single() {
|
|||||||
let image = key_image::generate(&secrets[0]);
|
let image = key_image::generate(&secrets[0]);
|
||||||
let (clsag, pseudo_out) = clsag::sign(
|
let (clsag, pseudo_out) = clsag::sign(
|
||||||
&mut OsRng,
|
&mut OsRng,
|
||||||
msg,
|
|
||||||
&vec![(
|
&vec![(
|
||||||
secrets[0],
|
secrets[0],
|
||||||
|
image,
|
||||||
clsag::Input::new(
|
clsag::Input::new(
|
||||||
ring.clone(),
|
Commitment::new(secrets[1], AMOUNT),
|
||||||
RING_INDEX,
|
Decoys {
|
||||||
Commitment::new(secrets[1], AMOUNT)
|
i: RING_INDEX,
|
||||||
).unwrap(),
|
offsets: (1 ..= RING_LEN).into_iter().map(|o| VarInt(o)).collect(),
|
||||||
image
|
ring: ring.clone()
|
||||||
|
}
|
||||||
|
).unwrap()
|
||||||
)],
|
)],
|
||||||
Scalar::zero()
|
random_scalar(&mut OsRng),
|
||||||
|
msg
|
||||||
).unwrap().swap_remove(0);
|
).unwrap().swap_remove(0);
|
||||||
assert!(clsag::verify(&clsag, &msg, image, &ring, pseudo_out));
|
assert!(clsag::verify(&clsag, image, &ring, pseudo_out, &msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
@@ -79,15 +84,27 @@ fn test_multisig() -> Result<(), MultisigError> {
|
|||||||
ring.push([&dest * &ED25519_BASEPOINT_TABLE, Commitment::new(mask, amount).calculate()]);
|
ring.push([&dest * &ED25519_BASEPOINT_TABLE, Commitment::new(mask, amount).calculate()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mask_sum = random_scalar(&mut OsRng);
|
||||||
let mut machines = Vec::with_capacity(t);
|
let mut machines = Vec::with_capacity(t);
|
||||||
for i in 1 ..= t {
|
for i in 1 ..= t {
|
||||||
machines.push(
|
machines.push(
|
||||||
sign::AlgorithmMachine::new(
|
sign::AlgorithmMachine::new(
|
||||||
clsag::Multisig::new(
|
clsag::Multisig::new(
|
||||||
Transcript::new(b"Monero Serai CLSAG Test".to_vec()),
|
Transcript::new(b"Monero Serai CLSAG Test".to_vec()),
|
||||||
clsag::Input::new(ring.clone(), RING_INDEX, Commitment::new(randomness, AMOUNT)).unwrap(),
|
Rc::new(RefCell::new(Some(
|
||||||
Rc::new(RefCell::new([1; 32])),
|
clsag::Details::new(
|
||||||
Rc::new(RefCell::new(Scalar::from(42u64)))
|
clsag::Input::new(
|
||||||
|
Commitment::new(randomness, AMOUNT),
|
||||||
|
Decoys {
|
||||||
|
i: RING_INDEX,
|
||||||
|
offsets: (1 ..= RING_LEN).into_iter().map(|o| VarInt(o)).collect(),
|
||||||
|
ring: ring.clone()
|
||||||
|
}
|
||||||
|
).unwrap(),
|
||||||
|
mask_sum
|
||||||
|
)
|
||||||
|
))),
|
||||||
|
Rc::new(RefCell::new(Some([1; 32])))
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
keys[i - 1].clone(),
|
keys[i - 1].clone(),
|
||||||
&(1 ..= THRESHOLD).collect::<Vec<usize>>()
|
&(1 ..= THRESHOLD).collect::<Vec<usize>>()
|
||||||
|
|||||||
@@ -60,8 +60,8 @@ pub async fn send_multisig() {
|
|||||||
b"Monero Serai Test Transaction".to_vec(),
|
b"Monero Serai Test Transaction".to_vec(),
|
||||||
&mut OsRng,
|
&mut OsRng,
|
||||||
&rpc,
|
&rpc,
|
||||||
keys[i - 1].clone(),
|
|
||||||
rpc.get_height().await.unwrap() - 10,
|
rpc.get_height().await.unwrap() - 10,
|
||||||
|
keys[i - 1].clone(),
|
||||||
&(1 ..= THRESHOLD).collect::<Vec<usize>>()
|
&(1 ..= THRESHOLD).collect::<Vec<usize>>()
|
||||||
).await.unwrap()
|
).await.unwrap()
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user