@enbox/browser 0.3.39 → 0.3.40
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/browser.mjs +16 -16
- package/dist/browser.mjs.map +4 -4
- package/dist/esm/browser-connect-handler.js +3 -3
- package/dist/esm/browser-connect-handler.js.map +1 -1
- package/dist/esm/dweb-connect-client.js +267 -190
- package/dist/esm/dweb-connect-client.js.map +1 -1
- package/dist/esm/dweb-connect-messages.js +54 -0
- package/dist/esm/dweb-connect-messages.js.map +1 -0
- package/dist/esm/dweb-connect-wallet.js +209 -0
- package/dist/esm/dweb-connect-wallet.js.map +1 -0
- package/dist/esm/index.js +9 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/types/dweb-connect-client.d.ts +123 -59
- package/dist/types/dweb-connect-client.d.ts.map +1 -1
- package/dist/types/dweb-connect-messages.d.ts +64 -0
- package/dist/types/dweb-connect-messages.d.ts.map +1 -0
- package/dist/types/dweb-connect-wallet.d.ts +101 -0
- package/dist/types/dweb-connect-wallet.d.ts.map +1 -0
- package/dist/types/index.d.ts +8 -4
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +9 -6
- package/src/browser-connect-handler.ts +4 -4
- package/src/dweb-connect-client.ts +327 -251
- package/src/dweb-connect-messages.ts +91 -0
- package/src/dweb-connect-wallet.ts +258 -0
- package/src/index.ts +24 -5
- package/dist/esm/dweb-connect-crypto.js +0 -112
- package/dist/esm/dweb-connect-crypto.js.map +0 -1
- package/dist/types/dweb-connect-crypto.d.ts +0 -52
- package/dist/types/dweb-connect-crypto.d.ts.map +0 -1
- package/src/dweb-connect-crypto.ts +0 -156
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire shapes for the DWeb Connect popup/postMessage channel.
|
|
3
|
+
*
|
|
4
|
+
* The dapp and the wallet exchange exactly three message kinds over
|
|
5
|
+
* `window.postMessage`:
|
|
6
|
+
*
|
|
7
|
+
* 1. `loaded` beacon (wallet → dapp): announces the wallet page is ready and
|
|
8
|
+
* carries a fresh ephemeral X25519 public key plus the wallet's origin.
|
|
9
|
+
* 2. `request` (dapp → wallet): the sealed connect request JWE — ciphertext
|
|
10
|
+
* only, encrypted to the beacon key with ECDH-ES and the wallet origin
|
|
11
|
+
* bound into the JWE `apv` header.
|
|
12
|
+
* 3. `response` (wallet → dapp): the sealed connect response JWE, or the
|
|
13
|
+
* literal deny token — again ciphertext only.
|
|
14
|
+
*
|
|
15
|
+
* Plaintext never crosses the channel, and both sides pin `targetOrigin`
|
|
16
|
+
* and verify `event.origin` / `event.source` on every message.
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { Jwk } from '@enbox/crypto';
|
|
22
|
+
|
|
23
|
+
/** Path of the wallet's DWeb Connect page, opened as a popup by the dapp. */
|
|
24
|
+
export const DWEB_CONNECT_PATH = '/dweb-connect';
|
|
25
|
+
|
|
26
|
+
/** Message type of the wallet's `loaded` beacon (wallet → dapp). */
|
|
27
|
+
export const DWEB_CONNECT_LOADED_MESSAGE_TYPE = 'enbox-connect-loaded';
|
|
28
|
+
|
|
29
|
+
/** Message type carrying the sealed connect request JWE (dapp → wallet). */
|
|
30
|
+
export const DWEB_CONNECT_REQUEST_MESSAGE_TYPE = 'enbox-connect-request';
|
|
31
|
+
|
|
32
|
+
/** Message type carrying the sealed connect response JWE or deny token (wallet → dapp). */
|
|
33
|
+
export const DWEB_CONNECT_RESPONSE_MESSAGE_TYPE = 'enbox-connect-response';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The wallet's readiness beacon. Emitted to `window.opener` once the wallet's
|
|
37
|
+
* DWeb Connect page has generated its per-session ephemeral X25519 key.
|
|
38
|
+
*/
|
|
39
|
+
export type DWebConnectLoadedMessage = {
|
|
40
|
+
type: typeof DWEB_CONNECT_LOADED_MESSAGE_TYPE;
|
|
41
|
+
|
|
42
|
+
/** Fresh ephemeral X25519 public JWK the dapp seals the request to. */
|
|
43
|
+
walletEpk: Jwk;
|
|
44
|
+
|
|
45
|
+
/** The wallet's own origin; must match the origin the dapp has pinned. */
|
|
46
|
+
walletOrigin: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** The dapp's sealed connect request, posted to the wallet popup. */
|
|
50
|
+
export type DWebConnectRequestMessage = {
|
|
51
|
+
type: typeof DWEB_CONNECT_REQUEST_MESSAGE_TYPE;
|
|
52
|
+
|
|
53
|
+
/** The sealed connect request as a Compact JWE string. */
|
|
54
|
+
jwe: string;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** The wallet's sealed connect response, posted back to the opener. */
|
|
58
|
+
export type DWebConnectResponseMessage = {
|
|
59
|
+
type: typeof DWEB_CONNECT_RESPONSE_MESSAGE_TYPE;
|
|
60
|
+
|
|
61
|
+
/** The sealed connect response JWE, or the literal `CONNECT_DENIED_TOKEN`. */
|
|
62
|
+
payload: string;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Applies the shared inbound-message trust filter for the popup channel:
|
|
67
|
+
* pins the message to the expected `event.origin`, pins its `event.source` to
|
|
68
|
+
* the expected window, and requires a non-null object payload.
|
|
69
|
+
*
|
|
70
|
+
* Returns the payload cast to a plain record when every check passes, or
|
|
71
|
+
* `undefined` when the message must be dropped. Both the dapp-side and
|
|
72
|
+
* wallet-side transports early-return on `undefined` before inspecting any
|
|
73
|
+
* message field — this is the security-critical filter that keeps hostile
|
|
74
|
+
* origins and stray windows off the channel.
|
|
75
|
+
*/
|
|
76
|
+
export function getTrustedMessage(
|
|
77
|
+
event: MessageEvent,
|
|
78
|
+
expectedOrigin: string,
|
|
79
|
+
expectedSource: Window | undefined,
|
|
80
|
+
): Record<string, unknown> | undefined {
|
|
81
|
+
// Origin pinning: only messages from the expected origin count.
|
|
82
|
+
if (event.origin !== expectedOrigin) { return undefined; }
|
|
83
|
+
|
|
84
|
+
// Source pinning: the message must come from the expected window.
|
|
85
|
+
if (event.source !== expectedSource) { return undefined; }
|
|
86
|
+
|
|
87
|
+
const data: unknown = event.data;
|
|
88
|
+
if (typeof data !== 'object' || data === null) { return undefined; }
|
|
89
|
+
|
|
90
|
+
return data as Record<string, unknown>;
|
|
91
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DWeb Connect wallet transport — the wallet side of the popup/postMessage
|
|
3
|
+
* connect flow, exported for wallet apps (e.g. the Enbox web wallet).
|
|
4
|
+
*
|
|
5
|
+
* A wallet's `/dweb-connect` page creates one transport per session:
|
|
6
|
+
*
|
|
7
|
+
* 1. `create()` generates a fresh ephemeral X25519 key pair and emits the
|
|
8
|
+
* `loaded` beacon (public key + wallet origin) to the opener window with a
|
|
9
|
+
* pinned `targetOrigin`.
|
|
10
|
+
* 2. `awaitRequest()` receives the sealed request ciphertext and opens it via
|
|
11
|
+
* the `@enbox/connect` kernel (`ConnectProvider.openRequest`), which
|
|
12
|
+
* value-checks the `apv` origin binding against this wallet's origin.
|
|
13
|
+
* 3. After the consent ceremony, the wallet posts the sealed response JWE via
|
|
14
|
+
* `sendResponse()` — or the deny token via `deny()` — back to the opener,
|
|
15
|
+
* again with a pinned `targetOrigin`.
|
|
16
|
+
*
|
|
17
|
+
* The dapp origin is never assumed: it is taken from an explicit option or
|
|
18
|
+
* derived from `document.referrer`, and `create()` fails when neither is
|
|
19
|
+
* available. `'*'` is never used as a `targetOrigin`.
|
|
20
|
+
*
|
|
21
|
+
* @module
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type { ConnectRequest } from '@enbox/connect';
|
|
25
|
+
import type { Jwk } from '@enbox/crypto';
|
|
26
|
+
|
|
27
|
+
import { X25519 } from '@enbox/crypto';
|
|
28
|
+
import { CONNECT_DENIED_TOKEN, ConnectProvider } from '@enbox/connect';
|
|
29
|
+
|
|
30
|
+
import {
|
|
31
|
+
DWEB_CONNECT_LOADED_MESSAGE_TYPE,
|
|
32
|
+
DWEB_CONNECT_REQUEST_MESSAGE_TYPE,
|
|
33
|
+
DWEB_CONNECT_RESPONSE_MESSAGE_TYPE,
|
|
34
|
+
getTrustedMessage,
|
|
35
|
+
} from './dweb-connect-messages.js';
|
|
36
|
+
|
|
37
|
+
/** Default budget for awaiting the dapp's sealed request. */
|
|
38
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 300_000;
|
|
39
|
+
|
|
40
|
+
/** Options for creating a {@link WalletPostMessageTransport}. */
|
|
41
|
+
export type WalletPostMessageTransportOptions = {
|
|
42
|
+
/**
|
|
43
|
+
* Origin of the dapp that opened this wallet popup. When omitted, it is
|
|
44
|
+
* derived from `document.referrer`; creation fails when neither is
|
|
45
|
+
* available — the transport never falls back to `'*'`.
|
|
46
|
+
*/
|
|
47
|
+
dappOrigin?: string;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The dapp window to exchange messages with.
|
|
51
|
+
* @default window.opener
|
|
52
|
+
*/
|
|
53
|
+
dappWindow?: Window;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Budget in milliseconds for awaiting the dapp's sealed request.
|
|
57
|
+
* @default 300_000 (5 minutes)
|
|
58
|
+
*/
|
|
59
|
+
timeoutMs?: number;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Wallet-side postMessage transport for the DWeb Connect popup flow.
|
|
64
|
+
*
|
|
65
|
+
* Security properties:
|
|
66
|
+
* - `targetOrigin` of every outbound `postMessage` (beacon and response) is
|
|
67
|
+
* pinned to the dapp origin — never `'*'`.
|
|
68
|
+
* - Inbound messages are dropped unless `event.origin` equals the pinned dapp
|
|
69
|
+
* origin AND `event.source` is the dapp window.
|
|
70
|
+
* - The ephemeral X25519 private key never leaves the transport; the request
|
|
71
|
+
* JWE is opened by the kernel with the `apv` header value-checked against
|
|
72
|
+
* this wallet's own origin.
|
|
73
|
+
*/
|
|
74
|
+
export class WalletPostMessageTransport {
|
|
75
|
+
private readonly _dappOrigin: string;
|
|
76
|
+
private readonly _dappWindow: Window;
|
|
77
|
+
private readonly _recipientPrivateKey: Jwk;
|
|
78
|
+
|
|
79
|
+
private _requestReceived = false;
|
|
80
|
+
private _timeoutId?: ReturnType<typeof setTimeout>;
|
|
81
|
+
private _messageListener?: (event: MessageEvent) => void;
|
|
82
|
+
|
|
83
|
+
private readonly _requestPromise: Promise<ConnectRequest>;
|
|
84
|
+
private _resolveRequest?: (request: ConnectRequest) => void;
|
|
85
|
+
private _rejectRequest?: (error: Error) => void;
|
|
86
|
+
|
|
87
|
+
private constructor(params: { dappOrigin: string; dappWindow: Window; recipientPrivateKey: Jwk; timeoutMs: number }) {
|
|
88
|
+
this._dappOrigin = params.dappOrigin;
|
|
89
|
+
this._dappWindow = params.dappWindow;
|
|
90
|
+
this._recipientPrivateKey = params.recipientPrivateKey;
|
|
91
|
+
|
|
92
|
+
this._requestPromise = new Promise<ConnectRequest>((resolve, reject): void => {
|
|
93
|
+
this._resolveRequest = resolve;
|
|
94
|
+
this._rejectRequest = reject;
|
|
95
|
+
});
|
|
96
|
+
// Mark early rejections (timeout before `awaitRequest()` is called) as
|
|
97
|
+
// handled until the wallet attaches its own handlers.
|
|
98
|
+
this._requestPromise.catch((): undefined => undefined);
|
|
99
|
+
|
|
100
|
+
this._messageListener = (event: MessageEvent): void => { this.onMessage(event); };
|
|
101
|
+
window.addEventListener('message', this._messageListener);
|
|
102
|
+
|
|
103
|
+
this._timeoutId = setTimeout((): void => {
|
|
104
|
+
this.close();
|
|
105
|
+
this._rejectRequest?.(new Error('[@enbox/browser] Timed out waiting for the connect request from the dapp.'));
|
|
106
|
+
}, params.timeoutMs);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Creates the transport for one connect session: resolves and pins the dapp
|
|
111
|
+
* origin, generates a fresh ephemeral X25519 key pair, starts listening for
|
|
112
|
+
* the sealed request, and emits the `loaded` beacon to the dapp window.
|
|
113
|
+
*
|
|
114
|
+
* @throws If not in a browser, if no dapp window is available, or if the
|
|
115
|
+
* dapp origin can neither be supplied nor derived from the referrer.
|
|
116
|
+
*/
|
|
117
|
+
public static async create(options: WalletPostMessageTransportOptions = {}): Promise<WalletPostMessageTransport> {
|
|
118
|
+
if (typeof window === 'undefined') {
|
|
119
|
+
throw new Error('[@enbox/browser] WalletPostMessageTransport is only available in browser environments.');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const dappWindow = options.dappWindow ?? window.opener as Window | null;
|
|
123
|
+
if (!dappWindow) {
|
|
124
|
+
throw new Error('[@enbox/browser] No opener window: the DWeb Connect page must be opened as a popup by the dapp.');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const dappOrigin = resolveDappOrigin(options.dappOrigin);
|
|
128
|
+
|
|
129
|
+
const recipientPrivateKey = await X25519.generateKey();
|
|
130
|
+
const walletEpk: Jwk = { kty: 'OKP', crv: 'X25519', x: recipientPrivateKey.x };
|
|
131
|
+
|
|
132
|
+
const transport = new WalletPostMessageTransport({
|
|
133
|
+
dappOrigin,
|
|
134
|
+
dappWindow,
|
|
135
|
+
recipientPrivateKey,
|
|
136
|
+
timeoutMs: options.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// The listener is registered, so the beacon cannot race the request.
|
|
140
|
+
dappWindow.postMessage({
|
|
141
|
+
type : DWEB_CONNECT_LOADED_MESSAGE_TYPE,
|
|
142
|
+
walletEpk,
|
|
143
|
+
walletOrigin : globalThis.location.origin,
|
|
144
|
+
}, dappOrigin);
|
|
145
|
+
|
|
146
|
+
return transport;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** The pinned origin of the dapp this transport exchanges messages with. */
|
|
150
|
+
public get dappOrigin(): string {
|
|
151
|
+
return this._dappOrigin;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Awaits the dapp's sealed request and opens it via the kernel: ECDH-ES
|
|
156
|
+
* decryption with this session's ephemeral key, `apv` value-checked against
|
|
157
|
+
* this wallet's origin, JWT signature verification, and payload shape
|
|
158
|
+
* assertion. Rejects on timeout, on an unopenable request, or when the
|
|
159
|
+
* request does not use the `post_message` reply mode.
|
|
160
|
+
*/
|
|
161
|
+
public async awaitRequest(): Promise<ConnectRequest> {
|
|
162
|
+
return await this._requestPromise;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Posts the sealed response JWE (or the deny token) back to the dapp window
|
|
167
|
+
* with the `targetOrigin` pinned to the dapp origin, then closes the
|
|
168
|
+
* transport.
|
|
169
|
+
*
|
|
170
|
+
* @param idToken - The sealed response JWE, or the literal deny token.
|
|
171
|
+
*/
|
|
172
|
+
public sendResponse(idToken: string): void {
|
|
173
|
+
this._dappWindow.postMessage({
|
|
174
|
+
type : DWEB_CONNECT_RESPONSE_MESSAGE_TYPE,
|
|
175
|
+
payload : idToken,
|
|
176
|
+
}, this._dappOrigin);
|
|
177
|
+
this.close();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Posts the deny token back to the dapp — the user rejected the request. */
|
|
181
|
+
public deny(): void {
|
|
182
|
+
this.sendResponse(CONNECT_DENIED_TOKEN);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Stops listening and releases the session's timers. Idempotent. */
|
|
186
|
+
public close(): void {
|
|
187
|
+
clearTimeout(this._timeoutId);
|
|
188
|
+
if (this._messageListener !== undefined) {
|
|
189
|
+
window.removeEventListener('message', this._messageListener);
|
|
190
|
+
this._messageListener = undefined;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private onMessage(event: MessageEvent): void {
|
|
195
|
+
const message = getTrustedMessage(event, this._dappOrigin, this._dappWindow);
|
|
196
|
+
if (message === undefined) { return; }
|
|
197
|
+
|
|
198
|
+
if (message.type !== DWEB_CONNECT_REQUEST_MESSAGE_TYPE || this._requestReceived) { return; }
|
|
199
|
+
this._requestReceived = true;
|
|
200
|
+
|
|
201
|
+
if (typeof message.jwe !== 'string') {
|
|
202
|
+
this.settleRequest(new Error('[@enbox/browser] Connect request message did not carry a ciphertext payload.'));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
void this.openRequest(message.jwe);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private async openRequest(jwe: string): Promise<void> {
|
|
210
|
+
try {
|
|
211
|
+
const request = await ConnectProvider.openRequest({
|
|
212
|
+
jwe,
|
|
213
|
+
decryption: {
|
|
214
|
+
mode : 'ecdh-es',
|
|
215
|
+
recipientPrivateKey : this._recipientPrivateKey,
|
|
216
|
+
walletOrigin : globalThis.location.origin,
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
if (request.reply.mode !== 'post_message') {
|
|
221
|
+
this.settleRequest(new Error('[@enbox/browser] Connect request over the popup channel must use the `post_message` reply mode.'));
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
this.settleRequest(request);
|
|
226
|
+
} catch (error) {
|
|
227
|
+
this.settleRequest(error instanceof Error ? error : new Error(String(error)));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private settleRequest(outcome: ConnectRequest | Error): void {
|
|
232
|
+
clearTimeout(this._timeoutId);
|
|
233
|
+
if (outcome instanceof Error) {
|
|
234
|
+
this.close();
|
|
235
|
+
this._rejectRequest?.(outcome);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
this._resolveRequest?.(outcome);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Resolves the dapp origin to pin: the explicit option when provided,
|
|
244
|
+
* otherwise the origin of `document.referrer`. Never falls back to `'*'`.
|
|
245
|
+
*/
|
|
246
|
+
function resolveDappOrigin(explicitOrigin: string | undefined): string {
|
|
247
|
+
if (explicitOrigin !== undefined) {
|
|
248
|
+
return new URL(explicitOrigin).origin;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (document.referrer) {
|
|
252
|
+
return new URL(document.referrer).origin;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
throw new Error(
|
|
256
|
+
'[@enbox/browser] Unable to determine the dapp origin: no `dappOrigin` option was provided and `document.referrer` is empty.'
|
|
257
|
+
);
|
|
258
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -74,15 +74,34 @@ export type {
|
|
|
74
74
|
WalletConnectOptions,
|
|
75
75
|
} from '@enbox/auth/browser';
|
|
76
76
|
|
|
77
|
+
// ─── Re-exports from @enbox/connect ─────────────────────────────
|
|
78
|
+
//
|
|
79
|
+
// The connect kernel pieces wallet apps need alongside the popup
|
|
80
|
+
// transports: opening/sealing envelopes and the deny token.
|
|
81
|
+
|
|
82
|
+
export { CONNECT_DENIED_TOKEN, ConnectProvider } from '@enbox/connect';
|
|
83
|
+
export type { ConnectApproval, ConnectPermissionRequest, ConnectRequest } from '@enbox/connect';
|
|
84
|
+
|
|
77
85
|
// ─── Browser-specific exports ───────────────────────────────────
|
|
78
86
|
//
|
|
79
|
-
// DWeb Connect, wallet selector, DRL polyfills.
|
|
87
|
+
// DWeb Connect popup transports, wallet selector, DRL polyfills.
|
|
80
88
|
|
|
81
89
|
export * from './web-features.js';
|
|
82
90
|
export { BrowserConnectHandler, DEFAULT_WALLETS } from './browser-connect-handler.js';
|
|
83
91
|
export type { BrowserConnectHandlerOptions, WalletOption } from './browser-connect-handler.js';
|
|
84
|
-
export {
|
|
85
|
-
export type {
|
|
92
|
+
export { connectViaPopup, PopupClientTransport, PopupWindowClosedError } from './dweb-connect-client.js';
|
|
93
|
+
export type { PopupClientTransportOptions, PopupConnectOptions } from './dweb-connect-client.js';
|
|
94
|
+
export { WalletPostMessageTransport } from './dweb-connect-wallet.js';
|
|
95
|
+
export type { WalletPostMessageTransportOptions } from './dweb-connect-wallet.js';
|
|
96
|
+
export {
|
|
97
|
+
DWEB_CONNECT_LOADED_MESSAGE_TYPE,
|
|
98
|
+
DWEB_CONNECT_PATH,
|
|
99
|
+
DWEB_CONNECT_REQUEST_MESSAGE_TYPE,
|
|
100
|
+
DWEB_CONNECT_RESPONSE_MESSAGE_TYPE,
|
|
101
|
+
} from './dweb-connect-messages.js';
|
|
102
|
+
export type {
|
|
103
|
+
DWebConnectLoadedMessage,
|
|
104
|
+
DWebConnectRequestMessage,
|
|
105
|
+
DWebConnectResponseMessage,
|
|
106
|
+
} from './dweb-connect-messages.js';
|
|
86
107
|
export { showWalletSelector } from './ui/wallet-selector.js';
|
|
87
|
-
export { encryptPostMessagePayload, generateEphemeralKeyPair } from './dweb-connect-crypto.js';
|
|
88
|
-
export type { EncryptedPostMessagePayload } from './dweb-connect-crypto.js';
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ECDH + AES-256-GCM encryption for the DWeb Connect postMessage channel.
|
|
3
|
-
*
|
|
4
|
-
* Protects the authorization response (delegate private keys, decryption
|
|
5
|
-
* keys, grants) from same-origin XSS interception. Both the dapp and
|
|
6
|
-
* wallet generate ephemeral P-256 keypairs, exchange public keys during
|
|
7
|
-
* the handshake, derive a shared AES-256-GCM key via ECDH + HKDF, and
|
|
8
|
-
* encrypt/decrypt the payload.
|
|
9
|
-
*
|
|
10
|
-
* P-256 is used (instead of X25519) because `crypto.subtle.deriveKey`
|
|
11
|
-
* with ECDH requires a NIST curve in all major browsers. The keys are
|
|
12
|
-
* ephemeral — generated fresh for each connect session.
|
|
13
|
-
*
|
|
14
|
-
* @module
|
|
15
|
-
*/
|
|
16
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
17
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
18
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
19
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
20
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
21
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
22
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
23
|
-
});
|
|
24
|
-
};
|
|
25
|
-
const ECDH_PARAMS = { name: 'ECDH', namedCurve: 'P-256' };
|
|
26
|
-
const AES_KEY_PARAMS = { name: 'AES-GCM', length: 256 };
|
|
27
|
-
const HKDF_INFO = new TextEncoder().encode('dweb-connect-v1');
|
|
28
|
-
/**
|
|
29
|
-
* Generate an ephemeral P-256 ECDH keypair for a single connect session.
|
|
30
|
-
*
|
|
31
|
-
* @returns The keypair and the public key as a base64url-encoded raw export.
|
|
32
|
-
*/
|
|
33
|
-
export function generateEphemeralKeyPair() {
|
|
34
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
35
|
-
const keyPair = yield crypto.subtle.generateKey(ECDH_PARAMS, false, ['deriveBits']);
|
|
36
|
-
const rawPub = yield crypto.subtle.exportKey('raw', keyPair.publicKey);
|
|
37
|
-
return { keyPair, publicKeyBase64url: toBase64url(new Uint8Array(rawPub)) };
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Derive a shared AES-256-GCM key from a local ECDH private key and a
|
|
42
|
-
* remote public key.
|
|
43
|
-
*/
|
|
44
|
-
function deriveSharedKey(localPrivateKey, remotePublicKeyBase64url) {
|
|
45
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
46
|
-
const remotePubRaw = fromBase64url(remotePublicKeyBase64url);
|
|
47
|
-
const remotePublicKey = yield crypto.subtle.importKey('raw', remotePubRaw, ECDH_PARAMS, false, []);
|
|
48
|
-
// ECDH → raw shared secret, then HKDF → AES-256-GCM key.
|
|
49
|
-
const sharedBits = yield crypto.subtle.deriveBits({ name: 'ECDH', public: remotePublicKey }, localPrivateKey, 256);
|
|
50
|
-
const hkdfKey = yield crypto.subtle.importKey('raw', sharedBits, 'HKDF', false, ['deriveKey']);
|
|
51
|
-
return crypto.subtle.deriveKey({ name: 'HKDF', hash: 'SHA-256', salt: new Uint8Array(32), info: HKDF_INFO }, hkdfKey, AES_KEY_PARAMS, false, ['encrypt', 'decrypt']);
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Encrypt a JSON-serializable payload for postMessage transport.
|
|
56
|
-
*
|
|
57
|
-
* Called by the **wallet** side. Uses the wallet's ephemeral private key
|
|
58
|
-
* and the dapp's ephemeral public key to derive the shared AES key.
|
|
59
|
-
*
|
|
60
|
-
* @returns The encrypted payload plus the wallet's public key (for the dapp to derive the same shared key).
|
|
61
|
-
*/
|
|
62
|
-
export function encryptPostMessagePayload(payload, walletKeyPair, walletPublicKeyBase64url, dappPublicKeyBase64url) {
|
|
63
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
64
|
-
const sharedKey = yield deriveSharedKey(walletKeyPair.privateKey, dappPublicKeyBase64url);
|
|
65
|
-
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
66
|
-
const plaintext = new TextEncoder().encode(JSON.stringify(payload));
|
|
67
|
-
const ciphertextBuf = yield crypto.subtle.encrypt({ name: 'AES-GCM', iv }, sharedKey, plaintext);
|
|
68
|
-
return {
|
|
69
|
-
ciphertext: toBase64url(new Uint8Array(ciphertextBuf)),
|
|
70
|
-
iv: toBase64url(iv),
|
|
71
|
-
walletPublicKey: walletPublicKeyBase64url,
|
|
72
|
-
};
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Decrypt a postMessage payload received from the wallet.
|
|
77
|
-
*
|
|
78
|
-
* Called by the **dapp** side. Uses the dapp's ephemeral private key
|
|
79
|
-
* and the wallet's ephemeral public key to derive the same shared key.
|
|
80
|
-
*/
|
|
81
|
-
export function decryptPostMessagePayload(encrypted, dappKeyPair) {
|
|
82
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
83
|
-
const sharedKey = yield deriveSharedKey(dappKeyPair.privateKey, encrypted.walletPublicKey);
|
|
84
|
-
const iv = fromBase64url(encrypted.iv);
|
|
85
|
-
const ciphertext = fromBase64url(encrypted.ciphertext);
|
|
86
|
-
const plaintextBuf = yield crypto.subtle.decrypt({ name: 'AES-GCM', iv }, sharedKey, ciphertext);
|
|
87
|
-
return JSON.parse(new TextDecoder().decode(plaintextBuf));
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
// ── Base64url helpers ────────────────────────────────────────────
|
|
91
|
-
function toBase64url(bytes) {
|
|
92
|
-
let binary = '';
|
|
93
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
94
|
-
binary += String.fromCodePoint(bytes[i]);
|
|
95
|
-
}
|
|
96
|
-
let b64 = btoa(binary).replaceAll('+', '-').replaceAll('/', '_');
|
|
97
|
-
// Strip trailing padding without a backtracking-vulnerable regex.
|
|
98
|
-
while (b64.endsWith('=')) {
|
|
99
|
-
b64 = b64.slice(0, -1);
|
|
100
|
-
}
|
|
101
|
-
return b64;
|
|
102
|
-
}
|
|
103
|
-
function fromBase64url(str) {
|
|
104
|
-
const padded = str.replaceAll('-', '+').replaceAll('_', '/');
|
|
105
|
-
const binary = atob(padded);
|
|
106
|
-
const bytes = new Uint8Array(binary.length);
|
|
107
|
-
for (let i = 0; i < binary.length; i++) {
|
|
108
|
-
bytes[i] = binary.codePointAt(i);
|
|
109
|
-
}
|
|
110
|
-
return bytes;
|
|
111
|
-
}
|
|
112
|
-
//# sourceMappingURL=dweb-connect-crypto.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dweb-connect-crypto.js","sourceRoot":"","sources":["../../src/dweb-connect-crypto.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ECDH + AES-256-GCM encryption for the DWeb Connect postMessage channel.
|
|
3
|
-
*
|
|
4
|
-
* Protects the authorization response (delegate private keys, decryption
|
|
5
|
-
* keys, grants) from same-origin XSS interception. Both the dapp and
|
|
6
|
-
* wallet generate ephemeral P-256 keypairs, exchange public keys during
|
|
7
|
-
* the handshake, derive a shared AES-256-GCM key via ECDH + HKDF, and
|
|
8
|
-
* encrypt/decrypt the payload.
|
|
9
|
-
*
|
|
10
|
-
* P-256 is used (instead of X25519) because `crypto.subtle.deriveKey`
|
|
11
|
-
* with ECDH requires a NIST curve in all major browsers. The keys are
|
|
12
|
-
* ephemeral — generated fresh for each connect session.
|
|
13
|
-
*
|
|
14
|
-
* @module
|
|
15
|
-
*/
|
|
16
|
-
/** Exported public key as raw bytes (base64url-encoded for postMessage). */
|
|
17
|
-
export type ExportedPublicKey = string;
|
|
18
|
-
/** Encrypted payload sent via postMessage. */
|
|
19
|
-
export interface EncryptedPostMessagePayload {
|
|
20
|
-
/** The encrypted JSON payload as base64url. */
|
|
21
|
-
ciphertext: string;
|
|
22
|
-
/** AES-GCM initialization vector as base64url. */
|
|
23
|
-
iv: string;
|
|
24
|
-
/** The wallet's ephemeral P-256 public key as base64url raw bytes. */
|
|
25
|
-
walletPublicKey: string;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Generate an ephemeral P-256 ECDH keypair for a single connect session.
|
|
29
|
-
*
|
|
30
|
-
* @returns The keypair and the public key as a base64url-encoded raw export.
|
|
31
|
-
*/
|
|
32
|
-
export declare function generateEphemeralKeyPair(): Promise<{
|
|
33
|
-
keyPair: CryptoKeyPair;
|
|
34
|
-
publicKeyBase64url: ExportedPublicKey;
|
|
35
|
-
}>;
|
|
36
|
-
/**
|
|
37
|
-
* Encrypt a JSON-serializable payload for postMessage transport.
|
|
38
|
-
*
|
|
39
|
-
* Called by the **wallet** side. Uses the wallet's ephemeral private key
|
|
40
|
-
* and the dapp's ephemeral public key to derive the shared AES key.
|
|
41
|
-
*
|
|
42
|
-
* @returns The encrypted payload plus the wallet's public key (for the dapp to derive the same shared key).
|
|
43
|
-
*/
|
|
44
|
-
export declare function encryptPostMessagePayload(payload: Record<string, unknown>, walletKeyPair: CryptoKeyPair, walletPublicKeyBase64url: string, dappPublicKeyBase64url: string): Promise<EncryptedPostMessagePayload>;
|
|
45
|
-
/**
|
|
46
|
-
* Decrypt a postMessage payload received from the wallet.
|
|
47
|
-
*
|
|
48
|
-
* Called by the **dapp** side. Uses the dapp's ephemeral private key
|
|
49
|
-
* and the wallet's ephemeral public key to derive the same shared key.
|
|
50
|
-
*/
|
|
51
|
-
export declare function decryptPostMessagePayload(encrypted: EncryptedPostMessagePayload, dappKeyPair: CryptoKeyPair): Promise<Record<string, unknown>>;
|
|
52
|
-
//# sourceMappingURL=dweb-connect-crypto.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dweb-connect-crypto.d.ts","sourceRoot":"","sources":["../../src/dweb-connect-crypto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,4EAA4E;AAC5E,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC;AAEvC,8CAA8C;AAC9C,MAAM,WAAW,2BAA2B;IAC1C,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,EAAE,EAAE,MAAM,CAAC;IACX,sEAAsE;IACtE,eAAe,EAAE,MAAM,CAAC;CACzB;AAMD;;;;GAIG;AACH,wBAAsB,wBAAwB,IAAI,OAAO,CAAC;IACxD,OAAO,EAAE,aAAa,CAAC;IACvB,kBAAkB,EAAE,iBAAiB,CAAC;CACvC,CAAC,CAID;AAmCD;;;;;;;GAOG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,aAAa,EAAE,aAAa,EAC5B,wBAAwB,EAAE,MAAM,EAChC,sBAAsB,EAAE,MAAM,GAC7B,OAAO,CAAC,2BAA2B,CAAC,CAgBtC;AAED;;;;;GAKG;AACH,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,2BAA2B,EACtC,WAAW,EAAE,aAAa,GACzB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAYlC"}
|