Continuum Academy · Chapter 03
Anatomy of an in-toto attestation
The previous chapter separated hashes, attestations, provenance and signatures. We now know what each one does. Let’s open the attestation for payment-service-4.7.2.tar.gz.
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"name": "payment-service-4.7.2.tar.gz",
"digest": {
"sha256": "3a16...9f21"
}
}
],
"predicateType": "https://example.com/attestation/build/v1",
"predicate": {
"...": "..."
}
}
This is the structure of an in-toto Statement v1: the document that connects a subject to a claim. The example.com type and the predicate content are teaching examples, not standards. The digest is fictional and shortened; the ellipsis is not part of a usable digest. We will complete the example in this chapter.
We are studying the in-toto Attestation Framework, not in-toto’s historical layout/link mechanisms. Chapter 04 will replace our teaching Predicate with an actual SLSA Provenance structure.
1 — Four fields, four roles
First, read the four names without examining their values:
_type: how do we read the Statement’s structure?subject: which artifact are we talking about?predicateType: what kind of claim are we making?predicate: what data expresses that claim?
The Predicate is the part devoted to the claim itself. It sits inside the Statement, which identifies what the claim applies to. These two layers avoid having to redefine how to identify an artifact for every new kind of declaration.
2 — _type: what object are we reading?
{
"_type": "https://in-toto.io/Statement/v1"
}
This excerpt isolates the field; it is not a complete Statement. For the version covered here, the value is exactly https://in-toto.io/Statement/v1. It identifies the outer object’s schema: its structure and the rules for reading it.
A reader can recognize a Statement v1 before examining its claim. This value does not say whether the claim concerns a build, tests or a release. That is the job of predicateType. Both identifiers sit at the same level in the JSON, but answer different questions.
3 — subject: what are we talking about?
The square brackets around subject matter: it is an array. Our example has one entry, but a Statement can refer to several subjects. Every subject used here has a digest.
Within the entry, name helps a reader distinguish and locate the file: payment-service-4.7.2.tar.gz. The sha256 key and its value identify the algorithm and digest to compare. To match the Statement to the received content, the verifier uses the digest.
A recipient could rename the archive to release.tar.gz without changing its bytes. Conversely, two different files could have the same name. The name helps readers and may be used in local rules; it does not replace the cryptographic comparison of content.
4 — predicateType: what question are we asking?
predicateType is a URI, a type identifier. Here it is https://example.com/attestation/build/v1. This value identifies the definition used to read the Predicate, not a download address for the artifact.
The same Statement structure could carry provenance, a vulnerability scan result or release information. Those phrases describe categories; they are not, by themselves, the normative URIs to put in the JSON.
The subject answers “about what?” The type answers “what kind of claim?” If a tool does not understand the type, it cannot conclude that the claim meets its requirements simply because it can parse the JSON.
5 — predicate: what are we saying?
TEACHING EXAMPLE — NOT A STANDARD SCHEMA. This excerpt shows our type and its data:
{
"predicateType": "https://example.com/attestation/build/v1",
"predicate": {
"source": "https://git.example.org/payments/payment-service",
"commit": "7d34a8b4e1f98c2040e9844ab754ae068943bb16",
"builder": "payments-release-builder"
}
}
We chose three fields to make the object concrete. source, commit and builder are not generic fields required by in-toto. Our example defines them at this location; a different Predicate type can use a completely different structure.
in-toto provides the Statement framework. The Predicate type defines the meaning and schema of the internal data. In Statement v1, predicate is optional and its absence is equivalent to an empty object; the particular type then determines which data is needed. Our example includes it explicitly.
6 — Read the complete Statement
Here is a complete JSON object. The digest has the length of a SHA-256 value: 64 fictional hexadecimal characters, with no corresponding archive provided. The Predicate is still our teaching model.
Read the Statement, field by field
A complete JSON object. Highlighting guides the reading while every field stays visible. Custom teaching Predicate; fictional values.
All four fields are visible together. The notes explain the role of each one.
in-toto Statement v1
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"name": "payment-service-4.7.2.tar.gz",
"digest": {
"sha256": "3a16000000000000000000000000000000000000000000000000000000009f21"
}
}
],
"predicateType": "https://example.com/attestation/build/v1",
"predicate": {
"source": "https://git.example.org/payments/payment-service",
"commit": "7d34a8b4e1f98c2040e9844ab754ae068943bb16",
"builder": "payments-release-builder"
}
}- 01 / _type
The Statement/v1 value tells us how to read the outer object.
- 02 / subject
Matching the received file uses digest.sha256, not name.
- 03 / predicateType
This example.com URI is a teaching example. It is not SLSA provenance.
- 04 / predicate
source, commit and builder belong to our example; in-toto does not require these fields.
Read across the four areas: recognize the Statement, identify the content it concerns, select the claim definition, then read its data. Braces and brackets make the relationships visible: digest belongs to an entry in subject; builder belongs to our predicate.
There is no signature field in this object. That is not an omission in the diagram.
7 — But where is the signature?
We discussed signatures in the previous chapter. Yet this Statement contains none. The Statement and the signature envelope are separate layers.
The document carries the claim. The envelope carries its representation and the material needed to authenticate it. DSSE, short for Dead Simple Signing Envelope, is the envelope recommended by the framework; it is not the only permitted envelope in every context. The in-toto envelope specification describes this separation.
Wrapping our document does not move subject or predicate. We add a layer around its bytes, not a property inside the Statement.
8 — The Statement inside DSSE
The DSSE JSON envelope has fields named payloadType, payload and signatures. The payload is the content being carried: here, the Statement’s bytes, encoded in base64. This encoding represents them as a JSON string; it does not encrypt them.
A Statement inside its envelope
The document stays the same. DSSE carries its encoded bytes and signatures in an outer layer.
The decoded view shows the Statement inside payload. payloadType and signatures belong to the envelope.
DSSE Envelope
payloadTypeapplication/vnd.in-toto+jsonpayload
Shortened base64 preview; the data is not encrypted.
ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YxIiwKICAic3ViamVjdCI6IFsKICAgIHsKICAg…in-toto Statement v1 · Decoded view
_type- https://in-toto.io/Statement/v1
subject- payment-service-4.7.2.tar.gz
predicateType- https://example.com/attestation/build/v1
predicate- source · commit · builder
signatures
[
{
"keyid": "demo-key",
"sig": "<base64 signature>"
}
]No signature was calculated for this example. keyid is an optional hint, not an authenticated identity.
View JSON with the complete payload
The payload encodes exactly the Statement shown above. The sig value is a placeholder: this envelope is not a cryptographically verifiable example.
{
"payloadType": "application/vnd.in-toto+json",
"payload": "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YxIiwKICAic3ViamVjdCI6IFsKICAgIHsKICAgICAgIm5hbWUiOiAicGF5bWVudC1zZXJ2aWNlLTQuNy4yLnRhci5neiIsCiAgICAgICJkaWdlc3QiOiB7CiAgICAgICAgInNoYTI1NiI6ICIzYTE2MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA5ZjIxIgogICAgICB9CiAgICB9CiAgXSwKICAicHJlZGljYXRlVHlwZSI6ICJodHRwczovL2V4YW1wbGUuY29tL2F0dGVzdGF0aW9uL2J1aWxkL3YxIiwKICAicHJlZGljYXRlIjogewogICAgInNvdXJjZSI6ICJodHRwczovL2dpdC5leGFtcGxlLm9yZy9wYXltZW50cy9wYXltZW50LXNlcnZpY2UiLAogICAgImNvbW1pdCI6ICI3ZDM0YThiNGUxZjk4YzIwNDBlOTg0NGFiNzU0YWUwNjg5NDNiYjE2IiwKICAgICJidWlsZGVyIjogInBheW1lbnRzLXJlbGVhc2UtYnVpbGRlciIKICB9Cn0=",
"signatures": [
{
"keyid": "demo-key",
"sig": "<base64 signature>"
}
]
}Here is the simplified shape of that envelope:
{
"payloadType": "application/vnd.in-toto+json",
"payload": "<base64 encoded Statement>",
"signatures": [
{
"keyid": "demo-key",
"sig": "<base64 signature>"
}
]
}
The values in angle brackets are placeholders, not base64 to verify. The diagram’s expandable panel contains the exact encoding of the Statement shown above; its signature is still fictional.
payloadType tells the reader how to interpret the decoded bytes. A signed envelope can carry one or more signatures. Each sig normally contains a base64-encoded signature. keyid is an optional hint for finding a key; it is not itself authenticated and is not a trusted identity. These field names and roles come from the DSSE JSON envelope.
9 — Why not just sign the raw JSON?
DSSE cryptographically binds the payload type and the payload bytes, using Pre-Authentication Encoding, or PAE. This mechanism prepares an unambiguous message before signing. In particular, it prevents the same signed bytes from being reinterpreted as another message type.
The signature covers this preparation of the decoded bytes, not the envelope’s base64 text. A verifier preserves the exact bytes: reformatting the Statement and serializing it again can change the message being verified, even if the JSON values appear identical. The DSSE protocol specifies this processing.
10 — What does a verifier actually do?
Receiving the envelope and receiving the archive gives a verifier two different objects. It must establish their relationships, then evaluate the claim against its rules. A single signature check cannot establish all of these results.
Five checks, five questions
A view of the checks, with no simulated verdict. Each row needs different data or rules.
All five checks remain separate. Their display order is for teaching; implementations also need to parse an envelope to verify it.
- A
DSSEVerify the signature
Are the type and bytes authenticated by an accepted key?
- B
_typeRead the Statement
Is this schema version supported?
- C
Received artifactCompute → compare subject.digest
Does the file’s digest match the declared subject?
- D
predicateTypeSelect the interpretation
Can we interpret this kind of claim?
- E
predicateEvaluate against policy
Does this data meet our requirements?
VALID SIGNATURE ≠ MATCHING ARTIFACT
MATCHING ARTIFACT ≠ ACCEPTABLE CLAIM
A — Check the envelope. Does the signature match the payload type and bytes? Is the key accepted in this context? Cryptographic validity and key acceptance are separate decisions.
B — Read the Statement. Does _type identify a supported structure? An unfamiliar document does not become interpretable because it is signed.
C — Identify the subject. The verifier computes the digest of the archive it actually received and compares it with subject.digest. It needs the file’s bytes, not just the document describing it.
D — Understand the claim. Does predicateType identify a supported definition? Being able to decode JSON does not mean understanding every possible claim.
E — Evaluate the Predicate. Does the data meet the recipient’s policy? For our example, a rule might require an expected commit or a particular builder, without proving that the account accurately describes the execution.
VALID SIGNATURE ≠ MATCHING ARTIFACT
MATCHING ARTIFACT ≠ ACCEPTABLE CLAIM
These checks provide a reading guide, not a universal execution order. In practice, a tool must already parse the envelope to check its signature, then interpret the same authenticated bytes.
11 — Three common mistakes
- “
subject.namecryptographically identifies the artifact.” No: the name is readable; the digest matches content to the subject. - “A valid DSSE signature proves the Predicate is true.” No: it authenticates the message under the chosen key and trust model, without observing the declared events.
- “
_typeandpredicateTypeare interchangeable.” No: the first identifies the Statement schema; the second identifies the Predicate’s semantic type and schema.
12 — What the standard specifies, and what remains a decision
The in-toto Statement v1 specifies how to identify subjects, identify a Predicate type and place its data. DSSE provides a signing protocol and an envelope format. Different tools therefore have a shared structure to read.
This does not automatically choose trusted actors, accepted keys or the recipient’s business policy. It also does not guarantee the truth of the claim or the software’s security. Trust management will have its own chapter.
13 — Where does provenance fit?
As in chapter 02, provenance belongs in the claim. There is no additional generic provenance field to add to the Statement.
To use SLSA Provenance, we will keep the outer structure and the subject, with this arrangement:
in-toto Statement
subject → our artifact
predicateType → https://slsa.dev/provenance/v1
predicate → SLSA Provenance data
Changing the URI alone is not enough: the data must follow the model it identifies. We will build that Predicate in chapter 04, without treating our teaching object as if it were already SLSA.
14 — Continuum Attest
Continuum Attest stores a native receipt, distinct from an in-toto Statement. Its interoperability code converts that receipt through attest export --format in-toto into a Statement v1 carrying a SLSA Provenance v1 Predicate, inside a DSSE envelope. Exported digests use BLAKE3 rather than this chapter’s teaching SHA-256 example.
attest import --format in-toto checks the supported schema, field consistency and signature against the local trust store. It does not recompute the digest of a delivered artifact. These commands are documented in the CLI reference; they are not a universal verifier for every Predicate or policy.
15 — Takeaways
The Statement structures the claim. subject identifies what it concerns; predicateType identifies its kind; predicate holds its data. DSSE can wrap and sign that document.
We now know the structure. Next, we will replace our teaching Predicate with a real provenance model: 04 — Understanding SLSA Provenance, coming soon.