From 7c049720418a6b72e9ee539ae94862ff086cfe6b Mon Sep 17 00:00:00 2001 From: wuuuk Date: Fri, 8 Dec 2023 15:58:41 +0800 Subject: [PATCH] feat: verify message and verify transaction --- constant/intent.go | 10 +++++ go.mod | 1 - go.sum | 2 - models/intent.go | 27 ++++++++++++++ models/signature.go | 89 +++++++++++++++++++++++++++++++++++++++++++-- signer/intent.go | 27 -------------- signer/signer.go | 44 ++++++++++++++++++++++ 7 files changed, 167 insertions(+), 33 deletions(-) create mode 100644 constant/intent.go create mode 100644 models/intent.go delete mode 100644 signer/intent.go diff --git a/constant/intent.go b/constant/intent.go new file mode 100644 index 0000000..387e6da --- /dev/null +++ b/constant/intent.go @@ -0,0 +1,10 @@ +package constant + +type IntentScope = uint8 + +const ( + TransactionDataIntentScope IntentScope = 0 + TransactionEffectsIntentScope IntentScope = 1 + CheckpointSummaryIntentScope IntentScope = 2 + PersonalMessageIntentScope IntentScope = 3 +) diff --git a/go.mod b/go.mod index df8049a..ee075d1 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/block-vision/sui-go-sdk go 1.20 require ( - github.com/fardream/go-bcs v0.2.1 github.com/go-playground/validator/v10 v10.12.0 github.com/gorilla/websocket v1.5.0 github.com/tidwall/gjson v1.14.4 diff --git a/go.sum b/go.sum index 7a69b1c..861a7da 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fardream/go-bcs v0.2.1 h1:ffW/0Jr0b2WXLNPF8AX6wWI9ETVE4+aXkv2aIXVViwE= -github.com/fardream/go-bcs v0.2.1/go.mod h1:UsoxhIoe2GsVexX0s5NDLIChxeb/JUbjw7IWzzgF3Xk= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= diff --git a/models/intent.go b/models/intent.go new file mode 100644 index 0000000..862cc48 --- /dev/null +++ b/models/intent.go @@ -0,0 +1,27 @@ +package models + +import "github.com/block-vision/sui-go-sdk/constant" + +type AppId int + +const ( + Sui AppId = 0 +) + +type IntentVersion int + +const ( + V0 IntentVersion = 0 +) + +func IntentWithScope(intentScope constant.IntentScope) []int { + return []int{int(intentScope), int(V0), int(Sui)} +} + +func NewMessageWithIntent(message []byte, scope constant.IntentScope) []byte { + intent := []byte{scope, 0, 0} + intentMessage := make([]byte, len(intent)+len(message)) + copy(intentMessage, intent) + copy(intentMessage[len(intent):], message) + return intentMessage +} diff --git a/models/signature.go b/models/signature.go index a5ec43a..fcd5c04 100644 --- a/models/signature.go +++ b/models/signature.go @@ -5,9 +5,13 @@ import ( "crypto/ed25519" "encoding/base64" "encoding/hex" - "golang.org/x/crypto/blake2b" + "fmt" "log" "strings" + + "github.com/block-vision/sui-go-sdk/constant" + + "golang.org/x/crypto/blake2b" ) type InputObjectKind map[string]interface{} @@ -38,6 +42,12 @@ type HexData struct { data []byte } +type SignaturePubkeyPair struct { + SignatureScheme string + Signature []byte + PubKey []byte +} + func NewHexData(str string) (*HexData, error) { if strings.HasPrefix(str, "0x") || strings.HasPrefix(str, "0X") { str = str[2:] @@ -113,7 +123,7 @@ func (txn *TxnMetaData) SignSerializedSigWith(privateKey ed25519.PrivateKey) *Si } return &SignedTransactionSerializedSig{ TxBytes: txn.TxBytes, - Signature: toSerializedSignature(sigBytes, privateKey.Public().(ed25519.PublicKey)), + Signature: ToSerializedSignature(sigBytes, privateKey.Public().(ed25519.PublicKey)), } } @@ -125,7 +135,7 @@ func messageWithIntent(message []byte) []byte { return intentMessage } -func toSerializedSignature(signature, pubKey []byte) string { +func ToSerializedSignature(signature, pubKey []byte) string { signatureLen := len(signature) pubKeyLen := len(pubKey) serializedSignature := make([]byte, 1+signatureLen+pubKeyLen) @@ -134,3 +144,76 @@ func toSerializedSignature(signature, pubKey []byte) string { copy(serializedSignature[1+signatureLen:], pubKey) return base64.StdEncoding.EncodeToString(serializedSignature) } + +func FromSerializedSignature(serializedSignature string) (*SignaturePubkeyPair, error) { + _bytes, err := base64.StdEncoding.DecodeString(serializedSignature) + if err != nil { + return nil, err + } + signatureScheme := parseSignatureScheme(_bytes[0]) + if strings.EqualFold(serializedSignature, "") { + return nil, fmt.Errorf("multiSig is not supported") + } + + signature := _bytes[1 : len(_bytes)-32] + pubKeyBytes := _bytes[1+len(signature):] + + keyPair := &SignaturePubkeyPair{ + SignatureScheme: signatureScheme, + Signature: signature, + PubKey: pubKeyBytes, + } + return keyPair, nil +} + +func parseSignatureScheme(scheme byte) string { + switch scheme { + case 0: + return "ED25519" + case 1: + return "Secp256k1" + case 2: + return "Secp256r1" + case 3: + return "MultiSig" + default: + return "ED25519" + } +} + +func VerifyPersonalMessage(message string, signature string) (signer string, pass bool, err error) { + b64Message := base64.StdEncoding.EncodeToString([]byte(message)) + return VerifyMessage(b64Message, signature, constant.PersonalMessageIntentScope) +} + +func VerifyTransaction(b64Message string, signature string) (signer string, pass bool, err error) { + return VerifyMessage(b64Message, signature, constant.TransactionDataIntentScope) +} + +func VerifyMessage(message, signature string, scope constant.IntentScope) (signer string, pass bool, err error) { + b64Bytes, _ := base64.StdEncoding.DecodeString(message) + messageBytes := NewMessageWithIntent(b64Bytes, scope) + + serializedSignature, err := FromSerializedSignature(signature) + if err != nil { + return "", false, err + } + digest := blake2b.Sum256(messageBytes) + + pass = ed25519.Verify(serializedSignature.PubKey[:], digest[:], serializedSignature.Signature) + + signer = Ed25519PublicKeyToSuiAddress(serializedSignature.PubKey) + if err != nil { + return "", false, fmt.Errorf("invalid signer %v", err) + } + + return +} + +func Ed25519PublicKeyToSuiAddress(pubKey []byte) string { + newPubkey := []byte{byte(SigFlagEd25519)} + newPubkey = append(newPubkey, pubKey...) + + addrBytes := blake2b.Sum256(newPubkey) + return fmt.Sprintf("0x%s", hex.EncodeToString(addrBytes[:])[:64]) +} diff --git a/signer/intent.go b/signer/intent.go deleted file mode 100644 index 85f3bc9..0000000 --- a/signer/intent.go +++ /dev/null @@ -1,27 +0,0 @@ -package signer - -type AppId int - -const ( - Sui AppId = 0 -) - -type IntentVersion int - -const ( - V0 IntentVersion = 0 -) - -type IntentScope int - -const ( - TransactionData IntentScope = 0 - TransactionEffects IntentScope = 1 - CheckpointSummary IntentScope = 2 - PersonalMessage IntentScope = 3 -) - -func IntentWithScope(intentScope IntentScope) []int { - return []int{int(intentScope), int(V0), int(Sui)} -} - diff --git a/signer/signer.go b/signer/signer.go index 86e56a9..5067fc1 100644 --- a/signer/signer.go +++ b/signer/signer.go @@ -1,9 +1,14 @@ package signer import ( + "crypto" "crypto/ed25519" + "encoding/base64" "encoding/hex" + "github.com/block-vision/sui-go-sdk/common/keypair" + "github.com/block-vision/sui-go-sdk/constant" + "github.com/block-vision/sui-go-sdk/models" "github.com/tyler-smith/go-bip39" "golang.org/x/crypto/blake2b" ) @@ -49,3 +54,42 @@ func NewSignertWithMnemonic(mnemonic string) (*Signer, error) { } return NewSigner(key.Key), nil } + +type SignedMessageSerializedSig struct { + Message string `json:"message"` + Signature string `json:"signature"` +} + +func (s *Signer) SignMessage(data string, scope constant.IntentScope) (*SignedMessageSerializedSig, error) { + txBytes, _ := base64.StdEncoding.DecodeString(data) + message := models.NewMessageWithIntent(txBytes, scope) + digest := blake2b.Sum256(message) + var noHash crypto.Hash + sigBytes, err := s.PriKey.Sign(nil, digest[:], noHash) + if err != nil { + return nil, err + } + + ret := &SignedMessageSerializedSig{ + Message: data, + Signature: models.ToSerializedSignature(sigBytes, s.PriKey.Public().(ed25519.PublicKey)), + } + return ret, nil +} + +func (s *Signer) SignTransaction(b64TxBytes string) (*models.SignedTransactionSerializedSig, error) { + result, err := s.SignMessage(b64TxBytes, constant.PersonalMessageIntentScope) + if err != nil { + return nil, err + } + + return &models.SignedTransactionSerializedSig{ + TxBytes: result.Message, + Signature: result.Signature, + }, nil +} + +func (s *Signer) SignPersonalMessage(message string) (*SignedMessageSerializedSig, error) { + b64Message := base64.StdEncoding.EncodeToString([]byte(message)) + return s.SignMessage(b64Message, constant.PersonalMessageIntentScope) +}