mirror of
https://github.com/serai-dex/serai.git
synced 2024-11-17 01:17:36 +00:00
3.3.4 Use FROST context throughout Encryption
This commit is contained in:
parent
2d56d24d9c
commit
4d6a0bbd7d
3 changed files with 64 additions and 37 deletions
|
@ -98,11 +98,13 @@ fn ecdh<C: Ciphersuite>(private: &Zeroizing<C::F>, public: C::G) -> Zeroizing<C:
|
||||||
|
|
||||||
// Each ecdh must be distinct. Reuse of an ecdh for multiple ciphers will cause the messages to be
|
// Each ecdh must be distinct. Reuse of an ecdh for multiple ciphers will cause the messages to be
|
||||||
// leaked.
|
// leaked.
|
||||||
fn cipher<C: Ciphersuite>(dst: &'static [u8], ecdh: &Zeroizing<C::G>) -> ChaCha20 {
|
fn cipher<C: Ciphersuite>(context: &str, ecdh: &Zeroizing<C::G>) -> ChaCha20 {
|
||||||
// Ideally, we'd box this transcript with ZAlloc, yet that's only possible on nightly
|
// Ideally, we'd box this transcript with ZAlloc, yet that's only possible on nightly
|
||||||
// TODO: https://github.com/serai-dex/serai/issues/151
|
// TODO: https://github.com/serai-dex/serai/issues/151
|
||||||
let mut transcript = RecommendedTranscript::new(b"DKG Encryption v0.2");
|
let mut transcript = RecommendedTranscript::new(b"DKG Encryption v0.2");
|
||||||
transcript.domain_separate(dst);
|
transcript.append_message(b"context", context.as_bytes());
|
||||||
|
|
||||||
|
transcript.domain_separate(b"encryption_key");
|
||||||
|
|
||||||
let mut ecdh = ecdh.to_bytes();
|
let mut ecdh = ecdh.to_bytes();
|
||||||
transcript.append_message(b"shared_key", ecdh.as_ref());
|
transcript.append_message(b"shared_key", ecdh.as_ref());
|
||||||
|
@ -132,7 +134,7 @@ fn cipher<C: Ciphersuite>(dst: &'static [u8], ecdh: &Zeroizing<C::G>) -> ChaCha2
|
||||||
|
|
||||||
fn encrypt<R: RngCore + CryptoRng, C: Ciphersuite, E: Encryptable>(
|
fn encrypt<R: RngCore + CryptoRng, C: Ciphersuite, E: Encryptable>(
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
dst: &'static [u8],
|
context: &str,
|
||||||
from: Participant,
|
from: Participant,
|
||||||
to: C::G,
|
to: C::G,
|
||||||
mut msg: Zeroizing<E>,
|
mut msg: Zeroizing<E>,
|
||||||
|
@ -149,7 +151,7 @@ fn encrypt<R: RngCore + CryptoRng, C: Ciphersuite, E: Encryptable>(
|
||||||
// Generate a new key for this message, satisfying cipher's requirement of distinct keys per
|
// Generate a new key for this message, satisfying cipher's requirement of distinct keys per
|
||||||
// message, and enabling revealing this message without revealing any others
|
// message, and enabling revealing this message without revealing any others
|
||||||
let key = Zeroizing::new(C::random_nonzero_F(rng));
|
let key = Zeroizing::new(C::random_nonzero_F(rng));
|
||||||
cipher::<C>(dst, &ecdh::<C>(&key, to)).apply_keystream(msg.as_mut().as_mut());
|
cipher::<C>(context, &ecdh::<C>(&key, to)).apply_keystream(msg.as_mut().as_mut());
|
||||||
|
|
||||||
let pub_key = C::generator() * key.deref();
|
let pub_key = C::generator() * key.deref();
|
||||||
let nonce = Zeroizing::new(C::random_nonzero_F(rng));
|
let nonce = Zeroizing::new(C::random_nonzero_F(rng));
|
||||||
|
@ -159,7 +161,7 @@ fn encrypt<R: RngCore + CryptoRng, C: Ciphersuite, E: Encryptable>(
|
||||||
pop: SchnorrSignature::sign(
|
pop: SchnorrSignature::sign(
|
||||||
&key,
|
&key,
|
||||||
nonce,
|
nonce,
|
||||||
pop_challenge::<C>(pub_nonce, pub_key, from, msg.deref().as_ref()),
|
pop_challenge::<C>(context, pub_nonce, pub_key, from, msg.deref().as_ref()),
|
||||||
),
|
),
|
||||||
msg,
|
msg,
|
||||||
}
|
}
|
||||||
|
@ -192,7 +194,12 @@ impl<C: Ciphersuite, E: Encryptable> EncryptedMessage<C, E> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) fn invalidate_msg<R: RngCore + CryptoRng>(&mut self, rng: &mut R, from: Participant) {
|
pub(crate) fn invalidate_msg<R: RngCore + CryptoRng>(
|
||||||
|
&mut self,
|
||||||
|
rng: &mut R,
|
||||||
|
context: &str,
|
||||||
|
from: Participant,
|
||||||
|
) {
|
||||||
// Invalidate the message by specifying a new key/Schnorr PoP
|
// Invalidate the message by specifying a new key/Schnorr PoP
|
||||||
// This will cause all initial checks to pass, yet a decrypt to gibberish
|
// This will cause all initial checks to pass, yet a decrypt to gibberish
|
||||||
let key = Zeroizing::new(C::random_nonzero_F(rng));
|
let key = Zeroizing::new(C::random_nonzero_F(rng));
|
||||||
|
@ -203,7 +210,7 @@ impl<C: Ciphersuite, E: Encryptable> EncryptedMessage<C, E> {
|
||||||
self.pop = SchnorrSignature::sign(
|
self.pop = SchnorrSignature::sign(
|
||||||
&key,
|
&key,
|
||||||
nonce,
|
nonce,
|
||||||
pop_challenge::<C>(pub_nonce, pub_key, from, self.msg.deref().as_ref()),
|
pop_challenge::<C>(context, pub_nonce, pub_key, from, self.msg.deref().as_ref()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,7 +219,7 @@ impl<C: Ciphersuite, E: Encryptable> EncryptedMessage<C, E> {
|
||||||
pub(crate) fn invalidate_share_serialization<R: RngCore + CryptoRng>(
|
pub(crate) fn invalidate_share_serialization<R: RngCore + CryptoRng>(
|
||||||
&mut self,
|
&mut self,
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
dst: &'static [u8],
|
context: &str,
|
||||||
from: Participant,
|
from: Participant,
|
||||||
to: C::G,
|
to: C::G,
|
||||||
) {
|
) {
|
||||||
|
@ -228,7 +235,7 @@ impl<C: Ciphersuite, E: Encryptable> EncryptedMessage<C, E> {
|
||||||
assert!(!bool::from(C::F::from_repr(repr).is_some()));
|
assert!(!bool::from(C::F::from_repr(repr).is_some()));
|
||||||
|
|
||||||
self.msg.as_mut().as_mut().copy_from_slice(repr.as_ref());
|
self.msg.as_mut().as_mut().copy_from_slice(repr.as_ref());
|
||||||
*self = encrypt(rng, dst, from, to, self.msg.clone());
|
*self = encrypt(rng, context, from, to, self.msg.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assumes the encrypted message is a secret share.
|
// Assumes the encrypted message is a secret share.
|
||||||
|
@ -236,7 +243,7 @@ impl<C: Ciphersuite, E: Encryptable> EncryptedMessage<C, E> {
|
||||||
pub(crate) fn invalidate_share_value<R: RngCore + CryptoRng>(
|
pub(crate) fn invalidate_share_value<R: RngCore + CryptoRng>(
|
||||||
&mut self,
|
&mut self,
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
dst: &'static [u8],
|
context: &str,
|
||||||
from: Participant,
|
from: Participant,
|
||||||
to: C::G,
|
to: C::G,
|
||||||
) {
|
) {
|
||||||
|
@ -245,7 +252,7 @@ impl<C: Ciphersuite, E: Encryptable> EncryptedMessage<C, E> {
|
||||||
// Assumes the share isn't randomly 1
|
// Assumes the share isn't randomly 1
|
||||||
let repr = C::F::one().to_repr();
|
let repr = C::F::one().to_repr();
|
||||||
self.msg.as_mut().as_mut().copy_from_slice(repr.as_ref());
|
self.msg.as_mut().as_mut().copy_from_slice(repr.as_ref());
|
||||||
*self = encrypt(rng, dst, from, to, self.msg.clone());
|
*self = encrypt(rng, context, from, to, self.msg.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,8 +299,18 @@ impl<C: Ciphersuite> EncryptionKeyProof<C> {
|
||||||
// This doesn't need to take the msg. It just doesn't hurt as an extra layer.
|
// This doesn't need to take the msg. It just doesn't hurt as an extra layer.
|
||||||
// This still doesn't mean the DKG offers an authenticated channel. The per-message keys have no
|
// This still doesn't mean the DKG offers an authenticated channel. The per-message keys have no
|
||||||
// root of trust other than their existence in the assumed-to-exist external authenticated channel.
|
// root of trust other than their existence in the assumed-to-exist external authenticated channel.
|
||||||
fn pop_challenge<C: Ciphersuite>(nonce: C::G, key: C::G, sender: Participant, msg: &[u8]) -> C::F {
|
fn pop_challenge<C: Ciphersuite>(
|
||||||
|
context: &str,
|
||||||
|
nonce: C::G,
|
||||||
|
key: C::G,
|
||||||
|
sender: Participant,
|
||||||
|
msg: &[u8],
|
||||||
|
) -> C::F {
|
||||||
let mut transcript = RecommendedTranscript::new(b"DKG Encryption Key Proof of Possession v0.2");
|
let mut transcript = RecommendedTranscript::new(b"DKG Encryption Key Proof of Possession v0.2");
|
||||||
|
transcript.append_message(b"context", context.as_bytes());
|
||||||
|
|
||||||
|
transcript.domain_separate(b"proof_of_possession");
|
||||||
|
|
||||||
transcript.append_message(b"nonce", nonce.to_bytes());
|
transcript.append_message(b"nonce", nonce.to_bytes());
|
||||||
transcript.append_message(b"key", key.to_bytes());
|
transcript.append_message(b"key", key.to_bytes());
|
||||||
// This is sufficient to prevent the attack this is meant to stop
|
// This is sufficient to prevent the attack this is meant to stop
|
||||||
|
@ -306,8 +323,10 @@ fn pop_challenge<C: Ciphersuite>(nonce: C::G, key: C::G, sender: Participant, ms
|
||||||
C::hash_to_F(b"DKG-encryption-proof_of_possession", &transcript.challenge(b"schnorr"))
|
C::hash_to_F(b"DKG-encryption-proof_of_possession", &transcript.challenge(b"schnorr"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encryption_key_transcript() -> RecommendedTranscript {
|
fn encryption_key_transcript(context: &str) -> RecommendedTranscript {
|
||||||
RecommendedTranscript::new(b"DKG Encryption Key Correctness Proof v0.2")
|
let mut transcript = RecommendedTranscript::new(b"DKG Encryption Key Correctness Proof v0.2");
|
||||||
|
transcript.append_message(b"context", context.as_bytes());
|
||||||
|
transcript
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Error)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Error)]
|
||||||
|
@ -321,7 +340,7 @@ pub(crate) enum DecryptionError {
|
||||||
// A simple box for managing encryption.
|
// A simple box for managing encryption.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct Encryption<C: Ciphersuite> {
|
pub(crate) struct Encryption<C: Ciphersuite> {
|
||||||
dst: &'static [u8],
|
context: String,
|
||||||
i: Participant,
|
i: Participant,
|
||||||
enc_key: Zeroizing<C::F>,
|
enc_key: Zeroizing<C::F>,
|
||||||
enc_pub_key: C::G,
|
enc_pub_key: C::G,
|
||||||
|
@ -339,14 +358,10 @@ impl<C: Ciphersuite> Zeroize for Encryption<C> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: Ciphersuite> Encryption<C> {
|
impl<C: Ciphersuite> Encryption<C> {
|
||||||
pub(crate) fn new<R: RngCore + CryptoRng>(
|
pub(crate) fn new<R: RngCore + CryptoRng>(context: String, i: Participant, rng: &mut R) -> Self {
|
||||||
dst: &'static [u8],
|
|
||||||
i: Participant,
|
|
||||||
rng: &mut R,
|
|
||||||
) -> Self {
|
|
||||||
let enc_key = Zeroizing::new(C::random_nonzero_F(rng));
|
let enc_key = Zeroizing::new(C::random_nonzero_F(rng));
|
||||||
Self {
|
Self {
|
||||||
dst,
|
context,
|
||||||
i,
|
i,
|
||||||
enc_pub_key: C::generator() * enc_key.deref(),
|
enc_pub_key: C::generator() * enc_key.deref(),
|
||||||
enc_key,
|
enc_key,
|
||||||
|
@ -376,7 +391,7 @@ impl<C: Ciphersuite> Encryption<C> {
|
||||||
participant: Participant,
|
participant: Participant,
|
||||||
msg: Zeroizing<E>,
|
msg: Zeroizing<E>,
|
||||||
) -> EncryptedMessage<C, E> {
|
) -> EncryptedMessage<C, E> {
|
||||||
encrypt(rng, self.dst, self.i, self.enc_keys[&participant], msg)
|
encrypt(rng, &self.context, self.i, self.enc_keys[&participant], msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn decrypt<R: RngCore + CryptoRng, I: Copy + Zeroize, E: Encryptable>(
|
pub(crate) fn decrypt<R: RngCore + CryptoRng, I: Copy + Zeroize, E: Encryptable>(
|
||||||
|
@ -394,18 +409,18 @@ impl<C: Ciphersuite> Encryption<C> {
|
||||||
batch,
|
batch,
|
||||||
batch_id,
|
batch_id,
|
||||||
msg.key,
|
msg.key,
|
||||||
pop_challenge::<C>(msg.pop.R, msg.key, from, msg.msg.deref().as_ref()),
|
pop_challenge::<C>(&self.context, msg.pop.R, msg.key, from, msg.msg.deref().as_ref()),
|
||||||
);
|
);
|
||||||
|
|
||||||
let key = ecdh::<C>(&self.enc_key, msg.key);
|
let key = ecdh::<C>(&self.enc_key, msg.key);
|
||||||
cipher::<C>(self.dst, &key).apply_keystream(msg.msg.as_mut().as_mut());
|
cipher::<C>(&self.context, &key).apply_keystream(msg.msg.as_mut().as_mut());
|
||||||
(
|
(
|
||||||
msg.msg,
|
msg.msg,
|
||||||
EncryptionKeyProof {
|
EncryptionKeyProof {
|
||||||
key,
|
key,
|
||||||
dleq: DLEqProof::prove(
|
dleq: DLEqProof::prove(
|
||||||
rng,
|
rng,
|
||||||
&mut encryption_key_transcript(),
|
&mut encryption_key_transcript(&self.context),
|
||||||
&[C::generator(), msg.key],
|
&[C::generator(), msg.key],
|
||||||
&self.enc_key,
|
&self.enc_key,
|
||||||
),
|
),
|
||||||
|
@ -423,10 +438,10 @@ impl<C: Ciphersuite> Encryption<C> {
|
||||||
// There's no encryption key proof if the accusation is of an invalid signature
|
// There's no encryption key proof if the accusation is of an invalid signature
|
||||||
proof: Option<EncryptionKeyProof<C>>,
|
proof: Option<EncryptionKeyProof<C>>,
|
||||||
) -> Result<Zeroizing<E>, DecryptionError> {
|
) -> Result<Zeroizing<E>, DecryptionError> {
|
||||||
if !msg
|
if !msg.pop.verify(
|
||||||
.pop
|
msg.key,
|
||||||
.verify(msg.key, pop_challenge::<C>(msg.pop.R, msg.key, from, msg.msg.deref().as_ref()))
|
pop_challenge::<C>(&self.context, msg.pop.R, msg.key, from, msg.msg.deref().as_ref()),
|
||||||
{
|
) {
|
||||||
Err(DecryptionError::InvalidSignature)?;
|
Err(DecryptionError::InvalidSignature)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -435,13 +450,13 @@ impl<C: Ciphersuite> Encryption<C> {
|
||||||
proof
|
proof
|
||||||
.dleq
|
.dleq
|
||||||
.verify(
|
.verify(
|
||||||
&mut encryption_key_transcript(),
|
&mut encryption_key_transcript(&self.context),
|
||||||
&[C::generator(), msg.key],
|
&[C::generator(), msg.key],
|
||||||
&[self.enc_keys[&decryptor], *proof.key],
|
&[self.enc_keys[&decryptor], *proof.key],
|
||||||
)
|
)
|
||||||
.map_err(|_| DecryptionError::InvalidProof)?;
|
.map_err(|_| DecryptionError::InvalidProof)?;
|
||||||
|
|
||||||
cipher::<C>(self.dst, &proof.key).apply_keystream(msg.msg.as_mut().as_mut());
|
cipher::<C>(&self.context, &proof.key).apply_keystream(msg.msg.as_mut().as_mut());
|
||||||
Ok(msg.msg)
|
Ok(msg.msg)
|
||||||
} else {
|
} else {
|
||||||
Err(DecryptionError::InvalidProof)
|
Err(DecryptionError::InvalidProof)
|
||||||
|
|
|
@ -132,7 +132,7 @@ impl<C: Ciphersuite> KeyGenMachine<C> {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Additionally create an encryption mechanism to protect the secret shares
|
// Additionally create an encryption mechanism to protect the secret shares
|
||||||
let encryption = Encryption::new(b"FROST", self.params.i, rng);
|
let encryption = Encryption::new(self.context.clone(), self.params.i, rng);
|
||||||
|
|
||||||
// Step 4: Broadcast
|
// Step 4: Broadcast
|
||||||
let msg =
|
let msg =
|
||||||
|
|
|
@ -13,6 +13,8 @@ use crate::{
|
||||||
type FrostEncryptedMessage<C> = EncryptedMessage<C, SecretShare<<C as Ciphersuite>::F>>;
|
type FrostEncryptedMessage<C> = EncryptedMessage<C, SecretShare<<C as Ciphersuite>::F>>;
|
||||||
type FrostSecretShares<C> = HashMap<Participant, FrostEncryptedMessage<C>>;
|
type FrostSecretShares<C> = HashMap<Participant, FrostEncryptedMessage<C>>;
|
||||||
|
|
||||||
|
const CONTEXT: &str = "DKG Test Key Generation";
|
||||||
|
|
||||||
// Commit, then return enc key and shares
|
// Commit, then return enc key and shares
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
fn commit_enc_keys_and_shares<R: RngCore + CryptoRng, C: Ciphersuite>(
|
fn commit_enc_keys_and_shares<R: RngCore + CryptoRng, C: Ciphersuite>(
|
||||||
|
@ -27,7 +29,7 @@ fn commit_enc_keys_and_shares<R: RngCore + CryptoRng, C: Ciphersuite>(
|
||||||
let mut enc_keys = HashMap::new();
|
let mut enc_keys = HashMap::new();
|
||||||
for i in (1 ..= PARTICIPANTS).map(Participant) {
|
for i in (1 ..= PARTICIPANTS).map(Participant) {
|
||||||
let params = ThresholdParams::new(THRESHOLD, PARTICIPANTS, i).unwrap();
|
let params = ThresholdParams::new(THRESHOLD, PARTICIPANTS, i).unwrap();
|
||||||
let machine = KeyGenMachine::<C>::new(params, "DKG Test Key Generation".to_string());
|
let machine = KeyGenMachine::<C>::new(params, CONTEXT.to_string());
|
||||||
let (machine, these_commitments) = machine.generate_coefficients(rng);
|
let (machine, these_commitments) = machine.generate_coefficients(rng);
|
||||||
machines.insert(i, machine);
|
machines.insert(i, machine);
|
||||||
|
|
||||||
|
@ -179,7 +181,12 @@ mod literal {
|
||||||
// We then malleate 1's blame proof, so 1 ends up malicious
|
// We then malleate 1's blame proof, so 1 ends up malicious
|
||||||
// Doesn't simply invalidate the PoP as that won't have a blame statement
|
// Doesn't simply invalidate the PoP as that won't have a blame statement
|
||||||
// By mutating the encrypted data, we do ensure a blame statement is created
|
// By mutating the encrypted data, we do ensure a blame statement is created
|
||||||
secret_shares.get_mut(&TWO).unwrap().get_mut(&ONE).unwrap().invalidate_msg(&mut OsRng, TWO);
|
secret_shares
|
||||||
|
.get_mut(&TWO)
|
||||||
|
.unwrap()
|
||||||
|
.get_mut(&ONE)
|
||||||
|
.unwrap()
|
||||||
|
.invalidate_msg(&mut OsRng, CONTEXT, TWO);
|
||||||
|
|
||||||
let mut blame = None;
|
let mut blame = None;
|
||||||
let machines = machines
|
let machines = machines
|
||||||
|
@ -209,7 +216,12 @@ mod literal {
|
||||||
let (mut machines, _, mut secret_shares) =
|
let (mut machines, _, mut secret_shares) =
|
||||||
commit_enc_keys_and_shares::<_, Ristretto>(&mut OsRng);
|
commit_enc_keys_and_shares::<_, Ristretto>(&mut OsRng);
|
||||||
|
|
||||||
secret_shares.get_mut(&TWO).unwrap().get_mut(&ONE).unwrap().invalidate_msg(&mut OsRng, TWO);
|
secret_shares
|
||||||
|
.get_mut(&TWO)
|
||||||
|
.unwrap()
|
||||||
|
.get_mut(&ONE)
|
||||||
|
.unwrap()
|
||||||
|
.invalidate_msg(&mut OsRng, CONTEXT, TWO);
|
||||||
|
|
||||||
let mut blame = None;
|
let mut blame = None;
|
||||||
let machines = machines
|
let machines = machines
|
||||||
|
@ -240,7 +252,7 @@ mod literal {
|
||||||
|
|
||||||
secret_shares.get_mut(&ONE).unwrap().get_mut(&TWO).unwrap().invalidate_share_serialization(
|
secret_shares.get_mut(&ONE).unwrap().get_mut(&TWO).unwrap().invalidate_share_serialization(
|
||||||
&mut OsRng,
|
&mut OsRng,
|
||||||
b"FROST",
|
CONTEXT,
|
||||||
ONE,
|
ONE,
|
||||||
enc_keys[&TWO],
|
enc_keys[&TWO],
|
||||||
);
|
);
|
||||||
|
@ -273,7 +285,7 @@ mod literal {
|
||||||
|
|
||||||
secret_shares.get_mut(&ONE).unwrap().get_mut(&TWO).unwrap().invalidate_share_value(
|
secret_shares.get_mut(&ONE).unwrap().get_mut(&TWO).unwrap().invalidate_share_value(
|
||||||
&mut OsRng,
|
&mut OsRng,
|
||||||
b"FROST",
|
CONTEXT,
|
||||||
ONE,
|
ONE,
|
||||||
enc_keys[&TWO],
|
enc_keys[&TWO],
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue