From cb3efc9a235bd1f520f35489948074c58a3092b7 Mon Sep 17 00:00:00 2001 From: Jsp <98713940+JspIIV@users.noreply.github.com> Date: Sun, 13 Sep 2026 13:29:47 +0300 Subject: [PATCH] fix(genesis): require a 32-byte validator public key in the schema The chain only accepts 32-byte keys -- ValidatorRegistry.registerValidator reverts with InvalidPublicKeyFormat on anything else -- but the genesis schema typed publicKey as schemaHex, which is any even-length hex. The 32-byte rule lived only as a throw inside buildValidatorManagerGenesisAllocs, so a 31-byte key passed schemaGenesisConfig.parse and the uniqueness check, and failed later with an Error that names no path. Use schemaBytes32, which already exists and is exactly 32 bytes, and drop the runtime throw it makes unreachable: the builder re-parses the schema on entry, so nothing gets past it. The rejection now carries a path, validators..publicKey, like the other genesis invariants. Tests: a 31-byte and a 33-byte key are rejected at the schema with the path set, a 32-byte key is accepted. Both rejections fail on main. --- scripts/genesis/ValidatorManager.ts | 16 +++++----- ...lidator-manager-genesis-validation.test.ts | 31 +++++++++++++++++++ 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/scripts/genesis/ValidatorManager.ts b/scripts/genesis/ValidatorManager.ts index 713546cd..ff98357a 100644 --- a/scripts/genesis/ValidatorManager.ts +++ b/scripts/genesis/ValidatorManager.ts @@ -22,7 +22,7 @@ import { enforceOperatorsNotProxyAdmin, schemaAddress, schemaBigInt, - schemaHex, + schemaBytes32, slotForAddressMap, slotForBytes32Map, slotIndex, @@ -54,7 +54,11 @@ export const schemaValidatorManager = z */ validators: z.array( z.object({ - publicKey: schemaHex, + /** + * Ed25519 public key, exactly 32 bytes. ValidatorRegistry.registerValidator + * rejects any other length with InvalidPublicKeyFormat, so the schema does too. + */ + publicKey: schemaBytes32, votingPower: schemaBigInt.max(UINT64_MAX), /** * Controllers authorized to manage this validator. Each controller is @@ -217,12 +221,8 @@ export const buildValidatorManagerGenesisAllocs = async (ctx: BuilderContext, co const idSetArraySlot = fromHex(keccak256(slotIndex(REGISTRY_STORAGE_LOCATION + 1n)), 'bigint') + BigInt(index) const idSetMapSlotHex = slotForBytes32Map(REGISTRY_STORAGE_LOCATION + 2n, registrationId) const validatorSlot = fromHex(slotForBytes32Map(REGISTRY_STORAGE_LOCATION + 0n, registrationId), 'bigint') - const publicKeyLength = BigInt(fromHex(validator.publicKey, 'bytes').length) - - if (publicKeyLength !== 32n) { - // Only support 32 bytes public key now. - throw new Error(`Public key must be 32 bytes`) - } + // schemaBytes32 already guarantees the key is exactly 32 bytes. + const publicKeyLength = 32n return [ // _validatorsByRegistrationId, mapping(uint256 => Validator = (enum, bytes, uint64)) diff --git a/tests/unit/validator-manager-genesis-validation.test.ts b/tests/unit/validator-manager-genesis-validation.test.ts index 4ab6d5fd..8cdfb80b 100644 --- a/tests/unit/validator-manager-genesis-validation.test.ts +++ b/tests/unit/validator-manager-genesis-validation.test.ts @@ -71,3 +71,34 @@ describe('ValidatorManager genesis validator-set validation', () => { expect(result.success).to.be.true }) }) + +describe('ValidatorManager genesis public-key validation', () => { + // ValidatorRegistry.registerValidator requires exactly 32 bytes; the schema + // has to say so too, or a bad key only fails later inside the alloc builder. + const PUBLIC_KEY_31 = `0x${'11'.repeat(31)}` + const PUBLIC_KEY_33 = `0x${'11'.repeat(33)}` + + it('rejects a 31-byte public key at the schema', () => { + const result = schemaValidatorManager.safeParse(configWithValidators([validator(PUBLIC_KEY_31, CONTROLLER_A, 20n)])) + + expect(result.success).to.be.false + if (!result.success) { + expect(result.error.issues.some((issue) => issue.path.join('.') === 'validators.0.publicKey')).to.be.true + } + }) + + it('rejects a 33-byte public key at the schema', () => { + const result = schemaValidatorManager.safeParse(configWithValidators([validator(PUBLIC_KEY_33, CONTROLLER_A, 20n)])) + + expect(result.success).to.be.false + if (!result.success) { + expect(result.error.issues.some((issue) => issue.path.join('.') === 'validators.0.publicKey')).to.be.true + } + }) + + it('accepts a 32-byte public key', () => { + const result = schemaValidatorManager.safeParse(configWithValidators([validator(PUBLIC_KEY_A, CONTROLLER_A, 20n)])) + + expect(result.success).to.be.true + }) +})