# live-conformance/c2pa-sdk-identity — identity assertions in manifests a C2PA SDK signed The corpus of [`extensions/c2pa-binding.md`](../../extensions/c2pa-binding.md) §9.3: PNG files a C2PA SDK signed, each with the manifest [energy-binding](../../extensions/energy-binding.md) §6 emits and the Creator Assertions Working Group's identity assertions as §4.11 has a WAI tool write them through the SDK: the assertion of §4.10, written by the writer of WAI's own stores, in the placeholder the SDK reserves. **Pinned, not regenerated.** The SDK draws a manifest's identifier (`urn:c2pa:` and a version 4 UUID), a 16-byte salt for every assertion and the parent ingredient's `instanceID` from the system's random source. Every hash over an assertion, the actions' reference to the ingredient, the identity assertion's payload and both signatures follow from those, so two signings of one input differ in bytes and agree in length and in everything a verdict reads. The files here were written by `wai_c2pa_sdk_identity_vectors gen` with the SDK at the version `wai-rs`'s lockfile holds (the `c2pa` crate, version 0.92.1, which every claim names in its `claim_generator_info`), and `vectors.json` pins each by its SHA-256. They are written again when the lockfile moves the SDK, and only then: on 2026-11-12, from 0.91.7 to 0.91.11, and on 2026-11-14, from 0.91.11 to 0.92.1. Running `gen` again writes another corpus with the same outcomes, and is not how this one is checked. **Files.** `vectors.json` holds `time` (the instant *T*), `identityAnchors` (the identity trust anchors, each a certificate's DER in base64), `claimAnchors` (the claim signer's anchor, which only a C2PA validator needs), `sdk` (the SDK whose validator's reports are pinned, and its version) and the vectors: each its `name`, `about`, `asset` (the signed PNG), `sha256` (its pin), `referenced` (`created`, or `created_or_gathered` for a verifier configured to admit the claim's gathered assertions), `expect`, and `sdk` (what that SDK's validator reports of the file when it holds both anchors: its validation `state` and its `failure` codes, sorted). Every key and certificate is derived from fixed seeds, and none is a key anyone holds. The claim signer is an Ed25519 certificate for `c2pa-kp-claimSigning` and `id-kp-emailProtection` under the corpus's claim anchor; a validator that does not hold that anchor reports `signingCredential.untrusted` for every file, and one that does not hold the identity anchor `cawg.x509.credential.untrusted` for every identity assertion. **Equivalence.** A conforming verifier given a vector's asset, the anchors and *T* reaches the outcome `expect` states (§4.11): for `refuse`, the `reason` of §7 and the code §7 gives it (`c2pa`); for `accept`, each identity assertion's `label`, `roles`, `actor` (the SHA-256 of its end-entity certificate), `referenced` (the labels of the assertions it references, in order), `anchor` (the SHA-256 of the anchor its chain reached) and `signed` (which serialization of its payload the signature is over: `schema`, the members in the schema's order, which a writer signs by default, or `deterministic`, the core deterministic encoding; §4.10). **The vectors.** - Good: `creator` (over the hard binding, the actions and the energy binding, role `cawg.creator`), `creator-and-publisher` (two identity assertions, `cawg.identity` and `cawg.identity__1`), `hard-binding-alone` (no role), `creator-deterministic` (the creator's assertion signed over the core deterministic encoding, the form a writer may ask for), `sdk-builder-written` (the creator's assertion written and signed by the SDK's own identity builder, nothing of it this profile's: its members are in the schema's order in the store and it holds a `pad2`), and `gathered-reference-admitted` (the file of `gathered-reference`, read by a verifier that admits `gathered_assertions`). - Refused, each a manifest the SDK signed around one defect: `carried-from-another-manifest` and `gathered-reference` (`identity_assertion_mismatch`), `hard-binding-missing`, `assertion-twice`, `pad-nonzero`, `pad1-missing` (`identity_malformed`), `sig-type-unknown`, `algorithm-unsupported`, `signed-by-another-key`, `signed-third-order` (the creator's signature over the payload's members in the order `sig_type`, `role`, `referenced_assertions`, which is neither serialization) and `signed-other-members` (the creator's signature over a payload with the publisher's role, beside a payload that states the creator's) (`identity_signature_mismatch`, each), `credential-untrusted`, `credential-expired` and `credential-wrong-purpose` (`identity_credential_invalid`). Every reason §7.2 gives an identity assertion refuses one. **Checking it.** ``` cargo run --no-default-features --features c2pa_emit,c2pa_envelope --bin wai_c2pa_sdk_identity_vectors -- verify live-conformance/c2pa-sdk-identity python3 -I live-conformance/c2pa_sdk_identity_verify.py live-conformance/c2pa-sdk-identity ``` The first holds every file to its pin and its outcome, has the SDK validate every file's claim signature and hard binding (`claimSignature.validated`, `assertion.dataHash.match`, and no failure of the claim, its hashed URIs or its data hash), holds the SDK's validator, configured with the corpus's two anchors, to the report `vectors.json` pins for each file and to accepting whole every manifest whose identity assertions are signed by default, and signs every vector again to the same outcomes and the same reports. The second is the second implementation, written from the extension's text: it reads the PNG, the store and the claim itself, verifies each identity assertion with the verifier `c2pa_live_verify.py` holds WAI's own stores to, requires every mutated copy of a good vector to be refused, and drops each of its checks in turn and requires a vector or a mutated copy to catch it. **What the SDK's own validator reports.** The SDK reads identity assertions itself. Of the corpus's files, on 2026-11-14, at the version `vectors.json` names (0.91.11 reported the same of every vector of the files it signed, on 2026-11-12, and 0.91.7 of its own on 2026-11-11), configured with the corpus's claim anchor and identity anchor (its `manifest` and `cawg` trust kinds), as each vector's `sdk` pins it: - **the manifests this profile writes by default are accepted whole**: for `creator`, `creator-and-publisher`, `hard-binding-alone` and `sdk-builder-written` its state is `Trusted`, it reports no failure code, and for each identity assertion it reports `cawg.x509.credential.trusted`, `cawg.x509.signature.validated` and `cawg.identity.well-formed`; - for `creator-deterministic` it reports `cawg.x509.signature.mismatch` and nothing else, with the credential trusted. Identity Assertion 1.3 §8.2.1 has the holder sign the `signer_payload` in RFC 8949 §4.2.1's core deterministic encoding, whose map keys are in the bytewise order of their encodings (`role`, `sig_type`, `referenced_assertions`). The SDK does not verify those bytes: it verifies a signature over its own serialization of the payload's members in the order the schema lists them (`referenced_assertions`, `sig_type`, `role`), and its builder signs the same. That is the clause the deployed validators depart from, and it is why §4.10 signs the schema's order by default and keeps §8.2.1's encoding as a form a writer may ask for ([decisions](../../decisions.md) D6). This profile's verifier accepts both and states which; - it reports the code this profile does for `carried-from-another-manifest` (`cawg.identity.assertion.mismatch`), `hard-binding-missing`, `assertion-twice`, `pad-nonzero`, `sig-type-unknown`, `credential-untrusted`, and for `signed-by-another-key`, `signed-third-order` and `signed-other-members` (`cawg.x509.signature.mismatch`, each); `cawg.x509.signature.outside_validity` with `cawg.x509.credential.untrusted` for `credential-expired`; `assertion.required.missing` for `pad1-missing`; and `cawg.x509.credential.invalid` for `algorithm-unsupported`; - it reports no failure for `credential-wrong-purpose` (it accepts as an identity credential a certificate whose only purpose is claim signing, which §4.10's profile refuses, `identity_credential_invalid`) or for `gathered-reference` (it looks for a referenced assertion in `gathered_assertions` as well, which this profile does only where its verifier is configured to). The tests `the_sdks_validator_accepts_the_identity_assertions_this_profile_writes`, `the_sdks_validator_accepts_the_stores_the_store_writer_writes` (the stores of `c2pa-identity`, beside their envelopes) and `the_sdks_validator_departs_from_8_2_1_and_reports_a_mismatch_of_its_bytes` of `c2pa_emit::identity_corpus` hold the first two points, and every vector's `sdk` pin the rest, so an SDK whose validator reports otherwise is noticed.