Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow ostracon to verify the old r2ishiguro vrf proofs #652

Merged
merged 14 commits into from
Jul 13, 2023
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