Working multisig TXs

This commit is contained in:
Luke Parker
2022-04-30 04:32:19 -04:00
parent d6649fffb1
commit 9ccf683e9d
12 changed files with 577 additions and 325 deletions

View File

@@ -24,7 +24,7 @@ use crate::{
#[cfg(feature = "multisig")]
mod multisig;
#[cfg(feature = "multisig")]
pub use multisig::{TransactionData, Multisig};
pub use multisig::Multisig;
#[derive(Error, Debug)]
pub enum Error {

View File

@@ -1,4 +1,5 @@
use core::fmt::Debug;
use std::{rc::Rc, cell::RefCell};
use rand_core::{RngCore, CryptoRng, SeedableRng};
use rand_chacha::ChaCha12Rng;
@@ -14,7 +15,7 @@ use curve25519_dalek::{
use group::Group;
use dalek_ff_group as dfg;
use frost::{Curve, FrostError, algorithm::Algorithm, sign::ParamsView};
use frost::{Curve, FrostError, algorithm::Algorithm, MultisigView};
use monero::util::ringct::{Key, Clsag};
@@ -25,11 +26,6 @@ use crate::{
clsag::{Input, sign_core, verify}
};
pub trait TransactionData: Clone + Debug {
fn msg(&self) -> [u8; 32];
fn mask_sum(&self) -> Scalar;
}
#[allow(non_snake_case)]
#[derive(Clone, Debug)]
struct ClsagSignInterim {
@@ -42,23 +38,26 @@ struct ClsagSignInterim {
#[allow(non_snake_case)]
#[derive(Clone, Debug)]
pub struct Multisig<D: TransactionData> {
pub struct Multisig {
entropy: Vec<u8>,
AH: (dfg::EdwardsPoint, dfg::EdwardsPoint),
input: Input,
image: EdwardsPoint,
data: D,
msg: Rc<RefCell<[u8; 32]>>,
mask_sum: Rc<RefCell<Scalar>>,
interim: Option<ClsagSignInterim>
}
impl<D: TransactionData> Multisig<D> {
impl Multisig {
pub fn new(
input: Input,
data: D
) -> Result<Multisig<D>, MultisigError> {
msg: Rc<RefCell<[u8; 32]>>,
mask_sum: Rc<RefCell<Scalar>>,
) -> Result<Multisig, MultisigError> {
Ok(
Multisig {
entropy: vec![],
@@ -67,38 +66,45 @@ impl<D: TransactionData> Multisig<D> {
input,
image: EdwardsPoint::identity(),
data,
msg,
mask_sum,
interim: None
}
)
}
pub fn serialized_len() -> usize {
3 * (32 + 64)
}
}
impl<D: TransactionData> Algorithm<Ed25519> for Multisig<D> {
impl Algorithm<Ed25519> for Multisig {
type Signature = (Clsag, EdwardsPoint);
// We arguably don't have to commit to the nonces at all thanks to xG and yG being committed to,
// both of those being proven to have the same scalar as xH and yH, yet it doesn't hurt
// As for the image, that should be committed to by the msg from TransactionData, yet putting it
// here as well ensures the security bounds of this
// As for the image, that should be committed to by the msg, yet putting it here as well ensures
// the security bounds of this
fn addendum_commit_len() -> usize {
3 * 32
}
fn preprocess_addendum<R: RngCore + CryptoRng>(
rng: &mut R,
view: &ParamsView<Ed25519>,
view: &MultisigView<Ed25519>,
nonces: &[dfg::Scalar; 2]
) -> Vec<u8> {
let (mut serialized, proof) = key_image::generate_share(rng, view);
let (share, proof) = key_image::generate_share(rng, view);
#[allow(non_snake_case)]
let H = hash_to_point(&view.group_key().0);
#[allow(non_snake_case)]
let nH = (nonces[0].0 * H, nonces[1].0 * H);
serialized.reserve_exact(3 * (32 + 64));
let mut serialized = Vec::with_capacity(Multisig::serialized_len());
serialized.extend(share.compress().to_bytes());
serialized.extend(nH.0.compress().to_bytes());
serialized.extend(nH.1.compress().to_bytes());
serialized.extend(&DLEqProof::prove(rng, &nonces[0].0, &H, &nH.0).serialize());
@@ -109,12 +115,12 @@ impl<D: TransactionData> Algorithm<Ed25519> for Multisig<D> {
fn process_addendum(
&mut self,
view: &ParamsView<Ed25519>,
view: &MultisigView<Ed25519>,
l: usize,
commitments: &[dfg::EdwardsPoint; 2],
serialized: &[u8]
) -> Result<(), FrostError> {
if serialized.len() != (3 * (32 + 64)) {
if serialized.len() != Multisig::serialized_len() {
// Not an optimal error but...
Err(FrostError::InvalidCommitmentQuantity(l, 9, serialized.len() / 32))?;
}
@@ -122,7 +128,7 @@ impl<D: TransactionData> Algorithm<Ed25519> for Multisig<D> {
// 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
self.entropy.extend(&l.to_le_bytes());
self.entropy.extend(&serialized[0 .. (3 * 32)]);
self.entropy.extend(&serialized[0 .. Multisig::addendum_commit_len()]);
let (share, serialized) = key_image::verify_share(view, l, serialized).map_err(|_| FrostError::InvalidShare(l))?;
self.image += share;
@@ -154,19 +160,16 @@ impl<D: TransactionData> Algorithm<Ed25519> for Multisig<D> {
}
fn context(&self) -> Vec<u8> {
let mut context = vec![];
// This should be redundant as the image should be in the addendum if using Multisig and in msg
// if signing a Transaction, yet this ensures CLSAG takes responsibility for its own security
// boundaries
context.extend(&self.image.compress().to_bytes());
context.extend(&self.data.msg());
let mut context = Vec::with_capacity(32 + 32 + 1 + (2 * 11 * 32));
context.extend(&*self.msg.borrow());
context.extend(&self.mask_sum.borrow().to_bytes());
context.extend(&self.input.context());
context
}
fn sign_share(
&mut self,
view: &ParamsView<Ed25519>,
view: &MultisigView<Ed25519>,
nonce_sum: dfg::EdwardsPoint,
b: dfg::Scalar,
nonce: dfg::Scalar,
@@ -186,10 +189,10 @@ impl<D: TransactionData> Algorithm<Ed25519> for Multisig<D> {
#[allow(non_snake_case)]
let (clsag, c, mu_C, z, mu_P, C_out) = sign_core(
&mut rng,
&self.data.msg(),
&self.msg.borrow(),
&self.input,
&self.image,
self.data.mask_sum(),
*self.mask_sum.borrow(),
nonce_sum.0,
self.AH.0.0
);
@@ -210,7 +213,7 @@ impl<D: TransactionData> Algorithm<Ed25519> for Multisig<D> {
let mut clsag = interim.clsag.clone();
clsag.s[self.input.i] = Key { key: (sum.0 - interim.s).to_bytes() };
if verify(&clsag, &self.data.msg(), self.image, &self.input.ring, interim.C_out) {
if verify(&clsag, &self.msg.borrow(), self.image, &self.input.ring, interim.C_out) {
return Some((clsag, interim.C_out));
}
return None;

View File

@@ -1,15 +1,15 @@
use rand_core::{RngCore, CryptoRng};
use curve25519_dalek::edwards::{EdwardsPoint, CompressedEdwardsY};
use frost::sign::ParamsView;
use frost::MultisigView;
use crate::{hash_to_point, frost::{MultisigError, Ed25519, DLEqProof}};
#[allow(non_snake_case)]
pub fn generate_share<R: RngCore + CryptoRng>(
rng: &mut R,
view: &ParamsView<Ed25519>
) -> (Vec<u8>, Vec<u8>) {
view: &MultisigView<Ed25519>
) -> (EdwardsPoint, Vec<u8>) {
let H = hash_to_point(&view.group_key().0);
let image = view.secret_share().0 * H;
// Includes a proof. Since:
@@ -20,14 +20,11 @@ pub fn generate_share<R: RngCore + CryptoRng>(
// lagranged_secret * G is known. lagranged_secret * H is being sent
// Any discrete log equality proof confirms the same secret was used,
// forming a valid key_image share
(
image.compress().to_bytes().to_vec(),
DLEqProof::prove(rng, &view.secret_share().0, &H, &image).serialize()
)
(image, DLEqProof::prove(rng, &view.secret_share().0, &H, &image).serialize())
}
pub fn verify_share(
view: &ParamsView<Ed25519>,
view: &MultisigView<Ed25519>,
l: usize,
share: &[u8]
) -> Result<(EdwardsPoint, Vec<u8>), MultisigError> {

View File

@@ -26,6 +26,9 @@ use monero::{
}
};
#[cfg(feature = "multisig")]
use frost::FrostError;
use crate::{
Commitment,
random_scalar,
@@ -33,13 +36,12 @@ use crate::{
key_image, bulletproofs, clsag,
rpc::{Rpc, RpcError}
};
#[cfg(feature = "multisig")]
use crate::frost::MultisigError;
mod mixins;
#[cfg(feature = "multisig")]
mod multisig;
#[cfg(feature = "multisig")]
pub use multisig::Multisig;
#[derive(Error, Debug)]
pub enum TransactionError {
@@ -60,10 +62,16 @@ pub enum TransactionError {
#[error("clsag error ({0})")]
ClsagError(clsag::Error),
#[error("invalid transaction ({0})")]
InvalidTransaction(RpcError)
InvalidTransaction(RpcError),
#[cfg(feature = "multisig")]
#[error("frost error {0}")]
FrostError(FrostError),
#[cfg(feature = "multisig")]
#[error("multisig error {0}")]
MultisigError(MultisigError)
}
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct SpendableOutput {
pub tx: Hash,
pub o: usize,
@@ -192,157 +200,23 @@ enum Preparation<'a, R: RngCore + CryptoRng> {
Follower([u8; 32], Bulletproof)
}
fn prepare_outputs<'a, R: RngCore + CryptoRng>(
prep: &mut Preparation<'a, R>,
inputs: &[SpendableOutput],
payments: &[(Address, u64)],
change: Address,
fee_per_byte: u64
) -> Result<(Vec<u8>, Scalar, Transaction), TransactionError> {
let fee = fee_per_byte * 2000; // TODO
// TODO TX MAX SIZE
// Make sure we have enough funds
let in_amount = inputs.iter().map(|input| input.commitment.amount).sum();
let out_amount = fee + payments.iter().map(|payment| payment.1).sum::<u64>();
if in_amount < out_amount {
Err(TransactionError::NotEnoughFunds(in_amount, out_amount))?;
}
// Add the change output
let mut payments = payments.to_vec();
payments.push((change, in_amount - out_amount));
// Grab the prep
let mut entropy = [0; 32];
let mut bp = None;
match prep {
Preparation::Leader(ref mut rng) => {
// The Leader generates the entropy for the one time keys and the bulletproof
rng.fill_bytes(&mut entropy);
},
Preparation::Follower(e, b) => {
entropy = e.clone();
bp = Some(b.clone());
}
}
let mut seed = b"StealthAddress_randomness".to_vec();
// Leader selected entropy to prevent de-anonymization via recalculation of randomness
seed.extend(&entropy);
// This output can only be spent once. Therefore, it forces all one time keys used here to be
// unique, even if the leader reuses entropy. While another transaction could use a different
// input ordering to swap which 0 is, that input set can't contain this input without being a
// double spend
seed.extend(&inputs[0].tx.0);
seed.extend(&inputs[0].o.to_le_bytes());
let mut rng = ChaCha12Rng::from_seed(Blake2b512::digest(seed)[0 .. 32].try_into().unwrap());
let mut outputs = Vec::with_capacity(payments.len());
let mut commitments = Vec::with_capacity(payments.len());
for o in 0 .. payments.len() {
outputs.push(Output::new(&mut rng, payments[o], o)?);
commitments.push(Commitment::new(outputs[o].mask, payments[o].1));
}
if bp.is_none() {
// Generate the bulletproof if leader
bp = Some(bulletproofs::generate(&commitments)?);
} else {
// Verify the bulletproof if follower
if !bulletproofs::verify(
bp.as_ref().unwrap(),
&commitments.iter().map(|c| c.calculate()).collect::<Vec<EdwardsPoint>>()
) {
Err(TransactionError::InvalidPreparation("invalid bulletproof".to_string()))?;
}
}
// Create the TX extra
let mut extra = ExtraField(vec![
SubField::TxPublicKey(PublicKey { point: outputs[0].R.compress() })
]);
extra.0.push(SubField::AdditionalPublickKey(
outputs[1 .. outputs.len()].iter().map(|output| PublicKey { point: output.R.compress() }).collect()
));
// Format it for monero-rs
let mut mrs_outputs = Vec::with_capacity(outputs.len());
let mut out_pk = Vec::with_capacity(outputs.len());
let mut ecdh_info = Vec::with_capacity(outputs.len());
for o in 0 .. outputs.len() {
mrs_outputs.push(TxOut {
amount: VarInt(0),
target: TxOutTarget::ToKey { key: PublicKey { point: outputs[o].dest.compress() } }
});
out_pk.push(CtKey {
mask: Key { key: commitments[o].calculate().compress().to_bytes() }
});
ecdh_info.push(EcdhInfo::Bulletproof { amount: outputs[o].amount });
}
Ok((
match prep {
// Encode the prep
Preparation::Leader(..) => {
let mut prep = entropy.to_vec();
bp.as_ref().unwrap().consensus_encode(&mut prep).expect("Couldn't encode bulletproof");
prep
},
Preparation::Follower(..) => {
vec![]
}
},
outputs.iter().map(|output| output.mask).sum(),
Transaction {
prefix: TransactionPrefix {
version: VarInt(2),
unlock_time: VarInt(0),
inputs: vec![],
outputs: mrs_outputs,
extra
},
signatures: vec![],
rct_signatures: RctSig {
sig: Some(RctSigBase {
rct_type: RctType::Clsag,
txn_fee: VarInt(fee),
pseudo_outs: vec![],
ecdh_info,
out_pk
}),
p: Some(RctSigPrunable {
range_sigs: vec![],
bulletproofs: vec![bp.unwrap()],
MGs: vec![],
Clsags: vec![],
pseudo_outs: vec![]
})
}
}
))
}
async fn prepare_inputs(
rpc: &Rpc,
spend: &Scalar,
inputs: &[SpendableOutput],
tx: &mut Transaction
) -> Result<Vec<(Scalar, clsag::Input, EdwardsPoint)>, TransactionError> {
let mut mixins = Vec::with_capacity(inputs.len());
let mut signable = Vec::with_capacity(inputs.len());
for (i, input) in inputs.iter().enumerate() {
// Select mixins
let (m, mix) = mixins::select(
let (m, mixins) = mixins::select(
rpc.get_o_indexes(input.tx).await.map_err(|e| TransactionError::RpcError(e))?[input.o]
);
mixins.push(mix);
signable.push((
spend + input.key_offset,
clsag::Input::new(
rpc.get_ring(&mixins[i]).await.map_err(|e| TransactionError::RpcError(e))?,
rpc.get_ring(&mixins).await.map_err(|e| TransactionError::RpcError(e))?,
m,
input.commitment
).map_err(|e| TransactionError::ClsagError(e))?,
@@ -351,7 +225,7 @@ async fn prepare_inputs(
tx.prefix.inputs.push(TxIn::ToKey {
amount: VarInt(0),
key_offsets: mixins::offset(&mixins[i]).iter().map(|x| VarInt(*x)).collect(),
key_offsets: mixins::offset(&mixins).iter().map(|x| VarInt(*x)).collect(),
k_image: KeyImage { image: Hash(signable[i].2.compress().to_bytes()) }
});
}
@@ -390,19 +264,142 @@ impl SignableTransaction {
)
}
fn prepare_outputs<'a, R: RngCore + CryptoRng>(
&self,
prep: &mut Preparation<'a, R>
) -> Result<(Vec<u8>, Scalar, Transaction), TransactionError> {
let fee = self.fee_per_byte * 2000; // TODO
// TODO TX MAX SIZE
// Make sure we have enough funds
let in_amount = self.inputs.iter().map(|input| input.commitment.amount).sum();
let out_amount = fee + self.payments.iter().map(|payment| payment.1).sum::<u64>();
if in_amount < out_amount {
Err(TransactionError::NotEnoughFunds(in_amount, out_amount))?;
}
// Add the change output
let mut payments = self.payments.clone();
payments.push((self.change, in_amount - out_amount));
// Grab the prep
let mut entropy = [0; 32];
let mut bp = None;
match prep {
Preparation::Leader(ref mut rng) => {
// The Leader generates the entropy for the one time keys and the bulletproof
rng.fill_bytes(&mut entropy);
},
Preparation::Follower(e, b) => {
entropy = e.clone();
bp = Some(b.clone());
}
}
let mut seed = b"StealthAddress_randomness".to_vec();
// Leader selected entropy to prevent de-anonymization via recalculation of randomness
seed.extend(&entropy);
// This output can only be spent once. Therefore, it forces all one time keys used here to be
// unique, even if the leader reuses entropy. While another transaction could use a different
// input ordering to swap which 0 is, that input set can't contain this input without being a
// double spend
seed.extend(&self.inputs[0].tx.0);
seed.extend(&self.inputs[0].o.to_le_bytes());
let mut rng = ChaCha12Rng::from_seed(Blake2b512::digest(seed)[0 .. 32].try_into().unwrap());
let mut outputs = Vec::with_capacity(payments.len());
let mut commitments = Vec::with_capacity(payments.len());
for o in 0 .. payments.len() {
outputs.push(Output::new(&mut rng, payments[o], o)?);
commitments.push(Commitment::new(outputs[o].mask, payments[o].1));
}
if bp.is_none() {
// Generate the bulletproof if leader
bp = Some(bulletproofs::generate(&commitments)?);
} else {
// Verify the bulletproof if follower
if !bulletproofs::verify(
bp.as_ref().unwrap(),
&commitments.iter().map(|c| c.calculate()).collect::<Vec<EdwardsPoint>>()
) {
Err(TransactionError::InvalidPreparation("invalid bulletproof".to_string()))?;
}
}
// Create the TX extra
let mut extra = ExtraField(vec![
SubField::TxPublicKey(PublicKey { point: outputs[0].R.compress() })
]);
extra.0.push(SubField::AdditionalPublickKey(
outputs[1 .. outputs.len()].iter().map(|output| PublicKey { point: output.R.compress() }).collect()
));
// Format it for monero-rs
let mut mrs_outputs = Vec::with_capacity(outputs.len());
let mut out_pk = Vec::with_capacity(outputs.len());
let mut ecdh_info = Vec::with_capacity(outputs.len());
for o in 0 .. outputs.len() {
mrs_outputs.push(TxOut {
amount: VarInt(0),
target: TxOutTarget::ToKey { key: PublicKey { point: outputs[o].dest.compress() } }
});
out_pk.push(CtKey {
mask: Key { key: commitments[o].calculate().compress().to_bytes() }
});
ecdh_info.push(EcdhInfo::Bulletproof { amount: outputs[o].amount });
}
Ok((
match prep {
// Encode the prep
Preparation::Leader(..) => {
let mut prep = entropy.to_vec();
bp.as_ref().unwrap().consensus_encode(&mut prep).expect("Couldn't encode bulletproof");
prep
},
Preparation::Follower(..) => {
vec![]
}
},
outputs.iter().map(|output| output.mask).sum(),
Transaction {
prefix: TransactionPrefix {
version: VarInt(2),
unlock_time: VarInt(0),
inputs: vec![],
outputs: mrs_outputs,
extra
},
signatures: vec![],
rct_signatures: RctSig {
sig: Some(RctSigBase {
rct_type: RctType::Clsag,
txn_fee: VarInt(fee),
pseudo_outs: vec![],
ecdh_info,
out_pk
}),
p: Some(RctSigPrunable {
range_sigs: vec![],
bulletproofs: vec![bp.unwrap()],
MGs: vec![],
Clsags: vec![],
pseudo_outs: vec![]
})
}
}
))
}
pub async fn sign<R: RngCore + CryptoRng>(
&self,
rng: &mut R,
rpc: &Rpc,
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 (_, mask_sum, mut tx) = self.prepare_outputs(&mut Preparation::Leader(rng))?;
let signable = prepare_inputs(rpc, spend, &self.inputs, &mut tx).await?;

View File

@@ -0,0 +1,237 @@
use std::{rc::Rc, cell::RefCell};
use rand_core::{RngCore, CryptoRng};
use rand_chacha::ChaCha12Rng;
use curve25519_dalek::{scalar::Scalar, edwards::{EdwardsPoint, CompressedEdwardsY}};
use frost::{FrostError, MultisigKeys, MultisigParams, sign::{State, StateMachine, AlgorithmMachine}};
use monero::{
Hash, VarInt,
consensus::deserialize,
util::ringct::Key,
blockdata::transaction::{KeyImage, TxIn, Transaction}
};
use crate::{
frost::Ed25519,
key_image,
clsag,
rpc::Rpc,
transaction::{TransactionError, Preparation, SignableTransaction, mixins}
};
pub struct TransactionMachine {
leader: bool,
signable: SignableTransaction,
our_images: Vec<EdwardsPoint>,
inputs: Vec<TxIn>,
tx: Option<Transaction>,
mask_sum: Rc<RefCell<Scalar>>,
msg: Rc<RefCell<[u8; 32]>>,
clsags: Vec<AlgorithmMachine<Ed25519, clsag::Multisig>>
}
impl SignableTransaction {
pub async fn multisig<R: RngCore + CryptoRng>(
self,
rng: &mut R,
rpc: &Rpc,
keys: Rc<MultisigKeys<Ed25519>>,
included: &[usize]
) -> Result<TransactionMachine, TransactionError> {
let mut our_images = vec![];
let mut inputs = vec![];
let mask_sum = Rc::new(RefCell::new(Scalar::zero()));
let msg = Rc::new(RefCell::new([0; 32]));
let mut clsags = vec![];
for input in &self.inputs {
// Select mixins
let (m, mixins) = mixins::select(
rpc.get_o_indexes(input.tx).await.map_err(|e| TransactionError::RpcError(e))?[input.o]
);
let keys = keys.offset(dalek_ff_group::Scalar(input.key_offset));
let (image, _) = key_image::generate_share(
rng,
&keys.view(included).map_err(|e| TransactionError::FrostError(e))?
);
our_images.push(image);
clsags.push(
AlgorithmMachine::new(
clsag::Multisig::new(
clsag::Input::new(
rpc.get_ring(&mixins).await.map_err(|e| TransactionError::RpcError(e))?,
m,
input.commitment
).map_err(|e| TransactionError::ClsagError(e))?,
msg.clone(),
mask_sum.clone()
).map_err(|e| TransactionError::MultisigError(e))?,
Rc::new(keys),
included
).map_err(|e| TransactionError::FrostError(e))?
);
inputs.push(TxIn::ToKey {
amount: VarInt(0),
key_offsets: mixins::offset(&mixins).iter().map(|x| VarInt(*x)).collect(),
k_image: KeyImage { image: Hash([0; 32]) }
});
}
// Verify these outputs by a dummy prep
self.prepare_outputs(&mut Preparation::Leader(rng))?;
Ok(TransactionMachine {
leader: keys.params().i() == included[0],
signable: self,
our_images,
inputs,
tx: None,
mask_sum,
msg,
clsags
})
}
}
impl StateMachine for TransactionMachine {
type Signature = Transaction;
fn preprocess<R: RngCore + CryptoRng>(
&mut self,
rng: &mut R
) -> Result<Vec<u8>, FrostError> {
if self.state() != State::Fresh {
Err(FrostError::InvalidSignTransition(State::Fresh, self.state()))?;
}
// Iterate over each CLSAG calling preprocess
let mut serialized = vec![];
for clsag in self.clsags.iter_mut() {
serialized.extend(&clsag.preprocess(rng)?);
}
if self.leader {
let (prep, mask_sum, tx) = self.signable.prepare_outputs(&mut Preparation::Leader(rng)).unwrap();
self.mask_sum.replace(mask_sum);
self.tx = Some(tx);
serialized.extend(&prep);
}
Ok(serialized)
}
fn sign(
&mut self,
commitments: &[Option<Vec<u8>>],
_: &[u8]
) -> Result<Vec<u8>, FrostError> {
if self.state() != State::Preprocessed {
Err(FrostError::InvalidSignTransition(State::Preprocessed, self.state()))?;
}
// FROST commitments, image, commitments, and their proofs
let clsag_len = 64 + clsag::Multisig::serialized_len();
let clsag_lens = clsag_len * self.clsags.len();
// Split out the prep and update the TX
let mut tx = None;
if self.leader {
tx = self.tx.take();
} else {
for (l, prep) in commitments.iter().enumerate() {
if prep.is_none() {
continue;
}
let prep = prep.as_ref().unwrap();
// Handle the prep with a seeded RNG type to make rustc happy
let (_, mask_sum, tx_inner) = self.signable.prepare_outputs::<ChaCha12Rng>(
&mut Preparation::Follower(
prep[clsag_lens .. (clsag_lens + 32)].try_into().map_err(|_| FrostError::InvalidCommitment(l))?,
deserialize(&prep[(clsag_lens + 32) .. prep.len()]).map_err(|_| FrostError::InvalidCommitment(l))?
)
).map_err(|_| FrostError::InvalidShare(l))?; // Not invalid outputs due to doing a dummy prep as leader
self.mask_sum.replace(mask_sum);
tx = Some(tx_inner);
break;
}
}
// Calculate the key images and update the TX
// Multisig will parse/calculate/validate this as needed, yet doing so here as well provides
// the easiest API overall
for c in 0 .. self.clsags.len() {
let mut image = self.our_images[c];
for (l, serialized) in commitments.iter().enumerate() {
if serialized.is_none() {
continue;
}
image += CompressedEdwardsY(
serialized.as_ref().unwrap()[((c * clsag_len) + 64) .. ((c * clsag_len) + 96)]
.try_into().map_err(|_| 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")
};
}
let mut tx = tx.unwrap();
tx.prefix.inputs = self.inputs.clone();
self.msg.replace(tx.signature_hash().unwrap().0);
self.tx = Some(tx);
// Iterate over each CLSAG calling sign
let mut serialized = Vec::with_capacity(self.clsags.len() * 32);
for (c, clsag) in self.clsags.iter_mut().enumerate() {
serialized.extend(&clsag.sign(
&commitments.iter().map(
|commitments| commitments.clone().map(
|commitments| commitments[(c * clsag_len) .. ((c * clsag_len) + clsag_len)].to_vec()
)
).collect::<Vec<_>>(),
&vec![]
)?);
}
Ok(serialized)
}
fn complete(&mut self, shares: &[Option<Vec<u8>>]) -> Result<Transaction, FrostError> {
if self.state() != State::Signed {
Err(FrostError::InvalidSignTransition(State::Signed, self.state()))?;
}
let mut tx = self.tx.take().unwrap();
let mut prunable = tx.rct_signatures.p.unwrap();
for (c, clsag) in self.clsags.iter_mut().enumerate() {
let (clsag, pseudo_out) = clsag.complete(&shares.iter().map(
|share| share.clone().map(|share| share[(c * 32) .. ((c * 32) + 32)].to_vec())
).collect::<Vec<_>>())?;
prunable.Clsags.push(clsag);
prunable.pseudo_outs.push(Key { key: pseudo_out.compress().to_bytes() });
}
tx.rct_signatures.p = Some(prunable);
Ok(tx)
}
fn multisig_params(&self) -> MultisigParams {
self.clsags[0].multisig_params()
}
fn state(&self) -> State {
self.clsags[0].state()
}
}