Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
4c6c301
Initial plan
Copilot Feb 6, 2026
3d93734
Fix mnemonic persistence: display once without saving to file
Copilot Feb 6, 2026
87e274e
Implement one-time mnemonic display in output.log with clear warnings
Copilot Feb 6, 2026
205432f
Document one-time mnemonic display feature in README
Copilot Feb 6, 2026
df9c651
Add graphical mnemonic display as a metric in manifest
Copilot Feb 7, 2026
58276e9
Refactor: Parse mnemonic from output.log instead of temporary file
Copilot Feb 7, 2026
0f0d94a
Use symbol in output.log instead of .mnemonic_viewed file
Copilot Feb 7, 2026
b022bc1
Use environment variable instead of log marker for mnemonic tracking
Copilot Feb 7, 2026
17edec3
Assign mnemonic to environment variable during execution
Copilot Feb 7, 2026
dd0ed7d
Write mnemonic only to environment variable, not to output.log
Copilot Feb 7, 2026
7316b09
Update sha256 hash in manifest.json for pup.nix
Copilot Feb 19, 2026
f0b14f8
Add mnemonic masking with toggle control in settings
Copilot Mar 11, 2026
67a6489
Update pup version to 0.0.3 in manifest.json
Copilot Mar 12, 2026
400c926
Fix mnemonic visibility: default to shown on first initialization (v0…
Copilot Mar 12, 2026
177a8d7
Implement button-to-reveal workflow: hide by default with toggle to r…
Copilot Mar 12, 2026
7edbe0c
Use checkbox reveal button separate from pup enable toggle (v0.0.6)
Copilot Mar 12, 2026
9f2fd03
Fix: Write mnemonic to temp file for monitor cross-process communicat…
Copilot Mar 16, 2026
ca7944b
Update spv_enclave manifest nixFileSha256 to current pup.nix hash
Copilot Mar 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions spv_enclave/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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!**
28 changes: 25 additions & 3 deletions spv_enclave/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand All @@ -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": [
{
Expand Down Expand Up @@ -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",
Expand Down
78 changes: 78 additions & 0 deletions spv_enclave/monitor/monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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},
Expand Down Expand Up @@ -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")
}
}
}

Expand Down
31 changes: 27 additions & 4 deletions spv_enclave/pup.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down