> ## Documentation Index
> Fetch the complete documentation index at: https://docs.algovoi.co.uk/llms.txt
> Use this file to discover all available pages before exploring further.

# Bring Your Own Keys

> Hold your AlgoVoi signing and encryption keys in your own HSM, KMS or Vault. Custody is pluggable and signed output is byte identical, so verify is too.

The enterprise question this answers: *"can we keep the signing key in our own HSM / KMS / Vault, so
AlgoVoi never holds it — without changing how the evidence verifies?"* Yes.

Key custody is **pluggable**, exactly like the [storage backend](/verifiable-archive). You pass a
`Signer`; AlgoVoi calls it to sign each receipt and never touches the raw private key. And it is
**additive**: the signed envelope is byte-for-byte the same shape regardless of where the key lives, so
the [free verifier](/rfc9421-verifier), the conformance vectors, and your existing receipts all keep
verifying with zero changes. Custody changes *where the key sits*, never *the evidence format*.

<Info>
  **Included with [Records Vault](/records-vault)** — BYO-key custody (HSM / KMS / Vault) ships in the
  bundle, no separate purchase. And the whole stack — engine, the KMS/HSM/Vault adapters, and every
  dependency — installs **entirely from the AlgoVoi private index with no PyPI**, so **air-gapped
  deployments are first-class**: one licence token, zero external network.
</Info>

## Custody modes

| Mode            | Where the private key lives                                           | Use when                                                        |
| --------------- | --------------------------------------------------------------------- | --------------------------------------------------------------- |
| **Local**       | In-process (the default, bundled)                                     | Air-gapped or single-node deployments                           |
| **KMS-wrapped** | Wrapped (encrypted) by a key-encryption-key in your KMS / HSM / Vault | You want external custody **today** — works with every provider |
| **Native HSM**  | Never leaves the HSM (PKCS#11)                                        | Your HSM signs the algorithm natively                           |

Most enterprises use **KMS-wrapped**: the Falcon-1024 key is stored encrypted by a key-encryption-key
(KEK) that lives in your KMS/HSM and never leaves it. At sign time the key is unwrapped into memory,
the receipt is signed, and the buffer is zeroized — the plaintext key is never persisted. This works
with any provider right now because the KMS only performs a classical unwrap, not the post-quantum
signing operation.

## The interface

A `Signer` is four methods:

```python theme={null}
class Signer(Protocol):
    def public_key(self) -> bytes: ...
    def kid(self) -> str: ...
    def sign(self, message: bytes) -> bytes: ...
    def can_sign(self) -> bool: ...
```

You hand a `Signer` to the archive, and it threads automatically through every Records Vault evidence
stream (access log, timestamps, legal hold, and the rest):

```python theme={null}
from algovoi_doc_archive.archive import Archive
from algovoi_records_vault import RecordsVault

archive = Archive(signer=my_signer, storage=my_storage, encryptor=my_encryptor)
vault = RecordsVault(archive=archive)   # all streams now sign through my_signer
```

## Local custody (default)

```python theme={null}
from algovoi_doc_archive.signer import LocalSigner

signer = LocalSigner(public_key, secret_key)   # public-key-only = read-only / audit view
```

## KMS-wrapped custody

`WrappedKeySigner` holds only the *wrapped* key and an `unwrap` callable that asks your KMS/HSM/Vault
to decrypt it. The KEK never leaves your provider.

<CodeGroup>
  ```python AWS KMS theme={null}
  from algovoi_doc_archive_kms import kms_wrap, kms_signer

  wrapped = kms_wrap(falcon_secret_key, key_id="alias/algovoi")   # one-time setup
  signer = kms_signer(public_key, wrapped, region_name="eu-west-2")
  # encryption key too: kms_encryptor(mlkem_public_key, kms_wrap(mlkem_secret_key, key_id=...))
  ```

  ```python HashiCorp Vault theme={null}
  from algovoi_doc_archive_vault import vault_wrap, vault_signer

  wrapped = vault_wrap(falcon_secret_key, key_name="algovoi", url=VAULT_ADDR, token=VAULT_TOKEN)
  signer = vault_signer(public_key, wrapped, key_name="algovoi", url=VAULT_ADDR, token=VAULT_TOKEN)
  # encryption key too: vault_encryptor(mlkem_public_key, vault_wrap(mlkem_secret_key, key_name=...), key_name=...)
  ```

  ```python PKCS#11 (HSM) theme={null}
  from algovoi_doc_archive.signer import WrappedKeySigner
  # unwrap via your PKCS#11 session's C_UnwrapKey / decrypt with the HSM-resident KEK
  signer = WrappedKeySigner(public_key, wrapped_secret_key, unwrap=hsm_unwrap)
  ```
</CodeGroup>

To set up, wrap your Falcon private key once with your provider's KEK and store the wrapped blob; the
plaintext key never has to live on disk.

## Encryption keys too

The same pattern covers the **ML-KEM decryption key** that protects your documents at rest. Encryption
needs only the public key (no custody concern); decryption unwraps the secret key through your KMS,
decapsulates, then zeroizes. `WrappedKeyEncryptor` is the encryption counterpart of `WrappedKeySigner`
and produces the identical blob format, so archives stay interchangeable.

```python theme={null}
from algovoi_doc_archive.archive import Archive
from algovoi_doc_archive.encryption import WrappedKeyEncryptor

encryptor = WrappedKeyEncryptor(
    mlkem_public_key,
    wrapped_mlkem_secret_key,                       # ML-KEM key wrapped by your KMS KEK
    unwrap=lambda w: kms.decrypt(CiphertextBlob=w)["Plaintext"],
)
archive = Archive(signer=my_signer, encryptor=encryptor, storage=my_storage)
```

With both a `WrappedKeySigner` and a `WrappedKeyEncryptor`, the keys behind **archive notarisation,
document encryption/decryption, and every evidence-stream entry stay in your custody** — AlgoVoi holds
them only as the transient, zeroized in-memory unwrap at the moment of signing or decryption, never
persisted in the clear.

<Note>
  **Scope of wrapped custody.** A `Signer` / `Encryptor` covers notarisation, document
  encryption/decryption, and the signing of **every evidence-stream entry** (timestamps, access log,
  legal hold, consent, custody, keyring, ACL, redaction). A few standalone ceremonies still take a key
  you supply **directly** rather than through the wrapped-custody path — the migration-manifest
  signature (`RecordsVault.sign_manifest`), key-rotation proofs, custodian counter-signatures, and
  recovery-share splitting — because each proves control of a *specific* key and accepts that key as
  input. For those steps, unwrap from your KMS/HSM in your own code at the point of use. Routing them
  through a `Signer` as well is on the roadmap.
</Note>

## Verification is unchanged

A receipt signed through any custody mode verifies identically — same algorithm, same envelope, same
public key. Your auditors and counterparties use the same [free verifier](/rfc9421-verifier), and your
key and its custody model are published in the [Product Key Registry](/product-key-registry).

```bash theme={null}
python3 algovoi_verify.py receipt receipt.txt public_key.b64   # passes regardless of custody
```

## Availability

Bring-your-own-key custody is an **Enterprise** capability. The provider adapters
(`algovoi-doc-archive-kms`, `-vault`, `-pkcs11`) are licensed packages that ship with
[Records Vault](/records-vault) — available **perpetual (one-time) or annual subscription** from the
[suite store](https://api.algovoi.co.uk/suite-store); local custody is built in.
Talk to us about a pilot: **[pilot@algovoi.co.uk](mailto:pilot@algovoi.co.uk)**.
