@oxy.so/core 1.5.1 → 1.6.1
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.
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/HttpService.js +72 -9
- package/dist/cjs/crypto/identityMove.js +53 -34
- package/dist/cjs/crypto/identityProof.js +28 -0
- package/dist/cjs/crypto/webIdentityCarrier.js +145 -82
- package/dist/cjs/i18n/locales/ar-SA.json +1 -0
- package/dist/cjs/i18n/locales/ca-ES.json +1 -0
- package/dist/cjs/i18n/locales/de-DE.json +1 -0
- package/dist/cjs/i18n/locales/en-US.json +1 -0
- package/dist/cjs/i18n/locales/es-ES.json +1 -0
- package/dist/cjs/i18n/locales/fr-FR.json +1 -0
- package/dist/cjs/i18n/locales/it-IT.json +1 -0
- package/dist/cjs/i18n/locales/ja-JP.json +1 -0
- package/dist/cjs/i18n/locales/ko-KR.json +1 -0
- package/dist/cjs/i18n/locales/locales/ar-SA.json +1 -0
- package/dist/cjs/i18n/locales/locales/ca-ES.json +1 -0
- package/dist/cjs/i18n/locales/locales/de-DE.json +1 -0
- package/dist/cjs/i18n/locales/locales/en-US.json +1 -0
- package/dist/cjs/i18n/locales/locales/es-ES.json +1 -0
- package/dist/cjs/i18n/locales/locales/fr-FR.json +1 -0
- package/dist/cjs/i18n/locales/locales/it-IT.json +1 -0
- package/dist/cjs/i18n/locales/locales/ja-JP.json +1 -0
- package/dist/cjs/i18n/locales/locales/ko-KR.json +1 -0
- package/dist/cjs/i18n/locales/locales/pt-PT.json +1 -0
- package/dist/cjs/i18n/locales/locales/zh-CN.json +1 -0
- package/dist/cjs/i18n/locales/pt-PT.json +1 -0
- package/dist/cjs/i18n/locales/zh-CN.json +1 -0
- package/dist/cjs/index.js +22 -14
- package/dist/cjs/inference/OxyInferenceClient.js +62 -5
- package/dist/cjs/mixins/OxyServices.auth.js +129 -0
- package/dist/cjs/mixins/OxyServices.identity.js +11 -44
- package/dist/cjs/mixins/index.js +0 -2
- package/dist/cjs/server/index.js +10 -1
- package/dist/cjs/server/securityHeaders.js +9 -2
- package/dist/cjs/server/traffic.js +29 -2
- package/dist/cjs/server/workloadIdentity.js +170 -0
- package/dist/cjs/session/refresh.js +12 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/HttpService.js +72 -9
- package/dist/esm/crypto/identityMove.js +51 -33
- package/dist/esm/crypto/identityProof.js +24 -0
- package/dist/esm/crypto/webIdentityCarrier.js +137 -77
- package/dist/esm/i18n/locales/ar-SA.json +1 -0
- package/dist/esm/i18n/locales/ca-ES.json +1 -0
- package/dist/esm/i18n/locales/de-DE.json +1 -0
- package/dist/esm/i18n/locales/en-US.json +1 -0
- package/dist/esm/i18n/locales/es-ES.json +1 -0
- package/dist/esm/i18n/locales/fr-FR.json +1 -0
- package/dist/esm/i18n/locales/it-IT.json +1 -0
- package/dist/esm/i18n/locales/ja-JP.json +1 -0
- package/dist/esm/i18n/locales/ko-KR.json +1 -0
- package/dist/esm/i18n/locales/locales/ar-SA.json +1 -0
- package/dist/esm/i18n/locales/locales/ca-ES.json +1 -0
- package/dist/esm/i18n/locales/locales/de-DE.json +1 -0
- package/dist/esm/i18n/locales/locales/en-US.json +1 -0
- package/dist/esm/i18n/locales/locales/es-ES.json +1 -0
- package/dist/esm/i18n/locales/locales/fr-FR.json +1 -0
- package/dist/esm/i18n/locales/locales/it-IT.json +1 -0
- package/dist/esm/i18n/locales/locales/ja-JP.json +1 -0
- package/dist/esm/i18n/locales/locales/ko-KR.json +1 -0
- package/dist/esm/i18n/locales/locales/pt-PT.json +1 -0
- package/dist/esm/i18n/locales/locales/zh-CN.json +1 -0
- package/dist/esm/i18n/locales/pt-PT.json +1 -0
- package/dist/esm/i18n/locales/zh-CN.json +1 -0
- package/dist/esm/index.js +5 -4
- package/dist/esm/inference/OxyInferenceClient.js +63 -6
- package/dist/esm/mixins/OxyServices.auth.js +96 -0
- package/dist/esm/mixins/OxyServices.identity.js +11 -44
- package/dist/esm/mixins/index.js +0 -2
- package/dist/esm/server/index.js +7 -0
- package/dist/esm/server/securityHeaders.js +9 -2
- package/dist/esm/server/traffic.js +29 -2
- package/dist/esm/server/workloadIdentity.js +166 -0
- package/dist/esm/session/refresh.js +12 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/HttpService.d.ts +24 -0
- package/dist/types/crypto/identityMove.d.ts +48 -32
- package/dist/types/crypto/identityProof.d.ts +16 -0
- package/dist/types/crypto/webIdentityCarrier.d.ts +74 -47
- package/dist/types/index.d.ts +7 -6
- package/dist/types/inference/OxyInferenceClient.d.ts +21 -1
- package/dist/types/mixins/OxyServices.auth.d.ts +32 -0
- package/dist/types/mixins/OxyServices.identity.d.ts +16 -30
- package/dist/types/mixins/index.d.ts +1 -2
- package/dist/types/server/index.d.ts +8 -0
- package/dist/types/server/securityHeaders.d.ts +8 -0
- package/dist/types/server/workloadIdentity.d.ts +32 -0
- package/package.json +2 -2
- package/src/HttpService.ts +74 -9
- package/src/__tests__/httpServiceRateLimitedRefresh.test.ts +160 -0
- package/src/__tests__/serviceTokenWorkloadFallback.test.ts +85 -0
- package/src/crypto/__tests__/identityMove.test.ts +93 -51
- package/src/crypto/__tests__/identityProof.test.ts +81 -0
- package/src/crypto/__tests__/webIdentityCarrier.test.ts +21 -63
- package/src/crypto/__tests__/webIdentityRecoveryMaterial.test.ts +117 -0
- package/src/crypto/identityMove.ts +77 -43
- package/src/crypto/identityProof.ts +36 -0
- package/src/crypto/webIdentityCarrier.ts +185 -117
- package/src/i18n/locales/ar-SA.json +1 -0
- package/src/i18n/locales/ca-ES.json +1 -0
- package/src/i18n/locales/de-DE.json +1 -0
- package/src/i18n/locales/en-US.json +1 -0
- package/src/i18n/locales/es-ES.json +1 -0
- package/src/i18n/locales/fr-FR.json +1 -0
- package/src/i18n/locales/it-IT.json +1 -0
- package/src/i18n/locales/ja-JP.json +1 -0
- package/src/i18n/locales/ko-KR.json +1 -0
- package/src/i18n/locales/pt-PT.json +1 -0
- package/src/i18n/locales/zh-CN.json +1 -0
- package/src/index.ts +26 -15
- package/src/inference/OxyInferenceClient.ts +84 -7
- package/src/inference/__tests__/OxyInferenceClient.test.ts +48 -0
- package/src/mixins/OxyServices.auth.ts +100 -1
- package/src/mixins/OxyServices.identity.ts +17 -67
- package/src/mixins/__tests__/OxyServices.identity.test.ts +1 -76
- package/src/mixins/index.ts +0 -3
- package/src/server/__tests__/securityHeaders.test.ts +15 -0
- package/src/server/__tests__/traffic.test.ts +132 -1
- package/src/server/__tests__/workloadIdentity.test.ts +128 -0
- package/src/server/index.ts +9 -0
- package/src/server/securityHeaders.ts +19 -6
- package/src/server/traffic.ts +31 -1
- package/src/server/workloadIdentity.ts +207 -0
- package/src/session/__tests__/refresh.test.ts +53 -3
- package/src/session/refresh.ts +12 -0
- package/dist/cjs/mixins/OxyServices.deviceTransfer.js +0 -314
- package/dist/esm/mixins/OxyServices.deviceTransfer.js +0 -311
- package/dist/types/mixins/OxyServices.deviceTransfer.d.ts +0 -149
- package/src/mixins/OxyServices.deviceTransfer.ts +0 -393
- package/src/mixins/__tests__/OxyServices.deviceTransfer.test.ts +0 -271
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Web identity
|
|
2
|
+
* Web identity holder — the crypto of a browser holding a personal root (ADR 0024).
|
|
3
3
|
*
|
|
4
|
-
* An Oxy
|
|
5
|
-
* secp256k1 key (`recoveryPhrase.ts`)
|
|
6
|
-
*
|
|
7
|
-
* being able to use it:
|
|
4
|
+
* An Oxy root is usually a BIP-39 mnemonic whose seed's first 32 bytes are the
|
|
5
|
+
* secp256k1 key (`recoveryPhrase.ts`), and for a few imported identities a raw
|
|
6
|
+
* private key with no phrase. Commons keeps it in the device keychain. This
|
|
7
|
+
* module lets a browser hold the SAME root without Oxy ever being able to use it:
|
|
8
8
|
*
|
|
9
|
-
|
|
9
|
+
secret ──AEAD(DEK)──▶ sealed secret (phrase entropy or raw key)
|
|
10
10
|
* DEK ──AEAD(KEK_i)─▶ wraps[i] KEK_i = HKDF(PRF output of passkey i)
|
|
11
11
|
*
|
|
12
12
|
* The PRF output is produced inside the user's authenticator, behind user
|
|
13
13
|
* verification, and never leaves the page that asked for it. The envelope is
|
|
14
|
-
* therefore safe to store anywhere — the
|
|
15
|
-
*
|
|
14
|
+
* therefore safe to store anywhere — the holder host's IndexedDB and the server
|
|
15
|
+
* copy alike — and opens only with a registered passkey or the recovery material.
|
|
16
16
|
*
|
|
17
17
|
* PURE: no storage, no network, no WebAuthn call, no platform globals beyond the
|
|
18
18
|
* CSPRNG the AEAD polyfill guarantees. The caller runs the ceremony and hands the
|
|
@@ -27,11 +27,11 @@ import { entropyToMnemonic, generateMnemonic, mnemonicToEntropy, mnemonicToSeedS
|
|
|
27
27
|
import { wordlist } from '@scure/bip39/wordlists/english';
|
|
28
28
|
import { sha256 } from '@noble/hashes/sha256';
|
|
29
29
|
import { bytesToHex, hexToBytes, utf8ToBytes } from '@noble/hashes/utils';
|
|
30
|
-
import { signMessage } from '@oxy.so/protocol';
|
|
31
30
|
import { deriveSecp256k1PublicKey } from '@oxy.so/protocol/secp256k1';
|
|
32
31
|
import {
|
|
33
32
|
WEB_IDENTITY_ENVELOPE_VERSION,
|
|
34
33
|
type WebIdentityEnvelope,
|
|
34
|
+
type WebIdentitySecretKind,
|
|
35
35
|
type WebIdentityWrap,
|
|
36
36
|
} from '@oxy.so/contracts';
|
|
37
37
|
import { AEAD_KEY_LENGTH, decryptAead, encryptAead } from './aead';
|
|
@@ -46,11 +46,11 @@ import { hkdfSha256 } from './kdf';
|
|
|
46
46
|
*/
|
|
47
47
|
export const WEB_IDENTITY_PRF_INPUT: Uint8Array = sha256(utf8ToBytes('oxy-web-identity-carrier/prf/v1'));
|
|
48
48
|
|
|
49
|
-
/** HKDF salt of the key-encryption-key schedule.
|
|
49
|
+
/** HKDF salt of the key-encryption-key schedule. Shared by envelope versions 1 and 2. */
|
|
50
50
|
const KEK_SALT = utf8ToBytes('oxy-web-identity-carrier/kek/v1');
|
|
51
51
|
|
|
52
|
-
/** A WebAuthn PRF output is 32 bytes (HMAC-SHA-256). */
|
|
53
|
-
const
|
|
52
|
+
/** A WebAuthn PRF output is 32 bytes (HMAC-SHA-256). Nothing shorter or longer is usable. */
|
|
53
|
+
export const WEB_IDENTITY_PRF_OUTPUT_LENGTH = 32;
|
|
54
54
|
|
|
55
55
|
/** Why an envelope did not open. Every value is a normal outcome a UI can explain. */
|
|
56
56
|
export type WebIdentityUnlockFailure =
|
|
@@ -59,7 +59,7 @@ export type WebIdentityUnlockFailure =
|
|
|
59
59
|
/**
|
|
60
60
|
* The passkey IS listed but its PRF output does not open its wrap — a synced
|
|
61
61
|
* copy returning a different PRF value, or a provider that silently changed it.
|
|
62
|
-
* Recoverable with another passkey or the
|
|
62
|
+
* Recoverable with another passkey or the recovery material.
|
|
63
63
|
*/
|
|
64
64
|
| 'prf-mismatch'
|
|
65
65
|
/** The envelope itself is malformed or tampered with. */
|
|
@@ -72,25 +72,45 @@ export class WebIdentityUnlockError extends Error {
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
/**
|
|
76
|
-
export interface
|
|
75
|
+
/** A root that came from a BIP-39 phrase (12–24 words). Hold it for one operation, then wipe. */
|
|
76
|
+
export interface OpenedMnemonicIdentity {
|
|
77
|
+
kind: 'mnemonic';
|
|
77
78
|
mnemonic: string;
|
|
78
|
-
/** 32-byte secp256k1 private key, lowercase hex. */
|
|
79
|
+
/** 32-byte secp256k1 private key, lowercase hex (`seed[0:32]`). */
|
|
79
80
|
privateKey: string;
|
|
80
81
|
/** Uncompressed SEC1 public key, lowercase hex (Oxy's canonical form). */
|
|
81
82
|
publicKey: string;
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
|
|
85
|
+
/**
|
|
86
|
+
* A root that was imported as a raw private key and never had a phrase. It has
|
|
87
|
+
* no mnemonic, and nothing may ever derive or display one for it (ADR 0024 D5).
|
|
88
|
+
*/
|
|
89
|
+
export interface OpenedRawKeyIdentity {
|
|
90
|
+
kind: 'raw-key';
|
|
91
|
+
mnemonic: null;
|
|
92
|
+
privateKey: string;
|
|
93
|
+
publicKey: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The identity in usable form. Hold it for one operation, then wipe it. */
|
|
97
|
+
export type OpenedWebIdentity = OpenedMnemonicIdentity | OpenedRawKeyIdentity;
|
|
98
|
+
|
|
99
|
+
/** Material a person can recover a root from. */
|
|
100
|
+
export type WebIdentityRecoveryMaterial =
|
|
101
|
+
| { kind: 'mnemonic'; mnemonic: string }
|
|
102
|
+
| { kind: 'raw-key'; privateKey: string };
|
|
103
|
+
|
|
104
|
+
function aadFor(parts: Record<string, string | number | null>): Uint8Array {
|
|
85
105
|
return utf8ToBytes(JSON.stringify(parts));
|
|
86
106
|
}
|
|
87
107
|
|
|
88
|
-
function
|
|
89
|
-
return aadFor({ v: WEB_IDENTITY_ENVELOPE_VERSION, purpose: '
|
|
108
|
+
function secretAad(publicKey: string, secretKind: WebIdentitySecretKind): Uint8Array {
|
|
109
|
+
return aadFor({ v: WEB_IDENTITY_ENVELOPE_VERSION, purpose: 'secret', secretKind, publicKey });
|
|
90
110
|
}
|
|
91
111
|
|
|
92
|
-
function wrapAad(publicKey: string, credentialId: string): Uint8Array {
|
|
93
|
-
return aadFor({ v: WEB_IDENTITY_ENVELOPE_VERSION, purpose: 'wrap', publicKey, credentialId });
|
|
112
|
+
function wrapAad(publicKey: string, credentialId: string, rpId: string): Uint8Array {
|
|
113
|
+
return aadFor({ v: WEB_IDENTITY_ENVELOPE_VERSION, purpose: 'wrap', publicKey, credentialId, rpId });
|
|
94
114
|
}
|
|
95
115
|
|
|
96
116
|
/** Overwrite a buffer holding secret material. Best effort — JS gives no stronger guarantee. */
|
|
@@ -98,12 +118,25 @@ export function wipeBytes(bytes: Uint8Array): void {
|
|
|
98
118
|
bytes.fill(0);
|
|
99
119
|
}
|
|
100
120
|
|
|
121
|
+
/** Best-effort removal of secret strings from an opened identity. */
|
|
122
|
+
export function wipeOpenedIdentity(identity: OpenedWebIdentity): void {
|
|
123
|
+
(identity as { privateKey: string }).privateKey = '';
|
|
124
|
+
if (identity.kind === 'mnemonic') (identity as { mnemonic: string }).mnemonic = '';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Normalize a typed phrase: trimmed, lowercase, single spaces. */
|
|
128
|
+
export function normalizeMnemonic(mnemonic: string): string {
|
|
129
|
+
return mnemonic.trim().toLowerCase().split(/\s+/).join(' ');
|
|
130
|
+
}
|
|
131
|
+
|
|
101
132
|
/**
|
|
102
133
|
* Derive the identity from a mnemonic exactly as Commons does
|
|
103
|
-
* (`RecoveryPhraseService`): seed → first 32 bytes → secp256k1.
|
|
134
|
+
* (`RecoveryPhraseService`): seed → first 32 bytes → secp256k1. Any standard
|
|
135
|
+
* BIP-39 length (12, 15, 18, 21, 24 words) is accepted; the derivation does not
|
|
136
|
+
* depend on the length.
|
|
104
137
|
*/
|
|
105
|
-
export function deriveIdentityFromMnemonic(mnemonic: string):
|
|
106
|
-
const normalized = mnemonic
|
|
138
|
+
export function deriveIdentityFromMnemonic(mnemonic: string): OpenedMnemonicIdentity {
|
|
139
|
+
const normalized = normalizeMnemonic(mnemonic);
|
|
107
140
|
if (!validateMnemonic(normalized, wordlist)) {
|
|
108
141
|
throw new Error('Invalid recovery phrase');
|
|
109
142
|
}
|
|
@@ -112,11 +145,45 @@ export function deriveIdentityFromMnemonic(mnemonic: string): OpenedWebIdentity
|
|
|
112
145
|
wipeBytes(seed);
|
|
113
146
|
const privateKey = bytesToHex(privateKeyBytes);
|
|
114
147
|
wipeBytes(privateKeyBytes);
|
|
115
|
-
return { mnemonic: normalized, privateKey, publicKey: deriveSecp256k1PublicKey(privateKey) };
|
|
148
|
+
return { kind: 'mnemonic', mnemonic: normalized, privateKey, publicKey: deriveSecp256k1PublicKey(privateKey) };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** A raw 32-byte private key (hex, optional `0x`) as an identity. Never gains a phrase. */
|
|
152
|
+
export function deriveIdentityFromPrivateKey(privateKey: string): OpenedRawKeyIdentity {
|
|
153
|
+
const normalized = privateKey.trim().toLowerCase().replace(/^0x/, '');
|
|
154
|
+
if (!/^[0-9a-f]{64}$/.test(normalized)) {
|
|
155
|
+
throw new Error('Invalid private key');
|
|
156
|
+
}
|
|
157
|
+
let publicKey: string;
|
|
158
|
+
try {
|
|
159
|
+
publicKey = deriveSecp256k1PublicKey(normalized);
|
|
160
|
+
} catch {
|
|
161
|
+
throw new Error('Invalid private key');
|
|
162
|
+
}
|
|
163
|
+
return { kind: 'raw-key', mnemonic: null, privateKey: normalized, publicKey };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Read what a person typed as recovery material: a BIP-39 phrase, or a 64-hex
|
|
168
|
+
* private key. Throws `Invalid recovery material` for anything else.
|
|
169
|
+
*/
|
|
170
|
+
export function parseRecoveryMaterial(input: string): WebIdentityRecoveryMaterial {
|
|
171
|
+
const trimmed = input.trim();
|
|
172
|
+
if (/^(0x)?[0-9a-fA-F]{64}$/.test(trimmed)) return { kind: 'raw-key', privateKey: trimmed };
|
|
173
|
+
const words = normalizeMnemonic(trimmed);
|
|
174
|
+
if (words && validateMnemonic(words, wordlist)) return { kind: 'mnemonic', mnemonic: words };
|
|
175
|
+
throw new Error('Invalid recovery material');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Derive the identity recovery material names. */
|
|
179
|
+
export function deriveIdentityFromRecoveryMaterial(material: WebIdentityRecoveryMaterial): OpenedWebIdentity {
|
|
180
|
+
return material.kind === 'mnemonic'
|
|
181
|
+
? deriveIdentityFromMnemonic(material.mnemonic)
|
|
182
|
+
: deriveIdentityFromPrivateKey(material.privateKey);
|
|
116
183
|
}
|
|
117
184
|
|
|
118
185
|
/** A brand-new 12-word identity. Nothing is persisted. */
|
|
119
|
-
export function generateWebIdentity():
|
|
186
|
+
export function generateWebIdentity(): OpenedMnemonicIdentity {
|
|
120
187
|
return deriveIdentityFromMnemonic(generateMnemonic(wordlist, 128));
|
|
121
188
|
}
|
|
122
189
|
|
|
@@ -127,36 +194,58 @@ export function generateDataKey(): Uint8Array {
|
|
|
127
194
|
return key;
|
|
128
195
|
}
|
|
129
196
|
|
|
197
|
+
/** Whether `bytes` is a usable PRF output: exactly 32 bytes. */
|
|
198
|
+
export function isUsablePrfOutput(bytes: Uint8Array | null | undefined): bytes is Uint8Array {
|
|
199
|
+
return bytes instanceof Uint8Array && bytes.byteLength === WEB_IDENTITY_PRF_OUTPUT_LENGTH;
|
|
200
|
+
}
|
|
201
|
+
|
|
130
202
|
/** The key-encryption key a passkey's PRF output yields for its own wrap. */
|
|
131
203
|
export function deriveKeyEncryptionKey(prfOutput: Uint8Array, credentialId: string): Uint8Array {
|
|
132
|
-
if (prfOutput.length !==
|
|
133
|
-
throw new Error(`PRF output must be ${
|
|
204
|
+
if (prfOutput.length !== WEB_IDENTITY_PRF_OUTPUT_LENGTH) {
|
|
205
|
+
throw new Error(`PRF output must be ${WEB_IDENTITY_PRF_OUTPUT_LENGTH} bytes, got ${prfOutput.length}`);
|
|
134
206
|
}
|
|
135
207
|
return hkdfSha256(prfOutput, KEK_SALT, utf8ToBytes(credentialId), AEAD_KEY_LENGTH);
|
|
136
208
|
}
|
|
137
209
|
|
|
138
|
-
/**
|
|
139
|
-
export
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
210
|
+
/** One passkey's contribution to an envelope. */
|
|
211
|
+
export interface WrapInput {
|
|
212
|
+
prfOutput: Uint8Array;
|
|
213
|
+
credentialId: string;
|
|
214
|
+
/** The RP ID the passkey lives under; bound into the wrap. */
|
|
215
|
+
rpId: string;
|
|
216
|
+
/** Set when this PRF output came from a ceremony separate from the one that created the wrap. */
|
|
217
|
+
verifiedAt?: string;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function wrapFor(
|
|
143
221
|
publicKey: string,
|
|
144
|
-
|
|
222
|
+
dataKey: Uint8Array,
|
|
223
|
+
input: WrapInput,
|
|
224
|
+
now: Date,
|
|
145
225
|
): WebIdentityWrap {
|
|
146
|
-
const kek = deriveKeyEncryptionKey(prfOutput, credentialId);
|
|
226
|
+
const kek = deriveKeyEncryptionKey(input.prfOutput, input.credentialId);
|
|
147
227
|
try {
|
|
148
|
-
const { nonce, ciphertext } = encryptAead(kek, dataKey, wrapAad(publicKey, credentialId));
|
|
228
|
+
const { nonce, ciphertext } = encryptAead(kek, dataKey, wrapAad(publicKey, input.credentialId, input.rpId));
|
|
149
229
|
return {
|
|
150
|
-
credentialId,
|
|
230
|
+
credentialId: input.credentialId,
|
|
151
231
|
nonce: bytesToHex(nonce),
|
|
152
232
|
wrappedKey: bytesToHex(ciphertext),
|
|
153
233
|
createdAt: now.toISOString(),
|
|
234
|
+
rpId: input.rpId,
|
|
235
|
+
...(input.verifiedAt ? { verifiedAt: input.verifiedAt } : {}),
|
|
154
236
|
};
|
|
155
237
|
} finally {
|
|
156
238
|
wipeBytes(kek);
|
|
157
239
|
}
|
|
158
240
|
}
|
|
159
241
|
|
|
242
|
+
function secretOf(identity: OpenedWebIdentity): { kind: WebIdentitySecretKind; bytes: Uint8Array } {
|
|
243
|
+
if (identity.kind === 'mnemonic') {
|
|
244
|
+
return { kind: 'mnemonic-entropy', bytes: mnemonicToEntropy(identity.mnemonic, wordlist) };
|
|
245
|
+
}
|
|
246
|
+
return { kind: 'raw-private-key', bytes: hexToBytes(identity.privateKey) };
|
|
247
|
+
}
|
|
248
|
+
|
|
160
249
|
/**
|
|
161
250
|
* Seal an identity into a new envelope that opens with the given passkey.
|
|
162
251
|
*
|
|
@@ -164,29 +253,37 @@ export function wrapDataKey(
|
|
|
164
253
|
* session; wipe it when done.
|
|
165
254
|
*/
|
|
166
255
|
export function sealWebIdentity(
|
|
167
|
-
identity:
|
|
168
|
-
firstWrap:
|
|
256
|
+
identity: OpenedWebIdentity,
|
|
257
|
+
firstWrap: WrapInput,
|
|
169
258
|
now: Date = new Date(),
|
|
170
259
|
): { envelope: WebIdentityEnvelope; dataKey: Uint8Array } {
|
|
171
|
-
const derived =
|
|
172
|
-
|
|
173
|
-
|
|
260
|
+
const derived: OpenedWebIdentity =
|
|
261
|
+
identity.kind === 'raw-key' ? deriveIdentityFromPrivateKey(identity.privateKey) : deriveIdentityFromMnemonic(identity.mnemonic);
|
|
262
|
+
const publicKey = identity.publicKey.toLowerCase();
|
|
263
|
+
if (derived.publicKey !== publicKey) {
|
|
264
|
+
wipeOpenedIdentity(derived);
|
|
265
|
+
throw new Error('The recovery material does not belong to this identity');
|
|
174
266
|
}
|
|
267
|
+
const secret = secretOf(derived);
|
|
268
|
+
wipeOpenedIdentity(derived);
|
|
175
269
|
const dataKey = generateDataKey();
|
|
176
|
-
const entropy = mnemonicToEntropy(derived.mnemonic, wordlist);
|
|
177
270
|
try {
|
|
178
|
-
const { nonce, ciphertext } = encryptAead(dataKey,
|
|
271
|
+
const { nonce, ciphertext } = encryptAead(dataKey, secret.bytes, secretAad(publicKey, secret.kind));
|
|
179
272
|
const envelope: WebIdentityEnvelope = {
|
|
180
273
|
version: WEB_IDENTITY_ENVELOPE_VERSION,
|
|
181
274
|
algorithm: 'xchacha20poly1305',
|
|
182
|
-
publicKey
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
275
|
+
publicKey,
|
|
276
|
+
secretKind: secret.kind,
|
|
277
|
+
secretNonce: bytesToHex(nonce),
|
|
278
|
+
sealedSecret: bytesToHex(ciphertext),
|
|
279
|
+
wraps: [wrapFor(publicKey, dataKey, firstWrap, now)],
|
|
186
280
|
};
|
|
187
281
|
return { envelope, dataKey };
|
|
282
|
+
} catch (error) {
|
|
283
|
+
wipeBytes(dataKey);
|
|
284
|
+
throw error;
|
|
188
285
|
} finally {
|
|
189
|
-
wipeBytes(
|
|
286
|
+
wipeBytes(secret.bytes);
|
|
190
287
|
}
|
|
191
288
|
}
|
|
192
289
|
|
|
@@ -202,7 +299,7 @@ export function unwrapDataKey(
|
|
|
202
299
|
}
|
|
203
300
|
const kek = deriveKeyEncryptionKey(prfOutput, credentialId);
|
|
204
301
|
try {
|
|
205
|
-
return decryptAead(kek, hexToBytes(wrap.nonce), hexToBytes(wrap.wrappedKey), wrapAad(envelope.publicKey, credentialId));
|
|
302
|
+
return decryptAead(kek, hexToBytes(wrap.nonce), hexToBytes(wrap.wrappedKey), wrapAad(envelope.publicKey, credentialId, wrap.rpId));
|
|
206
303
|
} catch {
|
|
207
304
|
throw new WebIdentityUnlockError('prf-mismatch', 'This passkey returned a different secret than when it was registered');
|
|
208
305
|
} finally {
|
|
@@ -213,29 +310,35 @@ export function unwrapDataKey(
|
|
|
213
310
|
/**
|
|
214
311
|
* Open an envelope with its data key.
|
|
215
312
|
*
|
|
216
|
-
* The derived public key is checked against the envelope's own: a sealed
|
|
313
|
+
* The derived public key is checked against the envelope's own: a sealed secret
|
|
217
314
|
* that decrypts into a different identity is treated as corruption, never used.
|
|
218
315
|
*/
|
|
219
316
|
export function openWebIdentity(envelope: WebIdentityEnvelope, dataKey: Uint8Array): OpenedWebIdentity {
|
|
220
|
-
|
|
317
|
+
const publicKey = envelope.publicKey.toLowerCase();
|
|
318
|
+
const kind = envelope.secretKind;
|
|
319
|
+
let secret: Uint8Array;
|
|
221
320
|
try {
|
|
222
|
-
|
|
223
|
-
dataKey,
|
|
224
|
-
hexToBytes(envelope.entropyNonce),
|
|
225
|
-
hexToBytes(envelope.sealedEntropy),
|
|
226
|
-
entropyAad(envelope.publicKey),
|
|
227
|
-
);
|
|
321
|
+
secret = decryptAead(dataKey, hexToBytes(envelope.secretNonce), hexToBytes(envelope.sealedSecret), secretAad(envelope.publicKey, kind));
|
|
228
322
|
} catch {
|
|
229
323
|
throw new WebIdentityUnlockError('corrupt', 'The sealed identity could not be opened');
|
|
230
324
|
}
|
|
231
325
|
try {
|
|
232
|
-
|
|
233
|
-
|
|
326
|
+
let identity: OpenedWebIdentity;
|
|
327
|
+
try {
|
|
328
|
+
identity =
|
|
329
|
+
kind === 'mnemonic-entropy'
|
|
330
|
+
? deriveIdentityFromMnemonic(entropyToMnemonic(secret, wordlist))
|
|
331
|
+
: deriveIdentityFromPrivateKey(bytesToHex(secret));
|
|
332
|
+
} catch {
|
|
333
|
+
throw new WebIdentityUnlockError('corrupt', 'The sealed identity is malformed');
|
|
334
|
+
}
|
|
335
|
+
if (identity.publicKey !== publicKey) {
|
|
336
|
+
wipeOpenedIdentity(identity);
|
|
234
337
|
throw new WebIdentityUnlockError('corrupt', 'The sealed identity does not match its public key');
|
|
235
338
|
}
|
|
236
339
|
return identity;
|
|
237
340
|
} finally {
|
|
238
|
-
wipeBytes(
|
|
341
|
+
wipeBytes(secret);
|
|
239
342
|
}
|
|
240
343
|
}
|
|
241
344
|
|
|
@@ -254,19 +357,30 @@ export function unlockWebIdentity(
|
|
|
254
357
|
}
|
|
255
358
|
|
|
256
359
|
/** Add (or replace) one passkey's wrap. Requires the data key of an unlocked envelope. */
|
|
257
|
-
export function addWrap(
|
|
258
|
-
envelope: WebIdentityEnvelope,
|
|
259
|
-
dataKey: Uint8Array,
|
|
260
|
-
prfOutput: Uint8Array,
|
|
261
|
-
credentialId: string,
|
|
262
|
-
now: Date = new Date(),
|
|
263
|
-
): WebIdentityEnvelope {
|
|
360
|
+
export function addWrap(envelope: WebIdentityEnvelope, dataKey: Uint8Array, wrapInput: WrapInput, now: Date = new Date()): WebIdentityEnvelope {
|
|
264
361
|
// Proves `dataKey` belongs to THIS envelope before anything new can open it.
|
|
265
|
-
openWebIdentity(envelope, dataKey);
|
|
266
|
-
const others = envelope.wraps.filter((entry) => entry.credentialId !== credentialId);
|
|
362
|
+
wipeOpenedIdentity(openWebIdentity(envelope, dataKey));
|
|
363
|
+
const others = envelope.wraps.filter((entry) => entry.credentialId !== wrapInput.credentialId);
|
|
267
364
|
return {
|
|
268
365
|
...envelope,
|
|
269
|
-
wraps: [...others,
|
|
366
|
+
wraps: [...others, wrapFor(envelope.publicKey, dataKey, wrapInput, now)],
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Record that a wrap's passkey opened the envelope in a ceremony of its own. The
|
|
372
|
+
* AEAD does not cover `verifiedAt`; it is holder metadata the owner writes with a
|
|
373
|
+
* root proof, not a cryptographic claim.
|
|
374
|
+
*/
|
|
375
|
+
export function markWrapVerified(envelope: WebIdentityEnvelope, credentialId: string, now: Date = new Date()): WebIdentityEnvelope {
|
|
376
|
+
if (!envelope.wraps.some((entry) => entry.credentialId === credentialId)) {
|
|
377
|
+
throw new WebIdentityUnlockError('unknown-credential', 'This passkey cannot open this identity');
|
|
378
|
+
}
|
|
379
|
+
return {
|
|
380
|
+
...envelope,
|
|
381
|
+
wraps: envelope.wraps.map((entry) =>
|
|
382
|
+
entry.credentialId === credentialId ? { ...entry, verifiedAt: now.toISOString() } : entry,
|
|
383
|
+
),
|
|
270
384
|
};
|
|
271
385
|
}
|
|
272
386
|
|
|
@@ -278,49 +392,3 @@ export function removeWrap(envelope: WebIdentityEnvelope, credentialId: string):
|
|
|
278
392
|
}
|
|
279
393
|
return { ...envelope, wraps: remaining };
|
|
280
394
|
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* The message an identity key signs to authorize an account action
|
|
284
|
-
* (`link_identity`, `web_envelope_delete`, …): `JSON.stringify({ action, userId,
|
|
285
|
-
* timestamp })`, byte-identical to what the API reconstructs.
|
|
286
|
-
*/
|
|
287
|
-
export function buildIdentityActionMessage(action: string, userId: string, timestamp: number): string {
|
|
288
|
-
return JSON.stringify({ action, userId, timestamp });
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/** Sign an identity-action message with an opened identity's key. */
|
|
292
|
-
export async function signIdentityAction(
|
|
293
|
-
identity: Pick<OpenedWebIdentity, 'privateKey'>,
|
|
294
|
-
action: string,
|
|
295
|
-
userId: string,
|
|
296
|
-
timestamp: number = Date.now(),
|
|
297
|
-
): Promise<{ signature: string; timestamp: number }> {
|
|
298
|
-
const signature = await signMessage(buildIdentityActionMessage(action, userId, timestamp), identity.privateKey);
|
|
299
|
-
return { signature, timestamp };
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* The 6-digit short authentication string both sides of an identity transfer show.
|
|
304
|
-
*
|
|
305
|
-
* Bound to the pairing and to BOTH ephemeral public keys in their roles, so a relay
|
|
306
|
-
* that substitutes either key produces a different code on each screen. The user
|
|
307
|
-
* comparing the two codes is what makes the transfer safe against the relay itself.
|
|
308
|
-
*/
|
|
309
|
-
export function deriveTransferSas(input: {
|
|
310
|
-
pairingId: string;
|
|
311
|
-
initiatorEphemeralPublicKey: string;
|
|
312
|
-
responderEphemeralPublicKey: string;
|
|
313
|
-
}): string {
|
|
314
|
-
const digest = sha256(
|
|
315
|
-
utf8ToBytes(
|
|
316
|
-
JSON.stringify({
|
|
317
|
-
v: 'oxy-identity-transfer-sas-v1',
|
|
318
|
-
pairingId: input.pairingId.toLowerCase(),
|
|
319
|
-
initiator: input.initiatorEphemeralPublicKey.toLowerCase(),
|
|
320
|
-
responder: input.responderEphemeralPublicKey.toLowerCase(),
|
|
321
|
-
}),
|
|
322
|
-
),
|
|
323
|
-
);
|
|
324
|
-
const value = ((digest[0] << 24) | (digest[1] << 16) | (digest[2] << 8) | digest[3]) >>> 0;
|
|
325
|
-
return String(value % 1_000_000).padStart(6, '0');
|
|
326
|
-
}
|
|
@@ -108,6 +108,7 @@
|
|
|
108
108
|
"loading": "جارٍ تحميل الحسابات...",
|
|
109
109
|
"qrHeadline": "سجّل الدخول بهوية Oxy الخاصة بك",
|
|
110
110
|
"signInWithOxy": "تسجيل الدخول باستخدام Oxy",
|
|
111
|
+
"signUpWithOxy": "التسجيل باستخدام Oxy",
|
|
111
112
|
"useIdentityOnDevice": "لا تملك Commons؟ استخدم مفتاح مرور على هذا الجهاز بدلاً من ذلك",
|
|
112
113
|
"getCommons": "احصل على Commons",
|
|
113
114
|
"commonsNotInstalled": "لا تملك Commons؟ احصل على التطبيق لتسجيل الدخول بمعرّف Oxy الخاص بك.",
|
|
@@ -108,6 +108,7 @@
|
|
|
108
108
|
"loading": "S'estan carregant els comptes...",
|
|
109
109
|
"qrHeadline": "Inicia sessió amb la teva identitat d'Oxy",
|
|
110
110
|
"signInWithOxy": "Inicia sessió amb Oxy",
|
|
111
|
+
"signUpWithOxy": "Registra't amb Oxy",
|
|
111
112
|
"useIdentityOnDevice": "No tens Commons? Fes servir una clau d'accés en aquest dispositiu",
|
|
112
113
|
"getCommons": "Descarrega Commons",
|
|
113
114
|
"commonsNotInstalled": "No tens Commons? Descarrega l'app per iniciar sessió amb el teu Oxy ID.",
|
|
@@ -108,6 +108,7 @@
|
|
|
108
108
|
"loading": "Konten werden geladen...",
|
|
109
109
|
"qrHeadline": "Melden Sie sich mit Ihrer Oxy-Identität an",
|
|
110
110
|
"signInWithOxy": "Mit Oxy anmelden",
|
|
111
|
+
"signUpWithOxy": "Mit Oxy registrieren",
|
|
111
112
|
"useIdentityOnDevice": "Kein Commons? Verwenden Sie stattdessen einen Passkey auf diesem Gerät",
|
|
112
113
|
"getCommons": "Commons herunterladen",
|
|
113
114
|
"commonsNotInstalled": "Kein Commons? Holen Sie sich die App, um sich mit Ihrer Oxy-ID anzumelden.",
|
|
@@ -416,6 +416,7 @@
|
|
|
416
416
|
"scanQr": "Scan QR code",
|
|
417
417
|
"qrHeadline": "Sign in with your Oxy identity",
|
|
418
418
|
"signInWithOxy": "Sign in with Oxy",
|
|
419
|
+
"signUpWithOxy": "Sign up with Oxy",
|
|
419
420
|
"useIdentityOnDevice": "No Commons? Use a passkey on this device instead",
|
|
420
421
|
"getCommons": "Get Commons",
|
|
421
422
|
"commonsNotInstalled": "Don't have Commons? Get the app to sign in with your Oxy ID.",
|
|
@@ -656,6 +656,7 @@
|
|
|
656
656
|
"scanQr": "Escanear código QR",
|
|
657
657
|
"qrHeadline": "Inicia sesión con tu identidad de Oxy",
|
|
658
658
|
"signInWithOxy": "Inicia sesión con Oxy",
|
|
659
|
+
"signUpWithOxy": "Regístrate con Oxy",
|
|
659
660
|
"useIdentityOnDevice": "¿No tienes Commons? Usa una clave de acceso en este dispositivo",
|
|
660
661
|
"getCommons": "Descargar Commons",
|
|
661
662
|
"commonsNotInstalled": "¿No tienes Commons? Descarga la app para iniciar sesión con tu Oxy ID.",
|
|
@@ -108,6 +108,7 @@
|
|
|
108
108
|
"loading": "Chargement des comptes...",
|
|
109
109
|
"qrHeadline": "Connectez-vous avec votre identité Oxy",
|
|
110
110
|
"signInWithOxy": "Se connecter avec Oxy",
|
|
111
|
+
"signUpWithOxy": "S'inscrire avec Oxy",
|
|
111
112
|
"useIdentityOnDevice": "Pas de Commons ? Utilisez une clé d'accès sur cet appareil",
|
|
112
113
|
"getCommons": "Obtenir Commons",
|
|
113
114
|
"commonsNotInstalled": "Pas de Commons ? Téléchargez l'app pour vous connecter avec votre Oxy ID.",
|
|
@@ -108,6 +108,7 @@
|
|
|
108
108
|
"loading": "Caricamento account...",
|
|
109
109
|
"qrHeadline": "Accedi con la tua identità Oxy",
|
|
110
110
|
"signInWithOxy": "Accedi con Oxy",
|
|
111
|
+
"signUpWithOxy": "Registrati con Oxy",
|
|
111
112
|
"useIdentityOnDevice": "Non hai Commons? Usa una passkey su questo dispositivo",
|
|
112
113
|
"getCommons": "Ottieni Commons",
|
|
113
114
|
"commonsNotInstalled": "Non hai Commons? Scarica l'app per accedere con il tuo Oxy ID.",
|
|
@@ -108,6 +108,7 @@
|
|
|
108
108
|
"loading": "アカウントを読み込み中...",
|
|
109
109
|
"qrHeadline": "Oxy IDでサインイン",
|
|
110
110
|
"signInWithOxy": "Oxyでサインイン",
|
|
111
|
+
"signUpWithOxy": "Oxyで登録",
|
|
111
112
|
"useIdentityOnDevice": "Commonsがありませんか?このデバイスのパスキーを使用",
|
|
112
113
|
"getCommons": "Commonsを入手",
|
|
113
114
|
"commonsNotInstalled": "Commonsがありませんか?アプリを入手してOxy IDでサインインしましょう。",
|
|
@@ -108,6 +108,7 @@
|
|
|
108
108
|
"loading": "계정을 불러오는 중...",
|
|
109
109
|
"qrHeadline": "Oxy ID로 로그인",
|
|
110
110
|
"signInWithOxy": "Oxy로 로그인",
|
|
111
|
+
"signUpWithOxy": "Oxy로 가입하기",
|
|
111
112
|
"useIdentityOnDevice": "Commons가 없으신가요? 이 기기의 패스키를 사용하세요",
|
|
112
113
|
"getCommons": "Commons 다운로드",
|
|
113
114
|
"commonsNotInstalled": "Commons가 없으신가요? 앱을 다운로드하여 Oxy ID로 로그인하세요.",
|
|
@@ -108,6 +108,7 @@
|
|
|
108
108
|
"loading": "A carregar contas...",
|
|
109
109
|
"qrHeadline": "Inicie sessão com a sua identidade Oxy",
|
|
110
110
|
"signInWithOxy": "Iniciar sessão com Oxy",
|
|
111
|
+
"signUpWithOxy": "Registar-se com Oxy",
|
|
111
112
|
"useIdentityOnDevice": "Não tem o Commons? Use uma chave de acesso neste dispositivo",
|
|
112
113
|
"getCommons": "Obter o Commons",
|
|
113
114
|
"commonsNotInstalled": "Não tem o Commons? Obtenha a app para iniciar sessão com o seu Oxy ID.",
|
|
@@ -108,6 +108,7 @@
|
|
|
108
108
|
"loading": "正在加载账户...",
|
|
109
109
|
"qrHeadline": "使用您的 Oxy 身份登录",
|
|
110
110
|
"signInWithOxy": "使用 Oxy 登录",
|
|
111
|
+
"signUpWithOxy": "使用 Oxy 注册",
|
|
111
112
|
"useIdentityOnDevice": "没有 Commons?改用此设备上的通行密钥",
|
|
112
113
|
"getCommons": "获取 Commons",
|
|
113
114
|
"commonsNotInstalled": "没有 Commons?获取应用以使用您的 Oxy ID 登录。",
|
package/src/index.ts
CHANGED
|
@@ -86,10 +86,6 @@ export type {
|
|
|
86
86
|
ContactDiscoveryMatch,
|
|
87
87
|
ContactDiscoveryResponse,
|
|
88
88
|
} from './mixins/OxyServices.contacts';
|
|
89
|
-
export type {
|
|
90
|
-
InitDeviceTransferResult,
|
|
91
|
-
DeviceTransferOutcome,
|
|
92
|
-
} from './mixins/OxyServices.deviceTransfer';
|
|
93
89
|
export type {
|
|
94
90
|
BulkFollowEntry,
|
|
95
91
|
BulkFollowResult,
|
|
@@ -234,7 +230,6 @@ export {
|
|
|
234
230
|
export { buildUserDid } from './mixins/OxyServices.identity';
|
|
235
231
|
export type {
|
|
236
232
|
IdentityRecordType,
|
|
237
|
-
UnlinkableAuthMethodType,
|
|
238
233
|
LinkAuthMethodResult,
|
|
239
234
|
PublishRecordResult,
|
|
240
235
|
VerifyRecordResult,
|
|
@@ -338,41 +333,55 @@ export {
|
|
|
338
333
|
export type { AeadResult } from './crypto/aead';
|
|
339
334
|
export { deriveSharedSecret } from './crypto/ecdh';
|
|
340
335
|
|
|
341
|
-
// Web identity
|
|
342
|
-
//
|
|
336
|
+
// Web identity holder — the same root as Commons, sealed under a passkey's PRF
|
|
337
|
+
// output; identity proofs; transfer to Commons (docs/adr/0024-one-oxy-account-root-holders.md)
|
|
343
338
|
export {
|
|
344
339
|
WEB_IDENTITY_PRF_INPUT,
|
|
345
340
|
WebIdentityUnlockError,
|
|
341
|
+
WEB_IDENTITY_PRF_OUTPUT_LENGTH,
|
|
346
342
|
addWrap,
|
|
347
|
-
buildIdentityActionMessage,
|
|
348
343
|
deriveIdentityFromMnemonic,
|
|
344
|
+
deriveIdentityFromPrivateKey,
|
|
345
|
+
deriveIdentityFromRecoveryMaterial,
|
|
349
346
|
deriveKeyEncryptionKey,
|
|
350
|
-
deriveTransferSas,
|
|
351
347
|
generateDataKey,
|
|
352
348
|
generateWebIdentity,
|
|
349
|
+
isUsablePrfOutput,
|
|
350
|
+
markWrapVerified,
|
|
351
|
+
normalizeMnemonic,
|
|
353
352
|
openWebIdentity,
|
|
353
|
+
parseRecoveryMaterial,
|
|
354
354
|
removeWrap,
|
|
355
355
|
sealWebIdentity,
|
|
356
|
-
signIdentityAction,
|
|
357
356
|
unlockWebIdentity,
|
|
358
357
|
unwrapDataKey,
|
|
359
358
|
wipeBytes,
|
|
360
|
-
|
|
359
|
+
wipeOpenedIdentity,
|
|
360
|
+
} from './crypto/webIdentityCarrier';
|
|
361
|
+
export type {
|
|
362
|
+
OpenedMnemonicIdentity,
|
|
363
|
+
OpenedRawKeyIdentity,
|
|
364
|
+
OpenedWebIdentity,
|
|
365
|
+
WebIdentityRecoveryMaterial,
|
|
366
|
+
WebIdentityUnlockFailure,
|
|
367
|
+
WrapInput,
|
|
361
368
|
} from './crypto/webIdentityCarrier';
|
|
362
|
-
export
|
|
369
|
+
export { digestIdentityPayload, signIdentityProof } from './crypto/identityProof';
|
|
363
370
|
export {
|
|
364
|
-
IDENTITY_MOVE_ACTIONS,
|
|
365
|
-
buildMoveMessage,
|
|
366
371
|
buildMoveQrPayload,
|
|
372
|
+
createMoveCommitment,
|
|
367
373
|
deriveMoveKey,
|
|
368
374
|
deriveMoveSas,
|
|
375
|
+
digestMoveCiphertext,
|
|
369
376
|
generateMoveEphemeralKeyPair,
|
|
370
377
|
openMovedIdentity,
|
|
371
378
|
parseMoveQrPayload,
|
|
372
379
|
sealIdentityForMove,
|
|
373
|
-
|
|
380
|
+
signMoveReceipt,
|
|
381
|
+
verifyMoveCommitment,
|
|
374
382
|
verifyMoveReceipt,
|
|
375
383
|
} from './crypto/identityMove';
|
|
384
|
+
export type { MoveReceiptClaims } from './crypto/identityMove';
|
|
376
385
|
|
|
377
386
|
// ---------------------------------------------------------------------------
|
|
378
387
|
// Devices
|
|
@@ -909,6 +918,8 @@ export type {
|
|
|
909
918
|
OxyInferenceResponse,
|
|
910
919
|
OxyGenerationReceipt,
|
|
911
920
|
OxyResponsesRequest,
|
|
921
|
+
OxySpeechRequest,
|
|
922
|
+
OxySpeechResponse,
|
|
912
923
|
} from './inference/OxyInferenceClient';
|
|
913
924
|
|
|
914
925
|
export { runSessionColdBoot } from './boot/sessionColdBoot';
|