@textrp/briij-js-sdk 43.2.1 → 44.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +24 -1
  2. package/README.md +66 -0
  3. package/lib/@types/auth.d.ts +38 -0
  4. package/lib/@types/auth.d.ts.map +1 -1
  5. package/lib/@types/auth.js.map +1 -1
  6. package/lib/@types/snarkjs.d.js +0 -0
  7. package/lib/@types/snarkjs.d.js.map +1 -0
  8. package/lib/auth/credential.d.ts +25 -0
  9. package/lib/auth/credential.d.ts.map +1 -0
  10. package/lib/auth/credential.js +48 -0
  11. package/lib/auth/credential.js.map +1 -0
  12. package/lib/auth/did.d.ts +22 -0
  13. package/lib/auth/did.d.ts.map +1 -0
  14. package/lib/auth/did.js +66 -0
  15. package/lib/auth/did.js.map +1 -0
  16. package/lib/auth/wallet.d.ts +22 -0
  17. package/lib/auth/wallet.d.ts.map +1 -0
  18. package/lib/auth/wallet.js +27 -0
  19. package/lib/auth/wallet.js.map +1 -0
  20. package/lib/auth/zkpE2EE.d.ts +22 -0
  21. package/lib/auth/zkpE2EE.d.ts.map +1 -0
  22. package/lib/auth/zkpE2EE.js +69 -0
  23. package/lib/auth/zkpE2EE.js.map +1 -0
  24. package/lib/briij.d.ts +5 -0
  25. package/lib/briij.d.ts.map +1 -1
  26. package/lib/briij.js +5 -0
  27. package/lib/briij.js.map +1 -1
  28. package/lib/client.d.ts +40 -1
  29. package/lib/client.d.ts.map +1 -1
  30. package/lib/client.js +259 -113
  31. package/lib/client.js.map +1 -1
  32. package/lib/xrpl/clientSignedBinding.d.ts +21 -0
  33. package/lib/xrpl/clientSignedBinding.d.ts.map +1 -0
  34. package/lib/xrpl/clientSignedBinding.js +112 -0
  35. package/lib/xrpl/clientSignedBinding.js.map +1 -0
  36. package/package.json +3 -1
  37. package/src/@types/auth.ts +44 -0
  38. package/src/@types/snarkjs.d.ts +17 -0
  39. package/src/auth/credential.ts +63 -0
  40. package/src/auth/did.ts +89 -0
  41. package/src/auth/wallet.ts +50 -0
  42. package/src/auth/zkpE2EE.ts +88 -0
  43. package/src/briij.ts +13 -0
  44. package/src/client.ts +187 -0
  45. package/src/components/LoginStepper.tsx +50 -0
  46. package/src/xrpl/clientSignedBinding.ts +136 -0
package/src/client.ts CHANGED
@@ -204,6 +204,11 @@ import {
204
204
  } from "./webrtc/groupCall.ts";
205
205
  import { MediaHandler } from "./webrtc/mediaHandler.ts";
206
206
  import {
207
+ type CredentialCreateResult,
208
+ type CredentialVerifyResult,
209
+ type DidCredentialMetadata,
210
+ type DidResolutionResult,
211
+ type ZkpVerifyResult,
207
212
  type ILoginFlowsResponse,
208
213
  type IRefreshTokenResponse,
209
214
  type LoginRequest,
@@ -220,6 +225,15 @@ import {
220
225
  type XrplAuthCompleteRequest,
221
226
  type XrplWalletChallengePayload,
222
227
  } from "./@types/auth.ts";
228
+ import { createMinimalDidDocument, deriveXrplDid, resolveDidViaHomeserver } from "./auth/did.ts";
229
+ import {
230
+ loadDidCredentialMetadata,
231
+ requestCredentialCreate,
232
+ storeDidCredentialMetadata,
233
+ verifyCredential,
234
+ } from "./auth/credential.ts";
235
+ import { generateE2eeZkProof } from "./auth/zkpE2EE.ts";
236
+ import { buildWalletLoginSubmission, type WalletProofProvider, type WalletProofResult } from "./auth/wallet.ts";
223
237
  import { TypedEventEmitter } from "./models/typed-event-emitter.ts";
224
238
  import { MAIN_ROOM_TIMELINE, ReceiptType } from "./@types/read_receipts.ts";
225
239
  import { type MSC3575SlidingSyncRequest, type MSC3575SlidingSyncResponse, type SlidingSync } from "./sliding-sync.ts";
@@ -6997,6 +7011,179 @@ export class BriijClient extends TypedEventEmitter<EmittedEvents, ClientEventHan
6997
7011
  });
6998
7012
  }
6999
7013
 
7014
+ /**
7015
+ * XRPL DID + credential login flow:
7016
+ * 1) challenge request, 2) wallet proof submission, 3) DID resolve, 4) credential request.
7017
+ */
7018
+ public async loginWithXrplDidCredential(params: {
7019
+ address: string;
7020
+ username?: string;
7021
+ network?: string;
7022
+ didNetwork?: "testnet" | "mainnet";
7023
+ e2eePubkeyCommitment?: string;
7024
+ longevityMode?: boolean;
7025
+ walletProofProvider: WalletProofProvider;
7026
+ zkpLongevityMode?: {
7027
+ enabled: boolean;
7028
+ e2eePrivateKey?: string;
7029
+ strict?: boolean;
7030
+ generateProof?: (context: {
7031
+ didUri: string;
7032
+ xrplAddress: string;
7033
+ credentialId: string;
7034
+ e2eePubkeyCommitment: string;
7035
+ }) => Promise<{ proof: Record<string, unknown>; publicSignals: string[] | Record<string, string> }>;
7036
+ };
7037
+ }): Promise<
7038
+ LoginResponse & {
7039
+ did: DidResolutionResult;
7040
+ didDocument: ReturnType<typeof createMinimalDidDocument>;
7041
+ credential: CredentialCreateResult;
7042
+ credentialVerification: CredentialVerifyResult;
7043
+ metadata: DidCredentialMetadata;
7044
+ zkp?: ZkpVerifyResult;
7045
+ }
7046
+ > {
7047
+ const network = params.network ?? "xrpl";
7048
+ const didNetwork = params.didNetwork ?? "testnet";
7049
+
7050
+ const challenge = await this.getXrplAuthChallenge({
7051
+ address: params.address,
7052
+ network,
7053
+ username: params.username,
7054
+ preferred_localpart: params.username,
7055
+ });
7056
+
7057
+ const walletProof: WalletProofResult = await params.walletProofProvider(challenge.challenge, network);
7058
+ if (walletProof.address !== params.address) {
7059
+ throw new Error("Wallet proof address mismatch");
7060
+ }
7061
+
7062
+ const loginResponse = await this.completeXrplAuth(
7063
+ buildWalletLoginSubmission(walletProof, challenge.session, network, params.username),
7064
+ );
7065
+ await this.applyLoginResponse(loginResponse, XRPL_WALLET_LOGIN_TYPE);
7066
+
7067
+ const didUri = deriveXrplDid(walletProof.address, didNetwork);
7068
+ const commitment = params.e2eePubkeyCommitment ?? (await this.createE2eeCommitment(loginResponse.user_id));
7069
+ const didDocument = createMinimalDidDocument(didUri, commitment);
7070
+
7071
+ const did = await resolveDidViaHomeserver(async (path: string) => {
7072
+ return this.http.authedRequest<DidResolutionResult>(Method.Get, path);
7073
+ }, walletProof.address);
7074
+
7075
+ const credential = await requestCredentialCreate(
7076
+ async (path, method, body) => this.http.authedRequest<CredentialCreateResult>(Method.Post, path, undefined, body),
7077
+ {
7078
+ subject: walletProof.address,
7079
+ did_uri: did.did_uri ?? didUri,
7080
+ e2ee_pubkey_commitment: commitment,
7081
+ },
7082
+ );
7083
+
7084
+ const credentialVerification = await verifyCredential(
7085
+ async (path, method, body) =>
7086
+ this.http.authedRequest<CredentialVerifyResult>(Method.Post, path, undefined, body),
7087
+ credential.credential_id,
7088
+ );
7089
+
7090
+ const zkpModeEnabled = params.longevityMode ?? params.zkpLongevityMode?.enabled ?? false;
7091
+ let zkp: ZkpVerifyResult | undefined;
7092
+ if (zkpModeEnabled) {
7093
+ try {
7094
+ const generatedProof =
7095
+ (await params.zkpLongevityMode?.generateProof?.({
7096
+ didUri: did.did_uri ?? didUri,
7097
+ xrplAddress: walletProof.address,
7098
+ credentialId: credential.credential_id,
7099
+ e2eePubkeyCommitment: commitment,
7100
+ })) ??
7101
+ (await generateE2eeZkProof(
7102
+ {
7103
+ didUri: did.did_uri ?? didUri,
7104
+ xrplAddress: walletProof.address,
7105
+ credentialId: credential.credential_id,
7106
+ e2eePrivateKey: params.zkpLongevityMode?.e2eePrivateKey ?? commitment,
7107
+ },
7108
+ {
7109
+ wasmPath: "/circuits/e2ee_credential.wasm",
7110
+ zkeyPath: "/circuits/e2ee_credential.zkey",
7111
+ },
7112
+ ));
7113
+
7114
+ zkp = await this.http.authedRequest<ZkpVerifyResult>(Method.Post, "/zkp/verify", undefined, {
7115
+ proof: generatedProof.proof,
7116
+ public_signals: generatedProof.publicSignals,
7117
+ e2ee_pubkey_commitment: commitment,
7118
+ });
7119
+ } catch (error) {
7120
+ if (params.zkpLongevityMode?.strict) {
7121
+ throw error;
7122
+ }
7123
+ zkp = { valid: false, reason: "zkp_verification_skipped" };
7124
+ }
7125
+ }
7126
+
7127
+ const metadata: DidCredentialMetadata = {
7128
+ didUri: did.did_uri ?? didUri,
7129
+ credentialId: credential.credential_id,
7130
+ issuedAt: Date.now(),
7131
+ };
7132
+ storeDidCredentialMetadata(loginResponse.user_id, metadata);
7133
+ await this.persistDidDeviceBinding({
7134
+ didUri: metadata.didUri,
7135
+ credentialId: metadata.credentialId,
7136
+ e2eePubkeyCommitment: commitment,
7137
+ xrplAddress: walletProof.address,
7138
+ network,
7139
+ });
7140
+
7141
+ return {
7142
+ ...loginResponse,
7143
+ did,
7144
+ didDocument,
7145
+ credential,
7146
+ credentialVerification,
7147
+ metadata,
7148
+ zkp,
7149
+ };
7150
+ }
7151
+
7152
+ public getStoredDidCredentialMetadata(userId: string): DidCredentialMetadata | null {
7153
+ return loadDidCredentialMetadata(userId);
7154
+ }
7155
+
7156
+ private async createE2eeCommitment(seed: string): Promise<string> {
7157
+ if (!globalThis.crypto?.subtle) {
7158
+ return encodeUnpaddedBase64Url(new TextEncoder().encode(seed));
7159
+ }
7160
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(seed));
7161
+ return Array.from(new Uint8Array(digest))
7162
+ .map((b) => b.toString(16).padStart(2, "0"))
7163
+ .join("");
7164
+ }
7165
+
7166
+ private async persistDidDeviceBinding(binding: {
7167
+ didUri: string;
7168
+ credentialId: string;
7169
+ e2eePubkeyCommitment: string;
7170
+ xrplAddress: string;
7171
+ network: string;
7172
+ }): Promise<void> {
7173
+ try {
7174
+ await this.setAccountData(WALLET_IDENTITY_ACCOUNT_DATA_TYPE, {
7175
+ chain_id: "xrpl",
7176
+ account_id: binding.xrplAddress,
7177
+ network: binding.network,
7178
+ did_uri: binding.didUri,
7179
+ credential_id: binding.credentialId,
7180
+ e2ee_pubkey_commitment: binding.e2eePubkeyCommitment,
7181
+ });
7182
+ } catch (error) {
7183
+ logger.warn("Failed to persist DID/E2EE device binding metadata", error);
7184
+ }
7185
+ }
7186
+
7000
7187
  /**
7001
7188
  * Store a chain-agnostic wallet recovery envelope in account data.
7002
7189
  *
@@ -0,0 +1,50 @@
1
+ /*
2
+ Copyright 2026 Xurge Digital Lab
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+ */
10
+
11
+ /* Mermaid flow reference:
12
+ flowchart LR
13
+ wallet[Connect Wallet] --> login[Submit wallet proof to /login]
14
+ login --> did[Resolve/Create DID]
15
+ did --> cred[Request CredentialCreate]
16
+ cred --> done[Persist DID + credential metadata]
17
+ */
18
+
19
+ export interface LoginStepperProps {
20
+ currentStep: 1 | 2 | 3 | 4 | 5;
21
+ longevityModeEnabled?: boolean;
22
+ }
23
+
24
+ /**
25
+ * Lightweight JSX component for apps consuming the SDK.
26
+ */
27
+ export function LoginStepper({ currentStep, longevityModeEnabled = false }: LoginStepperProps): string {
28
+ const longevityToggle = `${longevityModeEnabled ? "[x]" : "[ ]"} Enable Longevity Mode (ZKP)`;
29
+ const warning = longevityModeEnabled
30
+ ? "ZKP path enabled: strongest DID-bound E2EE continuity."
31
+ : "Warning: continuing without ZKP uses wallet-signature fallback only.";
32
+ const steps = [
33
+ "1. Wallet Proof",
34
+ "2. DID Resolve/Create",
35
+ "3. Credential Create",
36
+ "4. Persist Metadata",
37
+ "5. ZKP Longevity Verify (optional)",
38
+ ];
39
+ const renderedSteps = steps
40
+ .map((step, idx) => {
41
+ if (idx === 4 && !longevityModeEnabled) {
42
+ return `[ ] ${step}`;
43
+ }
44
+ const active = idx + 1 <= currentStep ? "[x]" : "[ ]";
45
+ return `${active} ${step}`;
46
+ })
47
+ .join("\n");
48
+
49
+ return `${longevityToggle}\n${warning}\n${renderedSteps}`;
50
+ }
@@ -0,0 +1,136 @@
1
+ /*
2
+ Copyright 2026 The Matrix.org Foundation C.I.C.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { logger } from "../logger.ts";
18
+
19
+ export interface XrplClientSignedWalletAdapter {
20
+ getAddress(): Promise<string>;
21
+ signAndSubmit(tx: Record<string, unknown>): Promise<{
22
+ hash: string;
23
+ txBlob?: string;
24
+ signedTxBlob?: string;
25
+ }>;
26
+ }
27
+
28
+ export interface XrplClientSignedBindingConfig {
29
+ homeserverBaseUrl: string;
30
+ accessToken: string;
31
+ wallet: XrplClientSignedWalletAdapter;
32
+ network?: "xrpl" | "xahau";
33
+ }
34
+
35
+ export interface XrplClientSignedBindingResult {
36
+ did: Record<string, unknown>;
37
+ credential: Record<string, unknown>;
38
+ }
39
+
40
+ let bindingConfig: Partial<XrplClientSignedBindingConfig> = {};
41
+
42
+ export function configureXrplClientSignedBinding(config: Partial<XrplClientSignedBindingConfig>): void {
43
+ bindingConfig = {
44
+ ...bindingConfig,
45
+ ...config,
46
+ };
47
+ }
48
+
49
+ export async function runClientSignedDidCredentialBinding(
50
+ e2eePubkeyCommitment: string,
51
+ ): Promise<XrplClientSignedBindingResult> {
52
+ const { homeserverBaseUrl, accessToken, wallet } = bindingConfig;
53
+ const network = bindingConfig.network ?? "xrpl";
54
+ if (!homeserverBaseUrl || !accessToken || !wallet) {
55
+ throw new Error("XRPL client-signed binding is not configured");
56
+ }
57
+
58
+ const xrplAddress = await wallet.getAddress();
59
+ const didPrepare = await postJson(homeserverBaseUrl, accessToken, "/_matrix/client/v3/did/prepare", {
60
+ xrpl_address: xrplAddress,
61
+ network,
62
+ e2ee_pubkey_commitment: e2eePubkeyCommitment,
63
+ });
64
+ const didSignResult = await wallet.signAndSubmit(asObject(didPrepare.unsigned_tx, "did unsigned_tx"));
65
+ const didTxBlob = didSignResult.txBlob ?? didSignResult.signedTxBlob;
66
+ if (!didTxBlob) {
67
+ throw new Error("Wallet adapter must return txBlob/signedTxBlob for DID finalize");
68
+ }
69
+ const didFinalize = await postJson(homeserverBaseUrl, accessToken, "/_matrix/client/v3/did/finalize", {
70
+ xrpl_address: xrplAddress,
71
+ network,
72
+ session: didPrepare.session,
73
+ tx_blob: didTxBlob,
74
+ tx_hash: didSignResult.hash,
75
+ });
76
+
77
+ const credentialPrepare = await postJson(homeserverBaseUrl, accessToken, "/_matrix/client/v3/credential/prepare", {
78
+ xrpl_address: xrplAddress,
79
+ network,
80
+ e2ee_pubkey_commitment: e2eePubkeyCommitment,
81
+ });
82
+ const credentialSignResult = await wallet.signAndSubmit(
83
+ asObject(credentialPrepare.unsigned_tx, "credential unsigned_tx"),
84
+ );
85
+ const credentialTxBlob = credentialSignResult.txBlob ?? credentialSignResult.signedTxBlob;
86
+ if (!credentialTxBlob) {
87
+ throw new Error("Wallet adapter must return txBlob/signedTxBlob for credential finalize");
88
+ }
89
+ const credentialFinalize = await postJson(
90
+ homeserverBaseUrl,
91
+ accessToken,
92
+ "/_matrix/client/v3/credential/finalize",
93
+ {
94
+ xrpl_address: xrplAddress,
95
+ network,
96
+ session: credentialPrepare.session,
97
+ tx_blob: credentialTxBlob,
98
+ tx_hash: credentialSignResult.hash,
99
+ },
100
+ );
101
+
102
+ logger.info("Completed client-signed DID/Credential binding for %s", xrplAddress);
103
+ return {
104
+ did: didFinalize,
105
+ credential: credentialFinalize,
106
+ };
107
+ }
108
+
109
+ function asObject(value: unknown, name: string): Record<string, unknown> {
110
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
111
+ throw new Error(`Invalid ${name} payload from homeserver`);
112
+ }
113
+ return value as Record<string, unknown>;
114
+ }
115
+
116
+ async function postJson(
117
+ homeserverBaseUrl: string,
118
+ accessToken: string,
119
+ path: string,
120
+ body: Record<string, unknown>,
121
+ ): Promise<Record<string, unknown>> {
122
+ const response = await fetch(`${homeserverBaseUrl.replace(/\/+$/, "")}${path}`, {
123
+ method: "POST",
124
+ headers: {
125
+ Authorization: `Bearer ${accessToken}`,
126
+ "Content-Type": "application/json",
127
+ },
128
+ body: JSON.stringify(body),
129
+ });
130
+ const payload = (await response.json().catch(() => ({}))) as Record<string, unknown>;
131
+ if (!response.ok) {
132
+ const message = typeof payload.error === "string" ? payload.error : `HTTP ${response.status}`;
133
+ throw new Error(`XRPL client-signed request failed (${path}): ${message}`);
134
+ }
135
+ return payload;
136
+ }