@novasamatech/host-papp 0.8.4 → 0.8.6
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/sso/auth/impl.d.ts +2 -0
- package/dist/sso/auth/impl.js +14 -4
- package/dist/sso/auth/scale/handshakeV2.d.ts +14 -82
- package/dist/sso/auth/scale/handshakeV2.js +16 -93
- package/dist/sso/auth/v2/service.d.ts +4 -0
- package/dist/sso/auth/v2/service.js +4 -5
- package/dist/sso/auth/v2/state.d.ts +7 -27
- package/dist/sso/auth/v2/state.js +5 -3
- package/dist/sso/sessionManager/userSession.js +6 -0
- package/dist/sso/userSecretRepository.d.ts +0 -1
- package/dist/sso/userSecretRepository.js +2 -11
- package/dist/sso/userSessionRepository.d.ts +53 -27
- package/dist/sso/userSessionRepository.js +16 -17
- package/package.json +5 -5
package/dist/sso/auth/impl.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export type AuthComponent = ReturnType<typeof createAuth>;
|
|
|
17
17
|
export type OnAuthSuccess = (event: {
|
|
18
18
|
session: StoredUserSession;
|
|
19
19
|
identityChatPrivateKey: Uint8Array;
|
|
20
|
+
/** `papp_encr_pub` — PApp's P-256 SSO ECDH public key. */
|
|
21
|
+
ssoEncPubKey: Uint8Array;
|
|
20
22
|
}) => Promise<void> | void;
|
|
21
23
|
type Params = {
|
|
22
24
|
hostMetadata?: HostMetadata;
|
package/dist/sso/auth/impl.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createAccountId, createLocalSessionAccount, createRemoteSessionAccount } from '@novasamatech/statement-store';
|
|
2
2
|
import { ResultAsync, errAsync, okAsync } from 'neverthrow';
|
|
3
|
+
import { createSharedSecret } from '../../crypto.js';
|
|
3
4
|
import { createFlowId, emitHostPappDebugMessage } from '../../debugBus.js';
|
|
4
5
|
import { AbortError } from '../../helpers/abortError.js';
|
|
5
6
|
import { createState, readonly } from '../../helpers/state.js';
|
|
@@ -148,21 +149,30 @@ export function createAuth({ hostMetadata, deviceIdentity, deviceIdentityStore,
|
|
|
148
149
|
};
|
|
149
150
|
function persistAndNotify(identity, success, _flowId) {
|
|
150
151
|
const localAccount = createLocalSessionAccount(createAccountId(identity.statementAccountPublicKey));
|
|
151
|
-
|
|
152
|
-
|
|
152
|
+
if (!success.peerStatementAccountId) {
|
|
153
|
+
return errAsync(new Error("Can't derive peerStatementAccountId from statement proof signer"));
|
|
154
|
+
}
|
|
155
|
+
const sharedSecret = createSharedSecret(identity.encryptionPrivateKey, success.ssoEncPubKey);
|
|
156
|
+
const remoteAccount = createRemoteSessionAccount(createAccountId(success.peerStatementAccountId), sharedSecret);
|
|
157
|
+
const session = createStoredUserSession(localAccount, remoteAccount, createAccountId(success.rootAccountId), {
|
|
153
158
|
identityAccountId: createAccountId(success.identityAccountId),
|
|
154
159
|
identityChatPublicKey: success.identityChatPublicKey,
|
|
160
|
+
ssoEncPubKey: success.ssoEncPubKey,
|
|
161
|
+
rootEntropySource: success.rootEntropySource,
|
|
155
162
|
});
|
|
156
163
|
return userSecretRepository
|
|
157
164
|
.write(session.id, {
|
|
158
165
|
ssSecret: identity.statementAccountSecret,
|
|
159
166
|
encrSecret: identity.encryptionPrivateKey,
|
|
160
|
-
entropy: new Uint8Array(0),
|
|
161
167
|
identityChatPrivateKey: success.identityChatPrivateKey,
|
|
162
168
|
})
|
|
163
169
|
.andThen(() => ssoSessionRepository.add(session))
|
|
164
170
|
.andThen(() => onAuthSuccess
|
|
165
|
-
? ResultAsync.fromPromise(Promise.resolve(onAuthSuccess({
|
|
171
|
+
? ResultAsync.fromPromise(Promise.resolve(onAuthSuccess({
|
|
172
|
+
session,
|
|
173
|
+
identityChatPrivateKey: success.identityChatPrivateKey,
|
|
174
|
+
ssoEncPubKey: success.ssoEncPubKey,
|
|
175
|
+
})), toError).map(() => session)
|
|
166
176
|
: okAsync(session));
|
|
167
177
|
}
|
|
168
178
|
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* `Success` carries:
|
|
12
12
|
* - `identityAccountId` — user identity sr25519 accountId (32 bytes).
|
|
13
|
-
*
|
|
13
|
+
* Addressing for chat / username lookup / session
|
|
14
14
|
* topic derivation.
|
|
15
15
|
* - `rootAccountId` — user root sr25519 accountId (32 bytes). Parent
|
|
16
16
|
* for soft-derivation of product accounts; PApp
|
|
@@ -23,11 +23,13 @@
|
|
|
23
23
|
* P-256 uncompressed). Tells the host which key to
|
|
24
24
|
* use when addressing chat envelopes back to the
|
|
25
25
|
* authorising PApp device.
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
26
|
+
* - `ssoEncPubKey` — `papp_encr_pub` (65 bytes, P-256 uncompressed); see
|
|
27
|
+
* the `HandshakeSuccessV2` codec doc below.
|
|
28
|
+
* - `rootEntropySource` — 32 bytes; `blake2b256_keyed(rootAccountSecret,
|
|
29
|
+
* "product-entropy-derivation")` per RFC-0007 (layer 1).
|
|
30
|
+
* Lets the host derive product entropy deterministically
|
|
31
|
+
* (`host_derive_entropy`) without ever holding the raw
|
|
32
|
+
* root account secret. See the codec doc below.
|
|
31
33
|
*/
|
|
32
34
|
export declare const MetadataKey: import("scale-ts").Codec<{
|
|
33
35
|
tag: "Custom";
|
|
@@ -96,10 +98,6 @@ export declare const HandshakeProposalV2: import("scale-ts").Codec<{
|
|
|
96
98
|
value: undefined;
|
|
97
99
|
}, string][];
|
|
98
100
|
}>;
|
|
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
101
|
export declare const VersionedHandshakeProposal: import("scale-ts").Codec<{
|
|
104
102
|
tag: "_v1Reserved";
|
|
105
103
|
value: undefined;
|
|
@@ -131,89 +129,21 @@ export declare const VersionedHandshakeProposal: import("scale-ts").Codec<{
|
|
|
131
129
|
}, string][];
|
|
132
130
|
};
|
|
133
131
|
}>;
|
|
134
|
-
/** 32 + 32 + 32 + 65 = 161 bytes (spec v0.2.1) */
|
|
135
132
|
export declare const HandshakeSuccessV2: import("scale-ts").Codec<{
|
|
136
133
|
identityAccountId: Uint8Array<ArrayBufferLike>;
|
|
137
134
|
rootAccountId: Uint8Array<ArrayBufferLike>;
|
|
138
135
|
identityChatPrivateKey: Uint8Array<ArrayBufferLike>;
|
|
136
|
+
ssoEncPubKey: Uint8Array<ArrayBufferLike>;
|
|
139
137
|
deviceEncPubKey: Uint8Array<ArrayBufferLike>;
|
|
138
|
+
rootEntropySource: Uint8Array<ArrayBufferLike>;
|
|
140
139
|
}>;
|
|
141
|
-
|
|
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
|
-
*/
|
|
140
|
+
/** Derive the identity chat P-256 public key (uncompressed) from its private scalar. */
|
|
179
141
|
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
142
|
export declare const HandshakeStatusV2: import("scale-ts").Codec<{
|
|
189
143
|
tag: "AllowanceAllocation";
|
|
190
144
|
value: undefined;
|
|
191
145
|
}>;
|
|
192
|
-
|
|
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
|
-
*/
|
|
146
|
+
/** Inner handshake response variant. */
|
|
217
147
|
export declare const EncryptedHandshakeResponseV2: import("scale-ts").Codec<{
|
|
218
148
|
tag: "Failed";
|
|
219
149
|
value: string;
|
|
@@ -229,7 +159,9 @@ export declare const EncryptedHandshakeResponseV2: import("scale-ts").Codec<{
|
|
|
229
159
|
identityAccountId: Uint8Array<ArrayBufferLike>;
|
|
230
160
|
rootAccountId: Uint8Array<ArrayBufferLike>;
|
|
231
161
|
identityChatPrivateKey: Uint8Array<ArrayBufferLike>;
|
|
162
|
+
ssoEncPubKey: Uint8Array<ArrayBufferLike>;
|
|
232
163
|
deviceEncPubKey: Uint8Array<ArrayBufferLike>;
|
|
164
|
+
rootEntropySource: Uint8Array<ArrayBufferLike>;
|
|
233
165
|
};
|
|
234
166
|
}>;
|
|
235
167
|
export declare const HandshakeResponseV2: import("scale-ts").Codec<{
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* `Success` carries:
|
|
12
12
|
* - `identityAccountId` — user identity sr25519 accountId (32 bytes).
|
|
13
|
-
*
|
|
13
|
+
* Addressing for chat / username lookup / session
|
|
14
14
|
* topic derivation.
|
|
15
15
|
* - `rootAccountId` — user root sr25519 accountId (32 bytes). Parent
|
|
16
16
|
* for soft-derivation of product accounts; PApp
|
|
@@ -23,18 +23,20 @@
|
|
|
23
23
|
* P-256 uncompressed). Tells the host which key to
|
|
24
24
|
* use when addressing chat envelopes back to the
|
|
25
25
|
* authorising PApp device.
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
26
|
+
* - `ssoEncPubKey` — `papp_encr_pub` (65 bytes, P-256 uncompressed); see
|
|
27
|
+
* the `HandshakeSuccessV2` codec doc below.
|
|
28
|
+
* - `rootEntropySource` — 32 bytes; `blake2b256_keyed(rootAccountSecret,
|
|
29
|
+
* "product-entropy-derivation")` per RFC-0007 (layer 1).
|
|
30
|
+
* Lets the host derive product entropy deterministically
|
|
31
|
+
* (`host_derive_entropy`) without ever holding the raw
|
|
32
|
+
* root account secret. See the codec doc below.
|
|
31
33
|
*/
|
|
32
34
|
import { p256 } from '@noble/curves/nist.js';
|
|
33
35
|
import { Bytes, Enum, Struct, Tuple, Vector, _void, str } from 'scale-ts';
|
|
34
36
|
const AccountIdCodec = Bytes(32);
|
|
35
37
|
const PublicKeyCodec = Bytes(65);
|
|
36
38
|
const PrivateKeyCodec = Bytes(32);
|
|
37
|
-
|
|
39
|
+
const EntropySourceCodec = Bytes(32);
|
|
38
40
|
export const MetadataKey = Enum({
|
|
39
41
|
Custom: str,
|
|
40
42
|
HostName: _void,
|
|
@@ -52,106 +54,27 @@ export const HandshakeProposalV2 = Struct({
|
|
|
52
54
|
device: Device,
|
|
53
55
|
metadata: Vector(MetadataEntry),
|
|
54
56
|
});
|
|
55
|
-
/**
|
|
56
|
-
* V2 lives at SCALE discriminant 1; index 0 is reserved for legacy V1 which
|
|
57
|
-
* neither side emits. The `_v1Reserved` slot pushes V2 to discriminant 1.
|
|
58
|
-
*/
|
|
59
57
|
export const VersionedHandshakeProposal = Enum({
|
|
60
58
|
_v1Reserved: _void,
|
|
61
59
|
V2: HandshakeProposalV2,
|
|
62
60
|
});
|
|
63
|
-
//
|
|
64
|
-
/** 32 + 32 + 32 + 65 = 161 bytes (spec v0.2.1) */
|
|
61
|
+
// V2 Response
|
|
65
62
|
export const HandshakeSuccessV2 = Struct({
|
|
66
63
|
identityAccountId: AccountIdCodec,
|
|
67
64
|
rootAccountId: AccountIdCodec,
|
|
68
65
|
identityChatPrivateKey: PrivateKeyCodec,
|
|
66
|
+
/** PApp's P-256 SSO ECDH public key (`papp_encr_pub`). */
|
|
67
|
+
ssoEncPubKey: PublicKeyCodec,
|
|
69
68
|
deviceEncPubKey: PublicKeyCodec,
|
|
69
|
+
/** Layer-1 source for deterministic product entropy derivation (RFC-0007). */
|
|
70
|
+
rootEntropySource: EntropySourceCodec,
|
|
70
71
|
});
|
|
71
|
-
/**
|
|
72
|
-
export const HandshakeSuccessV2Legacy = Struct({
|
|
73
|
-
identityAccountId: AccountIdCodec,
|
|
74
|
-
identityChatPrivateKey: PrivateKeyCodec,
|
|
75
|
-
deviceEncPubKey: PublicKeyCodec,
|
|
76
|
-
});
|
|
77
|
-
/**
|
|
78
|
-
* Length-dispatched decoder for the inner `EncryptedHandshakeResponseV2`
|
|
79
|
-
* plaintext. v0.2 (Android) ships 129-byte Success bodies without
|
|
80
|
-
* `rootAccountId`; v0.2.1 ships 161-byte bodies with it. Surfaces
|
|
81
|
-
* `rootAccountId: null` for the legacy case — chat doesn't need it.
|
|
82
|
-
*/
|
|
83
|
-
export const decodeEncryptedHandshakeResponseV2 = (bytes) => {
|
|
84
|
-
if (bytes.length === 0)
|
|
85
|
-
throw new Error('EncryptedHandshakeResponseV2: empty plaintext');
|
|
86
|
-
const tag = bytes[0];
|
|
87
|
-
// `slice` not `subarray` — scale-ts decoders read from buffer byteOffset 0.
|
|
88
|
-
const body = bytes.slice(1);
|
|
89
|
-
if (tag === 0) {
|
|
90
|
-
if (body.length === 0)
|
|
91
|
-
throw new Error('EncryptedHandshakeResponseV2: Pending body empty');
|
|
92
|
-
return { tag: 'Pending', value: { tag: 'AllowanceAllocation', value: undefined } };
|
|
93
|
-
}
|
|
94
|
-
if (tag === 1) {
|
|
95
|
-
if (body.length === 161) {
|
|
96
|
-
const decoded = HandshakeSuccessV2.dec(body);
|
|
97
|
-
return {
|
|
98
|
-
tag: 'Success',
|
|
99
|
-
value: {
|
|
100
|
-
identityAccountId: decoded.identityAccountId,
|
|
101
|
-
rootAccountId: decoded.rootAccountId,
|
|
102
|
-
identityChatPrivateKey: decoded.identityChatPrivateKey,
|
|
103
|
-
deviceEncPubKey: decoded.deviceEncPubKey,
|
|
104
|
-
},
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
if (body.length === 129) {
|
|
108
|
-
const decoded = HandshakeSuccessV2Legacy.dec(body);
|
|
109
|
-
return {
|
|
110
|
-
tag: 'Success',
|
|
111
|
-
value: {
|
|
112
|
-
identityAccountId: decoded.identityAccountId,
|
|
113
|
-
rootAccountId: null,
|
|
114
|
-
identityChatPrivateKey: decoded.identityChatPrivateKey,
|
|
115
|
-
deviceEncPubKey: decoded.deviceEncPubKey,
|
|
116
|
-
},
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
throw new Error(`EncryptedHandshakeResponseV2: Success body length ${body.length} not in {129, 161}`);
|
|
120
|
-
}
|
|
121
|
-
if (tag === 2) {
|
|
122
|
-
return { tag: 'Failed', value: str.dec(body) };
|
|
123
|
-
}
|
|
124
|
-
throw new Error(`EncryptedHandshakeResponseV2: unknown variant tag ${tag}`);
|
|
125
|
-
};
|
|
126
|
-
/**
|
|
127
|
-
* Derive the user identity chat P-256 public key from the shared private
|
|
128
|
-
* scalar received in `HandshakeSuccessV2`. Both desktop and mobile must derive
|
|
129
|
-
* identically (uncompressed 65-byte form) so downstream session topics agree.
|
|
130
|
-
*/
|
|
72
|
+
/** Derive the identity chat P-256 public key (uncompressed) from its private scalar. */
|
|
131
73
|
export const deriveIdentityChatPublicKey = (privateKey) => p256.getPublicKey(privateKey, false);
|
|
132
|
-
/**
|
|
133
|
-
* Inner Pending sub-statuses. Only `AllowanceAllocation` today.
|
|
134
|
-
*
|
|
135
|
-
* Encoded with its own SCALE discriminant byte — the peer's SCALE library does
|
|
136
|
-
* NOT elide enum-variant indices for sealed interfaces (verified on the wire:
|
|
137
|
-
* `Pending(AllowanceAllocation)` arrives as `0x00 0x00`). Both sides must keep
|
|
138
|
-
* the discriminant when encoding so dispatch agrees.
|
|
139
|
-
*/
|
|
140
74
|
export const HandshakeStatusV2 = Enum({
|
|
141
75
|
AllowanceAllocation: _void,
|
|
142
76
|
});
|
|
143
|
-
/**
|
|
144
|
-
* Inner handshake response variant. Wire shape (matches peer SCALE encoding):
|
|
145
|
-
*
|
|
146
|
-
* - Pending → 0x00 || HandshakeStatusV2 (today: 0x00 for AllowanceAllocation), 2 bytes total
|
|
147
|
-
* - Success → 0x01 || HandshakeSuccessV2 (161 bytes), 162 bytes total
|
|
148
|
-
* - Failed → 0x02 || SCALE-encoded UTF-8 reason string
|
|
149
|
-
*
|
|
150
|
-
* Earlier builds shipped a custom length-dispatched codec assuming elision —
|
|
151
|
-
* that assumption was wrong and produced false `Failed("")` reads on every
|
|
152
|
-
* `Pending` response. Native scale-ts `Enum` preserves the discriminant and
|
|
153
|
-
* matches the peer's wire format directly.
|
|
154
|
-
*/
|
|
77
|
+
/** Inner handshake response variant. */
|
|
155
78
|
export const EncryptedHandshakeResponseV2 = Enum({
|
|
156
79
|
Pending: HandshakeStatusV2,
|
|
157
80
|
Success: HandshakeSuccessV2,
|
|
@@ -55,6 +55,10 @@ export type StartPairingDeps = {
|
|
|
55
55
|
* `initialProcessedDataHex` value is up to date.
|
|
56
56
|
*/
|
|
57
57
|
onStatementProcessed?: (dataHex: string) => void;
|
|
58
|
+
/**
|
|
59
|
+
* Turns on logging for statement store interaction
|
|
60
|
+
*/
|
|
61
|
+
__DEBUG?: boolean;
|
|
58
62
|
};
|
|
59
63
|
export type Pairing = {
|
|
60
64
|
qrPayload: string;
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* higher layers drive a UI hook off this and update an onboarding screen.
|
|
24
24
|
*/
|
|
25
25
|
import { BehaviorSubject } from 'rxjs';
|
|
26
|
-
import {
|
|
26
|
+
import { EncryptedHandshakeResponseV2, VersionedHandshakeResponse } from '../scale/handshakeV2.js';
|
|
27
27
|
import { decryptResponseEnvelope } from './envelope.js';
|
|
28
28
|
import { buildPairingDeeplink } from './proposal.js';
|
|
29
29
|
import { advance, fromInnerResponse, isTerminal, submitted } from './state.js';
|
|
@@ -75,14 +75,13 @@ export const startPairingV2 = (deps) => {
|
|
|
75
75
|
pollHandle = null;
|
|
76
76
|
};
|
|
77
77
|
const log = (msg, extra) => {
|
|
78
|
+
if (!deps.__DEBUG)
|
|
79
|
+
return;
|
|
78
80
|
if (extra === undefined)
|
|
79
81
|
console.info(`[sso-v2] ${msg}`);
|
|
80
82
|
else
|
|
81
83
|
console.info(`[sso-v2] ${msg}`, extra);
|
|
82
84
|
};
|
|
83
|
-
log(`subscribing to pairing topic ${toHexFull(topic)}`);
|
|
84
|
-
log(`device statementAccountId ${toHexFull(deps.deviceIdentity.statementAccountPublicKey)}`);
|
|
85
|
-
log(`device encryptionPublicKey ${toHexFull(deps.deviceIdentity.encryptionPublicKey)}`);
|
|
86
85
|
// One-shot probe: query the topic right away so we know whether any answer
|
|
87
86
|
// statement was already posted before our subscription connected.
|
|
88
87
|
void deps.statementStore.queryStatements({ matchAll: [topic] }).match(statements => log(`queryStatements probe: ${statements.length} statement(s)`), err => log('queryStatements probe failed', err));
|
|
@@ -121,7 +120,7 @@ export const startPairingV2 = (deps) => {
|
|
|
121
120
|
const peerStatementAccountId = extractStatementSigner(statement);
|
|
122
121
|
let next;
|
|
123
122
|
try {
|
|
124
|
-
next = fromInnerResponse(
|
|
123
|
+
next = fromInnerResponse(EncryptedHandshakeResponseV2.dec(innerBytes), peerStatementAccountId);
|
|
125
124
|
}
|
|
126
125
|
catch (err) {
|
|
127
126
|
log(`inner decode failed; innerBytes (${innerBytes.length}b) = ${toHexFull(innerBytes)}`, err);
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
* The state object is what UIs render and what the chat layer gates on
|
|
15
15
|
* before submitting any V2 statements.
|
|
16
16
|
*/
|
|
17
|
-
import type {
|
|
17
|
+
import type { CodecType } from 'scale-ts';
|
|
18
|
+
import type { EncryptedHandshakeResponseV2, HandshakeSuccessV2 } from '../scale/handshakeV2.js';
|
|
18
19
|
export type HandshakeIdleState = {
|
|
19
20
|
tag: 'Idle';
|
|
20
21
|
};
|
|
@@ -25,35 +26,14 @@ export type HandshakePendingState = {
|
|
|
25
26
|
tag: 'Pending';
|
|
26
27
|
reason: 'AllowanceAllocation';
|
|
27
28
|
};
|
|
28
|
-
export type HandshakeSuccessState = {
|
|
29
|
+
export type HandshakeSuccessState = CodecType<typeof HandshakeSuccessV2> & {
|
|
29
30
|
tag: 'Success';
|
|
30
|
-
/** User identity sr25519 accountId (32 bytes). */
|
|
31
|
-
identityAccountId: Uint8Array;
|
|
32
|
-
/**
|
|
33
|
-
* User root sr25519 accountId (32 bytes) — the parent for soft-derivation
|
|
34
|
-
* of product accounts. Nullable: peers on spec v0.2 (Android
|
|
35
|
-
* `feature/location-for-handshake`) omit this field. Product-account
|
|
36
|
-
* derivation degrades gracefully when absent; chat does not use it.
|
|
37
|
-
*/
|
|
38
|
-
rootAccountId: Uint8Array | null;
|
|
39
|
-
/**
|
|
40
|
-
* User identity chat P-256 private key (32 bytes raw scalar) shared by
|
|
41
|
-
* PApp with this device per the multi-device spec. Sensitive; persist in
|
|
42
|
-
* OS-keychain-backed secure storage and never forward.
|
|
43
|
-
*/
|
|
44
|
-
identityChatPrivateKey: Uint8Array;
|
|
45
31
|
/**
|
|
46
32
|
* Derived locally from `identityChatPrivateKey` via P-256 scalar
|
|
47
33
|
* multiplication (uncompressed 65-byte form). Both sides MUST derive
|
|
48
34
|
* identically; downstream session topics depend on it.
|
|
49
35
|
*/
|
|
50
36
|
identityChatPublicKey: Uint8Array;
|
|
51
|
-
/**
|
|
52
|
-
* Encryption public key of the authorising PApp device (65 bytes,
|
|
53
|
-
* P-256 uncompressed). Used by the host when addressing chat envelopes
|
|
54
|
-
* back to the authorising device.
|
|
55
|
-
*/
|
|
56
|
-
deviceEncPubKey: Uint8Array;
|
|
57
37
|
/**
|
|
58
38
|
* The pairing-topic statement was signed by PApp's device statement
|
|
59
39
|
* account. `HandshakeSuccessV2` doesn't carry it in the encrypted body, so
|
|
@@ -71,11 +51,11 @@ export type HandshakeState = HandshakeIdleState | HandshakeSubmittedState | Hand
|
|
|
71
51
|
export declare const idle: () => HandshakeIdleState;
|
|
72
52
|
export declare const submitted: () => HandshakeSubmittedState;
|
|
73
53
|
/**
|
|
74
|
-
* Translate
|
|
75
|
-
*
|
|
76
|
-
*
|
|
54
|
+
* Translate a decoded `EncryptedHandshakeResponseV2` into the public state.
|
|
55
|
+
* Pure — no I/O. The caller decrypts the outer envelope and decodes the inner
|
|
56
|
+
* payload via `EncryptedHandshakeResponseV2.dec` first.
|
|
77
57
|
*/
|
|
78
|
-
export declare const fromInnerResponse: (response:
|
|
58
|
+
export declare const fromInnerResponse: (response: CodecType<typeof EncryptedHandshakeResponseV2>, peerStatementAccountId?: Uint8Array | null) => HandshakeState;
|
|
79
59
|
/**
|
|
80
60
|
* Forward-only transition guard: rejects regressions like Success → Pending.
|
|
81
61
|
* Idempotent on same-tag transitions (returns current by reference) so callers
|
|
@@ -18,9 +18,9 @@ import { deriveIdentityChatPublicKey } from '../scale/handshakeV2.js';
|
|
|
18
18
|
export const idle = () => ({ tag: 'Idle' });
|
|
19
19
|
export const submitted = () => ({ tag: 'Submitted' });
|
|
20
20
|
/**
|
|
21
|
-
* Translate
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* Translate a decoded `EncryptedHandshakeResponseV2` into the public state.
|
|
22
|
+
* Pure — no I/O. The caller decrypts the outer envelope and decodes the inner
|
|
23
|
+
* payload via `EncryptedHandshakeResponseV2.dec` first.
|
|
24
24
|
*/
|
|
25
25
|
export const fromInnerResponse = (response, peerStatementAccountId = null) => {
|
|
26
26
|
switch (response.tag) {
|
|
@@ -35,6 +35,8 @@ export const fromInnerResponse = (response, peerStatementAccountId = null) => {
|
|
|
35
35
|
identityChatPrivateKey: response.value.identityChatPrivateKey,
|
|
36
36
|
identityChatPublicKey: deriveIdentityChatPublicKey(response.value.identityChatPrivateKey),
|
|
37
37
|
deviceEncPubKey: response.value.deviceEncPubKey,
|
|
38
|
+
ssoEncPubKey: response.value.ssoEncPubKey,
|
|
39
|
+
rootEntropySource: response.value.rootEntropySource,
|
|
38
40
|
peerStatementAccountId,
|
|
39
41
|
};
|
|
40
42
|
case 'Failed':
|
|
@@ -79,6 +79,12 @@ export function createUserSession({ userSession, statementStore, encryption, sto
|
|
|
79
79
|
statementStore,
|
|
80
80
|
encryption,
|
|
81
81
|
prover,
|
|
82
|
+
// V1 sessions historically derived SessionId by feeding the peer's raw
|
|
83
|
+
// encryption pubkey into khash; preserve that behaviour here so existing
|
|
84
|
+
// V1 channels stay byte-identical post-refactor. V2 callers (e.g.
|
|
85
|
+
// polkadot-desktop's V2SsoSession) pass the ECDH-derived shared secret
|
|
86
|
+
// explicitly per Mobile SSO spec v0.2.2.
|
|
87
|
+
sessionKey: userSession.remoteAccount.publicKey,
|
|
82
88
|
maxRequestSize: MAX_SSO_REQUEST_SIZE,
|
|
83
89
|
});
|
|
84
90
|
const processedMessages = fieldListView({
|
|
@@ -6,7 +6,6 @@ type StoredUserSecrets = CodecType<typeof StoredUserSecretsCodec>;
|
|
|
6
6
|
declare const StoredUserSecretsCodec: import("scale-ts").Codec<{
|
|
7
7
|
ssSecret: SsSecret;
|
|
8
8
|
encrSecret: EncrSecret;
|
|
9
|
-
entropy: Uint8Array<ArrayBufferLike>;
|
|
10
9
|
identityChatPrivateKey: Uint8Array<ArrayBufferLike>;
|
|
11
10
|
}>;
|
|
12
11
|
export type UserSecretRepository = ReturnType<typeof createUserSecretRepository>;
|
|
@@ -8,26 +8,17 @@ import { toError } from '../helpers/utils.js';
|
|
|
8
8
|
const StoredUserSecretsCodec = Struct({
|
|
9
9
|
ssSecret: BrandedBytesCodec(),
|
|
10
10
|
encrSecret: BrandedBytesCodec(),
|
|
11
|
-
entropy: Bytes(),
|
|
12
11
|
// V2 addition: user identity chat private key (P-256 raw scalar, 32 bytes).
|
|
13
12
|
// Sensitive — kept encrypted at rest alongside the per-session ss/encr secrets.
|
|
14
13
|
identityChatPrivateKey: Bytes(32),
|
|
15
14
|
});
|
|
16
15
|
export function createUserSecretRepository(salt, storage) {
|
|
17
|
-
const baseKey = '
|
|
16
|
+
const baseKey = 'UserSecretsV2';
|
|
18
17
|
const encode = fromThrowable(StoredUserSecretsCodec.enc, toError);
|
|
19
18
|
const decode = fromThrowable((value) => {
|
|
20
19
|
if (!value)
|
|
21
20
|
return null;
|
|
22
|
-
|
|
23
|
-
return StoredUserSecretsCodec.dec(value);
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
// 0.7.x V1 blobs are missing `identityChatPrivateKey` and decode short.
|
|
27
|
-
// Treat as absent — a fresh handshake (required after 0.8.0 upgrade)
|
|
28
|
-
// will overwrite.
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
21
|
+
return StoredUserSecretsCodec.dec(value);
|
|
31
22
|
}, toError);
|
|
32
23
|
const encrypt = fromThrowable((value) => {
|
|
33
24
|
const aes = getAes(salt);
|
|
@@ -15,14 +15,18 @@ declare const storedUserSessionCodec: import("scale-ts").Codec<{
|
|
|
15
15
|
pin: string | undefined;
|
|
16
16
|
};
|
|
17
17
|
rootAccountId: AccountId;
|
|
18
|
-
identityAccountId: AccountId
|
|
19
|
-
identityChatPublicKey: Uint8Array<ArrayBufferLike
|
|
18
|
+
identityAccountId: AccountId;
|
|
19
|
+
identityChatPublicKey: Uint8Array<ArrayBufferLike>;
|
|
20
|
+
ssoEncPubKey: Uint8Array<ArrayBufferLike>;
|
|
21
|
+
rootEntropySource: Uint8Array<ArrayBufferLike>;
|
|
20
22
|
}>;
|
|
21
23
|
type StoredUserSessionV2Extras = {
|
|
22
|
-
identityAccountId
|
|
23
|
-
identityChatPublicKey
|
|
24
|
+
identityAccountId: AccountId;
|
|
25
|
+
identityChatPublicKey: Uint8Array;
|
|
26
|
+
ssoEncPubKey: Uint8Array;
|
|
27
|
+
rootEntropySource: Uint8Array;
|
|
24
28
|
};
|
|
25
|
-
export declare function createStoredUserSession(localAccount: LocalSessionAccount, remoteAccount: RemoteSessionAccount, rootAccountId: AccountId, extras
|
|
29
|
+
export declare function createStoredUserSession(localAccount: LocalSessionAccount, remoteAccount: RemoteSessionAccount, rootAccountId: AccountId, extras: StoredUserSessionV2Extras): StoredUserSession;
|
|
26
30
|
export declare const createUserSessionRepository: (storage: StorageAdapter) => {
|
|
27
31
|
add(value: {
|
|
28
32
|
id: string;
|
|
@@ -36,8 +40,10 @@ export declare const createUserSessionRepository: (storage: StorageAdapter) => {
|
|
|
36
40
|
pin: string | undefined;
|
|
37
41
|
};
|
|
38
42
|
rootAccountId: AccountId;
|
|
39
|
-
identityAccountId: AccountId
|
|
40
|
-
identityChatPublicKey: Uint8Array<ArrayBufferLike
|
|
43
|
+
identityAccountId: AccountId;
|
|
44
|
+
identityChatPublicKey: Uint8Array<ArrayBufferLike>;
|
|
45
|
+
ssoEncPubKey: Uint8Array<ArrayBufferLike>;
|
|
46
|
+
rootEntropySource: Uint8Array<ArrayBufferLike>;
|
|
41
47
|
}): import("neverthrow").ResultAsync<{
|
|
42
48
|
id: string;
|
|
43
49
|
localAccount: {
|
|
@@ -50,8 +56,10 @@ export declare const createUserSessionRepository: (storage: StorageAdapter) => {
|
|
|
50
56
|
pin: string | undefined;
|
|
51
57
|
};
|
|
52
58
|
rootAccountId: AccountId;
|
|
53
|
-
identityAccountId: AccountId
|
|
54
|
-
identityChatPublicKey: Uint8Array<ArrayBufferLike
|
|
59
|
+
identityAccountId: AccountId;
|
|
60
|
+
identityChatPublicKey: Uint8Array<ArrayBufferLike>;
|
|
61
|
+
ssoEncPubKey: Uint8Array<ArrayBufferLike>;
|
|
62
|
+
rootEntropySource: Uint8Array<ArrayBufferLike>;
|
|
55
63
|
}, Error>;
|
|
56
64
|
filter(fn: (value: {
|
|
57
65
|
id: string;
|
|
@@ -65,8 +73,10 @@ export declare const createUserSessionRepository: (storage: StorageAdapter) => {
|
|
|
65
73
|
pin: string | undefined;
|
|
66
74
|
};
|
|
67
75
|
rootAccountId: AccountId;
|
|
68
|
-
identityAccountId: AccountId
|
|
69
|
-
identityChatPublicKey: Uint8Array<ArrayBufferLike
|
|
76
|
+
identityAccountId: AccountId;
|
|
77
|
+
identityChatPublicKey: Uint8Array<ArrayBufferLike>;
|
|
78
|
+
ssoEncPubKey: Uint8Array<ArrayBufferLike>;
|
|
79
|
+
rootEntropySource: Uint8Array<ArrayBufferLike>;
|
|
70
80
|
}) => boolean): import("neverthrow").ResultAsync<{
|
|
71
81
|
id: string;
|
|
72
82
|
localAccount: {
|
|
@@ -79,8 +89,10 @@ export declare const createUserSessionRepository: (storage: StorageAdapter) => {
|
|
|
79
89
|
pin: string | undefined;
|
|
80
90
|
};
|
|
81
91
|
rootAccountId: AccountId;
|
|
82
|
-
identityAccountId: AccountId
|
|
83
|
-
identityChatPublicKey: Uint8Array<ArrayBufferLike
|
|
92
|
+
identityAccountId: AccountId;
|
|
93
|
+
identityChatPublicKey: Uint8Array<ArrayBufferLike>;
|
|
94
|
+
ssoEncPubKey: Uint8Array<ArrayBufferLike>;
|
|
95
|
+
rootEntropySource: Uint8Array<ArrayBufferLike>;
|
|
84
96
|
}[], Error>;
|
|
85
97
|
mutate(fn: (value: {
|
|
86
98
|
id: string;
|
|
@@ -94,8 +106,10 @@ export declare const createUserSessionRepository: (storage: StorageAdapter) => {
|
|
|
94
106
|
pin: string | undefined;
|
|
95
107
|
};
|
|
96
108
|
rootAccountId: AccountId;
|
|
97
|
-
identityAccountId: AccountId
|
|
98
|
-
identityChatPublicKey: Uint8Array<ArrayBufferLike
|
|
109
|
+
identityAccountId: AccountId;
|
|
110
|
+
identityChatPublicKey: Uint8Array<ArrayBufferLike>;
|
|
111
|
+
ssoEncPubKey: Uint8Array<ArrayBufferLike>;
|
|
112
|
+
rootEntropySource: Uint8Array<ArrayBufferLike>;
|
|
99
113
|
}[]) => {
|
|
100
114
|
id: string;
|
|
101
115
|
localAccount: {
|
|
@@ -108,8 +122,10 @@ export declare const createUserSessionRepository: (storage: StorageAdapter) => {
|
|
|
108
122
|
pin: string | undefined;
|
|
109
123
|
};
|
|
110
124
|
rootAccountId: AccountId;
|
|
111
|
-
identityAccountId: AccountId
|
|
112
|
-
identityChatPublicKey: Uint8Array<ArrayBufferLike
|
|
125
|
+
identityAccountId: AccountId;
|
|
126
|
+
identityChatPublicKey: Uint8Array<ArrayBufferLike>;
|
|
127
|
+
ssoEncPubKey: Uint8Array<ArrayBufferLike>;
|
|
128
|
+
rootEntropySource: Uint8Array<ArrayBufferLike>;
|
|
113
129
|
}[]): import("neverthrow").ResultAsync<{
|
|
114
130
|
id: string;
|
|
115
131
|
localAccount: {
|
|
@@ -122,8 +138,10 @@ export declare const createUserSessionRepository: (storage: StorageAdapter) => {
|
|
|
122
138
|
pin: string | undefined;
|
|
123
139
|
};
|
|
124
140
|
rootAccountId: AccountId;
|
|
125
|
-
identityAccountId: AccountId
|
|
126
|
-
identityChatPublicKey: Uint8Array<ArrayBufferLike
|
|
141
|
+
identityAccountId: AccountId;
|
|
142
|
+
identityChatPublicKey: Uint8Array<ArrayBufferLike>;
|
|
143
|
+
ssoEncPubKey: Uint8Array<ArrayBufferLike>;
|
|
144
|
+
rootEntropySource: Uint8Array<ArrayBufferLike>;
|
|
127
145
|
}[], Error>;
|
|
128
146
|
read(): import("neverthrow").ResultAsync<{
|
|
129
147
|
id: string;
|
|
@@ -137,8 +155,10 @@ export declare const createUserSessionRepository: (storage: StorageAdapter) => {
|
|
|
137
155
|
pin: string | undefined;
|
|
138
156
|
};
|
|
139
157
|
rootAccountId: AccountId;
|
|
140
|
-
identityAccountId: AccountId
|
|
141
|
-
identityChatPublicKey: Uint8Array<ArrayBufferLike
|
|
158
|
+
identityAccountId: AccountId;
|
|
159
|
+
identityChatPublicKey: Uint8Array<ArrayBufferLike>;
|
|
160
|
+
ssoEncPubKey: Uint8Array<ArrayBufferLike>;
|
|
161
|
+
rootEntropySource: Uint8Array<ArrayBufferLike>;
|
|
142
162
|
}[], Error>;
|
|
143
163
|
write(value: {
|
|
144
164
|
id: string;
|
|
@@ -152,8 +172,10 @@ export declare const createUserSessionRepository: (storage: StorageAdapter) => {
|
|
|
152
172
|
pin: string | undefined;
|
|
153
173
|
};
|
|
154
174
|
rootAccountId: AccountId;
|
|
155
|
-
identityAccountId: AccountId
|
|
156
|
-
identityChatPublicKey: Uint8Array<ArrayBufferLike
|
|
175
|
+
identityAccountId: AccountId;
|
|
176
|
+
identityChatPublicKey: Uint8Array<ArrayBufferLike>;
|
|
177
|
+
ssoEncPubKey: Uint8Array<ArrayBufferLike>;
|
|
178
|
+
rootEntropySource: Uint8Array<ArrayBufferLike>;
|
|
157
179
|
}[]): import("neverthrow").ResultAsync<null, Error> | import("neverthrow").ResultAsync<{
|
|
158
180
|
id: string;
|
|
159
181
|
localAccount: {
|
|
@@ -166,8 +188,10 @@ export declare const createUserSessionRepository: (storage: StorageAdapter) => {
|
|
|
166
188
|
pin: string | undefined;
|
|
167
189
|
};
|
|
168
190
|
rootAccountId: AccountId;
|
|
169
|
-
identityAccountId: AccountId
|
|
170
|
-
identityChatPublicKey: Uint8Array<ArrayBufferLike
|
|
191
|
+
identityAccountId: AccountId;
|
|
192
|
+
identityChatPublicKey: Uint8Array<ArrayBufferLike>;
|
|
193
|
+
ssoEncPubKey: Uint8Array<ArrayBufferLike>;
|
|
194
|
+
rootEntropySource: Uint8Array<ArrayBufferLike>;
|
|
171
195
|
}[], Error>;
|
|
172
196
|
clear(): import("neverthrow").ResultAsync<void, Error>;
|
|
173
197
|
subscribe(fn: (value: {
|
|
@@ -182,8 +206,10 @@ export declare const createUserSessionRepository: (storage: StorageAdapter) => {
|
|
|
182
206
|
pin: string | undefined;
|
|
183
207
|
};
|
|
184
208
|
rootAccountId: AccountId;
|
|
185
|
-
identityAccountId: AccountId
|
|
186
|
-
identityChatPublicKey: Uint8Array<ArrayBufferLike
|
|
209
|
+
identityAccountId: AccountId;
|
|
210
|
+
identityChatPublicKey: Uint8Array<ArrayBufferLike>;
|
|
211
|
+
ssoEncPubKey: Uint8Array<ArrayBufferLike>;
|
|
212
|
+
rootEntropySource: Uint8Array<ArrayBufferLike>;
|
|
187
213
|
}[]) => void): VoidFunction;
|
|
188
214
|
};
|
|
189
215
|
export {};
|
|
@@ -2,7 +2,7 @@ import { AccountIdCodec, LocalSessionAccountCodec, RemoteSessionAccountCodec } f
|
|
|
2
2
|
import { fieldListView } from '@novasamatech/storage-adapter';
|
|
3
3
|
import { nanoid } from 'nanoid';
|
|
4
4
|
import { fromHex, toHex } from 'polkadot-api/utils';
|
|
5
|
-
import { Bytes,
|
|
5
|
+
import { Bytes, Struct, Vector, str } from 'scale-ts';
|
|
6
6
|
// V2 fields trail V1 fields so a future schema rev can append further
|
|
7
7
|
// `Option`-wrapped fields without breaking decode of 0.8.0 blobs.
|
|
8
8
|
const storedUserSessionCodec = Struct({
|
|
@@ -10,10 +10,18 @@ const storedUserSessionCodec = Struct({
|
|
|
10
10
|
localAccount: LocalSessionAccountCodec,
|
|
11
11
|
remoteAccount: RemoteSessionAccountCodec,
|
|
12
12
|
rootAccountId: AccountIdCodec,
|
|
13
|
-
identityAccountId:
|
|
14
|
-
identityChatPublicKey:
|
|
13
|
+
identityAccountId: AccountIdCodec,
|
|
14
|
+
identityChatPublicKey: Bytes(65),
|
|
15
|
+
// `papp_encr_pub` (65-byte uncompressed P-256). Persisted so the host can
|
|
16
|
+
// rebuild its SSO session transport (`shared_secret_session =
|
|
17
|
+
// ECDH(host_encr_secret, ssoEncPubKey)`) on a cold start without re-running
|
|
18
|
+
// the handshake.
|
|
19
|
+
ssoEncPubKey: Bytes(65),
|
|
20
|
+
// RFC-0007 layer-1 `rootEntropySource` from the handshake; consumed by the
|
|
21
|
+
// host's `host_derive_entropy` handler via `deriveProductEntropyFromSource`.
|
|
22
|
+
rootEntropySource: Bytes(32),
|
|
15
23
|
});
|
|
16
|
-
export function createStoredUserSession(localAccount, remoteAccount, rootAccountId, extras
|
|
24
|
+
export function createStoredUserSession(localAccount, remoteAccount, rootAccountId, extras) {
|
|
17
25
|
return {
|
|
18
26
|
id: nanoid(12),
|
|
19
27
|
localAccount,
|
|
@@ -21,25 +29,16 @@ export function createStoredUserSession(localAccount, remoteAccount, rootAccount
|
|
|
21
29
|
rootAccountId,
|
|
22
30
|
identityAccountId: extras.identityAccountId,
|
|
23
31
|
identityChatPublicKey: extras.identityChatPublicKey,
|
|
32
|
+
ssoEncPubKey: extras.ssoEncPubKey,
|
|
33
|
+
rootEntropySource: extras.rootEntropySource,
|
|
24
34
|
};
|
|
25
35
|
}
|
|
26
36
|
export const createUserSessionRepository = (storage) => {
|
|
27
37
|
const codec = Vector(storedUserSessionCodec);
|
|
28
38
|
return fieldListView({
|
|
29
39
|
storage,
|
|
30
|
-
key: '
|
|
31
|
-
from: x =>
|
|
32
|
-
try {
|
|
33
|
-
return codec.dec(fromHex(x));
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
// 0.7.x V1 blobs use the prior codec shape and won't decode against
|
|
37
|
-
// V2's extended struct. Treat as empty so the caller (and the
|
|
38
|
-
// fieldListView mutate machinery) start clean; the next write
|
|
39
|
-
// overwrites the bad blob.
|
|
40
|
-
return [];
|
|
41
|
-
}
|
|
42
|
-
},
|
|
40
|
+
key: 'SsoSessionsV2',
|
|
41
|
+
from: x => codec.dec(fromHex(x)),
|
|
43
42
|
to: x => toHex(codec.enc(x)),
|
|
44
43
|
});
|
|
45
44
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@novasamatech/host-papp",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.8.
|
|
4
|
+
"version": "0.8.6",
|
|
5
5
|
"description": "Polkadot app integration",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
@@ -34,10 +34,10 @@
|
|
|
34
34
|
"@noble/ciphers": "2.2.0",
|
|
35
35
|
"@noble/curves": "2.2.0",
|
|
36
36
|
"@noble/hashes": "2.2.0",
|
|
37
|
-
"@novasamatech/host-api": "0.8.
|
|
38
|
-
"@novasamatech/scale": "0.8.
|
|
39
|
-
"@novasamatech/statement-store": "0.8.
|
|
40
|
-
"@novasamatech/storage-adapter": "0.8.
|
|
37
|
+
"@novasamatech/host-api": "0.8.6",
|
|
38
|
+
"@novasamatech/scale": "0.8.6",
|
|
39
|
+
"@novasamatech/statement-store": "0.8.6",
|
|
40
|
+
"@novasamatech/storage-adapter": "0.8.6",
|
|
41
41
|
"@polkadot-api/utils": "^0.4.0",
|
|
42
42
|
"@polkadot-labs/hdkd-helpers": "^0.0.30",
|
|
43
43
|
"nanoevents": "9.1.0",
|