From 5d3987af5d4d65769a21ab7f52ba42f394b3c050 Mon Sep 17 00:00:00 2001 From: Miguel Victoria Date: Wed, 4 Sep 2024 21:05:36 +0200 Subject: [PATCH 1/2] feat: add verifySignature native function --- .../gno.land/r/gnoland/ghverify/contract.gno | 5 +++ gnovm/stdlibs/generated.go | 36 +++++++++++++++++++ gnovm/stdlibs/std/native.gno | 5 +++ gnovm/stdlibs/std/native.go | 8 +++++ gnovm/stdlibs/std/native_test.go | 25 +++++++++++++ 5 files changed, 79 insertions(+) diff --git a/examples/gno.land/r/gnoland/ghverify/contract.gno b/examples/gno.land/r/gnoland/ghverify/contract.gno index b40c9ef1448..2bf519c29c0 100644 --- a/examples/gno.land/r/gnoland/ghverify/contract.gno +++ b/examples/gno.land/r/gnoland/ghverify/contract.gno @@ -83,6 +83,11 @@ func RequestVerification(githubHandle string) { ); err != nil { panic(err) } + std.Emit( + "verification_requested", + "from", gnoAddress, + "handle", githubHandle, + ) } // GnorkleEntrypoint is the entrypoint to the gnorkle oracle handler. diff --git a/gnovm/stdlibs/generated.go b/gnovm/stdlibs/generated.go index 0af38950ce1..9775bc877cc 100644 --- a/gnovm/stdlibs/generated.go +++ b/gnovm/stdlibs/generated.go @@ -721,6 +721,42 @@ var nativeFuncs = [...]NativeFunc{ )) }, }, + { + "std", + "verifySignature", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + {Name: gno.N("p2"), Type: gno.X("string")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("bool")}, + }, + false, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() + p2 string + rp2 = reflect.ValueOf(&p2).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV, rp2) + + r0 := libs_std.X_verifySignature(p0, p1, p2) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, { "strconv", "Itoa", diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno index 22a16fc18d1..a46fb567dd5 100644 --- a/gnovm/stdlibs/std/native.gno +++ b/gnovm/stdlibs/std/native.gno @@ -56,6 +56,10 @@ func DecodeBech32(addr Address) (prefix string, bz [20]byte, ok bool) { return decodeBech32(string(addr)) } +func VerifySignature(pubKeySigner string,msg string, signature string) bool { + return verifySignature(pubKeySigner, msg, signature) +} + // Variations which don't use named types. func origSend() (denoms []string, amounts []int64) func origCaller() string @@ -65,3 +69,4 @@ func getRealm(height int) (address string, pkgPath string) func derivePkgAddr(pkgPath string) string func encodeBech32(prefix string, bz [20]byte) string func decodeBech32(addr string) (prefix string, bz [20]byte, ok bool) +func verifySignature(pubKeySigner string,msg string, signature string) bool diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go index f185a52f249..a11d2df6e22 100644 --- a/gnovm/stdlibs/std/native.go +++ b/gnovm/stdlibs/std/native.go @@ -150,6 +150,14 @@ func X_encodeBech32(prefix string, bytes [20]byte) string { return b32 } +func X_verifySignature(pubKeySigner string, msg string, signature string) bool { + key, err := crypto.PubKeyFromBech32(pubKeySigner) + if err != nil { + panic(err) // should not happen + } + return key.VerifyBytes([]byte(msg), []byte(signature)) +} + func X_decodeBech32(addr string) (prefix string, bytes [20]byte, ok bool) { prefix, bz, err := bech32.Decode(addr) if err != nil || len(bz) != 20 { diff --git a/gnovm/stdlibs/std/native_test.go b/gnovm/stdlibs/std/native_test.go index 851785575d7..aa41346378c 100644 --- a/gnovm/stdlibs/std/native_test.go +++ b/gnovm/stdlibs/std/native_test.go @@ -5,10 +5,14 @@ import ( "github.com/stretchr/testify/assert" + "github.com/gnolang/gno/gnovm/pkg/gnoenv" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/crypto/keys" ) +const DefaultAccount_Seed = "source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast" + func TestPrevRealmIsOrigin(t *testing.T) { var ( user = gno.DerivePkgAddr("user1.gno").Bech32() @@ -192,3 +196,24 @@ func TestPrevRealmIsOrigin(t *testing.T) { }) } } + +func TestVerify(t *testing.T) { + kb, _ := keys.NewKeyBaseFromDir(gnoenv.HomeDir()) + pass := "hardPass" + info, err := kb.CreateAccount("user", DefaultAccount_Seed, pass, pass, 0, 0) + assert.NoError(t, err) + + publicKey := info.GetPubKey().String() // gpub1pgfj7ard9eg82cjtv4u4xetrwqer2dntxyfzxz3pqfzjcj8wph4wl0x7tqu3k7geuqsz2d45eddys0hgf0xd7dr2dupnqukpghs + goodMessage := "Verification Ok" + maliciousMessage := "Malicious Message" + signature, _, err := kb.Sign("user", pass, []byte(goodMessage)) + assert.NoError(t, err) + + if !X_verifySignature(publicKey, goodMessage, string(signature)) { + t.Error("verify failed") + } + + if X_verifySignature(publicKey, maliciousMessage, string(signature)) { + t.Error("verify worked on malicious message") + } +} From a068551cc38c4daa5f7383efde7fc1224c53765f Mon Sep 17 00:00:00 2001 From: Miguel Victoria Date: Mon, 9 Sep 2024 17:01:08 +0200 Subject: [PATCH 2/2] Change gnorkle code --- .../p/demo/gnorkle/gnorkle/instance.gno | 35 +++++++++++++++++++ .../gno.land/p/demo/gnorkle/message/parse.gno | 10 ++++++ .../gno.land/p/demo/gnorkle/message/type.gno | 3 ++ gnovm/stdlibs/generated.go | 8 ++++- gnovm/stdlibs/std/native.gno | 4 +-- gnovm/stdlibs/std/native.go | 14 ++++++-- gnovm/stdlibs/std/native_test.go | 10 ++++-- 7 files changed, 77 insertions(+), 7 deletions(-) diff --git a/examples/gno.land/p/demo/gnorkle/gnorkle/instance.gno b/examples/gno.land/p/demo/gnorkle/gnorkle/instance.gno index 22746d569a8..11f3e0fe7fc 100644 --- a/examples/gno.land/p/demo/gnorkle/gnorkle/instance.gno +++ b/examples/gno.land/p/demo/gnorkle/gnorkle/instance.gno @@ -113,7 +113,30 @@ func (i *Instance) HandleMessage(msg string, postHandler PostMessageHandler) (st switch funcType { case message.FuncTypeRequest: return i.GetFeedDefinitions(caller) + case message.FuncTypeRelay: + publicKey, signature, relayedMsg := message.ParseRelayedMessage(msg) + i.assertSignatureIsValid(publicKey, signature, relayedMsg) + + funcType, relayedMsg := message.ParseFunc(relayedMsg) + + id, relayedMsg := message.ParseID(relayedMsg) + if err := assertValidID(id); err != nil { + return "", err + } + + feedWithWhitelist, err := i.getFeedWithWhitelist(id) + if err != nil { + return "", err + } + + if err := feedWithWhitelist.Ingest(funcType, relayedMsg, caller); err != nil { + return "", err + } + + if postHandler != nil { + postHandler.Handle(i, funcType, feedWithWhitelist) + } default: id, msg := message.ParseID(msg) if err := assertValidID(id); err != nil { @@ -237,3 +260,15 @@ func (i *Instance) GetFeedDefinitions(forAddress string) (string, error) { buf.WriteString("]") return buf.String(), nil } + +func (i *Instance) assertSignatureIsValid(publicKey, signature, msg string,) { + validSignature, signer := std.VerifySignature(publicKey, signature, msg) + if !validSignature { + panic("invalid signature") + } + + // For the moment just accept general whitelisted addressed not feed specific ones + if !addressIsWhitelisted(&i.whitelist, nil, signer, nil) { + panic("address is not whitelisted") + } +} \ No newline at end of file diff --git a/examples/gno.land/p/demo/gnorkle/message/parse.gno b/examples/gno.land/p/demo/gnorkle/message/parse.gno index 633dcc38b66..54ddf72e105 100644 --- a/examples/gno.land/p/demo/gnorkle/message/parse.gno +++ b/examples/gno.land/p/demo/gnorkle/message/parse.gno @@ -23,3 +23,13 @@ func parseFirstToken(rawMsg string) (string, string) { return msgParts[0], msgParts[1] } + +// ParseRelayedMessage parses a relayed message and returns: +// the signing public key +// the signature (commonly hexadecimal) +// The relayed message (Containing the messageFunction != Relay) +func ParseRelayedMessage(rawMsg string) ( string, string, string) { + publicKey, remainder := parseFirstToken(rawMsg) + signature, remainder := parseFirstToken(remainder) + return publicKey, signature, remainder +} diff --git a/examples/gno.land/p/demo/gnorkle/message/type.gno b/examples/gno.land/p/demo/gnorkle/message/type.gno index a80e568ea24..8e7c8481563 100644 --- a/examples/gno.land/p/demo/gnorkle/message/type.gno +++ b/examples/gno.land/p/demo/gnorkle/message/type.gno @@ -12,4 +12,7 @@ const ( // FuncTypeRequest means the agent is requesting feed definitions for all those // that it is whitelisted to provide data for. FuncTypeRequest FuncType = "request" + // FuncTypeRelay means a non whitelisted-user is relaying a message from a whitelisted agent + // the relayer user should provide a publicKey, the message to handle and a valid signature + FuncTypeRelay FuncType = "relay" ) diff --git a/gnovm/stdlibs/generated.go b/gnovm/stdlibs/generated.go index 9775bc877cc..9091e9d34ba 100644 --- a/gnovm/stdlibs/generated.go +++ b/gnovm/stdlibs/generated.go @@ -731,6 +731,7 @@ var nativeFuncs = [...]NativeFunc{ }, []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("bool")}, + {Name: gno.N("r1"), Type: gno.X("string")}, }, false, func(m *gno.Machine) { @@ -748,13 +749,18 @@ var nativeFuncs = [...]NativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV, rp2) - r0 := libs_std.X_verifySignature(p0, p1, p2) + r0, r1 := libs_std.X_verifySignature(p0, p1, p2) m.PushValue(gno.Go2GnoValue( m.Alloc, m.Store, reflect.ValueOf(&r0).Elem(), )) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r1).Elem(), + )) }, }, { diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno index a46fb567dd5..cbc29b6b167 100644 --- a/gnovm/stdlibs/std/native.gno +++ b/gnovm/stdlibs/std/native.gno @@ -56,7 +56,7 @@ func DecodeBech32(addr Address) (prefix string, bz [20]byte, ok bool) { return decodeBech32(string(addr)) } -func VerifySignature(pubKeySigner string,msg string, signature string) bool { +func VerifySignature(pubKeySigner string, signature string, msg string) (bool,string) { return verifySignature(pubKeySigner, msg, signature) } @@ -69,4 +69,4 @@ func getRealm(height int) (address string, pkgPath string) func derivePkgAddr(pkgPath string) string func encodeBech32(prefix string, bz [20]byte) string func decodeBech32(addr string) (prefix string, bz [20]byte, ok bool) -func verifySignature(pubKeySigner string,msg string, signature string) bool +func verifySignature(pubKeySigner string,msg string, signature string) (bool,string) diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go index a11d2df6e22..9acb3a7e2d9 100644 --- a/gnovm/stdlibs/std/native.go +++ b/gnovm/stdlibs/std/native.go @@ -1,6 +1,8 @@ package std import ( + "encoding/hex" + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/tm2/pkg/bech32" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -150,12 +152,20 @@ func X_encodeBech32(prefix string, bytes [20]byte) string { return b32 } -func X_verifySignature(pubKeySigner string, msg string, signature string) bool { +func X_verifySignature(pubKeySigner string, msg string, signature string) (bool, string) { key, err := crypto.PubKeyFromBech32(pubKeySigner) if err != nil { panic(err) // should not happen } - return key.VerifyBytes([]byte(msg), []byte(signature)) + + decodedData, err := hex.DecodeString(signature) + if err != nil { + panic(err) // should not happen + } + + validSignature := key.VerifyBytes([]byte(msg), decodedData) + + return validSignature, key.Address().String() } func X_decodeBech32(addr string) (prefix string, bytes [20]byte, ok bool) { diff --git a/gnovm/stdlibs/std/native_test.go b/gnovm/stdlibs/std/native_test.go index aa41346378c..79c89ba3441 100644 --- a/gnovm/stdlibs/std/native_test.go +++ b/gnovm/stdlibs/std/native_test.go @@ -208,12 +208,18 @@ func TestVerify(t *testing.T) { maliciousMessage := "Malicious Message" signature, _, err := kb.Sign("user", pass, []byte(goodMessage)) assert.NoError(t, err) + signatureValid, signer := X_verifySignature(publicKey, goodMessage, string(signature)) - if !X_verifySignature(publicKey, goodMessage, string(signature)) { + if !signatureValid { t.Error("verify failed") } - if X_verifySignature(publicKey, maliciousMessage, string(signature)) { + if signer != info.GetAddress().String() { + t.Error("signer is not equal to address") + } + + signatureValid, _ = X_verifySignature(publicKey, maliciousMessage, string(signature)) + if signatureValid { t.Error("verify worked on malicious message") } }