Vendor keys

The first policy is the only one that pack will ever sign

Creating a vendor key is the one step in minidauth that costs money and cannot be undone. The first policy you sign on that key is the only policy its authorizer pack will ever sign, and the pack is spent whether or not the signature succeeded. Two paid keys went in the bin before we understood the second half of that sentence.

What the pack is

When a vendor key is finalised, the network mints an authorizer pack for it. The pack is what lets a brand new key do anything at all: there are no policies yet, so there is nothing else that could authorise a signing request. It binds the vendor rotating key point that was active at the moment of finalisation, and it authorises a Policy:1 sign.

It authorises exactly one of them. When a node processes a request carrying that pack it revokes its own entry as it goes, deliberately, so that everything afterwards has to run through the policy the request just deployed. As a design that makes sense, and the trouble is only that the revocation is unconditional in ways which are not obvious from outside.

Three ways to lose a key

1. Scope the first policy too narrowly

The pack signs attestation units as well as policies, and every token your users get is built from attestation units. So the policy that replaces the pack has to cover both AttestationUnit:1 and Policy:1, for example with the any wildcard. Miss one side and the key survives in a shape that is no use:

  • A policy over AttestationUnit:1 alone keeps tokens working and can never deploy anything again.
  • A policy over PolicyEnabledEncryption:1 alone ends policy deployment on that key permanently, and there is no route back.

You cannot fix this later by deploying something better. A deployed policy can be rotated in place, but the replacement has to carry the same key id, the same contract and the same models, so rotation is a lever for changing parameters and expiry, not for narrowing or widening reach. Whatever the first policy can authorise, it can authorise for the life of the key.

2. Retry

This is the one that cost us a key. The revocation is per node, and it happens when that node processes the request, not when the cohort produces a signature. A first attempt that misses the threshold for any transient reason, a few nodes slow or unreachable, a tunnel blip, still burns every node that did respond. The retry then burns more of the pool that is still live, and the attempt after that burns more, until fewer nodes hold a valid entry than the threshold needs and the sign is permanently impossible.

Nothing local tells you this is happening. The policy list stays empty the whole time, because no policy ever got deployed, which looks exactly like the failure you are retrying. On one key the fan-out ended at nine nodes answering and eleven returning This authorizer has not been allowed, against a threshold of fourteen. The key was dead and the console still showed an empty policy list and a plausible error.

Never retry a first policy deployment. Check the whole cohort is reachable before the one attempt you get. A missed threshold on the first try already means minting a new key.

3. Send something only some of the nodes reject

The second key died differently and more instructively. The policy referenced a contract, and we had left uploadContract out of the spec, so the contract source never travelled with the request. Two of the nodes validate the contract and rejected it with Policy referenced a contract which doesn't exist. The other twelve do not validate it, co-signed happily, and revoked their entries on the way past.

Twelve is below the threshold of fourteen, so no policy was deployed, and the pack was spent regardless: the next attempt came back with eight nodes still holding a valid entry and twelve reporting it revoked. A request that is malformed in a way only part of the cohort checks for is still a request the rest of the cohort will burn itself on.

The rotation trap, for completeness

There is a fourth way, and it does not involve deploying anything. The pack binds the vendor rotating key point that was live at finalisation, and a rotation is not re-issued into it. So rotating before the first policy is signed leaves you signing with a key the pack does not recognise, and the node says:

VRK:1: VRK Authorization Flow failed because VRK signature
of this request could not be verified.

The message is accurate and says nothing whatsoever about rotation, which cost us an afternoon of looking in the wrong place. There is a cheap way to detect it in advance: both the pack and the active vendor rotating key carry a 32 byte Ed25519 point, and those two points are identical until a rotation happens, so comparing them before you send anything tells you whether the attempt can work at all. Signing the first policy is a capability you have before your first rotation and not after it.

What to do instead

  1. Rehearse on a key you do not mind losing. A free tier key on the live network is created with {"licensingTier":"free","email":"..."} and finalises immediately with no payment step. It is not limited in the ways that matter here: policy deployment, voucher decryption and the whole tideless flow work on it. The paid tier buys licensing and quota, not capability. Run your exact policy spec through a free tier key first, and only then spend a paid one.
  2. Make the first request byte complete. For a bootstrap covering AttestationUnit:1 and Policy:1 over a contract registered from source, that means "uploadContract": true in the spec so the source ships with the request.
  3. Set the policy version the network speaks. The bindings build version 4 and the released network understands 3, so MC_POLICY_VERSION=3. Without it the deployment fails with Error with signing model: Policy:1, and the actual reason is in a response body the bindings discard.
  4. Check the cohort is up. Ask every node in the cohort whether it is active before you send the one request that matters.
  5. Grant somebody the admin role first. minidauth refuses the first policy deployment until a person holds governance-admin, because there is no way back from deploying with nobody able to govern the result.

Why it is like this

All four failures come from the same property, which is that there is no administrator anywhere who can undo a decision the network has already made, including on our behalf. That property is the entire reason to use any of this: a key that can be rescued by an appeal to a support desk is also a key that can be taken by anyone who can convince a support desk.

What it costs is that the first few minutes of a key's life carry more weight than everything that follows, and the tooling does not yet make that obvious enough to save you. Read Running minidauth end to end before your first call, not after it.

More posts