diff --git a/.agents/skills/building-with-hypercerts-lexicons/SKILL.md b/.agents/skills/building-with-hypercerts-lexicons/SKILL.md index 5687622..7f5228f 100644 --- a/.agents/skills/building-with-hypercerts-lexicons/SKILL.md +++ b/.agents/skills/building-with-hypercerts-lexicons/SKILL.md @@ -246,18 +246,9 @@ if (result.success) { | **Badge Definition** | `app.certified.badge.definition` | Defines a badge with type, title, icon, optional issuer allowlist | | **Badge Award** | `app.certified.badge.award` | Awards a badge to a user, project, or activity | | **Badge Response** | `app.certified.badge.response` | Recipient accepts or rejects a badge award | -| **EVM Link** | `app.certified.link.evm` | Verifiable ATProto DID to EVM wallet link via EIP-712 signature. Can additionally carry `signatures[]` for record provenance | +| **EVM Link** | `app.certified.link.evm` | Verifiable ATProto DID to EVM wallet link via EIP-712 signature | | **Follow** | `app.certified.graph.follow` | Social-graph follow: declares the author follows another account by DID. Schema-compatible with `app.bsky.graph.follow` | -### Signatures — cryptographic attestation - -| Lexicon | NSID | Purpose | -| ------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Signature Defs** | `app.certified.signature.defs` | Shared type definitions. Provides `#list` (open union array of inline signatures and strongRefs to remote proofs) and `#inline` (the inline signature object shape). Referenced by the `signatures` property on every record lexicon. | -| **Signature Proof** | `app.certified.signature.proof` | Remote attestation proof record holding the CID of attested content. Lives in the attestor's repository and is referenced from the attested record via `com.atproto.repo.strongRef`. | - -All 21 record lexicons carry an optional `signatures` property (a ref to `app.certified.signature.defs#list`). The on-the-wire shape, signing procedure, and verification procedure conform to Nick Gerakines' [ATProtocol Attestation Specification](https://tangled.org/strings/did:plc:cbkjy5n7bk3ax2wplmtjofq2/3m3fy2xuahc22) (see also the [accompanying blog post](https://ngerakines.leaflet.pub/3m3idxul5hc2r)): the signed input is the CID of the record (not its raw bytes), and CID generation injects a `$sig` object carrying the housing repository's DID so signatures cannot be replayed across content versions or across repositories. ECDSA with the low-S variant per BIP-0062 is required; the signing curve (P-256 or K-256) is determined by the multicodec prefix of the verification method's `publicKeyMultibase`. - ## Relationship Map ```text @@ -294,12 +285,6 @@ CERTIFIED actor/organization (org metadata) badge/response ──> badge/award ──> badge/definition graph/follow ───────────> account DID (social follow) - signature/defs (shared #list and #inline defs) - signature/proof (remote attestation proof record) - - Every record lexicon also carries an optional `signatures` array - referencing app.certified.signature.defs#list (not drawn — the - arrow would fan out identically from every record). ``` Every arrow is a `strongRef` or union reference stored on AT Protocol. @@ -498,113 +483,6 @@ const receipt = { }; ``` -### Attaching Cryptographic Signatures - -Any record can carry an optional `signatures` array, enabling platform -attestation and verification that records were created through trusted -services. Two patterns are supported in any combination: - -1. **Inline signatures** — embedded directly in the record via - `app.certified.signature.defs#inline`. -2. **Remote attestations** — references to - `app.certified.signature.proof` records in other repositories via - `com.atproto.repo.strongRef`. - -**Step 1: build an inline signature.** Compute the spec-defined CID -over the record-to-be-signed, then ECDSA-sign it with the platform's -key. Uses [`@atproto/crypto`](https://www.npmjs.com/package/@atproto/crypto) -(ATProto's signing primitives — handles low-S automatically), -[`@ipld/dag-cbor`](https://www.npmjs.com/package/@ipld/dag-cbor) for -canonical encoding, and [`multiformats`](https://www.npmjs.com/package/multiformats) -for CID construction: - -```typescript -import { Secp256k1Keypair } from "@atproto/crypto"; -import * as dagCbor from "@ipld/dag-cbor"; -import { CID } from "multiformats/cid"; -import { sha256 } from "multiformats/hashes/sha2"; -import * as raw from "multiformats/codecs/raw"; -import { ACTIVITY_NSID } from "@hypercerts-org/lexicon"; - -// 1. The record we want to sign (without the signatures field). -const recordToSign = { - $type: ACTIVITY_NSID, - title: "Verified Reforestation Project", - shortDescription: "Planted 1,000 trees in partnership with local community", - createdAt: "2026-05-21T12:00:00.000Z", -}; - -// 2. Insert temporary $sig metadata: attestation type + housing repo DID. -// This binds the signature to a specific repository, preventing -// cross-repo replay. -const platformDid = "did:plc:platform123"; -const cborInput = { - ...recordToSign, - $sig: { - $type: "app.certified.signature.defs#inline", - repository: platformDid, - }, -}; - -// 3. Encode canonically as DAG-CBOR, then build the 36-byte CIDv1 -// (dag-cbor codec 0x71 + SHA-256 multihash). -const cborBytes = dagCbor.encode(cborInput); -const hash = await sha256.digest(cborBytes); -const cid = CID.createV1(dagCbor.code, hash); -const cidBytes = cid.bytes; // 36 bytes: 0x01 0x71 0x12 0x20 + 32-byte hash - -// 4. ECDSA-sign the CID bytes. Keypair.sign() applies the standard -// ECDSA hash-then-sign convention and enforces low-S per BIP-0062. -const keypair = await Secp256k1Keypair.create({ exportable: false }); -const signatureBytes = await keypair.sign(cidBytes); - -const inlineSignature = { - $type: "app.certified.signature.defs#inline" as const, - signature: signatureBytes, - key: `${platformDid}#signing`, // DID verification method reference -}; -``` - -**Step 2: build a remote attestation reference** (optional — only if -the attestation lives in another repo's proof record): - -```typescript -const remoteAttestation = { - $type: "com.atproto.repo.strongRef" as const, - uri: "at://did:plc:verifier/app.certified.signature.proof/abc123", - cid: "bafy...", // CID of the proof record being referenced -}; -``` - -**Step 3: attach to the record.** Both shapes can coexist in the same -array; consumers verify each entry independently. - -```typescript -const signedActivity = { - ...recordToSign, - signatures: [inlineSignature, remoteAttestation], -}; -``` - -See the -[ATProtocol Attestation Specification](https://tangled.org/strings/did:plc:cbkjy5n7bk3ax2wplmtjofq2/3m3fy2xuahc22) -for the full procedure and verification rules, and the -[accompanying blog post](https://ngerakines.leaflet.pub/3m3idxul5hc2r) -for background and motivation. - -### Creating a Remote Attestation Proof - -```typescript -import { SIGNATURE_PROOF_NSID } from "@hypercerts-org/lexicon"; - -const proof = { - $type: SIGNATURE_PROOF_NSID, - cid: "bafy...", // CID of the attested content (computed as above) - note: "Verified by platform quality assurance process", - createdAt: new Date().toISOString(), -}; -``` - ## Key Concepts ### strongRef diff --git a/.changeset/add-signature-support.md b/.changeset/add-signature-support.md deleted file mode 100644 index a8c615e..0000000 --- a/.changeset/add-signature-support.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -"@hypercerts-org/lexicon": minor ---- - -Add cryptographic signature support to all lexicons - -Adds optional cryptographic signature support to all record lexicons, enabling platform attestation and verification that records were created through trusted services. The on-the-wire shape, signing procedure, and verification procedure all conform to Nick Gerakines' [ATProtocol Attestation Specification](https://tangled.org/strings/did:plc:cbkjy5n7bk3ax2wplmtjofq2/3m3fy2xuahc22) (see also the [accompanying blog post](https://ngerakines.leaflet.pub/3m3idxul5hc2r)). - -**New lexicons:** - -- `app.certified.signature.defs` - Shared type definitions for signatures. Provides: - - `#list` - open union array of inline signatures and `com.atproto.repo.strongRef` references to remote proofs - - `#inline` - inline signature object with two required fields: `signature` (ECDSA bytes, low-S per BIP-0062, signing the record's CID) and `key` (DID verification method reference). The signing curve is determined by the multicodec prefix on the verification method's `publicKeyMultibase`, so no separate algorithm-tag field is carried on the signature itself. -- `app.certified.signature.proof` - Remote attestation proof record containing the CID of the attested content (computed with the spec's `$sig`-repository binding). - -**Changes to existing lexicons:** - -All 21 record lexicons now include an optional `signatures` property (a ref to `app.certified.signature.defs#list`) placed directly on the record with no wrapper object: - -- `org.hypercerts.claim.activity` -- `org.hypercerts.claim.contribution` -- `org.hypercerts.claim.contributorInformation` -- `org.hypercerts.claim.rights` -- `org.hypercerts.collection` -- `org.hypercerts.context.acknowledgement` -- `org.hypercerts.context.attachment` -- `org.hypercerts.context.evaluation` -- `org.hypercerts.context.measurement` -- `org.hypercerts.funding.receipt` -- `org.hypercerts.workscope.tag` -- `org.hyperboards.board` -- `org.hyperboards.displayProfile` -- `app.certified.badge.award` -- `app.certified.badge.definition` -- `app.certified.badge.response` -- `app.certified.actor.profile` -- `app.certified.actor.organization` -- `app.certified.location` -- `app.certified.graph.follow` -- `app.certified.link.evm` - -Note on `app.certified.link.evm`: this record carries two orthogonal integrity primitives. The EIP-712 `proof` field proves wallet consent (the EVM key holder agreed to be linked to the DID). The `signatures` array proves record provenance (e.g. that a platform UI minted the record, defending against replay of harvested EIP-712 signatures). Both are useful and they do not conflict. - -**Design notes:** - -This is a non-breaking extension - signatures are optional on all records. Two signature patterns are supported: - -1. **Inline signatures** - Embedded directly in the record via `app.certified.signature.defs#inline`. -2. **Remote attestations** - References to proof records in other repositories via `com.atproto.repo.strongRef`. diff --git a/.gitignore b/.gitignore index bb8ffb4..d9a2185 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ dist/ generated/ pnpm-lock.yaml /tmp/ -tests/extracted/ lexicons.md # Dolt database files (added by bd init) diff --git a/AGENTS.md b/AGENTS.md index 07566ed..7e5b436 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -294,25 +294,10 @@ npm run check ### Writing Tests -Tests live in `tests/` and by default use one file per lexicon, named +Tests live in `tests/` with one file per lexicon, named `validate-.test.ts` (e.g. `validate-link-evm.test.ts`, `validate-rights.test.ts`). -This is a convention, not a strict rule. When a test genuinely -exercises behavior that spans multiple lexicons or code files — -shared `*.defs` lexicons referenced from many records, cross-cutting -union semantics, validation behavior that surfaces only in -composition, etc. — pick the clearest, most concise name that -describes what's actually under test, even if that file doesn't map -1:1 to a single lexicon. Don't duplicate near-identical tests across -every carrier just to satisfy the naming convention. - -For example, `validate-signature-defs.test.ts` exercises -`app.certified.signature.defs#list` (union of inline + strongRef, -array semantics) via a representative record carrier, rather than -spawning 19 near-identical files for every record lexicon that -references the def. - The generated code provides two ways to validate records: - **`validate()` from `generated/lexicons.js`** — generic, untyped. diff --git a/ERD.puml b/ERD.puml index 01304e9..130db6c 100644 --- a/ERD.puml +++ b/ERD.puml @@ -240,26 +240,6 @@ dataclass rights { !endif } -' Signature-related lexicons (app.certified.signature.defs#inline, -' app.certified.signature.defs#list, app.certified.signature.proof) -' are deliberately omitted from this diagram. -' -' Every record lexicon carries an optional `signatures` array referencing -' app.certified.signature.defs#list, so the relationship is uniform across -' all ~20 record entities. Drawing those arrows would add a thicket of -' identical "signatures" edges from every dataclass on the diagram without -' communicating any additional structure. The signature-related lexicons -' are documented in SCHEMAS.md and README.md instead. - -' app.certified.link.evm -dataclass linkEvm { - !if (SHOW_FIELDS == "true") - address - proof - createdAt - !endif -} - ' org.hypercerts.collection dataclass collection { !if (SHOW_FIELDS == "true") @@ -425,7 +405,4 @@ follow::subject --> contributorEntity : follows ' This screws up the layout 'badgeAward::subject --[norank]-> collection -' Signature relationships are intentionally not drawn here -- see the -' comment near the top of the file (before linkEvm) for rationale. - @enduml diff --git a/README.md b/README.md index ff69bb4..831dd34 100644 --- a/README.md +++ b/README.md @@ -46,8 +46,6 @@ CERTIFIED ─ shared lexicons (certified.app) actor/organization (org metadata) badge/response ──► badge/award ──► badge/definition graph/follow ────────────► account DID (social follow) - signature/defs (shared #list and #inline defs) - signature/proof (remote attestation proof record) ``` Every arrow (`►`) is a `strongRef` or union reference stored on the @@ -280,17 +278,6 @@ await agent.api.com.atproto.repo.createRecord({ | **EVM Link** | `app.certified.link.evm` | Verifiable ATProto DID ↔ EVM wallet link via EIP-712 signature. Extensible for future proof methods (e.g. ERC-1271, ERC-6492). | | **Follow** | `app.certified.graph.follow` | Social-graph follow relationship — declares that the author follows another account by DID. Schema-compatible with `app.bsky.graph.follow`. | -### Signatures (`app.certified.signature.*`) - -| Lexicon | NSID | Description | -| --------------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Shared Defs** | `app.certified.signature.defs` | Shared type definitions for signatures. Provides the `#list` array def (a union of inline signatures and strongRefs) and the `#inline` object def (the inline signature shape), all referenced by the `signatures` property on records. | -| **Proof** | `app.certified.signature.proof` | Remote attestation proof record containing the CID of attested content. Lives in the attestor's repository and can be referenced via strongRef. | - -All record lexicons include an optional `signatures` property (a ref to `app.certified.signature.defs#list`) enabling cryptographic attestations. See [Cryptographic Signatures](#cryptographic-signatures) for usage details. - -Note on `app.certified.link.evm`: it carries _two_ orthogonal integrity primitives. The EIP-712 `proof` field proves wallet consent (the EVM key holder agreed to be linked to the DID). The `signatures` array proves record provenance (e.g. that a platform UI minted the record, defending against replay of harvested EIP-712 signatures). Both are useful and they do not conflict. - > **Full property tables** → [SCHEMAS.md](SCHEMAS.md) ## Schema Conventions @@ -577,122 +564,6 @@ const attachment = { }; ``` -### Cryptographic Signatures - -All record lexicons support optional cryptographic signatures via the `signatures` property. This enables platform attestation and verification that records were created through trusted services. - -The on-the-wire shape, signing procedure, and verification procedure all conform to Nick Gerakines' [ATProtocol Attestation Specification](https://tangled.org/strings/did:plc:cbkjy5n7bk3ax2wplmtjofq2/3m3fy2xuahc22) (see also the [accompanying blog post](https://ngerakines.leaflet.pub/3m3idxul5hc2r)). In particular, signatures sign the **CID** of the record (not its raw bytes), and CID generation injects a temporary `$sig` object carrying the housing repository's DID so that signatures cannot be replayed across content versions or across repositories. - -Two patterns are supported: - -1. **Inline signatures**: Embedded directly in the record via `app.certified.signature.defs#inline`. -2. **Remote attestations**: References to `app.certified.signature.proof` records in other repositories, via `com.atproto.repo.strongRef`. - -#### Signing Algorithm - -The spec mandates **ECDSA** with the low-S variant per BIP-0062. The signing curve is determined by the multicodec prefix of the verification method's `publicKeyMultibase` in the signer's DID document: - -| Multicodec prefix | Curve | JOSE alg | -| ----------------- | ----------------- | -------- | -| `0xE701` | secp256k1 (K-256) | `ES256K` | -| `0x1200` | P-256 (secp256r1) | `ES256` | - -There is deliberately no `signatureType` field on the inline signature: the algorithm is canonically derived from the resolved key, and a separate tag would risk disagreeing with the multicodec. - -#### Step 1: Build an Inline Signature - -Compute the spec-defined CID over the record-to-be-signed, then ECDSA-sign it with the platform's key. Uses [`@atproto/crypto`](https://www.npmjs.com/package/@atproto/crypto) (ATProto's signing primitives — handles low-S automatically), [`@ipld/dag-cbor`](https://www.npmjs.com/package/@ipld/dag-cbor) for canonical encoding, and [`multiformats`](https://www.npmjs.com/package/multiformats) for CID construction: - -```typescript -import { Secp256k1Keypair } from "@atproto/crypto"; -import * as dagCbor from "@ipld/dag-cbor"; -import { CID } from "multiformats/cid"; -import { sha256 } from "multiformats/hashes/sha2"; -import { ACTIVITY_NSID } from "@hypercerts-org/lexicon"; - -// 1. The record we want to sign (without the signatures field). -const recordToSign = { - $type: ACTIVITY_NSID, - title: "Verified Reforestation Project", - shortDescription: "Planted 1,000 trees in partnership with local community", - createdAt: "2026-05-21T12:00:00.000Z", -}; - -// 2. Insert temporary $sig metadata: attestation type + housing repo DID. -// This binds the signature to a specific repository, preventing -// cross-repo replay. -const platformDid = "did:plc:platform123"; -const cborInput = { - ...recordToSign, - $sig: { - $type: "app.certified.signature.defs#inline", - repository: platformDid, - }, -}; - -// 3. Encode canonically as DAG-CBOR, then build the 36-byte CIDv1 -// (dag-cbor codec 0x71 + SHA-256 multihash). -const cborBytes = dagCbor.encode(cborInput); -const hash = await sha256.digest(cborBytes); -const cid = CID.createV1(dagCbor.code, hash); -const cidBytes = cid.bytes; // 36 bytes: 0x01 0x71 0x12 0x20 + 32-byte hash - -// 4. ECDSA-sign the CID bytes. Keypair.sign() applies the standard -// ECDSA hash-then-sign convention and enforces low-S per BIP-0062. -const keypair = await Secp256k1Keypair.create({ exportable: false }); -const signatureBytes = await keypair.sign(cidBytes); - -const inlineSignature = { - $type: "app.certified.signature.defs#inline" as const, - signature: signatureBytes, - key: `${platformDid}#signing`, // DID verification method reference -}; -``` - -#### Step 2: Build a Remote Attestation Reference (Optional) - -Only if the attestation lives in another repo's proof record: - -```typescript -const remoteAttestation = { - $type: "com.atproto.repo.strongRef" as const, - uri: "at://did:plc:verifier/app.certified.signature.proof/abc123", - cid: "bafy...", // CID of the proof record being referenced -}; -``` - -#### Step 3: Attach to the Record - -Both shapes can coexist in the same array; consumers verify each entry independently. - -```typescript -const signedActivity = { - ...recordToSign, - signatures: [inlineSignature, remoteAttestation], -}; -``` - -#### Verifying a Signature - -To **verify**, reconstruct the same CID using the housing repo's DID, resolve `key` to a public key via the DID document, and check the ECDSA signature against the CID bytes. - -#### Remote Attestation Proofs - -For remote attestations, create a proof record in the attestor's repository: - -```typescript -import { SIGNATURE_PROOF_NSID } from "@hypercerts-org/lexicon"; - -const proof = { - $type: SIGNATURE_PROOF_NSID, - cid: "bafy...", // CID of the attested content (computed as above) - note: "Verified by platform quality assurance process", - createdAt: new Date().toISOString(), -}; -``` - -For the full specification, see Nick Gerakines' [ATProtocol Attestation Specification](https://tangled.org/strings/did:plc:cbkjy5n7bk3ax2wplmtjofq2/3m3fy2xuahc22). For background and motivation, see the [accompanying blog post](https://ngerakines.leaflet.pub/3m3idxul5hc2r). - ## Development ### Commands diff --git a/SCHEMAS.md b/SCHEMAS.md index ef61544..6e4f150 100644 --- a/SCHEMAS.md +++ b/SCHEMAS.md @@ -29,7 +29,6 @@ Hypercerts-specific lexicons for tracking impact work and claims. | `locations` | `ref[]` | ❌ | An array of strong references to the location where activity was performed. The record referenced must conform with the lexicon app.certified.location. | maxLength: 1000 | | `rights` | `ref` | ❌ | A strong reference to the rights that this hypercert has. The record referenced must conform with the lexicon org.hypercerts.claim.rights. | | | `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created | | -| `signatures` | `ref` | ❌ | Optional cryptographic signatures attesting to this record's content. | | #### Defs @@ -84,7 +83,6 @@ A free-form string describing the work scope for simple or legacy scopes. | `startDate` | `string` | ❌ | When this contribution started. Should fall within the parent hypercert's timeframe. | | | `endDate` | `string` | ❌ | When this contribution finished. Should fall within the parent hypercert's timeframe. | | | `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created. | | -| `signatures` | `ref` | ❌ | Optional cryptographic signatures attesting to this record's content. | | --- @@ -96,13 +94,12 @@ A free-form string describing the work scope for simple or legacy scopes. #### Properties -| Property | Type | Required | Description | Comments | -| ------------- | -------- | -------- | --------------------------------------------------------------------- | --------------- | -| `identifier` | `string` | ❌ | DID (did:plc:...) or URI to a social profile of the contributor. | maxLength: 2048 | -| `displayName` | `string` | ❌ | Human-readable name for the contributor as it should appear in UI. | maxLength: 100 | -| `image` | `union` | ❌ | The contributor visual representation as a URI or image blob. | | -| `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created. | | -| `signatures` | `ref` | ❌ | Optional cryptographic signatures attesting to this record's content. | | +| Property | Type | Required | Description | Comments | +| ------------- | -------- | -------- | ------------------------------------------------------------------ | --------------- | +| `identifier` | `string` | ❌ | DID (did:plc:...) or URI to a social profile of the contributor. | maxLength: 2048 | +| `displayName` | `string` | ❌ | Human-readable name for the contributor as it should appear in UI. | maxLength: 100 | +| `image` | `union` | ❌ | The contributor visual representation as a URI or image blob. | | +| `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created. | | --- @@ -121,7 +118,6 @@ A free-form string describing the work scope for simple or legacy scopes. | `rightsDescription` | `string` | ✅ | Detailed explanation of the rights holders' permissions, restrictions, and conditions | maxLength: 10000, maxGraphemes: 1000 | | `attachment` | `union` | ❌ | An attachment to define the rights further, e.g. a legal document. | | | `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created | | -| `signatures` | `ref` | ❌ | Optional cryptographic signatures attesting to this record's content. | | --- @@ -145,7 +141,6 @@ A free-form string describing the work scope for simple or legacy scopes. | `items` | `ref[]` | ❌ | Array of items in this collection with optional weights. | maxLength: 1000 | | `location` | `ref` | ❌ | A strong reference to the location where this collection's activities were performed. The record referenced must conform with the lexicon app.certified.location. | | | `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created | | -| `signatures` | `ref` | ❌ | Optional cryptographic signatures attesting to this record's content. | | #### Defs @@ -175,7 +170,6 @@ An item in a collection, with an identifier and optional weight. | `acknowledged` | `boolean` | ✅ | Whether the relationship is acknowledged (true) or rejected (false). | | | `comment` | `string` | ❌ | Optional plain-text comment providing additional context or reasoning. | maxLength: 10000, maxGraphemes: 1000 | | `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created. | | -| `signatures` | `ref` | ❌ | Optional cryptographic signatures attesting to this record's content. | | --- @@ -198,7 +192,6 @@ An item in a collection, with an identifier and optional weight. | `description` | `union` | ❌ | Long-form description of the attachment. An inline string for plain text or markdown, a Leaflet linear document for rich-text content, or a strong reference to an external description record. | | | `location` | `ref` | ❌ | A strong reference to the location where this attachment's subject matter occurred. The record referenced must conform with the lexicon app.certified.location. | | | `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created. | | -| `signatures` | `ref` | ❌ | Optional cryptographic signatures attesting to this record's content. | | --- @@ -220,7 +213,6 @@ An item in a collection, with an identifier and optional weight. | `score` | `ref` | ❌ | Overall score for an evaluation on a numeric scale. | | | `location` | `ref` | ❌ | An optional reference for georeferenced evaluations. The record referenced must conform with the lexicon app.certified.location. | | | `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created | | -| `signatures` | `ref` | ❌ | Optional cryptographic signatures attesting to this record's content. | | #### Defs @@ -260,7 +252,6 @@ Overall score for an evaluation on a numeric scale. | `comment` | `string` | ❌ | Short comment of this measurement, suitable for previews and list views. Rich text annotations may be provided via `commentFacets`. | maxLength: 3000, maxGraphemes: 300 | | `commentFacets` | `ref[]` | ❌ | Rich text annotations for `comment` (mentions, URLs, hashtags, etc). | | | `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created | | -| `signatures` | `ref` | ❌ | Optional cryptographic signatures attesting to this record's content. | | --- @@ -285,7 +276,6 @@ Overall score for an evaluation on a numeric scale. | `notes` | `string` | ❌ | Optional notes or additional context for this funding receipt. | maxLength: 500 | | `occurredAt` | `string` | ❌ | Timestamp when the payment occurred. | | | `createdAt` | `string` | ✅ | Client-declared timestamp when this receipt record was created. | | -| `signatures` | `ref` | ❌ | Optional cryptographic signatures attesting to this record's content. | | #### Defs @@ -335,7 +325,6 @@ A free-text string value (e.g. a display name, wallet address, or other identifi | `sameAs` | `string[]` | ❌ | URIs to semantically equivalent concepts in external ontologies or taxonomies (e.g., Wikidata QIDs, ENVO terms, SDG targets). Used for interoperability, not as documentation. | maxLength: 20 | | `referenceDocument` | `union` | ❌ | Link to a governance or reference document where this work scope tag is defined and further explained. | | | `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created. | | -| `signatures` | `ref` | ❌ | Optional cryptographic signatures attesting to this record's content. | | --- @@ -360,7 +349,6 @@ Certified lexicons are common/shared lexicons that can be used across multiple p | `name` | `string` | ❌ | Human-readable name for this location (e.g. 'Golden Gate Park', 'San Francisco Bay Area') | maxLength: 1000, maxGraphemes: 100 | | `description` | `string` | ❌ | Additional context about this location, such as its significance to the work or specific boundaries | maxLength: 2000, maxGraphemes: 500 | | `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created | | -| `signatures` | `ref` | ❌ | Optional cryptographic signatures attesting to this record's content. | | #### Defs @@ -390,7 +378,6 @@ A location represented as a string, e.g. coordinates or a small GeoJSON string. | `description` | `string` | ❌ | Optional short statement describing what the badge represents. | maxLength: 5000, maxGraphemes: 500 | | `allowedIssuers` | `ref[]` | ❌ | Optional allowlist of DIDs allowed to issue this badge. If omitted, anyone may issue it. | maxLength: 100 | | `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created | | -| `signatures` | `ref` | ❌ | Optional cryptographic signatures attesting to this record's content. | | --- @@ -402,14 +389,13 @@ A location represented as a string, e.g. coordinates or a small GeoJSON string. #### Properties -| Property | Type | Required | Description | Comments | -| ------------ | -------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -| `badge` | `ref` | ✅ | Strong reference to the badge definition at the time of award. The record referenced must conform with the lexicon app.certified.badge.definition. | | -| `subject` | `union` | ✅ | Entity the badge award is for (either an account DID or any specific AT Protocol record), e.g. a user, a project, or a specific activity claim. | | -| `note` | `string` | ❌ | Optional statement explaining the reason for this badge award. | maxLength: 500 | -| `url` | `string` | ❌ | Optional URL the badge award links to. | maxLength: 2048 | -| `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created | | -| `signatures` | `ref` | ❌ | Optional cryptographic signatures attesting to this record's content. | | +| Property | Type | Required | Description | Comments | +| ----------- | -------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | +| `badge` | `ref` | ✅ | Strong reference to the badge definition at the time of award. The record referenced must conform with the lexicon app.certified.badge.definition. | | +| `subject` | `union` | ✅ | Entity the badge award is for (either an account DID or any specific AT Protocol record), e.g. a user, a project, or a specific activity claim. | | +| `note` | `string` | ❌ | Optional statement explaining the reason for this badge award. | maxLength: 500 | +| `url` | `string` | ❌ | Optional URL the badge award links to. | maxLength: 2048 | +| `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created | | --- @@ -427,7 +413,6 @@ A location represented as a string, e.g. coordinates or a small GeoJSON string. | `response` | `string` | ✅ | The recipient’s response for the badge (accepted or rejected). | Known values: `accepted`, `rejected` | | `weight` | `string` | ❌ | Optional relative weight for accepted badges, assigned by the recipient. | maxLength: 50 | | `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created | | -| `signatures` | `ref` | ❌ | Optional cryptographic signatures attesting to this record's content. | | --- @@ -448,7 +433,6 @@ A location represented as a string, e.g. coordinates or a small GeoJSON string. | `longDescription` | `union` | ❌ | Long-form description of the organization, such as its mission, history, or detailed project narrative. An inline string for plain text or markdown, a Leaflet linear document record embedded directly, or a strong reference to an existing document record. | | | `visibility` | `string` | ❌ | Controls whether the organization or project is publicly discoverable on platforms that honor this setting. | Known values: `public`, `unlisted` | | `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created. | | -| `signatures` | `ref` | ❌ | Optional cryptographic signatures attesting to this record's content. | | #### Defs @@ -480,7 +464,6 @@ A labeled URL reference. | `avatar` | `union` | ❌ | Small image to be displayed next to posts from account. AKA, 'profile picture' | | | `banner` | `union` | ❌ | Larger horizontal image to display behind profile view. | | | `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created | | -| `signatures` | `ref` | ❌ | Optional cryptographic signatures attesting to this record's content. | | --- @@ -492,12 +475,11 @@ A labeled URL reference. #### Properties -| Property | Type | Required | Description | -| ------------ | -------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `subject` | `string` | ✅ | DID of the account being followed. | -| `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created. | -| `via` | `ref` | ❌ | Optional strong reference to a record that mediated this follow (e.g. a starter pack or other curated list). Mirrors the optional `via` field on app.bsky.graph.follow; the referenced record may conform with any lexicon. | -| `signatures` | `ref` | ❌ | Optional cryptographic signatures attesting to this record's content. | +| Property | Type | Required | Description | +| ----------- | -------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `subject` | `string` | ✅ | DID of the account being followed. | +| `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created. | +| `via` | `ref` | ❌ | Optional strong reference to a record that mediated this follow (e.g. a starter pack or other curated list). Mirrors the optional `via` field on app.bsky.graph.follow; the referenced record may conform with any lexicon. | --- @@ -509,12 +491,11 @@ A labeled URL reference. #### Properties -| Property | Type | Required | Description | Comments | -| ------------ | -------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | -| `address` | `string` | ✅ | EVM wallet address (0x-prefixed, with EIP-55 checksum recommended). | maxLength: 42 | -| `proof` | `union` | ✅ | Cryptographic proof of wallet ownership. The union is open to allow future proof methods (e.g. ERC-1271, ERC-6492). Each variant bundles its signature with the corresponding message format. | | -| `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created. | | -| `signatures` | `ref` | ❌ | Optional cryptographic signatures attesting to this record's content. The EIP-712 `proof` field proves wallet consent (orthogonal trust statement); `signatures` proves record provenance (e.g. that a platform UI minted this record). | | +| Property | Type | Required | Description | Comments | +| ----------- | -------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | +| `address` | `string` | ✅ | EVM wallet address (0x-prefixed, with EIP-55 checksum recommended). | maxLength: 42 | +| `proof` | `union` | ✅ | Cryptographic proof of wallet ownership. The union is open to allow future proof methods (e.g. ERC-1271, ERC-6492). Each variant bundles its signature with the corresponding message format. | | +| `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created. | | #### Defs @@ -541,39 +522,6 @@ The EIP-712 typed data message that was signed by the wallet. Contains the field --- -### `app.certified.signature.defs` - -**Description:** Common type definitions for cryptographic signatures attached to records, per the ATProtocol Attestation Specification. - -#### Defs - -##### `app.certified.signature.defs#inline` - -Inline attestation signature embedded directly in a record. Conforms to the ATProtocol Attestation Specification: the signed input is the 36-byte CIDv1 (dag-cbor + SHA-256) of the record with the `signatures` field removed and a temporary `$sig` metadata object (containing `$type` and the housing repository DID) inserted before canonical DAG-CBOR encoding. ECDSA with the low-S variant per BIP-0062 is required; the curve (P-256 or K-256) is determined by the multicodec prefix of the verification method's `publicKeyMultibase`. - -| Property | Type | Required | Description | -| ----------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `signature` | `bytes` | ✅ | ECDSA signature bytes (raw r,s) over the 36-byte CID of the record. Low-S variant per BIP-0062 is mandatory. | -| `key` | `string` | ✅ | Full DID verification method reference (format: did:{method}:{identifier}#{fragment}). Identifies the signer and the specific key used; the key's multicodec prefix determines the signing curve. | - ---- - -### `app.certified.signature.proof` - -**Description:** Remote attestation proof containing the CID of the content being attested. - -**Key:** `tid` - -#### Properties - -| Property | Type | Required | Description | Comments | -| ----------- | -------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | -| `cid` | `string` | ✅ | CID of the attested content, computed per the ATProtocol Attestation Specification: encode the record with the `signatures` field removed and a temporary `$sig` object inserted (containing `$type` and the housing `repository` DID) as canonical DAG-CBOR, then SHA-256 hash to produce a 36-byte CIDv1. | | -| `note` | `string` | ❌ | Optional note explaining the attestation purpose or context. | maxLength: 500 | -| `createdAt` | `string` | ❌ | Client-declared timestamp when this proof was created. | | - ---- - ## Type Definitions Common type definitions used across all protocols. @@ -736,7 +684,6 @@ Specifies the sub-string range a facet feature applies to. Start index is inclus | `config` | `ref` | ❌ | Visual configuration for a hyperboard's background, colors, and layout. | | | `contributorConfigs` | `ref[]` | ❌ | Per-contributor configuration entries for this board. | maxLength: 1000 | | `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created. | | -| `signatures` | `ref` | ❌ | Optional cryptographic signatures attesting to this record's content. | | #### Defs @@ -791,7 +738,6 @@ Configuration for a specific contributor within a board. Values serve as fallbac | `hoverIframeUrl` | `string` | ❌ | Default hover iframe URL for this user across boards. | maxLength: 2048 | | `url` | `string` | ❌ | Default click-through link URL for this user across boards. | maxLength: 2048 | | `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created. | | -| `signatures` | `ref` | ❌ | Optional cryptographic signatures attesting to this record's content. | | --- diff --git a/lexicons/app/certified/actor/organization.json b/lexicons/app/certified/actor/organization.json index c736416..fbea68f 100644 --- a/lexicons/app/certified/actor/organization.json +++ b/lexicons/app/certified/actor/organization.json @@ -56,11 +56,6 @@ "type": "string", "format": "datetime", "description": "Client-declared timestamp when this record was originally created." - }, - "signatures": { - "type": "ref", - "ref": "app.certified.signature.defs#list", - "description": "Optional cryptographic signatures attesting to this record's content." } } } diff --git a/lexicons/app/certified/actor/profile.json b/lexicons/app/certified/actor/profile.json index e9252f1..c5a9894 100644 --- a/lexicons/app/certified/actor/profile.json +++ b/lexicons/app/certified/actor/profile.json @@ -53,11 +53,6 @@ "type": "string", "format": "datetime", "description": "Client-declared timestamp when this record was originally created" - }, - "signatures": { - "type": "ref", - "ref": "app.certified.signature.defs#list", - "description": "Optional cryptographic signatures attesting to this record's content." } } } diff --git a/lexicons/app/certified/badge/award.json b/lexicons/app/certified/badge/award.json index ed6c08d..20854eb 100644 --- a/lexicons/app/certified/badge/award.json +++ b/lexicons/app/certified/badge/award.json @@ -35,11 +35,6 @@ "type": "string", "format": "datetime", "description": "Client-declared timestamp when this record was originally created" - }, - "signatures": { - "type": "ref", - "ref": "app.certified.signature.defs#list", - "description": "Optional cryptographic signatures attesting to this record's content." } } } diff --git a/lexicons/app/certified/badge/definition.json b/lexicons/app/certified/badge/definition.json index 8659242..809f82a 100644 --- a/lexicons/app/certified/badge/definition.json +++ b/lexicons/app/certified/badge/definition.json @@ -58,11 +58,6 @@ "type": "string", "format": "datetime", "description": "Client-declared timestamp when this record was originally created" - }, - "signatures": { - "type": "ref", - "ref": "app.certified.signature.defs#list", - "description": "Optional cryptographic signatures attesting to this record's content." } } } diff --git a/lexicons/app/certified/badge/response.json b/lexicons/app/certified/badge/response.json index 1ae27ac..168ab7b 100644 --- a/lexicons/app/certified/badge/response.json +++ b/lexicons/app/certified/badge/response.json @@ -29,11 +29,6 @@ "type": "string", "format": "datetime", "description": "Client-declared timestamp when this record was originally created" - }, - "signatures": { - "type": "ref", - "ref": "app.certified.signature.defs#list", - "description": "Optional cryptographic signatures attesting to this record's content." } } } diff --git a/lexicons/app/certified/graph/follow.json b/lexicons/app/certified/graph/follow.json index 4fe43f3..7bfc41f 100644 --- a/lexicons/app/certified/graph/follow.json +++ b/lexicons/app/certified/graph/follow.json @@ -24,11 +24,6 @@ "type": "ref", "ref": "com.atproto.repo.strongRef", "description": "Optional strong reference to a record that mediated this follow (e.g. a starter pack or other curated list). Mirrors the optional `via` field on app.bsky.graph.follow; the referenced record may conform with any lexicon." - }, - "signatures": { - "type": "ref", - "ref": "app.certified.signature.defs#list", - "description": "Optional cryptographic signatures attesting to this record's content." } } } diff --git a/lexicons/app/certified/link/evm.json b/lexicons/app/certified/link/evm.json index e2906f9..f22427e 100644 --- a/lexicons/app/certified/link/evm.json +++ b/lexicons/app/certified/link/evm.json @@ -25,11 +25,6 @@ "type": "string", "format": "datetime", "description": "Client-declared timestamp when this record was originally created." - }, - "signatures": { - "type": "ref", - "ref": "app.certified.signature.defs#list", - "description": "Optional cryptographic signatures attesting to this record's content. The EIP-712 `proof` field proves wallet consent (orthogonal trust statement); `signatures` proves record provenance (e.g. that a platform UI minted this record)." } } } diff --git a/lexicons/app/certified/location.json b/lexicons/app/certified/location.json index 644def3..853a6b3 100644 --- a/lexicons/app/certified/location.json +++ b/lexicons/app/certified/location.json @@ -67,11 +67,6 @@ "type": "string", "format": "datetime", "description": "Client-declared timestamp when this record was originally created" - }, - "signatures": { - "type": "ref", - "ref": "app.certified.signature.defs#list", - "description": "Optional cryptographic signatures attesting to this record's content." } } } diff --git a/lexicons/app/certified/signature/defs.json b/lexicons/app/certified/signature/defs.json deleted file mode 100644 index 32aec45..0000000 --- a/lexicons/app/certified/signature/defs.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "lexicon": 1, - "id": "app.certified.signature.defs", - "description": "Common type definitions for cryptographic signatures attached to records, per the ATProtocol Attestation Specification.", - "defs": { - "list": { - "type": "array", - "description": "Reusable array of cryptographic signatures attesting to a record's content. Open union of inline signatures and strong references to remote attestation proof records.", - "items": { - "type": "union", - "refs": [ - "app.certified.signature.defs#inline", - "com.atproto.repo.strongRef" - ] - } - }, - "inline": { - "type": "object", - "description": "Inline attestation signature embedded directly in a record. Conforms to the ATProtocol Attestation Specification: the signed input is the 36-byte CIDv1 (dag-cbor + SHA-256) of the record with the `signatures` field removed and a temporary `$sig` metadata object (containing `$type` and the housing repository DID) inserted before canonical DAG-CBOR encoding. ECDSA with the low-S variant per BIP-0062 is required; the curve (P-256 or K-256) is determined by the multicodec prefix of the verification method's `publicKeyMultibase`.", - "required": ["signature", "key"], - "properties": { - "signature": { - "type": "bytes", - "description": "ECDSA signature bytes (raw r,s) over the 36-byte CID of the record. Low-S variant per BIP-0062 is mandatory." - }, - "key": { - "type": "string", - "description": "Full DID verification method reference (format: did:{method}:{identifier}#{fragment}). Identifies the signer and the specific key used; the key's multicodec prefix determines the signing curve.", - "maxLength": 512 - } - } - } - } -} diff --git a/lexicons/app/certified/signature/proof.json b/lexicons/app/certified/signature/proof.json deleted file mode 100644 index fa20760..0000000 --- a/lexicons/app/certified/signature/proof.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "lexicon": 1, - "id": "app.certified.signature.proof", - "description": "A remote attestation proof record holding the CID of attested content. Lives in the attestor's repository and is referenced from the attested record's `signatures` array via `com.atproto.repo.strongRef`. Conforms to the ATProtocol Attestation Specification.", - "defs": { - "main": { - "type": "record", - "description": "Remote attestation proof containing the CID of the content being attested.", - "key": "tid", - "record": { - "type": "object", - "required": ["cid"], - "properties": { - "cid": { - "type": "string", - "format": "cid", - "description": "CID of the attested content, computed per the ATProtocol Attestation Specification: encode the record with the `signatures` field removed and a temporary `$sig` object inserted (containing `$type` and the housing `repository` DID) as canonical DAG-CBOR, then SHA-256 hash to produce a 36-byte CIDv1." - }, - "note": { - "type": "string", - "description": "Optional note explaining the attestation purpose or context.", - "maxLength": 500 - }, - "createdAt": { - "type": "string", - "format": "datetime", - "description": "Client-declared timestamp when this proof was created." - } - } - } - } - } -} diff --git a/lexicons/org/hyperboards/board.json b/lexicons/org/hyperboards/board.json index c4fba9f..6eaac83 100644 --- a/lexicons/org/hyperboards/board.json +++ b/lexicons/org/hyperboards/board.json @@ -33,11 +33,6 @@ "type": "string", "format": "datetime", "description": "Client-declared timestamp when this record was originally created." - }, - "signatures": { - "type": "ref", - "ref": "app.certified.signature.defs#list", - "description": "Optional cryptographic signatures attesting to this record's content." } } } diff --git a/lexicons/org/hyperboards/displayProfile.json b/lexicons/org/hyperboards/displayProfile.json index 3c4f323..d0af7f8 100644 --- a/lexicons/org/hyperboards/displayProfile.json +++ b/lexicons/org/hyperboards/displayProfile.json @@ -56,11 +56,6 @@ "type": "string", "format": "datetime", "description": "Client-declared timestamp when this record was originally created." - }, - "signatures": { - "type": "ref", - "ref": "app.certified.signature.defs#list", - "description": "Optional cryptographic signatures attesting to this record's content." } } } diff --git a/lexicons/org/hypercerts/claim/activity.json b/lexicons/org/hypercerts/claim/activity.json index 7991aae..86c4182 100644 --- a/lexicons/org/hypercerts/claim/activity.json +++ b/lexicons/org/hypercerts/claim/activity.json @@ -87,11 +87,6 @@ "type": "string", "format": "datetime", "description": "Client-declared timestamp when this record was originally created" - }, - "signatures": { - "type": "ref", - "ref": "app.certified.signature.defs#list", - "description": "Optional cryptographic signatures attesting to this record's content." } } } diff --git a/lexicons/org/hypercerts/claim/contribution.json b/lexicons/org/hypercerts/claim/contribution.json index edafe6e..791a367 100644 --- a/lexicons/org/hypercerts/claim/contribution.json +++ b/lexicons/org/hypercerts/claim/contribution.json @@ -35,11 +35,6 @@ "type": "string", "format": "datetime", "description": "Client-declared timestamp when this record was originally created." - }, - "signatures": { - "type": "ref", - "ref": "app.certified.signature.defs#list", - "description": "Optional cryptographic signatures attesting to this record's content." } } } diff --git a/lexicons/org/hypercerts/claim/contributorInformation.json b/lexicons/org/hypercerts/claim/contributorInformation.json index e6dd1c6..b3bd775 100644 --- a/lexicons/org/hypercerts/claim/contributorInformation.json +++ b/lexicons/org/hypercerts/claim/contributorInformation.json @@ -32,11 +32,6 @@ "type": "string", "format": "datetime", "description": "Client-declared timestamp when this record was originally created." - }, - "signatures": { - "type": "ref", - "ref": "app.certified.signature.defs#list", - "description": "Optional cryptographic signatures attesting to this record's content." } } } diff --git a/lexicons/org/hypercerts/claim/rights.json b/lexicons/org/hypercerts/claim/rights.json index 30f3c8f..f242f46 100644 --- a/lexicons/org/hypercerts/claim/rights.json +++ b/lexicons/org/hypercerts/claim/rights.json @@ -43,11 +43,6 @@ "type": "string", "format": "datetime", "description": "Client-declared timestamp when this record was originally created" - }, - "signatures": { - "type": "ref", - "ref": "app.certified.signature.defs#list", - "description": "Optional cryptographic signatures attesting to this record's content." } } } diff --git a/lexicons/org/hypercerts/collection.json b/lexicons/org/hypercerts/collection.json index bde6c5a..ba974b9 100644 --- a/lexicons/org/hypercerts/collection.json +++ b/lexicons/org/hypercerts/collection.json @@ -79,11 +79,6 @@ "type": "string", "format": "datetime", "description": "Client-declared timestamp when this record was originally created" - }, - "signatures": { - "type": "ref", - "ref": "app.certified.signature.defs#list", - "description": "Optional cryptographic signatures attesting to this record's content." } } } diff --git a/lexicons/org/hypercerts/context/acknowledgement.json b/lexicons/org/hypercerts/context/acknowledgement.json index 8fde4b8..b412254 100644 --- a/lexicons/org/hypercerts/context/acknowledgement.json +++ b/lexicons/org/hypercerts/context/acknowledgement.json @@ -34,11 +34,6 @@ "type": "string", "format": "datetime", "description": "Client-declared timestamp when this record was originally created." - }, - "signatures": { - "type": "ref", - "ref": "app.certified.signature.defs#list", - "description": "Optional cryptographic signatures attesting to this record's content." } } } diff --git a/lexicons/org/hypercerts/context/attachment.json b/lexicons/org/hypercerts/context/attachment.json index b10c141..ee18819 100644 --- a/lexicons/org/hypercerts/context/attachment.json +++ b/lexicons/org/hypercerts/context/attachment.json @@ -80,11 +80,6 @@ "type": "string", "format": "datetime", "description": "Client-declared timestamp when this record was originally created." - }, - "signatures": { - "type": "ref", - "ref": "app.certified.signature.defs#list", - "description": "Optional cryptographic signatures attesting to this record's content." } } } diff --git a/lexicons/org/hypercerts/context/evaluation.json b/lexicons/org/hypercerts/context/evaluation.json index 17aef90..3f18463 100644 --- a/lexicons/org/hypercerts/context/evaluation.json +++ b/lexicons/org/hypercerts/context/evaluation.json @@ -65,11 +65,6 @@ "type": "string", "format": "datetime", "description": "Client-declared timestamp when this record was originally created" - }, - "signatures": { - "type": "ref", - "ref": "app.certified.signature.defs#list", - "description": "Optional cryptographic signatures attesting to this record's content." } } } diff --git a/lexicons/org/hypercerts/context/measurement.json b/lexicons/org/hypercerts/context/measurement.json index 3b3fb19..08d8d49 100644 --- a/lexicons/org/hypercerts/context/measurement.json +++ b/lexicons/org/hypercerts/context/measurement.json @@ -99,11 +99,6 @@ "type": "string", "format": "datetime", "description": "Client-declared timestamp when this record was originally created" - }, - "signatures": { - "type": "ref", - "ref": "app.certified.signature.defs#list", - "description": "Optional cryptographic signatures attesting to this record's content." } } } diff --git a/lexicons/org/hypercerts/funding/receipt.json b/lexicons/org/hypercerts/funding/receipt.json index e0522e7..fb2806c 100644 --- a/lexicons/org/hypercerts/funding/receipt.json +++ b/lexicons/org/hypercerts/funding/receipt.json @@ -72,11 +72,6 @@ "type": "string", "format": "datetime", "description": "Client-declared timestamp when this receipt record was created." - }, - "signatures": { - "type": "ref", - "ref": "app.certified.signature.defs#list", - "description": "Optional cryptographic signatures attesting to this record's content." } } } diff --git a/lexicons/org/hypercerts/workscope/tag.json b/lexicons/org/hypercerts/workscope/tag.json index 346d99f..c1260ca 100644 --- a/lexicons/org/hypercerts/workscope/tag.json +++ b/lexicons/org/hypercerts/workscope/tag.json @@ -79,11 +79,6 @@ "type": "string", "format": "datetime", "description": "Client-declared timestamp when this record was originally created." - }, - "signatures": { - "type": "ref", - "ref": "app.certified.signature.defs#list", - "description": "Optional cryptographic signatures attesting to this record's content." } } } diff --git a/package-lock.json b/package-lock.json index 42dd41e..f007988 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,13 +13,11 @@ "multiformats": "^13.3.6" }, "devDependencies": { - "@atproto/crypto": "^0.5.0", "@atproto/lex-cli": "^0.9.5", "@atproto/xrpc": "^0.7.5", "@changesets/changelog-github": "^0.5.2", "@changesets/cli": "^2.29.8", "@eslint/js": "^9.39.2", - "@ipld/dag-cbor": "^10.0.1", "@rollup/plugin-commonjs": "^29.0.0", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.3", @@ -34,7 +32,7 @@ "tslib": "^2.8.1", "typescript": "^5.7.2", "typescript-eslint": "^8.51.0", - "viem": "^2.47.5", + "viem": "^2.46.3", "vitest": "^4.0.16" }, "peerDependencies": { @@ -59,31 +57,6 @@ "zod": "^3.23.8" } }, - "node_modules/@atproto/crypto": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@atproto/crypto/-/crypto-0.5.0.tgz", - "integrity": "sha512-HBfq6z+tzkiBzPhHOvRFkQU3inTQIUJAkb1UEyGx+pdPTnFFtnfIoo9Kr1ZV/FzMIgnHOA8H2UJC3dxxFl1eVg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@noble/curves": "^1.7.0", - "@noble/hashes": "^1.6.1", - "uint8arrays": "^5.0.0" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@atproto/crypto/node_modules/uint8arrays": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.1.tgz", - "integrity": "sha512-9muQwa4wZG4dKi9gMAIBtnk2Pw87SRpvWTH6lOGm19V2Uqxr4uomUf2PGqPnWc+qs06sN8owUU4jfcoWOcfwVQ==", - "dev": true, - "license": "Apache-2.0 OR MIT", - "dependencies": { - "multiformats": "^13.0.0" - } - }, "node_modules/@atproto/lex-cli": { "version": "0.9.6", "resolved": "https://registry.npmjs.org/@atproto/lex-cli/-/lex-cli-0.9.6.tgz", @@ -1243,24 +1216,6 @@ } } }, - "node_modules/@ipld/dag-cbor": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-10.0.1.tgz", - "integrity": "sha512-nF07iiZPqduSXyMxc0jGANArRHFa9hjMQpQlgLOV2O/3xI1CNb5sXhvbmigbMiz5owSGi0Oq10VtauupMojuiw==", - "dev": true, - "license": "Apache-2.0 OR MIT", - "dependencies": { - "cborg": "^5.0.1", - "multiformats": "^14.0.0" - } - }, - "node_modules/@ipld/dag-cbor/node_modules/multiformats": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.0.tgz", - "integrity": "sha512-iWK1RrAS58p2NDfeZFuSUSv3ZPewTIhsGbh/5NgeGGJwJmRljLxGtjRR3nkn+loG3zl+IrfR/W1590QnrSK+Gg==", - "dev": true, - "license": "Apache-2.0 OR MIT" - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -2487,16 +2442,6 @@ "node": ">=6" } }, - "node_modules/cborg": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/cborg/-/cborg-5.1.1.tgz", - "integrity": "sha512-BDbSRIp6XrQXkTc7g+DN0RB9RrDPTUfals2ecWUlt3juPLjbAvy/V72mJcXY0Ehu0Dq/3WpNCOCT68HUTbW+lw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "cborg": "lib/bin.js" - } - }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -3795,9 +3740,9 @@ "license": "MIT" }, "node_modules/ox": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.5.tgz", - "integrity": "sha512-HgmHmBveYO40H/R3K6TMrwYtHsx/u6TAB+GpZlgJCoW0Sq5Ttpjih0IZZiwGQw7T6vdW4IAyobYrE2mdAvyF8Q==", + "version": "0.12.4", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.12.4.tgz", + "integrity": "sha512-+P+C7QzuwPV8lu79dOwjBKfB2CbnbEXe/hfyyrff1drrO1nOOj3Hc87svHfcW1yneRr3WXaKr6nz11nq+/DF9Q==", "dev": true, "funding": [ { @@ -4672,9 +4617,9 @@ } }, "node_modules/viem": { - "version": "2.47.5", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.47.5.tgz", - "integrity": "sha512-nVrJEQ8GL4JoVIrMBF3wwpTUZun0cpojfnOZ+96GtDWhqxZkVdy6vOEgu+jwfXqfTA/+wrR+YsN9TBQmhDUk0g==", + "version": "2.46.3", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.46.3.tgz", + "integrity": "sha512-2LJS+Hyh2sYjHXQtzfv1kU9pZx9dxFzvoU/ZKIcn0FNtOU0HQuIICuYdWtUDFHaGXbAdVo8J1eCvmjkL9JVGwg==", "dev": true, "funding": [ { @@ -4690,7 +4635,7 @@ "@scure/bip39": "1.6.0", "abitype": "1.2.3", "isows": "1.0.7", - "ox": "0.14.5", + "ox": "0.12.4", "ws": "8.18.3" }, "peerDependencies": { diff --git a/package.json b/package.json index a7f065f..3460ea8 100644 --- a/package.json +++ b/package.json @@ -69,13 +69,11 @@ "multiformats": "^13.3.6" }, "devDependencies": { - "@atproto/crypto": "^0.5.0", "@atproto/lex-cli": "^0.9.5", "@atproto/xrpc": "^0.7.5", "@changesets/changelog-github": "^0.5.2", "@changesets/cli": "^2.29.8", "@eslint/js": "^9.39.2", - "@ipld/dag-cbor": "^10.0.1", "@rollup/plugin-commonjs": "^29.0.0", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.3", @@ -90,7 +88,7 @@ "tslib": "^2.8.1", "typescript": "^5.7.2", "typescript-eslint": "^8.51.0", - "viem": "^2.47.5", + "viem": "^2.46.3", "vitest": "^4.0.16" }, "peerDependencies": { diff --git a/tests/validate-signature-defs-inline.test.ts b/tests/validate-signature-defs-inline.test.ts deleted file mode 100644 index 7e2eaa2..0000000 --- a/tests/validate-signature-defs-inline.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { validate } from "../generated/lexicons.js"; -import { validateInline } from "../generated/types/app/certified/signature/defs.js"; - -describe("app.certified.signature.defs#inline", () => { - it("should accept a valid inline signature", () => { - const result = validateInline({ - $type: "app.certified.signature.defs#inline", - signature: new Uint8Array([1, 2, 3, 4]), - key: "did:plc:abc123#signing", - }); - expect(result.success).toBe(true); - if (result.success) { - expect(result.value.key).toBe("did:plc:abc123#signing"); - } - }); - - it("should reject when required field 'signature' is missing", () => { - const result = validate( - { - key: "did:plc:abc123#signing", - }, - "app.certified.signature.defs", - "inline", - false, - ); - expect(result.success).toBe(false); - }); - - it("should reject when required field 'key' is missing", () => { - const result = validate( - { - signature: new Uint8Array([1, 2, 3]), - }, - "app.certified.signature.defs", - "inline", - false, - ); - expect(result.success).toBe(false); - }); -}); diff --git a/tests/validate-signature-defs.test.ts b/tests/validate-signature-defs.test.ts deleted file mode 100644 index 3570064..0000000 --- a/tests/validate-signature-defs.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { ids } from "../generated/lexicons.js"; -import * as Activity from "../generated/types/org/hypercerts/claim/activity.js"; - -/** - * Tests the `app.certified.signature.defs#list` array def — specifically - * its union semantics (inline signature + strongRef) and array-level - * behavior (optional, empty, mixed). Uses activity as a representative - * carrier; all 21 record lexicons reference the same def. - */ -describe("app.certified.signature.defs#list (via activity carrier)", () => { - const baseActivity = { - title: "Test Activity", - shortDescription: "A test activity for signature validation", - createdAt: "2024-06-15T12:00:00.000Z", - }; - - it("should accept a record without signatures", () => { - const result = Activity.validateMain({ - $type: ids.OrgHypercertsClaimActivity, - ...baseActivity, - }); - expect(result.success).toBe(true); - }); - - it("should accept a record with an inline signature", () => { - const result = Activity.validateMain({ - $type: ids.OrgHypercertsClaimActivity, - ...baseActivity, - signatures: [ - { - $type: "app.certified.signature.defs#inline", - signature: new Uint8Array([1, 2, 3, 4]), - key: "did:plc:platform123#signing", - }, - ], - }); - expect(result.success).toBe(true); - }); - - it("should accept a record with a remote attestation (strongRef)", () => { - const result = Activity.validateMain({ - $type: ids.OrgHypercertsClaimActivity, - ...baseActivity, - signatures: [ - { - $type: "com.atproto.repo.strongRef", - uri: "at://did:plc:verifier/app.certified.signature.proof/3k2abc", - cid: "bafyreie5737gdxlw5i64vngml6xvqeatqy3a4erphoqtso54z2eooh4zae", - }, - ], - }); - expect(result.success).toBe(true); - }); - - it("should accept a record with both inline and remote signatures", () => { - const result = Activity.validateMain({ - $type: ids.OrgHypercertsClaimActivity, - ...baseActivity, - signatures: [ - { - $type: "app.certified.signature.defs#inline", - signature: new Uint8Array([0xde, 0xad]), - key: "did:plc:signer1#key-1", - }, - { - $type: "com.atproto.repo.strongRef", - uri: "at://did:plc:verifier/app.certified.signature.proof/abc123", - cid: "bafyreie5737gdxlw5i64vngml6xvqeatqy3a4erphoqtso54z2eooh4zae", - }, - { - $type: "app.certified.signature.defs#inline", - signature: new Uint8Array([0xca, 0xfe]), - key: "did:plc:signer2#key-2", - }, - ], - }); - expect(result.success).toBe(true); - }); - - it("should accept an empty signatures array", () => { - const result = Activity.validateMain({ - $type: ids.OrgHypercertsClaimActivity, - ...baseActivity, - signatures: [], - }); - expect(result.success).toBe(true); - }); -}); diff --git a/tests/validate-signature-proof.test.ts b/tests/validate-signature-proof.test.ts deleted file mode 100644 index ff75623..0000000 --- a/tests/validate-signature-proof.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { validate, ids } from "../generated/lexicons.js"; -import * as SignatureProof from "../generated/types/app/certified/signature/proof.js"; - -describe("app.certified.signature.proof", () => { - it("should accept a valid proof with all fields", () => { - const result = SignatureProof.validateMain({ - $type: "app.certified.signature.proof", - cid: "bafyreie5737gdxlw5i64vngml6xvqeatqy3a4erphoqtso54z2eooh4zae", - note: "Verified by platform quality assurance process", - createdAt: "2024-06-15T12:00:00.000Z", - }); - expect(result.success).toBe(true); - if (result.success) { - expect(result.value.cid).toBe( - "bafyreie5737gdxlw5i64vngml6xvqeatqy3a4erphoqtso54z2eooh4zae", - ); - expect(result.value.note).toBe( - "Verified by platform quality assurance process", - ); - } - }); - - it("should accept a proof with only the required cid field", () => { - const result = SignatureProof.validateMain({ - $type: "app.certified.signature.proof", - cid: "bafyreie5737gdxlw5i64vngml6xvqeatqy3a4erphoqtso54z2eooh4zae", - }); - expect(result.success).toBe(true); - }); - - it("should reject when required field 'cid' is missing", () => { - const result = validate( - { - note: "Some note", - createdAt: "2024-06-15T12:00:00.000Z", - }, - ids.AppCertifiedSignatureProof, - "main", - false, - ); - expect(result.success).toBe(false); - }); - - it("should require $type since proof is a record type", () => { - const result = validate( - { - cid: "bafyreie5737gdxlw5i64vngml6xvqeatqy3a4erphoqtso54z2eooh4zae", - }, - ids.AppCertifiedSignatureProof, - "main", - true, - ); - expect(result.success).toBe(false); - }); - - it("should reject an invalid datetime format", () => { - const result = validate( - { - cid: "bafyreie5737gdxlw5i64vngml6xvqeatqy3a4erphoqtso54z2eooh4zae", - createdAt: "not-a-date", - }, - ids.AppCertifiedSignatureProof, - "main", - false, - ); - expect(result.success).toBe(false); - }); -}); diff --git a/tests/validate-signing-example.test.ts b/tests/validate-signing-example.test.ts deleted file mode 100644 index 4e7e312..0000000 --- a/tests/validate-signing-example.test.ts +++ /dev/null @@ -1,256 +0,0 @@ -/** - * End-to-end execution of the cryptographic-signature example in - * README.md and the building-with-hypercerts-lexicons skill. - * - * The docs ARE the test source. Each markdown file's "Cryptographic - * Signatures" section contains a sequence of TypeScript code blocks - * that together build an inline signature, a remote-attestation - * strongRef, and the final signed record. This test extracts those - * blocks, concatenates them into a single ESM module, dynamically - * imports it, and verifies: - * - * 1. The example runs end-to-end without throwing - * 2. The produced inlineSignature has the correct shape and bytes - * 3. The produced signedActivity is lexicon-valid - * 4. The produced signature actually verifies via @atproto/crypto - * - * If the docs example drifts (renamed API, broken procedure, removed - * lexicon ref, etc.), this test fails. If the test logic needs to - * change, the docs are what to edit — not the test fixture. - */ -import { describe, it, expect } from "vitest"; -import { readFileSync, writeFileSync, mkdirSync } from "fs"; -import { resolve, dirname } from "path"; -import { fileURLToPath, pathToFileURL } from "url"; -import { verifySignature } from "@atproto/crypto"; -import * as Activity from "../generated/types/org/hypercerts/claim/activity.js"; - -const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), ".."); -const EXTRACT_DIR = resolve(ROOT, "tests/extracted"); - -interface ExtractedExample { - signedActivity: { signatures: unknown[] }; - inlineSignature: { - $type: string; - signature: Uint8Array; - key: string; - }; - remoteAttestation: { - $type: string; - uri: string; - cid: string; - }; - cidBytes: Uint8Array; - signatureBytes: Uint8Array; - keypair: { did(): string }; -} - -/** - * Extract all TypeScript code blocks under a heading whose text - * matches `sectionPattern` (and any nested subheadings until the next - * heading at the same or shallower level). - */ -function extractSectionBlocks( - markdown: string, - sectionPattern: RegExp, -): string[] { - const lines = markdown.split("\n"); - const blocks: string[] = []; - let inSection = false; - let sectionLevel = 0; - let inFence = false; - let fenceLang = ""; - let current: string[] = []; - - for (const line of lines) { - const headingMatch = line.match(/^(#+)\s+(.*)$/); - if (headingMatch && !inFence) { - const level = headingMatch[1].length; - const text = headingMatch[2]; - if (!inSection) { - if (sectionPattern.test(text)) { - inSection = true; - sectionLevel = level; - } - } else if (level <= sectionLevel) { - inSection = false; - } - continue; - } - if (!inSection) continue; - - const fenceMatch = line.match(/^```(\w*)/); - if (fenceMatch) { - if (!inFence) { - inFence = true; - fenceLang = fenceMatch[1].toLowerCase(); - current = []; - } else { - if (/^(ts|tsx|typescript)$/.test(fenceLang)) { - blocks.push(current.join("\n")); - } - inFence = false; - fenceLang = ""; - } - continue; - } - if (inFence) current.push(line); - } - return blocks; -} - -/** - * Concatenate the extracted blocks into one ESM module, hoisting and - * deduping `import` statements, then appending an `export` clause - * exposing the identifiers we want the test to inspect. - */ -function buildModuleSource(blocks: string[]): string { - const importLines = new Set(); - const bodyChunks: string[] = []; - - for (const block of blocks) { - const blockLines = block.split("\n"); - const body: string[] = []; - let i = 0; - // Imports may span multiple lines if a `{ ... }` import is broken - // across them, so accumulate until a line ends with a quote+semi. - while (i < blockLines.length) { - const line = blockLines[i]; - if (/^\s*import\b/.test(line)) { - let stmt = line; - while (!/["'];?\s*$/.test(stmt) && i + 1 < blockLines.length) { - i += 1; - stmt += "\n" + blockLines[i]; - } - importLines.add(stmt.trim()); - i += 1; - continue; - } - body.push(line); - i += 1; - } - bodyChunks.push(body.join("\n")); - } - - return [ - "// AUTO-GENERATED from documentation code blocks.", - "// Do not edit; edit the source markdown instead.", - "/* eslint-disable */", - ...Array.from(importLines), - "", - bodyChunks.join("\n\n"), - "", - "export {", - " signedActivity,", - " inlineSignature,", - " remoteAttestation,", - " cidBytes,", - " signatureBytes,", - " keypair,", - "};", - "", - ].join("\n"); -} - -async function loadExampleFrom( - relPath: string, - outputBasename: string, -): Promise { - const markdown = readFileSync(resolve(ROOT, relPath), "utf-8"); - const blocks = extractSectionBlocks(markdown, /Cryptographic Signatures/i); - if (blocks.length < 3) { - throw new Error( - `Expected at least 3 TypeScript blocks under "Cryptographic Signatures" in ${relPath}, found ${blocks.length}`, - ); - } - const source = buildModuleSource(blocks); - mkdirSync(EXTRACT_DIR, { recursive: true }); - const outPath = resolve(EXTRACT_DIR, `${outputBasename}.ts`); - writeFileSync(outPath, source, "utf-8"); - return (await import(pathToFileURL(outPath).href)) as ExtractedExample; -} - -const SOURCES: Array<[label: string, relPath: string, basename: string]> = [ - [ - "SKILL.md", - ".agents/skills/building-with-hypercerts-lexicons/SKILL.md", - "signing-example-skill", - ], - ["README.md", "README.md", "signing-example-readme"], -]; - -for (const [label, relPath, basename] of SOURCES) { - describe(`signing example extracted from ${label}`, () => { - let example: ExtractedExample; - - it("extracts and runs the documented signing procedure without throwing", async () => { - example = await loadExampleFrom(relPath, basename); - expect(example).toBeDefined(); - }); - - it("produces a 36-byte CIDv1 with the dag-cbor/SHA-256 prefix", () => { - expect(example.cidBytes).toBeInstanceOf(Uint8Array); - expect(example.cidBytes.length).toBe(36); - // CIDv1 + dag-cbor codec + SHA-256 multihash + 32-byte digest length - expect(Array.from(example.cidBytes.slice(0, 4))).toEqual([ - 0x01, 0x71, 0x12, 0x20, - ]); - }); - - it("produces 64 raw r,s bytes from Secp256k1Keypair.sign()", () => { - expect(example.signatureBytes).toBeInstanceOf(Uint8Array); - expect(example.signatureBytes.length).toBe(64); - }); - - it("builds an inline signature with the correct $type and shape", () => { - expect(example.inlineSignature.$type).toBe( - "app.certified.signature.defs#inline", - ); - expect(example.inlineSignature.signature).toBe(example.signatureBytes); - expect(example.inlineSignature.key).toMatch(/^did:[^:]+:.+#.+$/); - }); - - it("builds a remote-attestation strongRef", () => { - expect(example.remoteAttestation.$type).toBe( - "com.atproto.repo.strongRef", - ); - expect(example.remoteAttestation.uri).toMatch(/^at:\/\//); - }); - - it("attaches both signatures to the final record", () => { - expect(example.signedActivity.signatures).toHaveLength(2); - expect(example.signedActivity.signatures[0]).toBe( - example.inlineSignature, - ); - expect(example.signedActivity.signatures[1]).toBe( - example.remoteAttestation, - ); - }); - - it("produces a record that passes lexicon validation", () => { - // Replace the placeholder remote-attestation strongRef CID with a - // syntactically-valid CID so the record can be validated; the - // example uses "bafy..." which the lexicon validator rejects. - const VALID_PROOF_CID = - "bafyreie5737gdxlw5i64vngml6xvqeatqy3a4erphoqtso54z2eooh4zae"; - const validatable = { - ...example.signedActivity, - signatures: [ - example.inlineSignature, - { ...example.remoteAttestation, cid: VALID_PROOF_CID }, - ], - }; - const result = Activity.validateMain(validatable); - expect(result.success).toBe(true); - }); - - it("produced signature verifies against the signing public key", async () => { - const ok = await verifySignature( - example.keypair.did(), - example.cidBytes, - example.signatureBytes, - ); - expect(ok).toBe(true); - }); - }); -}