ChipRegistryV2: a registration gate, with tests - #1
Open
gHashTag wants to merge 2 commits into
Open
Conversation
…loyment The deployed ChipRegistry accepts any caller with any 32-byte value, checked only against a constant published in the contract itself. Its own documentation says this is deliberate - it exposes the gate-free path for testnet bring-up - but MiningPool's registration check is described externally as device identity, and on that registry it is not one. It establishes that a registration transaction happened. This cannot be retrofitted. MiningPool's ownership was renounced at deployment, so setChipRegistry can never be called and the registry it consults can never be replaced. This targets the next deployment. The gate requires a signature from the chip over a digest binding five things: the identifier being claimed, the address submitting the transaction, this registry, this chain, and a nonce. Each closes a distinct replay - a captured signature cannot be submitted by a third party, replayed onto another deployment, replayed onto a fork or another network, or replayed here. Nonces are scoped per chip so that concurrent registrations cannot invalidate each other. The identifier is the chip's own address, left zero-padded. That makes it self-authenticating: a signature recovers to an address and the identifier being registered must be that address. The alternative - an arbitrary value plus a separate signing key - lets anyone bind any identifier to a key they hold, which reintroduces the problem the gate exists to solve. The phi-anchor check is retained but demoted in the documentation to a format assertion. A constant published in the contract cannot gate anything, since anyone who can read the contract can supply it. What this proves: whoever registered an identifier held the key it derives from, at registration, for this registry, on this chain, once. What it does not prove: that the key lives on a die rather than in a file. No on-chain check establishes that. So the floor rises from anyone may claim to be any chip to only the holder of a chip's key may register that chip, and that is the whole claim. Thirteen tests, all passing, including a fuzz run over attacker and victim keys. One of them is a demonstration against the deployed registry rather than this one, because a gate is only worth having if the thing it replaces is open. This has not been reviewed by anyone who did not write it, and it sits in front of the token supply. It should not be deployed on that basis. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
One enrolment per device, permanently. This was already the behaviour and is now stated as a requirement, because it turns out to be load-bearing outside the contract. A key generator built on a physically unclonable function needs a debiasing step, since the responses are biased. Of the debiasing methods in the literature three of four are explicitly not reusable: enrolling the same device twice leaks more than one enrolment does, because the debiasing step is stochastic and bit errors between enrolments shift which response pairs are retained. The one reusable method requires an inner repetition code, and a repetition code multiplies the code length while leaving its dimension alone - so it cannot carry the information a 128-bit key needs at any response entropy. The reusable option is not expensive here; it does not exist. That leaves one enrolment per device as the only constructible policy, and this contract must never allow a second one. Two paths are already closed: a registered chip is refused by the AlreadyRegistered check, and a slashed chip is refused by the same check, because slashing sets a flag without clearing registeredAt. The second is easy to break by a well-meaning change. Clearing the record on slash, or adding an unregister path, would read like tidying up and would silently make the key generator insecure. The new test refuses re-registration after a slash, with a fresh nonce and a valid signature, and from a different submitter in case the record were ever keyed on the attestor. Injecting the tidy-up - clearing registeredAt on slash - makes it fail, which is the point of having it. 14 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Not for merge without independent review. This sits in front of the token supply and has been written and tested by one party only.
The problem
The deployed
ChipRegistryaccepts any caller with any 32-byte value, checked only againstPHI_ANCHOR— a constant published in the contract, so anyone who can read it can supply it. The contract's own docstring says this is deliberate for testnet bring-up. ButMiningPool's check is described externally as device identity, and on that registry it establishes only that a registration transaction happened.There is a test in this PR demonstrating it against the deployed contract, because a gate is only worth having if the thing it replaces is open.
Why a new contract rather than a patch
MiningPool's ownership was renounced at deployment.setChipRegistrycan never be called, so the registry it consults can never be replaced. This targets the next deployment.The gate
A signature from the chip over a digest binding five things — the identifier claimed, the submitting address, this registry, this chain, and a nonce. Each closes a distinct replay: third-party submission, cross-deployment, cross-chain, and repeat. Nonces are per chip so concurrent registrations cannot invalidate each other.
The identifier is the chip's address, zero-padded into
bytes32, making it self-authenticating. The alternative — arbitrary identifier plus separate signing key — lets anyone bind any identifier to a key they hold, which is the problem the gate exists to solve.PHI_ANCHORis retained but documented as a format assertion rather than a gate.What it proves, and what it does not
Proves: whoever registered an identifier held the key it derives from, at registration, for this registry, on this chain, once.
Does not prove: that the key lives on a die rather than in a file. No on-chain check establishes that; that needs a challenge the hardware answers under a constraint software cannot meet, which is partial at best.
So the floor rises from anyone may claim to be any chip to only the holder of a chip's key may register that chip.
Tests
Covering: the happy path; a signature from the wrong key; an identifier that is not an address; each of the four replay bindings; the retained anchor, family and double-registration checks; attestor-only slashing; and a 256-run fuzz over attacker and victim keys.