How Clients Authenticate with Ed25519 or P‑256 Signatures in mpCium

mpCium implements a dual-signature authentication scheme allowing clients to prove identity using either Ed25519 (EdDSA) or P‑256 (ECDSA) keys, with the LocalSigner type in pkg/client/local_signer.go dispatching to algorithm-specific signing routines and the server verifying via pkg/identity/identity.go.

The fystack/mpcium repository provides a cryptographic authentication layer that supports both modern Ed25519 curves and NIST P‑256 ECDSA signatures. This flexibility enables integration with diverse blockchain ecosystems while maintaining a unified verification interface in the identity package.

The Three-Stage Authentication Flow

mpCium authenticates clients through a structured three-stage process involving key initialization, payload signing, and server-side verification according to the specific cryptographic algorithm configured.

Stage 1: Key Selection and Loading

The client creates a LocalSigner that determines which algorithm to use based on the EventInitiatorKeyTypeEd25519 or EventInitiatorKeyTypeP256 constant. In pkg/client/local_signer.go, the NewLocalSigner constructor (lines 33‑66) reads the key material from a file path specified in LocalSignerOptions, automatically detecting .age extensions for encrypted keys.

The constructor dispatches to algorithm-specific loaders:

  • loadEd25519Key (lines 99‑106) parses the 32‑byte private key for Ed25519.
  • loadP256Key (lines 109‑116) loads the private key for P‑256 curve operations.

Stage 2: Message Signing

The client constructs an initiator message such as SignTxMessage or GenerateKeyMessage defined in pkg/types/initiator_msg.go. Before transmission, the message must include a signature over its canonical payload.

The LocalSigner.Sign method (lines 22‑34 of local_signer.go) automatically dispatches to the appropriate cryptographic routine:

  • Ed25519: calls ed25519.Sign from the Go standard library.
  • P‑256: calls encryption.SignWithP256 from pkg/encryption/p256.go.

The signer invokes msg.Raw() (implemented at lines 64‑80 of initiator_msg.go) to obtain a deterministic JSON payload excluding the Signature field itself, then stores the resulting signature in the message's Signature field.

Stage 3: Server-Side Verification

When the mpCium node receives the initiator message, pkg/identity/identity.go handles verification using public keys stored in the node's identity file and optional authorizer configurations.

For the initiator signature, the system selects the verifier based on the configured key type:

  • fileStore.verifyEd25519 (lines 55‑66) retrieves the stored Ed25519 public key via GetPublicKey (lines 39‑49) and executes ed25519.Verify.
  • fileStore.verifyP256 (lines 71‑82) calls encryption.VerifyP256Signature to perform ECDSA verification on the P‑256 curve.

If the message includes authorizer signatures defined in the authorizer_public_keys section of config.yaml, the verifyAuthorizerSignature function (lines 20‑42) matches each authorizer's algorithm field (AlgorithmEd25519 or AlgorithmP256) to the cached key type and invokes the corresponding verification routine.

Implementation Examples

Authenticating with Ed25519

To authenticate using Ed25519, initialize the signer with EventInitiatorKeyTypeEd25519 and construct the transaction message:

// Load the Ed25519 signer from pkg/client/local_signer.go
signer, err := client.NewLocalSigner(
    types.EventInitiatorKeyTypeEd25519,
    client.LocalSignerOptions{KeyPath: "./event_initiator.key"},
)
if err != nil {
    log.Fatal(err)
}

// Build the transaction request
txMsg := &types.SignTxMessage{
    KeyType:             types.KeyTypeEd25519,
    WalletID:            "wallet-123",
    NetworkInternalCode: "solana-devnet",
    TxID:                uuid.New().String(),
    Tx:                  []byte{0xde, 0xad, 0xbe, 0xef},
}

// Sign and publish via the MPC client
mpcClient := client.NewMPCClient(client.Options{
    NatsConn: natsConn,
    Signer:   signer,
})
if err := mpcClient.SignTransaction(txMsg); err != nil {
    log.Fatal(err)
}

Behind the scenes, SignTransaction internally calls signer.Sign(txMsg.Raw()), which triggers ed25519.Sign from the standard library. The server-side verifyEd25519 function in pkg/identity/identity.go validates the signature against the initiator's stored public key.

Authenticating with P‑256

For P‑256 authentication, specify EventInitiatorKeyTypeP256 during signer creation. Note that wallet key types use KeyTypeSecp256k1 to indicate P‑256 curve usage:

// Load the P‑256 signer
signer256, err := client.NewLocalSigner(
    types.EventInitiatorKeyTypeP256,
    client.LocalSignerOptions{KeyPath: "./event_initiator_p256.key"},
)
if err != nil {
    log.Fatal(err)
}

// Construct the message with secp256k1 key type for P‑256 wallets
msg := &types.SignTxMessage{
    KeyType:             types.KeyTypeSecp256k1, // Indicates P‑256 wallet
    WalletID:            "wallet-456",
    NetworkInternalCode: "ethereum-mainnet",
    TxID:                uuid.New().String(),
    Tx:                  []byte("transaction_data"),
}

// The client automatically uses encryption.SignWithP256
mpcClient := client.NewMPCClient(client.Options{
    NatsConn: natsConn,
    Signer:   signer256,
})
mpcClient.SignTransaction(msg)

Verification follows the verifyP256 path in identity.go, which delegates to encryption.VerifyP256Signature using the stored ECDSA public key.

Verifying Authorizer Signatures

Authorizers provide additional signatures configured in config.yaml with explicit algorithm declarations:

authorizer_public_keys:
  auditor1:
    public_key: "a1b2c3d4..."  # 32-byte hex for Ed25519

    algorithm: "ed25519"
  compliance2:
    public_key: "04d5e6f7..."  # DER-encoded P‑256 key

    algorithm: "p256"

When including authorizer signatures in the message:

rawPayload, _ := txMsg.Raw()

txMsg.AuthorizerSignatures = []types.AuthorizerSignature{
    {
        AuthorizerID: "auditor1",
        Signature:    ed25519.Sign(privEd25519, rawPayload),
    },
    {
        AuthorizerID: "compliance2",
        Signature:    encryption.SignWithP256(privP256, rawPayload),
    },
}

The server's verifyAuthorizerSignature function automatically selects ed25519.Verify or encryption.VerifyP256Signature based on the algorithm field in the configuration.

Summary

  • Dual-algorithm support: mpCium clients authenticate using either Ed25519 or P‑256 signatures via the LocalSigner interface in pkg/client/local_signer.go.
  • Automatic dispatch: The Sign method (lines 22‑34) automatically routes to ed25519.Sign or encryption.SignWithP256 based on the initiator key type specified during construction.
  • Deterministic payloads: All signatures are computed over the canonical output of msg.Raw() (lines 64‑80 of pkg/types/initiator_msg.go), ensuring consistent verification data.
  • Flexible verification: The server-side identity.go file provides verifyEd25519 (lines 55‑66) and verifyP256 (lines 71‑82) for initiators, plus verifyAuthorizerSignature (lines 20‑42) for multi-party authorization.

Frequently Asked Questions

How do I configure an mpCium client to use Ed25519 instead of P‑256?

Pass types.EventInitiatorKeyTypeEd25519 as the first argument to client.NewLocalSigner in pkg/client/local_signer.go, and ensure your key file contains a valid 32‑byte Ed25519 private key. The constructor (lines 33‑66) automatically selects the Ed25519 loading path via loadEd25519Key (lines 99‑106).

What determines whether the server uses Ed25519 or P‑256 verification?

The server checks the initiator's configured key type stored in the identity file, then dispatches to either fileStore.verifyEd25519 or fileStore.verifyP256 in pkg/identity/identity.go (lines 55‑82). Each function retrieves the appropriate public key via GetPublicKey (lines 39‑49) and calls the corresponding verification routine.

Can authorizers use a different signature algorithm than the initiator?

Yes. The verifyAuthorizerSignature function (lines 20‑42 of pkg/identity/identity.go) reads the algorithm field from the authorizer_public_keys configuration and independently selects Ed25519 or P‑256 verification. This allows mixed-algorithm scenarios where, for example, an initiator uses Ed25519 while authorizers use P‑256.

Where is the P‑256 signing logic implemented?

P‑256 signing is implemented in pkg/encryption/p256.go via the SignWithP256 function, which the LocalSigner calls when configured with EventInitiatorKeyTypeP256. Verification uses encryption.VerifyP256Signature (also in p256.go) invoked by verifyP256 in pkg/identity/identity.go.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →