hyperledger/fabric · error

empty signature bytes

Error message

empty signature bytes

What it means

The SignedProposal's Signature field is nil, so the endorser has nothing to cryptographically verify against the creator identity. Fabric requires every proposal to be signed by the submitting client's private key over the ProposalBytes.

Source

Thrown at core/endorser/msgvalidation.go:166

		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())
	// ensure that creator is a valid certificate
	err = creator.Validate()
	if err != nil {
		logger.Warnw("access denied: identity is not valid", "error", err, "identity", protoutil.LogMessageForSerializedIdentity(up.SignatureHeader.Creator))
		return genericAuthError
	}

	logger = logger.With("mspID", creator.GetMSPIdentifier())

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Sign the ProposalBytes with the creator's private key (ECDSA with SHA-256 in Fabric) and store the result in SignedProposal.Signature.
  2. Check the signing identity/key is loaded and that the signer's error (if any) is not ignored before sending.
  3. Use the SDK's sign/submit pipeline so signing happens automatically between proposal creation and dispatch.

Example fix

// before
signed := &peer.SignedProposal{ProposalBytes: proposalBytes}
// after
sig, err := signer.Sign(proposalBytes)
if err != nil { return err }
signed := &peer.SignedProposal{ProposalBytes: proposalBytes, Signature: sig}
Defensive patterns

Strategy: validation

Validate before calling

if sp.Signature == nil || len(sp.Signature) == 0 {
    return errors.New("proposal must be signed: sign ProposalBytes with the creator's private key")
}

Type guard

func isSigned(sp *peer.SignedProposal) bool {
    return sp != nil && len(sp.Signature) > 0
}

Prevention

When it happens

Trigger: ProcessProposal -> preProcess -> Validate on a SignedProposal where Signature was never populated — e.g. building the proposal but skipping the signing step, or a signer returning nil on error that was ignored.

Common situations: Client wallet missing or private key not loadable so the sign call silently returned nil, custom protobuf assembly that sets ProposalBytes but not Signature, middleware stripping the signature.

Related errors


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