github.com/TrueCloudLab/frostfs-api-go/v2@v2.0.0-20230228134343-196241c4e79a/util/signature/walletconnect/sign.go (about) 1 package walletconnect 2 3 import ( 4 "crypto/ecdsa" 5 "crypto/elliptic" 6 "crypto/rand" 7 "encoding/binary" 8 "encoding/hex" 9 10 crypto "github.com/TrueCloudLab/frostfs-crypto" 11 ) 12 13 const ( 14 // saltSize is the salt size added to signed message. 15 saltSize = 16 16 // signatureLen is the length of RFC6979 signature. 17 signatureLen = 64 18 ) 19 20 // SignedMessage contains mirrors `SignedMessage` struct from the WalletConnect API. 21 // https://neon.coz.io/wksdk/core/modules.html#SignedMessage 22 type SignedMessage struct { 23 Data []byte 24 Message []byte 25 PublicKey []byte 26 Salt []byte 27 } 28 29 // Sign signs message using WalletConnect API. The returned signature 30 // contains RFC6979 signature and 16-byte salt. 31 func Sign(p *ecdsa.PrivateKey, msg []byte) ([]byte, error) { 32 sm, err := SignMessage(p, msg) 33 if err != nil { 34 return nil, err 35 } 36 return append(sm.Data, sm.Salt...), nil 37 } 38 39 // Verify verifies message using WalletConnect API. 40 func Verify(p *ecdsa.PublicKey, data, sign []byte) bool { 41 if len(sign) != signatureLen+saltSize { 42 return false 43 } 44 45 salt := sign[signatureLen:] 46 return VerifyMessage(p, SignedMessage{ 47 Data: sign[:signatureLen], 48 Message: createMessageWithSalt(data, salt), 49 Salt: salt, 50 }) 51 } 52 53 // SignMessage signs message with a private key and returns structure similar to 54 // `signMessage` of the WalletConnect API. 55 // https://github.com/CityOfZion/wallet-connect-sdk/blob/89c236b/packages/wallet-connect-sdk-core/src/index.ts#L496 56 // https://github.com/CityOfZion/neon-wallet/blob/1174a9388480e6bbc4f79eb13183c2a573f67ca8/app/context/WalletConnect/helpers.js#L133 57 func SignMessage(p *ecdsa.PrivateKey, msg []byte) (SignedMessage, error) { 58 var salt [saltSize]byte 59 _, _ = rand.Read(salt[:]) 60 61 msg = createMessageWithSalt(msg, salt[:]) 62 sign, err := crypto.SignRFC6979(p, msg) 63 if err != nil { 64 return SignedMessage{}, err 65 } 66 67 return SignedMessage{ 68 Data: sign, 69 Message: msg, 70 PublicKey: elliptic.MarshalCompressed(p.Curve, p.X, p.Y), 71 Salt: salt[:], 72 }, nil 73 } 74 75 // VerifyMessage verifies message with a private key and returns structure similar to 76 // `verifyMessage` of WalletConnect API. 77 // https://github.com/CityOfZion/wallet-connect-sdk/blob/89c236b/packages/wallet-connect-sdk-core/src/index.ts#L515 78 // https://github.com/CityOfZion/neon-wallet/blob/1174a9388480e6bbc4f79eb13183c2a573f67ca8/app/context/WalletConnect/helpers.js#L147 79 func VerifyMessage(p *ecdsa.PublicKey, m SignedMessage) bool { 80 if p == nil { 81 x, y := elliptic.UnmarshalCompressed(elliptic.P256(), m.PublicKey) 82 if x == nil || y == nil { 83 return false 84 } 85 p = &ecdsa.PublicKey{ 86 Curve: elliptic.P256(), 87 X: x, 88 Y: y, 89 } 90 } 91 return crypto.VerifyRFC6979(p, m.Message, m.Data) == nil 92 } 93 94 func createMessageWithSalt(msg, salt []byte) []byte { 95 // 4 byte prefix + length of the message with salt in bytes + 96 // + salt + message + 2 byte postfix. 97 saltedLen := hex.EncodedLen(len(salt)) + len(msg) 98 data := make([]byte, 4+getVarIntSize(saltedLen)+saltedLen+2) 99 100 n := copy(data, []byte{0x01, 0x00, 0x01, 0xf0}) // fixed prefix 101 n += putVarUint(data[n:], uint64(saltedLen)) // salt is hex encoded, double its size 102 n += hex.Encode(data[n:], salt[:]) // for some reason we encode salt in hex 103 n += copy(data[n:], msg) 104 copy(data[n:], []byte{0x00, 0x00}) 105 106 return data 107 } 108 109 // Following functions are copied from github.com/nspcc-dev/neo-go/pkg/io package 110 // to avoid having another dependency. 111 112 // getVarIntSize returns the size in number of bytes of a variable integer. 113 // Reference: https://github.com/neo-project/neo/blob/26d04a642ac5a1dd1827dabf5602767e0acba25c/src/neo/IO/Helper.cs#L131 114 func getVarIntSize(value int) int { 115 var size uintptr 116 117 if value < 0xFD { 118 size = 1 // unit8 119 } else if value <= 0xFFFF { 120 size = 3 // byte + uint16 121 } else { 122 size = 5 // byte + uint32 123 } 124 return int(size) 125 } 126 127 // putVarUint puts val in varint form to the pre-allocated buffer. 128 func putVarUint(data []byte, val uint64) int { 129 if val < 0xfd { 130 data[0] = byte(val) 131 return 1 132 } 133 if val <= 0xFFFF { 134 data[0] = byte(0xfd) 135 binary.LittleEndian.PutUint16(data[1:], uint16(val)) 136 return 3 137 } 138 139 data[0] = byte(0xfe) 140 binary.LittleEndian.PutUint32(data[1:], uint32(val)) 141 return 5 142 }