feat(overlay-topics): add tm_uora_dpp / ls_uora_dpp for attestation anchors - #423
Draft
mo-jaber wants to merge 1 commit into
Draft
feat(overlay-topics): add tm_uora_dpp / ls_uora_dpp for attestation anchors#423mo-jaber wants to merge 1 commit into
mo-jaber wants to merge 1 commit into
Conversation
…nchors Admission and lookup for UORA attestation anchors in the `uora-anchor-v2` format, keyed on the `did:key` of the party that made the claim. A UORA attestation is a claim one party makes about one product: who made it, who it passed to, what happened to it. The claims never go on chain, both because they can carry personal data and because a digest is 32 bytes whatever the claim's size, which is what keeps per-event anchoring affordable at fleet scale. The anchor carries the digest plus the issuer, subject, type and anchoring service in the clear, so an index has something to key on. What admission proves: the seven fields were sealed together by the key locking the output, and that key is the BRC-42 child of the anchoring service named in field 6 under counterparty `anyone`. Producing an output satisfying both needs that service's private key, so every admitted anchor names its author checkably, by anyone holding the transaction, with nothing configured. That last part is why the service key is a field rather than a constructor argument: a shared node serving several deployments would otherwise need amending whenever one was added, and a reader holding only the transaction could not attribute it at all. `anchorServiceKeys` narrows what an instance carries and is documented as a preference about what to index, not a boundary. What it does not prove: that the party in field 3 made the claim. That is a did:key copied onto the chain as given, and anyone able to write an anchor can write any DID into it. What a claim is worth is settled by the attestation's own signature, off chain. Both the topic manager docblock and `getDocumentation()` say so, because the field invites the opposite reading. Anchors are leaves: never spent, no predecessor, no transition rules, nothing retained. Every valid anchor in a transaction is admitted rather than exactly one, which leaves a service free to batch without this topic changing. Additive only. No existing topic, export, schema, dependency or behaviour changes. Built on the existing `shared/identifyPushDropOutputs` helper and follows `certmap`'s pattern of validating an output against an operator the token itself names. 27 tests: 411 in the package, up from 384. Two worth reading are `refuses an anchor naming a service its locking key cannot come from`, which is the format's whole claim, and `does not reuse a fields array`, which pins that PushDrop.lock appends the signature to the array it is handed.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
Adds
tm_uora_dppandls_uora_dpp: admission and lookup for UORA attestation anchors, keyed on thedid:keyof the party that made the claim.Draft on purpose.
CONTRIBUTING.mdasks that a substantial new capability be discussed before implementation, so this is the proposal with working code attached. Close it without hard feelings if the direction is wrong.The problem
A digital product passport records claims made about one physical object over its life: who manufactured it, who it passed to, what service or recycling happened to it. Each claim has a different author, and the point is that a reader can check one without trusting whoever showed it to them.
The claims cannot go on chain, because they carry personal data and a battery fleet produces one per service event. So only a digest is anchored. But a digest alone cannot be indexed by author: an overlay holding the hash of a document it has never seen knows something existed and cannot say whose claim it was.
So "what has this party attested" has nowhere to be answered. That is what this topic is for.
What it adds
An anchor is a 1-satoshi PushDrop output:
uora-anchor-v2did:keyPushDrop.lockLookups take
issuer,issuerKey,subject,attestationIdordigest, narrowed optionally byuoraTypeoranchoredBy. Answers are the outputs as BEEF, so a caller checks them against the chain rather than trusting the index.The decision worth reviewing
Field 6 names the anchoring service, and the locking key must be its BRC-42 child at
[1, 'uora anchor v2'], key id the attestation id, counterpartyanyone. Producing that needs the service's private key.So every admitted anchor names its author checkably, by anyone holding only the transaction, with nothing configured. A shared node can carry anchors from a deployment it was never told about and still say whose each one is. This follows
certmap, which validates against the registry operator its token names. Our first design instead configured each instance with a list of services to accept, which a shared node cannot sensibly be, and which leaves a reader holding only the transaction unable to attribute it at all.anchorServiceKeyssurvives as an optional narrowing, documented as a preference about what to index rather than a security control.What it does not prove: that the party in field 3 made the claim. Anyone able to write an anchor can write any DID there. That is settled by the attestation's own signature, off chain. The class docblock and
getDocumentation()both say so, because the field invites the opposite reading.Anchors are leaves. Never spent, nothing retained. Every valid anchor in a transaction is admitted rather than exactly one, so a service can batch without this topic changing.
Compatibility
Additive only. No existing topic, export, type, schema, dependency or behaviour changes.
src/index.tsand added to thepack:checkallowlistuoraDppAnchors, created lazily@bsv/sdkusage stays inside the declared^2.1.6peer range; no dependency addedshared/identifyPushDropOutputshelperEvidence
typechecklint(oxlint--deny-warnings)format:checkpack:checktest27 new tests, negative cases included: an anchor naming a service its locking key cannot derive from, a field edited after signing, a lifted signature, a non-canonical key, a wrong key id, an Ed25519
did:key, an over-long field. Lookup coverage usesmongodb-memory-server, as the package's existing tests do.Exercised on mainnet, end to end
did:keydid:key:zQ3shjnZjhHL3fcgraoPJhZUFQ4y6TBPg7K4iVyChLskjMc2Yuora-anchor-v2, digest27ba6868…ab4fls_uora_dppwhat that DID has attestedanchoredByeach return it; a wronguoraTypeand an unrelated DID return nothingAnchor:
a66141b1c2386bf53e00b441d2cdcaf18948a575fb68e11a175093c89f6476da:0The last row touches none of the issuing infrastructure, and it found a defect no unit test had: the writer's verify command had never learned to print the v2 attribution.
Status
This supports a demonstration programme and the data specification behind it is not final. That is the honest reason for the draft: better to answer the questions below now than after a release.
Exactly one anchor exists in this format on mainnet, kept to one on purpose. Changing the layout today costs a paragraph rather than a migration.
Questions
Should this use
@bsv/didinstead of hand-rollingdid:key? The reader needs about fifteen lines of multibase encoding. I wrote them locally, then found@bsv/didis a sibling workspace package exporting exactly this. The argument against is that it pullsqrcode, and an overlay index has no business shipping a QR library. Your call.Topic naming.
tm_uora_dpphas an underscore where the convention here is unbroken (tm_supplychain,tm_walletconfig). It sits beside atm_dppthe programme already runs, and the plan below names it, so a rename is @sirdeggen's call rather than mine.Does the anchor format belong in
specs/eventually? Not proposed now. It should earn that by being used.Provenance
Implements a deliverable named in a BSV Association programme plan of 12 June 2026, "DPP on BSV, Plan and Next Steps", by Darren Kellenschwiler (@sirdeggen), whose resolver and protocol layer specifies "a new
tm_uora_dpptopic manager + DID-keyed lookup in ts-stackpackages/overlays/topics, rolled into our existing overlay infra, mainnet; attestations anchored as signed BSV transactions".The writer lives in a separate BSV Association service. Its format document is implementable without access to either repository, and the two implementations are pinned by a fixture committed verbatim in both, since neither can import the other.
No overlapping work: searches for
uora,dppandproduct passportreturn nothing, and no open PR touchespackages/overlays/topics.