@novasamatech/host-papp 0.8.12 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +26 -1
  2. package/dist/crypto.js +8 -11
  3. package/dist/crypto.spec.d.ts +1 -0
  4. package/dist/crypto.spec.js +31 -0
  5. package/dist/index.d.ts +1 -0
  6. package/dist/sso/allowance/repository.js +2 -1
  7. package/dist/sso/auth/scale/handshakeV2.d.ts +5 -5
  8. package/dist/sso/auth/scale/handshakeV2.js +11 -10
  9. package/dist/sso/auth/v2/envelope.d.ts +6 -6
  10. package/dist/sso/auth/v2/envelope.js +9 -9
  11. package/dist/sso/auth/v2/state.d.ts +2 -2
  12. package/dist/sso/auth/v2/topic.d.ts +1 -1
  13. package/dist/sso/auth/v2/topic.js +1 -1
  14. package/dist/sso/deviceIdentityStore.js +2 -1
  15. package/dist/sso/sessionManager/scale/createTransaction.d.ts +9 -3
  16. package/dist/sso/sessionManager/scale/createTransaction.js +2 -2
  17. package/dist/sso/sessionManager/scale/productSubtree.js +2 -1
  18. package/dist/sso/sessionManager/scale/remoteMessage.d.ts +75 -21
  19. package/dist/sso/sessionManager/scale/remoteMessage.js +4 -0
  20. package/dist/sso/sessionManager/scale/resourceAllocation.d.ts +14 -5
  21. package/dist/sso/sessionManager/scale/resourceAllocation.js +8 -3
  22. package/dist/sso/sessionManager/scale/ringVrf.d.ts +14 -2
  23. package/dist/sso/sessionManager/scale/ringVrf.js +2 -1
  24. package/dist/sso/sessionManager/scale/signVrf.d.ts +7 -1
  25. package/dist/sso/sessionManager/scale/signVrf.js +2 -2
  26. package/dist/sso/sessionManager/scale/signing.d.ts +39 -15
  27. package/dist/sso/sessionManager/scale/signing.js +2 -2
  28. package/dist/sso/sessionManager/userSession.d.ts +7 -0
  29. package/dist/sso/sessionManager/userSession.js +18 -0
  30. package/dist/sso/userSecretRepository.js +2 -1
  31. package/dist/sso/userSessionRepository.js +13 -8
  32. package/dist/sso/userSessionRepository.spec.js +4 -4
  33. package/package.json +5 -5
package/README.md CHANGED
@@ -233,7 +233,10 @@ selects the member key for the ring; `callingProductId` names the product the ho
233
233
  for. Both take the same `(context, ring)` so the alias in the proof matches `getRingVrfAlias`.
234
234
 
235
235
  ```ts
236
- const context = ['product.dot', '0x00']; // [productId, suffix] suffix is a 0x-hex string
236
+ // [productId, suffix]. The suffix is the wire `Index(u32) | Raw([u8; 32])` selector
237
+ // (RFC 0022): `Index` for a plain index, `Raw` for a raw 32-byte index. It
238
+ // expands to the same 32-byte value as a product account's derivation index.
239
+ const context = ['product.dot', { tag: 'Index', value: 0 }];
237
240
  const ring = {
238
241
  chainId: '0x…', // 32-byte chain genesis hash
239
242
  junctions: [{ tag: 'PalletInstance', value: 42 }],
@@ -250,6 +253,28 @@ proof.match(
250
253
  );
251
254
  ```
252
255
 
256
+ ## Product subtree public keys
257
+
258
+ Product accounts live at `//product//{productId}/{index}` (RFC 0022). The
259
+ product junction is **hard**, so the user's root public key alone no longer
260
+ determines product account public keys — the host asks the paired device for the
261
+ product-subtree public key once, then soft-derives account public keys locally
262
+ from it.
263
+
264
+ ```ts
265
+ const subtreeKey = await currentSession.getProductSubtree('product.dot');
266
+
267
+ subtreeKey.match(
268
+ publicKey => cacheProductSubtree('product.dot', publicKey), // one round trip per product, ever
269
+ error => console.error('subtree lookup failed:', error),
270
+ );
271
+ ```
272
+
273
+ The request is consent-free — the response carries no secret material. Only
274
+ `AutoSigning` does: its payload is now the product-subtree secret key alone
275
+ (`productRootPrivateKey`, 64-byte expanded sr25519 secret), which exposes exactly
276
+ that product's subtree. The former `productDerivationSecret` is gone.
277
+
253
278
  ## Identity lookups
254
279
 
255
280
  `papp.identity` resolves on-chain identity data (lite / full username, credibility, slots)
package/dist/crypto.js CHANGED
@@ -1,13 +1,13 @@
1
- import { p256 } from '@noble/curves/nist.js';
1
+ import { x25519 } from '@noble/curves/ed25519.js';
2
+ import { Bytes } from '@novasamatech/scale';
2
3
  import { createSr25519Secret, deriveSr25519PublicKey, signWithSr25519Secret, verifySr25519Signature, } from '@novasamatech/statement-store';
3
4
  import { entropyToMiniSecret, mnemonicToEntropy } from '@polkadot-labs/hdkd-helpers';
4
- import { Bytes } from 'scale-ts';
5
5
  // schemas
6
6
  export function BrandedBytesCodec(length) {
7
7
  return Bytes(length);
8
8
  }
9
9
  export const SsPubKey = BrandedBytesCodec(32);
10
- export const EncrPubKey = BrandedBytesCodec(65);
10
+ export const EncrPubKey = BrandedBytesCodec(32);
11
11
  // helpers
12
12
  const textEncoder = new TextEncoder();
13
13
  export function stringToBytes(str) {
@@ -27,16 +27,13 @@ export function deriveSr25519Account(mnemonic, derivation) {
27
27
  }
28
28
  // encryption key pair
29
29
  export function createEncrSecret(entropy) {
30
- const miniSecret = entropyToMiniSecret(entropy);
31
- const seed = new Uint8Array(48);
32
- seed.set(miniSecret);
33
- const { secretKey } = p256.keygen(seed);
34
- return secretKey;
30
+ // The 32-byte mini-secret is the X25519 private scalar (clamped internally by @noble on use).
31
+ return entropyToMiniSecret(entropy);
35
32
  }
36
33
  export function getEncrPub(secret) {
37
- return p256.getPublicKey(secret, false);
34
+ return x25519.getPublicKey(secret);
38
35
  }
39
36
  export function createSharedSecret(secret, publicKey) {
40
- // slicing first byte: @noble/curves adds y offset at the start
41
- return p256.getSharedSecret(secret, publicKey).slice(1, 33);
37
+ // The X25519 output is used whole. @noble aborts on an all-zero (small-order) result per RFC 7748.
38
+ return x25519.getSharedSecret(secret, publicKey);
42
39
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,31 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createSharedSecret, getEncrPub } from './crypto.js';
3
+ // RFC 7748 §5.2 X25519 test vector.
4
+ const fromHex = (hex) => Uint8Array.from(hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
5
+ const ALICE_PRIVATE = '77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a';
6
+ const ALICE_PUBLIC = '8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a';
7
+ const BOB_PRIVATE = '5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb';
8
+ const BOB_PUBLIC = 'de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f';
9
+ const SHARED_SECRET = '4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742';
10
+ const asSecret = (hex) => fromHex(hex);
11
+ describe('X25519 encryption keys', () => {
12
+ it('derives a 32-byte X25519 public key from a private scalar (RFC 7748 vector)', () => {
13
+ const publicKey = getEncrPub(asSecret(ALICE_PRIVATE));
14
+ expect(publicKey.length).toBe(32);
15
+ expect(publicKey).toEqual(fromHex(ALICE_PUBLIC));
16
+ });
17
+ it('computes the RFC 7748 shared secret whole, without slicing', () => {
18
+ const shared = createSharedSecret(asSecret(ALICE_PRIVATE), fromHex(BOB_PUBLIC));
19
+ expect(shared.length).toBe(32);
20
+ expect(shared).toEqual(fromHex(SHARED_SECRET));
21
+ });
22
+ it('agrees on the same shared secret from either side', () => {
23
+ const fromAlice = createSharedSecret(asSecret(ALICE_PRIVATE), fromHex(BOB_PUBLIC));
24
+ const fromBob = createSharedSecret(asSecret(BOB_PRIVATE), fromHex(ALICE_PUBLIC));
25
+ expect(fromAlice).toEqual(fromBob);
26
+ });
27
+ it('rejects a small-order public key (all-zero shared secret)', () => {
28
+ const smallOrderPoint = new Uint8Array(32);
29
+ expect(() => createSharedSecret(asSecret(ALICE_PRIVATE), smallOrderPoint)).toThrow();
30
+ });
31
+ });
package/dist/index.d.ts CHANGED
@@ -14,3 +14,4 @@ export type { SignRawLegacyRequest, SignRawLegacyResponse, SigningPayloadRequest
14
14
  export type { RingVrfAliasRequest, RingVrfAliasResponse, RingVrfProofRequest, RingVrfProofResponse, } from './sso/sessionManager/scale/ringVrf.js';
15
15
  export type { SignVrfErr, SignVrfRequest, SignVrfResponse } from './sso/sessionManager/scale/signVrf.js';
16
16
  export type { CreateTransactionLegacyRequest, CreateTransactionRequest, CreateTransactionResponse, } from './sso/sessionManager/scale/createTransaction.js';
17
+ export type { ProductSubtreeRequest, ProductSubtreeResponse } from './sso/sessionManager/scale/productSubtree.js';
@@ -1,8 +1,9 @@
1
1
  import { gcm } from '@noble/ciphers/aes.js';
2
2
  import { blake2b } from '@noble/hashes/blake2.js';
3
+ import { Bytes } from '@novasamatech/scale';
3
4
  import { fromThrowable } from 'neverthrow';
4
5
  import { fromHex, toHex } from 'polkadot-api/utils';
5
- import { Bytes, Enum, Struct, Vector, _void, str } from 'scale-ts';
6
+ import { Enum, Struct, Vector, _void, str } from 'scale-ts';
6
7
  import { stringToBytes } from '../../crypto.js';
7
8
  import { toError } from '../../helpers/utils.js';
8
9
  const AllowanceResourceKindCodec = Enum({
@@ -16,14 +16,14 @@
16
16
  * for soft-derivation of product accounts; PApp
17
17
  * and host MUST derive identically so a dapp sees
18
18
  * the same address on every device.
19
- * - `identityChatPrivateKey`— user identity chat P-256 private scalar (32 bytes),
19
+ * - `identityChatPrivateKey`— user identity chat X25519 private scalar (32 bytes),
20
20
  * shared per the multi-device spec so this device
21
21
  * can decrypt traffic addressed to the user identity
22
- * - `deviceEncPubKey` — encryption public key of the PApp device (65 bytes,
23
- * P-256 uncompressed). Tells the host which key to
22
+ * - `deviceEncPubKey` — encryption public key of the PApp device (32 bytes,
23
+ * X25519). Tells the host which key to
24
24
  * use when addressing chat envelopes back to the
25
25
  * authorising PApp device.
26
- * - `ssoEncPubKey` — `papp_encr_pub` (65 bytes, P-256 uncompressed); see
26
+ * - `ssoEncPubKey` — `papp_encr_pub` (32 bytes, X25519); see
27
27
  * the `HandshakeSuccessV2` codec doc below.
28
28
  * - `rootEntropySource` — 32 bytes; `blake2b256_keyed(rootAccountSecret,
29
29
  * "product-entropy-derivation")` per RFC-0007 (layer 1).
@@ -137,7 +137,7 @@ export declare const HandshakeSuccessV2: import("scale-ts").Codec<{
137
137
  deviceEncPubKey: Uint8Array<ArrayBufferLike>;
138
138
  rootEntropySource: Uint8Array<ArrayBufferLike>;
139
139
  }>;
140
- /** Derive the identity chat P-256 public key (uncompressed) from its private scalar. */
140
+ /** Derive the identity chat X25519 public key (32 bytes) from its private scalar. */
141
141
  export declare const deriveIdentityChatPublicKey: (privateKey: Uint8Array) => Uint8Array;
142
142
  export declare const HandshakeStatusV2: import("scale-ts").Codec<{
143
143
  tag: "AllowanceAllocation";
@@ -16,14 +16,14 @@
16
16
  * for soft-derivation of product accounts; PApp
17
17
  * and host MUST derive identically so a dapp sees
18
18
  * the same address on every device.
19
- * - `identityChatPrivateKey`— user identity chat P-256 private scalar (32 bytes),
19
+ * - `identityChatPrivateKey`— user identity chat X25519 private scalar (32 bytes),
20
20
  * shared per the multi-device spec so this device
21
21
  * can decrypt traffic addressed to the user identity
22
- * - `deviceEncPubKey` — encryption public key of the PApp device (65 bytes,
23
- * P-256 uncompressed). Tells the host which key to
22
+ * - `deviceEncPubKey` — encryption public key of the PApp device (32 bytes,
23
+ * X25519). Tells the host which key to
24
24
  * use when addressing chat envelopes back to the
25
25
  * authorising PApp device.
26
- * - `ssoEncPubKey` — `papp_encr_pub` (65 bytes, P-256 uncompressed); see
26
+ * - `ssoEncPubKey` — `papp_encr_pub` (32 bytes, X25519); see
27
27
  * the `HandshakeSuccessV2` codec doc below.
28
28
  * - `rootEntropySource` — 32 bytes; `blake2b256_keyed(rootAccountSecret,
29
29
  * "product-entropy-derivation")` per RFC-0007 (layer 1).
@@ -31,10 +31,11 @@
31
31
  * (`host_derive_entropy`) without ever holding the raw
32
32
  * root account secret. See the codec doc below.
33
33
  */
34
- import { p256 } from '@noble/curves/nist.js';
35
- import { Bytes, Enum, Struct, Tuple, Vector, _void, str } from 'scale-ts';
34
+ import { x25519 } from '@noble/curves/ed25519.js';
35
+ import { Bytes } from '@novasamatech/scale';
36
+ import { Enum, Struct, Tuple, Vector, _void, str } from 'scale-ts';
36
37
  const AccountIdCodec = Bytes(32);
37
- const PublicKeyCodec = Bytes(65);
38
+ const PublicKeyCodec = Bytes(32);
38
39
  const PrivateKeyCodec = Bytes(32);
39
40
  const EntropySourceCodec = Bytes(32);
40
41
  export const MetadataKey = Enum({
@@ -63,14 +64,14 @@ export const HandshakeSuccessV2 = Struct({
63
64
  identityAccountId: AccountIdCodec,
64
65
  rootAccountId: AccountIdCodec,
65
66
  identityChatPrivateKey: PrivateKeyCodec,
66
- /** PApp's P-256 SSO ECDH public key (`papp_encr_pub`). */
67
+ /** PApp's X25519 SSO ECDH public key (`papp_encr_pub`). */
67
68
  ssoEncPubKey: PublicKeyCodec,
68
69
  deviceEncPubKey: PublicKeyCodec,
69
70
  /** Layer-1 source for deterministic product entropy derivation (RFC-0007). */
70
71
  rootEntropySource: EntropySourceCodec,
71
72
  });
72
- /** Derive the identity chat P-256 public key (uncompressed) from its private scalar. */
73
- export const deriveIdentityChatPublicKey = (privateKey) => p256.getPublicKey(privateKey, false);
73
+ /** Derive the identity chat X25519 public key (32 bytes) from its private scalar. */
74
+ export const deriveIdentityChatPublicKey = (privateKey) => x25519.getPublicKey(privateKey);
74
75
  export const HandshakeStatusV2 = Enum({
75
76
  AllowanceAllocation: _void,
76
77
  });
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * Decrypt the outer envelope of a `HandshakeResponseV2` statement payload.
3
3
  *
4
- * The answering side generates a one-shot P-256 keypair, performs ECDH against
5
- * the host device's encryption public key, and AES-GCM encrypts the sensitive
6
- * payload (the SCALE-encoded `EncryptedHandshakeResponseV2`) with a key
7
- * derived from the shared secret.
4
+ * The answering side generates a one-shot X25519 keypair, performs ECDH against
5
+ * the host device's encryption public key, and ChaCha20-Poly1305 encrypts the
6
+ * sensitive payload (the SCALE-encoded `EncryptedHandshakeResponseV2`) with a
7
+ * key derived from the shared secret.
8
8
  *
9
- * The shared-secret-to-AES-key derivation (HKDF-SHA256 over the ECDH X
10
- * coordinate) is delegated to `createEncryption(sharedSecret)` from
9
+ * The shared-secret-to-AEAD-key derivation (HKDF-SHA256 over the X25519 shared
10
+ * secret) is delegated to `createEncryption(sharedSecret)` from
11
11
  * `@novasamatech/statement-store` — byte-compatible with the existing V1
12
12
  * chat-request encryption helper, so we don't fork primitives here.
13
13
  */
@@ -1,21 +1,21 @@
1
1
  /**
2
2
  * Decrypt the outer envelope of a `HandshakeResponseV2` statement payload.
3
3
  *
4
- * The answering side generates a one-shot P-256 keypair, performs ECDH against
5
- * the host device's encryption public key, and AES-GCM encrypts the sensitive
6
- * payload (the SCALE-encoded `EncryptedHandshakeResponseV2`) with a key
7
- * derived from the shared secret.
4
+ * The answering side generates a one-shot X25519 keypair, performs ECDH against
5
+ * the host device's encryption public key, and ChaCha20-Poly1305 encrypts the
6
+ * sensitive payload (the SCALE-encoded `EncryptedHandshakeResponseV2`) with a
7
+ * key derived from the shared secret.
8
8
  *
9
- * The shared-secret-to-AES-key derivation (HKDF-SHA256 over the ECDH X
10
- * coordinate) is delegated to `createEncryption(sharedSecret)` from
9
+ * The shared-secret-to-AEAD-key derivation (HKDF-SHA256 over the X25519 shared
10
+ * secret) is delegated to `createEncryption(sharedSecret)` from
11
11
  * `@novasamatech/statement-store` — byte-compatible with the existing V1
12
12
  * chat-request encryption helper, so we don't fork primitives here.
13
13
  */
14
- import { p256 } from '@noble/curves/nist.js';
14
+ import { x25519 } from '@noble/curves/ed25519.js';
15
15
  import { createEncryption } from '@novasamatech/statement-store';
16
- const ecdhX = (privateKey, peerPublicKey) => p256.getSharedSecret(privateKey, peerPublicKey).slice(1, 33);
16
+ const ecdh = (privateKey, peerPublicKey) => x25519.getSharedSecret(privateKey, peerPublicKey);
17
17
  export const decryptResponseEnvelope = (deviceEncryptionPrivateKey, envelope) => {
18
- const shared = ecdhX(deviceEncryptionPrivateKey, envelope.tmpKey);
18
+ const shared = ecdh(deviceEncryptionPrivateKey, envelope.tmpKey);
19
19
  const result = createEncryption(shared).decrypt(envelope.encrypted);
20
20
  if (result.isErr())
21
21
  throw result.error;
@@ -29,8 +29,8 @@ export type HandshakePendingState = {
29
29
  export type HandshakeSuccessState = CodecType<typeof HandshakeSuccessV2> & {
30
30
  tag: 'Success';
31
31
  /**
32
- * Derived locally from `identityChatPrivateKey` via P-256 scalar
33
- * multiplication (uncompressed 65-byte form). Both sides MUST derive
32
+ * Derived locally from `identityChatPrivateKey` via X25519 scalar
33
+ * multiplication (32-byte form). Both sides MUST derive
34
34
  * identically; downstream session topics depend on it.
35
35
  */
36
36
  identityChatPublicKey: Uint8Array;
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * Where:
8
8
  * - `statementAccountId` = the host's sr25519 device public key (32 bytes)
9
- * - `encryptionPublicKey` = the host's P-256 device public key (65 bytes uncompressed)
9
+ * - `encryptionPublicKey` = the host's X25519 device public key (32 bytes)
10
10
  *
11
11
  * Both sides compute the same topic/channel deterministically from the same
12
12
  * pubkeys carried in the QR-coded `VersionedHandshakeProposal::V2`, so they
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * Where:
8
8
  * - `statementAccountId` = the host's sr25519 device public key (32 bytes)
9
- * - `encryptionPublicKey` = the host's P-256 device public key (65 bytes uncompressed)
9
+ * - `encryptionPublicKey` = the host's X25519 device public key (32 bytes)
10
10
  *
11
11
  * Both sides compute the same topic/channel deterministically from the same
12
12
  * pubkeys carried in the QR-coded `VersionedHandshakeProposal::V2`, so they
@@ -1,9 +1,10 @@
1
1
  import { gcm } from '@noble/ciphers/aes.js';
2
2
  import { blake2b } from '@noble/hashes/blake2.js';
3
+ import { Bytes } from '@novasamatech/scale';
3
4
  import { createSr25519Secret, deriveSr25519PublicKey } from '@novasamatech/statement-store';
4
5
  import { errAsync, fromPromise, okAsync } from 'neverthrow';
5
6
  import { fromHex, toHex } from 'polkadot-api/utils';
6
- import { Bytes, Option, Struct, str } from 'scale-ts';
7
+ import { Option, Struct, str } from 'scale-ts';
7
8
  import { getEncrPub, stringToBytes } from '../crypto.js';
8
9
  import { toError } from '../helpers/utils.js';
9
10
  const KEY = 'DeviceIdentity';
@@ -4,8 +4,14 @@ export declare const CreateTransactionRequestCodec: import("scale-ts").Codec<{
4
4
  payload: {
5
5
  tag: "v1";
6
6
  value: {
7
- signer: [string, number];
8
- genesisHash: Uint8Array<ArrayBufferLike>;
7
+ signer: [string, {
8
+ tag: "Index";
9
+ value: number;
10
+ } | {
11
+ tag: "Raw";
12
+ value: Uint8Array<ArrayBufferLike>;
13
+ }];
14
+ genesisHash: `0x${string}`;
9
15
  callData: Uint8Array<ArrayBufferLike>;
10
16
  extensions: {
11
17
  id: string;
@@ -22,7 +28,7 @@ export declare const CreateTransactionLegacyRequestCodec: import("scale-ts").Cod
22
28
  tag: "v1";
23
29
  value: {
24
30
  signer: Uint8Array<ArrayBufferLike>;
25
- genesisHash: Uint8Array<ArrayBufferLike>;
31
+ genesisHash: `0x${string}`;
26
32
  callData: Uint8Array<ArrayBufferLike>;
27
33
  extensions: {
28
34
  id: string;
@@ -1,6 +1,6 @@
1
1
  import { LegacyTransaction, ProductAccountTransaction } from '@novasamatech/host-api';
2
- import { Enum } from '@novasamatech/scale';
3
- import { Bytes, Result, Struct, str } from 'scale-ts';
2
+ import { Bytes, Enum } from '@novasamatech/scale';
3
+ import { Result, Struct, str } from 'scale-ts';
4
4
  export const CreateTransactionRequestCodec = Struct({
5
5
  payload: Enum({
6
6
  v1: ProductAccountTransaction,
@@ -1,5 +1,6 @@
1
1
  import { DotNsIdentifier } from '@novasamatech/host-api';
2
- import { Bytes, Result, Struct, str } from 'scale-ts';
2
+ import { Bytes } from '@novasamatech/scale';
3
+ import { Result, Struct, str } from 'scale-ts';
3
4
  // RFC-0022 made `//product//{productId}` a hard junction, so the root public
4
5
  // key alone no longer determines product account public keys. This request
5
6
  // closes the gap: the Account Holder returns the product-subtree public key,
@@ -10,9 +10,33 @@ export declare const RemoteMessageCodec: import("scale-ts").Codec<{
10
10
  } | {
11
11
  tag: "SignRequest";
12
12
  value: {
13
+ tag: "Raw";
14
+ value: {
15
+ productAccountId: [string, {
16
+ tag: "Index";
17
+ value: number;
18
+ } | {
19
+ tag: "Raw";
20
+ value: Uint8Array<ArrayBufferLike>;
21
+ }];
22
+ data: {
23
+ tag: "Bytes";
24
+ value: Uint8Array<ArrayBufferLike>;
25
+ } | {
26
+ tag: "Payload";
27
+ value: string;
28
+ };
29
+ };
30
+ } | {
13
31
  tag: "Payload";
14
32
  value: {
15
- productAccountId: [string, number];
33
+ productAccountId: [string, {
34
+ tag: "Index";
35
+ value: number;
36
+ } | {
37
+ tag: "Raw";
38
+ value: Uint8Array<ArrayBufferLike>;
39
+ }];
16
40
  blockHash: `0x${string}`;
17
41
  blockNumber: `0x${string}`;
18
42
  era: `0x${string}`;
@@ -29,18 +53,6 @@ export declare const RemoteMessageCodec: import("scale-ts").Codec<{
29
53
  mode: number | undefined;
30
54
  withSignedTransaction: boolean | void;
31
55
  };
32
- } | {
33
- tag: "Raw";
34
- value: {
35
- productAccountId: [string, number];
36
- data: {
37
- tag: "Bytes";
38
- value: Uint8Array<ArrayBufferLike>;
39
- } | {
40
- tag: "Payload";
41
- value: string;
42
- };
43
- };
44
56
  };
45
57
  } | {
46
58
  tag: "SignResponse";
@@ -55,7 +67,13 @@ export declare const RemoteMessageCodec: import("scale-ts").Codec<{
55
67
  tag: "RingVrfAliasRequest";
56
68
  value: {
57
69
  callingProductId: string;
58
- context: [string, `0x${string}`];
70
+ context: [string, {
71
+ tag: "Index";
72
+ value: number;
73
+ } | {
74
+ tag: "Raw";
75
+ value: Uint8Array<ArrayBufferLike>;
76
+ }];
59
77
  ring: {
60
78
  chainId: `0x${string}`;
61
79
  junctions: ({
@@ -87,7 +105,13 @@ export declare const RemoteMessageCodec: import("scale-ts").Codec<{
87
105
  value: undefined;
88
106
  } | {
89
107
  tag: "SmartContractAllowance";
90
- value: number;
108
+ value: {
109
+ tag: "Index";
110
+ value: number;
111
+ } | {
112
+ tag: "Raw";
113
+ value: Uint8Array<ArrayBufferLike>;
114
+ };
91
115
  } | {
92
116
  tag: "AutoSigning";
93
117
  value: undefined;
@@ -117,7 +141,6 @@ export declare const RemoteMessageCodec: import("scale-ts").Codec<{
117
141
  } | {
118
142
  tag: "AutoSigning";
119
143
  value: {
120
- productDerivationSecret: string;
121
144
  productRootPrivateKey: Uint8Array<ArrayBufferLike>;
122
145
  };
123
146
  } | {
@@ -137,8 +160,14 @@ export declare const RemoteMessageCodec: import("scale-ts").Codec<{
137
160
  payload: {
138
161
  tag: "v1";
139
162
  value: {
140
- signer: [string, number];
141
- genesisHash: Uint8Array<ArrayBufferLike>;
163
+ signer: [string, {
164
+ tag: "Index";
165
+ value: number;
166
+ } | {
167
+ tag: "Raw";
168
+ value: Uint8Array<ArrayBufferLike>;
169
+ }];
170
+ genesisHash: `0x${string}`;
142
171
  callData: Uint8Array<ArrayBufferLike>;
143
172
  extensions: {
144
173
  id: string;
@@ -162,7 +191,7 @@ export declare const RemoteMessageCodec: import("scale-ts").Codec<{
162
191
  tag: "v1";
163
192
  value: {
164
193
  signer: Uint8Array<ArrayBufferLike>;
165
- genesisHash: Uint8Array<ArrayBufferLike>;
194
+ genesisHash: `0x${string}`;
166
195
  callData: Uint8Array<ArrayBufferLike>;
167
196
  extensions: {
168
197
  id: string;
@@ -195,7 +224,13 @@ export declare const RemoteMessageCodec: import("scale-ts").Codec<{
195
224
  tag: "RingVrfProofRequest";
196
225
  value: {
197
226
  callingProductId: string;
198
- context: [string, `0x${string}`];
227
+ context: [string, {
228
+ tag: "Index";
229
+ value: number;
230
+ } | {
231
+ tag: "Raw";
232
+ value: Uint8Array<ArrayBufferLike>;
233
+ }];
199
234
  ring: {
200
235
  chainId: `0x${string}`;
201
236
  junctions: ({
@@ -227,7 +262,13 @@ export declare const RemoteMessageCodec: import("scale-ts").Codec<{
227
262
  } | {
228
263
  tag: "SignVrfRequest";
229
264
  value: {
230
- productAccountId: [string, number];
265
+ productAccountId: [string, {
266
+ tag: "Index";
267
+ value: number;
268
+ } | {
269
+ tag: "Raw";
270
+ value: Uint8Array<ArrayBufferLike>;
271
+ }];
231
272
  productId: string;
232
273
  transcriptLabel: Uint8Array<ArrayBufferLike>;
233
274
  items: {
@@ -246,6 +287,19 @@ export declare const RemoteMessageCodec: import("scale-ts").Codec<{
246
287
  reason: string;
247
288
  }, "SignVrfErr::Unknown">>;
248
289
  };
290
+ } | {
291
+ tag: "ProductSubtreeRequest";
292
+ value: {
293
+ productId: string;
294
+ };
295
+ } | {
296
+ tag: "ProductSubtreeResponse";
297
+ value: {
298
+ respondingTo: string;
299
+ payload: import("scale-ts").ResultPayload<{
300
+ productPublicKey: Uint8Array<ArrayBufferLike>;
301
+ }, string>;
302
+ };
249
303
  };
250
304
  };
251
305
  }>;
@@ -1,6 +1,7 @@
1
1
  import { Enum } from '@novasamatech/scale';
2
2
  import { Struct, _void, str } from 'scale-ts';
3
3
  import { CreateTransactionLegacyRequestCodec, CreateTransactionRequestCodec, CreateTransactionResponseCodec, } from './createTransaction.js';
4
+ import { ProductSubtreeRequestCodec, ProductSubtreeResponseCodec } from './productSubtree.js';
4
5
  import { ResourceAllocationRequestCodec, ResourceAllocationResponseCodec } from './resourceAllocation.js';
5
6
  import { RingVrfAliasRequestCodec, RingVrfAliasResponseCodec, RingVrfProofRequestCodec, RingVrfProofResponseCodec, } from './ringVrf.js';
6
7
  import { SignVrfRequestCodec, SignVrfResponseCodec } from './signVrf.js';
@@ -27,6 +28,9 @@ export const RemoteMessageCodec = Struct({
27
28
  RingVrfProofResponse: RingVrfProofResponseCodec,
28
29
  SignVrfRequest: SignVrfRequestCodec,
29
30
  SignVrfResponse: SignVrfResponseCodec,
31
+ // RFC-0022 additions — appended so existing variant indexes stay stable.
32
+ ProductSubtreeRequest: ProductSubtreeRequestCodec,
33
+ ProductSubtreeResponse: ProductSubtreeResponseCodec,
30
34
  }),
31
35
  }),
32
36
  });
@@ -5,7 +5,13 @@ export declare const ApAllocatableResourceCodec: import("scale-ts").Codec<{
5
5
  value: undefined;
6
6
  } | {
7
7
  tag: "SmartContractAllowance";
8
- value: number;
8
+ value: {
9
+ tag: "Index";
10
+ value: number;
11
+ } | {
12
+ tag: "Raw";
13
+ value: Uint8Array<ArrayBufferLike>;
14
+ };
9
15
  } | {
10
16
  tag: "AutoSigning";
11
17
  value: undefined;
@@ -25,7 +31,6 @@ export declare const ApAllocatedResourceCodec: import("scale-ts").Codec<{
25
31
  } | {
26
32
  tag: "AutoSigning";
27
33
  value: {
28
- productDerivationSecret: string;
29
34
  productRootPrivateKey: Uint8Array<ArrayBufferLike>;
30
35
  };
31
36
  } | {
@@ -51,7 +56,6 @@ export declare const ApAllocationOutcomeCodec: import("scale-ts").Codec<{
51
56
  } | {
52
57
  tag: "AutoSigning";
53
58
  value: {
54
- productDerivationSecret: string;
55
59
  productRootPrivateKey: Uint8Array<ArrayBufferLike>;
56
60
  };
57
61
  } | {
@@ -73,7 +77,13 @@ export declare const ResourceAllocationRequestCodec: import("scale-ts").Codec<{
73
77
  value: undefined;
74
78
  } | {
75
79
  tag: "SmartContractAllowance";
76
- value: number;
80
+ value: {
81
+ tag: "Index";
82
+ value: number;
83
+ } | {
84
+ tag: "Raw";
85
+ value: Uint8Array<ArrayBufferLike>;
86
+ };
77
87
  } | {
78
88
  tag: "AutoSigning";
79
89
  value: undefined;
@@ -102,7 +112,6 @@ export declare const ResourceAllocationResponseCodec: import("scale-ts").Codec<{
102
112
  } | {
103
113
  tag: "AutoSigning";
104
114
  value: {
105
- productDerivationSecret: string;
106
115
  productRootPrivateKey: Uint8Array<ArrayBufferLike>;
107
116
  };
108
117
  } | {
@@ -1,9 +1,10 @@
1
1
  import { DerivationIndex, DotNsIdentifier } from '@novasamatech/host-api';
2
- import { Status } from '@novasamatech/scale';
3
- import { Bytes, Enum, Result, Struct, Vector, _void, str } from 'scale-ts';
2
+ import { Bytes, Status } from '@novasamatech/scale';
3
+ import { Enum, Result, Struct, Vector, _void, str } from 'scale-ts';
4
4
  export const ApAllocatableResourceCodec = Enum({
5
5
  StatementStoreAllowance: _void,
6
6
  BulletInAllowance: _void,
7
+ // Account the allowance is granted for, addressed by the RFC-0022 selector.
7
8
  SmartContractAllowance: DerivationIndex,
8
9
  AutoSigning: _void,
9
10
  });
@@ -15,8 +16,12 @@ export const ApAllocatedResourceCodec = Enum({
15
16
  slotAccountKey: Bytes(),
16
17
  }),
17
18
  SmartContractAllowance: _void,
19
+ // RFC-0022: the payload collapsed to the product-subtree secret key alone.
20
+ // `//product//{productId}` is a hard junction, so this key exposes exactly
21
+ // that product's subtree — the secret path component is gone.
18
22
  AutoSigning: Struct({
19
- productDerivationSecret: str,
23
+ // `Sr25519PrivateKey ++ Sr25519Nonce` (64 bytes): the full expanded secret
24
+ // needed to sign and soft-derive `/{index}` below the product root.
20
25
  productRootPrivateKey: Bytes(),
21
26
  }),
22
27
  });
@@ -2,7 +2,13 @@ import type { CodecType } from 'scale-ts';
2
2
  export type RingVrfAliasRequest = CodecType<typeof RingVrfAliasRequestCodec>;
3
3
  export declare const RingVrfAliasRequestCodec: import("scale-ts").Codec<{
4
4
  callingProductId: string;
5
- context: [string, `0x${string}`];
5
+ context: [string, {
6
+ tag: "Index";
7
+ value: number;
8
+ } | {
9
+ tag: "Raw";
10
+ value: Uint8Array<ArrayBufferLike>;
11
+ }];
6
12
  ring: {
7
13
  chainId: `0x${string}`;
8
14
  junctions: ({
@@ -27,7 +33,13 @@ export declare const RingVrfAliasResponseCodec: import("scale-ts").Codec<{
27
33
  export type RingVrfProofRequest = CodecType<typeof RingVrfProofRequestCodec>;
28
34
  export declare const RingVrfProofRequestCodec: import("scale-ts").Codec<{
29
35
  callingProductId: string;
30
- context: [string, `0x${string}`];
36
+ context: [string, {
37
+ tag: "Index";
38
+ value: number;
39
+ } | {
40
+ tag: "Raw";
41
+ value: Uint8Array<ArrayBufferLike>;
42
+ }];
31
43
  ring: {
32
44
  chainId: `0x${string}`;
33
45
  junctions: ({
@@ -1,5 +1,6 @@
1
1
  import { ContextualAlias, CreateProofErr, DotNsIdentifier, GetAliasErr, ProductProofContext, RingLocation, RingVrfProof, } from '@novasamatech/host-api';
2
- import { Bytes, Result, Struct, str } from 'scale-ts';
2
+ import { Bytes } from '@novasamatech/scale';
3
+ import { Result, Struct, str } from 'scale-ts';
3
4
  export const RingVrfAliasRequestCodec = Struct({
4
5
  callingProductId: DotNsIdentifier,
5
6
  context: ProductProofContext,
@@ -13,7 +13,13 @@ import type { CodecType } from 'scale-ts';
13
13
  */
14
14
  export type SignVrfRequest = CodecType<typeof SignVrfRequestCodec>;
15
15
  export declare const SignVrfRequestCodec: import("scale-ts").Codec<{
16
- productAccountId: [string, number];
16
+ productAccountId: [string, {
17
+ tag: "Index";
18
+ value: number;
19
+ } | {
20
+ tag: "Raw";
21
+ value: Uint8Array<ArrayBufferLike>;
22
+ }];
17
23
  productId: string;
18
24
  transcriptLabel: Uint8Array<ArrayBufferLike>;
19
25
  items: {
@@ -1,6 +1,6 @@
1
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';
2
+ import { Bytes, ErrEnum } from '@novasamatech/scale';
3
+ import { Result, Struct, Vector, _void, str } from 'scale-ts';
4
4
  export const SignVrfRequestCodec = Struct({
5
5
  productAccountId: ProductAccountId,
6
6
  productId: str,
@@ -1,7 +1,13 @@
1
1
  import type { CodecType } from 'scale-ts';
2
2
  export type SigningPayloadRequest = CodecType<typeof SigningPayloadRequestCodec>;
3
3
  export declare const SigningPayloadRequestCodec: import("scale-ts").Codec<{
4
- productAccountId: [string, number];
4
+ productAccountId: [string, {
5
+ tag: "Index";
6
+ value: number;
7
+ } | {
8
+ tag: "Raw";
9
+ value: Uint8Array<ArrayBufferLike>;
10
+ }];
5
11
  blockHash: `0x${string}`;
6
12
  blockNumber: `0x${string}`;
7
13
  era: `0x${string}`;
@@ -20,7 +26,13 @@ export declare const SigningPayloadRequestCodec: import("scale-ts").Codec<{
20
26
  }>;
21
27
  export type SigningRawRequest = CodecType<typeof SigningRawRequestCodec>;
22
28
  export declare const SigningRawRequestCodec: import("scale-ts").Codec<{
23
- productAccountId: [string, number];
29
+ productAccountId: [string, {
30
+ tag: "Index";
31
+ value: number;
32
+ } | {
33
+ tag: "Raw";
34
+ value: Uint8Array<ArrayBufferLike>;
35
+ }];
24
36
  data: {
25
37
  tag: "Bytes";
26
38
  value: Uint8Array<ArrayBufferLike>;
@@ -47,9 +59,33 @@ export declare const SignRawLegacyResponseCodec: import("scale-ts").Codec<{
47
59
  }>;
48
60
  export type SigningRequest = CodecType<typeof SigningRequestCodec>;
49
61
  export declare const SigningRequestCodec: import("scale-ts").Codec<{
62
+ tag: "Raw";
63
+ value: {
64
+ productAccountId: [string, {
65
+ tag: "Index";
66
+ value: number;
67
+ } | {
68
+ tag: "Raw";
69
+ value: Uint8Array<ArrayBufferLike>;
70
+ }];
71
+ data: {
72
+ tag: "Bytes";
73
+ value: Uint8Array<ArrayBufferLike>;
74
+ } | {
75
+ tag: "Payload";
76
+ value: string;
77
+ };
78
+ };
79
+ } | {
50
80
  tag: "Payload";
51
81
  value: {
52
- productAccountId: [string, number];
82
+ productAccountId: [string, {
83
+ tag: "Index";
84
+ value: number;
85
+ } | {
86
+ tag: "Raw";
87
+ value: Uint8Array<ArrayBufferLike>;
88
+ }];
53
89
  blockHash: `0x${string}`;
54
90
  blockNumber: `0x${string}`;
55
91
  era: `0x${string}`;
@@ -66,18 +102,6 @@ export declare const SigningRequestCodec: import("scale-ts").Codec<{
66
102
  mode: number | undefined;
67
103
  withSignedTransaction: boolean | void;
68
104
  };
69
- } | {
70
- tag: "Raw";
71
- value: {
72
- productAccountId: [string, number];
73
- data: {
74
- tag: "Bytes";
75
- value: Uint8Array<ArrayBufferLike>;
76
- } | {
77
- tag: "Payload";
78
- value: string;
79
- };
80
- };
81
105
  }>;
82
106
  export type SigningPayloadResponseData = CodecType<typeof SigningPayloadResponseDataCodec>;
83
107
  export declare const SigningPayloadResponseDataCodec: import("scale-ts").Codec<{
@@ -1,6 +1,6 @@
1
1
  import { AccountId, ProductAccountId } from '@novasamatech/host-api';
2
- import { Enum, Hex, OptionBool } from '@novasamatech/scale';
3
- import { Bytes, Option, Result, Struct, Vector, str, u32 } from 'scale-ts';
2
+ import { Bytes, Enum, Hex, OptionBool } from '@novasamatech/scale';
3
+ import { Option, Result, Struct, Vector, str, u32 } from 'scale-ts';
4
4
  export const SigningPayloadRequestCodec = Struct({
5
5
  productAccountId: ProductAccountId,
6
6
  blockHash: Hex(),
@@ -26,6 +26,13 @@ export type UserSession = StoredUserSession & {
26
26
  createTransactionLegacy(payload: CreateTransactionLegacyRequest): ResultAsync<Uint8Array, Error>;
27
27
  getRingVrfAlias(callingProductId: string, context: CodecType<typeof ProductProofContext>, ring: CodecType<typeof RingLocation>): ResultAsync<CodecType<typeof ContextualAlias>, Error>;
28
28
  createRingVrfProof(callingProductId: string, context: CodecType<typeof ProductProofContext>, ring: CodecType<typeof RingLocation>, message: Uint8Array): ResultAsync<CodecType<typeof RingVrfProof>, Error>;
29
+ /**
30
+ * Fetches the sr25519 public key of `//product//{productId}` (RFC-0022).
31
+ *
32
+ * Consent-free and cacheable: one round trip per product, ever. Account
33
+ * public keys are then soft-derived locally from the returned subtree key.
34
+ */
35
+ getProductSubtree(productId: string): ResultAsync<Uint8Array, Error>;
29
36
  signVrf(payload: SignVrfRequest): ResultAsync<CodecType<typeof VrfSignature>, Error>;
30
37
  requestResourceAllocation(request: ResourceAllocationRequest): ResultAsync<ApAllocationOutcome[], Error>;
31
38
  subscribe(callback: Callback<CodecType<typeof RemoteMessageCodec>, ResultAsync<boolean, Error>>): VoidFunction;
@@ -287,6 +287,24 @@ export function createUserSession({ userSession, statementStore, encryption, sto
287
287
  return withHostActionTrace(awaitReplyOrAckFailure(request, reply).andThen(result => result.success ? ok(result.value) : err(result.value)), messageId, userSession.id);
288
288
  });
289
289
  },
290
+ getProductSubtree(productId) {
291
+ return enqueue(() => {
292
+ const messageId = nanoid();
293
+ const data = enumValue('v1', enumValue('ProductSubtreeRequest', { productId }));
294
+ emitHostAction(messageId, actionKindFromMessageData(data), userSession.id);
295
+ const responseFilter = (message) => {
296
+ if (message.data.tag === 'v1' &&
297
+ message.data.value.tag === 'ProductSubtreeResponse' &&
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.productPublicKey) : err(new Error(result.value)));
305
+ return withHostActionTrace(withQueueTimeout(inner, 'getProductSubtree'), messageId, userSession.id);
306
+ });
307
+ },
290
308
  signVrf(payload) {
291
309
  return enqueue(() => {
292
310
  const messageId = nanoid();
@@ -1,8 +1,9 @@
1
1
  import { gcm } from '@noble/ciphers/aes.js';
2
2
  import { blake2b } from '@noble/hashes/blake2.js';
3
+ import { Bytes } from '@novasamatech/scale';
3
4
  import { fromThrowable } from 'neverthrow';
4
5
  import { fromHex, toHex } from 'polkadot-api/utils';
5
- import { Bytes, Struct } from 'scale-ts';
6
+ import { Struct } from 'scale-ts';
6
7
  import { BrandedBytesCodec, stringToBytes } from '../crypto.js';
7
8
  import { toError } from '../helpers/utils.js';
8
9
  const StoredUserSecretsCodec = Struct({
@@ -1,30 +1,31 @@
1
+ import { Bytes } from '@novasamatech/scale';
1
2
  import { AccountIdCodec, LocalSessionAccountCodec, RemoteSessionAccountCodec } from '@novasamatech/statement-store';
2
3
  import { fieldListView } from '@novasamatech/storage-adapter';
3
4
  import { nanoid } from 'nanoid';
4
5
  import { fromHex, toHex } from 'polkadot-api/utils';
5
- import { Bytes, Struct, Vector, str } from 'scale-ts';
6
+ import { Struct, Vector, str } from 'scale-ts';
6
7
  const storedUserSessionCodec = Struct({
7
8
  id: str,
8
9
  localAccount: LocalSessionAccountCodec,
9
10
  remoteAccount: RemoteSessionAccountCodec,
10
11
  rootAccountId: AccountIdCodec,
11
12
  identityAccountId: AccountIdCodec,
12
- identityChatPublicKey: Bytes(65),
13
- // `papp_encr_pub` (65-byte uncompressed P-256). Persisted so the host can
13
+ identityChatPublicKey: Bytes(32),
14
+ // `papp_encr_pub` (32-byte X25519). Persisted so the host can
14
15
  // rebuild its SSO session transport (`shared_secret_session =
15
16
  // ECDH(host_encr_secret, ssoEncPubKey)`) on a cold start without re-running
16
17
  // the handshake.
17
- ssoEncPubKey: Bytes(65),
18
+ ssoEncPubKey: Bytes(32),
18
19
  // RFC-0007 layer-1 `rootEntropySource` from the handshake; consumed by the
19
20
  // host's `host_derive_entropy` handler via `deriveProductEntropyFromSource`.
20
21
  rootEntropySource: Bytes(32),
21
- // Encryption public key of the authorising PApp device (65-byte uncompressed
22
- // P-256), lifted from `HandshakeResponseV2.deviceEncPubKey`. Distinct from
22
+ // Encryption public key of the authorising PApp device (32-byte X25519),
23
+ // lifted from `HandshakeResponseV2.deviceEncPubKey`. Distinct from
23
24
  // `ssoEncPubKey` (the SSO session keypair) and from `remoteAccount.publicKey`
24
25
  // (the derived SSO shared secret): this is the peer device's long-lived ECDH
25
26
  // key, used by the host's device-sync channel to address the paired device.
26
27
  // Always present — `HandshakeResponseV2` carries it for every V2 pairing.
27
- deviceEncPubKey: Bytes(65),
28
+ deviceEncPubKey: Bytes(32),
28
29
  });
29
30
  export function createStoredUserSession(localAccount, remoteAccount, rootAccountId, extras) {
30
31
  return {
@@ -43,7 +44,11 @@ export const createUserSessionRepository = (storage) => {
43
44
  const codec = Vector(storedUserSessionCodec);
44
45
  return fieldListView({
45
46
  storage,
46
- key: 'SsoSessionsV3',
47
+ // V4: the encryption keys shrank from 65-byte P-256 to 32-byte X25519
48
+ // (CHAT-RFC-0004). Fixed-size `Bytes` does not length-check on decode, so a
49
+ // V3 blob would decode misaligned into a session that looks valid but
50
+ // carries garbage keys — bump the key so old blobs are dropped instead.
51
+ key: 'SsoSessionsV4',
47
52
  from: x => codec.dec(fromHex(x)),
48
53
  to: x => toHex(codec.enc(x)),
49
54
  });
@@ -5,14 +5,14 @@ import { describe, expect, it } from 'vitest';
5
5
  import { createStoredUserSession, createUserSessionRepository } from './userSessionRepository.js';
6
6
  const bytes = (len, fill) => new Uint8Array(len).fill(fill);
7
7
  // remoteAccount.publicKey is a fixed 32-byte field (the SSO shared secret),
8
- // which is exactly why the peer device's 65-byte encryption key needs its own
8
+ // which is exactly why the peer device's 32-byte encryption key needs its own
9
9
  // `deviceEncPubKey` field rather than being squeezed in here.
10
10
  const SHARED_SECRET = bytes(32, 0x07);
11
- const DEVICE_ENC_PUB = bytes(65, 0x04);
11
+ const DEVICE_ENC_PUB = bytes(32, 0x04);
12
12
  const baseExtras = {
13
13
  identityAccountId: createAccountId(bytes(32, 0x11)),
14
- identityChatPublicKey: bytes(65, 0x22),
15
- ssoEncPubKey: bytes(65, 0x33),
14
+ identityChatPublicKey: bytes(32, 0x22),
15
+ ssoEncPubKey: bytes(32, 0x33),
16
16
  rootEntropySource: bytes(32, 0x44),
17
17
  };
18
18
  const makeSession = (deviceEncPubKey = DEVICE_ENC_PUB) => createStoredUserSession(createLocalSessionAccount(createAccountId(bytes(32, 0x01))), createRemoteSessionAccount(createAccountId(bytes(32, 0x02)), SHARED_SECRET), createAccountId(bytes(32, 0x55)), { ...baseExtras, deviceEncPubKey });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@novasamatech/host-papp",
3
3
  "type": "module",
4
- "version": "0.8.12",
4
+ "version": "0.9.1",
5
5
  "description": "Polkadot app integration",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
@@ -34,10 +34,10 @@
34
34
  "@noble/ciphers": "2.2.0",
35
35
  "@noble/curves": "2.2.0",
36
36
  "@noble/hashes": "2.2.0",
37
- "@novasamatech/host-api": "0.8.12",
38
- "@novasamatech/scale": "0.8.12",
39
- "@novasamatech/statement-store": "0.8.12",
40
- "@novasamatech/storage-adapter": "0.8.12",
37
+ "@novasamatech/host-api": "0.9.1",
38
+ "@novasamatech/scale": "0.9.1",
39
+ "@novasamatech/statement-store": "0.9.1",
40
+ "@novasamatech/storage-adapter": "0.9.1",
41
41
  "@polkadot-api/utils": "^0.4.0",
42
42
  "@polkadot-labs/hdkd-helpers": "^0.0.31",
43
43
  "nanoevents": "10.0.0",