DEV Community

Cover image for Fortress wallets vs disposable envelope
Alleo
Alleo

Posted on • Originally published at hackenproof.com

Fortress wallets vs disposable envelope

Fortress wallet vs disposable envelope

A fortress wallet (like MetaMask) is meant to be kept in a secure place for a long time—often years—and used to hold funds you care about over the long term. You guard one root secret (typically a seed phrase). You do not paste it into a chat or email.

In everyday life the opposite is also needed: a disposable envelope. Something is placed inside, handed off, and the envelope is discarded. For crypto, that means a one-time wallet: create it, fund it, transfer full ownership, and move on.

Examples:

  • Create many wallets, fund them with a memecoin or NFT, and distribute them to campaign participants.
  • Gift crypto without asking for a recipient’s address—fund a wallet and send the wallet itself (link or QR), not only a transfer to an address they already have.

The sections below describe how that one-time model is implemented: client-side key derivation, credential rules, and Scrypt parameters.


Security philosophy

For a disposable envelope, the purpose is literally one-time. It is not a vault for large amounts, and top-tier cold-storage security is not the main goal. Convenience comes first.

That leads to a deterministic design: the same credentials always produce the same private key. Converting passphrase and PIN into a key with a one-way process is the natural implementation.

Brain wallets already demonstrated the failure mode—weak passwords hashed once and guessed at scale. The choice of hash function and its parameters therefore matters.

Scrypt was chosen and tuned for a tradeoff between safety and hashing time on desktop and mobile browsers. Same credential → same wallet (same private key). Brute force is made expensive; a GPU farm should not be economical to crack a five-dollar gift wallet.

passphrase + PIN → zxcvbn checks → Scrypt → Keccak256 → private key → address
Enter fullscreen mode Exit fullscreen mode

Everything runs in the browser. There is no server that stores or recovers your credentials.


Passphrase + PIN, auto-fill, and zxcvbn

Even when “maximum security” is not the product goal, funds in the envelope should still be reasonably safe.

People often pick weak or already-leaked passwords. The app enforces zxcvbn before hashing and rejects weak passphrase/PIN pairs.

User-typed secrets usually have lower entropy than machine-generated ones. Auto-fill (randomPassphrase() and randomPIN() in @mybucks.online/core) was added so strong credentials can be generated without the user inventing them for every gift.

For a one-time wallet: generate with auto-fill, fund, hand off, discard the credentials after claim. Do not reuse your email, bank, or MetaMask passwords.

There is no “forgot password” recovery. If credentials are lost, funds on that address are gone.


Key generation

Minimal JavaScript for the Scrypt path used by mybucks.online (also in @mybucks.online/core and key-generation):

import { Buffer } from "buffer";
import { ethers } from "ethers";
import { scrypt } from "scrypt-js";

const abi = new ethers.AbiCoder();

const HASH_OPTIONS = { N: 131072, r: 8, p: 1, keyLen: 64 };
const KDF_DOMAIN_SEPARATOR = "mybucks.online-core.generateHash.v2";

async function generatePrivateKey(passphrase, pin) {
  const encoded = abi.encode(
    ["string", "string", "string"],
    [KDF_DOMAIN_SEPARATOR, passphrase, pin],
  );
  const saltBuffer = Buffer.from(ethers.keccak256(encoded).slice(2), "hex");

  const hashBuffer = await scrypt(
    Buffer.from(passphrase),
    saltBuffer,
    HASH_OPTIONS.N,
    HASH_OPTIONS.r,
    HASH_OPTIONS.p,
    HASH_OPTIONS.keyLen,
    (p) => console.log(Math.floor(p * 100)),
  );
  const hashHex = Buffer.from(hashBuffer).toString("hex");
  return ethers.keccak256(abi.encode(["string"], [hashHex]));
}
Enter fullscreen mode Exit fullscreen mode

Using the npm package:

import { generateHash, getEvmWalletAddress } from "@mybucks.online/core";

const hash = await generateHash(passphrase, pin);
const address = getEvmWalletAddress(hash);
Enter fullscreen mode Exit fullscreen mode

Key generation docs · CodeSandbox

Next (series): Gifting links and #wallet= URL delivery.

Top comments (0)