/* Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ package protoutil import ( "bytes" "crypto/sha256" b64 "encoding/base64" "github.com/golang/protobuf/proto" "github.com/hyperledger/fabric-protos-go/common" "github.com/hyperledger/fabric-protos-go/peer" "github.com/pkg/errors" ) // GetPayloads gets the underlying payload objects in a TransactionAction func GetPayloads(txActions *peer.TransactionAction) (*peer.ChaincodeActionPayload, *peer.ChaincodeAction, error) { // TODO: pass in the tx type (in what follows we're assuming the // type is ENDORSER_TRANSACTION) ccPayload, err := UnmarshalChaincodeActionPayload(txActions.Payload) if err != nil { return nil, nil, err } if ccPayload.Action == nil || ccPayload.Action.ProposalResponsePayload == nil { return nil, nil, errors.New("no payload in ChaincodeActionPayload") } pRespPayload, err := UnmarshalProposalResponsePayload(ccPayload.Action.ProposalResponsePayload) if err != nil { return nil, nil, err } if pRespPayload.Extension == nil { return nil, nil, errors.New("response payload is missing extension") } respPayload, err := UnmarshalChaincodeAction(pRespPayload.Extension) if err != nil { return ccPayload, nil, err } return ccPayload, respPayload, nil } // GetEnvelopeFromBlock gets an envelope from a block's Data field. func GetEnvelopeFromBlock(data []byte) (*common.Envelope, error) { // Block always begins with an envelope var err error env := &common.Envelope{} if err = proto.Unmarshal(data, env); err != nil { return nil, errors.Wrap(err, "error unmarshalling Envelope") } return env, nil } // CreateSignedEnvelope creates a signed envelope of the desired type, with // marshaled dataMsg and signs it func CreateSignedEnvelope( txType common.HeaderType, channelID string, signer Signer, dataMsg proto.Message, msgVersion int32, epoch uint64, ) (*common.Envelope, error) { return CreateSignedEnvelopeWithTLSBinding(txType, channelID, signer, dataMsg, msgVersion, epoch, nil) } // CreateSignedEnvelopeWithTLSBinding creates a signed envelope of the desired // type, with marshaled dataMsg and signs it. It also includes a TLS cert hash // into the channel header func CreateSignedEnvelopeWithTLSBinding( txType common.HeaderType, channelID string, signer Signer, dataMsg proto.Message, msgVersion int32, epoch uint64, tlsCertHash []byte, ) (*common.Envelope, error) { payloadChannelHeader := MakeChannelHeader(txType, msgVersion, channelID, epoch) payloadChannelHeader.TlsCertHash = tlsCertHash var err error payloadSignatureHeader := &common.SignatureHeader{} if signer != nil { payloadSignatureHeader, err = NewSignatureHeader(signer) if err != nil { return nil, err } } data, err := proto.Marshal(dataMsg) if err != nil { return nil, errors.Wrap(err, "error marshaling") } paylBytes := MarshalOrPanic( &common.Payload{ Header: MakePayloadHeader(payloadChannelHeader, payloadSignatureHeader), Data: data, }, ) var sig []byte if signer != nil { sig, err = signer.Sign(paylBytes) if err != nil { return nil, err } } env := &common.Envelope{ Payload: paylBytes, Signature: sig, } return env, nil } // Signer is the interface needed to sign a transaction type Signer interface { Sign(msg []byte) ([]byte, error) Serialize() ([]byte, error) } // CreateSignedTx assembles an Envelope message from proposal, endorsements, // and a signer. This function should be called by a client when it has // collected enough endorsements for a proposal to create a transaction and // submit it to peers for ordering func CreateSignedTx( proposal *peer.Proposal, signer Signer, resps ...*peer.ProposalResponse, ) (*common.Envelope, error) { if len(resps) == 0 { return nil, errors.New("at least one proposal response is required") } if signer == nil { return nil, errors.New("signer is required when creating a signed transaction") } // the original header hdr, err := UnmarshalHeader(proposal.Header) if err != nil { return nil, err } // the original payload pPayl, err := UnmarshalChaincodeProposalPayload(proposal.Payload) if err != nil { return nil, err } // check that the signer is the same that is referenced in the header signerBytes, err := signer.Serialize() if err != nil { return nil, err } shdr, err := UnmarshalSignatureHeader(hdr.SignatureHeader) if err != nil { return nil, err } if !bytes.Equal(signerBytes, shdr.Creator) { return nil, errors.New("signer must be the same as the one referenced in the header") } // ensure that all actions are bitwise equal and that they are successful var a1 []byte for n, r := range resps { if r.Response.Status < 200 || r.Response.Status >= 400 { return nil, errors.Errorf("proposal response was not successful, error code %d, msg %s", r.Response.Status, r.Response.Message) } if n == 0 { a1 = r.Payload continue } if !bytes.Equal(a1, r.Payload) { return nil, errors.Errorf("ProposalResponsePayloads do not match (base64): '%s' vs '%s'", b64.StdEncoding.EncodeToString(r.Payload), b64.StdEncoding.EncodeToString(a1)) } } // fill endorsements according to their uniqueness endorsersUsed := make(map[string]struct{}) var endorsements []*peer.Endorsement for _, r := range resps { if r.Endorsement == nil { continue } key := string(r.Endorsement.Endorser) if _, used := endorsersUsed[key]; used { continue } endorsements = append(endorsements, r.Endorsement) endorsersUsed[key] = struct{}{} } if len(endorsements) == 0 { return nil, errors.Errorf("no endorsements") } // create ChaincodeEndorsedAction cea := &peer.ChaincodeEndorsedAction{ProposalResponsePayload: resps[0].Payload, Endorsements: endorsements} // obtain the bytes of the proposal payload that will go to the transaction propPayloadBytes, err := GetBytesProposalPayloadForTx(pPayl) if err != nil { return nil, err } // serialize the chaincode action payload cap := &peer.ChaincodeActionPayload{ChaincodeProposalPayload: propPayloadBytes, Action: cea} capBytes, err := GetBytesChaincodeActionPayload(cap) if err != nil { return nil, err } // create a transaction taa := &peer.TransactionAction{Header: hdr.SignatureHeader, Payload: capBytes} taas := make([]*peer.TransactionAction, 1) taas[0] = taa tx := &peer.Transaction{Actions: taas} // serialize the tx txBytes, err := GetBytesTransaction(tx) if err != nil { return nil, err } // create the payload payl := &common.Payload{Header: hdr, Data: txBytes} paylBytes, err := GetBytesPayload(payl) if err != nil { return nil, err } // sign the payload sig, err := signer.Sign(paylBytes) if err != nil { return nil, err } // here's the envelope return &common.Envelope{Payload: paylBytes, Signature: sig}, nil } // CreateProposalResponse creates a proposal response. func CreateProposalResponse( hdrbytes []byte, payl []byte, response *peer.Response, results []byte, events []byte, ccid *peer.ChaincodeID, signingEndorser Signer, ) (*peer.ProposalResponse, error) { hdr, err := UnmarshalHeader(hdrbytes) if err != nil { return nil, err } // obtain the proposal hash given proposal header, payload and the // requested visibility pHashBytes, err := GetProposalHash1(hdr, payl) if err != nil { return nil, errors.WithMessage(err, "error computing proposal hash") } // get the bytes of the proposal response payload - we need to sign them prpBytes, err := GetBytesProposalResponsePayload(pHashBytes, response, results, events, ccid) if err != nil { return nil, err } // serialize the signing identity endorser, err := signingEndorser.Serialize() if err != nil { return nil, errors.WithMessage(err, "error serializing signing identity") } // sign the concatenation of the proposal response and the serialized // endorser identity with this endorser's key signature, err := signingEndorser.Sign(append(prpBytes, endorser...)) if err != nil { return nil, errors.WithMessage(err, "could not sign the proposal response payload") } resp := &peer.ProposalResponse{ // Timestamp: TODO! Version: 1, // TODO: pick right version number Endorsement: &peer.Endorsement{ Signature: signature, Endorser: endorser, }, Payload: prpBytes, Response: &peer.Response{ Status: 200, Message: "OK", }, } return resp, nil } // CreateProposalResponseFailure creates a proposal response for cases where // endorsement proposal fails either due to a endorsement failure or a // chaincode failure (chaincode response status >= shim.ERRORTHRESHOLD) func CreateProposalResponseFailure( hdrbytes []byte, payl []byte, response *peer.Response, results []byte, events []byte, chaincodeName string, ) (*peer.ProposalResponse, error) { hdr, err := UnmarshalHeader(hdrbytes) if err != nil { return nil, err } // obtain the proposal hash given proposal header, payload and the requested visibility pHashBytes, err := GetProposalHash1(hdr, payl) if err != nil { return nil, errors.WithMessage(err, "error computing proposal hash") } // get the bytes of the proposal response payload prpBytes, err := GetBytesProposalResponsePayload(pHashBytes, response, results, events, &peer.ChaincodeID{Name: chaincodeName}) if err != nil { return nil, err } resp := &peer.ProposalResponse{ // Timestamp: TODO! Payload: prpBytes, Response: response, } return resp, nil } // GetSignedProposal returns a signed proposal given a Proposal message and a // signing identity func GetSignedProposal(prop *peer.Proposal, signer Signer) (*peer.SignedProposal, error) { // check for nil argument if prop == nil || signer == nil { return nil, errors.New("nil arguments") } propBytes, err := proto.Marshal(prop) if err != nil { return nil, err } signature, err := signer.Sign(propBytes) if err != nil { return nil, err } return &peer.SignedProposal{ProposalBytes: propBytes, Signature: signature}, nil } // MockSignedEndorserProposalOrPanic creates a SignedProposal with the // passed arguments func MockSignedEndorserProposalOrPanic( channelID string, cs *peer.ChaincodeSpec, creator, signature []byte, ) (*peer.SignedProposal, *peer.Proposal) { prop, _, err := CreateChaincodeProposal( common.HeaderType_ENDORSER_TRANSACTION, channelID, &peer.ChaincodeInvocationSpec{ChaincodeSpec: cs}, creator) if err != nil { panic(err) } propBytes, err := proto.Marshal(prop) if err != nil { panic(err) } return &peer.SignedProposal{ProposalBytes: propBytes, Signature: signature}, prop } func MockSignedEndorserProposal2OrPanic( channelID string, cs *peer.ChaincodeSpec, signer Signer, ) (*peer.SignedProposal, *peer.Proposal) { serializedSigner, err := signer.Serialize() if err != nil { panic(err) } prop, _, err := CreateChaincodeProposal( common.HeaderType_ENDORSER_TRANSACTION, channelID, &peer.ChaincodeInvocationSpec{ChaincodeSpec: &peer.ChaincodeSpec{}}, serializedSigner) if err != nil { panic(err) } sProp, err := GetSignedProposal(prop, signer) if err != nil { panic(err) } return sProp, prop } // GetBytesProposalPayloadForTx takes a ChaincodeProposalPayload and returns // its serialized version according to the visibility field func GetBytesProposalPayloadForTx( payload *peer.ChaincodeProposalPayload, ) ([]byte, error) { // check for nil argument if payload == nil { return nil, errors.New("nil arguments") } // strip the transient bytes off the payload cppNoTransient := &peer.ChaincodeProposalPayload{Input: payload.Input, TransientMap: nil} cppBytes, err := GetBytesChaincodeProposalPayload(cppNoTransient) if err != nil { return nil, err } return cppBytes, nil } // GetProposalHash2 gets the proposal hash - this version // is called by the committer where the visibility policy // has already been enforced and so we already get what // we have to get in ccPropPayl func GetProposalHash2(header *common.Header, ccPropPayl []byte) ([]byte, error) { // check for nil argument if header == nil || header.ChannelHeader == nil || header.SignatureHeader == nil || ccPropPayl == nil { return nil, errors.New("nil arguments") } hash := sha256.New() // hash the serialized Channel Header object hash.Write(header.ChannelHeader) // hash the serialized Signature Header object hash.Write(header.SignatureHeader) // hash the bytes of the chaincode proposal payload that we are given hash.Write(ccPropPayl) return hash.Sum(nil), nil } // GetProposalHash1 gets the proposal hash bytes after sanitizing the // chaincode proposal payload according to the rules of visibility func GetProposalHash1(header *common.Header, ccPropPayl []byte) ([]byte, error) { // check for nil argument if header == nil || header.ChannelHeader == nil || header.SignatureHeader == nil || ccPropPayl == nil { return nil, errors.New("nil arguments") } // unmarshal the chaincode proposal payload cpp, err := UnmarshalChaincodeProposalPayload(ccPropPayl) if err != nil { return nil, err } ppBytes, err := GetBytesProposalPayloadForTx(cpp) if err != nil { return nil, err } hash2 := sha256.New() // hash the serialized Channel Header object hash2.Write(header.ChannelHeader) // hash the serialized Signature Header object hash2.Write(header.SignatureHeader) // hash of the part of the chaincode proposal payload that will go to the tx hash2.Write(ppBytes) return hash2.Sum(nil), nil } // GetOrComputeTxIDFromEnvelope gets the txID present in a given transaction // envelope. If the txID is empty, it constructs the txID from nonce and // creator fields in the envelope. func GetOrComputeTxIDFromEnvelope(txEnvelopBytes []byte) (string, error) { txEnvelope, err := UnmarshalEnvelope(txEnvelopBytes) if err != nil { return "", errors.WithMessage(err, "error getting txID from envelope") } txPayload, err := UnmarshalPayload(txEnvelope.Payload) if err != nil { return "", errors.WithMessage(err, "error getting txID from payload") } if txPayload.Header == nil { return "", errors.New("error getting txID from header: payload header is nil") } chdr, err := UnmarshalChannelHeader(txPayload.Header.ChannelHeader) if err != nil { return "", errors.WithMessage(err, "error getting txID from channel header") } if chdr.TxId != "" { return chdr.TxId, nil } sighdr, err := UnmarshalSignatureHeader(txPayload.Header.SignatureHeader) if err != nil { return "", errors.WithMessage(err, "error getting nonce and creator for computing txID") } txid := ComputeTxID(sighdr.Nonce, sighdr.Creator) return txid, nil }