add keyfileio to manage private key storage on disk
This commit is contained in:
parent
1f40c7214e
commit
73bbb29026
@ -8,7 +8,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/btcsuite/btcutil/bloom"
|
"github.com/btcsuite/btcutil/bloom"
|
||||||
@ -46,7 +45,7 @@ type SPVCon struct {
|
|||||||
mBlockQueue chan RootAndHeight
|
mBlockQueue chan RootAndHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
func OpenSPV(remoteNode string, hfn string,
|
func OpenSPV(remoteNode string, hfn, tsfn string,
|
||||||
inTs *TxStore, p *chaincfg.Params) (SPVCon, error) {
|
inTs *TxStore, p *chaincfg.Params) (SPVCon, error) {
|
||||||
// create new SPVCon
|
// create new SPVCon
|
||||||
var s SPVCon
|
var s SPVCon
|
||||||
@ -70,7 +69,7 @@ func OpenSPV(remoteNode string, hfn string,
|
|||||||
s.localVersion = VERSION
|
s.localVersion = VERSION
|
||||||
|
|
||||||
// transaction store for this SPV connection
|
// transaction store for this SPV connection
|
||||||
err = inTs.OpenDB("utxo.db")
|
err = inTs.OpenDB(tsfn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
@ -130,26 +129,6 @@ func OpenSPV(remoteNode string, hfn string,
|
|||||||
return s, nil
|
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 {
|
func (s *SPVCon) openHeaderFile(hfn string) error {
|
||||||
_, err := os.Stat(hfn)
|
_, err := os.Stat(hfn)
|
||||||
if err != nil {
|
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"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
"github.com/boltdb/bolt"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
"github.com/btcsuite/btcutil/bloom"
|
"github.com/btcsuite/btcutil/bloom"
|
||||||
|
"github.com/btcsuite/btcutil/hdkeychain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TxStore struct {
|
type TxStore struct {
|
||||||
@ -17,7 +20,13 @@ type TxStore struct {
|
|||||||
Utxos []Utxo // stacks on stacks
|
Utxos []Utxo // stacks on stacks
|
||||||
Sum int64 // racks on racks
|
Sum int64 // racks on racks
|
||||||
Adrs []MyAdr // endeavouring to acquire capital
|
Adrs []MyAdr // endeavouring to acquire capital
|
||||||
|
LastIdx uint32 // should equal len(Adrs)
|
||||||
StateDB *bolt.DB // place to write all this down
|
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.
|
type Utxo struct { // cash money.
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
|
||||||
@ -16,6 +17,42 @@ var (
|
|||||||
KEYState = []byte("LastUpdate") // last state of DB
|
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 {
|
func (u *Utxo) SaveToDB(dbx *bolt.DB) error {
|
||||||
return dbx.Update(func(tx *bolt.Tx) error {
|
return dbx.Update(func(tx *bolt.Tx) error {
|
||||||
duf := tx.Bucket(BKTUtxos)
|
duf := tx.Bucket(BKTUtxos)
|
||||||
|
Loading…
Reference in New Issue
Block a user