@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.
Files changed (122) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +16 -0
  3. package/dist/cjs/.tsbuildinfo +1 -0
  4. package/dist/cjs/chain/continuity.js +54 -0
  5. package/dist/cjs/chain/engine.js +34 -0
  6. package/dist/cjs/chain/recordStore.js +25 -0
  7. package/dist/cjs/chain/types.js +22 -0
  8. package/dist/cjs/chain/verify.js +82 -0
  9. package/dist/cjs/envelope/canonicalJson.js +107 -0
  10. package/dist/cjs/envelope/recordId.js +60 -0
  11. package/dist/cjs/envelope/sign.js +75 -0
  12. package/dist/cjs/envelope/signingInput.js +32 -0
  13. package/dist/cjs/identity/resolver.js +50 -0
  14. package/dist/cjs/index.js +71 -0
  15. package/dist/cjs/node/constants.js +85 -0
  16. package/dist/cjs/node/didWebResolver.js +126 -0
  17. package/dist/cjs/node/httpFetch.js +61 -0
  18. package/dist/cjs/node/index.js +71 -0
  19. package/dist/cjs/node/nodeApp.js +344 -0
  20. package/dist/cjs/node/nodeClient.js +204 -0
  21. package/dist/cjs/node/rateLimit.js +187 -0
  22. package/dist/cjs/node/verifyRecord.js +51 -0
  23. package/dist/cjs/platform/crypto.js +186 -0
  24. package/dist/cjs/platform/crypto.native.js +204 -0
  25. package/dist/cjs/platform/expoTypes.js +24 -0
  26. package/dist/cjs/platform/platform.js +33 -0
  27. package/dist/cjs/secp256k1.js +148 -0
  28. package/dist/cjs/transparency/checkpoint.js +79 -0
  29. package/dist/cjs/transparency/tree.js +197 -0
  30. package/dist/esm/.tsbuildinfo +1 -0
  31. package/dist/esm/chain/continuity.js +51 -0
  32. package/dist/esm/chain/engine.js +31 -0
  33. package/dist/esm/chain/recordStore.js +24 -0
  34. package/dist/esm/chain/types.js +19 -0
  35. package/dist/esm/chain/verify.js +78 -0
  36. package/dist/esm/envelope/canonicalJson.js +104 -0
  37. package/dist/esm/envelope/recordId.js +56 -0
  38. package/dist/esm/envelope/sign.js +69 -0
  39. package/dist/esm/envelope/signingInput.js +29 -0
  40. package/dist/esm/identity/resolver.js +47 -0
  41. package/dist/esm/index.js +36 -0
  42. package/dist/esm/node/constants.js +82 -0
  43. package/dist/esm/node/didWebResolver.js +122 -0
  44. package/dist/esm/node/httpFetch.js +55 -0
  45. package/dist/esm/node/index.js +28 -0
  46. package/dist/esm/node/nodeApp.js +336 -0
  47. package/dist/esm/node/nodeClient.js +198 -0
  48. package/dist/esm/node/rateLimit.js +182 -0
  49. package/dist/esm/node/verifyRecord.js +48 -0
  50. package/dist/esm/platform/crypto.js +145 -0
  51. package/dist/esm/platform/crypto.native.js +196 -0
  52. package/dist/esm/platform/expoTypes.js +23 -0
  53. package/dist/esm/platform/platform.js +29 -0
  54. package/dist/esm/secp256k1.js +137 -0
  55. package/dist/esm/transparency/checkpoint.js +73 -0
  56. package/dist/esm/transparency/tree.js +189 -0
  57. package/dist/types/.tsbuildinfo +1 -0
  58. package/dist/types/chain/continuity.d.ts +28 -0
  59. package/dist/types/chain/engine.d.ts +27 -0
  60. package/dist/types/chain/recordStore.d.ts +85 -0
  61. package/dist/types/chain/types.d.ts +79 -0
  62. package/dist/types/chain/verify.d.ts +45 -0
  63. package/dist/types/envelope/canonicalJson.d.ts +44 -0
  64. package/dist/types/envelope/recordId.d.ts +30 -0
  65. package/dist/types/envelope/sign.d.ts +47 -0
  66. package/dist/types/envelope/signingInput.d.ts +33 -0
  67. package/dist/types/identity/resolver.d.ts +67 -0
  68. package/dist/types/index.d.ts +32 -0
  69. package/dist/types/node/constants.d.ts +80 -0
  70. package/dist/types/node/didWebResolver.d.ts +47 -0
  71. package/dist/types/node/httpFetch.d.ts +60 -0
  72. package/dist/types/node/index.d.ts +28 -0
  73. package/dist/types/node/nodeApp.d.ts +120 -0
  74. package/dist/types/node/nodeClient.d.ts +135 -0
  75. package/dist/types/node/rateLimit.d.ts +95 -0
  76. package/dist/types/node/verifyRecord.d.ts +41 -0
  77. package/dist/types/platform/crypto.d.ts +93 -0
  78. package/dist/types/platform/crypto.native.d.ts +77 -0
  79. package/dist/types/platform/expoTypes.d.ts +99 -0
  80. package/dist/types/platform/platform.d.ts +25 -0
  81. package/dist/types/secp256k1.d.ts +45 -0
  82. package/dist/types/transparency/checkpoint.d.ts +71 -0
  83. package/dist/types/transparency/tree.d.ts +135 -0
  84. package/package.json +157 -0
  85. package/src/__tests__/canonicalJson.test.ts +116 -0
  86. package/src/__tests__/chain.test.ts +279 -0
  87. package/src/__tests__/didWebResolver.test.ts +132 -0
  88. package/src/__tests__/envelope.test.ts +267 -0
  89. package/src/__tests__/nodeApp.test.ts +410 -0
  90. package/src/__tests__/nodeClient.test.ts +177 -0
  91. package/src/__tests__/nodeHarness.ts +151 -0
  92. package/src/__tests__/optionalNativePeers.test.ts +233 -0
  93. package/src/__tests__/rateLimit.test.ts +268 -0
  94. package/src/__tests__/runnerGuard.test.ts +85 -0
  95. package/src/__tests__/secp256k1.test.ts +118 -0
  96. package/src/__tests__/transparency.test.ts +353 -0
  97. package/src/chain/continuity.ts +59 -0
  98. package/src/chain/engine.ts +43 -0
  99. package/src/chain/recordStore.ts +98 -0
  100. package/src/chain/types.ts +85 -0
  101. package/src/chain/verify.ts +102 -0
  102. package/src/envelope/canonicalJson.ts +120 -0
  103. package/src/envelope/recordId.ts +63 -0
  104. package/src/envelope/sign.ts +86 -0
  105. package/src/envelope/signingInput.ts +48 -0
  106. package/src/identity/resolver.ts +90 -0
  107. package/src/index.ts +101 -0
  108. package/src/node/constants.ts +105 -0
  109. package/src/node/didWebResolver.ts +162 -0
  110. package/src/node/httpFetch.ts +88 -0
  111. package/src/node/index.ts +87 -0
  112. package/src/node/nodeApp.ts +471 -0
  113. package/src/node/nodeClient.ts +322 -0
  114. package/src/node/rateLimit.ts +233 -0
  115. package/src/node/verifyRecord.ts +60 -0
  116. package/src/platform/crypto.native.ts +251 -0
  117. package/src/platform/crypto.ts +172 -0
  118. package/src/platform/expoTypes.ts +99 -0
  119. package/src/platform/platform.ts +31 -0
  120. package/src/secp256k1.ts +207 -0
  121. package/src/transparency/checkpoint.ts +109 -0
  122. package/src/transparency/tree.ts +258 -0
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ /**
3
+ * Chain continuity — the pure "does this record extend the head by exactly one?"
4
+ * check, with NO storage or crypto dependency.
5
+ *
6
+ * This is the single definition of continuity that used to be duplicated in
7
+ * oxy-api (`verifyChainContinuity`) and the node store (`appendTxn`). The engine
8
+ * calls it with the head it read from the injected store; a store MAY call it
9
+ * again inside its atomic append, but the unique-index backstop (surfaced as
10
+ * `chain_conflict`) is the real race guard.
11
+ *
12
+ * v1 envelopes have no chain coordinates, so they always pass (the caller does
13
+ * not advance a chain for them).
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.checkContinuity = checkContinuity;
17
+ /**
18
+ * True when `head` represents an actual existing chain (as opposed to `null` or
19
+ * the "no chain yet" sentinel head a store may return).
20
+ */
21
+ function hasChain(head) {
22
+ return head !== null && head.headRecordId !== null && head.seq >= 0;
23
+ }
24
+ /**
25
+ * Check that `env` validly extends `head`:
26
+ *
27
+ * - **v1** (no chain coordinates): always `{ ok: true }` — v1 records are not
28
+ * chained.
29
+ * - **no head** (genesis position): only a genesis (`seq === 0`, `prev` null)
30
+ * is accepted; anything else is `chain_gap` (it claims to extend a chain that
31
+ * does not exist).
32
+ * - **head exists**: `env.prev` MUST equal `head.headRecordId` (else
33
+ * `chain_fork`, which also covers a re-genesis whose `prev` is `null`), and
34
+ * `env.seq` MUST equal `head.seq + 1` (else `bad_seq`).
35
+ */
36
+ function checkContinuity(head, env) {
37
+ if (env.version !== 2) {
38
+ return { ok: true };
39
+ }
40
+ const isGenesis = env.seq === 0 && (env.prev === null || env.prev === undefined);
41
+ if (!hasChain(head)) {
42
+ if (!isGenesis) {
43
+ return { ok: false, reason: 'chain_gap' };
44
+ }
45
+ return { ok: true };
46
+ }
47
+ if (env.prev !== head.headRecordId) {
48
+ return { ok: false, reason: 'chain_fork' };
49
+ }
50
+ if (env.seq !== head.seq + 1) {
51
+ return { ok: false, reason: 'bad_seq' };
52
+ }
53
+ return { ok: true };
54
+ }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ /**
3
+ * Chain engine — verify-then-append orchestration.
4
+ *
5
+ * The one entry point an app's adapter calls to publish a record: it runs the
6
+ * full {@link verifyEnvelope} state machine, computes the content address
7
+ * (`recordId`), and hands the verified envelope to the injected
8
+ * {@link RecordStore} to persist atomically. The store owns the durable
9
+ * concurrency backstop (`chain_conflict` on a unique-index collision); the
10
+ * engine owns the verification + ordering policy.
11
+ *
12
+ * Storage and identity are both injected, so the engine has zero knowledge of
13
+ * Mongo/SQLite, Oxy DIDs, or any app's lexicon — exactly what makes it reusable.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.verifyAndAppend = verifyAndAppend;
17
+ const recordId_1 = require("../envelope/recordId");
18
+ const verify_1 = require("./verify");
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
+ async function verifyAndAppend(store, resolver, env, opts = {}) {
28
+ const verification = await (0, verify_1.verifyEnvelope)(store, resolver, env, opts);
29
+ if (!verification.ok) {
30
+ return verification;
31
+ }
32
+ const recordId = await (0, recordId_1.computeRecordId)(env);
33
+ return store.append(env.subject, env, recordId);
34
+ }
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ /**
3
+ * Storage interfaces — the injected persistence the chain engine drives.
4
+ *
5
+ * The engine ({@link ./verify}, {@link ./engine}) is storage-agnostic: it owns
6
+ * the verification state machine and continuity logic, and delegates EVERY read
7
+ * and write to an injected {@link RecordStore}. An app supplies a store over its
8
+ * own backend (oxy-api over Mongo `SignedRecord`/`RepoHead`, a node over SQLite,
9
+ * Mention over its own Mongo) without the engine knowing anything Oxy- or
10
+ * app-specific.
11
+ *
12
+ * All methods are **subject-keyed**: `subject` is the chain's subject DID
13
+ * (`env.subject`). A store maps that DID to its own primary key (e.g. an Oxy
14
+ * userId) internally; the engine never sees that mapping.
15
+ *
16
+ * ## Concurrency contract
17
+ *
18
+ * `append` MUST be atomic (record insert + head advance in one unit) and MUST
19
+ * translate a duplicate-key collision on the unique `(subject, seq)` /
20
+ * `recordId` index — i.e. a concurrent writer that already took this `seq` — into
21
+ * `{ ok: false, reason: 'chain_conflict' }` (Mongo E11000 / SQLite
22
+ * `SQLITE_CONSTRAINT`). That is the real multi-writer race guard; the engine's
23
+ * pre-append continuity check is only the fast-path rejection.
24
+ */
25
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ /**
3
+ * Chain engine types — the per-subject hash-chain vocabulary.
4
+ *
5
+ * A "chain" is a single signer's append-only log of signed-record envelopes
6
+ * ("personal blockchain": one signer, no consensus/mining), ordered by a
7
+ * strictly-increasing `seq` with each record's `prev` pointing at the content
8
+ * address (`recordId`) of the one before it. These types are storage-agnostic:
9
+ * the engine ({@link ./verify}, {@link ./engine}) drives them over an injected
10
+ * {@link ./recordStore.RecordStore}, and any app (Oxy identity/civic/node,
11
+ * Mention posts, …) supplies its own store + resolver.
12
+ *
13
+ * The {@link RejectionReason} union is the SINGLE source of truth for every way
14
+ * an append can fail — it consolidates what used to be three divergent copies
15
+ * (oxy-api's `EnvelopeRejectionReason`, the node store's `AppendOutcome.reason`,
16
+ * and the node verifier's `VerifyRejectionReason`). The exact strings match the
17
+ * ones oxy-api returns today, so API responses are byte-for-byte unchanged.
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.UNCHAINED_SEQ = void 0;
21
+ /** The `seq` reported for a v1 (unchained) append — it has no sequence. */
22
+ exports.UNCHAINED_SEQ = -1;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ /**
3
+ * Envelope verification — the ordered state machine that decides whether a
4
+ * signed-record envelope may be appended to its subject's chain.
5
+ *
6
+ * This is the engine that used to live, Oxy-specific, in
7
+ * `api/services/signedRecord.service.verifyEnvelope`. Every Oxy detail has been
8
+ * lifted out into the two injected collaborators:
9
+ * - the {@link VerificationMethodResolver} owns "is this key authorized for this
10
+ * issuer?" (self vs. custodial vs. untrusted),
11
+ * - the {@link RecordStore} owns the monotonicity frontier + the chain head.
12
+ *
13
+ * The ordered checks (first failure wins):
14
+ * 1. **shape** — the base `signedRecordEnvelopeSchema` (open `type`, opaque
15
+ * `record`). An app re-narrows `type` to its own set in its adapter.
16
+ * 2. **signature** — `verifyEnvelopeSignature` recomputes the canonical signing
17
+ * input and checks the secp256k1 signature against the embedded `publicKey`.
18
+ * 3. **issuer authorization** — `resolver.resolve(subject)` + {@link isAuthorizedKey}.
19
+ * 4. **freshness** — `issuedAt` not beyond the tolerated clock skew.
20
+ * 5. **monotonicity** — `issuedAt` strictly newer than the store's latest record
21
+ * for the same logical key (replay/rollback defence).
22
+ * 6. **continuity** — `checkContinuity` against the store's chain head (v1 skips).
23
+ *
24
+ * The `subject_mismatch` binding ("is the caller allowed to write for THIS
25
+ * subject?") is intentionally NOT here — it is an adapter-policy decision the
26
+ * caller makes before invoking the engine, not a property of the envelope.
27
+ */
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.DEFAULT_CLOCK_SKEW_MS = void 0;
30
+ exports.verifyEnvelope = verifyEnvelope;
31
+ const contracts_1 = require("@oxy.so/contracts");
32
+ const sign_1 = require("../envelope/sign");
33
+ const resolver_1 = require("../identity/resolver");
34
+ const continuity_1 = require("./continuity");
35
+ /** Default tolerated forward clock skew for a record's `issuedAt` (5 minutes). */
36
+ exports.DEFAULT_CLOCK_SKEW_MS = 5 * 60 * 1000;
37
+ /**
38
+ * Run the full verification state machine for `env` against the injected
39
+ * `store` (monotonicity + continuity) and `resolver` (issuer authorization).
40
+ * Returns a verdict; it never throws on a bad envelope.
41
+ */
42
+ async function verifyEnvelope(store, resolver, env, opts = {}) {
43
+ // 1. Base envelope shape (open `type`, `record` opaque). An app's adapter
44
+ // re-narrows `type` to its own accepted set before/around this call.
45
+ if (!contracts_1.signedRecordEnvelopeSchema.safeParse(env).success) {
46
+ return { ok: false, reason: 'invalid_envelope' };
47
+ }
48
+ // 2. Signature is internally consistent with the embedded `publicKey`. Cheap,
49
+ // pure crypto — rejected before any store/resolver I/O.
50
+ if (!(await (0, sign_1.verifyEnvelopeSignature)(env))) {
51
+ return { ok: false, reason: 'bad_signature' };
52
+ }
53
+ // Enforce that v2 envelopes strictly contain required chain fields.
54
+ if (env.version === 2 && (typeof env.seq !== 'number' || typeof env.collection !== 'string' || typeof env.rkey !== 'string')) {
55
+ return { ok: false, reason: 'invalid_envelope' };
56
+ }
57
+ // 3. The signing key is an authorized writer for the issuer (self-issued ⇒ a
58
+ // current VM of the subject; custodial ⇒ the custodial key; else untrusted).
59
+ const resolved = await resolver.resolve(env.subject);
60
+ const authorization = (0, resolver_1.isAuthorizedKey)(resolved, env);
61
+ if (!authorization.ok) {
62
+ return authorization;
63
+ }
64
+ // 4. Freshness: not issued beyond the tolerated forward clock skew.
65
+ const now = opts.now ?? Date.now();
66
+ const clockSkewMs = opts.clockSkewMs ?? exports.DEFAULT_CLOCK_SKEW_MS;
67
+ if (env.issuedAt > now + clockSkewMs) {
68
+ return { ok: false, reason: 'issued_in_future' };
69
+ }
70
+ // 5. Monotonicity: strictly newer than the latest record for the same key.
71
+ const latestIssuedAt = await store.latestIssuedAtForKey(env.subject, env);
72
+ if (latestIssuedAt !== null && env.issuedAt <= latestIssuedAt) {
73
+ return { ok: false, reason: 'stale_issued_at' };
74
+ }
75
+ // 6. Continuity: the record extends the chain head by exactly one. Only v2 is
76
+ // chained, so v1 never reads the head (it has no chain to extend).
77
+ if (env.version !== 2) {
78
+ return { ok: true };
79
+ }
80
+ const head = await store.getHead(env.subject);
81
+ return (0, continuity_1.checkContinuity)(head, env);
82
+ }
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ /**
3
+ * Canonical JSON (RFC 8785 / JCS-style) serialization.
4
+ *
5
+ * `canonicalize(value)` produces a deterministic string for any JSON-compatible
6
+ * value so that a client which SIGNS a record and a server which VERIFIES it
7
+ * agree byte-for-byte on the signing input — regardless of the order in which
8
+ * object keys happen to be written, how the value was deserialized, or which
9
+ * runtime built it.
10
+ *
11
+ * This is the load-bearing primitive for the protocol's signed records
12
+ * (`signEnvelope` + `verifyEnvelopeSignature`): every implementation imports
13
+ * THIS function from `@oxy.so/protocol`, so cross-implementation number/string
14
+ * formatting differences cannot cause a verify mismatch.
15
+ *
16
+ * Rules (the JSON Canonicalization Scheme subset we need):
17
+ * - Objects: keys are sorted (ascending, by UTF-16 code unit — the default
18
+ * `Array.prototype.sort` order) and serialized recursively. Properties whose
19
+ * value is `undefined`, a function, or a symbol are OMITTED (matching
20
+ * `JSON.stringify` object semantics).
21
+ * - Arrays: element order is PRESERVED; `undefined`/function/symbol elements
22
+ * serialize to `null` (matching `JSON.stringify` array semantics).
23
+ * - `null`, booleans, strings, and finite numbers serialize via the standard
24
+ * JSON representation.
25
+ * - Values exposing a `toJSON()` method (e.g. `Date`) are replaced by its
26
+ * result first, then serialized — so a `Date` and its ISO-string equivalent
27
+ * canonicalize identically (the wire always carries the string form).
28
+ * - Non-finite numbers (`NaN`, `Infinity`) and `bigint` are not part of the
29
+ * JSON data model and throw, rather than silently producing `null`.
30
+ *
31
+ * Platform-agnostic — zero dependencies, no `require()`, no react/react-native/
32
+ * expo. Safe in the dual CJS + ESM build.
33
+ */
34
+ Object.defineProperty(exports, "__esModule", { value: true });
35
+ exports.canonicalize = canonicalize;
36
+ function hasToJSON(value) {
37
+ return typeof value.toJSON === 'function';
38
+ }
39
+ /**
40
+ * Serialize a single value into its canonical JSON fragment. Recursive; called
41
+ * on each nested member. Object keys are sorted at every level.
42
+ */
43
+ function serialize(value) {
44
+ if (value === null) {
45
+ return 'null';
46
+ }
47
+ const valueType = typeof value;
48
+ if (valueType === 'number') {
49
+ if (!Number.isFinite(value)) {
50
+ throw new Error('canonicalize: non-finite numbers cannot be serialized');
51
+ }
52
+ return JSON.stringify(value);
53
+ }
54
+ if (valueType === 'string' || valueType === 'boolean') {
55
+ return JSON.stringify(value);
56
+ }
57
+ if (valueType === 'bigint') {
58
+ throw new Error('canonicalize: bigint values cannot be serialized');
59
+ }
60
+ if (Array.isArray(value)) {
61
+ const items = value.map((item) => {
62
+ const itemType = typeof item;
63
+ // JSON array semantics: undefined / function / symbol become null so the
64
+ // element positions (and therefore the array length) are preserved.
65
+ if (item === undefined || itemType === 'function' || itemType === 'symbol') {
66
+ return 'null';
67
+ }
68
+ return serialize(item);
69
+ });
70
+ return `[${items.join(',')}]`;
71
+ }
72
+ if (valueType === 'object') {
73
+ const obj = value;
74
+ if (hasToJSON(obj)) {
75
+ return serialize(obj.toJSON());
76
+ }
77
+ const record = obj;
78
+ const parts = [];
79
+ for (const key of Object.keys(record).sort()) {
80
+ const member = record[key];
81
+ const memberType = typeof member;
82
+ // JSON object semantics: properties with undefined / function / symbol
83
+ // values are omitted entirely.
84
+ if (member === undefined || memberType === 'function' || memberType === 'symbol') {
85
+ continue;
86
+ }
87
+ parts.push(`${JSON.stringify(key)}:${serialize(member)}`);
88
+ }
89
+ return `{${parts.join(',')}}`;
90
+ }
91
+ // undefined / function / symbol at the top level have no JSON representation.
92
+ throw new Error(`canonicalize: cannot serialize a value of type ${valueType}`);
93
+ }
94
+ /**
95
+ * Produce the canonical JSON string for `value`.
96
+ *
97
+ * Deterministic: two structurally-equal values yield identical strings even if
98
+ * their object keys were written in different orders. Use this — never an
99
+ * ad-hoc `JSON.stringify` of a hand-sorted object — as the signing input for
100
+ * signed records, so client signing and server verification cannot drift.
101
+ *
102
+ * @throws if `value` (or any nested member used as the top-level/primitive)
103
+ * contains a non-finite number or a `bigint`, which have no JSON form.
104
+ */
105
+ function canonicalize(value) {
106
+ return serialize(value);
107
+ }
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ /**
3
+ * Content hashing — SHA-256 + `recordId` (content address).
4
+ *
5
+ * `sha256` is the protocol's single platform-aware SHA-256: it uses
6
+ * `expo-crypto` on React Native, Node's built-in `crypto` on the server, and
7
+ * the Web Crypto API in the browser — always producing the same lowercase-hex
8
+ * digest. `computeRecordId` is `sha256(signedRecordSigningInput(fields))`: the
9
+ * content address that a record's chain `prev` pointer references.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.sha256 = sha256;
13
+ exports.computeRecordId = computeRecordId;
14
+ const platform_1 = require("../platform/platform");
15
+ const crypto_1 = require("../platform/crypto");
16
+ const signingInput_1 = require("./signingInput");
17
+ /**
18
+ * Compute the SHA-256 hash of a string, returned as lowercase hex.
19
+ *
20
+ * Platform-aware: `expo-crypto` (RN) → Node `crypto` (server) → Web Crypto
21
+ * (browser). The three paths produce byte-identical digests, so a record
22
+ * hashed on a device and re-hashed on the server agree.
23
+ */
24
+ async function sha256(message) {
25
+ // In React Native, use expo-crypto
26
+ if ((0, platform_1.isReactNative)()) {
27
+ const Crypto = await (0, crypto_1.loadExpoCrypto)();
28
+ return Crypto.digestStringAsync(Crypto.CryptoDigestAlgorithm.SHA256, message);
29
+ }
30
+ if ((0, platform_1.isNodeJS)()) {
31
+ try {
32
+ const nodeCrypto = await (0, crypto_1.loadNodeCrypto)();
33
+ return nodeCrypto.createHash('sha256').update(message).digest('hex');
34
+ }
35
+ catch {
36
+ // Node crypto failed to load — fall through to the Web Crypto API below,
37
+ // which is a correct, equivalent SHA-256 on any runtime that exposes it.
38
+ }
39
+ }
40
+ // Browser: use Web Crypto API
41
+ const encoder = new TextEncoder();
42
+ const data = encoder.encode(message);
43
+ const hashBuffer = await globalThis.crypto.subtle.digest('SHA-256', data);
44
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
45
+ return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
46
+ }
47
+ /**
48
+ * Compute the `recordId` (content address) of a signed record: the SHA-256 hex
49
+ * digest of its canonical {@link signedRecordSigningInput}.
50
+ *
51
+ * Deterministic and stable across runtimes (it reuses the same canonicalization
52
+ * + SHA-256 the signature itself is built on). The recordId is what `prev`
53
+ * references in the per-subject hash chain, so every implementation MUST
54
+ * compute it identically — all call this function. It is taken over the SIGNING
55
+ * input (excluding `publicKey`/`signature`), so it is a pure content address of
56
+ * the record's meaning, independent of who signed.
57
+ */
58
+ async function computeRecordId(fields) {
59
+ return sha256((0, signingInput_1.signedRecordSigningInput)(fields));
60
+ }
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ /**
3
+ * Signing & verification — explicit-key crypto for signed-record envelopes.
4
+ *
5
+ * Stateless: every function takes the key material explicitly (no KeyManager,
6
+ * no secure storage). `@oxy.so/core` binds these to a device key; nodes and the
7
+ * API verify with them. The scheme is `ES256K-DER-SHA256` everywhere:
8
+ * secp256k1 over the SHA-256 of the canonical bytes, DER-encoded.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.signMessage = signMessage;
12
+ exports.verifySignature = verifySignature;
13
+ exports.signEnvelope = signEnvelope;
14
+ exports.verifyEnvelopeSignature = verifyEnvelopeSignature;
15
+ const signingInput_1 = require("./signingInput");
16
+ const recordId_1 = require("./recordId");
17
+ const secp256k1_1 = require("../secp256k1");
18
+ /** The one signature algorithm identifier the protocol emits. */
19
+ const ALG = 'ES256K-DER-SHA256';
20
+ /**
21
+ * Sign an arbitrary message with an explicit private key.
22
+ *
23
+ * Hashes the message with SHA-256, then signs the digest with secp256k1,
24
+ * returning the DER-encoded hex signature. The low-level primitive behind both
25
+ * {@link signEnvelope} and `@oxy.so/core`'s device-key signing helpers.
26
+ */
27
+ async function signMessage(message, privateKeyHex) {
28
+ const digest = await (0, recordId_1.sha256)(message);
29
+ return (0, secp256k1_1.signSecp256k1Digest)(privateKeyHex, digest);
30
+ }
31
+ /**
32
+ * Verify a DER-encoded signature over a message against a public key.
33
+ *
34
+ * Returns `false` on any error (invalid signature, malformed key/signature,
35
+ * etc.) rather than throwing, so callers can treat verification as a boolean.
36
+ */
37
+ async function verifySignature(message, signature, publicKeyHex) {
38
+ try {
39
+ const digest = await (0, recordId_1.sha256)(message);
40
+ return (0, secp256k1_1.verifySecp256k1Digest)(publicKeyHex, digest, signature);
41
+ }
42
+ catch {
43
+ // Malformed key / signature / input is not a valid signature.
44
+ return false;
45
+ }
46
+ }
47
+ /**
48
+ * Build a fully-signed {@link SignedRecordEnvelope} from its signing fields and
49
+ * an explicit private key.
50
+ *
51
+ * Computes the canonical {@link signedRecordSigningInput}, signs it
52
+ * (`ES256K-DER-SHA256`), and attaches the DERIVED `publicKey` (uncompressed
53
+ * hex — identical to `KeyManager`'s stored key for the same private key) plus
54
+ * the `alg`/`signature`. The signature covers every field EXCEPT
55
+ * `publicKey`/`signature`.
56
+ */
57
+ async function signEnvelope(fields, privateKeyHex) {
58
+ const signingInput = (0, signingInput_1.signedRecordSigningInput)(fields);
59
+ const signature = await signMessage(signingInput, privateKeyHex);
60
+ const publicKey = (0, secp256k1_1.deriveSecp256k1PublicKey)(privateKeyHex);
61
+ return { ...fields, publicKey, alg: ALG, signature };
62
+ }
63
+ /**
64
+ * Verify a signed-record envelope: recompute the canonical signing input from
65
+ * the envelope's own fields and check the signature against the envelope's
66
+ * `publicKey`.
67
+ *
68
+ * This confirms the signature is internally consistent with the embedded
69
+ * `publicKey`. It does NOT establish that `publicKey` is an authorized
70
+ * verification method for `subject` — that authorization check belongs to the
71
+ * server / node owner check.
72
+ */
73
+ async function verifyEnvelopeSignature(envelope) {
74
+ return verifySignature((0, signingInput_1.signedRecordSigningInput)(envelope), envelope.signature, envelope.publicKey);
75
+ }
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ /**
3
+ * Signed-record signing input — "what the signature covers".
4
+ *
5
+ * The single definition shared by every implementation (client signing and
6
+ * server verification), so a record signed by one and verified by another
7
+ * cannot drift.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.signedRecordSigningInput = signedRecordSigningInput;
11
+ const canonicalJson_1 = require("./canonicalJson");
12
+ /**
13
+ * Compute the canonical signing input for a signed-record envelope.
14
+ *
15
+ * - **v1**: the canonical JSON of `{version, type, subject, issuer, record,
16
+ * issuedAt}` — BYTE-IDENTICAL to the original scheme, so every signature
17
+ * already in production keeps verifying.
18
+ * - **v2**: the canonical JSON additionally includes the hash-chain fields
19
+ * `{seq, prev, collection, rkey}`. Because {@link canonicalize} sorts keys,
20
+ * the on-the-wire field order is irrelevant; the resulting canonical key
21
+ * order is `collection, issuedAt, issuer, prev, record, rkey, seq, subject,
22
+ * type, version`. `prev` is `null` at genesis (serialized as `null`, not
23
+ * omitted), so it is always part of the signed bytes.
24
+ */
25
+ function signedRecordSigningInput(fields) {
26
+ const { version, type, subject, issuer, record, issuedAt } = fields;
27
+ if (version === 2) {
28
+ const { seq, prev, collection, rkey } = fields;
29
+ return (0, canonicalJson_1.canonicalize)({ version, type, subject, issuer, record, issuedAt, seq, prev, collection, rkey });
30
+ }
31
+ return (0, canonicalJson_1.canonicalize)({ version, type, subject, issuer, record, issuedAt });
32
+ }
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ /**
3
+ * Verification-method resolution — the injected authorization policy the chain
4
+ * engine consults to decide whether an envelope's signing key is allowed to
5
+ * write to the subject's chain.
6
+ *
7
+ * The engine is identity-agnostic: it does NOT know about Oxy DIDs, the `User`
8
+ * model, custodial keys, or any app's notion of "current key". It asks an
9
+ * injected {@link VerificationMethodResolver} to resolve a subject DID to its
10
+ * current verification methods (plus the optional custodial issuer that may sign
11
+ * provenance records ABOUT the subject), then applies the uniform authorization
12
+ * rule in {@link isAuthorizedKey}.
13
+ *
14
+ * This is what lets the SAME engine serve Oxy identity/civic records (where the
15
+ * resolver reads `User.publicKey`/`authMethods` and the Oxy custodial key), a
16
+ * self-hosted node (where the authority is a configured owner key), and Mention
17
+ * posts (subject VMs from the Oxy DID + a Mention custodial server key) — each
18
+ * supplies its own resolver; the decision logic lives here, once.
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.isAuthorizedKey = isAuthorizedKey;
22
+ /**
23
+ * Decide whether `env`'s signing key (`env.publicKey`) is authorized for its
24
+ * `issuer`, given the subject's resolved verification methods:
25
+ *
26
+ * - **Self-issued** (`issuer === subject`): the key MUST be one of the subject's
27
+ * `currentPublicKeys`; otherwise `public_key_not_a_current_verification_method`.
28
+ * - **Custodial** (`issuer === custodialIssuer`): the key MUST equal
29
+ * `custodialPublicKey`; otherwise `public_key_not_a_current_verification_method`.
30
+ * - **Any other issuer** (including an unresolvable subject): `untrusted_issuer`.
31
+ *
32
+ * The signature itself is checked separately (against `env.publicKey`), so this
33
+ * only decides whether that key is an authorized writer — it is not a trust
34
+ * shortcut.
35
+ */
36
+ function isAuthorizedKey(resolved, env) {
37
+ if (resolved !== null && env.issuer === env.subject) {
38
+ return resolved.currentPublicKeys.includes(env.publicKey)
39
+ ? { ok: true }
40
+ : { ok: false, reason: 'public_key_not_a_current_verification_method' };
41
+ }
42
+ if (resolved !== null &&
43
+ resolved.custodialIssuer !== undefined &&
44
+ env.issuer === resolved.custodialIssuer) {
45
+ return resolved.custodialPublicKey !== undefined && env.publicKey === resolved.custodialPublicKey
46
+ ? { ok: true }
47
+ : { ok: false, reason: 'public_key_not_a_current_verification_method' };
48
+ }
49
+ return { ok: false, reason: 'untrusted_issuer' };
50
+ }
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ /**
3
+ * @oxy.so/protocol — the app-agnostic Oxy Protocol base.
4
+ *
5
+ * The reusable substrate any Oxy app can use to decentralize its own content:
6
+ * the signed-record envelope grammar (canonical JSON, signing input, content
7
+ * address), explicit-key signing/verification, and the platform-aware crypto
8
+ * loaders. App-specific lexicons and the chain engine layer on top of this.
9
+ *
10
+ * Platform-agnostic root entry. Node-only pieces live under the `./node`
11
+ * subpath so they never enter React Native / web bundles.
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.loadSharedIdentityBridge = exports.getRandomBytesRN = exports.loadAsyncStorage = exports.loadSecureStore = exports.loadExpoCrypto = exports.loadNodeCrypto = exports.isNodeJS = exports.isReactNative = exports.isAuthorizedKey = exports.verifyCheckpointSignature = exports.signCheckpoint = exports.checkpointHash = exports.checkpointSigningInput = exports.verifyInclusionProof = exports.inclusionProof = exports.buildTransparencyTreeFromHeads = exports.buildTransparencyTree = exports.transparencyLeafHash = exports.EMPTY_TRANSPARENCY_ROOT = exports.verifyAndAppend = exports.DEFAULT_CLOCK_SKEW_MS = exports.verifyEnvelope = exports.checkContinuity = exports.UNCHAINED_SEQ = exports.verifyEnvelopeSignature = exports.signEnvelope = exports.verifySignature = exports.signMessage = exports.computeRecordId = exports.sha256 = exports.signedRecordSigningInput = exports.canonicalize = void 0;
15
+ // ---------------------------------------------------------------------------
16
+ // Envelope — canonical JSON, signing input, content address, signing/verify
17
+ // ---------------------------------------------------------------------------
18
+ var canonicalJson_1 = require("./envelope/canonicalJson");
19
+ Object.defineProperty(exports, "canonicalize", { enumerable: true, get: function () { return canonicalJson_1.canonicalize; } });
20
+ var signingInput_1 = require("./envelope/signingInput");
21
+ Object.defineProperty(exports, "signedRecordSigningInput", { enumerable: true, get: function () { return signingInput_1.signedRecordSigningInput; } });
22
+ var recordId_1 = require("./envelope/recordId");
23
+ Object.defineProperty(exports, "sha256", { enumerable: true, get: function () { return recordId_1.sha256; } });
24
+ Object.defineProperty(exports, "computeRecordId", { enumerable: true, get: function () { return recordId_1.computeRecordId; } });
25
+ var sign_1 = require("./envelope/sign");
26
+ Object.defineProperty(exports, "signMessage", { enumerable: true, get: function () { return sign_1.signMessage; } });
27
+ Object.defineProperty(exports, "verifySignature", { enumerable: true, get: function () { return sign_1.verifySignature; } });
28
+ Object.defineProperty(exports, "signEnvelope", { enumerable: true, get: function () { return sign_1.signEnvelope; } });
29
+ Object.defineProperty(exports, "verifyEnvelopeSignature", { enumerable: true, get: function () { return sign_1.verifyEnvelopeSignature; } });
30
+ var types_1 = require("./chain/types");
31
+ Object.defineProperty(exports, "UNCHAINED_SEQ", { enumerable: true, get: function () { return types_1.UNCHAINED_SEQ; } });
32
+ var continuity_1 = require("./chain/continuity");
33
+ Object.defineProperty(exports, "checkContinuity", { enumerable: true, get: function () { return continuity_1.checkContinuity; } });
34
+ var verify_1 = require("./chain/verify");
35
+ Object.defineProperty(exports, "verifyEnvelope", { enumerable: true, get: function () { return verify_1.verifyEnvelope; } });
36
+ Object.defineProperty(exports, "DEFAULT_CLOCK_SKEW_MS", { enumerable: true, get: function () { return verify_1.DEFAULT_CLOCK_SKEW_MS; } });
37
+ var engine_1 = require("./chain/engine");
38
+ Object.defineProperty(exports, "verifyAndAppend", { enumerable: true, get: function () { return engine_1.verifyAndAppend; } });
39
+ // ---------------------------------------------------------------------------
40
+ // Transparency — Merkle commitment over chain heads + co-signable checkpoints
41
+ // ---------------------------------------------------------------------------
42
+ var tree_1 = require("./transparency/tree");
43
+ Object.defineProperty(exports, "EMPTY_TRANSPARENCY_ROOT", { enumerable: true, get: function () { return tree_1.EMPTY_TRANSPARENCY_ROOT; } });
44
+ Object.defineProperty(exports, "transparencyLeafHash", { enumerable: true, get: function () { return tree_1.transparencyLeafHash; } });
45
+ Object.defineProperty(exports, "buildTransparencyTree", { enumerable: true, get: function () { return tree_1.buildTransparencyTree; } });
46
+ Object.defineProperty(exports, "buildTransparencyTreeFromHeads", { enumerable: true, get: function () { return tree_1.buildTransparencyTreeFromHeads; } });
47
+ Object.defineProperty(exports, "inclusionProof", { enumerable: true, get: function () { return tree_1.inclusionProof; } });
48
+ Object.defineProperty(exports, "verifyInclusionProof", { enumerable: true, get: function () { return tree_1.verifyInclusionProof; } });
49
+ var checkpoint_1 = require("./transparency/checkpoint");
50
+ Object.defineProperty(exports, "checkpointSigningInput", { enumerable: true, get: function () { return checkpoint_1.checkpointSigningInput; } });
51
+ Object.defineProperty(exports, "checkpointHash", { enumerable: true, get: function () { return checkpoint_1.checkpointHash; } });
52
+ Object.defineProperty(exports, "signCheckpoint", { enumerable: true, get: function () { return checkpoint_1.signCheckpoint; } });
53
+ Object.defineProperty(exports, "verifyCheckpointSignature", { enumerable: true, get: function () { return checkpoint_1.verifyCheckpointSignature; } });
54
+ // ---------------------------------------------------------------------------
55
+ // Identity — injected verification-method resolution + authorization rule
56
+ // ---------------------------------------------------------------------------
57
+ var resolver_1 = require("./identity/resolver");
58
+ Object.defineProperty(exports, "isAuthorizedKey", { enumerable: true, get: function () { return resolver_1.isAuthorizedKey; } });
59
+ // ---------------------------------------------------------------------------
60
+ // Platform — runtime predicates + lazy crypto/storage loaders
61
+ // ---------------------------------------------------------------------------
62
+ var platform_1 = require("./platform/platform");
63
+ Object.defineProperty(exports, "isReactNative", { enumerable: true, get: function () { return platform_1.isReactNative; } });
64
+ Object.defineProperty(exports, "isNodeJS", { enumerable: true, get: function () { return platform_1.isNodeJS; } });
65
+ var crypto_1 = require("./platform/crypto");
66
+ Object.defineProperty(exports, "loadNodeCrypto", { enumerable: true, get: function () { return crypto_1.loadNodeCrypto; } });
67
+ Object.defineProperty(exports, "loadExpoCrypto", { enumerable: true, get: function () { return crypto_1.loadExpoCrypto; } });
68
+ Object.defineProperty(exports, "loadSecureStore", { enumerable: true, get: function () { return crypto_1.loadSecureStore; } });
69
+ Object.defineProperty(exports, "loadAsyncStorage", { enumerable: true, get: function () { return crypto_1.loadAsyncStorage; } });
70
+ Object.defineProperty(exports, "getRandomBytesRN", { enumerable: true, get: function () { return crypto_1.getRandomBytesRN; } });
71
+ Object.defineProperty(exports, "loadSharedIdentityBridge", { enumerable: true, get: function () { return crypto_1.loadSharedIdentityBridge; } });