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 ¶
- func BuildNameFirstUpdateScript(name, randHex, value string, pubKeyHash []byte) ([]byte, error)
- func BuildNameNewScript(hash, pubKeyHash []byte) ([]byte, error)
- func BuildNameUpdateScript(name, value string, pubKeyHash []byte) ([]byte, error)
- func ComputeNameNewHash(randBytes []byte, name string) []byte
- func CreateNameUpdateTxRaw(name, newValue string, destAddress btcutil.Address, ...) (*wire.MsgTx, error)
- func GenerateRand() ([]byte, error)
- type KeyPair
- type UTXO
- type Wallet
- func (w *Wallet) CreateNameFirstUpdateTx(name, randHex, value string, utxos []UTXO, nameNewUtxoIndex int, feeRate int64, ...) (*wire.MsgTx, error)
- func (w *Wallet) CreateNameNewTx(randBytes []byte, name string, utxos []UTXO, feeRate int64, ...) (*wire.MsgTx, []byte, error)
- func (w *Wallet) CreateNameUpdateTx(name, newValue string, utxos []UTXO, nameUtxoIndex int, feeRate int64, ...) (*wire.MsgTx, error)
- func (w *Wallet) EncryptWallet(password string) error
- func (w *Wallet) GenerateKey() (string, error)
- func (w *Wallet) GetAddresses() []string
- func (w *Wallet) GetKey(address string) (*KeyPair, error)
- func (w *Wallet) HasKey(address string) bool
- func (w *Wallet) IsEncrypted() bool
- func (w *Wallet) IsLocked() bool
- func (w *Wallet) Lock() error
- func (w *Wallet) SignTransaction(tx *wire.MsgTx, utxos []UTXO) error
- func (w *Wallet) Unlock(password string) error
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func BuildNameFirstUpdateScript ¶
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 ¶
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 ¶
BuildNameUpdateScript creates a NAME_UPDATE output script. The script format is: OP_NAME_UPDATE <name> <value> OP_2DROP OP_DROP <P2PKH script>
func ComputeNameNewHash ¶
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 ¶
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 (*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 ¶
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 ¶
GenerateKey creates a new key pair and returns its address.
func (*Wallet) GetAddresses ¶
GetAddresses returns all addresses in the wallet in deterministic sorted order.
func (*Wallet) GetKey ¶
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) IsEncrypted ¶
IsEncrypted returns true if the wallet is encrypted.
func (*Wallet) Lock ¶
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 ¶
SignTransaction signs all inputs in a transaction. This is the public API that acquires the lock and validates input count.