BIP44-compliant CKD + sending transactions w/o unlocking
This commit is contained in:
parent
e403fec849
commit
3b0f776d1d
|
@ -53,6 +53,11 @@ const (
|
||||||
// fingerprint, 4 bytes child number, 32 bytes chain code, and 33 bytes
|
// fingerprint, 4 bytes child number, 32 bytes chain code, and 33 bytes
|
||||||
// public/private key data.
|
// public/private key data.
|
||||||
serializedKeyLen = 4 + 1 + 4 + 4 + 32 + 33 // 78 bytes
|
serializedKeyLen = 4 + 1 + 4 + 4 + 32 + 33 // 78 bytes
|
||||||
|
|
||||||
|
CoinTypeBTC = 0 // 0x80000000
|
||||||
|
CoinTypeTestNet = 1 // 0x80000001
|
||||||
|
CoinTypeETH = 60 // 0x8000003c
|
||||||
|
CoinTypeETC = 60 // 0x80000000
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -63,11 +68,15 @@ var (
|
||||||
ErrDerivingHardenedFromPublic = errors.New("cannot derive a hardened key from public key")
|
ErrDerivingHardenedFromPublic = errors.New("cannot derive a hardened key from public key")
|
||||||
ErrBadChecksum = errors.New("bad extended key checksum")
|
ErrBadChecksum = errors.New("bad extended key checksum")
|
||||||
ErrInvalidKeyLen = errors.New("serialized extended key length is invalid")
|
ErrInvalidKeyLen = errors.New("serialized extended key length is invalid")
|
||||||
|
ErrDerivingChild = errors.New("error deriving child key")
|
||||||
|
ErrInvalidMasterKey = errors.New("invalid master key supplied")
|
||||||
|
|
||||||
PrivateKeyVersion, _ = hex.DecodeString("0488ADE4")
|
PrivateKeyVersion, _ = hex.DecodeString("0488ADE4")
|
||||||
PublicKeyVersion, _ = hex.DecodeString("0488B21E")
|
PublicKeyVersion, _ = hex.DecodeString("0488B21E")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type CoinType int
|
||||||
|
|
||||||
type ExtendedKey struct {
|
type ExtendedKey struct {
|
||||||
Version []byte // 4 bytes, mainnet: 0x0488B21E public, 0x0488ADE4 private; testnet: 0x043587CF public, 0x04358394 private
|
Version []byte // 4 bytes, mainnet: 0x0488B21E public, 0x0488ADE4 private; testnet: 0x043587CF public, 0x04358394 private
|
||||||
Depth uint16 // 1 byte, depth: 0x00 for master nodes, 0x01 for level-1 derived keys, ....
|
Depth uint16 // 1 byte, depth: 0x00 for master nodes, 0x01 for level-1 derived keys, ....
|
||||||
|
@ -184,6 +193,45 @@ func (parent *ExtendedKey) Child(i uint32) (*ExtendedKey, error) {
|
||||||
return child, nil
|
return child, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Child1 returns Status CKD#1 (used for ETH and SHH).
|
||||||
|
// BIP44 format is used: m / purpose' / coin_type' / account' / change / address_index
|
||||||
|
func (master *ExtendedKey) BIP44Child(coinType, i uint32) (*ExtendedKey, error) {
|
||||||
|
if !master.IsPrivate {
|
||||||
|
return nil, ErrInvalidMasterKey
|
||||||
|
}
|
||||||
|
|
||||||
|
if master.Depth != 0 {
|
||||||
|
return nil, ErrInvalidMasterKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// m/44'/60'/0'/0/index
|
||||||
|
extKey, err := master.Derive([]uint32{
|
||||||
|
HardenedKeyStart + 44, // purpose
|
||||||
|
HardenedKeyStart + coinType, // cointype
|
||||||
|
HardenedKeyStart + 0, // account
|
||||||
|
0, // 0 - public, 1 - private
|
||||||
|
i, // index
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return extKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (parent *ExtendedKey) Derive(path []uint32) (*ExtendedKey, error) {
|
||||||
|
var err error
|
||||||
|
extKey := parent
|
||||||
|
for _, i := range path {
|
||||||
|
extKey, err = extKey.Child(i)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrDerivingChild
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return extKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (k *ExtendedKey) Neuter() (*ExtendedKey, error) {
|
func (k *ExtendedKey) Neuter() (*ExtendedKey, error) {
|
||||||
// Already an extended public key.
|
// Already an extended public key.
|
||||||
if !k.IsPrivate {
|
if !k.IsPrivate {
|
||||||
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBIP32Vectors(t *testing.T) {
|
func TestBIP32Vectors(t *testing.T) {
|
||||||
hkStart := uint32(0x80000000)
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
seed string
|
seed string
|
||||||
|
@ -31,35 +30,35 @@ func TestBIP32Vectors(t *testing.T) {
|
||||||
{
|
{
|
||||||
"test vector 1 chain m/0H",
|
"test vector 1 chain m/0H",
|
||||||
"000102030405060708090a0b0c0d0e0f",
|
"000102030405060708090a0b0c0d0e0f",
|
||||||
[]uint32{hkStart},
|
[]uint32{extkeys.HardenedKeyStart},
|
||||||
"xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw",
|
"xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw",
|
||||||
"xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7",
|
"xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"test vector 1 chain m/0H/1",
|
"test vector 1 chain m/0H/1",
|
||||||
"000102030405060708090a0b0c0d0e0f",
|
"000102030405060708090a0b0c0d0e0f",
|
||||||
[]uint32{hkStart, 1},
|
[]uint32{extkeys.HardenedKeyStart, 1},
|
||||||
"xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ",
|
"xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ",
|
||||||
"xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs",
|
"xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"test vector 1 chain m/0H/1/2H",
|
"test vector 1 chain m/0H/1/2H",
|
||||||
"000102030405060708090a0b0c0d0e0f",
|
"000102030405060708090a0b0c0d0e0f",
|
||||||
[]uint32{hkStart, 1, hkStart + 2},
|
[]uint32{extkeys.HardenedKeyStart, 1, extkeys.HardenedKeyStart + 2},
|
||||||
"xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5",
|
"xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5",
|
||||||
"xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM",
|
"xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"test vector 1 chain m/0H/1/2H/2",
|
"test vector 1 chain m/0H/1/2H/2",
|
||||||
"000102030405060708090a0b0c0d0e0f",
|
"000102030405060708090a0b0c0d0e0f",
|
||||||
[]uint32{hkStart, 1, hkStart + 2, 2},
|
[]uint32{extkeys.HardenedKeyStart, 1, extkeys.HardenedKeyStart + 2, 2},
|
||||||
"xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV",
|
"xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV",
|
||||||
"xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334",
|
"xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"test vector 1 chain m/0H/1/2H/2/1000000000",
|
"test vector 1 chain m/0H/1/2H/2/1000000000",
|
||||||
"000102030405060708090a0b0c0d0e0f",
|
"000102030405060708090a0b0c0d0e0f",
|
||||||
[]uint32{hkStart, 1, hkStart + 2, 2, 1000000000},
|
[]uint32{extkeys.HardenedKeyStart, 1, extkeys.HardenedKeyStart + 2, 2, 1000000000},
|
||||||
"xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy",
|
"xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy",
|
||||||
"xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76",
|
"xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76",
|
||||||
},
|
},
|
||||||
|
@ -81,28 +80,28 @@ func TestBIP32Vectors(t *testing.T) {
|
||||||
{
|
{
|
||||||
"test vector 2 chain m/0/2147483647H",
|
"test vector 2 chain m/0/2147483647H",
|
||||||
"fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
|
"fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
|
||||||
[]uint32{0, hkStart + 2147483647},
|
[]uint32{0, extkeys.HardenedKeyStart + 2147483647},
|
||||||
"xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a",
|
"xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a",
|
||||||
"xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9",
|
"xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"test vector 2 chain m/0/2147483647H/1",
|
"test vector 2 chain m/0/2147483647H/1",
|
||||||
"fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
|
"fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
|
||||||
[]uint32{0, hkStart + 2147483647, 1},
|
[]uint32{0, extkeys.HardenedKeyStart + 2147483647, 1},
|
||||||
"xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon",
|
"xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon",
|
||||||
"xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef",
|
"xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"test vector 2 chain m/0/2147483647H/1/2147483646H",
|
"test vector 2 chain m/0/2147483647H/1/2147483646H",
|
||||||
"fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
|
"fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
|
||||||
[]uint32{0, hkStart + 2147483647, 1, hkStart + 2147483646},
|
[]uint32{0, extkeys.HardenedKeyStart + 2147483647, 1, extkeys.HardenedKeyStart + 2147483646},
|
||||||
"xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL",
|
"xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL",
|
||||||
"xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc",
|
"xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"test vector 2 chain m/0/2147483647H/1/2147483646H/2",
|
"test vector 2 chain m/0/2147483647H/1/2147483646H/2",
|
||||||
"fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
|
"fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
|
||||||
[]uint32{0, hkStart + 2147483647, 1, hkStart + 2147483646, 2},
|
[]uint32{0, extkeys.HardenedKeyStart + 2147483647, 1, extkeys.HardenedKeyStart + 2147483646, 2},
|
||||||
"xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt",
|
"xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt",
|
||||||
"xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j",
|
"xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j",
|
||||||
},
|
},
|
||||||
|
@ -127,12 +126,10 @@ tests:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ind := range test.path {
|
extKey, err = extKey.Derive(test.path)
|
||||||
extKey, err = extKey.Child(ind)
|
if err != nil {
|
||||||
if err != nil {
|
t.Errorf("cannot derive child: %v", err)
|
||||||
t.Errorf("cannot derive child: %v", err)
|
continue tests
|
||||||
continue tests
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
privKeyStr := extKey.String()
|
privKeyStr := extKey.String()
|
||||||
|
@ -271,16 +268,10 @@ tests:
|
||||||
t.Errorf("NewKeyFromString #%d (%s): unexpected error creating extended key: %v", i, test.name, err)
|
t.Errorf("NewKeyFromString #%d (%s): unexpected error creating extended key: %v", i, test.name, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
extKey, err = extKey.Derive(test.path)
|
||||||
for _, childNum := range test.path {
|
if err != nil {
|
||||||
var err error
|
t.Errorf("cannot derive child: %v", err)
|
||||||
extKey, err = extKey.Child(childNum)
|
continue tests
|
||||||
if err != nil {
|
|
||||||
t.Errorf("err: %v", err)
|
|
||||||
continue tests
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("test %d (%s): %s", i, test.name, extKey.String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
privStr := extKey.String()
|
privStr := extKey.String()
|
||||||
|
@ -288,6 +279,8 @@ tests:
|
||||||
t.Errorf("Child #%d (%s): mismatched serialized private extended key -- got: %s, want: %s",
|
t.Errorf("Child #%d (%s): mismatched serialized private extended key -- got: %s, want: %s",
|
||||||
i, test.name, privStr, test.wantPriv)
|
i, test.name, privStr, test.wantPriv)
|
||||||
continue
|
continue
|
||||||
|
} else {
|
||||||
|
t.Logf("test %d (%s): %s", i, test.name, extKey.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -391,21 +384,18 @@ tests:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, childNum := range test.path {
|
extKey, err = extKey.Derive(test.path)
|
||||||
var err error
|
if err != nil {
|
||||||
extKey, err = extKey.Child(childNum)
|
t.Errorf("cannot derive child: %v", err)
|
||||||
if err != nil {
|
continue tests
|
||||||
t.Errorf("err: %v", err)
|
|
||||||
continue tests
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("test %d (%s): %s", i, test.name, extKey.String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pubStr := extKey.String()
|
pubStr := extKey.String()
|
||||||
if pubStr != test.wantPub {
|
if pubStr != test.wantPub {
|
||||||
t.Errorf("Child #%d (%s): mismatched serialized public extended key -- got: %s, want: %s", i, test.name, pubStr, test.wantPub)
|
t.Errorf("Child #%d (%s): mismatched serialized public extended key -- got: %s, want: %s", i, test.name, pubStr, test.wantPub)
|
||||||
continue
|
continue
|
||||||
|
} else {
|
||||||
|
t.Logf("test %d (%s): %s", i, test.name, extKey.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -448,6 +438,17 @@ func TestErrors(t *testing.T) {
|
||||||
t.Errorf("Child: mismatched error -- got: %v, want: %v", err, extkeys.ErrDerivingHardenedFromPublic)
|
t.Errorf("Child: mismatched error -- got: %v, want: %v", err, extkeys.ErrDerivingHardenedFromPublic)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err = pubKey.BIP44Child(extkeys.CoinTypeETH, 0)
|
||||||
|
if err != extkeys.ErrInvalidMasterKey {
|
||||||
|
t.Errorf("BIP44Child: mistmatched error -- got: %v, want: %v", err, extkeys.ErrInvalidMasterKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
childKey, _ := extKey.Child(extkeys.HardenedKeyStart + 1)
|
||||||
|
_, err = childKey.BIP44Child(extkeys.CoinTypeETH, 0) // this should be called from master only
|
||||||
|
if err != extkeys.ErrInvalidMasterKey {
|
||||||
|
t.Errorf("BIP44Child: mistmatched error -- got: %v, want: %v", err, extkeys.ErrInvalidMasterKey)
|
||||||
|
}
|
||||||
|
|
||||||
// NewKeyFromString failure tests.
|
// NewKeyFromString failure tests.
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -497,6 +498,35 @@ func TestErrors(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBIP44ChildDerivation(t *testing.T) {
|
||||||
|
keyString := "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi"
|
||||||
|
derivedKey1String := "xprvA38t8tFW4vbuB7WJXEqMFmZqRrcZUKWqqMcGjjKjr2hbfvPhRtLLJGL4ayWG8shF1VkuUikVGodGshLiKRS7WrdsrGSVDQCY33qoPBxG2Kp"
|
||||||
|
derivedKey2String := "xprvA38t8tFW4vbuDgBNpekPnuMSfpWziDLdF7W9Zd3mPy6eDEkM5F17vk59RtVoFbNdBBq84EJf5CqdZhhEoBkAM4DXHQsDqvUxVnncfnDQEFg"
|
||||||
|
|
||||||
|
extKey, err := extkeys.NewKeyFromString(keyString)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewKeyFromString: cannot create extended key")
|
||||||
|
}
|
||||||
|
|
||||||
|
accounKey1, err := extKey.BIP44Child(extkeys.CoinTypeETH, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error dering BIP44-compliant key")
|
||||||
|
}
|
||||||
|
if accounKey1.String() != derivedKey1String {
|
||||||
|
t.Errorf("BIP44Child: key mismatch -- got: %v, want: %v", accounKey1.String(), derivedKey1String)
|
||||||
|
}
|
||||||
|
t.Logf("Account 1 key: %s", accounKey1.String())
|
||||||
|
|
||||||
|
accounKey2, err := extKey.BIP44Child(extkeys.CoinTypeETH, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error dering BIP44-compliant key")
|
||||||
|
}
|
||||||
|
if accounKey2.String() != derivedKey2String {
|
||||||
|
t.Errorf("BIP44Child: key mismatch -- got: %v, want: %v", accounKey2.String(), derivedKey2String)
|
||||||
|
}
|
||||||
|
t.Logf("Account 1 key: %s", accounKey2.String())
|
||||||
|
}
|
||||||
|
|
||||||
//func TestNewKey(t *testing.T) {
|
//func TestNewKey(t *testing.T) {
|
||||||
// mnemonic := NewMnemonic()
|
// mnemonic := NewMnemonic()
|
||||||
//
|
//
|
||||||
|
|
|
@ -14,9 +14,9 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func splitHMAC(seed, salt []byte) (secretKey, chainCode []byte, err error) {
|
func splitHMAC(seed, salt []byte) (secretKey, chainCode []byte, err error) {
|
||||||
hmac := hmac.New(sha512.New, salt)
|
data := hmac.New(sha512.New, salt)
|
||||||
hmac.Write(seed)
|
data.Write(seed)
|
||||||
I := hmac.Sum(nil)
|
I := data.Sum(nil)
|
||||||
|
|
||||||
// Split I into two 32-byte sequences, IL and IR.
|
// Split I into two 32-byte sequences, IL and IR.
|
||||||
// IL = master secret key
|
// IL = master secret key
|
||||||
|
|
|
@ -9,13 +9,10 @@ extern bool GethServiceSignalEvent( const char *jsonEvent );
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"encoding/json"
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
@ -26,8 +23,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
scryptN = 4096
|
ErrInvalidGethNode = errors.New("no running node detected for account unlock")
|
||||||
scryptP = 6
|
ErrInvalidWhisperService = errors.New("whisper service is unavailable")
|
||||||
|
ErrInvalidAccountManager = errors.New("could not retrieve account manager")
|
||||||
|
ErrAddressToAccountMappingFailure = errors.New("cannot retreive a valid account for a given address")
|
||||||
|
ErrAccountToKeyMappingFailure = errors.New("cannot retreive a valid key for a given account")
|
||||||
|
ErrUnlockCalled = errors.New("no need to unlock accounts, use Login() instead")
|
||||||
|
ErrWhisperIdentityInjectionFailure = errors.New("failed to inject identity into Whisper")
|
||||||
)
|
)
|
||||||
|
|
||||||
// createAccount creates an internal geth account
|
// createAccount creates an internal geth account
|
||||||
|
@ -45,25 +47,27 @@ func createAccount(password string) (string, string, string, error) {
|
||||||
return "", "", "", errextra.Wrap(err, "Can not create mnemonic seed")
|
return "", "", "", errextra.Wrap(err, "Can not create mnemonic seed")
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate extended key (see BIP32)
|
// generate extended master key (see BIP32)
|
||||||
extKey, err := extkeys.NewMaster(m.MnemonicSeed(mnemonic, password), []byte(extkeys.Salt))
|
extKey, err := extkeys.NewMaster(m.MnemonicSeed(mnemonic, password), []byte(extkeys.Salt))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", errextra.Wrap(err, "Can not create master extended key")
|
return "", "", "", errextra.Wrap(err, "Can not create master extended key")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// derive hardened child (see BIP44)
|
||||||
|
extChild1, err := extKey.BIP44Child(extkeys.CoinTypeETH, 0)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", errextra.Wrap(err, "Can not derive hardened child key (#1)")
|
||||||
|
}
|
||||||
|
|
||||||
// generate the account
|
// generate the account
|
||||||
account, err := accountManager.NewAccountUsingExtendedKey(extKey, password, w)
|
account, err := accountManager.NewAccountUsingExtendedKey(extChild1, password, w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", errextra.Wrap(err, "Account manager could not create the account")
|
return "", "", "", errextra.Wrap(err, "Account manager could not create the account")
|
||||||
}
|
}
|
||||||
address := fmt.Sprintf("%x", account.Address)
|
address := fmt.Sprintf("%x", account.Address)
|
||||||
|
|
||||||
// recover the public key to return
|
// recover the public key to return
|
||||||
keyContents, err := ioutil.ReadFile(account.File)
|
account, key, err := accountManager.AccountDecryptedKey(account, password)
|
||||||
if err != nil {
|
|
||||||
return address, "", "", errextra.Wrap(err, "Could not load the key contents")
|
|
||||||
}
|
|
||||||
key, err := accounts.DecryptKey(keyContents, password)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return address, "", "", errextra.Wrap(err, "Could not recover the key")
|
return address, "", "", errextra.Wrap(err, "Could not recover the key")
|
||||||
}
|
}
|
||||||
|
@ -90,7 +94,13 @@ func remindAccountDetails(password, mnemonic string) (string, string, error) {
|
||||||
return "", "", errextra.Wrap(err, "Can not create master extended key")
|
return "", "", errextra.Wrap(err, "Can not create master extended key")
|
||||||
}
|
}
|
||||||
|
|
||||||
privateKeyECDSA := extKey.ToECDSA()
|
// derive hardened child (see BIP44)
|
||||||
|
extChild1, err := extKey.BIP44Child(extkeys.CoinTypeETH, 0)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", errextra.Wrap(err, "Can not derive hardened child key (#1)")
|
||||||
|
}
|
||||||
|
|
||||||
|
privateKeyECDSA := extChild1.ToECDSA()
|
||||||
address := fmt.Sprintf("%x", crypto.PubkeyToAddress(privateKeyECDSA.PublicKey))
|
address := fmt.Sprintf("%x", crypto.PubkeyToAddress(privateKeyECDSA.PublicKey))
|
||||||
pubKey := common.ToHex(crypto.FromECDSAPub(&privateKeyECDSA.PublicKey))
|
pubKey := common.ToHex(crypto.FromECDSAPub(&privateKeyECDSA.PublicKey))
|
||||||
|
|
||||||
|
@ -105,30 +115,43 @@ func remindAccountDetails(password, mnemonic string) (string, string, error) {
|
||||||
return "", "", errors.New("No running node detected for account unlock")
|
return "", "", errors.New("No running node detected for account unlock")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func selectAccount(address, password string) error {
|
||||||
|
if currentNode == nil {
|
||||||
|
return ErrInvalidGethNode
|
||||||
|
}
|
||||||
|
|
||||||
|
if accountManager == nil {
|
||||||
|
return ErrInvalidAccountManager
|
||||||
|
}
|
||||||
|
account, err := utils.MakeAddress(accountManager, address)
|
||||||
|
if err != nil {
|
||||||
|
return ErrAddressToAccountMappingFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
account, accountKey, err := accountManager.AccountDecryptedKey(account, password)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: %v", ErrAccountToKeyMappingFailure.Error(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if whisperService == nil {
|
||||||
|
return ErrInvalidWhisperService
|
||||||
|
}
|
||||||
|
if err := whisperService.InjectIdentity(accountKey.PrivateKey); err != nil {
|
||||||
|
return ErrWhisperIdentityInjectionFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// unlockAccount unlocks an existing account for a certain duration and
|
// unlockAccount unlocks an existing account for a certain duration and
|
||||||
// inject the account as a whisper identity if the account was created as
|
// inject the account as a whisper identity if the account was created as
|
||||||
// a whisper enabled account
|
// a whisper enabled account
|
||||||
func unlockAccount(address, password string, seconds int) error {
|
func unlockAccount(address, password string, seconds int) error {
|
||||||
|
if currentNode == nil {
|
||||||
if currentNode != nil {
|
return ErrInvalidGethNode
|
||||||
|
|
||||||
if accountManager != nil {
|
|
||||||
account, err := utils.MakeAddress(accountManager, address)
|
|
||||||
if err != nil {
|
|
||||||
return errextra.Wrap(err, "Could not retrieve account from address")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = accountManager.TimedUnlock(account, password, time.Duration(seconds)*time.Second)
|
|
||||||
if err != nil {
|
|
||||||
return errextra.Wrap(err, "Could not decrypt account")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errors.New("Could not retrieve account manager")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.New("No running node detected for account unlock")
|
return ErrUnlockCalled
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// createAndStartNode creates a node entity and starts the
|
// createAndStartNode creates a node entity and starts the
|
||||||
|
@ -172,12 +195,12 @@ func onSendTransactionRequest(queuedTx les.QueuedTx) {
|
||||||
C.GethServiceSignalEvent(C.CString(string(body)))
|
C.GethServiceSignalEvent(C.CString(string(body)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func completeTransaction(hash string) (common.Hash, error) {
|
func completeTransaction(hash, password string) (common.Hash, error) {
|
||||||
if currentNode != nil {
|
if currentNode != nil {
|
||||||
if lightEthereum != nil {
|
if lightEthereum != nil {
|
||||||
backend := lightEthereum.StatusBackend
|
backend := lightEthereum.StatusBackend
|
||||||
|
|
||||||
return backend.CompleteQueuedTransaction(les.QueuedTxHash(hash))
|
return backend.CompleteQueuedTransaction(les.QueuedTxHash(hash), password)
|
||||||
}
|
}
|
||||||
|
|
||||||
return common.Hash{}, errors.New("can not retrieve LES service")
|
return common.Hash{}, errors.New("can not retrieve LES service")
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -24,11 +24,16 @@ const (
|
||||||
testAddress = "0x89b50b2b26947ccad43accaef76c21d175ad85f4"
|
testAddress = "0x89b50b2b26947ccad43accaef76c21d175ad85f4"
|
||||||
testAddressPassword = "asdf"
|
testAddressPassword = "asdf"
|
||||||
testNodeSyncSeconds = 180
|
testNodeSyncSeconds = 180
|
||||||
testAccountPassword = "badpassword"
|
newAccountPassword = "badpassword"
|
||||||
|
|
||||||
|
whisperMessage1 = "test message 1 (K1 -> K1)"
|
||||||
|
whisperMessage2 = "test message 2 (K1 -> '')"
|
||||||
|
whisperMessage3 = "test message 3 ('' -> '')"
|
||||||
|
whisperMessage4 = "test message 4 ('' -> K1)"
|
||||||
|
whisperMessage5 = "test message 5 (K2 -> K1)"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestAccountBindings makes sure we can create an account and subsequently unlock that account
|
func TestRemindAccountDetails(t *testing.T) {
|
||||||
func TestAccountBindings(t *testing.T) {
|
|
||||||
err := prepareTestNode()
|
err := prepareTestNode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
|
@ -36,16 +41,15 @@ func TestAccountBindings(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// create an account
|
// create an account
|
||||||
address, pubKey, mnemonic, err := createAccount(testAccountPassword)
|
address, pubKey, mnemonic, err := createAccount(newAccountPassword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err.Error())
|
t.Errorf("could not create account: %v", err)
|
||||||
t.Error("Test failed: could not create account")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
glog.V(logger.Info).Infof("Account created: {address: %s, key: %s, mnemonic:%s}", address, pubKey, mnemonic)
|
glog.V(logger.Info).Infof("Account created: {address: %s, key: %s, mnemonic:%s}", address, pubKey, mnemonic)
|
||||||
|
|
||||||
// try reminding using password + mnemonic
|
// try reminding using password + mnemonic
|
||||||
addressCheck, pubKeyCheck, err := remindAccountDetails(testAccountPassword, mnemonic)
|
addressCheck, pubKeyCheck, err := remindAccountDetails(newAccountPassword, mnemonic)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("remind details failed: %v", err)
|
t.Errorf("remind details failed: %v", err)
|
||||||
return
|
return
|
||||||
|
@ -53,54 +57,241 @@ func TestAccountBindings(t *testing.T) {
|
||||||
if address != addressCheck || pubKey != pubKeyCheck {
|
if address != addressCheck || pubKey != pubKeyCheck {
|
||||||
t.Error("Test failed: remind account details failed to pull the correct details")
|
t.Error("Test failed: remind account details failed to pull the correct details")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// unlock the created account
|
func TestAccountSelect(t *testing.T) {
|
||||||
err = unlockAccount(address, "badpassword", 3)
|
|
||||||
|
err := prepareTestNode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
t.Error(err)
|
||||||
t.Error("Test failed: could not unlock account")
|
return
|
||||||
}
|
}
|
||||||
time.Sleep(2 * time.Second)
|
|
||||||
|
|
||||||
// test to see if the account was injected in whisper
|
// test to see if the account was injected in whisper
|
||||||
var whisperInstance *whisper.Whisper
|
var whisperInstance *whisper.Whisper
|
||||||
if err := currentNode.Service(&whisperInstance); err != nil {
|
if err := currentNode.Service(&whisperInstance); err != nil {
|
||||||
t.Errorf("whisper service not running: %v", err)
|
t.Errorf("whisper service not running: %v", err)
|
||||||
}
|
}
|
||||||
identitySucsess := whisperInstance.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey)))
|
|
||||||
if !identitySucsess || err != nil {
|
// create an accounts
|
||||||
t.Errorf("Test failed: identity not injected into whisper: %v", err)
|
address1, pubKey1, _, err := createAccount(newAccountPassword)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
t.Error("Test failed: could not create account")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
glog.V(logger.Info).Infof("Account created: {address: %s, key: %s}", address1, pubKey1)
|
||||||
|
|
||||||
|
address2, pubKey2, _, err := createAccount(newAccountPassword)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
t.Error("Test failed: could not create account")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
glog.V(logger.Info).Infof("Account created: {address: %s, key: %s}", address2, pubKey2)
|
||||||
|
|
||||||
|
// inject key of newly created account into Whisper, as identity
|
||||||
|
if whisperInstance.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey1))) {
|
||||||
|
t.Errorf("identity already present in whisper")
|
||||||
}
|
}
|
||||||
|
|
||||||
// test to see if we can post with the injected whisper identity
|
// try selecting with wrong password
|
||||||
|
err = selectAccount(address1, "wrongPassword")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("select account is expected to throw error: wrong password used")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = selectAccount(address1, newAccountPassword)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test failed: could not select account: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !whisperInstance.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey1))) {
|
||||||
|
t.Errorf("identity not injected into whisper: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// select another account, make sure that previous account is wiped out from Whisper cache
|
||||||
|
if whisperInstance.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey2))) {
|
||||||
|
t.Errorf("identity already present in whisper")
|
||||||
|
}
|
||||||
|
err = selectAccount(address2, newAccountPassword)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test failed: could not select account: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !whisperInstance.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey2))) {
|
||||||
|
t.Errorf("identity not injected into whisper: %v", err)
|
||||||
|
}
|
||||||
|
if whisperInstance.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey1))) {
|
||||||
|
t.Errorf("identity should be removed, but it is still present in whisper")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWhisperMessaging(t *testing.T) {
|
||||||
|
err := prepareTestNode()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// test to see if the account was injected in whisper
|
||||||
|
var whisperInstance *whisper.Whisper
|
||||||
|
if err := currentNode.Service(&whisperInstance); err != nil {
|
||||||
|
t.Errorf("whisper service not running: %v", err)
|
||||||
|
}
|
||||||
|
whisperAPI := whisper.NewPublicWhisperAPI(whisperInstance)
|
||||||
|
|
||||||
|
// prepare message
|
||||||
postArgs := whisper.PostArgs{
|
postArgs := whisper.PostArgs{
|
||||||
From: pubKey,
|
From: "",
|
||||||
To: pubKey,
|
To: "",
|
||||||
TTL: 100,
|
TTL: 10,
|
||||||
Topics: [][]byte{[]byte("test topic")},
|
Topics: [][]byte{[]byte("test topic")},
|
||||||
Payload: "test message",
|
Payload: "test message",
|
||||||
}
|
}
|
||||||
whisperAPI := whisper.NewPublicWhisperAPI(whisperInstance)
|
|
||||||
postSuccess, err := whisperAPI.Post(postArgs)
|
|
||||||
if !postSuccess || err != nil {
|
|
||||||
t.Errorf("Test failed: Could not post to whisper: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// import test account (with test ether on it)
|
// create an accounts
|
||||||
err = copyFile(filepath.Join(testDataDir, "testnet", "keystore", "test-account.pk"), filepath.Join("data", "test-account.pk"))
|
address1, pubKey1, _, err := createAccount(newAccountPassword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Test failed: cannot copy test account PK: %v", err)
|
fmt.Println(err.Error())
|
||||||
|
t.Error("Test failed: could not create account")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
time.Sleep(2 * time.Second)
|
glog.V(logger.Info).Infof("Account created: {address: %s, key: %s}", address1, pubKey1)
|
||||||
|
|
||||||
// unlock test account (to send ether from it)
|
address2, pubKey2, _, err := createAccount(newAccountPassword)
|
||||||
err = unlockAccount(testAddress, testAddressPassword, 300)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err.Error())
|
||||||
t.Error("Test failed: could not unlock account")
|
t.Error("Test failed: could not create account")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
time.Sleep(2 * time.Second)
|
glog.V(logger.Info).Infof("Account created: {address: %s, key: %s}", address2, pubKey2)
|
||||||
|
|
||||||
|
// start watchers
|
||||||
|
var receivedMessages = map[string]bool{
|
||||||
|
whisperMessage1: false,
|
||||||
|
whisperMessage2: false,
|
||||||
|
whisperMessage3: false,
|
||||||
|
whisperMessage4: false,
|
||||||
|
whisperMessage5: false,
|
||||||
|
}
|
||||||
|
whisperService.Watch(whisper.Filter{
|
||||||
|
//From: crypto.ToECDSAPub(common.FromHex(pubKey1)),
|
||||||
|
//To: crypto.ToECDSAPub(common.FromHex(pubKey2)),
|
||||||
|
Fn: func(msg *whisper.Message) {
|
||||||
|
glog.V(logger.Info).Infof("Whisper message received: %s", msg.Payload)
|
||||||
|
receivedMessages[string(msg.Payload)] = true
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// inject key of newly created account into Whisper, as identity
|
||||||
|
if whisperInstance.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey1))) {
|
||||||
|
t.Errorf("identity already present in whisper")
|
||||||
|
}
|
||||||
|
err = selectAccount(address1, newAccountPassword)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test failed: could not select account: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
identitySucceess := whisperInstance.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey1)))
|
||||||
|
if !identitySucceess || err != nil {
|
||||||
|
t.Errorf("identity not injected into whisper: %v", err)
|
||||||
|
}
|
||||||
|
if whisperInstance.HasIdentity(crypto.ToECDSAPub(common.FromHex(pubKey2))) { // ensure that second id is not injected
|
||||||
|
t.Errorf("identity already present in whisper")
|
||||||
|
}
|
||||||
|
|
||||||
|
// double selecting (shouldn't be a problem)
|
||||||
|
err = selectAccount(address1, newAccountPassword)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test failed: could not select account: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST 0: From != nil && To != nil: encrypted signed message (but we cannot decrypt it - so watchers will not report this)
|
||||||
|
postArgs.From = pubKey1
|
||||||
|
postArgs.To = pubKey2 // owner of that public key will be able to decrypt it
|
||||||
|
postSuccess, err := whisperAPI.Post(postArgs)
|
||||||
|
if !postSuccess || err != nil {
|
||||||
|
t.Errorf("could not post to whisper: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST 1: From != nil && To != nil: encrypted signed message (to self)
|
||||||
|
postArgs.From = pubKey1
|
||||||
|
postArgs.To = pubKey1
|
||||||
|
postArgs.Payload = whisperMessage1
|
||||||
|
postSuccess, err = whisperAPI.Post(postArgs)
|
||||||
|
if !postSuccess || err != nil {
|
||||||
|
t.Errorf("could not post to whisper: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// send from account that is not in Whisper identity list
|
||||||
|
postArgs.From = pubKey2
|
||||||
|
postSuccess, err = whisperAPI.Post(postArgs)
|
||||||
|
if err == nil || err.Error() != fmt.Sprintf("unknown identity to send from: %s", pubKey2) {
|
||||||
|
t.Errorf("expected error not voiced: we are sending from non-injected whisper identity")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST 2: From != nil && To == nil: signed broadcast (known sender)
|
||||||
|
postArgs.From = pubKey1
|
||||||
|
postArgs.To = ""
|
||||||
|
postArgs.Payload = whisperMessage2
|
||||||
|
postSuccess, err = whisperAPI.Post(postArgs)
|
||||||
|
if !postSuccess || err != nil {
|
||||||
|
t.Errorf("could not post to whisper: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST 3: From == nil && To == nil: anonymous broadcast
|
||||||
|
postArgs.From = ""
|
||||||
|
postArgs.To = ""
|
||||||
|
postArgs.Payload = whisperMessage3
|
||||||
|
postSuccess, err = whisperAPI.Post(postArgs)
|
||||||
|
if !postSuccess || err != nil {
|
||||||
|
t.Errorf("could not post to whisper: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST 4: From == nil && To != nil: encrypted anonymous message
|
||||||
|
postArgs.From = ""
|
||||||
|
postArgs.To = pubKey1
|
||||||
|
postArgs.Payload = whisperMessage4
|
||||||
|
postSuccess, err = whisperAPI.Post(postArgs)
|
||||||
|
if !postSuccess || err != nil {
|
||||||
|
t.Errorf("could not post to whisper: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST 5: From != nil && To != nil: encrypted and signed response
|
||||||
|
postArgs.From = ""
|
||||||
|
postArgs.To = pubKey1
|
||||||
|
postArgs.Payload = whisperMessage5
|
||||||
|
postSuccess, err = whisperAPI.Post(postArgs)
|
||||||
|
if !postSuccess || err != nil {
|
||||||
|
t.Errorf("could not post to whisper: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(2 * time.Second) // allow whisper to poll
|
||||||
|
for message, status := range receivedMessages {
|
||||||
|
if !status {
|
||||||
|
t.Errorf("Expected message not received: %s", message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueuedTransactions(t *testing.T) {
|
||||||
|
err := prepareTestNode()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// create an account
|
||||||
|
address, pubKey, mnemonic, err := createAccount(newAccountPassword)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
t.Error("Test failed: could not create account")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
glog.V(logger.Info).Infof("Account created: {address: %s, key: %s, mnemonic:%s}", address, pubKey, mnemonic)
|
||||||
|
|
||||||
// test transaction queueing
|
// test transaction queueing
|
||||||
var lightEthereum *les.LightEthereum
|
var lightEthereum *les.LightEthereum
|
||||||
|
@ -114,8 +305,9 @@ func TestAccountBindings(t *testing.T) {
|
||||||
backend.SetTransactionQueueHandler(func(queuedTx les.QueuedTx) {
|
backend.SetTransactionQueueHandler(func(queuedTx les.QueuedTx) {
|
||||||
glog.V(logger.Info).Infof("Queued transaction hash: %v\n", queuedTx.Hash.Hex())
|
glog.V(logger.Info).Infof("Queued transaction hash: %v\n", queuedTx.Hash.Hex())
|
||||||
var txHash common.Hash
|
var txHash common.Hash
|
||||||
if txHash, err = completeTransaction(queuedTx.Hash.Hex()); err != nil {
|
if txHash, err = completeTransaction(queuedTx.Hash.Hex(), testAddressPassword); err != nil {
|
||||||
t.Errorf("Test failed: cannot complete queued transation[%s]: %v", queuedTx.Hash.Hex(), err)
|
t.Errorf("Test failed: cannot complete queued transation[%s]: %v", queuedTx.Hash.Hex(), err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
glog.V(logger.Info).Infof("Transaction complete: https://testnet.etherscan.io/tx/%s", txHash.Hex())
|
glog.V(logger.Info).Infof("Transaction complete: https://testnet.etherscan.io/tx/%s", txHash.Hex())
|
||||||
|
@ -123,7 +315,7 @@ func TestAccountBindings(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
// try completing non-existing transaction
|
// try completing non-existing transaction
|
||||||
if _, err := completeTransaction("0x1234512345123451234512345123456123451234512345123451234512345123"); err == nil {
|
if _, err := completeTransaction("0x1234512345123451234512345123456123451234512345123451234512345123", testAddressPassword); err == nil {
|
||||||
t.Errorf("Test failed: error expected and not recieved")
|
t.Errorf("Test failed: error expected and not recieved")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,14 +344,13 @@ func TestAccountBindings(t *testing.T) {
|
||||||
t.Error("Test failed: transaction was never queued or completed")
|
t.Error("Test failed: transaction was never queued or completed")
|
||||||
}
|
}
|
||||||
|
|
||||||
//// clean up
|
|
||||||
//err = os.RemoveAll(".ethereumtest")
|
|
||||||
//if err != nil {
|
|
||||||
// t.Error("Test failed: could not clean up temporary datadir")
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareTestNode() error {
|
func prepareTestNode() error {
|
||||||
|
if currentNode != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
rpcport = 8546 // in order to avoid conflicts with running react-native app
|
rpcport = 8546 // in order to avoid conflicts with running react-native app
|
||||||
|
|
||||||
syncRequired := false
|
syncRequired := false
|
||||||
|
@ -173,6 +364,13 @@ func prepareTestNode() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// import test account (with test ether on it)
|
||||||
|
err = copyFile(filepath.Join(testDataDir, "testnet", "keystore", "test-account.pk"), filepath.Join("data", "test-account.pk"))
|
||||||
|
if err != nil {
|
||||||
|
glog.V(logger.Warn).Infof("Test failed: cannot copy test account PK: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// start geth node and wait for it to initialize
|
// start geth node and wait for it to initialize
|
||||||
go createAndStartNode(dataDir)
|
go createAndStartNode(dataDir)
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
|
@ -183,7 +381,16 @@ func prepareTestNode() error {
|
||||||
if syncRequired {
|
if syncRequired {
|
||||||
glog.V(logger.Warn).Infof("Sync is required, it will take %d seconds", testNodeSyncSeconds)
|
glog.V(logger.Warn).Infof("Sync is required, it will take %d seconds", testNodeSyncSeconds)
|
||||||
time.Sleep(testNodeSyncSeconds * time.Second) // LES syncs headers, so that we are up do date when it is done
|
time.Sleep(testNodeSyncSeconds * time.Second) // LES syncs headers, so that we are up do date when it is done
|
||||||
|
} else {
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cleanup() {
|
||||||
|
err := os.RemoveAll(testDataDir)
|
||||||
|
if err != nil {
|
||||||
|
glog.V(logger.Warn).Infof("Test failed: could not clean up temporary datadir")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -58,10 +58,22 @@ func RemindAccountDetails(password, mnemonic *C.char) *C.char {
|
||||||
|
|
||||||
//export Login
|
//export Login
|
||||||
func Login(address, password *C.char) *C.char {
|
func Login(address, password *C.char) *C.char {
|
||||||
// Equivalent to unlocking an account briefly, to inject a whisper identity,
|
// loads a key file (for a given address), tries to decrypt it using the password, to verify ownership
|
||||||
// then locking the account again
|
// if verified, purges all the previous identities from Whisper, and injects verified key as shh identity
|
||||||
out := UnlockAccount(address, password, 1)
|
err := selectAccount(C.GoString(address), C.GoString(password))
|
||||||
return out
|
|
||||||
|
errString := emptyError
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
errString = err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
out := JSONError{
|
||||||
|
Error: errString,
|
||||||
|
}
|
||||||
|
outBytes, _ := json.Marshal(&out)
|
||||||
|
|
||||||
|
return C.CString(string(outBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
//export UnlockAccount
|
//export UnlockAccount
|
||||||
|
@ -87,8 +99,8 @@ func UnlockAccount(address, password *C.char, seconds int) *C.char {
|
||||||
}
|
}
|
||||||
|
|
||||||
//export CompleteTransaction
|
//export CompleteTransaction
|
||||||
func CompleteTransaction(hash *C.char) *C.char {
|
func CompleteTransaction(hash, password *C.char) *C.char {
|
||||||
txHash, err := completeTransaction(C.GoString(hash))
|
txHash, err := completeTransaction(C.GoString(hash), C.GoString(password))
|
||||||
|
|
||||||
errString := emptyError
|
errString := emptyError
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -137,7 +137,7 @@ func makeDefaultExtra() []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
func preprocessDataDir(dataDir string) (string, error) {
|
func preprocessDataDir(dataDir string) (string, error) {
|
||||||
testDataDir := path.Join(dataDir, "testnet")
|
testDataDir := path.Join(dataDir, "testnet", "keystore")
|
||||||
if _, err := os.Stat(testDataDir); os.IsNotExist(err) {
|
if _, err := os.Stat(testDataDir); os.IsNotExist(err) {
|
||||||
if err := os.MkdirAll(testDataDir, 0755); err != nil {
|
if err := os.MkdirAll(testDataDir, 0755); err != nil {
|
||||||
return dataDir, ErrDataDirPreprocessingFailed
|
return dataDir, ErrDataDirPreprocessingFailed
|
||||||
|
@ -145,7 +145,7 @@ func preprocessDataDir(dataDir string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy over static peer nodes list (LES auto-discovery is not stable yet)
|
// copy over static peer nodes list (LES auto-discovery is not stable yet)
|
||||||
dst := filepath.Join(testDataDir, "static-nodes.json")
|
dst := filepath.Join(dataDir, "testnet", "static-nodes.json")
|
||||||
if _, err := os.Stat(dst); os.IsNotExist(err) {
|
if _, err := os.Stat(dst); os.IsNotExist(err) {
|
||||||
src := filepath.Join("data", "static-nodes.json")
|
src := filepath.Join("data", "static-nodes.json")
|
||||||
if err := copyFile(dst, src); err != nil {
|
if err := copyFile(dst, src); err != nil {
|
||||||
|
|
|
@ -123,6 +123,10 @@ func (am *Manager) Accounts() []Account {
|
||||||
return am.cache.accounts()
|
return am.cache.accounts()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (am *Manager) AccountDecryptedKey(a Account, auth string) (Account, *Key, error) {
|
||||||
|
return am.getDecryptedKey(a, auth)
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteAccount deletes the key matched by account if the passphrase is correct.
|
// DeleteAccount deletes the key matched by account if the passphrase is correct.
|
||||||
// If a contains no filename, the address must match a unique key.
|
// If a contains no filename, the address must match a unique key.
|
||||||
func (am *Manager) DeleteAccount(a Account, passphrase string) error {
|
func (am *Manager) DeleteAccount(a Account, passphrase string) error {
|
||||||
|
@ -327,15 +331,6 @@ func (am *Manager) NewAccountUsingExtendedKey(k *extkeys.ExtendedKey, passphrase
|
||||||
// than waiting for file system notifications to pick it up.
|
// than waiting for file system notifications to pick it up.
|
||||||
am.cache.add(account)
|
am.cache.add(account)
|
||||||
|
|
||||||
// sync key to subprotocols (e.g., whisper identity)
|
|
||||||
if am.sync != nil {
|
|
||||||
address := fmt.Sprintf("%x", account.Address)
|
|
||||||
err := am.syncAccounts(address, key)
|
|
||||||
if err != nil {
|
|
||||||
return account, fmt.Errorf("failed to sync accounts: %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return account, nil
|
return account, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1173,7 +1173,7 @@ func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args Sen
|
||||||
|
|
||||||
// CompleteQueuedTransaction creates a transaction by unpacking queued transaction, signs it and submits to the
|
// CompleteQueuedTransaction creates a transaction by unpacking queued transaction, signs it and submits to the
|
||||||
// transaction pool.
|
// transaction pool.
|
||||||
func (s *PublicTransactionPoolAPI) CompleteQueuedTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) {
|
func (s *PublicTransactionPoolAPI) CompleteQueuedTransaction(ctx context.Context, args SendTxArgs, passphrase string) (common.Hash, error) {
|
||||||
var err error
|
var err error
|
||||||
args, err = prepareSendTxArgs(ctx, args, s.b)
|
args, err = prepareSendTxArgs(ctx, args, s.b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1195,7 +1195,7 @@ func (s *PublicTransactionPoolAPI) CompleteQueuedTransaction(ctx context.Context
|
||||||
tx = types.NewTransaction(args.Nonce.Uint64(), *args.To, args.Value.BigInt(), args.Gas.BigInt(), args.GasPrice.BigInt(), common.FromHex(args.Data))
|
tx = types.NewTransaction(args.Nonce.Uint64(), *args.To, args.Value.BigInt(), args.Gas.BigInt(), args.GasPrice.BigInt(), common.FromHex(args.Data))
|
||||||
}
|
}
|
||||||
|
|
||||||
signature, err := s.b.AccountManager().Sign(args.From, tx.SigHash().Bytes())
|
signature, err := s.b.AccountManager().SignWithPassphrase(args.From, passphrase, tx.SigHash().Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Hash{}, err
|
return common.Hash{}, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,13 +102,13 @@ func (b *StatusBackend) SendTransaction(ctx context.Context, args SendTxArgs) er
|
||||||
}
|
}
|
||||||
|
|
||||||
// CompleteQueuedTransaction wraps call to PublicTransactionPoolAPI.CompleteQueuedTransaction
|
// CompleteQueuedTransaction wraps call to PublicTransactionPoolAPI.CompleteQueuedTransaction
|
||||||
func (b *StatusBackend) CompleteQueuedTransaction(hash QueuedTxHash) (common.Hash, error) {
|
func (b *StatusBackend) CompleteQueuedTransaction(hash QueuedTxHash, passphrase string) (common.Hash, error) {
|
||||||
queuedTx, err := b.txEvictingQueue.getQueuedTransaction(hash)
|
queuedTx, err := b.txEvictingQueue.getQueuedTransaction(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Hash{}, err
|
return common.Hash{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.txapi.CompleteQueuedTransaction(context.Background(), ethapi.SendTxArgs(queuedTx.Args))
|
return b.txapi.CompleteQueuedTransaction(context.Background(), ethapi.SendTxArgs(queuedTx.Args), passphrase)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTransactionQueue wraps call to PublicTransactionPoolAPI.GetTransactionQueue
|
// GetTransactionQueue wraps call to PublicTransactionPoolAPI.GetTransactionQueue
|
||||||
|
|
|
@ -65,7 +65,8 @@ type Whisper struct {
|
||||||
protocol p2p.Protocol
|
protocol p2p.Protocol
|
||||||
filters *filter.Filters
|
filters *filter.Filters
|
||||||
|
|
||||||
keys map[string]*ecdsa.PrivateKey
|
keys map[string]*ecdsa.PrivateKey
|
||||||
|
keysMu sync.RWMutex // Mutex to sync identity keys
|
||||||
|
|
||||||
messages map[common.Hash]*Envelope // Pool of messages currently tracked by this node
|
messages map[common.Hash]*Envelope // Pool of messages currently tracked by this node
|
||||||
expirations map[uint32]*set.SetNonTS // Message expiration pool (TODO: something lighter)
|
expirations map[uint32]*set.SetNonTS // Message expiration pool (TODO: something lighter)
|
||||||
|
@ -130,7 +131,9 @@ func (self *Whisper) NewIdentity() *ecdsa.PrivateKey {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
self.keysMu.Lock()
|
||||||
self.keys[string(crypto.FromECDSAPub(&key.PublicKey))] = key
|
self.keys[string(crypto.FromECDSAPub(&key.PublicKey))] = key
|
||||||
|
self.keysMu.Unlock()
|
||||||
|
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
|
@ -138,25 +141,32 @@ func (self *Whisper) NewIdentity() *ecdsa.PrivateKey {
|
||||||
// HasIdentity checks if the the whisper node is configured with the private key
|
// HasIdentity checks if the the whisper node is configured with the private key
|
||||||
// of the specified public pair.
|
// of the specified public pair.
|
||||||
func (self *Whisper) HasIdentity(key *ecdsa.PublicKey) bool {
|
func (self *Whisper) HasIdentity(key *ecdsa.PublicKey) bool {
|
||||||
|
self.keysMu.RLock()
|
||||||
|
defer self.keysMu.RUnlock()
|
||||||
|
|
||||||
return self.keys[string(crypto.FromECDSAPub(key))] != nil
|
return self.keys[string(crypto.FromECDSAPub(key))] != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetIdentity retrieves the private key of the specified public identity.
|
// GetIdentity retrieves the private key of the specified public identity.
|
||||||
func (self *Whisper) GetIdentity(key *ecdsa.PublicKey) *ecdsa.PrivateKey {
|
func (self *Whisper) GetIdentity(key *ecdsa.PublicKey) *ecdsa.PrivateKey {
|
||||||
|
self.keysMu.RLock()
|
||||||
|
defer self.keysMu.RUnlock()
|
||||||
|
|
||||||
return self.keys[string(crypto.FromECDSAPub(key))]
|
return self.keys[string(crypto.FromECDSAPub(key))]
|
||||||
}
|
}
|
||||||
|
|
||||||
// InjectIdentity injects a manually added identity/key pair into the whisper keys
|
// InjectIdentity injects a manually added identity/key pair into the whisper keys
|
||||||
func (self *Whisper) InjectIdentity(key *ecdsa.PrivateKey) error {
|
func (self *Whisper) InjectIdentity(key *ecdsa.PrivateKey) error {
|
||||||
|
if self.HasIdentity(&key.PublicKey) { // no need to re-inject
|
||||||
identity := string(crypto.FromECDSAPub(&key.PublicKey))
|
return nil
|
||||||
self.keys[identity] = key
|
|
||||||
if _, ok := self.keys[identity]; !ok {
|
|
||||||
return fmt.Errorf("key insert into keys map failed")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
identityString := common.ToHex(crypto.FromECDSAPub(&key.PublicKey))
|
self.keysMu.Lock()
|
||||||
fmt.Printf("Injected identity into whisper: %s\n", identityString)
|
self.keys = make(map[string]*ecdsa.PrivateKey) // reset key store
|
||||||
|
self.keys[string(crypto.FromECDSAPub(&key.PublicKey))] = key
|
||||||
|
self.keysMu.Unlock()
|
||||||
|
|
||||||
|
fmt.Printf("Injected identity into whisper: %s\n", common.ToHex(crypto.FromECDSAPub(&key.PublicKey)))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,6 +318,9 @@ func (self *Whisper) postEvent(envelope *Envelope) {
|
||||||
// returning the decrypted message and the key used to achieve it. If not keys
|
// returning the decrypted message and the key used to achieve it. If not keys
|
||||||
// are configured, open will return the payload as if non encrypted.
|
// are configured, open will return the payload as if non encrypted.
|
||||||
func (self *Whisper) open(envelope *Envelope) *Message {
|
func (self *Whisper) open(envelope *Envelope) *Message {
|
||||||
|
self.keysMu.RLock()
|
||||||
|
defer self.keysMu.RUnlock()
|
||||||
|
|
||||||
// Short circuit if no identity is set, and assume clear-text
|
// Short circuit if no identity is set, and assume clear-text
|
||||||
if len(self.keys) == 0 {
|
if len(self.keys) == 0 {
|
||||||
if message, err := envelope.Open(nil); err == nil {
|
if message, err := envelope.Open(nil); err == nil {
|
||||||
|
|
Loading…
Reference in New Issue