Report security issues via email or by opening a GitHub issue. Please allow reasonable time for a response before public disclosure.
The minting covenant is a CashScript contract that enforces the following transaction structure on every mint() call:
- Output 0: The minting token (NFT with minting capability) is returned to the same covenant address. The contract checks its own locking bytecode, so the token cannot be redirected without a covenant upgrade.
- Output 1: An immutable NFT carrying the user-supplied commitment is sent to the address provided by the caller.
- No platform fee is enforced at the protocol level. The contract does not require payment to any platform address.
mint()is permissionless — any caller can invoke it with a valid transaction structure. There is no allowlist or authentication at the contract layer.
The covenant includes a migrate() function that allows the platform to move the minting token to a new contract address. This is standard practice for early-stage CashScript deployments where contract bugs may need to be corrected.
What migrate() can do:
- Move the minting token (the capability to mint) to a new covenant address.
What migrate() cannot do:
- Mint arbitrary NFTs.
- Transfer, modify, or destroy NFTs already held by users.
- Access or modify any user data, keys, or encrypted state.
- Affect any on-chain asset except the single minting token held by the covenant.
Why it exists:
- Contract upgrades and bug fixes during the beta period.
- BCH CashScript contracts are immutable once deployed; migration is the standard upgrade path.
Current governance:
migrate()is gated by a single platform key. The HASH160 of this key is embedded in the constructor argument at deployment time.
Roadmap:
- Once the protocol is stable and there is a meaningful user base, the admin key will be replaced with a timelocked multisig governance scheme. This removes single-key control and requires multiple parties and a time delay for any migration.
The NFT commitment is defined as:
commitment = SHA256(compressed_pubkey_hex_string)
The input to SHA256 is the hex-encoded string representation of the compressed public key, not the raw bytes. This is intentional and documented to ensure cross-platform reproducibility.
Block hashes use canonical JSON serialization: all object keys are sorted recursively before serialization. This guarantees deterministic hashes across all runtimes and languages.
All cryptographic operations in the TypeScript SDK are cross-verified against the Python reference implementation. The test suite includes 518 tests, including explicit Python cross-compatibility vectors.
All encryption happens client-side. The server never receives or stores plaintext user data, keys, or block contents.
| Layer | Algorithm |
|---|---|
| Block data and chain state | AES-256-GCM |
| Inter-agent encryption | ECIES (secp256k1) |
| Master key derivation | PBKDF2-SHA256, 600,000 iterations (OWASP 2025 recommendation) |
| Key derivation function | HKDF-SHA256 with 12-byte nonces |
The master key is derived locally from the user's password and is never transmitted. Encrypted blobs uploaded to IPFS or the server are opaque ciphertext.
- Identity keys use ECDSA on secp256k1.
- Qube ID is derived as
SHA256(pubkey)[:8].hex().upper()— a short, human-readable identifier derived deterministically from the public key. - Private keys are stored encrypted under the user's master key and never leave the device in plaintext.