Skip to content

Commit 7c0c883

Browse files
authored
Merge pull request #2017 from lucasbalieiro/add-time-tolerance-to-noise-validation
noise-sv2: add 10s clock skew tolerance to signature verification
2 parents 4c23002 + 33f14db commit 7c0c883

File tree

5 files changed

+43
-6
lines changed

5 files changed

+43
-6
lines changed

sv2/codec-sv2/src/error.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,14 @@ impl fmt::Display for Error {
7878
),
7979
MissingBytes(u) => write!(f, "Missing `{u}` Noise bytes"),
8080
#[cfg(feature = "noise_sv2")]
81-
NoiseSv2Error(e) => write!(f, "Noise SV2 Error: `{e:?}`"),
81+
NoiseSv2Error(e) => match e {
82+
NoiseError::InvalidCertificate(msg) => {
83+
write!(f, "Invalid Certificate: {}", msg)
84+
}
85+
other => {
86+
write!(f, "Noise SV2 Error: {:?}", other)
87+
}
88+
},
8289
#[cfg(feature = "noise_sv2")]
8390
NotInHandShakeState => write!(
8491
f,

sv2/noise-sv2/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "noise_sv2"
3-
version = "1.4.0"
3+
version = "1.4.1"
44
authors = ["The Stratum V2 Developers"]
55
edition = "2021"
66
readme = "README.md"

sv2/noise-sv2/src/error.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ use alloc::vec::Vec;
66

77
use aes_gcm::Error as AesGcm;
88

9+
use crate::signature_message::SignatureNoiseMessage;
10+
911
/// Noise protocol error handling.
1012
#[derive(Debug, PartialEq, Eq)]
1113
pub enum Error {
@@ -31,7 +33,7 @@ pub enum Error {
3133
InvalidCipherState,
3234

3335
/// Provided certificate is invalid or cannot be verified.
34-
InvalidCertificate([u8; 74]),
36+
InvalidCertificate(SignatureNoiseMessage),
3537

3638
/// A raw public key is invalid or cannot be parsed.
3739
InvalidRawPublicKey,

sv2/noise-sv2/src/initiator.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ impl Initiator {
396396
};
397397
Ok(codec)
398398
} else {
399-
Err(Error::InvalidCertificate(plaintext))
399+
Err(Error::InvalidCertificate(plaintext.into()))
400400
}
401401
}
402402

sv2/noise-sv2/src/signature_message.rs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
// validity period.
2323

2424
use core::convert::TryInto;
25+
use core::fmt;
2526

2627
use secp256k1::{hashes::sha256, schnorr::Signature, Keypair, Message, Secp256k1, XOnlyPublicKey};
2728

@@ -32,6 +33,7 @@ use secp256k1::{hashes::sha256, schnorr::Signature, Keypair, Message, Secp256k1,
3233
///
3334
/// This structure ensures that messages are authenticated and valid only within
3435
/// a specified time window, using Schnorr signatures over the `secp256k1` elliptic curve.
36+
#[derive(Debug, PartialEq, Eq)]
3537
pub struct SignatureNoiseMessage {
3638
// Version of the protocol being used.
3739
pub version: u16,
@@ -43,6 +45,23 @@ pub struct SignatureNoiseMessage {
4345
pub signature: [u8; 64],
4446
}
4547

48+
impl fmt::Display for SignatureNoiseMessage {
49+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50+
// write header
51+
write!(
52+
f,
53+
"SignatureNoiseMessage {{ version: {}, valid_from: {}, not_valid_after: {}, signature: ",
54+
self.version, self.valid_from, self.not_valid_after
55+
)?;
56+
57+
// write signature as hex manually
58+
for byte in &self.signature {
59+
write!(f, "{:02x}", byte)?;
60+
}
61+
62+
write!(f, " }}")
63+
}
64+
}
4665
impl From<[u8; 74]> for SignatureNoiseMessage {
4766
// Converts a 74-byte array into a [`SignatureNoiseMessage`].
4867
//
@@ -81,7 +100,8 @@ impl SignatureNoiseMessage {
81100
self.verify_with_now(pk, authority_pk, now)
82101
}
83102

84-
/// Verifies the validity and authenticity of the `SignatureNoiseMessage` at a given timestamp.
103+
/// Verifies the validity and authenticity of the `SignatureNoiseMessage` at a given timestamp
104+
/// with 10 seconds of tolerance.
85105
///
86106
/// See [`Self::verify`] for more details.
87107
///
@@ -94,8 +114,16 @@ impl SignatureNoiseMessage {
94114
authority_pk: &Option<XOnlyPublicKey>,
95115
now: u32,
96116
) -> bool {
117+
// Allow the local clock to drift up to 10 seconds ahead or behind.
118+
// See https://github.com/stratum-mining/stratum/issues/2015
119+
const TIME_LEEWAY: u32 = 10;
120+
97121
if let Some(authority_pk) = authority_pk {
98-
if self.valid_from <= now && self.not_valid_after >= now {
122+
// Use saturating ops to cap edges (valid_from ≥ 0, not_valid_after ≤ u32::MAX),
123+
// preventing wrap-around and subtle validation bugs with untrusted timestamps.
124+
if self.valid_from.saturating_sub(TIME_LEEWAY) <= now
125+
&& self.not_valid_after.saturating_add(TIME_LEEWAY) >= now
126+
{
99127
let secp = Secp256k1::verification_only();
100128
let (m, s) = self.split();
101129
// m = SHA-256(version || valid_from || not_valid_after || server_static_key)

0 commit comments

Comments
 (0)