From bd440c890782de2b0cb06134aba08b26e1d0b1bc Mon Sep 17 00:00:00 2001 From: noot <36753753+noot@users.noreply.github.com> Date: Mon, 22 Feb 2021 13:29:58 -0500 Subject: [PATCH] implement batch verification (#28) --- batch.go | 125 ++++++++++++++++++++++++++++++++++++++++++++++++++ batch_test.go | 77 +++++++++++++++++++++++++++++++ keys.go | 2 +- 3 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 batch.go create mode 100644 batch_test.go diff --git a/batch.go b/batch.go new file mode 100644 index 0000000..23674f0 --- /dev/null +++ b/batch.go @@ -0,0 +1,125 @@ +package schnorrkel + +import ( + "errors" + + "github.com/gtank/merlin" + r255 "github.com/gtank/ristretto255" +) + +// VerifyBatch batch verifies the given signatures +func VerifyBatch(transcripts []*merlin.Transcript, signatures []*Signature, pubkeys []*PublicKey) (bool, error) { + if len(transcripts) != len(signatures) || len(signatures) != len(pubkeys) || len(pubkeys) != len(transcripts) { + return false, errors.New("the number of transcripts, signatures, and public keys must be equal") + } + + zero := r255.NewElement().Zero() + + // compute H(R_i || P_i || m_i) + hs := make([]*r255.Scalar, len(transcripts)) + s := make([]r255.Scalar, len(transcripts)) + for i, t := range transcripts { + t.AppendMessage([]byte("proto-name"), []byte("Schnorr-sig")) + pubc := pubkeys[i].Compress() + t.AppendMessage([]byte("sign:pk"), pubc[:]) + t.AppendMessage([]byte("sign:R"), signatures[i].R.Encode([]byte{})) + + h := t.ExtractBytes([]byte("sign:c"), 64) + s[i] = *r255.NewScalar() + hs[i] = &s[i] + hs[i].FromUniformBytes(h) + } + + // compute ∑ P_i H(R_i || P_i || m_i) + ps := make([]*r255.Element, len(pubkeys)) + for i, p := range pubkeys { + ps[i] = p.key + } + + phs := r255.NewElement().MultiScalarMult(hs, ps) + + // compute ∑ s_0 ... s_n and ∑ R_0 ... R_n + ss := r255.NewScalar() + rs := r255.NewElement() + for _, s := range signatures { + ss = r255.NewScalar().Add(ss, s.S) + rs = r255.NewElement().Add(rs, s.R) + } + + // ∑ P_i H(R_i || P_i || m_i) + ∑ R_i + z := r255.NewElement().Add(phs, rs) + + // B ∑ s_i + sb := r255.NewElement().ScalarBaseMult(ss) + + // check -B ∑ s_i + ∑ P_i H(R_i || P_i || m_i) + ∑ R_i = 0 + sb_neg := r255.NewElement().Negate(sb) + res := r255.NewElement().Add(sb_neg, z) + + return res.Equal(zero) == 1, nil +} + +type BatchVerifier struct { + hs []*r255.Scalar // transcript scalar + ss *r255.Scalar // sum of signature.S: ∑ s_0 ... s_n + rs *r255.Element // sum of signature.R: ∑ R_0 ... R_n + pubkeys []*r255.Element +} + +func NewBatchVerifier() *BatchVerifier { + return &BatchVerifier{ + hs: []*r255.Scalar{}, + ss: r255.NewScalar(), + rs: r255.NewElement(), + pubkeys: []*r255.Element{}, + } +} + +func (v *BatchVerifier) Add(t *merlin.Transcript, sig *Signature, pubkey *PublicKey) error { + if t == nil { + return errors.New("provided transcript is nil") + } + + if sig == nil { + return errors.New("provided signature is nil") + } + + if pubkey == nil { + return errors.New("provided public key is nil") + } + + t.AppendMessage([]byte("proto-name"), []byte("Schnorr-sig")) + pubc := pubkey.Compress() + t.AppendMessage([]byte("sign:pk"), pubc[:]) + t.AppendMessage([]byte("sign:R"), sig.R.Encode([]byte{})) + + h := t.ExtractBytes([]byte("sign:c"), 64) + s := r255.NewScalar() + s.FromUniformBytes(h) + v.hs = append(v.hs, s) + + v.ss.Add(v.ss, sig.S) + v.rs.Add(v.rs, sig.R) + + v.pubkeys = append(v.pubkeys, pubkey.key) + return nil +} + +func (v *BatchVerifier) Verify() bool { + zero := r255.NewElement().Zero() + + // compute ∑ P_i H(R_i || P_i || m_i) + phs := r255.NewElement().MultiScalarMult(v.hs, v.pubkeys) + + // ∑ P_i H(R_i || P_i || m_i) + ∑ R_i + z := r255.NewElement().Add(phs, v.rs) + + // B ∑ s_i + sb := r255.NewElement().ScalarBaseMult(v.ss) + + // check -B ∑ s_i + ∑ P_i H(R_i || P_i || m_i) + ∑ R_i = 0 + sb_neg := r255.NewElement().Negate(sb) + res := r255.NewElement().Add(sb_neg, z) + + return res.Equal(zero) == 1 +} diff --git a/batch_test.go b/batch_test.go new file mode 100644 index 0000000..d8fdd73 --- /dev/null +++ b/batch_test.go @@ -0,0 +1,77 @@ +package schnorrkel + +import ( + "fmt" + "testing" + + "github.com/gtank/merlin" + "github.com/stretchr/testify/require" +) + +func TestBatchVerify(t *testing.T) { + num := 16 + transcripts := make([]*merlin.Transcript, num) + sigs := make([]*Signature, num) + pubkeys := make([]*PublicKey, num) + + for i := 0; i < num; i++ { + transcript := merlin.NewTranscript(fmt.Sprintf("hello_%d", i)) + priv, pub, err := GenerateKeypair() + require.NoError(t, err) + + sigs[i], err = priv.Sign(transcript) + require.NoError(t, err) + + transcripts[i] = merlin.NewTranscript(fmt.Sprintf("hello_%d", i)) + pubkeys[i] = pub + } + + ok, err := VerifyBatch(transcripts, sigs, pubkeys) + require.NoError(t, err) + require.True(t, ok) +} + +func TestBatchVerify_Bad(t *testing.T) { + num := 16 + transcripts := make([]*merlin.Transcript, num) + sigs := make([]*Signature, num) + pubkeys := make([]*PublicKey, num) + + for i := 0; i < num; i++ { + transcript := merlin.NewTranscript(fmt.Sprintf("hello_%d", i)) + priv, pub, err := GenerateKeypair() + require.NoError(t, err) + + sigs[i], err = priv.Sign(transcript) + require.NoError(t, err) + + transcripts[i] = merlin.NewTranscript(fmt.Sprintf("hello_%d", i)) + pubkeys[i] = pub + } + + transcripts[6] = merlin.NewTranscript(fmt.Sprintf("hello_%d", 999)) + ok, err := VerifyBatch(transcripts, sigs, pubkeys) + require.NoError(t, err) + require.False(t, ok) +} + +func TestBatchVerifier(t *testing.T) { + num := 16 + v := NewBatchVerifier() + + for i := 0; i < num; i++ { + transcript := merlin.NewTranscript(fmt.Sprintf("hello_%d", i)) + priv, pub, err := GenerateKeypair() + require.NoError(t, err) + + sig, err := priv.Sign(transcript) + require.NoError(t, err) + + transcript = merlin.NewTranscript(fmt.Sprintf("hello_%d", i)) + err = v.Add(transcript, sig, pub) + require.NoError(t, err) + } + + ok := v.Verify() + require.True(t, ok) +} diff --git a/keys.go b/keys.go index 008cc72..f5681a0 100644 --- a/keys.go +++ b/keys.go @@ -24,7 +24,7 @@ type SecretKey struct { nonce [32]byte } -// PublicKey is a member +// PublicKey is a field element type PublicKey struct { key *r255.Element }