Sign inSign up

blinklabs/bursa

By blinklabs

•Updated about 1 hour ago

Bursa is a programmatic Cardano wallet and CLI tool

Image
0

10K+

blinklabs/bursa repository overview

⁠bursa

Bursa Logo

Programmatic Cardano Wallet

Bursa is two things that share one set of keys:

  • A Cardano wallet library and CLI — BIP39 seeds, CIP-1852 derivation, addresses, certificates and native scripts, usable as a Go package or from the bursa command. That is what the rest of this README covers.
  • A full-node desktop wallet — a single binary that embeds and supervises a Dingo⁠ node and serves a wallet interface over loopback, with hardware-wallet support, staking, Conway governance and stake-pool operations. It lives in ui/⁠ and has its own README⁠.

⁠Documentation

⁠Supported CIPs

Bursa implements the following Cardano Improvement Proposals:

  • CIP-0003: Wallet Key Generation - Complete BIP39 seed generation and key derivation
  • CIP-0005: Bech32 Address Format - Bech32 encoding/decoding for addresses and keys
  • CIP-0011: Staking Key Delegation - Stake key derivation and reward address generation
  • CIP-0016: Cryptographic Key Serialization - CBOR serialization for all key types
  • CIP-0018: Multi-Stake Keys - Support for multiple stake keys per wallet
  • CIP-0019: Cardano Addresses - Full address format support (mainnet/testnet)
  • CIP-0105: Conway Era Key Chains - Governance key derivation (DRep, Committee)
  • CIP-1852: HD Wallets - Hierarchical deterministic wallet structure
  • CIP-1853: Stake Pool Cold Keys - Pool operator identity keys
  • CIP-1854: Multi-signature Scripts - Native script support and validation
  • CIP-1855: Forging Policy Keys - Native asset minting/burning policy keys

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⁠.

⁠Highly Available Signer Watermarks

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 storage encryption

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:

KeyConfigEnvironment
Google Cloud KMSgoogle.kms_resource_idGCP_KMS_RESOURCE_ID
AWS KMSaws.kms_key_arnAWS_KMS_KEY_ARN
ageage.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.

⁠Wallet storage encryption

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.

⁠AWS Secrets Manager wallet storage

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.

⁠Kubernetes and Helm deployment contract

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.
⁠Key Derivation Commands

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).

⁠Address Commands

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.

⁠Certificate Commands

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.

⁠Cardano-CLI Compatibility

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.

⁠Signing-key envelope contract

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.

Tag summary

Content type

Image

Digest

sha256:3546b4ec1…

Size

50.7 MB

Last updated

8 days ago

docker pull blinklabs/bursa