Covenant: a declarative smart contract language that deploys to Orbit chains (feedback welcome)

Hi everyone, I’m Valisthea. I do security research and build developer tooling at Kairos Lab.

For the past few months I’ve been building Covenant, an open source declarative smart contract language (written in Rust, compiles to EVM bytecode). I’m sharing it here because it already runs on Orbit chains, and I’d value feedback from the people building and running them.

What it is

Instead of writing the mechanics by hand, you declare intent. You write a construct like token, vault, or ceremony, and the compiler synthesizes the boilerplate for you (for example, the full ERC-20 surface from a token declaration). The core design rule is fail-loud: if the compiler cannot produce provably correct bytecode for something, it raises a compile error instead of emitting code that merely looks right. The goal is to close the gap between what the source says and what the chain actually does.

Why it’s relevant to Orbit

Because Orbit chains are EVM compatible, the same Covenant source that runs on Ethereum deploys to them with no changes. I took it beyond Ethereum testnets and deployed a contract to Robinhood Chain testnet: a small token exercising mint, burn, and an onchain fee transfer. It’s live and verifiable:

In practice this means one contract targets any EVM Orbit chain out of the box, which felt worth surfacing to this community.

Honest status

I want to be upfront about where this is:

  • Testnet only. Deployed tokens have zero value and exist as compiler evidence, not as products.
  • The privacy primitives (FHE, ZK, post-quantum) are currently mocked. Real cryptography is a later milestone.
  • It has been adversarially reviewed by our own internal tooling, but has not had an external audit yet.
  • Solo builder. Open source (Apache-2.0 for the compiler, CC0 for the specs).

What I’m looking for

Feedback from anyone building on or operating Orbit chains, in particular:

  • What would make contract authoring easier or safer on your chain?
  • Are there Orbit specific concerns (custom gas token, permissioning, precompiles) a compiler like this should account for?

Repo: GitHub - Valisthea/covenant-language · GitHub
Site: covenant-lang.org

Thanks for reading. Happy to answer anything.

1 Like

Thanks for sharing this,

As someone looking at this from a risk and governance perspective, I see a few gaps / questions that would be useful to clarify before Orbit builders can treat Covenant as a serious option in their stack:

Security and correctness guarantees
– You mention an internal adversarial review but no external audit yet. Are there concrete plans or timelines for third‑party audits, especially if Orbit chains start experimenting with Covenant in production settings?

– Given the “fail‑loud” design goal, is there any formal verification, model checking, or proof framework backing the compiler’s claim of “provably correct bytecode”, or is this currently based on internal test coverage and invariants?
arbitrum

Testnet‑only status and scope of usage
– Right now, deployed tokens are explicitly described as testnet‑only with zero value and primarily “compiler evidence”. Do you have any guidance or disclaimers for Orbit operators to ensure this does not get prematurely used for mainnet / production flows?

– It might be helpful to spell out a recommended adoption path (e.g., experimental devnets → Orbit testnets → limited production pilots) so that governance and risk teams know how to frame this in their internal policies.

Orbit‑specific constraints (custom gas, permissioning, precompiles)
– You explicitly ask about custom gas tokens, permissioning, and precompiles. From the governance side, it would help if you documented what currently doesn’t work or isn’t supported yet for these Orbit‑specific customizations.

– For example, how does Covenant handle:

chains with non‑ETH gas tokens,

non‑standard precompiles or system contracts,

permissioned / semi‑permissioned Orbit environments?
A limitations section tailored to Orbit would make it easier for chain teams to evaluate fit.

Single‑maintainer / project continuity risk
– You are transparent that this is currently a solo‑builder effort. From a DAO / public‑goods standpoint, that raises the usual questions about bus factor, long‑term maintenance, and responsiveness to critical bugs.

– Do you have any roadmap for expanding the contributor set, or for aligning with any institution (e.g., Kairos Lab or another org) that would commit to longer‑term support if Orbit adoption grows?

Roadmap for privacy primitives (FHE, ZK, PQ)
– Since the privacy primitives are mocked for now, it would be good to understand how tightly your design depends on them. If an Orbit builder adopts Covenant early, how disruptive will it be when real cryptography is plugged in later?

– A rough sequence of milestones here (e.g., which primitive comes first, how it will be exposed at the language level) would help governance teams assess future upgrade and migration risk.

Overall, the idea of declarative constructs (token, vault, ceremony) and a “fail‑loud” compiler is very compelling for Orbit chains, especially if it can reduce boilerplate and implementation‑level footguns. At the same time, for DAO‑governed ecosystems, we need more explicit clarity on audit plans, Orbit‑specific limitations, and project continuity before this can be recommended beyond experimental use.

Happy to provide more detailed feedback once there’s documentation or specs focused specifically on an “Orbit adoption path” and a security / audit roadmap.

1 Like

Thanks, this is exactly the kind of scrutiny I was hoping for. Taking your points in order, including two places where you were right that I was not precise enough.

On “provably correct bytecode”

You are right to challenge that phrase and I retract it. There is no formal verification, no model checking, and no proof framework behind Covenant. What backs the fail-loud behaviour is a test suite (1,102 tests), regression tests that are negative-control verified, a fuzz harness, and adversarial review. That is evidence, not proof, and I should not have used a word that implies the latter. I am correcting the wording in the docs and on the site.

The accurate claim is narrower: when the compiler reaches a construct it cannot lower faithfully, it raises a compile error instead of emitting bytecode that merely looks plausible. It is a refusal mechanism, not a correctness proof.

1. Security and correctness guarantees

An external audit is the V1.0 gate and it is explicitly blocking. I will not call this production ready before an independent team has looked at it.

I cannot give you a timeline, and the reason is money rather than intent. Auditing a compiler is not the same scope as auditing a single contract: the surface is the whole toolchain that produces the bytecode, so a serious review means a firm with compiler and EVM codegen expertise spending real weeks on it. That is a budget an unfunded solo project does not have. There is no grant and no revenue behind Covenant today, so there is no audit I can honestly schedule. I would rather say that than post a date I cannot hold. If funding ever makes it possible, the scope, the auditors and the full report will be public, including whatever they find.

In the meantime I would rather you judge the internal work yourself than take my characterisation of it. The adversarial reviews I have run against Covenant exist as full write ups, with findings, severities, reproductions and fixes, and I am going to publish them rather than summarise them.

To be unambiguous about what they are: the engine that produces them is my own, so these are self reviews and they carry none of the independence of a third party audit. Publishing them is not a substitute for one. What it does is make the evidence behind my claims inspectable instead of asserted, methodology included, so you can decide for yourself how much weight it deserves. That includes the Critical one of those reviews found in my own confidentiality primitive, where a construct meant to restrict disclosure to the owner compiled with no access check at all until it was fixed.

One methodological detail, since it is the strongest thing I can offer short of an audit: every regression test guarding a fail-loud diagnostic is checked for non-vacuity. Neutralise the guard and the test must fail. That prevents the common failure mode where a suite is green because it never actually exercises what it claims to cover.

2. Testnet status and adoption path

Agreed that this should be explicit rather than implied, and I will write the staged path you describe.

One mechanism already exists that may be more useful to a risk team than a disclaimer. Every build artifact carries a mockedCryptoPrimitives field in its metadata JSON, listing which mocked primitive families (fhe, zk, pq) the emitted bytecode actually calls. It is populated from the IR regardless of target, so it is ground truth about the bytecode rather than a build flag, and it is machine readable. A chain operator can gate CI on it being empty.

Two honest gaps in that signal, which I would rather state than have someone discover:

  • The human-facing warning[mocked-crypto] only prints for the named real-chain targets, not for the default local target.
  • Ceremony and amnesia opcodes are deliberately not counted in that field, on the reasoning that the ceremony helper is a real state machine rather than a stub. The practical effect is that a pure ceremony contract produces an empty mockedCryptoPrimitives while still depending on placeholder cryptography. That is a gap in the signal and I intend to close it.

3. Orbit specific constraints

I went and read the compiler rather than answer from impression, and the honest answer starts with a correction to my own post.

There is no generic EVM target. The target selector is a closed enum: a local mock chain, Sepolia, and one other testnet. Any other chain name is a hard build failure, and there is no flag to supply custom helper addresses or a target registry. So “Covenant targets Orbit chains” overstates it, and I should not have implied otherwise.

What is actually true, and why the Robinhood Chain deployment worked:

Constructs that use no cryptography emit no chain specific address at all. token (the ERC-20 surface), nft (ERC-721), record, counter, board, market, module, and native value transfer produce plain EVM bytecode with nothing baked in. That artifact is chain agnostic, which is why it deploys and runs on an Orbit chain with standard tooling even though the compiler has no notion of that chain. That is the portable subset, and it is the part I would consider stable.

Constructs that use cryptography hard depend on four helper contracts at fixed addresses. Those addresses are PUSH20 immediates in the emitted bytecode. There is no runtime lookup, no constructor injection, and no on-chain registry. Two consequences worth stating plainly:

  • The addresses are deterministic only because they assume the Arachnid CREATE2 factory at 0x4e59...956C exists on the chain. On an Orbit chain without it, the prediction does not hold.
  • There is currently no compile time or runtime check that code actually exists at those addresses on the target chain, so a call into an empty address would fail at runtime rather than at build time. I consider that a defect against my own fail-loud principle, not a design choice.

Custom gas token: no ETH or 18 decimal assumption exists at the bytecode level. Native transfer lowers to a raw CALL carrying an unscaled value word, so it follows whatever the chain’s native asset is. The decimals you see in a token declaration is that token’s own ERC-20 value, unrelated to the gas asset. Untested empirically on a non ETH gas chain, but there is no source level assumption to break.

Gas schedules: the compiler is not gas aware yet. Every emitted call pushes a literal large gas value. Under a custom L2 gas schedule this is untested, and I would treat it as unknown rather than safe.

Custom precompiles: there is no supported way to register one today.

Permissioned environments: the toolchain does not deploy anything, it writes artifacts to disk, so there is no allowlist interaction on the compiler side. It does mean that on a permissioned chain the helper deployment step needs the same permission as any other deployment, which matters only if you intend to use the cryptographic constructs.

One footgun I want on the record: passing evm as the target name aliases to the local mock chain rather than meaning “generic EVM”. For the portable subset that is harmless since no addresses are emitted, but for a cryptographic construct it would emit calls to mock addresses that are empty on any real chain. That naming is misleading and I am changing it.

Rather than leave any of this at “should work”, I will run the portable subset and a helper dependent construct against a real Orbit chain and post the results here as a limitations matrix, including what fails.

4. Single maintainer and continuity

Fair, and I will not dress it up. It is one person. Kairos Lab is my own outfit, so pointing at it does not remove the bus factor, it renames it.

What I can point to is structural rather than institutional. Everything is public and permissively licensed (Apache-2.0 for the compiler, CC0 for the specs), there is no CLA, and there are no private components in the path that produces bytecode. If I stop, the project is forkable rather than stranded. For a solo project that is the honest mitigation and it is deliberately set up that way. Growing the contributor set is a goal, but I would rather earn that with a tool people use than announce it.

5. Privacy primitives roadmap

This is the best question in the set, and the answer is that exposure is compartmentalised rather than global. The boundary is not only per construct, it is also per qualifier:

You are not touching the mocked primitives if you use token, nft, record, counter, board, market, or module with plain fields. Nothing changes for those when real cryptography lands.

You are touching them if you use confidential token or ceremony, or if you use an encrypted field, a verified_by guard, or a pq_signed guard inside any construct. That last part matters: a single encrypted field pulls a plain construct into the mocked path, so the boundary is worth checking per contract rather than per keyword. The mockedCryptoPrimitives metadata field is the reliable way to check it.

For completeness, the post quantum key registry construct does not currently compile at all. It is hard blocked by a diagnostic on a dynamic bytes path, so it is not something anyone can adopt today.

The design intent is that primitives sit behind a helper interface, so language level syntax stays stable and what changes is which contract answers the call. The caveat I want on the record: real FHE, ZK and post quantum verification have gas and code size characteristics the mocks do not, so I expect the swap to affect feasibility and cost, not just addresses. Anyone adopting early should treat the cryptographic constructs as experimental and the plain EVM subset as the stable surface.

Likely ordering is post quantum signature verification first as the most tractable, ZK verification next, FHE last. No dates attached.

Next

Two deliverables out of this thread: an Orbit adoption path with a limitations matrix, and a security and audit roadmap stating what exists, what does not, and in what order. Then I will come back with the Orbit test results, including the parts that break.

Thanks for taking the time to write this out. It has already produced two fixes on my side.

1 Like

Following up, including on the part I could not deliver

@MconnectDAO, I made seven commitments in that reply. Six are done. The
seventh I could not complete, and going to do it found two defects, one of
which I then described wrongly in the first draft of this post.

Commitment Status
Retract “provably correct bytecode” and correct the docs and site Done. The phrase appears nowhere in the repo or on the site
Publish the adversarial reviews in full rather than summarise Done. 43 findings, published before any were fixed
Write a staged adoption path with a limitations matrix Done. docs/orbit-adoption.md
Publish a security and audit roadmap Done. docs/security-and-audit-roadmap.md
Fix the misleading evm target name Done. Hard build error, no longer an alias
Close the mockedCryptoPrimitives blind spot on ceremony Done. A pure ceremony contract now reports ["amnesia"]
Run a helper-dependent construct against a real Orbit chain Not done. See below

The review, published unfixed

Eight discovery lanes that did not share findings raised 48 candidates. A
refutation pass, told to destroy each one, killed 5. The surviving 43, of
which 14 Critical
, went out with no remediation applied, so the report was
public while every defect in it was still live in the released compiler.
Full write-ups.

v0.9.7 closed all 43. Ten by fixing the behaviour, the rest by making the
compiler refuse rather than emit something it could not vouch for. That added
21 diagnostics, 19 errors and 2 warnings, several of which cover more than one
finding. Test suite went from 1,105 at v0.9.6 to 1,257 at v0.9.7.

The test I did not run, and the two defects it surfaced

I promised to run the portable subset and a helper-dependent construct against
a real Orbit chain and post what fails. I ran the first. I could not run the
second, because there is nowhere to run it: the helper contracts are not
deployed on any Orbit chain, and deploying them is a step I have not taken.

Going to set it up found two things, neither of them in a released version
yet. Both are on main, not in the v0.9.7 tag, so if you download the release
and try them you will not see them.

One target pointed at helper addresses nobody had verified. They were the
addresses predicted for Sepolia, reused on the assumption that the CREATE2
factory exists elsewhere. The address manifest recorded none. E533 now
refuses to build a helper-reaching contract for a target whose helpers are
unconfirmed.

The default target was worse, and this is the one that matters to you.
mockchain bakes in the in-tab mock precompile addresses, 0x0101 and
neighbours, which exist on no public network. It is the default, so it is what
you get by typing nothing, and the artifact on disk carries no target in its
filename. A contract using mocked cryptography built that way printed ok
with no warning at all. Deploy it to an Orbit chain and it reverts on the
first action reaching a helper. W534 now says so at build time.

I said in my last reply that this class of gap existed. I had understated it:
I described the missing check, not the fact that the default path walked
straight into it in silence.

One correction to how I first wrote this up. My draft said an empty helper
address could let a verification read as passing. That is false, and this
compiler had already made it false. Every helper call site emits a success
check and a returndata-size check, both branching to REVERT, so an empty
address trips the size check. Those guards are KSR-CVN-013 and
KSR-CVN-014, fixed at v0.6. The real failure is a contract that compiles
clean, deploys clean and is dead on arrival, which is bad enough. The
diagnostic text and the Orbit guide carried the same overstatement and are
corrected.

Also worth stating precisely: three of the four helper addresses are CREATE2
predictions. The fourth, the ceremony helper, was deployed by an ordinary
transaction and is not reproducible elsewhere by redeploying with the same
salt.

The portable subset, checkable on Robinhood Chain

Constructs that touch no cryptography emit no chain-specific address, and that
bytecode is byte-identical across the EVM targets. That is why it runs on a
chain the compiler has never heard of.

The token at
0x3E80F8c7911240e6092D523af79B13c046bd2FdE,
chain 46630, 2,422 bytes of deployed code:

Call Raw return Reading
totalSupply() 999000000000000000000000 999,000 at 18 decimals, from a 1,000,000 mint
burned_total() 1000000000000000000000 1,000 burned
fee_rate_bps() 100 1.00%
fees_collected() 100000000000000000000 100 collected

One caveat so nobody has to find it: that artifact was compiled before the
v0.9.6 review, so it is evidence that the portable subset deploys and runs on
an Orbit chain, not evidence about the current compiler.

Still open

No external audit, no date, for the funding reason I gave last time. One
maintainer. registry does not compile in any form. board holds a schema
but cannot store posts. vault synthesizes nothing and adds no reentrancy
protection, contrary to my original post. Gas schedules untested under a
custom L2 schedule, custom precompiles unregisterable, custom gas token
untested empirically. And no helpers deployed on any Orbit chain, which is the
gap that makes the cryptographic constructs unusable there today.

If you operate an Orbit chain and want that changed, deploying the four
helpers on it and adding a target with the verified addresses is the concrete
step, and I would rather do it with an operator than guess.

1 Like

@Valisthea, the way you handled the previous review is the correct direction: claims were narrowed, open limitations were recorded, and unsupported behavior was converted into refusal rather than defended.

I reviewed the current main at source level, including the target resolver, helper registry, registry-consistency test, artifact metadata, helper-call codegen, CeremonyHelper, ceremony synthesis, and the current technical-debt ledger.

I have not executed the compiler or deployed the contracts in this review, so the points below are source-level findings and architectural closure requirements, not runtime acceptance results.

The next blocking problem is broader than one missing Orbit deployment.

A Covenant deployment currently depends on a semantic system of the form:

source
→ compiler identity
→ optimizer and EVM revision
→ target-chain definition
→ helper ABI and helper runtime code
→ generated artifact
→ deployment environment

Until that complete system has one frozen identity, matching source to emitted bytecode is necessary but not sufficient. The same emitted contract can behave differently if a helper address, selector, implementation, call mode, return shape, or chain environment changes.

1. Close the current registry/compiler inconsistencies first

There is a concrete selector disagreement in the current tree.

crates/covenant-evm-backend/src/target.rs maps:

AmnesiaBegin
amnesiaSetup(uint256)
0x09dc3eb0

That is the correct selector for the one-argument overload currently called by the compiler.

However, config/helper-addresses-v0.9.0.json records:

amnesiaSetupV091
0xa1c83bd6

The same registry correctly records 0xcba445bb for the three-argument amnesiaSetup(uint256,uint256,uint256) overload, so the V0.9.1 one-argument entry appears stale or incorrect.

The current registry_consistency.rs cannot catch this. It checks four addresses and the registry version through permissive string scanning, but it does not validate:

  • canonical function signatures;
  • recomputed selectors;
  • ABI hashes;
  • chain IDs;
  • deployed runtime code hashes;
  • deployment transactions and blocks;
  • active versus superseded helper identities;
  • verification links.

The active ceremony address is 0x627f..., while the ceremony verification link still points to the earlier 0x6cAB... deployment. The registry also has code_hashes: null.

I would remove the duplicated manually maintained selector/address surfaces.

Use one typed helper-interface registry as the source of truth and generate from it:

  1. the Rust dispatch table;
  2. the external JSON registry;
  3. the helper ABI manifest;
  4. the consistency tests;
  5. the build metadata entries.

Every selector should be recomputed from its canonical signature during CI. A manually entered selector should never be accepted merely because the address entries match.

helpers_verified_deployed() should also stop being a hardcoded Boolean property of a target enum. Verification should be derived from a frozen target descriptor and a concrete chain receipt.

2. Resolve the helper return-shape mismatch

There is a second concrete semantic mismatch at the ceremony boundary.

CeremonyHelper.amnesiaDestroy(uint256) returns dynamic bytes.

The synthesized Covenant destroy() path lowers Opcode::DestructionProof as Ty::Bool.

The generic helper-call decoder accepts any returndata of at least 32 bytes and then reads MLOAD(0) as the result. For a Solidity dynamic-bytes return, that first word is the ABI offset, normally 0x20, not the destruction commitment.

Therefore, after a successful helper call, the generated contract interprets the dynamic-return offset as a truthy Boolean. It does not consume, validate, store, or return the destruction commitment itself.

This should not remain under a generic “at least 32 bytes” decoder. The helper method’s return contract must be explicit.

There are two coherent designs:

V0.9-compatible design

Make the state-changing destroy method return a strict Boolean success value and expose the commitment through a separately typed fixed-width view, preferably bytes32, with its own selector and decoder.

Proof-carrying design

Change the IR and generated Covenant interface so that destroy() receives and handles the actual commitment/proof type. That requires a real dynamic ABI implementation if the result remains bytes.

Until one of those designs is complete, a deployment profile should refuse this path rather than interpret an ABI offset as a Boolean proof result.

The opcode should also not be named or documented as a destruction proof while the current V0.9 object is only a deterministic commitment over submitted values. A commitment may be useful evidence, but it does not prove cryptographic erasure.

3. Define a canonical target descriptor

A target should not be only a closed enum plus hardcoded addresses.

Introduce a versioned, canonically serialized target descriptor containing at least:

  • descriptor schema version;
  • target identifier;
  • chain ID;
  • chain fingerprint or genesis block hash;
  • relevant Orbit rollup/system-contract identity;
  • qualified EVM revision;
  • qualified ArbOS version or configuration;
  • native gas-token properties;
  • gas-schedule profile;
  • permissioning mode;
  • supported custom precompiles and system contracts;
  • helper-suite version;
  • helper security classification: mocked, experimental, or production-qualified.

For every helper referenced by the artifact, include:

  • helper family and method set;
  • address;
  • canonical signatures;
  • selectors;
  • ABI or interface hash;
  • deployed runtime EXTCODEHASH;
  • deployment transaction and block;
  • deployment mechanism;
  • CREATE2 factory, salt, and init-code hash where applicable;
  • call mode: CALL or STATICCALL;
  • expected return shape;
  • stateful or read-only classification;
  • immutable, proxy, beacon, or other implementation type.

Canonicalize this descriptor and bind its digest as targetDescriptorHash.

For helper-free contracts, the target descriptor must not affect emitted bytecode. For helper-dependent contracts, it becomes part of the artifact’s semantic identity.

4. Expand the build receipt

The current CompilationMetadata records the Covenant version, optimizer configuration, EVM version, ERC versions, precompile ABI version, and mocked primitive families. That is useful, but it does not identify the full system that produced and governs the artifact.

A detached deterministic build receipt should additionally bind:

  • normalized source hash;
  • compiler commit;
  • compiler binary hash;
  • browser/WASM compiler hash where applicable;
  • Cargo.lock hash;
  • rust-toolchain.toml hash;
  • normalized IR hash;
  • optimizer configuration;
  • target-descriptor hash;
  • helper-suite hash;
  • deployment-bytecode hash;
  • runtime-bytecode hash;
  • ABI hash;
  • selector-table hash;
  • storage-layout hash;
  • complete diagnostic set;
  • mocked or experimental primitive list;
  • construct-capability states.

Timestamps should remain outside the deterministic identity so that independent builds can reproduce the same receipt root.

The verifier must require the exact compiler or WASM identity, optimizer configuration, target descriptor, and helper suite. “The source recompiles to these bytes” is incomplete if a different compiler/helper system could assign different semantics to those bytes.

5. Add three separate helper gates

The compiler should remain offline-capable. It does not need to contact a chain during every build.

Instead, separate the gates cleanly.

Compile-time gate

Refuse helper-dependent compilation unless the supplied target descriptor is complete, internally consistent, and contains all referenced method signatures, selectors, return shapes, addresses, and runtime code hashes.

Deployment preflight

A command such as covenant target verify should query the selected RPC and verify:

  • exact chain identity;
  • code exists at every referenced helper address;
  • runtime code hashes match the descriptor;
  • required system contracts and precompiles exist;
  • helper ABI probes return the expected shape;
  • the chain configuration matches the qualified profile.

The result should be a signed or hashed verification receipt.

Constructor gate

For target-bound helper-dependent artifacts, the constructor should independently enforce:

  • the expected CHAINID;
  • the exact EXTCODEHASH of every helper actually referenced by that artifact.

The current code explicitly skips the native-precompile canary on helper-contract targets and leaves the inverse code-hash check for a later sprint. That inverse check should become a deployment blocker rather than an optional hardening item.

If a helper is upgradeable, proxy code hash alone is insufficient. Either reject upgradeable helpers for this profile or bind the complete implementation closure: proxy type, implementation or beacon, implementation code hash, admin authority, and upgrade policy.

Immutable, non-proxy helpers are substantially easier to reason about here.

6. Unify the two ceremony state machines

The synthesized Covenant contract and CeremonyHelper currently enforce related but different semantics.

The synthesized contract reads the source-declared threshold and enforces a distinct-caller count against it.

The helper’s one-argument amnesiaSetup(uint256) independently defaults to:

  • guardians = 3;
  • threshold = 2.

That means the generated contract and helper can carry different threshold facts for the same ceremony.

The source-declared guardian and threshold values should be threaded through the IR and into the three-argument helper call. The one-argument overload should be removed from new deployable helper suites or explicitly classified as a fixed-fixture compatibility method.

The helper should also enforce its own local invariants:

  • guardiansCount > 0;
  • threshold > 0;
  • threshold <= guardiansCount;
  • both values fit safely into uint128 before casting.

The current unchecked narrowing from uint256 to uint128 should not be accepted merely because the Covenant frontend normally supplies small values. The helper is a public contract boundary and must validate its own inputs.

tx.origin should not identify a guardian. It diverges from the caller identity seen by the generated ceremony contract and fails under smart accounts, relayers, and account-abstraction flows. The generated contract should pass the already authenticated guardian identity explicitly to the helper.

amnesiaFinalize() currently sets the helper phase to Finalized even when thresholdMet is false. The generated caller presently reverts on a false result, which rolls the nested helper state back, but the helper’s own transition predicate remains weaker than its declared state machine. The helper should itself revert or preserve Active when the threshold is not met.

These invariants should hold locally at every layer, not only because one specific generated caller compensates for a weaker callee.

7. Centralize all helper dispatch

The helper boundary should be one exhaustive typed table:

opcode
→ helper family
→ canonical signature
→ selector
→ address
→ call mode
→ input ABI
→ output ABI
→ statefulness
→ security class

No helper selector should be generated through a separate path.

The current debt ledger already records two paths outside the principal E520 coverage:

  • AssertEncrypted;
  • the confidential genesis-mint FheEncryptTrivial path.

Those should be moved into the same exhaustive dispatch mechanism. A new opcode must be impossible to lower unless every target-dependent property is defined.

Read-only helper methods should use STATICCALL. Stateful ceremony methods should use CALL. Treating every helper-contract operation as CALL is operationally simple, but it gives read-only helpers more authority than their semantics require.

Return-data validation must be method-specific. “At least 32 bytes” is not a sufficient ABI contract for methods with different fixed and dynamic result shapes.

8. Create a strict deployable profile

There is a practical conflict in the current diagnostic policy: some unsupported dynamic ABI cases remain warnings because turning them into errors breaks examples and ordinary exploratory programs.

That conflict should be resolved with profiles, not by allowing deployable artifacts with known wrong ABI behavior.

For example:

  • check or playground profile: warnings may remain visible for exploration;
  • build --profile deploy: every known semantic or ABI incompleteness is an error.

The deployable profile should reject at least:

  • dynamic calldata that would be skipped or read as zero;
  • non-constant dynamic returns that are not correctly encoded;
  • non-indexed dynamic event values that are not ABI-correct;
  • first-class constructs that synthesize no claimed behavior;
  • helper calls without a qualified target descriptor;
  • mocked primitives outside an explicitly allowed test profile;
  • unresolved helper return shapes.

Publish a machine-readable capability registry with states such as:

  • IMPLEMENTED_PORTABLE;
  • IMPLEMENTED_TARGET_BOUND;
  • EXPERIMENTAL;
  • MOCKED_TESTNET_ONLY;
  • REFUSED;
  • NOT_IMPLEMENTED.

registry, board, and vault should be classified from actual emitted semantics, not only from parser-level availability. A keyword that parses but cannot provide its declared behavior must not look deployable.

9. Formalize the portable-subset invariant

The byte-identical portable subset is one of the most valuable properties in the project. It should be a permanent metamorphic acceptance gate.

For identical source, compiler identity, optimizer configuration, and EVM revision, when the helper-call set is empty:

  • deploy bytecode must be identical across all target descriptors;
  • runtime bytecode must be identical;
  • no helper address literal may appear;
  • the helper-suite dependency set must be empty;
  • the mocked-primitive set must be empty;
  • ABI and storage-layout hashes must be identical.

The target-specific verification receipt may differ, but the semantic bytecode must not.

For helper-dependent builds, determinism should instead be defined over:

source

  • compiler
  • optimizer
  • target descriptor
  • helper suite.

This gives operators a precise boundary between “portable EVM output” and “target-bound distributed execution system.”

10. Qualify Orbit behavior as a matrix, not a single deployment

The next Orbit evidence should cover failure boundaries as well as a successful token deployment.

At minimum:

  • ordinary Orbit testnet;
  • custom native gas token;
  • materially different gas schedule;
  • permissioned helper deployment;
  • missing helper code;
  • wrong code at the expected helper address;
  • helper ABI mismatch;
  • malformed, short, and oversized returndata;
  • helper revert;
  • absent CREATE2 factory;
  • proxy or upgradeable helper;
  • custom precompile configuration;
  • mocked helper on a live-value chain;
  • chain-ID mismatch.

The universal literal gas cap used for emitted calls should be replaced by an explicit method-level gas policy or at least qualified under each tested Orbit configuration. The caller must retain enough gas to perform returndata validation and revert deterministically.

Each case should freeze:

  • source;
  • target descriptor;
  • build receipt;
  • deployment transaction;
  • preflight receipt;
  • pre-state;
  • transaction result;
  • post-state;
  • logs;
  • gas used;
  • exact failure classification.

A green deployment alone shows reachability. It does not establish the boundary within which the compiler/helper system remains correct.

11. Split the audit path into defensible layers

The funding problem for a full compiler-plus-cryptography audit is real, but the first external scope does not need to include every future primitive at once.

A defensible sequence would be:

Audit A — portable deployment profile

  • frontend and language semantics;
  • fail-loud completeness;
  • privacy-analysis invariants;
  • IR and optimizer;
  • EVM code generation;
  • ABI, selectors, events, and storage layout;
  • implemented plain ERC surfaces;
  • artifact metadata and reproducible verification;
  • all helper-dependent and mocked constructs hard-blocked.

Audit B — helper bridge and ceremony state machine

  • target descriptors;
  • selector and ABI generation;
  • helper code-hash binding;
  • call and return semantics;
  • ceremony lifecycle;
  • Orbit deployment and chain-configuration assumptions.

Audit C — real cryptographic implementations

  • FHE;
  • ZK;
  • post-quantum verification;
  • VDF/Shamir semantics;
  • their gas, liveness, and failure assumptions.

This does not replace a full V1.0 audit. It creates a smaller first surface whose production claim can be stated precisely without inheriting unresolved mock-cryptography risk.

Recommended order

I would close the work in this order:

  1. Correct the selector registry and verification link.
  2. Resolve the amnesiaDestroy dynamic-return/Boolean mismatch.
  3. Generate all helper interfaces and consistency tests from one typed registry.
  4. Freeze the target-descriptor and build-receipt schemas.
  5. Implement live preflight plus constructor CHAINID/EXTCODEHASH gates.
  6. Thread declared guardian and threshold semantics through the helper ABI.
  7. Remove tx.origin and make the helper state machine locally valid.
  8. Introduce the strict deployable profile and capability registry.
  9. Run the Orbit qualification matrix.
  10. Externally audit the portable profile first, then the helper-bound profile.

The highest-value next artifact is not another isolated deployment.

It is one frozen target descriptor, one complete build receipt, one generated helper-interface registry, and one independently checkable Sepolia or Orbit verification receipt. Once those exist, an operator can determine exactly what compiler, bytecode, helper code, chain state, and security assumptions they are authorizing.

1 Like

@cxclrfx, I checked every concrete claim against the source before answering,
and recomputed the selectors rather than reading the tables. Everything you
raised is correct.

Nothing below is fixed yet. This is the verification and the plan, so you
can hold me to both.


1. What reproduces

You said you had not executed the compiler. You did not need to.

The selector disagreement

$ cast sig "amnesiaSetup(uint256)"
0x09dc3eb0

$ cast sig "amnesiaSetup(uint256,uint256,uint256)"
0xcba445bb

crates/covenant-evm-backend/src/target.rs:225 emits 0x09dc3eb0, which is
correct. config/helper-addresses-v0.9.0.json publishes:

"amnesiaSetup":     "0xcba445bb",
"amnesiaSetupV091": "0xa1c83bd6",

0xa1c83bd6 matches neither signature. The compiler is right and the
published manifest is wrong, which is the worse way round: an operator
verifying a deployment against that document reaches a false conclusion.

registry_consistency.rs cannot catch it. It does four string searches inside
an 80-character window around a key, plus a version comparison:

fn json_contains_address(key: &str, addr: &[u8; 20]) -> bool {
    let registry_lc = REGISTRY_JSON.to_lowercase();
    let key_marker  = format!(r#""{key}""#);
    let addr_hex    = addr_to_lower_hex(addr);
    // ... window scan

No test anywhere in the suite recomputes a selector from its signature.

The rest of the registry

The ceremony verification link points at 0x6cAB... while the compiler emits
0x627f.... code_hashes is absent, not merely null.

CeremonyHelper.sol

// line 68
function amnesiaSetup(uint256 nonce) external returns (uint256 sessionId) {
    return _amnesiaSetup(nonce, 3, 2);
}

The guardian and threshold values declared in the Covenant source never reach
the helper.

// lines 124, 126
s.guardiansList.push(tx.origin);
emit AmnesiaShareSubmitted(sessionId, tx.origin);
// lines 143-144
thresholdMet = s.sharesSubmitted.length >= s.threshold;
s.phase = Phase.Finalized;          // unconditional
// lines 100-101
s.guardiansCount = uint128(guardiansCount);   // unchecked
s.threshold      = uint128(threshold);        // unchecked

And helpers_verified_deployed() is exactly the hardcoded Boolean on an enum
you describe. I added it two days ago, so that one is fresh rather than
inherited.


2. The destroy mismatch, and one thing you did not mention

You are right, and the consequence is slightly worse than you put it.

amnesiaDestroy returns dynamic bytes:

function amnesiaDestroy(uint256 sessionId)
    external
    returns (bytes memory destructionProof)
{
    ...
    bytes32 commitment = keccak256(
        abi.encodePacked(s.sharesSubmitted, sessionId)
    );
    destructionProof = abi.encode(sessionId, commitment);

So the first returndata word is the ABI offset, 0x20. The synthesizer emits
the opcode as Ty::Bool and then asserts on it:

// crates/covenant-stdlib/src/amnesia_ceremony.rs:488
let result = b.emit_instr(Opcode::DestructionProof, vec![session_id], Some(Ty::Bool));

// KSR-CVN-001: phase advances only if DestructionProof succeeded.
b.emit_instr(Opcode::Assert, vec![result], None);

The generic decoder reads MLOAD(0), gets 32, and 32 is truthy. The assert
can never fail.
What actually protects the phase transition is the helper’s
three reverts, not the guard the comment claims. A documented protection that
does not exist is worse than a missing one.

The part I had not seen until I traced it. destroy() is declared
returns (bool) in the ABI and returns 32. A strict decoder expects 0 or
1. That is the same defect class the previous review fixed on the input
side, where a non-canonical bool argument is now rejected. The compiler
refuses one coming in and emits one going out.

The commitment is never consumed, validated or stored on the Covenant side.
And your secondary point stands: keccak256(sharesSubmitted ++ sessionId) is
a deterministic commitment over submitted values. Calling it a destruction
proof claims more than the object establishes, and the opcode name is going to
change.


3. One constraint that reorders your plan

The four helpers are deployed and immutable, three by CREATE2 and one by an
ordinary transaction. Fixing CeremonyHelper.sol forces a redeployment, new
addresses, and a new helper-suite version.

So the ceremony corrections and the versioned descriptor are not two separate
workstreams: the first compels the second. Better to freeze once, properly,
than redeploy twice.


4. Plan

Day estimates for one person, no margin.

# Sprint Days
1 Remove the false statements 3 to 4
2 Refuse what is not correct 4 to 6
3 Fix and republish the helper suite 5 to 8
4 Target descriptor and build receipt 8 to 12
5 Portable-subset invariant 2 to 3
6 Deployment gates 5 to 8
7 Strict deployable profile and capability registry 4 to 6
8 Orbit qualification matrix 10 to 15

1. Remove the false statements. Correct the selector entry and the
verification link, populate code_hashes from live EXTCODEHASH, rewrite the
KSR-CVN-001 comment, rename the opcode from proof to commitment, and add the
test that recomputes every selector from its canonical signature. That last
one is what would have caught 0xa1c83bd6.

2. Refuse what is not correct. Block the destroy() path until its return
shape is resolved, since refusing beats reading an offset as a Boolean.
Per-method return validation instead of the generic 32-byte floor.
STATICCALL for read-only helper methods. Pull AssertEncrypted and the
confidential genesis-mint path into the central dispatch table.

3. Fix and republish the helper suite. Thread the declared guardians and
threshold into the three-argument call and retire the one-argument overload
from deployable suites. Replace tx.origin with the identity the generated
contract already authenticated. Make amnesiaFinalize revert or stay Active
below threshold. Bound the casts and enforce the local invariants. For destroy
I intend the V0.9-compatible design: a strict Boolean plus a separately typed
bytes32 view, rather than a dynamic ABI implementation.

4. Target descriptor and build receipt. Starting with the fields that
change semantics rather than all twenty at once, and generating the Rust
table, the JSON and the tests from one typed source. That is what makes a
selector divergence structurally impossible instead of merely tested, which is
your actual point.

5. The portable-subset invariant. Promoted ahead of the remaining
infrastructure because it is cheap and permanent: identical bytecode across
every target descriptor when the helper-call set is empty, as a metamorphic
gate.

6. Deployment gates. covenant target verify against a live RPC, and a
constructor gate on CHAINID and the exact EXTCODEHASH of every referenced
helper. The native-precompile canary is currently skipped on helper targets
and the inverse check deferred; it becomes a blocker.

7. Strict deployable profile and capability registry. Resolves the
warning-versus-error conflict without letting artifacts ship with known-wrong
ABI behaviour, and classifies registry, board and vault by what they
emit rather than by what parses.

8. Orbit qualification matrix. Your fourteen cases are the right list. The
cost is access, not testing. Missing code, wrong code, ABI mismatch, malformed
returndata, helper revert and absent CREATE2 factory all simulate on a local
anvil, so those come first. Custom gas token, custom gas schedule and
permissioned deployment need a chain I do not control, so those wait for an
operator in the loop. The universal literal gas value gets replaced by a
per-method policy in the same pass.

One change to your ordering: run the Orbit matrix after the descriptor exists
rather than before, otherwise it freezes artifacts that will need refreezing.


5. On the audit split

Your three-layer sequence is better than the single scope I was about to
submit, and I am adopting it. Auditing the portable profile first, with every
helper-dependent and mocked construct hard-blocked, gives a smaller surface
whose production claim can be stated precisely without inheriting mock-crypto
risk. It also answers the obvious committee objection, which is why anyone
should fund an audit of primitives the project itself calls placeholders.

Thank you for this. It cost you considerably more effort than the average
forum reply, and it is the most useful thing anyone has done for the project.

Valisthea, this is the right way to respond to a technical review.

You reproduced the claims independently, distinguished the actual consequence from the original wording, identified an additional non-canonical Boolean defect, and published a concrete correction plan rather than defending the existing implementation.

I also rechecked current main after your reply.

Commit f1b041f already corrects the published selector, the active CeremonyHelper verification link, and the previously absent observed code hashes, and adds the first selector-recomputation and portable-subset gates.

That is a meaningful immediate response.

There is, however, one layer still missing from point 3.

You are correct that an immutable CeremonyHelper correction forces a new address and therefore a new helper-suite identity.

But this is not only a helper redeployment problem.

It is a migration of the complete semantic dependency graph:

Covenant source
→ compiler identity
→ helper interface
→ helper implementation
→ helper deployment
→ target descriptor
→ generated Covenant bytecode
→ existing helper sessions
→ verifier identity

That changes the order in which the work should be frozen.

1. Point 3 requires four separate identities

The target descriptor should not be the first object and cannot be the only object.

Some information is known before deployment; other information exists only after deployment.

I would separate the system into four immutable records.

A. Helper Interface Specification

Chain-independent and frozen before implementation deployment:

  • suite schema version;
  • helper family;
  • canonical signatures;
  • selectors;
  • argument types;
  • return types;
  • state mutability;
  • call mode;
  • revert contract;
  • state-transition semantics;
  • evidence object semantics;
  • security classification;
  • interface hash.

This should be the typed source from which Rust dispatch, Solidity interfaces, JSON output, ABI expectations, and tests are generated.

B. Helper Suite Build Receipt

Frozen before deployment:

  • exact source-tree hash;
  • Solidity compiler version;
  • compiler settings;
  • optimizer settings;
  • metadata policy;
  • creation bytecode hash;
  • runtime bytecode hash;
  • init-code hash;
  • ABI hash;
  • storage-layout hash;
  • test manifest;
  • suite composition.

This produces the expected identity of the code.

C. Chain Deployment Receipt

Created after deployment, separately for every helper:

  • chain ID;
  • chain fingerprint;
  • helper identity;
  • deployment mechanism;
  • factory;
  • salt;
  • init-code hash;
  • deployer;
  • transaction hash;
  • block number;
  • block hash;
  • resulting address;
  • observed EXTCODEHASH;
  • expected runtime hash;
  • equality result.

D. Target Descriptor

The target descriptor should then reference:

  • the Helper Interface Specification;
  • the Helper Suite Build Receipt;
  • the relevant Chain Deployment Receipts;
  • the qualified chain environment.

The Covenant artifact receipt can finally reference the target-descriptor hash.

The corrected order is therefore:

freeze interface
→ build candidate
→ freeze expected bytecode identity
→ deploy once
→ record exact chain identity
→ construct target descriptor
→ compile target-bound Covenant artifact

In the current plan, Sprint 3 redeploys the helper suite before Sprint 4 defines the target descriptor and build receipt. That risks freezing the deployment before the object that gives the deployment meaning is complete.

Your “freeze once, properly” conclusion is correct.

The practical consequence is that the normative interface and expected build identity must be frozen before the one deployment.

2. Treat the correction as a migration, not only a new address

The current architecture explicitly states that helper addresses are embedded into generated bytecode and remain there for the life of the Covenant contract.

Therefore, deploying a corrected helper does not correct any previously generated contract.

For every legacy Covenant artifact, the old address remains permanent.

The helper also stores sessions internally, so existing sessions do not automatically move into a corrected helper.

The correction therefore needs an affected-artifact and session policy:

  • which compiler versions emitted the old address;
  • which generated contracts reference it;
  • which sessions exist in the old helper;
  • whether any session can be completed safely;
  • whether migration is possible;
  • whether the old path must be abandoned;
  • how the verifier reports legacy artifacts;
  • how the compiler rejects superseded suites;
  • whether the playground can still reproduce historical builds without presenting them as current.

Required suite states should include:

  • CURRENT;
  • SUPERSEDED;
  • LEGACY_REPRODUCTION_ONLY;
  • UNSAFE_DO_NOT_USE;
  • RETIRED.

A new helper version fixes future compilations.

It does not retroactively repair the old dependency graph.

Also, a new suite version does not necessarily require redeploying every unchanged immutable helper.

The suite can be a versioned composition that references:

  • a new CeremonyHelper receipt;
  • existing unchanged FHE/PQ/ZK helper receipts;
  • their exact previously frozen code identities.

That avoids unnecessary redeployment while still producing a new aggregate suite identity.

3. Reconcile the deployment history before the next deployment

The current public sources describe three different deployment stories.

Your reply states:

  • three helpers were deployed by CREATE2;
  • CeremonyHelper was deployed by an ordinary transaction.

The current helpers/script/Deploy.s.sol deploys all four contracts using CREATE2 syntax, including:

SALT_CEREMONY = keccak256("covenant-v0.9.1-ceremony")

The helper README also describes all four helpers through deterministic CREATE2 salts.

The registry then states:

All 4 CREATE2 addresses match Sprint 30 predictions exactly

but the Sprint 30/V0.9.0 predicted CeremonyHelper address is:

0x6cAB...

while the active V0.9.1 CeremonyHelper is:

0x627f...

These statements may refer to different historical deployments, but they cannot currently be reconstructed from one canonical receipt.

Before the next suite is deployed, record the deployment identity separately for each helper.

A single global:

deployed_at_block

is not sufficient where the active suite combines contracts from different transactions, versions, salts, or deployment mechanisms.

4. Do not mutate the V0.9.0 manifest under the same identity

The selector and verification-link corrections are correct.

The identity problem is that the file remains:

helper-addresses-v0.9.0.json

with:

"version": "0.9.0"

while it now contains:

  • the active V0.9.1 CeremonyHelper;
  • corrected selector information;
  • new code hashes;
  • a superseded V0.9.0 helper;
  • mutable status text.

This means two materially different documents have existed under one semantic identity.

The historical V0.9.0 manifest should remain immutable.

The clean recovery is:

  1. preserve the original V0.9.0 manifest hash;
  2. record that its selector and verification entry were incorrect;
  3. publish a detached correction/supersession receipt;
  4. publish a new manifest identity for the actual active suite;
  5. make the compiler and verifier bind the exact manifest hash, not only a compatible version expression such as ^0.9.0.

The correction history is part of the evidence.

It should not disappear when the current JSON becomes accurate.

5. Distinguish observed code hash from expected code identity

Populating code_hashes is useful, but:

code_hashes_read_at: "sepolia latest, 2026-07-30"

is not yet a reproducible verification receipt.

An observed EXTCODEHASH answers:

What code was present at this address when the query was made?

It does not answer:

Is this the exact runtime code produced by the accepted source and compiler configuration?

That requires:

expected runtime hash from the build receipt

observed EXTCODEHASH at an exact block

The receipt should include the exact block number and block hash rather than latest.

Otherwise the system can authenticate the identity of an unknown deployment without proving that it is the intended deployment.

6. The current smoke test is helper-level, not compiler-to-helper end-to-end

The registry records:

CeremonyHelper.amnesiaSetup(42, 3, 2)

as the first-call method and describes the result as an end-to-end smoke test.

The current compiler path, however, emits:

amnesiaSetup(uint256)

A direct cast call to the three-argument overload proves that the helper accepts that ABI.

It does not prove that a generated Covenant contract emits the correct selector, argument layout, call mode, return decoder, and local state transition.

The actual end-to-end gate should be:

  1. compile a frozen Covenant ceremony fixture;
  2. confirm the generated bytecode embeds the expected helper address;
  3. deploy the generated Covenant contract;
  4. call its public setup();
  5. trace the generated call into the helper;
  6. verify the exact selector and calldata;
  7. verify the returned session ID;
  8. verify both helper state and Covenant-local state;
  9. bind the complete run to the compiler, suite, target, and deployment receipts.

The direct cast test should remain, but should be named a helper deployment smoke test.

7. The selector gate is not yet exhaustive

The new selector tests close the immediate regression, but the current manifest test does not actually enumerate every selector in the manifest.

The manifest contains approximately twenty selector entries.

The test-side MANIFEST array contains six.

The direct dispatch-to-manifest comparison contains five mappings.

The current test therefore proves that the listed subset is correct.

It does not prove that:

  • every manifest selector was checked;
  • no extra untested selector exists;
  • no compiler dispatch entry is absent from the manifest;
  • no selector collision exists;
  • argument and return shapes agree;
  • mutability and call mode agree.

The typed interface registry you propose is the real fix.

Its generated acceptance should require:

  • exact entry-count equality;
  • no unconsumed manifest entries;
  • no unrepresented dispatch entries;
  • no selector collisions;
  • ABI equality against the compiled Solidity interface;
  • return-shape equality;
  • call-mode and mutability equality.

No hand-maintained parallel list should decide what “every selector” means.

8. Keep destruction and its evidence atomic

The proposed strict Boolean plus separate bytes32 view removes the dynamic-ABI/Boolean mismatch.

I would still change the return contract.

The cleanest V0.9-compatible interface is:

amnesiaDestroy(uint256 sessionId) returns (bytes32 commitment)

Failure is already expressed by revert.

On success, the generated Covenant contract should:

  1. require exactly 32 bytes of returndata;
  2. decode the commitment;
  3. store it locally;
  4. emit it in the generated Covenant event;
  5. advance the local phase only after successful decoding.

A second view such as:

getDestructionCommitment(uint256)

can remain as a recovery and inspection surface.

It should not be the only way the generated Covenant artifact receives the evidence produced by its own state transition.

A valid alternative is:

returns (bool ok, bytes32 commitment)

with exact 64-byte decoding and a strict canonical ok == 1.

But the Boolean carries little information when every failure already reverts.

The important invariant is:

destruction state transition
+
commitment creation
+
commitment consumption
+
local phase transition

occur in one atomic execution path.

9. Bind the commitment to the ceremony policy, not only the submitted values

The present commitment binds:

  • sharesSubmitted;
  • sessionId.

It does not explicitly bind:

  • commitment schema/domain version;
  • chain ID;
  • helper address or suite identity;
  • ceremony contract;
  • guardian count;
  • threshold;
  • submitted guardian identities.

After the shares and guardian list are deleted, the retained commitment should still identify the complete policy and participation object whose values were destroyed.

At minimum, its canonical preimage should bind:

  • a domain separator;
  • schema version;
  • chain;
  • helper;
  • ceremony;
  • session;
  • guardian count;
  • threshold;
  • identity-set commitment;
  • share-set commitment.

This still does not become proof of cryptographic erasure.

It becomes a well-scoped commitment to the exact ceremony state that was destroyed.

10. Define the guardian trust boundary explicitly

Replacing tx.origin is necessary, but the replacement needs a precise claim.

The generated Covenant contract currently enforces distinct callers.

The language does not yet support a pre-registered guardian-address set.

Therefore, the current guarantee is:

threshold distinct submitter identities participated

It is not:

threshold authorized members of a predefined guardian set participated

If the generated contract passes an identity argument to the helper, the helper is accepting an identity attested by that ceremony contract.

It is not independently authenticating the human or account behind that identity.

The helper should still enforce its local invariants:

  • nonzero identity;
  • one share per identity;
  • submitted-share count not exceeding guardiansCount;
  • threshold within bounds;
  • no finalization below threshold.

But the documentation should preserve the distinction between:

  • contract-attested submitter identity;
  • independently verified guardian membership.

11. Refuse the current destroy path immediately

The current source now documents that the assert is vacuous and that the helper’s reverts are the real protection.

That is honest and useful.

But the path remains synthesized and callable until Sprint 2.

At the same time, the public README still describes:

  • a real four-phase ceremony lifecycle;
  • an externally verified setup → destroy milestone.

The project is testnet-only, so this is not a live-value emergency.

It is still better to make refusal the first containment action:

  • compiler error for helper-bound destroy;
  • playground refusal;
  • CLI refusal;
  • capability state REFUSED_ABI_MISMATCH;
  • legacy milestone explicitly marked as historical and semantically limited.

A comment explaining a non-functional guard should not coexist for days or weeks with a public path that still emits that guard.

12. Make the portable-subset gate exhaustive through the capability registry

Promoting the portable invariant early is correct.

The current gate is still fixture-based.

Once the capability registry exists, it should generate the portable test matrix:

  • every construct classified IMPLEMENTED_PORTABLE must contribute a fixture;
  • adding a portable construct without a fixture must fail CI;
  • every portable fixture must compile byte-identically across all target descriptors;
  • every target-bound construct must provide a negative control showing that target identity does affect its dependency receipt.

This prevents the invariant from silently becoming incomplete as the language grows.

Revised closure order

I would now use this order:

  1. Refuse the known-wrong destroy path everywhere.

  2. Freeze the historical V0.9.0 and V0.9.1 identities and publish the correction/supersession relationship.

  3. Define the typed Helper Interface Specification.

  4. Finalize the corrected CeremonyHelper ABI and the generated compiler ABI together.

  5. Build the candidate helper suite and freeze the expected build receipt.

  6. Run exhaustive interface, ABI, state-machine, and negative-boundary tests.

  7. Deploy the immutable candidate once.

  8. Produce per-helper Chain Deployment Receipts.

  9. Construct the Target Descriptor from the frozen specification, expected build identity, and observed deployment identity.

  10. Run the true compiler-to-helper end-to-end acceptance.

  11. Publish the legacy-artifact and session migration/supersession matrix.

  12. Enable constructor CHAINID and exact EXTCODEHASH gates.

  13. Enable the strict deployable profile and generated capability registry.

  14. Run the Orbit qualification matrix against the frozen descriptor.

  15. Audit the portable profile, then the helper bridge, then the real cryptographic implementations.

The acceptance result I would hold the correction to is not merely:

new helper deployed

It is:

  • no legacy artifact is misidentified as current or safe;
  • no new artifact can compile against an incomplete suite identity;
  • every deployed helper is bound to its expected build;
  • the actual compiler-generated route is exercised end to end;
  • the destruction commitment is consumed atomically;
  • the helper and generated contract enforce compatible local state machines;
  • the verifier can distinguish every historical and current dependency graph.

Your response and the immediate commit are strong.

The remaining work is to make the correction itself the first artifact governed by the identity system it is introducing.

1 Like