MILL 1.6.0 is a big one. It adds a "Continue with Google" onboarding path for non-technical users, a per-event signing consent system for private-key signing, a draft NIP + reference implementation for cross-client key recovery, full header/footer branding, and a real fix for Amber (NIP-55) login — while keeping mill a zero-config, drop-in <script> tag.
Nothing here is breaking for existing integrations. Every new capability is opt-in and off by default; if you don't configure it, the picker looks and behaves as it did in 1.5.
✨ Continue with Google — onboarding for normies
A familiar "Sign in with Google" path for people who don't want to think about keys. Mill generates and holds the key, the user sets a short PIN, and the encrypted key lives in the user's own Google Drive (the hidden appDataFolder — private to the app). Returning users unlock on any device with their PIN.
- Real Google logo, first-class in the picker and under "I'm new here".
- Bring your own key — import an existing
nsecat setup instead of generating. - Take control of my keys — reveal the
nsecand export a portable NIP-49ncryptsecwhenever the user chooses. Onboard easy, leave anytime. - Manage stored keys — list, remove, and hold multiple accounts on one Google account, with an account chooser when several share a PIN.
Opt-in: it appears only when you deploy the OAuth shim and set oauthShim. See docs/google-login-setup.md and shim/mill-oauth.html.
MILL.open({ oauthShim: 'https://auth.yourdomain.com/mill-oauth.html' });On the PIN, honestly: a short PIN protects against casual access, not against someone who already has the encrypted blob. The real protection is the user's Google account and its 2FA — and the UI says so. For at-rest security independent of the account, users export a passphrase-protected
ncryptsec.
🔐 Per-event signing consent (private key)
When mill holds the key, it's the signer — so it now owns the approval UX, instead of silently signing everything.
- A consent card shows what is being signed (kind, decoded content, tags) with a remember-my-choice duration — just this time / 5 min / 1 hour / this session / always.
- Allow / Deny / Ask per event kind, with a permissions manager to review and revoke — reachable from the card (no host wiring) or via
MILL.openSettings(). - Two independent gates, the way Amber works: a session password unlocks the key once; consent approves each event. A remembered grant signs without re-prompting.
- Fixes a bug where "prompt every time" categories silently signed anyway in the first session.
Human-readable kind labels are adapted from grain's kind table.
🌐 Cross-client key recovery (experimental — draft NIP)
The start of true interoperability: recover the same identity in other Nostr clients from a Google account + a strong recovery phrase.
- A draft NIP — a passphrase-encrypted key, addressed by the cloud account, stored on relays — hardened by a three-lens adversarial review (cryptography, Nostr-protocol, ops/privacy).
- A reference implementation in
src/nipbackup.js: scrypt + HKDF derivation, NIP-49ncryptsecpayload, signature-verified relay fetch with quorum, a generated ≥70-bit recovery phrase.
Off unless you set backupRelays. Experimental: the kind and relay set are provisional and may change. Mill still forces the user to keep their own key. Read the NIP's Security Considerations before enabling.
MILL.open({ oauthShim: '…', backupRelays: ['wss://your-relay'] });🎨 Header & footer branding
Make the modal yours.
- Header — a real brand block: an image logo at natural size (or emoji), a title, a short message, aligned left or center. Set any of these and the header is fully yours; no mill wording leaks in.
- Footer — your tagline + links (Terms, Privacy, …), plus an optional, on-by-default "Signer by MILL" attribution you can repoint or hide.
- The hardcoded "Account Access" label and "Not sure? …" tip are now configurable or hideable.
MILL.open({
header: { logo: 'https://you.com/logo.png', title: 'YourApp', message: 'Your keys, your Nostr.', align: 'center' },
footer: { links: [{ label: 'Terms', href: '/terms' }, { label: 'Privacy', href: '/privacy' }] },
});📱 Amber (NIP-55) actually works again
Amber ≥ 6.0.0 broke web callbacks (query strings in callbackUrl get shredded). Mill now defaults to Amber's clipboard return — no server, no callback route, no host code — and if you do pass a callback URL, it's normalized to a fragment that survives modern Amber. returnType is corrected for sign_event.
🧹 Also in this release
- Mobile permissions screen fixed — no more off-screen overflow; a plain-language summary that expands to the full editor. (The codebase gained its first responsive rules.)
- Signing-permission vocabulary reworked from "unlock/password" to Auto-approve / Review, matching the new consent model.
- NIP-46 bunker connections are quiet by default (no console spam; the in-modal diagnostic panel is unaffected).
- The OAuth shim and docs now ship in the npm package (
shim/,docs/), as a clear template.
📦 Install
<script src="https://cdn.jsdelivr.net/npm/nostr-mill@1.6.0/dist/mill.umd.min.js"></script>npm install nostr-mill@1.6.0New API options on MILL.open: oauthShim, backupRelays, header, footer, tip. New result.method: google. New MILL.openSettings(). Full docs in the README.
Note: the bundle grew (~244 KB → ~276 KB min) — the cloud-backup crypto pulls in
@noble/hashes(scrypt/HKDF) and the@scure/bip39wordlist. These are only exercised by the opt-in Google/recovery paths.
Full changelog: v1.5.0...v1.6.0