@novasamatech/host-papp 0.5.0-6 → 0.5.0-7

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.
Files changed (102) hide show
  1. package/dist/adapters/identity/rpc.d.ts +4 -2
  2. package/dist/adapters/identity/rpc.js +96 -26
  3. package/dist/adapters/identity/types.d.ts +3 -1
  4. package/dist/adapters/lazyClient/papi.js +5 -0
  5. package/dist/adapters/lazyClient/types.d.ts +1 -0
  6. package/dist/adapters/statement/rpc.js +58 -10
  7. package/dist/adapters/statement/types.d.ts +6 -3
  8. package/dist/adapters/storage/localStorage.js +26 -4
  9. package/dist/adapters/storage/memory.js +14 -4
  10. package/dist/adapters/storage/types.d.ts +5 -2
  11. package/dist/adapters/storage/types.js +1 -1
  12. package/dist/adapters/transport/rpc.d.ts +3 -0
  13. package/dist/adapters/transport/rpc.js +51 -0
  14. package/dist/adapters/transport/types.d.ts +6 -0
  15. package/dist/adapters/transport/types.js +1 -0
  16. package/dist/components/auth/codec.d.ts +9 -0
  17. package/dist/components/auth/codec.js +10 -0
  18. package/dist/components/auth/codecs.d.ts +9 -0
  19. package/dist/components/auth/codecs.js +10 -0
  20. package/dist/components/auth/index.d.ts +36 -0
  21. package/dist/components/auth/index.js +150 -0
  22. package/dist/components/auth/types.d.ts +15 -0
  23. package/dist/components/auth/types.js +1 -0
  24. package/dist/components/session.d.ts +34 -0
  25. package/dist/components/session.js +54 -0
  26. package/dist/components/transport.d.ts +27 -0
  27. package/dist/components/transport.js +57 -0
  28. package/dist/components/user/codec.d.ts +16 -0
  29. package/dist/components/user/codec.js +13 -0
  30. package/dist/components/user/index.d.ts +22 -0
  31. package/dist/components/user/index.js +58 -0
  32. package/dist/components/user/ssoMessageStream.d.ts +10 -0
  33. package/dist/components/user/ssoMessageStream.js +8 -0
  34. package/dist/components/user/ssoSession.d.ts +5 -0
  35. package/dist/components/user/ssoSession.js +5 -0
  36. package/dist/components/user/storage.d.ts +27 -0
  37. package/dist/components/user/storage.js +143 -0
  38. package/dist/components/user/types.d.ts +6 -0
  39. package/dist/components/user/types.js +1 -0
  40. package/dist/components/user/userSessionStorage.d.ts +20 -0
  41. package/dist/components/user/userSessionStorage.js +24 -0
  42. package/dist/components/user.d.ts +74 -0
  43. package/dist/components/user.js +188 -0
  44. package/dist/constants.d.ts +2 -1
  45. package/dist/constants.js +5 -1
  46. package/dist/helpers/abortError.d.ts +4 -0
  47. package/dist/helpers/abortError.js +8 -0
  48. package/dist/helpers/callbackRaceResolver.d.ts +1 -0
  49. package/dist/helpers/callbackRaceResolver.js +17 -0
  50. package/dist/helpers/result.d.ts +12 -0
  51. package/dist/helpers/result.js +15 -0
  52. package/dist/helpers/result.spec.d.ts +1 -0
  53. package/dist/helpers/result.spec.js +23 -0
  54. package/dist/helpers/utils.d.ts +2 -1
  55. package/dist/helpers/utils.js +11 -2
  56. package/dist/helpers.d.ts +1 -0
  57. package/dist/helpers.js +3 -0
  58. package/dist/index.d.ts +2 -1
  59. package/dist/modules/accounts.d.ts +1 -0
  60. package/dist/modules/accounts.js +2 -0
  61. package/dist/modules/crypto.d.ts +5 -2
  62. package/dist/modules/crypto.js +16 -5
  63. package/dist/modules/secretStorage.d.ts +7 -9
  64. package/dist/modules/secretStorage.js +20 -33
  65. package/dist/modules/session/helpers.d.ts +5 -0
  66. package/dist/modules/session/helpers.js +29 -0
  67. package/dist/modules/session/session.d.ts +12 -0
  68. package/dist/modules/session/session.js +46 -0
  69. package/dist/modules/session/types.d.ts +12 -0
  70. package/dist/modules/session/types.js +1 -0
  71. package/dist/modules/signIn.d.ts +32 -11
  72. package/dist/modules/signIn.js +95 -95
  73. package/dist/modules/state.d.ts +16 -0
  74. package/dist/modules/state.js +50 -0
  75. package/dist/modules/statementStore.d.ts +10 -11
  76. package/dist/modules/statementStore.js +14 -14
  77. package/dist/modules/statementTopic.d.ts +34 -0
  78. package/dist/modules/statementTopic.js +46 -0
  79. package/dist/modules/storageView.d.ts +25 -0
  80. package/dist/modules/storageView.js +51 -0
  81. package/dist/modules/syncStorage.d.ts +25 -0
  82. package/dist/modules/syncStorage.js +76 -0
  83. package/dist/modules/transport/codec.d.ts +24 -0
  84. package/dist/modules/transport/codec.js +36 -0
  85. package/dist/modules/transport/crypto.d.ts +2 -0
  86. package/dist/modules/transport/crypto.js +18 -0
  87. package/dist/modules/transport/transport.d.ts +27 -0
  88. package/dist/modules/transport/transport.js +56 -0
  89. package/dist/modules/user.d.ts +67 -0
  90. package/dist/modules/user.js +188 -0
  91. package/dist/modules/userManager.d.ts +15 -0
  92. package/dist/modules/userManager.js +105 -0
  93. package/dist/modules/userStorage.d.ts +19 -0
  94. package/dist/modules/userStorage.js +108 -0
  95. package/dist/modules/userStore.d.ts +15 -0
  96. package/dist/modules/userStore.js +105 -0
  97. package/dist/papp.d.ts +8 -7
  98. package/dist/papp.js +19 -31
  99. package/dist/structs.d.ts +10 -10
  100. package/dist/structs.js +17 -13
  101. package/dist/types.d.ts +1 -1
  102. package/package.json +8 -6
@@ -0,0 +1,188 @@
1
+ import { toHex } from '@polkadot-api/utils';
2
+ import { Bytes, Enum, Tuple, str } from 'scale-ts';
3
+ import { err, fromPromise, ok, seq } from '../helpers/result.js';
4
+ import { isAbortError, toError } from '../helpers/utils.js';
5
+ import { ENCR_SECRET_SEED_SIZE, EncrPubKey, SS_SECRET_SEED_SIZE, SsPubKey, createEncrSecret, createRandomSeed, createSharedSecret, createSsSecret, createSymmetricKey, decrypt, getEncrPub, getSsPub, khash, mergeBytes, stringToBytes, } from '../modules/crypto.js';
6
+ import { createSecretStorage } from '../modules/secretStorage.js';
7
+ import { createSession } from '../modules/statementStore.js';
8
+ import { createSyncStorage } from '../modules/syncStorage.js';
9
+ import { createUserStorage } from '../modules/userStorage.js';
10
+ // codecs
11
+ export const HandshakeData = Enum({
12
+ V1: Tuple(SsPubKey, EncrPubKey, str),
13
+ });
14
+ export const HandshakeResponsePayload = Enum({
15
+ // [encrypted, tmp_key]
16
+ V1: Tuple(Bytes(), Bytes(65)),
17
+ });
18
+ export const HandshakeResponseSensitiveData = Tuple(Bytes(65), Bytes(32));
19
+ export function createUserComponent({ appId, metadata, statements, storage }) {
20
+ const userStorage = createUserStorage(appId, storage);
21
+ const secretStorage = createSecretStorage(appId, storage);
22
+ const authStatus = createSyncStorage({ step: 'none' });
23
+ let authPromise = null;
24
+ let abort = null;
25
+ async function handshake(signal) {
26
+ authStatus.write({ step: 'initial' });
27
+ const secrets = await getSecretKeys(appId, secretStorage);
28
+ return secrets.andThenPromise(async ({ ssPublicKey, encrPublicKey, encrSecret }) => {
29
+ const handshakeTopic = createHandshakeTopic({ encrPublicKey, ssPublicKey });
30
+ const handshakePayload = createHandshakePayloadV1({ ssPublicKey, encrPublicKey, metadata });
31
+ authStatus.write({ step: 'pairing', payload: createDeeplink(handshakePayload) });
32
+ const statementStoreResponse = fromPromise(waitForStatements(statements, handshakeTopic, signal, (statements, resolve) => {
33
+ for (const statement of [...statements].reverse()) {
34
+ if (!statement.data)
35
+ continue;
36
+ const { sessionTopic, accountId } = retrieveSessionTopic({
37
+ payload: statement.data.asBytes(),
38
+ encrSecret,
39
+ ssPublicKey,
40
+ });
41
+ resolve({ sessionTopic, accountId: toHex(accountId) });
42
+ break;
43
+ }
44
+ }), toError);
45
+ return statementStoreResponse
46
+ .then(x => x.andThenPromise(userStorage.sessions.create))
47
+ .then(async (result) => result
48
+ .map(user => {
49
+ authStatus.write({ step: 'finished', user });
50
+ return user;
51
+ })
52
+ .orElse(e => {
53
+ const error = toError(e);
54
+ if (isAbortError(error)) {
55
+ authStatus.write({ step: 'none' });
56
+ return ok(null);
57
+ }
58
+ else {
59
+ authStatus.write({ step: 'error', message: error.message });
60
+ return err(error);
61
+ }
62
+ }));
63
+ });
64
+ }
65
+ const userModule = {
66
+ authStatus,
67
+ storage: userStorage,
68
+ async authenticate() {
69
+ if (authPromise) {
70
+ return authPromise;
71
+ }
72
+ abort = new AbortController();
73
+ authPromise = handshake(abort.signal);
74
+ return authPromise;
75
+ },
76
+ abortAuthentication() {
77
+ if (abort) {
78
+ authPromise = null;
79
+ authStatus.reset();
80
+ abort.abort();
81
+ }
82
+ },
83
+ };
84
+ return userModule;
85
+ }
86
+ function createHandshakeTopic({ encrPublicKey, ssPublicKey, }) {
87
+ return khash(ssPublicKey, mergeBytes(encrPublicKey, stringToBytes('topic')));
88
+ }
89
+ function createHandshakePayloadV1({ encrPublicKey, ssPublicKey, metadata, }) {
90
+ return HandshakeData.enc({
91
+ tag: 'V1',
92
+ value: [ssPublicKey, encrPublicKey, metadata],
93
+ });
94
+ }
95
+ function parseHandshakePayload(payload) {
96
+ const decoded = HandshakeResponsePayload.dec(payload);
97
+ switch (decoded.tag) {
98
+ case 'V1':
99
+ return {
100
+ encrypted: decoded.value[0],
101
+ tmpKey: decoded.value[1],
102
+ };
103
+ default:
104
+ throw new Error('Unsupported handshake payload version');
105
+ }
106
+ }
107
+ function retrieveSessionTopic({ payload, encrSecret, ssPublicKey, }) {
108
+ const { encrypted, tmpKey } = parseHandshakePayload(payload);
109
+ const symmetricKey = createSymmetricKey(createSharedSecret(encrSecret, tmpKey));
110
+ const decrypted = decrypt(symmetricKey, encrypted);
111
+ const [pappEncrPublicKey, userPublicKey] = HandshakeResponseSensitiveData.dec(decrypted);
112
+ const sharedSecret = createSharedSecret(encrSecret, pappEncrPublicKey);
113
+ const session = createSession({
114
+ sharedSecret: sharedSecret,
115
+ accountA: ssPublicKey,
116
+ accountB: pappEncrPublicKey,
117
+ });
118
+ return {
119
+ accountId: userPublicKey,
120
+ sessionTopic: session.a,
121
+ };
122
+ }
123
+ async function getSsKeys(appId, secretStorage) {
124
+ return (await secretStorage.readSsSecret())
125
+ .andThenPromise(async (ssSecret) => {
126
+ if (ssSecret) {
127
+ return ok(ssSecret);
128
+ }
129
+ const seed = createRandomSeed(appId, SS_SECRET_SEED_SIZE);
130
+ const newSsSecret = createSsSecret(seed);
131
+ const write = await secretStorage.writeSsSecret(newSsSecret);
132
+ return write.map(() => newSsSecret);
133
+ })
134
+ .then(x => x.map(ssSecret => ({
135
+ ssSecret: ssSecret,
136
+ ssPublicKey: getSsPub(ssSecret),
137
+ })));
138
+ }
139
+ async function getEncrKeys(appId, secretStorage) {
140
+ return (await secretStorage.readEncrSecret())
141
+ .andThenPromise(async (encrSecret) => {
142
+ if (encrSecret) {
143
+ return ok(encrSecret);
144
+ }
145
+ const seed = createRandomSeed(appId, ENCR_SECRET_SEED_SIZE);
146
+ const newEncrSecret = createEncrSecret(seed);
147
+ const write = await secretStorage.writeEncrSecret(newEncrSecret);
148
+ return write.map(() => newEncrSecret);
149
+ })
150
+ .then(x => x.map(encrSecret => ({
151
+ encrSecret,
152
+ encrPublicKey: getEncrPub(encrSecret),
153
+ })));
154
+ }
155
+ async function getSecretKeys(appId, secretStorage) {
156
+ return seq(await getSsKeys(appId, secretStorage), await getEncrKeys(appId, secretStorage)).map(([ss, encr]) => ({
157
+ ...ss,
158
+ ...encr,
159
+ }));
160
+ }
161
+ function createDeeplink(payload) {
162
+ return `polkadotapp://pair?handshake=${toHex(payload)}`;
163
+ }
164
+ function waitForStatements(transport, topic, abortSignal, callback) {
165
+ return new Promise((resolve, reject) => {
166
+ const unsubscribe = transport.subscribeStatements([topic], statements => {
167
+ if (abortSignal?.aborted) {
168
+ unsubscribe();
169
+ try {
170
+ abortSignal.throwIfAborted();
171
+ }
172
+ catch (e) {
173
+ reject(e);
174
+ }
175
+ }
176
+ try {
177
+ callback(statements, value => {
178
+ unsubscribe();
179
+ resolve(value);
180
+ });
181
+ }
182
+ catch (e) {
183
+ unsubscribe();
184
+ reject(e);
185
+ }
186
+ });
187
+ });
188
+ }
@@ -1 +1,2 @@
1
- export declare const SS_ENDPOINTS: string[];
1
+ export declare const SS_DEV_ENDPOINTS: string[];
2
+ export declare const SS_PROD_ENDPOINTS: string[];
package/dist/constants.js CHANGED
@@ -1 +1,5 @@
1
- export const SS_ENDPOINTS = ['wss://pop3-testnet.parity-lab.parity.io/7910'];
1
+ export const SS_DEV_ENDPOINTS = ['wss://pop-testnet.parity-lab.parity.io:443/9910'];
2
+ export const SS_PROD_ENDPOINTS = [
3
+ 'wss://pop3-testnet.parity-lab.parity.io:443/7911',
4
+ 'wss://pop3-testnet.parity-lab.parity.io:443/7912',
5
+ ];
@@ -0,0 +1,4 @@
1
+ export declare class AbortError extends Error {
2
+ constructor(message?: string);
3
+ static isAbortError(err: unknown): err is AbortError;
4
+ }
@@ -0,0 +1,8 @@
1
+ export class AbortError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ }
5
+ static isAbortError(err) {
6
+ return err instanceof AbortError;
7
+ }
8
+ }
@@ -0,0 +1 @@
1
+ export declare function callbackRaceResolver<T, R>(callback: (value: R) => unknown, preprocess: (value: T) => PromiseLike<R>): (value: T) => unknown;
@@ -0,0 +1,17 @@
1
+ export function callbackRaceResolver(callback, preprocess) {
2
+ let abort = new AbortController();
3
+ return async (value) => {
4
+ abort.abort();
5
+ abort = new AbortController();
6
+ try {
7
+ const result = await preprocess(value);
8
+ if (abort.signal.aborted) {
9
+ return;
10
+ }
11
+ callback(result);
12
+ }
13
+ catch {
14
+ /* empty */
15
+ }
16
+ };
17
+ }
@@ -0,0 +1,12 @@
1
+ import type { Result, ResultAsync } from 'neverthrow';
2
+ type InferOk<R> = R extends Result<infer Ok, unknown> ? Ok : never;
3
+ type InferOkAsync<R> = R extends ResultAsync<infer Ok, unknown> ? Ok : never;
4
+ type InferErr<R> = R extends Result<unknown, infer Err> ? Err : never;
5
+ type InferErrAsync<R> = R extends ResultAsync<unknown, infer Err> ? Err : never;
6
+ type InferOks<Results> = Results extends [infer Head, ...infer Tail] ? [InferOk<Head>, ...InferOks<Tail>] : Results extends Result<unknown, unknown>[] ? InferOk<Results[number]>[] : [];
7
+ type InferOksAsync<Results> = Results extends [infer Head, ...infer Tail] ? [InferOkAsync<Head>, ...InferOksAsync<Tail>] : Results extends ResultAsync<unknown, unknown>[] ? InferOkAsync<Results[number]>[] : [];
8
+ type SeqResults<Results extends Result<unknown, unknown>[]> = Result<InferOks<Results>, InferErr<Results[number]> | Error>;
9
+ type SeqResultsAsync<Results extends ResultAsync<unknown, unknown>[]> = ResultAsync<InferOksAsync<Results>, InferErrAsync<Results[number]> | Error>;
10
+ export declare function seq<const Results extends Result<unknown, unknown>[]>(...result: Results): SeqResults<Results>;
11
+ export declare function seqAsync<const Results extends ResultAsync<unknown, unknown>[]>(...result: Results): SeqResultsAsync<Results>;
12
+ export {};
@@ -0,0 +1,15 @@
1
+ import { err, errAsync } from 'neverthrow';
2
+ export function seq(...result) {
3
+ const [head, ...tail] = result;
4
+ if (head === undefined) {
5
+ return err(new Error('Seq is empty'));
6
+ }
7
+ return tail.reduce((a, r) => a.andThen(av => r.map(rv => [...av, rv])), head.map(r => [r]));
8
+ }
9
+ export function seqAsync(...result) {
10
+ const [head, ...tail] = result;
11
+ if (head === undefined) {
12
+ return errAsync(new Error('Seq is empty'));
13
+ }
14
+ return tail.reduce((a, r) => a.andThen(av => r.map(rv => [...av, rv])), head.map(r => [r]));
15
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import { err, errAsync, ok, okAsync } from 'neverthrow';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { seq, seqAsync } from './result.js';
4
+ describe('Result', () => {
5
+ describe('seq', () => {
6
+ it('should concat ok values', () => {
7
+ expect(seq(ok(1), ok('2'))._unsafeUnwrap()).toEqual([1, '2']);
8
+ });
9
+ it('should fallback to err', () => {
10
+ expect(seq(ok(1), err('2')).orElse(ok)._unsafeUnwrap()).toEqual('2');
11
+ expect(seq(err(1), ok('2')).orElse(ok)._unsafeUnwrap()).toEqual(1);
12
+ });
13
+ });
14
+ describe('seqAsync', () => {
15
+ it('should concat ok values', async () => {
16
+ expect((await seqAsync(okAsync(1), okAsync('2')))._unsafeUnwrap()).toEqual([1, '2']);
17
+ });
18
+ it('should fallback to err', async () => {
19
+ await expect(seqAsync(okAsync(1), errAsync('2')).then(x => x._unsafeUnwrapErr())).resolves.toEqual('2');
20
+ await expect(seqAsync(errAsync(1), okAsync('2')).then(x => x._unsafeUnwrapErr())).resolves.toEqual(1);
21
+ });
22
+ });
23
+ });
@@ -1 +1,2 @@
1
- export declare function isAbortError(err: object): boolean;
1
+ export declare function toError(err: unknown): Error;
2
+ export declare function nonNullable<T>(value: T): value is Exclude<NonNullable<T>, void>;
@@ -1,3 +1,12 @@
1
- export function isAbortError(err) {
2
- return err && 'name' in err && err.name === 'AbortError';
1
+ export function toError(err) {
2
+ if (err instanceof Error) {
3
+ return err;
4
+ }
5
+ if (err) {
6
+ return new Error(err.toString());
7
+ }
8
+ return new Error('Unknown error occurred.');
9
+ }
10
+ export function nonNullable(value) {
11
+ return value !== null && value !== undefined;
3
12
  }
@@ -0,0 +1 @@
1
+ export declare function isAbortError(err: object): boolean;
@@ -0,0 +1,3 @@
1
+ export function isAbortError(err) {
2
+ return err && 'name' in err && err.name === 'AbortError';
3
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export type { PappAdapter } from './papp.js';
2
- export type { SignInStatus } from './modules/signIn.js';
2
+ export type { AuthentificationStatus } from './components/auth/types.js';
3
+ export type { UserSession } from './components/user/userSessionStorage.js';
3
4
  export type { Identity } from './adapters/identity/types.js';
4
5
  export declare function createPappHostAdapter(appId: string, metadata: string): import("./papp.js").PappAdapter;
@@ -0,0 +1 @@
1
+ export declare function getAccountsFlow(): void;
@@ -0,0 +1,2 @@
1
+ // TODO implement
2
+ export function getAccountsFlow() { }
@@ -4,6 +4,7 @@ export type SsPublicKey = Branded<Uint8Array, 'SsPublicKey'>;
4
4
  export type SsSecret = Branded<Uint8Array, 'SsSecret'>;
5
5
  export type EncrPublicKey = Branded<Uint8Array, 'EncrPublicKey'>;
6
6
  export type EncrSecret = Branded<Uint8Array, 'EncrSecret'>;
7
+ export type SharedSecret = Branded<Uint8Array, 'SharedSecret'>;
7
8
  export type SharedSession = Branded<Uint8Array, 'SharedSession'>;
8
9
  export declare const SsPubKey: Codec<SsPublicKey>;
9
10
  export declare const EncrPubKey: Codec<EncrPublicKey>;
@@ -12,13 +13,15 @@ export declare function bytesToString(bytes: Uint8Array): string;
12
13
  export declare function mergeBytes(...bytes: Uint8Array[]): Uint8Array<ArrayBuffer>;
13
14
  export declare const SS_SECRET_SEED_SIZE = 32;
14
15
  export declare function createSsSecret(seed: Uint8Array): SsSecret;
16
+ export declare function createSsHardDerivation(secret: SsSecret, derivation: string | number): SsSecret;
15
17
  export declare function getSsPub(secret: SsSecret): SsPublicKey;
18
+ export declare function signWithSsSecret(secret: SsSecret, message: Uint8Array): Uint8Array<ArrayBufferLike>;
16
19
  export declare const ENCR_SECRET_SEED_SIZE = 48;
17
20
  export declare function createEncrSecret(seed: Uint8Array): EncrSecret;
18
21
  export declare function getEncrPub(secret: EncrSecret): EncrPublicKey;
19
22
  export declare function createRandomSeed(suffix: string, size: number): Uint8Array<ArrayBufferLike>;
20
23
  export declare function createStableSeed(value: string, size: number): Uint8Array<ArrayBufferLike>;
21
24
  export declare function khash(secret: Uint8Array, message: Uint8Array): Uint8Array<ArrayBufferLike>;
22
- export declare function createSharedSecret(secret: EncrSecret, publicKey: Uint8Array): Uint8Array<ArrayBufferLike>;
23
- export declare function createSymmetricKey(sharedSecret: Uint8Array): Uint8Array<ArrayBuffer>;
25
+ export declare function createSharedSecret(secret: EncrSecret, publicKey: Uint8Array): SharedSecret;
26
+ export declare function encrypt(secret: Uint8Array, cipherText: Uint8Array): Uint8Array<ArrayBufferLike>;
24
27
  export declare function decrypt(secret: Uint8Array, message: Uint8Array): Uint8Array<ArrayBufferLike>;
@@ -4,8 +4,8 @@ import { blake2b } from '@noble/hashes/blake2.js';
4
4
  import { hkdf } from '@noble/hashes/hkdf.js';
5
5
  import { sha256 } from '@noble/hashes/sha2.js';
6
6
  import { randomBytes } from '@noble/hashes/utils.js';
7
- import { getPublicKey as sr25519GetPublicKey, secretFromSeed as sr25519SecretFromSeed } from '@scure/sr25519';
8
- import { Bytes } from 'scale-ts';
7
+ import { HDKD as sr25519HDKD, getPublicKey as sr25519GetPublicKey, secretFromSeed as sr25519SecretFromSeed, sign as sr25519Sign, } from '@scure/sr25519';
8
+ import { Bytes, str, u32 } from 'scale-ts';
9
9
  // schemas
10
10
  function brandedBytesCodec(length) {
11
11
  return Bytes(length);
@@ -36,9 +36,17 @@ export const SS_SECRET_SEED_SIZE = 32;
36
36
  export function createSsSecret(seed) {
37
37
  return sr25519SecretFromSeed(seed);
38
38
  }
39
+ export function createSsHardDerivation(secret, derivation) {
40
+ const chainCode = new Uint8Array(32);
41
+ chainCode.set(typeof derivation === 'string' ? str.enc(derivation) : u32.enc(derivation));
42
+ return sr25519HDKD.secretHard(secret, chainCode);
43
+ }
39
44
  export function getSsPub(secret) {
40
45
  return sr25519GetPublicKey(secret);
41
46
  }
47
+ export function signWithSsSecret(secret, message) {
48
+ return sr25519Sign(secret, message);
49
+ }
42
50
  // encryption key pair
43
51
  export const ENCR_SECRET_SEED_SIZE = 48;
44
52
  export function createEncrSecret(seed) {
@@ -59,10 +67,13 @@ export function khash(secret, message) {
59
67
  return blake2b(message, { dkLen: 256 / 8, key: secret });
60
68
  }
61
69
  export function createSharedSecret(secret, publicKey) {
62
- return p256.getSharedSecret(secret, publicKey);
70
+ return p256.getSharedSecret(secret, publicKey).slice(1, 33);
63
71
  }
64
- export function createSymmetricKey(sharedSecret) {
65
- return sharedSecret.slice(1, 33);
72
+ export function encrypt(secret, cipherText) {
73
+ const nonce = randomBytes(12);
74
+ const aesKey = hkdf(sha256, secret, new Uint8Array(), new Uint8Array(), 32);
75
+ const aes = gcm(aesKey, nonce);
76
+ return aes.encrypt(mergeBytes(nonce, cipherText));
66
77
  }
67
78
  export function decrypt(secret, message) {
68
79
  const nonce = message.slice(0, 12);
@@ -1,14 +1,12 @@
1
+ import type { ResultAsync } from 'neverthrow';
1
2
  import type { StorageAdapter } from '../adapters/storage/types.js';
2
- import type { SessionTopic } from '../types.js';
3
3
  import type { EncrSecret, SsSecret } from './crypto.js';
4
4
  export type SecretStorage = {
5
- readSsSecret(): Promise<SsSecret | null>;
6
- writeSsSecret(value: SsSecret): Promise<boolean>;
7
- readEncrSecret(): Promise<EncrSecret | null>;
8
- writeEncrSecret(value: EncrSecret): Promise<boolean>;
9
- readSessionTopic(): Promise<SessionTopic | null>;
10
- writeSessionTopic(value: SessionTopic): Promise<boolean>;
11
- readPappAccountId(): Promise<string | null>;
12
- writePappAccountId(value: string): Promise<boolean>;
5
+ readSsSecret(accountId: string): ResultAsync<SsSecret | null, Error>;
6
+ writeSsSecret(accountId: string, value: SsSecret): ResultAsync<void, Error>;
7
+ clearSsSecret(accountId: string): ResultAsync<void, Error>;
8
+ readEncrSecret(accountId: string): ResultAsync<EncrSecret | null, Error>;
9
+ writeEncrSecret(accountId: string, value: EncrSecret): ResultAsync<void, Error>;
10
+ clearEncrSecret(accountId: string): ResultAsync<void, Error>;
13
11
  };
14
12
  export declare function createSecretStorage(appId: string, storage: StorageAdapter): SecretStorage;
@@ -1,53 +1,40 @@
1
1
  import { gcm } from '@noble/ciphers/aes.js';
2
2
  import { blake2b } from '@noble/hashes/blake2.js';
3
3
  import { fromHex, toHex } from '@polkadot-api/utils';
4
- import { bytesToString, stringToBytes } from './crypto.js';
4
+ import { stringToBytes } from './crypto.js';
5
5
  export function createSecretStorage(appId, storage) {
6
6
  const ssSecret = rwBytes('SsSecret', appId, storage);
7
7
  const encrSecret = rwBytes('EncrSecret', appId, storage);
8
- const sessionTopic = rwBytes('SessionTopic', appId, storage);
9
- const pappAccountId = rwString('PappAccountId', appId, storage);
10
8
  return {
11
9
  readSsSecret: ssSecret.read,
12
10
  writeSsSecret: ssSecret.write,
11
+ clearSsSecret: ssSecret.clear,
13
12
  readEncrSecret: encrSecret.read,
14
13
  writeEncrSecret: encrSecret.write,
15
- readSessionTopic: sessionTopic.read,
16
- writeSessionTopic: sessionTopic.write,
17
- readPappAccountId: pappAccountId.read,
18
- writePappAccountId: pappAccountId.write,
14
+ clearEncrSecret: encrSecret.clear,
19
15
  };
20
16
  }
21
- const withAppId = (appId, key) => `${appId}_${key}`;
22
- const rwBytes = (key, appId, storage) => ({
23
- async read() {
24
- const value = await storage.read(withAppId(appId, key));
25
- if (value) {
26
- const aes = getAes(appId);
27
- return aes.decrypt(fromHex(value));
28
- }
29
- else {
30
- return null;
31
- }
17
+ const createKey = (key, context) => `${key}_${context}`;
18
+ const rwBytes = (baseKey, appId, storage) => ({
19
+ read(context) {
20
+ return storage.read(createKey(baseKey, context)).map(value => {
21
+ if (value) {
22
+ const aes = getAes(appId);
23
+ return aes.decrypt(fromHex(value));
24
+ }
25
+ else {
26
+ return null;
27
+ }
28
+ });
32
29
  },
33
- write(value) {
30
+ write(context, value) {
34
31
  const aes = getAes(appId);
35
- return storage.write(withAppId(appId, key), toHex(aes.encrypt(value)));
32
+ return storage.write(createKey(baseKey, context), toHex(aes.encrypt(value)));
33
+ },
34
+ clear(context) {
35
+ return storage.clear(createKey(baseKey, context));
36
36
  },
37
37
  });
38
- const rwString = (key, appId, storage) => {
39
- const bytes = rwBytes(key, appId, storage);
40
- return {
41
- async read() {
42
- const value = await bytes.read();
43
- return value ? bytesToString(value) : null;
44
- },
45
- write(value) {
46
- const b = stringToBytes(value);
47
- return bytes.write(b);
48
- },
49
- };
50
- };
51
38
  function getAes(appId) {
52
39
  return gcm(blake2b(stringToBytes(appId), { dkLen: 16 }), blake2b(stringToBytes('nonce'), { dkLen: 32 }));
53
40
  }
@@ -0,0 +1,5 @@
1
+ import type { Account } from './types.js';
2
+ export declare function createSessionId(accountA: Account, accountB: Account): Uint8Array<ArrayBufferLike>;
3
+ export declare function createRequestChannel(session: Uint8Array): Uint8Array<ArrayBufferLike>;
4
+ export declare function createResponseChannel(session: Uint8Array): Uint8Array<ArrayBufferLike>;
5
+ export declare function createAccount(accountId: Uint8Array, publicKey: Uint8Array, pin?: string): Account;
@@ -0,0 +1,29 @@
1
+ import { mergeUint8 } from '@polkadot-api/utils';
2
+ import { khash, stringToBytes } from '../crypto.js';
3
+ export function createSessionId(accountA, accountB) {
4
+ const sessionPrefix = stringToBytes('session');
5
+ const pinSeparator = stringToBytes('/');
6
+ function makePin(pin) {
7
+ return pin ? mergeUint8([pinSeparator, stringToBytes(pin)]) : pinSeparator;
8
+ }
9
+ const accountSessionParams = mergeUint8([
10
+ accountA.accountId,
11
+ accountB.accountId,
12
+ makePin(accountA.pin),
13
+ makePin(accountB.pin),
14
+ ]);
15
+ return khash(accountA.publicKey, mergeUint8(sessionPrefix, accountSessionParams));
16
+ }
17
+ export function createRequestChannel(session) {
18
+ return khash(session, stringToBytes('request'));
19
+ }
20
+ export function createResponseChannel(session) {
21
+ return khash(session, stringToBytes('response'));
22
+ }
23
+ export function createAccount(accountId, publicKey, pin) {
24
+ return {
25
+ accountId,
26
+ publicKey,
27
+ pin,
28
+ };
29
+ }
@@ -0,0 +1,12 @@
1
+ import type { Codec } from 'scale-ts';
2
+ import type { StorageAdapter } from '../../adapters/storage/types.js';
3
+ import type { Transport } from '../transport/transport.js';
4
+ import type { Account, Session } from './types.js';
5
+ export type SessionParams<T extends Codec<any>> = {
6
+ ownAccount: Account;
7
+ peerAccount: Account;
8
+ transport: Transport;
9
+ storage: StorageAdapter;
10
+ codec: T;
11
+ };
12
+ export declare function createSession<T extends Codec<any>>({ ownAccount, peerAccount, transport, storage, codec, }: SessionParams<T>): Session<T>;
@@ -0,0 +1,46 @@
1
+ import { okAsync } from 'neverthrow';
2
+ import { toHex } from 'polkadot-api/utils';
3
+ import { storageListView } from '../storageView.js';
4
+ import { createSessionId } from './helpers.js';
5
+ export function createSession({ ownAccount, peerAccount, transport, storage, codec, }) {
6
+ // const ownSession = createSessionId(ownAccount, peerAccount);
7
+ const peerSession = createSessionId(peerAccount, ownAccount);
8
+ const processedStorage = storageListView({
9
+ key: `ProcessesMessages_${toHex(peerSession)}`,
10
+ storage,
11
+ initial: [],
12
+ from: JSON.parse,
13
+ to: JSON.stringify,
14
+ });
15
+ let subscriptions = [];
16
+ return {
17
+ subscribe(callback) {
18
+ const unsub = transport.handleRequest(ownAccount, peerAccount, codec, async (message) => {
19
+ processedStorage.read().andThen(processed => {
20
+ if (processed.includes(message.requestId)) {
21
+ return okAsync();
22
+ }
23
+ return callback(message.data).andThen(processed => {
24
+ if (processed) {
25
+ return processedStorage.mutate(p => p.concat(message.requestId));
26
+ }
27
+ else {
28
+ return okAsync();
29
+ }
30
+ });
31
+ });
32
+ });
33
+ subscriptions.push(unsub);
34
+ return () => {
35
+ unsub();
36
+ subscriptions = subscriptions.filter(x => x !== unsub);
37
+ };
38
+ },
39
+ dispose() {
40
+ for (const unsub of subscriptions) {
41
+ unsub();
42
+ }
43
+ subscriptions = [];
44
+ },
45
+ };
46
+ }
@@ -0,0 +1,12 @@
1
+ import type { ResultAsync } from 'neverthrow';
2
+ import type { Codec, CodecType } from 'scale-ts';
3
+ import type { Callback } from '../../types.js';
4
+ export type Session<T extends Codec<any>> = {
5
+ subscribe(callback: Callback<CodecType<T>, ResultAsync<boolean, Error>>): VoidFunction;
6
+ dispose(): void;
7
+ };
8
+ export type Account = {
9
+ accountId: Uint8Array;
10
+ publicKey: Uint8Array;
11
+ pin: string | undefined;
12
+ };
@@ -0,0 +1 @@
1
+ export {};