mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Use dom-sep tags in the transcripts
Also simplifies form in some places
This commit is contained in:
@@ -26,7 +26,9 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
impl Input {
|
impl Input {
|
||||||
pub fn transcript<T: TranscriptTrait>(&self, transcript: &mut T) {
|
fn transcript<T: TranscriptTrait>(&self, transcript: &mut T) {
|
||||||
|
// Doesn't dom-sep as this is considered part of the larger input signing proof
|
||||||
|
|
||||||
// Ring index
|
// Ring index
|
||||||
transcript.append_message(b"ring_index", &[self.i]);
|
transcript.append_message(b"ring_index", &[self.i]);
|
||||||
|
|
||||||
@@ -170,8 +172,9 @@ impl Algorithm<Ed25519> for Multisig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn transcript(&self) -> Option<Self::Transcript> {
|
fn transcript(&self) -> Option<Self::Transcript> {
|
||||||
let mut transcript = Self::Transcript::new(b"CLSAG");
|
let mut transcript = Self::Transcript::new(b"Monero Multisig");
|
||||||
self.input.transcript(&mut transcript);
|
self.input.transcript(&mut transcript);
|
||||||
|
transcript.append_message(b"dom-sep", b"CLSAG");
|
||||||
// Given the fact there's only ever one possible value for this, this may technically not need
|
// 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
|
// 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
|
// It doesn't hurt to have though and ensures security boundaries are well formed
|
||||||
|
|||||||
@@ -52,25 +52,20 @@ impl SignableTransaction {
|
|||||||
// 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
|
||||||
// The use of input shared keys technically makes this one time given a competent wallet which
|
// The use of input shared keys technically makes this one time given a competent wallet which
|
||||||
// can withstand the burning attack
|
// can withstand the burning attack (and has a static spend key? TODO visit bounds)
|
||||||
// The lack of dedicated entropy here is frustrating. We can probably provide entropy inclusion
|
// 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
|
// if we move CLSAG ring to a Rc RefCell like msg and mask? TODO
|
||||||
|
// For the above TODO, also consider FROST's TODO of a global transcript instance
|
||||||
let mut transcript = Transcript::new(b"Input Mixins");
|
let mut transcript = Transcript::new(b"Input Mixins");
|
||||||
let mut shared_keys = Vec::with_capacity(self.inputs.len() * 32);
|
// Does dom-sep despite not being a proof because it's a unique section (and we have no dom-sep yet)
|
||||||
|
transcript.append_message("dom-sep", "inputs_outputs");
|
||||||
for input in &self.inputs {
|
for input in &self.inputs {
|
||||||
shared_keys.extend(&input.key_offset.to_bytes());
|
transcript.append_message(b"input_shared_key", &input.key_offset.to_bytes());
|
||||||
}
|
}
|
||||||
transcript.append_message(b"input_shared_keys", &shared_keys);
|
|
||||||
let mut payments = Vec::with_capacity(self.payments.len() * ((2 * 32) + 8));
|
|
||||||
for payment in &self.payments {
|
for payment in &self.payments {
|
||||||
// Network byte and spend/view key
|
transcript.append_message(b"payment_address", &payment.0.as_bytes());
|
||||||
// Doesn't use the full address as monero-rs may provide a payment ID which adds bytes
|
transcript.append_message(b"payment_amount", &payment.1.to_le_bytes());
|
||||||
// By simply cutting this short, we get the relevant data without length differences nor the
|
|
||||||
// need to prefix
|
|
||||||
payments.extend(&payment.0.as_bytes()[0 .. 65]);
|
|
||||||
payments.extend(payment.1.to_le_bytes());
|
|
||||||
}
|
}
|
||||||
transcript.append_message(b"payments", &payments);
|
|
||||||
|
|
||||||
// Select mixins
|
// Select mixins
|
||||||
let mixins = mixins::select(
|
let mixins = mixins::select(
|
||||||
@@ -129,11 +124,12 @@ impl SignableTransaction {
|
|||||||
|
|
||||||
// Seeded RNG so multisig participants agree on one time keys to use, preventing burning attacks
|
// Seeded RNG so multisig participants agree on one time keys to use, preventing burning attacks
|
||||||
fn outputs_rng(tx: &SignableTransaction, entropy: [u8; 32]) -> <Transcript as TranscriptTrait>::SeededRng {
|
fn outputs_rng(tx: &SignableTransaction, entropy: [u8; 32]) -> <Transcript as TranscriptTrait>::SeededRng {
|
||||||
let mut transcript = Transcript::new(b"StealthAddress");
|
let mut transcript = Transcript::new(b"Stealth Addresses");
|
||||||
// This output can only be spent once. Therefore, it forces all one time keys used here to be
|
// This output can only be spent once. Therefore, it forces all one time keys used here to be
|
||||||
// unique, even if the entropy is reused. While another transaction could use a different input
|
// unique, even if the entropy is reused. 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
|
// ordering to swap which 0 is, that input set can't contain this input without being a double
|
||||||
// spend
|
// spend
|
||||||
|
transcript.append_message(b"dom-sep", b"input_0");
|
||||||
transcript.append_message(b"hash", &tx.inputs[0].tx.0);
|
transcript.append_message(b"hash", &tx.inputs[0].tx.0);
|
||||||
transcript.append_message(b"index", &u64::try_from(tx.inputs[0].o).unwrap().to_le_bytes());
|
transcript.append_message(b"index", &u64::try_from(tx.inputs[0].o).unwrap().to_le_bytes());
|
||||||
transcript.seeded_rng(b"tx_keys", Some(entropy))
|
transcript.seeded_rng(b"tx_keys", Some(entropy))
|
||||||
|
|||||||
@@ -218,15 +218,26 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
|
|||||||
// While Merlin, which may or may not be the transcript used here, wants application level
|
// While Merlin, which may or may not be the transcript used here, wants application level
|
||||||
// transcripts passed around to proof systems, this maintains a desired level of abstraction and
|
// transcripts passed around to proof systems, this maintains a desired level of abstraction and
|
||||||
// works without issue
|
// works without issue
|
||||||
|
// Not to mention, mandating a global transcript would conflict with the IETF draft UNLESS an
|
||||||
|
// IetfTranscript was declared which ignores field names and solely does their values, with a
|
||||||
|
// fresh instantiation per sign round. That could likely be made to align without issue
|
||||||
|
// TODO: Consider Option<Transcript>?
|
||||||
let mut transcript = params.algorithm.transcript();
|
let mut transcript = params.algorithm.transcript();
|
||||||
|
if params.keys.offset.is_some() && transcript.is_none() {
|
||||||
|
transcript = Some(A::Transcript::new(b"FROST Offset"));
|
||||||
|
}
|
||||||
|
if transcript.is_some() {
|
||||||
|
// https://github.com/rust-lang/rust/issues/91345
|
||||||
|
transcript = transcript.map(|mut t| { t.append_message(b"dom-sep", b"FROST"); t });
|
||||||
|
}
|
||||||
|
|
||||||
// If the offset functionality provided by this library is in use, include it in the transcript.
|
// If the offset functionality provided by this library is in use, include it in the transcript.
|
||||||
// Not compliant with the IETF spec which doesn't have a concept of offsets, nor does it use
|
// Not compliant with the IETF spec which doesn't have a concept of offsets, nor does it use
|
||||||
// transcripts
|
// transcripts
|
||||||
if params.keys.offset.is_some() {
|
if params.keys.offset.is_some() {
|
||||||
let mut offset_transcript = transcript.unwrap_or(A::Transcript::new(b"FROST_offset"));
|
transcript = transcript.map(
|
||||||
offset_transcript.append_message(b"offset", &C::F_to_le_bytes(¶ms.keys.offset.unwrap()));
|
|mut t| { t.append_message(b"offset", &C::F_to_le_bytes(¶ms.keys.offset.unwrap())); t }
|
||||||
transcript = Some(offset_transcript);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a transcript was defined, move the commitments used for the binding factor into it
|
// If a transcript was defined, move the commitments used for the binding factor into it
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ pub trait Transcript {
|
|||||||
label: &'static [u8],
|
label: &'static [u8],
|
||||||
additional_entropy: Option<[u8; 32]>
|
additional_entropy: Option<[u8; 32]>
|
||||||
) -> Self::SeededRng;
|
) -> Self::SeededRng;
|
||||||
|
|
||||||
|
// TODO: Consider a domain_separate function
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|||||||
Reference in New Issue
Block a user