DashPass — Encrypted Credential Vault on Dash Platform
DashPass lets you store API keys, passwords, and other secrets encrypted on the Dash blockchain. Only someone with your private key can decrypt them — not the blockchain nodes, not your AI agent, not anyone else.
Your AI agent calls a CLI tool to store and retrieve credentials. Encryption happens locally before anything touches the network. The blockchain only ever sees ciphertext. Think of it as a password manager where the "cloud" is a decentralized blockchain.
Why DashPass Instead of a .env File
.env file |
DashPass | |
|---|---|---|
| Where secrets live | Plain-text file on one machine | Encrypted on decentralized blockchain |
| Disk failure | Secrets gone (unless backed up) | Recoverable with your key |
| Encryption | None | AES-256-GCM per credential |
| Rotation tracking | Manual | Built-in version history |
| Expiry alerts | None | check --expiring-within 7d |
| Multi-machine | Copy file around (risky) | Any machine with your key |
Quick Reference
CLI=skills/dashpass/scripts/dashpass-cli.mjs
# Store a credential
echo "sk-xxx" | node $CLI put --service anthropic-api --type api-key --level sensitive --label "Anthropic key" --value-stdin
# Retrieve a credential
node $CLI get --service anthropic-api --pipe
# Retrieve via mutual confirmation (2-of-2 Shamir shares)
node $CLI get --service anthropic-api --mutual
# List all credentials
node $CLI list
# Rotate to new value
echo "sk-NEW" | node $CLI rotate --service anthropic-api --value-stdin
# Check expiring credentials
node $CLI check --expiring-within 7d
# Vault status + credit balance
node $CLI status
# Delete a credential
node $CLI delete --service my-service
# Export as env vars (for eval)
eval $(node $CLI env --services anthropic-api,brave-search-api)
# Initialize Shamir 2-of-2 shares from CRITICAL_WIF
node $CLI init-shares
# Check share health
node $CLI share-status
# --- Key Backup & Recovery (standalone, no evo-sdk needed) ---
REC=skills/dashpass/scripts/dashpass-recover-cli.mjs
# Back up the vault key as a 24-word mnemonic (write it on paper, offline)
node $REC backup > /dev/tty # prints to stdout; redirect as you see fit
# Confirm a written mnemonic still matches the current key (non-destructive)
cat words.txt | node $REC verify # exit 0 = matches, 1 = mismatch
# Recover the WIF from the mnemonic after losing CRITICAL_WIF
cat words.txt | node $REC recover # prints WIF; then: export CRITICAL_WIF=<wif>
Mutual Confirmation Protocol
DashPass supports a 2-of-2 mutual confirmation mode using Shamir Secret Sharing over GF(2^8). This upgrades the single-key Scheme C to require both Evo (main agent) and CC (execution agent) to agree before any credential can be decrypted.
How It Works
init-shares: Splits the 32-byte private key derived fromCRITICAL_WIFinto two Shamir shares using a random degree-1 polynomial evaluated at x=1 (Share A) and x=2 (Share B).- Share storage: Share A goes to
~/.dashpass/evo.share, Share B to~/.dashpass/cc.share(both 0600 permissions). get --mutual: Reads both shares, combines via Lagrange interpolation to reconstruct the private key, derives the per-credential AES key (same ECDH+HKDF as Scheme C), decrypts, then zeroes all sensitive buffers.- Audit trail: Every request/approval/denial/execution is logged to
~/.dashpass/audit.login JSONL format. No key or share material is ever logged.
Security Properties
- Information-theoretic: Neither share alone reveals any information about the key (each byte is independently split).
- Backward-compatible: Without shares,
getstill works via Scheme C. With shares,get --mutualuses the new protocol. - Memory-safe: Private key bytes and AES keys are zeroed immediately after use via
Buffer.fill(0).
Setup
# One-time: generate shares from CRITICAL_WIF
node $CLI init-shares
# Verify health
node $CLI share-status
Key Backup & Recovery (P0-3)
The whole vault is encrypted under one root: the 32-byte private key derived from
CRITICAL_WIF. Lose that key and every credential is gone — there is no
maintainer-held recovery. v0.9 closes this with a BIP-39 mnemonic backup of the
key, modelled on standard wallet recovery-word practice.
This lives in a separate, dependency-light tool (dashpass-recover-cli.mjs),
not the main CLI, on purpose:
recoveris needed exactly whenCRITICAL_WIFis lost — but the main CLI exits immediately whenCRITICAL_WIFis unset, so recovery cannot live there.- Recovery must work when the Platform SDK or network is unavailable. The tool
imports only
node:crypto+@scure/bip39— no@dashevo/evo-sdk.
Commands
| Command | Input | Output | Notes |
|---|---|---|---|
backup |
CRITICAL_WIF (env) |
24-word mnemonic (stdout) | Write it on paper, offline. |
verify |
CRITICAL_WIF + mnemonic (stdin) |
exit 0 / 1 | Non-destructive check that a written backup is correct. |
recover |
mnemonic (stdin) | WIF (stdout) | After loss: export CRITICAL_WIF=<wif>. |
The mnemonic encodes the raw 32-byte key. Scheme C strips the WIF version/compression byte, so the derived AES key depends only on those 32 bytes — a recovered key decrypts every existing credential, unchanged.
Security notes
- The mnemonic is as sensitive as the WIF itself — full vault control. Never store it on the same disk as the vault; treat it like wallet seed words.
backup/recoverzero the key buffer (Buffer.fill(0)) after use.- Recovery reconstructs the same key — it is not a second key hierarchy and does not re-encrypt anything.
Architecture
Encryption Flow (Scheme C)
CRITICAL_WIF
│
▼
wifToPrivateKey() ← Base58Check decode, extract 32-byte private key
│
▼
ECDH self-sign (secp256k1) ← computeSecret(getPublicKey()) → sharedSecret
│
▼
HKDF-SHA256 ← hkdfSync('sha256', sharedSecret, salt, 'dashpass-v1', 32)
│
▼
AES-256-GCM ← per-credential encrypt/decrypt
│
▼
Dash Platform ← encryptedBlob + salt + nonce stored on-chain
Each credential gets a unique salt (32 bytes) and nonce (12 bytes), so even identical plaintext values produce different ciphertext.
Mutual Confirmation Flow (Shamir 2-of-2)
INIT (one-time):
┌─────────────────────────────────────────┐
│ CRITICAL_WIF → 32-byte private key │
│ │ │
│ ▼ │
│ For each byte i: │
│ a0 = key[i], a1 = random │
│ f(x) = a0 ⊕ (a1 · x) [GF(2^8)] │
│ │ │
│ ┌────┴────┐ │
│ ▼ ▼ │
│ f(1) f(2) │
│ Share A Share B │
│ evo.share cc.share │
│ (0600) (0600) │
└─────────────────────────────────────────┘
DECRYPT (each request):
┌─────────────────────────────────────────┐
│ 1. CC: requestDecrypt(service, reason) │
│ 2. Evo: approveDecrypt(request) │
│ 3. combineShares(A, B) │
│ │ │
│ ▼ │
│ Lagrange interpolation at x=0: │
│ key[i] = sA[i]·L1 ⊕ sB[i]·L2 │
│ L1 = 2·inv(3), L2 = inv(3) │
│ │ │
│ ▼ │
│ Reconstructed private key │
│ │ │
│ ▼ │
│ ECDH + HKDF → AES key → decrypt │
│ │ │
│ ▼ │
│ Zero all buffers (privKey, aesKey, │
│ sharedSecret) │
└─────────────────────────────────────────┘
Key design choices:
- Split the raw private key, not derived AES keys. One pair of shares covers all credentials — no per-credential splitting needed.
- GF(2^8) with irreducible polynomial 0x11d. Standard Rijndael/AES field. Byte-level operations, no bignum library required.
- Precomputed Lagrange coefficients. L1 and L2 are constants (2-of-2 at x=1, x=2), computed once at module load.
Security Model
What This Protects Against
| Threat | Protection |
|---|---|
| Single share compromise | Neither share alone reveals any information about the key (information-theoretic security in GF(2^8)) |
| Unauthorized decryption | Both shares must be present — a rogue process with access to only one share file cannot decrypt |
| Key material in memory | All sensitive buffers (privKeyBytes, aesKey, sharedSecret) are zeroed with Buffer.fill(0) after use |
| Audit evasion | Every request/approve/deny/execute is logged to ~/.dashpass/audit.log in JSONL format; no key material is ever logged |
| File permission escalation | Share files are stored with 0600 permissions; directory is 0700 |
| Corrupted shares | init-shares performs a round-trip verification before declaring success |
What This Does NOT Protect Against
| Limitation | Explanation |
|---|---|
| Same-machine attacker with root | Both shares live on the same filesystem. A root-level attacker can read both. This is a same-machine deployment limitation. |
| JavaScript string immutability | Hex-encoded share strings are JS immutable strings — they cannot be zeroed from memory. They persist until garbage collected. |
| Process memory dump | If the process is memory-dumped during executeDecrypt, the reconstructed key is briefly in a Buffer. The window is minimized but not zero. |
| Share file backup/sync | If ~/.dashpass/ is included in backups or cloud sync, shares may be replicated to less-secure locations. |
Honest Assessment
The current deployment is same-machine 2-of-2: both Evo (main agent) and CC (execution agent) run on the same host. This means:
- The security gain is procedural, not physical — it enforces a two-step confirmation workflow and audit trail, preventing a single code path from silently accessing credentials.
- It is not equivalent to a true multi-party setup where shares are on different machines controlled by different entities.
- The upgrade path to cross-machine deployment is straightforward: move
cc.shareto a separate host and replacereadShareB()with a network request.
Troubleshooting
Share files missing or corrupted
# Check share health
node $CLI share-status
# If shares are missing or unhealthy, re-initialize:
rm -f ~/.dashpass/evo.share ~/.dashpass/cc.share
node $CLI init-shares
Note: Re-initializing creates new shares from CRITICAL_WIF. Existing encrypted credentials are unaffected — they are decrypted using the same private key derived from the same WIF.
Permission errors on share files
# Shares must be 0600, directory must be 0700
chmod 700 ~/.dashpass
chmod 600 ~/.dashpass/evo.share ~/.dashpass/cc.share
If init-shares reports success but share-status shows wrong permissions, check if a umask override is active.
get --mutual fails with decryption error
- Verify shares are healthy:
node $CLI share-status— both must showHealthy: yes - Verify WIF hasn't changed: If
CRITICAL_WIFwas rotated afterinit-shares, the shares are stale. Re-initialize. - Check credential exists:
node $CLI list— confirm the service name matches exactly.
Audit log growing large
The audit log at ~/.dashpass/audit.log is append-only JSONL. To rotate:
mv ~/.dashpass/audit.log ~/.dashpass/audit.log.$(date +%Y%m%d)
# New entries will create a fresh audit.log automatically
Scheme C still works without shares
Yes — this is by design. Without shares, get (without --mutual) decrypts directly using CRITICAL_WIF. Shares are optional and additive. You can set up mutual confirmation at any time without re-encrypting existing credentials.
When to Use DashPass
Activate this skill when the user or agent needs to:
- Store an API key, token, password, or other secret
- Retrieve a previously stored credential
- Rotate / update an existing credential
- Check which credentials are expiring
- List all stored credentials
- Delete a credential from the vault
- Export credentials as environment variables
- Check vault status or credit balance
- Discuss credential management strategy
- Compare DashPass with other secret storage approaches
Agent Behavior Rules
- Never log or display decrypted values unless the user explicitly asks. Use
--pipefor programmatic access. - Always use
--value-stdin(pipe) forputandrotate. Never use--valuewith literal secrets — it leaks to shell history. - Never hardcode WIF or Identity ID in scripts. They come from environment variables only.
- Wait 3-5 seconds between consecutive write operations (put, rotate, delete) to the same Identity. Platform nonce timing constraint.
- Check
statusfirst if any operation fails with credit or balance errors. - Testnet only — do not attempt mainnet operations unless the user explicitly authorizes.
- Treat
CRITICAL_WIFas radioactive — if it appears in conversation, immediately warn the user about exposure risk. - The recovery mnemonic is as radioactive as the WIF — it grants full vault control. Never display, log, or store it on the vault's disk;
backupoutput goes to paper/offline only.
First-Time Setup
If the user has not used DashPass before, read the setup guide:
Read {baseDir}/setup.md
Detailed References
For full CLI command documentation (all parameters, examples, output formats):
Read {baseDir}/references/cli-commands.md
For encryption details, architecture diagrams, trust model, and security analysis:
Read {baseDir}/references/security-model.md
For troubleshooting common errors and known limitations:
Read {baseDir}/references/faq.md
For the prior security audit summary:
Read {baseDir}/references/security-analysis-summary.md
Command → Reference Map
| Intent | CLI Command | Reference |
|---|---|---|
| Store a secret | put |
{baseDir}/references/cli-commands.md |
| Retrieve a secret | get |
{baseDir}/references/cli-commands.md |
| List credentials | list |
{baseDir}/references/cli-commands.md |
| Rotate a credential | rotate |
{baseDir}/references/cli-commands.md |
| Check expiring | check |
{baseDir}/references/cli-commands.md |
| Vault status | status |
{baseDir}/references/cli-commands.md |
| Delete credential | delete |
{baseDir}/references/cli-commands.md |
| Export as env vars | env |
{baseDir}/references/cli-commands.md |
| Init Shamir shares | init-shares |
{baseDir}/scripts/mutual-confirm.mjs |
| Check share health | share-status |
{baseDir}/scripts/mutual-confirm.mjs |
| Mutual decrypt | get --mutual |
{baseDir}/scripts/mutual-confirm.mjs |
| Back up the key | recover-cli backup |
{baseDir}/scripts/dashpass-recover-cli.mjs |
| Verify a backup | recover-cli verify |
{baseDir}/scripts/dashpass-recover-cli.mjs |
| Recover a lost key | recover-cli recover |
{baseDir}/scripts/dashpass-recover-cli.mjs |
| How encryption works | — | {baseDir}/references/security-model.md |
| Error troubleshooting | — | {baseDir}/references/faq.md |
| First-time setup | — | {baseDir}/setup.md |
