WeaveDocs
Weft

Sigil Anchoring

Committing a Weft root on the Sigil chain so verifiers can prove an artifact existed at a specific block.

What this page covers

The "off-chain bytes, on-chain commitment" pattern. Weft keeps the bytes; Sigil keeps a small commitment that lets a verifier prove the artifact at a given root existed at a given block.

This page describes the model and the data plumbing. The actual transaction shape lives in the Sigil architecture overview, which covers how on-chain commitments compose with DID-bearing identity.

Why anchor a Weft root

Three properties become provable once a root is anchored:

  1. Existence — at block N, the producer DID committed root R. Verifiers can check the chain for the inclusion of that commit.
  2. Authorization — the commit transaction is signed by the producer's identity (typically the same DID as ManifestProducer.did).
  3. Order — across multiple roots, block ordering establishes which was produced first.

What is not added by anchoring:

  • Availability. Sigil does not store the bytes. Anchoring proves a root existed; it does not prove anyone is serving it.
  • Validity. Sigil does not run WeftManifest::validate. A producer can anchor a malformed manifest; consumers must still validate when fetching.
Warning

Anchoring is existence-only. A consumer must independently fetch the manifest from the DHT, verify the signature, and verify every chunk on retrieval. Anchoring does not relieve consumers of any verification step.

The data flow

   Producer
   ────────
   1. publish to a WeftDhtStore  → manifest, chunks, availability on the DHT
   2. assemble a Sigil commitment transaction:
        - Weft root (32 bytes)
        - producer DID
        - optional MARS asset id
        - optional artifact metadata commitment (hash of canonical bytes)
   3. sign + submit the transaction
   4. wait for inclusion
   5. record the chain reference in ArtifactMetadata.sigil_commitment_cid

   Consumer
   ────────
   1. read the Sigil commitment for the artifact id
   2. extract the Weft root
   3. fetch the manifest from the DHT (manifest_key(root))
   4. verify the manifest signature
   5. fetch chunks; verify each chunk id; assemble payload

Carrying the Sigil reference in the manifest

ArtifactMetadata has a sigil_commitment_cid: Option<String> field. Once a root has been anchored, the producer can re-publish with the field populated:

use weft::manifest::ArtifactMetadata;

let artifact = ArtifactMetadata {
    artifact_id:          Some("dataset-2026-05-14".into()),
    media_type:           "application/x-zip".into(),
    schema:               "weave.dataset.v1".into(),
    mars_asset_id:        Some("mars://asset/abc123".into()),
    sigil_commitment_cid: Some("sigil:block:42/tx:7".into()),
};

The sigil_commitment_cid is opaque to Weft — it is a verifiable pointer the consumer follows. The format is defined by the Sigil chain.

Verifier flow

use weft::manifest::WeftManifest;

async fn verify_with_chain(
    manifest_bytes: &[u8],
    chain_client: &impl ChainClient,
) -> Result<(), Box<dyn std::error::Error>> {
    let manifest = WeftManifest::from_canonical_bytes(manifest_bytes)?;

    // 1) Validate the manifest is internally consistent.
    manifest.validate()?;

    // 2) Verify the producer signature.
    let pubkey = chain_client.resolve_did(&manifest.producer.did).await?;
    let signing_payload = manifest.signing_payload()?;
    let sig = ed25519_dalek::Signature::from_slice(&manifest.producer.signature)?;
    ed25519_dalek::Verifier::verify(&pubkey, &signing_payload, &sig)?;

    // 3) Verify the chain commitment, if present.
    if let Some(commit_ref) = &manifest.artifact.sigil_commitment_cid {
        let commit = chain_client.read_commitment(commit_ref).await?;
        if commit.weft_root != manifest.root {
            return Err("chain commitment root != manifest root".into());
        }
        if commit.producer_did != manifest.producer.did {
            return Err("chain commitment producer != manifest producer".into());
        }
    }

    Ok(())
}

trait ChainClient {
    async fn resolve_did(&self, did: &str) -> Result<ed25519_dalek::VerifyingKey, Box<dyn std::error::Error>>;
    async fn read_commitment(&self, cid: &str) -> Result<ChainCommitment, Box<dyn std::error::Error>>;
}

struct ChainCommitment {
    weft_root:    weft::WeftHash,
    producer_did: String,
}

Replace ChainClient with the Sigil RPC client your application uses; the shape above is the minimum the verifier needs.

When to anchor

WorkloadAnchor?
Public model release; reputation mattersYes
Training corpus shared with downstream auditorsYes
Internal dataset shared between agents you controlOptional
Ephemeral cache contentNo
Anything you might want to revoke laterAnchor with a revocation predicate in MARS or a separate Sigil contract

What gets revoked

Sigil anchoring is append-only. To "revoke" an artifact, anchor a second commitment that supersedes the first. Verifiers that follow the chain see both records and apply whatever revocation policy your application defines.

The Weft data itself stays accessible as long as peers serve it. Anchoring revocation is a policy layer above Weft, not a delete operation on the DHT.

Coordinating with MARS

MARS is the universal asset registry. The recommended flow:

  1. Mint the asset in MARS, receiving a mars_asset_id.
  2. Publish to Weft with ArtifactMetadata.mars_asset_id set.
  3. Anchor on Sigil with both weft_root and mars_asset_id in the commitment.
  4. Republish the manifest with sigil_commitment_cid set so the in-band manifest is self-describing.

After this, a verifier with just the manifest bytes can reach MARS, Sigil, and the producer's DID without out-of-band context.