Token-Bound Executable Skills — the artifact identity & integrity layer under skill registries (ERC-721 extension)

Summary

We propose an ERC-721 extension that binds a token to an executable skill: a package of files, led by a primary Markdown document, that an off-chain runtime (including LLM agent runtimes) can fetch from anywhere, verify byte-for-byte against on-chain anchors, and execute.

On-chain, each token carries a SkillBinding: mdHash (SHA-256 of the plaintext primary document — a commitment that survives encryption), packageHash (SHA-256 of a deterministically encoded, content-addressed object graph — the SkillRoot — covering every file execution depends on), and a contract-incremented version with a closed prev chain. Around it: a mutable packageURI that is transport, never trust; an update authority that is a publication right separate from ERC-721 ownership; and an irreversible freezeSkill. Optional layers: an on-chain plaintext primary document (permanent once published), and an in-package confidentiality descriptor — encryption is a package property, not a token flag, so ciphertext integrity is verifiable before any key is released.

This proposal intentionally does not define registries, discovery, installation, attestation, reputation, access, licensing, or settlement.

Relationship to skill and tool registries

ERC-8239 and ERC-8257 are building the registry layer: where a skill or tool is registered, how it is discovered, installed, and attested. This proposal sits underneath that layer and answers a different question: exactly what executable bytes, package version, and publication history does the token commit to?

An ERC-8239 SkillRegistry token could optionally implement this interface on the same tokenId; an ERC-8257 tool manifest could reference a token-bound artifact externally. One concrete thing this unlocks: usage and installation attestations can pin packageHash + version, so when a skill moves from v1 to v2, historical attestations still resolve to exactly the v1 bytes rather than to a moving head.

Registries answer where a skill is registered and how agents relate to it. This proposal answers what the token commits to.

Motivation

AI agents have on-chain identities (ERC-8004) and smart accounts (ERC-4337), and skill/tool registries are emerging (ERC-8239, ERC-8257) — but the artifact itself still lives as unverifiable, silently mutable content behind a URI. That is the root condition of registry supply-chain attacks: the same name, the same entry, different bytes. tokenURI cannot fix it; it is a display pointer with no integrity guarantee.

Two hashes and a URI alone would just be custom metadata. What makes this a standard is the normative object model beneath them — a deterministic DAG-CBOR graph with per-file CIDs and a closed, append-only version chain — which defines exactly what the commitments protect, byte for byte, and lets any independent implementation reproduce them.

This is running, not proposed on paper

  • Specification frozen (full draft text in the reply below; SHA-256 779C7F9D…D274A68D).

  • Reference implementation, public and CC0release v1.0.0: Solidity contracts (ISkillToken = 0x734553a6, IOnchainSkillDocument = 0x7050dd2c, compiler-verified), a zero-dependency Python packer/verifier implementing the deterministic encoding, JSON Schemas, and a Foundry suite where every MUST clause of the spec is one of 12 passing assertions.

  • Six frozen test vectors built from a real production skill (public / confidential / license-encrypted / custom primary path / companion-only update / key rotation), plus path negative tests — reproduced byte-for-byte across five independent machines. Across all six, the plaintext mdHash stays constant: one document identity surviving encryption, re-packaging, and key rotation.

  • Live on Sepolia: 0x12cc1a5319c6F08bFB50982e3814A376A59fE550. Skill Token #1 is the public-v1 frozen vector; its mint tx (block 11261685) carries mdHash ‖ packageHash ‖ version verbatim in the genesis SkillUpdated event — the skill’s fingerprints are part of public chain history.

  • Verify it yourself, no local setup beyond cast:

bash

cast call 0x12cc1a5319c6F08bFB50982e3814A376A59fE550 \
  "skillOf(uint256)((bytes32,bytes32,uint64))" 1 \
  --rpc-url https://ethereum-sepolia-rpc.publicnode.com
# returns (mdHash, packageHash, version) — then check the real package against them:
python3 tools/skill-pack/verify.py vectors/public-v1 --mdhash <1st> --packagehash <2nd>

Deliberately out of scope

Rental, subscription, metering, delegation, gated upgrades, and license issuance are left to companion extensions. A skill’s token reference is singular; its usage value must scale — rights contracts (typically ERC-1155) reference the skill externally via SkillRef = (chainId, skillContract, skillTokenId). A companion metered-license proposal will follow.

Open questions for the community

  1. Registry composition: for ERC-8239-style registries, is same-tokenId optional implementation of ISkillToken the right composition pattern — and should usage/installation events pin packageHash + version so historical attestations keep exact-version precision?

  2. Versioning shape: one token per skill with a closed version chain, versus a new token per version — the latter preserves history but fragments the skill’s identity across tokens. Where should the community land?

  3. Rights layer: we are drafting the ERC-1155 companion (skillRefOf, issuance, metering). Teams interested in co-designing it?

  4. Confidentiality profiles: the descriptor treats profile as opaque (threshold networks, TEE custody…). Should a lightweight profile registry exist, and where?

  5. Deterministic encoding: we chose DAG-CBOR (RFC 8949 deterministic rules) with 37-byte identity-prefixed CIDv1 links — feedback from IPLD implementers very welcome.

  6. On-chain document permanence: hasOnchainSkillDocument is monotone (once true, forever true) because chain history makes disclosure irreversible — should the interface reflect that reality?

Authors

Gary Yang (@garyyang-finchip)

Thanks to @bransdotcom for an early discussion on versioning.

Full draft text below (KERNEL v4.3, frozen — SHA-256 779C7F9D…D274A68D). The preamble (frontmatter) will accompany the PR to ethereum/ERCs.

Abstract

This standard binds an ERC-721 token to an executable skill: a package of files, led by a primary Markdown document, that an off-chain runtime (including LLM-based agent runtimes) can fetch, verify byte-for-byte, and execute.

ERC-721 Asset Layer                    ── who owns this skill token
tokenId
├── owner
├── approved / operator
└── tokenURI (Metadata ext., optional) display identity only

Mandatory Skill Binding                ── which version it binds; who may update; whether it may change
tokenId
├── mdHash                             plaintext primary-document commitment
├── packageHash                        published-package commitment (plaintext or ciphertext)
├── version                            content version
├── packageURI                         mutable transport hint
├── updateAuthority                    independent publication right
└── frozen                             permanently immutable content

Optional On-chain Document             ── read the plaintext primary document from the chain itself
tokenId
├── hasOnchainSkillDocument()          once true, permanently true
└── skillDocument()  →  exact plaintext primary-document bytes

Off-chain Normative Layer              ── what the commitments protect, how to fetch and verify
packageHash
└── SkillRoot
    ├── md { path, cid }               primary document: raw or ciphertext CID
    ├── manifest                       public, machine-readable, minimal schema below
    ├── entry / license / files       files may be raw or ciphertext CIDs
    ├── confidentiality?               OPTIONAL confidentiality descriptor
    └── prev                           mandatory version chain (absent only at version 1)

External Scalable Rights Layer         ── companion standards; not part of this interface
SkillRef = (chainId, skillContract, skillTokenId)
└── ERC-1155 rights contracts: licenses / editions / usage credits

Each token defines a canonical skill reference. Identity is the hashes; transport is a replaceable hint; publication is a right separate from ownership; freezing is an irreversible promise; confidentiality is a package property, not a token field. Rental, subscription, metering, delegation, gated upgrades, and license issuance are deliberately left to companion extensions.

This proposal standardizes executable-artifact identity, integrity, versioning, and publication governance. It intentionally does not define skill registries, discovery, installation state, usage attestations, reputation, access rights, licensing, or settlement.

Motivation

AI agents have on-chain identities (ERC-8004) and smart accounts (ERC-4337), but the skills they run live in centralized registries: unverifiable, unownable, silently mutable — the root condition of registry supply-chain attacks. tokenURI cannot fix this; it is a display pointer with no integrity guarantee.

Registry and package-manager protocols can identify, locate, and distribute a skill, but an execution runtime must answer a lower-level question: which exact document, file graph, package version, and publication history does a token commit to? Without a shared artifact-binding model, two clients may resolve the same registered skill while receiving different companion files, following different update histories, or interpreting confidentiality metadata differently. This proposal standardizes that artifact boundary independently of any particular registry, marketplace, agent identity system, or runtime.

Two hashes and a URI alone would be custom metadata. What makes this a standard is the pair of normative definitions beneath them: packageHash commits to a deterministic content-addressed object graph (the SkillRoot) covering every file the skill’s execution depends on, and mdHash commits to the plaintext primary document within it. Together they form a verifiable binding between a token and an executable artifact — publishable in the clear or as ciphertext, readable on-chain where the publisher chooses, and referenced by scalable rights systems built on top.

A skill’s token reference is singular; its usage value must be replicable, distributable, tiered, and metered. This standard defines the former. Multi-token rights contracts (a companion standard) reference it via (chainId, skillContract, skillTokenId) and handle the latter: ERC-721 makes a skill identifiable; ERC-1155 makes the skill economy scalable.

Specification

The key words “MUST”, “MUST NOT”, “SHOULD”, and “MAY” are to be interpreted as described in RFC 2119 and RFC 8174.

Core interface (mandatory)

interface IERCXXXXSkill /* standalone extension interface; see Compliance */ {

    struct SkillBinding {
        bytes32 mdHash;      // SHA-256 digest of the plaintext primary Markdown document
        bytes32 packageHash; // SHA-256 digest of the encoded SkillRoot as published
        uint64  version;     // content version, starts at 1
    }

    event SkillUpdated(uint256 indexed tokenId, bytes32 mdHash, bytes32 packageHash, uint64 version);
    event SkillURIUpdated(uint256 indexed tokenId, string packageURI);
    event SkillUpdateAuthorityChanged(uint256 indexed tokenId,
                                      address indexed previousAuthority,
                                      address indexed newAuthority);
    event SkillFrozen(uint256 indexed tokenId);

    function skillOf(uint256 tokenId) external view returns (SkillBinding memory);
    function skillURI(uint256 tokenId) external view returns (string memory);
    function updateAuthorityOf(uint256 tokenId) external view returns (address);
    function isSkillFrozen(uint256 tokenId) external view returns (bool);

    function updateSkill(uint256 tokenId, bytes32 mdHash, bytes32 packageHash) external;
    function setSkillURI(uint256 tokenId, string calldata packageURI) external;
    function setUpdateAuthority(uint256 tokenId, address newAuthority) external;
    function freezeSkill(uint256 tokenId) external;
}

Confidentiality adds no field here: encryption is described inside the hash-protected package, not as a token flag. License supply, pricing, and mint mechanics likewise never enter this interface.

On-chain document interface (optional)

interface IERCXXXXOnchainSkillDocument /* own ERC-165 id */ {

    /// @notice Whether a plaintext on-chain copy of the primary document exists for tokenId.
    /// @dev MUST revert for nonexistent tokenId. Once true, MUST remain true permanently.
    function hasOnchainSkillDocument(uint256 tokenId) external view returns (bool);

    /// @notice The exact plaintext UTF-8 bytes of the primary Skill document.
    /// @dev MUST revert when hasOnchainSkillDocument(tokenId) == false.
    function skillDocument(uint256 tokenId) external view returns (bytes memory document);

    /// @notice Atomically update the on-chain document together with the binding.
    /// @dev mdHash is computed in-contract as sha256(document).
    function updateSkillWithDocument(uint256 tokenId, bytes calldata document, bytes32 packageHash) external;

    /// @notice Publish the current primary document on-chain without a version change.
    /// @dev MUST revert unless sha256(document) == skillOf(tokenId).mdHash.
    ///      MUST NOT change packageHash or version. Sets hasOnchainSkillDocument permanently true.
    function publishSkillDocument(uint256 tokenId, bytes calldata document) external;

    event SkillDocumentPublished(uint256 indexed tokenId, bytes32 mdHash);
}

skillDocument returns bytes: the commitment is over exact raw bytes. When a copy exists, sha256(skillDocument(tokenId)) MUST equal skillOf(tokenId).mdHash at all times.

Existence is permanent. hasOnchainSkillDocument MAY transition from false to true — via publishSkillDocument (disclosing the current document without touching packageHash or version, since the package itself is unchanged) or via updateSkillWithDocument (when the document content actually changes) — and MUST NOT ever transition back: publishing the primary document on-chain is an irreversible disclosure, and the interface state reflects that. A token that has once published its document on-chain may later encrypt companion files, but its primary document can never return to genuine confidentiality (see Security Considerations).

Consistency with updates. When hasOnchainSkillDocument(tokenId) == true and a proposed update would change mdHash, plain updateSkill MUST revert; the update MUST go through updateSkillWithDocument, which computes mdHash = sha256(document) in-contract and updates document, both commitments, and version in one transaction.

The on-chain document is exclusively the primary Markdown document. No other package file may be published through this extension; the sha256(document) == mdHash invariant makes this structural rather than procedural. The on-chain document is always plaintext: for skills whose primary document is confidential, hasOnchainSkillDocument MUST return false and skillDocument MUST revert. Storage technique is implementation-defined and invisible to callers. An on-chain document never replaces mdHash.

Compliance

  • The contract MUST implement ERC-721 and ERC-165.
  • supportsInterface MUST return true for IERCXXXXSkill, and additionally for IERCXXXXOnchainSkillDocument when implemented. The interfaces deliberately do not inherit IERC721.
  • A compliant contract SHOULD implement the ERC-721 Metadata extension. If tokenURI is implemented, it MUST NOT be used as a verification source; it MAY mirror facts such as encrypted: true or document_onchain: true for wallets, without authority.

Identity versus transport

The skill’s identity is (mdHash, packageHash, version). packageURI is a transport hint and is not part of the identity:

  • setSkillURI MUST NOT change version and MUST emit SkillURIUpdated. Replacing a dead endpoint never constitutes a new skill version. packageURI is a single URI; multi-mirror retrieval is expressed by URI resolvers or off-chain retrieval manifests, not by this field.
  • packageURI MUST NOT be treated as a trust input: consumers MUST verify all retrieved content against the commitments and MUST discard non-matching content, whatever its source.

Update authority

Publication is a right separate from ownership:

  • updateAuthorityOf(tokenId) is initialized at mint. Only the current update authority may call updateSkill, updateSkillWithDocument, setSkillURI, setUpdateAuthority, and freezeSkill.
  • setUpdateAuthority(tokenId, address(0)) MUST revert: abandoning maintenance is expressed by freezeSkill, never by burning the authority — a zero authority would leave packageURI permanently unmaintainable without setting frozen.
  • ownerOf(tokenId) does not automatically equal the update authority; ERC-721 transfers MUST NOT change the update authority; ERC-721 approved/operator approvals MUST NOT confer any of these powers.

Freezing

  • freezeSkill MUST be irreversible and MUST emit SkillFrozen. Once frozen, updateSkill and updateSkillWithDocument MUST revert forever.
  • Freezing binds content — mdHash, packageHash, version — not transport: setSkillURI and setUpdateAuthority remain callable, so a frozen skill’s retrieval hints can outlive any storage provider.

Existence and update behavior

  • skillOf, skillURI, updateAuthorityOf, isSkillFrozen on a nonexistent tokenId MUST revert.
  • At mint the binding MUST be fully populated, version MUST equal 1, and the contract MUST emit SkillUpdated and SkillUpdateAuthorityChanged(tokenId, address(0), initialAuthority).
  • mdHash and packageHash MUST NOT be bytes32(0).
  • Updates MUST set mdHash and packageHash atomically in one call. The new packageHash MUST differ from the current one. mdHash MAY remain unchanged when the primary document is unchanged — editing companion files or rotating encryption keys legitimately changes only packageHash.
  • Callers never pass version: the contract increments it by exactly 1 per successful update and MUST revert on uint64 overflow. Versions are append-only.

Hash definitions (normative)

  • mdHash = SHA-256(plaintext primary Markdown document raw bytes). No line-ending, whitespace, BOM, or Unicode normalization; no salting or keying of any kind. The commitment is always to the exact plaintext, even when the published copy is encrypted.
  • packageHash = SHA-256(deterministically encoded SkillRoot bytes) — the package as published, which under confidentiality means the ciphertext objects. It is never the hash of a zip, tar, or .skill archive.

Package paths (normative)

All paths in this standard (md.path, entry.path, files keys, confidentiality objects keys) MUST be relative, case-sensitive UTF-8 POSIX paths. Paths MUST NOT begin with /; MUST NOT contain \, empty segments, or the segments . or ... No path normalization is performed: paths match byte-for-byte or not at all.

SkillRoot (normative off-chain object)

SkillRoot MUST be encoded as deterministic DAG-CBOR per the DAG-CBOR specification and RFC 8949 core deterministic encoding: definite lengths, shortest-form integers, and map keys sorted by the bytewise lexical order of their encoded bytes (string keys therefore sort effectively length-first due to the CBOR length prefix).

skill-root = {
  "md": md-entry,                  ; primary document
  "manifest": link,                ; machine-readable self-description; MUST be public
  "entry": entry,
  ? "license": license-entry,     ; legal/commercial terms
  ? "files": { * tstr => link },   ; remaining files; raw or ciphertext CIDs
  ? "confidentiality": link,       ; OPTIONAL confidentiality descriptor; absent = fully public package
  ? "prev": link                   ; version chain, rules below
}
md-entry      = { "path": tstr, "cid": link }
license-entry = { "path": tstr, "cid": link }
entry         = { "path": tstr, "cid": link, "profile": tstr }
link     = #6.42(bytes .size (37))
  ; DAG-CBOR link: tag 42 over a byte string of exactly 37 bytes:
  ;   0x00                       identity multibase prefix
  ;   0x01                       CIDv1
  ;   0x71 (dag-cbor) / 0x55 (raw)   codec varint
  ;   0x12 0x20                  sha2-256 multihash prefix
  ;   32-byte digest
  • Optional fields MUST be omitted when absent; null MUST NOT appear. SkillRoot, md-entry, license-entry, and entry are closed maps (extensibility lives in the manifest).
  • Primary document: a SkillRoot has exactly one primary document. md.path names it within the package namespace and MUST NOT appear as a key of files. Likewise license.path, when present, names the license file, MUST differ from md.path, and MUST NOT appear as a key of files — every addressable object in the SkillRoot carries an explicit path, so confidentiality objects resolve unambiguously; md.cid MUST identify the exact published bytes of that document. The publisher MAY choose the filename; SKILL.md SHOULD be used as the conventional default. The document MUST be UTF-8 encoded text and SHOULD be Markdown; its path SHOULD end in .md or .markdown, and consumers MUST NOT determine document type from the extension alone.
  • Version chain (prev): at version == 1, prev MUST be omitted; at version > 1, prev MUST be present and the digest inside its CID MUST equal the packageHash of the immediately preceding version.
  • If entry.path == md.path, entry.cid MUST equal md.cid; otherwise files[entry.path] MUST exist and equal entry.cid.
  • entry.profile is an opaque identifier of the execution environment; unknown profiles MUST NOT be executed by guesswork.
  • Package boundary: everything execution depends on MUST be reachable from the SkillRoot; unreachable content enjoys no protection under packageHash.

Manifest (normative minimal schema)

The manifest is a public, machine-readable JSON document. REQUIRED fields: schemaVersion, name, skillVersion, summary, entrypoint. manifest.entrypoint MUST equal SkillRoot.entry.path. Consumers MUST ignore unknown manifest fields; extension fields SHOULD use namespaced keys (e.g. x-vendor/*). Human-readable version strings live here (skillVersion), never on-chain.

Confidentiality descriptor (optional, in-package; normative schema)

When the confidentiality link is present, it MUST resolve to a public JSON descriptor. REQUIRED fields: schemaVersion, mode, profile, objects. policyHash and keyManagement are profile-specific: their presence and semantics are defined by the declared profile, not by this standard.

{
  "schemaVersion": "1.0",
  "mode": "encrypted",
  "profile": "x-lit-threshold-access-v1",
  "objects": {
    "SKILL.md":    { "ciphertext": "bafy…", "plaintextHash": "0x…" },
    "src/main.py": { "ciphertext": "bafy…", "plaintextHash": "0x…" }
  },
  "keyManagement": { "type": "threshold-network" }
}
  • Every encrypted object MUST appear in objects under its package path (per the path rules above). objects[path].ciphertext MUST equal the corresponding CID in the SkillRoot: md.cid when path == md.path, license.cid when a license entry exists and path == license.path, and files[path] otherwise. plaintextHash MUST be the 32-byte SHA-256 digest of the plaintext, hex-encoded.
  • If the primary document is encrypted, objects[md.path].plaintextHash MUST equal the token’s mdHash.
  • profile is an opaque identifier of the confidentiality scheme. A runtime encountering an unknown confidentiality profile MUST NOT attempt to obtain keys or decrypt by guesswork.
  • The manifest and the confidentiality descriptor themselves MUST remain unencrypted.
  • Partial encryption is expected and legitimate (e.g. public SKILL.md, manifest, and examples; encrypted source and configuration).
  • Key rotation re-encrypts content, changing ciphertext CIDs and therefore packageHash: this MUST be published as a version update. Plaintext-unchanged rotation leaves mdHash unchanged — the version history makes this legible.
  • Execution-privacy targets (TEE, FHE, ZK-proof profiles) are distinct from storage encryption and MAY be declared in manifest extension namespaces; standardizing them is future work.

Verification

Public mode (no confidentiality link) — a verifier MUST: read skillOf(tokenId); retrieve the SkillRoot bytes from any source; check SHA-256(bytes) == packageHash; decode, re-encode under the deterministic rules, and check byte equality; check the schema, path rules, and version-chain rules; check digest(md.cid) == mdHash; retrieve leaves as needed, checking each against its CID; deliver only fully verified content.

Confidential mode — the same through schema checking, then: verify each retrieved ciphertext against its CID (integrity of the published package needs no keys); confirm execution rights through whatever rights layer applies; obtain keys per the declared profile; decrypt; check sha256(plaintext primary document) == mdHash and each decrypted object against its plaintextHash; deliver only fully verified plaintext to the runtime.

In both modes, partial retrieval (root → manifest → primary document → full package) is a first-class workflow. Runtimes MUST pin the versions they execute by hash, MUST NOT automatically follow updates, and SHOULD require explicit re-authorization after observing SkillUpdated.

Scalable rights (companion standards)

This interface defines one canonical skill reference per token. Scaling distribution is the job of rights contracts — typically ERC-1155 — that reference the skill externally:

struct SkillRef { uint256 chainId; address skillContract; uint256 skillTokenId; }

Duplicating the ERC-721 skill token itself for sales is NOT RECOMMENDED: copies fragment the reference, version stream, and update authority. Sell rights that point at the skill, not copies of the skill. The rights interface (skillRefOf, issuance, supply, pricing) is deliberately outside this standard.

Rationale

Two hashes and a URI alone would be metadata; the normative SkillRoot is what makes this a binding standard. The chain holds commitments; the object graph defines exactly what they commit to, byte for byte.

Why packageHash must change on every update but mdHash may not. Every update publishes a new package (new SkillRoot, new prev link); but the primary document legitimately survives edits to companion files and key rotations.

Why the primary document carries an explicit path. Confidentiality objects, entry resolution, and tooling all address files by path; leaving the primary document’s path implicit would smuggle a naming convention into a byte-precise standard. md { path, cid } mirrors entry { path, cid, profile }.

Why the on-chain document is permanent once published. Chain history preserves the bytes regardless of later state; an interface that could report false after a disclosure would misrepresent reality. The monotone rule makes the state truthful.

Why zero-address authority is forbidden. Abandonment has a dedicated, honest expression — freezeSkill — which sets the flag consumers actually check. A zero authority would strand transport maintenance while leaving frozen == false, the worst of both.

Why confidentiality is a package property, not a token flag. A boolean cannot express which files are encrypted, under which scheme, with which custody, or how keys rotate — and a token field would sit outside the hash protection. A buyer can verify the published ciphertext package before paying; verifying plaintext against mdHash necessarily follows decryption.

Why mdHash stays mandatory even with an on-chain document. The hash is the document’s identity — linking the on-chain copy, the in-package copy (or its plaintextHash under encryption), and every version comparison to the same document.

Transport outside identity; publication outside ownership; freezing in the kernel, gates in extensions; contract-incremented versions — gateways die without version bumps; marketplaces trade without publishing; an irreversible freeze costs one boolean; counters owned by contracts make monotonicity a property, not a convention.

Why rights scale in a companion layer. ERC-721 answers which skill this is; ERC-1155 rights contracts answer who holds which class of rights, and how many; execution-rights extensions answer who may run it now; confidentiality profiles answer how content is obtained after rights are proven.

Relation to prior art. ERC-4907 is the formal model as a minimal 721 extension but governs usage roles, not what the token is. ERC-5169 attaches contract-level client scripts; here each token binds a content-addressed, versioned, per-file-verifiable executable artifact. ERC-8004 anchors who an agent is; this standard anchors what a capability is.

Relationship to skill and tool registries. ERC-8239 defines an ERC-721-based Agent Skill Registry together with skill-manifest commitments and agent-skill installation and usage attestations. ERC-8257 defines a mapping-based Agent Tool Registry with canonical tool metadata, creator and origin binding, and optional predicate-based access control.

Both proposals operate primarily at the registration, discovery, access, or attestation layers. This proposal does not define any of those layers. It defines the deterministic executable artifact and version history bound to an ERC-721 token: registry-level commitments establish where an artifact is described; this proposal additionally standardizes what exact executable package, file graph, plaintext primary document, and version history a token commits to.

Because ERC-8239 represents each registered skill as an ERC-721 token, an ERC-8239 SkillRegistry can also implement this interface for the same token identifier. In that composition, ERC-8239 supplies registration, discovery, installation, and attestation semantics, while this proposal supplies package identity, per-file integrity, version chaining, publication authority, freezing, and optional confidentiality. ERC-8257 does not require ERC-721 representation; an ERC-8257 tool manifest can instead reference a token-bound executable artifact as an external artifact dependency.

An ERC-8239 manifest hash, an ERC-8257 metadata hash, and this proposal’s packageHash commit to different objects and are not interchangeable.

Backwards Compatibility

Fully compatible with ERC-721 infrastructure: ownership, approvals, and transfers are untouched. Wallets unaware of this extension display skill tokens as ordinary NFTs; they will not display update authority, frozen status, on-chain documents, or confidentiality facts.

Reference Implementation

To be provided in assets/ before submission: Solidity contracts; a skill pack packaging tool (including confidential packaging); the two ERC-165 interface identifiers; JSON Schemas for the manifest and the confidentiality descriptor; path-validation test cases; and fixed test vectors for the deterministic encoding — including public v1, a v2 update changing only a companion file (constant mdHash), an encrypted key-rotation update, and an on-chain-document atomic update — so independent implementations can be checked byte-for-byte.

Security Considerations

Updates are the attack surface. Substitution beneath an unchanged hash is computationally infeasible; risk concentrates in the update functions. Mitigations: the independent update authority (never reachable via ERC-721 approvals), irreversible freezing, append-only versions with a closed prev chain, runtime pinning norms. Companion extensions add timelocked and attested gates.

Buyers must inspect publication control. ERC-721 ownership transfer does not transfer update authority. Purchasers MUST inspect updateAuthorityOf and isSkillFrozen before acquisition: buying a token whose update authority remains with a third party means buying an asset whose content that party can still change.

Skill content is untrusted input. Prompt injection targets LLM runtimes through the primary document. This standard anchors integrity, not benignity: runtimes MUST sandbox execution with least-privilege tooling; vetting belongs to attestation and reputation layers.

On-chain plaintext is irreversible disclosure. A document published via the on-chain document extension can never be re-secreted; the permanent hasOnchainSkillDocument state reflects this. Converting a skill to encrypted mode in a later version does not erase historical plaintext from chain history.

Plaintext commitments reveal equality. A public mdHash (and plaintextHash entries) confirms content equality: an adversary who can guess a document’s exact bytes can confirm the guess against the hash. Publishers of short or highly predictable confidential documents should weigh this exposure; salted or private commitment schemes are outside this standard and left to future extensions.

Ownership is not confidentiality — and confidentiality is not this standard’s key custody. Public packages are downloadable by anyone knowing the CID. Confidential packages anchor ciphertext integrity on-chain, but key release depends on the declared profile’s custodian: its availability, honesty, and revocation behavior are outside this standard and MUST be documented by implementations. A compromised custodian can leak plaintext but can never alter the skill undetected.

Key rotation and stale ciphertext. Rotation MUST surface as a version update; availability of old plaintext to consumers pinned on old versions is a custodial policy question, not an integrity one.

Unprotected content. Only content reachable from the SkillRoot is protected; out-of-graph files are not part of the skill.

Availability. Anchors outlive any storage provider; publishers SHOULD store redundantly; frozen skills retain a live setSkillURI precisely so transport can outlive providers.

Appendix A: Lifecycle example (informative)

A creator with my-skill/ (SKILL.md, manifest.json, runtime.yaml, src/main.py, config/schema.json) runs a packaging tool that: reads all file bytes → (optionally encrypts selected files and writes the confidentiality descriptor) → computes leaf CIDs → builds the SkillRoot (linking prev to the previous version’s packageHash when updating) → computes packageHash over its canonical bytes and mdHash over the plaintext primary document → uploads to content-addressed storage → mints or updates with (mdHash, packageHash, packageURI).

A consumer’s agent, given chainId + contract + tokenId: checks ERC-165 → reads skillOf/skillURI → fetches and verifies the SkillRoot → in public mode verifies and loads content directly; in confidential mode verifies ciphertext, proves rights, obtains keys per the declared profile, decrypts, and verifies plaintext hashes → builds a sandboxed environment per entry.profile → runs the skill. Tooling can wrap this as skill install|verify|run eip155:1/0xSkill/42 — tool-layer conveniences, not contract functions.

Copyright

Copyright and related rights waived via CC0.

Sharp proposal — the part that matters most isn’t the two hashes, it’s the normative object model underneath them, and specifically three separations you’ve drawn correctly: identity is the hash (not the URI), publication authority is a right separate from ERC-721 ownership, and confidentiality is a property of the package, not a token flag (so ciphertext integrity is checkable before any key release). That last one is easy to get wrong and you didn’t.

This sits directly underneath a lane we co-author in (ERC-8299/WYRIWE, ERC-8323/Source-Token Agent Binding). Two structural parallels worth naming:

  1. Your mdHash → packageHash is the same shape as WYRIWE’s raw_input_hash → sanitization_pipeline_hash: commit to the raw bytes first, then chain a hash over the transformation, so a later dispute can recompute either link independently instead of trusting the pipeline.

  2. Your packageHash (permanent) vs packageURI (mutable transport hint) vs updateAuthority (a right, not ownership) is exactly ERC-8323’s split between getSourceNFT (permanent provenance) and isSourceNFTOwnershipValid (live, re-checkable state) — same underlying principle: don’t let a mutable pointer or a live-state check quietly stand in for a permanent commitment, or vice versa.

The gap worth naming honestly, since your own framing (“registries answer WHERE, this answers WHAT”) sets it up perfectly: this proves WHICH bytes a token commits to and ran. It doesn’t and shouldn’t try to prove whether running them, on a specific input, produced a sound or authorized result — that’s a layer above yours, not a flaw in yours. WYRIWE’s judgment-execution attestation (rawProposalHash → verdictHash → executedActionHash) is built for exactly that next question: bind one specific invocation, against one specific input, of a specific packageHash+version, to a signed pre-action verdict and the action it produced. Your byte-level integrity is the substrate a verifier like that needs before it can even ask the harder question. Worth a companion note connecting the two once this settles — happy to draft it if useful.