Make scrypt parameters overridable/optional

This commit is contained in:
Lars Hesel Christensen 2014-10-24 08:56:54 +02:00 committed by Dave Collins
parent ec8a5bc10c
commit 402fcf0dd0
4 changed files with 77 additions and 55 deletions

View file

@ -65,8 +65,3 @@ func hexToBytes(origHex string) []byte {
} }
return buf return buf
} }
func init() {
// Tune the scrypt params down for tests so they execute quickly.
waddrmgr.TstSetScryptParams(16, 8, 1)
}

View file

@ -23,34 +23,24 @@ interface. The functions are only exported while the tests are being run.
package waddrmgr package waddrmgr
import ( import "github.com/conformal/btcwallet/snacl"
"github.com/conformal/btcwallet/snacl"
)
// TstMaxRecentHashes makes the unexported maxRecentHashes constant available // TstMaxRecentHashes makes the unexported maxRecentHashes constant available
// when tests are run. // when tests are run.
var TstMaxRecentHashes = maxRecentHashes var TstMaxRecentHashes = maxRecentHashes
// TstSetScryptParams allows the scrypt parameters to be set to much lower // Replace the Manager.newSecretKey function with the given one and calls
// values while the tests are running so they are faster. // the callback function. Afterwards the original newSecretKey
func TstSetScryptParams(n, r, p int) { // function will be restored.
scryptN = n func TstRunWithReplacedNewSecretKey(callback func()) {
scryptR = r orig := newSecretKey
scryptP = p defer func() {
} newSecretKey = orig
}()
// TstReplaceNewSecretKeyFunc replaces the new secret key generation function newSecretKey = func(passphrase *[]byte, config *Options) (*snacl.SecretKey, error) {
// with a version that intentionally fails.
func TstReplaceNewSecretKeyFunc() {
newSecretKey = func(passphrase *[]byte) (*snacl.SecretKey, error) {
return nil, snacl.ErrDecryptFailed return nil, snacl.ErrDecryptFailed
} }
} callback()
// TstResetNewSecretKeyFunc resets the new secret key generation function to the
// original version.
func TstResetNewSecretKeyFunc() {
newSecretKey = defaultNewSecretKey
} }
// TstCheckPublicPassphrase return true if the provided public passphrase is // TstCheckPublicPassphrase return true if the provided public passphrase is

View file

@ -76,14 +76,6 @@ const (
internalBranch uint32 = 1 internalBranch uint32 = 1
) )
var (
// scryptN, scryptR, and scryptP are the parameters used for scrypt
// password-based key derivation.
scryptN = 262144 // 2^18
scryptR = 8
scryptP = 1
)
// addrKey is used to uniquely identify an address even when those addresses // addrKey is used to uniquely identify an address even when those addresses
// would end up being the same bitcoin address (as is the case for pay-to-pubkey // would end up being the same bitcoin address (as is the case for pay-to-pubkey
// and pay-to-pubkey-hash style of addresses). // and pay-to-pubkey-hash style of addresses).
@ -123,8 +115,9 @@ type unlockDeriveInfo struct {
} }
// defaultNewSecretKey returns a new secret key. See newSecretKey. // defaultNewSecretKey returns a new secret key. See newSecretKey.
func defaultNewSecretKey(passphrase *[]byte) (*snacl.SecretKey, error) { func defaultNewSecretKey(passphrase *[]byte, config *Options) (*snacl.SecretKey, error) {
return snacl.NewSecretKey(passphrase, scryptN, scryptR, scryptP) return snacl.NewSecretKey(passphrase, config.ScryptN, config.ScryptR,
config.ScryptP)
} }
// newSecretKey is used as a way to replace the new secret key generation // newSecretKey is used as a way to replace the new secret key generation
@ -225,6 +218,9 @@ type Manager struct {
// the private extended key (hence nor the underlying private key) in // the private extended key (hence nor the underlying private key) in
// order to encrypt it. // order to encrypt it.
deriveOnUnlock []*unlockDeriveInfo deriveOnUnlock []*unlockDeriveInfo
// config holds overridable options, such as scrypt parameters.
config *Options
} }
// lock performs a best try effort to remove and zero all secret keys associated // lock performs a best try effort to remove and zero all secret keys associated
@ -650,7 +646,7 @@ func (m *Manager) ChangePassphrase(oldPassphrase, newPassphrase []byte, private
// Generate a new master key from the passphrase which is used to secure // Generate a new master key from the passphrase which is used to secure
// the actual secret keys. // the actual secret keys.
newMasterKey, err := newSecretKey(&newPassphrase) newMasterKey, err := newSecretKey(&newPassphrase, m.config)
if err != nil { if err != nil {
str := "failed to create new master private key" str := "failed to create new master private key"
return managerError(ErrCrypto, str, err) return managerError(ErrCrypto, str, err)
@ -788,7 +784,7 @@ func (m *Manager) ExportWatchingOnly(newDbPath string, pubPassphrase []byte) (*M
return nil, err return nil, err
} }
return loadManager(watchingDb, pubPassphrase, m.net) return loadManager(watchingDb, pubPassphrase, m.net, m.config)
} }
// Export writes the manager database to the provided writer. // Export writes the manager database to the provided writer.
@ -1447,7 +1443,7 @@ func (m *Manager) AllActiveAddresses() ([]btcutil.Address, error) {
func newManager(db *managerDB, net *btcnet.Params, masterKeyPub *snacl.SecretKey, func newManager(db *managerDB, net *btcnet.Params, masterKeyPub *snacl.SecretKey,
masterKeyPriv *snacl.SecretKey, cryptoKeyPub EncryptorDecryptor, masterKeyPriv *snacl.SecretKey, cryptoKeyPub EncryptorDecryptor,
cryptoKeyPrivEncrypted, cryptoKeyScriptEncrypted []byte, cryptoKeyPrivEncrypted, cryptoKeyScriptEncrypted []byte,
syncInfo *syncState) *Manager { syncInfo *syncState, config *Options) *Manager {
return &Manager{ return &Manager{
db: db, db: db,
@ -1463,6 +1459,7 @@ func newManager(db *managerDB, net *btcnet.Params, masterKeyPub *snacl.SecretKey
cryptoKeyPriv: &cryptoKey{}, cryptoKeyPriv: &cryptoKey{},
cryptoKeyScriptEncrypted: cryptoKeyScriptEncrypted, cryptoKeyScriptEncrypted: cryptoKeyScriptEncrypted,
cryptoKeyScript: &cryptoKey{}, cryptoKeyScript: &cryptoKey{},
config: config,
} }
} }
@ -1543,7 +1540,7 @@ func checkBranchKeys(acctKey *hdkeychain.ExtendedKey) error {
// loadManager returns a new address manager that results from loading it from // loadManager returns a new address manager that results from loading it from
// the passed opened database. The public passphrase is required to decrypt the // the passed opened database. The public passphrase is required to decrypt the
// public keys. // public keys.
func loadManager(db *managerDB, pubPassphrase []byte, net *btcnet.Params) (*Manager, error) { func loadManager(db *managerDB, pubPassphrase []byte, net *btcnet.Params, config *Options) (*Manager, error) {
// Perform all database lookups in a read-only view. // Perform all database lookups in a read-only view.
var watchingOnly bool var watchingOnly bool
var masterKeyPubParams, masterKeyPrivParams []byte var masterKeyPubParams, masterKeyPrivParams []byte
@ -1630,7 +1627,7 @@ func loadManager(db *managerDB, pubPassphrase []byte, net *btcnet.Params) (*Mana
// the defaults for the additional fields which are not specified in the // the defaults for the additional fields which are not specified in the
// call to new with the values loaded from the database. // call to new with the values loaded from the database.
mgr := newManager(db, net, &masterKeyPub, &masterKeyPriv, cryptoKeyPub, mgr := newManager(db, net, &masterKeyPub, &masterKeyPriv, cryptoKeyPub,
cryptoKeyPrivEnc, cryptoKeyScriptEnc, syncInfo) cryptoKeyPrivEnc, cryptoKeyScriptEnc, syncInfo, config)
mgr.watchingOnly = watchingOnly mgr.watchingOnly = watchingOnly
return mgr, nil return mgr, nil
} }
@ -1640,9 +1637,12 @@ func loadManager(db *managerDB, pubPassphrase []byte, net *btcnet.Params) (*Mana
// public information such as addresses. This is important since access to // public information such as addresses. This is important since access to
// BIP0032 extended keys means it is possible to generate all future addresses. // BIP0032 extended keys means it is possible to generate all future addresses.
// //
// If a config structure is passed to the function, that configuration
// will override the defaults.
//
// A ManagerError with an error code of ErrNoExist will be returned if the // A ManagerError with an error code of ErrNoExist will be returned if the
// passed database does not exist. // passed database does not exist.
func Open(dbPath string, pubPassphrase []byte, net *btcnet.Params) (*Manager, error) { func Open(dbPath string, pubPassphrase []byte, net *btcnet.Params, config *Options) (*Manager, error) {
// Return an error if the specified database does not exist. // Return an error if the specified database does not exist.
if !fileExists(dbPath) { if !fileExists(dbPath) {
str := "the specified address manager does not exist" str := "the specified address manager does not exist"
@ -1654,7 +1654,27 @@ func Open(dbPath string, pubPassphrase []byte, net *btcnet.Params) (*Manager, er
return nil, err return nil, err
} }
return loadManager(db, pubPassphrase, net) if config == nil {
config = defaultConfig
}
return loadManager(db, pubPassphrase, net, config)
}
// Options is used to hold the optional parameters passed to Create or
// Load.
type Options struct {
ScryptN int
ScryptR int
ScryptP int
}
// defaultConfig is an instance of the Options struct initialized with
// default configuration options.
var defaultConfig = &Options{
ScryptN: 262144, // 2^18
ScryptR: 8,
ScryptP: 1,
} }
// Create returns a new locked address manager at the given database path. The // Create returns a new locked address manager at the given database path. The
@ -1669,9 +1689,12 @@ func Open(dbPath string, pubPassphrase []byte, net *btcnet.Params) (*Manager, er
// private passphrase is required to unlock the address manager in order to gain // private passphrase is required to unlock the address manager in order to gain
// access to any private keys and information. // access to any private keys and information.
// //
// If a config structure is passed to the function, that configuration
// will override the defaults.
//
// A ManagerError with an error code of ErrAlreadyExists will be returned if the // A ManagerError with an error code of ErrAlreadyExists will be returned if the
// passed database already exists. // passed database already exists.
func Create(dbPath string, seed, pubPassphrase, privPassphrase []byte, net *btcnet.Params) (*Manager, error) { func Create(dbPath string, seed, pubPassphrase, privPassphrase []byte, net *btcnet.Params, config *Options) (*Manager, error) {
// Return an error if the specified database already exists. // Return an error if the specified database already exists.
if fileExists(dbPath) { if fileExists(dbPath) {
return nil, managerError(ErrAlreadyExists, errAlreadyExists, nil) return nil, managerError(ErrAlreadyExists, errAlreadyExists, nil)
@ -1682,6 +1705,10 @@ func Create(dbPath string, seed, pubPassphrase, privPassphrase []byte, net *btcn
return nil, err return nil, err
} }
if config == nil {
config = defaultConfig
}
// Generate the BIP0044 HD key structure to ensure the provided seed // Generate the BIP0044 HD key structure to ensure the provided seed
// can generate the required structure with no issues. // can generate the required structure with no issues.
@ -1729,12 +1756,12 @@ func Create(dbPath string, seed, pubPassphrase, privPassphrase []byte, net *btcn
// Generate new master keys. These master keys are used to protect the // Generate new master keys. These master keys are used to protect the
// crypto keys that will be generated next. // crypto keys that will be generated next.
masterKeyPub, err := newSecretKey(&pubPassphrase) masterKeyPub, err := newSecretKey(&pubPassphrase, config)
if err != nil { if err != nil {
str := "failed to master public key" str := "failed to master public key"
return nil, managerError(ErrCrypto, str, err) return nil, managerError(ErrCrypto, str, err)
} }
masterKeyPriv, err := newSecretKey(&privPassphrase) masterKeyPriv, err := newSecretKey(&privPassphrase, config)
if err != nil { if err != nil {
str := "failed to master private key" str := "failed to master private key"
return nil, managerError(ErrCrypto, str, err) return nil, managerError(ErrCrypto, str, err)
@ -1856,5 +1883,5 @@ func Create(dbPath string, seed, pubPassphrase, privPassphrase []byte, net *btcn
cryptoKeyPriv.Zero() cryptoKeyPriv.Zero()
cryptoKeyScript.Zero() cryptoKeyScript.Zero()
return newManager(db, net, masterKeyPub, masterKeyPriv, cryptoKeyPub, return newManager(db, net, masterKeyPub, masterKeyPriv, cryptoKeyPub,
cryptoKeyPrivEnc, cryptoKeyScriptEnc, syncInfo), nil cryptoKeyPrivEnc, cryptoKeyScriptEnc, syncInfo, config), nil
} }

View file

@ -83,6 +83,12 @@ func testNamePrefix(tc *testContext) string {
return prefix + fmt.Sprintf("account #%d", tc.account) return prefix + fmt.Sprintf("account #%d", tc.account)
} }
var fastScrypt = &waddrmgr.Options{
ScryptN: 16,
ScryptR: 8,
ScryptP: 1,
}
// testManagedPubKeyAddress ensures the data returned by all exported functions // testManagedPubKeyAddress ensures the data returned by all exported functions
// provided by the passed managed p ublic key address matches the corresponding // provided by the passed managed p ublic key address matches the corresponding
// fields in the provided expected address. // fields in the provided expected address.
@ -1018,12 +1024,14 @@ func testChangePassphrase(tc *testContext) bool {
// generate a new secret key by replacing the generation function one // generate a new secret key by replacing the generation function one
// that intentionally errors. // that intentionally errors.
testName := "ChangePassphrase (public) with invalid new secret key" testName := "ChangePassphrase (public) with invalid new secret key"
waddrmgr.TstReplaceNewSecretKeyFunc()
err := tc.manager.ChangePassphrase(pubPassphrase, pubPassphrase2, false) var err error
waddrmgr.TstRunWithReplacedNewSecretKey(func() {
err = tc.manager.ChangePassphrase(pubPassphrase, pubPassphrase2, false)
})
if !checkManagerError(tc.t, testName, err, waddrmgr.ErrCrypto) { if !checkManagerError(tc.t, testName, err, waddrmgr.ErrCrypto) {
return false return false
} }
waddrmgr.TstResetNewSecretKeyFunc()
// Attempt to change public passphrase with invalid old passphrase. // Attempt to change public passphrase with invalid old passphrase.
testName = "ChangePassphrase (public) with invalid old passphrase" testName = "ChangePassphrase (public) with invalid old passphrase"
@ -1159,7 +1167,7 @@ func testExportWatchingOnly(tc *testContext) bool {
mgr.Close() mgr.Close()
// Open the watching-only manager and run all the tests again. // Open the watching-only manager and run all the tests again.
mgr, err = waddrmgr.Open(woMgrName, pubPassphrase, &btcnet.MainNetParams) mgr, err = waddrmgr.Open(woMgrName, pubPassphrase, &btcnet.MainNetParams, fastScrypt)
if err != nil { if err != nil {
tc.t.Errorf("Open Watching-Only: unexpected error: %v", err) tc.t.Errorf("Open Watching-Only: unexpected error: %v", err)
return false return false
@ -1431,14 +1439,15 @@ func TestManager(t *testing.T) {
// returned. // returned.
mgrName := "mgrtest.bin" mgrName := "mgrtest.bin"
_ = os.Remove(mgrName) _ = os.Remove(mgrName)
_, err := waddrmgr.Open(mgrName, pubPassphrase, &btcnet.MainNetParams) _, err := waddrmgr.Open(mgrName, pubPassphrase, &btcnet.MainNetParams,
fastScrypt)
if !checkManagerError(t, "Open non-existant", err, waddrmgr.ErrNoExist) { if !checkManagerError(t, "Open non-existant", err, waddrmgr.ErrNoExist) {
return return
} }
// Create a new manager. // Create a new manager.
mgr, err := waddrmgr.Create(mgrName, seed, pubPassphrase, privPassphrase, mgr, err := waddrmgr.Create(mgrName, seed, pubPassphrase, privPassphrase,
&btcnet.MainNetParams) &btcnet.MainNetParams, fastScrypt)
if err != nil { if err != nil {
t.Errorf("Create: unexpected error: %v", err) t.Errorf("Create: unexpected error: %v", err)
return return
@ -1450,7 +1459,7 @@ func TestManager(t *testing.T) {
// Attempt to create the manager again to ensure the expected error is // Attempt to create the manager again to ensure the expected error is
// returned. // returned.
_, err = waddrmgr.Create(mgrName, seed, pubPassphrase, privPassphrase, _, err = waddrmgr.Create(mgrName, seed, pubPassphrase, privPassphrase,
&btcnet.MainNetParams) &btcnet.MainNetParams, fastScrypt)
if !checkManagerError(t, "Create existing", err, waddrmgr.ErrAlreadyExists) { if !checkManagerError(t, "Create existing", err, waddrmgr.ErrAlreadyExists) {
mgr.Close() mgr.Close()
return return
@ -1469,7 +1478,8 @@ func TestManager(t *testing.T) {
// Open the manager and run all the tests again in open mode which // Open the manager and run all the tests again in open mode which
// avoids reinserting new addresses like the create mode tests do. // avoids reinserting new addresses like the create mode tests do.
mgr, err = waddrmgr.Open(mgrName, pubPassphrase, &btcnet.MainNetParams) mgr, err = waddrmgr.Open(mgrName, pubPassphrase, &btcnet.MainNetParams,
fastScrypt)
if err != nil { if err != nil {
t.Errorf("Open: unexpected error: %v", err) t.Errorf("Open: unexpected error: %v", err)
return return