diff --git a/CHANGELOG.md b/CHANGELOG.md index fe893ba..454c797 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ adheres to the format set out by [Keep a Changelog](https://keepachangelog.com/e - [`Fix`] Make NodeClient match AptosRpcClient interface - [`Dependency`] Update `golang.org/x/crypto` to `v0.32.0` - [`Dependency`] Update `github.com/hasura/go-graphql-client` to `v0.13.1` +- [`Fix`] Make NodeClient satisfy AptosRpcClient interface +- [`Breaking`] Change Account signer input to use Address instead of AuthKey, authkey comes from the private key # v1.4.1 (01/02/2024) diff --git a/account.go b/account.go index cf04d32..51bd915 100644 --- a/account.go +++ b/account.go @@ -30,8 +30,8 @@ var AccountThree = types.AccountThree var AccountFour = types.AccountFour // NewAccountFromSigner creates an account from a Signer, which is most commonly a private key -func NewAccountFromSigner(signer crypto.Signer, authKey ...crypto.AuthenticationKey) (*Account, error) { - return types.NewAccountFromSigner(signer, authKey...) +func NewAccountFromSigner(signer crypto.Signer, accountAddress ...AccountAddress) (*Account, error) { + return types.NewAccountFromSigner(signer, accountAddress...) } // NewEd25519Account creates a legacy Ed25519 account, this is most commonly used in wallets diff --git a/internal/types/account.go b/internal/types/account.go index 4e61f24..8f15733 100644 --- a/internal/types/account.go +++ b/internal/types/account.go @@ -17,11 +17,11 @@ type Account struct { } // NewAccountFromSigner creates an account from a [crypto.Signer] with an optional [crypto.AuthenticationKey] -func NewAccountFromSigner(signer crypto.Signer, authKey ...crypto.AuthenticationKey) (*Account, error) { +func NewAccountFromSigner(signer crypto.Signer, address ...AccountAddress) (*Account, error) { out := &Account{} - if len(authKey) == 1 { - copy(out.Address[:], authKey[0][:]) - } else if len(authKey) > 1 { + if len(address) == 1 { + copy(out.Address[:], address[0][:]) + } else if len(address) > 1 { // Throw error return nil, errors.New("must only provide one auth key") } else { @@ -60,6 +60,42 @@ func NewSecp256k1Account() (*Account, error) { return NewAccountFromSigner(signer) } +// ExtractMessageSigner extracts the message signer from the account for +func (account *Account) ExtractMessageSigner() (crypto.MessageSigner, bool) { + ed25519PrivateKey, ok := account.Signer.(*crypto.Ed25519PrivateKey) + if ok { + return ed25519PrivateKey, ok + } + singleSigner, ok := account.Signer.(*crypto.SingleSigner) + if ok { + return singleSigner.Signer, ok + } + return nil, false +} + +// ExtractPrivateKeyString extracts the private key string +func (account *Account) ExtractPrivateKeyString() (string, error) { + // Handle the key by itself + ed25519PrivateKey, ok := account.Signer.(*crypto.Ed25519PrivateKey) + if ok { + return ed25519PrivateKey.ToAIP80() + } + + // Handle key in single signer + singleSigner, ok := account.Signer.(*crypto.SingleSigner) + if ok { + innerSigner := singleSigner.Signer + switch innerSigner.(type) { + case *crypto.Ed25519PrivateKey: + return innerSigner.(*crypto.Ed25519PrivateKey).ToAIP80() + case *crypto.Secp256k1PrivateKey: + return innerSigner.(*crypto.Secp256k1PrivateKey).ToAIP80() + } + } + + return "", errors.New("signer is not a private key") +} + // Sign signs a message, returning an appropriate authenticator for the signer func (account *Account) Sign(message []byte) (authenticator *crypto.AccountAuthenticator, err error) { return account.Signer.Sign(message) diff --git a/internal/types/accountAddress_test.go b/internal/types/accountAddress_test.go index 433978e..02c26a4 100644 --- a/internal/types/accountAddress_test.go +++ b/internal/types/accountAddress_test.go @@ -3,6 +3,7 @@ package types import ( "encoding/json" "github.com/aptos-labs/aptos-go-sdk/bcs" + "github.com/aptos-labs/aptos-go-sdk/crypto" "github.com/stretchr/testify/assert" "testing" ) @@ -19,6 +20,13 @@ func TestAccountSpecialString(t *testing.T) { assert.Equal(t, aa, aa2) } +func TestAccountAddress_AuthKey(t *testing.T) { + authKey := &crypto.AuthenticationKey{} + var aa AccountAddress + aa.FromAuthKey(authKey) + assert.Equal(t, AccountZero, aa) +} + func TestSpecialAddresses(t *testing.T) { var addr AccountAddress err := addr.ParseStringRelaxed("0x0") diff --git a/internal/types/account_test.go b/internal/types/account_test.go index 5d3172a..fb7a5e3 100644 --- a/internal/types/account_test.go +++ b/internal/types/account_test.go @@ -1,6 +1,7 @@ package types import ( + "errors" "github.com/aptos-labs/aptos-go-sdk/crypto" "github.com/stretchr/testify/assert" "testing" @@ -22,6 +23,26 @@ func TestGenerateEd25519Account(t *testing.T) { assert.True(t, output.Auth.Verify(message)) } +func TestGenerateSingleSignerEd25519Account(t *testing.T) { + message := []byte{0x12, 0x34} + account, err := NewEd25519SingleSignerAccount() + assert.NoError(t, err) + output, err := account.Sign(message) + assert.NoError(t, err) + assert.Equal(t, crypto.AccountAuthenticatorSingleSender, output.Variant) + assert.True(t, output.Auth.Verify(message)) +} + +func TestGenerateSecp256k1Account(t *testing.T) { + message := []byte{0x12, 0x34} + account, err := NewSecp256k1Account() + assert.NoError(t, err) + output, err := account.Sign(message) + assert.NoError(t, err) + assert.Equal(t, crypto.AccountAuthenticatorSingleSender, output.Variant) + assert.True(t, output.Auth.Verify(message)) +} + func TestNewAccountFromSigner(t *testing.T) { message := []byte{0x12, 0x34} key, err := crypto.GenerateEd25519PrivateKey() @@ -43,24 +64,120 @@ func TestNewAccountFromSignerWithAddress(t *testing.T) { key, err := crypto.GenerateEd25519PrivateKey() assert.NoError(t, err) - authenticationKey := crypto.AuthenticationKey{} - - account, err := NewAccountFromSigner(key, authenticationKey) + account, err := NewAccountFromSigner(key, AccountZero) assert.NoError(t, err) output, err := account.Sign(message) assert.NoError(t, err) assert.Equal(t, crypto.AccountAuthenticatorEd25519, output.Variant) assert.True(t, output.Auth.Verify(message)) + outputSig, err := account.SignMessage(message) + assert.NoError(t, err) + assert.True(t, account.Signer.PubKey().Verify(message, outputSig)) + assert.Equal(t, AccountZero, account.Address) + assert.Equal(t, AccountZero, account.AccountAddress()) + assert.Equal(t, key.AuthKey(), account.AuthKey()) + assert.Equal(t, key.PubKey(), account.PubKey()) } func TestNewAccountFromSignerWithAddressMulti(t *testing.T) { key, err := crypto.GenerateEd25519PrivateKey() assert.NoError(t, err) - authenticationKey := crypto.AuthenticationKey{} + _, err = NewAccountFromSigner(key, AccountZero, AccountOne) + assert.Error(t, err) +} + +type WrapperSigner struct { + signer crypto.Signer +} + +func (w *WrapperSigner) Sign(_ []byte) (*crypto.AccountAuthenticator, error) { + return nil, errors.New("not implemented") +} +func (w *WrapperSigner) SignMessage(_ []byte) (crypto.Signature, error) { + return nil, errors.New("not implemented") +} +func (w *WrapperSigner) SimulationAuthenticator() *crypto.AccountAuthenticator { + return nil +} +func (w *WrapperSigner) AuthKey() *crypto.AuthenticationKey { + return &crypto.AuthenticationKey{} +} +func (w *WrapperSigner) PubKey() crypto.PublicKey { + // Note this is just for testing + return &crypto.Ed25519PublicKey{} +} + +func TestAccount_ExtractMessageSigner(t *testing.T) { + ed25519PrivateKey, err := crypto.GenerateEd25519PrivateKey() + assert.NoError(t, err) + ed25519Account, err := NewAccountFromSigner(ed25519PrivateKey) + assert.NoError(t, err) + + ed25519Out, ok := ed25519Account.ExtractMessageSigner() + assert.True(t, ok) + assert.Equal(t, ed25519PrivateKey, ed25519Out) + + ed25519SingleSignerAccount, err := NewAccountFromSigner(crypto.NewSingleSigner(ed25519PrivateKey)) + assert.NoError(t, err) + + ed25519Out, ok = ed25519SingleSignerAccount.ExtractMessageSigner() + assert.True(t, ok) + assert.Equal(t, ed25519PrivateKey, ed25519Out) + + secp256k1PrivateKey, err := crypto.GenerateSecp256k1Key() + assert.NoError(t, err) + secp256k1SingleSignerAccount, err := NewAccountFromSigner(crypto.NewSingleSigner(secp256k1PrivateKey)) + assert.NoError(t, err) + + secp256k1Out, ok := secp256k1SingleSignerAccount.ExtractMessageSigner() + assert.True(t, ok) + assert.Equal(t, secp256k1PrivateKey, secp256k1Out) + + wrapperSigner := &WrapperSigner{signer: secp256k1SingleSignerAccount} + customAccount, err := NewAccountFromSigner(wrapperSigner) + assert.NoError(t, err) + out, ok := customAccount.ExtractMessageSigner() + assert.False(t, ok) + assert.Nil(t, out) +} + +func TestAccount_ExtractPrivateKeyString(t *testing.T) { + ed25519PrivateKey, err := crypto.GenerateEd25519PrivateKey() + assert.NoError(t, err) + ed25519Account, err := NewAccountFromSigner(ed25519PrivateKey) + assert.NoError(t, err) - _, err = NewAccountFromSigner(key, authenticationKey, authenticationKey) + ed25519KeyString, err := ed25519Account.ExtractPrivateKeyString() + assert.NoError(t, err) + expectedEd25519String, err := ed25519PrivateKey.ToAIP80() + assert.NoError(t, err) + assert.Equal(t, expectedEd25519String, ed25519KeyString) + + ed25519SingleSignerAccount, err := NewAccountFromSigner(crypto.NewSingleSigner(ed25519PrivateKey)) + assert.NoError(t, err) + + ed25519SingleSignerKeyString, err := ed25519SingleSignerAccount.ExtractPrivateKeyString() + assert.NoError(t, err) + assert.Equal(t, expectedEd25519String, ed25519SingleSignerKeyString) + + secp256k1PrivateKey, err := crypto.GenerateSecp256k1Key() + assert.NoError(t, err) + secp256k1SingleSignerAccount, err := NewAccountFromSigner(crypto.NewSingleSigner(secp256k1PrivateKey)) + assert.NoError(t, err) + + expectedSecp256k1String, err := secp256k1PrivateKey.ToAIP80() + assert.NoError(t, err) + secp256k1SingleSignerKeyString, err := secp256k1SingleSignerAccount.ExtractPrivateKeyString() + assert.NoError(t, err) + assert.Equal(t, expectedSecp256k1String, secp256k1SingleSignerKeyString) + + wrapperSigner := &WrapperSigner{signer: secp256k1SingleSignerAccount} + customAccount, err := NewAccountFromSigner(wrapperSigner) + assert.NoError(t, err) + out, err := customAccount.ExtractPrivateKeyString() assert.Error(t, err) + assert.Empty(t, out) } diff --git a/internal/util/util.go b/internal/util/util.go index 7b016d8..0c8c406 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -2,7 +2,6 @@ package util import ( "encoding/hex" - "encoding/json" "fmt" "golang.org/x/crypto/sha3" "math/big" @@ -10,7 +9,7 @@ import ( "strings" ) -// SHA3256Hash hashes the input bytes using SHA3-256 +// Sha3256Hash hashes the input bytes using SHA3-256 func Sha3256Hash(bytes [][]byte) (output []byte) { hasher := sha3.New256() for _, b := range bytes { @@ -50,15 +49,3 @@ func StrToBigInt(val string) (num *big.Int, err error) { } return num, nil } - -// PrettyJson a simple pretty print for JSON examples -func PrettyJson(x any) string { - out := strings.Builder{} - enc := json.NewEncoder(&out) - enc.SetIndent("", " ") - err := enc.Encode(x) - if err != nil { - return "" - } - return out.String() -}