Assessment reports>Mina Token Bridge>Critical findings>Validator public key is not checked
Category: Coding Mistakes

Validator public key is not checked

Critical Severity
High Impact
Medium Likelihood

Description

To unlock funds from the bridge, a number of signatures by validators need to be submitted. On the Mina side, validation of the validator's public keys is performed by the validateValidator function, which is implemented as follows:

public async validateValidator(
  useSig1: Bool,
  validator1: PublicKey,
  useSig2: Bool,
  validator2: PublicKey,
  useSig3: Bool,
  validator3: PublicKey,
) {

  let count = UInt64.from(0);
  const zero = Field.from(0);
  const falseB = Bool(false);
  const trueB = Bool(true);
  const validatorManager = new ValidatorManager(this.validatorManager.getAndRequireEquals());
  const validateIndex = async (validator: PublicKey, useSig: Bool) => {
    const index = await validatorManager.getValidatorIndex(validator);
    const isGreaterThanZero = index.greaterThan(zero);
    let isOk = Provable.if(useSig, Provable.if(isGreaterThanZero, trueB, falseB), trueB);
    isOk.assertTrue("Public key not found in validators");
  };

  const notDupValidator12 = Provable.if(useSig1.and(useSig2), Provable.if(validator1.equals(validator2), falseB, trueB), trueB);
  const notDupValidator13 = Provable.if(useSig1.and(useSig3), Provable.if(validator1.equals(validator3), falseB, trueB),trueB);
  const notDupValidator23 = Provable.if(useSig2.and(useSig3), Provable.if(validator2.equals(validator3), falseB, trueB), trueB);

  const isDuplicate = Provable.if(
    notDupValidator12.and(notDupValidator13).and(notDupValidator23),
    falseB,
    trueB,
  );

  isDuplicate.assertFalse("Duplicate validator keys");

  count = Provable.if(useSig1, count.add(1), count);
  count = Provable.if(useSig2, count.add(1), count);
  count = Provable.if(useSig3, count.add(1), count);
  count.assertGreaterThanOrEqual(this.threshold.getAndRequireEquals(), "Not reached threshold");

}

The second half of this function performs two checks: it checks that the validators whose signatures are used are all different, and it checks that the total number of used signatures is high enough.

The validateValidator function also defines a function validateIndex, which uses the ValidatorManager contract to check that the public keys are indeed the public keys of approved validators. However, validateIndex is never called on the three public keys. This check is hence not performed by validateValidator.

Impact

Any key may be used to sign unlocking messages. The additional security of using a quorum of validators for unlocking over fully trusting the minter is thus lost, as the minter can drain the contract by creating three different keys and signing the unlocking message with those three keys.

Recommendations

We recommend calling validateIndex on the three pairs of the public key and Boolean.

Remediation

This issue has been acknowledged by Sotatek, and a fix was implemented in commit.

Zellic © 2025Back to top ↑