Package Provenance
Package provenance is the evidence trail for a smart-contract package. It answers:
- who signed this package;
- which package descriptor they signed;
- whether the signed descriptor still matches the WASM, manifest, and runtime being instantiated;
- whether the signer satisfies the network's active provenance policy.
Provenance is not the same as registry approval. Provenance proves origin and integrity for a package. The Contract Registry proves that the network approved that package for deployment.
How It Works
The provenance flow has two parts:
- Static package evidence: the package contains
noos-contract.jsonand, after signing,provenance/signatures.json. - Consensus validation: transaction
INSTANTIATE_CONTRACTmay carry that provenance, and validators check it against the active policy stored in chain state.
The full lifecycle is:
- Build the contract WASM.
- Generate or review the manifest.
- Create
noos-contract.jsonwith the WASM hash and manifest hash. - Sign the descriptor.
- Store signatures in
provenance/signatures.json. - Include the descriptor and signatures in the instantiate payload.
- Validators load the active provenance policy.
- Validators verify hashes, runtime, signatures, and signer policy.
- Accepted provenance is summarized in the contract metadata.
This is why package provenance starts as files instead of a database row. A package needs to be portable and reviewable before a node accepts it. The database records the network policy and the accepted result after instantiation; it does not continuously update the package's signature file.
Package Descriptor
The package descriptor is noos-contract.json. It is the object that signers approve.
{
"schemaVersion": 1,
"packageName": "report-store",
"packageVersion": "0.1.0",
"description": "Stores report metadata and emits report events.",
"runtime": "wasm-assemblyscript-v1",
"manifestPath": "manifest.json",
"wasmPath": "build/contract.wasm",
"codeHash": "<sha256-of-wasm>",
"manifestHash": "<canonical-manifest-sha256>",
"authors": [{ "name": "NOOS", "organizationId": "org-noos" }],
"license": "UNLICENSED",
"repository": "https://example.invalid/noos/report-store",
"keywords": ["reports"],
"audits": [],
"metadata": {}
}| Field | Meaning | Review guidance |
|---|---|---|
schemaVersion | Descriptor format version. | Current value is 1. Unknown versions should be rejected unless the network explicitly supports them. |
packageName | Stable package identity. | Should match the intended release, registry entry, and release workflow. |
packageVersion | Version of the package identity. | Should change when code, manifest, ABI, authorization, or behavior changes. |
description | Human review context. | Should explain what the contract does clearly enough for review. |
runtime | Runtime required by the package. | Must match the manifest runtime. Current package provenance supports wasm-assemblyscript-v1. |
manifestPath | Location of the manifest inside the package. | Should point inside the package directory and match the reviewed file. |
wasmPath | Location of the WASM inside the package. | Should point inside the package directory and match the reviewed binary. |
codeHash | SHA-256 hash of the packaged WASM bytes. | Recompute it before signing or approving. |
manifestHash | Canonical hash of the parsed manifest. | Recompute it before signing or approving. |
authors | Responsible authors or organizations. | Use for accountability and review routing. |
license | License or release policy context. | Should satisfy the deployment environment's policy. |
repository | Source repository URL. | Should point to the reviewed source when available. |
keywords | Discovery and classification tags. | Useful for tooling, not security-critical by itself. |
audits | Review or audit references. | Use for completed audits, internal reviews, or security sign-off notes. |
metadata | Extra release context. | Use for ticket ids, build ids, environment labels, or review references. |
The security-critical fields are package identity, runtime, paths, codeHash, and manifestHash. Changing the descriptor, WASM, manifest, runtime, package name, or package version after signing makes the signed evidence stale.
For manifest hashing details, see Manifests.
Signatures
Package signatures live in:
provenance/signatures.jsonA package with provenance normally looks like this:
contract-packages/report-store-0.1.0/
noos-contract.json
manifest.json
build/
contract.wasm
provenance/
signatures.jsonEach signature has this shape:
{
"signerType": "user",
"signerId": "user-admin",
"publicKey": "<USER_PUBLIC_KEY_PEM>",
"algorithm": "ed25519",
"signedAt": "2026-05-30T00:00:00.000Z",
"signature": "<BASE64_SIGNATURE>"
}provenance/signatures.json is a static package artifact. It is written when the package is signed. It is not a live chain table, and it is not rewritten by validators. If the descriptor, manifest, WASM, or signer set changes, regenerate the package evidence and sign again.
Supported signer types are:
| Signer type | Typical meaning |
|---|---|
user | A named developer, owner, or reviewer approved the package. |
organization | An organization-level authority approved the package. |
auditor | An internal or external reviewer signed after review. |
ci | A controlled build or release pipeline signed the descriptor. |
operator | An operator or release engineer approved deployment packaging. |
A cryptographically valid signature is necessary but not sufficient. Validators also check the active policy. A valid signature from a disallowed signer type, signer id, or public key is rejected when the policy restricts those fields.
Signing Envelope
NOOSChain verifies signatures over a stable descriptor envelope. Conceptually, the signed object is:
{
"nonce": "0",
"createdAt": "1970-01-01T00:00:00.000Z",
"type": "CONTRACT_PACKAGE_DESCRIPTOR",
"signerPublicKey": "<SIGNER_PUBLIC_KEY>",
"payload": {
"descriptor": "<noos-contract.json object>",
"signature": {
"signerType": "user",
"signerId": "user-admin",
"algorithm": "ed25519",
"signedAt": "2026-05-30T00:00:00.000Z"
}
}
}The descriptor hash is sha256(canonicalize(descriptor)). Because the descriptor includes codeHash and manifestHash, the signature binds the signer to the exact WASM bytes and canonical manifest that were reviewed.
Active Provenance Policy
The active package provenance policy is network state. Validators load the default row from contract_provenance_policies during transaction INSTANTIATE_CONTRACT.
If no row exists, the default policy is:
{
"mode": "optional"
}The policy payload is:
{
"id": "default",
"mode": "required",
"allowedSignerTypes": ["user", "auditor"],
"allowedSignerIds": ["user-admin", "auditor-main"],
"allowedPublicKeys": ["<PUBLIC_KEY_PEM>"],
"minSignatures": 2,
"reason": "Require developer and audit approval for production contracts",
"metadata": {
"changeControlId": "CHG-1234"
}
}| Field | Meaning |
|---|---|
id | The only accepted policy id is default. |
mode | optional allows unsigned instantiation; required rejects missing provenance. |
allowedSignerTypes | Optional allowlist of signer types. Empty or absent means no restriction by type. |
allowedSignerIds | Optional allowlist of signer ids. Empty or absent means no restriction by id. |
allowedPublicKeys | Optional allowlist of signer public keys. Empty or absent means no key restriction. |
minSignatures | Minimum accepted signatures. If absent, required mode needs one accepted signature and optional mode needs zero. |
reason | Human explanation for the policy change. |
metadata | Change-control, rollout, environment, or review metadata. |
The policy is set by transaction SET_CONTRACT_PROVENANCE_POLICY. The transaction requires chain:admin; non-admin submissions fail with CHAIN_PERMISSION_DENIED.
npm run noos -- tx build-and-submit --type SET_CONTRACT_PROVENANCE_POLICY --payload-file "contract-provenance-policy.json" --signer-public-key "$env:NOOS_CONTRACT_PACKAGE_SIGNER_PUBLIC_KEY" --signer-private-key-path ".secrets/user-admin.private.pem" --yesThis submits transaction SET_CONTRACT_PROVENANCE_POLICY.
Policy updates are consensus state changes in the contract_provenance_policy state-root namespace. Replay and snapshot restore read the same policy rows, so validators do not need a separate local policy file for normal production execution.
Older local tests and replay helpers may still pass a fallback policy through the protocol context. The active chain row wins when present; the fallback is only used when the default policy row does not exist.
Policy Models
| Model | Policy shape |
|---|---|
| Optional development | mode: "optional" with no signer restrictions. Unsigned packages can instantiate, but signed packages still record accepted provenance. |
| Single developer approval | mode: "required", allowedSignerTypes: ["user"], allowedSignerIds set to approved package owners, minSignatures: 1. |
| CI-built releases | mode: "required", allowedSignerTypes: ["ci"], allowedPublicKeys set to controlled pipeline keys. |
| Audit approval | mode: "required", allowedSignerTypes: ["auditor"], optionally restricted by auditor id or public key. |
| Dual approval | mode: "required" and minSignatures: 2, with allowlists covering the approved developer, auditor, CI, or operator signers. |
Use public-key allowlists for production when possible. Signer ids are useful for review clarity, but public keys are the cryptographic identity that validators verify.
Instantiate Validation
Transaction INSTANTIATE_CONTRACT can include:
{
"provenance": {
"descriptor": {
"schemaVersion": 1,
"packageName": "report-store",
"packageVersion": "0.1.0",
"runtime": "wasm-assemblyscript-v1",
"codeHash": "<sha256-of-wasm>",
"manifestHash": "<canonical-manifest-sha256>"
},
"signatures": []
}
}During deterministic block execution, validators check:
- the instantiate manifest parses successfully;
- the actual manifest hash matches the instantiate
manifestHash; - descriptor
codeHashequals the instantiatecodeHash; - descriptor
manifestHashequals the instantiatemanifestHash; - descriptor
runtimeequals the manifest runtime; - each accepted signature verifies over the descriptor signing envelope;
- accepted signatures match the active policy's type, id, public-key, and count requirements.
Common deterministic failures are:
| Failure code | Meaning |
|---|---|
CONTRACT_PROVENANCE_REQUIRED | Policy is required and the instantiate payload did not include provenance. |
CONTRACT_PROVENANCE_INVALID | Descriptor/hash/runtime mismatch, invalid signature set, or too few accepted signatures after partial acceptance. |
CONTRACT_PROVENANCE_SIGNER_NOT_ALLOWED | No provided signature both verified and matched the active signer policy. |
Accepted provenance is summarized into contract metadata:
{
"contractProvenance": {
"packageName": "report-store",
"packageVersion": "0.1.0",
"descriptorHash": "<sha256>",
"acceptedSigners": [
{
"signerType": "user",
"signerId": "user-admin",
"publicKey": "<USER_PUBLIC_KEY_PEM>",
"signedAt": "2026-05-30T00:00:00.000Z"
}
]
}
}Only accepted signer metadata is stored in the contract metadata. The full package files should still be archived for review, audit, and incident response.
Create And Sign A Package
Package a contract:
npm run contracts:as-sdk:package -- --manifest "apps/noos-contract-sdk-as/examples/typed-report-contract/package/manifest.json" --wasm "apps/noos-contract-sdk-as/build/typed-report-contract.wasm" --out "contract-packages/report-store-0.1.0" --package-name "report-store" --package-version "0.1.0" --description "Stores report metadata and emits report events."Sign the descriptor:
$env:NOOS_CONTRACT_PACKAGE_SIGNER_PUBLIC_KEY = Get-Content ".secrets/user-admin.public.pem" -Raw
$env:NOOS_CONTRACT_PACKAGE_SIGNER_PRIVATE_KEY_PEM = Get-Content ".secrets/user-admin.private.pem" -Raw
$env:NOOS_CONTRACT_PACKAGE_SIGNER_TYPE = "user"
$env:NOOS_CONTRACT_PACKAGE_SIGNER_ID = "user-admin"
npm run contracts:as-sdk:sign-package -- --package "contract-packages/report-store-0.1.0"The signing command writes provenance/signatures.json. The publishing helper can include that provenance in the instantiate payload when publishing the package. See Publishing for the full workflow.
Provenance Review Checklist
Use this checklist before signing a package, accepting another signer's signature, registering a package, or embedding provenance in an instantiate payload.
| Check | What to verify | Why it matters |
|---|---|---|
| Package identity | packageName, packageVersion, repository, authors, and intended contract id match the release. | Registry and release workflows match by package identity. |
| Version discipline | The version changed when behavior, ABI, authorization, buckets, migrations, or runtime assumptions changed. | Reusing a version makes audits ambiguous. |
| Runtime match | Descriptor runtime and manifest runtime are identical. | Runtime mismatch invalidates deterministic execution assumptions. |
| WASM hash | Recompute SHA-256 over the packaged WASM and compare it with codeHash. | Proves the signed descriptor points to the reviewed bytecode. |
| Manifest hash | Recompute the canonical manifest hash and compare it with manifestHash. | Proves the signed descriptor points to the reviewed manifest. |
| Descriptor hash | Record sha256(canonicalize(descriptor)). | Registry entries and contract metadata refer to this exact descriptor. |
| Signature validity | Verify each signature over the descriptor envelope. | Prevents copied, stale, or malformed signatures. |
| Signer type | Confirm the signer type is appropriate for the release. | A development signer may not be acceptable for production. |
| Signer id | Confirm the signer id is an approved actor for this package. | Prevents unrelated actors from approving a package. |
| Public key | Confirm the public key is expected and current. | Prevents plausible signer ids with unknown keys. |
| Signature count | Confirm enough signatures will be accepted by policy. | minSignatures may require multi-party approval. |
| Audit metadata | Confirm descriptor audits and metadata point to real review evidence when required. | Future incident review needs to know what was reviewed and by whom. |
| Registry alignment | If registry approval is required, confirm package name, version, code hash, manifest hash, and descriptor hash match the registry entry. | Provenance alone does not approve a package for the network. |
| Archive evidence | Keep the package directory, hashes, public keys, policy, review notes, and registry entry. | Replay and incident response need the original evidence. |
Common Rejection Reasons
provenanceis missing while policy mode isrequired;- descriptor
codeHashdoes not match the instantiatecodeHash; - descriptor
manifestHashdoes not match the instantiatemanifestHash; - descriptor runtime does not match the manifest runtime;
- signature was made over an older descriptor;
- signer type is not allowed by policy;
- signer id is not allowed by policy;
- signer public key is not allowed by policy;
minSignaturesis higher than the number of accepted signatures;- a package version was reused after behavior or manifest changes;
- provenance exists in the local package but was not embedded in the instantiate payload.
Evidence To Keep
Archive these artifacts with the launch or review record:
- package directory;
noos-contract.json;manifest.json;- packaged WASM;
provenance/signatures.json;- computed descriptor hash;
- computed WASM hash;
- computed canonical manifest hash;
- signer public keys or key registry references;
- active provenance policy used for validation;
- policy transaction hash, if policy changed for the release;
- review notes and audit evidence;
- registry entry id, if the package is approved in the registry.
Losing the package files does not remove accepted provenance from chain state, but it weakens audit and incident evidence. Keep the signed package because it shows what was reviewed before submission.