hyperledger/fabric · error

incorrectly computed txid '%s' -- expected '%s'

Error message

incorrectly computed txid '%s' -- expected '%s'

What it means

The TxID embedded in the ChannelHeader must equal protoutil.ComputeTxID(nonce, creator), i.e. the SHA-256 hash of nonce concatenated with creator. This binds the transaction ID to the authentication material and prevents ID spoofing or replay under a different ID. A mismatch means the client computed or copied the TxID incorrectly.

Source

Thrown at core/endorser/msgvalidation.go:158

	// ensure the epoch is 0
	if up.ChannelHeader.Epoch != 0 {
		return errors.Errorf("epoch is non-zero")
	}

	// ensure that there is a nonce
	if len(up.SignatureHeader.Nonce) == 0 {
		return errors.Errorf("nonce is empty")
	}

	// ensure that there is a creator
	if len(up.SignatureHeader.Creator) == 0 {
		return errors.New("creator is empty")
	}

	expectedTxID := protoutil.ComputeTxID(up.SignatureHeader.Nonce, up.SignatureHeader.Creator)
	if up.TxID() != expectedTxID {
		return errors.Errorf("incorrectly computed txid '%s' -- expected '%s'", up.TxID(), expectedTxID)
	}

	if up.SignedProposal.ProposalBytes == nil {
		return errors.Errorf("empty proposal bytes")
	}

	if up.SignedProposal.Signature == nil {
		return errors.Errorf("empty signature bytes")
	}

	// get the identity of the creator
	creator, err := idDeserializer.DeserializeIdentity(up.SignatureHeader.Creator)
	if err != nil {
		logger.Warnw("access denied", "error", err, "identity", protoutil.LogMessageForSerializedIdentity(up.SignatureHeader.Creator))
		return errors.Errorf("access denied: channel [%s] creator org unknown, creator is malformed", up.ChannelID())
	}

	genericAuthError := errors.Errorf("access denied: channel [%s] creator org [%s]", up.ChannelID(), creator.GetMSPIdentifier())

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Recompute TxID with protoutil.ComputeTxID(nonce, creator) (sha256(nonce || creator), hex-encoded) at proposal-build time.
  2. Generate the nonce and compute the TxID in the same code path so they stay in sync; never reuse a TxID across proposals.
  3. Prefer SDK proposal builders which guarantee TxID/nonce consistency.

Example fix

// before
nonce := randomNonce()
txid := previousTxid // reused
// after
nonce := randomNonce()
txid := protoutil.ComputeTxID(nonce, creator)
Defensive patterns

Strategy: validation

Validate before calling

expected := protoutil.ComputeTxID(shdr.Nonce, shdr.Creator)
if hdr.TxId != expected {
    return fmt.Errorf("txid mismatch: got %s want %s", hdr.TxId, expected)
}

Type guard

func txidMatches(h *common.ChannelHeader, sh *common.SignatureHeader) bool {
    return h != nil && sh != nil && h.TxId == protoutil.ComputeTxID(sh.Nonce, sh.Creator)
}

Prevention

When it happens

Trigger: ProcessProposal -> preProcess -> Validate on a SignedProposal where ChannelHeader.TxId differs from ComputeTxID(SignatureHeader.Nonce, SignatureHeader.Creator) — e.g. TxID reused from a previous tx, nonce regenerated after TxID computation, or TxID computed with a different hash/concat order.

Common situations: Custom clients that compute TxID themselves (wrong algorithm or wrong field order), retry logic that refreshes the nonce but not the TxID, clients copying TxIDs from gateway logs, Fabric upgrade changing ComputeTxID expectations (sha256).

Related errors


AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04). Data as JSON: /api/errors/2a17600698da915f. Report an issue: GitHub.