@novasamatech/host-papp 0.5.0-17 → 0.5.0-18

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/crypto.d.ts CHANGED
@@ -11,7 +11,7 @@ export declare const SsPubKey: Codec<SsPublicKey>;
11
11
  export declare const EncrPubKey: Codec<EncrPublicKey>;
12
12
  export declare function stringToBytes(str: string): Uint8Array<ArrayBuffer>;
13
13
  export declare function bytesToString(bytes: Uint8Array): string;
14
- export declare function createSsSecret(mnemonic: string): SsSecret;
14
+ export declare function createSsSecret(entropy: Uint8Array): SsSecret;
15
15
  export declare function createSsDerivation(secret: SsSecret, derivation: string): SsSecret;
16
16
  export declare function getSsPub(secret: SsSecret): SsPublicKey;
17
17
  export declare function signWithSsSecret(secret: SsSecret, message: Uint8Array): Uint8Array<ArrayBufferLike>;
@@ -24,6 +24,6 @@ export type DerivedSr25519Account = {
24
24
  verify(message: Uint8Array, signature: Uint8Array): boolean;
25
25
  };
26
26
  export declare function deriveSr25519Account(mnemonic: string, derivation: string): DerivedSr25519Account;
27
- export declare function createEncrSecret(mnemonic: string): EncrSecret;
27
+ export declare function createEncrSecret(entropy: Uint8Array): EncrSecret;
28
28
  export declare function getEncrPub(secret: EncrSecret): EncrPublicKey;
29
29
  export declare function createSharedSecret(secret: EncrSecret, publicKey: Uint8Array): SharedSecret;
package/dist/crypto.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { p256 } from '@noble/curves/nist.js';
2
- import { mnemonicToEntropy, mnemonicToMiniSecret } from '@polkadot-labs/hdkd-helpers';
2
+ import { entropyToMiniSecret, mnemonicToEntropy } from '@polkadot-labs/hdkd-helpers';
3
3
  import { HDKD as sr25519HDKD, getPublicKey as sr25519GetPublicKey, secretFromSeed as sr25519SecretFromSeed, sign as sr25519Sign, verify as sr25519Verify, } from '@scure/sr25519';
4
4
  import { Bytes, str } from 'scale-ts';
5
5
  // schemas
@@ -33,8 +33,8 @@ function createChainCode(derivation) {
33
33
  return chainCode;
34
34
  }
35
35
  // statement store key pair
36
- export function createSsSecret(mnemonic) {
37
- const miniSecret = mnemonicToMiniSecret(mnemonic);
36
+ export function createSsSecret(entropy) {
37
+ const miniSecret = entropyToMiniSecret(entropy);
38
38
  return sr25519SecretFromSeed(miniSecret);
39
39
  }
40
40
  export function createSsDerivation(secret, derivation) {
@@ -59,19 +59,20 @@ export function verifyWithSsSecret(message, signature, publicKey) {
59
59
  return sr25519Verify(message, signature, publicKey);
60
60
  }
61
61
  export function deriveSr25519Account(mnemonic, derivation) {
62
- const secret = createSsDerivation(createSsSecret(mnemonic), derivation);
62
+ const entropy = mnemonicToEntropy(mnemonic);
63
+ const secret = createSsDerivation(createSsSecret(entropy), derivation);
63
64
  const publicKey = getSsPub(secret);
64
65
  return {
65
66
  secret,
66
67
  publicKey,
67
- entropy: mnemonicToEntropy(mnemonic),
68
+ entropy,
68
69
  sign: message => signWithSsSecret(secret, message),
69
70
  verify: (message, signature) => verifyWithSsSecret(message, signature, publicKey),
70
71
  };
71
72
  }
72
73
  // encryption key pair
73
- export function createEncrSecret(mnemonic) {
74
- const miniSecret = mnemonicToMiniSecret(mnemonic);
74
+ export function createEncrSecret(entropy) {
75
+ const miniSecret = entropyToMiniSecret(entropy);
75
76
  const seed = new Uint8Array(48);
76
77
  seed.set(miniSecret);
77
78
  const { secretKey } = p256.keygen(seed);
@@ -1,4 +1,3 @@
1
1
  export declare class AbortError extends Error {
2
2
  constructor(message?: string);
3
- static isAbortError(err: unknown): err is AbortError;
4
3
  }
@@ -2,7 +2,4 @@ export class AbortError extends Error {
2
2
  constructor(message) {
3
3
  super(message);
4
4
  }
5
- static isAbortError(err) {
6
- return err instanceof AbortError;
7
- }
8
5
  }
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { SS_STABLE_STAGE_ENDPOINTS, SS_UNSTABLE_STAGE_ENDPOINTS } from './constants.js';
2
2
  export type { PappAdapter } from './papp.js';
3
3
  export { createPappAdapter } from './papp.js';
4
- export type { AuthentificationStatus } from './sso/auth/types.js';
4
+ export type { AttestationStatus, PairingStatus } from './sso/auth/types.js';
5
5
  export type { UserSession } from './sso/sessionManager/userSession.js';
6
6
  export type { Identity } from './identity/types.js';
7
7
  export type { SignPayloadRequest } from './sso/sessionManager/scale/signPayloadRequest.js';
package/dist/papp.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createLazyClient, createPapiStatementStoreAdapter } from '@novasamatech/statement-store';
2
2
  import { createLocalStorageAdapter } from '@novasamatech/storage-adapter';
3
3
  import { getWsProvider } from '@polkadot-api/ws-provider';
4
- import { SS_UNSTABLE_STAGE_ENDPOINTS } from './constants.js';
4
+ import { SS_STABLE_STAGE_ENDPOINTS } from './constants.js';
5
5
  import { createIdentityRepository } from './identity/impl.js';
6
6
  import { createIdentityRpcAdapter } from './identity/rpcAdapter.js';
7
7
  import { createAuth } from './sso/auth/impl.js';
@@ -9,7 +9,7 @@ import { createSsoSessionManager } from './sso/sessionManager/impl.js';
9
9
  import { createUserSecretRepository } from './sso/userSecretRepository.js';
10
10
  import { createUserSessionRepository } from './sso/userSessionRepository.js';
11
11
  export function createPappAdapter({ appId, metadata, adapters }) {
12
- const lazyClient = adapters?.lazyClient ?? createLazyClient(getWsProvider(SS_UNSTABLE_STAGE_ENDPOINTS));
12
+ const lazyClient = adapters?.lazyClient ?? createLazyClient(getWsProvider(SS_STABLE_STAGE_ENDPOINTS));
13
13
  const statementStore = adapters?.statementStore ?? createPapiStatementStoreAdapter(lazyClient);
14
14
  const identities = adapters?.identities ?? createIdentityRpcAdapter(lazyClient);
15
15
  const storage = adapters?.storage ?? createLocalStorageAdapter(appId);
@@ -11,7 +11,7 @@ type Params = {
11
11
  lazyClient: LazyClient;
12
12
  };
13
13
  export declare function createAuth({ metadata, statementStore, ssoSessionRepository, userSecretRepository, lazyClient, }: Params): {
14
- status: {
14
+ pairingStatus: {
15
15
  read: () => {
16
16
  step: "none";
17
17
  } | {
@@ -22,7 +22,7 @@ export declare function createAuth({ metadata, statementStore, ssoSessionReposit
22
22
  step: "pairing";
23
23
  payload: string;
24
24
  } | {
25
- step: "error";
25
+ step: "pairingError";
26
26
  message: string;
27
27
  } | {
28
28
  step: "finished";
@@ -38,7 +38,7 @@ export declare function createAuth({ metadata, statementStore, ssoSessionReposit
38
38
  step: "pairing";
39
39
  payload: string;
40
40
  } | {
41
- step: "error";
41
+ step: "pairingError";
42
42
  message: string;
43
43
  } | {
44
44
  step: "finished";
@@ -47,6 +47,32 @@ export declare function createAuth({ metadata, statementStore, ssoSessionReposit
47
47
  onFirstSubscribe: (callback: VoidFunction) => import("nanoevents").Unsubscribe;
48
48
  onLastUnsubscribe: (callback: VoidFunction) => import("nanoevents").Unsubscribe;
49
49
  };
50
+ attestationStatus: {
51
+ read: () => {
52
+ step: "none";
53
+ } | {
54
+ step: "attestation";
55
+ username: string;
56
+ } | {
57
+ step: "attestationError";
58
+ message: string;
59
+ } | {
60
+ step: "finished";
61
+ };
62
+ subscribe: (fn: (value: {
63
+ step: "none";
64
+ } | {
65
+ step: "attestation";
66
+ username: string;
67
+ } | {
68
+ step: "attestationError";
69
+ message: string;
70
+ } | {
71
+ step: "finished";
72
+ }) => void) => () => void;
73
+ onFirstSubscribe: (callback: VoidFunction) => import("nanoevents").Unsubscribe;
74
+ onLastUnsubscribe: (callback: VoidFunction) => import("nanoevents").Unsubscribe;
75
+ };
50
76
  authenticate(): ResultAsync<StoredUserSession | null, Error>;
51
77
  abortAuthentication(): void;
52
78
  };
@@ -10,37 +10,48 @@ import { createStoredUserSession } from '../userSessionRepository.js';
10
10
  import { createAliceVerifier, createAttestationService } from './attestationService.js';
11
11
  import { HandshakeData, HandshakeResponsePayload, HandshakeResponseSensitiveData } from './scale/handshake.js';
12
12
  export function createAuth({ metadata, statementStore, ssoSessionRepository, userSecretRepository, lazyClient, }) {
13
- const authStatus = createState({ step: 'none' });
13
+ const attestationStatus = createState({ step: 'none' });
14
+ const pairingStatus = createState({ step: 'none' });
14
15
  let authResult = null;
15
16
  let abort = null;
16
- function attestateAccount(account, signal) {
17
+ function attestAccount(account, signal) {
17
18
  const attestationService = createAttestationService(lazyClient);
18
- authStatus.write({ step: 'attestation' });
19
19
  const verifier = createAliceVerifier();
20
20
  const username = attestationService.claimUsername();
21
+ attestationStatus.write({ step: 'attestation', username });
21
22
  return attestationService
22
23
  .grantVerifierAllowance(verifier)
23
24
  .andThrough(() => processSignal(signal))
24
25
  .andThen(() => attestationService.registerLitePerson(username, account, verifier))
25
- .andThrough(() => processSignal(signal));
26
+ .andThrough(() => processSignal(signal))
27
+ .andTee(() => {
28
+ attestationStatus.write({ step: 'finished' });
29
+ })
30
+ .orTee(e => {
31
+ if (!(e instanceof AbortError)) {
32
+ attestationStatus.write({ step: 'attestationError', message: e.message });
33
+ }
34
+ });
26
35
  }
27
- function handshake(account, mnemonic, signal) {
36
+ function handshake(account, signal) {
28
37
  const localAccount = createLocalSessionAccount(createAccountId(account.publicKey));
29
- return createEncrKeys(mnemonic).asyncAndThen(({ encrSecret, encrPublicKey }) => {
30
- const handshakePayload = createHandshakePayloadV1({
31
- ssPublicKey: account.publicKey,
32
- encrPublicKey,
33
- metadata,
34
- }).andTee(payload => authStatus.write({ step: 'pairing', payload: createDeeplink(payload) }));
35
- const pappResponse = handshakePayload
36
- .andThen(() => createHandshakeTopic(localAccount, encrPublicKey))
37
- .asyncAndThen(topic => waitForStatements(callback => statementStore.subscribeStatements([topic], callback), signal, (statements, resolve) => {
38
+ pairingStatus.write({ step: 'initial' });
39
+ const encrKeys = createEncrKeys(account.entropy);
40
+ const handshakePayload = encrKeys.andThen(({ publicKey }) => createHandshakePayloadV1({
41
+ ssPublicKey: account.publicKey,
42
+ encrPublicKey: publicKey,
43
+ metadata,
44
+ }));
45
+ const handshakeTopic = encrKeys.andThen(({ publicKey }) => createHandshakeTopic(localAccount, publicKey));
46
+ const dataPrepared = Result.combine([handshakePayload, handshakeTopic, encrKeys]).andTee(([payload]) => pairingStatus.write({ step: 'pairing', payload: createDeeplink(payload) }));
47
+ return dataPrepared.asyncAndThen(([, handshakeTopic, encrKeys]) => {
48
+ const pappResponse = waitForStatements(callback => statementStore.subscribeStatements([handshakeTopic], callback), signal, (statements, resolve) => {
38
49
  for (const statement of statements) {
39
50
  if (!statement.data)
40
51
  continue;
41
52
  const session = retrieveSession({
42
53
  localAccount,
43
- encrSecret,
54
+ encrSecret: encrKeys.secret,
44
55
  payload: statement.data.asBytes(),
45
56
  }).unwrapOr(null);
46
57
  if (session) {
@@ -48,33 +59,46 @@ export function createAuth({ metadata, statementStore, ssoSessionRepository, use
48
59
  break;
49
60
  }
50
61
  }
51
- }));
52
- const secretesSaved = pappResponse.andThen(({ id }) => userSecretRepository.write(id, { ssSecret: account.secret, encrSecret, mnemonic }));
62
+ });
63
+ const secretesSaved = pappResponse.andThen(({ id }) => {
64
+ return userSecretRepository.write(id, {
65
+ ssSecret: account.secret,
66
+ encrSecret: encrKeys.secret,
67
+ entropy: account.entropy,
68
+ });
69
+ });
70
+ // secrets and sso session should be chained, or it can produce an incorrect state
53
71
  const userCreated = secretesSaved.andThen(() => pappResponse.andThen(ssoSessionRepository.add));
54
- const result = ResultAsync.combine([userCreated, secretesSaved]).map(([session]) => session);
55
- return result.orElse(e => (AbortError.isAbortError(e) ? ok(null) : err(toError(e))));
72
+ const sessionReceived = ResultAsync.combine([userCreated, secretesSaved]).map(([session]) => session);
73
+ return sessionReceived
74
+ .andTee(session => {
75
+ pairingStatus.write(session ? { step: 'finished', session } : { step: 'none' });
76
+ })
77
+ .orTee(e => {
78
+ if (!(e instanceof AbortError)) {
79
+ pairingStatus.write({ step: 'pairingError', message: e.message });
80
+ }
81
+ });
56
82
  });
57
83
  }
58
84
  const authModule = {
59
- status: readonly(authStatus),
85
+ pairingStatus: readonly(pairingStatus),
86
+ attestationStatus: readonly(attestationStatus),
60
87
  authenticate() {
61
88
  if (authResult) {
62
89
  return authResult;
63
90
  }
64
91
  abort = new AbortController();
65
- const signal = abort.signal;
66
- const mnemonic = generateMnemonic();
67
- const account = deriveSr25519Account(mnemonic, '//wallet');
68
- authStatus.write({ step: 'initial' });
69
- authResult = attestateAccount(account, signal)
70
- .andThen(() => handshake(account, mnemonic, signal))
71
- .andTee(session => {
72
- authStatus.write(session ? { step: 'finished', session } : { step: 'none' });
92
+ const account = deriveSr25519Account(generateMnemonic(), '//wallet//sso');
93
+ authResult = ResultAsync.combine([handshake(account, abort.signal), attestAccount(account, abort.signal)])
94
+ .map(([session]) => session)
95
+ .orElse(e => (e instanceof AbortError ? ok(null) : err(e)))
96
+ .andTee(() => {
97
+ abort = null;
73
98
  })
74
- .orTee(e => {
99
+ .orTee(() => {
75
100
  authResult = null;
76
101
  abort = null;
77
- authStatus.write({ step: 'error', message: e.message });
78
102
  });
79
103
  return authResult;
80
104
  },
@@ -84,7 +108,8 @@ export function createAuth({ metadata, statementStore, ssoSessionRepository, use
84
108
  abort = null;
85
109
  }
86
110
  authResult = null;
87
- authStatus.reset();
111
+ pairingStatus.reset();
112
+ attestationStatus.reset();
88
113
  },
89
114
  };
90
115
  return authModule;
@@ -106,11 +131,11 @@ function parseHandshakePayload(payload) {
106
131
  throw new Error('Unsupported handshake payload version');
107
132
  }
108
133
  }
109
- const createEncrKeys = fromThrowable((mnemonic) => {
110
- const encrSecret = createEncrSecret(mnemonic);
134
+ const createEncrKeys = fromThrowable((entropy) => {
135
+ const secret = createEncrSecret(entropy);
111
136
  return {
112
- encrSecret,
113
- encrPublicKey: getEncrPub(encrSecret),
137
+ secret,
138
+ publicKey: getEncrPub(secret),
114
139
  };
115
140
  }, toError);
116
141
  function retrieveSession({ payload, encrSecret, localAccount, }) {
@@ -1,5 +1,5 @@
1
1
  import type { StoredUserSession } from '../userSessionRepository.js';
2
- export type AuthentificationStatus = {
2
+ export type PairingStatus = {
3
3
  step: 'none';
4
4
  } | {
5
5
  step: 'initial';
@@ -9,9 +9,20 @@ export type AuthentificationStatus = {
9
9
  step: 'pairing';
10
10
  payload: string;
11
11
  } | {
12
- step: 'error';
12
+ step: 'pairingError';
13
13
  message: string;
14
14
  } | {
15
15
  step: 'finished';
16
16
  session: StoredUserSession;
17
17
  };
18
+ export type AttestationStatus = {
19
+ step: 'none';
20
+ } | {
21
+ step: 'attestation';
22
+ username: string;
23
+ } | {
24
+ step: 'attestationError';
25
+ message: string;
26
+ } | {
27
+ step: 'finished';
28
+ };
@@ -6,7 +6,7 @@ type StoredUserSecrets = CodecType<typeof StoredUserSecretsCodec>;
6
6
  declare const StoredUserSecretsCodec: import("scale-ts").Codec<{
7
7
  ssSecret: SsSecret;
8
8
  encrSecret: EncrSecret;
9
- mnemonic: string;
9
+ entropy: Uint8Array<ArrayBufferLike>;
10
10
  }>;
11
11
  export type UserSecretRepository = ReturnType<typeof createUserSecretRepository>;
12
12
  export declare function createUserSecretRepository(salt: string, storage: StorageAdapter): {
@@ -2,13 +2,13 @@ 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
4
  import { fromThrowable } from 'neverthrow';
5
- import { Struct, str } from 'scale-ts';
5
+ import { Bytes, Struct } from 'scale-ts';
6
6
  import { BrandedBytesCodec, stringToBytes } from '../crypto.js';
7
7
  import { toError } from '../helpers/utils.js';
8
8
  const StoredUserSecretsCodec = Struct({
9
9
  ssSecret: BrandedBytesCodec(),
10
10
  encrSecret: BrandedBytesCodec(),
11
- mnemonic: str,
11
+ entropy: Bytes(),
12
12
  });
13
13
  export function createUserSecretRepository(salt, storage) {
14
14
  const baseKey = 'UserSecrets';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@novasamatech/host-papp",
3
3
  "type": "module",
4
- "version": "0.5.0-17",
4
+ "version": "0.5.0-18",
5
5
  "description": "Polkadot app integration",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
@@ -28,9 +28,9 @@
28
28
  "@noble/ciphers": "2.1.1",
29
29
  "@noble/curves": "2.0.1",
30
30
  "@noble/hashes": "2.0.1",
31
- "@novasamatech/host-api": "0.5.0-17",
32
- "@novasamatech/statement-store": "0.5.0-17",
33
- "@novasamatech/storage-adapter": "0.5.0-17",
31
+ "@novasamatech/host-api": "0.5.0-18",
32
+ "@novasamatech/statement-store": "0.5.0-18",
33
+ "@novasamatech/storage-adapter": "0.5.0-18",
34
34
  "@polkadot-api/substrate-bindings": "^0.16.5",
35
35
  "@polkadot-labs/hdkd-helpers": "^0.0.27",
36
36
  "@scure/sr25519": "1.0.0",