Hmbown/CodeWhale · error · Error

public key must decode to 32 bytes

Error message

public key must decode to 32 bytes

What it means

publicKeyObjectFromRaw builds an Ed25519 public key from a raw base64-encoded 32-byte key by wrapping it in an SPKI DER prefix. After strictBase64 decoding it re-checks the length is exactly 32 bytes; any other length throws this error because only 32-byte Ed25519 raw keys can be wrapped.

Solutions

  1. Regenerate or re-export the public key and re-encode exactly 32 raw bytes as standard base64: `openssl pkey -pubin -in pub.pem -outform DER | tail -c 32 | base64`
  2. Verify the decoded length: `echo <b64> | base64 -d | wc -c` must print 32
  3. Ensure you are not pasting a hex-encoded key or a full SPKI PEM where raw base64 is expected

Example fix

// before
publicKeyObjectFromRaw(truncatedKeyB64); // decodes to 16 bytes
// after
const raw = Buffer.from(spkiDer.subarray(-32));
publicKeyObjectFromRaw(raw.toString('base64'));
Defensive patterns

Strategy: validation

Validate before calling

const raw = Buffer.from(keyB64, 'base64');
if (raw.length !== 32) throw new Error(`public key must be 32 raw bytes, got ${raw.length}`);

Type guard

const isRaw32B64 = (v) => typeof v === 'string' && Buffer.from(v, 'base64').length === 32;

Try / catch

try { publicKeyObjectFromRaw(keyB64); } catch (e) { if (e.message.includes('32 bytes')) throw new Error('re-export the Ed25519 public key as raw 32-byte base64'); throw e; }

Prevention

When it happens

Trigger: Calling publicKeyObjectFromRaw with a base64 string that decodes to fewer or more than 32 bytes (e.g. a truncated key, an Ed448 key, or a PEM body pasted in).

Common situations: Rotating signing keys and publishing a malformed public key file, copying the key from a hex encoding instead of base64, or accidentally publishing the private key's longer material.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/72861bad15a96348. Report an issue: GitHub.

Appendix: source

Thrown at web/scripts/facts-publish.mjs:78

    if (v === undefined) continue;
    parts.push(`${JSON.stringify(key)}:${canonicalize(v)}`);
  }
  return `{${parts.join(",")}}`;
}

export function signingMessage(keyId, payloadBytes) {
  return Buffer.concat([Buffer.from(DOMAIN, "utf8"), Buffer.from(keyId, "utf8"), Buffer.from([0]), payloadBytes]);
}

export function rawPublicKeyFromKeyObject(keyObject) {
  const spki = keyObject.export({ type: "spki", format: "der" });
  // Ed25519 SPKI DER is a fixed 12-byte prefix followed by the 32-byte key.
  return spki.subarray(spki.length - 32);
}

export function publicKeyObjectFromRaw(rawB64) {
  const raw = strictBase64(rawB64, 32);
  if (raw.length !== 32) throw new Error("public key must decode to 32 bytes");
  const prefix = Buffer.from("302a300506032b6570032100", "hex");
  return createPublicKey({ key: Buffer.concat([prefix, raw]), type: "spki", format: "der" });
}

export function signPayload(privateKey, keyId, payloadBytes) {
  return sign(null, signingMessage(keyId, payloadBytes), privateKey);
}

/** Canonical base64 is checked before decoding to bound allocation. */
export function strictBase64(value, maxBytes) {
  if (typeof value !== "string" || !value.length || value.length > 4 * Math.ceil(maxBytes / 3) ||
      (value.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(value))) throw new Error("invalid base64");
  const bytes = Buffer.from(value, "base64");
  if (bytes.length > maxBytes || bytes.toString("base64") !== value) throw new Error("invalid base64");
  return bytes;
}

export function utcTime(value) {

View on GitHub (pinned to 433685b202)