-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow ostracon to verify the old r2ishiguro vrf proofs (#652)
* 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
Showing
11 changed files
with
406 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
Oops, something went wrong.