Skip to content

Commit

Permalink
add VrfInOut.MakeBytes, add kusama toggle (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
noot authored Jan 22, 2021
1 parent d3c6d31 commit 47f54ad
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 15 deletions.
55 changes: 46 additions & 9 deletions vrf.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
r255 "github.com/gtank/ristretto255"
)

var kusamaVRF bool = true

type VrfInOut struct {
input *r255.Element
output *r255.Element
Expand All @@ -19,6 +21,11 @@ type VrfProof struct {
s *r255.Scalar
}

// SetKusama sets the VRF kusama option. Defaults to true.
func SetKusamaVRF(k bool) {
kusamaVRF = k
}

// Output returns a VrfOutput from a VrfInOut
func (io *VrfInOut) Output() *VrfOutput {
return &VrfOutput{
Expand All @@ -35,6 +42,21 @@ func (io *VrfInOut) Encode() []byte {
return append(inbytes[:], outbytes[:]...)
}

// MakeBytes returns raw bytes output from the VRF
// It returns a byte slice of the given size
// see https://github.com/w3f/schnorrkel/blob/master/src/vrf.rs#L334
func (io *VrfInOut) MakeBytes(size int, context []byte) []byte {
t := merlin.NewTranscript("VRFResult")
t.AppendMessage([]byte(""), context)
io.commit(t)
return t.ExtractBytes([]byte(""), size)
}

func (io *VrfInOut) commit(t *merlin.Transcript) {
t.AppendMessage([]byte("vrf-in"), io.input.Encode([]byte{}))
t.AppendMessage([]byte("vrf-out"), io.output.Encode([]byte{}))
}

// NewOutput creates a new VRF output from a 64-byte element
func NewOutput(in [32]byte) *VrfOutput {
output := r255.NewElement()
Expand All @@ -45,6 +67,7 @@ func NewOutput(in [32]byte) *VrfOutput {
}

// AttachInput returns a VrfInOut pair from an output
// https://github.com/w3f/schnorrkel/blob/master/src/vrf.rs#L249
func (out *VrfOutput) AttachInput(pub *PublicKey, t *merlin.Transcript) *VrfInOut {
input := pub.vrfHash(t)
return &VrfInOut{
Expand Down Expand Up @@ -109,8 +132,8 @@ func (sk *SecretKey) VrfSign(t *merlin.Transcript) (*VrfInOut, *VrfProof, error)
return nil, nil, err
}

t0 := merlin.NewTranscript("VRF")
proof, err := sk.dleqProve(t0, p)
extra := merlin.NewTranscript("VRF")
proof, err := sk.dleqProve(extra, p)
if err != nil {
return nil, nil, err
}
Expand All @@ -120,10 +143,21 @@ func (sk *SecretKey) VrfSign(t *merlin.Transcript) (*VrfInOut, *VrfProof, error)
// dleqProve creates a VRF proof for the transcript and input with this secret key.
// see: https://github.com/w3f/schnorrkel/blob/798ab3e0813aa478b520c5cf6dc6e02fd4e07f0a/src/vrf.rs#L604
func (sk *SecretKey) dleqProve(t *merlin.Transcript, p *VrfInOut) (*VrfProof, error) {
pub, err := sk.Public()
if err != nil {
return nil, err
}
pubenc := pub.Encode()

t.AppendMessage([]byte("proto-name"), []byte("DLEQProof"))
t.AppendMessage([]byte("vrf:h"), p.input.Encode([]byte{}))
if !kusamaVRF {
t.AppendMessage([]byte("vrf:pk"), pubenc[:])
}

// create random element R = g^r
// TODO: update toe use witness scalar
// https://github.com/w3f/schnorrkel/blob/master/src/vrf.rs#L620
r, err := NewRandomScalar()
if err != nil {
return nil, err
Expand All @@ -136,12 +170,9 @@ func (sk *SecretKey) dleqProve(t *merlin.Transcript, p *VrfInOut) (*VrfProof, er
hr := r255.NewElement().ScalarMult(r, p.input).Encode([]byte{})
t.AppendMessage([]byte("vrf:h^r"), hr)

pub, err := sk.Public()
if err != nil {
return nil, err
if kusamaVRF {
t.AppendMessage([]byte("vrf:pk"), pubenc[:])
}
pubenc := pub.Encode()
t.AppendMessage([]byte("vrf:pk"), pubenc[:])
t.AppendMessage([]byte("vrf:h^sk"), p.output.Encode([]byte{}))

c := challengeScalar(t, []byte("prove"))
Expand Down Expand Up @@ -181,7 +212,8 @@ func (sk *SecretKey) vrfCreateHash(t *merlin.Transcript) (*VrfInOut, error) {
}

// VrfVerify verifies that the proof and output created are valid given the public key and transcript.
func (pk *PublicKey) VrfVerify(t *merlin.Transcript, inout *VrfInOut, proof *VrfProof) (bool, error) {
func (pk *PublicKey) VrfVerify(t *merlin.Transcript, out *VrfOutput, proof *VrfProof) (bool, error) {
inout := out.AttachInput(pk, t)
t0 := merlin.NewTranscript("VRF")
return pk.dleqVerify(t0, inout, proof)
}
Expand All @@ -190,6 +222,9 @@ func (pk *PublicKey) VrfVerify(t *merlin.Transcript, inout *VrfInOut, proof *Vrf
func (pk *PublicKey) dleqVerify(t *merlin.Transcript, p *VrfInOut, proof *VrfProof) (bool, error) {
t.AppendMessage([]byte("proto-name"), []byte("DLEQProof"))
t.AppendMessage([]byte("vrf:h"), p.input.Encode([]byte{}))
if !kusamaVRF {
t.AppendMessage([]byte("vrf:pk"), pk.key.Encode([]byte{}))
}

// R = proof.c*pk + proof.s*g
R := r255.NewElement()
Expand All @@ -199,7 +234,9 @@ func (pk *PublicKey) dleqVerify(t *merlin.Transcript, p *VrfInOut, proof *VrfPro
// hr = proof.c * p.output + proof.s * p.input
hr := r255.NewElement().VarTimeMultiScalarMult([]*r255.Scalar{proof.c, proof.s}, []*r255.Element{p.output, p.input})
t.AppendMessage([]byte("vrf:h^r"), hr.Encode([]byte{}))
t.AppendMessage([]byte("vrf:pk"), pk.key.Encode([]byte{}))
if kusamaVRF {
t.AppendMessage([]byte("vrf:pk"), pk.key.Encode([]byte{}))
}
t.AppendMessage([]byte("vrf:h^sk"), p.output.Encode([]byte{}))

cexpected := challengeScalar(t, []byte("prove"))
Expand Down
76 changes: 70 additions & 6 deletions vrf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import (

"github.com/gtank/merlin"
r255 "github.com/gtank/ristretto255"
"github.com/stretchr/testify/require"
)

func TestInputAndOutput(t *testing.T) {
signTranscript := merlin.NewTranscript("vrf-test")
inoutTranscript := merlin.NewTranscript("vrf-test")
verifyTranscript := merlin.NewTranscript("vrf-test")

priv, pub, err := GenerateKeypair()
Expand All @@ -27,9 +27,8 @@ func TestInputAndOutput(t *testing.T) {
outbytes := [32]byte{}
copy(outbytes[:], outslice)
out := NewOutput(outbytes)
inout2 := out.AttachInput(pub, inoutTranscript)

ok, err := pub.VrfVerify(verifyTranscript, inout2, proof)
ok, err := pub.VrfVerify(verifyTranscript, out, proof)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -103,7 +102,7 @@ func TestVRFSignAndVerify(t *testing.T) {
t.Fatal(err)
}

ok, err := pub.VrfVerify(verifyTranscript, inout, proof)
ok, err := pub.VrfVerify(verifyTranscript, inout.Output(), proof)
if err != nil {
t.Fatal(err)
}
Expand All @@ -117,7 +116,7 @@ func TestVRFSignAndVerify(t *testing.T) {
t.Fatal(err)
}

ok, err = pub.VrfVerify(verify2Transcript, inout, proof)
ok, err = pub.VrfVerify(verify2Transcript, inout.Output(), proof)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -171,7 +170,7 @@ func TestVrfVerify_rust(t *testing.T) {
s: s,
}

ok, err := pub.VrfVerify(transcript, inout, proof)
ok, err := pub.VrfVerify(transcript, inout.Output(), proof)
if err != nil {
t.Fatal(err)
}
Expand All @@ -180,3 +179,68 @@ func TestVrfVerify_rust(t *testing.T) {
t.Fatal("did not verify vrf")
}
}

// input data from https://github.com/noot/schnorrkel/blob/master/src/vrf.rs#L922
func TestVrfInOut_MakeBytes(t *testing.T) {
transcript := NewSigningContext([]byte("yo!"), []byte("meow"))

pub := [32]byte{12, 132, 183, 11, 234, 190, 96, 172, 111, 239, 163, 137, 148, 163, 69, 79, 230, 61, 134, 41, 69, 90, 134, 229, 132, 128, 6, 63, 139, 220, 202, 0}
input := []byte{188, 162, 182, 161, 195, 26, 55, 223, 166, 205, 136, 92, 211, 130, 184, 194, 183, 81, 215, 192, 168, 12, 39, 55, 218, 165, 8, 105, 155, 73, 128, 68}
output := [32]byte{214, 40, 153, 246, 88, 74, 127, 242, 54, 193, 7, 5, 90, 51, 45, 5, 207, 59, 64, 68, 134, 232, 19, 223, 249, 88, 74, 125, 64, 74, 220, 48}
proof := [64]byte{144, 199, 179, 5, 250, 199, 220, 177, 12, 220, 242, 196, 168, 237, 106, 3, 62, 195, 74, 127, 134, 107, 137, 91, 165, 104, 223, 244, 3, 4, 141, 10, 129, 54, 134, 31, 49, 250, 205, 203, 254, 142, 87, 123, 216, 108, 190, 112, 204, 204, 188, 30, 84, 36, 247, 217, 59, 125, 45, 56, 112, 195, 84, 15}
make_bytes_16_expected := []byte{169, 57, 149, 50, 0, 243, 120, 138, 25, 250, 74, 235, 247, 137, 228, 40}

pubkey := NewPublicKey(pub)
out := new(VrfOutput)
err := out.Decode(output)
require.NoError(t, err)

inout := out.AttachInput(pubkey, transcript)
require.Equal(t, input, inout.input.Encode([]byte{}))

p := new(VrfProof)
err = p.Decode(proof)
require.NoError(t, err)

verifyTranscript := NewSigningContext([]byte("yo!"), []byte("meow"))
ok, err := pubkey.VrfVerify(verifyTranscript, out, p)
require.NoError(t, err)
require.True(t, ok)

bytes := inout.MakeBytes(16, []byte("substrate-babe-vrf"))
require.Equal(t, make_bytes_16_expected, bytes)
}

func TestVrfVerify_NotKusama(t *testing.T) {
kusamaVRF = false
defer func() {
kusamaVRF = true
}()

transcript := NewSigningContext([]byte("yo!"), []byte("meow"))
pub := [32]byte{178, 10, 148, 176, 134, 205, 129, 139, 45, 90, 42, 14, 71, 116, 227, 233, 15, 253, 56, 53, 123, 7, 89, 240, 129, 61, 83, 213, 88, 73, 45, 111}
input := []byte{118, 192, 145, 134, 145, 226, 209, 28, 62, 15, 187, 236, 43, 229, 255, 161, 72, 122, 128, 21, 28, 155, 72, 19, 67, 100, 50, 217, 72, 35, 95, 111}
output := [32]byte{114, 173, 188, 116, 143, 11, 157, 244, 87, 214, 231, 0, 234, 34, 157, 145, 62, 154, 68, 161, 121, 66, 49, 25, 123, 38, 138, 20, 207, 105, 7, 5}
proof := [64]byte{123, 219, 60, 236, 49, 106, 113, 229, 135, 98, 153, 252, 10, 63, 65, 174, 242, 191, 130, 65, 119, 177, 227, 15, 103, 219, 192, 100, 174, 204, 136, 3, 95, 148, 246, 105, 108, 51, 20, 173, 123, 108, 5, 49, 253, 21, 170, 41, 214, 1, 141, 97, 93, 182, 52, 175, 202, 186, 149, 213, 69, 57, 7, 14}
make_bytes_16_expected := []byte{193, 153, 104, 18, 4, 27, 121, 146, 149, 228, 12, 17, 251, 184, 117, 16}

pubkey := NewPublicKey(pub)
out := new(VrfOutput)
err := out.Decode(output)
require.NoError(t, err)

inout := out.AttachInput(pubkey, transcript)
require.Equal(t, input, inout.input.Encode([]byte{}))

p := new(VrfProof)
err = p.Decode(proof)
require.NoError(t, err)

verifyTranscript := NewSigningContext([]byte("yo!"), []byte("meow"))
ok, err := pubkey.VrfVerify(verifyTranscript, out, p)
require.NoError(t, err)
require.True(t, ok)

bytes := inout.MakeBytes(16, []byte("substrate-babe-vrf"))
require.Equal(t, make_bytes_16_expected, bytes)
}

0 comments on commit 47f54ad

Please sign in to comment.