Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ generated from [Conventional Commits](https://www.conventionalcommits.org/).

### Added

- Add `account.productDeviceChat` for host-private Chat v2 identity binding and
identity-route sealing/opening through local or paired account authorities,
guarded by a dedicated, product-scoped Chat-authority permission.
- Generate a transport-neutral `no_std` Rust client with typed request,
subscription, result-subscription, and host-initiated Worker subscription
codecs.
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ requests after a bounded deadline; pass `requestTimeoutMs` to `createTransport`

See [`js/packages/truapi/README.md`](js/packages/truapi/README.md) for the full client reference.

`account.productDeviceChat` binds a product-derived account to the connected
wallet's Chat v2 identity and seals or opens identity-route payloads without
exposing the wallet's X25519 private key. Browser pairing hosts forward the
operation over encrypted SSO; signing hosts require the calling product's
dedicated Chat-authority permission before using local wallet material.

## Repository layout

```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public enum ProductPermission: Equatable, Sendable {
public static let balanceAccessTypeName = "balance_access"
public static let statementSubmitAccessTypeName = "statement_submit"
public static let userIdentityAccessTypeName = "user_identity_access"
public static let chatAuthorityTypeName = "chat_authority"

case deviceCapability(DeviceCapabilityType)
case networkAccess(domain: String)
Expand All @@ -23,6 +24,7 @@ public enum ProductPermission: Equatable, Sendable {
case preimageSubmitAccess
case statementSubmitAccess
case userIdentityAccess
case chatAuthority

public var typeName: String {
switch self {
Expand All @@ -44,6 +46,8 @@ public enum ProductPermission: Equatable, Sendable {
Self.statementSubmitAccessTypeName
case .userIdentityAccess:
Self.userIdentityAccessTypeName
case .chatAuthority:
Self.chatAuthorityTypeName
}
}

Expand All @@ -60,7 +64,8 @@ public enum ProductPermission: Equatable, Sendable {
.chainSubmitAccess,
.preimageSubmitAccess,
.statementSubmitAccess,
.userIdentityAccess:
.userIdentityAccess,
.chatAuthority:
""
}
}
Expand Down Expand Up @@ -88,6 +93,8 @@ public enum ProductPermission: Equatable, Sendable {
return .statementSubmitAccess
case userIdentityAccessTypeName:
return .userIdentityAccess
case chatAuthorityTypeName:
return .chatAuthority
default:
return nil
}
Expand Down
51 changes: 51 additions & 0 deletions hosts/ios/polkadot-app/Localization/Products.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,23 @@
}
}
},
"app.permission.chatAuthority.title": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Chat identity authority"
}
},
"es-ES": {
"stringUnit": {
"state": "translated",
"value": "Autoridad de identidad de Chat"
}
}
}
},
"app.permission.userIdentity.title": {
"extractionState": "manual",
"localizations": {
Expand Down Expand Up @@ -647,6 +664,23 @@
}
}
},
"permission.body.chatAuthority": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Allows this product to bind its device account to your wallet Chat identity and encrypt or decrypt Chat routing data."
}
},
"es-ES": {
"stringUnit": {
"state": "translated",
"value": "Permite que este producto vincule su cuenta de dispositivo a la identidad de Chat de tu cartera y cifre o descifre los datos de enrutamiento de Chat."
}
}
}
},
"permission.body.userIdentityAccess": {
"extractionState": "manual",
"localizations": {
Expand Down Expand Up @@ -1004,6 +1038,23 @@
}
}
},
"permission.title.chatAuthority": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "%1$(productId)@ would like to use your Chat identity authority"
}
},
"es-ES": {
"stringUnit": {
"state": "translated",
"value": "%1$(productId)@ quiere usar tu autoridad de identidad de Chat"
}
}
}
},
"permission.title.deviceCapability": {
"extractionState": "manual",
"localizations": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,12 @@ private extension ProductPermissionPromptViewFactory {
body: String(localized: .Products.permissionBodyUserIdentityAccess),
icon: makeIcon(systemName: "person.text.rectangle")
)
case .chatAuthority:
PromptContent(
title: String(localized: .Products.permissionTitleChatAuthority(productId: productId)),
body: String(localized: .Products.permissionBodyChatAuthority),
icon: makeIcon(systemName: "message.badge.shield")
)
}
}

Expand Down Expand Up @@ -188,6 +194,8 @@ private extension ProductPermissionPromptViewFactory {
)
case .userIdentityAccess:
"- " + String(localized: .Products.permissionBodyUserIdentityAccess)
case .chatAuthority:
"- " + String(localized: .Products.permissionBodyChatAuthority)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ struct TrUAPIAllowanceRequest: Equatable {
/// and the statement-sign prompt.
protocol TrUAPIReviewPromptMapping: Sendable {
func makePermissionRequest(from review: IdentityDisclosureReview) -> TrUAPIPermissionRequest
func makePermissionRequest(from review: ChatAuthorityReview) -> TrUAPIPermissionRequest
func makePermissionRequest(from review: PreimageSubmitReview) -> TrUAPIPermissionRequest
func makePermissionRequest(from review: AccountAccessReview) -> TrUAPIPermissionRequest
func makePermissionRequest(from review: ProductSubtreeReview) -> TrUAPIPermissionRequest
Expand All @@ -40,6 +41,13 @@ struct TrUAPIReviewPromptMapper: TrUAPIReviewPromptMapping {
)
}

func makePermissionRequest(from review: ChatAuthorityReview) -> TrUAPIPermissionRequest {
TrUAPIPermissionRequest(
productId: review.productId,
permissions: [.chatAuthority]
)
}

/// `PreimageSubmitReview` carries no product identity: the submit is
/// host-mediated, so the prompt is raised without a product scope.
func makePermissionRequest(from _: PreimageSubmitReview) -> TrUAPIPermissionRequest {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ private extension TrUAPIConfirmationPresenter {
await confirmPermission(
promptMapper.makePermissionRequest(from: identityReview)
)
case let .chatAuthority(chatReview):
await confirmPermission(
promptMapper.makePermissionRequest(from: chatReview)
)
case let .preimageSubmit(preimageReview):
await confirmPermission(
promptMapper.makePermissionRequest(from: preimageReview)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ private extension AppPermissionsViewModelFactory {
String(localized: .Products.appPermissionUserIdentityTitle),
String(localized: .Products.permissionBodyUserIdentityAccess)
)
case .chatAuthority:
(
String(localized: .Products.appPermissionChatAuthorityTitle),
String(localized: .Products.permissionBodyChatAuthority)
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ struct TrUAPIReviewPromptMapperTests {
))
}

@Test
func mapsChatAuthorityToDedicatedPermission() {
let request = mapper.makePermissionRequest(
from: ChatAuthorityReview(productId: "chat.dot")
)

#expect(request == TrUAPIPermissionRequest(
productId: "chat.dot",
permissions: [.chatAuthority]
))
}

@Test
func mapsPreimageSubmitToHostProductPermission() {
let request = mapper.makePermissionRequest(from: PreimageSubmitReview(size: 1_024))
Expand Down
34 changes: 33 additions & 1 deletion rust/crates/truapi-client/src/generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use super::*;

/// Fingerprint of the generated wire contract.
pub const TRUAPI_WIRE_SCHEMA_HASH: &str = "0449982638d57658";
pub const TRUAPI_WIRE_SCHEMA_HASH: &str = "0e49a2f7d93138a3";

/// `account_connection_status_subscribe` method marker.
pub struct AccountConnectionStatusSubscribe;
Expand Down Expand Up @@ -239,6 +239,35 @@ impl RequestMethod for AccountRingVrfSign {
const DESCRIPTOR: MethodDescriptor = Self::DESCRIPTOR;
}

/// `account_product_device_chat` method marker.
pub struct AccountProductDeviceChat;
impl AccountProductDeviceChat {
/// Canonical metadata and frame ids for this method.
pub const DESCRIPTOR: MethodDescriptor = MethodDescriptor {
service: "Account",
method: "product_device_chat",
wire_name: "account_product_device_chat",
request_type: "truapi::versioned::account::HostProductDeviceChatRequest",
response_type: "truapi::versioned::account::HostProductDeviceChatResponse",
error_type: Some("truapi::versioned::account::HostProductDeviceChatError"),
kind: MethodKind::Request,
direction: Direction::ProductToHost,
required_execution: None,
sensitive: true,
wire: MethodWire::Request(RequestFrameIds {
request_id: 174,
response_id: 175,
}),
};
}
impl RequestMethod for AccountProductDeviceChat {
type Request = truapi::versioned::account::HostProductDeviceChatRequest;
type Response = truapi::versioned::account::HostProductDeviceChatResponse;
type Error = truapi::versioned::account::HostProductDeviceChatError;
const RESPONSE_VERSIONED: bool = true;
const DESCRIPTOR: MethodDescriptor = Self::DESCRIPTOR;
}

/// `account_get_legacy_accounts` method marker.
pub struct AccountGetLegacyAccounts;
impl AccountGetLegacyAccounts {
Expand Down Expand Up @@ -2121,6 +2150,7 @@ pub const APP_METHODS: &[MethodDescriptor] = &[
AccountRegisterRingVrfKey::DESCRIPTOR,
AccountListRingVrfKeys::DESCRIPTOR,
AccountRingVrfSign::DESCRIPTOR,
AccountProductDeviceChat::DESCRIPTOR,
AccountGetLegacyAccounts::DESCRIPTOR,
AccountGetUserId::DESCRIPTOR,
AccountRequestLogin::DESCRIPTOR,
Expand Down Expand Up @@ -2191,6 +2221,7 @@ pub const WIDGET_METHODS: &[MethodDescriptor] = &[
AccountRegisterRingVrfKey::DESCRIPTOR,
AccountListRingVrfKeys::DESCRIPTOR,
AccountRingVrfSign::DESCRIPTOR,
AccountProductDeviceChat::DESCRIPTOR,
AccountGetLegacyAccounts::DESCRIPTOR,
AccountGetUserId::DESCRIPTOR,
AccountRequestLogin::DESCRIPTOR,
Expand Down Expand Up @@ -2261,6 +2292,7 @@ pub const WORKER_METHODS: &[MethodDescriptor] = &[
AccountRegisterRingVrfKey::DESCRIPTOR,
AccountListRingVrfKeys::DESCRIPTOR,
AccountRingVrfSign::DESCRIPTOR,
AccountProductDeviceChat::DESCRIPTOR,
AccountGetLegacyAccounts::DESCRIPTOR,
AccountGetUserId::DESCRIPTOR,
AccountRequestLogin::DESCRIPTOR,
Expand Down
19 changes: 15 additions & 4 deletions rust/crates/truapi-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,10 +457,21 @@ mod tests {

#[test]
fn catalogs_partition_worker_only_chat() {
assert_eq!(APP_METHODS.len(), 66);
assert_eq!(WIDGET_METHODS.len(), 66);
assert_eq!(WORKER_METHODS.len(), 72);
assert_eq!(WORKER_ONLY_METHODS.len(), 6);
assert_eq!(APP_METHODS, WIDGET_METHODS);
assert_eq!(
WORKER_METHODS.len(),
APP_METHODS.len() + WORKER_ONLY_METHODS.len()
);
assert!(
APP_METHODS
.iter()
.all(|method| WORKER_METHODS.contains(method))
);
assert!(
WORKER_ONLY_METHODS
.iter()
.all(|method| WORKER_METHODS.contains(method) && !APP_METHODS.contains(method))
);
assert!(WORKER_ONLY_METHODS.iter().all(|method| {
method.service == "Chat" && method.required_execution == Some(ExecutionKind::Worker)
}));
Expand Down
39 changes: 39 additions & 0 deletions rust/crates/truapi-codegen/tests/golden/dispatcher.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading