Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
763 changes: 583 additions & 180 deletions api/protoblocktx/block_tx.pb.go

Large diffs are not rendered by default.

59 changes: 54 additions & 5 deletions api/protoblocktx/block_tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@ option go_package = "github.com/hyperledger/fabric-x-committer/api/protoblocktx"

package protoblocktx;

// Represents a transaction in the blockchain.
message Tx {
repeated TxNamespace namespaces = 1; // Namespaces associated with the transaction.
repeated bytes signatures = 2; // Signature per namespace.
// A list of namespaces that define the transaction's scope.
repeated TxNamespace namespaces = 1;

// A list of endorsements.
// IMPORTANT: This list MUST be the same size as the namespaces list.
// The Endorsement at index i corresponds to the namespace at index i.
repeated Endorsements endorsements = 2;
}

// Represents a namespace within a transaction.
Expand Down Expand Up @@ -44,9 +48,54 @@ message Write {
bytes value = 2; // The value associated with the key being written.
}

// Endorsements holds all the signatures that correspond to a single namespace
// in the transaction's namespaces list.
message Endorsements {
// The list of individual signatures for the corresponding namespace.
repeated EndorsementWithIdentity endorsements_with_identity = 1;
}

// EndorsementWithIdentity bundles a single signature with the identity of its creator.
message EndorsementWithIdentity {
// The actual cryptographic signature bytes.
bytes endorsement = 1;

// The identity of the creator who produced the signature, i.e., the endorsement.
Identity identity = 2;
}

message Identity {
// The identifier of the associated membership service provider
string msp_id = 1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: Is this field necessary? Can't we infer the MSP from the certificate's issuing CA?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This field is part of Fabric. It is included in the identity. Hence, I have replicated the same here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found only one reference to this field in utils/signature/verify.go:

idBytes, err := msp.NewSerializedIdentity(s.Identity.MspId, s.Identity.GetCertificate())

The question is, can we infer the MSP ID from s.Identity.GetCertificate()? Then pass it to NewSerializedIdentity().

Copy link
Contributor Author

@cendhu cendhu Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may infer the MSP ID from the Subject: CN=peer0.org1.example.com, OU=peer, O=Hyperledger, ST=North Carolina, C=US in the certificate. I will create an issue to check this later and use the Fabric way for now till we complete the whole feature.

Note that the endorser would fill this Identity field.


oneof creator {
// The full raw bytes of the creator's certificate (e.g., an X.509 certificate).
bytes certificate= 2;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super nit:

Suggested change
bytes certificate= 2;
bytes certificate = 2;


// An identifier for a certificate that is pre-stored or known by the committer.
string certificate_id = 3;
}
}

// Represents a namespace policy.
message NamespacePolicy {
string scheme = 1; // The scheme for signature verification.
PolicyType type = 1; // The type of policy used.
bytes policy = 2; // The policy rule.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: Can we use here oneof as well? This will remove the need for double marshalling and unmarshalling.

}

enum PolicyType {
// A policy for verifying a single signature that was generated via a Threshold Signature
// Scheme (TSS). In a TSS, a threshold (T) of N parties must cooperate to
// collectively compute and produce the single signature.
THRESHOLD_RULE = 0;

// A policy defined by an explicit rule that evaluates one or more required signatures.
// For example: "OR('Org1MSP.admin', 'Org2MSP.admin')"
SIGNATURE_RULE = 1;
}

message ThresholdRule {
string scheme = 1; // The scheme for signature verification.
bytes public_key = 2; // The public key for signature verification.
}

Expand Down Expand Up @@ -78,7 +127,7 @@ message NamespacePolicies {

message PolicyItem {
string namespace = 1;
bytes policy = 2;
bytes policy = 2; // This holds the complete NamespacePolicy.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: Originally, I used bytes policy to allow support for other policies.
The solution in this PR adds PolicyType to the NamespacePolicy, making it generic.
So I think it is best to use NamespacePolicy as the type of policy in the PolicyItem.

uint64 version = 3;
}

Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ go 1.24.3

require (
github.com/cenkalti/backoff/v4 v4.3.0
github.com/cockroachdb/errors v1.11.3
github.com/cockroachdb/errors v1.12.0
github.com/consensys/gnark-crypto v0.14.0
github.com/docker/docker v28.0.0+incompatible
github.com/docker/go-connections v0.5.0
Expand All @@ -19,7 +19,7 @@ require (
github.com/google/uuid v1.6.0
github.com/hyperledger/fabric-lib-go v1.1.3-0.20240523144151-25edd1eaf5f5
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.7
github.com/hyperledger/fabric-x-common v0.0.0-20251023133631-047a3c32c228
github.com/hyperledger/fabric-x-common v0.0.0-20251027142320-96a137a5adfb
github.com/jackc/puddle v1.3.0
github.com/mitchellh/mapstructure v1.5.0
github.com/onsi/gomega v1.34.2
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -675,8 +675,8 @@ github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I=
github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8=
github.com/cockroachdb/errors v1.12.0 h1:d7oCs6vuIMUQRVbi6jWWWEJZahLCfJpnJSVobd1/sUo=
github.com/cockroachdb/errors v1.12.0/go.mod h1:SvzfYNNBshAVbZ8wzNc/UPK3w1vf0dKDUP41ucAIf7g=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30=
Expand Down Expand Up @@ -921,8 +921,8 @@ github.com/hyperledger/fabric-lib-go v1.1.3-0.20240523144151-25edd1eaf5f5 h1:RPW
github.com/hyperledger/fabric-lib-go v1.1.3-0.20240523144151-25edd1eaf5f5/go.mod h1:SHNCq8AB0VpHAmvJEtdbzabv6NNV1F48JdmDihasBjc=
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.7 h1:sQ5qv8vQQfwewa1JlCiSCC8dLElmaU2/frLolpgibEY=
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.7/go.mod h1:bJnwzfv03oZQeCc863pdGTDgf5nmCy6Za3RAE7d2XsQ=
github.com/hyperledger/fabric-x-common v0.0.0-20251023133631-047a3c32c228 h1:JdVLvE5ExlHvGL2LuaX3IQLo+7ZExsJhPkDutmNCJEU=
github.com/hyperledger/fabric-x-common v0.0.0-20251023133631-047a3c32c228/go.mod h1:eztTd6MwzVOZtlWcOXWZepDBK/pQz3c3epu91BXAp6o=
github.com/hyperledger/fabric-x-common v0.0.0-20251027142320-96a137a5adfb h1:oh/k9KIvG/GOuBC92LaEZoI6IlFM91inQ7quoAV8L6w=
github.com/hyperledger/fabric-x-common v0.0.0-20251027142320-96a137a5adfb/go.mod h1:5C4Ab/S56VXzP9BpePfbKr3Clmi1t1soKMThJeGBSIQ=
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
Expand Down
4 changes: 2 additions & 2 deletions integration/runner/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -409,9 +409,9 @@ func (c *CommitterRuntime) MakeAndSendTransactionsToOrderer(
Namespaces: namespaces,
}
if expectedStatus != nil && expectedStatus[i] == protoblocktx.Status_ABORTED_SIGNATURE_INVALID {
tx.Signatures = make([][]byte, len(namespaces))
tx.Endorsements = make([]*protoblocktx.Endorsements, len(namespaces))
for nsIdx := range namespaces {
tx.Signatures[nsIdx] = []byte("dummy")
tx.Endorsements[nsIdx] = test.CreateEndorsementsForThresholdRule([]byte("dummy"))[0]
}
}
txs[i] = c.TxBuilder.MakeTx(tx)
Expand Down
5 changes: 4 additions & 1 deletion integration/test/config_update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

"github.com/onsi/gomega"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"

"github.com/hyperledger/fabric-x-committer/api/protoblocktx"
"github.com/hyperledger/fabric-x-committer/api/protoloadgen"
Expand Down Expand Up @@ -87,11 +88,13 @@ func TestConfigUpdate(t *testing.T) {

c.AddOrUpdateNamespaces(t, types.MetaNamespaceID)
metaPolicy := c.TxBuilder.TxSigner.HashSigners[types.MetaNamespaceID].GetVerificationPolicy()
key := &protoblocktx.ThresholdRule{}
require.NoError(t, proto.Unmarshal(metaPolicy.Policy, key))
submitConfigBlock := func(endpoints []*ordererconn.Endpoint) {
ordererEnv.SubmitConfigBlock(t, &workload.ConfigBlock{
ChannelID: c.SystemConfig.Policy.ChannelID,
OrdererEndpoints: endpoints,
MetaNamespaceVerificationKey: metaPolicy.PublicKey,
MetaNamespaceVerificationKey: key.PublicKey,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What additional changes are needed to remove the need for the MetaNamespaceVerificationKey?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the LifecycleEndorsementPolicy field of config block, we need to add policy type to accept either the threshold rule or signature rule. Currently, it supports only the signature rule. Then, we can remove this extra field.

})
}
submitConfigBlock(ordererEnv.AllRealOrdererEndpoints())
Expand Down
2 changes: 1 addition & 1 deletion loadgen/workload/config_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func CreateConfigBlock(policy *PolicyProfile) (*common.Block, error) {

// CreateDefaultConfigBlock creates a config block with default values.
func CreateDefaultConfigBlock(conf *ConfigBlock) (*common.Block, error) {
configBlock := genesisconfig.Load(genesisconfig.SampleFabricX, configtest.GetDevConfigDir())
configBlock := genesisconfig.Load(genesisconfig.TwoOrgsSampleFabricX, configtest.GetDevConfigDir())
tlsCertPath := filepath.Join(configtest.GetDevConfigDir(), "msp", "tlscacerts", "tlsroot.pem")
for _, consenter := range configBlock.Orderer.ConsenterMapping {
consenter.Identity = tlsCertPath
Expand Down
7 changes: 4 additions & 3 deletions loadgen/workload/conflicts.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"math/rand"

"github.com/hyperledger/fabric-x-committer/api/protoblocktx"
"github.com/hyperledger/fabric-x-committer/utils/test"
)

// Dependency types.
Expand Down Expand Up @@ -61,9 +62,9 @@ func newSignTxModifier(rnd *rand.Rand, profile *Profile) *signTxModifier {
func (g *signTxModifier) Modify(tx *protoblocktx.Tx) {
if g.invalidSignGenerator.Next() {
// Pre-assigning prevents TxBuilder from re-signing the TX.
tx.Signatures = make([][]byte, len(tx.Namespaces))
for i := range tx.Namespaces {
tx.Signatures[i] = g.invalidSignature
tx.Endorsements = make([]*protoblocktx.Endorsements, len(tx.Namespaces))
for i := range len(tx.Namespaces) {
tx.Endorsements[i] = test.CreateEndorsementsForThresholdRule(g.invalidSignature)[0]
}
}
}
Expand Down
15 changes: 10 additions & 5 deletions loadgen/workload/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import (
"os"

"github.com/cockroachdb/errors"
"github.com/hyperledger/fabric-x-common/protoutil"

"github.com/hyperledger/fabric-x-committer/api/protoblocktx"
"github.com/hyperledger/fabric-x-committer/api/types"
"github.com/hyperledger/fabric-x-committer/utils"
"github.com/hyperledger/fabric-x-committer/utils/logging"
"github.com/hyperledger/fabric-x-committer/utils/signature"
"github.com/hyperledger/fabric-x-committer/utils/signature/sigtest"
"github.com/hyperledger/fabric-x-committer/utils/test"
)

var logger = logging.New("load-gen-sign")
Expand Down Expand Up @@ -57,19 +59,19 @@ func NewTxSignerVerifier(policy *PolicyProfile) *TxSignerVerifier {

// Sign signs a TX.
func (e *TxSignerVerifier) Sign(txID string, tx *protoblocktx.Tx) {
tx.Signatures = make([][]byte, len(tx.Namespaces))
tx.Endorsements = make([]*protoblocktx.Endorsements, len(tx.Namespaces))
for nsIndex, ns := range tx.Namespaces {
signer, ok := e.HashSigners[ns.NsId]
if !ok {
continue
}
tx.Signatures[nsIndex] = signer.Sign(txID, tx, nsIndex)
tx.Endorsements[nsIndex] = test.CreateEndorsementsForThresholdRule(signer.Sign(txID, tx, nsIndex))[0]
}
}

// Verify verifies a signature on the transaction.
func (e *TxSignerVerifier) Verify(txID string, tx *protoblocktx.Tx) bool {
if len(tx.Signatures) < len(tx.Namespaces) {
if len(tx.Endorsements) < len(tx.Namespaces) {
return false
}

Expand Down Expand Up @@ -130,8 +132,11 @@ func (e *HashSignerVerifier) Verify(txID string, tx *protoblocktx.Tx, nsIndex in
// GetVerificationPolicy returns the verification policy.
func (e *HashSignerVerifier) GetVerificationPolicy() *protoblocktx.NamespacePolicy {
return &protoblocktx.NamespacePolicy{
Scheme: e.scheme,
PublicKey: e.pubKey,
Type: protoblocktx.PolicyType_THRESHOLD_RULE,
Policy: protoutil.MarshalOrPanic(&protoblocktx.ThresholdRule{
Scheme: e.scheme,
PublicKey: e.pubKey,
}),
}
}

Expand Down
4 changes: 2 additions & 2 deletions loadgen/workload/tx_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,14 @@ func (txb *TxBuilder) makeTx(optionalTxID *string, blockTx *protoblocktx.Tx) *pr

// 2. Signs the TX:
switch {
case blockTx.Signatures != nil:
case len(blockTx.Endorsements) > 0:
// If the TX already have a signature, it doesn't re-sign it.
case txb.TxSigner != nil:
// If TxSigner is given, it is used to sign the TX.
txb.TxSigner.Sign(txID, blockTx)
case txb.TxSigner == nil:
// Otherwise, it puts empty signatures for all namespaces to ensure well-formed TX.
blockTx.Signatures = make([][]byte, len(blockTx.Namespaces))
blockTx.Endorsements = make([]*protoblocktx.Endorsements, len(blockTx.Namespaces))
}

// 3. Serializes the envelope's payload.
Expand Down
2 changes: 1 addition & 1 deletion mock/sigverifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ func (m *SigVerifier) sendResponseBatch(
}
status := protoblocktx.Status_COMMITTED
isConfig := len(req.Tx.Namespaces) == 1 && req.Tx.Namespaces[0].NsId == types.ConfigNamespaceID
if len(req.Tx.Signatures) == 0 && !isConfig {
if len(req.Tx.Endorsements) == 0 && !isConfig {
status = protoblocktx.Status_ABORTED_SIGNATURE_INVALID
}
respBatch.Responses = append(respBatch.Responses, &protosigverifierservice.Response{
Expand Down
Loading