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/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..e7bb586f 160000 --- a/hosts/dotli +++ b/hosts/dotli @@ -1 +1 @@ -Subproject commit 7921ce413a4a1661b36c3f5032d91287a8f160bf +Subproject commit e7bb586f07f24c8556bcf31ceb996f7ef84f90a6 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-server/src/runtime.rs b/rust/crates/truapi-server/src/runtime.rs index 06e08fe2..9a118505 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,13 +2918,14 @@ 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], proof: [0x22; 64], }; let platform = Arc::new(StubPlatform { + sign_vrf_confirmed: true, sso_response_script: Some(sso_success_response_script( &session, RemoteMessage { @@ -2953,7 +2947,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(), @@ -2968,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 @@ -2978,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 = @@ -4537,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 } 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"