@solarpunkltd/swarm-id 0.0.0-stage → 0.8.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 (111) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +89 -2
  3. package/dist/bus/account-bus.d.ts +104 -0
  4. package/dist/bus/account-delta.d.ts +52 -0
  5. package/dist/bus/bus-context.d.ts +7 -0
  6. package/dist/bus/envelope.d.ts +9 -0
  7. package/dist/bus/messages.d.ts +241 -0
  8. package/dist/bus/presence.d.ts +62 -0
  9. package/dist/bus/signaling-transport.d.ts +79 -0
  10. package/dist/chunk/bmt.d.ts +16 -0
  11. package/dist/chunk/cac.d.ts +17 -0
  12. package/dist/chunk/constants.d.ts +10 -0
  13. package/dist/chunk/encrypted-cac.d.ts +47 -0
  14. package/dist/chunk/encryption.d.ts +85 -0
  15. package/dist/chunk/index.d.ts +5 -0
  16. package/dist/chunks/chat-binding-DO5aGhPl.js +23 -0
  17. package/dist/chunks/chat-binding-DO5aGhPl.js.map +1 -0
  18. package/dist/errors.d.ts +35 -0
  19. package/dist/index.d.ts +27 -0
  20. package/dist/internal.d.ts +49 -0
  21. package/dist/internal.esm.js +2 -0
  22. package/dist/internal.esm.js.map +1 -0
  23. package/dist/proxy/act/act.d.ts +58 -0
  24. package/dist/proxy/act/crypto.d.ts +46 -0
  25. package/dist/proxy/act/grantee-list.d.ts +81 -0
  26. package/dist/proxy/act/history.d.ts +182 -0
  27. package/dist/proxy/act/index.d.ts +114 -0
  28. package/dist/proxy/app-stamp-limits.d.ts +62 -0
  29. package/dist/proxy/chunk-upload-stream.d.ts +37 -0
  30. package/dist/proxy/chunking.d.ts +15 -0
  31. package/dist/proxy/collection.d.ts +38 -0
  32. package/dist/proxy/download-data.d.ts +27 -0
  33. package/dist/proxy/errors.d.ts +8 -0
  34. package/dist/proxy/feed-manifest.d.ts +60 -0
  35. package/dist/proxy/feeds/epochs/async-finder.d.ts +70 -0
  36. package/dist/proxy/feeds/epochs/epoch.d.ts +79 -0
  37. package/dist/proxy/feeds/epochs/finder.d.ts +60 -0
  38. package/dist/proxy/feeds/epochs/index.d.ts +30 -0
  39. package/dist/proxy/feeds/epochs/types.d.ts +110 -0
  40. package/dist/proxy/feeds/epochs/updater.d.ts +76 -0
  41. package/dist/proxy/feeds/epochs/utils.d.ts +21 -0
  42. package/dist/proxy/feeds/index.d.ts +4 -0
  43. package/dist/proxy/feeds/sequence/async-finder.d.ts +14 -0
  44. package/dist/proxy/feeds/sequence/finder.d.ts +11 -0
  45. package/dist/proxy/feeds/sequence/index.d.ts +22 -0
  46. package/dist/proxy/feeds/sequence/types.d.ts +82 -0
  47. package/dist/proxy/feeds/sequence/updater.d.ts +20 -0
  48. package/dist/proxy/index.d.ts +5 -0
  49. package/dist/proxy/manifest-builder.d.ts +164 -0
  50. package/dist/proxy/mantaray.d.ts +76 -0
  51. package/dist/proxy/soc-restamp-slots.d.ts +48 -0
  52. package/dist/proxy/stamp-chunk.d.ts +45 -0
  53. package/dist/proxy/stamp-worker-handler.d.ts +32 -0
  54. package/dist/proxy/stamp-worker-pool.d.ts +52 -0
  55. package/dist/proxy/stamp-worker.d.ts +1 -0
  56. package/dist/proxy/types.d.ts +38 -0
  57. package/dist/proxy/upload.d.ts +207 -0
  58. package/dist/schemas.d.ts +671 -0
  59. package/dist/stamp-worker.iife.js +1 -0
  60. package/dist/storage/debounced-uploader.d.ts +61 -0
  61. package/dist/storage/utilization-store.d.ts +113 -0
  62. package/dist/swarm-id-client.d.ts +1143 -0
  63. package/dist/swarm-id-proxy.d.ts +1079 -0
  64. package/dist/swarm-id.esm.js +2 -0
  65. package/dist/swarm-id.esm.js.map +1 -0
  66. package/dist/sync/batch-write-coordinator.d.ts +348 -0
  67. package/dist/sync/device-roster.d.ts +59 -0
  68. package/dist/sync/device-state.d.ts +199 -0
  69. package/dist/sync/fold-account-from-swarm.d.ts +53 -0
  70. package/dist/sync/index.d.ts +11 -0
  71. package/dist/sync/merge-snapshot.d.ts +57 -0
  72. package/dist/sync/partition-intent.d.ts +218 -0
  73. package/dist/sync/partition-lease.d.ts +531 -0
  74. package/dist/sync/partition-lock.d.ts +218 -0
  75. package/dist/sync/partition-state.d.ts +287 -0
  76. package/dist/sync/session-check.d.ts +29 -0
  77. package/dist/sync/store-interfaces.d.ts +48 -0
  78. package/dist/sync/sync-account.d.ts +45 -0
  79. package/dist/sync/timing-constants.d.ts +32 -0
  80. package/dist/sync/types.d.ts +18 -0
  81. package/dist/types.d.ts +3042 -0
  82. package/dist/utils/account-state-snapshot.d.ts +43 -0
  83. package/dist/utils/active-devices.d.ts +45 -0
  84. package/dist/utils/avatar.d.ts +30 -0
  85. package/dist/utils/batch-utilization.d.ts +840 -0
  86. package/dist/utils/batch-write-lock.d.ts +12 -0
  87. package/dist/utils/bee-error.d.ts +11 -0
  88. package/dist/utils/chat-binding.d.ts +113 -0
  89. package/dist/utils/coalesced-task.d.ts +28 -0
  90. package/dist/utils/collection.d.ts +6 -0
  91. package/dist/utils/connection-info.d.ts +11 -0
  92. package/dist/utils/constants.d.ts +28 -0
  93. package/dist/utils/device-id.d.ts +109 -0
  94. package/dist/utils/hex.d.ts +31 -0
  95. package/dist/utils/json-rpc.d.ts +34 -0
  96. package/dist/utils/key-derivation.d.ts +124 -0
  97. package/dist/utils/lock-soc.d.ts +35 -0
  98. package/dist/utils/postage-contract.d.ts +143 -0
  99. package/dist/utils/postage-stamp-association.d.ts +39 -0
  100. package/dist/utils/promise.d.ts +37 -0
  101. package/dist/utils/sealed-values.d.ts +23 -0
  102. package/dist/utils/seed-encryption.d.ts +17 -0
  103. package/dist/utils/site-allow-list.d.ts +54 -0
  104. package/dist/utils/stamp-lifespan.d.ts +37 -0
  105. package/dist/utils/storage-managers.d.ts +62 -0
  106. package/dist/utils/storage-probe.d.ts +40 -0
  107. package/dist/utils/tag.d.ts +11 -0
  108. package/dist/utils/ttl.d.ts +120 -0
  109. package/dist/utils/url.d.ts +55 -0
  110. package/dist/utils/versioned-storage.d.ts +183 -0
  111. package/package.json +91 -3
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Run `op` under an exclusive, cross-tab Web Lock keyed on the postage batch.
3
+ *
4
+ * All writers to a given batch — every same-origin context: dApp proxy iframes
5
+ * and the SwarmID UI — request the same lock name, so only one write runs at a
6
+ * time across tabs. This is the single definition of the `swarm-write-<batchId>`
7
+ * convention (the proxy and the account-sync path both use it).
8
+ *
9
+ * Falls back to running `op` directly when the Web Locks API is unavailable
10
+ * (Node / test environments).
11
+ */
12
+ export declare function withBatchWriteLock<T>(batchIdHex: string | undefined, op: () => Promise<T>): Promise<T>;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * True only for a definitive "chunk absent" — an HTTP 404, as Bee's `/soc`
3
+ * endpoint returns for a missing single-owner chunk (`BeeResponseError.status`).
4
+ * A timeout / 5xx / network error is INCONCLUSIVE, not a confirmed miss.
5
+ *
6
+ * NOTE: Bee's `/chunks` endpoint returns **500** ("read chunk failed") for an
7
+ * absent chunk, so this predicate is usable for the roster `/soc` existence read
8
+ * but NOT for the `/chunks`-based feed finders — there, absence and a transient
9
+ * server error are indistinguishable by status.
10
+ */
11
+ export declare function isNotFoundError(error: unknown): boolean;
@@ -0,0 +1,113 @@
1
+ /** The first line, which also versions the format. */
2
+ export declare const CHAT_BINDING_TITLE = "Swarm ID chat binding v1";
3
+ export interface SiteOriginOptions {
4
+ /**
5
+ * Also accept `http://localhost`, `http://127.0.0.1` and `http://[::1]`
6
+ * with any port. Only a development build passes this.
7
+ */
8
+ allowLocalHttp?: boolean;
9
+ }
10
+ /** A signed binding as it travels: the message and its signature. */
11
+ export interface ChatBinding {
12
+ message: string;
13
+ /** `0x` + r ‖ s ‖ v, 65 bytes, v 27 or 28. */
14
+ signature: string;
15
+ }
16
+ /** The three fields a binding message carries, as the message spells them. */
17
+ export interface ChatBindingFields {
18
+ site: string;
19
+ /** `0x` + 40 lower-case hex characters. */
20
+ chatAddress: string;
21
+ /** ISO 8601 UTC, as `Date.prototype.toISOString` writes it. */
22
+ date: string;
23
+ }
24
+ export interface VerifiedChatBinding extends ChatBindingFields {
25
+ /** The recovered signer, `0x` + 40 lower-case hex characters. */
26
+ signer: string;
27
+ }
28
+ /** What a binding has to name for a reader to accept it. */
29
+ export interface ExpectedChatBinding {
30
+ site: string;
31
+ chatAddress: string;
32
+ signer: string;
33
+ }
34
+ /**
35
+ * Whether `value` is a bare https origin in the exact form the browser
36
+ * reports one: a host and nothing else, no path, query, fragment or user
37
+ * info, lower case, punycode, no default port. Anything a URL parser would
38
+ * have to normalize is refused rather than normalized, so one site has one
39
+ * spelling.
40
+ */
41
+ export declare function isCleanSiteOrigin(value: string, options?: SiteOriginOptions): boolean;
42
+ /** Build the exact four-line message for these fields. */
43
+ export declare function buildChatBindingMessage(fields: {
44
+ site: string;
45
+ chatAddress: string;
46
+ date: Date;
47
+ }, options?: SiteOriginOptions): string;
48
+ /**
49
+ * Read the fields out of a binding message, refusing every shape but the one
50
+ * `buildChatBindingMessage` writes: another first line, a missing or extra
51
+ * line, a trailing newline, a site that is not a clean origin, an address in
52
+ * another spelling, a date in another form.
53
+ */
54
+ export declare function parseChatBindingMessage(message: string, options?: SiteOriginOptions): ChatBindingFields;
55
+ /**
56
+ * The chat address a site's binding names: the address of the site's chat key
57
+ * (`deriveChatKey`), bare lower-case hex. The one place that says which key a
58
+ * binding is about, for the window that signs one and the frame that checks a
59
+ * handed-over one.
60
+ *
61
+ * @param appSecret - the site's app secret (bare hex)
62
+ */
63
+ export declare function siteChatAddress(appSecret: string): string;
64
+ /**
65
+ * Sign the binding for `site` with the account's own key. The chat address is
66
+ * the address of the site's chat key, derived from that same key here, so no
67
+ * caller can bind an address the account does not hold for that site.
68
+ *
69
+ * @param accountKey - the account's private key (bare hex), whose address is
70
+ * the account's `id`
71
+ */
72
+ export declare function createChatBinding(accountKey: string, site: string, date: Date, options?: SiteOriginOptions): ChatBinding;
73
+ /**
74
+ * Check a binding's shape and recover who signed it. Throws on a message of
75
+ * any other shape and on a signature that is not 65 bytes with v 27 or 28 and
76
+ * a low s. A valid signature over a changed message recovers SOME address,
77
+ * which is why a reader compares `signer` with the account it expects
78
+ * (`acceptedChatBinding` does).
79
+ */
80
+ export declare function verifyChatBinding(binding: ChatBinding, options?: SiteOriginOptions): VerifiedChatBinding;
81
+ /**
82
+ * The binding, if it verifies and names exactly this site, this chat address
83
+ * and this signer, otherwise undefined. Addresses compare in any case and
84
+ * with or without `0x`.
85
+ */
86
+ export declare function acceptedChatBinding(binding: ChatBinding | undefined, expected: ExpectedChatBinding, options?: SiteOriginOptions): ChatBinding | undefined;
87
+ /**
88
+ * The connected apps with every chat binding removed except `site`'s own.
89
+ *
90
+ * Each site's binding publicly links the account to its chat on that site, and
91
+ * a proxy frame runs inside one site's page, where that page's scripts can
92
+ * reach what the frame holds. So everything handed to a frame keeps only the
93
+ * binding of the site it runs for. The account's own synced copy keeps all of
94
+ * them.
95
+ */
96
+ export declare function onlySiteChatBinding<App extends {
97
+ appUrl: string;
98
+ chatBinding?: ChatBinding;
99
+ }>(apps: readonly App[], site: string): App[];
100
+ /**
101
+ * Of two bindings for the connection to `site` in `account`'s state, the one
102
+ * `account` signed for `site` over one it did not, then the later date, the
103
+ * first on a tie, and whichever is there when only one is. The date is the
104
+ * signer's own claim, and any message with a valid signature recovers SOME
105
+ * signer, so a binding whose date was edited, or that another key signed, must
106
+ * never outrank the account's own by claiming a later one. A binding is never
107
+ * withdrawn, only replaced by a newer one (docs/Chat-Binding.md), so this is
108
+ * the whole of how two copies of a connection merge theirs.
109
+ *
110
+ * @param account - the account's address, its `id`, in any case and with or
111
+ * without `0x`
112
+ */
113
+ export declare function newerChatBinding(first: ChatBinding | undefined, second: ChatBinding | undefined, site: string, account: string): ChatBinding | undefined;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Run `task` at most once per `cooldownMs` across all same-origin browser tabs.
3
+ *
4
+ * An exclusive Web Lock (`navigator.locks`, keyed on `lockName`) serialises
5
+ * concurrent callers so two tabs can't run `task` at the same instant; a shared
6
+ * `localStorage` timestamp (`cooldownKey`) then lets the second caller skip when
7
+ * another tab already ran within the window. `force` bypasses the cooldown (e.g.
8
+ * a page load / manual refresh) but still honours the optional short `graceMs`
9
+ * window, so a burst of forced runs (N tabs on load, or two back-to-back forced
10
+ * triggers) still collapses to one.
11
+ *
12
+ * Effect: with several tabs each scheduling the same task, only ONE actually runs
13
+ * per window — the rest observe the fresh timestamp and return. Whatever the task
14
+ * persists (e.g. the account document) then propagates to the other tabs via their
15
+ * own `storage` listeners, so they never need to fetch themselves.
16
+ *
17
+ * Falls back to running `task` directly when the Web Locks API is unavailable
18
+ * (Node / test), still honouring the cooldown when `localStorage` is present.
19
+ */
20
+ export declare function runCoalescedAcrossTabs(opts: {
21
+ lockName: string;
22
+ cooldownKey: string;
23
+ cooldownMs: number;
24
+ force?: boolean;
25
+ /** Short window honoured even under `force` — collapses back-to-back forced runs. */
26
+ graceMs?: number;
27
+ task: () => Promise<void>;
28
+ }): Promise<void>;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * A file's path inside the folder it was picked with. `webkitdirectory` and a
3
+ * dropped directory prefix every path with the folder's own name; that segment
4
+ * goes, so `index.html` sits at the manifest root — the rule bee-js applies.
5
+ */
6
+ export declare function collectionPath(file: Pick<File, "name" | "webkitRelativePath">): string;
@@ -0,0 +1,11 @@
1
+ import type { ConnectionInfo } from "../types";
2
+ /**
3
+ * Structural equality for {@link ConnectionInfo} snapshots, scoped to the
4
+ * fields the proxy emits to dApps. Used by the proxy to suppress redundant
5
+ * `connectionInfoChanged` events.
6
+ *
7
+ * `storagePartitioned` is compared via boolean coercion so a writer that
8
+ * sends `false` and one that omits the field (effectively `undefined`)
9
+ * are treated as equal — they mean the same thing to the dApp.
10
+ */
11
+ export declare function connectionInfoEqual(a: ConnectionInfo | undefined, b: ConnectionInfo): boolean;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Time and Session Constants
3
+ *
4
+ * Centralized constants for time units and default session durations.
5
+ */
6
+ export declare const SECOND = 1000;
7
+ export declare const MINUTE: number;
8
+ export declare const HOUR: number;
9
+ export declare const DAY: number;
10
+ /**
11
+ * A saved session lets a site keep writing until its next load notices a
12
+ * revoke, so it is kept short. An account can set its own.
13
+ */
14
+ export declare const DEFAULT_SESSION_DURATION: number;
15
+ /**
16
+ * How long a connection to a dApp lasts, in ms: the account's own setting, or
17
+ * the default above.
18
+ *
19
+ * One rule with two callers on opposite sides of the handover — the popup
20
+ * writes `connectedUntil` from it when it saves the connection
21
+ * (`ui/src/lib/connect-handshake.ts`), and a partitioned proxy recomputes the
22
+ * same deadline when it hydrates (`hydratePartitionAccount`). Two copies of
23
+ * `?? DEFAULT_SESSION_DURATION` is one copy that goes stale when the rule
24
+ * gains a clamp or a floor (#590).
25
+ */
26
+ export declare function appSessionDuration(settings?: {
27
+ appSessionDuration?: number;
28
+ }): number;
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Device ID Utilities
3
+ *
4
+ * Manages a per-browser device identifier stored in localStorage and
5
+ * provides helpers for merging device lists across sync/restore.
6
+ */
7
+ import type { Device } from "../schemas";
8
+ /**
9
+ * Get the current device ID, creating one if it doesn't exist.
10
+ */
11
+ export declare function getOrCreateDeviceId(): string;
12
+ /**
13
+ * Merge a device list with the current device.
14
+ *
15
+ * Upserts the current device: creates the entry if absent, otherwise leaves it
16
+ * as it is apart from the name. A `deviceName` we are given REPLACES the stored
17
+ * one, so a caller that learns a better label — the partition naming of #570 —
18
+ * corrects the row already in the registry instead of only the next one
19
+ * created. A caller with nothing to say passes nothing and the stored label
20
+ * stands: the name must not be blanked by a context that does not know it.
21
+ *
22
+ * A replacement carries `nameUpdatedAt`, the name's own merge clock, without
23
+ * which the correction never left this device: `mergeDevicesList` ranks records
24
+ * on sign-in recency, a rename moves none of it, and a tie favours the local
25
+ * copy — so every other context kept its stale label forever (#663). Only an
26
+ * ACTUAL change stamps it. Both callers are polls, and a clock that moved every
27
+ * time a caller passed the name it already stored would make every quiet round
28
+ * a byte change to persist, which is the churn #652 removed.
29
+ *
30
+ * Touches neither `removedAt` nor `lastSignedInAt` on an existing entry. Both
31
+ * callers are refresh paths — the proxy's throttled roster poll and the UI's
32
+ * periodic fold — and a poll is not a sign-in: clearing our own tombstone here
33
+ * undid every removal on the next round (#611), and stamping `lastSignedInAt`
34
+ * here (#652) made every fold a byte change to persist, which a proxy holding
35
+ * a partition answered with a bus delta and a stamped feed write. #337's "a
36
+ * genuine sign-in re-activates a device removed elsewhere" still holds; it
37
+ * lives at the sign-in seam instead (`reactivateThisDevice`, the only one there
38
+ * is), which is the only place that can tell a user unlocking their account
39
+ * from a timer firing.
40
+ * Liveness is not this field's job either — the bus carries it.
41
+ */
42
+ export declare function mergeDevices(existing: Device[], currentDeviceId: string, deviceName?: string): Device[];
43
+ /**
44
+ * Detect a human-readable name for the current device and browser.
45
+ * Returns a string like "Chrome on Linux Desktop" or "Safari on iPhone".
46
+ */
47
+ export declare function detectDeviceName(): string;
48
+ /**
49
+ * The device name a STORAGE-PARTITIONED proxy iframe announces itself under.
50
+ *
51
+ * A partitioned iframe keeps its own `swarm-id-device-id` in the partition, and
52
+ * a partition is one (top-level site, iframe origin) pair — so two tabs of one
53
+ * dApp are one device, while two different dApps on the same browser are two.
54
+ * `detectDeviceName()` alone therefore hands a Safari user with three connected
55
+ * dApps three indistinguishable "Safari on Mac" rows (#570). The dApp host is
56
+ * what tells them apart, and the port comes with it: locally the dApps differ
57
+ * by port alone.
58
+ *
59
+ * Only for the partitioned case. An UNPARTITIONED proxy reads the trusted
60
+ * domain's first-party store, so it shares `swarm-id-device-id` with the
61
+ * identity UI — it is the same device, and naming that after whichever dApp
62
+ * happened to load it would be wrong.
63
+ *
64
+ * The host is finer-grained than the partition key, which is schemeful site
65
+ * (eTLD+1) and may drop the port. Two same-site dApps — `app1.foo.com` and
66
+ * `app2.foo.com`, or two localhost ports — therefore share ONE partition and
67
+ * one device row, and each announces that row under its own host, so the
68
+ * stored name alternates between them on their polls.
69
+ *
70
+ * The label is per-origin and the thing it labels is per-site, and both are
71
+ * right for their own job. The name is per-origin because the origin is what a
72
+ * user recognises. The row is per-site because it stands for a WRITER, not for
73
+ * a security boundary: nothing reads the device registry to decide whether a
74
+ * write is allowed (`knownDeviceIds` only feeds the lease rival set), and two
75
+ * contexts in one partition cannot write at the same time anyway — they share
76
+ * `withBatchWriteLock`, a Web Lock scoped to the store they share. Giving them
77
+ * a row each would spend two of a finite `partitionCount` on writers that are
78
+ * already serialised. Where per-origin state really is per-origin, the storage
79
+ * key says so: the stored session (#671), and the stamper's.
80
+ *
81
+ * What the alternation costs: nothing on the refresh itself. A partitioned
82
+ * proxy keeps the merged registry in memory, and the refresh that produces it
83
+ * is a pre-acquire hook rather than a publish, so a flip writes nothing and
84
+ * sends nothing. It does not stay in memory, though — the next bus-delta fold
85
+ * persists it, since `refreshStoredPartitionAccount` serialises the whole
86
+ * in-memory account, merged devices included, into the session record. And a
87
+ * flipped row that travels for any other reason (a delta, a feed publish)
88
+ * carries a newer name clock, so it wins the name wherever it lands rather
89
+ * than tying and losing: the alternation reaches the account, not just the two
90
+ * iframes.
91
+ */
92
+ export declare function partitionDeviceName(appOrigin: string): string;
93
+ /**
94
+ * Whether a merged device registry is worth persisting over the stored one.
95
+ *
96
+ * The old test was "did the list get LONGER", which threw away every merge that
97
+ * only refreshed a peer's `lastSignedInAt` or set a `removedAt` tombstone — and
98
+ * those are exactly the fields `activeDeviceIds` prunes the rival set on. A peer
99
+ * the roster shows as live could therefore age out of the stored copy, drop out
100
+ * of the rival set, and never get an intent read: the dual-acquire the registry
101
+ * refresh exists to prevent (#586).
102
+ *
103
+ * Every field counts, our own included. `mergeDevices` no longer stamps our
104
+ * `lastSignedInAt` on a poll (#652), so when it moves it is a genuine sign-in
105
+ * (a reactivation) and worth a save. Until then this excluded it, because
106
+ * counting a stamp that moved on every call meant a save and a storage event in
107
+ * every other tab, every poll round.
108
+ */
109
+ export declare function deviceRegistryChanged(stored: Device[], merged: Device[], selfDeviceId: string): boolean;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Hex utilities
3
+ *
4
+ * Canonical location for hex conversion functions
5
+ */
6
+ import type { Address } from "../schemas";
7
+ /**
8
+ * Convert a hex string (optional 0x prefix) to Uint8Array
9
+ *
10
+ * @param hexString - Hex string (e.g., "deadbeef" or "0xdeadbeef")
11
+ * @returns Uint8Array
12
+ * @throws {Error} If the input has odd length or non-hex characters — a
13
+ * malformed value must fail loudly, not silently decode to zero bytes
14
+ */
15
+ export declare function hexToUint8Array(hexString: string): Uint8Array;
16
+ /**
17
+ * Convert a Uint8Array to hex string
18
+ *
19
+ * @param bytes - Uint8Array to convert
20
+ * @returns Hex string (e.g., "deadbeef")
21
+ */
22
+ export declare function uint8ArrayToHex(bytes: Uint8Array): string;
23
+ /**
24
+ * Create a validated hex address string (40 lowercase hex chars).
25
+ * Accepts optional 0x prefix and any case.
26
+ *
27
+ * @param input - Ethereum address string (with or without 0x prefix)
28
+ * @returns Normalized 40-character lowercase hex string
29
+ * @throws {Error} If the input is not a valid 40-char hex address
30
+ */
31
+ export declare function hexAddress(input: string): Address;
@@ -0,0 +1,34 @@
1
+ /** A workable deadline for a chain read over a public RPC. */
2
+ export declare const CHAIN_READ_TIMEOUT_MS = 10000;
3
+ export interface JsonRpcOptions {
4
+ /** Deadline for the whole request, including the response body. */
5
+ timeoutMs: number;
6
+ /**
7
+ * How the endpoint is named in error messages, e.g. "The configured Gnosis
8
+ * RPC". Defaults to the endpoint's origin.
9
+ */
10
+ label?: string;
11
+ }
12
+ /** One call in a batch. Ids are assigned by {@link jsonRpcBatch}. */
13
+ export interface JsonRpcRequest {
14
+ method: string;
15
+ params: unknown[];
16
+ }
17
+ /**
18
+ * One checked JSON-RPC call. `T` is a cast, not a validation — a caller that
19
+ * reads a field off the result should check that field's type itself.
20
+ *
21
+ * @throws if the transport fails, the endpoint answers non-2xx, or the response
22
+ * carries an `error` or no usable `result`; a {@link TimeoutError} once the
23
+ * deadline passes.
24
+ */
25
+ export declare function jsonRpcCall<T>(rpcUrl: string, method: string, params: unknown[], options: JsonRpcOptions): Promise<T>;
26
+ /**
27
+ * A batch of checked calls, returned in the same order as `requests`. Responses
28
+ * are matched back by id, since JSON-RPC does not promise batch ordering.
29
+ *
30
+ * @throws if the transport fails, the batch comes back the wrong shape or with
31
+ * ids that do not match the requests, or ANY member fails the contract —
32
+ * a partial batch is not an answer.
33
+ */
34
+ export declare function jsonRpcBatch(rpcUrl: string, requests: JsonRpcRequest[], options: JsonRpcOptions): Promise<unknown[]>;
@@ -0,0 +1,124 @@
1
+ import { hexToUint8Array, uint8ArrayToHex } from "./hex";
2
+ /** Master key → the stored derivation key */
3
+ export declare const DERIVATION_KEY_LABEL = "derivation-key";
4
+ /** Derivation key → the Swarm encryption key */
5
+ export declare const SWARM_ENCRYPTION_LABEL = "swarm-encryption";
6
+ /** Derivation key → the postage signer that stamps the account's uploads */
7
+ export declare const POSTAGE_SIGNER_LABEL = "postage-signer";
8
+ /** Swarm encryption key → the backup signer that owns every feed */
9
+ export declare const BACKUP_KEY_LABEL = "backup-key";
10
+ /** Derivation key → the account-wide ACT sharing key (#519) */
11
+ export declare const ACT_SHARING_LABEL = "act-sharing";
12
+ /**
13
+ * App secret → the site's chat key, the key a site's chat signs with and whose
14
+ * address the site's chat binding names (docs/Chat-Binding.md).
15
+ *
16
+ * The version in the label is how a leaked chat key is replaced: a new label
17
+ * gives every account a new chat key on every site, the identity UI then signs
18
+ * a fresh binding at the next connect because the kept one names the old
19
+ * address, and readers stop accepting the old key because no binding names it.
20
+ */
21
+ export declare const CHAT_KEY_LABEL = "swarm-id chat key v1";
22
+ /**
23
+ * Derive a secret from a parent key under a label, with Web Crypto.
24
+ *
25
+ * HMAC-SHA256: deterministic, so the same parent key and label always give
26
+ * the same secret. `deriveSecretSync` is the same function without the
27
+ * `await`, for a caller that cannot have one; the test pins the two equal.
28
+ *
29
+ * @param parentKey - The key to derive from (hex string)
30
+ * @param label - What the derived key is for: one of the labels above, or the
31
+ * app's origin (e.g. "https://swarm-app.local:8080") for a per-app secret
32
+ * @returns The derived secret as a hex string
33
+ */
34
+ export declare function deriveSecret(parentKey: string, label: string): Promise<string>;
35
+ /**
36
+ * `deriveSecret` without the `await`: the same HMAC-SHA256 computed with
37
+ * `@noble/hashes`, for a caller that cannot await — `buildConnectionInfo` in
38
+ * the proxy builds ConnectionInfo synchronously (#692). Byte-identical to the
39
+ * Web Crypto path, which the test pins.
40
+ */
41
+ export declare function deriveSecretSync(parentKey: string, label: string): string;
42
+ /**
43
+ * Derive an AES-GCM-256 key from `secretHex` under `label`.
44
+ *
45
+ * The three steps — HMAC to a fresh secret, hex to bytes, import as AES-GCM —
46
+ * are one pipeline. Non-extractable and encrypt/decrypt only, which is the
47
+ * part worth stating once (#590). The bus envelope (`deriveBusContext`) is the
48
+ * live caller; the `.swarmid` file key is the identity UI's, derived by HKDF
49
+ * from the recovery-phrase entropy, and never comes through here.
50
+ */
51
+ export declare function deriveAesGcmKey(secretHex: string, label: string): Promise<CryptoKey>;
52
+ /**
53
+ * Derive account derivation key from account master key
54
+ *
55
+ * A generic 32-byte root key stored on the account, used for deterministically
56
+ * deriving further keys (e.g. swarmEncryptionKey) without re-authentication.
57
+ *
58
+ * @param accountMasterKey - Account master key (hex string)
59
+ * @returns 32-byte derivation key (as hex string)
60
+ */
61
+ export declare function deriveAccountDerivationKey(accountMasterKey: string): Promise<string>;
62
+ /**
63
+ * Derive Swarm encryption key from account derivation key
64
+ *
65
+ * Used for encrypting account snapshot data before upload to Swarm.
66
+ * Derived from the stored derivationKey rather than the master key directly.
67
+ *
68
+ * @param derivationKey - Account derivation key (hex string)
69
+ * @returns 32-byte encryption key (as hex string)
70
+ */
71
+ export declare function deriveSwarmEncryptionKey(derivationKey: string): Promise<string>;
72
+ /**
73
+ * Derive the account-level postage batch signer key from the account derivation
74
+ * key. Used as the PrivateKey for signing chunks uploaded with a postage batch.
75
+ *
76
+ * @param derivationKey - Account derivation key (hex string)
77
+ * @returns 32-byte signer key (as hex string)
78
+ */
79
+ export declare function derivePostageSignerKey(derivationKey: string): Promise<string>;
80
+ /**
81
+ * `derivePostageSignerKey` without the `await`, for the proxy's ConnectionInfo
82
+ * (#815): the signer's address is what an app buys a batch for.
83
+ */
84
+ export declare function derivePostageSignerKeySync(derivationKey: string): string;
85
+ /**
86
+ * The account-wide ACT key (#519): what other people grant access to when they
87
+ * mean the person rather than one of their apps. Derived from the derivation
88
+ * key, so every app origin and every device of the account arrives at the same
89
+ * key — and the master key still never leaves the identity UI. Its public half
90
+ * is `identity.sharingPublicKey`. Synchronous because ConnectionInfo is built
91
+ * without awaiting.
92
+ */
93
+ export declare function deriveSharingKey(derivationKey: string): {
94
+ secret: Uint8Array;
95
+ publicKey: string;
96
+ };
97
+ /**
98
+ * The first output of `derive` that is a valid secp256k1 private key, a
99
+ * number from 1 to n - 1 where n is the curve order. It asks under `label`
100
+ * first, then under `label/1`, `label/2` and so on. An HMAC-SHA256 output
101
+ * misses that range with a chance of about 2^-128, so in practice the first
102
+ * answer is the key, but the rule is fixed so every device agrees even then.
103
+ *
104
+ * @throws if no candidate within the attempt bound is a key, which only a
105
+ * broken `derive` can cause
106
+ */
107
+ export declare function firstValidPrivateKey(derive: (label: string) => Uint8Array, label: string): Uint8Array;
108
+ /**
109
+ * The site's chat key, from the site's app secret
110
+ * (`deriveSecret(masterKey, origin)`): `HMAC(appSecret, CHAT_KEY_LABEL)`, the
111
+ * same derivation `deriveAppSecret` runs for an app's own label, held to a
112
+ * valid secp256k1 key by `firstValidPrivateKey`. It depends on the account and
113
+ * the site alone, so every device of the account derives the same key.
114
+ * Synchronous because ConnectionInfo and the binding are built without
115
+ * awaiting.
116
+ *
117
+ * @param appSecret - the site's app secret (bare hex)
118
+ * @returns the 32-byte chat private key
119
+ */
120
+ export declare function deriveChatKey(appSecret: string): Uint8Array;
121
+ export declare const utils: {
122
+ hexToUint8Array: typeof hexToUint8Array;
123
+ uint8ArrayToHex: typeof uint8ArrayToHex;
124
+ };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Address derivation for the per-partition lock SOC. Lives in `utils/` (not
3
+ * `sync/`) so it can be imported by both `sync/partition-lock.ts` (the lock
4
+ * SOC reader/writer) and `utils/batch-utilization.ts` (the stamper that
5
+ * routes lock-SOC overstamps to the reserved slot) without a circular
6
+ * dependency through `toBucket`.
7
+ */
8
+ import { Identifier, type BatchId, type EthAddress } from "@ethersphere/bee-js";
9
+ /**
10
+ * Build the lock-SOC identifier for one partition of one batch. Universal
11
+ * across accounts — the per-account `owner` carries that separation — so the
12
+ * identifier encodes what remains: which batch, and which of its lanes.
13
+ *
14
+ * The batch is in here because it is in everything the lock protects. A lane's
15
+ * slot space is computed WITHIN a batch (`dataSlot`), and the state riding it
16
+ * is published under `makePartitionStateTopic(batchId, partition)`. Keying the
17
+ * lock on the partition alone made lane p of batch X and lane p of batch Y one
18
+ * chunk address, so two coordinators that share no slot took turns anyway —
19
+ * and per-app stamp overrides put two live coordinators on two batches without
20
+ * any multi-batch feature (#589).
21
+ */
22
+ export declare function makePartitionLockIdentifier(batchId: BatchId, partition: number): Identifier;
23
+ /**
24
+ * 32-byte SOC chunk address for the (batch, partition, owner) lock SOC. Used
25
+ * by the stamper to detect "this stamp is the lock SOC's overstamp" and route
26
+ * it to the reserved slot.
27
+ */
28
+ export declare function lockSocAddress(batchId: BatchId, partition: number, owner: EthAddress): Uint8Array;
29
+ /**
30
+ * Bucket index that the partition-lock SOC for (batch, partition, owner) lands
31
+ * in. Lock-SOC writes overstamp this single fixed bucket; the partition stamper
32
+ * reserves the bucket from data uploads so the tight heartbeat cadence
33
+ * doesn't exhaust the bucket's slot budget.
34
+ */
35
+ export declare function lockSocBucket(batchId: BatchId, partition: number, owner: EthAddress): number;