SeedPass JSON Entry Management and Extensibility

Table of Contents


Introduction

SeedPass is a secure password generator and manager leveraging Bitcoin's BIP-85 standard and integrating with the Nostr network for decentralized synchronization. Instead of pushing one large index file, SeedPass posts snapshot chunks of the index followed by lightweight delta events whenever changes occur. This chunked approach improves reliability and keeps bandwidth usage minimal. To enhance modularity, scalability, and security, SeedPass stores all entries in a single encrypted index file named seedpass_entries_db.json.enc. This document outlines the entry management system, ensuring that new kind types can be added seamlessly without disrupting existing functionalities.


Index File Format

All entries belonging to a seed profile are stored in an encrypted file named seedpass_entries_db.json.enc. This index uses schema_version 3 and contains an entries object keyed by numeric identifiers.

{
  "schema_version": 3,
  "entries": {
    "0": {
      "label": "example.com",
      "length": 8,
      "username": "user",
      "url": "https://example.com",
      "archived": false,
      "type": "password",
      "kind": "password",
      "notes": "",
      "custom_fields": [],
      "origin": ""
    }
  }
}

JSON Schema for Individual Entries

Each entry is stored within seedpass_entries_db.json.enc under the entries dictionary. The structure supports diverse entry types (kind) and allows for future expansions.

General Structure

{
  "label": "Example",
  "length": 8,
  "username": "user@example.com",
  "url": "https://example.com",
  "archived": false,
  "type": "password",
  "kind": "password",
  "notes": "",
  "custom_fields": [],
  "origin": "",
  "tags": [],
  "index": 0
}

Field Descriptions

Password Policy Fields

Example Entries

1. Generated Password

{
  "entry_num": 0,
  "index_num": 0,
  "fingerprint": "a1b2c3d4",
  "kind": "generated_password",
  "data": {
    "title": "Example Website",
    "username": "user@example.com",
    "email": "user@example.com",
    "url": "https://example.com",
    "password": "<encrypted_password>"
  },
  "custom_fields": [
    {"name": "department", "value": "finance"}
  ],
  "tags": ["work"],
  "timestamp": "2024-04-27T12:34:56Z",
  "metadata": {
    "created_at": "2024-04-27T12:34:56Z",
    "updated_at": "2024-04-27T12:34:56Z",
    "checksum": "abc123def456"
  }
}

2. Stored Password

{
  "entry_num": 1,
  "index_num": "q1wec4d426fs",
  "fingerprint": "a1b2c3d4",
  "kind": "stored_password",
  "data": {
    "title": "Another Service",
    "username": "another_user",
    "password": "<encrypted_password>"
  },
  "timestamp": "2024-04-27T12:35:56Z",
  "metadata": {
    "created_at": "2024-04-27T12:35:56Z",
    "updated_at": "2024-04-27T12:35:56Z",
    "checksum": "def789ghi012"
  }
}

Password Entry with Policy Overrides

{
  "label": "Custom Policy",
  "length": 16,
  "include_special_chars": false,
  "exclude_ambiguous": true
}

3. Managed User

{
  "entry_num": 2,
  "index_num": "a1b2c3d4e5f6",
  "fingerprint": "a1b2c3d4",
  "kind": "managed_user",
  "data": {
    "users_password": "<encrypted_users_password>"
  },
  "timestamp": "2024-04-27T12:36:56Z",
  "metadata": {
    "created_at": "2024-04-27T12:36:56Z",
    "updated_at": "2024-04-27T12:36:56Z",
    "checksum": "ghi345jkl678"
  }
}

4. 12 Word Seed

{
  "entry_num": 3,
  "index_num": "f7g8h9i0j1k2",
  "fingerprint": "a1b2c3d4",
  "kind": "12_word_seed",
  "data": {
    "seed_phrase": "<encrypted_seed_phrase>"
  },
  "timestamp": "2024-04-27T12:37:56Z",
  "metadata": {
    "created_at": "2024-04-27T12:37:56Z",
    "updated_at": "2024-04-27T12:37:56Z",
    "checksum": "jkl901mno234"
  }
}

5. Nostr Keys

{
  "entry_num": 4,
  "index_num": "l3m4n5o6p7q8",
  "fingerprint": "a1b2c3d4",
  "kind": "nostr_keys",
  "data": {
    "public_key": "<public_key>",
    "private_key": "<encrypted_private_key>"
  },
  "timestamp": "2024-04-27T12:38:56Z",
  "metadata": {
    "created_at": "2024-04-27T12:38:56Z",
    "updated_at": "2024-04-27T12:38:56Z",
    "checksum": "mno567pqr890"
  }
}

6. Note

{
  "entry_num": 5,
  "index_num": "r9s0t1u2v3w4",
  "fingerprint": "a1b2c3d4",
  "kind": "note",
  "data": {
    "content": "This is a secure note.",
    "tags": ["personal", "secure"]
  },
  "timestamp": "2024-04-27T12:39:56Z",
  "metadata": {
    "created_at": "2024-04-27T12:39:56Z",
    "updated_at": "2024-04-27T12:39:56Z",
    "checksum": "pqr345stu678"
  }
}

7. Key/Value

{
  "entry_num": 6,
  "fingerprint": "a1b2c3d4",
  "kind": "key_value",
  "data": {
    "key": "api_key",
    "value": "<encrypted_value>"
  },
  "tags": ["api"],
  "timestamp": "2024-04-27T12:40:56Z"
}

8. Managed Account

{
  "entry_num": 7,
  "fingerprint": "a1b2c3d4",
  "kind": "managed_account",
  "data": {
    "account": "alice@example.com",
    "password": "<encrypted_password>"
  },
  "timestamp": "2024-04-27T12:41:56Z"
}

Managed accounts store a child seed derived from the parent profile. The entry is saved under .seedpass/<parent_fp>/accounts/<child_fp> where <child_fp> is the managed account's fingerprint. When loaded, the CLI displays a breadcrumb like <parent_fp> > Managed Account > <child_fp>. Press Enter on the main menu to exit back to the parent profile.

The key field is purely descriptive, while value holds the sensitive string such as an API token. Notes and custom fields may also be included alongside the standard metadata.


Handling kind Types and Extensibility

Extensible JSON Schema Design

The JSON schema is designed to be extensible and forward-compatible, allowing new kind types to be added without impacting existing functionalities.

a. Core Structure

Each entry is encapsulated in its own JSON file with a standardized structure:

{
  "entry_num": 0,
  "index_num": 0,
  "fingerprint": "a1b2c3d4",
  "kind": "generated_password",
  "data": {
    // Fields specific to the kind
  },
  "timestamp": "2024-04-27T12:34:56Z",
  "metadata": {
    "created_at": "2024-04-27T12:34:56Z",
    "updated_at": "2024-04-27T12:34:56Z",
    "checksum": "<checksum_value>"
  }
}

b. The kind Field

Example:

"kind": "cryptocurrency_wallet"

c. The data Object

Example for a New Kind (cryptocurrency_wallet):

"data": {
  "wallet_name": "My Bitcoin Wallet",
  "address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
  "private_key": "<encrypted_private_key>"
}

Ensuring Backward Compatibility

To maintain compatibility as new kind types are introduced, implement the following practices:

a. Graceful Handling of Unknown Kinds

Pseudo-Code Example:

def process_entry(entry):
    kind = entry.get("kind")
    data = entry.get("data")
    fingerprint = entry.get("fingerprint")
    
    if kind == "generated_password":
        handle_generated_password(data, fingerprint)
    elif kind == "stored_password":
        handle_stored_password(data, fingerprint)
    # ... other known kinds ...
    else:
        log_warning(f"Unknown kind: {kind}. Skipping entry.")

b. Versioning the Schema

Example:

"seedpass_version": "1.0.0"

c. Documentation and Standards

Best Practices for Adding New Kinds

To ensure seamless integration of new kind types in the future, consider the following best practices:

a. Consistent Naming Conventions

b. Modular Code Architecture

Example:

# handlers.py

def handle_generated_password(data, fingerprint):
    # Implementation

def handle_stored_password(data, fingerprint):
    # Implementation

def handle_cryptocurrency_wallet(data, fingerprint):
    # Implementation

c. Validation and Error Handling

Example:

def handle_cryptocurrency_wallet(data, fingerprint):
    required_fields = ["wallet_name", "address", "private_key"]
    for field in required_fields:
        if field not in data:
            raise ValueError(f"Missing required field '{field}' in cryptocurrency_wallet entry.")
    # Proceed with processing

d. Backward Compatibility Testing


Adding New kind Types

Adding new kind types is straightforward due to the extensible JSON schema design. Below is a step-by-step guide to adding a new kind without affecting existing functionalities.

Example: Adding cryptocurrency_wallet

a. Define the New Kind Structure

Create a JSON file following the standardized structure with the new kind value.

{
  "entry_num": 6,
  "index_num": "x1y2z3a4b5c6",
  "fingerprint": "a1b2c3d4",
  "kind": "cryptocurrency_wallet",
  "data": {
    "wallet_name": "My Bitcoin Wallet",
    "address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
    "private_key": "<encrypted_private_key>"
  },
  "timestamp": "2024-04-27T12:40:56Z",
  "metadata": {
    "created_at": "2024-04-27T12:40:56Z",
    "updated_at": "2024-04-27T12:40:56Z",
    "checksum": "stu901vwx234"
  }
}

b. Update the Application to Handle the New Kind

Implement Handler Function:

def handle_cryptocurrency_wallet(data, fingerprint):
    wallet_name = data.get("wallet_name")
    address = data.get("address")
    private_key = decrypt(data.get("private_key"))
    # Process the cryptocurrency wallet entry
    # e.g., store in memory, display to user, etc.

Integrate the Handler:

def process_entry(entry):
    kind = entry.get("kind")
    data = entry.get("data")
    fingerprint = entry.get("fingerprint")
    
    if kind == "generated_password":
        handle_generated_password(data, fingerprint)
    elif kind == "stored_password":
        handle_stored_password(data, fingerprint)
    elif kind == "cryptocurrency_wallet":
        handle_cryptocurrency_wallet(data, fingerprint)
    # ... other known kinds ...
    else:
        log_warning(f"Unknown kind: {kind}. Skipping entry.")

c. No Impact on Existing Kinds

Existing kinds such as generated_password, stored_password, etc., continue to operate without any changes. The introduction of cryptocurrency_wallet is additive and does not interfere with the processing of other kinds.


Backup and Rollback Mechanism

To ensure data integrity and provide recovery options, SeedPass implements a robust backup and rollback system within the Fingerprint-Based Backup and Local Storage framework.

Backup Directory Structure

All backups are organized based on fingerprints, ensuring that each seed's data remains isolated and secure.

~/.seedpass/
├── a1b2c3d4/
│   ├── entries/
│   │   ├── entry_0.json
│   │   ├── entry_1.json
│   │   └── ...
│   ├── backups/
│   │   ├── entry_0_v1.json
│   │   ├── entry_0_v2.json
│   │   ├── entry_1_v1.json
│   │   └── ...
│   ├── parent_seed.enc
│   ├── seedpass_entries_db_checksum.txt
│   └── seedpass_entries_db.json
├── b5c6d7e8/
│   ├── entries/
│   │   ├── entry_0.json
│   │   ├── entry_1.json
│   │   └── ...
│   ├── backups/
│   │   ├── entry_0_v1.json
│   │   ├── entry_0_v2.json
│   │   ├── entry_1_v1.json
│   │   └── ...
│   ├── parent_seed.enc
│   ├── seedpass_entries_db_checksum.txt
│   └── seedpass_entries_db.json
└── ...

Backup Process

  1. Upon Modifying an Entry:

    • The current version of the entry is copied to the backups/ directory within the corresponding fingerprint folder with a version suffix (e.g., entry_0_v1.json).
    • The modified entry is saved in the entries/ directory within the same fingerprint folder.
  2. Versioning:

    • Each backup file includes a version number to track changes over time.

Rollback Functionality

Example Command:

seedpass rollback --fingerprint a1b2c3d4 --file entry_0_v1.json

Example Directory Structure After Rollback:

~/.seedpass/
├── a1b2c3d4/
│   ├── entries/
│   │   ├── entry_0.json  # Restored from entry_0_v1.json
│   │   ├── entry_1.json
│   │   └── ...
│   ├── backups/
│   │   ├── entry_0_v1.json
│   │   ├── entry_0_v2.json
│   │   ├── entry_1_v1.json
│   │   └── ...
│   ├── parent_seed.enc
│   ├── seedpass_script_checksum.txt
│   ├── seedpass_entries_db_checksum.txt
│   └── seedpass_entries_db.json
├── ...

Note: Restoring a backup overwrites the current entry. Ensure that you intend to revert to the selected backup before proceeding.