This repo is a demonstration of a wallet that uses Hashicorp Vault as a Key Management Service (KMS) to store and manage cryptographic keys. The wallet is a CLI application that allows users to interact with the wallet and KMS to craft, sign and submit transactions to the Algorand network.
Managing and securing cryptographic keys is a fundamental requirement for any system and not special to blockchain applications. Hashicorp Vault is a popular and widely used KMS that provides a secure and scalable way to manage keys and secrets.
A big advantage of Vault in Algorand application design is that it doesn't require the integration of extra modules to support non-native signature schemes (i.e secp256k1). Ed25519 is natively supported and Vault's security model is maintained without relying on external modules.
- Craft tx Model
- Encode
- Sign
- Attach Signature
- Re-Encode
- POST
This pattern ensure that applications comply with the principle of ISOLATION between appliation space and "trusted" space; i.e the KMS. Most web3 applications handle cryptographic keys in-memory and in the same run-time space as the application. This is a security risk as the keys can be easily compromised by an attacker who gains access to the application's memory space.
This ensures that if the application is compromised, the keys are still secure and cannot be accessed by the attacker.
C4Context
title "Isolation Wallet & KMS"
Boundary(b0, "Run-time", "OS") {
Boundary(b1, "Wallet", "Docker") {
System(app, "Wallet CLI", "wallet")
}
Boundary(b2, "KMS", "Docker") {
System(kms, "Hashicorp Vault", "Key Management Service")
}
BiRel(app, kms, "uses", "REST")
}
UpdateRelStyle(app, kms, $textColor="green", $lineColor="blue", $offsetX="0")
UpdateLayoutConfig($c4ShapeInRow="1", $c4BoundaryInRow="0")
- Local State
- Networking
- KMS
- Data Models
- Encoding
- API
This project relies on algo-models to craft the transaction model.
The AlgoSDK is a great tool for interacting with the Algorand network. However, the transaction models used as part of it's API's are not the real "raw" transaction models that are sent to the network. The SDK abstracts some datal model handling, which might look easier for the developer to handle, but this means that if we want to integrate an agnostic, traditional KMS that doesn't understand how to manipulate the data models, it wouldn't produce valid signatures.
More information about this decision can be found here
# Install local deps
$ yarn
# Launch Vault
$ docker-compose up -d vault
# Init and unseal Vault
$ yarn run vault:init
In .env
file, change VAULT_TOKEN
to the root token generated by Vault. You can find this in vault-seal-keys.json
file.
Changes other variables as needed.
yarn run start:dev -- --entryFile repl
It should look something like this:
[9:47:01 PM] Starting compilation in watch mode...
[9:47:04 PM] Found 0 errors. Watching for file changes.
[Nest] 9936 - 11/21/2024, 9:47:04 PM LOG [NestFactory] Starting Nest application...
[Nest] 9936 - 11/21/2024, 9:47:04 PM LOG [InstanceLoader] ChainModule dependencies initialized
[Nest] 9936 - 11/21/2024, 9:47:04 PM LOG [InstanceLoader] HttpModule dependencies initialized
[Nest] 9936 - 11/21/2024, 9:47:04 PM LOG [InstanceLoader] ConfigHostModule dependencies initialized
[Nest] 9936 - 11/21/2024, 9:47:04 PM LOG [InstanceLoader] VaultModule dependencies initialized
[Nest] 9936 - 11/21/2024, 9:47:04 PM LOG [InstanceLoader] ConfigModule dependencies initialized
[Nest] 9936 - 11/21/2024, 9:47:04 PM LOG [InstanceLoader] WalletModule dependencies initialized
[Nest] 9936 - 11/21/2024, 9:47:04 PM LOG REPL initialized
>
If an error message is displayed along side the LOG messages, please check the .env
file and ensure that the VAULT_TOKEN
is set correctly and that vault is running.
Submit each command individually. This is just a demonstration of the steps involved in crafting and sending a transaction.
w = get(Wallet)
addr = await w.getAddress()
crafter = w.craft()
tx = crafter.pay(100000, addr, addr)
.addFirstValidRound(46005730) //adjust this value
.addLastValidRound(46005808) //adjust this value
.get()
encoded = tx.encode()
sig = await w.sign(encoded)
ready = crafter.addSignature(encoded, sig)
await w.submitTransaction(ready)