{"record":{"id":"e32c09e1844a28fa","repo":"hyperledger/fabric","slug":"proposal-hash-does-not-match","errorCode":null,"errorMessage":"proposal hash does not match","messagePattern":"proposal hash does not match","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"core/common/validation/msgvalidation.go","lineNumber":240,"sourceCode":"\t\t// extract the proposal response payload\n\t\tprp, err := protoutil.UnmarshalProposalResponsePayload(ccActionPayload.Action.ProposalResponsePayload)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// build the original header by stitching together\n\t\t// the common ChannelHeader and the per-action SignatureHeader\n\t\thdrOrig := &common.Header{ChannelHeader: hdr.ChannelHeader, SignatureHeader: act.Header}\n\n\t\t// compute proposalHash\n\t\tpHash, err := protoutil.GetProposalHash2(hdrOrig, ccActionPayload.ChaincodeProposalPayload)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// ensure that the proposal hash matches\n\t\tif !bytes.Equal(pHash, prp.ProposalHash) {\n\t\t\treturn errors.New(\"proposal hash does not match\")\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// ValidateTransaction checks that the transaction envelope is properly formed\nfunc ValidateTransaction(e *common.Envelope, cryptoProvider bccsp.BCCSP) (*common.Payload, pb.TxValidationCode) {\n\tputilsLogger.Debugf(\"ValidateTransactionEnvelope starts for envelope %p\", e)\n\n\t// check for nil argument\n\tif e == nil {\n\t\tputilsLogger.Errorf(\"Error: nil envelope\")\n\t\treturn nil, pb.TxValidationCode_NIL_ENVELOPE\n\t}\n\n\t// get the payload from the envelope\n\tpayload, err := protoutil.UnmarshalPayload(e.Payload)","sourceCodeStart":222,"sourceCodeEnd":258,"githubUrl":"https://github.com/hyperledger/fabric/blob/2736b63f8fd5932511d56fe68b7039d15977f7f6/core/common/validation/msgvalidation.go#L222-L258","documentation":"For each action, the validator recomputes the proposal hash (pHash) from the chaincode proposal payload and compares it with the ProposalHash carried in the ProposalResponsePayload. A mismatch means the endorsement does not correspond to the proposal bytes in the transaction, so the action is invalid.","triggerScenarios":"ValidateTransaction where prp.ProposalHash differs from the hash recomputed from ccInspection/chaincode proposal payload bytes — e.g. the proposal was modified after endorsement or a response from a different proposal was used.","commonSituations":"Client mixes proposal responses from different invocations or channels; bytes are altered by a proxy/SDK between endorsement and tx creation; hash computed over a differently-encoded payload (field ordering/extra fields); SDK version changes changing serialization.","solutions":["Use the original proposal payload bytes (not re-marshalled ones) when building the transaction so the hash matches","Ensure the signed proposal submitted for endorsement is the same object used to build the final transaction","Check for any middleware/SDK that re-encodes the proposal; disable re-marshalling","If mixing endorsements, confirm all responses came from the same signed proposal"],"exampleFix":"// before\nrespBytes, _ := proto.Marshal(&proposal) // re-marshalled, may differ\naction, _ := utils.CreateTxEndorsement(respBytes, ...)\n// after\naction, _ := utils.CreateTxEndorsement(originalProposalBytes, ...) // bytes as signed","handlingStrategy":"validation","validationCode":"prp, err := protoutil.UnmarshalProposalResponsePayload(act.Payload)\nif err != nil { return err }\nif !bytes.Equal(prp.ProposalHash, computedProposalHash) { return errors.New(\"proposal hash mismatch before submit\") }","typeGuard":"func proposalHashMatches(act *common.TransactionAction, proposal []byte) bool {\n\tprp, err := protoutil.UnmarshalProposalResponsePayload(act.Payload)\n\tif err != nil { return false }\n\th, err := utils.GetProposalHash1(nil, proposal, nil) // per your SDK version\n\treturn err == nil && bytes.Equal(h, prp.ProposalHash)\n}","tryCatchPattern":"if err := ValidateTransaction(env, policy); err != nil {\n\tif strings.Contains(err.Error(), \"proposal hash does not match\") {\n\t\t// re-endorse: the endorsement doesn't match the proposal bytes\n\t}\n\treturn err\n}","preventionTips":["Keep and reuse the exact proposal bytes used for signing — never re-marshal before tx creation","Use the SDK's createTransaction(proposalResponses) with responses from one proposal","Avoid proxies/middleware that transform proposal payloads"],"tags":["fabric","transaction-validation","hash-mismatch","endorsement"],"backgroundTag":"hash-mismatch","analyzedSha":"2736b63f8fd5932511d56fe68b7039d15977f7f6","analyzedAt":"2026-09-04T08:52:36.465Z","contentChangedAt":"2026-09-04T08:52:36.465Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}