Skip to content

Commit 897201b

Browse files
authored
Revert "NON-BREAKING: [HYPER-181] add cryptographic signature support to all lexicons"
1 parent 478f5be commit 897201b

36 files changed

Lines changed: 34 additions & 1111 deletions

.agents/skills/building-with-hypercerts-lexicons/SKILL.md

Lines changed: 1 addition & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -246,18 +246,9 @@ if (result.success) {
246246
| **Badge Definition** | `app.certified.badge.definition` | Defines a badge with type, title, icon, optional issuer allowlist |
247247
| **Badge Award** | `app.certified.badge.award` | Awards a badge to a user, project, or activity |
248248
| **Badge Response** | `app.certified.badge.response` | Recipient accepts or rejects a badge award |
249-
| **EVM Link** | `app.certified.link.evm` | Verifiable ATProto DID to EVM wallet link via EIP-712 signature. Can additionally carry `signatures[]` for record provenance |
249+
| **EVM Link** | `app.certified.link.evm` | Verifiable ATProto DID to EVM wallet link via EIP-712 signature |
250250
| **Follow** | `app.certified.graph.follow` | Social-graph follow: declares the author follows another account by DID. Schema-compatible with `app.bsky.graph.follow` |
251251

252-
### Signatures — cryptographic attestation
253-
254-
| Lexicon | NSID | Purpose |
255-
| ------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
256-
| **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. |
257-
| **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`. |
258-
259-
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`.
260-
261252
## Relationship Map
262253

263254
```text
@@ -294,12 +285,6 @@ CERTIFIED
294285
actor/organization (org metadata)
295286
badge/response ──> badge/award ──> badge/definition
296287
graph/follow ───────────> account DID (social follow)
297-
signature/defs (shared #list and #inline defs)
298-
signature/proof (remote attestation proof record)
299-
300-
Every record lexicon also carries an optional `signatures` array
301-
referencing app.certified.signature.defs#list (not drawn — the
302-
arrow would fan out identically from every record).
303288
```
304289

305290
Every arrow is a `strongRef` or union reference stored on AT Protocol.
@@ -498,113 +483,6 @@ const receipt = {
498483
};
499484
```
500485

501-
### Attaching Cryptographic Signatures
502-
503-
Any record can carry an optional `signatures` array, enabling platform
504-
attestation and verification that records were created through trusted
505-
services. Two patterns are supported in any combination:
506-
507-
1. **Inline signatures** — embedded directly in the record via
508-
`app.certified.signature.defs#inline`.
509-
2. **Remote attestations** — references to
510-
`app.certified.signature.proof` records in other repositories via
511-
`com.atproto.repo.strongRef`.
512-
513-
**Step 1: build an inline signature.** Compute the spec-defined CID
514-
over the record-to-be-signed, then ECDSA-sign it with the platform's
515-
key. Uses [`@atproto/crypto`](https://www.npmjs.com/package/@atproto/crypto)
516-
(ATProto's signing primitives — handles low-S automatically),
517-
[`@ipld/dag-cbor`](https://www.npmjs.com/package/@ipld/dag-cbor) for
518-
canonical encoding, and [`multiformats`](https://www.npmjs.com/package/multiformats)
519-
for CID construction:
520-
521-
```typescript
522-
import { Secp256k1Keypair } from "@atproto/crypto";
523-
import * as dagCbor from "@ipld/dag-cbor";
524-
import { CID } from "multiformats/cid";
525-
import { sha256 } from "multiformats/hashes/sha2";
526-
import * as raw from "multiformats/codecs/raw";
527-
import { ACTIVITY_NSID } from "@hypercerts-org/lexicon";
528-
529-
// 1. The record we want to sign (without the signatures field).
530-
const recordToSign = {
531-
$type: ACTIVITY_NSID,
532-
title: "Verified Reforestation Project",
533-
shortDescription: "Planted 1,000 trees in partnership with local community",
534-
createdAt: "2026-05-21T12:00:00.000Z",
535-
};
536-
537-
// 2. Insert temporary $sig metadata: attestation type + housing repo DID.
538-
// This binds the signature to a specific repository, preventing
539-
// cross-repo replay.
540-
const platformDid = "did:plc:platform123";
541-
const cborInput = {
542-
...recordToSign,
543-
$sig: {
544-
$type: "app.certified.signature.defs#inline",
545-
repository: platformDid,
546-
},
547-
};
548-
549-
// 3. Encode canonically as DAG-CBOR, then build the 36-byte CIDv1
550-
// (dag-cbor codec 0x71 + SHA-256 multihash).
551-
const cborBytes = dagCbor.encode(cborInput);
552-
const hash = await sha256.digest(cborBytes);
553-
const cid = CID.createV1(dagCbor.code, hash);
554-
const cidBytes = cid.bytes; // 36 bytes: 0x01 0x71 0x12 0x20 + 32-byte hash
555-
556-
// 4. ECDSA-sign the CID bytes. Keypair.sign() applies the standard
557-
// ECDSA hash-then-sign convention and enforces low-S per BIP-0062.
558-
const keypair = await Secp256k1Keypair.create({ exportable: false });
559-
const signatureBytes = await keypair.sign(cidBytes);
560-
561-
const inlineSignature = {
562-
$type: "app.certified.signature.defs#inline" as const,
563-
signature: signatureBytes,
564-
key: `${platformDid}#signing`, // DID verification method reference
565-
};
566-
```
567-
568-
**Step 2: build a remote attestation reference** (optional — only if
569-
the attestation lives in another repo's proof record):
570-
571-
```typescript
572-
const remoteAttestation = {
573-
$type: "com.atproto.repo.strongRef" as const,
574-
uri: "at://did:plc:verifier/app.certified.signature.proof/abc123",
575-
cid: "bafy...", // CID of the proof record being referenced
576-
};
577-
```
578-
579-
**Step 3: attach to the record.** Both shapes can coexist in the same
580-
array; consumers verify each entry independently.
581-
582-
```typescript
583-
const signedActivity = {
584-
...recordToSign,
585-
signatures: [inlineSignature, remoteAttestation],
586-
};
587-
```
588-
589-
See the
590-
[ATProtocol Attestation Specification](https://tangled.org/strings/did:plc:cbkjy5n7bk3ax2wplmtjofq2/3m3fy2xuahc22)
591-
for the full procedure and verification rules, and the
592-
[accompanying blog post](https://ngerakines.leaflet.pub/3m3idxul5hc2r)
593-
for background and motivation.
594-
595-
### Creating a Remote Attestation Proof
596-
597-
```typescript
598-
import { SIGNATURE_PROOF_NSID } from "@hypercerts-org/lexicon";
599-
600-
const proof = {
601-
$type: SIGNATURE_PROOF_NSID,
602-
cid: "bafy...", // CID of the attested content (computed as above)
603-
note: "Verified by platform quality assurance process",
604-
createdAt: new Date().toISOString(),
605-
};
606-
```
607-
608486
## Key Concepts
609487

610488
### strongRef

.changeset/add-signature-support.md

Lines changed: 0 additions & 49 deletions
This file was deleted.

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ dist/
44
generated/
55
pnpm-lock.yaml
66
/tmp/
7-
tests/extracted/
87
lexicons.md
98

109
# Dolt database files (added by bd init)

AGENTS.md

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -294,25 +294,10 @@ npm run check
294294

295295
### Writing Tests
296296

297-
Tests live in `tests/` and by default use one file per lexicon, named
297+
Tests live in `tests/` with one file per lexicon, named
298298
`validate-<lexicon-slug>.test.ts` (e.g. `validate-link-evm.test.ts`,
299299
`validate-rights.test.ts`).
300300

301-
This is a convention, not a strict rule. When a test genuinely
302-
exercises behavior that spans multiple lexicons or code files —
303-
shared `*.defs` lexicons referenced from many records, cross-cutting
304-
union semantics, validation behavior that surfaces only in
305-
composition, etc. — pick the clearest, most concise name that
306-
describes what's actually under test, even if that file doesn't map
307-
1:1 to a single lexicon. Don't duplicate near-identical tests across
308-
every carrier just to satisfy the naming convention.
309-
310-
For example, `validate-signature-defs.test.ts` exercises
311-
`app.certified.signature.defs#list` (union of inline + strongRef,
312-
array semantics) via a representative record carrier, rather than
313-
spawning 19 near-identical files for every record lexicon that
314-
references the def.
315-
316301
The generated code provides two ways to validate records:
317302

318303
- **`validate()` from `generated/lexicons.js`** — generic, untyped.

ERD.puml

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -240,26 +240,6 @@ dataclass rights {
240240
!endif
241241
}
242242

243-
' Signature-related lexicons (app.certified.signature.defs#inline,
244-
' app.certified.signature.defs#list, app.certified.signature.proof)
245-
' are deliberately omitted from this diagram.
246-
'
247-
' Every record lexicon carries an optional `signatures` array referencing
248-
' app.certified.signature.defs#list, so the relationship is uniform across
249-
' all ~20 record entities. Drawing those arrows would add a thicket of
250-
' identical "signatures" edges from every dataclass on the diagram without
251-
' communicating any additional structure. The signature-related lexicons
252-
' are documented in SCHEMAS.md and README.md instead.
253-
254-
' app.certified.link.evm
255-
dataclass linkEvm {
256-
!if (SHOW_FIELDS == "true")
257-
address
258-
proof
259-
createdAt
260-
!endif
261-
}
262-
263243
' org.hypercerts.collection
264244
dataclass collection {
265245
!if (SHOW_FIELDS == "true")
@@ -425,7 +405,4 @@ follow::subject --> contributorEntity : follows
425405
' This screws up the layout
426406
'badgeAward::subject --[norank]-> collection
427407

428-
' Signature relationships are intentionally not drawn here -- see the
429-
' comment near the top of the file (before linkEvm) for rationale.
430-
431408
@enduml

0 commit comments

Comments
 (0)