Bug #3846
openDelegated ymid signature covers only the first 20 bytes of the message
0%
Description
Summary¶
The delegated identity signature produced by signDelegatedYmid() in src/authenticator.ts committed to only the first 20 bytes of its message.
The signed message is built at src/authenticator.ts:120:
const message = rawPublicKey.toString("hex") + identityExt.useCount.toString();
That is the raw P-256 public key (x||y, 64 bytes) as 128 hex characters with the use count appended as a decimal string.
Root cause¶
ymid_sign/ymid_verify passed the raw message to element_from_hash, which maps a buffer into a field element via ymc_mpz_from_hash (arith/field.c). That routine takes the first ceil(bits(r)/8) bytes verbatim — it converts an already-computed digest into an element and cannot compress a longer input. At the 512-bit pairing parameters in use, the cutoff is 20 bytes.
Impact¶
Only the first 20 hex characters were covered, i.e. the first 10 bytes of the X coordinate. Outside the signature:
- the remaining 22 bytes of X and all 32 bytes of Y;
- the entire
useCount, which could therefore be altered without invalidating the signature.
Measured directly: a signature over a 64-byte message still verified after changing any byte from offset 20 onward, and after appending arbitrary trailing data. Identity binding was unaffected — ids map into G1 and are covered up to 64 bytes.
Fix¶
Fixed in the ymid library: ymid_sign/ymid_verify now take a 32-byte SHA-256 digest (YMID_DIGEST_LEN) and reject any other length with YMID_ERR_INPUT, and each binding hashes before calling in. No plugin code changes — the vendored vendor/ymid-wasm build is refreshed.
Deployment note¶
Wire-breaking. Signatures produced before the change do not verify after it and vice versa, so signer and verifier must move together. Issued keys and public params are unaffected; no re-issuance is required.