multi-key 1.0.6

Multikey self-describing cryptographic key data
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
[![](https://img.shields.io/badge/made%20by-Cryptid%20Technologies-gold.svg?style=flat-square)][CRYPTID]
[![](https://img.shields.io/badge/project-provenance-purple.svg?style=flat-square)][PROVENANCE]
[![](https://img.shields.io/badge/project-multiformats-blue.svg?style=flat-square)][MULTIFORMATS]
![](https://github.com/cryptidtech/multi-key/actions/workflows/rust.yml/badge.svg)

# Multi-Key

A Rust implementation of the [multiformats][MULTIFORMATS] [multikey specification][MULTIKEY] and
[nonce specification][NONCE]. The published crate is **`multi-key`** (depend on it as
`multi-key = "1.0"` in `Cargo.toml` and import it as `multi_key` in Rust, e.g.
`use multi_key::Builder;`).

## Current Status

This implementation of the multikey specification supports an extensive set of public key
and secret key cryptography keys spanning 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](#supported-key-formats) section below for the exhaustive
list of codecs.

This implementation supports encrypting and decrypting keys at rest using
ChaCha20-Poly1305 AEAD with keys 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`/`OpenView` with a choice of four AEAD codecs
(ChaCha20-Poly1305, XChaCha20-Poly1305, AES-GCM-128, AES-GCM-256) and HKDF-SHA512 to derive
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 providing verifiable Feldman VSS for ECC keys, gf256
   byte-sharing for RSA and all PQ/hybrid keys, and a dual mode (gf256 + Feldman) for
   Ed25519/X25519.

This crate also supports converting to and from SSH format keys using the
[`ssh-key`][SSHKEY] crate, giving 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 through the [RFC 4251][RFC4251] standard for
"additional algorithms" names using the `@multikey` domain suffix. See the
[SSH Key Conversions](#ssh-key-conversions) section for the full table.

For the technical details of the design of the multikey or nonce format, please 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 to support any kind of cryptographic key in any state
(e.g. encrypted or unencrypted). This format is best thought of as 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, such that software processing these objects does not need to
support all encryption algorithms to accurately calculate the size of the serialized object
and skip over it if needed.

## 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][MULTICODEC] registry and are surfaced as
`multi_codec::Codec` variants.

### Classical Signing

| Algorithm | Codecs | Notes |
|---|---|---|
| Ed25519 | `Ed25519Pub` / `Ed25519Priv` | Ed25519 signatures |
| secp256k1 | `Secp256K1Pub` / `Secp256K1Priv` | ECDSA over secp256k1 |
| NIST P-256 | `P256Pub` / `P256Priv` | ECDSA + ECDH |
| NIST P-384 | `P384Pub` / `P384Priv` | ECDSA + ECDH |
| NIST P-521 | `P521Pub` / `P521Priv` | ECDSA + ECDH |
| RSA-2048 | `Rsa2048Pub` / `Rsa2048Priv` | RSA-SHA256 signatures |
| RSA-3072 | `Rsa3072Pub` / `Rsa3072Priv` | RSA-SHA256 signatures |
| RSA-4096 | `Rsa4096Pub` / `Rsa4096Priv` | RSA-SHA256 signatures |
| BLS12-381 G1 | `Bls12381G1Pub` / `Bls12381G1Priv` | BLS signatures on G1; also a TimeCrypt KEM |
| BLS12-381 G2 | `Bls12381G2Pub` / `Bls12381G2Priv` | BLS signatures on G2; also a TimeCrypt KEM |

### Post-Quantum Signing

| Algorithm | Codecs | Parameter sets |
|---|---|---|
| FN-DSA | `FnDsa512Pub`/`Priv`, `FnDsa1024Pub`/`Priv` | 512, 1024 |
| ML-DSA | `Mldsa65Pub`/`Priv`, `Mldsa87Pub`/`Priv` | 65, 87 |
| MAYO | `Mayo1Pub`/`Priv`, `Mayo2Pub`/`Priv`, `Mayo3Pub`/`Priv`, `Mayo5Pub`/`Priv` | 1, 2, 3, 5 |
| SLH-DSA | `SlhdsaSha2128FPub`/`Priv`, `SlhdsaSha2128SPub`/`Priv`, `SlhdsaSha2192FPub`/`Priv`, `SlhdsaSha2192SPub`/`Priv`, `SlhdsaSha2256FPub`/`Priv`, `SlhdsaSha2256SPub`/`Priv`, `SlhdsaShake128FPub`/`Priv`, `SlhdsaShake128SPub`/`Priv`, `SlhdsaShake192FPub`/`Priv`, `SlhdsaShake192SPub`/`Priv`, `SlhdsaShake256FPub`/`Priv`, `SlhdsaShake256SPub`/`Priv` | 12 sets: SHA-2/SHAKE × 128/192/256 × F/S |

### KEMs / Key Agreement

| Algorithm | Codecs | Notes |
|---|---|---|
| X25519 | `X25519Pub` / `X25519Priv` | ECDH; returns ephemeral public key from `seal` |
| ML-KEM | `Mlkem768Pub`/`Priv`, `Mlkem1024Pub`/`Priv` | 768, 1024 |
| sntrup | `Sntrup761Pub`/`Priv`, `Sntrup857Pub`/`Priv`, `Sntrup953Pub`/`Priv`, `Sntrup1013Pub`/`Priv`, `Sntrup1277Pub`/`Priv` | 761, 857, 953, 1013, 1277 |
| Classic McEliece | `Mceliece348864Pub` / `Mceliece348864Priv` | 348864 |
| FrodoKEM | `FrodoKem640AesPub`/`Priv`, `FrodoKem976AesPub`/`Priv`, `FrodoKem1344AesPub`/`Priv`, `FrodoKem640ShakePub`/`Priv`, `FrodoKem976ShakePub`/`Priv`, `FrodoKem1344ShakePub`/`Priv` | 640/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)

| Hybrid | Codecs | Components |
|---|---|---|
| Ed25519-MAYO2 | `Ed25519Mayo2Pub` / `Ed25519Mayo2Priv` | Ed25519 + MAYO-2 |
| Ed25519-ML-DSA-65 | `Ed25519Mldsa65Pub` / `Ed25519Mldsa65Priv` | Ed25519 + ML-DSA-65 |
| Ed25519-FN-DSA-512 | `Ed25519Fndsa512Pub` / `Ed25519Fndsa512Priv` | Ed25519 + FN-DSA-512 |
| BLS12-381-G1-ML-DSA-65 | `Bls12381G1Mldsa65Pub` / `Bls12381G1Mldsa65Priv` | BLS G1 + ML-DSA-65 |
| BLS12-381-G1-FN-DSA-512 | `Bls12381G1Fndsa512Pub` / `Bls12381G1Fndsa512Priv` | BLS G1 + FN-DSA-512 |
| BLS12-381-G1-MAYO-1 | `Bls12381G1Mayo1Pub` / `Bls12381G1Mayo1Priv` | BLS G1 + MAYO-1 |
| BLS12-381-G1-MAYO-2 | `Bls12381G1Mayo2Pub` / `Bls12381G1Mayo2Priv` | BLS G1 + MAYO-2 |

### Hybrid KEMs (Classical + Post-Quantum)

| Hybrid | Codecs | Components |
|---|---|---|
| X25519-sntrup761 | `X25519Sntrup761Pub` / `X25519Sntrup761Priv` | X25519 + sntrup761 |
| X25519-ML-KEM-768 | `X25519Mlkem768Pub` / `X25519Mlkem768Priv` | X25519 + ML-KEM-768 |
| X25519-FrodoKEM-640 | `X25519Frodokem640AesPub`/`Priv`, `X25519Frodokem640ShakePub`/`Priv` | X25519 + FrodoKEM-640 (AES/SHAKE) |
| X25519-McEliece-348864 | `X25519Mceliece348864Pub` / `X25519Mceliece348864Priv` | X25519 + Classic McEliece 348864 |

### Threshold Key Shares

| Mechanism | Codecs | Notes |
|---|---|---|
| BLS12-381 Shamir shares | `Bls12381G1PubShare`/`PrivShare`, `Bls12381G2PubShare`/`PrivShare` | Split/combine via `ThresholdView`; threshold sign/verify |
| DKG threshold shares | `Ed25519ThreshPubShare`/`PrivShare`, `P256ThreshPubShare`/`PrivShare`, `P384ThreshPubShare`/`PrivShare`, `Secp256K1ThreshPubShare`/`PrivShare`, `Bls12381ThreshPubShare`/`PrivShare`, `Ed448ThreshPubShare`/`PrivShare` | DKG metadata via `ThresholdKeyView`; authenticated marker (TSIG-1) |
| Generic `keysplit` shares | `KeySplitShare` | Feldman VSS (ECC), gf256 byte-sharing (RSA + PQ + hybrids), dual mode (Ed25519/X25519) |

### Symmetric

| Algorithm | Codec | Notes |
|---|---|---|
| ChaCha20-Poly1305 | `Chacha20Poly1305` | Used 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
provides "views" on the Multi-Key data. These are read-only abstract interfaces to the
Multi-Key attributes with implementations for different supporting algorithms.

Currently the set of views provides generic access to the general attributes
(`multi_key::AttrView`) of the Multi-Key, the key data (`multi_key::DataView`), as well as
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/decrypting a Multi-Key at rest,
- a conversion view (`multi_key::ConvView`) for converting the Multi-Key to other formats
  (e.g. to/from SSH key format, and secret keys to public keys),
- a fingerprint view (`multi_key::FingerprintView`) for getting a key fingerprint using a
  given hashing codec,
- a KDF view (`multi_key::KdfView`) for generating cipher keys for use by a cipher view to
  encrypt/decrypt the Multi-Key,
- a seal view (`multi_key::SealView`) and open view (`multi_key::OpenView`) for KEM-based
  message encryption/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`][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`/`verify_share` functions.
- `multi_key::threshold_marker` — DKG marker stamping/reading and TSIG-1 marker
  authentication, including the `MarkerView` trait and `threshold_kind`/`threshold_params`
  helpers.

It is important to note that the operations that seem to mutate the Multi-Key (e.g. encrypt,
decrypt, convert, etc.) in fact do a copy-on-write (CoW) operation and return a new
Multi-Key with the mutation applied.

## SSH Key Conversions

This crate converts to and from the SSH key format using the [`ssh-key`][SSHKEY] crate.
Standard SSH algorithms are handled natively; non-standard algorithms use the [RFC 4251][RFC4251]
"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)

| Algorithm | SSH algorithm name |
|---|---|
| Ed25519 | `ssh-ed25519` |
| ECDSA P-256 | `ecdsa-sha2-nistp256` |
| ECDSA P-384 | `ecdsa-sha2-nistp384` |
| ECDSA P-521 | `ecdsa-sha2-nistp521` |

### Custom `@multikey` algorithms (opaque SSH keys)

| Algorithm | SSH algorithm name |
|---|---|
| secp256k1 | `secp256k1@multikey` |
| BLS12-381 G1 | `bls12_381-g1@multikey` |
| BLS12-381 G1 share | `bls12_381-g1-share@multikey` |
| BLS12-381 G2 | `bls12_381-g2@multikey` |
| BLS12-381 G2 share | `bls12_381-g2-share@multikey` |
| RSA-2048/3072/4096 | `rsa-sha256@multikey` |
| ML-DSA-65 | `ml-dsa-65@multikey` |
| ML-DSA-87 | `ml-dsa-87@multikey` |
| FN-DSA-512 | `fn-dsa-512@multikey` |
| FN-DSA-1024 | `fn-dsa-1024@multikey` |
| MAYO-1 | `mayo-1@multikey` |
| MAYO-2 | `mayo-2@multikey` |
| MAYO-3 | `mayo-3@multikey` |
| MAYO-5 | `mayo-5@multikey` |
| SLH-DSA SHA-2 128f | `slh-dsa-sha2-128f@multikey` |
| SLH-DSA SHA-2 128s | `slh-dsa-sha2-128s@multikey` |
| SLH-DSA SHA-2 192f | `slh-dsa-sha2-192f@multikey` |
| SLH-DSA SHA-2 192s | `slh-dsa-sha2-192s@multikey` |
| SLH-DSA SHA-2 256f | `slh-dsa-sha2-256f@multikey` |
| SLH-DSA SHA-2 256s | `slh-dsa-sha2-256s@multikey` |
| SLH-DSA SHAKE 128f | `slh-dsa-shake-128f@multikey` |
| SLH-DSA SHAKE 128s | `slh-dsa-shake-128s@multikey` |
| SLH-DSA SHAKE 192f | `slh-dsa-shake-192f@multikey` |
| SLH-DSA SHAKE 192s | `slh-dsa-shake-192s@multikey` |
| SLH-DSA SHAKE 256f | `slh-dsa-shake-256f@multikey` |
| SLH-DSA SHAKE 256s | `slh-dsa-shake-256s@multikey` |

The import direction (`Builder::new_from_ssh_public_key` /
`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` / `Bls12381G2PrivShare` shares using `blsful`'s `SecretKey::split`.
Shares are recombined with `combine`, and threshold signing/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` / `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`) producing `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`, applicable to
BLS12-381 Shamir shares and the generic `keysplit` module.

### Disclosure Modes

| Mode | `t` (threshold) | `n` (limit) | Who sees `t` | Who sees `n` |
|---|---|---|---|---|
| `Full` (default, 0) | plaintext attribute | plaintext attribute | everyone | everyone |
| `Partial` (1) | encrypted (AEAD) | plaintext attribute | key-holder only | everyone (auditable) |
| `FullConfidentialial` (2) | encrypted (AEAD) | encrypted (AEAD) | key-holder only | key-holder only |

The encrypted values are sealed with **ChaCha20-Poly1305 AEAD** and stored as a CBOR-encoded
`ThresholdMetadata` blob in `AttrId::EncryptedThresholdMeta`. The cipher parameters (codec +
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/decrypt the metadata.

### When to Use Each Mode

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

- **`Partial`** — Use when the total number of participants `n` should be auditable (e.g. 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` but `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

| Consideration | Full | Partial | FullConfidentialial |
|---|---|---|---|
| Backward compatible | yes | yes (attribute defaults to Full if absent) | yes |
| Observer learns `t` | yes | no | no |
| Observer learns `n` | yes | yes | no |
| Requires `meta_key` | no | for reading `t` | for reading `t` and `n` |
| Auditable `n` | yes | yes | no |
| Risk if `meta_key` lost | n/a | `t` irrecoverable | `t` and `n` irrecoverable |
| Performance overhead | none | negligible (AEAD on ~10 bytes) | negligible |

**Key management risk:** Losing the `meta_key` makes `t` (Partial) or both `t`/`n`
(FullConfidentialial) irrecoverable, preventing key combination. The `meta_key` should be
stored/backed up using the existing at-rest encryption mechanisms. You can always convert back
to `Full` mode (with the `meta_key`) before losing it.

**DKG note:** DKG threshold values (`t`/`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 don't know `t`, but 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()`:**

```rust
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 disclosure
let shares = mk.threshold_view()?.split_with_disclosure(3, 5,
    ThresholdDisclosure::FullConfidentialial, Some(&meta_mk))?;
```

**2. Builder construction:**

```rust
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:**

```rust
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`:

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

### Combining Encrypted Shares

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

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

```rust
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`/`n`
(decrypting if needed with `current_meta_key`), then re-stamps the attributes in the target mode
(encrypting if needed with `meta_key`):

```rust
// Full → Partial
let partial = full.disclosure_view()?
    .to_disclosure(ThresholdDisclosure::Partial, Some(&meta_mk), None)?;

// Partial → FullConfidentialial
let confidential = partial.disclosure_view()?
    .to_disclosure(ThresholdDisclosure::FullConfidentialial, Some(&meta_mk), Some(&meta_mk))?;

// FullConfidentialial → Full
let 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 using **ChaCha20-Poly1305** AEAD (`CipherView`) with the
cipher key 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` / `OpenView`. The KEM shared secret is
expanded into an AEAD key via **HKDF-SHA512**, then one of four AEAD codecs may be used:

| AEAD codec | Key size | Nonce size |
|---|---|---|
| `Chacha20Poly1305` | 32 bytes | 12 bytes |
| `Xchacha20Poly1305` | 32 bytes | 24 bytes |
| `AesGcm128` | 16 bytes | 12 bytes |
| `AesGcm256` | 32 bytes | 12 bytes |

Individual KEM views may restrict the allowed AEAD codec (e.g. X25519-ML-KEM-768 only
permits `Chacha20Poly1305` per its specification).

## Cargo Features

| Feature | Default | Description |
|---|---|---|
| `serde` | yes | Serde serialization for `Multikey`, `KeyShare`, `SharePayload`, `ThresholdParticipant` |
| `wasm` | no | WebAssembly 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, returning a new `Multi-Key` rather than
  mutating in place.

## Links

- [Cryptid Technologies][CRYPTID]
- [Provenance Specifications][PROVENANCE]
- [Multiformats][MULTIFORMATS]
- [Multikey Specification][MULTIKEY]
- [Nonce Specification][NONCE]
- [Multisig][MULTISIG]
- [`ssh-key` crate][SSHKEY]
- [RFC 4251][RFC4251]

[CRYPTID]: https://cryptid.tech
[PROVENANCE]: https://github.com/cryptidtech/provenance-specifications/
[MULTIFORMATS]: https://github.com/multiformats/multiformats
[MULTIKEY]: https://github.com/cryptidtech/provenance-specifications/blob/main/specifications/multikey.md
[NONCE]: https://github.com/cryptidtech/provenance-specifications/blob/main/specifications/nonce.md
[MULTICODEC]: https://github.com/multiformats/multicodec
[SSHKEY]: https://crates.io/crates/ssh-key
[RFC4251]: https://www.rfc-editor.org/rfc/rfc4251.html#page-11
[MULTISIG]: https://github.com/cryptidtech/provenance-specifications/blob/main/specifications/multisig.md