Skip to content

Commit

Permalink
Allow ostracon to verify the old r2ishiguro vrf proofs (#652)
Browse files Browse the repository at this point in the history
* Add r2ishiguro implementation as the legacy

* Add tests

* Lint

* Chore

* Apply suggestions from code review

Modify the comments

* Add version control

* Refactor

* Fix test and refactor

* Update crypto/ed25519/migration.go

Remove old comment

* Fix race

* Remove backup file

* Use Finschia/r2ishiguro_vrf

* Use the tag for Finschia/r2ishiguro_vrf
  • Loading branch information
0Tech committed Nov 15, 2023
1 parent 7740a24 commit 97ab127
Show file tree
Hide file tree
Showing 11 changed files with 406 additions and 36 deletions.
7 changes: 4 additions & 3 deletions crypto/ed25519/ed25519.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import (
"fmt"
"io"

"github.com/oasisprotocol/curve25519-voi/primitives/ed25519"
vrf "github.com/oasisprotocol/curve25519-voi/primitives/ed25519/extra/ecvrf"

"github.com/Finschia/ostracon/crypto"
"github.com/Finschia/ostracon/crypto/tmhash"
tmjson "github.com/Finschia/ostracon/libs/json"
"github.com/oasisprotocol/curve25519-voi/primitives/ed25519"
vrf "github.com/oasisprotocol/curve25519-voi/primitives/ed25519/extra/ecvrf"
)

//-------------------------------------
Expand Down Expand Up @@ -174,7 +175,7 @@ func (pubKey PubKey) Type() string {
// The internal function of VRFVerify is implemented based on the IETF draft.
// See sections 3.1 and 3.2 here https://datatracker.ietf.org/doc/draft-irtf-cfrg-vrf/.
func (pubKey PubKey) VRFVerify(proof []byte, message []byte) (crypto.Output, error) {
isValid, hash := vrf.Verify(ed25519.PublicKey(pubKey), proof, message)
isValid, hash := VRFVerify(ed25519.PublicKey(pubKey), proof, message)
if !isValid {
return nil, fmt.Errorf("either Public Key or Proof is an invalid value.: err: %s",
hex.EncodeToString(proof))
Expand Down
13 changes: 13 additions & 0 deletions crypto/ed25519/internal/r2ishiguro/testutil/prove.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package testutil

import (
"github.com/Finschia/r2ishiguro_vrf/go/vrf_ed25519"

"github.com/Finschia/ostracon/crypto"
"github.com/Finschia/ostracon/crypto/ed25519"
)

func Prove(privateKey []byte, message []byte) (crypto.Proof, error) {
publicKey := ed25519.PrivKey(privateKey).PubKey().Bytes()
return vrf_ed25519.ECVRF_prove(publicKey, privateKey, message)
}
32 changes: 32 additions & 0 deletions crypto/ed25519/internal/r2ishiguro/vrf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package r2ishiguro

import (
"github.com/Finschia/r2ishiguro_vrf/go/vrf_ed25519"
)

const (
ProofSize = 81
)

func Verify(publicKey []byte, proof []byte, message []byte) (bool, []byte) {
isValid, err := vrf_ed25519.ECVRF_verify(publicKey, proof, message)
if err != nil || !isValid {
return false, nil
}

hash, err := ProofToHash(proof)
if err != nil {
return false, nil
}

return true, hash
}

func ProofToHash(proof []byte) ([]byte, error) {
// validate proof with ECVRF_decode_proof
_, _, _, err := vrf_ed25519.ECVRF_decode_proof(proof)
if err != nil {
return nil, err
}
return vrf_ed25519.ECVRF_proof2hash(proof), nil
}
91 changes: 91 additions & 0 deletions crypto/ed25519/internal/r2ishiguro/vrf_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package r2ishiguro_test

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/Finschia/ostracon/crypto/ed25519"
"github.com/Finschia/ostracon/crypto/ed25519/internal/r2ishiguro"
"github.com/Finschia/ostracon/crypto/ed25519/internal/r2ishiguro/testutil"
)

func TestVerify(t *testing.T) {
privKey := ed25519.GenPrivKey()
pubKey := privKey.PubKey().Bytes()
message := []byte("hello, world")

proof, err := testutil.Prove(privKey, message)
assert.NoError(t, err)
assert.NotNil(t, proof)

cases := map[string]struct {
message []byte
valid bool
}{
"valid": {
message: message,
valid: true,
},
"invalid": {
message: []byte("deadbeef"),
},
}

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
valid, _ := r2ishiguro.Verify(pubKey, proof, tc.message)
require.Equal(t, tc.valid, valid)
})
}
}

func TestProofToHash(t *testing.T) {
privKey := ed25519.GenPrivKey()
message := []byte("hello, world")

t.Run("to hash r2ishiguro proof", func(t *testing.T) {
proof, err := testutil.Prove(privKey, message)
require.NoError(t, err)
require.NotNil(t, proof)

output, err := r2ishiguro.ProofToHash(proof)
require.NoError(t, err)
require.NotNil(t, output)
})

t.Run("to hash other algo proof", func(t *testing.T) {
proof := []byte("proof of test")
output, err := r2ishiguro.ProofToHash(proof)
require.Error(t, err)
require.Nil(t, output)
})
}

func BenchmarkProveAndVerify(b *testing.B) {
privKey := ed25519.GenPrivKey()
pubKey := privKey.PubKey().Bytes()
message := []byte("hello, world")

var proof []byte
var err error
b.Run("VRF prove", func(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
proof, err = testutil.Prove(privKey, message)
}
})
require.NoError(b, err)
b.Run("VRF verify", func(b *testing.B) {
b.ResetTimer()
isValid, _ := r2ishiguro.Verify(pubKey, proof, message)
if !isValid {
err = fmt.Errorf("invalid")
} else {
err = nil
}
})
require.NoError(b, err)
}
137 changes: 137 additions & 0 deletions crypto/ed25519/migration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package ed25519

import (
"fmt"
"sync"

"github.com/oasisprotocol/curve25519-voi/primitives/ed25519"
voivrf "github.com/oasisprotocol/curve25519-voi/primitives/ed25519/extra/ecvrf"

r2vrf "github.com/Finschia/ostracon/crypto/ed25519/internal/r2ishiguro"
)

// vrf w/o prove
// vrf Prove() MUST use its latest implementation, while this allows
// to verify the old blocks.
type VrfNoProve interface {
Verify(pubKey ed25519.PublicKey, proof []byte, message []byte) (bool, []byte)
ProofToHash(proof []byte) ([]byte, error)
}

// following logics MUST use this instance:
// - VRFVerify()
// - ProofToHash()
var (
globalVrf = NewVersionedVrfNoProve()
globalVrfMu = sync.Mutex{}
)

func VRFVerify(pubKey ed25519.PublicKey, proof []byte, message []byte) (bool, []byte) {
globalVrfMu.Lock()
defer globalVrfMu.Unlock()
return globalVrf.Verify(pubKey, proof, message)
}

func ProofToHash(proof []byte) ([]byte, error) {
globalVrfMu.Lock()
defer globalVrfMu.Unlock()
return globalVrf.ProofToHash(proof)
}

// ValidateProof returns an error if the proof is not empty, but its
// size != vrf.ProofSize.
func ValidateProof(h []byte) error {
if len(h) > 0 {
proofSize := len(h)
if proofSize != voivrf.ProofSize && proofSize != r2vrf.ProofSize {
return fmt.Errorf("expected size to be %d bytes, got %d bytes",
voivrf.ProofSize,
len(h),
)
}
}
return nil
}

// versioned vrf have all the implementations inside.
// it updates its version whenever it encounters the new proof format.
// it CANNOT downgrade its version.
var _ VrfNoProve = (*versionedVrfNoProve)(nil)

type versionedVrfNoProve struct {
mu sync.Mutex
version int

proofSizeToVersion map[int]int
vrfs map[int]VrfNoProve
}

func NewVersionedVrfNoProve() VrfNoProve {
return &versionedVrfNoProve{
version: 0,
proofSizeToVersion: map[int]int{
r2vrf.ProofSize: 0,
voivrf.ProofSize: 1,
},
vrfs: map[int]VrfNoProve{
0: &r2VrfNoProve{},
1: &voiVrfNoProve{},
},
}
}

// getVersion emits error if the proof is old one.
func (v *versionedVrfNoProve) getVrf(proof []byte) (VrfNoProve, error) {
v.mu.Lock()
defer v.mu.Unlock()

proofSize := len(proof)
if version, exists := v.proofSizeToVersion[proofSize]; exists && version >= v.version {
v.version = version
return v.vrfs[version], nil
}
return nil, fmt.Errorf("invalid proof size: %d", proofSize)
}

func (v *versionedVrfNoProve) Verify(pubKey ed25519.PublicKey, proof []byte, message []byte) (bool, []byte) {
vrf, err := v.getVrf(proof)
if err != nil {
return false, nil
}

return vrf.Verify(pubKey, proof, message)
}

func (v *versionedVrfNoProve) ProofToHash(proof []byte) ([]byte, error) {
vrf, err := v.getVrf(proof)
if err != nil {
return nil, err
}
return vrf.ProofToHash(proof)
}

// github.com/oasisprotocol/curve25519-voi
var _ VrfNoProve = (*voiVrfNoProve)(nil)

type voiVrfNoProve struct{}

func (_ voiVrfNoProve) Verify(pubKey ed25519.PublicKey, proof []byte, message []byte) (bool, []byte) {
return voivrf.Verify(pubKey, proof, message)
}

func (_ voiVrfNoProve) ProofToHash(proof []byte) ([]byte, error) {
return voivrf.ProofToHash(proof)
}

// github.com/r2ishiguro/vrf
var _ VrfNoProve = (*r2VrfNoProve)(nil)

type r2VrfNoProve struct{}

func (_ r2VrfNoProve) Verify(pubKey ed25519.PublicKey, proof []byte, message []byte) (bool, []byte) {
return r2vrf.Verify(pubKey, proof, message)
}

func (_ r2VrfNoProve) ProofToHash(proof []byte) ([]byte, error) {
return r2vrf.ProofToHash(proof)
}
98 changes: 98 additions & 0 deletions crypto/ed25519/migration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package ed25519_test

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/Finschia/ostracon/crypto/ed25519"
voivrf "github.com/oasisprotocol/curve25519-voi/primitives/ed25519/extra/ecvrf"

r2vrf "github.com/Finschia/ostracon/crypto/ed25519/internal/r2ishiguro"
r2vrftestutil "github.com/Finschia/ostracon/crypto/ed25519/internal/r2ishiguro/testutil"
)

func TestVerify(t *testing.T) {
pubkey, message := []byte("pubkey"), []byte("message")
valid, _ := ed25519.VRFVerify(pubkey, make([]byte, 1), message)
require.False(t, valid)

cases := map[string]struct {
proof []byte
valid bool
}{
"invalid format": {
proof: make([]byte, 1),
},
"voi invalid proof": {
proof: make([]byte, voivrf.ProofSize),
},
"r2ishiguro invalid proof": {
proof: make([]byte, r2vrf.ProofSize),
},
}

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
valid, _ := ed25519.NewVersionedVrfNoProve().Verify(pubkey, tc.proof, message)
require.Equal(t, tc.valid, valid)
})
}
}

func TestProofToHash(t *testing.T) {
_, err := ed25519.ProofToHash(make([]byte, 1))
require.Error(t, err)

cases := map[string]struct {
proof []byte
valid bool
}{
"invalid format": {
proof: make([]byte, 1),
},
"voi invalid proof": {
proof: make([]byte, voivrf.ProofSize),
valid: true,
},
"r2ishiguro proof": {
proof: make([]byte, r2vrf.ProofSize),
},
}

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
_, err := ed25519.NewVersionedVrfNoProve().ProofToHash(tc.proof)
if !tc.valid {
require.Error(t, err)
return
}
require.NoError(t, err)
})
}
}

func TestVersionControl(t *testing.T) {
vrf := ed25519.NewVersionedVrfNoProve()

privKey := ed25519.GenPrivKey()
message := []byte("hello, world")

// generate proofs
oldProof, err := r2vrftestutil.Prove(privKey, message)
require.NoError(t, err)
newProof, err := privKey.VRFProve(message)
require.NoError(t, err)

// old one is valid for now
_, err = vrf.ProofToHash(oldProof)
require.NoError(t, err)

// new one is valid
_, err = vrf.ProofToHash(newProof)
require.NoError(t, err)

// old one is not valid anymore
_, err = vrf.ProofToHash(oldProof)
require.Error(t, err)
}
Loading

0 comments on commit 97ab127

Please sign in to comment.