Skip to main content

Anchor objects in S3 / R2 / GCS / Azure Blob to BSV via Satsignal. Files stay in your bucket; portable proof sidecars land next to them.

Project description

satsignal-blob

Anchor objects in S3 / R2 / GCS / Azure Blob to BSV via Satsignal. Files stay in your bucket; portable proof sidecars (.mbnt + .proof.json) land next to them.

Keep your files where they are. Satsignal gives them independent proof.

pip install satsignal-blob[s3]    # or [gcs], [azure], [all]

What it does

For each object under a bucket prefix, satsignal-blob streams the bytes through sha256, posts the digest (not the file) to Satsignal's POST /api/v1/anchors, and writes two sidecar objects next to the original:

sibling what it is who uses it
contract.pdf.mbnt full evidence bundle (zip) — txid, canonical doc, miner acceptance, BSV chain anchor anyone who needs to verify the receipt offline against a public BSV explorer
contract.pdf.proof.json one-page summary — sha256, txid, receipt URL, bundle URL, timestamp humans, dashboards, grep / jq pipelines

The original contract.pdf is untouched. The file bytes never leave your bucket — Satsignal only sees the sha256.

CLI quickstart

export SATSIGNAL_API_KEY=sk_...

# Anchor every PDF under a prefix, cap at 50 attempts.
satsignal-blob anchor s3://my-bucket/contracts/2026/ \
    --matter contracts-2026 \
    --include '*.pdf' \
    --max-files 50

# Dry-run first to preview hashes + plan without firing any anchors:
satsignal-blob anchor s3://my-bucket/contracts/2026/ \
    --matter contracts-2026 --include '*.pdf' --dry-run

A successful run prints one line per object plus a summary on stderr:

[OK]   s3://my-bucket/contracts/2026/a.pdf  sha=ab1234567890 bundle=87f9c2b4… txid=e7c4f1…
[OK]   s3://my-bucket/contracts/2026/b.pdf  sha=cd5678901234 bundle=2a91b3e5… txid=e7c4f1…
[SKIP] s3://my-bucket/contracts/2026/c.pdf  (.mbnt sidecar exists; --force to re-anchor)
satsignal-blob: anchored=2 duplicate=0 skipped_existing=1 skipped_sidecar=0 failed=0

Pass --json to emit one JSON object per line — pipe through jq / fluentd / anything line-oriented.

Supported backends

satsignal-blob resolves URIs through fsspec. Install the optional dep that matches your storage:

storage URI scheme install extra backend
AWS S3 / MinIO / Wasabi / B2 / Cloudflare R2 s3:// pip install satsignal-blob[s3] s3fs
Google Cloud Storage gs:// pip install satsignal-blob[gcs] gcsfs
Azure Blob Storage az:// pip install satsignal-blob[azure] adlfs
Local filesystem /path/ or file:/// (none) built-in

Cloudflare R2 is S3-compatible — use s3:// and configure the AWS_ENDPOINT_URL_S3 env var to point at your R2 endpoint.

Library API

from satsignal_blob import anchor_prefix

for outcome in anchor_prefix(
    "s3://my-bucket/contracts/2026/",
    api_key="sk_...",
    matter_slug="contracts-2026",
    include=["*.pdf"],
):
    if outcome.status == "anchored":
        print(outcome.receipt_url)

Or a single object:

from satsignal_blob import anchor_object

result = anchor_object(
    "s3://my-bucket/contracts/2026/contract.pdf",
    api_key="sk_...",
    matter_slug="contracts-2026",
)
print(result.txid, result.receipt_url)

Event-driven (S3 → Lambda)

For real-time anchoring on every PUT, wire an S3 ObjectCreated:* event to a Lambda that calls anchor_object. Copy examples/lambda_handler.py into a Python 3.11 Lambda, set three env vars, and you're done:

import os, urllib.parse
from satsignal_blob import anchor_object

API_KEY = os.environ["SATSIGNAL_API_KEY"]
WORKSPACE_SLUG = os.environ["SATSIGNAL_WORKSPACE_SLUG"]
MATTER_SLUG = os.environ["SATSIGNAL_MATTER_SLUG"]


def lambda_handler(event, context):
    results = []
    for record in event.get("Records", []):
        bucket = record["s3"]["bucket"]["name"]
        key = urllib.parse.unquote_plus(record["s3"]["object"]["key"])
        if key.endswith((".mbnt", ".proof.json")):
            continue  # don't anchor sidecars
        outcome = anchor_object(
            f"s3://{bucket}/{key}",
            api_key=API_KEY,
            matter_slug=MATTER_SLUG,
        )
        results.append({"key": key, "status": outcome.status,
                        "bundle_id": outcome.bundle_id})
    return {"results": results}

Same pattern works for Cloud Functions (GCS finalize) and Azure Functions (blob trigger) — just swap the scheme and the event shape.

What this proves (and what it doesn't)

A Satsignal anchor over sha256(file) proves:

  • The operator of this Satsignal workspace knew this sha256 by the block time of the on-chain anchor. Anyone with the file can later recompute the sha and verify against the chain entry — without trusting Satsignal or your storage provider.

It does not prove:

  • That the file existed before the anchor. The chain only records when the sha entered the ledger.
  • The identity of whoever uploaded it. The receipt is "this sha was anchored by this workspace at this time" — not "this person did it."

For workflows that need before-the-anchor existence (e.g. press- embargo proofs), pair satsignal-blob with a commit-reveal flow via satsignal-cli or the /commit_reveal.py helper.

Threat model

  • sha-only. File bytes never leave your process. The Satsignal API only sees the digest + size + optional label + filename.
  • Untrusted attribute fields. label and filename are clipped server-side at 256 chars and validated for control substrings. Still — don't put secrets in either.
  • Fail-open per object. One file's API failure does not abort the walk. The exit code is 1 if any file failed; the per-file error is in the JSON output.
  • No bytes on chain. Satsignal never anchors file contents — only the digest. This is by design and is not a config option.

Related packages

  • satsignal-cli — general-purpose anchor + verify CLI; can verify any .mbnt file written by satsignal-blob.
  • satsignal-mcp — MCP server exposing the same 5 primitives to MCP-aware agents.
  • satsignal-otel — anchor OpenTelemetry spans (failed evals, release gates).
  • satsignal-action — GitHub Action: anchor build artifacts on every release.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

satsignal_blob-0.1.0.tar.gz (20.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

satsignal_blob-0.1.0-py3-none-any.whl (16.3 kB view details)

Uploaded Python 3

File details

Details for the file satsignal_blob-0.1.0.tar.gz.

File metadata

  • Download URL: satsignal_blob-0.1.0.tar.gz
  • Upload date:
  • Size: 20.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for satsignal_blob-0.1.0.tar.gz
Algorithm Hash digest
SHA256 bf9c8620b1846f1e275b2ce1581c98bd0cee432c770cc0382bc8e2bb0d75c116
MD5 7fe346314de2d3b34493fd50d36619e9
BLAKE2b-256 4fec560c49758c1eedaeacc4ea5dd57c8e843fe0fbfb570801858e76eca60fe4

See more details on using hashes here.

File details

Details for the file satsignal_blob-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: satsignal_blob-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 16.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for satsignal_blob-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d3f9651a68c89d1ac8d45996dcb7f8ab719f4bfd8a669c9f5da4e310b5c17748
MD5 8ad23fc8a81c5642cddb323b739a5b61
BLAKE2b-256 c5f396ff57eeb74f66493c86a7e77faf660672217c980e16075e23cc048ed09d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page