Trust-root posture (D-006)
Manthan's trust root is the operator long-term seed — the 32-byte
ed25519 seed each node uses to derive its sigil, sign the Charter / LOTS /
canary, and sign envelopes. Anything that holds (or can derive) a node's
seed can speak as that node.
This doc pins how the seed is held per deployment track. It's the answer
to the D-006 acceptance criterion in PHASE_5_HANDOFF.md.
Posture by track
Compose / OSS (internal default)
Today (v0.2 internal-research): deterministic per-node seeds.
// herald-protocol/services/testnet/src/single-node.ts
const NODE_SEEDS: Record<string, number> = {
"genesis-alpha": 0xa1,
"genesis-beta": 0xb2,
"genesis-gamma": 0xc3,
};If MANTHAN_NODE_SEED_HEX is set, that hex is used. Otherwise the seed isnew Uint8Array(32).fill(NODE_SEEDS[node_name]) — i.e., a 32-byte buffer
of 0xa1/0xb2/0xc3. Anyone with the source can derive the keys.
This is honest, not hidden. The Charter §4 of every node says the witness
is "the operator alone" and the storage posture is "AWS Secrets Manager
under operator KMS" for the AWS path; for compose, the seeds are
filesystem-derived. It's appropriate for:
- a single-operator internal testnet that nobody outside the operator's
blast radius can talk to,
- demo / review sessions where the audience knows it's pre-production,
- regression harnesses (the local-service testnet relies on the determinism).
It is not appropriate for:
- a publicly-reachable deployment with external agents writing in,
- anything making real trust claims to outside verifiers,
- anything where a key compromise has business consequences.
Optional next step (still on the OSS track): sops-encrypted seeds.
When the deployment graduates beyond "obviously dev" but isn't yet on the
AWS managed path, the natural intermediate is:
1. Generate per-node random 32-byte hex seeds:
openssl rand -hex 32
2. Encrypt them with age or
sops under operator-held public keys
(yubikey or password-protected age identity).
3. Decrypt at deploy time into a .env.local that's gitignored, with
MANTHAN_NODE_SEED_HEX_ALPHA=<hex> etc.
4. Reference those env vars from infra/compose/.env.
5. Document the recovery procedure (where the encrypted seeds live, who
holds the unlock key, how rotation works).
This isn't wired in code today because the operator hasn't asked for it.
Adding it is ~half a day:
- New file
infra/compose/secrets/seeds.age(encrypted) - Decrypt step in
infra/scripts/decrypt-secrets.sh .env.localproduced from decrypted output- Compose
--env-fileflag honoured
Terraform / AWS managed (production roadmap)
Already wired in code.
Per infra/terraform/modules/storage/main.tf:
resource "random_id" "node_seed" {
for_each = toset(var.node_names)
byte_length = 32
}
resource "aws_secretsmanager_secret" "node_seed" {
for_each = toset(var.node_names)
name = "${var.name}/nodes/${each.key}/seed"
kms_key_id = aws_kms_key.this.arn
}
resource "aws_secretsmanager_secret_version" "node_seed" {
for_each = toset(var.node_names)
secret_id = aws_secretsmanager_secret.node_seed[each.key].id
secret_string = random_id.node_seed[each.key].hex
lifecycle { ignore_changes = [secret_string] }
}What this gives you on first AWS apply:
- A different 32-byte random seed per node (no determinism).
- Each seed sealed in AWS Secrets Manager under a customer-managed KMS key
(alias/manthan-<env>) with rotation enabled.
- Decryption gated by an IAM task role scoped to a single ECS service.
- Seed injected as
MANTHAN_NODE_SEED_HEXat task start; never written
to disk on the running task.
ignore_changeson the version so an out-of-band manual rotation
(operator runs aws secretsmanager update-secret) isn't reverted on
the next Terraform apply.
This is the Phase-5-acceptance posture for the AWS path. It's appropriate
for v0.3+ production; HSM is the Phase-6+ upgrade.
HSM (Phase 6+ / when production-triggered)
D-006 in the original handoff calls for HSM-signing the *bootstrap seed
list* (the file every node fetches at startup to know who its peers are).
The same custody question applies to operator long-term seeds when real
trust claims ride on them.
When that trigger fires, the migration is:
- AWS CloudHSM cluster OR AWS KMS asymmetric Ed25519 key.
- Pin the public-key SPKI fingerprint into
lib/herald-core-ts(or a
small manthan-trust-roots.ts).
- Sign the bootstrap seed list with
aws kms sign(cached; rotation is
multi-year cadence).
- Verifiers confirm the signature via
ed25519Verify(bootstrapAuthorityPubkey, …).
Operator seeds themselves can stay in Secrets Manager for production —
moving them to HSM is a separate decision tied to the value of what
they're protecting. The bootstrap seed list signing is the higher-leverage
HSM target because it's the trust root for new nodes joining.
What changes when
| Trigger | What to do |
|---|---|
| Stays internal / pre-product | Keep deterministic compose posture. Document it (this file). No code change. |
| Internal but with sensitive content | Move to sops-encrypted seeds; ~half a day of plumbing. |
| Production AWS apply | The Terraform path already does the right thing. No additional work. |
| Real external trust claims | HSM-sign the bootstrap seed list per handoff D-006. Phase 6+. |
Honesty test
The Charter / LOTS for each node already declare these postures
truthfully (§4 Key-ceremony witness expectations, §4.4 HSM and). The compose path's Charter says "automated CSPRNG / no human
storage
ceremony / sealed in Secrets Manager (AWS path) / filesystem-derived
(compose path)." Don't let that drift — if you change the posture, update
the Charter in the same commit. (See CONTRIBUTING.md tracker rule.)
Related
herald-protocol/services/testnet/src/single-node.ts—NODE_SEEDS
fallback.
infra/terraform/modules/storage/main.tf— AWS Secrets Manager wiring.herald-protocol/services/testnet/nodes/genesis-{alpha,beta,gamma}/charter.md
§4 — authoritative declaration per node.
MANTHAN_PLATFORM_TRACKER.md— D-006 entry.