hyperledger / hyperledger/fabric-x-common

[policies] A signature is verified once per sub-policy an implicit meta policy tries

Open
#192 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
21
Forks
15
Avg merge
2d 19h
Merged PRs (30d)
20

Description

`ImplicitMetaPolicy.EvaluateSignedData` (`common/policies/implicitmeta.go:70`) evaluates its
sub-policies by handing each one the whole signature set:

```go
for _, policy := range imp.SubPolicies {
if policy.EvaluateSignedData(signatureSet) == nil { // implicitmeta.go:93
```

Each leaf is a `cauthdsl` signature policy, and its `EvaluateSignedData`
(`common/cauthdsl/policy.go:88`) converts the set before consulting the policy at all:

```go
ids := policies.SignatureSetToValidIdentities(signatureSet, p.deserializer) // policy.go:93
```

`SignatureSetToValidIdentities` (`common/policies/policy.go:364`) verifies every signature as it
deserializes it. The principal is only checked afterwards, inside the compiled policy, by
`SatisfiesPrincipal` (`msp/identities.go:95`). So each leaf the tree visits performs a full
`identity.Verify` (`msp/identities.go:181`) and discards the result unless its principal happens to
match.

A channel-wide policy is a nested implicit meta policy over both organization groups, so the leaves
are every organization in the channel, not only those of one group. The tree of a 4-party network
generated by `armageddon generate` in the Fabric-X Orderer:

```
/Channel/Writers IMPLICIT_META ANY
├── /Channel/Application/Writers IMPLICIT_META ANY
│ └── /Channel/Application/peer1/Writers SIGNATURE 1of[signedBy(0)] over [peer1.MEMBER]
└── /Channel/Orderer/Writers IMPLICIT_META ANY
├── /Channel/Orderer/org1/Writers SIGNATURE 1of[signedBy(0)] over [org1.MEMBER]
├── /Channel/Orderer/org2/Writers SIGNATURE 1of[signedBy(0)] over [org2.MEMBER]
├── /Channel/Orderer/org3/Writers SIGNATURE 1of[signedBy(0)] over [org3.MEMBER]
└── /Channel/Orderer/org4/Writers SIGNATURE 1of[signedBy(0)] over [org4.MEMBER]
```

Five leaves for four parties: the four orderer organizations plus one application organization. **The
cost of admitting a request grows with the total number of organizations across every group**, even
though a request can only ever satisfy the leaf of the MSP that signed it — the creator is
deserialized by that MSP, and a principal naming a different one is rejected.

Measured in the Fabric-X Orderer against that network, one client request signed by `org1`, counting
`msp.(*identity).Verify` calls and policy evaluations with temporary counters over 40 independently
constructed bundles (Apple M1 Max, single goroutine, go1.27):

| per request | distribution over 40 bundles | mean |
|---|---|---|
| ECDSA verifications | 1×3, 2×17, 3×9, 4×7, 5×4 | **2.80** |
| `cauthdsl` leaf evaluations | 1×3, 2×17, 3×9, 4×7, 5×4 | 2.80 |
| implicit sub-policy evaluations | 2×3, 3×16, 4×7, 5×9, 6×1, 7×4 | 4.03 |

Leaf evaluations track verifications exactly, which is the mechanism: one verification per leaf
visited. Sub-policy evaluations run to 7 because the tree is two levels deep — up to two groups, and
up to five organizations within them. Certificate mode behaves the same way (mean 2.77).

Forcing the bundle ordering so that a chosen number of leaves is visited shows the cost is linear in
that number, and so in the number of organizations:

| leaves visited | ns/op | allocs/op | bytes/op | tx/s, 1 goroutine | tx/s, 10 goroutines |
|---|---|---|---|---|---|
| 1 | 77,597 | 75.0 | 3,972 | 12,887 | 38,441 |
| 2 | 155,591 | 138.0 | 6,923 | 6,427 | 18,359 |
| 3 | 228,856 | 201.0 | 9,873 | 4,370 | 11,688 |
| 4 | 291,691 | 264.1 | 12,822 | 3,428 | 10,597 |
| 5 | 386,630 | 331.1 | 15,980 | 2,586 | 7,793 |

Each additional leaf costs ~77 µs, ~64 allocations and ~3.0 KB, and five leaves cost 5.0× one leaf in
both time and throughput. Only one of those verifications can ever be the one that matters.

### The general case

For a channel with **n** orderer organizations and **m** application organizations there are `n + m`
leaves. A request can satisfy exactly one of them and the walk stops there, so it costs:

| | verifications per request |
|---|---|
| best case | **1** — the signer's group is tried first and its own leaf first |
| worst case | **n + m** — every leaf in the channel |

The work of admitting a request is therefore linear in the total number of organizations,
**Θ(n + m)**. The `n = 4, m = 1` network measured above averages 2.80
verifications per request against a worst case of 5.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with common/policies/implicitmeta.go:70 and trace the calls through common/cauthdsl/policy.go:88, common/policies/policy.go:364, and msp/identities.go:95,181. Reproduce the verification and evaluation counts for the described policy tree, then confirm that policy behavior is preserved without repeated verification and compare the benchmark costs.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
authentication, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.