blog·2026-03-16·6 min read

Why Aleo needs amendments for verifying-key upgrades

Aleo has a funny upgrade problem. Program identity wants to stay still. Proving infrastructure does not.

That mismatch was easy to shrug off when the main job was getting Leo code to compile, deploy, and execute at all. It gets harder to ignore once wallets cache proof material, indexers fingerprint deployments, and bots build long-running flows around known program IDs.

My view is simple: amendments are the missing layer in Aleo's deployment model. They let the network refresh verifying keys without pretending the source program turned into a different app, and without making teams reach for a full program upgrade when the only thing that changed was proof machinery.

That sounds small. I do not think it is.

Core idea

An Aleo deployment is not just source text. It also ships the per-function cryptographic material the network needs to verify executions. That matters because a verifier does not care about your intent; it cares that the proof matches the exact circuit and key material attached to that function.

Once you accept that, the awkward part becomes obvious. A proving-system refresh can change deploy-time artifacts even when the record layout, function names, and finalize logic are all the same. From the app's point of view, nothing changed. From the network's point of view, something definitely did.

Why normal upgrades fit badly

Aleo already has an upgrade story for code changes. That machinery is about editions, ownership checks, constructors, and checksum validation. When behavior changes, good - that is the right tool.

It is a bad fit for proof maintenance. If the token program is still the same token program, calling the update a new release muddies the audit trail. Wallets, indexers, relayers, and partner apps get told the app changed when the real change was lower in the stack.

That is the hole amendments fill. They let program identity stay fixed while the attached verifying-key set moves forward.

Three layers

One way to reason about an Aleo deployment is to split it into three layers:

  • Interface: program ID, function names, record shapes, mapping names, and expected external calls.
  • Semantics: the logic the developer intended to write.
  • Proof material: circuit compilation output and the verifying keys used to check executions.

Classic immutability glues all three together. Classic upgrades replace all three together more often than you want. Amendments sit between those two extremes.

This matters because verifying keys are not decoration. They are part of the verifier's contract with a function. A proof built against one circuit shape will not verify against a different key, even if a human reading the source would say the program still means the same thing.

And the proving stack does move. Compilers change constraint generation. Backends change. Security fixes happen. Parameter pipelines change. Sometimes those shifts do not alter developer intent at all, but they still change the artifacts the network has to accept.

Without amendments, teams get pushed into bad choices:

  • Redeploy under a new program ID and drag every integration through migration.
  • Publish a full upgradable-program release even though the app behavior is unchanged.
  • Keep old verification material around longer than they should because swapping it is operationally painful.

None of those choices is great. The first breaks continuity. The second muddies history. The third is how maintenance debt becomes production pain.

What tooling should do

Wallets feel this first because they already care about keys more than most chain wallets do. Good wallet UX depends on caching proof-related material so users do not pay the same setup cost again and again. Amendments make that cache model less naive.

A weak cache key looks like this:

ts
const cacheKey = `${programId}:${functionName}`

A safer one looks like this:

ts
const cacheKey = `${programId}:${functionName}:${amendmentCount}`

That extra field changes the failure mode. Old cache entries stop being invisible footguns and start expiring in a way the client can reason about.

Indexers need the same discipline. Treating deployment metadata as a one-time fact attached to a program ID is no longer enough. Verification material has history now, and that history belongs next to the program identity, not hidden behind it.

Something like this is closer to how indexers should think:

ts
type ProgramRef = {
  programId: string
  amendmentCount: number
}

type FunctionKeyRef = {
  programId: string
  functionName: string
  amendmentCount: number
  verifyingKeyDigest: string
}

That split buys cleaner audit trails. It also makes replay and debugging saner, because you can ask two separate questions: which program was this, and which verifying-key generation was active when this ran?

Bots and agent workflows need the same rule. If a system persists execution plans or proof artifacts, amendment state belongs in those plans. Skip it and you get the worst kind of failure: the app looks unchanged, but proof validation suddenly starts failing for reasons that are not obvious from the surface.

A cautious flow is boring, which is exactly what you want:

  1. Read program metadata.
  2. Record the current amendment count.
  3. Resolve the verifying key for each function you plan to call.
  4. Cache by program ID, function name, and amendment count.
  5. Refetch when the amendment count changes.

That is not flashy. It is just clean plumbing. Good infrastructure usually looks like that.

Why this trade makes sense

I do not think Aleo was ever going to get the EVM version of this story. On Ethereum, bytecode is close to the whole object and verification is cheap and generic. Aleo is different. Execution is proved off-chain and then checked on-chain against function-specific material. That changes the upgrade math.

It also explains why an amendment count on the node side matters so much. Tooling does not want to diff full deployment blobs every time it touches a program. It wants a cheap freshness signal. If the count changed, anything tied to verification material may be stale. If it did not, cached state is probably still safe to reuse.

The bigger win is honesty. A proving-key refresh is not the same event as a logic change. A network that forces both through the same door is lying a little about what happened. Amendments fix that by giving proof maintenance its own lane.

I also think this lines up with Aleo's broader style. The system keeps choosing explicit coordination points where other chains rely on looser runtime behavior. That can feel annoying when you want everything to look like the EVM by Friday. Still, for privacy-focused systems, I would rather have explicit version surfaces than hidden assumptions.

Programs with long shelf lives need stable references. Wallets need predictable cache invalidation. Indexers need history that reflects what actually changed. Agents need plans that do not explode because the proof layer moved under their feet. Amendments do not solve every upgrade problem, but they solve a real one, and they solve it at the right layer.

Sources