Skip to content
Merged
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
87 changes: 87 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
This file provides guidance to agents when working with code in this repository.

## What this project is

A Go implementation of an anonymous identity stack (Identity Mixer / Idemix) for blockchain systems, originally designed for use with Hyperledger Fabric. It provides zero-knowledge proof-based credentials: users can prove possession of a CA-signed credential and selectively disclose attributes (OU, Role, EnrollmentID, RevocationHandle) without revealing their identity.

## Commands

```bash
# CI entry point: checks + unit-tests + unit-tests-race
make all

# Format, vet, and license-header check
make checks # runs check-deps, gofmt, go vet, addlicense -check
make fmt # apply gofmt -l -s -w in place

# Tests
make unit-tests # go test ./...
make unit-tests-race # go test -race -cover with GORACE=history_size=7, 960s timeout

# Run a single package or test directly
go test ./bccsp/schemes/aries/...
go test ./bccsp/schemes/aries/ -run TestSignerSign

# Build idemixgen tool
make idemixgen # go install ./tools/idemixgen → $GOPATH/bin
make binaries # cross-compile to bin/amd64/ (linux) and bin/arm64/ (darwin)

# Protobuf regeneration
make installbuf # installs buf@v1.70.0
make genprotos # buf generate --template buf.gen.yaml

# Tidy modules
make tidy
```

## Architecture

### Two cryptographic schemes

The codebase implements two parallel cryptographic backends, both exposed through the same BCCSP interface:

1. **Legacy / dlog** (`bccsp/schemes/dlog/`) — The original Idemix implementation using discrete log proofs. It takes a `*math.Curve` and a `Translator` (handles curve-specific serialization). Instantiated via `idemix.New(keyStore, curve, translator, exportable)`.

2. **Aries** (`bccsp/schemes/aries/`) — A newer implementation using BBS+ signatures for credential issuance, intended to replace the dlog scheme. Instantiated via `idemix.NewAries(keyStore, curve, translator, exportable)`.

The **legacy guard** in CI enforces that new code must not add new imports of `schemes/dlog` or `schemes/weak-bb` outside of the known files listed in `.github/workflows/go.yml`. New code should use the Aries scheme.

### Package structure

```
bccsp/
types/ # Interfaces: Issuer, User, CredRequest, Credential, SignatureScheme, etc.
handlers/ # BCCSP operation wrappers: IssuerKeyGen, UserKeyGen, Signer, Verifier, etc.
keystore/ # Key storage implementations
schemes/
aries/ # Aries/BBS+ implementation of all bccsp/types interfaces
dlog/
bridge/ # Adapts dlog/crypto to bccsp/types interfaces
crypto/ # Core dlog Idemix math (signature, credential, nym, revocation)
weak-bb/ # Weak Boneh-Boyen signatures (used internally for revocation)
bccsp.go # New() and NewAries() constructors; multiplexer wiring
impl.go # CSP base: type-keyed dispatch maps for KeyGen/Sign/Verify/etc.

bbs/ # BBS+ signature implementation (used by aries/Cred)

msp/ # Hyperledger Fabric MSP integration: credential lifecycle,
# attribute indices (OU=0, Role=1, EnrollmentID=2, RevocationHandle=3)

tools/idemixgen/ # CLI tool to generate issuer keys and signer configs for Fabric MSP
```

### BCCSP dispatch pattern

`CSP` (in `bccsp/impl.go`) is a generic dispatcher that holds `map[reflect.Type]Signer` (and similar maps for KeyGen/Verify/Import/Deriv). Operations are routed by the Go type of the key or opts argument. `bccsp.go` wires concrete scheme implementations into these maps via `AddWrapper`. The `userSecreKeySignerMultiplexer` and `issuerPublicKeyVerifierMultiplexer` types handle cases where a single key type handles multiple opt types.

### Curve and translator

`github.com/IBM/mathlib` provides the elliptic curve abstractions (`*math.Curve`, `*math.G1`, `*math.G2`, `*math.Zr`). The `Translator` interface (dlog scheme) handles curve-specific protobuf serialization for group elements. The `bccsp/schemes/dlog/crypto/translator/amcl/` package provides concrete translators for each supported curve family (FP256BN, BLS12-381, etc.).

### Credential attributes

The four fixed attributes are accessed by index: OU (0), Role (1), EnrollmentID (2), RevocationHandle (3). These indices are referenced throughout `msp/` and must match across signing and verification.

### Protobuf

`.proto` files live alongside their generated `.pb.go` files. Run `make genprotos` (requires `buf`) to regenerate. The main proto files are `bccsp/schemes/dlog/crypto/idemix.proto` and `bccsp/schemes/aries/cred.proto`.
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

is this expected? We have CLAUDE.md pointing to @AGENTS.md, and AGENTS.md whose first line is CLAUDE.md?

28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,34 @@ This project is a Go implementation of an anonymous identity stack for blockchai
- [Signature verification](#signature-verification)
- [Auditing NymEid](#auditing-nymeid)

# MSP Usage

## Constructors

Two MSP constructors are available, corresponding to the two cryptographic backends:

- **`NewIdemixMsp(version)`** — uses the legacy dlog scheme. Accepts any supported curve; defaults to `FP256BN_AMCL` when `curve_id` is absent.
- **`NewIdemixMspAries(version)`** — uses the Aries/BBS+ scheme. Accepts only BBS curves (`BLS12_381_BBS` or `BLS12_381_BBS_GURVY`); defaults to `BLS12_381_BBS` when `curve_id` is absent.

## Curve selection

The elliptic curve is no longer hardcoded at construction time. Instead, `Setup` reads `IdemixMSPConfig.CurveId` (set by `idemixgen` when generating key material) and builds the BCCSP with the matching curve and translator.

Supported `curve_id` values and their backends:

| `curve_id` | Backend | Notes |
|---|-----------------------|---|
| `FP256BN_AMCL` | AMCL | Default for dlog |
| `BN254` | Gurvy | |
| `FP256BN_AMCL_MIRACL` | AMCL | Legacy compatibility |
| `BLS12_377_GURVY` | Gurvy | |
| `BLS12_381_GURVY` | Gurvy | |
| `BLS12_381` | Gurvy (it was Kilic) | |
| `BLS12_381_BBS` | Gurvy (it was Kilic) | Default for Aries |
| `BLS12_381_BBS_GURVY` | Gurvy | |

The `curve_id` value is written into the MSP config by `idemixgen --curve <curve_id>`. An empty `curve_id` triggers the per-scheme default (backward-compatible). `Setup` errors if the config type (`IDEMIX` vs `IDEMIX_ARIES`) does not match the constructor used, or if an Aries MSP is configured with a non-BBS curve.

# Protocol

Here we describe the cryptographic protocol that is implemented.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/IBM/idemix
go 1.26.3

require (
github.com/IBM/mathlib v0.2.1-0.20260708043658-e8f8fcd199a4
github.com/IBM/mathlib v0.3.0
github.com/alecthomas/kingpin/v2 v2.4.0
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.7
github.com/onsi/ginkgo/v2 v2.32.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
github.com/IBM/mathlib v0.2.1-0.20260708043658-e8f8fcd199a4 h1:HRDczSMpaugF7rq8AirQX7dvL9iK7TCyPnI+QkrqCX8=
github.com/IBM/mathlib v0.2.1-0.20260708043658-e8f8fcd199a4/go.mod h1:r5/+9SWcYCm1TSZE9HRXq9/ejjdtS0Ab2MjLYBuF9S4=
github.com/IBM/mathlib v0.3.0 h1:lYmtMn43OZRzuw8yVnKqPPZD4MnWV34fffFM/X4apvk=
github.com/IBM/mathlib v0.3.0/go.mod h1:r5/+9SWcYCm1TSZE9HRXq9/ejjdtS0Ab2MjLYBuF9S4=
github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE=
github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY=
Expand Down
149 changes: 123 additions & 26 deletions msp/idemixmsp.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (

idemix "github.com/IBM/idemix/bccsp"
"github.com/IBM/idemix/bccsp/keystore"
idemixcrypto "github.com/IBM/idemix/bccsp/schemes/dlog/crypto"
"github.com/IBM/idemix/bccsp/schemes/dlog/crypto/translator/amcl"
bccsp "github.com/IBM/idemix/bccsp/types"
im "github.com/IBM/idemix/msp/config"
Expand Down Expand Up @@ -69,6 +70,59 @@ const (
const rhIndex = 3
const eidIndex = 2

// Curve ID string constants matching the values written by idemixgen into IdemixMSPConfig.CurveId.
const (
curveIDFP256BN_AMCL = "FP256BN_AMCL"
curveIDBN254 = "BN254"
curveIDFP256BN_AMCL_MIRACL = "FP256BN_AMCL_MIRACL"
curveIDBLS12_377_GURVY = "BLS12_377_GURVY"
curveIDBLS12_381_GURVY = "BLS12_381_GURVY"
curveIDBLS12_381 = "BLS12_381"
curveIDBLS12_381_BBS = "BLS12_381_BBS"
curveIDBLS12_381_BBS_GURVY = "BLS12_381_BBS_GURVY"
)

// curveAndTranslator maps a curve_id string (as stored in IdemixMSPConfig) to the corresponding
// math.Curve and dlog Translator. Returns an error for unknown curve IDs.
func curveAndTranslator(curveID string) (*math.Curve, idemixcrypto.Translator, error) {
switch curveID {
case curveIDFP256BN_AMCL:
c := math.Curves[math.FP256BN_AMCL]

return c, &amcl.Fp256bn{C: c}, nil
case curveIDBN254:
c := math.Curves[math.BN254]

return c, &amcl.Gurvy{C: c}, nil
case curveIDFP256BN_AMCL_MIRACL:
c := math.Curves[math.FP256BN_AMCL_MIRACL]

return c, &amcl.Fp256bnMiracl{C: c}, nil
case curveIDBLS12_377_GURVY:
c := math.Curves[math.BLS12_377_GURVY]

return c, &amcl.Gurvy{C: c}, nil
case curveIDBLS12_381_GURVY:
c := math.Curves[math.BLS12_381_GURVY]

return c, &amcl.Gurvy{C: c}, nil
case curveIDBLS12_381:
c := math.Curves[math.BLS12_381]

return c, &amcl.Gurvy{C: c}, nil
case curveIDBLS12_381_BBS:
c := math.Curves[math.BLS12_381_BBS]

return c, &amcl.Gurvy{C: c}, nil
case curveIDBLS12_381_BBS_GURVY:
c := math.Curves[math.BLS12_381_BBS_GURVY]

return c, &amcl.Gurvy{C: c}, nil
default:
return nil, nil, fmt.Errorf("unknown curve id %q", curveID)
}
}

// Logger defines the logging interface required by Idemixmsp.
// This interface is compatible with the Go SDK log package and common logging facades.
type Logger interface {
Expand All @@ -87,6 +141,8 @@ type Idemixmsp struct {
revocationPK bccsp.Key
epoch int
logger Logger
aries bool
exportable bool
}

// defaultLogger is a simple logger implementation that wraps zap.SugaredLogger
Expand Down Expand Up @@ -141,37 +197,42 @@ func (l *stdLogger) IsEnabledFor(level zapcore.Level) bool {
return true // Standard logger always logs
}

// NewIdemixMsp creates a new instance of idemixmsp
// NewIdemixMsp creates a new instance of idemixmsp using the dlog scheme.
// The curve is determined at Setup time from IdemixMSPConfig.CurveId (default: FP256BN_AMCL).
func NewIdemixMsp(version MSPVersion) (MSP, error) {
logger := newDefaultLogger("idemix")
logger.Debugf("Creating Idemix-based MSP instance")
return NewIdemixMspWithLogger(version, newDefaultLogger("idemix"))
}

curve := math.Curves[math.FP256BN_AMCL]
csp, err := idemix.New(&keystore.Dummy{}, curve, &amcl.Fp256bn{C: curve}, true)
if err != nil {
panic(fmt.Sprintf("unexpected condition, error received [%s]", err))
// NewIdemixMspWithLogger creates a new instance of idemixmsp using the dlog scheme with a custom logger.
// The curve is determined at Setup time from IdemixMSPConfig.CurveId (default: FP256BN_AMCL).
// If logger is nil, the default logger is used.
func NewIdemixMspWithLogger(version MSPVersion, logger Logger) (MSP, error) {
if logger == nil {
logger = newDefaultLogger("idemix")
}

msp := Idemixmsp{csp: csp, logger: logger}
msp.version = version
logger.Debugf("Creating Idemix-based MSP instance")
msp := Idemixmsp{logger: logger, version: version, aries: false, exportable: true}

return &msp, nil
}

// NewIdemixMspAries creates a new
// instance of idemixmsp
// NewIdemixMspAries creates a new instance of idemixmsp using the Aries/BBS+ scheme.
// The curve is determined at Setup time from IdemixMSPConfig.CurveId; only BLS12_381_BBS
// and BLS12_381_BBS_GURVY are accepted (default: BLS12_381_BBS).
func NewIdemixMspAries(version MSPVersion) (MSP, error) {
logger := newDefaultLogger("idemix")
logger.Debugf("Creating Idemix-based MSP instance")
return NewIdemixMspAriesWithLogger(version, newDefaultLogger("idemix"))
}

curve := math.Curves[math.BLS12_381_BBS]
csp, err := idemix.NewAries(&keystore.Dummy{}, curve, &amcl.Gurvy{C: curve}, true)
if err != nil {
panic(fmt.Sprintf("unexpected condition, error received [%s]", err))
// NewIdemixMspAriesWithLogger creates a new instance of idemixmsp using the Aries/BBS+ scheme with a custom logger.
// The curve is determined at Setup time from IdemixMSPConfig.CurveId; only BLS12_381_BBS
// and BLS12_381_BBS_GURVY are accepted (default: BLS12_381_BBS).
// If logger is nil, the default logger is used.
func NewIdemixMspAriesWithLogger(version MSPVersion, logger Logger) (MSP, error) {
if logger == nil {
logger = newDefaultLogger("idemix")
}

msp := Idemixmsp{csp: csp, logger: logger}
msp.version = version
logger.Debugf("Creating Idemix-based MSP instance")
msp := Idemixmsp{logger: logger, version: version, aries: true, exportable: true}

return &msp, nil
}
Expand All @@ -192,11 +253,47 @@ func (msp *Idemixmsp) Setup(conf1 *m.MSPConfig) error {
msp.name = conf.Name
msp.logger.Debugf("Setting up Idemix MSP instance %s", msp.name)

switch conf1.Type {
case int32(IDEMIX):
case int32(IDEMIX_ARIES):
default:
return errors.New("setup error: config is not of type IDEMIX")
// Enforce that the config type matches the constructor used.
if msp.aries {
if conf1.Type != int32(IDEMIX_ARIES) {
return fmt.Errorf("setup error: aries MSP requires config of type IDEMIX_ARIES, got %d", conf1.Type)
}
} else {
if conf1.Type != int32(IDEMIX) {
return fmt.Errorf("setup error: dlog MSP requires config of type IDEMIX, got %d", conf1.Type)
}
}

// Determine the curve from config.CurveId.
curveID := conf.CurveId
if msp.aries {
switch curveID {
case "", curveIDBLS12_381_BBS:
curveID = curveIDBLS12_381_BBS
case curveIDBLS12_381_BBS_GURVY:
// accepted
default:
return fmt.Errorf("setup error: aries MSP requires a BBS curve, got %q", curveID)
}
Comment on lines +269 to +277

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We only support these 2?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

if you explicitly called NewIdemixMspAries, then we enforce this because the constructor was enforcing that curve

} else {
if curveID == "" {
curveID = curveIDFP256BN_AMCL
}
}

curve, tr, err := curveAndTranslator(curveID)
if err != nil {
return fmt.Errorf("setup error: %w", err)
}

// Build the BCCSP using the curve selected from config.
if msp.aries {
msp.csp, err = idemix.NewAries(&keystore.Dummy{}, curve, tr, msp.exportable)
} else {
msp.csp, err = idemix.New(&keystore.Dummy{}, curve, tr, msp.exportable)
}
if err != nil {
return fmt.Errorf("setup error: failed to create BCCSP: %w", err)
}

// Import Issuer Public Key
Expand Down
2 changes: 1 addition & 1 deletion msp/idemixmsp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func TestSetupBad(t *testing.T) {
conf := &msp.MSPConfig{Type: 1234, Config: nil}
err = msp1.Setup(conf)
require.Error(t, err)
require.Contains(t, err.Error(), "setup error: config is not of type IDEMIX")
require.Contains(t, err.Error(), "setup error:")

// Setup with bad idemix config bytes
conf = &msp.MSPConfig{Type: int32(IDEMIX), Config: []byte("barf")}
Expand Down
Loading