@novasamatech/host-papp 0.7.9 → 0.8.0-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.
@@ -1,230 +1,168 @@
1
- import { enumValue } from '@novasamatech/scale';
2
- import { createAccountId, createEncryption, createLocalSessionAccount, createRemoteSessionAccount, khash, } from '@novasamatech/statement-store';
3
- import { generateMnemonic } from '@polkadot-labs/hdkd-helpers';
4
- import { Result, ResultAsync, err, fromPromise, fromThrowable, ok } from 'neverthrow';
5
- import { mergeUint8, toHex } from 'polkadot-api/utils';
6
- import { createEncrSecret, createSharedSecret, deriveSr25519Account, getEncrPub, stringToBytes } from '../../crypto.js';
1
+ import { createAccountId, createLocalSessionAccount, createRemoteSessionAccount } from '@novasamatech/statement-store';
2
+ import { ResultAsync, errAsync, okAsync } from 'neverthrow';
7
3
  import { createFlowId, emitHostPappDebugMessage } from '../../debugBus.js';
8
4
  import { AbortError } from '../../helpers/abortError.js';
9
5
  import { createState, readonly } from '../../helpers/state.js';
10
6
  import { toError } from '../../helpers/utils.js';
11
7
  import { createStoredUserSession } from '../userSessionRepository.js';
12
- import { HandshakeData, HandshakeResponsePayload, HandshakeResponseSensitiveData } from './scale/handshake.js';
13
- export function createAuth({ metadata, hostMetadata, statementStore, ssoSessionRepository, userSecretRepository, }) {
8
+ import { startPairingV2 } from './v2/service.js';
9
+ export function createAuth({ hostMetadata, deviceIdentity, deviceIdentityStore, statementStore, ssoSessionRepository, userSecretRepository, onAuthSuccess, }) {
14
10
  const pairingStatus = createState({ step: 'none' });
15
11
  let authResult = null;
16
- let abort = null;
17
- function handshake(account, signal, flowId) {
18
- const localAccount = createLocalSessionAccount(createAccountId(account.publicKey));
19
- pairingStatus.write({ step: 'initial' });
20
- const encrKeys = createEncrKeys(account.entropy);
21
- const handshakePayload = encrKeys.andThen(({ publicKey }) => createHandshakePayloadV1({
22
- ssPublicKey: account.publicKey,
23
- encrPublicKey: publicKey,
24
- metadata,
25
- hostMetadata,
26
- }));
27
- const handshakeTopic = encrKeys.andThen(({ publicKey }) => createHandshakeTopic(localAccount, publicKey));
28
- const dataPrepared = Result.combine([handshakePayload, handshakeTopic, encrKeys]).andTee(([payload]) => {
29
- const deeplink = createDeeplink(payload);
30
- pairingStatus.write({ step: 'pairing', payload: deeplink });
31
- emitHostPappDebugMessage({
32
- layer: 'sso',
33
- event: 'deeplink_generated',
34
- flowId,
35
- timestamp: Date.now(),
36
- payload: { deeplink },
37
- });
38
- });
39
- return dataPrepared
40
- .asyncAndThen(([, handshakeTopic, encrKeys]) => {
41
- emitHostPappDebugMessage({
42
- layer: 'sso',
43
- event: 'awaiting_response',
44
- flowId,
45
- timestamp: Date.now(),
46
- payload: { topic: toHex(handshakeTopic) },
47
- });
48
- const pappResponse = waitForStatements(callback => statementStore.subscribeStatements({ matchAll: [handshakeTopic] }, page => callback(page.statements)), signal, (statements, resolve) => {
49
- for (const statement of statements) {
50
- if (!statement.data)
51
- continue;
52
- const session = retrieveSession({
53
- localAccount,
54
- encrSecret: encrKeys.secret,
55
- payload: statement.data,
56
- }).unwrapOr(null);
57
- if (session) {
58
- emitHostPappDebugMessage({
59
- layer: 'sso',
60
- event: 'response_received',
61
- flowId,
62
- timestamp: Date.now(),
63
- payload: { sessionId: session.id },
64
- });
65
- resolve(session);
66
- break;
67
- }
68
- }
69
- });
70
- return pappResponse.map(session => ({
71
- session,
72
- secretsPayload: {
73
- id: session.id,
74
- ssSecret: account.secret,
75
- encrSecret: encrKeys.secret,
76
- entropy: account.entropy,
77
- },
78
- }));
79
- })
80
- .andTee(({ session }) => {
81
- pairingStatus.write({ step: 'finished', session });
82
- })
83
- .orTee(e => {
84
- if (!(e instanceof AbortError)) {
85
- pairingStatus.write({ step: 'pairingError', message: e.message });
86
- }
87
- });
88
- }
89
- const authModule = {
12
+ let abortHandle = null;
13
+ return {
90
14
  pairingStatus: readonly(pairingStatus),
91
15
  authenticate() {
92
- if (authResult) {
16
+ if (authResult)
93
17
  return authResult;
94
- }
95
- abort = new AbortController();
96
- const account = deriveSr25519Account(generateMnemonic(), '//wallet//sso');
97
- const ssoFlowId = createFlowId();
18
+ const flowId = createFlowId();
19
+ pairingStatus.write({ step: 'initial' });
98
20
  emitHostPappDebugMessage({
99
21
  layer: 'sso',
100
22
  event: 'pairing_started',
101
- flowId: ssoFlowId,
23
+ flowId,
102
24
  timestamp: Date.now(),
103
- payload: { metadata },
25
+ payload: { metadata: hostMetadata },
104
26
  });
105
- authResult = handshake(account, abort.signal, ssoFlowId)
106
- .andThen(({ session, secretsPayload }) => {
107
- return userSecretRepository
108
- .write(secretsPayload.id, {
109
- ssSecret: secretsPayload.ssSecret,
110
- encrSecret: secretsPayload.encrSecret,
111
- entropy: secretsPayload.entropy,
112
- })
113
- .andThen(() => ssoSessionRepository.add(session))
114
- .map(() => session);
115
- })
27
+ let aborted = false;
28
+ let pairingAbort = null;
29
+ abortHandle = () => {
30
+ aborted = true;
31
+ pairingAbort?.();
32
+ };
33
+ const resolveDeviceIdentity = () => deviceIdentity
34
+ ? ResultAsync.fromPromise(Promise.resolve(deviceIdentity()), toError)
35
+ : deviceIdentityStore.loadOrCreate();
36
+ const flow = ResultAsync.combine([
37
+ resolveDeviceIdentity(),
38
+ deviceIdentityStore.readLastProcessedHandshakeStatement(),
39
+ ]).andThen(([identity, initialHex]) => {
40
+ if (aborted)
41
+ return okAsync(null);
42
+ const pairing = startPairingV2({
43
+ statementStore,
44
+ deviceIdentity: identity,
45
+ metadata: hostMetadata ?? {},
46
+ initialProcessedDataHex: initialHex,
47
+ onStatementProcessed: hex => {
48
+ void deviceIdentityStore.writeLastProcessedHandshakeStatement(hex);
49
+ },
50
+ });
51
+ pairingAbort = pairing.abort;
52
+ pairingStatus.write({ step: 'pairing', payload: pairing.qrPayload });
53
+ emitHostPappDebugMessage({
54
+ layer: 'sso',
55
+ event: 'deeplink_generated',
56
+ flowId,
57
+ timestamp: Date.now(),
58
+ payload: { deeplink: pairing.qrPayload },
59
+ });
60
+ emitHostPappDebugMessage({
61
+ layer: 'sso',
62
+ event: 'awaiting_response',
63
+ flowId,
64
+ timestamp: Date.now(),
65
+ payload: {},
66
+ });
67
+ return ResultAsync.fromPromise(new Promise((resolve, reject) => {
68
+ let settled = false;
69
+ const settle = (cb) => {
70
+ if (settled)
71
+ return;
72
+ settled = true;
73
+ cb();
74
+ };
75
+ const sub = pairing.state$.subscribe({
76
+ next: state => onState(state, s => settle(() => resolve(s)), e => settle(() => reject(e)), () => sub?.unsubscribe()),
77
+ complete: () => {
78
+ if (aborted)
79
+ settle(() => reject(new AbortError('Aborted by user.')));
80
+ },
81
+ error: e => settle(() => reject(toError(e))),
82
+ });
83
+ }), toError).andThen(success => persistAndNotify(identity, success, flowId));
84
+ });
85
+ authResult = flow
86
+ .orElse(e => (e instanceof AbortError ? okAsync(null) : errAsync(e)))
116
87
  .andTee(session => {
117
- if (session) {
88
+ if (session === null) {
89
+ pairingStatus.reset();
90
+ }
91
+ else {
92
+ pairingStatus.write({ step: 'finished', session });
118
93
  emitHostPappDebugMessage({
119
94
  layer: 'sso',
120
95
  event: 'session_established',
121
- flowId: ssoFlowId,
96
+ flowId,
122
97
  timestamp: Date.now(),
123
98
  payload: { sessionId: session.id },
124
99
  });
125
100
  }
126
- })
127
- .orElse(e => (e instanceof AbortError ? ok(null) : err(e)))
128
- .andTee(() => {
129
- abort = null;
101
+ abortHandle = null;
130
102
  })
131
103
  .orTee(e => {
132
- authResult = null;
133
- abort = null;
104
+ pairingStatus.write({ step: 'pairingError', message: e.message });
134
105
  emitHostPappDebugMessage({
135
106
  layer: 'sso',
136
107
  event: 'pairing_failed',
137
- flowId: ssoFlowId,
108
+ flowId,
138
109
  timestamp: Date.now(),
139
110
  payload: { reason: e.message },
140
111
  });
112
+ authResult = null;
113
+ abortHandle = null;
141
114
  });
142
115
  return authResult;
116
+ function onState(state, resolve, reject, unsubscribe) {
117
+ switch (state.tag) {
118
+ case 'Idle':
119
+ case 'Submitted':
120
+ return;
121
+ case 'Pending':
122
+ pairingStatus.write({ step: 'pending', stage: state.reason });
123
+ return;
124
+ case 'Success':
125
+ unsubscribe();
126
+ emitHostPappDebugMessage({
127
+ layer: 'sso',
128
+ event: 'response_received',
129
+ flowId,
130
+ timestamp: Date.now(),
131
+ payload: { identityAccountId: state.identityAccountId },
132
+ });
133
+ resolve(state);
134
+ return;
135
+ case 'Failed':
136
+ unsubscribe();
137
+ reject(new Error(state.reason));
138
+ return;
139
+ }
140
+ }
143
141
  },
144
142
  abortAuthentication() {
145
- if (abort) {
146
- abort.abort(new AbortError('Aborted by user.'));
147
- abort = null;
148
- }
143
+ abortHandle?.();
144
+ abortHandle = null;
149
145
  authResult = null;
150
146
  pairingStatus.reset();
151
147
  },
152
148
  };
153
- return authModule;
154
- }
155
- const createHandshakeTopic = fromThrowable((account, encrPublicKey) => khash(account.accountId, mergeUint8([encrPublicKey, stringToBytes('topic')])), toError);
156
- const createHandshakePayloadV1 = fromThrowable(({ encrPublicKey, ssPublicKey, metadata, hostMetadata, }) => {
157
- const hostVersion = hostMetadata?.hostVersion;
158
- const osType = hostMetadata?.osType;
159
- const osVersion = hostMetadata?.osVersion;
160
- return HandshakeData.enc(enumValue('v1', {
161
- ssPublicKey,
162
- encrPublicKey,
163
- metadata,
164
- hostVersion,
165
- osType,
166
- osVersion,
167
- }));
168
- }, toError);
169
- function parseHandshakePayload(payload) {
170
- const decoded = HandshakeResponsePayload.dec(payload);
171
- switch (decoded.tag) {
172
- case 'v1':
173
- return decoded.value;
174
- default:
175
- throw new Error('Unsupported handshake payload version');
176
- }
177
- }
178
- const createEncrKeys = fromThrowable((entropy) => {
179
- const secret = createEncrSecret(entropy);
180
- return {
181
- secret,
182
- publicKey: getEncrPub(secret),
183
- };
184
- }, toError);
185
- function retrieveSession({ payload, encrSecret, localAccount, }) {
186
- const { encrypted, tmpKey } = parseHandshakePayload(payload);
187
- const symmetricKey = createSharedSecret(encrSecret, tmpKey);
188
- return createEncryption(symmetricKey)
189
- .decrypt(encrypted)
190
- .map(decrypted => {
191
- const { sharedSecretDerivationKey, rootUserAccountId, identityAccountId } = HandshakeResponseSensitiveData.dec(decrypted);
192
- const sharedSecret = createSharedSecret(encrSecret, sharedSecretDerivationKey);
193
- const remoteAccount = createRemoteSessionAccount(createAccountId(identityAccountId), sharedSecret);
194
- return createStoredUserSession(localAccount, remoteAccount, createAccountId(rootUserAccountId));
195
- });
196
- }
197
- function createDeeplink(payload) {
198
- return `polkadotapp://pair?handshake=${toHex(payload)}`;
199
- }
200
- function waitForStatements(subscribe, signal, callback) {
201
- return fromPromise(new Promise((resolve, reject) => {
202
- const unsubscribe = subscribe(statements => {
203
- const abortError = processSignal(signal).match(() => null, e => e);
204
- if (abortError) {
205
- unsubscribe();
206
- reject(abortError);
207
- return;
208
- }
209
- try {
210
- callback(statements, value => {
211
- unsubscribe();
212
- resolve(value);
213
- });
214
- }
215
- catch (e) {
216
- unsubscribe();
217
- reject(e);
218
- }
149
+ function persistAndNotify(identity, success, _flowId) {
150
+ const localAccount = createLocalSessionAccount(createAccountId(identity.statementAccountPublicKey));
151
+ const remoteAccount = createRemoteSessionAccount(createAccountId(success.peerStatementAccountId ?? new Uint8Array(32)), success.deviceEncPubKey);
152
+ const session = createStoredUserSession(localAccount, remoteAccount, createAccountId(success.rootAccountId ?? new Uint8Array(32)), {
153
+ identityAccountId: createAccountId(success.identityAccountId),
154
+ identityChatPublicKey: success.identityChatPublicKey,
219
155
  });
220
- }), toError);
221
- }
222
- function processSignal(signal) {
223
- try {
224
- signal.throwIfAborted();
225
- return ok();
226
- }
227
- catch (e) {
228
- return err(toError(e));
156
+ return userSecretRepository
157
+ .write(session.id, {
158
+ ssSecret: identity.statementAccountSecret,
159
+ encrSecret: identity.encryptionPrivateKey,
160
+ entropy: new Uint8Array(0),
161
+ identityChatPrivateKey: success.identityChatPrivateKey,
162
+ })
163
+ .andThen(() => ssoSessionRepository.add(session))
164
+ .andThen(() => onAuthSuccess
165
+ ? ResultAsync.fromPromise(Promise.resolve(onAuthSuccess({ session, identityChatPrivateKey: success.identityChatPrivateKey })), toError).map(() => session)
166
+ : okAsync(session));
229
167
  }
230
168
  }
@@ -0,0 +1,260 @@
1
+ /**
2
+ * SCALE codecs for the V2 SSO handshake (multi-device shape).
3
+ *
4
+ * The host emits a `VersionedHandshakeProposal::V2` via QR carrying its
5
+ * `Device { statementAccountId, encryptionPublicKey }` and metadata. The
6
+ * authorising peer (PApp) responds over the Statement Store with a
7
+ * `VersionedHandshakeResponse`; its body is ECDH-encrypted to the host's
8
+ * encryption public key with the peer's ephemeral `tmpKey`. The inner payload
9
+ * after decrypt is `EncryptedHandshakeResponseV2 = Pending | Success | Failed`.
10
+ *
11
+ * `Success` carries:
12
+ * - `identityAccountId` — user identity sr25519 accountId (32 bytes).
13
+ * Adressing for chat / username lookup / session
14
+ * topic derivation.
15
+ * - `rootAccountId` — user root sr25519 accountId (32 bytes). Parent
16
+ * for soft-derivation of product accounts; PApp
17
+ * and host MUST derive identically so a dapp sees
18
+ * the same address on every device.
19
+ * - `identityChatPrivateKey`— user identity chat P-256 private scalar (32 bytes),
20
+ * shared per the multi-device spec so this device
21
+ * can decrypt traffic addressed to the user identity
22
+ * - `deviceEncPubKey` — encryption public key of the PApp device (65 bytes,
23
+ * P-256 uncompressed). Tells the host which key to
24
+ * use when addressing chat envelopes back to the
25
+ * authorising PApp device.
26
+ *
27
+ * Total wire length of `Success` is 32 + 32 + 32 + 65 = 161 bytes. Transit security
28
+ * comes from the outer envelope's ECDH-AES wrap; no per-field signature is
29
+ * carried — multi-device authorisation is asserted by the user-identity-signed
30
+ * roster events (`DeviceAdded`/`DeviceRemoved`) published separately.
31
+ */
32
+ export declare const MetadataKey: import("scale-ts").Codec<{
33
+ tag: "Custom";
34
+ value: string;
35
+ } | {
36
+ tag: "HostName";
37
+ value: undefined;
38
+ } | {
39
+ tag: "HostVersion";
40
+ value: undefined;
41
+ } | {
42
+ tag: "HostIcon";
43
+ value: undefined;
44
+ } | {
45
+ tag: "PlatformType";
46
+ value: undefined;
47
+ } | {
48
+ tag: "PlatformVersion";
49
+ value: undefined;
50
+ }>;
51
+ export declare const MetadataEntry: import("scale-ts").Codec<[{
52
+ tag: "Custom";
53
+ value: string;
54
+ } | {
55
+ tag: "HostName";
56
+ value: undefined;
57
+ } | {
58
+ tag: "HostVersion";
59
+ value: undefined;
60
+ } | {
61
+ tag: "HostIcon";
62
+ value: undefined;
63
+ } | {
64
+ tag: "PlatformType";
65
+ value: undefined;
66
+ } | {
67
+ tag: "PlatformVersion";
68
+ value: undefined;
69
+ }, string]>;
70
+ export declare const Device: import("scale-ts").Codec<{
71
+ statementAccountId: Uint8Array<ArrayBufferLike>;
72
+ encryptionPublicKey: Uint8Array<ArrayBufferLike>;
73
+ }>;
74
+ export declare const HandshakeProposalV2: import("scale-ts").Codec<{
75
+ device: {
76
+ statementAccountId: Uint8Array<ArrayBufferLike>;
77
+ encryptionPublicKey: Uint8Array<ArrayBufferLike>;
78
+ };
79
+ metadata: [{
80
+ tag: "Custom";
81
+ value: string;
82
+ } | {
83
+ tag: "HostName";
84
+ value: undefined;
85
+ } | {
86
+ tag: "HostVersion";
87
+ value: undefined;
88
+ } | {
89
+ tag: "HostIcon";
90
+ value: undefined;
91
+ } | {
92
+ tag: "PlatformType";
93
+ value: undefined;
94
+ } | {
95
+ tag: "PlatformVersion";
96
+ value: undefined;
97
+ }, string][];
98
+ }>;
99
+ /**
100
+ * V2 lives at SCALE discriminant 1; index 0 is reserved for legacy V1 which
101
+ * neither side emits. The `_v1Reserved` slot pushes V2 to discriminant 1.
102
+ */
103
+ export declare const VersionedHandshakeProposal: import("scale-ts").Codec<{
104
+ tag: "_v1Reserved";
105
+ value: undefined;
106
+ } | {
107
+ tag: "V2";
108
+ value: {
109
+ device: {
110
+ statementAccountId: Uint8Array<ArrayBufferLike>;
111
+ encryptionPublicKey: Uint8Array<ArrayBufferLike>;
112
+ };
113
+ metadata: [{
114
+ tag: "Custom";
115
+ value: string;
116
+ } | {
117
+ tag: "HostName";
118
+ value: undefined;
119
+ } | {
120
+ tag: "HostVersion";
121
+ value: undefined;
122
+ } | {
123
+ tag: "HostIcon";
124
+ value: undefined;
125
+ } | {
126
+ tag: "PlatformType";
127
+ value: undefined;
128
+ } | {
129
+ tag: "PlatformVersion";
130
+ value: undefined;
131
+ }, string][];
132
+ };
133
+ }>;
134
+ /** 32 + 32 + 32 + 65 = 161 bytes (spec v0.2.1) */
135
+ export declare const HandshakeSuccessV2: import("scale-ts").Codec<{
136
+ identityAccountId: Uint8Array<ArrayBufferLike>;
137
+ rootAccountId: Uint8Array<ArrayBufferLike>;
138
+ identityChatPrivateKey: Uint8Array<ArrayBufferLike>;
139
+ deviceEncPubKey: Uint8Array<ArrayBufferLike>;
140
+ }>;
141
+ export type HandshakeSuccessV2Value = {
142
+ identityAccountId: Uint8Array;
143
+ /** Nullable for v0.2 peers (Android `feature/location-for-handshake`). */
144
+ rootAccountId: Uint8Array | null;
145
+ identityChatPrivateKey: Uint8Array;
146
+ deviceEncPubKey: Uint8Array;
147
+ };
148
+ /** 32 + 32 + 65 = 129 bytes (spec v0.2 — Android `feature/location-for-handshake`) */
149
+ export declare const HandshakeSuccessV2Legacy: import("scale-ts").Codec<{
150
+ identityAccountId: Uint8Array<ArrayBufferLike>;
151
+ identityChatPrivateKey: Uint8Array<ArrayBufferLike>;
152
+ deviceEncPubKey: Uint8Array<ArrayBufferLike>;
153
+ }>;
154
+ export type DecodedHandshakeResponseV2 = {
155
+ tag: 'Pending';
156
+ value: {
157
+ tag: 'AllowanceAllocation';
158
+ value: undefined;
159
+ };
160
+ } | {
161
+ tag: 'Success';
162
+ value: HandshakeSuccessV2Value;
163
+ } | {
164
+ tag: 'Failed';
165
+ value: string;
166
+ };
167
+ /**
168
+ * Length-dispatched decoder for the inner `EncryptedHandshakeResponseV2`
169
+ * plaintext. v0.2 (Android) ships 129-byte Success bodies without
170
+ * `rootAccountId`; v0.2.1 ships 161-byte bodies with it. Surfaces
171
+ * `rootAccountId: null` for the legacy case — chat doesn't need it.
172
+ */
173
+ export declare const decodeEncryptedHandshakeResponseV2: (bytes: Uint8Array) => DecodedHandshakeResponseV2;
174
+ /**
175
+ * Derive the user identity chat P-256 public key from the shared private
176
+ * scalar received in `HandshakeSuccessV2`. Both desktop and mobile must derive
177
+ * identically (uncompressed 65-byte form) so downstream session topics agree.
178
+ */
179
+ export declare const deriveIdentityChatPublicKey: (privateKey: Uint8Array) => Uint8Array;
180
+ /**
181
+ * Inner Pending sub-statuses. Only `AllowanceAllocation` today.
182
+ *
183
+ * Encoded with its own SCALE discriminant byte — the peer's SCALE library does
184
+ * NOT elide enum-variant indices for sealed interfaces (verified on the wire:
185
+ * `Pending(AllowanceAllocation)` arrives as `0x00 0x00`). Both sides must keep
186
+ * the discriminant when encoding so dispatch agrees.
187
+ */
188
+ export declare const HandshakeStatusV2: import("scale-ts").Codec<{
189
+ tag: "AllowanceAllocation";
190
+ value: undefined;
191
+ }>;
192
+ export type EncryptedHandshakeResponseV2Value = {
193
+ tag: 'Pending';
194
+ value: {
195
+ tag: 'AllowanceAllocation';
196
+ value: undefined;
197
+ };
198
+ } | {
199
+ tag: 'Success';
200
+ value: HandshakeSuccessV2Value;
201
+ } | {
202
+ tag: 'Failed';
203
+ value: string;
204
+ };
205
+ /**
206
+ * Inner handshake response variant. Wire shape (matches peer SCALE encoding):
207
+ *
208
+ * - Pending → 0x00 || HandshakeStatusV2 (today: 0x00 for AllowanceAllocation), 2 bytes total
209
+ * - Success → 0x01 || HandshakeSuccessV2 (161 bytes), 162 bytes total
210
+ * - Failed → 0x02 || SCALE-encoded UTF-8 reason string
211
+ *
212
+ * Earlier builds shipped a custom length-dispatched codec assuming elision —
213
+ * that assumption was wrong and produced false `Failed("")` reads on every
214
+ * `Pending` response. Native scale-ts `Enum` preserves the discriminant and
215
+ * matches the peer's wire format directly.
216
+ */
217
+ export declare const EncryptedHandshakeResponseV2: import("scale-ts").Codec<{
218
+ tag: "Pending";
219
+ value: {
220
+ tag: "AllowanceAllocation";
221
+ value: undefined;
222
+ };
223
+ } | {
224
+ tag: "Success";
225
+ value: {
226
+ identityAccountId: Uint8Array<ArrayBufferLike>;
227
+ rootAccountId: Uint8Array<ArrayBufferLike>;
228
+ identityChatPrivateKey: Uint8Array<ArrayBufferLike>;
229
+ deviceEncPubKey: Uint8Array<ArrayBufferLike>;
230
+ };
231
+ } | {
232
+ tag: "Failed";
233
+ value: string;
234
+ }>;
235
+ export declare const HandshakeResponseV2: import("scale-ts").Codec<{
236
+ encrypted: Uint8Array<ArrayBufferLike>;
237
+ tmpKey: Uint8Array<ArrayBufferLike>;
238
+ }>;
239
+ /** Legacy V1 response shape — decoded for backward compat, never emitted by the V2 path. */
240
+ export declare const HandshakeResponseV1: import("scale-ts").Codec<{
241
+ encrypted: Uint8Array<ArrayBufferLike>;
242
+ tmpKey: Uint8Array<ArrayBufferLike>;
243
+ }>;
244
+ export declare const EncryptedHandshakeResponseV1: import("scale-ts").Codec<{
245
+ encryptionKey: Uint8Array<ArrayBufferLike>;
246
+ accountId: Uint8Array<ArrayBufferLike>;
247
+ }>;
248
+ export declare const VersionedHandshakeResponse: import("scale-ts").Codec<{
249
+ tag: "V2";
250
+ value: {
251
+ encrypted: Uint8Array<ArrayBufferLike>;
252
+ tmpKey: Uint8Array<ArrayBufferLike>;
253
+ };
254
+ } | {
255
+ tag: "V1";
256
+ value: {
257
+ encrypted: Uint8Array<ArrayBufferLike>;
258
+ tmpKey: Uint8Array<ArrayBufferLike>;
259
+ };
260
+ }>;