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
- Sign the ProposalBytes with the creator's private key (ECDSA with SHA-256 in Fabric) and store the result in SignedProposal.Signature.
- Check the signing identity/key is loaded and that the signer's error (if any) is not ignored before sending.
- 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
- Always run the SDK sign step between proposal creation and submit
- Check signer errors; never send an unsigned proposal
- Ensure the wallet/private key is available at submit time
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
- access denied: channel [%s] creator org [%s]
- signature mismatch
- policy for %s not satisfied
- error validating DeltaSet
- failed unmarshaling ECDSA signature on identity: %s
AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04).
Data as JSON: /api/errors/77968f5a87045785.
Report an issue: GitHub.