add keyfileio to manage private key storage on disk
This commit is contained in:
parent
1f40c7214e
commit
73bbb29026
@ -8,7 +8,6 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil/bloom"
|
||||
@ -46,7 +45,7 @@ type SPVCon struct {
|
||||
mBlockQueue chan RootAndHeight
|
||||
}
|
||||
|
||||
func OpenSPV(remoteNode string, hfn string,
|
||||
func OpenSPV(remoteNode string, hfn, tsfn string,
|
||||
inTs *TxStore, p *chaincfg.Params) (SPVCon, error) {
|
||||
// create new SPVCon
|
||||
var s SPVCon
|
||||
@ -70,7 +69,7 @@ func OpenSPV(remoteNode string, hfn string,
|
||||
s.localVersion = VERSION
|
||||
|
||||
// transaction store for this SPV connection
|
||||
err = inTs.OpenDB("utxo.db")
|
||||
err = inTs.OpenDB(tsfn)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
@ -130,26 +129,6 @@ func OpenSPV(remoteNode string, hfn string,
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (ts *TxStore) OpenDB(filename string) error {
|
||||
var err error
|
||||
ts.StateDB, err = bolt.Open(filename, 0644, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// create buckets if they're not already there
|
||||
return ts.StateDB.Update(func(tx *bolt.Tx) error {
|
||||
_, err = tx.CreateBucketIfNotExists(BKTUtxos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tx.CreateBucketIfNotExists(BKTOld)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *SPVCon) openHeaderFile(hfn string) error {
|
||||
_, err := os.Stat(hfn)
|
||||
if err != nil {
|
||||
|
191
uspv/keyfileio.go
Normal file
191
uspv/keyfileio.go
Normal file
@ -0,0 +1,191 @@
|
||||
package uspv
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcutil/hdkeychain"
|
||||
"github.com/howeyc/gopass"
|
||||
"golang.org/x/crypto/nacl/secretbox"
|
||||
"golang.org/x/crypto/scrypt"
|
||||
)
|
||||
|
||||
// warning! look at those imports! crypto! hopefully this works!
|
||||
|
||||
/* on-disk stored keys are 32bytes. This is good for ed25519 private keys,
|
||||
for seeds for bip32, for individual secp256k1 priv keys, and so on.
|
||||
32 bytes is enough for anyone.
|
||||
If you want fewer bytes, put some zeroes at the end */
|
||||
|
||||
// LoadKeyFromFileInteractive opens the file 'filename' and presents a
|
||||
// keyboard prompt for the passphrase to decrypt it. It returns the
|
||||
// key if decryption works, or errors out.
|
||||
func LoadKeyFromFileInteractive(filename string) (*[32]byte, error) {
|
||||
a, err := os.Stat(filename)
|
||||
if err != nil {
|
||||
return new([32]byte), err
|
||||
}
|
||||
if a.Size() < 80 { // there can't be a password...
|
||||
return LoadKeyFromFileArg(filename, nil)
|
||||
}
|
||||
fmt.Printf("passphrase: ")
|
||||
pass := gopass.GetPasswd()
|
||||
fmt.Printf("\n")
|
||||
return LoadKeyFromFileArg(filename, pass)
|
||||
}
|
||||
|
||||
// LoadKeyFromFileArg opens the file and returns the key. If the key is
|
||||
// unencrypted it will ignore the password argument.
|
||||
func LoadKeyFromFileArg(filename string, pass []byte) (*[32]byte, error) {
|
||||
priv32 := new([32]byte)
|
||||
keyhex, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return priv32, err
|
||||
}
|
||||
keyhex = []byte(strings.TrimSpace(string(keyhex)))
|
||||
enckey, err := hex.DecodeString(string(keyhex))
|
||||
if err != nil {
|
||||
return priv32, err
|
||||
}
|
||||
|
||||
if len(enckey) == 32 { // UNencrypted key, length 32
|
||||
fmt.Printf("WARNING!! Key file not encrypted!!\n")
|
||||
fmt.Printf("Anyone who can read the key file can take everything!\n")
|
||||
fmt.Printf("You should start over and use a good passphrase!\n")
|
||||
copy(priv32[:], enckey[:])
|
||||
return priv32, nil
|
||||
}
|
||||
// enckey should be 72 bytes. 24 for scrypt salt/box nonce,
|
||||
// 16 for box auth
|
||||
if len(enckey) != 72 {
|
||||
return priv32, fmt.Errorf("Key length error for %s ", filename)
|
||||
}
|
||||
// enckey is actually encrypted, get derived key from pass and salt
|
||||
// first extract salt
|
||||
salt := new([24]byte) // salt (also nonce for secretbox)
|
||||
dk32 := new([32]byte) // derived key array
|
||||
copy(salt[:], enckey[:24]) // first 24 bytes are scrypt salt/box nonce
|
||||
|
||||
dk, err := scrypt.Key(pass, salt[:], 16384, 8, 1, 32) // derive key
|
||||
if err != nil {
|
||||
return priv32, err
|
||||
}
|
||||
copy(dk32[:], dk[:]) // copy into fixed size array
|
||||
|
||||
// nonce for secretbox is the same as scrypt salt. Seems fine. Really.
|
||||
priv, worked := secretbox.Open(nil, enckey[24:], salt, dk32)
|
||||
if worked != true {
|
||||
return priv32, fmt.Errorf("Decryption failed for %s ", filename)
|
||||
}
|
||||
copy(priv32[:], priv[:]) //copy decrypted private key into array
|
||||
|
||||
priv = nil // this probably doesn't do anything but... eh why not
|
||||
return priv32, nil
|
||||
}
|
||||
|
||||
// saves a 32 byte key to file, prompting for passphrase.
|
||||
// if user enters empty passphrase (hits enter twice), will be saved
|
||||
// in the clear.
|
||||
func SaveKeyToFileInteractive(filename string, priv32 *[32]byte) error {
|
||||
var match bool
|
||||
var pass1, pass2 []byte
|
||||
for match != true {
|
||||
fmt.Printf("passphrase: ")
|
||||
pass1 = gopass.GetPasswd()
|
||||
fmt.Printf("repeat passphrase: ")
|
||||
pass2 = gopass.GetPasswd()
|
||||
if string(pass1) == string(pass2) {
|
||||
match = true
|
||||
} else {
|
||||
fmt.Printf("user input error. Try again gl hf dd.\n")
|
||||
}
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
return SaveKeyToFileArg(filename, priv32, pass1)
|
||||
}
|
||||
|
||||
// saves a 32 byte key to a file, encrypting with pass.
|
||||
// if pass is nil or zero length, doesn't encrypt and just saves in hex.
|
||||
func SaveKeyToFileArg(filename string, priv32 *[32]byte, pass []byte) error {
|
||||
if len(pass) == 0 { // zero-length pass, save unencrypted
|
||||
keyhex := fmt.Sprintf("%x\n", priv32[:])
|
||||
err := ioutil.WriteFile(filename, []byte(keyhex), 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("WARNING!! Key file not encrypted!!\n")
|
||||
fmt.Printf("Anyone who can read the key file can take everything!\n")
|
||||
fmt.Printf("You should start over and use a good passphrase!\n")
|
||||
fmt.Printf("Saved unencrypted key at %s\n", filename)
|
||||
return nil
|
||||
}
|
||||
|
||||
salt := new([24]byte) // salt for scrypt / nonce for secretbox
|
||||
dk32 := new([32]byte) // derived key from scrypt
|
||||
|
||||
//get 24 random bytes for scrypt salt (and secretbox nonce)
|
||||
_, err := rand.Read(salt[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// next use the pass and salt to make a 32-byte derived key
|
||||
dk, err := scrypt.Key(pass, salt[:], 16384, 8, 1, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
copy(dk32[:], dk[:])
|
||||
|
||||
enckey := append(salt[:], secretbox.Seal(nil, priv32[:], salt, dk32)...)
|
||||
// enckey = append(salt, enckey...)
|
||||
keyhex := fmt.Sprintf("%x\n", enckey)
|
||||
|
||||
err = ioutil.WriteFile(filename, []byte(keyhex), 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Wrote encrypted key to %s\n", filename)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadKeyFileToECPriv returns an extendedkey from a file.
|
||||
// If there's no file there, it'll make one. If there's a password needed,
|
||||
// it'll prompt for one. One stop function.
|
||||
func ReadKeyFileToECPriv(filename string) (*hdkeychain.ExtendedKey, error) {
|
||||
key32 := new([32]byte)
|
||||
_, err := os.Stat(filename)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// no key found, generate and save one
|
||||
fmt.Printf("No file %s, generating.\n", filename)
|
||||
rn, err := hdkeychain.GenerateSeed(32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(key32[:], rn[:])
|
||||
err = SaveKeyToFileInteractive(filename, key32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// unknown error, crash
|
||||
fmt.Printf("unknown\n")
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
key, err := LoadKeyFromFileInteractive(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rootpriv, err := hdkeychain.NewMaster(key[:], &chaincfg.TestNet3Params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rootpriv, nil
|
||||
}
|
@ -5,10 +5,13 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcutil/bloom"
|
||||
"github.com/btcsuite/btcutil/hdkeychain"
|
||||
)
|
||||
|
||||
type TxStore struct {
|
||||
@ -17,7 +20,13 @@ type TxStore struct {
|
||||
Utxos []Utxo // stacks on stacks
|
||||
Sum int64 // racks on racks
|
||||
Adrs []MyAdr // endeavouring to acquire capital
|
||||
LastIdx uint32 // should equal len(Adrs)
|
||||
StateDB *bolt.DB // place to write all this down
|
||||
// this is redundant with the SPVCon param... ugly and should be taken out
|
||||
param *chaincfg.Params // network parameters (testnet3, testnetL)
|
||||
|
||||
// From here, comes everything. It's a secret to everybody.
|
||||
rootPrivKey *hdkeychain.ExtendedKey
|
||||
}
|
||||
|
||||
type Utxo struct { // cash money.
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
|
||||
@ -16,6 +17,42 @@ var (
|
||||
KEYState = []byte("LastUpdate") // last state of DB
|
||||
)
|
||||
|
||||
func (ts *TxStore) OpenDB(filename string) error {
|
||||
var err error
|
||||
ts.StateDB, err = bolt.Open(filename, 0644, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// create buckets if they're not already there
|
||||
return ts.StateDB.Update(func(tx *bolt.Tx) error {
|
||||
_, err = tx.CreateBucketIfNotExists(BKTUtxos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tx.CreateBucketIfNotExists(BKTOld)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (ts *TxStore) PopulateAdrs(lastKey uint32) error {
|
||||
for k := uint32(0); k < lastKey; k++ {
|
||||
|
||||
priv, err := ts.rootPrivKey.Child(k)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
myadr, err := priv.Address(ts.param)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("made adr %s\n", myadr.String())
|
||||
ts.AddAdr(myadr, k)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (u *Utxo) SaveToDB(dbx *bolt.DB) error {
|
||||
return dbx.Update(func(tx *bolt.Tx) error {
|
||||
duf := tx.Bucket(BKTUtxos)
|
||||
|
Loading…
Reference in New Issue
Block a user