From b37d175e59cda8c6afd6b4b1d6b7ed921dc59279 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 8 Dec 2016 14:42:31 +0200 Subject: [PATCH] accounts, internal, mobile: polish accounts API, extend Android tests --- accounts/account_manager.go | 4 ++-- accounts/accounts_test.go | 4 ++-- internal/ethapi/api.go | 11 +++++------ mobile/accounts.go | 26 ++++++++++++++------------ mobile/android_test.go | 37 +++++++++++++++++++++++++++++++------ 5 files changed, 54 insertions(+), 28 deletions(-) diff --git a/accounts/account_manager.go b/accounts/account_manager.go index 12ff30bca..6460b6e85 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -152,8 +152,8 @@ func (am *Manager) Sign(addr common.Address, hash []byte) ([]byte, error) { // SignWithPassphrase signs hash if the private key matching the given address // can be decrypted with the given passphrase. The produced signature is in the // [R || S || V] format where V is 0 or 1. -func (am *Manager) SignWithPassphrase(addr common.Address, passphrase string, hash []byte) (signature []byte, err error) { - _, key, err := am.getDecryptedKey(Account{Address: addr}, passphrase) +func (am *Manager) SignWithPassphrase(a Account, passphrase string, hash []byte) (signature []byte, err error) { + _, key, err := am.getDecryptedKey(a, passphrase) if err != nil { return nil, err } diff --git a/accounts/accounts_test.go b/accounts/accounts_test.go index 2e5f2b44a..51ca6c256 100644 --- a/accounts/accounts_test.go +++ b/accounts/accounts_test.go @@ -95,7 +95,7 @@ func TestSignWithPassphrase(t *testing.T) { t.Fatal("expected account to be locked") } - _, err = am.SignWithPassphrase(acc.Address, pass, testSigData) + _, err = am.SignWithPassphrase(acc, pass, testSigData) if err != nil { t.Fatal(err) } @@ -104,7 +104,7 @@ func TestSignWithPassphrase(t *testing.T) { t.Fatal("expected account to be locked") } - if _, err = am.SignWithPassphrase(acc.Address, "invalid passwd", testSigData); err == nil { + if _, err = am.SignWithPassphrase(acc, "invalid passwd", testSigData); err == nil { t.Fatal("expected SignHash to fail with invalid password") } } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 561e72b01..c4fceb5bc 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -264,7 +264,7 @@ func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs } tx := args.toTransaction() signer := types.MakeSigner(s.b.ChainConfig(), s.b.CurrentBlock().Number()) - signature, err := s.am.SignWithPassphrase(args.From, passwd, signer.Hash(tx).Bytes()) + signature, err := s.am.SignWithPassphrase(accounts.Account{Address: args.From}, passwd, signer.Hash(tx).Bytes()) if err != nil { return common.Hash{}, err } @@ -294,11 +294,11 @@ func signHash(data []byte) []byte { // // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign func (s *PrivateAccountAPI) Sign(ctx context.Context, data hexutil.Bytes, addr common.Address, passwd string) (hexutil.Bytes, error) { - signature, err := s.b.AccountManager().SignWithPassphrase(addr, passwd, signHash(data)) + signature, err := s.b.AccountManager().SignWithPassphrase(accounts.Account{Address: addr}, passwd, signHash(data)) if err != nil { return nil, err } - signature[64] += 27 // SignWithPassphrase uses canonical secp256k1 signatures (v = 0 or 1), transform to yellow paper + signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper return signature, nil } @@ -319,7 +319,7 @@ func (s *PrivateAccountAPI) EcRecover(ctx context.Context, data, sig hexutil.Byt if sig[64] != 27 && sig[64] != 28 { return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") } - sig[64] -= 27 // Transform yellow paper signatures to canonical secp256k1 form + sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 rpk, err := crypto.Ecrecover(signHash(data), sig) if err != nil { @@ -1104,8 +1104,7 @@ func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encod func (s *PublicTransactionPoolAPI) Sign(addr common.Address, data hexutil.Bytes) (hexutil.Bytes, error) { signature, err := s.b.AccountManager().Sign(addr, signHash(data)) if err == nil { - // Sign uses canonical secp256k1 signatures (v = 0 or 1), transform to yellow paper - signature[64] += 27 + signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper } return signature, err } diff --git a/mobile/accounts.go b/mobile/accounts.go index 9a2937b6d..90f664d29 100644 --- a/mobile/accounts.go +++ b/mobile/accounts.go @@ -109,15 +109,17 @@ func (am *AccountManager) DeleteAccount(account *Account, passphrase string) err }, passphrase) } -// Sign signs hash with an unlocked private key matching the given address. +// Sign calculates a ECDSA signature for the given hash. The produced signature +// is in the [R || S || V] format where V is 0 or 1. func (am *AccountManager) Sign(address *Address, hash []byte) (signature []byte, _ error) { return am.manager.Sign(address.address, hash) } -// SignWithPassphrase signs hash if the private key matching the given address can be -// decrypted with the given passphrase. -func (am *AccountManager) SignWithPassphrase(address *Address, passphrase string, hash []byte) (signature []byte, _ error) { - return am.manager.SignWithPassphrase(address.address, passphrase, hash) +// SignWithPassphrase signs hash if the private key matching the given address +// can be decrypted with the given passphrase. The produced signature is in the +// [R || S || V] format where V is 0 or 1. +func (am *AccountManager) SignWithPassphrase(account *Account, passphrase string, hash []byte) (signature []byte, _ error) { + return am.manager.SignWithPassphrase(account.account, passphrase, hash) } // Unlock unlocks the given account indefinitely. @@ -130,15 +132,15 @@ func (am *AccountManager) Lock(address *Address) error { return am.manager.Lock(address.address) } -// TimedUnlock unlocks the given account with the passphrase. The account -// stays unlocked for the duration of timeout. A timeout of 0 unlocks the account -// until the program exits. The account must match a unique key file. +// TimedUnlock unlocks the given account with the passphrase. The account stays +// unlocked for the duration of timeout (nanoseconds). A timeout of 0 unlocks the +// account until the program exits. The account must match a unique key file. // // If the account address is already unlocked for a duration, TimedUnlock extends or // shortens the active unlock timeout. If the address was previously unlocked // indefinitely the timeout is not altered. -func (am *AccountManager) TimedUnlock(a *Account, passphrase string, timeout int64) error { - return am.manager.TimedUnlock(a.account, passphrase, time.Duration(timeout)) +func (am *AccountManager) TimedUnlock(account *Account, passphrase string, timeout int64) error { + return am.manager.TimedUnlock(account.account, passphrase, time.Duration(timeout)) } // NewAccount generates a new key and stores it into the key directory, @@ -165,8 +167,8 @@ func (am *AccountManager) ImportKey(keyJSON []byte, passphrase, newPassphrase st return &Account{acc}, nil } -// Update changes the passphrase of an existing account. -func (am *AccountManager) Update(account *Account, passphrase, newPassphrase string) error { +// UpdateAccount changes the passphrase of an existing account. +func (am *AccountManager) UpdateAccount(account *Account, passphrase, newPassphrase string) error { return am.manager.Update(account.account, passphrase, newPassphrase) } diff --git a/mobile/android_test.go b/mobile/android_test.go index 0a3fa93ae..9e38c1986 100644 --- a/mobile/android_test.go +++ b/mobile/android_test.go @@ -14,9 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -// Contains all the wrappers from the accounts package to support client side key -// management on mobile platforms. - package geth import ( @@ -46,14 +43,42 @@ public class AndroidTest extends InstrumentationTestCase { public AndroidTest() {} public void testAccountManagement() { - try { - AccountManager am = new AccountManager(getInstrumentation().getContext().getFilesDir() + "/keystore", Geth.LightScryptN, Geth.LightScryptP); + // Create an encrypted keystore manager with light crypto parameters. + AccountManager am = new AccountManager(getInstrumentation().getContext().getFilesDir() + "/keystore", Geth.LightScryptN, Geth.LightScryptP); + try { + // Create a new account with the specified encryption passphrase. Account newAcc = am.newAccount("Creation password"); + + // Export the newly created account with a different passphrase. The returned + // data from this method invocation is a JSON encoded, encrypted key-file. byte[] jsonAcc = am.exportKey(newAcc, "Creation password", "Export password"); - am.deleteAccount(newAcc, "Creation password"); + // Update the passphrase on the account created above inside the local keystore. + am.updateAccount(newAcc, "Creation password", "Update password"); + + // Delete the account updated above from the local keystore. + am.deleteAccount(newAcc, "Update password"); + + // Import back the account we've exported (and then deleted) above with yet + // again a fresh passphrase. Account impAcc = am.importKey(jsonAcc, "Export password", "Import password"); + + // Create a new account to sign transactions with + Account signer = am.newAccount("Signer password"); + Hash txHash = new Hash("0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"); + + // Sign a transaction with a single authorization + byte[] signature = am.signWithPassphrase(signer, "Signer password", txHash.getBytes()); + + // Sign a transaction with multiple manually cancelled authorizations + am.unlock(signer, "Signer password"); + signature = am.sign(signer.getAddress(), txHash.getBytes()); + am.lock(signer.getAddress()); + + // Sign a transaction with multiple automatically cancelled authorizations + am.timedUnlock(signer, "Signer password", 1000000000); + signature = am.sign(signer.getAddress(), txHash.getBytes()); } catch (Exception e) { fail(e.toString()); }