@oxyhq/core 3.11.0 → 3.12.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.
@@ -16,15 +16,42 @@ const ec = new EC('secp256k1');
16
16
  /**
17
17
  * Compute the canonical signing input for a signed-record envelope.
18
18
  *
19
- * This is the single definition of "what the signature covers": the canonical
20
- * JSON of `{version, type, subject, issuer, record, issuedAt}`. `@oxyhq/core`
19
+ * This is the single definition of "what the signature covers". `@oxyhq/core`
21
20
  * (client signing) and `@oxyhq/api` (server verification) both call this, so a
22
21
  * record signed by a client and verified by the server cannot drift.
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.
23
32
  */
24
33
  export function signedRecordSigningInput(fields) {
25
34
  const { version, type, subject, issuer, record, issuedAt } = fields;
35
+ if (version === 2) {
36
+ const { seq, prev, collection, rkey } = fields;
37
+ return canonicalize({ version, type, subject, issuer, record, issuedAt, seq, prev, collection, rkey });
38
+ }
26
39
  return canonicalize({ version, type, subject, issuer, record, issuedAt });
27
40
  }
41
+ /**
42
+ * Compute the `recordId` (content address) of a signed record: the SHA-256 hex
43
+ * digest of its canonical {@link signedRecordSigningInput}.
44
+ *
45
+ * Deterministic and stable across runtimes (it reuses the same canonicalization
46
+ * + SHA-256 the signature itself is built on). The recordId is what `prev`
47
+ * references in the per-subject hash chain, so `@oxyhq/core` (client) and
48
+ * `@oxyhq/api` (server) MUST compute it identically — both call this function.
49
+ * It is taken over the SIGNING input (excluding `publicKey`/`signature`), so it
50
+ * is a pure content address of the record's meaning, independent of who signed.
51
+ */
52
+ export async function computeRecordId(fields) {
53
+ return sha256(signedRecordSigningInput(fields));
54
+ }
28
55
  /**
29
56
  * Compute SHA-256 hash of a string
30
57
  */
@@ -340,6 +367,64 @@ export class SignatureService {
340
367
  signature,
341
368
  };
342
369
  }
370
+ /**
371
+ * Build a signed-record envelope (v2) carrying the per-subject hash-chain
372
+ * fields.
373
+ *
374
+ * Identical to {@link signRecord} (self-issued: `issuer === subject`; same
375
+ * `ES256K-DER-SHA256` scheme over {@link signedRecordSigningInput}) but
376
+ * `version` is `2` and the signed bytes additionally cover the chain fields:
377
+ *
378
+ * @param type - The record category.
379
+ * @param subject - The subject DID the record is about (also the issuer).
380
+ * @param record - The arbitrary record payload to attest to.
381
+ * @param chain - The hash-chain coordinates:
382
+ * - `seq` — strictly-increasing sequence number for this subject's chain.
383
+ * - `prev` — the `recordId` of the previous record, or `null` at genesis.
384
+ * - `collection` + `rkey` — the AtProto-style record key.
385
+ *
386
+ * The caller is responsible for fetching the current chain head (so `seq` /
387
+ * `prev` are correct) before signing. Requires a stored identity; throws if
388
+ * none exists.
389
+ */
390
+ static async signRecordV2(type, subject, record, chain) {
391
+ const publicKey = await KeyManager.getPublicKey();
392
+ if (!publicKey) {
393
+ throw new Error('No identity found. Please create or import an identity first.');
394
+ }
395
+ const version = 2;
396
+ const issuer = subject;
397
+ const issuedAt = Date.now();
398
+ const { seq, prev, collection, rkey } = chain;
399
+ const signingInput = signedRecordSigningInput({
400
+ version,
401
+ type,
402
+ subject,
403
+ issuer,
404
+ record,
405
+ issuedAt,
406
+ seq,
407
+ prev,
408
+ collection,
409
+ rkey,
410
+ });
411
+ const signature = await SignatureService.sign(signingInput);
412
+ return {
413
+ version,
414
+ type,
415
+ subject,
416
+ issuer,
417
+ record,
418
+ issuedAt,
419
+ seq,
420
+ prev,
421
+ collection,
422
+ rkey,
423
+ publicKey,
424
+ alg: 'ES256K-DER-SHA256',
425
+ signature,
426
+ };
427
+ }
343
428
  /**
344
429
  * Verify a signed-record envelope: recompute the canonical signing input from
345
430
  * the envelope's own fields and check the signature against the envelope's
package/dist/esm/index.js CHANGED
@@ -35,6 +35,7 @@ export { OxyAppDataIdentifierError } from './mixins/OxyServices.appData.js';
35
35
  // ---------------------------------------------------------------------------
36
36
  export { getNormalizedUserId, normalizeUserIdentity, normalizeUserIdentityOrNull, } from './utils/userIdentity.js';
37
37
  export { getCanonicalUserHandle, getNormalizedUserHandle, } from './utils/userHandle.js';
38
+ export { normalizeProfileLinks } from './utils/profileLinks.js';
38
39
  // ---------------------------------------------------------------------------
39
40
  // Self-sovereign identity (DID, signed records, auth-method ↔ VM mapping,
40
41
  // verified domains). Wire shapes (DidDocument, SignedRecordEnvelope,
@@ -43,6 +44,15 @@ export { getCanonicalUserHandle, getNormalizedUserHandle, } from './utils/userHa
43
44
  // ---------------------------------------------------------------------------
44
45
  export { buildUserDid } from './mixins/OxyServices.identity.js';
45
46
  // ---------------------------------------------------------------------------
47
+ // Civic / Commons "Oxy ID" (public signed cards + Oxy ID QR payload) and Fase 2
48
+ // anti-gaming (real-life attestation QR + validator/jury). Wire shapes
49
+ // (PublicCard, SignedPublicCard, RealLifeAttestationResult,
50
+ // ValidationRequestSummary, ValidationVoteResult, ValidationVerdict, …) live in
51
+ // `@oxyhq/contracts` — import them from there. The SDK adds the client verdict
52
+ // wrapper, the QR payload parsers/builders, and the submit inputs/results.
53
+ // ---------------------------------------------------------------------------
54
+ export { parseIdPayload, parseAttestPayload, verifyPublicCardAttestation, } from './mixins/OxyServices.civic.js';
55
+ // ---------------------------------------------------------------------------
46
56
  // Auth helpers (token refresh, error normalisation, retry policies)
47
57
  // ---------------------------------------------------------------------------
48
58
  export { SessionSyncRequiredError, AuthenticationFailedError, ensureValidToken, isAuthenticationError, withAuthErrorHandling, authenticatedApiCall, } from './utils/authHelpers.js';
@@ -54,7 +64,7 @@ export { mergeSessions, normalizeAndSortSessions, sessionsArraysEqual, } from '.
54
64
  // Crypto / identity
55
65
  // ---------------------------------------------------------------------------
56
66
  export { KeyManager, IdentityAlreadyExistsError, IdentityPersistError, } from './crypto/keyManager.js';
57
- export { SignatureService, signedRecordSigningInput } from './crypto/signatureService.js';
67
+ export { SignatureService, signedRecordSigningInput, computeRecordId } from './crypto/signatureService.js';
58
68
  export { canonicalize } from './crypto/canonicalJson.js';
59
69
  export { RecoveryPhraseService } from './crypto/recoveryPhrase.js';
60
70
  // ---------------------------------------------------------------------------