@oxy.so/protocol 1.0.0
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/LICENSE +202 -0
- package/NOTICE +16 -0
- package/dist/cjs/.tsbuildinfo +1 -0
- package/dist/cjs/chain/continuity.js +54 -0
- package/dist/cjs/chain/engine.js +34 -0
- package/dist/cjs/chain/recordStore.js +25 -0
- package/dist/cjs/chain/types.js +22 -0
- package/dist/cjs/chain/verify.js +82 -0
- package/dist/cjs/envelope/canonicalJson.js +107 -0
- package/dist/cjs/envelope/recordId.js +60 -0
- package/dist/cjs/envelope/sign.js +75 -0
- package/dist/cjs/envelope/signingInput.js +32 -0
- package/dist/cjs/identity/resolver.js +50 -0
- package/dist/cjs/index.js +71 -0
- package/dist/cjs/node/constants.js +85 -0
- package/dist/cjs/node/didWebResolver.js +126 -0
- package/dist/cjs/node/httpFetch.js +61 -0
- package/dist/cjs/node/index.js +71 -0
- package/dist/cjs/node/nodeApp.js +344 -0
- package/dist/cjs/node/nodeClient.js +204 -0
- package/dist/cjs/node/rateLimit.js +187 -0
- package/dist/cjs/node/verifyRecord.js +51 -0
- package/dist/cjs/platform/crypto.js +186 -0
- package/dist/cjs/platform/crypto.native.js +204 -0
- package/dist/cjs/platform/expoTypes.js +24 -0
- package/dist/cjs/platform/platform.js +33 -0
- package/dist/cjs/secp256k1.js +148 -0
- package/dist/cjs/transparency/checkpoint.js +79 -0
- package/dist/cjs/transparency/tree.js +197 -0
- package/dist/esm/.tsbuildinfo +1 -0
- package/dist/esm/chain/continuity.js +51 -0
- package/dist/esm/chain/engine.js +31 -0
- package/dist/esm/chain/recordStore.js +24 -0
- package/dist/esm/chain/types.js +19 -0
- package/dist/esm/chain/verify.js +78 -0
- package/dist/esm/envelope/canonicalJson.js +104 -0
- package/dist/esm/envelope/recordId.js +56 -0
- package/dist/esm/envelope/sign.js +69 -0
- package/dist/esm/envelope/signingInput.js +29 -0
- package/dist/esm/identity/resolver.js +47 -0
- package/dist/esm/index.js +36 -0
- package/dist/esm/node/constants.js +82 -0
- package/dist/esm/node/didWebResolver.js +122 -0
- package/dist/esm/node/httpFetch.js +55 -0
- package/dist/esm/node/index.js +28 -0
- package/dist/esm/node/nodeApp.js +336 -0
- package/dist/esm/node/nodeClient.js +198 -0
- package/dist/esm/node/rateLimit.js +182 -0
- package/dist/esm/node/verifyRecord.js +48 -0
- package/dist/esm/platform/crypto.js +145 -0
- package/dist/esm/platform/crypto.native.js +196 -0
- package/dist/esm/platform/expoTypes.js +23 -0
- package/dist/esm/platform/platform.js +29 -0
- package/dist/esm/secp256k1.js +137 -0
- package/dist/esm/transparency/checkpoint.js +73 -0
- package/dist/esm/transparency/tree.js +189 -0
- package/dist/types/.tsbuildinfo +1 -0
- package/dist/types/chain/continuity.d.ts +28 -0
- package/dist/types/chain/engine.d.ts +27 -0
- package/dist/types/chain/recordStore.d.ts +85 -0
- package/dist/types/chain/types.d.ts +79 -0
- package/dist/types/chain/verify.d.ts +45 -0
- package/dist/types/envelope/canonicalJson.d.ts +44 -0
- package/dist/types/envelope/recordId.d.ts +30 -0
- package/dist/types/envelope/sign.d.ts +47 -0
- package/dist/types/envelope/signingInput.d.ts +33 -0
- package/dist/types/identity/resolver.d.ts +67 -0
- package/dist/types/index.d.ts +32 -0
- package/dist/types/node/constants.d.ts +80 -0
- package/dist/types/node/didWebResolver.d.ts +47 -0
- package/dist/types/node/httpFetch.d.ts +60 -0
- package/dist/types/node/index.d.ts +28 -0
- package/dist/types/node/nodeApp.d.ts +120 -0
- package/dist/types/node/nodeClient.d.ts +135 -0
- package/dist/types/node/rateLimit.d.ts +95 -0
- package/dist/types/node/verifyRecord.d.ts +41 -0
- package/dist/types/platform/crypto.d.ts +93 -0
- package/dist/types/platform/crypto.native.d.ts +77 -0
- package/dist/types/platform/expoTypes.d.ts +99 -0
- package/dist/types/platform/platform.d.ts +25 -0
- package/dist/types/secp256k1.d.ts +45 -0
- package/dist/types/transparency/checkpoint.d.ts +71 -0
- package/dist/types/transparency/tree.d.ts +135 -0
- package/package.json +157 -0
- package/src/__tests__/canonicalJson.test.ts +116 -0
- package/src/__tests__/chain.test.ts +279 -0
- package/src/__tests__/didWebResolver.test.ts +132 -0
- package/src/__tests__/envelope.test.ts +267 -0
- package/src/__tests__/nodeApp.test.ts +410 -0
- package/src/__tests__/nodeClient.test.ts +177 -0
- package/src/__tests__/nodeHarness.ts +151 -0
- package/src/__tests__/optionalNativePeers.test.ts +233 -0
- package/src/__tests__/rateLimit.test.ts +268 -0
- package/src/__tests__/runnerGuard.test.ts +85 -0
- package/src/__tests__/secp256k1.test.ts +118 -0
- package/src/__tests__/transparency.test.ts +353 -0
- package/src/chain/continuity.ts +59 -0
- package/src/chain/engine.ts +43 -0
- package/src/chain/recordStore.ts +98 -0
- package/src/chain/types.ts +85 -0
- package/src/chain/verify.ts +102 -0
- package/src/envelope/canonicalJson.ts +120 -0
- package/src/envelope/recordId.ts +63 -0
- package/src/envelope/sign.ts +86 -0
- package/src/envelope/signingInput.ts +48 -0
- package/src/identity/resolver.ts +90 -0
- package/src/index.ts +101 -0
- package/src/node/constants.ts +105 -0
- package/src/node/didWebResolver.ts +162 -0
- package/src/node/httpFetch.ts +88 -0
- package/src/node/index.ts +87 -0
- package/src/node/nodeApp.ts +471 -0
- package/src/node/nodeClient.ts +322 -0
- package/src/node/rateLimit.ts +233 -0
- package/src/node/verifyRecord.ts +60 -0
- package/src/platform/crypto.native.ts +251 -0
- package/src/platform/crypto.ts +172 -0
- package/src/platform/expoTypes.ts +99 -0
- package/src/platform/platform.ts +31 -0
- package/src/secp256k1.ts +207 -0
- package/src/transparency/checkpoint.ts +109 -0
- package/src/transparency/tree.ts +258 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chain continuity — the pure "does this record extend the head by exactly one?"
|
|
3
|
+
* check, with NO storage or crypto dependency.
|
|
4
|
+
*
|
|
5
|
+
* This is the single definition of continuity that used to be duplicated in
|
|
6
|
+
* oxy-api (`verifyChainContinuity`) and the node store (`appendTxn`). The engine
|
|
7
|
+
* calls it with the head it read from the injected store; a store MAY call it
|
|
8
|
+
* again inside its atomic append, but the unique-index backstop (surfaced as
|
|
9
|
+
* `chain_conflict`) is the real race guard.
|
|
10
|
+
*
|
|
11
|
+
* v1 envelopes have no chain coordinates, so they always pass (the caller does
|
|
12
|
+
* not advance a chain for them).
|
|
13
|
+
*/
|
|
14
|
+
import type { SignedRecordEnvelope } from '@oxy.so/contracts';
|
|
15
|
+
import type { ChainHead, VerifyOutcome } from './types';
|
|
16
|
+
/**
|
|
17
|
+
* Check that `env` validly extends `head`:
|
|
18
|
+
*
|
|
19
|
+
* - **v1** (no chain coordinates): always `{ ok: true }` — v1 records are not
|
|
20
|
+
* chained.
|
|
21
|
+
* - **no head** (genesis position): only a genesis (`seq === 0`, `prev` null)
|
|
22
|
+
* is accepted; anything else is `chain_gap` (it claims to extend a chain that
|
|
23
|
+
* does not exist).
|
|
24
|
+
* - **head exists**: `env.prev` MUST equal `head.headRecordId` (else
|
|
25
|
+
* `chain_fork`, which also covers a re-genesis whose `prev` is `null`), and
|
|
26
|
+
* `env.seq` MUST equal `head.seq + 1` (else `bad_seq`).
|
|
27
|
+
*/
|
|
28
|
+
export declare function checkContinuity(head: ChainHead | null, env: SignedRecordEnvelope): VerifyOutcome;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chain engine — verify-then-append orchestration.
|
|
3
|
+
*
|
|
4
|
+
* The one entry point an app's adapter calls to publish a record: it runs the
|
|
5
|
+
* full {@link verifyEnvelope} state machine, computes the content address
|
|
6
|
+
* (`recordId`), and hands the verified envelope to the injected
|
|
7
|
+
* {@link RecordStore} to persist atomically. The store owns the durable
|
|
8
|
+
* concurrency backstop (`chain_conflict` on a unique-index collision); the
|
|
9
|
+
* engine owns the verification + ordering policy.
|
|
10
|
+
*
|
|
11
|
+
* Storage and identity are both injected, so the engine has zero knowledge of
|
|
12
|
+
* Mongo/SQLite, Oxy DIDs, or any app's lexicon — exactly what makes it reusable.
|
|
13
|
+
*/
|
|
14
|
+
import type { SignedRecordEnvelope } from '@oxy.so/contracts';
|
|
15
|
+
import type { VerificationMethodResolver } from '../identity/resolver';
|
|
16
|
+
import { type VerifyOptions } from './verify';
|
|
17
|
+
import type { RecordStore } from './recordStore';
|
|
18
|
+
import type { AppendOutcome } from './types';
|
|
19
|
+
/**
|
|
20
|
+
* Verify `env` and, if it passes, append it to the subject's chain.
|
|
21
|
+
*
|
|
22
|
+
* On a verification failure the rejection is returned WITHOUT touching the store.
|
|
23
|
+
* On success the (engine-computed) `recordId` is passed to `store.append`, whose
|
|
24
|
+
* own outcome — including the `chain_conflict` backstop on a concurrent-writer
|
|
25
|
+
* collision — is returned verbatim.
|
|
26
|
+
*/
|
|
27
|
+
export declare function verifyAndAppend(store: RecordStore, resolver: VerificationMethodResolver, env: SignedRecordEnvelope, opts?: VerifyOptions): Promise<AppendOutcome>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage interfaces — the injected persistence the chain engine drives.
|
|
3
|
+
*
|
|
4
|
+
* The engine ({@link ./verify}, {@link ./engine}) is storage-agnostic: it owns
|
|
5
|
+
* the verification state machine and continuity logic, and delegates EVERY read
|
|
6
|
+
* and write to an injected {@link RecordStore}. An app supplies a store over its
|
|
7
|
+
* own backend (oxy-api over Mongo `SignedRecord`/`RepoHead`, a node over SQLite,
|
|
8
|
+
* Mention over its own Mongo) without the engine knowing anything Oxy- or
|
|
9
|
+
* app-specific.
|
|
10
|
+
*
|
|
11
|
+
* All methods are **subject-keyed**: `subject` is the chain's subject DID
|
|
12
|
+
* (`env.subject`). A store maps that DID to its own primary key (e.g. an Oxy
|
|
13
|
+
* userId) internally; the engine never sees that mapping.
|
|
14
|
+
*
|
|
15
|
+
* ## Concurrency contract
|
|
16
|
+
*
|
|
17
|
+
* `append` MUST be atomic (record insert + head advance in one unit) and MUST
|
|
18
|
+
* translate a duplicate-key collision on the unique `(subject, seq)` /
|
|
19
|
+
* `recordId` index — i.e. a concurrent writer that already took this `seq` — into
|
|
20
|
+
* `{ ok: false, reason: 'chain_conflict' }` (Mongo E11000 / SQLite
|
|
21
|
+
* `SQLITE_CONSTRAINT`). That is the real multi-writer race guard; the engine's
|
|
22
|
+
* pre-append continuity check is only the fast-path rejection.
|
|
23
|
+
*/
|
|
24
|
+
import type { SignedRecordEnvelope } from '@oxy.so/contracts';
|
|
25
|
+
import type { AppendOutcome, ChainHead } from './types';
|
|
26
|
+
export interface RecordStore {
|
|
27
|
+
/** The subject's chain head, or `null` when the subject has no chain yet. */
|
|
28
|
+
getHead(subject: string): Promise<ChainHead | null>;
|
|
29
|
+
/**
|
|
30
|
+
* Atomically persist a verified envelope and advance the subject's chain.
|
|
31
|
+
*
|
|
32
|
+
* `recordId` is the engine-computed content address (`computeRecordId(env)`).
|
|
33
|
+
* Implementations MUST surface a duplicate-key collision as `chain_conflict`
|
|
34
|
+
* (see the concurrency contract above). v1 envelopes (no chain coordinates)
|
|
35
|
+
* are stored without advancing a chain and SHOULD report `seq: -1`.
|
|
36
|
+
*/
|
|
37
|
+
append(subject: string, env: SignedRecordEnvelope, recordId: string): Promise<AppendOutcome>;
|
|
38
|
+
/**
|
|
39
|
+
* The ordered slice of the subject's chain with `seq > sinceSeq`, ascending by
|
|
40
|
+
* `seq`, capped at `limit`. Only chained (v2) records have a `seq`, so v1 rows
|
|
41
|
+
* are naturally excluded. Pass `sinceSeq = -1` to start from genesis.
|
|
42
|
+
*/
|
|
43
|
+
getLogSince(subject: string, sinceSeq: number, limit: number): Promise<SignedRecordEnvelope[]>;
|
|
44
|
+
/**
|
|
45
|
+
* Resolve a `recordId` cursor to its chain `seq` (so a puller resumes from the
|
|
46
|
+
* last record it ingested), or `null` when no such record exists.
|
|
47
|
+
*/
|
|
48
|
+
resolveCursorSeq(subject: string, recordId: string): Promise<number | null>;
|
|
49
|
+
/**
|
|
50
|
+
* The latest verified envelope for an AtProto-style `(collection, rkey)` key —
|
|
51
|
+
* the materialized "current" value (last-writer-wins by chain order), or
|
|
52
|
+
* `null` when no record exists for that key.
|
|
53
|
+
*/
|
|
54
|
+
materializeCurrent(subject: string, collection: string, rkey: string): Promise<SignedRecordEnvelope | null>;
|
|
55
|
+
/**
|
|
56
|
+
* The `issuedAt` of the latest stored record for the envelope's LOGICAL key —
|
|
57
|
+
* the monotonicity frontier the engine compares against (replay/rollback
|
|
58
|
+
* defence). Scoping is the store's policy:
|
|
59
|
+
* - v2: per record key (`collection`, `rkey`) — last-writer-wins for THAT key.
|
|
60
|
+
* - v1: per `type` (the legacy identity/profile singletons).
|
|
61
|
+
*
|
|
62
|
+
* Returns `null` when there is no prior record (the record is the first of its
|
|
63
|
+
* key, so any `issuedAt` is acceptable).
|
|
64
|
+
*/
|
|
65
|
+
latestIssuedAtForKey(subject: string, env: SignedRecordEnvelope): Promise<number | null>;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Content-addressed blob storage — the bytes a record's blob refs point at,
|
|
69
|
+
* keyed by their SHA-256 (`sha256`) content address.
|
|
70
|
+
*
|
|
71
|
+
* Separate from {@link RecordStore} because not every app stores blobs in the
|
|
72
|
+
* same place a record lives (oxy-api identity records carry no blobs; a node
|
|
73
|
+
* pins them; Mention rehosts to the Oxy CDN). `Uint8Array` rather than Node's
|
|
74
|
+
* `Buffer` so the interface stays platform-agnostic.
|
|
75
|
+
*/
|
|
76
|
+
export interface BlobStore {
|
|
77
|
+
/**
|
|
78
|
+
* Pin `bytes` under their content address `hash`. Implementations MUST validate
|
|
79
|
+
* that `bytes` actually hash to `hash`, and SHOULD be idempotent (re-pinning
|
|
80
|
+
* the same hash is a no-op).
|
|
81
|
+
*/
|
|
82
|
+
putBlob(hash: string, bytes: Uint8Array): Promise<void>;
|
|
83
|
+
/** The bytes of a pinned blob, or `null` when absent. */
|
|
84
|
+
getBlob(hash: string): Promise<Uint8Array | null>;
|
|
85
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chain engine types — the per-subject hash-chain vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* A "chain" is a single signer's append-only log of signed-record envelopes
|
|
5
|
+
* ("personal blockchain": one signer, no consensus/mining), ordered by a
|
|
6
|
+
* strictly-increasing `seq` with each record's `prev` pointing at the content
|
|
7
|
+
* address (`recordId`) of the one before it. These types are storage-agnostic:
|
|
8
|
+
* the engine ({@link ./verify}, {@link ./engine}) drives them over an injected
|
|
9
|
+
* {@link ./recordStore.RecordStore}, and any app (Oxy identity/civic/node,
|
|
10
|
+
* Mention posts, …) supplies its own store + resolver.
|
|
11
|
+
*
|
|
12
|
+
* The {@link RejectionReason} union is the SINGLE source of truth for every way
|
|
13
|
+
* an append can fail — it consolidates what used to be three divergent copies
|
|
14
|
+
* (oxy-api's `EnvelopeRejectionReason`, the node store's `AppendOutcome.reason`,
|
|
15
|
+
* and the node verifier's `VerifyRejectionReason`). The exact strings match the
|
|
16
|
+
* ones oxy-api returns today, so API responses are byte-for-byte unchanged.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* The O(1) head pointer of a subject's chain.
|
|
20
|
+
*
|
|
21
|
+
* `headRecordId` is the content address of the latest record (`null` only on the
|
|
22
|
+
* "no chain yet" wire shape); `seq` is its sequence number; `recordCount` is the
|
|
23
|
+
* total appended so far. A store returns `null` (not a `ChainHead`) when the
|
|
24
|
+
* subject has no chain — the engine treats both `null` and a `headRecordId:null`
|
|
25
|
+
* head as "no chain".
|
|
26
|
+
*/
|
|
27
|
+
export interface ChainHead {
|
|
28
|
+
headRecordId: string | null;
|
|
29
|
+
seq: number;
|
|
30
|
+
recordCount: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Every way verifying or appending a signed record can be rejected — stable,
|
|
34
|
+
* machine-readable, and the ONE consolidated union across the protocol.
|
|
35
|
+
*
|
|
36
|
+
* - `invalid_envelope` — the envelope failed the base schema shape.
|
|
37
|
+
* - `subject_mismatch` — the envelope's `subject` is not who the caller is
|
|
38
|
+
* authorized to write for (an adapter-policy binding, surfaced here so a
|
|
39
|
+
* store/adapter can report it on the same channel).
|
|
40
|
+
* - `public_key_not_a_current_verification_method` — the issuer is recognized
|
|
41
|
+
* (self or custodial) but the signing key is not its current key.
|
|
42
|
+
* - `bad_signature` — the signature does not verify against the embedded key.
|
|
43
|
+
* - `issued_in_future` — `issuedAt` is beyond the tolerated clock skew.
|
|
44
|
+
* - `stale_issued_at` — `issuedAt` is not strictly newer than the latest record
|
|
45
|
+
* for the same logical key (replay/rollback defence).
|
|
46
|
+
* - `chain_gap` — a non-genesis record claims to extend a chain that has no head.
|
|
47
|
+
* - `chain_fork` — `prev` does not match the current head (or a re-genesis).
|
|
48
|
+
* - `bad_seq` — `seq` is not exactly `head.seq + 1`.
|
|
49
|
+
* - `chain_conflict` — a concurrent writer already took this `seq` (the store's
|
|
50
|
+
* unique-index backstop, surfaced from a duplicate-key error).
|
|
51
|
+
* - `untrusted_issuer` — the `issuer` is neither the subject nor a recognized
|
|
52
|
+
* custodial issuer.
|
|
53
|
+
*/
|
|
54
|
+
export type RejectionReason = 'invalid_envelope' | 'subject_mismatch' | 'public_key_not_a_current_verification_method' | 'bad_signature' | 'issued_in_future' | 'stale_issued_at' | 'chain_gap' | 'chain_fork' | 'bad_seq' | 'chain_conflict' | 'untrusted_issuer';
|
|
55
|
+
/** Verdict of verifying an envelope WITHOUT persisting it. */
|
|
56
|
+
export type VerifyOutcome = {
|
|
57
|
+
ok: true;
|
|
58
|
+
} | {
|
|
59
|
+
ok: false;
|
|
60
|
+
reason: RejectionReason;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Outcome of appending a verified envelope to a chain.
|
|
64
|
+
*
|
|
65
|
+
* On success it carries the record's content address (`recordId`) and its chain
|
|
66
|
+
* `seq` (`-1` for an unchained v1 record, which has no sequence). On failure it
|
|
67
|
+
* carries the {@link RejectionReason} (a continuity violation or the store's
|
|
68
|
+
* `chain_conflict` backstop).
|
|
69
|
+
*/
|
|
70
|
+
export type AppendOutcome = {
|
|
71
|
+
ok: true;
|
|
72
|
+
recordId: string;
|
|
73
|
+
seq: number;
|
|
74
|
+
} | {
|
|
75
|
+
ok: false;
|
|
76
|
+
reason: RejectionReason;
|
|
77
|
+
};
|
|
78
|
+
/** The `seq` reported for a v1 (unchained) append — it has no sequence. */
|
|
79
|
+
export declare const UNCHAINED_SEQ = -1;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Envelope verification — the ordered state machine that decides whether a
|
|
3
|
+
* signed-record envelope may be appended to its subject's chain.
|
|
4
|
+
*
|
|
5
|
+
* This is the engine that used to live, Oxy-specific, in
|
|
6
|
+
* `api/services/signedRecord.service.verifyEnvelope`. Every Oxy detail has been
|
|
7
|
+
* lifted out into the two injected collaborators:
|
|
8
|
+
* - the {@link VerificationMethodResolver} owns "is this key authorized for this
|
|
9
|
+
* issuer?" (self vs. custodial vs. untrusted),
|
|
10
|
+
* - the {@link RecordStore} owns the monotonicity frontier + the chain head.
|
|
11
|
+
*
|
|
12
|
+
* The ordered checks (first failure wins):
|
|
13
|
+
* 1. **shape** — the base `signedRecordEnvelopeSchema` (open `type`, opaque
|
|
14
|
+
* `record`). An app re-narrows `type` to its own set in its adapter.
|
|
15
|
+
* 2. **signature** — `verifyEnvelopeSignature` recomputes the canonical signing
|
|
16
|
+
* input and checks the secp256k1 signature against the embedded `publicKey`.
|
|
17
|
+
* 3. **issuer authorization** — `resolver.resolve(subject)` + {@link isAuthorizedKey}.
|
|
18
|
+
* 4. **freshness** — `issuedAt` not beyond the tolerated clock skew.
|
|
19
|
+
* 5. **monotonicity** — `issuedAt` strictly newer than the store's latest record
|
|
20
|
+
* for the same logical key (replay/rollback defence).
|
|
21
|
+
* 6. **continuity** — `checkContinuity` against the store's chain head (v1 skips).
|
|
22
|
+
*
|
|
23
|
+
* The `subject_mismatch` binding ("is the caller allowed to write for THIS
|
|
24
|
+
* subject?") is intentionally NOT here — it is an adapter-policy decision the
|
|
25
|
+
* caller makes before invoking the engine, not a property of the envelope.
|
|
26
|
+
*/
|
|
27
|
+
import { type SignedRecordEnvelope } from '@oxy.so/contracts';
|
|
28
|
+
import { type VerificationMethodResolver } from '../identity/resolver';
|
|
29
|
+
import type { RecordStore } from './recordStore';
|
|
30
|
+
import type { VerifyOutcome } from './types';
|
|
31
|
+
/** Tunable verification options. */
|
|
32
|
+
export interface VerifyOptions {
|
|
33
|
+
/** Tolerated forward clock skew for `issuedAt`, in ms. Default: 5 minutes. */
|
|
34
|
+
clockSkewMs?: number;
|
|
35
|
+
/** Override "now" (ms epoch) for deterministic tests. Default: `Date.now()`. */
|
|
36
|
+
now?: number;
|
|
37
|
+
}
|
|
38
|
+
/** Default tolerated forward clock skew for a record's `issuedAt` (5 minutes). */
|
|
39
|
+
export declare const DEFAULT_CLOCK_SKEW_MS: number;
|
|
40
|
+
/**
|
|
41
|
+
* Run the full verification state machine for `env` against the injected
|
|
42
|
+
* `store` (monotonicity + continuity) and `resolver` (issuer authorization).
|
|
43
|
+
* Returns a verdict; it never throws on a bad envelope.
|
|
44
|
+
*/
|
|
45
|
+
export declare function verifyEnvelope(store: RecordStore, resolver: VerificationMethodResolver, env: SignedRecordEnvelope, opts?: VerifyOptions): Promise<VerifyOutcome>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical JSON (RFC 8785 / JCS-style) serialization.
|
|
3
|
+
*
|
|
4
|
+
* `canonicalize(value)` produces a deterministic string for any JSON-compatible
|
|
5
|
+
* value so that a client which SIGNS a record and a server which VERIFIES it
|
|
6
|
+
* agree byte-for-byte on the signing input — regardless of the order in which
|
|
7
|
+
* object keys happen to be written, how the value was deserialized, or which
|
|
8
|
+
* runtime built it.
|
|
9
|
+
*
|
|
10
|
+
* This is the load-bearing primitive for the protocol's signed records
|
|
11
|
+
* (`signEnvelope` + `verifyEnvelopeSignature`): every implementation imports
|
|
12
|
+
* THIS function from `@oxy.so/protocol`, so cross-implementation number/string
|
|
13
|
+
* formatting differences cannot cause a verify mismatch.
|
|
14
|
+
*
|
|
15
|
+
* Rules (the JSON Canonicalization Scheme subset we need):
|
|
16
|
+
* - Objects: keys are sorted (ascending, by UTF-16 code unit — the default
|
|
17
|
+
* `Array.prototype.sort` order) and serialized recursively. Properties whose
|
|
18
|
+
* value is `undefined`, a function, or a symbol are OMITTED (matching
|
|
19
|
+
* `JSON.stringify` object semantics).
|
|
20
|
+
* - Arrays: element order is PRESERVED; `undefined`/function/symbol elements
|
|
21
|
+
* serialize to `null` (matching `JSON.stringify` array semantics).
|
|
22
|
+
* - `null`, booleans, strings, and finite numbers serialize via the standard
|
|
23
|
+
* JSON representation.
|
|
24
|
+
* - Values exposing a `toJSON()` method (e.g. `Date`) are replaced by its
|
|
25
|
+
* result first, then serialized — so a `Date` and its ISO-string equivalent
|
|
26
|
+
* canonicalize identically (the wire always carries the string form).
|
|
27
|
+
* - Non-finite numbers (`NaN`, `Infinity`) and `bigint` are not part of the
|
|
28
|
+
* JSON data model and throw, rather than silently producing `null`.
|
|
29
|
+
*
|
|
30
|
+
* Platform-agnostic — zero dependencies, no `require()`, no react/react-native/
|
|
31
|
+
* expo. Safe in the dual CJS + ESM build.
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* Produce the canonical JSON string for `value`.
|
|
35
|
+
*
|
|
36
|
+
* Deterministic: two structurally-equal values yield identical strings even if
|
|
37
|
+
* their object keys were written in different orders. Use this — never an
|
|
38
|
+
* ad-hoc `JSON.stringify` of a hand-sorted object — as the signing input for
|
|
39
|
+
* signed records, so client signing and server verification cannot drift.
|
|
40
|
+
*
|
|
41
|
+
* @throws if `value` (or any nested member used as the top-level/primitive)
|
|
42
|
+
* contains a non-finite number or a `bigint`, which have no JSON form.
|
|
43
|
+
*/
|
|
44
|
+
export declare function canonicalize(value: unknown): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content hashing — SHA-256 + `recordId` (content address).
|
|
3
|
+
*
|
|
4
|
+
* `sha256` is the protocol's single platform-aware SHA-256: it uses
|
|
5
|
+
* `expo-crypto` on React Native, Node's built-in `crypto` on the server, and
|
|
6
|
+
* the Web Crypto API in the browser — always producing the same lowercase-hex
|
|
7
|
+
* digest. `computeRecordId` is `sha256(signedRecordSigningInput(fields))`: the
|
|
8
|
+
* content address that a record's chain `prev` pointer references.
|
|
9
|
+
*/
|
|
10
|
+
import { type SignedRecordSigningFields } from './signingInput';
|
|
11
|
+
/**
|
|
12
|
+
* Compute the SHA-256 hash of a string, returned as lowercase hex.
|
|
13
|
+
*
|
|
14
|
+
* Platform-aware: `expo-crypto` (RN) → Node `crypto` (server) → Web Crypto
|
|
15
|
+
* (browser). The three paths produce byte-identical digests, so a record
|
|
16
|
+
* hashed on a device and re-hashed on the server agree.
|
|
17
|
+
*/
|
|
18
|
+
export declare function sha256(message: string): Promise<string>;
|
|
19
|
+
/**
|
|
20
|
+
* Compute the `recordId` (content address) of a signed record: the SHA-256 hex
|
|
21
|
+
* digest of its canonical {@link signedRecordSigningInput}.
|
|
22
|
+
*
|
|
23
|
+
* Deterministic and stable across runtimes (it reuses the same canonicalization
|
|
24
|
+
* + SHA-256 the signature itself is built on). The recordId is what `prev`
|
|
25
|
+
* references in the per-subject hash chain, so every implementation MUST
|
|
26
|
+
* compute it identically — all call this function. It is taken over the SIGNING
|
|
27
|
+
* input (excluding `publicKey`/`signature`), so it is a pure content address of
|
|
28
|
+
* the record's meaning, independent of who signed.
|
|
29
|
+
*/
|
|
30
|
+
export declare function computeRecordId(fields: SignedRecordSigningFields): Promise<string>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signing & verification — explicit-key crypto for signed-record envelopes.
|
|
3
|
+
*
|
|
4
|
+
* Stateless: every function takes the key material explicitly (no KeyManager,
|
|
5
|
+
* no secure storage). `@oxy.so/core` binds these to a device key; nodes and the
|
|
6
|
+
* API verify with them. The scheme is `ES256K-DER-SHA256` everywhere:
|
|
7
|
+
* secp256k1 over the SHA-256 of the canonical bytes, DER-encoded.
|
|
8
|
+
*/
|
|
9
|
+
import type { SignedRecordEnvelope } from '@oxy.so/contracts';
|
|
10
|
+
import { type SignedRecordSigningFields } from './signingInput';
|
|
11
|
+
/**
|
|
12
|
+
* Sign an arbitrary message with an explicit private key.
|
|
13
|
+
*
|
|
14
|
+
* Hashes the message with SHA-256, then signs the digest with secp256k1,
|
|
15
|
+
* returning the DER-encoded hex signature. The low-level primitive behind both
|
|
16
|
+
* {@link signEnvelope} and `@oxy.so/core`'s device-key signing helpers.
|
|
17
|
+
*/
|
|
18
|
+
export declare function signMessage(message: string, privateKeyHex: string): Promise<string>;
|
|
19
|
+
/**
|
|
20
|
+
* Verify a DER-encoded signature over a message against a public key.
|
|
21
|
+
*
|
|
22
|
+
* Returns `false` on any error (invalid signature, malformed key/signature,
|
|
23
|
+
* etc.) rather than throwing, so callers can treat verification as a boolean.
|
|
24
|
+
*/
|
|
25
|
+
export declare function verifySignature(message: string, signature: string, publicKeyHex: string): Promise<boolean>;
|
|
26
|
+
/**
|
|
27
|
+
* Build a fully-signed {@link SignedRecordEnvelope} from its signing fields and
|
|
28
|
+
* an explicit private key.
|
|
29
|
+
*
|
|
30
|
+
* Computes the canonical {@link signedRecordSigningInput}, signs it
|
|
31
|
+
* (`ES256K-DER-SHA256`), and attaches the DERIVED `publicKey` (uncompressed
|
|
32
|
+
* hex — identical to `KeyManager`'s stored key for the same private key) plus
|
|
33
|
+
* the `alg`/`signature`. The signature covers every field EXCEPT
|
|
34
|
+
* `publicKey`/`signature`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function signEnvelope(fields: SignedRecordSigningFields, privateKeyHex: string): Promise<SignedRecordEnvelope>;
|
|
37
|
+
/**
|
|
38
|
+
* Verify a signed-record envelope: recompute the canonical signing input from
|
|
39
|
+
* the envelope's own fields and check the signature against the envelope's
|
|
40
|
+
* `publicKey`.
|
|
41
|
+
*
|
|
42
|
+
* This confirms the signature is internally consistent with the embedded
|
|
43
|
+
* `publicKey`. It does NOT establish that `publicKey` is an authorized
|
|
44
|
+
* verification method for `subject` — that authorization check belongs to the
|
|
45
|
+
* server / node owner check.
|
|
46
|
+
*/
|
|
47
|
+
export declare function verifyEnvelopeSignature(envelope: SignedRecordEnvelope): Promise<boolean>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signed-record signing input — "what the signature covers".
|
|
3
|
+
*
|
|
4
|
+
* The single definition shared by every implementation (client signing and
|
|
5
|
+
* server verification), so a record signed by one and verified by another
|
|
6
|
+
* cannot drift.
|
|
7
|
+
*/
|
|
8
|
+
import type { SignedRecordEnvelope } from '@oxy.so/contracts';
|
|
9
|
+
/**
|
|
10
|
+
* The signing-input portion of a {@link SignedRecordEnvelope}: every field
|
|
11
|
+
* EXCEPT the `publicKey` and `signature`. Both the signer and the verifier
|
|
12
|
+
* canonicalize exactly these fields, so they agree on the bytes that the
|
|
13
|
+
* signature covers.
|
|
14
|
+
*
|
|
15
|
+
* The v2 chain fields (`seq`/`prev`/`collection`/`rkey`) are optional: a v1
|
|
16
|
+
* envelope omits them and is signed over only the base fields; a v2 envelope
|
|
17
|
+
* carries them and includes them in the signed bytes.
|
|
18
|
+
*/
|
|
19
|
+
export type SignedRecordSigningFields = Pick<SignedRecordEnvelope, 'version' | 'type' | 'subject' | 'issuer' | 'record' | 'issuedAt'> & Partial<Pick<SignedRecordEnvelope, 'seq' | 'prev' | 'collection' | 'rkey'>>;
|
|
20
|
+
/**
|
|
21
|
+
* Compute the canonical signing input for a signed-record envelope.
|
|
22
|
+
*
|
|
23
|
+
* - **v1**: the canonical JSON of `{version, type, subject, issuer, record,
|
|
24
|
+
* issuedAt}` — BYTE-IDENTICAL to the original scheme, so every signature
|
|
25
|
+
* already in production keeps verifying.
|
|
26
|
+
* - **v2**: the canonical JSON additionally includes the hash-chain fields
|
|
27
|
+
* `{seq, prev, collection, rkey}`. Because {@link canonicalize} sorts keys,
|
|
28
|
+
* the on-the-wire field order is irrelevant; the resulting canonical key
|
|
29
|
+
* order is `collection, issuedAt, issuer, prev, record, rkey, seq, subject,
|
|
30
|
+
* type, version`. `prev` is `null` at genesis (serialized as `null`, not
|
|
31
|
+
* omitted), so it is always part of the signed bytes.
|
|
32
|
+
*/
|
|
33
|
+
export declare function signedRecordSigningInput(fields: SignedRecordSigningFields): string;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verification-method resolution — the injected authorization policy the chain
|
|
3
|
+
* engine consults to decide whether an envelope's signing key is allowed to
|
|
4
|
+
* write to the subject's chain.
|
|
5
|
+
*
|
|
6
|
+
* The engine is identity-agnostic: it does NOT know about Oxy DIDs, the `User`
|
|
7
|
+
* model, custodial keys, or any app's notion of "current key". It asks an
|
|
8
|
+
* injected {@link VerificationMethodResolver} to resolve a subject DID to its
|
|
9
|
+
* current verification methods (plus the optional custodial issuer that may sign
|
|
10
|
+
* provenance records ABOUT the subject), then applies the uniform authorization
|
|
11
|
+
* rule in {@link isAuthorizedKey}.
|
|
12
|
+
*
|
|
13
|
+
* This is what lets the SAME engine serve Oxy identity/civic records (where the
|
|
14
|
+
* resolver reads `User.publicKey`/`authMethods` and the Oxy custodial key), a
|
|
15
|
+
* self-hosted node (where the authority is a configured owner key), and Mention
|
|
16
|
+
* posts (subject VMs from the Oxy DID + a Mention custodial server key) — each
|
|
17
|
+
* supplies its own resolver; the decision logic lives here, once.
|
|
18
|
+
*/
|
|
19
|
+
import type { SignedRecordEnvelope } from '@oxy.so/contracts';
|
|
20
|
+
import type { RejectionReason } from '../chain/types';
|
|
21
|
+
/**
|
|
22
|
+
* The verification methods that may sign records on a subject's chain.
|
|
23
|
+
*
|
|
24
|
+
* - `currentPublicKeys` — the subject's OWN current signing keys (self-issued
|
|
25
|
+
* records: `issuer === subject`). Empty when the subject has no self-sovereign
|
|
26
|
+
* key (e.g. a custodial-only account).
|
|
27
|
+
* - `custodialIssuer` / `custodialPublicKey` — an optional custodial authority
|
|
28
|
+
* that may sign provenance records ABOUT the subject (`issuer ===
|
|
29
|
+
* custodialIssuer`), and the single public key it signs with. Both present or
|
|
30
|
+
* both absent. The custodial public key is published (it lives in a DID
|
|
31
|
+
* document), so a plain-equality comparison is sufficient — it is not a
|
|
32
|
+
* secret.
|
|
33
|
+
*/
|
|
34
|
+
export interface ResolvedVerificationMethods {
|
|
35
|
+
currentPublicKeys: string[];
|
|
36
|
+
custodialIssuer?: string;
|
|
37
|
+
custodialPublicKey?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface VerificationMethodResolver {
|
|
40
|
+
/**
|
|
41
|
+
* Resolve a subject DID to its current verification methods, or `null` when the
|
|
42
|
+
* subject is unknown / cannot be resolved (no key is then authorized).
|
|
43
|
+
*/
|
|
44
|
+
resolve(subjectDid: string): Promise<ResolvedVerificationMethods | null>;
|
|
45
|
+
}
|
|
46
|
+
/** Verdict of the key-authorization decision (a subset of {@link RejectionReason}). */
|
|
47
|
+
export type KeyAuthorization = {
|
|
48
|
+
ok: true;
|
|
49
|
+
} | {
|
|
50
|
+
ok: false;
|
|
51
|
+
reason: Extract<RejectionReason, 'public_key_not_a_current_verification_method' | 'untrusted_issuer'>;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Decide whether `env`'s signing key (`env.publicKey`) is authorized for its
|
|
55
|
+
* `issuer`, given the subject's resolved verification methods:
|
|
56
|
+
*
|
|
57
|
+
* - **Self-issued** (`issuer === subject`): the key MUST be one of the subject's
|
|
58
|
+
* `currentPublicKeys`; otherwise `public_key_not_a_current_verification_method`.
|
|
59
|
+
* - **Custodial** (`issuer === custodialIssuer`): the key MUST equal
|
|
60
|
+
* `custodialPublicKey`; otherwise `public_key_not_a_current_verification_method`.
|
|
61
|
+
* - **Any other issuer** (including an unresolvable subject): `untrusted_issuer`.
|
|
62
|
+
*
|
|
63
|
+
* The signature itself is checked separately (against `env.publicKey`), so this
|
|
64
|
+
* only decides whether that key is an authorized writer — it is not a trust
|
|
65
|
+
* shortcut.
|
|
66
|
+
*/
|
|
67
|
+
export declare function isAuthorizedKey(resolved: ResolvedVerificationMethods | null, env: SignedRecordEnvelope): KeyAuthorization;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @oxy.so/protocol — the app-agnostic Oxy Protocol base.
|
|
3
|
+
*
|
|
4
|
+
* The reusable substrate any Oxy app can use to decentralize its own content:
|
|
5
|
+
* the signed-record envelope grammar (canonical JSON, signing input, content
|
|
6
|
+
* address), explicit-key signing/verification, and the platform-aware crypto
|
|
7
|
+
* loaders. App-specific lexicons and the chain engine layer on top of this.
|
|
8
|
+
*
|
|
9
|
+
* Platform-agnostic root entry. Node-only pieces live under the `./node`
|
|
10
|
+
* subpath so they never enter React Native / web bundles.
|
|
11
|
+
*/
|
|
12
|
+
export { canonicalize } from './envelope/canonicalJson';
|
|
13
|
+
export { signedRecordSigningInput } from './envelope/signingInput';
|
|
14
|
+
export type { SignedRecordSigningFields } from './envelope/signingInput';
|
|
15
|
+
export { sha256, computeRecordId } from './envelope/recordId';
|
|
16
|
+
export { signMessage, verifySignature, signEnvelope, verifyEnvelopeSignature, } from './envelope/sign';
|
|
17
|
+
export type { ChainHead, RejectionReason, VerifyOutcome, AppendOutcome, } from './chain/types';
|
|
18
|
+
export { UNCHAINED_SEQ } from './chain/types';
|
|
19
|
+
export { checkContinuity } from './chain/continuity';
|
|
20
|
+
export type { RecordStore, BlobStore } from './chain/recordStore';
|
|
21
|
+
export { verifyEnvelope, DEFAULT_CLOCK_SKEW_MS } from './chain/verify';
|
|
22
|
+
export type { VerifyOptions } from './chain/verify';
|
|
23
|
+
export { verifyAndAppend } from './chain/engine';
|
|
24
|
+
export { EMPTY_TRANSPARENCY_ROOT, transparencyLeafHash, buildTransparencyTree, buildTransparencyTreeFromHeads, inclusionProof, verifyInclusionProof, } from './transparency/tree';
|
|
25
|
+
export type { TransparencyHeadEntry, TransparencyTree, TransparencyTreeFromHeads, InclusionProofCheck, } from './transparency/tree';
|
|
26
|
+
export { checkpointSigningInput, checkpointHash, signCheckpoint, verifyCheckpointSignature, } from './transparency/checkpoint';
|
|
27
|
+
export type { TransparencyCheckpointFields, TransparencyCheckpointSignature, } from './transparency/checkpoint';
|
|
28
|
+
export { isAuthorizedKey } from './identity/resolver';
|
|
29
|
+
export type { VerificationMethodResolver, ResolvedVerificationMethods, KeyAuthorization, } from './identity/resolver';
|
|
30
|
+
export { isReactNative, isNodeJS } from './platform/platform';
|
|
31
|
+
export { loadNodeCrypto, loadExpoCrypto, loadSecureStore, loadAsyncStorage, getRandomBytesRN, loadSharedIdentityBridge, } from './platform/crypto';
|
|
32
|
+
export type { ExpoCryptoLike, ExpoSecureStoreLike, SharedIdentityBridge } from './platform/expoTypes';
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node-protocol shape constants — the wire-level contract of an Oxy-protocol
|
|
3
|
+
* data node, shared by the generic node app factory ({@link ./nodeApp}), the
|
|
4
|
+
* HTTP {@link ./nodeClient.NodeClient}, and any app's node deployment
|
|
5
|
+
* (`@oxy.so/node`, a future `mention-node`).
|
|
6
|
+
*
|
|
7
|
+
* These were previously hardcoded inside `@oxy.so/node`; they live here so the
|
|
8
|
+
* SAME values drive a server and a client without either side re-declaring (and
|
|
9
|
+
* drifting) the contract. Deployment-specific knobs (owner key, port, data dir)
|
|
10
|
+
* are still resolved per-deployment from the environment — only the protocol's
|
|
11
|
+
* own shape constants live here.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* The node-protocol version advertised at the well-known manifest. Bumped only
|
|
15
|
+
* on a breaking change to the wire shape of the log / head / record APIs. This
|
|
16
|
+
* is the DEFAULT `protocolId` a deployment advertises (overridable per app).
|
|
17
|
+
*/
|
|
18
|
+
export declare const PROTOCOL_VERSION: "oxy-node/1";
|
|
19
|
+
/** Default well-known manifest path (the existing `@oxy.so/node` value). */
|
|
20
|
+
export declare const DEFAULT_WELL_KNOWN_PATH = "/.well-known/oxy-node.json";
|
|
21
|
+
/** Default DID-document service-type label advertised by a node deployment. */
|
|
22
|
+
export declare const DEFAULT_SERVICE_TYPE = "OxyPersonalDataNode";
|
|
23
|
+
/**
|
|
24
|
+
* Default application namespace a node deployment serves. The records a node
|
|
25
|
+
* stores all live under this namespace (e.g. `app.oxy.*`); a `collections`
|
|
26
|
+
* allowlist (when set) MUST be within it.
|
|
27
|
+
*/
|
|
28
|
+
export declare const DEFAULT_APP_NAMESPACE = "app.oxy";
|
|
29
|
+
/** Default HTTP port when the port env var is unset (always overridable). */
|
|
30
|
+
export declare const DEFAULT_PORT = 4000;
|
|
31
|
+
/** Default and maximum number of log entries returned by `GET /oxy/log`. */
|
|
32
|
+
export declare const DEFAULT_LOG_LIMIT = 100;
|
|
33
|
+
export declare const MAX_LOG_LIMIT = 500;
|
|
34
|
+
/** Default upper bound on a single pinned blob's size when unset (25 MiB). */
|
|
35
|
+
export declare const DEFAULT_MAX_BLOB_BYTES: number;
|
|
36
|
+
/** Maximum number of envelopes accepted in one `POST /sync/push` batch. */
|
|
37
|
+
export declare const MAX_SYNC_BATCH = 200;
|
|
38
|
+
/** Body-size ceiling for JSON request bodies (`/records`, `/sync/push`). */
|
|
39
|
+
export declare const JSON_BODY_LIMIT = "5mb";
|
|
40
|
+
/** HTTP headers carrying an owner-signed action authorization (blob pins). */
|
|
41
|
+
export declare const OWNER_AUTH_HEADERS: {
|
|
42
|
+
readonly publicKey: "x-oxy-node-public-key";
|
|
43
|
+
readonly signature: "x-oxy-node-signature";
|
|
44
|
+
readonly timestamp: "x-oxy-node-timestamp";
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Freshness window for an owner-signed action (e.g. a blob pin). A signed
|
|
48
|
+
* authorization header older/newer than this (accounting for clock skew) is
|
|
49
|
+
* rejected, bounding replay of a captured pin authorization.
|
|
50
|
+
*/
|
|
51
|
+
export declare const OWNER_AUTH_MAX_AGE_MS: number;
|
|
52
|
+
/** Operating modes a node can advertise. */
|
|
53
|
+
export declare const NODE_MODES: readonly ["self-hosted", "managed"];
|
|
54
|
+
export type NodeMode = (typeof NODE_MODES)[number];
|
|
55
|
+
/** The node operation an owner can authorize with a signed header. */
|
|
56
|
+
export declare const OWNER_ACTION_BLOB_PIN: "blob-pin";
|
|
57
|
+
/** A 32-byte (64 hex char) lowercase SHA-256 digest, used as the blob address. */
|
|
58
|
+
export declare const SHA256_HEX: RegExp;
|
|
59
|
+
/** Chain head endpoint (`GET`). */
|
|
60
|
+
export declare const NODE_HEAD_PATH = "/oxy/head";
|
|
61
|
+
/** Ordered log endpoint (`GET ?since=&limit=`). */
|
|
62
|
+
export declare const NODE_LOG_PATH = "/oxy/log";
|
|
63
|
+
/** Single-record write endpoint (`POST`, owner-signed envelope). */
|
|
64
|
+
export declare const NODE_RECORDS_PATH = "/records";
|
|
65
|
+
/** Batch push endpoint (`POST`, owner-signed envelopes). */
|
|
66
|
+
export declare const NODE_SYNC_PUSH_PATH = "/sync/push";
|
|
67
|
+
/** Content-addressed blob endpoint prefix (`GET|PUT /blobs/:hash`). */
|
|
68
|
+
export declare const NODE_BLOBS_PATH = "/blobs";
|
|
69
|
+
/** Default time-to-first-byte deadline for a NodeClient request (ms). */
|
|
70
|
+
export declare const DEFAULT_CLIENT_TIMEOUT_MS = 8000;
|
|
71
|
+
/** Default redirect budget for a NodeClient request (each re-validated upstream). */
|
|
72
|
+
export declare const DEFAULT_CLIENT_MAX_REDIRECTS = 1;
|
|
73
|
+
/** Default bounded read for a `/oxy/head` response (tiny JSON). */
|
|
74
|
+
export declare const DEFAULT_HEAD_MAX_BYTES: number;
|
|
75
|
+
/** Default bounded read for a `/oxy/log` page response. */
|
|
76
|
+
export declare const DEFAULT_LOG_MAX_BYTES: number;
|
|
77
|
+
/** Default bounded read for a small JSON write response (`/records`, blob pin). */
|
|
78
|
+
export declare const DEFAULT_WRITE_RESPONSE_MAX_BYTES: number;
|
|
79
|
+
/** Default bounded read for a fetched `<did>.json` document. */
|
|
80
|
+
export declare const DEFAULT_DID_DOC_MAX_BYTES: number;
|