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
// Volume encryption — consumer-side key derivation for Phase 1.
//
// The provider creates a LUKS-encrypted volume keyed by the bytes
// shipped in `EncryptedSpawnPodRequest.volume_encryption.key_b64`.
// This module gives consumers a *deterministic* way to compute that
// key from material they already hold (their nsec + the workload id),
// so a respawn after eviction or top-up doesn't need a separate
// out-of-band key vault.
//
// Determinism is the load-bearing property:
// derive_volume_key(nsec, workload_id) == derive_volume_key(nsec, workload_id)
// always. The consumer can recompute the same key on every respawn
// without persisting anything beyond what they already persist (the
// nsec in `~/.paygress/identity` and the workload id printed at spawn
// time).
//
// Threat model recap (mirrors the doc on `VolumeEncryption`):
// - Defends against post-eviction disk forensics, lazy host backups,
// co-tenant attacks on shared storage, cold-disk seizure.
// - Does NOT defend against a live host with `CAP_SYS_PTRACE` reading
// /proc/<pid>/mem or extracting the LUKS key from the kernel
// keyring while the workload runs. That requires hardware
// confidential VMs (SEV-SNP / TDX), gated behind the
// `attested-research-tier` `IsolationLevel`.
//
// Why one-shot SHA-256 instead of HKDF: the inputs are
// already-uniform high-entropy material (a 32-byte secp256k1 secret
// key plus a UUID). HKDF's extract step exists to handle non-uniform
// input keying material; we don't have that. A domain-separated
// SHA-256 is sufficient and avoids pulling another dep just to derive
// 32 bytes.
use ;
/// Domain-separation tag for v1 volume keys. Bumping this breaks
/// every existing volume — only do so on a schema version bump
/// of `VolumeEncryption`.
const KDF_DOMAIN_V1: & = b"paygress-volume-v1\0";
/// Derive the 32-byte volume key from the consumer's nsec bytes and
/// the workload id.
///
/// Inputs:
/// - `nsec_bytes` — the consumer's 32-byte secp256k1 secret key
/// (raw bytes, not bech32-encoded).
/// - `workload_id` — the consumer-assigned workload identifier
/// (the same UUID-shaped string passed in
/// `EncryptedSpawnPodRequest.workload_id`).
///
/// The two inputs are length-prefixed implicitly via the trailing
/// NUL byte in `KDF_DOMAIN_V1` — `workload_id` cannot contain NULs
/// (it's a UUID), so collisions across the (nsec, workload_id)
/// boundary are not constructible.