What
DppAccessCredential (crates/dpp-crypto/src/access/credential/types.rs) carries no proof and no jws field:
pub struct DppAccessCredential {
pub context: Vec<String>,
pub credential_type: Vec<String>,
pub id: String,
pub issuer: String,
pub valid_from: DateTime<Utc>,
pub valid_until: DateTime<Utc>,
pub credential_subject: DppCredentialSubject,
pub credential_status: Option<CredentialStatus>,
}
CredentialBuilder::build() returns one of these, unsigned. SignedCredential exists in dpp-domain::identity but wraps PassportCredential — it attests a passport, not an access role.
So there is no signed representation of an access credential anywhere in the workspace. The issuer field is a self-asserted string.
Why this is load-bearing
The verification stack around it is otherwise complete and reads as authoritative:
verify_credential_claims — structure and validity window
verify_credential_claims_with_trust — is this issuer trusted for this audience
check_revocation — status-list bit
CredentialRole::audience() — the Art. 77(2) mapping
Every one of those operates on an unauthenticated document. verify_credential_claims is honest about it ("no signature check — that is the JWS verifier's responsibility"), but the trust check in particular is misleading in effect: it asks whether issuer is trusted, and anyone can put any DID in issuer. Trust checking without signature verification is decorative.
What is needed
A decision on the signed envelope, then the type to carry it. Two shapes, both standard:
- VC-JWT — the credential JSON is the payload of a compact JWS, and the wire format is that JWS. Nothing changes in
DppAccessCredential; the envelope is external and the engine unwraps it.
- Embedded proof — add a
proof member (W3C Data Integrity), so the credential is self-contained.
(1) composes better with dpp_crypto::jws::verify_jws, which already exists and takes (jws, public_key_b64). Either way the verifier needs the issuer's public key, which means resolving the issuer did:web — dpp-resolver already does DID-document fetching for passports, so the mechanism exists but is not reachable from dpp-crypto.
Impact
Blocks the audience-access campaign at the point where a credential must actually grant something (dpp-docs/work/AUDIENCE-ACCESS-PLAN.md chunk 04). Chunks that can proceed regardless: trusted-issuer configuration and revocation wiring, both of which are policy layers that will sit on top of whatever envelope is chosen.
dpp-engine#60 implements the transport and deliberately injects a PresentedCredential rather than an Audience, precisely because the document it parses is unauthenticated.
Suggested
Do not add a proof field speculatively. Pick the envelope first — VC-JWT is the lower-friction option given verify_jws — and record it, because it is a wire-format decision that binds every future issuer.
What
DppAccessCredential(crates/dpp-crypto/src/access/credential/types.rs) carries noproofand nojwsfield:CredentialBuilder::build()returns one of these, unsigned.SignedCredentialexists indpp-domain::identitybut wrapsPassportCredential— it attests a passport, not an access role.So there is no signed representation of an access credential anywhere in the workspace. The
issuerfield is a self-asserted string.Why this is load-bearing
The verification stack around it is otherwise complete and reads as authoritative:
verify_credential_claims— structure and validity windowverify_credential_claims_with_trust— is this issuer trusted for this audiencecheck_revocation— status-list bitCredentialRole::audience()— the Art. 77(2) mappingEvery one of those operates on an unauthenticated document.
verify_credential_claimsis honest about it ("no signature check — that is the JWS verifier's responsibility"), but the trust check in particular is misleading in effect: it asks whetherissueris trusted, and anyone can put any DID inissuer. Trust checking without signature verification is decorative.What is needed
A decision on the signed envelope, then the type to carry it. Two shapes, both standard:
DppAccessCredential; the envelope is external and the engine unwraps it.proofmember (W3C Data Integrity), so the credential is self-contained.(1) composes better with
dpp_crypto::jws::verify_jws, which already exists and takes(jws, public_key_b64). Either way the verifier needs the issuer's public key, which means resolving the issuerdid:web—dpp-resolveralready does DID-document fetching for passports, so the mechanism exists but is not reachable fromdpp-crypto.Impact
Blocks the audience-access campaign at the point where a credential must actually grant something (
dpp-docs/work/AUDIENCE-ACCESS-PLAN.mdchunk 04). Chunks that can proceed regardless: trusted-issuer configuration and revocation wiring, both of which are policy layers that will sit on top of whatever envelope is chosen.dpp-engine#60implements the transport and deliberately injects aPresentedCredentialrather than anAudience, precisely because the document it parses is unauthenticated.Suggested
Do not add a
prooffield speculatively. Pick the envelope first — VC-JWT is the lower-friction option givenverify_jws— and record it, because it is a wire-format decision that binds every future issuer.