Tutorial: Add Attestations to Your Workflow

This step-by-step tutorial walks you through adding attestation support to an existing JACS workflow. You'll go from basic signing to full attestation creation and verification in under 5 minutes.

Prerequisites

  • JACS installed (Python, Node.js, or CLI)
  • Attestation feature enabled (built with --features attestation)

Step 1: Create an Agent

Use an ephemeral agent for testing (no files on disk):

{{#tabs }} {{#tab name="Python" }}

from jacs.client import JacsClient

client = JacsClient.ephemeral(algorithm="ed25519")
print(f"Agent ID: {client.agent_id}")

{{#endtab }} {{#tab name="Node.js" }}

const { JacsClient } = require('@hai.ai/jacs/client');

const client = await JacsClient.ephemeral('ring-Ed25519');
console.log(`Agent ID: ${client.agentId}`);

{{#endtab }} {{#tab name="CLI" }}

export JACS_PRIVATE_KEY_PASSWORD="YourP@ssw0rd"
jacs quickstart --algorithm ed25519

{{#endtab }} {{#endtabs }}

Step 2: Sign a Document

Sign some data to establish the base document:

{{#tabs }} {{#tab name="Python" }}

signed = client.sign_message({"action": "approve", "amount": 100})
print(f"Document ID: {signed.document_id}")

{{#endtab }} {{#tab name="Node.js" }}

const signed = await client.signMessage({ action: 'approve', amount: 100 });
console.log(`Document ID: ${signed.documentId}`);

{{#endtab }} {{#endtabs }}

Step 3: Create an Attestation

Now add trust context -- why this document should be trusted:

{{#tabs }} {{#tab name="Python" }}

import hashlib
content_hash = hashlib.sha256(signed.raw_json.encode()).hexdigest()
attestation = client.create_attestation(
    subject={
        "type": "artifact",
        "id": signed.document_id,
        "digests": {"sha256": content_hash},
    },
    claims=[
        {
            "name": "reviewed_by",
            "value": "human",
            "confidence": 0.95,
            "assuranceLevel": "verified",
        }
    ],
)
print(f"Attestation ID: {attestation.document_id}")

{{#endtab }} {{#tab name="Node.js" }}

const { createHash } = require('crypto');
const contentHash = createHash('sha256').update(signed.raw).digest('hex');
const attestation = await client.createAttestation({
  subject: {
    type: 'artifact',
    id: signed.documentId,
    digests: { sha256: contentHash },
  },
  claims: [{
    name: 'reviewed_by',
    value: 'human',
    confidence: 0.95,
    assuranceLevel: 'verified',
  }],
});
console.log(`Attestation ID: ${attestation.documentId}`);

{{#endtab }} {{#endtabs }}

Step 4: Verify the Attestation

Local Verification (fast -- signature + hash only)

{{#tabs }} {{#tab name="Python" }}

result = client.verify_attestation(attestation.raw_json)
print(f"Valid: {result['valid']}")
print(f"Signature OK: {result['crypto']['signature_valid']}")
print(f"Hash OK: {result['crypto']['hash_valid']}")

{{#endtab }} {{#tab name="Node.js" }}

const result = await client.verifyAttestation(attestation.raw);
console.log(`Valid: ${result.valid}`);
console.log(`Signature OK: ${result.crypto.signature_valid}`);
console.log(`Hash OK: ${result.crypto.hash_valid}`);

{{#endtab }} {{#endtabs }}

Full Verification (thorough -- includes evidence + derivation chain)

{{#tabs }} {{#tab name="Python" }}

full = client.verify_attestation(attestation.raw_json, full=True)
print(f"Valid: {full['valid']}")
print(f"Evidence: {full.get('evidence', [])}")
print(f"Chain: {full.get('chain')}")

{{#endtab }} {{#tab name="Node.js" }}

const full = await client.verifyAttestation(attestation.raw, { full: true });
console.log(`Valid: ${full.valid}`);
console.log(`Evidence: ${JSON.stringify(full.evidence)}`);
console.log(`Chain: ${JSON.stringify(full.chain)}`);

{{#endtab }} {{#endtabs }}

Step 5: Add Evidence (Optional)

Evidence references link to external proofs that support your claims:

{{#tabs }} {{#tab name="Python" }}

attestation_with_evidence = client.create_attestation(
    subject={
        "type": "artifact",
        "id": "doc-001",
        "digests": {"sha256": "abc123..."},
    },
    claims=[{"name": "scanned", "value": True, "confidence": 1.0}],
    evidence=[
        {
            "kind": "custom",
            "digests": {"sha256": "evidence-hash..."},
            "uri": "https://scanner.example.com/results/123",
            "collectedAt": "2026-03-04T00:00:00Z",
            "verifier": {"name": "security-scanner", "version": "2.0"},
        }
    ],
)

{{#endtab }} {{#tab name="Node.js" }}

const attWithEvidence = await client.createAttestation({
  subject: {
    type: 'artifact',
    id: 'doc-001',
    digests: { sha256: 'abc123...' },
  },
  claims: [{ name: 'scanned', value: true, confidence: 1.0 }],
  evidence: [{
    kind: 'custom',
    digests: { sha256: 'evidence-hash...' },
    uri: 'https://scanner.example.com/results/123',
    collectedAt: '2026-03-04T00:00:00Z',
    verifier: { name: 'security-scanner', version: '2.0' },
  }],
});

{{#endtab }} {{#endtabs }}

Step 6: Export as DSSE (Optional)

Export your attestation as a DSSE (Dead Simple Signing Envelope) for compatibility with in-toto, SLSA, and Sigstore:

{{#tabs }} {{#tab name="Python" }}

envelope = client.export_attestation_dsse(attestation.raw_json)
print(f"Payload type: {envelope['payloadType']}")
print(f"Signatures: {len(envelope['signatures'])}")

{{#endtab }} {{#tab name="Node.js" }}

const envelope = await client.exportAttestationDsse(attestation.raw);
console.log(`Payload type: ${envelope.payloadType}`);
console.log(`Signatures: ${envelope.signatures.length}`);

{{#endtab }} {{#endtabs }}

What's Next?