/* * Copyright (c) 2014 Conformal Systems LLC * * 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 votingpool_test import ( "bytes" "encoding/hex" "fmt" "io/ioutil" "os" "path/filepath" "reflect" "runtime" "testing" "github.com/btcsuite/btcnet" "github.com/btcsuite/btcutil/hdkeychain" "github.com/btcsuite/btcwallet/votingpool" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/walletdb" _ "github.com/btcsuite/btcwallet/walletdb/bdb" ) var fastScrypt = &waddrmgr.Options{ ScryptN: 16, ScryptR: 8, ScryptP: 1, } // checkManagerError ensures the passed error is a ManagerError with an error // code that matches the passed error code. func checkManagerError(t *testing.T, testName string, gotErr error, wantErrCode waddrmgr.ErrorCode) bool { merr, ok := gotErr.(waddrmgr.ManagerError) if !ok { t.Errorf("%s: unexpected error type - got %T, want %T", testName, gotErr, waddrmgr.ManagerError{}) return false } if merr.ErrorCode != wantErrCode { t.Errorf("%s: unexpected error code - got %s, want %s", testName, merr.ErrorCode, wantErrCode) return false } return true } const ( privKey0 = "xprv9s21ZrQH143K2j9PK4CXkCu8sgxkpUxCF7p1KVwiV5tdnkeYzJXReUkxz5iB2FUzTXC1L15abCDG4RMxSYT5zhm67uvsnLYxuDhZfoFcB6a" privKey1 = "xprv9s21ZrQH143K4PtW77ATQAKAGk7KAFFCzxFuAcWduoMEeQhCgWpuYWQvMGZknqdispUbgLZV1YPqFCbpzMJij8tSZ5xPSaZqPbchojeNuq7" privKey2 = "xprv9s21ZrQH143K27XboWxXZGU5j7VZ9SqVBnmMQPKTbddiWAhuNzeLynKHaZTAti6N454tVUUcvy6u15DfuW68NCBUxry6ZsHHzqoA8UtzdMn" privKey3 = "xprv9s21ZrQH143K2vb4DGQymRejLcZSksBHTYLxB7Stg1c7Lk9JxgEUGZTozwUKxoEWJPoGSdGnJY1TW7LNFQCWrpZjDdEXJeqJuDde6BmdD4P" privKey4 = "xprv9s21ZrQH143K4JNmRvWeLc1PggzusKcDYV1y8fAMNDdb9Rm5X1AvGHizxEdhTVR3sc62XvifC6dLAXMuQesX1y6999xnDwQ3aVno8KviU9d" privKey5 = "xprv9s21ZrQH143K3dxrqESqeHZ7pSwM6Uq77ssQADSBs7qdFs6dyRWmRcPyLUTQRpgB3EduNhJuWkCGG2LHjuUisw8KKfXJpPqYJ1MSPrZpe1z" privKey6 = "xprv9s21ZrQH143K2nE8ENAMNksTTVxPrMxFNWUuwThMy2bcH9LHTtQDXSNq2pTNcbuq36n5A3J9pbXVqnq5LDXvqniFRLN299kW7Svnxsx9tQv" privKey7 = "xprv9s21ZrQH143K3p93xF1oFeB6ey5ruUesWjuPxA9Z2R5wf6BLYfGXz7fg7NavWkQ2cx3Vm8w2HV9uKpSprNNHnenGeW9XhYDPSjwS9hyCs33" privKey8 = "xprv9s21ZrQH143K3WxnnvPZ8SDGXndASvLTFwMLBVzNCVgs9rzP6rXgW92DLvozdyBm8T9bSQvrFm1jMpTJrRE6w1KY5tshFeDk9Nn3K6V5FYX" pubKey0 = "xpub661MyMwAqRbcFDDrR5jY7LqsRioFDwg3cLjc7tML3RRcfYyhXqqgCH5SqMSQdpQ1Xh8EtVwcfm8psD8zXKPcRaCVSY4GCqbb3aMEs27GitE" pubKey1 = "xpub661MyMwAqRbcGsxyD8hTmJFtpmwoZhy4NBBVxzvFU8tDXD2ME49A6JjQCYgbpSUpHGP1q4S2S1Pxv2EqTjwfERS5pc9Q2yeLkPFzSgRpjs9" pubKey2 = "xpub661MyMwAqRbcEbc4uYVXvQQpH9L3YuZLZ1gxCmj59yAhNy33vXxbXadmRpx5YZEupNSqWRrR7PqU6duS2FiVCGEiugBEa5zuEAjsyLJjKCh" pubKey3 = "xpub661MyMwAqRbcFQfXKHwz8ZbTtePwAKu8pmGYyVrWEM96DYUTWDYipMnHrFcemZHn13jcRMfsNU3UWQUudiaE7mhkWCHGFRMavF167DQM4Va" pubKey4 = "xpub661MyMwAqRbcGnTEXx3ehjx8EiqQGnL4uhwZw3ZxvZAa2E6E4YVAp63UoVtvm2vMDDF8BdPpcarcf7PWcEKvzHhxzAYw1zG23C2egeh82AR" pubKey5 = "xpub661MyMwAqRbcG83KwFyr1RVrNUmqVwYxV6nzxbqoRTNc8fRnWxq1yQiTBifTHhevcEM9ucZ1TqFS7Kv17Gd81cesv6RDrrvYS9SLPjPXhV5" pubKey6 = "xpub661MyMwAqRbcFGJbLPhMjtpC1XntFpg6jjQWjr6yXN8b9wfS1RiU5EhJt5L7qoFuidYawc3XJoLjT2PcjVpXryS3hn1WmSPCyvQDNuKsfgM" pubKey7 = "xpub661MyMwAqRbcGJDX4GYocn7qCzvMJwNisxpzkYZAakcvXtWV6CanXuz9xdfe5kTptFMJ4hDt2iTiT11zyN14u8R5zLvoZ1gnEVqNLxp1r3v" pubKey8 = "xpub661MyMwAqRbcG13FtwvZVaA15pTerP4JdAGvytPykqDr2fKXePqw3wLhCALPAixsE176jFkc2ac9K3tnF4KwaTRKUqFF5apWD6XL9LHCu7E" ) var ( // seed is the master seed used throughout the tests. seed = []byte{ 0x2a, 0x64, 0xdf, 0x08, 0x5e, 0xef, 0xed, 0xd8, 0xbf, 0xdb, 0xb3, 0x31, 0x76, 0xb5, 0xba, 0x2e, 0x62, 0xe8, 0xbe, 0x8b, 0x56, 0xc8, 0x83, 0x77, 0x95, 0x59, 0x8b, 0xb6, 0xc4, 0x40, 0xc0, 0x64, } pubPassphrase = []byte("_DJr{fL4H0O}*-0\n:V1izc)(6BomK") privPassphrase = []byte("81lUHXnOMZ@?XXd7O9xyDIWIbXX-lj") ) func init() { runtime.GOMAXPROCS(runtime.NumCPU()) } func setUp(t *testing.T) (tearDownFunc func(), mgr *waddrmgr.Manager, pool *votingpool.Pool) { t.Parallel() // Create a new wallet DB and addr manager. dir, err := ioutil.TempDir("", "pool_test") if err != nil { t.Fatalf("Failed to create db dir: %v", err) } db, err := walletdb.Create("bdb", filepath.Join(dir, "wallet.db")) if err != nil { t.Fatalf("Failed to create wallet DB: %v", err) } mgrNamespace, err := db.Namespace([]byte("waddrmgr")) if err != nil { t.Fatalf("Failed to create addr manager DB namespace: %v", err) } mgr, err = waddrmgr.Create(mgrNamespace, seed, pubPassphrase, privPassphrase, &btcnet.MainNetParams, fastScrypt) if err != nil { t.Fatalf("Failed to create addr manager: %v", err) } // Create a walletdb for votingpools. vpNamespace, err := db.Namespace([]byte("votingpool")) if err != nil { t.Fatalf("Failed to create VotingPool DB namespace: %v", err) } pool, err = votingpool.Create(vpNamespace, mgr, []byte{0x00}) if err != nil { t.Fatalf("Voting Pool creation failed: %v", err) } tearDownFunc = func() { db.Close() mgr.Close() os.RemoveAll(dir) } return tearDownFunc, mgr, pool } func TestLoadVotingPoolAndDepositScript(t *testing.T) { tearDown, manager, pool := setUp(t) defer tearDown() // setup poolID := "test" pubKeys := []string{pubKey0, pubKey1, pubKey2} err := votingpool.LoadAndCreateSeries(pool.TstNamespace(), manager, 1, poolID, 0, 2, pubKeys) if err != nil { t.Fatalf("Failed to create voting pool and series: %v", err) } // execute script, err := votingpool.LoadAndGetDepositScript(pool.TstNamespace(), manager, poolID, 0, 0, 0) if err != nil { t.Fatalf("Failed to get deposit script: %v", err) } // validate strScript := hex.EncodeToString(script) want := "5221035e94da75731a2153b20909017f62fcd49474c45f3b46282c0dafa8b40a3a312b2102e983a53dd20b7746dd100dfd2925b777436fc1ab1dd319433798924a5ce143e32102908d52a548ee9ef6b2d0ea67a3781a0381bc3570ad623564451e63757ff9393253ae" if want != strScript { t.Fatalf("Failed to get the right deposit script. Got %v, want %v", strScript, want) } } func TestLoadVotingPoolAndCreateSeries(t *testing.T) { tearDown, manager, pool := setUp(t) defer tearDown() poolID := "test" // first time, the voting pool is created pubKeys := []string{pubKey0, pubKey1, pubKey2} err := votingpool.LoadAndCreateSeries(pool.TstNamespace(), manager, 1, poolID, 0, 2, pubKeys) if err != nil { t.Fatalf("Creating voting pool and Creating series failed: %v", err) } // create another series where the voting pool is loaded this time pubKeys = []string{pubKey3, pubKey4, pubKey5} err = votingpool.LoadAndCreateSeries(pool.TstNamespace(), manager, 1, poolID, 1, 2, pubKeys) if err != nil { t.Fatalf("Loading voting pool and Creating series failed: %v", err) } } func TestLoadVotingPoolAndReplaceSeries(t *testing.T) { tearDown, manager, pool := setUp(t) defer tearDown() // setup poolID := "test" pubKeys := []string{pubKey0, pubKey1, pubKey2} err := votingpool.LoadAndCreateSeries(pool.TstNamespace(), manager, 1, poolID, 0, 2, pubKeys) if err != nil { t.Fatalf("Failed to create voting pool and series: %v", err) } pubKeys = []string{pubKey3, pubKey4, pubKey5} err = votingpool.LoadAndReplaceSeries(pool.TstNamespace(), manager, 1, poolID, 0, 2, pubKeys) if err != nil { t.Fatalf("Failed to replace series: %v", err) } } func TestLoadVotingPoolAndEmpowerSeries(t *testing.T) { tearDown, manager, pool := setUp(t) defer tearDown() // setup poolID := "test" pubKeys := []string{pubKey0, pubKey1, pubKey2} err := votingpool.LoadAndCreateSeries(pool.TstNamespace(), manager, 1, poolID, 0, 2, pubKeys) if err != nil { t.Fatalf("Creating voting pool and Creating series failed: %v", err) } // We need to unlock the manager in order to empower a series manager.Unlock(privPassphrase) err = votingpool.LoadAndEmpowerSeries(pool.TstNamespace(), manager, poolID, 0, privKey0) if err != nil { t.Fatalf("Load voting pool and Empower series failed: %v", err) } } func TestDepositScriptAddress(t *testing.T) { tearDown, _, pool := setUp(t) defer tearDown() tests := []struct { version uint32 series uint32 reqSigs uint32 pubKeys []string // map of branch:address (we only check the branch index at 0) addresses map[uint32]string }{ { version: 1, series: 0, reqSigs: 2, pubKeys: []string{pubKey0, pubKey1, pubKey2}, addresses: map[uint32]string{ 0: "3Hb4xcebcKg4DiETJfwjh8sF4uDw9rqtVC", 1: "34eVkREKgvvGASZW7hkgE2uNc1yycntMK6", 2: "3Qt1EaKRD9g9FeL2DGkLLswhK1AKmmXFSe", 3: "3PbExiaztsSYgh6zeMswC49hLUwhTQ86XG", }, }, } for i, test := range tests { if err := pool.CreateSeries(test.version, test.series, test.reqSigs, test.pubKeys); err != nil { t.Fatalf("Cannot creates series %v", test.series) } for branch, expectedAddress := range test.addresses { addr, err := pool.DepositScriptAddress(test.series, branch, 0) if err != nil { t.Fatalf("Failed to get DepositScriptAddress #%d: %v", i, err) } address := addr.EncodeAddress() if expectedAddress != address { t.Errorf("DepositScript #%d returned the wrong deposit script. Got %v, want %v", i, address, expectedAddress) } } } } func TestDepositScriptAddressForNonExistentSeries(t *testing.T) { tearDown, _, pool := setUp(t) defer tearDown() if _, err := pool.DepositScriptAddress(0, 0, 0); err == nil { t.Fatalf("Expected an error, got none") } else { rerr := err.(waddrmgr.ManagerError) if waddrmgr.ErrSeriesNotExists != rerr.ErrorCode { t.Errorf("Got %v, want ErrSeriesNotExists", rerr.ErrorCode) } } } func TestDepositScriptAddressForHardenedPubKey(t *testing.T) { tearDown, _, pool := setUp(t) defer tearDown() if err := pool.CreateSeries(1, 0, 2, []string{pubKey0, pubKey1, pubKey2}); err != nil { t.Fatalf("Cannot creates series") } // Ask for a DepositScriptAddress using an index for a hardened child, which should // fail as we use the extended public keys to derive childs. _, err := pool.DepositScriptAddress(0, 0, uint32(hdkeychain.HardenedKeyStart+1)) if err == nil { t.Fatalf("Expected an error, got none") } else { rerr := err.(waddrmgr.ManagerError) if waddrmgr.ErrKeyChain != rerr.ErrorCode { t.Errorf("Got %v, want ErrKeyChain", rerr.ErrorCode) } } } func TestLoadVotingPool(t *testing.T) { tearDown, mgr, pool := setUp(t) defer tearDown() pool2, err := votingpool.Load(pool.TstNamespace(), mgr, pool.ID) if err != nil { t.Errorf("Error loading VotingPool: %v", err) } if !bytes.Equal(pool2.ID, pool.ID) { t.Errorf("Voting pool obtained from DB does not match the created one") } } func TestCreateVotingPool(t *testing.T) { tearDown, mgr, pool := setUp(t) defer tearDown() pool2, err := votingpool.Create(pool.TstNamespace(), mgr, []byte{0x02}) if err != nil { t.Errorf("Error creating VotingPool: %v", err) } if !bytes.Equal(pool2.ID, []byte{0x02}) { t.Errorf("VotingPool ID mismatch: got %v, want %v", pool2.ID, []byte{0x02}) } } func TestCreateVotingPoolWhenAlreadyExists(t *testing.T) { tearDown, mgr, pool := setUp(t) defer tearDown() _, err := votingpool.Create(pool.TstNamespace(), mgr, pool.ID) checkManagerError(t, "", err, waddrmgr.ErrVotingPoolAlreadyExists) } func TestCreateSeries(t *testing.T) { tearDown, _, pool := setUp(t) defer tearDown() tests := []struct { version uint32 series uint32 reqSigs uint32 pubKeys []string }{ { version: 1, series: 0, reqSigs: 2, pubKeys: []string{pubKey0, pubKey1, pubKey2}, }, { version: 1, series: 1, reqSigs: 3, pubKeys: []string{pubKey0, pubKey1, pubKey2, pubKey3, pubKey4}, }, { version: 1, series: 2, reqSigs: 4, pubKeys: []string{pubKey0, pubKey1, pubKey2, pubKey3, pubKey4, pubKey5, pubKey6}, }, { version: 1, series: 3, reqSigs: 5, pubKeys: []string{pubKey0, pubKey1, pubKey2, pubKey3, pubKey4, pubKey5, pubKey6, pubKey7, pubKey8}, }, } for testNum, test := range tests { err := pool.CreateSeries(test.version, test.series, test.reqSigs, test.pubKeys[:]) if err != nil { t.Fatalf("%d: Cannot create series %d", testNum, test.series) } exists, err := pool.TstExistsSeries(test.series) if err != nil { t.Fatal(err) } if !exists { t.Errorf("%d: Series %d not in database", testNum, test.series) } } } func TestCreateSeriesWhenAlreadyExists(t *testing.T) { tearDown, _, pool := setUp(t) defer tearDown() pubKeys := []string{pubKey0, pubKey1, pubKey2} if err := pool.CreateSeries(1, 0, 1, pubKeys); err != nil { t.Fatalf("Cannot create series: %v", err) } err := pool.CreateSeries(1, 0, 1, pubKeys) checkManagerError(t, "", err, waddrmgr.ErrSeriesAlreadyExists) } func TestPutSeriesErrors(t *testing.T) { tearDown, _, pool := setUp(t) defer tearDown() tests := []struct { version uint32 reqSigs uint32 pubKeys []string err waddrmgr.ManagerError msg string }{ { pubKeys: []string{pubKey0}, err: waddrmgr.ManagerError{ErrorCode: waddrmgr.ErrTooFewPublicKeys}, msg: "Should return error when passed too few pubkeys", }, { reqSigs: 5, pubKeys: []string{pubKey0, pubKey1, pubKey2}, err: waddrmgr.ManagerError{ErrorCode: waddrmgr.ErrTooManyReqSignatures}, msg: "Should return error when reqSigs > len(pubKeys)", }, { pubKeys: []string{pubKey0, pubKey1, pubKey2, pubKey0}, err: waddrmgr.ManagerError{ErrorCode: waddrmgr.ErrKeyDuplicate}, msg: "Should return error when passed duplicate pubkeys", }, { pubKeys: []string{"invalidxpub1", "invalidxpub2", "invalidxpub3"}, err: waddrmgr.ManagerError{ErrorCode: waddrmgr.ErrKeyChain}, msg: "Should return error when passed invalid pubkey", }, { pubKeys: []string{privKey0, privKey1, privKey2}, err: waddrmgr.ManagerError{ErrorCode: waddrmgr.ErrKeyIsPrivate}, msg: "Should return error when passed private keys", }, } for i, test := range tests { err := pool.TstPutSeries(test.version, uint32(i), test.reqSigs, test.pubKeys) if err == nil { str := fmt.Sprintf(test.msg+" pubKeys: %v, reqSigs: %v", test.pubKeys, test.reqSigs) t.Errorf(str) } else { retErr := err.(waddrmgr.ManagerError) if test.err.ErrorCode != retErr.ErrorCode { t.Errorf( "Create series #%d - Incorrect error type. Got %s, want %s", i, retErr.ErrorCode, test.err.ErrorCode) } } } } func TestValidateAndDecryptKeys(t *testing.T) { tearDown, manager, pool := setUp(t) defer tearDown() rawPubKeys, err := encryptKeys([]string{pubKey0, pubKey1}, manager, waddrmgr.CKTPublic) if err != nil { t.Fatalf("Failed to encrypt public keys: %v", err) } // We need to unlock the manager in order to encrypt with the // private key. manager.Unlock(privPassphrase) rawPrivKeys, err := encryptKeys([]string{privKey0, ""}, manager, waddrmgr.CKTPrivate) if err != nil { t.Fatalf("Failed to encrypt private keys: %v", err) } pubKeys, privKeys, err := votingpool.TstValidateAndDecryptKeys(rawPubKeys, rawPrivKeys, pool) if err != nil { t.Fatalf("Error when validating/decrypting keys: %v", err) } if len(pubKeys) != 2 { t.Fatalf("Unexpected number of decrypted public keys: got %d, want 2", len(pubKeys)) } if len(privKeys) != 2 { t.Fatalf("Unexpected number of decrypted private keys: got %d, want 2", len(privKeys)) } if pubKeys[0].String() != pubKey0 || pubKeys[1].String() != pubKey1 { t.Fatalf("Public keys don't match: %v, %v", []string{pubKey0, pubKey1}, pubKeys) } if privKeys[0].String() != privKey0 || privKeys[1] != nil { t.Fatalf("Private keys don't match: %v, %v", []string{privKey0, ""}, privKeys) } neuteredKey, err := privKeys[0].Neuter() if err != nil { t.Fatalf("Unable to neuter private key: %v", err) } if pubKeys[0].String() != neuteredKey.String() { t.Errorf("Public key (%v) does not match neutered private key (%v)", pubKeys[0].String(), neuteredKey.String()) } } func TestValidateAndDecryptKeysErrors(t *testing.T) { tearDown, manager, pool := setUp(t) defer tearDown() encryptedPubKeys, err := encryptKeys([]string{pubKey0}, manager, waddrmgr.CKTPublic) if err != nil { t.Fatalf("Failed to encrypt public key: %v", err) } // We need to unlock the manager in order to encrypt with the // private key. manager.Unlock(privPassphrase) encryptedPrivKeys, err := encryptKeys([]string{privKey1}, manager, waddrmgr.CKTPrivate) if err != nil { t.Fatalf("Failed to encrypt private key: %v", err) } tests := []struct { rawPubKeys [][]byte rawPrivKeys [][]byte err waddrmgr.ErrorCode }{ { // Number of public keys does not match number of private keys. rawPubKeys: [][]byte{[]byte(pubKey0)}, rawPrivKeys: [][]byte{}, err: waddrmgr.ErrKeysPrivatePublicMismatch, }, { // Failure to decrypt public key. rawPubKeys: [][]byte{[]byte(pubKey0)}, rawPrivKeys: [][]byte{[]byte(privKey0)}, err: waddrmgr.ErrCrypto, }, { // Failure to decrypt private key. rawPubKeys: encryptedPubKeys, rawPrivKeys: [][]byte{[]byte(privKey0)}, err: waddrmgr.ErrCrypto, }, { // One public and one private key, but they don't match. rawPubKeys: encryptedPubKeys, rawPrivKeys: encryptedPrivKeys, err: waddrmgr.ErrKeyMismatch, }, } for i, test := range tests { _, _, err := votingpool.TstValidateAndDecryptKeys(test.rawPubKeys, test.rawPrivKeys, pool) checkManagerError(t, fmt.Sprintf("Test #%d", i), err, test.err) } } func encryptKeys(keys []string, mgr *waddrmgr.Manager, keyType waddrmgr.CryptoKeyType) ([][]byte, error) { encryptedKeys := make([][]byte, len(keys)) var err error for i, key := range keys { if key == "" { encryptedKeys[i] = nil } else { encryptedKeys[i], err = mgr.Encrypt(keyType, []byte(key)) } if err != nil { return nil, err } } return encryptedKeys, nil } func TestCannotReplaceEmpoweredSeries(t *testing.T) { tearDown, manager, pool := setUp(t) defer tearDown() var seriesID uint32 = 1 if err := pool.CreateSeries(1, seriesID, 3, []string{pubKey0, pubKey1, pubKey2, pubKey3}); err != nil { t.Fatalf("Failed to create series", err) } // We need to unlock the manager in order to empower a series. manager.Unlock(privPassphrase) if err := pool.EmpowerSeries(seriesID, privKey1); err != nil { t.Fatalf("Failed to empower series", err) } if err := pool.ReplaceSeries(1, seriesID, 2, []string{pubKey0, pubKey2, pubKey3}); err == nil { t.Errorf("Replaced an empowered series. That should not be possible", err) } else { gotErr := err.(waddrmgr.ManagerError) wantErrCode := waddrmgr.ErrorCode(waddrmgr.ErrSeriesAlreadyEmpowered) if wantErrCode != gotErr.ErrorCode { t.Errorf("Got %s, want %s", gotErr.ErrorCode, wantErrCode) } } } func TestReplaceNonExistingSeries(t *testing.T) { tearDown, _, pool := setUp(t) defer tearDown() pubKeys := []string{pubKey0, pubKey1, pubKey2} if err := pool.ReplaceSeries(1, 1, 3, pubKeys); err == nil { t.Errorf("Replaced non-existent series. This should not be possible.") } else { gotErr := err.(waddrmgr.ManagerError) wantErrCode := waddrmgr.ErrorCode(waddrmgr.ErrSeriesNotExists) if wantErrCode != gotErr.ErrorCode { t.Errorf("Got %s, want %s", gotErr.ErrorCode, wantErrCode) } } } type replaceSeriesTestEntry struct { testID int orig seriesRaw replaceWith seriesRaw } var replaceSeriesTestData = []replaceSeriesTestEntry{ { testID: 0, orig: seriesRaw{ id: 0, version: 1, reqSigs: 2, pubKeys: votingpool.CanonicalKeyOrder( []string{pubKey0, pubKey1, pubKey2, pubKey4}), }, replaceWith: seriesRaw{ id: 0, version: 1, reqSigs: 1, pubKeys: votingpool.CanonicalKeyOrder( []string{pubKey3, pubKey4, pubKey5}), }, }, { testID: 1, orig: seriesRaw{ id: 2, version: 1, reqSigs: 2, pubKeys: votingpool.CanonicalKeyOrder( []string{pubKey0, pubKey1, pubKey2}), }, replaceWith: seriesRaw{ id: 2, version: 1, reqSigs: 2, pubKeys: votingpool.CanonicalKeyOrder( []string{pubKey3, pubKey4, pubKey5, pubKey6}), }, }, { testID: 2, orig: seriesRaw{ id: 4, version: 1, reqSigs: 8, pubKeys: votingpool.CanonicalKeyOrder([]string{pubKey0, pubKey1, pubKey2, pubKey3, pubKey4, pubKey5, pubKey6, pubKey7, pubKey8}), }, replaceWith: seriesRaw{ id: 4, version: 1, reqSigs: 7, pubKeys: votingpool.CanonicalKeyOrder([]string{pubKey0, pubKey1, pubKey2, pubKey3, pubKey4, pubKey5, pubKey6, pubKey7}), }, }, } func TestReplaceExistingSeries(t *testing.T) { tearDown, _, pool := setUp(t) defer tearDown() for _, data := range replaceSeriesTestData { seriesID := data.orig.id testID := data.testID if err := pool.CreateSeries(data.orig.version, seriesID, data.orig.reqSigs, data.orig.pubKeys); err != nil { t.Fatalf("Test #%d: failed to create series in replace series setup", testID, err) } if err := pool.ReplaceSeries(data.replaceWith.version, seriesID, data.replaceWith.reqSigs, data.replaceWith.pubKeys); err != nil { t.Errorf("Test #%d: replaceSeries failed", testID, err) } validateReplaceSeries(t, pool, testID, data.replaceWith) } } // validateReplaceSeries validate the created series stored in the system // corresponds to the series we replaced the original with. func validateReplaceSeries(t *testing.T, pool *votingpool.Pool, testID int, replacedWith seriesRaw) { seriesID := replacedWith.id series := pool.GetSeries(seriesID) if series == nil { t.Fatalf("Test #%d Series #%d: series not found", testID, seriesID) } pubKeys := series.TstGetRawPublicKeys() // Check that the public keys match what we expect. if !reflect.DeepEqual(replacedWith.pubKeys, pubKeys) { t.Errorf("Test #%d, series #%d: pubkeys mismatch. Got %v, want %v", testID, seriesID, pubKeys, replacedWith.pubKeys) } // Check number of required sigs. if replacedWith.reqSigs != series.TstGetReqSigs() { t.Errorf("Test #%d, series #%d: required signatures mismatch. Got %d, want %d", testID, seriesID, series.TstGetReqSigs(), replacedWith.reqSigs) } // Check that the series is not empowered. if series.IsEmpowered() { t.Errorf("Test #%d, series #%d: series is empowered but should not be", testID, seriesID) } } func TestEmpowerSeries(t *testing.T) { tearDown, manager, pool := setUp(t) defer tearDown() seriesID := uint32(0) err := pool.CreateSeries(1, seriesID, 2, []string{pubKey0, pubKey1, pubKey2}) if err != nil { t.Fatalf("Failed to create series: %v", err) } tests := []struct { seriesID uint32 key string err error }{ { seriesID: 0, key: privKey0, }, { seriesID: 0, key: privKey1, }, { seriesID: 1, key: privKey0, // invalid series err: waddrmgr.ManagerError{ErrorCode: waddrmgr.ErrSeriesNotExists}, }, { seriesID: 0, key: "NONSENSE", // invalid private key err: waddrmgr.ManagerError{ErrorCode: waddrmgr.ErrKeyChain}, }, { seriesID: 0, key: pubKey5, // wrong type of key err: waddrmgr.ManagerError{ErrorCode: waddrmgr.ErrKeyIsPublic}, }, { seriesID: 0, key: privKey5, // key not corresponding to pub key err: waddrmgr.ManagerError{ErrorCode: waddrmgr.ErrKeysPrivatePublicMismatch}, }, } // We need to unlock the manager in order to empower a series. manager.Unlock(privPassphrase) for testNum, test := range tests { // Add the extended private key to voting pool. err := pool.EmpowerSeries(test.seriesID, test.key) if test.err != nil { if err == nil { t.Errorf("EmpowerSeries #%d Expected an error and got none", testNum) continue } if reflect.TypeOf(err) != reflect.TypeOf(test.err) { t.Errorf("DepositScript #%d wrong error type. Got: %v <%T>, want: %T", testNum, err, err, test.err) continue } rerr := err.(waddrmgr.ManagerError) trerr := test.err.(waddrmgr.ManagerError) if rerr.ErrorCode != trerr.ErrorCode { t.Errorf("DepositScript #%d wrong error code. Got: %v, want: %v", testNum, rerr.ErrorCode, trerr.ErrorCode) continue } continue } if err != nil { t.Errorf("EmpowerSeries #%d Unexpected error %v", testNum, err) continue } } } func TestGetSeries(t *testing.T) { tearDown, _, pool := setUp(t) defer tearDown() expectedPubKeys := votingpool.CanonicalKeyOrder([]string{pubKey0, pubKey1, pubKey2}) if err := pool.CreateSeries(1, 0, 2, expectedPubKeys); err != nil { t.Fatalf("Failed to create series: %v", err) } series := pool.GetSeries(0) if series == nil { t.Fatal("GetSeries() returned nil") } pubKeys := series.TstGetRawPublicKeys() if !reflect.DeepEqual(pubKeys, expectedPubKeys) { t.Errorf("Series pubKeys mismatch. Got %v, want %v", pubKeys, expectedPubKeys) } } type seriesRaw struct { id uint32 version uint32 reqSigs uint32 pubKeys []string privKeys []string } type testLoadAllSeriesTest struct { id int series []seriesRaw } var testLoadAllSeriesTests = []testLoadAllSeriesTest{ { id: 1, series: []seriesRaw{ { id: 0, version: 1, reqSigs: 2, pubKeys: []string{pubKey0, pubKey1, pubKey2}, }, { id: 1, version: 1, reqSigs: 2, pubKeys: []string{pubKey3, pubKey4, pubKey5}, privKeys: []string{privKey4}, }, { id: 2, version: 1, reqSigs: 3, pubKeys: []string{pubKey0, pubKey1, pubKey2, pubKey3, pubKey4}, privKeys: []string{privKey0, privKey2}, }, }, }, { id: 2, series: []seriesRaw{ { id: 0, version: 1, reqSigs: 2, pubKeys: []string{pubKey0, pubKey1, pubKey2}, }, }, }, } func setUpLoadAllSeries(t *testing.T, namespace walletdb.Namespace, mgr *waddrmgr.Manager, test testLoadAllSeriesTest) *votingpool.Pool { pool, err := votingpool.Create(namespace, mgr, []byte{byte(test.id + 1)}) if err != nil { t.Fatalf("Voting Pool creation failed: %v", err) } for _, series := range test.series { err := pool.CreateSeries(series.version, series.id, series.reqSigs, series.pubKeys) if err != nil { t.Fatalf("Test #%d Series #%d: failed to create series: %v", test.id, series.id, err) } for _, privKey := range series.privKeys { err := pool.EmpowerSeries(series.id, privKey) if err != nil { t.Fatalf("Test #%d Series #%d: empower with privKey %v failed: %v", test.id, series.id, privKey, err) } } } return pool } func TestLoadAllSeries(t *testing.T) { tearDown, manager, pool := setUp(t) defer tearDown() // We need to unlock the manager in order to empower a series. manager.Unlock(privPassphrase) for _, test := range testLoadAllSeriesTests { pool := setUpLoadAllSeries(t, pool.TstNamespace(), manager, test) pool.TstEmptySeriesLookup() err := pool.LoadAllSeries() if err != nil { t.Fatalf("Test #%d: failed to load voting pool: %v", test.id, err) } for _, seriesData := range test.series { validateLoadAllSeries(t, pool, test.id, seriesData) } } } func validateLoadAllSeries(t *testing.T, pool *votingpool.Pool, testID int, seriesData seriesRaw) { series := pool.GetSeries(seriesData.id) // Check that the series exists. if series == nil { t.Errorf("Test #%d, series #%d: series not found", testID, seriesData.id) } // Check that reqSigs is what we inserted. if seriesData.reqSigs != series.TstGetReqSigs() { t.Errorf("Test #%d, series #%d: required sigs are different. Got %d, want %d", testID, seriesData.id, series.TstGetReqSigs(), seriesData.reqSigs) } // Check that pubkeys and privkeys have the same length. publicKeys := series.TstGetRawPublicKeys() privateKeys := series.TstGetRawPrivateKeys() if len(privateKeys) != len(publicKeys) { t.Errorf("Test #%d, series #%d: wrong number of private keys. Got %d, want %d", testID, seriesData.id, len(privateKeys), len(publicKeys)) } sortedKeys := votingpool.CanonicalKeyOrder(seriesData.pubKeys) if !reflect.DeepEqual(publicKeys, sortedKeys) { t.Errorf("Test #%d, series #%d: public keys mismatch. Got %d, want %d", testID, seriesData.id, sortedKeys, publicKeys) } // Check that privkeys are what we inserted (length and content). foundPrivKeys := make([]string, 0, len(seriesData.pubKeys)) for _, privateKey := range privateKeys { if privateKey != "" { foundPrivKeys = append(foundPrivKeys, privateKey) } } foundPrivKeys = votingpool.CanonicalKeyOrder(foundPrivKeys) privKeys := votingpool.CanonicalKeyOrder(seriesData.privKeys) if !reflect.DeepEqual(privKeys, foundPrivKeys) { t.Errorf("Test #%d, series #%d: private keys mismatch. Got %d, want %d", testID, seriesData.id, foundPrivKeys, privKeys) } } func reverse(inKeys []*hdkeychain.ExtendedKey) []*hdkeychain.ExtendedKey { revKeys := make([]*hdkeychain.ExtendedKey, len(inKeys)) max := len(inKeys) for i := range inKeys { revKeys[i] = inKeys[max-i-1] } return revKeys } func TestBranchOrderZero(t *testing.T) { // test change address branch (0) for 0-10 keys for i := 0; i < 10; i++ { inKeys := createTestPubKeys(t, i, 0) wantKeys := reverse(inKeys) resKeys, err := votingpool.TstBranchOrder(inKeys, 0) if err != nil { t.Fatalf("Error ordering keys: %v", err) } if len(resKeys) != len(wantKeys) { t.Errorf("BranchOrder: wrong no. of keys. Got: %d, want %d", len(resKeys), len(inKeys)) return } for keyIdx := 0; i < len(inKeys); i++ { if resKeys[keyIdx] != wantKeys[keyIdx] { fmt.Printf("%p, %p\n", resKeys[i], wantKeys[i]) t.Errorf("BranchOrder(keys, 0): got %v, want %v", resKeys[i], wantKeys[i]) } } } } func TestBranchOrderNonZero(t *testing.T) { maxBranch := 5 maxTail := 4 // Test branch reordering for branch no. > 0. We test all branch values // within [1, 5] in a slice of up to 9 (maxBranch-1 + branch-pivot + // maxTail) keys. Hopefully that covers all combinations and edge-cases. // We test the case where branch no. is 0 elsewhere. for branch := 1; branch <= maxBranch; branch++ { for j := 0; j <= maxTail; j++ { first := createTestPubKeys(t, branch-1, 0) pivot := createTestPubKeys(t, 1, branch) last := createTestPubKeys(t, j, branch+1) inKeys := append(append(first, pivot...), last...) wantKeys := append(append(pivot, first...), last...) resKeys, err := votingpool.TstBranchOrder(inKeys, uint32(branch)) if err != nil { t.Fatalf("Error ordering keys: %v", err) } if len(resKeys) != len(inKeys) { t.Errorf("BranchOrder: wrong no. of keys. Got: %d, want %d", len(resKeys), len(inKeys)) } for idx := 0; idx < len(inKeys); idx++ { if resKeys[idx] != wantKeys[idx] { o, w, g := branchErrorFormat(inKeys, wantKeys, resKeys) t.Errorf("Branch: %d\nOrig: %v\nGot: %v\nWant: %v", branch, o, g, w) } } } } } func TestBranchOrderNilKeys(t *testing.T) { _, err := votingpool.TstBranchOrder(nil, 1) checkManagerError(t, "", err, waddrmgr.ErrInvalidValue) } func TestBranchOrderInvalidBranch(t *testing.T) { _, err := votingpool.TstBranchOrder(createTestPubKeys(t, 3, 0), 4) checkManagerError(t, "", err, waddrmgr.ErrInvalidBranch) } func branchErrorFormat(orig, want, got []*hdkeychain.ExtendedKey) (origOrder, wantOrder, gotOrder []int) { origOrder = []int{} origMap := make(map[*hdkeychain.ExtendedKey]int) for i, key := range orig { origMap[key] = i + 1 origOrder = append(origOrder, i+1) } wantOrder = []int{} for _, key := range want { wantOrder = append(wantOrder, origMap[key]) } gotOrder = []int{} for _, key := range got { gotOrder = append(gotOrder, origMap[key]) } return origOrder, wantOrder, gotOrder } func createTestPubKeys(t *testing.T, number, offset int) []*hdkeychain.ExtendedKey { xpubRaw := "xpub661MyMwAqRbcFwdnYF5mvCBY54vaLdJf8c5ugJTp5p7PqF9J1USgBx12qYMnZ9yUiswV7smbQ1DSweMqu8wn7Jociz4PWkuJ6EPvoVEgMw7" xpubKey, err := hdkeychain.NewKeyFromString(xpubRaw) if err != nil { t.Fatalf("Failed to generate new key", err) } keys := make([]*hdkeychain.ExtendedKey, number) for i := uint32(0); i < uint32(len(keys)); i++ { chPubKey, err := xpubKey.Child(i + uint32(offset)) if err != nil { t.Fatalf("Failed to generate child key", err) } keys[i] = chPubKey } return keys } func TestReverse(t *testing.T) { // Test the utility function that reverses a list of public keys. // 11 is arbitrary. for numKeys := 0; numKeys < 11; numKeys++ { keys := createTestPubKeys(t, numKeys, 0) revRevKeys := reverse(reverse(keys)) if len(keys) != len(revRevKeys) { t.Errorf("Reverse(Reverse(x)): the no. pubkeys changed. Got %d, want %d", len(revRevKeys), len(keys)) } for i := 0; i < len(keys); i++ { if keys[i] != revRevKeys[i] { t.Errorf("Reverse(Reverse(x)) != x. Got %v, want %v", revRevKeys[i], keys[i]) } } } } func TestEmpowerSeriesNeuterFailed(t *testing.T) { tearDown, _, pool := setUp(t) defer tearDown() seriesID := uint32(0) err := pool.CreateSeries(1, seriesID, 2, []string{pubKey0, pubKey1, pubKey2}) if err != nil { t.Fatalf("Failed to create series: %v", err) } // A private key with bad version (0xffffffff) will trigger an // error in (k *ExtendedKey).Neuter and the associated error path // in EmpowerSeries. badKey := "wM5uZBNTYmaYGiK8VaGi7zPGbZGLuQgDiR2Zk4nGfbRFLXwHGcMUdVdazRpNHFSR7X7WLmzzbAq8dA1ViN6eWKgKqPye1rJTDQTvBiXvZ7E3nmdx" err = pool.EmpowerSeries(seriesID, badKey) checkManagerError(t, "", err, waddrmgr.ErrKeyNeuter) } func TestDecryptExtendedKeyCannotCreateResultKey(t *testing.T) { tearDown, mgr, pool := setUp(t) defer tearDown() // the plaintext not being base58 encoded triggers the error cipherText, err := mgr.Encrypt(waddrmgr.CKTPublic, []byte("not-base58-encoded")) if err != nil { t.Fatalf("Failed to encrypt plaintext: %v", err) } if _, err := pool.TstDecryptExtendedKey(waddrmgr.CKTPublic, cipherText); err == nil { t.Errorf("Expected function to fail, but it didn't") } else { gotErr := err.(waddrmgr.ManagerError) wantErrCode := waddrmgr.ErrorCode(waddrmgr.ErrKeyChain) if gotErr.ErrorCode != wantErrCode { t.Errorf("Got %s, want %s", gotErr.ErrorCode, wantErrCode) } } } func TestDecryptExtendedKeyCannotDecrypt(t *testing.T) { tearDown, _, pool := setUp(t) defer tearDown() if _, err := pool.TstDecryptExtendedKey(waddrmgr.CKTPublic, []byte{}); err == nil { t.Errorf("Expected function to fail, but it didn't") } else { gotErr := err.(waddrmgr.ManagerError) wantErrCode := waddrmgr.ErrorCode(waddrmgr.ErrCrypto) if gotErr.ErrorCode != wantErrCode { t.Errorf("Got %s, want %s", gotErr.ErrorCode, wantErrCode) } } } func TestSerializationErrors(t *testing.T) { tearDown, mgr, _ := setUp(t) defer tearDown() tests := []struct { version uint32 pubKeys []string privKeys []string reqSigs uint32 err waddrmgr.ErrorCode }{ { version: 2, pubKeys: []string{pubKey0, pubKey1, pubKey2}, err: waddrmgr.ErrSeriesVersion, }, { pubKeys: []string{"NONSENSE"}, // Not a valid length public key. err: waddrmgr.ErrSeriesStorage, }, { pubKeys: []string{pubKey0, pubKey1, pubKey2}, privKeys: []string{privKey0}, // The number of public and private keys should be the same. err: waddrmgr.ErrSeriesStorage, }, { pubKeys: []string{pubKey0}, privKeys: []string{"NONSENSE"}, // Not a valid length private key. err: waddrmgr.ErrSeriesStorage, }, } // We need to unlock the manager in order to encrypt with the // private key. mgr.Unlock(privPassphrase) active := true for testNum, test := range tests { encryptedPubs, err := encryptKeys(test.pubKeys, mgr, waddrmgr.CKTPublic) if err != nil { t.Fatalf("Test #%d - Error encrypting pubkeys: %v", testNum, err) } encryptedPrivs, err := encryptKeys(test.privKeys, mgr, waddrmgr.CKTPrivate) if err != nil { t.Fatalf("Test #%d - Error encrypting privkeys: %v", testNum, err) } _, err = votingpool.SerializeSeries( test.version, active, test.reqSigs, encryptedPubs, encryptedPrivs) checkManagerError(t, fmt.Sprintf("Test #%d", testNum), err, test.err) } } func TestSerialization(t *testing.T) { tearDown, mgr, _ := setUp(t) defer tearDown() tests := []struct { version uint32 active bool pubKeys []string privKeys []string reqSigs uint32 }{ { version: 1, active: true, pubKeys: []string{pubKey0}, reqSigs: 1, }, { version: 0, active: false, pubKeys: []string{pubKey0}, privKeys: []string{privKey0}, reqSigs: 1, }, { pubKeys: []string{pubKey0, pubKey1, pubKey2}, privKeys: []string{privKey0, "", ""}, reqSigs: 2, }, { pubKeys: []string{pubKey0, pubKey1, pubKey2, pubKey3, pubKey4}, reqSigs: 3, }, { pubKeys: []string{pubKey0, pubKey1, pubKey2, pubKey3, pubKey4, pubKey5, pubKey6}, privKeys: []string{"", privKey1, "", privKey3, "", "", ""}, reqSigs: 4, }, } // We need to unlock the manager in order to encrypt with the // private key. mgr.Unlock(privPassphrase) for testNum, test := range tests { encryptedPubs, err := encryptKeys(test.pubKeys, mgr, waddrmgr.CKTPublic) if err != nil { t.Fatalf("Test #%d - Error encrypting pubkeys: %v", testNum, err) } encryptedPrivs, err := encryptKeys(test.privKeys, mgr, waddrmgr.CKTPrivate) if err != nil { t.Fatalf("Test #%d - Error encrypting privkeys: %v", testNum, err) } serialized, err := votingpool.SerializeSeries( test.version, test.active, test.reqSigs, encryptedPubs, encryptedPrivs) if err != nil { t.Fatalf("Test #%d - Error in serialization %v", testNum, err) } row, err := votingpool.DeserializeSeries(serialized) if err != nil { t.Fatalf("Test #%d - Failed to deserialize %v %v", testNum, serialized, err) } // TODO: Move all of these checks into one or more separate functions. if row.Version != test.version { t.Errorf("Serialization #%d - version mismatch: got %d want %d", testNum, row.Version, test.version) } if row.Active != test.active { t.Errorf("Serialization #%d - active mismatch: got %d want %d", testNum, row.Active, test.active) } if row.ReqSigs != test.reqSigs { t.Errorf("Serialization #%d - row reqSigs off. Got %d, want %d", testNum, row.ReqSigs, test.reqSigs) } if len(row.PubKeysEncrypted) != len(test.pubKeys) { t.Errorf("Serialization #%d - Wrong no. of pubkeys. Got %d, want %d", testNum, len(row.PubKeysEncrypted), len(test.pubKeys)) } for i, encryptedPub := range encryptedPubs { got := string(row.PubKeysEncrypted[i]) if got != string(encryptedPub) { t.Errorf("Serialization #%d - Pubkey deserialization. Got %v, want %v", testNum, got, string(encryptedPub)) } } if len(row.PrivKeysEncrypted) != len(row.PubKeysEncrypted) { t.Errorf("Serialization #%d - no. privkeys (%d) != no. pubkeys (%d)", testNum, len(row.PrivKeysEncrypted), len(row.PubKeysEncrypted)) } for i, encryptedPriv := range encryptedPrivs { got := string(row.PrivKeysEncrypted[i]) if got != string(encryptedPriv) { t.Errorf("Serialization #%d - Privkey deserialization. Got %v, want %v", testNum, got, string(encryptedPriv)) } } } } func TestDeserializationErrors(t *testing.T) { tearDown, _, _ := setUp(t) defer tearDown() tests := []struct { serialized []byte err waddrmgr.ErrorCode }{ { serialized: make([]byte, 1000000), // Too many bytes (over waddrmgr.seriesMaxSerial). err: waddrmgr.ErrSeriesStorage, }, { serialized: make([]byte, 10), // Not enough bytes (under waddrmgr.seriesMinSerial). err: waddrmgr.ErrSeriesStorage, }, { serialized: []byte{ 1, 0, 0, 0, // 4 bytes (version) 0, // 1 byte (active) 2, 0, 0, 0, // 4 bytes (reqSigs) 3, 0, 0, 0, // 4 bytes (nKeys) }, // Here we have the constant data but are missing any public/private keys. err: waddrmgr.ErrSeriesStorage, }, { serialized: []byte{2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Unsupported version. err: waddrmgr.ErrSeriesVersion, }, } for testNum, test := range tests { _, err := votingpool.DeserializeSeries(test.serialized) checkManagerError(t, fmt.Sprintf("Test #%d", testNum), err, test.err) } }