Pure Go Namecoin library and daemon using btcd as dependencies (not forks).
nmcd is a library-first Namecoin implementation built using btcd libraries. It can be embedded directly into your Go applications for in-process name resolution and registration, or run as a standalone daemon for traditional RPC access.
Key Highlights:
go get github.com/opd-ai/nmcd
package main
import (
"context"
"fmt"
"log"
"github.com/opd-ai/nmcd/client"
)
func main() {
// Create embedded client (runs in-process)
nc, err := client.NewClient(&client.Config{
Mode: client.ModeAuto, // Auto-detect daemon or use embedded
Network: "mainnet",
})
if err != nil {
log.Fatal(err)
}
defer nc.Close()
// Resolve a Namecoin name
ctx := context.Background()
record, err := nc.ResolveName(ctx, "d/example")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Name: %s\nValue: %s\nOwner: %s\n",
record.Name, record.Value, record.Address)
}
// Register a new name
result, err := nc.RegisterName(ctx, "d/mysite", `{"ip":"1.2.3.4"}`, nil)
fmt.Printf("Registration TX: %s\n", result.TxHash)
// Update existing name
result, err = nc.UpdateName(ctx, "d/mysite", `{"ip":"5.6.7.8"}`, nil)
fmt.Printf("Update TX: %s\n", result.TxHash)
// List names with filters
names, err := nc.ListNames(ctx, &client.ListFilter{
Namespace: "d/",
Limit: 100,
})
π See docs/EXAMPLES.md for detailed walkthroughs and patterns.
When run as a standalone daemon, nmcd provides:
The daemon automatically synchronizes with the Namecoin network using a headers-first sync protocol:
/ready endpoint to check if sync is completeThe embedded client mode automatically connects to the network and syncs blocks when MaxPeers > 0 (default: 8). By default, it uses DNS seed discovery to find peers. To disable automatic network sync, set MaxPeers to 0 or provide custom BootstrapPeers.
Starting with v1.0.0, nmcd follows Semantic Versioning and provides strong backward compatibility guarantees:
client package interface is stable and will maintain backward compatibilitySee CHANGELOG.md for:
Current Status: v0.1.0 (development) β Working towards v1.0.0 production release
nmcd supports three operational modes:
Automatically detects if a daemon is running on localhost:8336 and uses it; otherwise runs in embedded mode.
nc, err := client.NewClient(&client.Config{
Mode: client.ModeAuto, // Default
})
Use When:
Runs the full blockchain, database, and network stack in-process. No external daemon required.
nc, err := client.NewEmbeddedClient(&client.Config{
Mode: client.ModeEmbedded,
DataDir: "/path/to/data",
Network: "mainnet",
})
Network Connectivity:
By default, embedded mode automatically connects to the Namecoin network using DNS seed discovery (MaxPeers defaults to 8). To customize network behavior:
// Disable automatic network sync (offline mode)
nc, err := client.NewEmbeddedClient(&client.Config{
Mode: client.ModeEmbedded,
MaxPeers: 0, // No peer connections
})
// Use custom bootstrap peers (skip DNS seeds)
nc, err := client.NewEmbeddedClient(&client.Config{
Mode: client.ModeEmbedded,
BootstrapPeers: []string{
"peer1.example.com:8334",
"peer2.example.com:8334",
},
})
Use When:
Pros:
Cons:
Connects to an existing nmcd or Namecoin Core daemon via RPC.
nc, err := client.NewDaemonClient(&client.Config{
Mode: client.ModeDaemon,
RPCAddr: "http://localhost:8336",
RPCUser: "user",
RPCPassword: "pass",
})
Use When:
Pros:
Cons:
π See docs/MODES.md for detailed comparison and recommendations.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Your Go Application β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β nmcd Library (client/) β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β NameClient Interface (Public API) β β
β β β’ ResolveName(name) β NameRecord β β
β β β’ RegisterName(name, value, opts) β TxHash β β
β β β’ UpdateName(name, value, opts) β TxHash β β
β β β’ ListNames(filter) β []NameRecord β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β² β² β
β β β β
β βββββββββββ΄ββββββββββ ββββββββββββ΄βββββββββββ β
β β EmbeddedClient β β DaemonClient β β
β β (in-process) β β (RPC to daemon) β β
β βββββββββββ¬ββββββββββ βββββββββββββββββββββββ β
β β β
β βββββββββββΌββββββββββββββββββββββββββ β
β β Embedded Components β β
β β ββββββββββ ββββββββββ βββββββββββ β
β β β chain/ β βnamedb/ β βnetwork/ββ β
β β βvalidateβ βbbolt DBβ β peer ββ β
β β ββββββββββ ββββββββββ βββββββββββ β
β βββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
While nmcd is primarily a library, it can also run as a standalone daemon for traditional RPC access.
go build -v ./cmd/nmcd
# Run with defaults (auto-discovers peers via DNS seeds)
./nmcd
# Custom data directory
./nmcd -datadir=/path/to/data
# Testnet
./nmcd -network=testnet
# Custom RPC port
./nmcd -rpcaddr=127.0.0.1:18336
# Connect to specific peer (bypasses DNS seed discovery)
./nmcd -addpeer=peer.example.com:8334
# Enable RPC authentication (recommended for security)
./nmcd -rpcuser=myuser -rpcpassword=mypassword
Note: When using nmcd as a library, you donβt need to run the daemon unless you want to share blockchain state across multiple applications.
nmcd supports automatic peer discovery via DNS seeds. When started without the -addpeer flag, the node will:
Mainnet DNS Seeds:
nmc.seed.quisquis.deseed.nmc.markasoftware.comdnsseed1.nmc.dotbit.zonednsseed2.nmc.dotbit.zonednsseed.nmc.testls.spacenamecoin.seed.cypherstack.comTestnet DNS Seeds:
dnsseed.test.namecoin.webbtc.comTo bypass DNS seed discovery and connect to specific peers, use the -addpeer flag.
When running nmcd as a daemon, it exposes a JSON-RPC interface for external access. If youβre using nmcd as a library, use the Go API instead of RPC for better performance and type safety.
The RPC server supports HTTP Basic Authentication. When both -rpcuser and -rpcpassword flags are set, all RPC requests must include valid credentials.
Security Considerations:
ps, top). For production use, consider environment variables or a configuration file# With authentication enabled
curl -X POST http://127.0.0.1:8336 \
-u myuser:mypassword \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"getinfo","params":[],"id":1}'
getinfo - Get general informationgetblockcount - Get current block heightgetbestblockhash - Get best block hashgetconnectioncount - Get peer connection countgetpeerinfo - Get connected peer informationname_new - Create a NAME_NEW transaction to pre-register a name commitment
curl -X POST http://127.0.0.1:8336 \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"name_new","params":["d/example"],"id":1}'
This is the first step in the two-phase name registration process. It creates a commitment hash to prevent front-running. The response includes a rand value (hex-encoded random bytes) that must be saved for the NAME_FIRSTUPDATE step.
Returns:
{
"txid": "transaction_hash",
"name": "d/example",
"rand": "hex_encoded_random_bytes",
"status": "broadcasted"
}
Important: Save the rand value! Youβll need it for name_firstupdate.
name_firstupdate - Complete name registration with NAME_FIRSTUPDATE
curl -X POST http://127.0.0.1:8336 \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"name_firstupdate","params":["d/example","hex_rand_from_name_new","{\"ip\":\"1.2.3.4\"}"],"id":1}'
Parameters: ["name", "rand", "value"]
This is the second step in the two-phase registration process. Requirements:
name_newname_newrand parameter must match the value returned by name_newvalue must be valid UTF-8 (max 1023 bytes), JSON for d/ and id/ namespacesReturns:
{
"txid": "transaction_hash",
"name": "d/example",
"value": "{\"ip\":\"1.2.3.4\"}",
"status": "broadcasted"
}
name_show - Show name information
curl -X POST http://127.0.0.1:8336 \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"name_show","params":["d/example"],"id":1}'
name_list - List all registered names
curl -X POST http://127.0.0.1:8336 \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"name_list","params":[],"id":1}'
name_history - Get name history
curl -X POST http://127.0.0.1:8336 \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"name_history","params":["d/example"],"id":1}'
name_update - Update an existing nameβs value
curl -X POST http://127.0.0.1:8336 \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"name_update","params":["d/example","new_value"],"id":1}'
Parameters: ["name", "value"] or ["name", "value", "address"]
The wallet must have the private key for the address that owns the name. If no address is specified, the name stays at its current address.
getnewaddress - Generate a new address in the wallet
curl -X POST http://127.0.0.1:8336 \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"getnewaddress","params":[],"id":1}'
Returns a new address string. The address is persisted to the wallet file.
listaddresses - List all addresses in the wallet
curl -X POST http://127.0.0.1:8336 \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"listaddresses","params":[],"id":1}'
Returns an array of address strings currently stored in the wallet.
Security Warning: By default, nmcd wallets store private keys unencrypted in wallet.json with file permissions 0600. For production use, always encrypt your wallet.
encryptwallet - Encrypt the wallet with a password (one-time operation)
curl -X POST http://127.0.0.1:8336 \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"encryptwallet","params":["YourSecurePassword123"],"id":1}'
Parameters: ["password"]
Returns: Success message with backup reminder
walletpassphrase - Unlock the encrypted wallet temporarily
curl -X POST http://127.0.0.1:8336 \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"walletpassphrase","params":["YourSecurePassword123",300],"id":1}'
Parameters: ["password", timeout] or ["password"]
password - Your wallet passwordtimeout - Seconds to keep wallet unlocked (default: 60)Unlocks the wallet for the specified duration. The wallet will automatically lock after the timeout expires. While unlocked, you can:
Security: Keys are loaded into memory while unlocked and cleared on lock.
walletlock - Lock the wallet immediately
curl -X POST http://127.0.0.1:8336 \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"walletlock","params":[],"id":1}'
Locks an encrypted wallet, removing all private keys from memory. The wallet must be unlocked again with walletpassphrase before performing operations that require private keys.
Security Best Practices:
walletlock immediately after completing sensitive operationswalletpassphrasenmcd provides HTTP health check endpoints suitable for Kubernetes liveness and readiness probes, load balancers, and monitoring systems.
Health Endpoint (/health):
curl http://127.0.0.1:8336/health
Returns HTTP 200 OK when the daemon is running and initialized, or 503 Service Unavailable when initializing.
Response:
{
"status": "healthy",
"block_height": 500000,
"peers": 8,
"syncing": true
}
The syncing field is optional (omitempty) and appears when the node is initializing or syncing blocks.
Use this endpoint for liveness probes to detect if the process is alive and responsive.
Readiness Endpoint (/ready):
curl http://127.0.0.1:8336/ready
Returns HTTP 200 OK when the daemon is ready to serve requests (sync complete), or 503 Service Unavailable when syncing or initializing.
Response:
{
"status": "ready",
"block_height": 500000,
"peers": 8,
"syncing": false
}
Use this endpoint for readiness probes to detect if the node is ready to handle traffic.
Kubernetes Example:
livenessProbe:
httpGet:
path: /health
port: 8336
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8336
initialDelaySeconds: 5
periodSeconds: 5
Security Note: These endpoints do not require authentication and are intended for health checking systems. Bind the RPC server to localhost (default) or use network-level controls to restrict access.
nmcd can optionally expose metrics in Prometheus format for monitoring and observability. The metrics are served on a separate HTTP endpoint from the RPC server.
Enable Prometheus metrics:
# Enable on default port 9100
./nmcd -prometheusaddr=127.0.0.1:9100
# Or use custom port
./nmcd -prometheusaddr=127.0.0.1:9999
Once enabled, metrics are available at http://<address>/metrics in Prometheus text format.
Security Considerations:
The Prometheus metrics endpoint does not implement authentication and exposes operational data about the node (e.g., block processing statistics and name operation counts). For production deployments, you should:
-prometheusaddr to 127.0.0.1 (localhost only), orIf you bind the metrics endpoint to a non-localhost network interface, use network-level controls (firewall rules, VPN, or restricted subnets) to allow access only from trusted monitoring systems.
Available Metrics:
Block Processing:
nmcd_blocks_processed_total - Total blocks processednmcd_blocks_accepted_total - Blocks accepted on main chainnmcd_blocks_orphaned_total - Orphaned blocks receivednmcd_blocks_rejected_total - Blocks rejected due to validation errorsnmcd_last_block_height - Height of last processed blocknmcd_avg_block_process_time_seconds - Average block processing timeName Operations:
nmcd_name_operations_total - Total name operations processednmcd_name_new_total - NAME_NEW operationsnmcd_name_firstupdate_total - NAME_FIRSTUPDATE operationsnmcd_name_update_total - NAME_UPDATE operationsnmcd_names_expired_total - Names expired during processingNetwork:
nmcd_peers_connected - Currently connected peersnmcd_inbound_peers - Current inbound peersnmcd_outbound_peers - Current outbound peersnmcd_peer_disconnects_total - Total peer disconnectionsTransactions:
nmcd_txs_processed_total - Total transactions processednmcd_txs_in_mempool - Current transactions in mempoolValidation Errors:
nmcd_validation_errors_total - Total validation errorsnmcd_auxpow_errors_total - AuxPoW validation errorsnmcd_subsidy_errors_total - Block subsidy validation errorsnmcd_name_theft_attempts_total - Name theft attempts detectednmcd_double_spend_attempts_total - Double-spend attempts detectedDatabase Performance (New):
nmcd_namedb_size_bytes - Size of name database in bytesnmcd_namedb_read_latency_seconds - Average database read latencynmcd_namedb_write_latency_seconds - Average database write latencyRPC Performance (New):
nmcd_rpc_requests_total{method="METHOD"} - Total RPC requests by methodnmcd_rpc_duration_seconds{method="METHOD"} - Average RPC duration by methodError Breakdown (New):
nmcd_errors_total{type="validation"} - Validation errorsnmcd_errors_total{type="network"} - Network errorsnmcd_errors_total{type="database"} - Database errorsGo Runtime (New):
nmcd_go_goroutines - Number of goroutinesnmcd_go_memstats_alloc_bytes - Bytes allocated and in usenmcd_go_memstats_heap_alloc_bytes - Heap bytes allocated and in usenmcd_go_memstats_heap_idle_bytes - Heap bytes waiting to be usednmcd_go_memstats_heap_inuse_bytes - Heap bytes that are in useExample Prometheus Query:
curl http://127.0.0.1:9100/metrics
Prometheus Configuration:
scrape_configs:
- job_name: 'nmcd'
static_configs:
- targets: ['localhost:9100']
Grafana Dashboard:
The metrics can be visualized in Grafana. Key panels to consider:
nmcd_blocks_processed_total)nmcd_peers_connected)nmcd_name_*_total metrics)nmcd_*_errors_total metrics)nmcd_txs_in_mempool)nmcd_namedb_read_latency_seconds, nmcd_namedb_write_latency_seconds)rate(nmcd_rpc_requests_total[5m]), nmcd_rpc_duration_seconds)nmcd_errors_total grouped by type label)nmcd_go_memstats_alloc_bytes, nmcd_go_memstats_heap_inuse_bytes)nmcd_go_goroutines)Example Queries:
# Top 5 slowest RPC methods (with method label aggregation)
topk(5, avg by (method) (nmcd_rpc_duration_seconds))
# Error rate by category (per second, grouped by type)
sum by (type) (rate(nmcd_errors_total[5m]))
# Database read/write latency comparison
nmcd_namedb_read_latency_seconds
nmcd_namedb_write_latency_seconds
# RPC request rate by method
sum by (method) (rate(nmcd_rpc_requests_total[5m]))
The examples/ directory contains several working examples demonstrating library usage:
Run an example:
go run ./examples/simple_resolve d/example
go run ./examples/list_names --namespace=d/
π For detailed walkthroughs, see docs/EXAMPLES.md
nmcd includes basic wallet functionality for managing name operations. The wallet stores private keys in wallet.json within the data directory.
Security Note: The wallet file contains unencrypted private keys. Ensure proper file permissions (0600) and secure the data directory.
Library Usage: When using nmcd as a library, the wallet is automatically initialized in the data directory. Use DisableWallet: true in the config to disable wallet functionality if you only need name resolution.
Best For:
Example Applications:
Best For:
Example Setups:
The implementation supports three name operations:
Names expire after 36000 blocks (~250 days) and must be renewed.
Namecoin does not have a separate NAME_DELETE operation. To delete a name, use NAME_UPDATE with an empty value:
// Delete a name by setting empty value
result, err := nc.UpdateName(ctx, "d/mysite", "", nil)
This effectively removes the nameβs data while maintaining ownership on the blockchain. The name will still expire after 36,000 blocks unless renewed.
nmcd/
βββ client/ # π§ Library public API (import this!)
β βββ types.go # NameClient interface and types
β βββ embedded.go # In-process embedded client
β βββ daemon.go # RPC daemon client
β βββ *_test.go # Comprehensive test suite
β
βββ cmd/nmcd/ # Daemon binary entry point
β βββ main.go # CLI flags and daemon setup
β
βββ cmd/permamail/ # π§ Permamail CLI tool
β βββ main.go # Email forwarding management
β
βββ internal/ # Internal packages (not importable)
β βββ server/ # Daemon server implementation
β
βββ bridge/ # Email forwarding bridge adapter
βββ mail/ # SMTP routing and relay
βββ namedb/ # Name database (bbolt)
βββ chain/ # Blockchain wrapper (btcd integration)
βββ network/ # P2P networking (btcd/peer)
βββ rpc/ # JSON-RPC server (daemon mode)
βββ wallet/ # Basic wallet functionality
βββ config/ # Network configuration
β
βββ docs/ # π Documentation
β βββ API.md # Complete API reference
β βββ EMBEDDING.md # Integration guide
β βββ EXAMPLES.md # Example walkthroughs
β βββ MODES.md # Mode comparison
β βββ PERFORMANCE.md # Optimization tips
β
βββ examples/ # π― Working code examples
β βββ simple_resolve/ # Basic name resolution
β βββ embedded_client/ # Embedded mode demo
β βββ register_name/ # Name registration
β βββ update_name/ # Name updates
β βββ list_names/ # Name filtering
β βββ bridge_adapter/ # Email bridge usage
β βββ mail_router/ # Email routing example
β βββ smtp_relay/ # SMTP relay deployment
β βββ namedb/ # Direct database access
β
βββ README.md # This file
Import Paths:
// Primary library interface
import "github.com/opd-ai/nmcd/client"
// For advanced usage (typically not needed)
import "github.com/opd-ai/nmcd/namedb"
import "github.com/opd-ai/nmcd/config"
nmcd includes permamail, a command-line tool for managing decentralized email forwarding via Namecoin. It allows you to register .bit email addresses that forward to your real email address.
# Build both nmcd and permamail
make build
# Or build permamail only
go build -o permamail ./cmd/permamail
# Register a new .bit email address
permamail register alice --forward user@gmail.com
# Update forwarding configuration with backup
permamail update alice --forward newemail@proton.me --backup backup@proton.me
# Look up current configuration
permamail lookup alice
# Start SMTP relay server
# Note: Avoid passing SMTP passwords directly on the command line, as they
# may be visible to other local users via process listings (ps, /proc).
# Consider using environment variables or a config file with restricted permissions.
permamail serve --upstream smtp.sendgrid.net --upstreamport 587 \
--smtpuser apikey --smtppass <api-key>
Permamail consists of three main components:
bridge/): Translates Namecoin name records to email forwarding configurationmail/router.go): Routes .bit addresses to real email addresses with cachingmail/smtp.go): Accepts mail to .bit addresses and forwards to real inboxesThe permamail CLI integrates these components into a single tool for managing email forwarding.
Email forwarding is stored in Namecoin name values as JSON:
{
"email": "user@gmail.com",
"backup": ["backup@proton.me"],
"pubkey": "base64_encoded_public_key"
}
To run a production SMTP relay that forwards .bit emails:
# Start relay on port 2525, forwarding via Gmail
permamail serve --listen :2525 \
--upstream smtp.gmail.com \
--upstreamport 587 \
--smtpuser your.email@gmail.com \
--smtppass your-app-password
# Users can now send email to alice@mail.bit
# The relay will resolve alice -> user@gmail.com and forward
Note: Currently, name registration and updates require a running nmcd node with wallet enabled. The register and update commands prepare the configuration and display instructions for completing the operation via nmcd RPC. Full integrated wallet support is planned for future releases.
See examples/smtp_relay/ for a complete production-ready SMTP relay deployment with systemd integration.
Authentication:
-rpcuser and -rpcpassword to require HTTP Basic Authentication for all RPC requestsRate Limiting:
Config.RateLimit when using as a libraryRequest Size Limits:
Config.MaxRequestSize when using as a librarySecurity Headers:
X-Content-Type-Options: nosniff - Prevents MIME type sniffingX-Frame-Options: DENY - Prevents clickjacking attacksContent-Security-Policy: default-src 'none' - Restricts resource loadingExample RPC Server Configuration:
server, err := rpc.NewServer(&rpc.Config{
Blockchain: blockchain,
PeerMgr: peerMgr,
Wallet: wallet,
ListenAddr: "127.0.0.1:8336",
RPCUser: "myuser",
RPCPassword: "mypassword",
RateLimit: 100, // requests per minute (0 = use default)
MaxRequestSize: 1024 * 1024, // 1MB (0 = use default)
})
nmcd has comprehensive test coverage across all packages:
# Run all tests
go test ./...
# Run tests with coverage
go test -cover ./...
# Run tests with race detector
go test -race ./...
# Generate HTML coverage report
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html
Critical Packages:
Other Packages:
Note: Command-line entry points (cmd/*) and integration components have lower coverage by design, as they primarily wire together well-tested components.
π Detailed Coverage Analysis: See docs/development/COVERAGE.md for comprehensive coverage analysis and improvement roadmap.
To build nmcd from source, you need Go 1.24 or later:
# Clone the repository
git clone https://github.com/opd-ai/nmcd
cd nmcd
# Build the binary
make build
# Or build with custom version
go build -ldflags="-X main.version=1.0.0" ./cmd/nmcd
nmcd provides automated binary releases for multiple platforms. Releases are triggered when a version tag is pushed:
Supported Platforms:
Download Options:
# Pull latest version
docker pull ghcr.io/opd-ai/nmcd:latest
# Run nmcd in Docker, exposing RPC only on localhost
docker run -p 127.0.0.1:8336:8336 -v nmcd-data:/data ghcr.io/opd-ai/nmcd:latest
# Pull specific version
docker pull ghcr.io/opd-ai/nmcd:1.0.0
Docker images are optimized with multi-stage builds and compressed to <100MB.
Security note: The JSON-RPC interface is sensitive. Before exposing port
8336beyond localhost (for example by using-p 8336:8336or binding to a non-loopback address), configure strong RPC credentials via-rpcuserand-rpcpassword(or enforce equivalent network access controls such as a firewall, VPN, or TLS-terminating reverse proxy) to prevent unauthorized access.
Verifying Downloads:
# Verify SHA256 checksum (Linux/macOS)
sha256sum -c nmcd-linux-amd64.sha256
# Or manually compare
sha256sum nmcd-linux-amd64
cat nmcd-linux-amd64.sha256
Contributions are welcome! Areas of interest:
Please ensure:
go fmt, go vet)make test)See LICENSE file for details.