From 576bae528e1e2964e35b9b14fa795aa7d01e4cdb Mon Sep 17 00:00:00 2001 From: hirama Date: Fri, 27 Sep 2024 16:44:59 -0300 Subject: [PATCH 1/7] verify signature --- account.go | 87 +++++++++++++------------ transaction.go | 134 +++++--------------------------------- transaction_test.go | 12 +++- types/crypto.go | 126 +++++++++++++++++++++++++++++++++++ utils/key_pair.go | 18 ++--- utils/verify_signature.go | 44 +++++++++++++ 6 files changed, 247 insertions(+), 174 deletions(-) create mode 100644 types/crypto.go create mode 100644 utils/verify_signature.go diff --git a/account.go b/account.go index ba761a1..a0fc634 100644 --- a/account.go +++ b/account.go @@ -10,6 +10,7 @@ import ( "sync" "github.com/aurora-is-near/near-api-go/keystore" + "github.com/aurora-is-near/near-api-go/types" "github.com/aurora-is-near/near-api-go/utils" "github.com/btcsuite/btcd/btcutil/base58" "github.com/near/borsh-go" @@ -126,10 +127,10 @@ func (a *Account) SendMoney( receiverID string, amount big.Int, ) (map[string]interface{}, error) { - return a.SignAndSendTransaction(receiverID, []Action{ + return a.SignAndSendTransaction(receiverID, []types.Action{ { Enum: 3, - Transfer: Transfer{ + Transfer: types.Transfer{ Deposit: amount, }, }, @@ -138,15 +139,15 @@ func (a *Account) SendMoney( // AddKeys adds the given publicKeys to the account with full access. func (a *Account) AddKeys( - publicKeys ...utils.PublicKey, + publicKeys ...types.PublicKey, ) (map[string]interface{}, error) { fullAccessKey := fullAccessKey() - actions := make([]Action, 0) + actions := make([]types.Action, 0) for _, pk := range publicKeys { - actions = append(actions, Action{ + actions = append(actions, types.Action{ Enum: 5, - AddKey: AddKey{ - PublicKey: pk, + AddKey: types.AddKey{ + PublicKey: types.PublicKey(pk), AccessKey: fullAccessKey, }, }) @@ -156,14 +157,14 @@ func (a *Account) AddKeys( // DeleteKeys deletes the given publicKeys from the account. func (a *Account) DeleteKeys( - publicKeys ...utils.PublicKey, + publicKeys ...types.PublicKey, ) (map[string]interface{}, error) { - actions := make([]Action, 0) + actions := make([]types.Action, 0) for _, pk := range publicKeys { - actions = append(actions, Action{ + actions = append(actions, types.Action{ Enum: 6, - DeleteKey: DeleteKey{ - PublicKey: pk, + DeleteKey: types.DeleteKey{ + PublicKey: types.PublicKey(pk), }, }) } @@ -173,24 +174,24 @@ func (a *Account) DeleteKeys( // CreateAccount creates the newAccountID with the given publicKey and amount. func (a *Account) CreateAccount( newAccountID string, - publicKey utils.PublicKey, + publicKey types.PublicKey, amount big.Int, ) (map[string]interface{}, error) { - return a.SignAndSendTransaction(newAccountID, []Action{ + return a.SignAndSendTransaction(newAccountID, []types.Action{ { Enum: 0, CreateAccount: 0, }, { Enum: 3, - Transfer: Transfer{ + Transfer: types.Transfer{ Deposit: amount, }, }, { Enum: 5, - AddKey: AddKey{ - PublicKey: publicKey, + AddKey: types.AddKey{ + PublicKey: types.PublicKey(publicKey), AccessKey: fullAccessKey(), }, }, @@ -202,10 +203,10 @@ func (a *Account) CreateAccount( func (a *Account) DeleteAccount( beneficiaryID string, ) (map[string]interface{}, error) { - return a.SignAndSendTransaction(a.fullAccessKeyPair.AccountID, []Action{ + return a.SignAndSendTransaction(a.fullAccessKeyPair.AccountID, []types.Action{ { Enum: 7, - DeleteAccount: DeleteAccount{ + DeleteAccount: types.DeleteAccount{ BeneficiaryID: beneficiaryID, }, }, @@ -215,7 +216,7 @@ func (a *Account) DeleteAccount( // SignAndSendTransaction signs the given actions and sends them as a transaction to receiverID. func (a *Account) SignAndSendTransaction( receiverID string, - actions []Action, + actions []types.Action, ) (map[string]interface{}, error) { buf, err := utils.ExponentialBackoff(txNonceRetryWait, txNonceRetryNumber, txNonceRetryWaitBackoff, func() ([]byte, error) { @@ -241,7 +242,7 @@ func (a *Account) SignAndSendTransaction( func (a *Account) SignAndSendTransactionWithKey( receiverID string, publicKey string, - actions []Action, + actions []types.Action, ) (map[string]interface{}, error) { buf, err := utils.ExponentialBackoff(txNonceRetryWait, txNonceRetryNumber, txNonceRetryWaitBackoff, func() ([]byte, error) { @@ -268,7 +269,7 @@ func (a *Account) SignAndSendTransactionWithKeyAndNonce( receiverID string, publicKey string, nonce uint64, - actions []Action, + actions []types.Action, ) (map[string]interface{}, error) { buf, err := utils.ExponentialBackoff(txNonceRetryWait, txNonceRetryNumber, txNonceRetryWaitBackoff, func() ([]byte, error) { @@ -291,7 +292,7 @@ func (a *Account) SignAndSendTransactionWithKeyAndNonce( // SignAndSendTransactionAsync signs the given actions and sends it immediately func (a *Account) SignAndSendTransactionAsync( receiverID string, - actions []Action, + actions []types.Action, ) (string, error) { _, signedTx, err := a.signTransaction(receiverID, actions) if err != nil { @@ -309,7 +310,7 @@ func (a *Account) SignAndSendTransactionAsync( func (a *Account) SignAndSendTransactionAsyncWithKey( receiverID string, publicKey string, - actions []Action, + actions []types.Action, ) (string, error) { _, signedTx, err := a.signTransactionWithKey(receiverID, publicKey, actions) if err != nil { @@ -325,8 +326,8 @@ func (a *Account) SignAndSendTransactionAsyncWithKey( func (a *Account) signTransaction( receiverID string, - actions []Action, -) (txHash []byte, signedTx *SignedTransaction, err error) { + actions []types.Action, +) (txHash []byte, signedTx *types.SignedTransaction, err error) { _, ak, err := a.findAccessKey() if err != nil { return nil, nil, err @@ -362,8 +363,8 @@ func (a *Account) signTransaction( func (a *Account) signTransactionWithKey( receiverID string, publicKey string, - actions []Action, -) ([]byte, *SignedTransaction, error) { + actions []types.Action, +) ([]byte, *types.SignedTransaction, error) { ak, err := a.findAccessKeyWithPublicKey(publicKey) if err != nil { @@ -401,8 +402,8 @@ func (a *Account) signTransactionWithKeyAndNonce( receiverID string, publicKey string, nonce uint64, - actions []Action, -) ([]byte, *SignedTransaction, error) { + actions []types.Action, +) ([]byte, *types.SignedTransaction, error) { // get current block hash block, err := a.conn.Block() @@ -453,9 +454,9 @@ func (a *Account) FunctionCall( gas uint64, amount big.Int, ) (map[string]interface{}, error) { - return a.SignAndSendTransaction(contractID, []Action{{ + return a.SignAndSendTransaction(contractID, []types.Action{{ Enum: 2, - FunctionCall: FunctionCall{ + FunctionCall: types.FunctionCall{ MethodName: methodName, Args: args, Gas: gas, @@ -474,11 +475,11 @@ func (a *Account) FunctionCallWithMultiActionAndKey( amount big.Int, ) (map[string]interface{}, error) { - actions := make([]Action, 0) + actions := make([]types.Action, 0) for _, args := range argsSlice { - actions = append(actions, Action{ + actions = append(actions, types.Action{ Enum: 2, - FunctionCall: FunctionCall{ + FunctionCall: types.FunctionCall{ MethodName: methodName, Args: args, Gas: gas, @@ -501,11 +502,11 @@ func (a *Account) FunctionCallWithMultiActionAndKeyAndNonce( amount big.Int, ) (map[string]interface{}, error) { - actions := make([]Action, 0) + actions := make([]types.Action, 0) for _, args := range argsSlice { - actions = append(actions, Action{ + actions = append(actions, types.Action{ Enum: 2, - FunctionCall: FunctionCall{ + FunctionCall: types.FunctionCall{ MethodName: methodName, Args: args, Gas: gas, @@ -523,9 +524,9 @@ func (a *Account) FunctionCallAsync( gas uint64, amount big.Int, ) (string, error) { - return a.SignAndSendTransactionAsync(contractID, []Action{{ + return a.SignAndSendTransactionAsync(contractID, []types.Action{{ Enum: 2, - FunctionCall: FunctionCall{ + FunctionCall: types.FunctionCall{ MethodName: methodName, Args: args, Gas: gas, @@ -543,11 +544,11 @@ func (a *Account) FunctionCallAsyncWithMultiActionAndKey( gas uint64, amount big.Int, ) (string, error) { - actions := make([]Action, 0) + actions := make([]types.Action, 0) for _, args := range argsSlice { - actions = append(actions, Action{ + actions = append(actions, types.Action{ Enum: 2, - FunctionCall: FunctionCall{ + FunctionCall: types.FunctionCall{ MethodName: methodName, Args: args, Gas: gas, diff --git a/transaction.go b/transaction.go index 7b61122..aadf1f5 100644 --- a/transaction.go +++ b/transaction.go @@ -5,129 +5,32 @@ import ( "crypto/ed25519" "crypto/rand" "crypto/sha256" - "math/big" - "github.com/aurora-is-near/near-api-go/utils" + "github.com/aurora-is-near/near-api-go/types" "github.com/near/borsh-go" ) -// AccessKey encodes a NEAR access key. -type AccessKey struct { - Nonce uint64 - Permission AccessKeyPermission -} - -// AccessKeyPermission encodes a NEAR access key permission. -type AccessKeyPermission struct { - Enum borsh.Enum `borsh_enum:"true"` // treat struct as complex enum when serializing/deserializing - FunctionCall FunctionCallPermission - FullAccess borsh.Enum -} - -// FunctionCallPermission encodes a NEAR function call permission (an access -// key permission). -type FunctionCallPermission struct { - Allowance *big.Int - ReceiverId string - MethodNames []string -} - -func fullAccessKey() AccessKey { - return AccessKey{ +func fullAccessKey() types.AccessKey { + return types.AccessKey{ Nonce: 0, - Permission: AccessKeyPermission{ + Permission: types.AccessKeyPermission{ Enum: 1, FullAccess: 1, }, } } -// A Transaction encodes a NEAR transaction. -type Transaction struct { - SignerID string - PublicKey utils.PublicKey - Nonce uint64 - ReceiverID string - BlockHash [32]byte - Actions []Action -} - -// Action simulates an enum for Borsh encoding. -type Action struct { - Enum borsh.Enum `borsh_enum:"true"` // treat struct as complex enum when serializing/deserializing - CreateAccount borsh.Enum - DeployContract DeployContract - FunctionCall FunctionCall - Transfer Transfer - Stake Stake - AddKey AddKey - DeleteKey DeleteKey - DeleteAccount DeleteAccount -} - -// The DeployContract action. -type DeployContract struct { - Code []byte -} - -// The FunctionCall action. -type FunctionCall struct { - MethodName string - Args []byte - Gas uint64 - Deposit big.Int // u128 -} - -// The Transfer action. -type Transfer struct { - Deposit big.Int // u128 -} - -// The Stake action. -type Stake struct { - Stake big.Int // u128 - PublicKey utils.PublicKey -} - -// The AddKey action. -type AddKey struct { - PublicKey utils.PublicKey - AccessKey AccessKey -} - -// The DeleteKey action. -type DeleteKey struct { - PublicKey utils.PublicKey -} - -// The DeleteAccount action. -type DeleteAccount struct { - BeneficiaryID string -} - -// A Signature used for signing transaction. -type Signature struct { - KeyType uint8 - Data [64]byte -} - -// SignedTransaction encodes signed transactions for NEAR. -type SignedTransaction struct { - Transaction Transaction - Signature Signature -} - func createTransaction( signerID string, - publicKey utils.PublicKey, + publicKey ed25519.PublicKey, receiverID string, nonce uint64, blockHash []byte, - actions []Action, -) *Transaction { - var tx Transaction + actions []types.Action, +) *types.Transaction { + var tx types.Transaction tx.SignerID = signerID - tx.PublicKey = publicKey + tx.PublicKey.FromEd25519(publicKey) tx.ReceiverID = receiverID tx.Nonce = nonce copy(tx.BlockHash[:], blockHash) @@ -136,10 +39,9 @@ func createTransaction( } func signTransactionObject( - tx *Transaction, + tx *types.Transaction, privKey ed25519.PrivateKey, - accountID string, -) (txHash []byte, signedTx *SignedTransaction, err error) { +) (txHash []byte, signedTx *types.SignedTransaction, err error) { buf, err := borsh.Serialize(*tx) if err != nil { return nil, nil, err @@ -152,11 +54,11 @@ func signTransactionObject( return nil, nil, err } - var signature Signature - signature.KeyType = utils.ED25519 + var signature types.Signature + signature.KeyType = types.ED25519 copy(signature.Data[:], sig) - var stx SignedTransaction + var stx types.SignedTransaction stx.Transaction = *tx stx.Signature = signature @@ -166,18 +68,18 @@ func signTransactionObject( func signTransaction( receiverID string, nonce uint64, - actions []Action, + actions []types.Action, blockHash []byte, publicKey ed25519.PublicKey, privKey ed25519.PrivateKey, accountID string, -) (txHash []byte, signedTx *SignedTransaction, err error) { +) (txHash []byte, signedTx *types.SignedTransaction, err error) { // create transaction - tx := createTransaction(accountID, utils.PublicKeyFromEd25519(publicKey), + tx := createTransaction(accountID, publicKey, receiverID, nonce, blockHash, actions) // sign transaction object - txHash, signedTx, err = signTransactionObject(tx, privKey, accountID) + txHash, signedTx, err = signTransactionObject(tx, privKey) if err != nil { return nil, nil, err } diff --git a/transaction_test.go b/transaction_test.go index 46ddd6f..2352a5c 100644 --- a/transaction_test.go +++ b/transaction_test.go @@ -4,6 +4,8 @@ import ( "encoding/hex" "testing" + "github.com/aurora-is-near/near-api-go/types" + "github.com/aurora-is-near/near-api-go/utils" "github.com/near/borsh-go" ) @@ -14,8 +16,16 @@ func TestSignedTransactionStruct(t *testing.T) { if err != nil { t.Fatal(err) } - var signedTx SignedTransaction + var signedTx types.SignedTransaction if err = borsh.Deserialize(&signedTx, buf); err != nil { + t.Fatalf("Deserialization error: %v", err) + } + + ok, err := utils.VerifyTransactionSignature(&signedTx) + if err != nil { t.Fatal(err) } + if !ok { + t.Fatal("signature is not valid") + } } diff --git a/types/crypto.go b/types/crypto.go new file mode 100644 index 0000000..3f54a6b --- /dev/null +++ b/types/crypto.go @@ -0,0 +1,126 @@ +package types + +import ( + "crypto/ed25519" + "math/big" + + "github.com/near/borsh-go" +) + +// All supported key types +const ( + ED25519 = 0 +) + +// PublicKey encoding for NEAR. +type PublicKey struct { + KeyType uint8 + Data [32]byte +} + + +func (pk *PublicKey) FromEd25519(edPk ed25519.PublicKey) { + pk.KeyType = ED25519 + copy(pk.Data[:], edPk) +} + + +func (pk *PublicKey) ToEd25519() ed25519.PublicKey { + return ed25519.PublicKey(pk.Data[:]) +} + +// AccessKey encodes a NEAR access key. +type AccessKey struct { + Nonce uint64 + Permission AccessKeyPermission +} + +// AccessKeyPermission encodes a NEAR access key permission. +type AccessKeyPermission struct { + Enum borsh.Enum `borsh_enum:"true"` // treat struct as complex enum when serializing/deserializing + FunctionCall FunctionCallPermission + FullAccess borsh.Enum +} + +// FunctionCallPermission encodes a NEAR function call permission (an access +// key permission). +type FunctionCallPermission struct { + Allowance *big.Int + ReceiverId string + MethodNames []string +} + +// A Transaction encodes a NEAR transaction. +type Transaction struct { + SignerID string + PublicKey PublicKey + Nonce uint64 + ReceiverID string + BlockHash [32]byte + Actions []Action +} + +// Action simulates an enum for Borsh encoding. +type Action struct { + Enum borsh.Enum `borsh_enum:"true"` // treat struct as complex enum when serializing/deserializing + CreateAccount borsh.Enum + DeployContract DeployContract + FunctionCall FunctionCall + Transfer Transfer + Stake Stake + AddKey AddKey + DeleteKey DeleteKey + DeleteAccount DeleteAccount +} + +// The DeployContract action. +type DeployContract struct { + Code []byte +} + +// The FunctionCall action. +type FunctionCall struct { + MethodName string + Args []byte + Gas uint64 + Deposit big.Int // u128 +} + +// The Transfer action. +type Transfer struct { + Deposit big.Int // u128 +} + +// The Stake action. +type Stake struct { + Stake big.Int // u128 + PublicKey PublicKey +} + +// The AddKey action. +type AddKey struct { + PublicKey PublicKey + AccessKey AccessKey +} + +// The DeleteKey action. +type DeleteKey struct { + PublicKey PublicKey +} + +// The DeleteAccount action. +type DeleteAccount struct { + BeneficiaryID string +} + +// A Signature used for signing transaction. +type Signature struct { + KeyType uint8 + Data [64]byte +} + +// SignedTransaction encodes signed transactions for NEAR. +type SignedTransaction struct { + Transaction Transaction + Signature Signature +} diff --git a/utils/key_pair.go b/utils/key_pair.go index abb346c..cd56217 100644 --- a/utils/key_pair.go +++ b/utils/key_pair.go @@ -5,24 +5,14 @@ import ( "fmt" "strings" + "github.com/aurora-is-near/near-api-go/types" "github.com/btcsuite/btcd/btcutil/base58" ) -// All supported key types -const ( - ED25519 = 0 -) - -// PublicKey encoding for NEAR. -type PublicKey struct { - KeyType uint8 - Data [32]byte -} - // PublicKeyFromEd25519 derives a public key in NEAR encoding from pk. -func PublicKeyFromEd25519(pk ed25519.PublicKey) PublicKey { - var pubKey PublicKey - pubKey.KeyType = ED25519 +func PublicKeyFromEd25519(pk ed25519.PublicKey) types.PublicKey { + var pubKey types.PublicKey + pubKey.KeyType = types.ED25519 copy(pubKey.Data[:], pk) return pubKey } diff --git a/utils/verify_signature.go b/utils/verify_signature.go new file mode 100644 index 0000000..108aad6 --- /dev/null +++ b/utils/verify_signature.go @@ -0,0 +1,44 @@ +package utils + +import ( + "crypto/ed25519" + "crypto/sha256" + "errors" + + "github.com/aurora-is-near/near-api-go/types" + "github.com/near/borsh-go" +) + +// VerifyTransactionSignature verifies the signature of a given signed transaction. +// It returns true if the signature is valid, false otherwise. +func VerifyTransactionSignature(signedTx *types.SignedTransaction) (bool, error) { + // Serialize the unsigned transaction + unsignedTx := signedTx.Transaction + serializedTx, err := borsh.Serialize(unsignedTx) + if err != nil { + return false, err + } + + // Compute the hash of the serialized transaction + txHash := sha256.Sum256(serializedTx) + + // Extract the public key from the transaction + publicKeyData := unsignedTx.PublicKey.Data[:] + if unsignedTx.PublicKey.KeyType != types.ED25519 { + return false, errors.New("unsupported key type") + } + + // Extract the signature from the signed transaction + signatureData := signedTx.Signature.Data[:] + if signedTx.Signature.KeyType != types.ED25519 { + return false, errors.New("unsupported signature key type") + } + + // Verify the signature + isValid := ed25519.Verify(publicKeyData, txHash[:], signatureData) + if !isValid { + return false, errors.New("invalid signature") + } + + return true, nil +} From b0902bca077c0dd3f7789f8a84f5f3d2ff7096f5 Mon Sep 17 00:00:00 2001 From: hirama Date: Fri, 27 Sep 2024 16:51:00 -0300 Subject: [PATCH 2/7] refactor --- transaction_test.go | 3 +-- types/crypto.go | 10 +++++----- utils/verify_signature.go => verify_signature.go | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) rename utils/verify_signature.go => verify_signature.go (98%) diff --git a/transaction_test.go b/transaction_test.go index 2352a5c..673e1c8 100644 --- a/transaction_test.go +++ b/transaction_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/aurora-is-near/near-api-go/types" - "github.com/aurora-is-near/near-api-go/utils" "github.com/near/borsh-go" ) @@ -21,7 +20,7 @@ func TestSignedTransactionStruct(t *testing.T) { t.Fatalf("Deserialization error: %v", err) } - ok, err := utils.VerifyTransactionSignature(&signedTx) + ok, err := VerifyTransactionSignature(&signedTx) if err != nil { t.Fatal(err) } diff --git a/types/crypto.go b/types/crypto.go index 3f54a6b..7d851c2 100644 --- a/types/crypto.go +++ b/types/crypto.go @@ -19,14 +19,14 @@ type PublicKey struct { } -func (pk *PublicKey) FromEd25519(edPk ed25519.PublicKey) { - pk.KeyType = ED25519 - copy(pk.Data[:], edPk) +func (pubKey *PublicKey) FromEd25519(edPk ed25519.PublicKey) { + pubKey.KeyType = ED25519 + copy(pubKey.Data[:], edPk) } -func (pk *PublicKey) ToEd25519() ed25519.PublicKey { - return ed25519.PublicKey(pk.Data[:]) +func (pubKey *PublicKey) ToEd25519() ed25519.PublicKey { + return ed25519.PublicKey(pubKey.Data[:]) } // AccessKey encodes a NEAR access key. diff --git a/utils/verify_signature.go b/verify_signature.go similarity index 98% rename from utils/verify_signature.go rename to verify_signature.go index 108aad6..e1e97a9 100644 --- a/utils/verify_signature.go +++ b/verify_signature.go @@ -1,4 +1,4 @@ -package utils +package near import ( "crypto/ed25519" From 647327a9cacf83766bb468ebf79a9270cc826e66 Mon Sep 17 00:00:00 2001 From: hirama Date: Fri, 27 Sep 2024 17:10:43 -0300 Subject: [PATCH 3/7] two more utility methods --- verify_signature.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/verify_signature.go b/verify_signature.go index e1e97a9..e80cadb 100644 --- a/verify_signature.go +++ b/verify_signature.go @@ -3,6 +3,7 @@ package near import ( "crypto/ed25519" "crypto/sha256" + "encoding/hex" "errors" "github.com/aurora-is-near/near-api-go/types" @@ -42,3 +43,30 @@ func VerifyTransactionSignature(signedTx *types.SignedTransaction) (bool, error) return true, nil } + +// Verify signature of a given public key and signature +func VerifySignatureBytes(publicKey []byte, signature []byte, message []byte) (bool, error) { + pubKey := types.PublicKey{} + pubKey.FromEd25519(publicKey) + + signatureData := signature[:] + if len(signatureData) != ed25519.SignatureSize { + return false, errors.New("invalid signature size") + } + + hash := sha256.Sum256(message) + return ed25519.Verify(pubKey.ToEd25519(), hash[:], signatureData), nil +} + +// Verify signature of a given public key and signature as a hex string +func VerifySignatureHex(publicKeyHex string, signatureHex string, message []byte) (bool, error) { + publicKey, err := hex.DecodeString(publicKeyHex) + if err != nil { + return false, err + } + signature, err := hex.DecodeString(signatureHex) + if err != nil { + return false, err + } + return VerifySignatureBytes(publicKey, signature, message) +} From 92c4f9fc95f5dce2528b382c081a7d62001c3624 Mon Sep 17 00:00:00 2001 From: hirama Date: Fri, 27 Sep 2024 18:34:56 -0300 Subject: [PATCH 4/7] added test --- verify_signature.go | 6 +++- verify_signature_test.go | 69 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 verify_signature_test.go diff --git a/verify_signature.go b/verify_signature.go index e80cadb..ce992d2 100644 --- a/verify_signature.go +++ b/verify_signature.go @@ -59,7 +59,7 @@ func VerifySignatureBytes(publicKey []byte, signature []byte, message []byte) (b } // Verify signature of a given public key and signature as a hex string -func VerifySignatureHex(publicKeyHex string, signatureHex string, message []byte) (bool, error) { +func VerifySignatureHex(publicKeyHex string, signatureHex string, messageHex string) (bool, error) { publicKey, err := hex.DecodeString(publicKeyHex) if err != nil { return false, err @@ -68,5 +68,9 @@ func VerifySignatureHex(publicKeyHex string, signatureHex string, message []byte if err != nil { return false, err } + message, err := hex.DecodeString(messageHex) + if err != nil { + return false, err + } return VerifySignatureBytes(publicKey, signature, message) } diff --git a/verify_signature_test.go b/verify_signature_test.go new file mode 100644 index 0000000..3ce009a --- /dev/null +++ b/verify_signature_test.go @@ -0,0 +1,69 @@ +package near + +import ( + "crypto/ed25519" + "crypto/rand" + "crypto/sha256" + "encoding/hex" + "testing" +) + +func TestVerifySignature(t *testing.T) { + // Generate a new key pair + pubKey, privKey, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatalf("Failed to generate key pair: %v", err) + } + + // Create a message to sign + message := []byte("test message") + messageHex := hex.EncodeToString(message) + + // Hash the message + hash := sha256.Sum256(message) + + // Sign the hashed message + signature := ed25519.Sign(privKey, hash[:]) + signatureHex := hex.EncodeToString(signature) + + // Verify the signature + isValid, err := VerifySignatureBytes(pubKey, signature, message) + if err != nil { + t.Fatalf("Failed to verify signature: %v", err) + } + if !isValid { + t.Fatal("Signature is not valid") + } + + // Test invariant: Verify that the signature is invalid for a different message + otherMessage := []byte("different message") + isValid, err = VerifySignatureBytes(pubKey, signature, otherMessage) + if err != nil { + t.Fatalf("Failed to verify signature: %v", err) + } + if isValid { + t.Fatal("Signature should not be valid for a different message") + } + + // Test invariant: Verify that the signature is invalid for a different public key + otherPubKey, _, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatalf("Failed to generate key pair: %v", err) + } + isValid, err = VerifySignatureBytes(otherPubKey, signature, message) + if err != nil { + t.Fatalf("Failed to verify signature: %v", err) + } + if isValid { + t.Fatal("Signature should not be valid for a different public key") + } + + // Test VerifySignatureHex + isValid, err = VerifySignatureHex(hex.EncodeToString(pubKey), signatureHex, messageHex) + if err != nil { + t.Fatalf("Failed to verify signature: %v", err) + } + if !isValid { + t.Fatal("Signature should be valid for a different public key") + } +} From 4ca6c8aed89141a808f89f1c36c312327ade7208 Mon Sep 17 00:00:00 2001 From: hirama Date: Fri, 27 Sep 2024 20:09:29 -0300 Subject: [PATCH 5/7] added test for precomputed data --- verify_signature_test.go | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/verify_signature_test.go b/verify_signature_test.go index 3ce009a..d524356 100644 --- a/verify_signature_test.go +++ b/verify_signature_test.go @@ -64,6 +64,24 @@ func TestVerifySignature(t *testing.T) { t.Fatalf("Failed to verify signature: %v", err) } if !isValid { - t.Fatal("Signature should be valid for a different public key") + t.Fatal("Signature should be valid for the given public key") + } +} + +func TestVerifySignaturePrecomputed(t *testing.T) { + message := "+16005551111evm.test-account.testnet" + pubKey := "86fd8e75c00c88ad489b3c0c7dd8d13ef0953d4b03788acb05b281b2acd2bf86" + signature := "a45ea2a6999a69b35a8a9fc3eb60f2440c1d4f7a45641eb81f309a27dbd0342258159d99cd4f322deec8f028214e72072480c9d145b25d5f0f0f3df8f8f9c30b" + + // Convert message to hex + messageHex := hex.EncodeToString([]byte(message)) + + // Verify the signature + isValid, err := VerifySignatureHex(pubKey, signature, messageHex) + if err != nil { + t.Fatalf("Failed to verify signature: %v", err) + } + if !isValid { + t.Fatal("Signature should be valid for the given public key") } } From 8f46be4eff0e0ba7c8354d18e7e781dd375d6bef Mon Sep 17 00:00:00 2001 From: hirama Date: Tue, 8 Oct 2024 11:27:06 -0300 Subject: [PATCH 6/7] VerifySignatureBase58 --- utils/utils.go | 20 ++++++++++++++++++++ verify_signature.go | 31 +++++++++++++++++++++++++++++++ verify_signature_test.go | 20 +++++++++++++++++++- 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/utils/utils.go b/utils/utils.go index ccc4c44..2da43a0 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1,2 +1,22 @@ // Package utils implements helper functions for the Go NEAR API. package utils + +import ( + "crypto/ed25519" + "fmt" + "strings" + + "github.com/btcsuite/btcd/btcutil/base58" +) + +// Ed25519SignatureFromString derives an ed25519 signature from its base58 string representation prefixed with 'ed25519:'. +func Ed25519SignatureFromString(ed25519Signature string) ([]byte, error) { + if !strings.HasPrefix(ed25519Signature, ed25519Prefix) { + return nil, fmt.Errorf("'%s' is not an Ed25519 signature", ed25519Signature) + } + signatureBytes := base58.Decode(strings.TrimPrefix(ed25519Signature, ed25519Prefix)) + if len(signatureBytes) != ed25519.SignatureSize { + return nil, fmt.Errorf("unexpected byte length for signature '%s'", ed25519Signature) + } + return signatureBytes, nil +} diff --git a/verify_signature.go b/verify_signature.go index ce992d2..6d9aa2b 100644 --- a/verify_signature.go +++ b/verify_signature.go @@ -7,6 +7,7 @@ import ( "errors" "github.com/aurora-is-near/near-api-go/types" + "github.com/aurora-is-near/near-api-go/utils" "github.com/near/borsh-go" ) @@ -74,3 +75,33 @@ func VerifySignatureHex(publicKeyHex string, signatureHex string, messageHex str } return VerifySignatureBytes(publicKey, signature, message) } + +// VerifySignature verifies a signature given the public key, signature, and message as strings. +// The public key and signature should be in the "ed25519:..." format. +func VerifySignatureBase58(pubKeyStr, signatureStr, messageHex string) (bool, error) { + // Parse the public key + pubKey, err := utils.Ed25519PublicKeyFromString(pubKeyStr) + if err != nil { + return false, err + } + + // Parse the signature + signature, err := utils.Ed25519SignatureFromString(signatureStr) + if err != nil { + return false, err + } + + // Decode the message from hex + messageBytes, err := hex.DecodeString(messageHex) + if err != nil { + return false, err + } + + // Hash the message + hash := sha256.Sum256(messageBytes) + + // Verify the signature + isValid := ed25519.Verify(pubKey, hash[:], signature) + + return isValid, nil +} diff --git a/verify_signature_test.go b/verify_signature_test.go index d524356..53e260d 100644 --- a/verify_signature_test.go +++ b/verify_signature_test.go @@ -16,7 +16,7 @@ func TestVerifySignature(t *testing.T) { } // Create a message to sign - message := []byte("test message") + message := []byte("+16005551111evm.test-account.testnet") messageHex := hex.EncodeToString(message) // Hash the message @@ -85,3 +85,21 @@ func TestVerifySignaturePrecomputed(t *testing.T) { t.Fatal("Signature should be valid for the given public key") } } + +func TestVerifySignatureString(t *testing.T) { + message := "+16005551111evm.test-account.testnet" + pubKey := "ed25519:3MVWwQTnSU8RVNe1Z5Ytv9zVE16aXLuHkvEha4LvDH1z" + signature := "ed25519:3cn6uwA9hVtK8S4ib2coEt8HTV1pjuCLtZs5iUx4zKmXhAUd73rhXcmG2icFk1mF5zy4B2wVkjoisWZQczDG5nXV" + + // Convert message to hex + messageHex := hex.EncodeToString([]byte(message)) + + // Verify the signature + isValid, err := VerifySignatureBase58(pubKey, signature, messageHex) + if err != nil { + t.Fatalf("Failed to verify signature: %v", err) + } + if !isValid { + t.Fatal("Signature should be valid for the given public key") + } +} From d8973ca4b517fad2806266b28cfebeea39b00c9b Mon Sep 17 00:00:00 2001 From: hirama Date: Thu, 24 Oct 2024 20:08:46 -0300 Subject: [PATCH 7/7] added ParseEd25519PrivateKey --- keystore/keystore.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/keystore/keystore.go b/keystore/keystore.go index 2d98158..e0ea1a1 100644 --- a/keystore/keystore.go +++ b/keystore/keystore.go @@ -32,6 +32,11 @@ func GenerateEd25519KeyPair(accountID string) (*Ed25519KeyPair, error) { return KeyPairFromPrivateKey(accountID, privateKey), nil } +// ParseEd25519PrivateKey parses a private key string and returns an ed25519.PrivateKey. +func ParseEd25519PrivateKey(privateKeyString string) (ed25519.PrivateKey, error) { + return utils.Ed25519PrivateKeyFromString(privateKeyString) +} + // KeyPairFromPrivateKey creates a key-pair given an accountID and a private key. func KeyPairFromPrivateKey(accountID string, privateKey ed25519.PrivateKey) *Ed25519KeyPair { publicKey := privateKey.Public().(ed25519.PublicKey)