From 896c7ebabd3140adb218edcb19e25c253b887f56 Mon Sep 17 00:00:00 2001 From: pg Date: Tue, 4 Aug 2026 07:24:00 +0200 Subject: [PATCH 1/7] fix(server): complete RFC-0023 VRF host integration --- README.md | 2 ++ explorer/diagnosis-reports/pairing-host-cli.md | 1 + explorer/diagnosis-reports/signing-host-cli.md | 1 + hosts/dotli | 2 +- rust/crates/truapi-server/src/runtime.rs | 11 ++--------- 5 files changed, 7 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index b215ee1d..03b3d888 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,8 @@ This repopulates the ignored generated TS under `js/packages/truapi/`, including The full `make e2e-dotli` diagnosis builds and launches the local `truapi-host signing-host` CLI to answer dotli's pairing QR and auto-approve remote signing requests. It does not require the external signer-bot service. +The generated account examples exercise `account.signVrf` through both the +local signing and paired remote-SSO paths. When `HOST_CLI_SIGNER_MNEMONIC` is absent, the CLI manages a reusable isolated test identity under `.e2e-dotli/`. Set `E2E_DOTLI_SIGNING_HOST_BASE_PATH` to use a different state directory while debugging. diff --git a/explorer/diagnosis-reports/pairing-host-cli.md b/explorer/diagnosis-reports/pairing-host-cli.md index c32ca3e5..166a4c3a 100644 --- a/explorer/diagnosis-reports/pairing-host-cli.md +++ b/explorer/diagnosis-reports/pairing-host-cli.md @@ -9,6 +9,7 @@ | `Account/get_legacy_accounts` | ✅ | | | `Account/get_user_id` | ✅ | | | `Account/request_login` | ✅ | | +| `Account/sign_vrf` | ✅ | | | `Chain/follow_head_subscribe` | ✅ | | | `Chain/get_head_header` | ✅ | | | `Chain/get_head_body` | ✅ | | diff --git a/explorer/diagnosis-reports/signing-host-cli.md b/explorer/diagnosis-reports/signing-host-cli.md index bf343c44..4627985e 100644 --- a/explorer/diagnosis-reports/signing-host-cli.md +++ b/explorer/diagnosis-reports/signing-host-cli.md @@ -9,6 +9,7 @@ | `Account/get_legacy_accounts` | ✅ | | | `Account/get_user_id` | ✅ | | | `Account/request_login` | ✅ | | +| `Account/sign_vrf` | ✅ | | | `Chain/follow_head_subscribe` | ✅ | | | `Chain/get_head_header` | ✅ | | | `Chain/get_head_body` | ✅ | | diff --git a/hosts/dotli b/hosts/dotli index 7921ce41..ad3e326a 160000 --- a/hosts/dotli +++ b/hosts/dotli @@ -1 +1 @@ -Subproject commit 7921ce413a4a1661b36c3f5032d91287a8f160bf +Subproject commit ad3e326a487fb6663c7d0abcc6875765bbc4140e diff --git a/rust/crates/truapi-server/src/runtime.rs b/rust/crates/truapi-server/src/runtime.rs index 06e08fe2..c652894b 100644 --- a/rust/crates/truapi-server/src/runtime.rs +++ b/rust/crates/truapi-server/src/runtime.rs @@ -1066,13 +1066,6 @@ impl Account for ProductRuntimeHost { }, )) })?; - if !self.is_product_account_valid_for_caller(&request.account.dot_ns_identifier) { - return Err(CallError::Domain(HostAccountSignVrfError::V1( - v01::HostAccountSignVrfError::Unknown { - reason: "Product account does not belong to the calling product".to_string(), - }, - ))); - } validate_vrf_transcript(&request).map_err(|reason| { CallError::Domain(HostAccountSignVrfError::V1( v01::HostAccountSignVrfError::Unknown { reason }, @@ -2925,7 +2918,7 @@ mod tests { } #[test] - fn sign_vrf_forwards_mobile_sso_request_and_response() { + fn sign_vrf_forwards_cross_product_mobile_sso_request_and_response() { let session = sso_session_info(); let signature = v01::VrfSignature { pre_output: [0x11; 32], @@ -2953,7 +2946,7 @@ mod tests { ); install_pairing_session(&host, session.clone()); let request = v01::HostAccountSignVrfRequest { - account: account_id("myapp.dot", 0), + account: account_id("other-product.dot", 0), transcript_label: b"ctx".to_vec(), items: vec![v01::VrfTranscriptItem { label: b"domain".to_vec(), From da6e75cb5a16dc11832808bfb686f291835c67f5 Mon Sep 17 00:00:00 2001 From: pg Date: Tue, 4 Aug 2026 07:29:59 +0200 Subject: [PATCH 2/7] chore(dotli): advance RFC-0023 compatibility fix --- hosts/dotli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosts/dotli b/hosts/dotli index ad3e326a..f7d518b8 160000 --- a/hosts/dotli +++ b/hosts/dotli @@ -1 +1 @@ -Subproject commit ad3e326a487fb6663c7d0abcc6875765bbc4140e +Subproject commit f7d518b8fb3b6b84dc2c3555fa5940042c1e3224 From ee5192e1aaab65245b2e7bdfa5d6afd82425d19d Mon Sep 17 00:00:00 2001 From: pg Date: Tue, 4 Aug 2026 08:06:09 +0200 Subject: [PATCH 3/7] docs: drop VRF E2E README note --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 03b3d888..b215ee1d 100644 --- a/README.md +++ b/README.md @@ -153,8 +153,6 @@ This repopulates the ignored generated TS under `js/packages/truapi/`, including The full `make e2e-dotli` diagnosis builds and launches the local `truapi-host signing-host` CLI to answer dotli's pairing QR and auto-approve remote signing requests. It does not require the external signer-bot service. -The generated account examples exercise `account.signVrf` through both the -local signing and paired remote-SSO paths. When `HOST_CLI_SIGNER_MNEMONIC` is absent, the CLI manages a reusable isolated test identity under `.e2e-dotli/`. Set `E2E_DOTLI_SIGNING_HOST_BASE_PATH` to use a different state directory while debugging. From 2646831ae679f193aa06337ae99f8b4a926fd68c Mon Sep 17 00:00:00 2001 From: pg Date: Tue, 4 Aug 2026 08:10:39 +0200 Subject: [PATCH 4/7] chore(dotli): require RFC-0023 generated types --- hosts/dotli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosts/dotli b/hosts/dotli index f7d518b8..dc588da1 160000 --- a/hosts/dotli +++ b/hosts/dotli @@ -1 +1 @@ -Subproject commit f7d518b8fb3b6b84dc2c3555fa5940042c1e3224 +Subproject commit dc588da1ea9305dec5d4a0aced054eab6adf7950 From 110f8633833978eee5af6df75637221f4a0aa2d0 Mon Sep 17 00:00:00 2001 From: pg Date: Tue, 4 Aug 2026 08:22:15 +0200 Subject: [PATCH 5/7] chore(dotli): keep VRF prompt on account holder --- hosts/dotli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosts/dotli b/hosts/dotli index dc588da1..de78cd7c 160000 --- a/hosts/dotli +++ b/hosts/dotli @@ -1 +1 @@ -Subproject commit dc588da1ea9305dec5d4a0aced054eab6adf7950 +Subproject commit de78cd7cd1dd58e32d56ea29c72153ea9683bceb From d7bfbddf0ba43808c2a18ea647555672a9a00d1e Mon Sep 17 00:00:00 2001 From: pg Date: Tue, 4 Aug 2026 09:01:48 +0200 Subject: [PATCH 6/7] fix(server): confirm paired VRF signing before SSO --- hosts/dotli | 2 +- rust/crates/truapi-server/src/runtime.rs | 85 +++++++++++++++++++ .../truapi-server/src/runtime/pairing_host.rs | 18 +++- 3 files changed, 103 insertions(+), 2 deletions(-) diff --git a/hosts/dotli b/hosts/dotli index de78cd7c..e7bb586f 160000 --- a/hosts/dotli +++ b/hosts/dotli @@ -1 +1 @@ -Subproject commit de78cd7cd1dd58e32d56ea29c72153ea9683bceb +Subproject commit e7bb586f07f24c8556bcf31ceb996f7ef84f90a6 diff --git a/rust/crates/truapi-server/src/runtime.rs b/rust/crates/truapi-server/src/runtime.rs index c652894b..9a118505 100644 --- a/rust/crates/truapi-server/src/runtime.rs +++ b/rust/crates/truapi-server/src/runtime.rs @@ -2925,6 +2925,7 @@ mod tests { proof: [0x22; 64], }; let platform = Arc::new(StubPlatform { + sign_vrf_confirmed: true, sso_response_script: Some(sso_success_response_script( &session, RemoteMessage { @@ -2961,6 +2962,16 @@ mod tests { .unwrap(); assert_eq!(response, HostAccountSignVrfResponse::V1(signature)); + assert_eq!( + *platform + .sign_vrf_reviews + .lock() + .expect("VRF signing review list mutex poisoned"), + vec![truapi_platform::SignVrfReview { + calling_product_id: "myapp.dot".to_string(), + request: request.clone(), + }] + ); let message = submitted_remote_message(&platform, &session); let RemoteMessageData::V1(v1::RemoteMessage::SignVrfRequest(request_message)) = message.data @@ -2971,6 +2982,73 @@ mod tests { assert_eq!(request_message.payload, request); } + #[test] + fn sign_vrf_rejects_declined_pairing_host_confirmation_before_mobile_sso() { + let session = sso_session_info(); + let platform = Arc::new(StubPlatform { + sso_response_script: Some(sso_success_response_script( + &session, + RemoteMessage { + message_id: "wallet-vrf-declined".to_string(), + data: RemoteMessageData::V1(v1::RemoteMessage::SignVrfResponse( + crate::host_logic::sso::messages::SignVrfResponse { + responding_to: "vrf-declined".to_string(), + payload: Ok(v01::VrfSignature { + pre_output: [0x11; 32], + proof: [0x22; 64], + }), + }, + )), + }, + )), + ..Default::default() + }); + let host = ProductRuntimeHost::new( + platform.clone(), + runtime_config("myapp.dot"), + test_spawner(), + ); + install_pairing_session(&host, session); + let request = v01::HostAccountSignVrfRequest { + account: account_id("other-product.dot", 0), + transcript_label: b"ctx".to_vec(), + items: vec![v01::VrfTranscriptItem { + label: b"domain".to_vec(), + value: vec![1, 2], + }], + }; + + let err = futures::executor::block_on(host.sign_vrf( + &CallContext::with_request_id("vrf-declined".to_string()), + HostAccountSignVrfRequest::V1(request.clone()), + )) + .unwrap_err(); + + assert!(matches!( + err, + CallError::Domain(HostAccountSignVrfError::V1( + v01::HostAccountSignVrfError::Rejected + )) + )); + assert_eq!( + *platform + .sign_vrf_reviews + .lock() + .expect("VRF signing review list mutex poisoned"), + vec![truapi_platform::SignVrfReview { + calling_product_id: "myapp.dot".to_string(), + request, + }] + ); + assert!( + platform + .sent_rpc + .lock() + .expect("RPC request list mutex poisoned") + .is_empty() + ); + } + #[test] fn sign_vrf_rejects_oversized_transcript_before_sso() { let host = @@ -4530,6 +4608,13 @@ mod tests { )) .expect("persisted AutoSigning key signs locally"); let HostAccountSignVrfResponse::V1(signature) = response; + assert!( + platform + .sign_vrf_reviews + .lock() + .expect("VRF signing review list mutex poisoned") + .is_empty() + ); let keypair = crate::host_logic::product_account::derive_product_keypair( &root, diff --git a/rust/crates/truapi-server/src/runtime/pairing_host.rs b/rust/crates/truapi-server/src/runtime/pairing_host.rs index 518af880..6ffafe94 100644 --- a/rust/crates/truapi-server/src/runtime/pairing_host.rs +++ b/rust/crates/truapi-server/src/runtime/pairing_host.rs @@ -40,7 +40,10 @@ use futures::StreamExt; use tracing::{instrument, warn}; use truapi::versioned::account::{HostRequestLoginError, HostRequestLoginResponse}; use truapi::{CallContext, CallError, v01}; -use truapi_platform::{CoreStorageKey, PairingHostConfig, Platform, ProductContext}; +use truapi_platform::{ + CoreStorageKey, PairingHostConfig, Platform, ProductContext, SignVrfReview, + UserConfirmationReview, +}; /// Distinguishes all remote authority request entrypoints by wire label. #[derive(Clone, Copy, Debug, derive_more::Display)] @@ -888,6 +891,19 @@ impl PairingHost { ); return Ok(v01::VrfSignature { pre_output, proof }); } + let confirmed = self + .platform + .confirm_user_action(UserConfirmationReview::SignVrf(SignVrfReview { + calling_product_id: calling_product_id.clone(), + request: request.clone(), + })) + .await + .map_err(|err| AuthorityError::Unknown { + reason: format!("VRF signing confirmation failed: {err:?}"), + })?; + if !confirmed { + return Err(AuthorityError::Rejected); + } self.remote_sign_vrf(cx, &session, calling_product_id, request) .await } From d051c9e394af1ae75fb3bdec1cbfef57839fab0a Mon Sep 17 00:00:00 2001 From: pg Date: Tue, 4 Aug 2026 09:41:51 +0200 Subject: [PATCH 7/7] release: @parity/truapi 0.7.0, @parity/truapi-host 0.4.0 --- Cargo.lock | 2 +- js/packages/truapi-host/CHANGELOG.md | 14 ++++++++++++++ js/packages/truapi-host/package.json | 4 ++-- js/packages/truapi/CHANGELOG.md | 7 +++++++ js/packages/truapi/package.json | 2 +- package-lock.json | 6 +++--- rust/crates/truapi/Cargo.toml | 2 +- 7 files changed, 29 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0e38dbf4..6477ad80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4967,7 +4967,7 @@ dependencies = [ [[package]] name = "truapi" -version = "0.6.0" +version = "0.7.0" dependencies = [ "async-trait", "derive_more 2.1.1", diff --git a/js/packages/truapi-host/CHANGELOG.md b/js/packages/truapi-host/CHANGELOG.md index ae23bb37..7afae724 100644 --- a/js/packages/truapi-host/CHANGELOG.md +++ b/js/packages/truapi-host/CHANGELOG.md @@ -1,5 +1,19 @@ # @parity/truapi-host +## 0.4.0 + +### Minor Changes + +- Publish the RFC-0022 mobile host cutover and completed RFC-0023 account VRF + signing runtime. Pairing hosts persist product-scoped AutoSigning keys, sign + matching same-product requests locally, and require structured host and + Account Holder confirmations before forwarding every other request. + +### Patch Changes + +- Updated dependencies + - @parity/truapi@0.7.0 + ## 0.3.0 ### Minor Changes diff --git a/js/packages/truapi-host/package.json b/js/packages/truapi-host/package.json index 01d5f6dd..7d2792c3 100644 --- a/js/packages/truapi-host/package.json +++ b/js/packages/truapi-host/package.json @@ -1,6 +1,6 @@ { "name": "@parity/truapi-host", - "version": "0.3.0", + "version": "0.4.0", "description": "WASM-backed TrUAPI host runtime: embeds the Rust core, with web iframe and Web Worker entry points", "license": "MIT", "author": "Parity Technologies ", @@ -49,7 +49,7 @@ "test": "bun test" }, "dependencies": { - "@parity/truapi": "^0.6.0" + "@parity/truapi": "^0.7.0" }, "devDependencies": { "@types/bun": "^1.3.0", diff --git a/js/packages/truapi/CHANGELOG.md b/js/packages/truapi/CHANGELOG.md index 7e643411..965f7539 100644 --- a/js/packages/truapi/CHANGELOG.md +++ b/js/packages/truapi/CHANGELOG.md @@ -1,5 +1,12 @@ # @parity/truapi +## 0.7.0 + +### Minor Changes + +- Publish the package version paired with the RFC-0022 mobile host cutover and + the completed RFC-0023 account VRF signing flow. + ## 0.6.0 ### Minor Changes diff --git a/js/packages/truapi/package.json b/js/packages/truapi/package.json index 33393ec0..ffd18f27 100644 --- a/js/packages/truapi/package.json +++ b/js/packages/truapi/package.json @@ -1,6 +1,6 @@ { "name": "@parity/truapi", - "version": "0.6.0", + "version": "0.7.0", "description": "TrUAPI TypeScript transport, SCALE codecs, and generated API client", "license": "MIT", "author": "Parity Technologies ", diff --git a/package-lock.json b/package-lock.json index 9b8730ba..920b8a36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ }, "js/packages/truapi": { "name": "@parity/truapi", - "version": "0.6.0", + "version": "0.7.0", "license": "MIT", "dependencies": { "@noble/hashes": "^2.2.0", @@ -32,10 +32,10 @@ }, "js/packages/truapi-host": { "name": "@parity/truapi-host", - "version": "0.3.0", + "version": "0.4.0", "license": "MIT", "dependencies": { - "@parity/truapi": "^0.6.0" + "@parity/truapi": "^0.7.0" }, "devDependencies": { "@types/bun": "^1.3.0", diff --git a/rust/crates/truapi/Cargo.toml b/rust/crates/truapi/Cargo.toml index a771335f..24fdc573 100644 --- a/rust/crates/truapi/Cargo.toml +++ b/rust/crates/truapi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "truapi" -version = "0.6.0" +version = "0.7.0" edition.workspace = true license.workspace = true description = "TrUAPI trait and type definitions"