@tinycloud/share-sdk 1.4.4 → 1.5.0-beta.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 +9 -12
- package/dist/{addressed-publish-DorqWQJ8.d.ts → addressed-publish-D4IWNXuw.d.ts} +9 -29
- package/dist/{addressed-publish-BPFFqD5s.d.cts → addressed-publish-qp_bEVYu.d.cts} +9 -29
- package/dist/artifact.cjs +1 -0
- package/dist/artifact.cjs.map +1 -1
- package/dist/artifact.js +1 -0
- package/dist/artifact.js.map +1 -1
- package/dist/index.cjs +3 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -27
- package/dist/index.js.map +1 -1
- package/dist/share-index.cjs +1 -0
- package/dist/share-index.cjs.map +1 -1
- package/dist/share-index.js +1 -0
- package/dist/share-index.js.map +1 -1
- package/dist/share-root.cjs +2 -1
- package/dist/share-root.cjs.map +1 -1
- package/dist/share-root.d.cts +1 -1
- package/dist/share-root.d.ts +1 -1
- package/dist/share-root.js +2 -1
- package/dist/share-root.js.map +1 -1
- package/dist/web-recipient.cjs +1 -0
- package/dist/web-recipient.cjs.map +1 -1
- package/dist/web-recipient.js +1 -0
- package/dist/web-recipient.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -23,25 +23,22 @@ import {
|
|
|
23
23
|
// 1. Plan, before anything is written. The network must be the owner's default network with the EIP-55 checksummed address.
|
|
24
24
|
const plan = planPathBoundShare({ shareId, encryptionNetwork: defaultEncryptionNetworkForOwner(ownerDid) });
|
|
25
25
|
|
|
26
|
-
// 2. Hold
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
// network (deleted keys count, and the count is shared with `apps/` keys): each shared file uses one slot, so validate everything first
|
|
33
|
-
// (`planPathBoundShare` refuses a bad share id or network before any key exists). A failure after the key exists still costs the slot and there
|
|
34
|
-
// is no key delete: retry with a NEW shareId.
|
|
26
|
+
// 2. Hold decrypt under `<network>/share/` (`plan.registrationPermissions`, or `pathBoundSessionPermissions({ encryptionNetwork })` in the
|
|
27
|
+
// sign-in manifest, before any share exists): the node refuses to register the share unless the owner
|
|
28
|
+
// holds it; a bare-network decrypt never covers a share key. No capability is needed to encrypt: the share's key is deterministic and
|
|
29
|
+
// `keys.encrypt` wraps to its public key, which the node serves without authentication. The node records the key when the share policy
|
|
30
|
+
// is registered (step 5), in the same transaction. Each published share adds one key record, which counts toward the network's key limit
|
|
31
|
+
// (shared with `apps/` keys); `planPathBoundShare` refuses a bad share id or network before anything is encrypted.
|
|
35
32
|
// The share id is ONE grammar everywhere (plan, size bound, publisher): `[a-z0-9][a-z0-9_-]{15,63}` (16 to 64 characters; a lowercase `randomUUID()`,
|
|
36
33
|
// with or without its hyphens, is valid). Uppercase, `.`, a leading `_` or `-`, and anything longer than 64 characters are refused.
|
|
37
34
|
|
|
38
|
-
// 3.
|
|
35
|
+
// 3. Encrypt under the share's key (`keys` is a named-key client: `tcw.keys`, `node.keys` or `NamedKeyClient`).
|
|
39
36
|
const encrypted = await encryptAddressedShareContent({ plan, keys, plaintext, metadata: { contentType } });
|
|
40
37
|
|
|
41
38
|
// 4. Store EXACTLY encrypted.storedBytes (the canonical v2 envelope) at the share's path.
|
|
42
39
|
await put(path, encrypted.storedBytes);
|
|
43
40
|
|
|
44
|
-
// 5. Bind and publish.
|
|
41
|
+
// 5. Bind and publish. Registering the policy records the share's key.
|
|
45
42
|
const contentSource = bindPathBoundContentSource({
|
|
46
43
|
plan, envelope: encrypted.envelope, storedBytes: encrypted.storedBytes, kvResource: `${spaceId}/kv/${path}`, selector: "exact",
|
|
47
44
|
});
|
|
@@ -50,4 +47,4 @@ await publishAddressedShare({ /* shareId, target, resource, actions, ... */ cont
|
|
|
50
47
|
|
|
51
48
|
`publishAddressedShare` refuses a content source that is not bound to its own share's key (`AddressedContentSourceError`, match on `code`: `key-path-required`, `key-path-mismatch`, `digest-invalid`, `network-invalid`) before it signs, registers or writes anything; there is no network-wide fallback. Do not call `encryptToNetwork` with a `keyPath` for a share: it returns a v1 envelope wrapped to the network key, which neither the node nor the recipient opens. `assertAddressedShareFits` and `addressedShareBodyUpperBound` derive the key path from `shareId`. Opening an older share that has no key path is unchanged.
|
|
52
49
|
|
|
53
|
-
|
|
50
|
+
Hand-written content sources are not supported: the publisher checks the path, the digest's format and the network, but only `bindPathBoundContentSource` derives the raw-byte digest the recipient and the node compare.
|
|
@@ -9,11 +9,11 @@ import { S as ShareAuthorizationRequired, a as ShareAuthorizationMethod } from '
|
|
|
9
9
|
* opens only that one key, never the network. This module is the single place that derives the path, validates the
|
|
10
10
|
* network, serializes the stored envelope and computes the digests, so no consumer hand-writes any of them.
|
|
11
11
|
*
|
|
12
|
-
* Call sequence: `planPathBoundShare` (before anything is written) ->
|
|
13
|
-
*
|
|
14
|
-
* `
|
|
12
|
+
* Call sequence: `planPathBoundShare` (before anything is written) -> `encryptAddressedShareContent` (or the caller's own
|
|
13
|
+
* `keys.encrypt`, which encrypts to the key's public key) -> upload `storedBytes` -> `bindPathBoundContentSource` ->
|
|
14
|
+
* `publishAddressedShare`. The node records the share's key when the policy is registered; nothing creates it beforehand.
|
|
15
15
|
*/
|
|
16
|
-
type AddressedContentSourceErrorCode = "share-id-invalid" | "network-invalid" | "network-not-checksummed" | "key-path-required" | "key-path-mismatch" | "digest-invalid" | "
|
|
16
|
+
type AddressedContentSourceErrorCode = "share-id-invalid" | "network-invalid" | "network-not-checksummed" | "key-path-required" | "key-path-mismatch" | "digest-invalid" | "envelope-invalid" | "stored-bytes-mismatch";
|
|
17
17
|
/** A path-bound source could not be planned, built or accepted. Match on `code`, not the message or `instanceof`. */
|
|
18
18
|
declare class AddressedContentSourceError extends TypeError {
|
|
19
19
|
readonly code: AddressedContentSourceErrorCode;
|
|
@@ -56,18 +56,7 @@ interface PathBoundSharePlan {
|
|
|
56
56
|
readonly keyPath: string;
|
|
57
57
|
/** `<network>/share/<shareId>`: the one decrypt resource the share grants. */
|
|
58
58
|
readonly keyResource: string;
|
|
59
|
-
/**
|
|
60
|
-
* ROUTE A, per share at runtime: `key.create` on exactly this share's key, requested with `requestPermissions` before the key is created
|
|
61
|
-
* (one extra consent per share). For apps that cannot use the sign-in route (for example apps whose sign-in goes through the OpenKey widget
|
|
62
|
-
* `authorizeTinyCloud` route, which refuses decrypt and key.create outside `apps/`).
|
|
63
|
-
*/
|
|
64
|
-
readonly requiredPermissions: PathBoundPermissionEntry[];
|
|
65
|
-
/**
|
|
66
|
-
* ROUTE B, once at sign-in (manifest entries): `key.create` and `decrypt` on the default network's `<network>/share/` prefix, so every share's
|
|
67
|
-
* key can be created without a further prompt. Put these in the `permissions` of the manifest the owner signs in with.
|
|
68
|
-
*/
|
|
69
|
-
readonly sessionPermissions: PathBoundPermissionEntry[];
|
|
70
|
-
/** The owner session must hold these for the node to register the policy: decrypt under `<network>/share/` (also part of `sessionPermissions`). */
|
|
59
|
+
/** The owner session must hold these for the node to register the policy: decrypt under `<network>/share/`. */
|
|
71
60
|
readonly registrationPermissions: PathBoundPermissionEntry[];
|
|
72
61
|
/** Options for `keys.encrypt(plan.keyPath, plaintext, plan.encryptOptions({ metadata }))`. */
|
|
73
62
|
encryptOptions(extra?: {
|
|
@@ -78,8 +67,8 @@ interface PathBoundSharePlan {
|
|
|
78
67
|
};
|
|
79
68
|
}
|
|
80
69
|
/**
|
|
81
|
-
* The manifest entries
|
|
82
|
-
*
|
|
70
|
+
* The manifest entries that let one session register every share's policy: `decrypt` on the default network's `<network>/share/` prefix.
|
|
71
|
+
* Needs no share id, so an app builds its sign-in manifest before any share exists. Put the result in `manifest.permissions`.
|
|
83
72
|
*/
|
|
84
73
|
declare function pathBoundSessionPermissions(input: {
|
|
85
74
|
readonly encryptionNetwork: string;
|
|
@@ -114,14 +103,6 @@ declare function bindPathBoundContentSource(input: {
|
|
|
114
103
|
}): PathBoundContentSource;
|
|
115
104
|
/** The part of a named-key client (`NamedKeyClient`, web `tcw.keys`, node `node.keys`) this module calls. */
|
|
116
105
|
interface AddressedShareKeyClient {
|
|
117
|
-
createKey(path: string, options: {
|
|
118
|
-
networkId: string;
|
|
119
|
-
}): Promise<{
|
|
120
|
-
readonly networkId: string;
|
|
121
|
-
readonly keyPath: string;
|
|
122
|
-
readonly state?: string;
|
|
123
|
-
readonly keyVersion?: number;
|
|
124
|
-
}>;
|
|
125
106
|
encrypt(path: string, plaintext: Uint8Array, options: {
|
|
126
107
|
networkId: string;
|
|
127
108
|
metadata?: Record<string, string>;
|
|
@@ -134,9 +115,8 @@ interface EncryptedAddressedContent {
|
|
|
134
115
|
readonly byteLength: number;
|
|
135
116
|
}
|
|
136
117
|
/**
|
|
137
|
-
*
|
|
138
|
-
* (
|
|
139
|
-
* `plan.requiredPermissions` must already be held; an unauthorized answer is reported as `key-not-authorized` naming it.
|
|
118
|
+
* Encrypt the content under the share's named key. `keys.encrypt` wraps to the key's public key, which the node serves without
|
|
119
|
+
* authentication; the key needs no record yet (the node records it when the share policy is registered).
|
|
140
120
|
*/
|
|
141
121
|
declare function encryptAddressedShareContent(input: {
|
|
142
122
|
readonly plan: PathBoundSharePlan;
|
|
@@ -9,11 +9,11 @@ import { S as ShareAuthorizationRequired, a as ShareAuthorizationMethod } from '
|
|
|
9
9
|
* opens only that one key, never the network. This module is the single place that derives the path, validates the
|
|
10
10
|
* network, serializes the stored envelope and computes the digests, so no consumer hand-writes any of them.
|
|
11
11
|
*
|
|
12
|
-
* Call sequence: `planPathBoundShare` (before anything is written) ->
|
|
13
|
-
*
|
|
14
|
-
* `
|
|
12
|
+
* Call sequence: `planPathBoundShare` (before anything is written) -> `encryptAddressedShareContent` (or the caller's own
|
|
13
|
+
* `keys.encrypt`, which encrypts to the key's public key) -> upload `storedBytes` -> `bindPathBoundContentSource` ->
|
|
14
|
+
* `publishAddressedShare`. The node records the share's key when the policy is registered; nothing creates it beforehand.
|
|
15
15
|
*/
|
|
16
|
-
type AddressedContentSourceErrorCode = "share-id-invalid" | "network-invalid" | "network-not-checksummed" | "key-path-required" | "key-path-mismatch" | "digest-invalid" | "
|
|
16
|
+
type AddressedContentSourceErrorCode = "share-id-invalid" | "network-invalid" | "network-not-checksummed" | "key-path-required" | "key-path-mismatch" | "digest-invalid" | "envelope-invalid" | "stored-bytes-mismatch";
|
|
17
17
|
/** A path-bound source could not be planned, built or accepted. Match on `code`, not the message or `instanceof`. */
|
|
18
18
|
declare class AddressedContentSourceError extends TypeError {
|
|
19
19
|
readonly code: AddressedContentSourceErrorCode;
|
|
@@ -56,18 +56,7 @@ interface PathBoundSharePlan {
|
|
|
56
56
|
readonly keyPath: string;
|
|
57
57
|
/** `<network>/share/<shareId>`: the one decrypt resource the share grants. */
|
|
58
58
|
readonly keyResource: string;
|
|
59
|
-
/**
|
|
60
|
-
* ROUTE A, per share at runtime: `key.create` on exactly this share's key, requested with `requestPermissions` before the key is created
|
|
61
|
-
* (one extra consent per share). For apps that cannot use the sign-in route (for example apps whose sign-in goes through the OpenKey widget
|
|
62
|
-
* `authorizeTinyCloud` route, which refuses decrypt and key.create outside `apps/`).
|
|
63
|
-
*/
|
|
64
|
-
readonly requiredPermissions: PathBoundPermissionEntry[];
|
|
65
|
-
/**
|
|
66
|
-
* ROUTE B, once at sign-in (manifest entries): `key.create` and `decrypt` on the default network's `<network>/share/` prefix, so every share's
|
|
67
|
-
* key can be created without a further prompt. Put these in the `permissions` of the manifest the owner signs in with.
|
|
68
|
-
*/
|
|
69
|
-
readonly sessionPermissions: PathBoundPermissionEntry[];
|
|
70
|
-
/** The owner session must hold these for the node to register the policy: decrypt under `<network>/share/` (also part of `sessionPermissions`). */
|
|
59
|
+
/** The owner session must hold these for the node to register the policy: decrypt under `<network>/share/`. */
|
|
71
60
|
readonly registrationPermissions: PathBoundPermissionEntry[];
|
|
72
61
|
/** Options for `keys.encrypt(plan.keyPath, plaintext, plan.encryptOptions({ metadata }))`. */
|
|
73
62
|
encryptOptions(extra?: {
|
|
@@ -78,8 +67,8 @@ interface PathBoundSharePlan {
|
|
|
78
67
|
};
|
|
79
68
|
}
|
|
80
69
|
/**
|
|
81
|
-
* The manifest entries
|
|
82
|
-
*
|
|
70
|
+
* The manifest entries that let one session register every share's policy: `decrypt` on the default network's `<network>/share/` prefix.
|
|
71
|
+
* Needs no share id, so an app builds its sign-in manifest before any share exists. Put the result in `manifest.permissions`.
|
|
83
72
|
*/
|
|
84
73
|
declare function pathBoundSessionPermissions(input: {
|
|
85
74
|
readonly encryptionNetwork: string;
|
|
@@ -114,14 +103,6 @@ declare function bindPathBoundContentSource(input: {
|
|
|
114
103
|
}): PathBoundContentSource;
|
|
115
104
|
/** The part of a named-key client (`NamedKeyClient`, web `tcw.keys`, node `node.keys`) this module calls. */
|
|
116
105
|
interface AddressedShareKeyClient {
|
|
117
|
-
createKey(path: string, options: {
|
|
118
|
-
networkId: string;
|
|
119
|
-
}): Promise<{
|
|
120
|
-
readonly networkId: string;
|
|
121
|
-
readonly keyPath: string;
|
|
122
|
-
readonly state?: string;
|
|
123
|
-
readonly keyVersion?: number;
|
|
124
|
-
}>;
|
|
125
106
|
encrypt(path: string, plaintext: Uint8Array, options: {
|
|
126
107
|
networkId: string;
|
|
127
108
|
metadata?: Record<string, string>;
|
|
@@ -134,9 +115,8 @@ interface EncryptedAddressedContent {
|
|
|
134
115
|
readonly byteLength: number;
|
|
135
116
|
}
|
|
136
117
|
/**
|
|
137
|
-
*
|
|
138
|
-
* (
|
|
139
|
-
* `plan.requiredPermissions` must already be held; an unauthorized answer is reported as `key-not-authorized` naming it.
|
|
118
|
+
* Encrypt the content under the share's named key. `keys.encrypt` wraps to the key's public key, which the node serves without
|
|
119
|
+
* authentication; the key needs no record yet (the node records it when the share policy is registered).
|
|
140
120
|
*/
|
|
141
121
|
declare function encryptAddressedShareContent(input: {
|
|
142
122
|
readonly plan: PathBoundSharePlan;
|
package/dist/artifact.cjs
CHANGED
|
@@ -5484,6 +5484,7 @@ function serialize(value) {
|
|
|
5484
5484
|
function canonicalize(value) {
|
|
5485
5485
|
return serialize(value);
|
|
5486
5486
|
}
|
|
5487
|
+
var POLICY_ROOT_MAX_LIFETIME_SECONDS = 31 * 24 * 60 * 60;
|
|
5487
5488
|
|
|
5488
5489
|
// src/artifact.ts
|
|
5489
5490
|
var import_sha2562 = require("@noble/hashes/sha256");
|