mirror of
https://github.com/serai-dex/serai.git
synced 2025-04-23 14:38:14 +00:00
Use dom-sep tags in the transcripts
Also simplifies form in some places
This commit is contained in:
parent
0f481773df
commit
cc9c2e0d40
4 changed files with 31 additions and 19 deletions
|
@ -26,7 +26,9 @@ use crate::{
|
|||
};
|
||||
|
||||
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
|
||||
transcript.append_message(b"ring_index", &[self.i]);
|
||||
|
||||
|
@ -170,8 +172,9 @@ impl Algorithm<Ed25519> for Multisig {
|
|||
}
|
||||
|
||||
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);
|
||||
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
|
||||
// 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
|
||||
|
|
|
@ -52,25 +52,20 @@ impl SignableTransaction {
|
|||
// 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
|
||||
// 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
|
||||
// if we move CLSAG ring to a Rc RefCell like msg and mask? TODO
|
||||
let mut transcript = Transcript::new(b"InputMixins");
|
||||
let mut shared_keys = Vec::with_capacity(self.inputs.len() * 32);
|
||||
// For the above TODO, also consider FROST's TODO of a global transcript instance
|
||||
let mut transcript = Transcript::new(b"Input Mixins");
|
||||
// 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 {
|
||||
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 {
|
||||
// Network byte and spend/view key
|
||||
// Doesn't use the full address as monero-rs may provide a payment ID which adds 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"payment_address", &payment.0.as_bytes());
|
||||
transcript.append_message(b"payment_amount", &payment.1.to_le_bytes());
|
||||
}
|
||||
transcript.append_message(b"payments", &payments);
|
||||
|
||||
// Select mixins
|
||||
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
|
||||
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
|
||||
// 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
|
||||
// spend
|
||||
transcript.append_message(b"dom-sep", b"input_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.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
|
||||
// transcripts passed around to proof systems, this maintains a desired level of abstraction and
|
||||
// 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();
|
||||
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.
|
||||
// Not compliant with the IETF spec which doesn't have a concept of offsets, nor does it use
|
||||
// transcripts
|
||||
if params.keys.offset.is_some() {
|
||||
let mut offset_transcript = transcript.unwrap_or(A::Transcript::new(b"FROST_offset"));
|
||||
offset_transcript.append_message(b"offset", &C::F_to_le_bytes(¶ms.keys.offset.unwrap()));
|
||||
transcript = Some(offset_transcript);
|
||||
transcript = transcript.map(
|
||||
|mut t| { t.append_message(b"offset", &C::F_to_le_bytes(¶ms.keys.offset.unwrap())); t }
|
||||
);
|
||||
}
|
||||
|
||||
// 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],
|
||||
additional_entropy: Option<[u8; 32]>
|
||||
) -> Self::SeededRng;
|
||||
|
||||
// TODO: Consider a domain_separate function
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
Loading…
Reference in a new issue