mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-10 21:19:24 +00:00
Transcript crate with both a merlin backend and a basic label len value backend
Moves binding factor/seeded RNGs over to the transcripts.
This commit is contained in:
@@ -1,10 +1,7 @@
|
||||
use core::fmt::Debug;
|
||||
use std::{rc::Rc, cell::RefCell};
|
||||
|
||||
use rand_core::{RngCore, CryptoRng, SeedableRng};
|
||||
use rand_chacha::ChaCha12Rng;
|
||||
|
||||
use blake2::{Digest, Blake2b512};
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
use curve25519_dalek::{
|
||||
constants::ED25519_BASEPOINT_TABLE,
|
||||
@@ -13,19 +10,43 @@ use curve25519_dalek::{
|
||||
edwards::EdwardsPoint
|
||||
};
|
||||
|
||||
use group::Group;
|
||||
use dalek_ff_group as dfg;
|
||||
use frost::{Curve, FrostError, algorithm::Algorithm, MultisigView};
|
||||
|
||||
use monero::util::ringct::{Key, Clsag};
|
||||
|
||||
use group::Group;
|
||||
|
||||
use dalek_ff_group as dfg;
|
||||
use transcript::Transcript as TranscriptTrait;
|
||||
use frost::{Curve, FrostError, algorithm::Algorithm, MultisigView};
|
||||
|
||||
use crate::{
|
||||
Transcript,
|
||||
hash_to_point,
|
||||
frost::{MultisigError, Ed25519, DLEqProof},
|
||||
key_image,
|
||||
clsag::{Input, sign_core, verify}
|
||||
};
|
||||
|
||||
impl Input {
|
||||
pub fn transcript<T: TranscriptTrait>(&self, transcript: &mut T) {
|
||||
// Ring index
|
||||
transcript.append_message(b"ring_index", &[self.i]);
|
||||
|
||||
// Ring
|
||||
let mut ring = vec![];
|
||||
for pair in &self.ring {
|
||||
// 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
|
||||
ring.extend(&pair[0].compress().to_bytes());
|
||||
ring.extend(&pair[1].compress().to_bytes());
|
||||
}
|
||||
transcript.append_message(b"ring", &ring);
|
||||
|
||||
// Doesn't include the commitment's parts as the above ring + index includes the commitment
|
||||
// The only potential malleability would be if the G/H relationship is known breaking the
|
||||
// discrete log problem, which breaks everything already
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Clone, Debug)]
|
||||
struct ClsagSignInterim {
|
||||
@@ -39,15 +60,14 @@ struct ClsagSignInterim {
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Multisig {
|
||||
entropy: Vec<u8>,
|
||||
commitments_H: Vec<u8>,
|
||||
image: EdwardsPoint,
|
||||
AH: (dfg::EdwardsPoint, dfg::EdwardsPoint),
|
||||
|
||||
input: Input,
|
||||
|
||||
image: EdwardsPoint,
|
||||
|
||||
msg: Rc<RefCell<[u8; 32]>>,
|
||||
mask_sum: Rc<RefCell<Scalar>>,
|
||||
mask: Rc<RefCell<Scalar>>,
|
||||
|
||||
interim: Option<ClsagSignInterim>
|
||||
}
|
||||
@@ -56,19 +76,18 @@ impl Multisig {
|
||||
pub fn new(
|
||||
input: Input,
|
||||
msg: Rc<RefCell<[u8; 32]>>,
|
||||
mask_sum: Rc<RefCell<Scalar>>,
|
||||
mask: Rc<RefCell<Scalar>>,
|
||||
) -> Result<Multisig, MultisigError> {
|
||||
Ok(
|
||||
Multisig {
|
||||
entropy: vec![],
|
||||
commitments_H: vec![],
|
||||
image: EdwardsPoint::identity(),
|
||||
AH: (dfg::EdwardsPoint::identity(), dfg::EdwardsPoint::identity()),
|
||||
|
||||
input,
|
||||
|
||||
image: EdwardsPoint::identity(),
|
||||
|
||||
msg,
|
||||
mask_sum,
|
||||
mask,
|
||||
|
||||
interim: None
|
||||
}
|
||||
@@ -81,16 +100,9 @@ impl Multisig {
|
||||
}
|
||||
|
||||
impl Algorithm<Ed25519> for Multisig {
|
||||
type Transcript = Transcript;
|
||||
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, 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: &MultisigView<Ed25519>,
|
||||
@@ -125,15 +137,14 @@ impl Algorithm<Ed25519> for Multisig {
|
||||
Err(FrostError::InvalidCommitmentQuantity(l, 9, serialized.len() / 32))?;
|
||||
}
|
||||
|
||||
// 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 .. Multisig::addendum_commit_len()]);
|
||||
|
||||
let (share, serialized) = key_image::verify_share(view, l, serialized).map_err(|_| FrostError::InvalidShare(l))?;
|
||||
self.image += share;
|
||||
|
||||
let alt = &hash_to_point(&self.input.ring[self.input.i][0]);
|
||||
let alt = &hash_to_point(&self.input.ring[usize::from(self.input.i)][0]);
|
||||
|
||||
// Uses the same format FROST does for the expected commitments (nonce * G where this is nonce * H)
|
||||
self.commitments_H.extend(&u64::try_from(l).unwrap().to_le_bytes());
|
||||
self.commitments_H.extend(&serialized[0 .. 64]);
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
let H = (
|
||||
@@ -159,12 +170,20 @@ impl Algorithm<Ed25519> for Multisig {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn context(&self) -> Vec<u8> {
|
||||
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 transcript(&self) -> Option<Self::Transcript> {
|
||||
let mut transcript = Self::Transcript::new(b"CLSAG");
|
||||
self.input.transcript(&mut transcript);
|
||||
// Given the fact there's only ever one possible value for this, this may technically not need
|
||||
// to be committed to. If signing a TX, it's be double committed to thanks to the message
|
||||
// It doesn't hurt to have though and ensures security boundaries are well formed
|
||||
transcript.append_message(b"image", &self.image.compress().to_bytes());
|
||||
// Given this is guaranteed to match commitments, which FROST commits to, this also technically
|
||||
// doesn't need to be committed to if a canonical serialization is guaranteed
|
||||
// It, again, doesn't hurt to include and ensures security boundaries are well formed
|
||||
transcript.append_message(b"commitments_H", &self.commitments_H);
|
||||
transcript.append_message(b"message", &*self.msg.borrow());
|
||||
transcript.append_message(b"mask", &self.mask.borrow().to_bytes());
|
||||
Some(transcript)
|
||||
}
|
||||
|
||||
fn sign_share(
|
||||
@@ -178,13 +197,12 @@ impl Algorithm<Ed25519> for Multisig {
|
||||
// Apply the binding factor to the H variant of the nonce
|
||||
self.AH.0 += self.AH.1 * b;
|
||||
|
||||
// Use the context with the entropy to prevent passive observers of messages from being able to
|
||||
// break privacy, as the context includes the index of the output in the ring, which can only
|
||||
// be known if you have the view key and know which of the wallet's TXOs is being spent
|
||||
let mut seed = b"CLSAG_randomness".to_vec();
|
||||
seed.extend(&self.context());
|
||||
seed.extend(&self.entropy);
|
||||
let mut rng = ChaCha12Rng::from_seed(Blake2b512::digest(seed)[0 .. 32].try_into().unwrap());
|
||||
// Use the transcript to get a seeded random number generator
|
||||
// The transcript contains private data, preventing passive adversaries from recreating this
|
||||
// process even if they have access to commitments (specifically, the ring index being signed
|
||||
// for, along with the mask which should not only require knowing the shared keys yet also the
|
||||
// input commitment mask)
|
||||
let mut rng = self.transcript().unwrap().seeded_rng(b"decoy_responses", None);
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
let (clsag, c, mu_C, z, mu_P, C_out) = sign_core(
|
||||
@@ -192,7 +210,7 @@ impl Algorithm<Ed25519> for Multisig {
|
||||
&self.msg.borrow(),
|
||||
&self.input,
|
||||
&self.image,
|
||||
*self.mask_sum.borrow(),
|
||||
*self.mask.borrow(),
|
||||
nonce_sum.0,
|
||||
self.AH.0.0
|
||||
);
|
||||
@@ -212,7 +230,7 @@ impl Algorithm<Ed25519> for Multisig {
|
||||
let interim = self.interim.as_ref().unwrap();
|
||||
|
||||
let mut clsag = interim.clsag.clone();
|
||||
clsag.s[self.input.i] = Key { key: (sum.0 - interim.s).to_bytes() };
|
||||
clsag.s[usize::from(self.input.i)] = Key { key: (sum.0 - interim.s).to_bytes() };
|
||||
if verify(&clsag, &self.msg.borrow(), self.image, &self.input.ring, interim.C_out) {
|
||||
return Some((clsag, interim.C_out));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user