From d7a44428ce25d19e9060d7699120c5acf744edec Mon Sep 17 00:00:00 2001 From: dzejkop Date: Mon, 28 Sep 2026 12:12:21 +0200 Subject: [PATCH 1/3] feat(flamingo)!: match over the verifier WebSocket session Flamingo replaces the HTTP assignment and match routes with a single GET /v1/matches WebSocket (worldcoin/flamingo#125). Each match now opens a session that verifies the assignment delivered on it and sends the sealed inputs over the same socket; a reassignment opens a fresh session. Configured headers are sent on the upgrade request. Headers the handshake sets itself (Host, Connection, Upgrade, Sec-WebSocket-*) are rejected alongside Cookie, which no longer carries ALB affinity. The flamingo crates are pinned to the unreleased PR commit until a release is published. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 147 +++++------- Cargo.toml | 10 +- crates/walletkit-core/Cargo.toml | 2 + crates/walletkit-core/src/flamingo/mod.rs | 276 +++++++++++++--------- 4 files changed, 232 insertions(+), 203 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 12d685d7..f75a9faa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2494,35 +2494,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "cookie" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87" -dependencies = [ - "percent-encoding", - "time", - "version_check", -] - -[[package]] -name = "cookie_store" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206" -dependencies = [ - "cookie", - "document-features", - "idna", - "log", - "publicsuffix", - "serde", - "serde_derive", - "serde_json", - "time", - "url", -] - [[package]] name = "core-foundation" version = "0.10.1" @@ -3010,15 +2981,6 @@ dependencies = [ "syn 2.0.118", ] -[[package]] -name = "document-features" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" -dependencies = [ - "litrs", -] - [[package]] name = "dotenvy" version = "0.15.7" @@ -3287,8 +3249,7 @@ checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe" [[package]] name = "flamingo-verifier-api-types" version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6e9f337d49b8dc44f73f52218d1f82c887211ca9b3b7e49f3df66fbb0b44fca" +source = "git+https://github.com/worldcoin/flamingo?rev=43b54c1c263c0214ae973c4eac343f585e4090f9#43b54c1c263c0214ae973c4eac343f585e4090f9" dependencies = [ "serde", ] @@ -3296,27 +3257,29 @@ dependencies = [ [[package]] name = "flamingo-verifier-client" version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c10922846f79c40ea62838b779688948e16823ddfbbbbda02e8071b3419364b" +source = "git+https://github.com/worldcoin/flamingo?rev=43b54c1c263c0214ae973c4eac343f585e4090f9#43b54c1c263c0214ae973c4eac343f585e4090f9" dependencies = [ "base64 0.22.1", "flamingo-verifier-api-types", "flamingo-verifier-protocol", "flamingo-verifier-sealed-types", + "futures-util", "hex", "pontifex", - "reqwest 0.12.28", + "rustls", "serde", "serde_json", "thiserror 2.0.18", + "tokio", + "tokio-tungstenite 0.29.0", "url", + "webpki-roots 0.26.11", ] [[package]] name = "flamingo-verifier-protocol" version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc235dad5b4d0ce7a34fa660afea19febc1100fdc83a41513d6ae347a1df38e5" +source = "git+https://github.com/worldcoin/flamingo?rev=43b54c1c263c0214ae973c4eac343f585e4090f9#43b54c1c263c0214ae973c4eac343f585e4090f9" dependencies = [ "ark-ff 0.5.0", "coset", @@ -3330,8 +3293,7 @@ dependencies = [ [[package]] name = "flamingo-verifier-sealed-types" version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16f6af152bf2aa7d6ac01345e36f41216ce0b2c1bbafcd1092989d84206738b" +source = "git+https://github.com/worldcoin/flamingo?rev=43b54c1c263c0214ae973c4eac343f585e4090f9#43b54c1c263c0214ae973c4eac343f585e4090f9" dependencies = [ "ciborium", "flamingo-verifier-api-types", @@ -4612,12 +4574,6 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" -[[package]] -name = "litrs" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" - [[package]] name = "lock_api" version = "0.4.14" @@ -6326,22 +6282,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "psl-types" -version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" - -[[package]] -name = "publicsuffix" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" -dependencies = [ - "idna", - "psl-types", -] - [[package]] name = "quantum-box" version = "0.1.0" @@ -6685,8 +6625,6 @@ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64 0.22.1", "bytes", - "cookie", - "cookie_store", "futures-channel", "futures-core", "futures-util", @@ -6709,14 +6647,12 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-rustls", - "tokio-util", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", "web-sys", "webpki-roots 1.0.8", ] @@ -7814,6 +7750,16 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + [[package]] name = "signature" version = "2.2.0" @@ -8292,7 +8238,7 @@ dependencies = [ "taceo-poseidon2", "thiserror 2.0.18", "tokio", - "tokio-tungstenite", + "tokio-tungstenite 0.28.0", "tracing", "url", "uuid", @@ -8598,6 +8544,7 @@ dependencies = [ "mio", "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.61.2", @@ -8659,7 +8606,23 @@ dependencies = [ "rustls-pki-types", "tokio", "tokio-rustls", - "tungstenite", + "tungstenite 0.28.0", + "webpki-roots 0.26.11", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite 0.29.0", "webpki-roots 0.26.11", ] @@ -8948,6 +8911,25 @@ dependencies = [ "utf-8", ] +[[package]] +name = "tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.4", + "rustls", + "rustls-pki-types", + "sha1", + "thiserror 2.0.18", + "url", +] + [[package]] name = "turboshake" version = "0.7.1" @@ -9332,6 +9314,7 @@ dependencies = [ "flamingo-verifier-client", "flamingo-verifier-protocol", "flamingo-verifier-sealed-types", + "futures-util", "getrandom 0.3.4", "hex", "hkdf 0.12.4", @@ -9356,6 +9339,7 @@ dependencies = [ "thiserror 2.0.18", "tokio", "tokio-test", + "tokio-tungstenite 0.29.0", "tracing", "tracing-log", "tracing-subscriber 0.3.23", @@ -9538,19 +9522,6 @@ version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c31d56021e873866c968588ed85ccdf56db5c426e44afdb4618c39895104b920" -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "wasmtimer" version = "0.4.3" diff --git a/Cargo.toml b/Cargo.toml index 4f54a206..a95960d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,11 +41,12 @@ clap = "4" ctor = "0.2" dirs = "6" dotenvy = "0.15.7" -flamingo-verifier-api-types = "0.5.0" -flamingo-verifier-client = "0.5.0" -flamingo-verifier-protocol = "0.5.0" -flamingo-verifier-sealed-types = "0.5.0" +flamingo-verifier-api-types = { git = "https://github.com/worldcoin/flamingo", rev = "43b54c1c263c0214ae973c4eac343f585e4090f9" } +flamingo-verifier-client = { git = "https://github.com/worldcoin/flamingo", rev = "43b54c1c263c0214ae973c4eac343f585e4090f9" } +flamingo-verifier-protocol = { git = "https://github.com/worldcoin/flamingo", rev = "43b54c1c263c0214ae973c4eac343f585e4090f9" } +flamingo-verifier-sealed-types = { git = "https://github.com/worldcoin/flamingo", rev = "43b54c1c263c0214ae973c4eac343f585e4090f9" } eyre = "0.6" +futures-util = { version = "0.3", default-features = false } getrandom = "0.3" hex = "0.4" hkdf = "0.12" @@ -73,6 +74,7 @@ test-case = "3.3" thiserror = "2" tokio = "1" tokio-test = "0.4" +tokio-tungstenite = { version = "0.29", default-features = false } tracing = "0.1" tracing-log = "0.2" tracing-subscriber = "0.3" diff --git a/crates/walletkit-core/Cargo.toml b/crates/walletkit-core/Cargo.toml index a38d0924..fe9f067a 100644 --- a/crates/walletkit-core/Cargo.toml +++ b/crates/walletkit-core/Cargo.toml @@ -83,6 +83,7 @@ chacha20poly1305 = { workspace = true } chrono = { workspace = true } dotenvy = { workspace = true } eyre = { workspace = true } +futures-util = { workspace = true, features = ["sink"] } mockito = { workspace = true } regex = { workspace = true } taceo-oprf = { workspace = true, features = [ @@ -92,6 +93,7 @@ tempfile = { workspace = true } test-case = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } tokio-test = { workspace = true } +tokio-tungstenite = { workspace = true, features = ["handshake"] } tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } walletkit-testkit = { workspace = true } diff --git a/crates/walletkit-core/src/flamingo/mod.rs b/crates/walletkit-core/src/flamingo/mod.rs index 04f80c16..5d42686f 100644 --- a/crates/walletkit-core/src/flamingo/mod.rs +++ b/crates/walletkit-core/src/flamingo/mod.rs @@ -2,8 +2,8 @@ //! //! This module deliberately knows nothing about Orb PCP storage. Its caller supplies the live //! image and the credential material obtained through the platform's Oxide/OrbKit adapter. The -//! module owns assignment, attestation verification, sealing, transport, response opening, and -//! match-token verification. +//! module owns the WebSocket session, assignment, attestation verification, sealing, response +//! opening, and match-token verification. mod errors; mod types; @@ -21,12 +21,16 @@ use std::{collections::HashMap, sync::Arc}; use async_trait::async_trait; use flamingo_verifier_client::{ - Config, Error as ClientError, FlamingoVerifierClient, PcrMeasurement, - VerifiedAssignment, VerifiedMatchResult as MatchResult, + Config, Error as ClientError, FlamingoVerifierClient, FlamingoVerifierSession, + PcrMeasurement, VerifiedMatchResult as MatchResult, }; use flamingo_verifier_sealed_types::MatchInputs; use reqwest::{ - header::{HeaderMap, HeaderName, HeaderValue, COOKIE}, + header::{ + HeaderMap, HeaderName, HeaderValue, CONNECTION, COOKIE, HOST, + SEC_WEBSOCKET_EXTENSIONS, SEC_WEBSOCKET_KEY, SEC_WEBSOCKET_PROTOCOL, + SEC_WEBSOCKET_VERSION, UPGRADE, + }, Url, }; use tokio::sync::OnceCell; @@ -37,22 +41,41 @@ pub struct FlamingoMatcher { host_url: Url, config: Option, headers: HeaderMap, - client: OnceCell, + client: OnceCell, } +/// Headers the WebSocket handshake sets itself; a caller-supplied copy would be duplicated. +const HANDSHAKE_HEADERS: [HeaderName; 7] = [ + HOST, + CONNECTION, + UPGRADE, + SEC_WEBSOCKET_KEY, + SEC_WEBSOCKET_VERSION, + SEC_WEBSOCKET_PROTOCOL, + SEC_WEBSOCKET_EXTENSIONS, +]; + #[async_trait] trait MatchClient: Sync { - type Assignment: Send + Sync; + type Session: Send; - async fn request_assignment(&self) -> Result; + /// Opens a session whose assignment has already been verified. + async fn connect(&self) -> Result; async fn request_match( &self, - assignment: &Self::Assignment, + session: Self::Session, inputs: &MatchInputs, ) -> Result; } +/// The verifier client plus the headers sent on every WebSocket upgrade. +#[derive(Debug)] +struct SessionClient { + client: FlamingoVerifierClient, + headers: HeaderMap, +} + #[uniffi::export(async_runtime = "tokio")] impl FlamingoMatcher { /// Creates an instance with default values, use `with_measurements` and `with_headers` for customization. @@ -124,11 +147,13 @@ impl FlamingoMatcher { /// Returns a new instance with these default headers, replacing any previously configured set. /// - /// Use this to set authorization, client name, or other headers. The `Cookie` header is not allowed; the client manages affinity cookies automatically. + /// Use this to set authorization, client name, or other headers. They are sent on the + /// WebSocket upgrade request of every match session. /// /// # Errors /// Returns [`FlamingoError::Configuration`] for invalid names/values, case-insensitive duplicate - /// names, or a caller-supplied `Cookie` header (the client owns affinity cookies). + /// names, a `Cookie` header, or a header the WebSocket handshake sets itself (such as `Host`, + /// `Upgrade`, or `Sec-WebSocket-*`). pub fn with_headers( &self, headers: HashMap, @@ -143,8 +168,10 @@ impl FlamingoMatcher { /// Performs an attested 3-way embedding match. /// - /// - Fetches the enclave assignment and verifies its attestation, including PCRs unless explicitly bypassed. - /// - Encrypts and sends the match inputs using the enclave's attested public key. + /// - Opens a WebSocket session and verifies the enclave assignment delivered on it, including + /// PCRs unless explicitly bypassed. + /// - Encrypts and sends the match inputs over the same session using the enclave's attested + /// public key. /// - Decrypts the result and, on success, verifies the token's signature and signing-key attestation. /// /// # Errors @@ -162,7 +189,7 @@ impl FlamingoMatcher { } impl FlamingoMatcher { - async fn client(&self) -> Result<&FlamingoVerifierClient, FlamingoError> { + async fn client(&self) -> Result<&SessionClient, FlamingoError> { self.client .get_or_try_init(|| async { let config = self.config.clone().ok_or_else(|| { @@ -171,28 +198,40 @@ impl FlamingoMatcher { .to_string(), ) })?; - let http = reqwest::Client::builder().default_headers(self.headers.clone()); - FlamingoVerifierClient::with_http_client_builder(config, http) - .map_err(|error| FlamingoError::Verifier(error.to_string())) + let client = FlamingoVerifierClient::new(config) + .map_err(|error| FlamingoError::Verifier(error.to_string()))?; + Ok(SessionClient { + client, + headers: self.headers.clone(), + }) }) .await } } #[async_trait] -impl MatchClient for FlamingoVerifierClient { - type Assignment = VerifiedAssignment; - - async fn request_assignment(&self) -> Result { - self.request_assignment().await +impl MatchClient for SessionClient { + type Session = FlamingoVerifierSession; + + async fn connect(&self) -> Result { + let mut request = self.client.build_request()?; + for (name, value) in &self.headers { + // `parse_headers` only admits visible ASCII values, so this cannot fail. + let value = value.to_str().map_err(|_| ClientError::InvalidConfig { + attribute: "headers".to_string(), + reason: "header values must be visible ASCII".to_string(), + })?; + request = request.with_header(name.as_str(), value); + } + self.client.connect_with(request).await } async fn request_match( &self, - assignment: &Self::Assignment, + session: Self::Session, inputs: &MatchInputs, ) -> Result { - self.request_match(assignment, inputs).await + session.request_match(inputs).await } } @@ -204,9 +243,14 @@ fn parse_headers(headers: HashMap) -> Result( let mut reassigned = false; loop { - let assignment = client - .request_assignment() + // A session carries exactly one match, so a reassignment opens a fresh one. + let session = client + .connect() .await .map_err(|error| verifier_error(&error))?; - match client.request_match(&assignment, &request).await { + match client.request_match(session, &request).await { Ok(MatchResult::Success(statement)) => { return Ok(FlamingoMatchOutcome::Matched(Arc::new( VerifiedMatchToken::from(*statement), @@ -291,6 +336,15 @@ mod tests { atomic::{AtomicUsize, Ordering}, Mutex, }, + time::Duration, + }; + + use futures_util::{SinkExt, StreamExt}; + use tokio::net::{TcpListener, TcpStream}; + use tokio_tungstenite::{ + accept_hdr_async, + tungstenite::{handshake::server::Request, Message}, + WebSocketStream, }; use flamingo_verifier_client::{ @@ -324,15 +378,15 @@ mod tests { #[async_trait::async_trait] impl MatchClient for FakeClient { - type Assignment = usize; + type Session = usize; - async fn request_assignment(&self) -> Result { + async fn connect(&self) -> Result { Ok(self.assignments.fetch_add(1, Ordering::Relaxed)) } async fn request_match( &self, - _assignment: &Self::Assignment, + _session: Self::Session, _inputs: &MatchInputs, ) -> Result { self.results @@ -529,7 +583,8 @@ mod tests { } #[test] - fn rejects_invalid_duplicate_and_cookie_headers_without_exposing_values() { + fn rejects_invalid_duplicate_cookie_and_handshake_headers_without_exposing_values() + { let matcher = FlamingoMatcher::new("https://verifier.example.com").unwrap(); for headers in [ HashMap::from([("bad name".to_string(), "secret".to_string())]), @@ -539,6 +594,9 @@ mod tests { ("authorization".to_string(), "secret".to_string()), ]), HashMap::from([("cOoKiE".to_string(), "secret".to_string())]), + HashMap::from([("Host".to_string(), "secret".to_string())]), + HashMap::from([("upgrade".to_string(), "secret".to_string())]), + HashMap::from([("Sec-WebSocket-Key".to_string(), "secret".to_string())]), ] { let error = matcher.with_headers(headers).unwrap_err(); assert!(matches!(error, FlamingoError::Configuration(_))); @@ -583,100 +641,97 @@ mod tests { )); } + /// Serves one WebSocket upgrade, recording the request, then runs `session` on the socket. + // The handshake callback's error type is fixed by tungstenite. + #[allow(clippy::result_large_err)] + async fn serve_once( + session: F, + ) -> (String, tokio::sync::oneshot::Receiver) + where + F: FnOnce(WebSocketStream) -> Fut + Send + 'static, + Fut: std::future::Future + Send, + { + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let address = listener.local_addr().unwrap(); + let (seen, request) = tokio::sync::oneshot::channel(); + tokio::spawn(async move { + let (stream, _) = listener.accept().await.unwrap(); + let socket = + accept_hdr_async(stream, move |request: &Request, response| { + let _ = seen.send(request.clone()); + Ok(response) + }) + .await + .unwrap(); + session(socket).await; + }); + (format!("http://{address}"), request) + } + #[tokio::test] async fn missing_measurements_fail_before_any_request() { - let mut server = mockito::Server::new_async().await; - let assignment = server - .mock("POST", "/v1/enclave-assignment") - .expect(0) - .create_async() - .await; - let matcher = FlamingoMatcher::new(&server.url()) - .unwrap() - .with_headers(headers()) - .unwrap(); + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let matcher = + FlamingoMatcher::new(&format!("http://{}", listener.local_addr().unwrap())) + .unwrap() + .with_headers(headers()) + .unwrap(); assert!(matches!( matcher.perform_match(request()).await, Err(FlamingoError::Configuration(_)) )); assert!(matcher.client.get().is_none()); - assignment.assert_async().await; - drop(server); + assert!( + tokio::time::timeout(Duration::from_millis(50), listener.accept()) + .await + .is_err(), + "no connection may be opened without measurements" + ); } #[tokio::test] - async fn http_defaults_and_affinity_cookies_cover_both_routes() { - let mut server = mockito::Server::new_async().await; - let assignment = server - .mock("POST", "/v1/flamingo/v1/enclave-assignment") - .match_header("authorization", "Bearer test-token") - .match_header("client-name", "test-client") - .with_header("set-cookie", "AWSALB=assigned-pod; Path=/") - .with_status(204) - .expect(2) - .create_async() - .await; - let match_route = server - .mock("POST", "/v1/flamingo/v1/matches") - .match_header("authorization", "Bearer test-token") - .match_header("client-name", "test-client") - .match_header("cookie", "AWSALB=assigned-pod") - .with_status(409) - .expect(2) - .create_async() - .await; - let matcher = FlamingoMatcher::new(&format!("{}/v1/flamingo/", server.url())) + async fn upgrade_carries_configured_headers_to_the_prefixed_route() { + let (base_url, seen) = serve_once(|socket| async move { drop(socket) }).await; + let matcher = FlamingoMatcher::new(&format!("{base_url}/v1/flamingo/")) .unwrap() .with_measurements(measurements()) .unwrap() .with_headers(headers()) .unwrap(); - let client = matcher.client().await.unwrap(); - // Exercise the configured HTTP transport without fabricating a trusted Nitro attestation. - // The separate retry tests below cover the match orchestration. - let (http, _) = client.build_assignment_request().build_split(); - for _ in 0..2 { - assert_eq!( - client - .build_assignment_request() - .send() - .await - .unwrap() - .status(), - 204 - ); - assert_eq!( - http.post(format!("{}/v1/flamingo/v1/matches", server.url())) - .send() - .await - .unwrap() - .status(), - 409 - ); - } - assignment.assert_async().await; - match_route.assert_async().await; - drop(server); + + // The stub closes right after the upgrade, so the session fails before any assignment. + assert!(matches!( + matcher.perform_match(request()).await, + Err(FlamingoError::Verifier(_)) + )); + + let upgrade = seen.await.unwrap(); + assert_eq!(upgrade.uri().path(), "/v1/flamingo/v1/matches"); + assert_eq!(upgrade.headers()["authorization"], "Bearer test-token"); + assert_eq!(upgrade.headers()["client-name"], "test-client"); + assert_eq!(upgrade.headers().get_all("host").iter().count(), 1); } #[tokio::test] - async fn rejects_a_legacy_assignment_before_sending_images() { - let mut server = mockito::Server::new_async().await; - let assignment = server - .mock("POST", "/v1/enclave-assignment") - .match_header("authorization", "Bearer test-token") - .with_status(200) - .with_header("content-type", "application/json") - .with_body(r#"{"attestation":"YXR0ZXN0YXRpb24="}"#) - .expect(1) - .create_async() - .await; - let image_upload = server - .mock("POST", "/v1/matches") - .expect(0) - .create_async() - .await; - let matcher = FlamingoMatcher::new(&server.url()) + async fn rejects_an_unverifiable_assignment_before_sending_images() { + let (sent, frames) = tokio::sync::oneshot::channel(); + let (base_url, _) = serve_once(|mut socket| async move { + let first = socket.next().await.unwrap().unwrap(); + assert_eq!( + first, + Message::Text(r#"{"type":"assignment_request"}"#.into()) + ); + let assignment = + r#"{"type":"assignment","attestation":"hEBAQEA=","public_key":"a2V5"}"#; + socket.send(Message::Text(assignment.into())).await.unwrap(); + let mut binary = 0; + while let Some(Ok(frame)) = socket.next().await { + binary += usize::from(frame.is_binary()); + } + let _ = sent.send(binary); + }) + .await; + let matcher = FlamingoMatcher::new(&base_url) .unwrap() .with_measurements(measurements()) .unwrap() @@ -686,9 +741,8 @@ mod tests { let error = matcher.perform_match(request()).await.unwrap_err(); assert!(matches!(error, FlamingoError::Verifier(_))); - assignment.assert_async().await; - image_upload.assert_async().await; - drop(server); + drop(matcher); + assert_eq!(frames.await.unwrap(), 0, "no image frame may be sent"); } #[tokio::test] From f0b4407090dbcca53ccc69055fffb77c3135bdb4 Mon Sep 17 00:00:00 2001 From: dzejkop Date: Mon, 28 Sep 2026 17:08:14 +0200 Subject: [PATCH 2/3] chore(flamingo): pin the merged flamingo#125 commit Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 28 ++++++++++++++-------------- Cargo.toml | 8 ++++---- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f75a9faa..d6f014bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2399,7 +2399,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -2956,7 +2956,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -3131,7 +3131,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -3249,7 +3249,7 @@ checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe" [[package]] name = "flamingo-verifier-api-types" version = "0.5.0" -source = "git+https://github.com/worldcoin/flamingo?rev=43b54c1c263c0214ae973c4eac343f585e4090f9#43b54c1c263c0214ae973c4eac343f585e4090f9" +source = "git+https://github.com/worldcoin/flamingo?rev=1256ec161557a81258fb627c1d7a453687fe5118#1256ec161557a81258fb627c1d7a453687fe5118" dependencies = [ "serde", ] @@ -3257,7 +3257,7 @@ dependencies = [ [[package]] name = "flamingo-verifier-client" version = "0.5.0" -source = "git+https://github.com/worldcoin/flamingo?rev=43b54c1c263c0214ae973c4eac343f585e4090f9#43b54c1c263c0214ae973c4eac343f585e4090f9" +source = "git+https://github.com/worldcoin/flamingo?rev=1256ec161557a81258fb627c1d7a453687fe5118#1256ec161557a81258fb627c1d7a453687fe5118" dependencies = [ "base64 0.22.1", "flamingo-verifier-api-types", @@ -3279,7 +3279,7 @@ dependencies = [ [[package]] name = "flamingo-verifier-protocol" version = "0.5.0" -source = "git+https://github.com/worldcoin/flamingo?rev=43b54c1c263c0214ae973c4eac343f585e4090f9#43b54c1c263c0214ae973c4eac343f585e4090f9" +source = "git+https://github.com/worldcoin/flamingo?rev=1256ec161557a81258fb627c1d7a453687fe5118#1256ec161557a81258fb627c1d7a453687fe5118" dependencies = [ "ark-ff 0.5.0", "coset", @@ -3293,7 +3293,7 @@ dependencies = [ [[package]] name = "flamingo-verifier-sealed-types" version = "0.5.0" -source = "git+https://github.com/worldcoin/flamingo?rev=43b54c1c263c0214ae973c4eac343f585e4090f9#43b54c1c263c0214ae973c4eac343f585e4090f9" +source = "git+https://github.com/worldcoin/flamingo?rev=1256ec161557a81258fb627c1d7a453687fe5118#1256ec161557a81258fb627c1d7a453687fe5118" dependencies = [ "ciborium", "flamingo-verifier-api-types", @@ -4231,7 +4231,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -4932,7 +4932,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -6356,7 +6356,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -6884,7 +6884,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -6964,7 +6964,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs 1.0.8", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -8325,7 +8325,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -9639,7 +9639,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a95960d2..271948c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,10 +41,10 @@ clap = "4" ctor = "0.2" dirs = "6" dotenvy = "0.15.7" -flamingo-verifier-api-types = { git = "https://github.com/worldcoin/flamingo", rev = "43b54c1c263c0214ae973c4eac343f585e4090f9" } -flamingo-verifier-client = { git = "https://github.com/worldcoin/flamingo", rev = "43b54c1c263c0214ae973c4eac343f585e4090f9" } -flamingo-verifier-protocol = { git = "https://github.com/worldcoin/flamingo", rev = "43b54c1c263c0214ae973c4eac343f585e4090f9" } -flamingo-verifier-sealed-types = { git = "https://github.com/worldcoin/flamingo", rev = "43b54c1c263c0214ae973c4eac343f585e4090f9" } +flamingo-verifier-api-types = { git = "https://github.com/worldcoin/flamingo", rev = "1256ec161557a81258fb627c1d7a453687fe5118" } +flamingo-verifier-client = { git = "https://github.com/worldcoin/flamingo", rev = "1256ec161557a81258fb627c1d7a453687fe5118" } +flamingo-verifier-protocol = { git = "https://github.com/worldcoin/flamingo", rev = "1256ec161557a81258fb627c1d7a453687fe5118" } +flamingo-verifier-sealed-types = { git = "https://github.com/worldcoin/flamingo", rev = "1256ec161557a81258fb627c1d7a453687fe5118" } eyre = "0.6" futures-util = { version = "0.3", default-features = false } getrandom = "0.3" From bdc7e1d369495415573a7b7e029f7f73080051af Mon Sep 17 00:00:00 2001 From: Dzejkop Date: Mon, 28 Sep 2026 18:21:48 +0200 Subject: [PATCH 3/3] chore(flamingo): bump verifier crates to 0.6.0 --- Cargo.lock | 40 ++++++++++++++++++++++------------------ Cargo.toml | 8 ++++---- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d6f014bf..ee83b4ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2399,7 +2399,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2956,7 +2956,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3131,7 +3131,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3248,16 +3248,18 @@ checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe" [[package]] name = "flamingo-verifier-api-types" -version = "0.5.0" -source = "git+https://github.com/worldcoin/flamingo?rev=1256ec161557a81258fb627c1d7a453687fe5118#1256ec161557a81258fb627c1d7a453687fe5118" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a847b2a30cc6a5f185f89874503f7d065dfb1c6a560df79f40f36c1e2762f847" dependencies = [ "serde", ] [[package]] name = "flamingo-verifier-client" -version = "0.5.0" -source = "git+https://github.com/worldcoin/flamingo?rev=1256ec161557a81258fb627c1d7a453687fe5118#1256ec161557a81258fb627c1d7a453687fe5118" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebebfee43d850a87869f6bf3614e70b354531b14200800a885c4b561b094df5" dependencies = [ "base64 0.22.1", "flamingo-verifier-api-types", @@ -3278,8 +3280,9 @@ dependencies = [ [[package]] name = "flamingo-verifier-protocol" -version = "0.5.0" -source = "git+https://github.com/worldcoin/flamingo?rev=1256ec161557a81258fb627c1d7a453687fe5118#1256ec161557a81258fb627c1d7a453687fe5118" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ccb1e0e591ead605d91df094032e74041cb2339960483f00b74b18da3cba4db" dependencies = [ "ark-ff 0.5.0", "coset", @@ -3292,8 +3295,9 @@ dependencies = [ [[package]] name = "flamingo-verifier-sealed-types" -version = "0.5.0" -source = "git+https://github.com/worldcoin/flamingo?rev=1256ec161557a81258fb627c1d7a453687fe5118#1256ec161557a81258fb627c1d7a453687fe5118" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2596fe8d50f0ecd02fbe4849e9992e7fa62736f614a0842142cfbf8c1ab25775" dependencies = [ "ciborium", "flamingo-verifier-api-types", @@ -4231,7 +4235,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4932,7 +4936,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6356,7 +6360,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6884,7 +6888,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6964,7 +6968,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs 1.0.8", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -8325,7 +8329,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -9639,7 +9643,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 271948c9..8930c4d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,10 +41,10 @@ clap = "4" ctor = "0.2" dirs = "6" dotenvy = "0.15.7" -flamingo-verifier-api-types = { git = "https://github.com/worldcoin/flamingo", rev = "1256ec161557a81258fb627c1d7a453687fe5118" } -flamingo-verifier-client = { git = "https://github.com/worldcoin/flamingo", rev = "1256ec161557a81258fb627c1d7a453687fe5118" } -flamingo-verifier-protocol = { git = "https://github.com/worldcoin/flamingo", rev = "1256ec161557a81258fb627c1d7a453687fe5118" } -flamingo-verifier-sealed-types = { git = "https://github.com/worldcoin/flamingo", rev = "1256ec161557a81258fb627c1d7a453687fe5118" } +flamingo-verifier-api-types = "0.6.0" +flamingo-verifier-client = "0.6.0" +flamingo-verifier-protocol = "0.6.0" +flamingo-verifier-sealed-types = "0.6.0" eyre = "0.6" futures-util = { version = "0.3", default-features = false } getrandom = "0.3"