# `KeyPair` — Ed25519 Signing Keys

`pollyweb.keypair` provides the Ed25519 key material used by [`Domain`](domain.md) and [`Msg`](msg.md): a private signing key plus its derived public verification key.

**See also:** [`DNS`](dns.md), [`Domain`](domain.md), [`Msg`](msg.md)

---

## Quick start

```python
import pollyweb as pw

pair = pw.KeyPair()  # generates a fresh Ed25519 private key

msg = pw.Msg(To="receiver.dom", Subject="Hello@Host", Body={"text": "hi"})
domain = pw.Domain(Name="sender.dom", KeyPair=pair, Selector="pw1")
dns_record = domain.dns()
signed = domain.sign(msg)

signed.validate(pair.PublicKey)  # True
```

To export PEM files:

```python
private_pem = pair.private_pem_bytes()
public_pem = pair.public_pem_bytes()
```

You can also wrap an existing Ed25519 private key:

```python
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
import pollyweb as pw

existing = Ed25519PrivateKey.generate()
pair = pw.KeyPair(PrivateKey=existing)
```

---

## `KeyPair` fields

`KeyPair` is a dataclass with one stored field and one derived property.

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `PrivateKey` | `Ed25519PrivateKey` | — | auto-generated | The Ed25519 private key used for signing. If omitted, a fresh key is generated on construction. |

Each `KeyPair()` instance generates a new private key by default, so separate instances do not share key material.

---

## Properties

### `pair.PublicKey → Ed25519PublicKey`

Returns the public key derived from `PrivateKey`.

This is a computed property, not a constructor argument. It is typically used to validate signed messages without DNS lookup:

```python
signed.validate(pair.PublicKey)
```

The returned public key is equivalent to:

```python
pair.PrivateKey.public_key()
```

---

## Methods

### `pair.dkim(v="DKIM1") → str`

Returns the DKIM TXT record payload for the current public key.

```python
txt = pair.dkim()
# "v=DKIM1; k=ed25519; p=<base64-encoded public key>"
```

The value contains:

| Part | Meaning |
|---|---|
| `v=DKIM1` | DKIM record version |
| `k=ed25519` | Key algorithm |
| `p=...` | Base64-encoded raw Ed25519 public key |

Publish the returned string as a DNS TXT record at:

```text
{selector}._domainkey.pw.{domain}
```

If you are managing a full PollyWeb sending domain, prefer [`domain.dns()`](domain.md), which determines the correct selector and returns a `{selector: txt}` mapping. `Domain.sign()` uses that derived selector when populating `Msg.Selector`.

If you only need to inspect whether published DNS is compliant, use
[`DNS.check()`](dns.md).

You can override the version tag if needed:

```python
pair.dkim("DKIM2")
```

### `pair.private_pem_bytes() → bytes`

Returns the private key encoded as unencrypted PKCS#8 PEM bytes.

```python
private_pem = pair.private_pem_bytes()
```

This is intended for writing a local signing key file:

```python
from pathlib import Path

Path("private.pem").write_bytes(pair.private_pem_bytes())
```

### `pair.public_pem_bytes() → bytes`

Returns the public key encoded as SubjectPublicKeyInfo PEM bytes.

```python
public_pem = pair.public_pem_bytes()
```

This is intended for writing a shareable verification key file:

```python
from pathlib import Path

Path("public.pem").write_bytes(pair.public_pem_bytes())
```

---

## Design notes

**Generated by default** — `KeyPair()` creates a fresh Ed25519 private key automatically, which makes it convenient for tests and initial setup.

**Public key is derived, not stored separately** — `PublicKey` is computed from `PrivateKey`, so the pair cannot drift out of sync.

**DNS export is raw-key based** — `dkim()` serialises the Ed25519 public key in raw form and base64-encodes it for the `p=` tag, matching the format expected by `Msg.validate()` when resolving keys from DNS.

**PEM export is part of the keypair API** — use `private_pem_bytes()` and `public_pem_bytes()` instead of re-implementing `cryptography` serialization at call sites.
