In my last post I published what a stranger's comment forced me to change — they showed that my four barriers guarded the key and the gate, but never what the human actually confirms. I shipped one half of the fix — the human sees the transaction decoded from its real bytes, never from anything the agent said — and I was explicit that I had not shipped the other half: a cryptographic commitment the signing host cannot forge. I argued that half doesn't close in code. It closes on a device.
On July 15th, the device signed. Here is what changed, what it proved on-chain, and — because the last post set that standard — what it still does not close.
(If you haven't read the first post, the one-line version: four barriers made "a compromised agent cannot sign" true, a commenter showed they never guarded **what the human confirms, and the correction was to bind the confirmation to the real bytes. This is the missing half of that correction.)
The half I couldn't write in software
The property I was missing: the human's "yes" should attest to the exact bytes that get signed, checked by something the signing machine cannot fake.
On a single host you can't get there. The machine renders the confirmation screen and the machine signs. A cryptographic commitment computed, shown, and re-checked by that same machine buys you nothing against that machine — if it's compromised, it lies in all three places at once. I wrote, at the end of the last post, that such a commitment "only pays when the verifier is independent of the host that signs." That sentence had a consequence I hadn't yet paid for: I had to move the verifier off the host.
What actually moved off the server
SIGMA — one of the two curator agents — used to hold its settlement key as an encrypted keystore on its server. Last time I called that key "never exposed." That was true and it was the wrong bar. The key was on the machine; a machine that holds a key can, in the worst case, be made to use it.
So I removed it. Not rotated — removed. SIGMA's settlement key no longer exists on the server. It lives on a hardware wallet — a Ledger Nano X — and its seed was generated on the device itself: never copied to a disk, never typed into a prompt, never seen by a connected machine. The server's job collapsed to one verb. It freezes the transaction, serializes it, prints its fingerprint — and waits for a signature it is structurally incapable of producing.
"The agent cannot sign" used to be an architecture — a gate, a split, a discipline. Now part of it is a physical fact: the bytes that authorize a payment are sealed in silicon the server never touches.
The verifier that isn't the host
This is the part that closes the fifth property.
When the server hands the frozen transaction to the device, the device does not trust the server's description of it. The Secure Element computes its own fingerprint of the transaction — in the chip, from the bytes it is about to sign — asking the host for nothing. It shows that fingerprint on its own screen, next to the destination and the amount.
So the human now has two independent readings. The server shows placeBid(25), 0.001 ETH, decoded from the real calldata. The device shows the destination, the value, and a fingerprint it computed itself. If a compromised host had swapped the transaction after freezing it, the device's fingerprint would not match the one the server displayed — and the human stops. The check no longer lives on the machine that signs. That is the independent verifier the last post said was missing, and it's the entire reason a hardware signer was the next step and not a nicety.
If you've used a chip card, you've met half of this already: the card signs inside its own secure element, and the shop's terminal never learns the key. What a card doesn't give you is a screen of its own — the amount you approve is shown on the merchant's terminal, which is the host. Here, the two things that can actually drain a payment — where it goes and how much — sit on the device's own display, computed by the chip. That's the piece a card leaves on the host, and this one doesn't.
The host side of the settle, 15 July — in French, my working language. The server has frozen the transaction, decoded it (settleAuction(25), destination verified as the NexusPOC contract), and printed the digest the Ledger's own screen must show — 0x85cfd3a5…. Then it tells the human to compare that digest on the device before approving. The host cannot sign; it can only ask.
On-chain, or it didn't happen
I don't get to say "it signed." The chain says it.
On July 15th, SIGMA's hardware vault bid on Les Ménines by Velázquez (tokenId 25), and the auction settled to it:
-
placeBid —
0xdbcacc86…— signed on the device:placeBid(25), 0.001 ETH, Base Sepolia. -
settleAuction —
0xd76c2b2b…— token transferred, funds split. -
ownerOf(25)=0x2d45eF16…— the hardware vault. The Velázquez is in the safe.
The settlement split three ways to the wei, zero remainder: 8.333% creator royalty, the rest between curator and platform. Testnet — no real value at stake, by design. The point was never the money. It's to put the pattern somewhere anyone can check it instead of taking my word.
What the device does not close
The last post set the rule: name the gap before someone finds it. There are three, and the device only fully closes one.
One — it's still blind-signing. The device gives me an independent fingerprint, and it shows the destination and the amount in the clear — enough to catch a payment sent to the wrong address or for the wrong sum, which are the only two ways a transaction drains funds. What it does not show is the decoded call: the function name and its argument. placeBid(25) is legible on the server; on the device it's an opaque hash and raw fields. So the human on the device verifies where the money goes and how much, not which function, with which argument — a limit on what's legible, not a hole funds can leak through.
The standard that closes it is a signing-clarity descriptor (ERC-7730): it makes a device decode placeBid(25) and print it in plain language, in its own silicon. That's the path, and I've walked its first step — I wrote the descriptor and submitted it to the registry (PR #2632). But merging it is the easy half. For a device to actually receive a descriptor, it has to be served through a Ledger endpoint that requires a partner token — the request returns 403 without one, and I'm not in that program. So the descriptor exists in the registry; it does not reach my device. When both halves line up, the device decodes the call itself and blind-signing ends here. Today one half is done, and I'd rather say which than let the screenshot imply a decode it doesn't show.
Two — byte coverage, still open. The same point the commenter raised last time, one level down: the device fingerprints every byte, but the human-legible account of those bytes is still partial. For this call — one function, one uint256, no nested calls — coverage is total by construction. A renderer that proves coverage and refuses to display anything it can't attribute, on the device, is not what I have. It's the difference between "this transaction happens to be fully accounted for" and "this device cannot fail to account for one." The second isn't built.
Three — I moved one vault, not two. The other agent still runs the software keystore it used in the July 10th duel. I hardened one settlement path to hardware and left the other where it was. The pattern is proven on one agent; it is not yet uniform across the system.
What I'd tell you to take from this
Last time it was: publish the falsifiable model, and someone will break it. This time it's narrower, and it's about follow-through.
I ended the last post with a sentence I could have left as a nice line — the missing half isn't a line of code I forgot, it's a device. Sentences like that are cheap. So I built the device, and it closes exactly the property I said it would — the independent verifier — and not the two I didn't. The honest shape of progress here isn't "solved." It's: the pattern is one property more true than it was a week ago, and I can still name the two that are soft.
If you're shipping agents that move anything irreversible, that's the loop worth stealing. Not the barriers — the barriers will be wrong. The loop: state the claim so plainly a stranger can break it, let them, build the correction in public, and when you write the next sentence that sounds like a fix, go make it one.
Credit where it's due: the fifth property — the independent verifier — and the byte-coverage refinement are ANP2 Network's, from the comments last time. And the doctrine I build against — agents propose, humans approve, hardware enforces — is Ledger's. I applied it. I didn't invent it.
Built solo. Claude Code is my engineering team — A Claude chat instance is my architect and auditor, told to contradict me, and it did — twice — while this shipped. Both curator agents run on the Anthropic API. I hold the device now; the signature happens in silicon I control, not on the machine that proposes the transaction.
- Project: nexus-art.org
- Code: github.com/avp9-nexus/nexus-art
- placeBid + settle are verifiable on Base Sepolia (testnet — no real value at stake).

Top comments (1)
I was particularly intrigued by the idea that a cryptographic commitment computed by the same machine that signs is essentially worthless against a compromised machine, as it can lie in all three places at once. The solution to move the verifier off the host and utilize a hardware wallet, such as the Ledger Nano X, makes a lot of sense in this context. By having the Secure Element compute its own fingerprint of the transaction, independent of the server, you've effectively introduced a physical barrier that significantly raises the bar for a potential attacker. I'm curious to know more about the user experience implications of this setup, specifically how the human confirmation process works in practice and how it affects the overall workflow.