refactor(types): allow SignExt::sign_msg_with using a fixed nonce

This simplifies tests and avoid the use of deprecated `StepRng`.
This commit is contained in:
oxalica 2025-09-11 18:24:07 -04:00
parent 583f916cbe
commit 59a8851b32
4 changed files with 27 additions and 26 deletions

View file

@ -10,6 +10,7 @@ use rand::{Rng, SeedableRng, rngs::SmallRng};
use sha2::{Digest, Sha256};
const SEED: u64 = 0xDEAD_BEEF_BEEF_DEAD;
const FIXED_NONCE: u32 = 0x42;
const MOCK_PRIV_KEY1: [u8; 32] = *b"this is the testing private key1";
const MOCK_PRIV_KEY2: [u8; 32] = *b"that is the 2nd testing privkey.";
@ -77,18 +78,14 @@ fn bench_msg_sign_verify(c: &mut Criterion) {
let msg = avg_msg();
c.bench_function("msg-sign", |b| {
// FIXME: Provide a deterministic signing method using a given nonce?
let fixed_nonce_rng = &mut SmallRng::seed_from_u64(SEED);
b.iter(|| {
black_box(msg.clone())
.sign_msg_with(&id_key, &act_key_priv, timestamp, fixed_nonce_rng)
.sign_msg_with(&id_key, &act_key_priv, timestamp, FIXED_NONCE)
.unwrap()
})
});
let rng = &mut SmallRng::seed_from_u64(SEED);
let signed = msg
.sign_msg_with(&id_key, &act_key_priv, timestamp, rng)
.sign_msg_with(&id_key, &act_key_priv, timestamp, FIXED_NONCE)
.unwrap();
c.bench_function("msg-verify", |b| {

View file

@ -87,20 +87,23 @@ pub struct Signee<T> {
}
pub trait SignExt: Sized {
/// A convenient shortcut method of [`Signed::new`] for method chaining.
fn sign_msg_with(
self,
id_key: &PubKey,
act_key: &SigningKey,
timestamp: u64,
rng: &mut (impl RngCore + ?Sized),
nonce: u32,
) -> Result<Signed<Self>, SignatureError>;
/// A convenient shortcut method of [`SignExt::sign_msg`] using the current
/// timestamp and a random nonce from [`rand::rng`].
fn sign_msg(
self,
id_key: &PubKey,
act_key: &SigningKey,
) -> Result<Signed<Self>, SignatureError> {
self.sign_msg_with(id_key, act_key, get_timestamp(), &mut rand::rng())
self.sign_msg_with(id_key, act_key, get_timestamp(), rand::rng().next_u32())
}
}
@ -110,9 +113,9 @@ impl<T: Serialize> SignExt for T {
id_key: &PubKey,
act_key: &SigningKey,
timestamp: u64,
rng: &mut (impl RngCore + ?Sized),
nonce: u32,
) -> Result<Signed<Self>, SignatureError> {
Signed::new(id_key, act_key, timestamp, rng, self)
Signed::new(id_key, act_key, timestamp, nonce, self)
}
}
@ -138,15 +141,19 @@ impl<T: Serialize> Signed<T> {
/// Sign the payload with the given `key`.
///
/// This operation only fail when serialization of `payload` fails.
///
/// This function is pure and portable, if the serialization of `payload` is
/// pure and portable. That is, it always returns the bit-identical bytes if
/// it returns `Ok` as long as the arguments are bit-identical.
pub fn new(
id_key: &PubKey,
act_key: &SigningKey,
timestamp: u64,
rng: &mut (impl RngCore + ?Sized),
nonce: u32,
payload: T,
) -> Result<Self, SignatureError> {
let signee = Signee {
nonce: rng.next_u32(),
nonce,
payload,
timestamp,
user: UserKey {

View file

@ -274,9 +274,10 @@ mod tests {
#[test]
fn id_desc_verify() {
// Insecure but deterministic mock values.
const TIMESTAMP: u64 = 42;
const NONCE: u32 = 42;
let rng = &mut rand::rngs::mock::StepRng::new(42, 1);
let id_url = "https://example.com".parse::<IdUrl>().unwrap();
let id_priv = SigningKey::from_bytes(&[42; 32]);
let id_key = PubKey::from(id_priv.verifying_key());
@ -287,7 +288,7 @@ mod tests {
preferred_chat_server_urls: Vec::new(),
id_urls: vec![id_url],
}
.sign_msg_with(&id_key, &id_priv, TIMESTAMP, rng)
.sign_msg_with(&id_key, &id_priv, TIMESTAMP, NONCE)
.unwrap(),
};
@ -319,7 +320,7 @@ mod tests {
expire_time: TIMESTAMP + 1,
comment: String::new(),
}
.sign_msg_with(&id_key, &id_priv, TIMESTAMP, rng)
.sign_msg_with(&id_key, &id_priv, TIMESTAMP, NONCE)
.unwrap(),
);
id_desc.verify(None, TIMESTAMP).unwrap();
@ -339,7 +340,7 @@ mod tests {
comment: String::new(),
}
// Self-signed.
.sign_msg_with(&id_key, &act_priv, TIMESTAMP, rng)
.sign_msg_with(&id_key, &act_priv, TIMESTAMP, NONCE)
.unwrap(),
);
assert_err!(
@ -353,7 +354,7 @@ mod tests {
comment: String::new(),
}
// Wrong id_key.
.sign_msg_with(&act_pub, &act_priv, TIMESTAMP, rng)
.sign_msg_with(&act_pub, &act_priv, TIMESTAMP, NONCE)
.unwrap();
assert_err!(
id_desc.verify(None, TIMESTAMP),
@ -366,7 +367,7 @@ mod tests {
expire_time: u64::MAX,
comment: String::new(),
}
.sign_msg_with(&id_key, &id_priv, TIMESTAMP, rng)
.sign_msg_with(&id_key, &id_priv, TIMESTAMP, NONCE)
.unwrap();
assert_err!(
id_desc.verify(None, TIMESTAMP),
@ -379,7 +380,7 @@ mod tests {
expire_time: TIMESTAMP + 1,
comment: String::new(),
}
.sign_msg_with(&id_key, &id_priv, TIMESTAMP, rng)
.sign_msg_with(&id_key, &id_priv, TIMESTAMP, NONCE)
.unwrap();
id_desc.verify(None, TIMESTAMP).unwrap();

View file

@ -500,7 +500,8 @@ mod tests {
#[test]
fn canonical_msg() {
let mut fake_rng = rand::rngs::mock::StepRng::new(0x42, 1);
const NONCE: u32 = 0x42;
let id_key = SigningKey::from_bytes(&[0x42; 32]);
let act_key = SigningKey::from_bytes(&[0x43; 32]);
let timestamp = 0xDEAD_BEEF;
@ -508,12 +509,7 @@ mod tests {
rich_text: RichText::from("hello"),
room: Id(42),
}
.sign_msg_with(
&id_key.verifying_key().into(),
&act_key,
timestamp,
&mut fake_rng,
)
.sign_msg_with(&id_key.verifying_key().into(), &act_key, timestamp, NONCE)
.unwrap();
let json = serde_jcs::to_string(&msg).unwrap();