Skip to content

dpp-crypto: DppAccessCredential has no proof member — access credentials cannot be signed #69

Description

@LKSNDRTMLKV

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:

  1. 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.
  2. 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:webdpp-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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestquestionFurther information is requested

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions