From 0aa440745e951068480e881d8f5bf6ecb20bdc10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20D=C3=ADaz?= Date: Tue, 23 Jun 2026 11:39:57 +0200 Subject: [PATCH 1/5] Add additional information to doc strings --- ooniauth-py/ooniauth_py.pyi | 38 ++++++++++++++++++++++++++++++++++--- ooniauth-py/src/protocol.rs | 28 ++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/ooniauth-py/ooniauth_py.pyi b/ooniauth-py/ooniauth_py.pyi index 77b0de8..666bb07 100644 --- a/ooniauth-py/ooniauth_py.pyi +++ b/ooniauth-py/ooniauth_py.pyi @@ -51,7 +51,17 @@ class ServerState: measurement_hash: str, age_range: tuple[builtins.int, builtins.int], min_measurement_count: builtins.int, - ) -> str: ... + ) -> str: + r""" + Handle a submit request from the client. + + Note that the probe_cc and probe_asn should have the following format: + - probe_cc = two letters, uppercase, alpha-numeric + - probe_asn = AS-prefixed, 3 <= len(probe_asn) <= 12, int value after AS + + This validation is left to the backend server implementing the library + """ + def handle_submit_request_with_hash( self, nym: str, @@ -65,7 +75,13 @@ class ServerState: r""" Performs a submission request computing the hash from the input measurement. Computes the hash internally using the - [submit_measurement_hash] function + [submit_measurement_hash] function. + + Note that the probe_cc and probe_asn should have the following format: + - probe_cc = two letters, uppercase, alpha-numeric + - probe_asn = AS-prefixed, 3 <= len(probe_asn) <= 12, int value after AS + + This validation is left to the backend server implementing the library """ def handle_update_request( @@ -98,7 +114,17 @@ class UserState: measurement_hash: str, age_range: tuple[builtins.int, builtins.int], min_measurement_count: builtins.int, - ) -> SubmitRequest: ... + ) -> SubmitRequest: + r""" + Make a submission request, to send a measurement to the server + + Note that the probe_cc and probe_asn should have the following format: + - probe_cc = two letters, uppercase, alpha-numeric + - probe_asn = AS-prefixed, 3 <= len(probe_asn) <= 12, int value after AS + + This validation is left to the backend server implementing the library + """ + def make_submit_request_with_hash( self, probe_cc: str, @@ -110,6 +136,12 @@ class UserState: r""" Creates a submit request computing the hash from the input measurement. Computes the hash internally using the [submit_measurement_hash] function + + Note that the probe_cc and probe_asn should have the following format: + - probe_cc = two letters, uppercase, alpha-numeric + - probe_asn = AS-prefixed, 3 <= len(probe_asn) <= 12, int value after AS + + This validation is left to the backend server implementing the library """ def handle_submit_response(self, response: str) -> None: diff --git a/ooniauth-py/src/protocol.rs b/ooniauth-py/src/protocol.rs index 995d7b1..f353576 100644 --- a/ooniauth-py/src/protocol.rs +++ b/ooniauth-py/src/protocol.rs @@ -115,6 +115,13 @@ impl ServerState { ooni::ServerState::today() } + /// Handle a submit request from the client. + /// + /// Note that the probe_cc and probe_asn should have the following format: + /// - probe_cc = two letters, uppercase, alpha-numeric + /// - probe_asn = AS-prefixed, 3 <= len(probe_asn) <= 12, int value after AS + /// + /// This validation is left to the backend server implementing the library #[allow(clippy::too_many_arguments)] fn handle_submit_request( &self, @@ -144,7 +151,13 @@ impl ServerState { /// Performs a submission request computing the hash from the input /// measurement. Computes the hash internally using the - /// [submit_measurement_hash] function + /// [submit_measurement_hash] function. + /// + /// Note that the probe_cc and probe_asn should have the following format: + /// - probe_cc = two letters, uppercase, alpha-numeric + /// - probe_asn = AS-prefixed, 3 <= len(probe_asn) <= 12, int value after AS + /// + /// This validation is left to the backend server implementing the library #[allow(clippy::too_many_arguments)] fn handle_submit_request_with_hash( &self, @@ -301,6 +314,13 @@ impl UserState { Ok(()) } + /// Make a submission request, to send a measurement to the server + /// + /// Note that the probe_cc and probe_asn should have the following format: + /// - probe_cc = two letters, uppercase, alpha-numeric + /// - probe_asn = AS-prefixed, 3 <= len(probe_asn) <= 12, int value after AS + /// + /// This validation is left to the backend server implementing the library pub fn make_submit_request( &mut self, py: Python<'_>, @@ -324,6 +344,12 @@ impl UserState { /// Creates a submit request computing the hash from the input measurement. /// Computes the hash internally using the [submit_measurement_hash] function + /// + /// Note that the probe_cc and probe_asn should have the following format: + /// - probe_cc = two letters, uppercase, alpha-numeric + /// - probe_asn = AS-prefixed, 3 <= len(probe_asn) <= 12, int value after AS + /// + /// This validation is left to the backend server implementing the library pub fn make_submit_request_with_hash( &mut self, py: Python<'_>, From dacff38b023a4d0e1d06264bb5e8566fa298dbeb Mon Sep 17 00:00:00 2001 From: Michele Orru Date: Thu, 25 Jun 2026 18:18:44 -0700 Subject: [PATCH 2/5] fix: add debug_assert checks for input validation, and remove redundant documentation --- ooniauth-core/src/submit.rs | 32 ++++++++++++++++++++++---------- ooniauth-py/ooniauth_py.pyi | 16 ++-------------- ooniauth-py/src/protocol.rs | 35 +++++++++++++---------------------- 3 files changed, 37 insertions(+), 46 deletions(-) diff --git a/ooniauth-core/src/submit.rs b/ooniauth-core/src/submit.rs index ef49e7f..471f18c 100644 --- a/ooniauth-core/src/submit.rs +++ b/ooniauth-core/src/submit.rs @@ -77,6 +77,23 @@ pub fn digest_point(point: RistrettoPoint) -> [u8; 32] { out } +fn submit_domain_generator(probe_cc: &str, probe_asn: &str) -> G { + debug_assert!( + probe_cc.len() == 2 && probe_cc.bytes().all(|b| b.is_ascii_uppercase()), + "probe_cc must be a two-letter uppercase ASCII country code" + ); + debug_assert!( + (3..=12).contains(&probe_asn.len()) + && probe_asn.strip_prefix("AS").is_some_and(|asn| { + asn.bytes().all(|b| b.is_ascii_digit()) && asn.parse::().is_ok() + }), + "probe_asn must be AS-prefixed ASCII digits with total length 3..=12" + ); + + let domain_str = format!("ooni.org/{}/{}", probe_cc, probe_asn); + G::hash_from_bytes::(domain_str.as_bytes()) +} + fn inclusive_upper_bound(range: &std::ops::Range) -> u32 { range.end.saturating_sub(1) } @@ -113,9 +130,8 @@ impl UserState { ))?; // Domain-specific generator and NYM computation - let domain_str = format!("ooni.org/{}/{}", probe_cc, probe_asn); trace!("Computing DOMAIN for submit request"); - let DOMAIN = G::hash_from_bytes::(domain_str.as_bytes()); + let DOMAIN = submit_domain_generator(&probe_cc, &probe_asn); let NYM = Old.nym_id.unwrap() * DOMAIN; debug!("NYM computed successfully"); @@ -252,8 +268,7 @@ impl ServerState { nym_point, } = req; - let domain_str = format!("ooni.org/{}/{}", probe_cc, probe_asn); - let DOMAIN = G::hash_from_bytes::(domain_str.as_bytes()); + let DOMAIN = submit_domain_generator(probe_cc, probe_asn); // The probe id must be the salted hash of the nym point. // Otherwise, return an error. @@ -310,8 +325,7 @@ impl ServerState { #[cfg(test)] mod tests { use super::*; - use crate::{Scalar, ServerState, UserState, G}; - use sha2::Sha512; + use crate::{Scalar, ServerState, UserState}; #[test] fn test_domain_nym_computation() { @@ -320,16 +334,14 @@ mod tests { let probe_cc = "US"; let probe_asn = "AS1234"; - let domain_str = format!("ooni.org/{}/{}", probe_cc, probe_asn); - let domain = G::hash_from_bytes::(domain_str.as_bytes()); + let domain = submit_domain_generator(probe_cc, probe_asn); // Test with a known nym_id let nym_id = Scalar::from(42u32); let nym = nym_id * domain; // Different domain should produce different NYM - let different_domain_str = format!("ooni.org/{}/{}", "UK", "AS5678"); - let different_domain = G::hash_from_bytes::(different_domain_str.as_bytes()); + let different_domain = submit_domain_generator("UK", "AS5678"); let different_nym = nym_id * different_domain; assert_ne!( diff --git a/ooniauth-py/ooniauth_py.pyi b/ooniauth-py/ooniauth_py.pyi index 666bb07..56b4f42 100644 --- a/ooniauth-py/ooniauth_py.pyi +++ b/ooniauth-py/ooniauth_py.pyi @@ -56,7 +56,7 @@ class ServerState: Handle a submit request from the client. Note that the probe_cc and probe_asn should have the following format: - - probe_cc = two letters, uppercase, alpha-numeric + - probe_cc = two uppercase ASCII letters - probe_asn = AS-prefixed, 3 <= len(probe_asn) <= 12, int value after AS This validation is left to the backend server implementing the library @@ -76,12 +76,6 @@ class ServerState: Performs a submission request computing the hash from the input measurement. Computes the hash internally using the [submit_measurement_hash] function. - - Note that the probe_cc and probe_asn should have the following format: - - probe_cc = two letters, uppercase, alpha-numeric - - probe_asn = AS-prefixed, 3 <= len(probe_asn) <= 12, int value after AS - - This validation is left to the backend server implementing the library """ def handle_update_request( @@ -119,7 +113,7 @@ class UserState: Make a submission request, to send a measurement to the server Note that the probe_cc and probe_asn should have the following format: - - probe_cc = two letters, uppercase, alpha-numeric + - probe_cc = two uppercase ASCII letters - probe_asn = AS-prefixed, 3 <= len(probe_asn) <= 12, int value after AS This validation is left to the backend server implementing the library @@ -136,12 +130,6 @@ class UserState: r""" Creates a submit request computing the hash from the input measurement. Computes the hash internally using the [submit_measurement_hash] function - - Note that the probe_cc and probe_asn should have the following format: - - probe_cc = two letters, uppercase, alpha-numeric - - probe_asn = AS-prefixed, 3 <= len(probe_asn) <= 12, int value after AS - - This validation is left to the backend server implementing the library """ def handle_submit_response(self, response: str) -> None: diff --git a/ooniauth-py/src/protocol.rs b/ooniauth-py/src/protocol.rs index f353576..166876d 100644 --- a/ooniauth-py/src/protocol.rs +++ b/ooniauth-py/src/protocol.rs @@ -1,9 +1,9 @@ use base64::prelude::*; use ooniauth_core::registration::open_registration; use ooniauth_core::submit::submit; +use ooniauth_core::submit::submit_measurement_hash as core_submit_measurement_hash; use ooniauth_core::update::*; use ooniauth_core::{self as ooni, PublicParameters, SecretKey}; -use ooniauth_core::submit::submit_measurement_hash as core_submit_measurement_hash; use pyo3::{prelude::*, types::PyString}; use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pyfunction, gen_stub_pymethods}; @@ -118,7 +118,7 @@ impl ServerState { /// Handle a submit request from the client. /// /// Note that the probe_cc and probe_asn should have the following format: - /// - probe_cc = two letters, uppercase, alpha-numeric + /// - probe_cc = two uppercase ASCII letters /// - probe_asn = AS-prefixed, 3 <= len(probe_asn) <= 12, int value after AS /// /// This validation is left to the backend server implementing the library @@ -152,12 +152,6 @@ impl ServerState { /// Performs a submission request computing the hash from the input /// measurement. Computes the hash internally using the /// [submit_measurement_hash] function. - /// - /// Note that the probe_cc and probe_asn should have the following format: - /// - probe_cc = two letters, uppercase, alpha-numeric - /// - probe_asn = AS-prefixed, 3 <= len(probe_asn) <= 12, int value after AS - /// - /// This validation is left to the backend server implementing the library #[allow(clippy::too_many_arguments)] fn handle_submit_request_with_hash( &self, @@ -317,7 +311,7 @@ impl UserState { /// Make a submission request, to send a measurement to the server /// /// Note that the probe_cc and probe_asn should have the following format: - /// - probe_cc = two letters, uppercase, alpha-numeric + /// - probe_cc = two uppercase ASCII letters /// - probe_asn = AS-prefixed, 3 <= len(probe_asn) <= 12, int value after AS /// /// This validation is left to the backend server implementing the library @@ -344,12 +338,6 @@ impl UserState { /// Creates a submit request computing the hash from the input measurement. /// Computes the hash internally using the [submit_measurement_hash] function - /// - /// Note that the probe_cc and probe_asn should have the following format: - /// - probe_cc = two letters, uppercase, alpha-numeric - /// - probe_asn = AS-prefixed, 3 <= len(probe_asn) <= 12, int value after AS - /// - /// This validation is left to the backend server implementing the library pub fn make_submit_request_with_hash( &mut self, py: Python<'_>, @@ -480,10 +468,12 @@ mod tests { #[test] fn test_submit_measurement_hash_wrapper() { let measurement = b"measurement:US:AS1234"; - let expected = BASE64_STANDARD.encode(ooniauth_core::submit::submit_measurement_hash( - measurement, - )); - assert_eq!(crate::submit_measurement_hash(std::str::from_utf8(measurement).unwrap()), expected); + let expected = + BASE64_STANDARD.encode(ooniauth_core::submit::submit_measurement_hash(measurement)); + assert_eq!( + crate::submit_measurement_hash(std::str::from_utf8(measurement).unwrap()), + expected + ); } #[test] @@ -556,12 +546,13 @@ mod tests { pyo3::Python::initialize(); Python::attach(|py| { let server = crate::ServerState::new(); - let mut client = - crate::UserState::new(py, server.get_public_parameters(py)).unwrap(); + let mut client = crate::UserState::new(py, server.get_public_parameters(py)).unwrap(); let req = client.make_registration_request(py).unwrap(); let reg_response = server.handle_registration_request(py, req).unwrap(); - client.handle_registration_response(py, reg_response).unwrap(); + client + .handle_registration_response(py, reg_response) + .unwrap(); let cc: Py = PyString::new(py, "VE").into(); let asn: Py = PyString::new(py, "AS1234").into(); From 3eeef9332cdba26fbe66c5db3081c1098402ab41 Mon Sep 17 00:00:00 2001 From: Michele Orru Date: Thu, 25 Jun 2026 18:54:01 -0700 Subject: [PATCH 3/5] docs: more explicit information about panics --- ooniauth-py/ooniauth_py.pyi | 22 ++++++++++++++++------ ooniauth-py/src/protocol.rs | 22 ++++++++++++++++------ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/ooniauth-py/ooniauth_py.pyi b/ooniauth-py/ooniauth_py.pyi index 56b4f42..2a81fd0 100644 --- a/ooniauth-py/ooniauth_py.pyi +++ b/ooniauth-py/ooniauth_py.pyi @@ -55,11 +55,16 @@ class ServerState: r""" Handle a submit request from the client. - Note that the probe_cc and probe_asn should have the following format: + Both arguments `probe_cc` and `probe_asn` MUST have the following format: + - probe_cc = two uppercase ASCII letters - - probe_asn = AS-prefixed, 3 <= len(probe_asn) <= 12, int value after AS + - probe_asn = starts with "AS", 3 <= len(probe_asn) <= 12 + + This validation is done in backend server validating measurements. + + # Panics - This validation is left to the backend server implementing the library + The arguments `probe_cc` and `probe_asn` will cause a panic (in debug mode) if not valid. """ def handle_submit_request_with_hash( @@ -112,11 +117,16 @@ class UserState: r""" Make a submission request, to send a measurement to the server - Note that the probe_cc and probe_asn should have the following format: + The arguments `probe_cc` and `probe_asn` MUST have the following format: + - probe_cc = two uppercase ASCII letters - - probe_asn = AS-prefixed, 3 <= len(probe_asn) <= 12, int value after AS + - probe_asn = starts with "AS", 3 <= len(probe_asn) <= 12 + + This validation is done in backend server validating measurements. + + # Panics - This validation is left to the backend server implementing the library + The arguments `probe_cc` and `probe_asn` will cause a panic (in debug mode) if not valid. """ def make_submit_request_with_hash( diff --git a/ooniauth-py/src/protocol.rs b/ooniauth-py/src/protocol.rs index 166876d..5f5812c 100644 --- a/ooniauth-py/src/protocol.rs +++ b/ooniauth-py/src/protocol.rs @@ -117,11 +117,16 @@ impl ServerState { /// Handle a submit request from the client. /// - /// Note that the probe_cc and probe_asn should have the following format: + /// Both arguments `probe_cc` and `probe_asn` MUST have the following format: + /// /// - probe_cc = two uppercase ASCII letters - /// - probe_asn = AS-prefixed, 3 <= len(probe_asn) <= 12, int value after AS + /// - probe_asn = starts with "AS", 3 <= len(probe_asn) <= 12 + /// + /// This validation is done in backend server validating measurements. + /// + /// # Panics /// - /// This validation is left to the backend server implementing the library + /// The arguments `probe_cc` and `probe_asn` will cause a panic (in debug mode) if not valid. #[allow(clippy::too_many_arguments)] fn handle_submit_request( &self, @@ -310,11 +315,16 @@ impl UserState { /// Make a submission request, to send a measurement to the server /// - /// Note that the probe_cc and probe_asn should have the following format: + /// The arguments `probe_cc` and `probe_asn` MUST have the following format: + /// /// - probe_cc = two uppercase ASCII letters - /// - probe_asn = AS-prefixed, 3 <= len(probe_asn) <= 12, int value after AS + /// - probe_asn = starts with "AS", 3 <= len(probe_asn) <= 12 + /// + /// This validation is done in backend server validating measurements. + /// + /// # Panics /// - /// This validation is left to the backend server implementing the library + /// The arguments `probe_cc` and `probe_asn` will cause a panic (in debug mode) if not valid. pub fn make_submit_request( &mut self, py: Python<'_>, From e5852bdbc2583ecca681f4581f9f4c9a568482cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20D=C3=ADaz?= Date: Mon, 29 Jun 2026 11:48:47 +0200 Subject: [PATCH 4/5] Update documentation about the asn --- ooniauth-py/src/protocol.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ooniauth-py/src/protocol.rs b/ooniauth-py/src/protocol.rs index 5f5812c..c8738d3 100644 --- a/ooniauth-py/src/protocol.rs +++ b/ooniauth-py/src/protocol.rs @@ -120,7 +120,8 @@ impl ServerState { /// Both arguments `probe_cc` and `probe_asn` MUST have the following format: /// /// - probe_cc = two uppercase ASCII letters - /// - probe_asn = starts with "AS", 3 <= len(probe_asn) <= 12 + /// - probe_asn = starts with "AS", 3 <= len(probe_asn) <= 12, and the int + /// part after AS does not have leading 0s /// /// This validation is done in backend server validating measurements. /// @@ -318,7 +319,8 @@ impl UserState { /// The arguments `probe_cc` and `probe_asn` MUST have the following format: /// /// - probe_cc = two uppercase ASCII letters - /// - probe_asn = starts with "AS", 3 <= len(probe_asn) <= 12 + /// - probe_asn = starts with "AS", 3 <= len(probe_asn) <= 12, and the int + /// part after AS does not have leading 0s /// /// This validation is done in backend server validating measurements. /// From 3c23da767b328caefccb5b38f67482ff5fc993d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20D=C3=ADaz?= Date: Mon, 29 Jun 2026 11:50:43 +0200 Subject: [PATCH 5/5] Update docstring about asn --- ooniauth-py/ooniauth_py.pyi | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ooniauth-py/ooniauth_py.pyi b/ooniauth-py/ooniauth_py.pyi index 2a81fd0..2aea959 100644 --- a/ooniauth-py/ooniauth_py.pyi +++ b/ooniauth-py/ooniauth_py.pyi @@ -58,7 +58,8 @@ class ServerState: Both arguments `probe_cc` and `probe_asn` MUST have the following format: - probe_cc = two uppercase ASCII letters - - probe_asn = starts with "AS", 3 <= len(probe_asn) <= 12 + - probe_asn = starts with "AS", 3 <= len(probe_asn) <= 12, and the int + part after AS does not have leading 0s This validation is done in backend server validating measurements. @@ -120,7 +121,8 @@ class UserState: The arguments `probe_cc` and `probe_asn` MUST have the following format: - probe_cc = two uppercase ASCII letters - - probe_asn = starts with "AS", 3 <= len(probe_asn) <= 12 + - probe_asn = starts with "AS", 3 <= len(probe_asn) <= 12, and the int + part after AS does not have leading 0s This validation is done in backend server validating measurements.