wallet

package
v0.0.0-...-0e82d15 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 20, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Overview

Package wallet provides basic wallet functionality for nmcd.

The wallet package implements key management, address generation, and transaction signing for Namecoin name operations. It supports both unencrypted (legacy) and encrypted wallet storage with password protection.

Features

The wallet provides:

  • ECDSA key pair generation using secp256k1 curve
  • Address generation (P2PKH format)
  • Transaction signing for name operations
  • JSON file persistence with optional encryption
  • Password-based encryption with Argon2id key derivation

Key Management

Keys are stored as KeyPair structures containing:

  • PrivateKey: secp256k1 private key (32 bytes)
  • PublicKey: Corresponding public key
  • Address: Derived P2PKH address

Multiple key pairs can be stored in a single wallet, indexed by address.

Encryption

Version 2 wallets support password encryption:

  • Key derivation: Argon2id with configurable parameters
  • Encryption: AES-256-GCM for authenticated encryption
  • Salt: Unique random salt per wallet
  • Password verification: Hash stored separately from encryption key

The wallet must be unlocked to perform sensitive operations like signing or exporting private keys.

Transaction Creation

The wallet can create unsigned transaction templates for:

  • NAME_NEW: Pre-registration commitment
  • NAME_FIRSTUPDATE: Initial name registration
  • NAME_UPDATE: Value updates and expiration extension

Note: Transaction broadcasting requires additional UTXO management not currently implemented in the wallet package.

Thread Safety

Wallet is safe for concurrent use. All operations acquire appropriate locks via sync.RWMutex. However, Lock/Unlock operations should be coordinated at the application level to prevent unexpected state changes.

File Format

Wallets are stored as JSON files:

Version 1 (unencrypted):

{
  "version": 1,
  "keys": [{"private_key": "...", "address": "..."}]
}

Version 2 (encrypted):

{
  "version": 2,
  "encrypted": true,
  "salt": "...",
  "password_hash": "...",
  "keys": "encrypted-base64-data"
}

Security Considerations

  • Version 1 wallets store private keys in plaintext
  • File permissions should be 0600 (owner read/write only)
  • Password strength directly affects encryption security
  • Wallet unlock caches password in memory until Lock is called
  • Consider using hardware wallets for production funds

Example Usage

Creating a new wallet:

w, err := wallet.NewWallet("/path/to/wallet.json", chainParams)
if err != nil {
    log.Fatal(err)
}

// Generate a new address
addr, err := w.NewAddress()
if err != nil {
    log.Fatal(err)
}
fmt.Printf("New address: %s\n", addr.EncodeAddress())

Encrypting a wallet:

// Encrypt with password
err := w.Encrypt("secure-password")
if err != nil {
    log.Fatal(err)
}

// Later, unlock to use
err = w.Unlock("secure-password")
if err != nil {
    log.Fatal("Wrong password")
}
defer w.Lock()

Signing a transaction:

// Create NAME_UPDATE transaction
tx, err := w.CreateNameUpdate("d/example", `{"ip":"1.2.3.4"}`)
if err != nil {
    log.Fatal(err)
}

// Sign the transaction
err = w.SignTransaction(tx, addr)
if err != nil {
    log.Fatal(err)
}

Limitations

Current limitations:

  • No HD wallet support (BIP32/BIP44)
  • No multi-signature support
  • Limited UTXO management (requires external tracking)
  • Transaction creation but not broadcasting

Package wallet provides basic wallet functionality for nmcd.

Package wallet provides basic wallet functionality for nmcd. It supports key generation, storage, and NAME_UPDATE transaction creation.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BuildNameFirstUpdateScript

func BuildNameFirstUpdateScript(name, randHex, value string, pubKeyHash []byte) ([]byte, error)

BuildNameFirstUpdateScript creates a NAME_FIRSTUPDATE output script for completing name registration. The script format is: OP_NAME_FIRSTUPDATE <name> <rand> <value> OP_2DROP OP_2DROP <P2PKH script>

Parameters:

  • name: The name being registered (e.g., "d/example")
  • randHex: The random salt used in the NAME_NEW commitment, encoded as a hex string (must be non-empty)
  • value: The initial value for the name (typically JSON)
  • pubKeyHash: The 20-byte public key hash for the receiving address

Returns the complete script bytes or an error if parameters are invalid.

func BuildNameNewScript

func BuildNameNewScript(hash, pubKeyHash []byte) ([]byte, error)

BuildNameNewScript creates a NAME_NEW output script for name pre-registration. The script format is: OP_NAME_NEW <hash> OP_2DROP <P2PKH script>

Parameters:

  • hash: The commitment hash (typically Hash(name || rand))
  • pubKeyHash: The 20-byte public key hash for the receiving address

Returns the complete script bytes or an error if parameters are invalid.

func BuildNameUpdateScript

func BuildNameUpdateScript(name, value string, pubKeyHash []byte) ([]byte, error)

BuildNameUpdateScript creates a NAME_UPDATE output script. The script format is: OP_NAME_UPDATE <name> <value> OP_2DROP OP_DROP <P2PKH script>

func ComputeNameNewHash

func ComputeNameNewHash(randBytes []byte, name string) []byte

ComputeNameNewHash computes the NAME_NEW commitment hash. The commitment is RIPEMD160(SHA256(rand || name)).

func CreateNameUpdateTxRaw

func CreateNameUpdateTxRaw(
	name, newValue string,
	destAddress btcutil.Address,
	changeAddress btcutil.Address,
	utxos []UTXO,
	feeRate int64,
) (*wire.MsgTx, error)

CreateNameUpdateTxRaw creates a raw NAME_UPDATE transaction that can be signed. This is useful when the wallet doesn't have all the private keys. The changeAddress should typically be the sender's address, not the destination address. If the name is being transferred, destAddress is the new owner and changeAddress should be the current owner.

func GenerateRand

func GenerateRand() ([]byte, error)

GenerateRand generates random bytes for NAME_NEW commitment.

Types

type KeyPair

type KeyPair struct {
	PrivateKey *btcec.PrivateKey
	PublicKey  *btcec.PublicKey
	Address    btcutil.Address
}

KeyPair represents a private/public key pair.

type UTXO

type UTXO struct {
	TxHash   chainhash.Hash
	Vout     uint32
	Value    int64  // satoshis
	PkScript []byte // the output script
	Address  string // owner address
}

UTXO represents an unspent transaction output.

type Wallet

type Wallet struct {
	// contains filtered or unexported fields
}

Wallet manages keys and creates transactions for name operations.

func NewWallet

func NewWallet(dataDir string, chainParams *chaincfg.Params) (*Wallet, error)

NewWallet creates a new wallet instance.

func (*Wallet) CreateNameFirstUpdateTx

func (w *Wallet) CreateNameFirstUpdateTx(
	name, randHex, value string,
	utxos []UTXO,
	nameNewUtxoIndex int,
	feeRate int64,
	ownerAddress btcutil.Address,
) (*wire.MsgTx, error)

CreateNameFirstUpdateTx creates a NAME_FIRSTUPDATE transaction to complete name registration. This is the second step in the two-phase registration process, revealing the name and setting its initial value. Must be called at least 12 blocks after the NAME_NEW transaction.

Parameters:

  • name: Name being registered (must match the NAME_NEW commitment)
  • randHex: Hex-encoded random bytes from the NAME_NEW transaction (for commitment verification)
  • value: Initial value for the name (max 1023 bytes)
  • nameNewUtxo: UTXO from the NAME_NEW transaction (must be in utxos slice)
  • utxos: Available UTXOs to spend (must include nameNewUtxo)
  • nameNewUtxoIndex: Index of nameNewUtxo in the utxos slice
  • feeRate: Satoshis per byte for transaction fee
  • ownerAddress: Address that will own the name (can be different from NAME_NEW address)

Returns:

  • *wire.MsgTx: Signed NAME_FIRSTUPDATE transaction ready to broadcast
  • error: Any error encountered during transaction creation

func (*Wallet) CreateNameNewTx

func (w *Wallet) CreateNameNewTx(
	randBytes []byte,
	name string,
	utxos []UTXO,
	feeRate int64,
	ownerAddress btcutil.Address,
) (*wire.MsgTx, []byte, error)

CreateNameNewTx creates a NAME_NEW transaction for pre-registering a name commitment. This is the first step in the two-phase name registration process to prevent front-running.

Parameters:

  • randBytes: Random salt value (20 bytes) for the commitment
  • name: Name to be registered (e.g., "d/example")
  • utxos: Available UTXOs to fund the transaction
  • feeRate: Satoshis per byte for transaction fee
  • ownerAddress: Address that will own the name (used for commitment and change)

Returns:

  • *wire.MsgTx: Signed NAME_NEW transaction ready to broadcast
  • []byte: Random bytes used (must be saved for NAME_FIRSTUPDATE)
  • error: Any error encountered during transaction creation

func (*Wallet) CreateNameUpdateTx

func (w *Wallet) CreateNameUpdateTx(
	name, newValue string,
	utxos []UTXO,
	nameUtxoIndex int,
	feeRate int64,
	destAddress btcutil.Address,
) (*wire.MsgTx, error)

CreateNameUpdateTx creates a NAME_UPDATE transaction. Parameters:

  • name: the name to update
  • newValue: the new value for the name
  • utxos: available UTXOs to spend (must include the name UTXO)
  • nameUtxoIndex: index of the UTXO that currently holds the name
  • feeRate: satoshis per byte for the transaction fee
  • destAddress: optional destination address for the name (nil to keep at current address)

Returns the signed transaction and any error.

func (*Wallet) EncryptWallet

func (w *Wallet) EncryptWallet(password string) error

EncryptWallet encrypts the wallet with a password. This migrates an unencrypted wallet to encrypted format. Returns an error if the wallet is already encrypted.

Password policy: minimum 8 characters with at least 2 character classes (uppercase, lowercase, digit, special). This policy does not include a breach-list lookup; callers concerned with credential stuffing should validate passwords against a compromised-password database before calling this method.

func (*Wallet) GenerateKey

func (w *Wallet) GenerateKey() (string, error)

GenerateKey creates a new key pair and returns its address.

func (*Wallet) GetAddresses

func (w *Wallet) GetAddresses() []string

GetAddresses returns all addresses in the wallet in deterministic sorted order.

func (*Wallet) GetKey

func (w *Wallet) GetKey(address string) (*KeyPair, error)

GetKey returns a shallow copy of the key pair for the given address. The struct fields are copied but PrivateKey and PublicKey share the same underlying pointers as the wallet's internal map. Do not mutate the key objects.

func (*Wallet) HasKey

func (w *Wallet) HasKey(address string) bool

HasKey checks if the wallet has a key for the given address.

func (*Wallet) IsEncrypted

func (w *Wallet) IsEncrypted() bool

IsEncrypted returns true if the wallet is encrypted.

func (*Wallet) IsLocked

func (w *Wallet) IsLocked() bool

IsLocked returns true if the wallet is locked.

func (*Wallet) Lock

func (w *Wallet) Lock() error

Lock locks an encrypted wallet, clearing keys from memory. Returns an error if the wallet is not encrypted.

Limitation: btcec.PrivateKey does not expose its internal key bytes for zeroing. Lock drops all references to key objects (making them eligible for GC) and zeroes serialized copies, but the canonical private key bytes inside btcec.PrivateKey may persist in heap memory until garbage-collected.

func (*Wallet) SignTransaction

func (w *Wallet) SignTransaction(tx *wire.MsgTx, utxos []UTXO) error

SignTransaction signs all inputs in a transaction. This is the public API that acquires the lock and validates input count.

func (*Wallet) Unlock

func (w *Wallet) Unlock(password string) error

Unlock unlocks an encrypted wallet with a password. Returns an error if the password is incorrect or the wallet is not encrypted.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL