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/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) +}