This application enables a Ledger device to participate in the Babylon vault lifecycle: locking BTC into an HTLC, pre-signing vault exit transactions, and releasing the session secret once all pre-signatures are complete.
The app is a btcext extension — standard commands (SIGN_PSBT, GET_EXTENDED_PUBKEY, etc.) are handled by the bitcoin base app. Three custom INS codes (CLA 0xE1) implement Babylon-specific vault operations.
The vault lifecycle spans two sessions:
Session 1 — Lock: The depositor creates an HTLC output locking BTC on-chain.
DERIVE_CONTEXT_HASH— derives anhtlc_preimagebound to the on-chain context hash; returnshtlc_hashlock = SHA256(htlc_preimage)to the host.APPROVE_VAULT_INTENT— streams vault parameters (17 scalar fields + keeper/challenger public keys) and shows an approval screen; the device stores thevault_intent_tin RAM.SIGN_PSBT(Pre-PegIn) — validates the HTLC PSBT, displays vault amount, fee, and HTLC address, then signs.
Session 2 — Pre-sign: Before BTC is committed on-chain, all vault exit transactions are pre-signed.
DERIVE_CONTEXT_HASH+APPROVE_VAULT_INTENT— re-derives and re-loads the intent (no new approval screen).SIGN_PSBT(PegIn) — silent; verifieshtlc_hashlockbinding, script reconstruction, and fee.SIGN_PSBT(Payout × N+1) — silent; VP first, then keeper keys in lexicographic order.RELEASE_CONTEXT_SECRET— returnshtlc_preimageto the host and zeroes it on-device immediately.
A Refund transaction (HTLC timelock branch) can be signed from any session state without loading an intent.
- The device always reconstructs scripts from the loaded
vault_intent_tand rejects any PSBT that does not match — it never trusts scripts provided by the host. htlc_preimageis zeroed immediately (explicit_bzero) on any signing error, intent reload, or earlyRELEASE_CONTEXT_SECRETcall. In the source code and headers,htlc_preimageisvault_context_t.sandhtlc_hashlockisvault_context_t.h.- Payout order is enforced by the device: VP first, then VK keys in lexicographic order.
- All vault P2TR outputs use a NUMS internal key (
lift_x(0x50929b74...)) — no key-path spend is possible.
The app overrides two hooks exposed by the bitcoin base app via weak symbols.
Called during SIGN_PSBT. The app determines which of the four transaction types (Pre-PegIn, PegIn, Payout, Refund) is being signed, validates all external inputs against the loaded vault_intent_t, and displays the relevant UX screens.
Signs external inputs that belong to the vault protocol (e.g., the HTLC input in PegIn). The base app provides:
compute_sighash_segwitv1/sign_sighash_schnorr_and_yieldfor SegWit v1 (taproot) inputscompute_sighash_segwitv0/sign_sighash_ecdsa_and_yieldfor SegWit v0 inputs
See sign_psbt.h and txhashes.h for the full API.
Handles the three Babylon-specific INS codes on CLA 0xE1:
| INS | Command |
|---|---|
0x80 |
APPROVE_VAULT_INTENT |
0x81 |
DERIVE_CONTEXT_HASH |
0x82 |
RELEASE_CONTEXT_SECRET |
Please consult the code of the base app for exact documentation about those functions. Definitions are in the headers sign_psbt.h and txhashes.h.
The app builds two variants, selected with COIN=<variant>:
COIN |
App name | Network params | Ticker |
|---|---|---|---|
babylon_vault |
Babylon Vault | mainnet | BTC |
babylon_vault_testnet (default) |
Babylon Vault Testnet | testnet | sBTC |
Babylon's test network runs on Bitcoin signet, and the test build targets it. From the
device's point of view signet and testnet3 are indistinguishable — same tb address prefix,
BIP-32 version bytes and coin type (1), and the app has no network stack to notice the
consensus/magic-byte differences. So no separate signet variant is needed: the
babylon_vault_testnet build, with BITCOIN_NETWORK = testnet for the shared coin params,
signs signet transactions byte-for-byte correctly.
The official app name stays "Babylon Vault Testnet" (the Ledger guideline enforcer pins
the appName in the per-target manifests, and it is the single source for the on-device
name). The one signet-specific touch is the amount ticker, which reads sBTC instead of
TEST: the base submodule hardcodes COIN_COINID_SHORT="TEST" for the testnet network, so
the app Makefile overrides it after the include — DEFINES is expanded into -D flags
at compile time, so the post-include value wins (the base entry is filtered out first to
avoid a redefinition). No submodule change is required.
Initialize the submodule with:
$ git submodule update --init --recursive
Compile the app as usual. You should be able to launch it using Speculos.
Create a Python virtual environment and install the requirements:
$ python -m venv venv
$ source venv/bin/activate
$ pip install -r tests/requirements.txt
Launch the test suite; for example, if you compiled the app for Ledger Flex:
$ pytest --device=flex