Object Signing and the Blockchain Registry GitHub issue

vibecode
{"vibecode": {
    "doc": "blockchain",
    "role": "design spec for Puck's object-signing and blockchain registry: how UNS-addressed objects prove provenance and integrity",
    "key_concepts": ["object_signing", "uns_provenance", "authority_blocks",
        "third_party_endorsement", "engine_chain_settings"]
}}

Summary GitHub issue

The Puck blockchain will hold signatures of Caspian libraries. You can use those signatures to verify the provenance and timestamp of a library. You can also use the blockchain to find libraries within a given timespan.

Status GitHub issue

The blockchain design described here will ship as part of the V1 Puck ecoverse. Puck.uno will provide a public interface for the blockchain. Caspian will natively know about that service. We won't actually ship blockchain technology in the core product.

The Problem GitHub issue

Puck is a distributed object system. Objects (classes, capabilities, etc.) are identified by UNS addresses like borg.com/foo. When a Caspian engine fetches and uses an object, it needs confidence that:

  1. The object really came from borg.com — not from someone who injected a fake object with that UNS address.
  2. The object has not been modified — what the engine received is exactly what was published.

A UNS string alone proves neither. It is just a name.

The Solution GitHub issue

Software authors publish their packages at URLs they control, served over HTTPS. The TLS certificate they already have is the only credential they need.

When they want to publish a snapshot of their package, they tell a service at Puck.uno to record it. Puck.uno does two things:


Design Principles GitHub issue

Puck.uno holds one private key. That is the only cryptographic key in the system that Puck manages. Everything flows from it.

Domain owners manage nothing. A publisher like borg.com does not need keys, registration, or any special setup. They serve their objects over HTTPS. The TLS certificate they already have is sufficient proof of domain ownership.

Puck server operators manage nothing. They store and distribute bytes. Trust and verification are not their concern.

Engines ship with Puck's public key baked in. That single key is sufficient to verify the entire system.


The Open Ledger GitHub issue

The Puck blockchain is an open, append-only ledger of signed records about objects in the Puck distributed object system. Its purpose is to provide independently verifiable provenance. Anyone can confirm that a given object was fetched from its UNS address at a specific time and signed by a specific key.


Authority Blocks GitHub issue

An authority block is the anchor of a trust chain on the ledger. It's a signed record that establishes a public key as a known identity — every endorsement, delegation, or provenance record signed by that key chains back to its authority block.

Trust is determined by whose authority block you choose to trust, not by who is allowed to write to the ledger. The ledger is the record; the trust model is layered on top.

Anyone can post an authority block. Puck.uno posts the first one (its key is baked into engines), but a security auditor, a partner organisation, or a company running internal Caspian infrastructure can post their own and build an independent trust chain rooted in their own key. Engines can be configured to trust multiple authority blocks — Puck's for public libraries, an internal root for private ones.


Trust Delegation GitHub issue

A delegate block extends trust to another entity. Puck.uno can post a delegate block that says: "I trust this entity's endorsements." The delegation references the trusted entity's authority block by target_hash and lists the endorsement types it covers.

A delegation covers all blocks signed by the trusted entity from the time of their authority block onward — including blocks posted before the delegation itself was written. The order on the chain is not what matters; the anchor is the authority block. A consumer reading the chain should apply a delegation retroactively to any block signed by that entity after their authority block, regardless of whether the delegation appeared before or after those blocks.

This allows Puck.uno to hand off stewardship — to a regional maintainer, a successor organisation, or any trusted party — without breaking anything for engines that already trust the authority block. The delegation is on the chain, permanent and auditable.

Delegated trust can be chained: Puck trusts Entity A, Entity A trusts Entity B, and so on. Engines following the chain extend trust transitively.


How Puck Vouches for an Object GitHub issue

Signing is not automatic. The fact that a domain serves objects over HTTPS does not mean Puck will sign them. The domain owner must explicitly request signing through puck.uno.

  1. The domain owner submits the URL of their object to blockchain.puck.uno.
  2. Puck fetches the object from their domain over HTTPS — TLS proves it is talking to the real domain owner.
  3. Puck posts an endorse block with endorsement: "provenance", embedding a signature in the endorsement entry.

When an engine needs borg.com/foo, it queries Puck's API, receives the signed block, and verifies Puck's signature using the baked-in public key. If the signature is valid, the object is trusted.

The object must include a valid open source license. Any SPDX license identifier is accepted. The license is exposed as a license field on the object served at its URL:

json
{
    "name":    "borg.com/foo",
    "semver":  "1.2.0",
    "license": "MIT",
    ...
}

semver is optional. When present, it must be a valid SemVer 2.0 string and is used by the chain's duplicate-check rule (see endorse blocks below) and by resolvers that select libraries by semver. Publishers that don't version with semver simply omit the field; date-pinning still works.


Chain Design GitHub issue

The Puck blockchain is an open append-only ledger. There is no mining, no proof-of-work, and no gas. Anyone can post a record. Trust in a given record comes from trusting its signer (see Authority Blocks).

Each record in the chain is a JSON object with the following envelope fields:

Records are never modified or deleted.


Bootstrap blocks (positions 1 and 2) GitHub issue

The chain begins with two specific blocks, in order:

From block 3 onward, every block must carry a grammar field per the Grammar section below.

This bootstrap pair is the chain's only deviation from the "every block names its grammar" rule, and is hard-wired: parsers know that the chain at positions 1 and 2 has this exact shape and read them accordingly.


Grammar GitHub issue

Every block except the bootstrap pair (positions 1 and 2) must include a grammar field in its payload referencing a grammar block by hash and version string:

json
"grammar": {"hash": "a3f9c2...", "version": "1.0"}

Grammar blocks help keep the block syntax consistent while allowing for evolution in the syntax. Block 2 of the chain is always the first grammar block, posted by Puck.uno; further grammar blocks can be posted later as the format evolves.

There is no cutoff for switching to a new grammar. Each block carries its own grammar reference, so old and new grammars coexist indefinitely — parsers handle every block against whichever grammar that block points at. If a grammar needs updating, a new one gets published; existing blocks keep working against the grammar they were written for.


Signing Scheme GitHub issue

To sign a record:

  1. Construct the record as a JSON object with all fields except signature
  2. Serialize to minified JSON with all keys sorted alphabetically at every level
  3. Sign the UTF-8 bytes using Ed25519
  4. Base64-encode the signature and add it as the signature field

To verify, remove the signature field, re-serialize with sorted keys, and run Ed25519 verify against that string using the signer's public key (found in their authority block).

The record_hash is the SHA-256 hex digest of the fully serialized record including the signature field, keys sorted alphabetically.


Authority blocks GitHub issue

authority — anchor of trust; establishes a signer's identity and public key on the chain. Any entity may post their own authority block.

Required payload fields:

The content of puck_primer and vibecode for the production chain will be supplied by Miko. Both must be present before the authority block is posted to any production chain.


endorse — a claim made by the signer about a target. The target may be another block (referenced by target_hash) or content embedded within this block (target_hash: "self").

The chain does not store software bodies. A provenance endorsement signs an artifact by referencing it cryptographically — the artifact itself is hosted off-chain (the publisher's HTTPS server, a package registry, a content-addressed store, etc.). The on-chain record carries the artifact's hash so anyone can verify a fetched copy matches what was signed.

To publish provenance for software, post an endorse block with endorsement: "provenance" and include the artifact metadata directly in the endorsement entry alongside endorsement. The required cryptographic anchor is artifact_hash. Additional endorsement entries in the same block — or in a later block by a third party — can assert security, license, or other claims.

Provenance endorsement (signed by the publisher):

json
{
  "intent": "endorse",
  "grammar": {"hash": "...", "version": "1.0"},
  "target_hash": "self",
  "uns": "borg.com/parser",
  "semver": "2.1.0",
  "effective_date": "2026-05-07",
  "endorsements": [
    {
      "endorsement": "provenance",
      "name": "borg.com/parser",
      "description": "Parses structured text into a normalised output hash.",
      "language": "puck.uno/software/caspian",
      "license": "MIT",
      "semver": "2.1.0",
      "artifact_hash": "sha256:...",
      "artifact_url": "https://borg.com/parser/2.1.0.tar.gz"
    }
  ]
}

Third-party security endorsement (references a prior provenance block by hash):

json
{
  "intent": "endorse",
  "grammar": {"hash": "...", "version": "1.0"},
  "target_hash": "a3f9c2...",
  "endorsements": [
    {"endorsement": "security", "security": {"fedramp-moderate": true}, "notes": "Reviewed 2026-05-07"},
    {"endorsement": "license-verified", "notes": "Confirmed MIT via SPDX scan"}
  ]
}

Fields:


Semver duplicate check. When an endorse block carries semver, the chain rejects the block if another endorsement with the same uns + semver combination is already on record. Republishing an existing semver would create ambiguity for resolvers that select by semver — consumers asking for borg.com/parser at 2.1.0 should get exactly one answer. To fix a buggy publication, bump the semver and post a new endorsement.

This is distinct from the Submit endpoint's content-identity idempotency: identical bytes posted twice are silently coalesced; different bytes posted under the same uns + semver are rejected.

The chain does not enforce monotonic semver progression. A 1.2.5 patch posted after 2.0.0 is legitimate (parallel-branch publishing), as are pre-release tags and out-of-order historical posts. The only rule is uniqueness within a UNS.


delegate — grants another entity trusted endorser status for a specified set of endorsement types. The delegation is scoped to the entity's authority block via target_hash and covers all blocks that entity has signed or will sign from that authority block onward.

json
{
  "intent": "delegate",
  "grammar": {"hash": "...", "version": "1.0"},
  "entity": "castlesecurity.com",
  "endorsements": ["provenance", "security"],
  "target_hash": "<castlesecurity.com authority block record_hash>",
  "note": "..."
}

Fields:


deprecate — marks a UNS or semver as deprecated. Does not invalidate the record; consumers decide how to respond.

json
{
  "intent": "deprecate",
  "grammar": {"hash": "...", "version": "1.0"},
  "uns": "borg.com/parser",
  "semver": "1.0.0",
  "reason": "superseded by 2.0.0"
}

revoke — invalidates a previously posted record (e.g. due to key compromise or bad data).

json
{
  "intent": "revoke",
  "grammar": {"hash": "...", "version": "1.0"},
  "target_hash": "a3f9c2...",
  "reason": "key compromise"
}

Trust Tiers GitHub issue

Source Trust level
Blockchain (via Puck API) Highest — Puck signed it, immutable
Puck server over HTTPS High — TLS verified, but object could change
Other HTTPS source Policy-dependent
Unsigned Rejected

What Each Party Manages GitHub issue

Party Responsibility
borg.com Serve objects over HTTPS. Nothing else.
Puck.uno One private key. Fetch, sign, post to blockchain.
Puck server operators Store and serve bytes. Nothing else.
Caspian engines Puck's public key baked in. Verify on fetch.

API GitHub issue

The HTTPS API for the blockchain — endpoints, request/response shapes, idempotency rules, and the rest — lives in the sibling api/ directory. This doc covers the chain itself (design, signing, authority blocks, trust tiers); the API spec covers how clients talk to it.


Versioning GitHub issue

The Puck ecoverse uses date-pinned versioning as its general model — a single cutoff timestamp governs the entire library tree, set on %chain.cutoff at the top of the call chain. The general model is documented in versioning; this section describes how the blockchain anchors dates when it is in play.

Every block on the Puck blockchain carries a posted timestamp assigned at insertion. This is not set by the submitter — it is canonical and tamper-evident.

Default behaviour: latest within range. When you request an object with no version constraint, you get the most recently posted version. When you request with a date range, you get the most recently posted version that falls within that range.

Effective Date GitHub issue

A signer may set an effective_date on an endorsement to declare the date that should be used for version ordering in place of posted. This allows historical objects to be correctly ordered — a library released ten years ago can be posted to the chain today with effective_date set to its original release date.

effective_date is optional. Omitting it means posted governs.

Tombstone and Birthstone GitHub issue

A tombstone is an upper bound: "give me the latest version on or before this date." Setting a tombstone pins resolution to a point in time — useful for reproducible builds.

A birthstone is a lower bound: "do not give me anything older than this date." Useful for excluding objects published before a known-good baseline.

json
{"uns": "borg.com/parser", "tombstone": "2025-06-01T00:00:00Z", "birthstone": "2024-01-01T00:00:00Z"}

Dependency Resolution GitHub issue

Each object may declare its own dependencies — by UNS name — along with an optional date range per dependency. When the gateway resolves a request, it traverses the dependency graph and applies the outer request range at every node.

If an object declares a narrower range for one of its dependencies, the system intersects that range with the outer range. The narrower of the two wins. If the intersection is empty, resolution fails rather than silently selecting something outside the intended range.


Use Case: Third-Party Endorsement GitHub issue

Scenario: Castle Security is a security auditing company. A government contractor needs to verify that borg.com/parser meets NIST 800-53 security requirements before deploying it. Castle Security reviews the library and posts an endorsement to the chain.

A collaboration between Puck and Castle Security could be a mutually beneficial arrangement.

Step 1 — Puck vouches for provenance.

borg.com submits their library through puck.uno. Puck fetches it over HTTPS and posts a provenance endorsement block with the object embedded in the bucket:

json
{
  "intent": "endorse",
  "prev_hash": "...",
  "posted": "2026-05-04T09:00:00Z",
  "signer": "puck.uno",
  "payload": {
    "intent": "endorse",
    "grammar": {"hash": "...", "version": "1.0"},
    "target_hash": "self",
    "uns": "borg.com/parser",
    "semver": "2.1.0",
    "effective_date": "2026-05-04",
    "endorsements": [
      {
        "endorsement": "provenance",
        "name": "borg.com/parser",
        "fields": {"input": "string", "output": "string"},
        "license": "MIT"
      }
    ]
  },
  "signature": "base64..."
}

This block answers one question: did this object really come from borg.com? Nothing more.


Step 2 — Castle Security establishes its identity.

Castle Security has its own Ed25519 key pair. It posts its own authority block to the chain, establishing its identity independently of Puck. No permission from Puck is required.

json
{
  "intent": "authority",
  "prev_hash": "...",
  "posted": "2026-05-04T10:00:00Z",
  "signer": "castlesecurity.com",
  "payload": {
    "intent": "authority",
    "grammar": {"hash": "...", "version": "1.0"},
    "note": "Castle Security security audit authority — independent assessments for government contractors",
    "public_key": "-----BEGIN PUBLIC KEY-----\n...",
    "puck_primer": "..."
  },
  "signature": "base64..."
}

Step 3 — Castle Security reviews and endorses.

Castle Security fetches Puck's provenance block, reviews the bucket contents, and posts an endorsement referencing that block by its record_hash. Castle Security does not re-fetch from borg.com and does not re-post the source — they are endorsing the specific block Puck already verified.

json
{
  "intent": "endorse",
  "prev_hash": "...",
  "posted": "2026-05-04T11:00:00Z",
  "signer": "castlesecurity.com",
  "payload": {
    "intent": "endorse",
    "grammar": {"hash": "...", "version": "1.0"},
    "target_hash": "<record_hash of Puck's provenance block>",
    "endorsements": [
      {
        "endorsement": "security",
        "security": {"fedramp-moderate": true},
        "notes": "Reviewed 2026-05-04. borg.com/parser meets all applicable NIST 800-53 controls for input validation and output sanitization."
      }
    ]
  },
  "signature": "base64..."
}

Step 4 — The contractor's engine checks both.

The engine fetches borg.com/parser. It verifies:

  1. Puck's signature on the provenance block — origin confirmed, object unmodified
  2. Castle Security's endorsement referencing that same block — security criteria met

Both checks are independent. The engine trusts Puck's public key (baked in) and Castle Security's public key (configured by the contractor). Neither party needed to coordinate with the other. The shared ledger is what ties them together.

Collaboration: Puck Delegates to Castle Security GitHub issue

Although Puck and Castle Security can operate completely independently, collaborating could be a mutually beneficial arrangement.

Puck posts a delegate block naming Castle Security as a trusted endorser:

json
{
  "intent": "delegate",
  "prev_hash": "...",
  "posted": "...",
  "signer": "puck.uno",
  "payload": {
    "intent": "delegate",
    "grammar": {"hash": "...", "version": "1.0"},
    "entity": "castlesecurity.com",
    "endorsements": ["provenance", "security"],
    "target_hash": "<castlesecurity.com authority block record_hash>",
    "note": "Puck delegates provenance and security trust to Castle Security."
  },
  "signature": "base64..."
}

With this delegation in place, Castle Security can fetch objects from domains over HTTPS and post provenance endorsements signed with their own key. Engines that trust Puck's authority block follow the delegation chain and accept Castle Security's blocks as trusted provenance — exactly as they would accept blocks signed by Puck directly.

This offloads the fetch-and-sign work from Puck entirely. Castle Security becomes an operational partner: they fetch, they sign, they post, and they add their security endorsement in the same pass.

This removes friction for developers who need to ship within government specifications. They do not need to know anything about the partner's internal processes or query a separate API. The blockchain.puck.uno response tells them everything: where the object came from, that it hasn't been modified, and whether it meets the security criteria they care about.

The broader opportunity is significant. The Puck blockchain is not limited to Caspian objects — it can store Python libraries, Go modules, or any signed artifact. A company like Castle Security, trusted by Puck and trusted by governments, could position itself as a leading authority on security-cleared open source across languages and ecosystems. Any engine or toolchain that knows how to read the chain gains access to that trust infrastructure with no additional setup.

Partnership Goal GitHub issue

Puck is actively seeking a partner in this space — a company analogous to Castle Security whose endorsements and deprecations would be surfaced directly through the blockchain.puck.uno API.


Design Notes GitHub issue

Ed25519 is the right choice. 64-byte signatures, fast verification, no parameter choices that can be misconfigured, widely supported in every language runtime Puck is likely to encounter.

Alphabetically sorted canonical JSON. Signing uses minified JSON with all keys sorted alphabetically at every level. This is deterministic regardless of the order in which fields were constructed, and is compatible with RFC 8785 (JCS). All Puck tooling must sort keys before signing or verifying.

Hash chaining. Each record's prev_hash is the SHA-256 of the preceding record's full serialized form (including its signature). This makes the chain tamper-evident: altering any record invalidates every subsequent prev_hash.

Revocation and caching. A revoked object in cache should be re-fetched immediately. Recommended strategy: on any object use, check for a revoke block covering that record_hash with a short-lived local TTL (e.g. 5 minutes). If revoked, discard cache and re-fetch.

Grammar versioning. The grammar is expected to evolve slowly — a handful of base revisions and at most a few hundred DSL grammars in the most ambitious scenario. A block referencing an unrecognised grammar hash is not automatically invalid; consumers decide whether to accept or reject it.


Rebuild Notes GitHub issue

Block ordering invariant. Every rebuild must produce the bootstrap pair at positions 1 and 2 — block 1 is Puck.uno's authority block (with no grammar field), block 2 is the first grammar block. The rebuild tooling enforces this; if any post script or producer ever emits blocks in a different order, the rebuild fails before signing.

When rebuilding the chain after a field-name or shape change (e.g. the versionsemver rename for library semver), double-check that every consumer of the chain still supports the new and changed keys before running the rebuild. Touch-points include:

A field rename that lands in the data without code support means the rebuilt chain parses against stale validators and reads through stale fetch paths — silent data drift. Grep widely before rebuilding; the field name is the canonical search term.


Open Issues GitHub issue

Software namespace identifier bloat. As puck.uno/software grows (programming languages, DBMSs, frameworks), putting every identifier on the chain would bloat it. Most identifiers are just namespace declarations and don't need provenance or revocation the way published artifacts do. Do software identifiers belong on the chain at all, or should the chain only carry records that reference them?


© 2026 Puck.uno