Skip to content

Repository files navigation

Multi-Key

A Rust implementation of the multiformatsmultikey specification and nonce specification. The published crate is multi-key. Depend on it as multi-key = "1.0" in Cargo.toml. Import it as multi_key in Rust, for example use multi_key::Builder;.

Current Status

This implementation of the multikey specification supports an extensive set of public key and secret key cryptography keys. These span classical, post-quantum, and hybrid schemes:

  • Classical signing — Ed25519, secp256k1, NIST P-256/P-384/P-521, RSA-2048/3072/4096, and BLS12-381 G1/G2.
  • Post-quantum signing — FN-DSA, ML-DSA, MAYO, and SLH-DSA (all parameter sets).
  • Key encapsulation / key agreement — X25519, ML-KEM, sntrup, Classic McEliece, FrodoKEM, and the BLS12-381 TimeCrypt pairing-based KEM.
  • Hybrid signing — combinations of Ed25519 or BLS12-381 G1 with a PQ signing scheme.
  • Hybrid KEMs — combinations of X25519 with a PQ KEM.
  • Secret-key / symmetric — ChaCha20-Poly1305 keys.

See the Supported Key Formats section below for the exhaustive list of codecs.

This implementation supports encrypting and decrypting keys at rest with ChaCha20-Poly1305 AEAD. The keys are derived via the bcrypt PBKDF from a preimage. A legacy bare-ChaCha20 fallback is honored on decrypt so older keystores continue to work. Re-encrypting upgrades them to the authenticated AEAD format.

KEM-based message encryption uses SealView and OpenView with a choice of four AEAD codecs (ChaCha20-Poly1305, XChaCha20-Poly1305, AES-GCM-128, AES-GCM-256). HKDF-SHA512 derives the AEAD key from the KEM shared secret.

For threshold cryptography, this implementation supports three mechanisms:

  1. BLS12-381 Shamir splitting of G1/G2 keys, including threshold signing and verifying.
  2. Distributed Key Generation (DKG) threshold shares for Ed25519, P-256, P-384, secp256k1, BLS12-381, and Ed448, with an authenticated threshold marker bundle (TSIG-1).
  3. A generic keysplit module that provides verifiable Feldman VSS for ECC keys, gf256 byte-sharing for RSA and all PQ and hybrid keys, and a dual mode (gf256 + Feldman) for Ed25519 and X25519.

This crate also supports converting to and from SSH format keys with the ssh-key crate. This gives full OpenSSH compatibility for reading OpenSSH serialized keys and converting them to Multi-Key format. This includes non-standard SSH key protocols such as secp256k1 and BLS12-381 G1/G2 keys. These use the RFC 4251 standard for "additional algorithms" names with the @multikey domain suffix. See the SSH Key Conversions section for the full table.

For the technical details of the design of the multikey or nonce format, refer to the specifications linked above.

Introduction

This is a Rust implementation of a multicodec format for cryptographic keys. The design of the format is intentionally abstract. It supports any kind of cryptographic key in any state (for example, encrypted or unencrypted). The format is a container of key material with abstract, algorithm-specific views and a generic, self-describing data storage format.

Every piece of data in the serialized Multi-Key object either has a known fixed size or a self-describing variable size. Software that processes these objects does not need to support all encryption algorithms to calculate the size of the serialized object and skip over it.

Supported Key Formats

The tables below enumerate every key codec supported by this crate. Each algorithm has Pub (public key) and Priv (private key) variants unless otherwise noted. The codec identifiers come from the multicodec registry and are surfaced as multi_codec::Codec variants.

Classical Signing

AlgorithmCodecsNotes
Ed25519Ed25519Pub / Ed25519PrivEd25519 signatures
secp256k1Secp256K1Pub / Secp256K1PrivECDSA over secp256k1
NIST P-256P256Pub / P256PrivECDSA + ECDH
NIST P-384P384Pub / P384PrivECDSA + ECDH
NIST P-521P521Pub / P521PrivECDSA + ECDH
RSA-2048Rsa2048Pub / Rsa2048PrivRSA-SHA256 signatures
RSA-3072Rsa3072Pub / Rsa3072PrivRSA-SHA256 signatures
RSA-4096Rsa4096Pub / Rsa4096PrivRSA-SHA256 signatures
BLS12-381 G1Bls12381G1Pub / Bls12381G1PrivBLS signatures on G1; also a TimeCrypt KEM
BLS12-381 G2Bls12381G2Pub / Bls12381G2PrivBLS signatures on G2; also a TimeCrypt KEM

Post-Quantum Signing

AlgorithmCodecsParameter sets
FN-DSAFnDsa512Pub/Priv, FnDsa1024Pub/Priv512, 1024
ML-DSAMldsa65Pub/Priv, Mldsa87Pub/Priv65, 87
MAYOMayo1Pub/Priv, Mayo2Pub/Priv, Mayo3Pub/Priv, Mayo5Pub/Priv1, 2, 3, 5
SLH-DSASlhdsaSha2128FPub/Priv, SlhdsaSha2128SPub/Priv, SlhdsaSha2192FPub/Priv, SlhdsaSha2192SPub/Priv, SlhdsaSha2256FPub/Priv, SlhdsaSha2256SPub/Priv, SlhdsaShake128FPub/Priv, SlhdsaShake128SPub/Priv, SlhdsaShake192FPub/Priv, SlhdsaShake192SPub/Priv, SlhdsaShake256FPub/Priv, SlhdsaShake256SPub/Priv12 sets: SHA-2/SHAKE × 128/192/256 × F/S

KEMs / Key Agreement

AlgorithmCodecsNotes
X25519X25519Pub / X25519PrivECDH; returns ephemeral public key from seal
ML-KEMMlkem768Pub/Priv, Mlkem1024Pub/Priv768, 1024
sntrupSntrup761Pub/Priv, Sntrup857Pub/Priv, Sntrup953Pub/Priv, Sntrup1013Pub/Priv, Sntrup1277Pub/Priv761, 857, 953, 1013, 1277
Classic McElieceMceliece348864Pub / Mceliece348864Priv348864
FrodoKEMFrodoKem640AesPub/Priv, FrodoKem976AesPub/Priv, FrodoKem1344AesPub/Priv, FrodoKem640ShakePub/Priv, FrodoKem976ShakePub/Priv, FrodoKem1344ShakePub/Priv640/976/1344 × AES/SHAKE
BLS12-381 TimeCrypt(uses the G1/G2 codecs above)Pairing-based KEM built into the BLS views

Hybrid Signing (Classical + Post-Quantum)

HybridCodecsComponents
Ed25519-MAYO2Ed25519Mayo2Pub / Ed25519Mayo2PrivEd25519 + MAYO-2
Ed25519-ML-DSA-65Ed25519Mldsa65Pub / Ed25519Mldsa65PrivEd25519 + ML-DSA-65
Ed25519-FN-DSA-512Ed25519Fndsa512Pub / Ed25519Fndsa512PrivEd25519 + FN-DSA-512
BLS12-381-G1-ML-DSA-65Bls12381G1Mldsa65Pub / Bls12381G1Mldsa65PrivBLS G1 + ML-DSA-65
BLS12-381-G1-FN-DSA-512Bls12381G1Fndsa512Pub / Bls12381G1Fndsa512PrivBLS G1 + FN-DSA-512
BLS12-381-G1-MAYO-1Bls12381G1Mayo1Pub / Bls12381G1Mayo1PrivBLS G1 + MAYO-1
BLS12-381-G1-MAYO-2Bls12381G1Mayo2Pub / Bls12381G1Mayo2PrivBLS G1 + MAYO-2

Hybrid KEMs (Classical + Post-Quantum)

HybridCodecsComponents
X25519-sntrup761X25519Sntrup761Pub / X25519Sntrup761PrivX25519 + sntrup761
X25519-ML-KEM-768X25519Mlkem768Pub / X25519Mlkem768PrivX25519 + ML-KEM-768
X25519-FrodoKEM-640X25519Frodokem640AesPub/Priv, X25519Frodokem640ShakePub/PrivX25519 + FrodoKEM-640 (AES/SHAKE)
X25519-McEliece-348864X25519Mceliece348864Pub / X25519Mceliece348864PrivX25519 + Classic McEliece 348864

Threshold Key Shares

MechanismCodecsNotes
BLS12-381 Shamir sharesBls12381G1PubShare/PrivShare, Bls12381G2PubShare/PrivShareSplit/combine via ThresholdView; threshold sign/verify
DKG threshold sharesEd25519ThreshPubShare/PrivShare, P256ThreshPubShare/PrivShare, P384ThreshPubShare/PrivShare, Secp256K1ThreshPubShare/PrivShare, Bls12381ThreshPubShare/PrivShare, Ed448ThreshPubShare/PrivShareDKG metadata via ThresholdKeyView; authenticated marker (TSIG-1)
Generic keysplit sharesKeySplitShareFeldman VSS (ECC), gf256 byte-sharing (RSA + PQ + hybrids), dual mode (Ed25519/X25519)

Symmetric

AlgorithmCodecNotes
ChaCha20-Poly1305Chacha20Poly1305Used both for at-rest Multi-Key encryption and as a symmetric key codec

Views on the Multi-Key Data

To provide an abstract interface to cryptographic keys for all algorithms, this crate gives "views" on the Multi-Key data. These are read-only abstract interfaces to the Multi-Key attributes with implementations for different supporting algorithms.

The set of views provides generic access to the general attributes (multi_key::AttrView) of the Multi-Key, the key data (multi_key::DataView), and views on the KDF attributes (multi_key::KdfAttrView) and cipher attributes (multi_key::CipherAttrView) for encrypted Multi-Keys. For algorithms that support threshold operations, there is a threshold attributes view (multi_key::ThresholdAttrView) and a higher-level DKG metadata view (multi_key::ThresholdKeyView).

For operations you can do with a Multi-Key, there is:

  • a cipher view (multi_key::CipherView) for encrypting and decrypting a Multi-Key at rest,
  • a conversion view (multi_key::ConvView) for converting the Multi-Key to other formats (for example, to and from SSH key format, and secret keys to public keys),
  • a fingerprint view (multi_key::FingerprintView) for getting a key fingerprint with a given hashing codec,
  • a KDF view (multi_key::KdfView) for generating cipher keys for use by a cipher view to encrypt or decrypt the Multi-Key,
  • a seal view (multi_key::SealView) and open view (multi_key::OpenView) for KEM-based message encryption and decryption,
  • a threshold view (multi_key::ThresholdView) for key splitting and combining keys,
  • a sign view (multi_key::SignView) and verify view (multi_key::VerifyView) for creating and verifying Multisig digital signatures.

Two additional modules provide threshold functionality outside the view traits:

  • multi_key::keysplit — generic verifiable threshold key splitting (Feldman VSS, gf256, dual) exposed as free split, combine, and verify_share functions.
  • multi_key::threshold_marker — DKG marker stamping and reading and TSIG-1 marker authentication, including the MarkerView trait and threshold_kind and threshold_params helpers.

Operations that seem to mutate the Multi-Key (for example, encrypt, decrypt, convert) in fact do a copy-on-write (CoW) operation. They return a new Multi-Key with the mutation applied.

SSH Key Conversions

This crate converts to and from the SSH key format with the ssh-key crate. Standard SSH algorithms are handled natively. Non-standard algorithms use the RFC 4251 "additional algorithms" mechanism with an ssh_key::Algorithm::Other opaque key and an algorithm name ending in the literal @multikey suffix. This is a wire-format identifier, distinct from the crate name.

Native SSH algorithms (no @multikey suffix)

AlgorithmSSH algorithm name
Ed25519ssh-ed25519
ECDSA P-256ecdsa-sha2-nistp256
ECDSA P-384ecdsa-sha2-nistp384
ECDSA P-521ecdsa-sha2-nistp521

Custom @multikey algorithms (opaque SSH keys)

AlgorithmSSH algorithm name
secp256k1secp256k1@multikey
BLS12-381 G1bls12_381-g1@multikey
BLS12-381 G1 sharebls12_381-g1-share@multikey
BLS12-381 G2bls12_381-g2@multikey
BLS12-381 G2 sharebls12_381-g2-share@multikey
RSA-2048/3072/4096rsa-sha256@multikey
ML-DSA-65ml-dsa-65@multikey
ML-DSA-87ml-dsa-87@multikey
FN-DSA-512fn-dsa-512@multikey
FN-DSA-1024fn-dsa-1024@multikey
MAYO-1mayo-1@multikey
MAYO-2mayo-2@multikey
MAYO-3mayo-3@multikey
MAYO-5mayo-5@multikey
SLH-DSA SHA-2 128fslh-dsa-sha2-128f@multikey
SLH-DSA SHA-2 128sslh-dsa-sha2-128s@multikey
SLH-DSA SHA-2 192fslh-dsa-sha2-192f@multikey
SLH-DSA SHA-2 192sslh-dsa-sha2-192s@multikey
SLH-DSA SHA-2 256fslh-dsa-sha2-256f@multikey
SLH-DSA SHA-2 256sslh-dsa-sha2-256s@multikey
SLH-DSA SHAKE 128fslh-dsa-shake-128f@multikey
SLH-DSA SHAKE 128sslh-dsa-shake-128s@multikey
SLH-DSA SHAKE 192fslh-dsa-shake-192f@multikey
SLH-DSA SHAKE 192sslh-dsa-shake-192s@multikey
SLH-DSA SHAKE 256fslh-dsa-shake-256f@multikey
SLH-DSA SHAKE 256sslh-dsa-shake-256s@multikey

The import direction (Builder::new_from_ssh_public_key and Builder::new_from_ssh_private_key) supports all of the algorithms above.

Key types that do not support SSH conversion

All KEM-only and hybrid key types explicitly reject SSH conversion and return UnsupportedAlgorithm. These include X25519, ML-KEM, all sntrup sizes, Classic McEliece, all FrodoKEM variants, the BLS12-381 TimeCrypt KEM, and all hybrid signing and hybrid KEM schemes.

Threshold Operations

BLS12-381 Shamir Splitting

ThresholdView::split(threshold, limit) splits a Bls12381G1Priv or Bls12381G2Priv into Bls12381G1PrivShare or Bls12381G2PrivShare shares with blsful's SecretKey::split. Shares are recombined with combine. Threshold signing and verifying is supported on the share codecs. Requires 2 <= threshold <= limit <= 255.

DKG Threshold Shares

The DKG share codecs (Ed25519Thresh*, P256Thresh*, P384Thresh*, Secp256K1Thresh*, Bls12381Thresh*, Ed448Thresh*) carry DKG metadata attributes (DkgThreshold, DkgLimit, DkgIdentifier, DkgGroupPublicKey, DkgOwnerId). The ThresholdKeyView trait exposes group_pubkey(), is_threshold_key(), participant_count(), threshold(), and owner_vlad(). The threshold_marker module stamps and authenticates a marker bundle (TSIG-1) with a controller signing key via sign_marker and verify_marker.

Generic keysplit Module

multi_key::keysplit provides scheme-aware verifiable threshold splitting for any key type as free functions (split, combine, verify_share) that produce KeySplitShare Multi-Keys:

  • Feldman VSS — secp256k1, P-256/P-384/P-521, BLS12-381 G1/G2 (verifiable, with commitments).
  • gf256 byte-sharing — RSA and all PQ families (ML-DSA, ML-KEM, SLH-DSA, FN-DSA, MAYO, sntrup, FrodoKEM, Classic McEliece) and all hybrids.
  • Dual mode — Ed25519 and X25519: a gf256 share of the 32-byte seed (exact restore) plus a Feldman scalar share (threshold-signing-ready).

Threshold Confidentiality

By default, threshold t and share count n are stored as plaintext attributes on every key share. Any observer of a share learns the threshold parameters. This crate supports three configurable disclosure modes that control the confidentiality of t and n. These apply to BLS12-381 Shamir shares and the generic keysplit module.

Disclosure Modes

Modet (threshold)n (limit)Who sees tWho sees n
Full (default, 0)plaintext attributeplaintext attributeeveryoneeveryone
Partial (1)encrypted (AEAD)plaintext attributekey-holder onlyeveryone (auditable)
FullConfidentialial (2)encrypted (AEAD)encrypted (AEAD)key-holder onlykey-holder only

The encrypted values are sealed with ChaCha20-Poly1305 AEAD. They are stored as a CBOR-encoded ThresholdMetadata blob in AttrId::EncryptedThresholdMeta. The cipher parameters (codec and nonce) are recorded in AttrId::ThresholdMetaCipher so the blob is self-describing for decryption. A separate meta key (a 32-byte symmetric Multikey with Codec::Chacha20Poly1305) is required to encrypt and decrypt the metadata.

When to Use Each Mode

  • Full — Use when t and n are not sensitive. This is the default. It is backward-compatible with all existing shares. It is appropriate for open governance systems where the threshold structure is public knowledge.

  • Partial — Use when the total number of participants n should be auditable (for example, for governance transparency) but the threshold t should be hidden from share holders and observers. Hiding t means an adversary who compromises some shares does not know how many more they need to reconstruct the key. The meta_key is required to read t. The value n is freely readable.

  • FullConfidentialial — Use when both t and n must be kept secret. An observer who sees a share cannot determine the group size or how many shares are needed. This is the strongest confidentiality mode. The meta_key is required to read both t and n.

Trade-offs

ConsiderationFullPartialFullConfidentialial
Backward compatibleyesyes (attribute defaults to Full if absent)yes
Observer learns tyesnono
Observer learns nyesyesno
Requires meta_keynofor reading tfor reading t and n
Auditable nyesyesno
Risk if meta_key lostn/at irrecoverablet and n irrecoverable
Performance overheadnonenegligible (AEAD on ~10 bytes)negligible

Key management risk. Losing the meta_key makes t (Partial) or both t and n (FullConfidentialial) irrecoverable. This prevents key combination. The meta_key should be stored or backed up with the existing at-rest encryption mechanisms. You can always convert back to Full mode (with the meta_key) before you lose it.

DKG note. DKG threshold values (t and n) are inherently known to all participants because they are agreed during the DKG ceremony. The confidentiality modes do not apply to DKG shares. A to_disclosure() call on a DKG share returns an error. Future work could add "hidden threshold DKG" where participants do not know t. That requires protocol-level changes (FROST-style), not just encoding changes.

Creating Shares with a Disclosure Mode

There are three ways to produce shares in a given disclosure mode:

1. Direct creation via split_with_disclosure():

use multi_key::{Builder,Views,ThresholdDisclosure};let meta_key = multi_key::generate_meta_key();let meta_mk = Builder::new(Codec::Chacha20Poly1305).with_key_bytes(&meta_key.as_slice()).try_build()?;// BLS Shamir split with FullConfidentialial disclosurelet shares = mk.threshold_view()?.split_with_disclosure(3,5,ThresholdDisclosure::FullConfidentialial,Some(&meta_mk))?;

2. Builder construction:

let share = Builder::new(Codec::Bls12381G2PrivShare).with_disclosure(ThresholdDisclosure::Partial,Some(&meta_mk),3,5).with_identifier(&identifier).with_key_bytes(&key_bytes).try_build()?;

3. Convert an existing share:

let encrypted = share.disclosure_view()?
.to_disclosure(ThresholdDisclosure::FullConfidentialial,Some(&meta_mk),None)?;

Reading Threshold Parameters from Encrypted Shares

Use read_threshold_params() with the meta_key to decrypt t and n:

let(t, n) = encrypted.disclosure_view()?
.read_threshold_params(Some(&meta_mk))?;

Combining Encrypted Shares

let combined = mk.threshold_view()?
.combine_with_meta(Some(&meta_mk))?;

For the generic keysplit module, use split_with_disclosure() and combine_with_meta():

use multi_key::keysplit;let shares = keysplit::split_with_disclosure(&mk,3,5,ThresholdDisclosure::Partial,Some(&meta_mk), rand::rng())?;let combined = keysplit::combine_with_meta(&shares,Some(&meta_mk))?;

Converting Between Modes

The to_disclosure() method converts between any pair of modes. It reads the current t and n (decrypting if needed with current_meta_key), then re-stamps the attributes in the target mode (encrypting if needed with meta_key):

// Full → Partiallet partial = full.disclosure_view()?
.to_disclosure(ThresholdDisclosure::Partial,Some(&meta_mk),None)?;// Partial → FullConfidentialiallet confidential = partial.disclosure_view()?
.to_disclosure(ThresholdDisclosure::FullConfidentialial,Some(&meta_mk),Some(&meta_mk))?;// FullConfidentialial → Fulllet full_again = confidential.disclosure_view()?
.to_disclosure(ThresholdDisclosure::Full,None,Some(&meta_mk))?;

Encryption

At-Rest Multi-Key Encryption

Multi-Keys can be encrypted at rest with ChaCha20-Poly1305 AEAD (CipherView). The cipher key is derived from a preimage via the bcrypt PBKDF (KdfView, 32-byte salt, configurable rounds). A legacy bare-ChaCha20 fallback is honored on decrypt so keystores encrypted before AEAD was added continue to work. Re-encrypting upgrades them to the authenticated format.

KEM Seal / Open

KEM-based message encryption uses SealView and OpenView. The KEM shared secret is expanded into an AEAD key via HKDF-SHA512. Then one of four AEAD codecs may be used:

AEAD codecKey sizeNonce size
Chacha20Poly130532 bytes12 bytes
Xchacha20Poly130532 bytes24 bytes
AesGcm12816 bytes12 bytes
AesGcm25632 bytes12 bytes

Individual KEM views may restrict the allowed AEAD codec. For example, X25519-ML-KEM-768 only permits Chacha20Poly1305 per its specification.

Cargo Features

FeatureDefaultDescription
serdeyesSerde serialization for Multikey, KeyShare, SharePayload, ThresholdParticipant
wasmnoWebAssembly support via getrandom/wasm_js; switches blsful to the rust backend and ssh-key to ecdsa/ed25519/p256/p384/p521 features on wasm32

Security

  • Private keys are wrapped in Zeroizing buffers and automatically zeroized on drop.
  • Debug output for private key material is redacted.
  • All views are thread-safe (Send + Sync) for concurrent operations.
  • Mutation operations use copy-on-write semantics. They return a new Multi-Key rather than mutating in place.

Links

About

Multiformats cryptographic key handling

Resources

Security policy

Stars

8 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages