Bursa is a programmatic Cardano wallet and CLI tool
10K+
Programmatic Cardano Wallet
Bursa is two things that share one set of keys:
bursa command. That is what the rest of this README covers.ui/ and has
its own README.Bursa implements the following Cardano Improvement Proposals:
Start a Bursa wallet and interact with it using the Bursa API.
# Clone the Bursa repository
git clone [email protected]:blinklabs-io/bursa.git
cd bursa
# Start the Bursa API server
go run ./cmd/bursa api
Access API Swagger documentation on the default loopback listener: http://localhost:8080/swagger/index.html.
Signer replicas must share one PostgreSQL watermark database to preserve the
payload and operational-certificate counter guards across replicas. Configure
every replica with the same database and keep mode: enforce:
signer:
watermark:
type: postgres
mode: enforce
dsn_env: BURSA_SIGNER_WATERMARK_POSTGRES_DSN
Set the named environment variable to a pgx/libpq connection string. Bursa requires remote connections to verify the server hostname, for example:
postgres://[email protected]/bursa?sslmode=verify-full&sslrootcert=/etc/bursa/postgres-ca.pem
Unix sockets, localhost, and loopback addresses may connect without TLS for
local development.
Supply the password through the process environment or another pgx-supported credential mechanism; do not store credentials in the YAML file. The database role needs permission to create the two watermark tables on first startup and to read and write them thereafter.
PostgreSQL replication and failover are external to Bursa. A failover endpoint
must preserve all committed rows and continue to present one authoritative
database; routing replicas to independent databases defeats the HA signing
guards. Bursa reports the signer unready while the database is unavailable and
signing in enforce mode fails closed.
Bursa does not automatically import watermark history from the in-memory or SQLite stores. Do not cut an active signer over to an empty PostgreSQL database: preserve or import both the payload and counter watermark rows during a controlled outage, or enable PostgreSQL before the affected keys sign for the first time.
The API listens on 127.0.0.1 by default. Set API_LISTEN_ADDRESS to an
explicit address such as 0.0.0.0 only when remote access is intended and
protected by TLS and JWT authentication. A non-loopback listener refuses to
start unless API_TLS_CERT_FILE / api.tls_cert_file and
API_TLS_KEY_FILE / api.tls_key_file identify a server certificate and
private key, and exactly one of these API trust sources is configured:
API_JWT_SECRET / api.jwt_secret: an HS256 secret of at least 32 bytes;
keep it in a deployment secret, not a committed config file.API_JWKS_URL / api.jwks_url: an HTTPS JWKS endpoint (plain HTTP is only
accepted for loopback development).API_JWT_ISSUER / api.jwt_issuer and API_JWT_AUDIENCE /
api.jwt_audience optionally constrain accepted bearer tokens. The secret,
signing, and mnemonic/address-derivation endpoints require an
Authorization: Bearer <JWT> header whenever API authentication is configured.
When wallet persistence is enabled, the wallet create, restore, list,
get, update, and delete endpoints use the same authentication requirement
and additionally require a subject listed in
API_JWT_ADMIN_SUBJECTS / api.jwt_admin_subjects. This is an explicit
global-administrator model; wallet names are not caller ownership boundaries.
Wallet creation is POST /api/wallet/create; wallet responses are marked
Cache-Control: no-store. A remote Swagger URL uses the same HTTPS listener,
for example https://wallet.example.com:8080/swagger/index.html. Plain HTTP
remains available only for loopback development when no TLS files are set.
api:
address: 0.0.0.0
port: 8080
tls_cert_file: /run/secrets/bursa-api-cert.pem
tls_key_file: /run/secrets/bursa-api-key.pem
jwks_url: https://identity.example.com/.well-known/jwks.json
jwt_issuer: https://identity.example.com
jwt_audience: bursa-api
jwt_admin_subjects:
- wallet-service-admin
Wallet data stored with SOPS is encrypted to every configured master key; any one of them can decrypt it. At least one is required, and a malformed value is rejected before encryption starts:
| Key | Config | Environment |
|---|---|---|
| Google Cloud KMS | google.kms_resource_id | GCP_KMS_RESOURCE_ID |
| AWS KMS | aws.kms_key_arn | AWS_KMS_KEY_ARN |
| age | age.recipients (comma separated) | AGE_RECIPIENTS |
Decryption uses the SOPS conventions: the AWS SDK credential chain, Google
application default credentials, and SOPS_AGE_KEY or SOPS_AGE_KEY_FILE for
age identities.
The file backend encrypts wallet contents with SOPS when a master key is
configured. Bursa refuses to start with file storage and no SOPS master key,
or with SQLite storage, unless storage.allow_unencrypted_wallet_storage is
explicitly enabled (STORAGE_ALLOW_UNENCRYPTED_WALLET_STORAGE=true). SQLite
does not encrypt wallet contents, even when a SOPS key is configured. The
opt-in writes mnemonics and signing keys in plaintext; deleting a SQLite wallet
does not guarantee that old page data is removed from the database file. Use
the opt-in only for isolated development or test deployments.
To persist API-created wallets in AWS Secrets Manager, select the AWS backend and configure a secret-name prefix. SOPS requires at least one configured master key; this example uses AWS KMS. Bursa uses the AWS SDK credential chain and region settings, and requires JWT authentication plus an administrator subject whenever wallet storage is enabled:
storage:
backend: aws
aws:
secret_prefix: bursa-wallet-
kms_key_arn: arn:aws:kms:us-east-1:123456789012:key/example
api:
jwt_admin_subjects:
- wallet-service-admin
Set STORAGE_BACKEND, AWS_SECRET_PREFIX, and AWS_KMS_KEY_ARN through the
process environment when those values should stay outside the config file.
Configure API_JWT_SECRET or API_JWKS_URL as the API trust source. Remote
API listeners also require TLS.
The Bursa container listens on loopback by default. A Kubernetes Service must
set API_LISTEN_ADDRESS to the pod address (normally 0.0.0.0) and preserve
the API container/Service port of 8080. Metrics are served only on the
dedicated loopback listener, which defaults to 127.0.0.1:8081; scrape it with
a collector in the same pod. When Google
Secret Manager persistence is enabled, the deployment must inject
GOOGLE_PROJECT, GCP_KMS_RESOURCE_ID, and GCP_SECRET_PREFIX, provide
API_JWT_SECRET or API_JWKS_URL, provide at least one
API_JWT_ADMIN_SUBJECTS value, and mount the matching TLS certificate and key
as API_TLS_CERT_FILE and API_TLS_KEY_FILE. The API refuses a non-loopback
listener without both TLS files and authentication. These values belong in
the chart's secret/config wiring; do not place credentials in chart defaults.
GCP_SECRET_PREFIX must be a non-empty Secret Manager ID prefix containing
only letters, numbers, hyphens, or underscores.
For more information about Bursa CLI
go run ./cmd/bursa
Usage:
bursa [command]
Available Commands:
address Address utility commands
api Runs the api
cert Certificate generation commands
hash Hash utility commands
help Help about any command
kes-agent Run the KES agent daemon for a Cardano block producer
key Key derivation commands
script Script commands for multi-signature operations
sign CIP-8/CIP-30 message signing
signer Run the Cardano remote signing service
tx Offline transaction operations (sign, witness, assemble)
wallet Wallet commands
Flags:
-h, --help help for bursa
Use "bursa [command] --help" for more information about a command.
Derive individual keys from a BIP-39 mnemonic. Secret output needs an explicit
destination: --signing-key-file <path> writes a cardano-cli key file and
--signing-key-file - prints to standard output. key decrypt takes
--out-file the same way.
bursa key root --mnemonic-file seed.txt --signing-key-file - # Root extended private key
bursa key account --mnemonic-file seed.txt --signing-key-file - # Account key (CIP-1852)
bursa key payment --mnemonic-file seed.txt --signing-key-file - # Payment key (CIP-1852)
bursa key stake --mnemonic-file seed.txt --signing-key-file - # Stake key (CIP-1852)
bursa key policy --mnemonic-file seed.txt --signing-key-file - # Forging policy key (CIP-1855)
bursa key pool-cold --mnemonic-file seed.txt --signing-key-file - # Pool cold key (CIP-1853)
bursa key pool-owner --mnemonic-file seed.txt --signing-key-file owner.skey # Plain Ed25519 stake key for hardware (CIP-1852 path)
bursa key drep --mnemonic-file seed.txt --signing-key-file - # DRep key (CIP-0105)
bursa key committee-cold --mnemonic-file seed.txt --signing-key-file - # Committee cold key (CIP-0105)
bursa key committee-hot --mnemonic-file seed.txt --signing-key-file - # Committee hot key (CIP-0105)
bursa key vrf --mnemonic-file seed.txt --signing-key-file - # VRF key pair for block production
bursa key kes --mnemonic-file seed.txt --signing-key-file - # KES key pair for block production
Generate the Dijkstra-era BLS stake-pool key used by Leios and Peras. The
signing key is never printed. The signing and verification files use
cardano-cli-compatible BlsSigningKey_bls12-381-BLS-Signature-Minimal-Signature-Size
and BlsVerificationKey_bls12-381-BLS-Signature-Minimal-Signature-Size envelopes;
the JSON output contains the 96-byte public key and 48-byte proof of
possession required for pool registration.
bursa key bls --signing-key-file bls.skey \
--verification-key-file bls.vkey --output-file bls-registration.json
Back up the signing key like other pool credentials and plan rotation with the Dijkstra-era registration policy. Leios and Peras rotation policy remains protocol-dependent.
Mnemonic-derived keys are output in bech32 format with appropriate prefixes
(root_xsk, acct_xsk, addr_xsk, stake_xsk, policy_xsk, pool_xsk,
drep_xsk, cc_cold_xsk, cc_hot_xsk, vrf_sk, kes_sk).
Inspect Cardano addresses and display their components:
bursa address info <address> # Display address type, network, credentials, etc.
Supports all CIP-0019 address types including base, enterprise, pointer, reward, and legacy Byron addresses.
Generate certificates for stake pool operations and block production:
bursa cert op-cert --kes-vkey kes.vkey --cold-skey cold.skey \
--counter 0 --kes-period 200 --out node.cert # Operational certificate for block production
Operational certificates link KES keys to stake pool cold keys for secure block production.
Bursa generates key files that are fully compatible with cardano-cli. Use bursa wallet create or bursa wallet restore to generate key files, then use them directly with cardano-cli commands:
# Create a wallet with Bursa
bursa wallet create --output mywallet
# Use the generated keys with cardano-cli
cardano-cli address build \
--payment-verification-key-file mywallet/payment.vkey \
--stake-verification-key-file mywallet/stake.vkey \
--mainnet
This enables Bursa as a drop-in replacement for key generation while maintaining full compatibility with the Cardano ecosystem.
Keys derived through a Cardano HD path retain their existing public identity
when exported. Bursa therefore writes payment, stake, governance, policy,
multi-signature, Calidus, account, and root signing files as extended
Ed25519-BIP32 envelopes. The paired verification files remain the canonical
32-byte public identities used by addresses, credentials, and key hashes.
cardano-cli key verification-key emits an extended verification envelope for
an extended signing key; its first 32 bytes are the same paired public identity.
Pool-cold keys are the deliberate exception. Their canonical signing file is a standard, non-extended Ed25519 seed envelope, matching the pool verification key and pool ID. The optional extended pool-cold envelope expands that same seed and therefore implies the same pool identity. Bursa continues to accept valid non-extended envelopes produced from genuine Ed25519 seeds.
bursa key pool-owner applies the same seed rule to the CIP-1852 stake path,
for hardware that holds only plain Ed25519 keys. Its public key, stake key hash
and reward address differ from those of bursa key stake for the same path, so
a wallet restored from the mnemonic does not show them.
Signing files generated by older Bursa versions may declare a non-extended
type while containing the first 32 bytes of an HD key. Such a file does not
imply the verification key, address, credential, or pool identity that was
exported beside it. Do not fix one by changing only its type field. Regenerate
the signing and verification files together from the original mnemonic, then
compare the resulting address or key hash with the identity already in use
before signing. Bursa does not rewrite previously exported files or persisted
wallet items automatically.
Content type
Image
Digest
sha256:3546b4ec1…
Size
50.7 MB
Last updated
8 days ago
docker pull blinklabs/bursa