diff --git a/vrf.go b/vrf.go index bf1182a..152ac24 100644 --- a/vrf.go +++ b/vrf.go @@ -5,6 +5,8 @@ import ( r255 "github.com/gtank/ristretto255" ) +var kusamaVRF bool = true + type VrfInOut struct { input *r255.Element output *r255.Element @@ -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{ @@ -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() @@ -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{ @@ -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 } @@ -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 @@ -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")) @@ -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) } @@ -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() @@ -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")) diff --git a/vrf_test.go b/vrf_test.go index 4786820..ed18f02 100644 --- a/vrf_test.go +++ b/vrf_test.go @@ -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() @@ -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) } @@ -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) } @@ -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) } @@ -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) } @@ -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) +}