@novasamatech/host-papp 0.8.11 → 0.8.12
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/README.md +58 -68
- package/dist/helpers/createAsyncTaskPool.spec.js +11 -11
- package/dist/index.d.ts +2 -0
- package/dist/papp.js +3 -1
- package/dist/sso/sessionManager/impl.d.ts +3 -1
- package/dist/sso/sessionManager/impl.js +6 -4
- package/dist/sso/sessionManager/scale/productSubtree.d.ts +12 -0
- package/dist/sso/sessionManager/scale/productSubtree.js +21 -0
- package/dist/sso/sessionManager/scale/remoteMessage.d.ts +26 -4
- package/dist/sso/sessionManager/scale/remoteMessage.js +7 -1
- package/dist/sso/sessionManager/scale/ringVrf.d.ts +4 -27
- package/dist/sso/sessionManager/scale/ringVrf.js +4 -13
- package/dist/sso/sessionManager/scale/signVrf.d.ts +56 -0
- package/dist/sso/sessionManager/scale/signVrf.js +18 -0
- package/dist/sso/sessionManager/userSession.d.ts +12 -2
- package/dist/sso/sessionManager/userSession.js +27 -2
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -35,10 +35,6 @@ const papp = createPappAdapter({
|
|
|
35
35
|
// otherwise existing pairings will be lost.
|
|
36
36
|
appId: 'my-host-app',
|
|
37
37
|
|
|
38
|
-
// URL to a JSON document describing the host: { name: string, icon: string }.
|
|
39
|
-
// The icon should be a rasterized image at least 256x256 px.
|
|
40
|
-
metadata: 'https://my-host-app.example/papp-metadata.json',
|
|
41
|
-
|
|
42
38
|
// Optional environment metadata shown on the wallet's confirmation screen.
|
|
43
39
|
hostMetadata: {
|
|
44
40
|
hostVersion: '1.4.0',
|
|
@@ -48,14 +44,15 @@ const papp = createPappAdapter({
|
|
|
48
44
|
});
|
|
49
45
|
```
|
|
50
46
|
|
|
51
|
-
`createPappAdapter` returns
|
|
47
|
+
`createPappAdapter` returns five sub-modules:
|
|
52
48
|
|
|
53
|
-
| Module
|
|
54
|
-
|
|
|
55
|
-
| `papp.sso`
|
|
56
|
-
| `papp.sessions`
|
|
57
|
-
| `papp.secrets`
|
|
58
|
-
| `papp.identity`
|
|
49
|
+
| Module | Purpose |
|
|
50
|
+
| ---------------- | ------------------------------------------------------------------- |
|
|
51
|
+
| `papp.sso` | Authentication / pairing flow with a remote wallet. |
|
|
52
|
+
| `papp.sessions` | List of paired user sessions and per-session messaging (sign, etc.).|
|
|
53
|
+
| `papp.secrets` | Local secret storage for the derived guest accounts. |
|
|
54
|
+
| `papp.identity` | On-chain identity lookups for arbitrary account ids. |
|
|
55
|
+
| `papp.allowance` | Resource allowances (bulletin / statement-store signers) per product.|
|
|
59
56
|
|
|
60
57
|
Custom adapters (statement store, identity RPC, storage, lazy chain client) can be supplied
|
|
61
58
|
via the `adapters` option for testing or non-browser environments.
|
|
@@ -200,6 +197,34 @@ await currentSession.signRaw({
|
|
|
200
197
|
});
|
|
201
198
|
```
|
|
202
199
|
|
|
200
|
+
`signVrf` asks the wallet for an sr25519 (schnorrkel) VRF signature from a product account
|
|
201
|
+
(RFC-0023). The transcript travels as a recipe — a root domain-separation label plus an
|
|
202
|
+
ordered list of `(label, value)` items — which the wallet replays verbatim into a Merlin
|
|
203
|
+
transcript and signs. Callers that need a `signer` item must supply their own public key;
|
|
204
|
+
the host never injects it.
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
const encoder = new TextEncoder();
|
|
208
|
+
|
|
209
|
+
const vrf = await currentSession.signVrf({
|
|
210
|
+
productAccountId: ['product.dot', 0],
|
|
211
|
+
productId: 'product.dot',
|
|
212
|
+
transcriptLabel: encoder.encode('pop:airdrop'),
|
|
213
|
+
items: [
|
|
214
|
+
{ label: encoder.encode('domain'), value: domainBytes },
|
|
215
|
+
{ label: encoder.encode('signer'), value: accountPublicKey },
|
|
216
|
+
],
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
vrf.match(
|
|
220
|
+
({ preOutput, proof }) => submitLotteryTicket(preOutput, proof),
|
|
221
|
+
error => console.error('VRF signing failed:', error),
|
|
222
|
+
);
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
This is the non-`AutoSigning` path only: when `AutoSigning` covers the account the host
|
|
226
|
+
signs locally and never round-trips to the wallet.
|
|
227
|
+
|
|
203
228
|
## Ring VRF proofs and aliases
|
|
204
229
|
|
|
205
230
|
A `UserSession` can ask the paired device for a privacy-preserving contextual alias, or a
|
|
@@ -246,6 +271,13 @@ const lookup = async (accountId: string) => {
|
|
|
246
271
|
await papp.identity.getIdentities([accountIdA, accountIdB]);
|
|
247
272
|
```
|
|
248
273
|
|
|
274
|
+
A paired `UserSession` also exposes `getIdentity()` as a shortcut that looks up the identity
|
|
275
|
+
of its own user identity account — no account id to pass:
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
const identity = await session.getIdentity(); // Result<Identity | null, Error>
|
|
279
|
+
```
|
|
280
|
+
|
|
249
281
|
## V2 SSO handshake
|
|
250
282
|
|
|
251
283
|
V2 is a redesign of the SSO pairing flow that supports the same user identity across
|
|
@@ -258,38 +290,15 @@ identity, so contacts, chats, and roster events are shared between them.
|
|
|
258
290
|
V2 is **not interoperable with V1**: a V1-only peer can't decode a V2 proposal QR and vice
|
|
259
291
|
versa. Hosts that want to support both should branch on which protocol the peer advertises.
|
|
260
292
|
|
|
261
|
-
###
|
|
293
|
+
### The flow
|
|
262
294
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
the host's pubkeys
|
|
271
|
-
ECDH-encrypt + post:
|
|
272
|
-
Pending(AllowanceAllocation)
|
|
273
|
-
Success { encryptionKey,
|
|
274
|
-
accountId,
|
|
275
|
-
identitySignature }
|
|
276
|
-
Failed(reason)
|
|
277
|
-
service.subscribeStatements(topic) +
|
|
278
|
-
poll the topic every 2s
|
|
279
|
-
↓
|
|
280
|
-
decode VersionedHandshakeResponse::V2
|
|
281
|
-
→ ECDH-decrypt envelope with the
|
|
282
|
-
device encryption private key
|
|
283
|
-
→ SCALE-decode inner payload
|
|
284
|
-
→ state machine: Submitted → Pending →
|
|
285
|
-
Success | Failed
|
|
286
|
-
→ on Success persist user identity
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
The user identity carried in `Success` is the chat encryption pubkey + the user's identity
|
|
290
|
-
sr25519 accountId. The host verifies the 64-byte sr25519 `identitySignature` against the
|
|
291
|
-
canonical 97 bytes `statementAccountId || encryptionPublicKey`
|
|
292
|
-
(see `IDENTITY_SIGNATURE_PAYLOAD_BYTES`).
|
|
295
|
+
1. The host builds a pairing deeplink from its device keypair and shows it as a QR code.
|
|
296
|
+
2. The authorising device scans it and posts its response to the Statement Store: first a
|
|
297
|
+
`Pending` acknowledgement, then either `Success` — carrying the user's identity keys,
|
|
298
|
+
signed to authorise this device — or `Failed`.
|
|
299
|
+
3. The host polls the pairing topic, decrypts and verifies each response, and drives a
|
|
300
|
+
`Submitted → Pending → Success | Failed` state machine. On `Success` it persists the
|
|
301
|
+
user identity.
|
|
293
302
|
|
|
294
303
|
### Building and rendering the QR
|
|
295
304
|
|
|
@@ -318,7 +327,7 @@ renderQrCode(deeplink); // 'polkadotapp://pair?handshake=<hex>'
|
|
|
318
327
|
import { startPairingV2 } from '@novasamatech/host-papp';
|
|
319
328
|
|
|
320
329
|
const pairing = startPairingV2({
|
|
321
|
-
statementStore
|
|
330
|
+
statementStore, // any StatementStoreAdapter
|
|
322
331
|
deviceIdentity: {
|
|
323
332
|
statementAccountPublicKey: device.statementAccountPublicKey,
|
|
324
333
|
encryptionPublicKey: device.encryptionPublicKey,
|
|
@@ -380,33 +389,14 @@ The service skips any incoming statement whose bytes match `initialProcessedData
|
|
|
380
389
|
re-encrypts every Success with a fresh ephemeral key + AES-GCM nonce, so a genuine re-pair
|
|
381
390
|
always produces different bytes and passes the dedupe.
|
|
382
391
|
|
|
383
|
-
|
|
392
|
+
## Reading allowances
|
|
384
393
|
|
|
385
|
-
|
|
386
|
-
|
|
394
|
+
Each `UserSession` can read its own persisted allowance slot-account key for a given
|
|
395
|
+
product and resource. The session id is implicit — you only pass the product and resource:
|
|
387
396
|
|
|
388
397
|
```ts
|
|
389
|
-
|
|
398
|
+
const session = papp.sessions.sessions.read().at(0);
|
|
390
399
|
|
|
391
|
-
|
|
392
|
-
const
|
|
393
|
-
// topic = blake2b256_keyed(encryptionPublicKey || "topic", key=statementAccountId)
|
|
394
|
-
// channel = blake2b256_keyed(encryptionPublicKey || "channel", key=statementAccountId)
|
|
400
|
+
// resource: 'bulletin' | 'statementStore'
|
|
401
|
+
const key = await session.readAllowance(productId, 'statementStore'); // Result<Uint8Array | null, Error>
|
|
395
402
|
```
|
|
396
|
-
|
|
397
|
-
### Codec exports
|
|
398
|
-
|
|
399
|
-
The SCALE codecs are exported as plain `Codec<T>` values for callers that need to
|
|
400
|
-
encode/decode statements outside the orchestrator:
|
|
401
|
-
|
|
402
|
-
| Export | Description |
|
|
403
|
-
| --------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
404
|
-
| `VersionedHandshakeProposal` | Outer enum; V2 at SCALE discriminant 1, with `_v1Reserved` at 0. |
|
|
405
|
-
| `HandshakeProposalV2` | `{ device, metadata }` — what the QR encodes. |
|
|
406
|
-
| `Device` | `{ statementAccountId(32), encryptionPublicKey(65) }`. |
|
|
407
|
-
| `MetadataKey`, `MetadataEntry` | Metadata enum + `(MetadataKey, str)` tuple. |
|
|
408
|
-
| `VersionedHandshakeResponse` | Outer enum for the answer; `V1` legacy + `V2`. |
|
|
409
|
-
| `HandshakeResponseV2` | `{ encrypted, tmpKey(65) }` — the ECDH-wrapped envelope. |
|
|
410
|
-
| `EncryptedHandshakeResponseV2` | Inner payload after envelope decrypt: `Pending` (1 byte), `Success` (161 bytes), `Failed`. |
|
|
411
|
-
| `HandshakeSuccessV2` | `{ encryptionKey(65), accountId(32), identitySignature(64) }`. |
|
|
412
|
-
| `IDENTITY_SIGNATURE_PAYLOAD_BYTES`| `97` — the bytes the user identity sr25519 signs over. |
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { setTimeout } from 'node:timers/promises';
|
|
2
|
-
import {
|
|
2
|
+
import { fromPromise, okAsync } from 'neverthrow';
|
|
3
3
|
import { describe, expect, it, vi } from 'vitest';
|
|
4
4
|
import { createAsyncTaskPool } from './createAsyncTaskPool.js';
|
|
5
5
|
import { toError } from './utils.js';
|
|
@@ -8,7 +8,7 @@ describe('asyncTaskPool', () => {
|
|
|
8
8
|
it('should exec async task', async () => {
|
|
9
9
|
const pool = createAsyncTaskPool({ poolSize: 1, retryCount: 0, retryDelay: () => 0 });
|
|
10
10
|
const result = await pool.call(() => fromPromise(delay().then(() => 'test'), toError));
|
|
11
|
-
expect(result).
|
|
11
|
+
await expect(result).toBeOkWith('test');
|
|
12
12
|
});
|
|
13
13
|
it('should handle sync errors', async () => {
|
|
14
14
|
const pool = createAsyncTaskPool({ poolSize: 1, retryCount: 0, retryDelay: () => 0 });
|
|
@@ -16,13 +16,13 @@ describe('asyncTaskPool', () => {
|
|
|
16
16
|
const result = await pool.call(() => {
|
|
17
17
|
throw error;
|
|
18
18
|
});
|
|
19
|
-
expect(result).
|
|
19
|
+
await expect(result).toBeErrWith(error);
|
|
20
20
|
});
|
|
21
21
|
it('should handle async errors', async () => {
|
|
22
22
|
const pool = createAsyncTaskPool({ poolSize: 1, retryCount: 0, retryDelay: () => 0 });
|
|
23
23
|
const error = new Error('test');
|
|
24
24
|
const result = await pool.call(() => fromPromise(Promise.reject(error), toError));
|
|
25
|
-
|
|
25
|
+
await expect(result).toBeErrWith(error);
|
|
26
26
|
});
|
|
27
27
|
it('should handle queue', async () => {
|
|
28
28
|
const pool = createAsyncTaskPool({ poolSize: 2, retryCount: 0, retryDelay: () => 0 });
|
|
@@ -52,7 +52,7 @@ describe('asyncTaskPool', () => {
|
|
|
52
52
|
tries++;
|
|
53
53
|
throw new Error();
|
|
54
54
|
});
|
|
55
|
-
expect(result).
|
|
55
|
+
await expect(result).toBeOkWith('test');
|
|
56
56
|
});
|
|
57
57
|
it('should throw on retry limit exceeding', async () => {
|
|
58
58
|
const spy = vi.fn(() => 0);
|
|
@@ -66,7 +66,7 @@ describe('asyncTaskPool', () => {
|
|
|
66
66
|
throw new Error();
|
|
67
67
|
});
|
|
68
68
|
expect(spy).toBeCalledTimes(1);
|
|
69
|
-
expect(result).
|
|
69
|
+
await expect(result).toBeErrWith(new Error());
|
|
70
70
|
});
|
|
71
71
|
it('should correctly calculate retry delay', async () => {
|
|
72
72
|
const spy = vi.fn((retry) => retry * 10);
|
|
@@ -125,9 +125,9 @@ describe('asyncTaskPool', () => {
|
|
|
125
125
|
const queued = pool.call(queuedSpy, { signal: controller.signal });
|
|
126
126
|
controller.abort();
|
|
127
127
|
const queuedResult = await queued;
|
|
128
|
-
expect(queuedResult
|
|
128
|
+
await expect(queuedResult).toBeErr();
|
|
129
129
|
expect(queuedSpy).not.toHaveBeenCalled();
|
|
130
|
-
expect(
|
|
130
|
+
await expect(await active).toBeOk();
|
|
131
131
|
});
|
|
132
132
|
it('rejects the in-flight active task when the signal aborts', async () => {
|
|
133
133
|
const pool = createAsyncTaskPool({ poolSize: 1, retryCount: 0, retryDelay: 0 });
|
|
@@ -136,7 +136,7 @@ describe('asyncTaskPool', () => {
|
|
|
136
136
|
signal: controller.signal,
|
|
137
137
|
});
|
|
138
138
|
controller.abort();
|
|
139
|
-
expect(
|
|
139
|
+
await expect(await active).toBeErr();
|
|
140
140
|
});
|
|
141
141
|
it('frees the slot for later tasks after an abort', async () => {
|
|
142
142
|
const pool = createAsyncTaskPool({ poolSize: 1, retryCount: 0, retryDelay: 0 });
|
|
@@ -147,7 +147,7 @@ describe('asyncTaskPool', () => {
|
|
|
147
147
|
controller.abort();
|
|
148
148
|
await aborted;
|
|
149
149
|
const next = await pool.call(() => okAsync('next'));
|
|
150
|
-
expect(next).
|
|
150
|
+
await expect(next).toBeOkWith('next');
|
|
151
151
|
});
|
|
152
152
|
it('rejects immediately when called with an already-aborted signal', async () => {
|
|
153
153
|
const pool = createAsyncTaskPool({ poolSize: 1, retryCount: 0, retryDelay: 0 });
|
|
@@ -155,7 +155,7 @@ describe('asyncTaskPool', () => {
|
|
|
155
155
|
controller.abort();
|
|
156
156
|
const spy = vi.fn(() => okAsync('x'));
|
|
157
157
|
const result = await pool.call(spy, { signal: controller.signal });
|
|
158
|
-
expect(result
|
|
158
|
+
await expect(result).toBeErr();
|
|
159
159
|
expect(spy).not.toHaveBeenCalled();
|
|
160
160
|
});
|
|
161
161
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -6,9 +6,11 @@ export type { PairingStatus } from './sso/auth/types.js';
|
|
|
6
6
|
export type { DeviceIdentityForPairing } from './sso/auth/v2/service.js';
|
|
7
7
|
export type { AllowanceErrorReason, AllowanceService } from './sso/allowance/index.js';
|
|
8
8
|
export { AllowanceError } from './sso/allowance/index.js';
|
|
9
|
+
export type { AllowanceResourceKind } from './sso/allowance/index.js';
|
|
9
10
|
export type { UserSession } from './sso/sessionManager/userSession.js';
|
|
10
11
|
export type { StoredUserSession } from './sso/userSessionRepository.js';
|
|
11
12
|
export type { Identity } from './identity/types.js';
|
|
12
13
|
export type { SignRawLegacyRequest, SignRawLegacyResponse, SigningPayloadRequest, SigningPayloadResponse, SigningRawRequest, SigningRequest, } from './sso/sessionManager/scale/signing.js';
|
|
13
14
|
export type { RingVrfAliasRequest, RingVrfAliasResponse, RingVrfProofRequest, RingVrfProofResponse, } from './sso/sessionManager/scale/ringVrf.js';
|
|
15
|
+
export type { SignVrfErr, SignVrfRequest, SignVrfResponse } from './sso/sessionManager/scale/signVrf.js';
|
|
14
16
|
export type { CreateTransactionLegacyRequest, CreateTransactionRequest, CreateTransactionResponse, } from './sso/sessionManager/scale/createTransaction.js';
|
package/dist/papp.js
CHANGED
|
@@ -19,6 +19,7 @@ export function createPappAdapter({ appId, hostMetadata, deviceIdentity, onAuthS
|
|
|
19
19
|
const ssoSessionRepository = createUserSessionRepository(storage);
|
|
20
20
|
const userSecretRepository = createUserSecretRepository(appId, storage);
|
|
21
21
|
const allowanceRepository = createAllowanceRepository(appId, storage);
|
|
22
|
+
const identityRepository = createIdentityRepository({ adapter: identities, storage });
|
|
22
23
|
const deviceIdentityStore = createDeviceIdentityStore(appId, storage);
|
|
23
24
|
const sessions = createSsoSessionManager({
|
|
24
25
|
storage,
|
|
@@ -26,6 +27,7 @@ export function createPappAdapter({ appId, hostMetadata, deviceIdentity, onAuthS
|
|
|
26
27
|
ssoSessionRepository,
|
|
27
28
|
userSecretRepository,
|
|
28
29
|
allowanceRepository,
|
|
30
|
+
identityRepository,
|
|
29
31
|
});
|
|
30
32
|
return {
|
|
31
33
|
sso: createAuth({
|
|
@@ -39,7 +41,7 @@ export function createPappAdapter({ appId, hostMetadata, deviceIdentity, onAuthS
|
|
|
39
41
|
}),
|
|
40
42
|
sessions,
|
|
41
43
|
secrets: userSecretRepository,
|
|
42
|
-
identity:
|
|
44
|
+
identity: identityRepository,
|
|
43
45
|
allowance: createAllowanceService({ sessions: sessions.sessions, repository: allowanceRepository }),
|
|
44
46
|
};
|
|
45
47
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { StatementStoreAdapter } from '@novasamatech/statement-store';
|
|
2
2
|
import type { StorageAdapter } from '@novasamatech/storage-adapter';
|
|
3
|
+
import type { IdentityRepository } from '../../identity/types.js';
|
|
3
4
|
import type { Callback } from '../../types.js';
|
|
4
5
|
import type { AllowanceRepository } from '../allowance/index.js';
|
|
5
6
|
import type { UserSecretRepository } from '../userSecretRepository.js';
|
|
@@ -12,8 +13,9 @@ type Params = {
|
|
|
12
13
|
ssoSessionRepository: UserSessionRepository;
|
|
13
14
|
userSecretRepository: UserSecretRepository;
|
|
14
15
|
allowanceRepository: AllowanceRepository;
|
|
16
|
+
identityRepository: IdentityRepository;
|
|
15
17
|
};
|
|
16
|
-
export declare function createSsoSessionManager({ ssoSessionRepository, userSecretRepository, allowanceRepository, statementStore, storage, }: Params): {
|
|
18
|
+
export declare function createSsoSessionManager({ ssoSessionRepository, userSecretRepository, allowanceRepository, identityRepository, statementStore, storage, }: Params): {
|
|
17
19
|
sessions: {
|
|
18
20
|
read: () => UserSession[];
|
|
19
21
|
subscribe: (callback: Callback<UserSession[]>) => () => void;
|
|
@@ -4,7 +4,7 @@ import { emitHostPappDebugMessage } from '../../debugBus.js';
|
|
|
4
4
|
import { createState } from '../../helpers/state.js';
|
|
5
5
|
import { createSsoStatementProver } from '../ssoSessionProver.js';
|
|
6
6
|
import { createUserSession } from './userSession.js';
|
|
7
|
-
export function createSsoSessionManager({ ssoSessionRepository, userSecretRepository, allowanceRepository, statementStore, storage, }) {
|
|
7
|
+
export function createSsoSessionManager({ ssoSessionRepository, userSecretRepository, allowanceRepository, identityRepository, statementStore, storage, }) {
|
|
8
8
|
const localSessions = createState({});
|
|
9
9
|
const sessionUnsubscribes = new Map();
|
|
10
10
|
const releaseSession = (id) => {
|
|
@@ -22,7 +22,7 @@ export function createSsoSessionManager({ ssoSessionRepository, userSecretReposi
|
|
|
22
22
|
toRemove.delete(userSession.id);
|
|
23
23
|
if (userSession.id in activeSessions)
|
|
24
24
|
continue;
|
|
25
|
-
const session = createSession(userSession, statementStore, storage, userSecretRepository);
|
|
25
|
+
const session = createSession(userSession, statementStore, storage, userSecretRepository, allowanceRepository, identityRepository);
|
|
26
26
|
toAdd.add(session);
|
|
27
27
|
emitHostPappDebugMessage({
|
|
28
28
|
layer: 'session',
|
|
@@ -73,7 +73,7 @@ export function createSsoSessionManager({ ssoSessionRepository, userSecretReposi
|
|
|
73
73
|
subscribe: (callback) => localSessions.subscribe(sessions => callback(Object.values(sessions))),
|
|
74
74
|
},
|
|
75
75
|
disconnect(userSession) {
|
|
76
|
-
const session = createSession(userSession, statementStore, storage, userSecretRepository);
|
|
76
|
+
const session = createSession(userSession, statementStore, storage, userSecretRepository, allowanceRepository, identityRepository);
|
|
77
77
|
return session
|
|
78
78
|
.sendDisconnectMessage()
|
|
79
79
|
.andThen(() => disconnect(userSession))
|
|
@@ -88,7 +88,7 @@ export function createSsoSessionManager({ ssoSessionRepository, userSecretReposi
|
|
|
88
88
|
},
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
|
-
function createSession(userSession, statementStore, storage, userSecretRepository) {
|
|
91
|
+
function createSession(userSession, statementStore, storage, userSecretRepository, allowanceRepository, identityRepository) {
|
|
92
92
|
const encryption = createEncryption(userSession.remoteAccount.publicKey);
|
|
93
93
|
const prover = createSsoStatementProver(userSession, userSecretRepository);
|
|
94
94
|
return createUserSession({
|
|
@@ -97,5 +97,7 @@ function createSession(userSession, statementStore, storage, userSecretRepositor
|
|
|
97
97
|
encryption,
|
|
98
98
|
storage,
|
|
99
99
|
prover,
|
|
100
|
+
allowanceRepository,
|
|
101
|
+
identityRepository,
|
|
100
102
|
});
|
|
101
103
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CodecType } from 'scale-ts';
|
|
2
|
+
export type ProductSubtreeRequest = CodecType<typeof ProductSubtreeRequestCodec>;
|
|
3
|
+
export declare const ProductSubtreeRequestCodec: import("scale-ts").Codec<{
|
|
4
|
+
productId: string;
|
|
5
|
+
}>;
|
|
6
|
+
export type ProductSubtreeResponse = CodecType<typeof ProductSubtreeResponseCodec>;
|
|
7
|
+
export declare const ProductSubtreeResponseCodec: import("scale-ts").Codec<{
|
|
8
|
+
respondingTo: string;
|
|
9
|
+
payload: import("scale-ts").ResultPayload<{
|
|
10
|
+
productPublicKey: Uint8Array<ArrayBufferLike>;
|
|
11
|
+
}, string>;
|
|
12
|
+
}>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { DotNsIdentifier } from '@novasamatech/host-api';
|
|
2
|
+
import { Bytes, Result, Struct, str } from 'scale-ts';
|
|
3
|
+
// RFC-0022 made `//product//{productId}` a hard junction, so the root public
|
|
4
|
+
// key alone no longer determines product account public keys. This request
|
|
5
|
+
// closes the gap: the Account Holder returns the product-subtree public key,
|
|
6
|
+
// from which the Host soft-derives account public keys locally.
|
|
7
|
+
//
|
|
8
|
+
// Consent-free — the response carries no secret material. Fetch once per
|
|
9
|
+
// product and cache; only `AutoSigning` (secret material) requires consent.
|
|
10
|
+
/** 32-byte sr25519 public key of `//product//{productId}`. */
|
|
11
|
+
const Sr25519PublicKey = Bytes(32);
|
|
12
|
+
export const ProductSubtreeRequestCodec = Struct({
|
|
13
|
+
productId: DotNsIdentifier,
|
|
14
|
+
});
|
|
15
|
+
export const ProductSubtreeResponseCodec = Struct({
|
|
16
|
+
// referencing to RemoteMessage.messageId
|
|
17
|
+
respondingTo: str,
|
|
18
|
+
payload: Result(Struct({
|
|
19
|
+
productPublicKey: Sr25519PublicKey,
|
|
20
|
+
}), str),
|
|
21
|
+
});
|
|
@@ -74,9 +74,9 @@ export declare const RemoteMessageCodec: import("scale-ts").Codec<{
|
|
|
74
74
|
payload: import("scale-ts").ResultPayload<{
|
|
75
75
|
context: Uint8Array<ArrayBufferLike>;
|
|
76
76
|
alias: Uint8Array<ArrayBufferLike>;
|
|
77
|
-
}, import("@novasamatech/scale").CodecError<undefined, "
|
|
77
|
+
}, import("@novasamatech/scale").CodecError<undefined, "GetAliasErr::Rejected"> | import("@novasamatech/scale").CodecError<{
|
|
78
78
|
reason: string;
|
|
79
|
-
}, "
|
|
79
|
+
}, "GetAliasErr::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "GetAliasErr::RingNotFound"> | import("@novasamatech/scale").CodecError<undefined, "GetAliasErr::NotMember">>;
|
|
80
80
|
};
|
|
81
81
|
} | {
|
|
82
82
|
tag: "ResourceAllocationRequest";
|
|
@@ -220,9 +220,31 @@ export declare const RemoteMessageCodec: import("scale-ts").Codec<{
|
|
|
220
220
|
};
|
|
221
221
|
ringIndex: number;
|
|
222
222
|
ringRevision: number;
|
|
223
|
-
}, import("@novasamatech/scale").CodecError<undefined, "
|
|
223
|
+
}, import("@novasamatech/scale").CodecError<undefined, "CreateProofErr::Rejected"> | import("@novasamatech/scale").CodecError<{
|
|
224
224
|
reason: string;
|
|
225
|
-
}, "
|
|
225
|
+
}, "CreateProofErr::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "CreateProofErr::RingNotFound"> | import("@novasamatech/scale").CodecError<undefined, "CreateProofErr::NotMember">>;
|
|
226
|
+
};
|
|
227
|
+
} | {
|
|
228
|
+
tag: "SignVrfRequest";
|
|
229
|
+
value: {
|
|
230
|
+
productAccountId: [string, number];
|
|
231
|
+
productId: string;
|
|
232
|
+
transcriptLabel: Uint8Array<ArrayBufferLike>;
|
|
233
|
+
items: {
|
|
234
|
+
label: Uint8Array<ArrayBufferLike>;
|
|
235
|
+
value: Uint8Array<ArrayBufferLike>;
|
|
236
|
+
}[];
|
|
237
|
+
};
|
|
238
|
+
} | {
|
|
239
|
+
tag: "SignVrfResponse";
|
|
240
|
+
value: {
|
|
241
|
+
respondingTo: string;
|
|
242
|
+
payload: import("scale-ts").ResultPayload<{
|
|
243
|
+
preOutput: Uint8Array<ArrayBufferLike>;
|
|
244
|
+
proof: Uint8Array<ArrayBufferLike>;
|
|
245
|
+
}, import("@novasamatech/scale").CodecError<undefined, "SignVrfErr::Rejected"> | import("@novasamatech/scale").CodecError<{
|
|
246
|
+
reason: string;
|
|
247
|
+
}, "SignVrfErr::Unknown">>;
|
|
226
248
|
};
|
|
227
249
|
};
|
|
228
250
|
};
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import { Enum
|
|
1
|
+
import { Enum } from '@novasamatech/scale';
|
|
2
|
+
import { Struct, _void, str } from 'scale-ts';
|
|
2
3
|
import { CreateTransactionLegacyRequestCodec, CreateTransactionRequestCodec, CreateTransactionResponseCodec, } from './createTransaction.js';
|
|
3
4
|
import { ResourceAllocationRequestCodec, ResourceAllocationResponseCodec } from './resourceAllocation.js';
|
|
4
5
|
import { RingVrfAliasRequestCodec, RingVrfAliasResponseCodec, RingVrfProofRequestCodec, RingVrfProofResponseCodec, } from './ringVrf.js';
|
|
6
|
+
import { SignVrfRequestCodec, SignVrfResponseCodec } from './signVrf.js';
|
|
5
7
|
import { SignRawLegacyRequestCodec, SignRawLegacyResponseCodec, SigningRequestCodec, SigningResponseCodec, } from './signing.js';
|
|
6
8
|
export const RemoteMessageCodec = Struct({
|
|
7
9
|
messageId: str,
|
|
8
10
|
data: Enum({
|
|
11
|
+
// Declaration order is the SCALE wire order and must stay in lockstep with the
|
|
12
|
+
// truapi `host_logic::sso::messages::v1::RemoteMessage` enum. Append only.
|
|
9
13
|
v1: Enum({
|
|
10
14
|
Disconnected: _void,
|
|
11
15
|
SignRequest: SigningRequestCodec,
|
|
@@ -21,6 +25,8 @@ export const RemoteMessageCodec = Struct({
|
|
|
21
25
|
SignRawLegacyResponse: SignRawLegacyResponseCodec,
|
|
22
26
|
RingVrfProofRequest: RingVrfProofRequestCodec,
|
|
23
27
|
RingVrfProofResponse: RingVrfProofResponseCodec,
|
|
28
|
+
SignVrfRequest: SignVrfRequestCodec,
|
|
29
|
+
SignVrfResponse: SignVrfResponseCodec,
|
|
24
30
|
}),
|
|
25
31
|
}),
|
|
26
32
|
});
|
|
@@ -1,27 +1,4 @@
|
|
|
1
1
|
import type { CodecType } from 'scale-ts';
|
|
2
|
-
export declare const RingVrfError: [import("scale-ts").Encoder<import("@novasamatech/scale").CodecError<undefined, "RingVrfError::Rejected"> | import("@novasamatech/scale").CodecError<{
|
|
3
|
-
reason: string;
|
|
4
|
-
}, "RingVrfError::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "RingVrfError::RingNotFound"> | import("@novasamatech/scale").CodecError<undefined, "RingVrfError::NotMember">>, import("scale-ts").Decoder<import("@novasamatech/scale").CodecError<undefined, "RingVrfError::Rejected"> | import("@novasamatech/scale").CodecError<{
|
|
5
|
-
reason: string;
|
|
6
|
-
}, "RingVrfError::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "RingVrfError::RingNotFound"> | import("@novasamatech/scale").CodecError<undefined, "RingVrfError::NotMember">>] & {
|
|
7
|
-
enc: import("scale-ts").Encoder<import("@novasamatech/scale").CodecError<undefined, "RingVrfError::Rejected"> | import("@novasamatech/scale").CodecError<{
|
|
8
|
-
reason: string;
|
|
9
|
-
}, "RingVrfError::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "RingVrfError::RingNotFound"> | import("@novasamatech/scale").CodecError<undefined, "RingVrfError::NotMember">>;
|
|
10
|
-
dec: import("scale-ts").Decoder<import("@novasamatech/scale").CodecError<undefined, "RingVrfError::Rejected"> | import("@novasamatech/scale").CodecError<{
|
|
11
|
-
reason: string;
|
|
12
|
-
}, "RingVrfError::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "RingVrfError::RingNotFound"> | import("@novasamatech/scale").CodecError<undefined, "RingVrfError::NotMember">>;
|
|
13
|
-
} & {
|
|
14
|
-
readonly RingNotFound: import("@novasamatech/scale").ErrCodec<undefined, "RingVrfError::RingNotFound">;
|
|
15
|
-
readonly NotMember: import("@novasamatech/scale").ErrCodec<undefined, "RingVrfError::NotMember">;
|
|
16
|
-
readonly Rejected: import("@novasamatech/scale").ErrCodec<undefined, "RingVrfError::Rejected">;
|
|
17
|
-
readonly Unknown: import("@novasamatech/scale").ErrCodec<{
|
|
18
|
-
reason: string;
|
|
19
|
-
}, "RingVrfError::Unknown">;
|
|
20
|
-
} & {
|
|
21
|
-
[Symbol.hasInstance](v: unknown): v is import("@novasamatech/scale").CodecError<undefined, "RingVrfError::Rejected"> | import("@novasamatech/scale").CodecError<{
|
|
22
|
-
reason: string;
|
|
23
|
-
}, "RingVrfError::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "RingVrfError::RingNotFound"> | import("@novasamatech/scale").CodecError<undefined, "RingVrfError::NotMember">;
|
|
24
|
-
};
|
|
25
2
|
export type RingVrfAliasRequest = CodecType<typeof RingVrfAliasRequestCodec>;
|
|
26
3
|
export declare const RingVrfAliasRequestCodec: import("scale-ts").Codec<{
|
|
27
4
|
callingProductId: string;
|
|
@@ -43,9 +20,9 @@ export declare const RingVrfAliasResponseCodec: import("scale-ts").Codec<{
|
|
|
43
20
|
payload: import("scale-ts").ResultPayload<{
|
|
44
21
|
context: Uint8Array<ArrayBufferLike>;
|
|
45
22
|
alias: Uint8Array<ArrayBufferLike>;
|
|
46
|
-
}, import("@novasamatech/scale").CodecError<undefined, "
|
|
23
|
+
}, import("@novasamatech/scale").CodecError<undefined, "GetAliasErr::Rejected"> | import("@novasamatech/scale").CodecError<{
|
|
47
24
|
reason: string;
|
|
48
|
-
}, "
|
|
25
|
+
}, "GetAliasErr::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "GetAliasErr::RingNotFound"> | import("@novasamatech/scale").CodecError<undefined, "GetAliasErr::NotMember">>;
|
|
49
26
|
}>;
|
|
50
27
|
export type RingVrfProofRequest = CodecType<typeof RingVrfProofRequestCodec>;
|
|
51
28
|
export declare const RingVrfProofRequestCodec: import("scale-ts").Codec<{
|
|
@@ -74,7 +51,7 @@ export declare const RingVrfProofResponseCodec: import("scale-ts").Codec<{
|
|
|
74
51
|
};
|
|
75
52
|
ringIndex: number;
|
|
76
53
|
ringRevision: number;
|
|
77
|
-
}, import("@novasamatech/scale").CodecError<undefined, "
|
|
54
|
+
}, import("@novasamatech/scale").CodecError<undefined, "CreateProofErr::Rejected"> | import("@novasamatech/scale").CodecError<{
|
|
78
55
|
reason: string;
|
|
79
|
-
}, "
|
|
56
|
+
}, "CreateProofErr::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "CreateProofErr::RingNotFound"> | import("@novasamatech/scale").CodecError<undefined, "CreateProofErr::NotMember">>;
|
|
80
57
|
}>;
|
|
@@ -1,14 +1,5 @@
|
|
|
1
|
-
import { ContextualAlias, DotNsIdentifier, ProductProofContext, RingLocation, RingVrfProof, } from '@novasamatech/host-api';
|
|
2
|
-
import {
|
|
3
|
-
import { Bytes, Result, Struct, _void, str } from 'scale-ts';
|
|
4
|
-
// Shared failure set for both alias and proof requests over the SSO channel;
|
|
5
|
-
// mirrors the Account Holder's `RingVrfError` (RFC 0004).
|
|
6
|
-
export const RingVrfError = ErrEnum('RingVrfError', {
|
|
7
|
-
RingNotFound: [_void, 'RingVrf: ring not found'],
|
|
8
|
-
NotMember: [_void, 'RingVrf: selected member key is not a member of the ring'],
|
|
9
|
-
Rejected: [_void, 'RingVrf: rejected'],
|
|
10
|
-
Unknown: [Struct({ reason: str }), 'RingVrf: unknown error'],
|
|
11
|
-
});
|
|
1
|
+
import { ContextualAlias, CreateProofErr, DotNsIdentifier, GetAliasErr, ProductProofContext, RingLocation, RingVrfProof, } from '@novasamatech/host-api';
|
|
2
|
+
import { Bytes, Result, Struct, str } from 'scale-ts';
|
|
12
3
|
export const RingVrfAliasRequestCodec = Struct({
|
|
13
4
|
callingProductId: DotNsIdentifier,
|
|
14
5
|
context: ProductProofContext,
|
|
@@ -16,7 +7,7 @@ export const RingVrfAliasRequestCodec = Struct({
|
|
|
16
7
|
});
|
|
17
8
|
export const RingVrfAliasResponseCodec = Struct({
|
|
18
9
|
respondingTo: str,
|
|
19
|
-
payload: Result(ContextualAlias,
|
|
10
|
+
payload: Result(ContextualAlias, GetAliasErr),
|
|
20
11
|
});
|
|
21
12
|
export const RingVrfProofRequestCodec = Struct({
|
|
22
13
|
callingProductId: DotNsIdentifier,
|
|
@@ -26,5 +17,5 @@ export const RingVrfProofRequestCodec = Struct({
|
|
|
26
17
|
});
|
|
27
18
|
export const RingVrfProofResponseCodec = Struct({
|
|
28
19
|
respondingTo: str,
|
|
29
|
-
payload: Result(RingVrfProof,
|
|
20
|
+
payload: Result(RingVrfProof, CreateProofErr),
|
|
30
21
|
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { CodecType } from 'scale-ts';
|
|
2
|
+
/**
|
|
3
|
+
* Host → Account Holder request for an sr25519 (schnorrkel) VRF signature over a
|
|
4
|
+
* caller-supplied Merlin transcript (RFC-0023, "Accounts Protocol companion").
|
|
5
|
+
*
|
|
6
|
+
* The Account Holder derives `productAccountId`, presents the signing confirmation,
|
|
7
|
+
* replays the transcript verbatim — `Transcript::new(transcriptLabel)` then one
|
|
8
|
+
* `append_message(label, value)` per item, in order — and signs it. It performs no
|
|
9
|
+
* interpretation of labels or values.
|
|
10
|
+
*
|
|
11
|
+
* This is the non-`AutoSigning` path; when `AutoSigning` covers the account the host
|
|
12
|
+
* signs locally and never sends this message.
|
|
13
|
+
*/
|
|
14
|
+
export type SignVrfRequest = CodecType<typeof SignVrfRequestCodec>;
|
|
15
|
+
export declare const SignVrfRequestCodec: import("scale-ts").Codec<{
|
|
16
|
+
productAccountId: [string, number];
|
|
17
|
+
productId: string;
|
|
18
|
+
transcriptLabel: Uint8Array<ArrayBufferLike>;
|
|
19
|
+
items: {
|
|
20
|
+
label: Uint8Array<ArrayBufferLike>;
|
|
21
|
+
value: Uint8Array<ArrayBufferLike>;
|
|
22
|
+
}[];
|
|
23
|
+
}>;
|
|
24
|
+
/** Failure returned by the Account Holder for a VRF signing request. */
|
|
25
|
+
export type SignVrfErr = CodecType<typeof SignVrfErrCodec>;
|
|
26
|
+
export declare const SignVrfErrCodec: [import("scale-ts").Encoder<import("@novasamatech/scale").CodecError<undefined, "SignVrfErr::Rejected"> | import("@novasamatech/scale").CodecError<{
|
|
27
|
+
reason: string;
|
|
28
|
+
}, "SignVrfErr::Unknown">>, import("scale-ts").Decoder<import("@novasamatech/scale").CodecError<undefined, "SignVrfErr::Rejected"> | import("@novasamatech/scale").CodecError<{
|
|
29
|
+
reason: string;
|
|
30
|
+
}, "SignVrfErr::Unknown">>] & {
|
|
31
|
+
enc: import("scale-ts").Encoder<import("@novasamatech/scale").CodecError<undefined, "SignVrfErr::Rejected"> | import("@novasamatech/scale").CodecError<{
|
|
32
|
+
reason: string;
|
|
33
|
+
}, "SignVrfErr::Unknown">>;
|
|
34
|
+
dec: import("scale-ts").Decoder<import("@novasamatech/scale").CodecError<undefined, "SignVrfErr::Rejected"> | import("@novasamatech/scale").CodecError<{
|
|
35
|
+
reason: string;
|
|
36
|
+
}, "SignVrfErr::Unknown">>;
|
|
37
|
+
} & {
|
|
38
|
+
readonly Rejected: import("@novasamatech/scale").ErrCodec<undefined, "SignVrfErr::Rejected">;
|
|
39
|
+
readonly Unknown: import("@novasamatech/scale").ErrCodec<{
|
|
40
|
+
reason: string;
|
|
41
|
+
}, "SignVrfErr::Unknown">;
|
|
42
|
+
} & {
|
|
43
|
+
[Symbol.hasInstance](v: unknown): v is import("@novasamatech/scale").CodecError<undefined, "SignVrfErr::Rejected"> | import("@novasamatech/scale").CodecError<{
|
|
44
|
+
reason: string;
|
|
45
|
+
}, "SignVrfErr::Unknown">;
|
|
46
|
+
};
|
|
47
|
+
export type SignVrfResponse = CodecType<typeof SignVrfResponseCodec>;
|
|
48
|
+
export declare const SignVrfResponseCodec: import("scale-ts").Codec<{
|
|
49
|
+
respondingTo: string;
|
|
50
|
+
payload: import("scale-ts").ResultPayload<{
|
|
51
|
+
preOutput: Uint8Array<ArrayBufferLike>;
|
|
52
|
+
proof: Uint8Array<ArrayBufferLike>;
|
|
53
|
+
}, import("@novasamatech/scale").CodecError<undefined, "SignVrfErr::Rejected"> | import("@novasamatech/scale").CodecError<{
|
|
54
|
+
reason: string;
|
|
55
|
+
}, "SignVrfErr::Unknown">>;
|
|
56
|
+
}>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ProductAccountId, VrfSignature, VrfTranscriptItem } from '@novasamatech/host-api';
|
|
2
|
+
import { ErrEnum } from '@novasamatech/scale';
|
|
3
|
+
import { Bytes, Result, Struct, Vector, _void, str } from 'scale-ts';
|
|
4
|
+
export const SignVrfRequestCodec = Struct({
|
|
5
|
+
productAccountId: ProductAccountId,
|
|
6
|
+
productId: str,
|
|
7
|
+
transcriptLabel: Bytes(),
|
|
8
|
+
items: Vector(VrfTranscriptItem),
|
|
9
|
+
});
|
|
10
|
+
export const SignVrfErrCodec = ErrEnum('SignVrfErr', {
|
|
11
|
+
Rejected: [_void, 'Rejected'],
|
|
12
|
+
Unknown: [Struct({ reason: str }), ({ reason }) => reason],
|
|
13
|
+
});
|
|
14
|
+
export const SignVrfResponseCodec = Struct({
|
|
15
|
+
// referencing to RemoteMessage.messageId
|
|
16
|
+
respondingTo: str,
|
|
17
|
+
payload: Result(VrfSignature, SignVrfErrCodec),
|
|
18
|
+
});
|
|
@@ -1,15 +1,22 @@
|
|
|
1
|
-
import { ContextualAlias, ProductProofContext, RingLocation, RingVrfProof } from '@novasamatech/host-api';
|
|
1
|
+
import { ContextualAlias, ProductProofContext, RingLocation, RingVrfProof, VrfSignature } from '@novasamatech/host-api';
|
|
2
2
|
import type { Encryption, StatementProver, StatementStoreAdapter } from '@novasamatech/statement-store';
|
|
3
3
|
import type { StorageAdapter } from '@novasamatech/storage-adapter';
|
|
4
4
|
import { ResultAsync } from 'neverthrow';
|
|
5
5
|
import type { CodecType } from 'scale-ts';
|
|
6
|
+
import type { Identity, IdentityRepository } from '../../identity/types.js';
|
|
6
7
|
import type { Callback } from '../../types.js';
|
|
8
|
+
import type { AllowanceRepository, AllowanceResourceKind } from '../allowance/index.js';
|
|
7
9
|
import type { StoredUserSession } from '../userSessionRepository.js';
|
|
8
10
|
import type { CreateTransactionLegacyRequest, CreateTransactionRequest } from './scale/createTransaction.js';
|
|
9
11
|
import { RemoteMessageCodec } from './scale/remoteMessage.js';
|
|
10
12
|
import type { ApAllocationOutcome, ResourceAllocationRequest } from './scale/resourceAllocation.js';
|
|
13
|
+
import type { SignVrfRequest } from './scale/signVrf.js';
|
|
11
14
|
import type { SignRawLegacyRequest, SigningPayloadRequest, SigningPayloadResponseData, SigningRawRequest } from './scale/signing.js';
|
|
12
15
|
export type UserSession = StoredUserSession & {
|
|
16
|
+
/** Read this session's persisted allowance slot-account key for a product/resource. */
|
|
17
|
+
readAllowance(productId: string, resource: AllowanceResourceKind): ResultAsync<Uint8Array | null, Error>;
|
|
18
|
+
/** Look up the on-chain identity of this session's user identity account. */
|
|
19
|
+
getIdentity(): ResultAsync<Identity | null, Error>;
|
|
13
20
|
sendDisconnectMessage(): ResultAsync<void, Error>;
|
|
14
21
|
abortPendingRequests(): ResultAsync<void, Error>;
|
|
15
22
|
signPayload(payload: SigningPayloadRequest): ResultAsync<SigningPayloadResponseData, Error>;
|
|
@@ -19,14 +26,17 @@ export type UserSession = StoredUserSession & {
|
|
|
19
26
|
createTransactionLegacy(payload: CreateTransactionLegacyRequest): ResultAsync<Uint8Array, Error>;
|
|
20
27
|
getRingVrfAlias(callingProductId: string, context: CodecType<typeof ProductProofContext>, ring: CodecType<typeof RingLocation>): ResultAsync<CodecType<typeof ContextualAlias>, Error>;
|
|
21
28
|
createRingVrfProof(callingProductId: string, context: CodecType<typeof ProductProofContext>, ring: CodecType<typeof RingLocation>, message: Uint8Array): ResultAsync<CodecType<typeof RingVrfProof>, Error>;
|
|
29
|
+
signVrf(payload: SignVrfRequest): ResultAsync<CodecType<typeof VrfSignature>, Error>;
|
|
22
30
|
requestResourceAllocation(request: ResourceAllocationRequest): ResultAsync<ApAllocationOutcome[], Error>;
|
|
23
31
|
subscribe(callback: Callback<CodecType<typeof RemoteMessageCodec>, ResultAsync<boolean, Error>>): VoidFunction;
|
|
24
32
|
dispose(): void;
|
|
25
33
|
};
|
|
26
|
-
export declare function createUserSession({ userSession, statementStore, encryption, storage, prover, }: {
|
|
34
|
+
export declare function createUserSession({ userSession, statementStore, encryption, storage, prover, allowanceRepository, identityRepository, }: {
|
|
27
35
|
userSession: StoredUserSession;
|
|
28
36
|
statementStore: StatementStoreAdapter;
|
|
29
37
|
encryption: Encryption;
|
|
30
38
|
storage: StorageAdapter;
|
|
31
39
|
prover: StatementProver;
|
|
40
|
+
allowanceRepository: AllowanceRepository;
|
|
41
|
+
identityRepository: IdentityRepository;
|
|
32
42
|
}): UserSession;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { ContextualAlias, ProductProofContext, RingLocation, RingVrfProof } from '@novasamatech/host-api';
|
|
1
|
+
import { ContextualAlias, ProductProofContext, RingLocation, RingVrfProof, VrfSignature } from '@novasamatech/host-api';
|
|
2
2
|
import { enumValue } from '@novasamatech/scale';
|
|
3
3
|
import { createSession } from '@novasamatech/statement-store';
|
|
4
4
|
import { fieldListView } from '@novasamatech/storage-adapter';
|
|
5
5
|
import { nanoid } from 'nanoid';
|
|
6
6
|
import { ResultAsync, err, ok, okAsync } from 'neverthrow';
|
|
7
|
+
import { toHex } from 'polkadot-api/utils';
|
|
7
8
|
import { emitHostPappDebugMessage } from '../../debugBus.js';
|
|
8
9
|
import { createAsyncTaskPool } from '../../helpers/createAsyncTaskPool.js';
|
|
9
10
|
import { toError } from '../../helpers/utils.js';
|
|
@@ -80,7 +81,7 @@ function withHostActionTrace(result, messageId, sessionId) {
|
|
|
80
81
|
});
|
|
81
82
|
});
|
|
82
83
|
}
|
|
83
|
-
export function createUserSession({ userSession, statementStore, encryption, storage, prover, }) {
|
|
84
|
+
export function createUserSession({ userSession, statementStore, encryption, storage, prover, allowanceRepository, identityRepository, }) {
|
|
84
85
|
const requestQueue = createAsyncTaskPool({ poolSize: 1, retryCount: 0, retryDelay: 0 });
|
|
85
86
|
// Shared abort handle for everything currently on the request queue.
|
|
86
87
|
// abortPendingRequests() fires it to drop the in-flight task plus anything
|
|
@@ -111,6 +112,12 @@ export function createUserSession({ userSession, statementStore, encryption, sto
|
|
|
111
112
|
});
|
|
112
113
|
return {
|
|
113
114
|
...userSession,
|
|
115
|
+
readAllowance(productId, resource) {
|
|
116
|
+
return allowanceRepository.read(userSession.id, productId, resource);
|
|
117
|
+
},
|
|
118
|
+
getIdentity() {
|
|
119
|
+
return identityRepository.getIdentity(toHex(userSession.identityAccountId));
|
|
120
|
+
},
|
|
114
121
|
signPayload(payload) {
|
|
115
122
|
return enqueue(() => {
|
|
116
123
|
const messageId = nanoid();
|
|
@@ -280,6 +287,24 @@ export function createUserSession({ userSession, statementStore, encryption, sto
|
|
|
280
287
|
return withHostActionTrace(awaitReplyOrAckFailure(request, reply).andThen(result => result.success ? ok(result.value) : err(result.value)), messageId, userSession.id);
|
|
281
288
|
});
|
|
282
289
|
},
|
|
290
|
+
signVrf(payload) {
|
|
291
|
+
return enqueue(() => {
|
|
292
|
+
const messageId = nanoid();
|
|
293
|
+
const data = enumValue('v1', enumValue('SignVrfRequest', payload));
|
|
294
|
+
emitHostAction(messageId, actionKindFromMessageData(data), userSession.id);
|
|
295
|
+
const responseFilter = (message) => {
|
|
296
|
+
if (message.data.tag === 'v1' &&
|
|
297
|
+
message.data.value.tag === 'SignVrfResponse' &&
|
|
298
|
+
message.data.value.value.respondingTo === messageId) {
|
|
299
|
+
return message.data.value.value.payload;
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
const request = session.request(RemoteMessageCodec, { messageId, data });
|
|
303
|
+
const reply = session.waitForRequestMessage(RemoteMessageCodec, responseFilter);
|
|
304
|
+
const inner = awaitReplyOrAckFailure(request, reply).andThen(result => result.success ? ok(result.value) : err(result.value));
|
|
305
|
+
return withHostActionTrace(withQueueTimeout(inner, 'signVrf'), messageId, userSession.id);
|
|
306
|
+
});
|
|
307
|
+
},
|
|
283
308
|
requestResourceAllocation(payload) {
|
|
284
309
|
return enqueue(() => {
|
|
285
310
|
const messageId = nanoid();
|
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.12",
|
|
5
5
|
"description": "Polkadot app integration",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
@@ -34,14 +34,14 @@
|
|
|
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.12",
|
|
38
|
+
"@novasamatech/scale": "0.8.12",
|
|
39
|
+
"@novasamatech/statement-store": "0.8.12",
|
|
40
|
+
"@novasamatech/storage-adapter": "0.8.12",
|
|
41
41
|
"@polkadot-api/utils": "^0.4.0",
|
|
42
|
-
"@polkadot-labs/hdkd-helpers": "^0.0.
|
|
43
|
-
"nanoevents": "
|
|
44
|
-
"nanoid": "
|
|
42
|
+
"@polkadot-labs/hdkd-helpers": "^0.0.31",
|
|
43
|
+
"nanoevents": "10.0.0",
|
|
44
|
+
"nanoid": "6.0.0",
|
|
45
45
|
"neverthrow": "^8.2.0",
|
|
46
46
|
"polkadot-api": ">=2",
|
|
47
47
|
"rxjs": "^7.8.2",
|