diff --git a/spv_enclave/README.md b/spv_enclave/README.md index 4a6c476..0818503 100644 --- a/spv_enclave/README.md +++ b/spv_enclave/README.md @@ -9,3 +9,30 @@ This pup will install [Libdogecoin SPV](https://github.com/dogecoinfoundation/libdogecoin) as a pup on your node. It will generate a new wallet and start block sync from the last checkpoint. + +## ⚠️ Important: One-Time Mnemonic Display + +**On first initialization**, this pup will generate your wallet's mnemonic phrase. You must reveal it **ONCE** to save it: + +### How to Reveal Your Mnemonic + +#### The mnemonic is **HIDDEN by default** for security. To reveal it: + +1. **Start the Pup** → Click "Enabled" in MENU to start the pup services +2. **View Metrics** → You'll see: `[🔒 Hidden - Check 'Click to Reveal Mnemonic' in Wallet Security settings to view]` +3. **Click Reveal Checkbox** → Go to **Settings → Wallet Security → Check "🔓 Click to Reveal Mnemonic"** +4. **Return to Metrics** → The actual mnemonic words will now be visible +5. **Save Your Mnemonic** → Copy and store it securely offline +6. **One-Time Only** → After you view it once, it permanently shows: `[Mnemonic was displayed and should have been saved]` + +> **Note:** The reveal checkbox is **separate from** the main "Enabled" toggle that controls the entire pup. You must start the pup first, then use the reveal checkbox to see the mnemonic. + +### Security Features + +- 🔒 **Hidden by default** - Mnemonic starts masked, you must check the reveal box +- 🔓 **Reveal checkbox** - Dedicated checkbox in Wallet Security settings +- ⚠️ **One-time display** - Can only be viewed during first initialization +- 💾 **Never persisted** - Stored in temporary file, deleted after first display +- ✅ **Independent control** - Separate from pup enable/disable + +**Important: After enabling the pup, go to Settings → Wallet Security → Check the reveal box to see your mnemonic, then save it securely!** diff --git a/spv_enclave/manifest.json b/spv_enclave/manifest.json index e883dc5..14abe9e 100644 --- a/spv_enclave/manifest.json +++ b/spv_enclave/manifest.json @@ -2,7 +2,7 @@ "manifestVersion": 1, "meta": { "name": "Libdogecoin SPV Enclave", - "version": "0.0.2", + "version": "0.0.7", "logoPath": "logo.png", "shortDescription": "Run a libdogecoin SPV node on your dogebox", "longDescription": "Libdogecoin SPV Enclave runs a minimal node on your dogebox with the key management enclave. Insert a Yubikey before operation.", @@ -11,12 +11,27 @@ } }, "config": { - "sections": null + "sections": [ + { + "name": "security", + "label": "Wallet Security", + "fields": [ + { + "name": "REVEAL_MNEMONIC", + "label": "🔓 Click to Reveal Mnemonic", + "type": "checkbox", + "required": false, + "default": "false", + "help": "Check this box to reveal your wallet mnemonic in the Metrics display. The mnemonic is hidden until you check this box. Once revealed and saved, it will be permanently hidden." + } + ] + } + ] }, "container": { "build": { "nixFile": "pup.nix", - "nixFileSha256": "71aade30afe5570e6e89fa85b0e1e33bbf9c8fc8f25d8b04b9538dfdf8188763" + "nixFileSha256": "8ec96292c9e862f2844709ad81aa7eb4b9c88c47794d24f46cdfbd00f4e679df" }, "services": [ { @@ -79,6 +94,13 @@ ], "dependencies": null, "metrics": [ + { + "name": "mnemonic", + "label": "⚠️ Wallet Mnemonic (ONE-TIME DISPLAY)", + "type": "string", + "history": 1, + "sensitive": true + }, { "name": "chaintip", "label": "Chain Tip", diff --git a/spv_enclave/monitor/monitor.go b/spv_enclave/monitor/monitor.go index 0329efe..c827608 100644 --- a/spv_enclave/monitor/monitor.go +++ b/spv_enclave/monitor/monitor.go @@ -12,7 +12,10 @@ import ( "time" ) +var storageDirectory = "/storage" + type Metrics struct { + Mnemonic string `json:"mnemonic"` Chaintip string `json:"chaintip"` Balance string `json:"balance"` Addresses string `json:"addresses"` @@ -58,9 +61,56 @@ func fetchEndpoint(endpoint string) (string, error) { return string(body), nil } +// readMnemonic reads the mnemonic from temp file or environment variable +// Returns the mnemonic on first read, then marks as viewed and returns a message +func readMnemonic() string { + // Check if already viewed via environment variable + if os.Getenv("MNEMONIC_VIEWED") == "true" { + return "[Mnemonic was displayed and should have been saved]" + } + + // Try to read mnemonic from temporary file first (for cross-process communication) + mnemonicFile := storageDirectory + "/.mnemonic_temp" + mnemonic := "" + + if data, err := os.ReadFile(mnemonicFile); err == nil { + mnemonic = strings.TrimSpace(string(data)) + } else { + // Fall back to environment variable (shouldn't happen but just in case) + mnemonic = os.Getenv("MNEMONIC_PHRASE") + } + + // If not set yet, check if wallet is being initialized + if mnemonic == "" { + // Check if wallet.db exists to determine state + walletDbFile := storageDirectory + "/wallet.db" + if _, err := os.Stat(walletDbFile); os.IsNotExist(err) { + return "[Waiting for wallet initialization...]" + } + // Wallet exists but mnemonic not available - already been cleared + return "[Mnemonic was displayed and should have been saved]" + } + + // Check if user has revealed the mnemonic via checkbox + revealMnemonic := os.Getenv("REVEAL_MNEMONIC") + if revealMnemonic != "true" { + // Return masked version with reveal instructions + words := strings.Fields(mnemonic) + if len(words) > 0 { + return "[🔒 Hidden - Check 'Click to Reveal Mnemonic' in Wallet Security settings to view]" + } + } + + // Return the mnemonic (will be marked as viewed after successful submission) + return mnemonic +} + func collectMetrics() (Metrics, error) { var metrics Metrics + // Read mnemonic for one-time display + metrics.Mnemonic = readMnemonic() + // Fetch chain tip chaintipStr, err := fetchEndpoint("/getChaintip") if err != nil { @@ -164,6 +214,7 @@ func submitMetrics(metrics Metrics) { } jsonData := map[string]interface{}{ + "mnemonic": map[string]interface{}{"value": metrics.Mnemonic}, "chaintip": map[string]interface{}{"value": metrics.Chaintip}, "balance": map[string]interface{}{"value": metrics.Balance}, "addresses": map[string]interface{}{"value": metrics.Addresses}, @@ -201,6 +252,33 @@ func submitMetrics(metrics Metrics) { body, _ := io.ReadAll(resp.Body) log.Printf("Unexpected status code when submitting metrics: %d", resp.StatusCode) log.Printf("Response body: %s", string(body)) + return + } + + // After successful submission, mark mnemonic as viewed if it was just displayed + markMnemonicAsViewed(metrics.Mnemonic) +} + +// markMnemonicAsViewed marks the mnemonic as viewed and deletes the temporary file +func markMnemonicAsViewed(mnemonic string) { + // Only mark as viewed if we actually sent a real mnemonic (not a status message) + if !strings.HasPrefix(mnemonic, "[") { + // Set environment variable to mark as viewed + if err := os.Setenv("MNEMONIC_VIEWED", "true"); err != nil { + log.Printf("Error setting MNEMONIC_VIEWED environment variable: %v", err) + } else { + log.Println("Mnemonic displayed successfully - marked as viewed via environment variable") + } + + // Delete the temporary mnemonic file for security + mnemonicFile := storageDirectory + "/.mnemonic_temp" + if err := os.Remove(mnemonicFile); err != nil { + if !os.IsNotExist(err) { + log.Printf("Error deleting temporary mnemonic file: %v", err) + } + } else { + log.Println("Temporary mnemonic file deleted successfully") + } } } diff --git a/spv_enclave/pup.nix b/spv_enclave/pup.nix index 808ae7b..620d9aa 100644 --- a/spv_enclave/pup.nix +++ b/spv_enclave/pup.nix @@ -23,11 +23,34 @@ let fi # Generate a mnemonic with the libdogecoin key management enclave - if [ ! -f "${storageDirectory}/present" ]; then - # YubiKey (TOTP) path - { sleep 1; printf '\n'; sleep 1; printf 'y\n'; } | \ + if [ ! -f "${storageDirectory}/wallet.db" ]; then + # Create output.log and display one-time mnemonic warning + echo "============================================" > "${storageDirectory}/output.log" + echo "⚠️ ONE-TIME MNEMONIC DISPLAY ⚠️" >> "${storageDirectory}/output.log" + echo "============================================" >> "${storageDirectory}/output.log" + echo "IMPORTANT: Save this mnemonic phrase now!" >> "${storageDirectory}/output.log" + echo "This is your ONLY opportunity to see it." >> "${storageDirectory}/output.log" + echo "It will NOT be saved or shown again." >> "${storageDirectory}/output.log" + echo "============================================" >> "${storageDirectory}/output.log" + echo "" >> "${storageDirectory}/output.log" + + # YubiKey (TOTP) path - capture mnemonic and write to temporary file for monitor + MNEMONIC_PHRASE=$({ sleep 1; printf '\n'; sleep 1; printf 'y\n'; } | \ SHELL=/run/current-system/sw/bin/bash \ - ${util-linux}/bin/script -q -e -c "${optee_libdogecoin}/bin/optee_libdogecoin -c generate_mnemonic -z" /dev/null 2>&1 | tee "${storageDirectory}/present" + ${util-linux}/bin/script -q -e -c "${optee_libdogecoin}/bin/optee_libdogecoin -c generate_mnemonic -z" /dev/null 2>&1) + + # Write mnemonic to temporary file for monitor to read + # This file will be deleted by monitor after first successful display + echo "$MNEMONIC_PHRASE" > "${storageDirectory}/.mnemonic_temp" + chmod 600 "${storageDirectory}/.mnemonic_temp" + + echo "" >> "${storageDirectory}/output.log" + echo "🔐 Mnemonic generated successfully!" >> "${storageDirectory}/output.log" + echo "📊 View your mnemonic in the Metrics dashboard" >> "${storageDirectory}/output.log" + echo "⚠️ This is a ONE-TIME display - save it now!" >> "${storageDirectory}/output.log" + echo "============================================" >> "${storageDirectory}/output.log" + echo "Starting wallet initialization..." >> "${storageDirectory}/output.log" + echo "============================================" >> "${storageDirectory}/output.log" # Give the TEE a moment sleep 1