{"record":{"id":"1f0f1d14ce2efab7","repo":"ruvnet/ruflo","slug":"both-ed25519-private-and-public-pem-keys-are-requi","errorCode":null,"errorMessage":"both Ed25519 private and public PEM keys are required","messagePattern":"both Ed25519 private and public PEM keys are required","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/services/flywheel-receipt.ts","lineNumber":398,"sourceCode":"    evidence: input.evidence ?? {\n      corpusRoles: {\n        selectionTaskIds: [],\n        promotionHoldoutTaskIds: [],\n        guardTaskIds: [],\n      },\n      verification: {},\n      canary: {},\n    },\n    termVerification: input.termVerification ?? [],\n    decision,\n    issuedAt: new Date(now).toISOString(),\n    expiresAt: new Date(now + (input.ttlMs ?? 24 * 60 * 60 * 1000)).toISOString(),\n  } satisfies Omit<FlywheelReceiptPayload, 'receiptId'>;\n  const receiptId = sha256Ref(canonicalizeJcs(receiptIdentityPayload(base)));\n  const payload: FlywheelReceiptPayload = { ...base, receiptId };\n  const receipt: FlywheelEvaluationReceipt = { payload };\n  if (input.privateKeyPem || input.publicKeyPem) {\n    if (!input.privateKeyPem || !input.publicKeyPem) throw new Error('both Ed25519 private and public PEM keys are required');\n    receipt.signature = {\n      algorithm: 'ed25519',\n      domain: RECEIPT_DOMAIN,\n      publicKeyPem: input.publicKeyPem,\n      signatureBase64: edSign(null, signedBytes(payload), input.privateKeyPem).toString('base64'),\n    };\n  }\n  return receipt;\n}\n\nexport interface ReceiptVerification {\n  valid: boolean;\n  signed: boolean;\n  errors: string[];\n}\n\nexport function verifyFlywheelReceipt(receipt: FlywheelEvaluationReceipt, trustedPublicKeys?: Set<string>): ReceiptVerification {\n  const errors: string[] = [];","sourceCodeStart":380,"sourceCodeEnd":416,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/cli/src/services/flywheel-receipt.ts#L380-L416","documentation":"Thrown by createFlywheelReceipt() when exactly one of privateKeyPem / publicKeyPem is supplied. Receipt signing uses Ed25519: the private key signs the canonicalized payload and the public key is embedded in the receipt for verification, so both halves are required. Supplying neither produces a valid unsigned receipt; supplying only one is always a caller bug.","triggerScenarios":"Calling createFlywheelReceipt({ privateKeyPem }) without publicKeyPem, or vice versa. Common when keys are loaded from separate env vars or files and one is missing.","commonSituations":"PEM keys split across RUFLO_FLYWHEEL_PRIVATE_KEY / RUFLO_FLYWHEEL_PUBLIC_KEY env vars where only one is set; key rotation where the new pair wasn't fully provisioned; test setup that generated one key but not the other.","solutions":["Pass BOTH privateKeyPem and publicKeyPem, or pass NEITHER for an unsigned receipt.","Load both keys from the same keypair file/source so they cannot diverge.","Add an early guard: if (privateKeyPem ?? publicKeyPem) assert both are non-empty strings."],"exampleFix":"// before\ncreateFlywheelReceipt({ /* ... */, privateKeyPem });\n// missing publicKeyPem\n// after\ncreateFlywheelReceipt({ /* ... */, privateKeyPem, publicKeyPem });","handlingStrategy":"type-guard","validationCode":"if ((input.privateKeyPem ?? undefined) !== (input.publicKeyPem ?? undefined)) {\n  if (!input.privateKeyPem || !input.publicKeyPem) {\n    throw new Error('provide both privateKeyPem and publicKeyPem, or neither for an unsigned receipt');\n  }\n}","typeGuard":"function hasBothOrNoKeys(input: { privateKeyPem?: string; publicKeyPem?: string }): boolean {\n  return (!!input.privateKeyPem && !!input.publicKeyPem) || (!input.privateKeyPem && !input.publicKeyPem);\n}","tryCatchPattern":"try {\n  createFlywheelReceipt(input);\n} catch (e) {\n  if (e instanceof Error && /both Ed25519/.test(e.message)) {\n    // either load the missing key or drop signing entirely\n    input.privateKeyPem = undefined; input.publicKeyPem = undefined;\n  } else throw e;\n}","preventionTips":["Load Ed25519 keypairs from a single source so both halves are always present together.","Treat signing as optional-by-default; only enable when both keys are confirmed.","Add a constructor/startup check that verifies keypair completeness."],"tags":["validation","cryptography","signing","ed25519","receipt"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}