NON-BREAKING: [HYPER-181] add cryptographic signature support to all lexicons#170
Conversation
🦋 Changeset detectedLatest commit: efa9b4f The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Caution Review failedThe pull request is closed. Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (35)
📝 WalkthroughWalkthroughThis PR introduces optional cryptographic signature support to the Hypercerts lexicon by defining new signature lexicons, extending 21 existing record schemas with optional ChangesCryptographic Signature Attestation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related PRs
Suggested reviewers
Poem
Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (1 error)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
65cdd6e to
cb7411e
Compare
|
Fixes HYPER-181 |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
lexicons/org/hypercerts/claim/rights.json (1)
46-51: LGTM!The optional
signatureDatafield is correctly defined and follows the established pattern.Consider adding test coverage for the new field. The existing tests in
tests/validate-rights.test.tsdon't validate behavior withsignatureDatapresent or absent. While not blocking, adding a test case with a validsignatureDatapayload would improve validation coverage.,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lexicons/org/hypercerts/claim/rights.json` around lines 46 - 51, Add test coverage for the new optional signatureData field by updating tests/validate-rights.test.ts: add at least one positive test that includes a valid signatureData object matching the "app.certified.signature.list" ref schema and asserts the rights validation succeeds, and optionally a negative test or absence case to assert behavior when signatureData is missing or malformed; locate the payload construction and validation calls in the file and extend them to include a signatureData property referencing the same sample structure used by the "app.certified.signature.list" definition.lexicons/app/certified/signature/list.json (1)
9-21: RequiresignatureswhensignatureDatais present.Right now
{}is valid forapp.certified.signature.list, which permits empty attestation envelopes. Requiringsignatures(and ideally at least one item) makes the schema intent explicit.Proposed schema tweak
"defs": { "main": { "type": "object", "description": "Container object with a signatures array. Referenced by all record lexicons to provide optional cryptographic attestation support.", + "required": ["signatures"], "properties": { "signatures": { "type": "array", "description": "Array of cryptographic signatures attesting to record content. Uses an open union to support inline signatures and strong references to remote proof records.", "items": { "type": "union", "refs": [ "app.certified.signature.inline", "com.atproto.repo.strongRef" ] }, + "minLength": 1, "maxLength": 100 } } } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lexicons/app/certified/signature/list.json` around lines 9 - 21, The schema for app.certified.signature.list currently allows an empty object; update the schema so that when signatureData is present the signatures property is required and contains at least one item: add a conditional clause (JSON Schema if/then) that checks for the presence of "signatureData" and in the then branch requires "signatures", and also set "signatures" to have "minItems": 1; make these changes inside the same object that defines "properties" for "signatures" in the app.certified.signature.list schema to ensure inline/strongRef entries are enforced.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.changeset/fuzzy-pandas-smile.md:
- Around line 17-37: The release notes incorrectly state "All 19 record
lexicons" and omit app.certified.link.evm; update the header count to "All 20
record lexicons" and add app.certified.link.evm to the enumerated list of record
lexicons (ensure the list includes the existing 19 entries plus
app.certified.link.evm and adjust any surrounding wording to match).
In `@lexicons/app/certified/signature/list.json`:
- Around line 1-25: ERD.puml is missing the new structural relationships for
signatureData that point to app.certified.signature.list; update ERD.puml to add
association lines from each lexicon that now contains a signatureData field
(e.g., activity, attachment, measurement, evaluation, acknowledgement, rights,
and the ~20+ other lexicons) to the app.certified.signature.list entity,
ensuring the relationship reflects the array/collection semantics and does not
include facet-only fields; also ensure the signature lexicons
(app.certified.signature.inline and app.certified.signature.proof) are
represented as related entities per the regenerated SCHEMAS.md so the diagram
shows the full signatureData → app.certified.signature.list → (inline | proof)
relationships.
In `@lexicons/app/certified/signature/proof.json`:
- Line 4: Update the CID canonicalization wording to refer to the new
signatureData structure instead of signatures: replace any mention of
"signatures" with "signatureData" and clarify that canonicalization should strip
or reference the signatureData property (which itself contains nested
"signatures") when computing the CID; ensure the example language in
proof.json's description and the CID canonicalization section explicitly states
"signatureData (object containing nested 'signatures')" so implementers
hash/sign the correct payload.
---
Nitpick comments:
In `@lexicons/app/certified/signature/list.json`:
- Around line 9-21: The schema for app.certified.signature.list currently allows
an empty object; update the schema so that when signatureData is present the
signatures property is required and contains at least one item: add a
conditional clause (JSON Schema if/then) that checks for the presence of
"signatureData" and in the then branch requires "signatures", and also set
"signatures" to have "minItems": 1; make these changes inside the same object
that defines "properties" for "signatures" in the app.certified.signature.list
schema to ensure inline/strongRef entries are enforced.
In `@lexicons/org/hypercerts/claim/rights.json`:
- Around line 46-51: Add test coverage for the new optional signatureData field
by updating tests/validate-rights.test.ts: add at least one positive test that
includes a valid signatureData object matching the
"app.certified.signature.list" ref schema and asserts the rights validation
succeeds, and optionally a negative test or absence case to assert behavior when
signatureData is missing or malformed; locate the payload construction and
validation calls in the file and extend them to include a signatureData property
referencing the same sample structure used by the "app.certified.signature.list"
definition.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 934048b3-708e-42f4-968e-ebfa308f98a1
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (27)
.changeset/fuzzy-pandas-smile.mdREADME.mdSCHEMAS.mdlexicons/app/certified/actor/organization.jsonlexicons/app/certified/actor/profile.jsonlexicons/app/certified/badge/award.jsonlexicons/app/certified/badge/definition.jsonlexicons/app/certified/badge/response.jsonlexicons/app/certified/link/evm.jsonlexicons/app/certified/location.jsonlexicons/app/certified/signature/inline.jsonlexicons/app/certified/signature/list.jsonlexicons/app/certified/signature/proof.jsonlexicons/org/hyperboards/board.jsonlexicons/org/hyperboards/displayProfile.jsonlexicons/org/hypercerts/claim/activity.jsonlexicons/org/hypercerts/claim/contribution.jsonlexicons/org/hypercerts/claim/contributorInformation.jsonlexicons/org/hypercerts/claim/rights.jsonlexicons/org/hypercerts/collection.jsonlexicons/org/hypercerts/context/acknowledgement.jsonlexicons/org/hypercerts/context/attachment.jsonlexicons/org/hypercerts/context/evaluation.jsonlexicons/org/hypercerts/context/measurement.jsonlexicons/org/hypercerts/funding/receipt.jsonlexicons/org/hypercerts/workscope/tag.jsonpackage.json
cb7411e to
4a5e234
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
lexicons/app/certified/signature/proof.json (1)
14-18: Clarify CID canonicalization details to avoid cross-implementation drift.Line 17 states what is attested, but not the exact canonicalization/encoding process. A short pointer to the precise canonicalization rule (or explicit algorithm wording) would reduce interoperability risk.
📝 Suggested wording refinement
- "description": "CID of the attested content (record without signatureData field, with $sig metadata including repository DID)." + "description": "CID of the attested content, computed using the attestation canonicalization rules (remove signatureData before hashing and include $sig metadata with repository DID as defined by the attestation spec)."As per coding guidelines, “Lexicon files in /lexicons must comply with the atproto lexicon style guide … developers may deviate if they have good reasons, but must document deviations in the lexicon JSON file itself.”
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: c3391f0b-5cea-4ef7-bb29-a9dec04d9f8b
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (28)
.changeset/fuzzy-pandas-smile.mdERD.pumlREADME.mdSCHEMAS.mdlexicons/app/certified/actor/organization.jsonlexicons/app/certified/actor/profile.jsonlexicons/app/certified/badge/award.jsonlexicons/app/certified/badge/definition.jsonlexicons/app/certified/badge/response.jsonlexicons/app/certified/link/evm.jsonlexicons/app/certified/location.jsonlexicons/app/certified/signature/inline.jsonlexicons/app/certified/signature/list.jsonlexicons/app/certified/signature/proof.jsonlexicons/org/hyperboards/board.jsonlexicons/org/hyperboards/displayProfile.jsonlexicons/org/hypercerts/claim/activity.jsonlexicons/org/hypercerts/claim/contribution.jsonlexicons/org/hypercerts/claim/contributorInformation.jsonlexicons/org/hypercerts/claim/rights.jsonlexicons/org/hypercerts/collection.jsonlexicons/org/hypercerts/context/acknowledgement.jsonlexicons/org/hypercerts/context/attachment.jsonlexicons/org/hypercerts/context/evaluation.jsonlexicons/org/hypercerts/context/measurement.jsonlexicons/org/hypercerts/funding/receipt.jsonlexicons/org/hypercerts/workscope/tag.jsonpackage.json
🚧 Files skipped from review as they are similar to previous changes (18)
- lexicons/org/hypercerts/funding/receipt.json
- lexicons/app/certified/badge/response.json
- lexicons/org/hypercerts/context/measurement.json
- lexicons/org/hyperboards/board.json
- lexicons/org/hypercerts/claim/rights.json
- lexicons/app/certified/actor/profile.json
- lexicons/app/certified/signature/inline.json
- lexicons/org/hyperboards/displayProfile.json
- lexicons/app/certified/actor/organization.json
- lexicons/org/hypercerts/collection.json
- README.md
- lexicons/org/hypercerts/claim/activity.json
- lexicons/org/hypercerts/context/evaluation.json
- lexicons/app/certified/badge/definition.json
- lexicons/app/certified/signature/list.json
- lexicons/org/hypercerts/claim/contribution.json
- package.json
- lexicons/app/certified/location.json
|
I'm about to meet Nick and will ask him about the signatures object question. |
|
Nick pointed me to the fact that you can have a
That last sentence including the "for example" is the only clue given that it can also be |
Flatten signatures array directly onto recordsCore change: Before: Generated TypeScript: // list.ts — now a type alias, not an interface
export type Main = (
| $Typed<AppCertifiedSignatureInline.Main>
| $Typed<ComAtprotoRepoStrongRef.Main>
| { $type: string }
)[]
// activity.ts — flat property
export interface Main {
// ...
signatures?: AppCertifiedSignatureList.Main
}Files changed: 26 files — the All checks pass: lint, typecheck, build, 11/11 tests. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
SCHEMAS.md (1)
508-511: Enhance documentation completeness for array-type lexicon.The
app.certified.signature.listdocumentation is notably sparse compared to other lexicons in this file. While it's correct that this is an array type (not an object) and thus has no traditional "properties," the documentation should still convey the array constraints and structure that developers need.Based on the lexicon definition (context snippet 2), this section is missing:
- The
maxLength: 100constraint on the array- Explicit mention that this is an array type (not just implied by description)
- Clear documentation that items are a union of
app.certified.signature.inlineandcom.atproto.repo.strongRef📋 Suggested documentation enhancement
Consider expanding this section to include:
### `app.certified.signature.list` **Description:** Array of cryptographic signatures attesting to record content. Uses an open union to support inline signatures and strong references to remote proof records. + +**Type:** Array (maxLength: 100) + +#### Array Items + +| Type | Description | +| ------- | ----------------------------------------------------------------------------------------------------- | +| `union` | Each item can be either an inline signature (`app.certified.signature.inline`) or a strong reference (`com.atproto.repo.strongRef`) to a remote proof record. |🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@SCHEMAS.md` around lines 508 - 511, Update the `app.certified.signature.list` documentation to explicitly state that it is an array type (not an object), document the array constraint `maxLength: 100`, and describe that each item is a union of `app.certified.signature.inline` and `com.atproto.repo.strongRef`; reference the lexicon name `app.certified.signature.list` and the item types so readers can locate the related schemas and understand the expected structure and limits.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@SCHEMAS.md`:
- Around line 508-511: Update the `app.certified.signature.list` documentation
to explicitly state that it is an array type (not an object), document the array
constraint `maxLength: 100`, and describe that each item is a union of
`app.certified.signature.inline` and `com.atproto.repo.strongRef`; reference the
lexicon name `app.certified.signature.list` and the item types so readers can
locate the related schemas and understand the expected structure and limits.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: c474b99f-f6d5-4443-ad45-caea8276191f
📒 Files selected for processing (26)
.changeset/fuzzy-pandas-smile.mdERD.pumlREADME.mdSCHEMAS.mdlexicons/app/certified/actor/organization.jsonlexicons/app/certified/actor/profile.jsonlexicons/app/certified/badge/award.jsonlexicons/app/certified/badge/definition.jsonlexicons/app/certified/badge/response.jsonlexicons/app/certified/link/evm.jsonlexicons/app/certified/location.jsonlexicons/app/certified/signature/list.jsonlexicons/app/certified/signature/proof.jsonlexicons/org/hyperboards/board.jsonlexicons/org/hyperboards/displayProfile.jsonlexicons/org/hypercerts/claim/activity.jsonlexicons/org/hypercerts/claim/contribution.jsonlexicons/org/hypercerts/claim/contributorInformation.jsonlexicons/org/hypercerts/claim/rights.jsonlexicons/org/hypercerts/collection.jsonlexicons/org/hypercerts/context/acknowledgement.jsonlexicons/org/hypercerts/context/attachment.jsonlexicons/org/hypercerts/context/evaluation.jsonlexicons/org/hypercerts/context/measurement.jsonlexicons/org/hypercerts/funding/receipt.jsonlexicons/org/hypercerts/workscope/tag.json
✅ Files skipped from review due to trivial changes (9)
- lexicons/org/hypercerts/workscope/tag.json
- lexicons/org/hypercerts/context/acknowledgement.json
- lexicons/org/hypercerts/context/attachment.json
- lexicons/app/certified/actor/profile.json
- lexicons/org/hypercerts/claim/contribution.json
- lexicons/org/hyperboards/displayProfile.json
- lexicons/app/certified/signature/list.json
- lexicons/org/hypercerts/funding/receipt.json
- .changeset/fuzzy-pandas-smile.md
🚧 Files skipped from review as they are similar to previous changes (14)
- lexicons/app/certified/badge/definition.json
- lexicons/org/hypercerts/collection.json
- lexicons/app/certified/actor/organization.json
- lexicons/org/hyperboards/board.json
- lexicons/app/certified/badge/award.json
- lexicons/org/hypercerts/claim/rights.json
- lexicons/app/certified/location.json
- lexicons/org/hypercerts/context/measurement.json
- lexicons/org/hypercerts/claim/contributorInformation.json
- lexicons/app/certified/badge/response.json
- README.md
- ERD.puml
- lexicons/app/certified/signature/proof.json
- lexicons/org/hypercerts/context/evaluation.json
|
Sent to Nick: I also find the term "proof" a bit of a misnomer because an attestation is really just a claim not a hard proof of anything. |
|
I agree that we can use "array" type directly for a "definition", but my claim was for a "lexicon". You can not define a lexicon that has type "array" directly. But as I write the comment, I realize signatures would generally be different for all these records, so there doesn't stand any scenario where we might need to store the signature list in a record and then reference it. Moreovere, if you still find a use case (I couldn't) where we might need to reference only the signature list so that it can be referenced in other records, than we need to get back to the lexicon definition. |
|
Do all these schemas really need the signatures? Or should we limit it to those where we will actively use them? |
|
@aspiers is this also something that we want to add to this lexicon update? Is this mostly done? (I haven't analyzed the approach myself really) |
|
I resolved my remaining concerns with Nick. I think we have a straightforward plan for the way forward with this - it's probably already mostly ready and just needs rebasing. I definitely think we want to include this, ideally as part of the current round of updates, although it's a non-breaking change so could be added in a fast follow-up 1.1.0 release if necessary. |
09afb0f to
887bb98
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@tests/validate-signatures-on-record.test.ts`:
- Around line 5-10: The test file tests/validate-signatures-on-record.test.ts is
cross-lexicon but must follow the per-lexicon convention; rename the file to
validate-<lexicon-slug>.test.ts (e.g., validate-activity.test.ts) and change the
test suite description from "signatures property on records" to a lexicon-scoped
title (e.g., "signatures property on activity records"), or split the assertions
into separate lexicon-specific files if multiple lexicons are covered; update
any imports/exports or test runner references that point to
tests/validate-signatures-on-record.test.ts and ensure the describe block and
any helper usages (the suite name and test selectors) reflect the chosen lexicon
symbol so the file is a single-lexicon test.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 81442dda-bb97-46df-8818-47b76120f589
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (31)
.changeset/add-signature-support.mdERD.pumlREADME.mdSCHEMAS.mdlexicons/app/certified/actor/organization.jsonlexicons/app/certified/actor/profile.jsonlexicons/app/certified/badge/award.jsonlexicons/app/certified/badge/definition.jsonlexicons/app/certified/badge/response.jsonlexicons/app/certified/link/evm.jsonlexicons/app/certified/location.jsonlexicons/app/certified/signature/inline.jsonlexicons/app/certified/signature/list.jsonlexicons/app/certified/signature/proof.jsonlexicons/org/hyperboards/board.jsonlexicons/org/hyperboards/displayProfile.jsonlexicons/org/hypercerts/claim/activity.jsonlexicons/org/hypercerts/claim/contribution.jsonlexicons/org/hypercerts/claim/contributorInformation.jsonlexicons/org/hypercerts/claim/rights.jsonlexicons/org/hypercerts/collection.jsonlexicons/org/hypercerts/context/acknowledgement.jsonlexicons/org/hypercerts/context/attachment.jsonlexicons/org/hypercerts/context/evaluation.jsonlexicons/org/hypercerts/context/measurement.jsonlexicons/org/hypercerts/funding/receipt.jsonlexicons/org/hypercerts/workscope/tag.jsonpackage.jsontests/validate-signature-inline.test.tstests/validate-signature-proof.test.tstests/validate-signatures-on-record.test.ts
✅ Files skipped from review due to trivial changes (20)
- package.json
- lexicons/app/certified/actor/organization.json
- lexicons/app/certified/link/evm.json
- lexicons/org/hypercerts/context/acknowledgement.json
- lexicons/app/certified/actor/profile.json
- lexicons/org/hypercerts/context/attachment.json
- lexicons/app/certified/badge/definition.json
- lexicons/app/certified/badge/award.json
- lexicons/org/hypercerts/claim/contributorInformation.json
- lexicons/app/certified/location.json
- lexicons/org/hypercerts/collection.json
- lexicons/org/hypercerts/workscope/tag.json
- lexicons/org/hypercerts/context/evaluation.json
- lexicons/org/hypercerts/funding/receipt.json
- lexicons/app/certified/signature/list.json
- lexicons/app/certified/signature/inline.json
- lexicons/org/hypercerts/claim/rights.json
- lexicons/app/certified/signature/proof.json
- lexicons/app/certified/badge/response.json
- lexicons/org/hyperboards/displayProfile.json
🚧 Files skipped from review as they are similar to previous changes (8)
- lexicons/org/hyperboards/board.json
- lexicons/org/hypercerts/claim/activity.json
- lexicons/org/hypercerts/context/measurement.json
- lexicons/org/hypercerts/claim/contribution.json
- .changeset/add-signature-support.md
- ERD.puml
- README.md
- SCHEMAS.md
887bb98 to
e354b5f
Compare
Sorry @satyam-mishra-pce I missed these good points before due to the craziness of Vancouver. I think you are right. We don't have any immediate need for a lexicon which is purely a signature list, but if we ever needed it in the future it would be easy to construct using the def we're adding here. That said, I wonder if we should avoid putting it in |
e354b5f to
05e69fc
Compare
Add optional cryptographic signature support to record lexicons, enabling platform attestation and verification that records were created through trusted services. Inspired by: - ATProtocol Attestation Specification by Nick Gerakines - The platformAttestation pattern from app.bumicerts.link.evm New lexicons: - app.certified.signature.inline: Inline signature with JOSE algorithm identifiers (ES256K, ES256, Ed25519) per ATProto convention - app.certified.signature.defs: Shared type definitions providing the `#list` array def (an open union of inline signatures and strongRefs to remote attestation proofs). Follows the atproto convention of putting reusable defs in a dedicated `*.defs` lexicon; see the lexicon spec section on "Lexicon Files" and the style guide's recommendation for `.defs` schemas. - app.certified.signature.proof: Remote attestation proof record holding the CID of attested content, hosted in the attestor's repository 19 record lexicons gain an optional `signatures` property (a flat array referencing app.certified.signature.defs#list) so callers access them as record.signatures[] with no wrapper nesting. This matches the ATProtocol Attestation Specification while keeping the array definition DRY via a single shared lexicon def. Lexicons updated: - org.hypercerts.claim.* (activity, contribution, contributorInformation, rights) - org.hypercerts.collection - org.hypercerts.context.* (acknowledgement, attachment, evaluation, measurement) - org.hypercerts.funding.receipt - org.hypercerts.workscope.tag - org.hyperboards.* (board, displayProfile) - app.certified.badge.* (award, definition, response) - app.certified.actor.* (profile, organization) - app.certified.location app.certified.link.evm is deliberately excluded: it already carries its own EIP-712 wallet-ownership proof in the `proof` field, which is the integrity mechanism for its semantic DID ↔ wallet claim. The EIP-712 signature is incompatible with the ATProto attestation spec (different preimage, hash function, signature format, and binding model), and adding a second signing mechanism on the same record would provide no additional trust while inviting confusion about which signature is authoritative. Includes validation tests for inline signatures, proof records, and signatures attached to records, plus regenerated SCHEMAS.md, ERD.puml, README.md, and a changeset.
Spec-conformance passAudited the signature lexicons against the upstream ATProtocol Attestation Specification and tightened our wording from "inspired by" to a real conformance claim. Documenting the decisions here since several earlier design choices turned out to be unjustified or actively misaligned. What changed
What I did not change
Audit clean against the spec
Open questions / follow-ups welcome. |
|
Re: my earlier comment about whether to move the list definition into a |
…tion Tighten the signature lexicons added in this PR to conform strictly to the ATProtocol Attestation Specification, rather than carrying speculative extensions inherited from the original design draft. - Remove signatureType from app.certified.signature.inline. The spec has no such field; the signing curve is canonically derived from the multicodec prefix on the verification method's publicKeyMultibase. A separate tag was redundant in the happy path and a downgrade-attack vector in the adversarial path (could disagree with the multicodec). - Drop Ed25519 from the documented algorithm story. EdDSA isn't excluded by normative MUST/SHALL language in the spec but the spec's signing primitive is ECDSA + BIP-0062 low-S and the multicodec->curve table only covers 0xE701 (K-256) and 0x1200 (P-256). Ed25519-signed records would be schema-valid but unverifiable by spec-conforming verifiers. - Remove maxLength: 100 cap on app.certified.signature.defs#list. No rationale was ever recorded for the bound and the spec does not restrict array length. - Update lexicon descriptions to use spec terminology: 36-byte CIDv1, canonical DAG-CBOR, BIP-0062 low-S, \$sig repository binding. - Rewrite README signatures section to claim conformance explicitly, drop the now-stale JOSE algorithm-identifiers table, add a brief signing/verification procedure summary, and explain replay defense via CID + \$sig.repository. - Update changeset language from "inspired by" to "conforms to". - Drop signatureType usages from tests and remove the now-meaningless open-knownValues test case. - Regenerate SCHEMAS.md.
Bring the agent-facing skill guide in line with the rest of the signature work on this PR. Adds: - Signatures subsection in Lexicon Overview covering signature.defs and signature.proof - Note in the EVM Link row that link.evm can additionally carry signatures[] for record provenance on top of EIP-712 wallet consent - Updated Relationship Map listing signature/defs and signature/proof, with a note explaining why the per-record signatures arrows aren't drawn - "Attaching Cryptographic Signatures" example showing mixed inline + remote-attestation usage, plus a short signing-procedure summary pointing at the ATProto Attestation Specification - "Creating a Remote Attestation Proof" example for the proof record
The Leaflet URL is a useful background blog post but not the normative specification document. Update all doc references so the primary link is the canonical spec at tangled.org and the blog post appears as a secondary "see also" link. Affects README, changeset, and the building-with-hypercerts-lexicons skill.
Previously the cryptographic signatures example presented a record with signatures already embedded, using a placeholder comment for the ECDSA bytes and explaining the signing procedure only afterward. That reads backwards: a developer wanting to attach a signature needs to see how the bytes are produced first. Rewrite both README and SKILL.md so the example walks step by step: 1. Build the inline signature object — compute the spec-defined CID over the record (insert temporary \$sig with repository DID, encode canonical DAG-CBOR, hash to 36-byte CIDv1) and ECDSA-sign with @atproto/crypto's Secp256k1Keypair.sign() which enforces low-S automatically. 2. Build a remote attestation strongRef (optional). 3. Attach either or both to the record via spread. Uses canonical ATProto + IPLD libraries (@atproto/crypto, @ipld/dag-cbor, multiformats) rather than placeholder pseudocode. multiformats is already a runtime dep of this package; the others are shown as consumer imports.
Defends against docs/test divergence by making the docs themselves the test source. The new test reads README.md and the building-with-hypercerts-lexicons skill, finds the "Cryptographic Signatures" section in each, extracts every fenced TypeScript code block under it, concatenates them into one ESM module (deduping imports and appending synthetic exports for the identifiers we want to inspect), writes the result to tests/extracted/ (gitignored), and dynamically imports. For each source file, asserts: - Extraction + execution succeeds without throwing - The produced CID is a 36-byte CIDv1 with the dag-cbor/SHA-256 prefix - Secp256k1Keypair.sign() returns 64 raw r,s bytes - The inline signature has the correct \$type and references the signature bytes - The remote-attestation strongRef has the correct \$type and at:// URI - Both signatures attach to the final record in order - The resulting record (with a syntactically valid CID substituted for the "bafy..." placeholder) passes lexicon validation - The produced signature verifies via @atproto/crypto verifySignature Adds @atproto/crypto and @ipld/dag-cbor to devDependencies (only used by this test; not exposed to package consumers).
Line 51 restated what line 13's #inline description already says about ECDSA + low-S + multicodec-derived curve. Addresses review comment on PR #170.
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
Align with the per-lexicon naming convention in AGENTS.md
("one file per lexicon, named validate-<lexicon-slug>.test.ts").
The tests cover the array def at app.certified.signature.defs#list
(open union of inline + strongRef, optional/empty/mixed array
semantics), so signature-defs is the lexicon under test. Activity
is just the representative carrier — all 19 record lexicons share
the same def.
Addresses CodeRabbit feedback on PR #170.
…g tests The "one file per lexicon" rule is a default, not a strict requirement. When a test exercises behavior that spans multiple lexicons or code files (shared *.defs lexicons, cross-cutting union semantics, etc.), pick the clearest name for what's under test rather than duplicating near-identical tests across every carrier. Cite validate-signature-defs.test.ts as an illustrative example. Surfaced by CodeRabbit feedback on PR #170.
Line 51 restated what line 13's #inline description already says about ECDSA + low-S + multicodec-derived curve. Addresses review comment on PR #170.
|
@s-adamantine and crew I am trying to understand where are the private keys actually stored, and how can an app use certified.app or ePDS to offer login in a way that the user can maintain local-first private keys? Maybe I am missing something, but i don't see that in the docs (yet), so i'm asking here as its the most relevant thread i found so far. Context: I develop local-first ipfs based apps, so i am very familiar with the crypto primitives and did system you are using, and would love to collaborate and/or rely on the certified stack as long as each agent can generate and own the private keys. |
|
Hi @gotjoshua thanks for your interest! Firstly please note this PR is superseded by #219. Secondly, we don't yet have any infrastructure to allow users to store local-only private keys, or even specify where private keys should live. This lexicon schema extension is just the foundation layer providing somewhere for attestation signatures to live. Passkeys and crypto wallets are obvious candidate solutions for this, as we certainly don't want to badly reinvent wheels, especially when storage of private keys is such a security-sensitive matter. However see #217 for some of the challenges around using crypto wallets. Like you we are very keen on the idea of adding support for users to login and attest to things using their own self-custodied private keys, and have already done some design in these areas. But I think we have some other urgent work to finish before we can go too much deeper into this. |
|
thanks @aspiers
ok, but does that mean that the certified servers store the private key associated with each did they create? |
|
Yes this is how almost every PDS in the ATmosphere currently works. E.g. if you sign up for an account on bsky.social, you don't get given a private key to store in the browser or in a wallet. |
|
thanks for the clarification @aspiers ! so its a bit like a "custodial" crypto wallet situation. users trust the app servers to keep the keys and to sign on their behalf, true? have you looked into UCAN at all? |
|
Yes it's similar to that but users have the option to set a recovery key and migrate their account elsewhere. Yes we are familiar with UCAN. Happy to discuss further but I recommend joining the Hypercerts Telegram community and/or filing separate GitHub issues, as this PR is already closed and we're getting a bit off-topic now ;) |
Align with the per-lexicon naming convention in AGENTS.md
("one file per lexicon, named validate-<lexicon-slug>.test.ts").
The tests cover the array def at app.certified.signature.defs#list
(open union of inline + strongRef, optional/empty/mixed array
semantics), so signature-defs is the lexicon under test. Activity
is just the representative carrier — all 19 record lexicons share
the same def.
Addresses CodeRabbit feedback on PR #170.
…g tests The "one file per lexicon" rule is a default, not a strict requirement. When a test exercises behavior that spans multiple lexicons or code files (shared *.defs lexicons, cross-cutting union semantics, etc.), pick the clearest name for what's under test rather than duplicating near-identical tests across every carrier. Cite validate-signature-defs.test.ts as an illustrative example. Surfaced by CodeRabbit feedback on PR #170.
Line 51 restated what line 13's #inline description already says about ECDSA + low-S + multicodec-derived curve. Addresses review comment on PR #170.
[HYPER-181] add cryptographic signature support to record lexicons (resubmission of #170)
Summary
Adds optional cryptographic signature support to all 21 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 at badge.blue (see also the accompanying blog post).
In particular, signatures sign the CID of the record (not its raw bytes), and CID generation injects a temporary
$sigobject carrying the housing repository's DID so signatures cannot be replayed across content versions or across repositories.Changes
New lexicons
app.certified.signature.defs— shared type definitions for signatures. Provides:#list— open union array of inline signatures andcom.atproto.repo.strongRefreferences to remote proofs#inline— inline signature object with two required fields:signature(raw ECDSA r,s bytes, low-S per BIP-0062, signing the record's 36-byte CIDv1) andkey(full DID verification method reference, formatdid:{method}:{id}#{fragment}). The signing curve is canonically derived from the multicodec prefix on the verification method'spublicKeyMultibase, so no separate algorithm-tag field is carried on the signature itself.app.certified.signature.proof— remote attestation proof record holding the CID of the attested content (computed with the spec's$sig-repository binding). Lives in the attestor's repository; referenced from the attested record viastrongRef. Optional application-specific fields:note,createdAt.Modified lexicons
All 21 record lexicons gain an optional
signaturesproperty (a ref toapp.certified.signature.defs#list), placed directly on the record with no wrapper object so callers access them asrecord.signatures[]:org.hypercerts.claim.*(activity, contribution, contributorInformation, rights)org.hypercerts.collectionorg.hypercerts.context.*(acknowledgement, attachment, evaluation, measurement)org.hypercerts.funding.receiptorg.hypercerts.workscope.tagorg.hyperboards.*(board, displayProfile)app.certified.badge.*(award, definition, response)app.certified.actor.*(profile, organization)app.certified.locationapp.certified.graph.followapp.certified.link.evmNote on
app.certified.link.evm: this record carries two orthogonal integrity primitives. The EIP-712prooffield proves wallet consent (the EVM key holder agreed to be linked to the DID). Thesignaturesarray 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. Bridging thesignatures[]machinery to EVM keys carried inlink.evmis a follow-up; see the dedicated issue.Design notes
This is a non-breaking extension — signatures are optional on all records. Two signature patterns are supported:
app.certified.signature.defs#inlineapp.certified.signature.proofrecords in other repositories, viacom.atproto.repo.strongRefSigning algorithm
The spec mandates ECDSA with the low-S variant per BIP-0062. The signing curve is determined by the multicodec prefix on the verification method's
publicKeyMultibase:0xE701ES256K0x1200ES256There is deliberately no
signatureTypefield on the inline signature: the algorithm is canonically derived from the resolved key, and a separate tag would risk disagreeing with the multicodec.Signing & verification procedure (summary)
To sign:
signaturesfield.$sigobject containing$typeand the housingrepositoryDID.0x01 0x71 0x12 0x20+ 32-byte hash).key.To verify, reconstruct the same CID using the housing repo's DID, resolve
keyto a public key via the DID document, and check the ECDSA signature against the CID bytes.Documentation tests
The signing example in README.md and the
building-with-hypercerts-lexiconsskill is no longer just illustrative prose: a new test (tests/validate-signing-example.test.ts) extracts the TypeScript code blocks from the "Cryptographic Signatures" section of each markdown file, concatenates them into a runnable ESM module, dynamically imports the result, and asserts:Secp256k1Keypair.sign()returns 64 raw r,s bytes.$typeand shape.@atproto/crypto'sverifySignature(sign + verify round-trip).This makes documentation drift structurally impossible: any edit to the docs example that breaks the procedure fails CI, because the docs are the test source.
@atproto/cryptoand@ipld/dag-cborwere added as devDependencies to support this test.Example usage
References
Checklist
npm run checkpassesSummary by CodeRabbit
Release Notes
New Features
Documentation
Tests
Chores