From 213404c20d60f3313c716cfc86a9042a9d8ef29a Mon Sep 17 00:00:00 2001 From: Greg Nazario Date: Thu, 2 May 2024 10:14:58 -0700 Subject: [PATCH 1/2] [crypto] Refactor crypto to be generic / pull ed25519 out of transaction logic --- account.go | 54 ++++++++++++++++++++++++++++++------- crypto.go | 27 +++++++++++++++++++ ed25519.go | 64 ++++++++++++++++++++++++++++++++++++++++++++ transactions.go | 24 +++++++---------- transactions_test.go | 3 +-- util.go | 3 +-- 6 files changed, 146 insertions(+), 29 deletions(-) create mode 100644 crypto.go create mode 100644 ed25519.go diff --git a/account.go b/account.go index a1e33c8..8161b5b 100644 --- a/account.go +++ b/account.go @@ -1,8 +1,6 @@ package aptos import ( - "crypto" - "crypto/ed25519" "crypto/rand" "encoding/hex" "errors" @@ -12,6 +10,17 @@ import ( "golang.org/x/crypto/sha3" ) +// Seeds for deriving addresses from addresses +const ( + Ed25519Scheme = uint8(0) + MultiEd25519Scheme = uint8(1) + SingleKeyScheme = uint8(2) + MultiKeyScheme = uint8(3) + deriveObjectScheme = uint8(252) + namedObjectScheme = uint8(254) + resourceAccountScheme = uint8(255) +) + type AccountAddress [32]byte // TODO: find nicer naming for this? Move account to a package so this can be account.ONE ? Wrap in a singleton struct for Account.One ? @@ -46,33 +55,58 @@ func (aa AccountAddress) String() string { func (aa AccountAddress) MarshalBCS(bcs *Serializer) { bcs.FixedBytes(aa[:]) } + func (aa *AccountAddress) UnmarshalBCS(bcs *Deserializer) { bcs.ReadFixedBytesInto((*aa)[:]) } + func (aa *AccountAddress) Random() { rand.Read((*aa)[:]) } -func (aa *AccountAddress) FromEd25519PubKey(pubkey ed25519.PublicKey) { - // TODO: Other SDK implementations have an internal AuthenticationKey type to wrap this. Maybe follow that pattern later? + +func (aa *AccountAddress) FromPublicKey(pubkey PublicKey) { hasher := sha3.New256() - hasher.Write(pubkey[:]) - hasher.Write([]byte{0}) + hasher.Write(pubkey.Bytes()) + hasher.Write([]byte{pubkey.Scheme()}) hasher.Sum((*aa)[:0]) } +func (aa *AccountAddress) NamedObjectAddress(seed []byte) (accountAddress AccountAddress) { + return aa.DerivedAddress(seed, namedObjectScheme) +} + +func (aa *AccountAddress) ObjectAddressFromObject(objectAddress *AccountAddress) (accountAddress AccountAddress) { + return aa.DerivedAddress(objectAddress[:], deriveObjectScheme) +} + +func (aa *AccountAddress) ResourceAccount(seed []byte) (accountAddress AccountAddress) { + return aa.DerivedAddress(seed, resourceAccountScheme) +} + +// DerivedAddress addresses are derived by the address, the seed, then the type byte +func (aa *AccountAddress) DerivedAddress(seed []byte, type_byte uint8) (accountAddress AccountAddress) { + accountAddress = AccountAddress{} + hasher := sha3.New256() + hasher.Write(aa[:]) + hasher.Write(seed[:]) + hasher.Write([]byte{type_byte}) + hasher.Sum(accountAddress[:]) + return +} + type Account struct { Address AccountAddress - PrivateKey crypto.PrivateKey + PrivateKey PrivateKey } func NewAccount() (*Account, error) { - pubkey, privkey, err := ed25519.GenerateKey(nil) + privkey, pubkey, err := GenerateEd5519Keys() if err != nil { return nil, err } out := &Account{} - out.Address.FromEd25519PubKey(pubkey) - out.PrivateKey = privkey + out.Address.FromPublicKey(pubkey) + out.PrivateKey = &privkey return out, nil } diff --git a/crypto.go b/crypto.go new file mode 100644 index 0000000..9b1a833 --- /dev/null +++ b/crypto.go @@ -0,0 +1,27 @@ +package aptos + +// Signer a generic interface for any kind of signing +type Signer interface { + Sign(msg []byte) (authenticator Authenticator, err error) +} + +// PrivateKey a generic interface for a signing private key +type PrivateKey interface { + Signer + + /// PubKey Retrieve the public key for signature verification + PubKey() PublicKey +} + +// PublicKey a generic interface for a public key associated with the private key +type PublicKey interface { + // BCSStruct The public key must be serializable or it will not be used in transactions + BCSStruct + + // Bytes the raw bytes for an authenticator + Bytes() []byte + // Scheme The scheme used for address derivation + Scheme() uint8 + + // TODO: add verify +} diff --git a/ed25519.go b/ed25519.go new file mode 100644 index 0000000..4b89ec4 --- /dev/null +++ b/ed25519.go @@ -0,0 +1,64 @@ +package aptos + +import ( + "crypto/ed25519" +) + +type Ed25519PrivateKey struct { + inner ed25519.PrivateKey +} + +func GenerateEd5519Keys() (privkey Ed25519PrivateKey, pubkey Ed25519PublicKey, err error) { + pub, priv, err := ed25519.GenerateKey(nil) + if err != nil { + return + } + privkey = Ed25519PrivateKey{priv} + pubkey = Ed25519PublicKey{pub} + return +} + +func (key *Ed25519PrivateKey) PubKey() PublicKey { + pubkey := key.inner.Public() + return Ed25519PublicKey{ + pubkey.(ed25519.PublicKey), + } +} + +func (key *Ed25519PrivateKey) Sign(msg []byte) (authenticator Authenticator, err error) { + publicKeyBytes := key.PubKey().Bytes() + signature := ed25519.Sign(key.inner, msg) + + auth := &Ed25519Authenticator{} + copy(auth.PublicKey[:], publicKeyBytes[:]) + copy(auth.Signature[:], signature[:]) // TODO: Signature type? + authenticator = Authenticator{ + AuthenticatorEd25519, + auth, + } + return +} + +type Ed25519PublicKey struct { + inner ed25519.PublicKey +} + +func (key Ed25519PublicKey) Bytes() []byte { + return key.inner[:] +} +func (key Ed25519PublicKey) Scheme() uint8 { + return Ed25519Scheme +} + +func (key Ed25519PublicKey) MarshalBCS(bcs *Serializer) { + bcs.FixedBytes(key.inner[:]) +} + +func (key Ed25519PublicKey) UnmarshalBCS(bcs *Deserializer) { + key = Ed25519PublicKey{} + bytes := bcs.ReadFixedBytes(32) + if bcs.Error() != nil { + return + } + copy(key.inner[:], bytes) +} diff --git a/transactions.go b/transactions.go index 35ec228..dcf4385 100644 --- a/transactions.go +++ b/transactions.go @@ -1,7 +1,6 @@ package aptos import ( - "crypto/ed25519" "errors" "fmt" "math/big" @@ -31,6 +30,7 @@ func (txn *RawTransaction) MarshalBCS(bcs *Serializer) { bcs.U64(txn.ExpirationTimetampSeconds) bcs.U8(txn.ChainId) } + func (txn *RawTransaction) UnmarshalBCS(bcs *Deserializer) { txn.Sender.UnmarshalBCS(bcs) txn.SequenceNumber = bcs.U64() @@ -40,6 +40,7 @@ func (txn *RawTransaction) UnmarshalBCS(bcs *Deserializer) { txn.ExpirationTimetampSeconds = bcs.U64() txn.ChainId = bcs.U8() } + func (txn *RawTransaction) SignableBytes() (signableBytes []byte, err error) { ser := Serializer{} txn.MarshalBCS(&ser) @@ -54,27 +55,20 @@ func (txn *RawTransaction) SignableBytes() (signableBytes []byte, err error) { copy(signableBytes[len(prehash):], txnbytes) return signableBytes, nil } -func (txn *RawTransaction) SignEd25519(privateKey ed25519.PrivateKey) (stxn *SignedTransaction, err error) { + +func (txn *RawTransaction) Sign(privateKey PrivateKey) (stxn *SignedTransaction, err error) { signableBytes, err := txn.SignableBytes() if err != nil { return } - signature := ed25519.Sign(privateKey, signableBytes) - eauth := &Ed25519Authenticator{} - pubkey := privateKey.Public() - if pkb, ok := pubkey.(ed25519.PublicKey); ok { - copy(eauth.PublicKey[:], pkb[:]) - } else { - panic(fmt.Sprintf("could not get bytes from pubkey: %T %#v", pubkey, pubkey)) - } - copy(eauth.Signature[:], signature) - aa := Authenticator{ - Kind: AuthenticatorEd25519, - Auth: eauth, + authenticator, err := privateKey.Sign(signableBytes) + if err != nil { + return } + stxn = &SignedTransaction{ Transaction: *txn, - Authenticator: aa, + Authenticator: authenticator, } return } diff --git a/transactions_test.go b/transactions_test.go index dbb5203..4a93cbf 100644 --- a/transactions_test.go +++ b/transactions_test.go @@ -1,7 +1,6 @@ package aptos import ( - "crypto/ed25519" "encoding/binary" "testing" @@ -40,7 +39,7 @@ func TestRawTransactionSign(t *testing.T) { ChainId: 4, } - stxn, err := txn.SignEd25519(sender.PrivateKey.(ed25519.PrivateKey)) + stxn, err := txn.Sign(sender.PrivateKey) assert.NoError(t, err) _, ok := stxn.Authenticator.Auth.(*Ed25519Authenticator) diff --git a/util.go b/util.go index 9409795..cc44015 100644 --- a/util.go +++ b/util.go @@ -1,7 +1,6 @@ package aptos import ( - "crypto/ed25519" "encoding/binary" "time" ) @@ -51,6 +50,6 @@ func APTTransferTransaction(client *Client, sender *Account, dest AccountAddress ChainId: chainId, } - stxn, err = txn.SignEd25519(sender.PrivateKey.(ed25519.PrivateKey)) + stxn, err = txn.Sign(sender.PrivateKey) return stxn, err } From 56bc536e9a87f6395927c941e38d7539cf057f0c Mon Sep 17 00:00:00 2001 From: Greg Nazario Date: Thu, 2 May 2024 10:37:05 -0700 Subject: [PATCH 2/2] [crypto] Fix go client with bytes function --- cmd/goclient/goclient.go | 5 ++--- crypto.go | 2 ++ ed25519.go | 4 ++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/cmd/goclient/goclient.go b/cmd/goclient/goclient.go index a5ac324..26d1d1a 100644 --- a/cmd/goclient/goclient.go +++ b/cmd/goclient/goclient.go @@ -1,7 +1,6 @@ package main import ( - "crypto/ed25519" "encoding/binary" "encoding/hex" "encoding/json" @@ -213,14 +212,14 @@ func main() { amount := uint64(200_000_000) err = client.Fund(alice.Address, amount) maybefail(err, "faucet err: %s", err) - fmt.Fprintf(os.Stdout, "new account %s funded for %d, privkey = %s\n", alice.Address.String(), amount, hex.EncodeToString(alice.PrivateKey.(ed25519.PrivateKey)[:])) + fmt.Fprintf(os.Stdout, "new account %s funded for %d, privkey = %s\n", alice.Address.String(), amount, hex.EncodeToString(alice.PrivateKey.Bytes())) bob, err := aptos.NewAccount() maybefail(err, "new account: %s", err) //amount = uint64(10_000_000) err = client.Fund(bob.Address, amount) maybefail(err, "faucet err: %s", err) - fmt.Fprintf(os.Stdout, "new account %s funded for %d, privkey = %s\n", bob.Address.String(), amount, hex.EncodeToString(bob.PrivateKey.(ed25519.PrivateKey)[:])) + fmt.Fprintf(os.Stdout, "new account %s funded for %d, privkey = %s\n", bob.Address.String(), amount, hex.EncodeToString(bob.PrivateKey.Bytes())) time.Sleep(2 * time.Second) stxn, err := aptos.APTTransferTransaction(client, alice, bob.Address, 42) diff --git a/crypto.go b/crypto.go index 9b1a833..de6f4f7 100644 --- a/crypto.go +++ b/crypto.go @@ -11,6 +11,8 @@ type PrivateKey interface { /// PubKey Retrieve the public key for signature verification PubKey() PublicKey + + Bytes() []byte } // PublicKey a generic interface for a public key associated with the private key diff --git a/ed25519.go b/ed25519.go index 4b89ec4..1f241d3 100644 --- a/ed25519.go +++ b/ed25519.go @@ -25,6 +25,10 @@ func (key *Ed25519PrivateKey) PubKey() PublicKey { } } +func (key *Ed25519PrivateKey) Bytes() []byte { + return key.inner[:] +} + func (key *Ed25519PrivateKey) Sign(msg []byte) (authenticator Authenticator, err error) { publicKeyBytes := key.PubKey().Bytes() signature := ed25519.Sign(key.inner, msg)