mirror of
https://github.com/serai-dex/serai.git
synced 2024-11-16 17:07:35 +00:00
Working multisig TXs
This commit is contained in:
parent
d6649fffb1
commit
9ccf683e9d
12 changed files with 577 additions and 325 deletions
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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?;
|
||||
|
||||
|
|
237
coins/monero/src/transaction/multisig.rs
Normal file
237
coins/monero/src/transaction/multisig.rs
Normal 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()
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
use std::{rc::Rc, cell::RefCell};
|
||||
|
||||
use rand::{RngCore, rngs::OsRng};
|
||||
|
||||
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar};
|
||||
|
@ -50,20 +52,6 @@ fn test_single() {
|
|||
assert!(clsag::verify(&clsag, &msg, image, &ring, pseudo_out));
|
||||
}
|
||||
|
||||
#[cfg(feature = "multisig")]
|
||||
#[derive(Clone, Debug)]
|
||||
struct TransactionData;
|
||||
#[cfg(feature = "multisig")]
|
||||
impl clsag::TransactionData for TransactionData {
|
||||
fn msg(&self) -> [u8; 32] {
|
||||
[1; 32]
|
||||
}
|
||||
|
||||
fn mask_sum(&self) -> Scalar {
|
||||
Scalar::from(21u64)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "multisig")]
|
||||
#[test]
|
||||
fn test_multisig() -> Result<(), MultisigError> {
|
||||
|
@ -94,7 +82,8 @@ fn test_multisig() -> Result<(), MultisigError> {
|
|||
sign::AlgorithmMachine::new(
|
||||
clsag::Multisig::new(
|
||||
clsag::Input::new(ring.clone(), RING_INDEX, Commitment::new(randomness, AMOUNT)).unwrap(),
|
||||
TransactionData
|
||||
Rc::new(RefCell::new([1; 32])),
|
||||
Rc::new(RefCell::new(Scalar::from(42u64)))
|
||||
).unwrap(),
|
||||
keys[i - 1].clone(),
|
||||
&(1 ..= THRESHOLD).collect::<Vec<usize>>()
|
||||
|
|
|
@ -2,11 +2,10 @@
|
|||
|
||||
use std::rc::Rc;
|
||||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use ff::Field;
|
||||
use dalek_ff_group::{ED25519_BASEPOINT_TABLE, Scalar, EdwardsPoint};
|
||||
use dalek_ff_group::{ED25519_BASEPOINT_TABLE, Scalar};
|
||||
|
||||
pub use frost::{
|
||||
FrostError, MultisigParams, MultisigKeys,
|
||||
|
@ -15,50 +14,8 @@ pub use frost::{
|
|||
|
||||
use monero_serai::frost::Ed25519;
|
||||
|
||||
pub const THRESHOLD: usize = 5;
|
||||
pub const PARTICIPANTS: usize = 8;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DummyAlgorithm;
|
||||
impl Algorithm<Ed25519> for DummyAlgorithm {
|
||||
type Signature = ();
|
||||
|
||||
fn addendum_commit_len() -> usize { unimplemented!() }
|
||||
|
||||
fn preprocess_addendum<R: RngCore + CryptoRng>(
|
||||
_: &mut R,
|
||||
_: &sign::ParamsView<Ed25519>,
|
||||
_: &[Scalar; 2],
|
||||
) -> Vec<u8> { unimplemented!() }
|
||||
|
||||
fn process_addendum(
|
||||
&mut self,
|
||||
_: &sign::ParamsView<Ed25519>,
|
||||
_: usize,
|
||||
_: &[EdwardsPoint; 2],
|
||||
_: &[u8],
|
||||
) -> Result<(), FrostError> { unimplemented!() }
|
||||
|
||||
fn context(&self) -> Vec<u8> { unimplemented!() }
|
||||
|
||||
fn sign_share(
|
||||
&mut self,
|
||||
_: &sign::ParamsView<Ed25519>,
|
||||
_: EdwardsPoint,
|
||||
_: Scalar,
|
||||
_: Scalar,
|
||||
_: &[u8],
|
||||
) -> Scalar { unimplemented!() }
|
||||
|
||||
fn verify(&self, _: EdwardsPoint, _: EdwardsPoint, _: Scalar) -> Option<Self::Signature> { unimplemented!() }
|
||||
|
||||
fn verify_share(
|
||||
&self,
|
||||
_: EdwardsPoint,
|
||||
_: EdwardsPoint,
|
||||
_: Scalar,
|
||||
) -> bool { unimplemented!() }
|
||||
}
|
||||
pub const THRESHOLD: usize = 3;
|
||||
pub const PARTICIPANTS: usize = 5;
|
||||
|
||||
pub fn generate_keys() -> (Vec<Rc<MultisigKeys<Ed25519>>>, Scalar) {
|
||||
let mut params = vec![];
|
||||
|
|
|
@ -4,15 +4,13 @@ use rand::{RngCore, rngs::OsRng};
|
|||
|
||||
use curve25519_dalek::{traits::Identity, edwards::EdwardsPoint};
|
||||
|
||||
use monero_serai::{frost::MultisigError, key_image};
|
||||
|
||||
use ::frost::sign;
|
||||
use monero_serai::key_image;
|
||||
|
||||
mod frost;
|
||||
use crate::frost::{THRESHOLD, PARTICIPANTS, DummyAlgorithm, generate_keys};
|
||||
use crate::frost::{THRESHOLD, PARTICIPANTS, generate_keys};
|
||||
|
||||
#[test]
|
||||
fn test() -> Result<(), MultisigError> {
|
||||
fn test() {
|
||||
let (keys, group_private) = generate_keys();
|
||||
let image = key_image::generate(&group_private);
|
||||
|
||||
|
@ -27,16 +25,16 @@ fn test() -> Result<(), MultisigError> {
|
|||
for i in 1 ..= PARTICIPANTS {
|
||||
if included.contains(&i) {
|
||||
// If they were included, include their view
|
||||
views.push(sign::Params::new(DummyAlgorithm, keys[i - 1].clone(), &included).unwrap().view());
|
||||
views.push(keys[i - 1].view(&included).unwrap());
|
||||
let share = key_image::generate_share(&mut OsRng, &views[i - 1]);
|
||||
let mut serialized = share.0;
|
||||
let mut serialized = share.0.compress().to_bytes().to_vec();
|
||||
serialized.extend(b"abc");
|
||||
serialized.extend(&share.1);
|
||||
shares.push(serialized);
|
||||
} else {
|
||||
// If they weren't included, include dummy data
|
||||
// If they weren't included, include dummy data to fill the Vec
|
||||
// Uses the view of someone actually included as Params::new verifies inclusion
|
||||
views.push(sign::Params::new(DummyAlgorithm, keys[included[0] - 1].clone(), &included).unwrap().view());
|
||||
views.push(keys[included[0] - 1].view(&included).unwrap());
|
||||
shares.push(vec![]);
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +48,4 @@ fn test() -> Result<(), MultisigError> {
|
|||
}
|
||||
assert_eq!(image, multi_image);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
73
coins/monero/tests/send_multisig.rs
Normal file
73
coins/monero/tests/send_multisig.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
#![cfg(feature = "multisig")]
|
||||
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use blake2::{digest::Update, Digest, Blake2b512};
|
||||
|
||||
use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE;
|
||||
use dalek_ff_group::Scalar;
|
||||
|
||||
use monero::{
|
||||
cryptonote::hash::Hashable,
|
||||
network::Network,
|
||||
util::{key::PublicKey, address::Address}
|
||||
};
|
||||
|
||||
use monero_serai::{transaction::{self, SignableTransaction}, rpc::Rpc};
|
||||
|
||||
mod rpc;
|
||||
use crate::rpc::mine_block;
|
||||
|
||||
mod frost;
|
||||
use crate::frost::{THRESHOLD, generate_keys, sign};
|
||||
|
||||
#[tokio::test]
|
||||
pub async fn send_multisig() {
|
||||
let rpc = Rpc::new("http://127.0.0.1:18081".to_string());
|
||||
|
||||
let fee_per_byte = 50000000;
|
||||
let fee = fee_per_byte * 2000;
|
||||
|
||||
let (keys, _) = generate_keys();
|
||||
let t = keys[0].params().t();
|
||||
|
||||
// Generate an address
|
||||
let view = Scalar::from_hash(Blake2b512::new().chain("Serai DEX")).0;
|
||||
let spend = keys[0].group_key().0;
|
||||
let addr = Address::standard(
|
||||
Network::Mainnet,
|
||||
PublicKey { point: spend.compress() },
|
||||
PublicKey { point: (&view * &ED25519_BASEPOINT_TABLE).compress() }
|
||||
);
|
||||
|
||||
// Mine blocks to that address
|
||||
let start = rpc.get_height().await.unwrap();
|
||||
for _ in 0 .. 7 {
|
||||
mine_block(&rpc, addr.to_string()).await.unwrap();
|
||||
}
|
||||
|
||||
// Get the input TX
|
||||
let tx = rpc.get_block_transactions(start).await.unwrap().swap_remove(0);
|
||||
let output = transaction::scan(&tx, view, spend).swap_remove(0);
|
||||
let amount = output.commitment.amount - fee;
|
||||
|
||||
let mut machines = Vec::with_capacity(t);
|
||||
for i in 1 ..= t {
|
||||
machines.push(
|
||||
SignableTransaction::new(
|
||||
vec![output.clone()], vec![(addr, amount)], addr, fee_per_byte
|
||||
).unwrap().multisig(
|
||||
&mut OsRng,
|
||||
&rpc,
|
||||
keys[i - 1].clone(),
|
||||
&(1 ..= THRESHOLD).collect::<Vec<usize>>()
|
||||
).await.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
let txs = sign(&mut machines, keys);
|
||||
for s in 0 .. (t - 1) {
|
||||
assert_eq!(txs[s].hash(), txs[0].hash());
|
||||
}
|
||||
rpc.publish_transaction(&txs[0]).await.unwrap();
|
||||
}
|
|
@ -4,7 +4,7 @@ use rand_core::{RngCore, CryptoRng};
|
|||
|
||||
use group::Group;
|
||||
|
||||
use crate::{Curve, FrostError, sign};
|
||||
use crate::{Curve, FrostError, MultisigView};
|
||||
|
||||
/// Algorithm to use FROST with
|
||||
pub trait Algorithm<C: Curve>: Clone {
|
||||
|
@ -17,14 +17,14 @@ pub trait Algorithm<C: Curve>: Clone {
|
|||
/// Generate an addendum to FROST"s preprocessing stage
|
||||
fn preprocess_addendum<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
params: &sign::ParamsView<C>,
|
||||
params: &MultisigView<C>,
|
||||
nonces: &[C::F; 2],
|
||||
) -> Vec<u8>;
|
||||
|
||||
/// Proccess the addendum for the specified participant. Guaranteed to be ordered
|
||||
fn process_addendum(
|
||||
&mut self,
|
||||
params: &sign::ParamsView<C>,
|
||||
params: &MultisigView<C>,
|
||||
l: usize,
|
||||
commitments: &[C::G; 2],
|
||||
serialized: &[u8],
|
||||
|
@ -39,7 +39,7 @@ pub trait Algorithm<C: Curve>: Clone {
|
|||
/// The nonce will already have been processed into the combined form d + (e * p)
|
||||
fn sign_share(
|
||||
&mut self,
|
||||
params: &sign::ParamsView<C>,
|
||||
params: &MultisigView<C>,
|
||||
nonce_sum: C::G,
|
||||
b: C::F,
|
||||
nonce: C::F,
|
||||
|
@ -98,7 +98,7 @@ impl<C: Curve, H: Hram<C>> Algorithm<C> for Schnorr<C, H> {
|
|||
|
||||
fn preprocess_addendum<R: RngCore + CryptoRng>(
|
||||
_: &mut R,
|
||||
_: &sign::ParamsView<C>,
|
||||
_: &MultisigView<C>,
|
||||
_: &[C::F; 2],
|
||||
) -> Vec<u8> {
|
||||
vec![]
|
||||
|
@ -106,7 +106,7 @@ impl<C: Curve, H: Hram<C>> Algorithm<C> for Schnorr<C, H> {
|
|||
|
||||
fn process_addendum(
|
||||
&mut self,
|
||||
_: &sign::ParamsView<C>,
|
||||
_: &MultisigView<C>,
|
||||
_: usize,
|
||||
_: &[C::G; 2],
|
||||
_: &[u8],
|
||||
|
@ -120,7 +120,7 @@ impl<C: Curve, H: Hram<C>> Algorithm<C> for Schnorr<C, H> {
|
|||
|
||||
fn sign_share(
|
||||
&mut self,
|
||||
params: &sign::ParamsView<C>,
|
||||
params: &MultisigView<C>,
|
||||
nonce_sum: C::G,
|
||||
_: C::F,
|
||||
nonce: C::F,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use core::{ops::Mul, fmt::Debug};
|
||||
|
||||
use ff::PrimeField;
|
||||
use ff::{Field, PrimeField};
|
||||
use group::{Group, GroupOps, ScalarMul};
|
||||
|
||||
use thiserror::Error;
|
||||
|
@ -8,6 +8,7 @@ use thiserror::Error;
|
|||
pub mod key_gen;
|
||||
pub mod algorithm;
|
||||
pub mod sign;
|
||||
use sign::lagrange;
|
||||
|
||||
/// Set of errors for curve-related operations, namely encoding and decoding
|
||||
#[derive(Error, Debug)]
|
||||
|
@ -190,6 +191,33 @@ pub enum FrostError {
|
|||
InternalError(String),
|
||||
}
|
||||
|
||||
// View of keys passable to algorithm implementations
|
||||
#[derive(Clone)]
|
||||
pub struct MultisigView<C: Curve> {
|
||||
group_key: C::G,
|
||||
included: Vec<usize>,
|
||||
secret_share: C::F,
|
||||
verification_shares: Vec<C::G>,
|
||||
}
|
||||
|
||||
impl<C: Curve> MultisigView<C> {
|
||||
pub fn group_key(&self) -> C::G {
|
||||
self.group_key
|
||||
}
|
||||
|
||||
pub fn included(&self) -> Vec<usize> {
|
||||
self.included.clone()
|
||||
}
|
||||
|
||||
pub fn secret_share(&self) -> C::F {
|
||||
self.secret_share
|
||||
}
|
||||
|
||||
pub fn verification_share(&self, l: usize) -> C::G {
|
||||
self.verification_shares[l]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct MultisigKeys<C: Curve> {
|
||||
/// Multisig Parameters
|
||||
|
@ -229,6 +257,30 @@ impl<C: Curve> MultisigKeys<C> {
|
|||
self.verification_shares.clone()
|
||||
}
|
||||
|
||||
pub fn view(&self, included: &[usize]) -> Result<MultisigView<C>, FrostError> {
|
||||
if (included.len() < self.params.t) || (self.params.n < included.len()) {
|
||||
Err(FrostError::InvalidSigningSet("invalid amount of participants included".to_string()))?;
|
||||
}
|
||||
|
||||
let secret_share = self.secret_share * lagrange::<C::F>(self.params.i, &included);
|
||||
let (offset, offset_share) = if self.offset.is_some() {
|
||||
let offset = self.offset.unwrap();
|
||||
(offset, offset * C::F::from(included.len().try_into().unwrap()).invert().unwrap())
|
||||
} else {
|
||||
(C::F::zero(), C::F::zero())
|
||||
};
|
||||
|
||||
Ok(MultisigView {
|
||||
group_key: self.group_key + (C::generator_table() * offset),
|
||||
secret_share: secret_share + offset_share,
|
||||
verification_shares: self.verification_shares.clone().iter().enumerate().map(
|
||||
|(l, share)| (*share * lagrange::<C::F>(l, &included)) +
|
||||
(C::generator_table() * offset_share)
|
||||
).collect(),
|
||||
included: included.to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn serialized_len(n: usize) -> usize {
|
||||
1 + usize::from(C::id_len()) + (3 * 8) + C::F_len() + C::G_len() + (n * C::G_len())
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use core::{convert::{TryFrom, TryInto}, cmp::min, fmt};
|
||||
use core::{convert::TryFrom, cmp::min, fmt};
|
||||
use std::rc::Rc;
|
||||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
@ -6,7 +6,7 @@ use rand_core::{RngCore, CryptoRng};
|
|||
use ff::{Field, PrimeField};
|
||||
use group::Group;
|
||||
|
||||
use crate::{Curve, MultisigParams, MultisigKeys, FrostError, algorithm::Algorithm};
|
||||
use crate::{Curve, FrostError, MultisigParams, MultisigKeys, MultisigView, algorithm::Algorithm};
|
||||
|
||||
/// Calculate the lagrange coefficient
|
||||
pub fn lagrange<F: PrimeField>(
|
||||
|
@ -30,39 +30,12 @@ pub fn lagrange<F: PrimeField>(
|
|||
num * denom.invert().unwrap()
|
||||
}
|
||||
|
||||
// View of params passable to algorithm implementations
|
||||
#[derive(Clone)]
|
||||
pub struct ParamsView<C: Curve> {
|
||||
group_key: C::G,
|
||||
included: Vec<usize>,
|
||||
secret_share: C::F,
|
||||
verification_shares: Vec<C::G>,
|
||||
}
|
||||
|
||||
impl<C: Curve> ParamsView<C> {
|
||||
pub fn group_key(&self) -> C::G {
|
||||
self.group_key
|
||||
}
|
||||
|
||||
pub fn included(&self) -> Vec<usize> {
|
||||
self.included.clone()
|
||||
}
|
||||
|
||||
pub fn secret_share(&self) -> C::F {
|
||||
self.secret_share
|
||||
}
|
||||
|
||||
pub fn verification_share(&self, l: usize) -> C::G {
|
||||
self.verification_shares[l]
|
||||
}
|
||||
}
|
||||
|
||||
/// Pairing of an Algorithm with a MultisigKeys instance and this specific signing set
|
||||
#[derive(Clone)]
|
||||
pub struct Params<C: Curve, A: Algorithm<C>> {
|
||||
algorithm: A,
|
||||
keys: Rc<MultisigKeys<C>>,
|
||||
view: ParamsView<C>,
|
||||
view: MultisigView<C>,
|
||||
}
|
||||
|
||||
// Currently public to enable more complex operations as desired, yet solely used in testing
|
||||
|
@ -75,7 +48,7 @@ impl<C: Curve, A: Algorithm<C>> Params<C, A> {
|
|||
let mut included = included.to_vec();
|
||||
(&mut included).sort_unstable();
|
||||
|
||||
// included < threshold
|
||||
// Included < threshold
|
||||
if included.len() < keys.params.t {
|
||||
Err(FrostError::InvalidSigningSet("not enough signers".to_string()))?;
|
||||
}
|
||||
|
@ -98,37 +71,15 @@ impl<C: Curve, A: Algorithm<C>> Params<C, A> {
|
|||
Err(FrostError::InvalidSigningSet("signing despite not being included".to_string()))?;
|
||||
}
|
||||
|
||||
let secret_share = keys.secret_share * lagrange::<C::F>(keys.params.i, &included);
|
||||
let (offset, offset_share) = if keys.offset.is_some() {
|
||||
let offset = keys.offset.unwrap();
|
||||
(offset, offset * C::F::from(included.len().try_into().unwrap()).invert().unwrap())
|
||||
} else {
|
||||
(C::F::zero(), C::F::zero())
|
||||
};
|
||||
|
||||
Ok(
|
||||
Params {
|
||||
algorithm,
|
||||
// Out of order arguments to prevent additional cloning
|
||||
view: ParamsView {
|
||||
group_key: keys.group_key + (C::generator_table() * offset),
|
||||
secret_share: secret_share + offset_share,
|
||||
verification_shares: keys.verification_shares.clone().iter().enumerate().map(
|
||||
|(l, share)| (*share * lagrange::<C::F>(l, &included)) +
|
||||
(C::generator_table() * offset_share)
|
||||
).collect(),
|
||||
included: included,
|
||||
},
|
||||
keys
|
||||
}
|
||||
)
|
||||
// Out of order arguments to prevent additional cloning
|
||||
Ok(Params { algorithm, view: keys.view(&included).unwrap(), keys })
|
||||
}
|
||||
|
||||
pub fn multisig_params(&self) -> MultisigParams {
|
||||
self.keys.params
|
||||
}
|
||||
|
||||
pub fn view(&self) -> ParamsView<C> {
|
||||
pub fn view(&self) -> MultisigView<C> {
|
||||
self.view.clone()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue