@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.
- package/README.md +26 -1
- package/dist/crypto.js +8 -11
- package/dist/crypto.spec.d.ts +1 -0
- package/dist/crypto.spec.js +31 -0
- package/dist/index.d.ts +1 -0
- package/dist/sso/allowance/repository.js +2 -1
- package/dist/sso/auth/scale/handshakeV2.d.ts +5 -5
- package/dist/sso/auth/scale/handshakeV2.js +11 -10
- package/dist/sso/auth/v2/envelope.d.ts +6 -6
- package/dist/sso/auth/v2/envelope.js +9 -9
- package/dist/sso/auth/v2/state.d.ts +2 -2
- package/dist/sso/auth/v2/topic.d.ts +1 -1
- package/dist/sso/auth/v2/topic.js +1 -1
- package/dist/sso/deviceIdentityStore.js +2 -1
- package/dist/sso/sessionManager/scale/createTransaction.d.ts +9 -3
- package/dist/sso/sessionManager/scale/createTransaction.js +2 -2
- package/dist/sso/sessionManager/scale/productSubtree.js +2 -1
- package/dist/sso/sessionManager/scale/remoteMessage.d.ts +75 -21
- package/dist/sso/sessionManager/scale/remoteMessage.js +4 -0
- package/dist/sso/sessionManager/scale/resourceAllocation.d.ts +14 -5
- package/dist/sso/sessionManager/scale/resourceAllocation.js +8 -3
- package/dist/sso/sessionManager/scale/ringVrf.d.ts +14 -2
- package/dist/sso/sessionManager/scale/ringVrf.js +2 -1
- package/dist/sso/sessionManager/scale/signVrf.d.ts +7 -1
- package/dist/sso/sessionManager/scale/signVrf.js +2 -2
- package/dist/sso/sessionManager/scale/signing.d.ts +39 -15
- package/dist/sso/sessionManager/scale/signing.js +2 -2
- package/dist/sso/sessionManager/userSession.d.ts +7 -0
- package/dist/sso/sessionManager/userSession.js +18 -0
- package/dist/sso/userSecretRepository.js +2 -1
- package/dist/sso/userSessionRepository.js +13 -8
- package/dist/sso/userSessionRepository.spec.js +4 -4
- 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
|
-
|
|
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 {
|
|
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(
|
|
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
|
-
|
|
31
|
-
|
|
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
|
|
34
|
+
return x25519.getPublicKey(secret);
|
|
38
35
|
}
|
|
39
36
|
export function createSharedSecret(secret, publicKey) {
|
|
40
|
-
//
|
|
41
|
-
return
|
|
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 {
|
|
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
|
|
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 (
|
|
23
|
-
*
|
|
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` (
|
|
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
|
|
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
|
|
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 (
|
|
23
|
-
*
|
|
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` (
|
|
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 {
|
|
35
|
-
import { Bytes
|
|
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(
|
|
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
|
|
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
|
|
73
|
-
export const deriveIdentityChatPublicKey = (privateKey) =>
|
|
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
|
|
5
|
-
* the host device's encryption public key, and
|
|
6
|
-
* payload (the SCALE-encoded `EncryptedHandshakeResponseV2`) with a
|
|
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-
|
|
10
|
-
*
|
|
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
|
|
5
|
-
* the host device's encryption public key, and
|
|
6
|
-
* payload (the SCALE-encoded `EncryptedHandshakeResponseV2`) with a
|
|
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-
|
|
10
|
-
*
|
|
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 {
|
|
14
|
+
import { x25519 } from '@noble/curves/ed25519.js';
|
|
15
15
|
import { createEncryption } from '@novasamatech/statement-store';
|
|
16
|
-
const
|
|
16
|
+
const ecdh = (privateKey, peerPublicKey) => x25519.getSharedSecret(privateKey, peerPublicKey);
|
|
17
17
|
export const decryptResponseEnvelope = (deviceEncryptionPrivateKey, envelope) => {
|
|
18
|
-
const shared =
|
|
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
|
|
33
|
-
* multiplication (
|
|
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
|
|
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
|
|
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 {
|
|
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,
|
|
8
|
-
|
|
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:
|
|
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 {
|
|
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
|
|
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,
|
|
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,
|
|
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:
|
|
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,
|
|
141
|
-
|
|
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:
|
|
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,
|
|
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,
|
|
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:
|
|
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:
|
|
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 {
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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 {
|
|
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,
|
|
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,
|
|
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,
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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(
|
|
13
|
-
// `papp_encr_pub` (
|
|
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(
|
|
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 (
|
|
22
|
-
//
|
|
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(
|
|
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
|
-
|
|
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
|
|
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(
|
|
11
|
+
const DEVICE_ENC_PUB = bytes(32, 0x04);
|
|
12
12
|
const baseExtras = {
|
|
13
13
|
identityAccountId: createAccountId(bytes(32, 0x11)),
|
|
14
|
-
identityChatPublicKey: bytes(
|
|
15
|
-
ssoEncPubKey: bytes(
|
|
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.
|
|
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.
|
|
38
|
-
"@novasamatech/scale": "0.
|
|
39
|
-
"@novasamatech/statement-store": "0.
|
|
40
|
-
"@novasamatech/storage-adapter": "0.
|
|
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",
|