mirror of
https://github.com/LBRYFoundation/lbcwallet.git
synced 2025-08-23 17:47:29 +00:00
Initial commit.
This commit is contained in:
commit
a56e4e89d2
9 changed files with 1773 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
btcwallet
|
95
README.md
Normal file
95
README.md
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
btcwallet
|
||||||
|
=========
|
||||||
|
|
||||||
|
btcwallet is a daemon handling bitcoin wallet functions. It relies on
|
||||||
|
a running btcd instance for asynchronous blockchain queries and
|
||||||
|
notifications over websockets.
|
||||||
|
|
||||||
|
In addition to the HTTP server run by btcd to provide RPC and
|
||||||
|
websocket connections, btcwallet requires an HTTP server of its own to
|
||||||
|
provide websocket connections to wallet frontends. Websockets allow for
|
||||||
|
asynchronous queries, replies, and notifications.
|
||||||
|
|
||||||
|
This project is currently under active development is not production
|
||||||
|
ready yet.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Frontends wishing to use btcwallet must connect to the websocket
|
||||||
|
`/wallet`. Messages sent to btcwallet over this websocket are
|
||||||
|
expected to follow the standard [Bitcoin JSON
|
||||||
|
API](https://en.bitcoin.it/wiki/Original_Bitcoin_client/API_Calls_list)
|
||||||
|
and replies follow the same API. The btcd package `btcjson` provides
|
||||||
|
types and functions for creating messages that this API. However, due
|
||||||
|
to taking a synchronous protocol like RPC and using it asynchronously,
|
||||||
|
it is recommend for frontends to use the JSON `id` field as a sequence
|
||||||
|
number so replies can be mapped back to the messages they originated
|
||||||
|
from.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
btcwallet can be installed with the go get command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get github.com/conformal/btcwallet
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
To run btcwallet, you must have btcd installed and running. By
|
||||||
|
default btcd will run its HTTP server for RPC and websocket
|
||||||
|
connections on port 8332. However, bitcoind frontends expecting
|
||||||
|
wallet functionality may require to poll on port 8332, requiring the
|
||||||
|
btcd component in a btcwallet+btcd replacement stack to run on an
|
||||||
|
alternate port. For this reason, btcwallet by default connects to
|
||||||
|
btcd on port 8334 and runs its own HTTP server on 8332. When using
|
||||||
|
both btcd and btcwallet, it is recommend to run btcd on the
|
||||||
|
non-standard port 8334 using the `-r` command line flag.
|
||||||
|
|
||||||
|
Assumming btcd is running on port 8334, btcwallet can be
|
||||||
|
started by running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
btcwallet -f /path/to/wallet
|
||||||
|
```
|
||||||
|
|
||||||
|
## GPG Verification Key
|
||||||
|
|
||||||
|
All official release tags are signed by Conformal so users can ensure the code
|
||||||
|
has not been tampered with and is coming from Conformal. To verify the
|
||||||
|
signature perform the following:
|
||||||
|
|
||||||
|
- Download the public key from the Conformal website at
|
||||||
|
https://opensource.conformal.com/GIT-GPG-KEY-conformal.txt
|
||||||
|
|
||||||
|
- Import the public key into your GPG keyring:
|
||||||
|
```bash
|
||||||
|
gpg --import GIT-GPG-KEY-conformal.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
- Verify the release tag with the following command where `TAG_NAME` is a
|
||||||
|
placeholder for the specific tag:
|
||||||
|
```bash
|
||||||
|
git tag -v TAG_NAME
|
||||||
|
```
|
||||||
|
|
||||||
|
## What works
|
||||||
|
- New addresses can be queried if they are in the wallet file address pool
|
||||||
|
- Unknown commands are sent to btcd
|
||||||
|
- Unhandled btcd notifications (i.e. new blockchain height) are sent to each
|
||||||
|
connected frontend
|
||||||
|
- btcd replies are routed back to the correct frontend who initiated the request
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
- Create a new wallet if one is not available
|
||||||
|
- Update UTXO database based on btcd notifications
|
||||||
|
- Require authentication before wallet functionality can be accessed
|
||||||
|
- Support TLS
|
||||||
|
- Documentation
|
||||||
|
- Code cleanup
|
||||||
|
- Optimize
|
||||||
|
- Much much more. Stay tuned.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
btcwallet is licensed under the liberal ISC License.
|
66
cmd.go
Normal file
66
cmd.go
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013 Conformal Systems LLC <info@conformal.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/conformal/btcwallet/wallet"
|
||||||
|
"github.com/conformal/seelog"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
log seelog.LoggerInterface = seelog.Default
|
||||||
|
cfg *config
|
||||||
|
wallets = make(map[string]*wallet.Wallet)
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
tcfg, _, err := loadConfig()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
cfg = tcfg
|
||||||
|
|
||||||
|
// Open wallet
|
||||||
|
file, err := os.Open(cfg.WalletFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error opening wallet:", err)
|
||||||
|
}
|
||||||
|
w := new(wallet.Wallet)
|
||||||
|
if _, err = w.ReadFrom(file); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Associate this wallet with default account.
|
||||||
|
wallets[""] = w
|
||||||
|
|
||||||
|
// Start HTTP server to listen and send messages to frontend and btcd
|
||||||
|
// backend. Try reconnection if connection failed.
|
||||||
|
for {
|
||||||
|
if err := ListenAndServe(); err == ConnRefused {
|
||||||
|
// wait and try again.
|
||||||
|
log.Info("Unable to connect to btcd. Retrying in 5 seconds.")
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
} else if err != nil {
|
||||||
|
log.Info(err.Error())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
176
cmdmgr.go
Normal file
176
cmdmgr.go
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013 Conformal Systems LLC <info@conformal.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/conformal/btcjson"
|
||||||
|
"time"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// seq holds the btcwallet sequence number for frontend messages
|
||||||
|
// which must be sent to and received from btcd. A Mutex protects
|
||||||
|
// against concurrent access.
|
||||||
|
seq = struct {
|
||||||
|
sync.Mutex
|
||||||
|
n uint64
|
||||||
|
}{}
|
||||||
|
|
||||||
|
// replyRouter maps uint64 ids to reply channels, so btcd replies can
|
||||||
|
// be routed to the correct frontend.
|
||||||
|
replyRouter = struct {
|
||||||
|
sync.Mutex
|
||||||
|
m map[uint64]chan []byte
|
||||||
|
}{
|
||||||
|
m: make(map[uint64]chan []byte),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcessFrontendMsg checks the message sent from a frontend. If the
|
||||||
|
// message method is one that must be handled by btcwallet, the request
|
||||||
|
// is processed here. Otherwise, the message is sent to btcd.
|
||||||
|
func ProcessFrontendMsg(reply chan []byte, msg []byte) {
|
||||||
|
cmd, err := btcjson.JSONGetMethod(msg)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Unable to parse JSON method from message.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch cmd {
|
||||||
|
case "getaddressesbyaccount":
|
||||||
|
GetAddressesByAccount(reply, msg)
|
||||||
|
case "getnewaddress":
|
||||||
|
GetNewAddress(reply, msg)
|
||||||
|
case "walletlock":
|
||||||
|
WalletLock(reply, msg)
|
||||||
|
case "walletpassphrase":
|
||||||
|
WalletPassphrase(reply, msg)
|
||||||
|
default:
|
||||||
|
// btcwallet does not understand method. Pass to btcd.
|
||||||
|
log.Info("Unknown btcwallet method", cmd)
|
||||||
|
|
||||||
|
seq.Lock()
|
||||||
|
n := seq.n
|
||||||
|
seq.n++
|
||||||
|
seq.Unlock()
|
||||||
|
|
||||||
|
var m map[string]interface{}
|
||||||
|
json.Unmarshal(msg, &m)
|
||||||
|
m["id"] = fmt.Sprintf("btcwallet(%v)-%v", n, m["id"])
|
||||||
|
newMsg, err := json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Error marshalling json: " + err.Error())
|
||||||
|
}
|
||||||
|
replyRouter.Lock()
|
||||||
|
replyRouter.m[n] = reply
|
||||||
|
replyRouter.Unlock()
|
||||||
|
btcdMsgs <- newMsg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAddressesByAccount Gets all addresses for an account.
|
||||||
|
func GetAddressesByAccount(reply chan []byte, msg []byte) {
|
||||||
|
var v map[string]interface{}
|
||||||
|
json.Unmarshal(msg, &v)
|
||||||
|
params := v["params"].([]interface{})
|
||||||
|
id := v["id"]
|
||||||
|
r := btcjson.Reply{
|
||||||
|
Id: &id,
|
||||||
|
}
|
||||||
|
if w := wallets[params[0].(string)]; w != nil {
|
||||||
|
r.Result = w.GetActiveAddresses()
|
||||||
|
} else {
|
||||||
|
r.Result = []interface{}{}
|
||||||
|
}
|
||||||
|
mr, err := json.Marshal(r)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Error marshalling reply: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
reply <- mr
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNewAddress gets or generates a new address for an account.
|
||||||
|
//
|
||||||
|
// TODO(jrick): support non-default account wallets.
|
||||||
|
func GetNewAddress(reply chan []byte, msg []byte) {
|
||||||
|
var v map[string]interface{}
|
||||||
|
json.Unmarshal(msg, &v)
|
||||||
|
params := v["params"].([]interface{})
|
||||||
|
if len(params) == 0 || params[0].(string) == "" {
|
||||||
|
if w := wallets[""]; w != nil {
|
||||||
|
addr := w.NextUnusedAddress()
|
||||||
|
id := v["id"]
|
||||||
|
r := btcjson.Reply{
|
||||||
|
Result: addr,
|
||||||
|
Id: &id,
|
||||||
|
}
|
||||||
|
mr, err := json.Marshal(r)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Error marshalling reply: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
reply <- mr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WalletLock locks the wallet.
|
||||||
|
//
|
||||||
|
// TODO(jrick): figure out how multiple wallets/accounts will work
|
||||||
|
// with this.
|
||||||
|
func WalletLock(reply chan []byte, msg []byte) {
|
||||||
|
// TODO(jrick)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// WalletPassphrase stores the decryption key for the default account,
|
||||||
|
// unlocking the wallet.
|
||||||
|
//
|
||||||
|
// TODO(jrick): figure out how multiple wallets/accounts will work
|
||||||
|
// with this.
|
||||||
|
func WalletPassphrase(reply chan []byte, msg []byte) {
|
||||||
|
var v map[string]interface{}
|
||||||
|
json.Unmarshal(msg, &v)
|
||||||
|
params := v["params"].([]interface{})
|
||||||
|
if len(params) != 2 {
|
||||||
|
log.Error("walletpasshprase: incorrect parameters")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
passphrase, ok := params[0].(string)
|
||||||
|
if !ok {
|
||||||
|
log.Error("walletpasshprase: incorrect parameters")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
timeout, ok := params[1].(float64)
|
||||||
|
if !ok {
|
||||||
|
log.Error("walletpasshprase: incorrect parameters")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if w := wallets[""]; w != nil {
|
||||||
|
w.Unlock([]byte(passphrase))
|
||||||
|
go func() {
|
||||||
|
time.Sleep(time.Second * time.Duration(int64(timeout)))
|
||||||
|
fmt.Println("finally locking")
|
||||||
|
w.Lock()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
133
config.go
Normal file
133
config.go
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/conformal/go-flags"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultConfigFilename = "btcwallet.conf"
|
||||||
|
defaultBtcdPort = 8334
|
||||||
|
defaultLogLevel = "info"
|
||||||
|
defaultServerPort = 8332
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultConfigFile = filepath.Join(btcwalletHomeDir(), defaultConfigFilename)
|
||||||
|
)
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"`
|
||||||
|
BtcdPort int `short:"b" long:"btcdport" description:"Port to connect to btcd on"`
|
||||||
|
DebugLevel string `short:"d" long:"debuglevel" description:"Logging level {trace, debug, info, warn, error, critical}"`
|
||||||
|
ConfigFile string `short:"C" long:"configfile" description:"Path to configuration file"`
|
||||||
|
SvrPort int `short:"p" long:"serverport" description:"Port to serve frontend websocket connections on"`
|
||||||
|
WalletFile string `short:"f" long:"walletfile" description:"Path to wallet file"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// btcwalletHomeDir returns an OS appropriate home directory for btcwallet.
|
||||||
|
func btcwalletHomeDir() string {
|
||||||
|
// Search for Windows APPDATA first. This won't exist on POSIX OSes.
|
||||||
|
appData := os.Getenv("APPDATA")
|
||||||
|
if appData != "" {
|
||||||
|
return filepath.Join(appData, "btcwallet")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to standard HOME directory that works for most POSIX OSes.
|
||||||
|
home := os.Getenv("HOME")
|
||||||
|
if home != "" {
|
||||||
|
return filepath.Join(home, ".btcwallet")
|
||||||
|
}
|
||||||
|
|
||||||
|
// In the worst case, use the current directory.
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
|
||||||
|
// filesExists reports whether the named file or directory exists.
|
||||||
|
func fileExists(name string) bool {
|
||||||
|
if _, err := os.Stat(name); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadConfig initializes and parses the config using a config file and command
|
||||||
|
// line options.
|
||||||
|
//
|
||||||
|
// The configuration proceeds as follows:
|
||||||
|
// 1) Start with a default config with sane settings
|
||||||
|
// 2) Pre-parse the command line to check for an alternative config file
|
||||||
|
// 3) Load configuration file overwriting defaults with any specified options
|
||||||
|
// 4) Parse CLI options and overwrite/add any specified options
|
||||||
|
//
|
||||||
|
// The above results in btcwallet functioning properly without any config
|
||||||
|
// settings while still allowing the user to override settings with config files
|
||||||
|
// and command line options. Command line options always take precedence.
|
||||||
|
func loadConfig() (*config, []string, error) {
|
||||||
|
// Default config.
|
||||||
|
cfg := config{
|
||||||
|
DebugLevel: defaultLogLevel,
|
||||||
|
ConfigFile: defaultConfigFile,
|
||||||
|
BtcdPort: defaultBtcdPort,
|
||||||
|
SvrPort: defaultServerPort,
|
||||||
|
}
|
||||||
|
|
||||||
|
// A config file in the current directory takes precedence.
|
||||||
|
if fileExists(defaultConfigFilename) {
|
||||||
|
cfg.ConfigFile = defaultConfigFile
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-parse the command line options to see if an alternative config
|
||||||
|
// file or the version flag was specified.
|
||||||
|
preCfg := cfg
|
||||||
|
preParser := flags.NewParser(&preCfg, flags.Default)
|
||||||
|
_, err := preParser.Parse()
|
||||||
|
if err != nil {
|
||||||
|
if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
|
||||||
|
preParser.WriteHelp(os.Stderr)
|
||||||
|
}
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the version and exit if the version flag was specified.
|
||||||
|
if preCfg.ShowVersion {
|
||||||
|
appName := filepath.Base(os.Args[0])
|
||||||
|
appName = strings.TrimSuffix(appName, filepath.Ext(appName))
|
||||||
|
fmt.Println(appName, "version", version())
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load additional config from file.
|
||||||
|
parser := flags.NewParser(&cfg, flags.Default)
|
||||||
|
err = parser.ParseIniFile(preCfg.ConfigFile)
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(*os.PathError); !ok {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
parser.WriteHelp(os.Stderr)
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
log.Warnf("%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse command line options again to ensure they take precedence.
|
||||||
|
remainingArgs, err := parser.Parse()
|
||||||
|
if err != nil {
|
||||||
|
if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
|
||||||
|
parser.WriteHelp(os.Stderr)
|
||||||
|
}
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// wallet file must be valid
|
||||||
|
if !fileExists(cfg.WalletFile) {
|
||||||
|
return &cfg, nil, errors.New("Wallet file does not exist.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cfg, remainingArgs, nil
|
||||||
|
}
|
361
sockets.go
Normal file
361
sockets.go
Normal file
|
@ -0,0 +1,361 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013 Conformal Systems LLC <info@conformal.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.google.com/p/go.net/websocket"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/conformal/btcjson"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ConnRefused = errors.New("Connection refused")
|
||||||
|
|
||||||
|
// Channel to close to notify that connection to btcd has been lost.
|
||||||
|
btcdDisconnected = make(chan int)
|
||||||
|
|
||||||
|
// Channel to send messages btcwallet does not understand to btcd.
|
||||||
|
btcdMsgs = make(chan []byte, 100)
|
||||||
|
|
||||||
|
// Adds a frontend listener channel
|
||||||
|
addFrontendListener = make(chan (chan []byte))
|
||||||
|
|
||||||
|
// Removes a frontend listener channel
|
||||||
|
deleteFrontendListener = make(chan (chan []byte))
|
||||||
|
|
||||||
|
// Messages sent to this channel are sent to each connected frontend.
|
||||||
|
frontendNotificationMaster = make(chan []byte, 100)
|
||||||
|
|
||||||
|
replyHandlers = struct {
|
||||||
|
sync.Mutex
|
||||||
|
m map[uint64]func(interface{}) bool
|
||||||
|
}{
|
||||||
|
m: make(map[uint64]func(interface{}) bool),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// frontendListenerDuplicator listens for new wallet listener channels
|
||||||
|
// and duplicates messages sent to frontendNotificationMaster to all
|
||||||
|
// connected listeners.
|
||||||
|
func frontendListenerDuplicator() {
|
||||||
|
// frontendListeners is a map holding each currently connected frontend
|
||||||
|
// listener as the key. The value is ignored, as this is only used as
|
||||||
|
// a set.
|
||||||
|
frontendListeners := make(map[chan []byte]bool)
|
||||||
|
|
||||||
|
// Don't want to add or delete a wallet listener while iterating
|
||||||
|
// through each to propigate to every attached wallet. Use a mutex to
|
||||||
|
// prevent this.
|
||||||
|
mtx := new(sync.Mutex)
|
||||||
|
|
||||||
|
// Check for listener channels to add or remove from set.
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case c := <-addFrontendListener:
|
||||||
|
mtx.Lock()
|
||||||
|
frontendListeners[c] = true
|
||||||
|
mtx.Unlock()
|
||||||
|
case c := <-deleteFrontendListener:
|
||||||
|
mtx.Lock()
|
||||||
|
delete(frontendListeners, c)
|
||||||
|
mtx.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Duplicate all messages sent across frontendNotificationMaster to each
|
||||||
|
// listening wallet.
|
||||||
|
for {
|
||||||
|
ntfn := <-frontendNotificationMaster
|
||||||
|
mtx.Lock()
|
||||||
|
for c, _ := range frontendListeners {
|
||||||
|
c <- ntfn
|
||||||
|
}
|
||||||
|
mtx.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// frontendReqsNotifications is the handler function for websocket
|
||||||
|
// connections from a btcwallet instance. It reads messages from wallet and
|
||||||
|
// sends back replies, as well as notififying wallets of chain updates.
|
||||||
|
// There can possibly be many of these running, one for each currently
|
||||||
|
// connected frontend.
|
||||||
|
func frontendReqsNotifications(ws *websocket.Conn) {
|
||||||
|
// Add frontend notification channel to set so this handler receives
|
||||||
|
// updates.
|
||||||
|
frontendNotification := make(chan []byte)
|
||||||
|
addFrontendListener <- frontendNotification
|
||||||
|
defer func() {
|
||||||
|
deleteFrontendListener <- frontendNotification
|
||||||
|
}()
|
||||||
|
|
||||||
|
// jsonMsgs receives JSON messages from the currently connected frontend.
|
||||||
|
jsonMsgs := make(chan []byte)
|
||||||
|
|
||||||
|
// Receive messages from websocket and send across jsonMsgs until
|
||||||
|
// connection is lost
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
var m []byte
|
||||||
|
if err := websocket.Message.Receive(ws, &m); err != nil {
|
||||||
|
close(jsonMsgs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonMsgs <- m
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-btcdDisconnected:
|
||||||
|
var idStr interface{} = "btcwallet:btcddisconnected"
|
||||||
|
r := btcjson.Reply{
|
||||||
|
Id: &idStr,
|
||||||
|
}
|
||||||
|
m, _ := json.Marshal(r)
|
||||||
|
websocket.Message.Send(ws, m)
|
||||||
|
return
|
||||||
|
case m, ok := <-jsonMsgs:
|
||||||
|
if !ok {
|
||||||
|
// frontend disconnected.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Handle JSON message here.
|
||||||
|
go ProcessFrontendMsg(frontendNotification, m)
|
||||||
|
case ntfn, _ := <-frontendNotification:
|
||||||
|
if err := websocket.Message.Send(ws, ntfn); err != nil {
|
||||||
|
// Frontend disconnected.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BtcdHandler listens for replies and notifications from btcd over a
|
||||||
|
// websocket and sends messages that btcwallet does not understand to
|
||||||
|
// btcd. Unlike FrontendHandler, exactly one BtcdHandler goroutine runs.
|
||||||
|
func BtcdHandler(ws *websocket.Conn) {
|
||||||
|
disconnected := make(chan int)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
close(disconnected)
|
||||||
|
close(btcdDisconnected)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Listen for replies/notifications from btcd, and decide how to handle them.
|
||||||
|
replies := make(chan []byte)
|
||||||
|
go func() {
|
||||||
|
defer close(replies)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-disconnected:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
var m []byte
|
||||||
|
if err := websocket.Message.Receive(ws, &m); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
replies <- m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// TODO(jrick): hook this up with addresses in wallet.
|
||||||
|
// reqTxsForAddress("addr")
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case rply, ok := <-replies:
|
||||||
|
if !ok {
|
||||||
|
// btcd disconnected
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Handle message here.
|
||||||
|
go ProcessBtcdNotificationReply(rply)
|
||||||
|
case r := <-btcdMsgs:
|
||||||
|
if err := websocket.Message.Send(ws, r); err != nil {
|
||||||
|
// btcd disconnected.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessBtcdNotificationReply unmarshalls the JSON notification or
|
||||||
|
// reply received from btcd and decides how to handle it. Replies are
|
||||||
|
// routed back to the frontend who sent the message, and wallet
|
||||||
|
// notifications are processed by btcwallet, and frontend notifications
|
||||||
|
// are sent to every connected frontend.
|
||||||
|
func ProcessBtcdNotificationReply(b []byte) {
|
||||||
|
// Check if the json id field was set by btcwallet.
|
||||||
|
var routeId uint64
|
||||||
|
var origId string
|
||||||
|
|
||||||
|
var m map[string]interface{}
|
||||||
|
json.Unmarshal(b, &m)
|
||||||
|
idStr, ok := m["id"].(string)
|
||||||
|
if !ok {
|
||||||
|
// btcd should only ever be sending JSON messages with a string in
|
||||||
|
// the id field. Log the error and drop the message.
|
||||||
|
log.Error("Unable to process btcd notification or reply.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n, _ := fmt.Sscanf(idStr, "btcwallet(%d)-%s", &routeId, &origId)
|
||||||
|
if n == 1 {
|
||||||
|
// Request originated from btcwallet. Run and remove correct
|
||||||
|
// handler.
|
||||||
|
replyHandlers.Lock()
|
||||||
|
f := replyHandlers.m[routeId]
|
||||||
|
replyHandlers.Unlock()
|
||||||
|
if f != nil {
|
||||||
|
go func() {
|
||||||
|
if f(m["result"]) {
|
||||||
|
replyHandlers.Lock()
|
||||||
|
delete(replyHandlers.m, routeId)
|
||||||
|
replyHandlers.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
} else if n == 2 {
|
||||||
|
// Attempt to route btcd reply to correct frontend.
|
||||||
|
replyRouter.Lock()
|
||||||
|
c := replyRouter.m[routeId]
|
||||||
|
if c != nil {
|
||||||
|
delete(replyRouter.m, routeId)
|
||||||
|
} else {
|
||||||
|
// Can't route to a frontend, drop reply.
|
||||||
|
log.Info("Unable to route btcd reply to frontend. Dropping.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
replyRouter.Unlock()
|
||||||
|
|
||||||
|
// Convert string back to number if possible.
|
||||||
|
var origIdNum float64
|
||||||
|
n, _ := fmt.Sscanf(origId, "%f", &origIdNum)
|
||||||
|
if n == 1 {
|
||||||
|
m["id"] = origIdNum
|
||||||
|
} else {
|
||||||
|
m["id"] = origId
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error marshalling btcd reply. Dropping.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c <- b
|
||||||
|
} else {
|
||||||
|
// btcd notification must either be handled by btcwallet or sent
|
||||||
|
// to all frontends if btcwallet can not handle it.
|
||||||
|
switch idStr {
|
||||||
|
default:
|
||||||
|
frontendNotificationMaster <- b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServe connects to a running btcd instance over a websocket
|
||||||
|
// for sending and receiving chain-related messages, failing if the
|
||||||
|
// connection can not be established. An additional HTTP server is then
|
||||||
|
// started to provide websocket connections for any number of btcwallet
|
||||||
|
// frontends.
|
||||||
|
func ListenAndServe() error {
|
||||||
|
// Attempt to connect to running btcd instance. Bail if it fails.
|
||||||
|
btcdws, err := websocket.Dial(
|
||||||
|
fmt.Sprintf("ws://localhost:%d/wallet", cfg.BtcdPort),
|
||||||
|
"",
|
||||||
|
"http://localhost/")
|
||||||
|
if err != nil {
|
||||||
|
return ConnRefused
|
||||||
|
}
|
||||||
|
go BtcdHandler(btcdws)
|
||||||
|
|
||||||
|
log.Info("Established connection to btcd.")
|
||||||
|
|
||||||
|
// We'll need to duplicate replies to frontends to each frontend.
|
||||||
|
// Replies are sent to frontendReplyMaster, and duplicated to each valid
|
||||||
|
// channel in frontendReplySet. This runs a goroutine to duplicate
|
||||||
|
// requests for each channel in the set.
|
||||||
|
go frontendListenerDuplicator()
|
||||||
|
|
||||||
|
// XXX(jrick): We need some sort of authentication before websocket
|
||||||
|
// connections are allowed, and perhaps TLS on the server as well.
|
||||||
|
http.Handle("/frontend", websocket.Handler(frontendReqsNotifications))
|
||||||
|
if err := http.ListenAndServe(fmt.Sprintf(":%d", cfg.SvrPort), nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func reqTxsForAddress(addr string) {
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
seq.Lock()
|
||||||
|
n := seq.n
|
||||||
|
seq.n++
|
||||||
|
seq.Unlock()
|
||||||
|
|
||||||
|
id := fmt.Sprintf("btcwallet(%v)", n)
|
||||||
|
msg, err := btcjson.CreateMessageWithId("getblockhash", id, i)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(msg)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
replyHandlers.Lock()
|
||||||
|
replyHandlers.m[n] = func(result interface{}) bool {
|
||||||
|
fmt.Println(result)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
replyHandlers.Unlock()
|
||||||
|
|
||||||
|
btcdMsgs <- msg
|
||||||
|
}
|
||||||
|
|
||||||
|
seq.Lock()
|
||||||
|
n := seq.n
|
||||||
|
seq.n++
|
||||||
|
seq.Unlock()
|
||||||
|
|
||||||
|
m := &btcjson.Message{
|
||||||
|
Jsonrpc: "",
|
||||||
|
Id: fmt.Sprintf("btcwallet(%v)", n),
|
||||||
|
Method: "rescanforutxo",
|
||||||
|
Params: []interface{}{
|
||||||
|
"17XhEvq9Nahdj7Xe1nv6oRe1tEmaHUuynH",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
msg, err := json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
replyHandlers.Lock()
|
||||||
|
replyHandlers.m[n] = func(result interface{}) bool {
|
||||||
|
fmt.Println("result:", result)
|
||||||
|
return result == nil
|
||||||
|
}
|
||||||
|
replyHandlers.Unlock()
|
||||||
|
|
||||||
|
btcdMsgs <- msg
|
||||||
|
}
|
68
version.go
Normal file
68
version.go
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// semanticAlphabet
|
||||||
|
const semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-"
|
||||||
|
|
||||||
|
// These constants define the application version and follow the semantic
|
||||||
|
// versioning 2.0.0 spec (http://semver.org/).
|
||||||
|
const (
|
||||||
|
appMajor uint = 0
|
||||||
|
appMinor uint = 1
|
||||||
|
appPatch uint = 0
|
||||||
|
|
||||||
|
// appPreRelease MUST only contain characters from semanticAlphabet
|
||||||
|
// per the semantic versioning spec.
|
||||||
|
appPreRelease = "alpha"
|
||||||
|
)
|
||||||
|
|
||||||
|
// appBuild is defined as a variable so it can be overridden during the build
|
||||||
|
// process with '-ldflags "-X main.appBuild foo' if needed. It MUST only
|
||||||
|
// contain characters from semanticAlphabet per the semantic versioning spec.
|
||||||
|
var appBuild string
|
||||||
|
|
||||||
|
// version returns the application version as a properly formed string per the
|
||||||
|
// semantic versioning 2.0.0 spec (http://semver.org/).
|
||||||
|
func version() string {
|
||||||
|
// Start with the major, minor, and path versions.
|
||||||
|
version := fmt.Sprintf("%d.%d.%d", appMajor, appMinor, appPatch)
|
||||||
|
|
||||||
|
// Append pre-release version if there is one. The hyphen called for
|
||||||
|
// by the semantic versioning spec is automatically appended and should
|
||||||
|
// not be contained in the pre-release string. The pre-release version
|
||||||
|
// is not appended if it contains invalid characters.
|
||||||
|
preRelease := normalizeVerString(appPreRelease)
|
||||||
|
if preRelease != "" {
|
||||||
|
version = fmt.Sprintf("%s-%s", version, preRelease)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append build metadata if there is any. The plus called for
|
||||||
|
// by the semantic versioning spec is automatically appended and should
|
||||||
|
// not be contained in the build metadata string. The build metadata
|
||||||
|
// string is not appended if it contains invalid characters.
|
||||||
|
build := normalizeVerString(appBuild)
|
||||||
|
if build != "" {
|
||||||
|
version = fmt.Sprintf("%s+%s", version, build)
|
||||||
|
}
|
||||||
|
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalizeVerString returns the passed string stripped of all characters which
|
||||||
|
// are not valid according to the semantic versioning guidelines for pre-release
|
||||||
|
// version and build metadata strings. In particular they MUST only contain
|
||||||
|
// characters in semanticAlphabet.
|
||||||
|
func normalizeVerString(str string) string {
|
||||||
|
var result bytes.Buffer
|
||||||
|
for _, r := range str {
|
||||||
|
if strings.ContainsRune(semanticAlphabet, r) {
|
||||||
|
result.WriteRune(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.String()
|
||||||
|
}
|
813
wallet/wallet.go
Normal file
813
wallet/wallet.go
Normal file
|
@ -0,0 +1,813 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013 Conformal Systems LLC <info@conformal.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package wallet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"code.google.com/p/go.crypto/ripemd160"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/conformal/btcec"
|
||||||
|
"github.com/conformal/btcutil"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Length in bytes of KDF output.
|
||||||
|
kdfOutputBytes = 32
|
||||||
|
|
||||||
|
// Maximum length in bytes of a comment that can have a size represented
|
||||||
|
// as a uint16.
|
||||||
|
maxCommentLen = (1 << 16) - 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// Possible errors when dealing with wallets.
|
||||||
|
var (
|
||||||
|
ChecksumErr = errors.New("Checksum mismatch")
|
||||||
|
MalformedEntryErr = errors.New("Malformed entry")
|
||||||
|
WalletDoesNotExist = errors.New("Non-existant wallet")
|
||||||
|
)
|
||||||
|
|
||||||
|
type entryHeader byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
addrCommentHeader entryHeader = 1 << iota
|
||||||
|
txCommentHeader
|
||||||
|
deletedHeader
|
||||||
|
addrHeader entryHeader = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
// We want to use binaryRead and binaryWrite instead of binary.Read
|
||||||
|
// and binary.Write because those from the binary package do not return
|
||||||
|
// the number of bytes actually written or read. We need to return
|
||||||
|
// this value to correctly support the io.ReaderFrom and io.WriterTo
|
||||||
|
// interfaces.
|
||||||
|
func binaryRead(r io.Reader, order binary.ByteOrder, data interface{}) (n int64, err error) {
|
||||||
|
var read int
|
||||||
|
buf := make([]byte, binary.Size(data))
|
||||||
|
if read, err = r.Read(buf); err != nil {
|
||||||
|
return int64(read), err
|
||||||
|
}
|
||||||
|
return int64(read), binary.Read(bytes.NewBuffer(buf), order, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// See comment for binaryRead().
|
||||||
|
func binaryWrite(w io.Writer, order binary.ByteOrder, data interface{}) (n int64, err error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err = binary.Write(&buf, order, data); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
written, err := w.Write(buf.Bytes())
|
||||||
|
return int64(written), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func keyOneIter(passphrase, salt []byte, memReqts uint64) []byte {
|
||||||
|
saltedpass := append(passphrase, salt...)
|
||||||
|
lutbl := make([]byte, memReqts)
|
||||||
|
|
||||||
|
// Seed for lookup table
|
||||||
|
seed := sha512.Sum512(saltedpass)
|
||||||
|
copy(lutbl[:sha512.Size], seed[:])
|
||||||
|
|
||||||
|
for nByte := 0; nByte < (int(memReqts) - sha512.Size); nByte += sha512.Size {
|
||||||
|
hash := sha512.Sum512(lutbl[nByte : nByte+sha512.Size])
|
||||||
|
copy(lutbl[nByte+sha512.Size:nByte+2*sha512.Size], hash[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
x := lutbl[cap(lutbl)-sha512.Size:]
|
||||||
|
|
||||||
|
seqCt := uint32(memReqts / sha512.Size)
|
||||||
|
nLookups := seqCt / 2
|
||||||
|
for i := uint32(0); i < nLookups; i++ {
|
||||||
|
// Armory ignores endianness here. We assume LE.
|
||||||
|
newIdx := binary.LittleEndian.Uint32(x[cap(x)-4:]) % seqCt
|
||||||
|
|
||||||
|
// Index of hash result at newIdx
|
||||||
|
vIdx := newIdx * sha512.Size
|
||||||
|
v := lutbl[vIdx : vIdx+sha512.Size]
|
||||||
|
|
||||||
|
// XOR hash x with hash v
|
||||||
|
for j := 0; j < sha512.Size; j++ {
|
||||||
|
x[j] ^= v[j]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save new hash to x
|
||||||
|
hash := sha512.Sum512(x)
|
||||||
|
copy(x, hash[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
return x[:kdfOutputBytes]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key implements the key derivation function used by Armory
|
||||||
|
// based on the ROMix algorithm described in Colin Percival's paper
|
||||||
|
// "Stronger Key Derivation via Sequential Memory-Hard Functions"
|
||||||
|
// (http://www.tarsnap.com/scrypt/scrypt.pdf).
|
||||||
|
func Key(passphrase, salt []byte, memReqts uint64, nIters uint32) []byte {
|
||||||
|
masterKey := passphrase
|
||||||
|
for i := uint32(0); i < nIters; i++ {
|
||||||
|
masterKey = keyOneIter(masterKey, salt, memReqts)
|
||||||
|
}
|
||||||
|
return masterKey
|
||||||
|
}
|
||||||
|
|
||||||
|
type varEntries []io.WriterTo
|
||||||
|
|
||||||
|
func (v *varEntries) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
ss := ([]io.WriterTo)(*v)
|
||||||
|
|
||||||
|
var written int64
|
||||||
|
for _, s := range ss {
|
||||||
|
var err error
|
||||||
|
if written, err = s.WriteTo(w); err != nil {
|
||||||
|
return n + written, err
|
||||||
|
}
|
||||||
|
n += written
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *varEntries) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
var read int64
|
||||||
|
|
||||||
|
// Remove any previous entries.
|
||||||
|
*v = nil
|
||||||
|
wts := ([]io.WriterTo)(*v)
|
||||||
|
|
||||||
|
// Keep reading entries until an EOF is reached.
|
||||||
|
for {
|
||||||
|
var header entryHeader
|
||||||
|
if read, err = binaryRead(r, binary.LittleEndian, &header); err != nil {
|
||||||
|
// EOF here is not an error.
|
||||||
|
if err == io.EOF {
|
||||||
|
return n + read, nil
|
||||||
|
}
|
||||||
|
return n + read, err
|
||||||
|
}
|
||||||
|
n += read
|
||||||
|
|
||||||
|
var wt io.WriterTo = nil
|
||||||
|
switch header {
|
||||||
|
case addrHeader:
|
||||||
|
var entry addrEntry
|
||||||
|
if read, err = entry.ReadFrom(r); err != nil {
|
||||||
|
return n + read, err
|
||||||
|
}
|
||||||
|
n += read
|
||||||
|
wt = &entry
|
||||||
|
case addrCommentHeader:
|
||||||
|
var entry addrCommentEntry
|
||||||
|
if read, err = entry.ReadFrom(r); err != nil {
|
||||||
|
return n + read, err
|
||||||
|
}
|
||||||
|
n += read
|
||||||
|
wt = &entry
|
||||||
|
case txCommentHeader:
|
||||||
|
var entry txCommentEntry
|
||||||
|
if read, err = entry.ReadFrom(r); err != nil {
|
||||||
|
return n + read, err
|
||||||
|
}
|
||||||
|
n += read
|
||||||
|
wt = &entry
|
||||||
|
case deletedHeader:
|
||||||
|
var entry deletedEntry
|
||||||
|
if read, err = entry.ReadFrom(r); err != nil {
|
||||||
|
return n + read, err
|
||||||
|
}
|
||||||
|
n += read
|
||||||
|
default:
|
||||||
|
return n, fmt.Errorf("Unknown entry header: %d", uint8(header))
|
||||||
|
}
|
||||||
|
if wt != nil {
|
||||||
|
wts = append(wts, wt)
|
||||||
|
*v = wts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wallet represents an btcd/Armory wallet in memory. It
|
||||||
|
// implements the io.ReaderFrom and io.WriterTo interfaces to read
|
||||||
|
// from and write to any type of byte streams, including files.
|
||||||
|
// TODO(jrick) remove as many more magic numbers as possible.
|
||||||
|
type Wallet struct {
|
||||||
|
fileID [8]byte
|
||||||
|
version uint32
|
||||||
|
netMagicBytes [4]byte
|
||||||
|
walletFlags [8]byte
|
||||||
|
uniqID [6]byte
|
||||||
|
createDate [8]byte
|
||||||
|
name [32]byte
|
||||||
|
description [256]byte
|
||||||
|
highestUsed int64
|
||||||
|
kdfParams kdfParameters
|
||||||
|
encryptionParams [256]byte
|
||||||
|
keyGenerator btcAddress
|
||||||
|
appendedEntries varEntries
|
||||||
|
|
||||||
|
// These are not serialized
|
||||||
|
addrMap map[[ripemd160.Size]byte]*btcAddress
|
||||||
|
addrCommentMap map[[ripemd160.Size]byte]*[]byte
|
||||||
|
chainIdxMap map[int64]*[ripemd160.Size]byte
|
||||||
|
txCommentMap map[[sha256.Size]byte]*[]byte
|
||||||
|
lastChainIdx int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTo serializes a Wallet and writes it to a io.Writer,
|
||||||
|
// returning the number of bytes written and any errors encountered.
|
||||||
|
func (wallet *Wallet) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
// Iterate through each entry needing to be written. If data
|
||||||
|
// implements io.WriterTo, use its WriteTo func. Otherwise,
|
||||||
|
// data is a pointer to a fixed size value.
|
||||||
|
datas := []interface{}{
|
||||||
|
&wallet.fileID,
|
||||||
|
&wallet.version,
|
||||||
|
&wallet.netMagicBytes,
|
||||||
|
&wallet.walletFlags,
|
||||||
|
&wallet.uniqID,
|
||||||
|
&wallet.createDate,
|
||||||
|
&wallet.name,
|
||||||
|
&wallet.description,
|
||||||
|
&wallet.highestUsed,
|
||||||
|
&wallet.kdfParams,
|
||||||
|
&wallet.encryptionParams,
|
||||||
|
&wallet.keyGenerator,
|
||||||
|
make([]byte, 1024),
|
||||||
|
&wallet.appendedEntries,
|
||||||
|
}
|
||||||
|
var read int64
|
||||||
|
for _, data := range datas {
|
||||||
|
if s, ok := data.(io.WriterTo); ok {
|
||||||
|
read, err = s.WriteTo(w)
|
||||||
|
} else {
|
||||||
|
read, err = binaryWrite(w, binary.LittleEndian, data)
|
||||||
|
}
|
||||||
|
n += read
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFrom reads data from a io.Reader and saves it to a Wallet,
|
||||||
|
// returning the number of bytes read and any errors encountered.
|
||||||
|
func (wallet *Wallet) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
var read int64
|
||||||
|
|
||||||
|
wallet.addrMap = make(map[[ripemd160.Size]byte]*btcAddress)
|
||||||
|
wallet.addrCommentMap = make(map[[ripemd160.Size]byte]*[]byte)
|
||||||
|
wallet.chainIdxMap = make(map[int64]*[ripemd160.Size]byte)
|
||||||
|
wallet.txCommentMap = make(map[[sha256.Size]byte]*[]byte)
|
||||||
|
|
||||||
|
// Iterate through each entry needing to be read. If data
|
||||||
|
// implements io.ReaderFrom, use its ReadFrom func. Otherwise,
|
||||||
|
// data is a pointer to a fixed sized value.
|
||||||
|
datas := []interface{}{
|
||||||
|
&wallet.fileID,
|
||||||
|
&wallet.version,
|
||||||
|
&wallet.netMagicBytes,
|
||||||
|
&wallet.walletFlags,
|
||||||
|
&wallet.uniqID,
|
||||||
|
&wallet.createDate,
|
||||||
|
&wallet.name,
|
||||||
|
&wallet.description,
|
||||||
|
&wallet.highestUsed,
|
||||||
|
&wallet.kdfParams,
|
||||||
|
&wallet.encryptionParams,
|
||||||
|
&wallet.keyGenerator,
|
||||||
|
make([]byte, 1024),
|
||||||
|
&wallet.appendedEntries,
|
||||||
|
}
|
||||||
|
for _, data := range datas {
|
||||||
|
var err error
|
||||||
|
if rf, ok := data.(io.ReaderFrom); ok {
|
||||||
|
read, err = rf.ReadFrom(r)
|
||||||
|
} else {
|
||||||
|
read, err = binaryRead(r, binary.LittleEndian, data)
|
||||||
|
}
|
||||||
|
n += read
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add root address to address map
|
||||||
|
wallet.addrMap[wallet.keyGenerator.pubKeyHash] = &wallet.keyGenerator
|
||||||
|
wallet.chainIdxMap[wallet.keyGenerator.chainIndex] = &wallet.keyGenerator.pubKeyHash
|
||||||
|
|
||||||
|
// Fill unserializied fields.
|
||||||
|
wts := ([]io.WriterTo)(wallet.appendedEntries)
|
||||||
|
for _, wt := range wts {
|
||||||
|
switch wt.(type) {
|
||||||
|
case *addrEntry:
|
||||||
|
e := wt.(*addrEntry)
|
||||||
|
wallet.addrMap[e.pubKeyHash160] = &e.addr
|
||||||
|
wallet.chainIdxMap[e.addr.chainIndex] = &e.pubKeyHash160
|
||||||
|
if wallet.lastChainIdx < e.addr.chainIndex {
|
||||||
|
wallet.lastChainIdx = e.addr.chainIndex
|
||||||
|
}
|
||||||
|
case *addrCommentEntry:
|
||||||
|
e := wt.(*addrCommentEntry)
|
||||||
|
wallet.addrCommentMap[e.pubKeyHash160] = &e.comment
|
||||||
|
case *txCommentEntry:
|
||||||
|
e := wt.(*txCommentEntry)
|
||||||
|
wallet.txCommentMap[e.txHash] = &e.comment
|
||||||
|
default:
|
||||||
|
return n, errors.New("Unknown appended entry")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock derives an AES key from passphrase and wallet's KDF
|
||||||
|
// parameters and unlocks the root key of the wallet.
|
||||||
|
func (wallet *Wallet) Unlock(passphrase []byte) error {
|
||||||
|
key := Key(passphrase, wallet.kdfParams.salt[:],
|
||||||
|
wallet.kdfParams.mem, wallet.kdfParams.nIter)
|
||||||
|
|
||||||
|
// Attempt unlocking root address
|
||||||
|
return wallet.keyGenerator.unlock(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock does a best effort to zero the keys.
|
||||||
|
// Being go this might not succeed but try anway.
|
||||||
|
// TODO(jrick)
|
||||||
|
func (wallet *Wallet) Lock() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns wallet version as string and int.
|
||||||
|
// TODO(jrick)
|
||||||
|
func (wallet *Wallet) Version() (string, int) {
|
||||||
|
return "", 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(jrick)
|
||||||
|
func (wallet *Wallet) NextUnusedAddress() string {
|
||||||
|
_ = wallet.lastChainIdx
|
||||||
|
wallet.highestUsed++
|
||||||
|
new160, err := wallet.addr160ForIdx(wallet.highestUsed)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
addr := wallet.addrMap[*new160]
|
||||||
|
if addr != nil {
|
||||||
|
return btcutil.Base58Encode(addr.pubKeyHash[:])
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wallet *Wallet) addr160ForIdx(idx int64) (*[ripemd160.Size]byte, error) {
|
||||||
|
if idx > wallet.lastChainIdx {
|
||||||
|
return nil, errors.New("Chain index out of range")
|
||||||
|
}
|
||||||
|
return wallet.chainIdxMap[idx], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wallet *Wallet) GetActiveAddresses() []string {
|
||||||
|
addrs := []string{}
|
||||||
|
for i := int64(-1); i <= wallet.highestUsed; i++ {
|
||||||
|
addr160, err := wallet.addr160ForIdx(i)
|
||||||
|
if err != nil {
|
||||||
|
return addrs
|
||||||
|
}
|
||||||
|
addr := wallet.addrMap[*addr160]
|
||||||
|
addrs = append(addrs, btcutil.Base58Encode(addr.pubKeyHash[:]))
|
||||||
|
}
|
||||||
|
return addrs
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func OpenWallet(file string) (*Wallet, error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
type btcAddress struct {
|
||||||
|
pubKeyHash [ripemd160.Size]byte
|
||||||
|
version uint32
|
||||||
|
flags uint64
|
||||||
|
chaincode [32]byte
|
||||||
|
chainIndex int64
|
||||||
|
chainDepth int64
|
||||||
|
initVector [16]byte
|
||||||
|
privKey [32]byte
|
||||||
|
pubKey [65]byte
|
||||||
|
firstSeen uint64
|
||||||
|
lastSeen uint64
|
||||||
|
firstBlock uint32
|
||||||
|
lastBlock uint32
|
||||||
|
privKeyCT []byte // Points to clear text private key if unlocked.
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFrom reads an encrypted address from an io.Reader.
|
||||||
|
func (addr *btcAddress) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
var read int64
|
||||||
|
|
||||||
|
// Checksums
|
||||||
|
var chkPubKeyHash uint32
|
||||||
|
var chkChaincode uint32
|
||||||
|
var chkInitVector uint32
|
||||||
|
var chkPrivKey uint32
|
||||||
|
var chkPubKey uint32
|
||||||
|
|
||||||
|
// Read serialized wallet into addr fields and checksums.
|
||||||
|
datas := []interface{}{
|
||||||
|
&addr.pubKeyHash,
|
||||||
|
&chkPubKeyHash,
|
||||||
|
&addr.version,
|
||||||
|
&addr.flags,
|
||||||
|
&addr.chaincode,
|
||||||
|
&chkChaincode,
|
||||||
|
&addr.chainIndex,
|
||||||
|
&addr.chainDepth,
|
||||||
|
&addr.initVector,
|
||||||
|
&chkInitVector,
|
||||||
|
&addr.privKey,
|
||||||
|
&chkPrivKey,
|
||||||
|
&addr.pubKey,
|
||||||
|
&chkPubKey,
|
||||||
|
&addr.firstSeen,
|
||||||
|
&addr.lastSeen,
|
||||||
|
&addr.firstBlock,
|
||||||
|
&addr.lastBlock,
|
||||||
|
}
|
||||||
|
for _, data := range datas {
|
||||||
|
if read, err = binaryRead(r, binary.LittleEndian, data); err != nil {
|
||||||
|
return n + read, err
|
||||||
|
}
|
||||||
|
n += read
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify checksums, correct errors where possible.
|
||||||
|
checks := []struct {
|
||||||
|
data []byte
|
||||||
|
chk uint32
|
||||||
|
}{
|
||||||
|
{addr.pubKeyHash[:], chkPubKeyHash},
|
||||||
|
{addr.chaincode[:], chkChaincode},
|
||||||
|
{addr.initVector[:], chkInitVector},
|
||||||
|
{addr.privKey[:], chkPrivKey},
|
||||||
|
{addr.pubKey[:], chkPubKey},
|
||||||
|
}
|
||||||
|
for i, _ := range checks {
|
||||||
|
if err = verifyAndFix(checks[i].data, checks[i].chk); err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(jrick) verify encryption
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (addr *btcAddress) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
var written int64
|
||||||
|
|
||||||
|
datas := []interface{}{
|
||||||
|
&addr.pubKeyHash,
|
||||||
|
walletHash(addr.pubKeyHash[:]),
|
||||||
|
&addr.version,
|
||||||
|
&addr.flags,
|
||||||
|
&addr.chaincode,
|
||||||
|
walletHash(addr.chaincode[:]),
|
||||||
|
&addr.chainIndex,
|
||||||
|
&addr.chainDepth,
|
||||||
|
&addr.initVector,
|
||||||
|
walletHash(addr.initVector[:]),
|
||||||
|
&addr.privKey,
|
||||||
|
walletHash(addr.privKey[:]),
|
||||||
|
&addr.pubKey,
|
||||||
|
walletHash(addr.pubKey[:]),
|
||||||
|
&addr.firstSeen,
|
||||||
|
&addr.lastSeen,
|
||||||
|
&addr.firstBlock,
|
||||||
|
&addr.lastBlock,
|
||||||
|
}
|
||||||
|
for _, data := range datas {
|
||||||
|
written, err = binaryWrite(w, binary.LittleEndian, data)
|
||||||
|
if err != nil {
|
||||||
|
return n + written, err
|
||||||
|
}
|
||||||
|
n += written
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (addr *btcAddress) unlock(key []byte) error {
|
||||||
|
aesBlockDecrypter, err := aes.NewCipher([]byte(key))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
aesDecrypter := cipher.NewCFBDecrypter(aesBlockDecrypter, addr.initVector[:])
|
||||||
|
ct := make([]byte, 32)
|
||||||
|
aesDecrypter.XORKeyStream(ct, addr.privKey[:])
|
||||||
|
addr.privKeyCT = ct
|
||||||
|
|
||||||
|
pubKey, err := btcec.ParsePubKey(addr.pubKey[:], btcec.S256())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("ParsePubKey faild:", err)
|
||||||
|
}
|
||||||
|
x, y := btcec.S256().ScalarBaseMult(addr.privKeyCT)
|
||||||
|
if x.Cmp(pubKey.X) != 0 || y.Cmp(pubKey.Y) != 0 {
|
||||||
|
return fmt.Errorf("decryption failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(jrick)
|
||||||
|
func (addr *btcAddress) changeEncryptionKey(oldkey, newkey []byte) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(jrick)
|
||||||
|
func (addr *btcAddress) verifyEncryptionKey() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(jrick)
|
||||||
|
func newRandomAddress(key []byte) *btcAddress {
|
||||||
|
addr := &btcAddress{}
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func walletHash(b []byte) uint32 {
|
||||||
|
sum := btcwire.DoubleSha256(b)
|
||||||
|
return binary.LittleEndian.Uint32(sum)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(jrick) add error correction.
|
||||||
|
func verifyAndFix(b []byte, chk uint32) error {
|
||||||
|
if walletHash(b) != chk {
|
||||||
|
return ChecksumErr
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type kdfParameters struct {
|
||||||
|
mem uint64
|
||||||
|
nIter uint32
|
||||||
|
salt [32]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (params *kdfParameters) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
var written int64
|
||||||
|
|
||||||
|
memBytes := make([]byte, 8)
|
||||||
|
nIterBytes := make([]byte, 4)
|
||||||
|
binary.LittleEndian.PutUint64(memBytes, params.mem)
|
||||||
|
binary.LittleEndian.PutUint32(nIterBytes, params.nIter)
|
||||||
|
chkedBytes := append(memBytes, nIterBytes...)
|
||||||
|
chkedBytes = append(chkedBytes, params.salt[:]...)
|
||||||
|
|
||||||
|
datas := []interface{}{
|
||||||
|
¶ms.mem,
|
||||||
|
¶ms.nIter,
|
||||||
|
¶ms.salt,
|
||||||
|
walletHash(chkedBytes),
|
||||||
|
make([]byte, 256-(binary.Size(params)+4)), // padding
|
||||||
|
}
|
||||||
|
for _, data := range datas {
|
||||||
|
if written, err = binaryWrite(w, binary.LittleEndian, data); err != nil {
|
||||||
|
return n + written, err
|
||||||
|
}
|
||||||
|
n += written
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (params *kdfParameters) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
var read int64
|
||||||
|
|
||||||
|
// These must be read in but are not saved directly to params.
|
||||||
|
chkedBytes := make([]byte, 44)
|
||||||
|
var chk uint32
|
||||||
|
padding := make([]byte, 256-(binary.Size(params)+4))
|
||||||
|
|
||||||
|
datas := []interface{}{
|
||||||
|
chkedBytes,
|
||||||
|
&chk,
|
||||||
|
padding,
|
||||||
|
}
|
||||||
|
for _, data := range datas {
|
||||||
|
if read, err = binaryRead(r, binary.LittleEndian, data); err != nil {
|
||||||
|
return n + read, err
|
||||||
|
}
|
||||||
|
n += read
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify checksum
|
||||||
|
if err = verifyAndFix(chkedBytes, chk); err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write params
|
||||||
|
buf := bytes.NewBuffer(chkedBytes)
|
||||||
|
datas = []interface{}{
|
||||||
|
¶ms.mem,
|
||||||
|
¶ms.nIter,
|
||||||
|
¶ms.salt,
|
||||||
|
}
|
||||||
|
for _, data := range datas {
|
||||||
|
if err = binary.Read(buf, binary.LittleEndian, data); err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type addrEntry struct {
|
||||||
|
pubKeyHash160 [ripemd160.Size]byte
|
||||||
|
addr btcAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *addrEntry) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
var written int64
|
||||||
|
|
||||||
|
// Write header
|
||||||
|
if written, err = binaryWrite(w, binary.LittleEndian, addrHeader); err != nil {
|
||||||
|
return n + written, err
|
||||||
|
}
|
||||||
|
n += written
|
||||||
|
|
||||||
|
// Write hash
|
||||||
|
if written, err = binaryWrite(w, binary.LittleEndian, &e.pubKeyHash160); err != nil {
|
||||||
|
return n + written, err
|
||||||
|
}
|
||||||
|
n += written
|
||||||
|
|
||||||
|
// Write btcAddress
|
||||||
|
written, err = e.addr.WriteTo(w)
|
||||||
|
return n + written, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *addrEntry) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
var read int64
|
||||||
|
|
||||||
|
if read, err = binaryRead(r, binary.LittleEndian, &e.pubKeyHash160); err != nil {
|
||||||
|
return n + read, err
|
||||||
|
}
|
||||||
|
n += read
|
||||||
|
|
||||||
|
read, err = e.addr.ReadFrom(r)
|
||||||
|
return n + read, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type addrCommentEntry struct {
|
||||||
|
pubKeyHash160 [ripemd160.Size]byte
|
||||||
|
comment []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *addrCommentEntry) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
var written int64
|
||||||
|
|
||||||
|
// Comments shall not overflow their entry.
|
||||||
|
if len(e.comment) > maxCommentLen {
|
||||||
|
return n, MalformedEntryErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write header
|
||||||
|
if written, err = binaryWrite(w, binary.LittleEndian, addrCommentHeader); err != nil {
|
||||||
|
return n + written, err
|
||||||
|
}
|
||||||
|
n += written
|
||||||
|
|
||||||
|
// Write hash
|
||||||
|
if written, err = binaryWrite(w, binary.LittleEndian, &e.pubKeyHash160); err != nil {
|
||||||
|
return n + written, err
|
||||||
|
}
|
||||||
|
n += written
|
||||||
|
|
||||||
|
// Write length
|
||||||
|
if written, err = binaryWrite(w, binary.LittleEndian, uint16(len(e.comment))); err != nil {
|
||||||
|
return n + written, err
|
||||||
|
}
|
||||||
|
n += written
|
||||||
|
|
||||||
|
// Write comment
|
||||||
|
written, err = binaryWrite(w, binary.LittleEndian, e.comment)
|
||||||
|
return n + written, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *addrCommentEntry) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
var read int64
|
||||||
|
|
||||||
|
if read, err = binaryRead(r, binary.LittleEndian, &e.pubKeyHash160); err != nil {
|
||||||
|
return n + read, err
|
||||||
|
}
|
||||||
|
n += read
|
||||||
|
|
||||||
|
var clen uint16
|
||||||
|
if read, err = binaryRead(r, binary.LittleEndian, &clen); err != nil {
|
||||||
|
return n + read, err
|
||||||
|
}
|
||||||
|
n += read
|
||||||
|
|
||||||
|
e.comment = make([]byte, clen)
|
||||||
|
read, err = binaryRead(r, binary.LittleEndian, e.comment)
|
||||||
|
return n + read, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type txCommentEntry struct {
|
||||||
|
txHash [sha256.Size]byte
|
||||||
|
comment []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *txCommentEntry) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
var written int64
|
||||||
|
|
||||||
|
// Comments shall not overflow their entry.
|
||||||
|
if len(e.comment) > maxCommentLen {
|
||||||
|
return n, MalformedEntryErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write header
|
||||||
|
if written, err = binaryWrite(w, binary.LittleEndian, txCommentHeader); err != nil {
|
||||||
|
return n + written, err
|
||||||
|
}
|
||||||
|
n += written
|
||||||
|
|
||||||
|
// Write length
|
||||||
|
if written, err = binaryWrite(w, binary.LittleEndian, uint16(len(e.comment))); err != nil {
|
||||||
|
return n + written, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write comment
|
||||||
|
written, err = binaryWrite(w, binary.LittleEndian, e.comment)
|
||||||
|
return n + written, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *txCommentEntry) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
var read int64
|
||||||
|
|
||||||
|
if read, err = binaryRead(r, binary.LittleEndian, &e.txHash); err != nil {
|
||||||
|
return n + read, err
|
||||||
|
}
|
||||||
|
n += read
|
||||||
|
|
||||||
|
var clen uint16
|
||||||
|
if read, err = binaryRead(r, binary.LittleEndian, &clen); err != nil {
|
||||||
|
return n + read, err
|
||||||
|
}
|
||||||
|
n += read
|
||||||
|
|
||||||
|
e.comment = make([]byte, clen)
|
||||||
|
read, err = binaryRead(r, binary.LittleEndian, e.comment)
|
||||||
|
return n + read, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type deletedEntry struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *deletedEntry) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
var read int64
|
||||||
|
|
||||||
|
var ulen uint16
|
||||||
|
if read, err = binaryRead(r, binary.LittleEndian, &ulen); err != nil {
|
||||||
|
return n + read, err
|
||||||
|
}
|
||||||
|
n += read
|
||||||
|
|
||||||
|
unused := make([]byte, ulen)
|
||||||
|
if nRead, err := r.Read(unused); err == io.EOF {
|
||||||
|
return n + int64(nRead), nil
|
||||||
|
} else {
|
||||||
|
return n + int64(nRead), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type UTXOStore struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type utxo struct {
|
||||||
|
pubKeyHash [ripemd160.Size]byte
|
||||||
|
*btcwire.TxOut
|
||||||
|
block int64
|
||||||
|
}
|
60
wallet/wallet_test.go
Normal file
60
wallet/wallet_test.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013 Conformal Systems LLC <info@conformal.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package wallet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBtcAddressSerializer(t *testing.T) {
|
||||||
|
var addr = btcAddress{
|
||||||
|
pubKeyHash: [20]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19},
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Create("btcaddress.bin")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
if _, err := addr.WriteTo(file); err != nil {
|
||||||
|
t.Error(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
file.Seek(0, 0)
|
||||||
|
|
||||||
|
var readAddr btcAddress
|
||||||
|
_, err = readAddr.ReadFrom(file)
|
||||||
|
if err != nil {
|
||||||
|
spew.Dump(&readAddr)
|
||||||
|
t.Error(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf1, buf2 := new(bytes.Buffer), new(bytes.Buffer)
|
||||||
|
binary.Write(buf1, binary.LittleEndian, addr)
|
||||||
|
binary.Write(buf2, binary.LittleEndian, readAddr)
|
||||||
|
if !bytes.Equal(buf1.Bytes(), buf2.Bytes()) {
|
||||||
|
t.Error("Original and read btcAddress differ.")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue