Tinylayer Enclavia signer
tinylayer-enclave is the only trusted stateful component in protocol 2. It
holds one global BIP340 key and a compact rollback-protected row per activated
coin. It signs BIP446 TemplateHash messages; it does not parse Bitcoin update
transactions.
Read the protocol specification before
deployment. The complete compiled signer/workload source
is rendered directly from src/lib.rs.
Responsibilities
The workload:
- publishes one global x-only signing key;
- allocates an opaque row on activation;
- binds that row to state 1 and its bootstrap TemplateHash;
- verifies the current bearer capability;
- signs exactly one next template for each state;
- rotates capability hash and state number atomically;
- returns byte-identical results for exact retries;
- rejects stale capabilities and sibling successors.
The wallet remains responsible for the funding outpoint and for reconstructing and validating every Bitcoin transaction, Taproot tree, amount, signature, locktime, history entry, confirmation, and outspend.
Protected state
global signing secret
coin ID → {
current capability hash,
state number,
latest template hash,
activation request,
exact last request and response,
}
The default capacity is 21,000 active rows. The workload must run with Enclavia’s rollback/fork protection. Restoring an older state while retaining the same global key would allow sibling signatures and is outside the protocol.
HTTP API
The workload listens on port 8080:
GET /health
POST /v2
All typed calls use one strict JSON endpoint.
Service info
{"method":"info"}
Response:
{
"method": "info",
"result": {
"protocol_version": 2,
"signing_pubkey": "<x-only key>"
}
}
info is stateless. Wallets call it before constructing a funding address.
Activation
{
"method": "activate",
"params": {
"coin_id": [0, 1],
"initial_capability_hash": [0, 1],
"initial_template_hash": [0, 1]
}
}
Arrays above are abbreviated; wire values contain exactly 32 integers.
The activation request is intentionally Bitcoin-opaque. The workload receives no transaction, outpoint, amount, script, proof, network name, explorer URL, or RPC configuration. It performs no network I/O.
Exact activation replay returns the existing status. A different activation for the same coin ID fails. Because the signer cannot distinguish a real coin from an invented ID, external admission or rate limiting is required before exposing activation to an adversarial public network.
Status
{"method":"status","params":{"coin_id":[0,1]}}
Status exposes the global key, current capability hash, state number, and latest template hash. It never exposes capabilities or private keys.
Sign
{
"method": "sign",
"params": {
"coin_id": [0, 1],
"current_capability": [0, 1],
"next_capability_hash": [0, 1],
"next_state_number": 2,
"template_hash": [0, 1]
}
}
The signer requires exactly stored state + 1, a changed capability, and a
changed template hash. It signs the raw 32-byte BIP446 hash with BIP340 and
persists the new state before returning.
A dropped response is handled by resending the exact request. A different request with the old capability is rejected.
Why one global key
Protocol 1 generated a key before funding. Protocol 2 publishes one global key so the wallet can derive its address locally. The BIP448 bootstrap leaf guarantees a safe initial state without a server signature, allowing allocation to move to the first transfer.
A global key increases blast radius if enclave isolation fails, but no valid Bitcoin update can spend without the separate client signature.
Build
cargo build --locked --release \
-p tinylayer-enclave \
--features workload \
--bin tinylayer-workload
The Docker image builds the same workload. /health only proves the process is
reachable; it does not prove attestation, admission policy, or coin state.
Local debug
Start the Bitcoin-opaque workload directly:
cargo run -p tinylayer-enclave --features workload --bin tinylayer-workload
The CLI may connect directly only when initialized with
--unsafe-plaintext. Never use that mode for secrets or public deployment.
Deployment invariants
- One rollback-protected state lineage for the global key.
- No independent replicas that can sign from divergent snapshots.
- No Bitcoin RPC, explorer, chain data, or egress inside the workload.
- Raw workload port reachable only through the intended Enclavia transport.
- PCR0, PCR1, and PCR2 distributed through an authenticated deployment record.
- Capacity and activation failures monitored.
- Public activation gated or rate-limited by infrastructure outside Enclavia.
- Exact request bodies retained across uncertain responses.
Wallets independently verify funding and chain state. External activation admission protects capacity but is deliberately outside the measured signer.