Institutional custody has a brutal threat model: the systems that hold keys are worth attacking with nation-state effort, a single rogue administrator can be catastrophic, and regulators expect proof — not promises — that history hasn’t been rewritten. MEDUSA (Multi-signature Enterprise Digital Custody Architecture) is my answer to that problem: a multi-tenant custody platform for Bitcoin and Stellar where no internet-facing component can reach the keys, every sensitive change requires cryptographic approval, and every decision is sealed into a tamper-evident chain.

Role Architecture and implementation, end to end
Stack Kotlin, Spring Boot 3.2, JDK 21, PostgreSQL 16, HashiCorp Vault, React + TypeScript, browser extension
Cryptography ECDSA secp256k1, AES-256-GCM, hash-chained governance blocks with Merkle roots
Deployment Six services on Fly.io + Docker, Prometheus + Grafana observability
Status Beta — token-gated access, Bitcoin + Stellar live

The problem

A custody backend is, by definition, an internet-facing service that ultimately causes funds to move. The standard architecture — API gateway calls signing service, signing service calls HSM — means every exposed endpoint is one authorization bug away from the vault, and every service in the call chain is a place where a stolen credential becomes stolen funds. Three more constraints stack on top:

  • Governance is plural. Institutions don’t approve transfers with one admin password; they approve with three of five directors, or a treasury committee plus a compliance officer. The platform has to encode that as cryptography, not as a checkbox in an admin panel.
  • Audit means verification, not logs. A database log can be edited by anyone with database access. Regulators and clients increasingly want change history they can verify independently of the operator.
  • Tenancy is a blast-radius problem. A multi-tenant custody platform must guarantee that one client’s incident — or one client’s malicious insider — never becomes another client’s problem.

Design decisions

Pull-based topology: zero inbound connections to the secure zone

The single most important decision in MEDUSA is that secure components never listen. The only internet-facing service is a DMZ backend that authenticates users and records pending change requests — it cannot open a connection to the governance or wallet services at all, because no route, port, or credential exists for it to do so. Instead, the governance service polls the DMZ for pending work, and wallet services poll governance for approved operations, pushing signed results back outward when done.

This inverts the usual attack economics. An attacker who fully compromises the DMZ backend gains the ability to request things — requests that still require M-of-N signatures to go anywhere. The polling loop also degrades gracefully: if governance goes down, pending changes queue harmlessly in the DMZ; nothing in the secure zone ever depends on the DMZ being trustworthy or even available.

The cost is that everything a call-based architecture gets for free needs explicit design: idempotent polling (a governance crash mid-poll must not double-approve), payload hashes as the deduplication key across the boundary, and explicit result push-back with state reconciliation. That trade — engineering effort for structural security — is the platform’s core bet.

Approvals as an immutable governance chain

Rather than storing approvals as mutable database rows, governance decisions are sealed into an append-only chain of blocks:

 ┌────────────┐     ┌──────────────────┐     ┌──────────────────────┐
 │  Block 0   │◄────│     Block 1      │◄────│       Block N        │
 │  Genesis   │     │ prevHash ────────┘     │ prevHash ────────────┘
 │            │     │ changesRoot      │     │ changesRoot (Merkle) │
 │            │     │ signature: ECDSA │     │ signature: ECDSA     │
 └────────────┘     └──────────────────┘     │ exponentialRefs[] ───┼──► Blocks N-2, N-4, N-8 …
                                             └──────────────────────┘

Each block carries the hash of its predecessor, a Merkle root over the changes it approves, an ECDSA signature from the governance service, and exponential back-references that let a verifier check chain integrity in logarithmic hops instead of walking every block. Tampering with any historical approval breaks every hash after it. This turns the audit trail from “trust our database” into a structure an external auditor can verify mechanically — and because each tenant has an independent chain, verification is per-institution, not per-platform.

Anti-rewind and anti-replay controls guard the chain’s edges: external chain-tip files pin the latest known block outside the database, payload-hash deduplication rejects operations that have already been decided, and timestamp bounds (±5 minutes) box the window in which a captured request is worth anything.

Policy as signed workflows, not configuration

Approval policies support four modes — AUTO_APPROVE, REQUIRE_SINGLE, REQUIRE_ALL, and REQUIRE_THRESHOLD (M-of-N) — with pattern matching, priorities, and condition expressions, so an institution can express “transfers under 1 BTC auto-approve; everything else needs 3 of 5.”

The subtle part: changing a policy is itself a policy-governed operation. Every policy mutation — create, edit, enable, disable, archive, delete — becomes a signing workflow entity with a canonical payload hash, a witness set of public keys, and a seven-day expiry. System policies are protected from deletion outright. Every endpoint enforces organization membership and role checks on top of the cryptographic requirements.

The signing UX is where the security model meets real humans. Signatures are collected through the MEDUSA browser extension or pasted from a CLI, and the client verifies every signature in-browser before submission — the frontend computes the payload hash locally (canonical serialization, hashed client-side) and validates Ed25519/ECDSA signatures against the witness set as they arrive, showing live valid/invalid status per signer. Users can preview and download the exact YAML command payload they are signing. The principle: you sign what you see, not what a server claims you’re signing. Founder keys (key ID, public key, algorithm, label) bootstrap an organization’s cryptographic identity, with a documented legacy path for organizations migrating in before they have founder keys.

Tenant isolation at every layer

Each institution gets schema-level database isolation, an independent governance chain, and scoped HashiCorp Vault paths — AppRole authentication per service, path-based isolation per tenant, and the Transit secrets engine for key operations so raw key material never sits in application memory. The anti-replay controls above are enforced per tenant. The blast radius of any single compromise — a credential, a schema, a chain — is one tenant, one layer.

Architecture

                        INTERNET
                           │
        ┌──────────────────┼──────────────────────────────┐
        │   CLIENT         ▼                              │
        │  ┌─────────────────────┐  ┌──────────────────┐  │
        │  │  React Frontend     │  │ Signing Extension│  │
        │  │  :5174              │  │ (or CLI)         │  │
        │  └──────────┬──────────┘  └────────┬─────────┘  │
        └─────────────┼──────────────────────┼────────────┘
                      ▼ HTTPS + JWT          │ signatures
        ┌─────────────────────────┐          │
   DMZ  │  medusa-backend  :8084  │◄─────────┘
        │  auth · pending changes │
        └─────────────────────────┘
                      ▲
                      │ ①  POLL pending changes (outbound only)
        ══════════════╪══════════════════════════ no inbound path ═══
                      │
        ┌─────────────┴───────────┐         SECURE ZONE
        │ medusa-governance :8102 │  event store · policy · chain
        └─────────────────────────┘
                      ▲
                      │ ②  POLL approved operations
        ┌─────────────┴───────────┐
        │  wallet-gateway  :8100  │  JWT · routing
        └───────┬─────────┬───────┘
                ▼         ▼
      ┌───────────────┐ ┌───────────────┐      ┌────────────────┐
      │ wallet-bitcoin│ │ wallet-stellar│─────►│ HashiCorp Vault│
      │     :8103     │ │     :8101     │      │     :8200      │
      └───────┬───────┘ └───────┬───────┘      │ AppRole·Transit│
              ▼                 ▼              └────────────────┘
       Bitcoin Network    Stellar Network

Every arrow crossing the secure-zone line points outward. Six services — frontend, DMZ backend, governance event store, wallet gateway, and per-chain wallet services — are independently deployable with PostgreSQL per service and Prometheus + Grafana across the fleet.

The life of an operation

 User                DMZ backend         Governance            Wallet svc
  │  ① initiate op       │                   │                     │
  │  (signed client-side)│                   │                     │
  ├─────────────────────►│                   │                     │
  │                      │ ② record pending  │                     │
  │                      │    change         │                     │
  │                      │◄──────────────────┤ ③ poll · validate   │
  │                      │                   │   anti-replay ·     │
  │                      │                   │   collect M-of-N ·  │
  │                      │                   │   seal block        │
  │                      │                   │◄────────────────────┤ ④ poll approved op
  │                      │                   │                     │ ⑤ execute w/ Vault keys
  │                      │◄──────────────────┼─────────────────────┤    push signed result
  │◄─────────────────────┤ ⑥ update state,   │                     │
  │      notified        │    notify user    │                     │

Six steps, two polls, zero inbound connections. Between step ② and ③ the operation exists only as a pending request; between ③ and ④ it exists as a cryptographically sealed approval; only at ⑤ does anything touch a key — and by then the decision it executes is already immutable history.

Status and limits

MEDUSA is in beta behind token-gated access (X-Beta-Token model). Bitcoin and Stellar are the two live chains; banking-rail integration is designed but not built. The governance chain is verified by the platform itself and by client-side checks — independent third-party audit tooling is future work, and it’s the work I’d prioritize next, because the chain’s whole value is that outsiders can check it. I present these limits alongside the claims deliberately: in custody, the gap between “implemented” and “asserted” is exactly where funds are lost.

What this demonstrates

Designing MEDUSA meant making security decisions structural rather than procedural — the secure zone can’t be reached because there is no path; history can’t be rewritten because hashes chain; policy can’t be bypassed because policy changes are themselves policy-governed; a compromised UI can’t forge consent because clients hash and verify locally. It also meant paying the honest price of those choices in idempotent polling loops, reconciliation logic, and signing UX that real committee members can operate. If you’re building custody, signing infrastructure, or any system where the threat model includes your own administrators, book a consultation or write to [email protected].