You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The maintainer explicitly chose to continue accepting legacy credential proofs. Preserve existing verification behavior for v0.13; this issue no longer requires a pre-release acceptance change or compatibility decision.
Distinguish unsigned legacy timestamp tampering from backdating by a signing-key holder. Binding the timestamp in modern proofs prevents editing it without the key, but does not establish an independently trusted signing time: an old-key holder can also create a newly signed, backdated modern proof. Removing legacy acceptance alone would not solve that broader historical-authorization question and would invalidate existing legacy credentials.
The analysis below is retained as background, not an approved plan to reject legacy proofs. #1090's broader proof-set policy is separate.
Problem
Credential verification accepts the legacy EcdsaSecp256k1Signature2019 proof
type indefinitely, and resolves the issuer's document at the proof's own created — a member that type leaves outside the signature.
verifyProof resolves with versionTime: proof.created
(packages/keymaster/src/keymaster.ts:1632) to select the version of the
issuer's document that supplies the verification key. On a DataIntegrityProof
that member is inside the signed configuration, so altering it only makes that
proof fail. On a legacy proof it can be moved freely while the signature still
verifies.
The consequence is that a holder of an issuer key that has since been rotated
out can sign a legacy-labelled credential, date it before the rotation, and have
it resolve to the document version that still lists that key. Rotating a
compromised key does not retire credentials signed with it.
Nothing emits this type for credentials any more
Credentials are signed by archonEcdsaJcs2019Proof and eddsaJcs2022Proof
together (:1381-1382). The legacy type is emitted only by addOperationProof
(:1272), which serves DID operations. Acceptance is what remains: supportedProof (:1555) and the Python port (python/keymaster/src/keymaster/core.py:1976, :2000, :2078).
What acceptance is buying
Credentials issued before #1085. They cannot be re-signed by anyone but their
original issuer, so dropping acceptance makes them unverifiable. The size of
that population is unknown and only grows.
Constraints
Not a consensus change. Credential proofs never reach the gatekeeper's verifyProofFormat; verifyProof is local policy. Operations are unaffected
and keep the legacy type, which both gatekeeper ports hard-require (DID operation proofs claim EcdsaSecp256k1Signature2019 but do not implement it #1087).
The surfaces are the two keymaster ports and Herald, which verifies through
keymaster (services/herald/server/src/routes.ts:124-135).
A date-based cutoff cannot work. Gating on "legacy proofs created before
X" is defeated by moving created, which is unsigned on exactly the proofs
being phased out.
Honouring created is what keeps old credentials verifiable across a
rotation, and is also what lets an attacker choose a version. Resolving the
issuer's current document instead would close the hole without dropping the
type, at the cost of every credential whose signing key has since been
rotated.
Compatibility decision — 2026-09-23
The maintainer explicitly chose to continue accepting legacy credential proofs. Preserve existing verification behavior for v0.13; this issue no longer requires a pre-release acceptance change or compatibility decision.
Distinguish unsigned legacy timestamp tampering from backdating by a signing-key holder. Binding the timestamp in modern proofs prevents editing it without the key, but does not establish an independently trusted signing time: an old-key holder can also create a newly signed, backdated modern proof. Removing legacy acceptance alone would not solve that broader historical-authorization question and would invalidate existing legacy credentials.
The analysis below is retained as background, not an approved plan to reject legacy proofs. #1090's broader proof-set policy is separate.
Problem
Credential verification accepts the legacy
EcdsaSecp256k1Signature2019prooftype indefinitely, and resolves the issuer's document at the proof's own
created— a member that type leaves outside the signature.verifyProofresolves withversionTime: proof.created(
packages/keymaster/src/keymaster.ts:1632) to select the version of theissuer's document that supplies the verification key. On a
DataIntegrityProofthat member is inside the signed configuration, so altering it only makes that
proof fail. On a legacy proof it can be moved freely while the signature still
verifies.
The consequence is that a holder of an issuer key that has since been rotated
out can sign a legacy-labelled credential, date it before the rotation, and have
it resolve to the document version that still lists that key. Rotating a
compromised key does not retire credentials signed with it.
Nothing emits this type for credentials any more
Credentials are signed by
archonEcdsaJcs2019ProofandeddsaJcs2022Prooftogether (
:1381-1382). The legacy type is emitted only byaddOperationProof(
:1272), which serves DID operations. Acceptance is what remains:supportedProof(:1555) and the Python port (python/keymaster/src/keymaster/core.py:1976,:2000,:2078).What acceptance is buying
Credentials issued before #1085. They cannot be re-signed by anyone but their
original issuer, so dropping acceptance makes them unverifiable. The size of
that population is unknown and only grows.
Constraints
verifyProofFormat;verifyProofis local policy. Operations are unaffectedand keep the legacy type, which both gatekeeper ports hard-require (DID operation proofs claim EcdsaSecp256k1Signature2019 but do not implement it #1087).
The surfaces are the two keymaster ports and Herald, which verifies through
keymaster (
services/herald/server/src/routes.ts:124-135).X" is defeated by moving
created, which is unsigned on exactly the proofsbeing phased out.
createdis what keeps old credentials verifiable across arotation, and is also what lets an attacker choose a version. Resolving the
issuer's current document instead would close the hole without dropping the
type, at the cost of every credential whose signing key has since been
rotated.
Related
createdproblem on the operation path, where it doesrequire a fork