hyperledger/fabric · error

invalid txid. got [%s], expected [%s]

Error message

invalid txid. got [%s], expected [%s]

What it means

CheckTxID verifies that a transaction ID equals ComputeTxID(nonce, creator), i.e. the SHA256 hash of the nonce concatenated with the creator's serialized identity. If the supplied txid differs from the computed hash, the proposal/transaction is rejected because its binding to nonce and creator cannot be trusted.

Source

Thrown at protoutil/proputils.go:401

// ComputeTxID computes TxID as the Hash computed
// over the concatenation of nonce and creator.
func ComputeTxID(nonce, creator []byte) string {
	// TODO: Get the Hash function to be used from
	// channel configuration
	hasher := sha256.New()
	hasher.Write(nonce)
	hasher.Write(creator)
	return hex.EncodeToString(hasher.Sum(nil))
}

// CheckTxID checks that txid is equal to the Hash computed
// over the concatenation of nonce and creator.
func CheckTxID(txid string, nonce, creator []byte) error {
	computedTxID := ComputeTxID(nonce, creator)

	if txid != computedTxID {
		return errors.Errorf("invalid txid. got [%s], expected [%s]", txid, computedTxID)
	}

	return nil
}

// InvokedChaincodeName takes the proposal bytes of a SignedProposal, and unpacks it all the way down,
// until either an error is encountered, or the chaincode name is found. This is useful primarily
// for chaincodes which wish to know the chaincode name originally invoked, in order to deny cc2cc
// invocations (or, perhaps to deny direct invocations and require cc2cc).
func InvokedChaincodeName(proposalBytes []byte) (string, error) {
	proposal := &peer.Proposal{}
	err := proto.Unmarshal(proposalBytes, proposal)
	if err != nil {
		return "", errors.WithMessage(err, "could not unmarshal proposal")
	}

	proposalPayload := &peer.ChaincodeProposalPayload{}
	err = proto.Unmarshal(proposal.Payload, proposalPayload)

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Regenerate the txid with protoutil.ComputeTxID(nonce, creator) using exactly the same nonce and creator bytes placed in the envelope's SignatureHeader.
  2. Ensure the creator bytes in the SignatureHeader are identical to the bytes hashed into the txid (same msp/identity serialization).
  3. Do not reuse a txid across proposals: compute a fresh nonce and txid per transaction.
  4. If using an SDK, let it compute the txid rather than supplying one manually.

Example fix

// before
txid := uuid.New().String()

// after
nonce, err := crypto.GetRandomNonce()
if err != nil {
    return err
}
creator := signerCert.Raw
txid := protoutil.ComputeTxID(nonce, creator)
Defensive patterns

Strategy: validation

Validate before calling

if txid != protoutil.ComputeTxID(nonce, creator) {
    return fmt.Errorf("txid %s does not match nonce/creator hash", txid)
}
// proceed only when equal

Try / catch

if err := protoutil.CheckTxID(txid, nonce, creator); err != nil {
    return fmt.Errorf("rejecting transaction: %w (recompute txid from nonce+creator)", err)
}

Prevention

When it happens

Trigger: ValidateTransaction or TestProposalTxID receives a txid that does not equal the hash of the given nonce+creator: the client generated the txid with different nonce/creator bytes, used a random string, hashed with a different algorithm/encoding, or reused a txid from another proposal.

Common situations: SDK-generated txids computed over a different identity serialization (e.g. after cert renewal); manually crafted envelopes in tests; chaincode clients that cache txids across retries with regenerated nonces; switching fabric SDK versions that changed txid computation.

Related errors


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