New users cannot onboard after upgrade due to registerKeyLog regression
After upgrading to KeyLogRegistryUpgrade, registerKeyLog always reverts because validateTransaction unconditionally hashes the empty confirming key before the isPair guard. No new user can register an inception key on the bridge.
Description
Note: The problem described in [F-2026-0002] is fixed in
KeyLogRegistryUpgrade.solbut it introduces a different type of regression described here.KeyLogRegistryUpgrade.solandKeyLogRegistry.solshould not be used in production before the recommended fix described below.
The [F-2026-0002] fix in KeyLogRegistryUpgrade changed line 184 of
validateTransaction from:
// Original (buggy):address confirmingPublicKeyHash =getAddressFromPublicKey(unconfirmed.publicKey);// Upgrade (fixed for pairs, but breaks single registration):address confirmingPublicKeyHash =getAddressFromPublicKey(confirming.publicKey);
This fix is correct for registerKeyLogPair (where confirming.publicKey
is a real 64-byte key). However, registerKeyLog passes an empty
confirming key:
function registerKeyLog(KeyData memory key) external onlyAuthorized {address publicKeyHash = getAddressFromPublicKey(key.publicKey);(KeyEventFlag flag, ) = validateTransaction(key,KeyData({publicKey: "", // <-- empty bytesprerotatedKeyHash: address(0),twicePrerotatedKeyHash: address(0),outputAddress: address(0),prevPublicKeyHash: address(0)}),false // isPair = false);}
validateTransaction calls
getAddressFromPublicKey(confirming.publicKey) at line 184
unconditionally, before the if (!isPair) return guard at line 211.
Since confirming.publicKey is "" (0 bytes), getAddressFromPublicKey
reverts with "Public key must be 64 bytes".
This means registerKeyLog always reverts after upgrading to
KeyLogRegistryUpgrade.
Vulnerable Scenario
- Protocol deploys
KeyLogRegistryUpgrade. - New user calls
Bridge.registerKeyPairWithTransfer()with no confirming key (ctx.confirming.outputAddress == address(0)). - Bridge evaluates
isPair = false. - Bridge calls
keyLogRegistry.registerKeyLog(unconfirmedKeyData). registerKeyLogconstructs an empty confirmingKeyDatawithpublicKey: ""and callsvalidateTransaction(key, emptyConfirming, false).validateTransactionexecutesgetAddressFromPublicKey(confirming.publicKey), this isgetAddressFromPublicKey("").getAddressFromPublicKeychecksrequire(publicKey.length == 64), fails because length is 0.- Transaction reverts with
"Public key must be 64 bytes", inception never registered. - Result: no new user can register their first key on the bridge.
Impact
After deploying KeyLogRegistryUpgrade:
- All new user onboarding breaks, no inception events can be registered.
- Existing users with confirmed key pairs can still rotate (via
registerKeyLogPair). - The protocol cannot onboard any new users until a second upgrade fixes this regression.
Recommendation
Move the getAddressFromPublicKey(confirming.publicKey) call inside the
isPair block:
function validateTransaction(KeyData memory unconfirmed,KeyData memory confirming,bool isPair) public view returns (KeyEventFlag, KeyEventFlag) {(KeyLogEntry memory lastEntry, bool hasEntries) =getLatestChainEntry(unconfirmed.publicKey);address unconfirmedPublicKeyHash =getAddressFromPublicKey(unconfirmed.publicKey);- address confirmingPublicKeyHash =- getAddressFromPublicKey(confirming.publicKey);+ address confirmingPublicKeyHash; // only computed for pairs// ... existing validation for unconfirmed ...if (!isPair) {require(unconfirmed.prerotatedKeyHash == unconfirmed.outputAddress, "...");require(confirming.outputAddress == address(0), "...");return (firstFlag, KeyEventFlag.UNCONFIRMED);}+ confirmingPublicKeyHash =+ getAddressFromPublicKey(confirming.publicKey);// ... rest of pair validation ...
Resolution
YadaCoin, Confirmed. Moved confirmingPublicKeyHash computation
inside the isPair block so that non-pair registerKeyLog calls no
longer revert on the missing confirming public key.
Zealynx, Fixed. Verified the fix resolves the regression for
non-pair registrations. Also identified and confirmed removal of a
redundant require statement that was unreachable after the
restructuring.

