Skip to content

ChipRegistryV2: a registration gate, with tests - #1

Open
gHashTag wants to merge 2 commits into
mainfrom
chip-registry-gate
Open

ChipRegistryV2: a registration gate, with tests#1
gHashTag wants to merge 2 commits into
mainfrom
chip-registry-gate

Conversation

@gHashTag

Copy link
Copy Markdown
Owner

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 ChipRegistry accepts any caller with any 32-byte value, checked only against PHI_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. But MiningPool'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. setChipRegistry can 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_ANCHOR is 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

Ran 13 tests for test/ChipRegistryV2.t.sol:ChipRegistryV2Test
13 passed; 0 failed

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.

…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant