{"record":{"id":"77d9d8d4c66ecfaf","repo":"ruvnet/ruflo","slug":"footer-hash-must-be-sha256-size-bytes-got-fo","errorCode":null,"errorMessage":"Footer hash must be ${SHA256_SIZE} bytes, got ${footerHash.length}","messagePattern":"Footer hash must be (.+?) bytes, got (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/appliance/rvfa-signing.ts","lineNumber":327,"sourceCode":"    };\n\n    // Embed signature in header and rebuild\n    header.signature = metadata;\n    const rebuilt = rebuildRvfa(buf, header, sectionData, footer);\n    await writeFile(rvfaPath, rebuilt);\n\n    return metadata;\n  }\n\n  /**\n   * Sign a section footer hash (detached signature).\n   *\n   * @param footerHash  The 32-byte SHA256 footer hash from an RVFA file.\n   * @returns Hex-encoded Ed25519 signature.\n   */\n  async signSections(footerHash: Buffer): Promise<string> {\n    if (footerHash.length !== SHA256_SIZE) {\n      throw new Error(\n        `Footer hash must be ${SHA256_SIZE} bytes, got ${footerHash.length}`,\n      );\n    }\n    const sig = sign(null, footerHash, this.keyObj);\n    return sig.toString('hex');\n  }\n\n  /**\n   * Sign an RVFP patch file (detached signature).\n   *\n   * @param patchData  The raw patch binary data.\n   * @returns Hex-encoded Ed25519 signature.\n   */\n  async signPatch(patchData: Buffer): Promise<string> {\n    const digest = createHash('sha256').update(patchData).digest();\n    const sig = sign(null, digest, this.keyObj);\n    return sig.toString('hex');\n  }","sourceCodeStart":309,"sourceCodeEnd":345,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/cli/src/appliance/rvfa-signing.ts#L309-L345","documentation":"RvfaSigner.signSections(footerHash) requires exactly the 32-byte SHA256 footer hash from an RVFA image — it signs that digest with Ed25519. Passing anything of a different length (a 64-byte hex string, a raw file buffer, a base64 blob, a SHA-512 digest) is rejected before signing because the detached signature contract is specifically over the 32-byte footer hash.","triggerScenarios":"signSections(hexString) instead of the raw digest; signSections(createHash('sha512').digest()) (64 bytes); signSections(entireFile); or passing a hash re-encoded as UTF-8 (a 64-char hex string becomes 64 bytes).","commonSituations":"Hash plumbing confusion: upstream code produced hex while the API wants raw bytes; switching hash algorithms (sha512/blake3) without updating the signing call; copy-paste from a codebase that signed whole files rather than footer hashes.","solutions":["Pass the raw binary digest: createHash('sha256').update(data).digest() with no 'hex' argument — exactly 32 bytes","If you have a hex string, convert first: Buffer.from(hex, 'hex')","Confirm you're extracting the actual footer: buf.subarray(buf.length - 32), not a hash of the header or a recomputed value","Keep the digest computation and signSections adjacent so encodings can't drift apart"],"exampleFix":"// before — hex digest passed as utf8 bytes (64B) &/or wrong hash\nconst hash = createHash('sha256').update(data).digest('hex');\nconst sig = await signer.signSections(Buffer.from(hash));\n\n// after — raw 32-byte digest of the footer region\nconst footer = buf.subarray(buf.length - 32);\nconst sig = await signer.signSections(footer); // already the 32B SHA256","handlingStrategy":"validation","validationCode":"import { createHash } from 'node:crypto';\n\nconst footerHash = buf.subarray(buf.length - 32); // raw 32B SHA256 region\nif (footerHash.length !== 32) throw new Error('expected 32-byte footer');\n// equivalently: createHash('sha256').update(data).digest() (no encoding arg)","typeGuard":"function isSha256Footer(b: Buffer): b is Buffer { return b.length === 32; }","tryCatchPattern":"try { const sig = await signer.signSections(footerHash); }\ncatch (e) {\n  if (/Footer hash must be 32 bytes/.test(String((e as Error).message))) {\n    // you passed hex/base64/whole-file: convert to the raw 32-byte digest and retry\n  }\n  throw e;\n}","preventionTips":["Keep digest() without an encoding argument — raw bytes are the signing interface","If converting from hex, use Buffer.from(hex, 'hex'), never Buffer.from(hex)","Extract the footer as subarray(buf.length - 32) rather than recomputing it"],"tags":["rvfa","signing","ed25519","hash-length"],"backgroundTag":"sha256-digest-length-mismatch","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}