From 0795529a6ba1578c8ad61a3586151b942b8d8542 Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Fri, 12 Sep 2025 15:16:11 -0400 Subject: [PATCH 01/11] add gobatchverifier implementation --- crypto/batchverifier.go | 52 +- crypto/batchverifier_bench_test.go | 126 ++ crypto/batchverifier_test.go | 113 +- crypto/gobatchverifier.go | 216 ++++ crypto/gobatchverifier_test.go | 1123 +++++++++++++++++ .../crypto_sign/ed25519/ref10/batch.c | 2 +- crypto/onetimesig.go | 11 +- go.mod | 2 + go.sum | 4 + tools/block-generator/go.mod | 2 + tools/block-generator/go.sum | 4 + 11 files changed, 1615 insertions(+), 40 deletions(-) create mode 100644 crypto/batchverifier_bench_test.go create mode 100644 crypto/gobatchverifier.go create mode 100644 crypto/gobatchverifier_test.go diff --git a/crypto/batchverifier.go b/crypto/batchverifier.go index 65b2febeaa..b3dc5c46a2 100644 --- a/crypto/batchverifier.go +++ b/crypto/batchverifier.go @@ -73,14 +73,34 @@ func ed25519_randombytes_unsafe(p unsafe.Pointer, len C.size_t) { const minBatchVerifierAlloc = 16 const useSingleVerifierDefault = true +// ed25519BatchVerifierFactory is the global singleton used for batch signature verification. +// By default it uses the libsodium implementation. This can be changed during initialization +// (e.g., by the config package when algod loads) to use the ed25519consensus implementation. +var ed25519BatchVerifierFactory func(hint int) BatchVerifier = makeLibsodiumBatchVerifier + +// SetEd25519BatchVerifier allows the config package to switch the implementation +// at startup based on configuration. Pass true to use ed25519consensus, false for libsodium. +func SetEd25519BatchVerifier(useEd25519Consensus bool) { + if useEd25519Consensus { + ed25519BatchVerifierFactory = makeEd25519ConsensusBatchVerifier + } else { + ed25519BatchVerifierFactory = makeLibsodiumBatchVerifier + } +} + // MakeBatchVerifier creates a BatchVerifier instance with the provided options. +// small-order A (public keys) while still rejecting non-canonical encodings. func MakeBatchVerifier() BatchVerifier { - return MakeBatchVerifierWithHint(minBatchVerifierAlloc) + return ed25519BatchVerifierFactory(minBatchVerifierAlloc) } -// MakeBatchVerifierWithHint creates a cgoBatchVerifier instance. This function pre-allocates -// amount of free space to enqueue signatures without expanding +// MakeBatchVerifierWithHint creates a BatchVerifier instance. This function pre-allocates +// amount of free space to enqueue signatures without expanding. func MakeBatchVerifierWithHint(hint int) BatchVerifier { + return ed25519BatchVerifierFactory(hint) +} + +func makeLibsodiumBatchVerifier(hint int) BatchVerifier { // preallocate enough storage for the expected usage. We will reallocate as needed. if hint < minBatchVerifierAlloc { hint = minBatchVerifierAlloc @@ -152,7 +172,7 @@ func (b *cgoBatchVerifier) VerifyWithFeedback() (failed []bool, err error) { } allValid, failed := cgoBatchVerificationImpl(messages, msgLengths, b.publicKeys, b.signatures) if allValid { - return failed, nil + return nil, nil } return failed, ErrBatchHasFailedSigs } @@ -170,7 +190,7 @@ func (b *cgoBatchVerifier) singleVerify() (failed []bool, err error) { if containsFailed { return failed, ErrBatchHasFailedSigs } - return failed, nil + return nil, nil } // cgoBatchVerificationImpl invokes the ed25519 batch verification algorithm. @@ -185,18 +205,26 @@ func cgoBatchVerificationImpl(messages []byte, msgLengths []uint64, publicKeys [ signatures2D := make([]*C.uchar, numberOfSignatures) // call the batch verifier + // Use unsafe.SliceData to safely get pointers to underlying arrays allValid := C.ed25519_batch_wrapper( - &messages2D[0], &publicKeys2D[0], &signatures2D[0], - (*C.uchar)(&messages[0]), - (*C.ulonglong)(&msgLengths[0]), - (*C.uchar)(&publicKeys[0][0]), - (*C.uchar)(&signatures[0][0]), + (**C.uchar)(unsafe.SliceData(messages2D)), + (**C.uchar)(unsafe.SliceData(publicKeys2D)), + (**C.uchar)(unsafe.SliceData(signatures2D)), + (*C.uchar)(unsafe.SliceData(messages)), + (*C.ulonglong)(unsafe.SliceData(msgLengths)), + (*C.uchar)(unsafe.SliceData(publicKeys[0][:])), + (*C.uchar)(unsafe.SliceData(signatures[0][:])), C.size_t(numberOfSignatures), - (*C.int)(&valid[0])) + (*C.int)(unsafe.SliceData(valid))) + + if allValid == 0 { // all signatures valid + return true, nil + } + // not all signatures valid, identify the failed signatures failed = make([]bool, numberOfSignatures) for i := 0; i < numberOfSignatures; i++ { failed[i] = (valid[i] == 0) } - return allValid == 0, failed + return false, failed } diff --git a/crypto/batchverifier_bench_test.go b/crypto/batchverifier_bench_test.go new file mode 100644 index 0000000000..f293d4cb75 --- /dev/null +++ b/crypto/batchverifier_bench_test.go @@ -0,0 +1,126 @@ +// Copyright (C) 2019-2025 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package crypto + +import ( + cryptorand "crypto/rand" + "io" + "testing" + + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" +) + +func randSignedMsg(t testing.TB, r io.Reader) (SignatureVerifier, Hashable, Signature) { + mlen := 100 + msg := TestingHashable{data: make([]byte, mlen)} + n, err := r.Read(msg.data) + require.NoError(t, err) + require.Equal(t, n, mlen) + var s Seed + n, err = r.Read(s[:]) + require.NoError(t, err) + require.Equal(t, 32, n) + secrets := GenerateSignatureSecrets(s) + return secrets.SignatureVerifier, msg, secrets.Sign(msg) +} + +// BenchmarkBatchVerifierImpls benchmarks different batch verification implementations +// with realistic batch sizes (100 batches of 64 signatures each) +func BenchmarkBatchVerifierImpls(b *testing.B) { + partitiontest.PartitionTest(b) + + bN := 100 + batchSize := 64 + msgs := make([][]Hashable, bN) + pks := make([][]SignatureVerifier, bN) + sigs := make([][]Signature, bN) + r := cryptorand.Reader + for i := 0; i < bN; i++ { + for j := 0; j < batchSize; j++ { + pk, msg, sig := randSignedMsg(b, r) + msgs[i] = append(msgs[i], msg) + pks[i] = append(pks[i], pk) + sigs[i] = append(sigs[i], sig) + } + } + + b.Log("running with", b.N, "signatures in", len(msgs), "batches of", batchSize, "signatures") + runImpl := func(b *testing.B, bv BatchVerifier, + msgs [][]Hashable, pks [][]SignatureVerifier, sigs [][]Signature) { + b.Logf("Running %T with %d batches", bv, min(b.N, len(msgs))) + b.StartTimer() + for i := 0; i < min(b.N, len(msgs)); i++ { + for j := range msgs[i] { + bv.EnqueueSignature(pks[i][j], msgs[i][j], sigs[i][j]) + } + require.NoError(b, bv.Verify()) + } + b.StopTimer() + } + b.StopTimer() + b.ResetTimer() + + b.Run("libsodium_single", func(b *testing.B) { + bv := makeLibsodiumBatchVerifier(batchSize) + bv.(*cgoBatchVerifier).useSingle = true + runImpl(b, bv, msgs, pks, sigs) + }) + b.Run("libsodium_batch", func(b *testing.B) { + bv := makeLibsodiumBatchVerifier(batchSize) + bv.(*cgoBatchVerifier).useSingle = false + runImpl(b, bv, msgs, pks, sigs) + }) + b.Run("ed25519consensus", func(b *testing.B) { + bv := makeEd25519ConsensusBatchVerifier(batchSize) + runImpl(b, bv, msgs, pks, sigs) + }) +} + +func BenchmarkCanonicalityCheck(b *testing.B) { + partitiontest.PartitionTest(b) + + const maxN = 10000 + pubkeys := make([]SignatureVerifier, maxN) + sigs := make([]Signature, maxN) + for i := 0; i < maxN; i++ { + var s Seed + RandBytes(s[:]) + sigSecrets := GenerateSignatureSecrets(s) + pubkeys[i] = sigSecrets.SignatureVerifier + msg := randString() + sigs[i] = sigSecrets.Sign(msg) + } + + b.Run("pubkey_check", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = isCanonicalPoint(pubkeys[i%maxN][:]) + } + }) + + b.Run("signature_R_check", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = isCanonicalPoint(sigs[i%maxN][:32]) + } + }) + + b.Run("both_checks", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = !isCanonicalPoint(pubkeys[i%maxN][:]) || !isCanonicalPoint(sigs[i%maxN][:32]) + } + }) +} diff --git a/crypto/batchverifier_test.go b/crypto/batchverifier_test.go index 6f3c5954fc..ac91c19023 100644 --- a/crypto/batchverifier_test.go +++ b/crypto/batchverifier_test.go @@ -27,10 +27,42 @@ import ( "github.com/algorand/go-algorand/test/partitiontest" ) +// runnableTB is an interface constraint for types that have both testing.TB methods and Run +type runnableTB[T any] interface { + testing.TB + Run(string, func(T)) bool +} + +// runBatchVerifierImpls runs testing.{T,B}.Run against 3 batch verifier implementations as subtests. +func runBatchVerifierImpls[T runnableTB[T]](tb T, runFunc func(T, func(int) BatchVerifier)) { + tb.Run("libsodium_single", func(t T) { + runFunc(t, func(hint int) BatchVerifier { + bv := makeLibsodiumBatchVerifier(hint) + bv.(*cgoBatchVerifier).useSingle = true + return bv + }) + }) + tb.Run("libsodium_batch", func(t T) { + runFunc(t, func(hint int) BatchVerifier { + bv := makeLibsodiumBatchVerifier(hint) + bv.(*cgoBatchVerifier).useSingle = false + return bv + }) + }) + tb.Run("ed25519consensus", func(t T) { + runFunc(t, func(hint int) BatchVerifier { + return makeEd25519ConsensusBatchVerifier(hint) + }) + }) +} + func TestBatchVerifierSingle(t *testing.T) { partitiontest.PartitionTest(t) + runBatchVerifierImpls(t, testBatchVerifierSingle) +} +func testBatchVerifierSingle(t *testing.T, makeBV func(int) BatchVerifier) { // test expected success - bv := MakeBatchVerifier() + bv := makeBV(0) msg := randString() var s Seed RandBytes(s[:]) @@ -40,7 +72,7 @@ func TestBatchVerifierSingle(t *testing.T) { require.NoError(t, bv.Verify()) // test expected failure - bv = MakeBatchVerifier() + bv = makeBV(0) msg = randString() RandBytes(s[:]) sigSecrets = GenerateSignatureSecrets(s) @@ -53,9 +85,12 @@ func TestBatchVerifierSingle(t *testing.T) { func TestBatchVerifierBulk(t *testing.T) { partitiontest.PartitionTest(t) + runBatchVerifierImpls(t, testBatchVerifierBulk) +} +func testBatchVerifierBulk(t *testing.T, makeBV func(int) BatchVerifier) { for i := 1; i < 64*2+3; i++ { n := i - bv := MakeBatchVerifierWithHint(n) + bv := makeBV(n) var s Seed for i := 0; i < n; i++ { @@ -68,13 +103,15 @@ func TestBatchVerifierBulk(t *testing.T) { require.Equal(t, n, bv.GetNumberOfEnqueuedSignatures()) require.NoError(t, bv.Verify()) } - } func TestBatchVerifierBulkWithExpand(t *testing.T) { partitiontest.PartitionTest(t) + runBatchVerifierImpls(t, testBatchVerifierBulkWithExpand) +} +func testBatchVerifierBulkWithExpand(t *testing.T, makeBV func(int) BatchVerifier) { n := 64 - bv := MakeBatchVerifier() + bv := makeBV(0) // Start with no hint to test expansion var s Seed RandBytes(s[:]) @@ -89,8 +126,11 @@ func TestBatchVerifierBulkWithExpand(t *testing.T) { func TestBatchVerifierWithInvalidSiganture(t *testing.T) { partitiontest.PartitionTest(t) + runBatchVerifierImpls(t, testBatchVerifierWithInvalidSignature) +} +func testBatchVerifierWithInvalidSignature(t *testing.T, makeBV func(int) BatchVerifier) { n := 64 - bv := MakeBatchVerifier() + bv := makeBV(0) var s Seed RandBytes(s[:]) @@ -111,8 +151,11 @@ func TestBatchVerifierWithInvalidSiganture(t *testing.T) { } func BenchmarkBatchVerifier(b *testing.B) { + runBatchVerifierImpls(b, benchmarkBatchVerifier) +} +func benchmarkBatchVerifier(b *testing.B, makeBV func(int) BatchVerifier) { c := makeCurve25519Secret() - bv := MakeBatchVerifierWithHint(1) + bv := makeBV(1) for i := 0; i < b.N; i++ { str := randString() bv.EnqueueSignature(c.SignatureVerifier, str, c.Sign(str)) @@ -125,9 +168,12 @@ func BenchmarkBatchVerifier(b *testing.B) { // BenchmarkBatchVerifierBig with b.N over 1000 will report the expected performance // gain as the batchsize increases. All sigs are valid. func BenchmarkBatchVerifierBig(b *testing.B) { + runBatchVerifierImpls(b, benchmarkBatchVerifierBig) +} +func benchmarkBatchVerifierBig(b *testing.B, makeBV func(int) BatchVerifier) { c := makeCurve25519Secret() for batchSize := 1; batchSize <= 96; batchSize++ { - bv := MakeBatchVerifierWithHint(batchSize) + bv := makeBV(batchSize) for i := 0; i < batchSize; i++ { str := randString() bv.EnqueueSignature(c.SignatureVerifier, str, c.Sign(str)) @@ -149,16 +195,23 @@ func BenchmarkBatchVerifierBig(b *testing.B) { // invalid sigs to even numbered batch sizes. This shows the impact of invalid sigs on the // performance. Basically, all the gains from batching disappear. func BenchmarkBatchVerifierBigWithInvalid(b *testing.B) { + runBatchVerifierImpls(b, benchmarkBatchVerifierBigWithInvalid) +} +func benchmarkBatchVerifierBigWithInvalid(b *testing.B, makeBV func(int) BatchVerifier) { c := makeCurve25519Secret() badSig := Signature{} for batchSize := 1; batchSize <= 96; batchSize++ { - bv := MakeBatchVerifierWithHint(batchSize) + bv := makeBV(batchSize) + sigs := make([]Signature, batchSize) for i := 0; i < batchSize; i++ { str := randString() if batchSize%2 == 0 && (i == 0 || rand.Float32() < 0.1) { bv.EnqueueSignature(c.SignatureVerifier, str, badSig) + sigs[i] = badSig } else { - bv.EnqueueSignature(c.SignatureVerifier, str, c.Sign(str)) + sig := c.Sign(str) + bv.EnqueueSignature(c.SignatureVerifier, str, sig) + sigs[i] = sig } } b.Run(fmt.Sprintf("running batchsize %d", batchSize), func(b *testing.B) { @@ -170,13 +223,16 @@ func BenchmarkBatchVerifierBigWithInvalid(b *testing.B) { for x := 0; x < count; x++ { failed, err := bv.VerifyWithFeedback() if err != nil { + require.Len(b, failed, batchSize) for i, f := range failed { - if bv.(*cgoBatchVerifier).signatures[i] == badSig { + if sigs[i] == badSig { require.True(b, f) } else { require.False(b, f) } } + } else { + require.Nil(b, failed) } } }) @@ -185,22 +241,27 @@ func BenchmarkBatchVerifierBigWithInvalid(b *testing.B) { func TestEmpty(t *testing.T) { partitiontest.PartitionTest(t) - bv := MakeBatchVerifier() + runBatchVerifierImpls(t, testEmpty) +} +func testEmpty(t *testing.T, makeBV func(int) BatchVerifier) { + bv := makeBV(0) require.NoError(t, bv.Verify()) failed, err := bv.VerifyWithFeedback() require.NoError(t, err) - require.Empty(t, failed) + require.Nil(t, failed) } // TestBatchVerifierIndividualResults tests that VerifyWithFeedback // returns the correct failed signature indexes func TestBatchVerifierIndividualResults(t *testing.T) { partitiontest.PartitionTest(t) - + runBatchVerifierImpls(t, testBatchVerifierIndividualResults) +} +func testBatchVerifierIndividualResults(t *testing.T, makeBV func(int) BatchVerifier) { for i := 1; i < 64*2+3; i++ { n := i - bv := MakeBatchVerifierWithHint(n) + bv := makeBV(n) var s Seed badSigs := make([]bool, n, n) hasBadSig := false @@ -221,12 +282,13 @@ func TestBatchVerifierIndividualResults(t *testing.T) { failed, err := bv.VerifyWithFeedback() if hasBadSig { require.ErrorIs(t, err, ErrBatchHasFailedSigs) + require.Equal(t, len(badSigs), len(failed)) + for i := range badSigs { + require.Equal(t, badSigs[i], failed[i]) + } } else { require.NoError(t, err) - } - require.Equal(t, len(badSigs), len(failed)) - for i := range badSigs { - require.Equal(t, badSigs[i], failed[i]) + require.Nil(t, failed) } } } @@ -235,10 +297,12 @@ func TestBatchVerifierIndividualResults(t *testing.T) { // returns the correct failed signature indexes when all are valid func TestBatchVerifierIndividualResultsAllValid(t *testing.T) { partitiontest.PartitionTest(t) - + runBatchVerifierImpls(t, testBatchVerifierIndividualResultsAllValid) +} +func testBatchVerifierIndividualResultsAllValid(t *testing.T, makeBV func(int) BatchVerifier) { for i := 1; i < 64*2+3; i++ { n := i - bv := MakeBatchVerifierWithHint(n) + bv := makeBV(n) var s Seed for i := 0; i < n; i++ { msg := randString() @@ -250,10 +314,7 @@ func TestBatchVerifierIndividualResultsAllValid(t *testing.T) { require.Equal(t, n, bv.GetNumberOfEnqueuedSignatures()) failed, err := bv.VerifyWithFeedback() require.NoError(t, err) - require.Equal(t, bv.GetNumberOfEnqueuedSignatures(), len(failed)) - for _, f := range failed { - require.False(t, f) - } + require.Nil(t, failed) } } @@ -265,7 +326,7 @@ func TestBatchVerifierGC(t *testing.T) { t.Run("", func(t *testing.T) { t.Parallel() - bv := MakeBatchVerifierWithHint(n) + bv := makeLibsodiumBatchVerifier(n) var s Seed for i := 0; i < n; i++ { diff --git a/crypto/gobatchverifier.go b/crypto/gobatchverifier.go new file mode 100644 index 0000000000..43656ee260 --- /dev/null +++ b/crypto/gobatchverifier.go @@ -0,0 +1,216 @@ +// Copyright (C) 2019-2025 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package crypto + +import ( + "bytes" + + "github.com/hdevalence/ed25519consensus" +) + +// ed25519ConsensusVerifySingle performs single signature verification using ed25519consensus, +// with additional checks to reject non-canonical encodings and small-order public keys. +func ed25519ConsensusVerifySingle(publicKey []byte, message []byte, signature []byte) bool { + // Check for non-canonical public key or R (first 32 bytes of signature), and reject small-order public keys + if !isCanonicalPoint(publicKey) || !isCanonicalPoint(signature[:32]) || hasSmallOrder(publicKey) { + return false + } + + return ed25519consensus.Verify(publicKey, message, signature) +} + +type ed25519ConsensusVerifyEntry struct { + msgHashRep []byte + publicKey SignatureVerifier + signature Signature + failedChecks bool +} + +type ed25519ConsensusBatchVerifier struct { + entries []ed25519ConsensusVerifyEntry // used in VerifyWithFeedback to identify failed signatures + failedChecks bool // true if any entry failed non-canonical or small-order checks + bv ed25519consensus.BatchVerifier +} + +func makeEd25519ConsensusBatchVerifier(hint int) BatchVerifier { + if hint < minBatchVerifierAlloc { + hint = minBatchVerifierAlloc + } + return &ed25519ConsensusBatchVerifier{ + entries: make([]ed25519ConsensusVerifyEntry, 0, hint), + bv: ed25519consensus.NewPreallocatedBatchVerifier(hint), + } +} + +func (b *ed25519ConsensusBatchVerifier) EnqueueSignature(sigVerifier SignatureVerifier, message Hashable, sig Signature) { + msgHashRep := HashRep(message) + failedChecks := !isCanonicalPoint(sigVerifier[:]) || !isCanonicalPoint(sig[:32]) || hasSmallOrder(sigVerifier[:]) + + entry := ed25519ConsensusVerifyEntry{ + msgHashRep: msgHashRep, + publicKey: sigVerifier, + signature: sig, + failedChecks: failedChecks, + } + b.entries = append(b.entries, entry) + + if failedChecks { + b.failedChecks = true + } else { + b.bv.Add(sigVerifier[:], msgHashRep, sig[:]) + } +} + +func (b *ed25519ConsensusBatchVerifier) GetNumberOfEnqueuedSignatures() int { + return len(b.entries) +} + +func (b *ed25519ConsensusBatchVerifier) Verify() error { + if len(b.entries) == 0 { + return nil + } + + // Fail if any pre-checks failed or if batch verification fails + if b.failedChecks || !b.bv.Verify() { + return ErrBatchHasFailedSigs + } + return nil +} + +func (b *ed25519ConsensusBatchVerifier) VerifyWithFeedback() (failed []bool, err error) { + if len(b.entries) == 0 { + return nil, nil + } + + if !b.failedChecks && b.bv.Verify() { + return nil, nil + } + + failed = make([]bool, len(b.entries)) + for i := range b.entries { + if b.entries[i].failedChecks { + failed[i] = true + } else { + failed[i] = !ed25519ConsensusVerifySingle(b.entries[i].publicKey[:], b.entries[i].msgHashRep, b.entries[i].signature[:]) + } + } + + return failed, ErrBatchHasFailedSigs +} + +// Check that Y is canonical, using the succeed-fast algorithm from +// the "Taming the many EdDSAs" paper. +func isCanonicalY(p []byte) bool { + if len(p) != 32 { + return false + } + + if p[0] < 237 { + return true + } + for i := 1; i < 31; i++ { + if p[i] != 255 { + return true + } + } + return (p[31] | 128) != 255 +} + +// isCanonicalPoint is a variable-time check that returns true if the +// 32-byte ed25519 point encoding is canonical. +func isCanonicalPoint(p []byte) bool { + if !isCanonicalY(p) { + return false + } + + // Test for the two cases with a non-canonical sign bit not caught by the + // non-canonical y-coordinate check above. They are points number 9 and 10 + // from Table 1 of the "Taming the many EdDSAs" paper. + for _, invalidEncoding := range [][32]byte{ + { // (−0, 1) + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + }, + { // (-0, 2^255-20) + 0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + }, + } { + if bytes.Equal(p[:], invalidEncoding[:]) { + return false + } + } + + return true +} + +// from libsodium/crypto_core/ed25519/ref10/ed25519_ref10.c ge25519_has_small_order +var smallOrderPoints = [][32]byte{ + /* 0 (order 4) */ + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + /* 1 (order 1) */ + {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + /* 2707385501144840649318225287225658788936804267575313519463743609750303402022 + (order 8) */ + {0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4, + 0x89, 0xf2, 0xef, 0x98, 0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, + 0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x05}, + /* 55188659117513257062467267217118295137698188065244968500265048394206261417927 + (order 8) */ + {0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, + 0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, + 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0x7a}, + /* p-1 (order 2) */ + {0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}, + /* p (=0, order 4) */ + {0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}, + /* p+1 (=1, order 1) */ + {0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}, +} + +// hasSmallOrder checks if a point is in the small-order blacklist. +// Based on libsodium ge25519_has_small_order. +func hasSmallOrder(p []byte) bool { + if len(p) != 32 { + return false + } + + for _, point := range smallOrderPoints { + if !bytes.Equal(p[:31], point[:31]) { + continue + } + // For the last byte, ignore the sign bit (bit 7) + if (p[31] & 0x7f) == point[31] { + return true + } + } + return false +} diff --git a/crypto/gobatchverifier_test.go b/crypto/gobatchverifier_test.go new file mode 100644 index 0000000000..3b67ec85c1 --- /dev/null +++ b/crypto/gobatchverifier_test.go @@ -0,0 +1,1123 @@ +// Copyright (C) 2019-2025 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package crypto + +import ( + "bufio" + "crypto/ed25519" + "encoding/hex" + "fmt" + "math/rand" + "os" + "regexp" + "slices" + "strconv" + "strings" + "testing" + + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// ensure internal ed25519 types match the expected []byte lengths used by ed25519consensus package +func TestEd25519ConsensusBatchVerifierTypes(t *testing.T) { + partitiontest.PartitionTest(t) + + require.Len(t, ed25519PublicKey{}, ed25519.PublicKeySize) + require.Len(t, ed25519Signature{}, ed25519.SignatureSize) +} + +// Test vectors for 12 edge cases listed in Appendix C of "Taming the many EdDSAs" https://eprint.iacr.org/2020/1244 +// These are also checked in test_edge_cases in go-algorand/crypto/libsodium-fork/test/default/batch.c +func TestBatchVerifierLibsodiumEdgeCases(t *testing.T) { + partitiontest.PartitionTest(t) + + hexVecs := make([]batchTestCaseHex, len(tamingEdDSAsTestVectors)) + expectedFail := make([]bool, len(tamingEdDSAsTestVectors)) + for i, tc := range tamingEdDSAsTestVectors { + hexVecs[i] = batchTestCaseHex{pkHex: tc.pk, sigHex: tc.sig, msgHex: tc.msg} + expectedFail[i] = tc.expectedFail + } + runBatchVerifierImpls(t, func(t *testing.T, makeBV func(int) BatchVerifier) { + testBatchVectors(t, makeBV, decodeHexTestCases(t, hexVecs), expectedFail) + }) +} + +// Test vectors from "It's 255:19AM" blog post about ZIP-215 development, also used to create the +// 14x14 visualizations of different criteria across implementations in Henry de Valence's blog post +// "It's 255:19AM..." https://hdevalence.ca/blog/2020-10-04-its-25519am/ +func TestBatchVerifierEd25519ConsensusTestData(t *testing.T) { + partitiontest.PartitionTest(t) + + const msgHex = "5a63617368" // used for all signatures in this test + hexVecs := make([]batchTestCaseHex, len(ed25519consensusCases)) + for i, tc := range ed25519consensusCases { + hexVecs[i] = batchTestCaseHex{pkHex: tc.pk, sigHex: tc.sig, msgHex: msgHex} + } + // All of these test vectors should fail, matching our strict criteria + expectedFail := make([]bool, len(hexVecs)) + for i := range expectedFail { + expectedFail[i] = true + } + + runBatchVerifierImpls(t, func(t *testing.T, makeBV func(int) BatchVerifier) { + testBatchVectors(t, makeBV, decodeHexTestCases(t, hexVecs), expectedFail) + }) +} + +// Test vectors from unit tests for our libsodium- and ed25519-donna-based batch verification implementation +// introduced in PR #3031. +func TestBatchVerifierLibsodiumTestData(t *testing.T) { + partitiontest.PartitionTest(t) + + // read vectors hard-coded in test source file + const testVectorFile = "./libsodium-fork/test/default/batch.c" + const testVectorSize = 1025 + f, err := os.Open(testVectorFile) + if err != nil { + panic(err) + } + defer f.Close() + scanner := bufio.NewScanner(f) + + type testCase struct { + seed, pk, sig []byte + m string + } + var testCases []testCase + // each line is {{sk},{pk},{sig},"m"} where sk, pk, sig are comma-delimited lists of hex-encoded bytes + re := regexp.MustCompile(`\{\{(.*?)\},\{(.*?)\},\{(.*?)\},(.*?)\}`) + for i := 0; scanner.Scan(); i++ { + var tc testCase + line := scanner.Text() + matches := re.FindStringSubmatch(line) + if matches == nil || len(matches) != 5 { + continue + } + tc.seed = decodeCByteArray(matches[1], ed25519.SeedSize) + tc.pk = decodeCByteArray(matches[2], ed25519.PublicKeySize) + tc.sig = decodeCByteArray(matches[3], ed25519.SignatureSize) + tc.m, err = strconv.Unquote(matches[4]) + require.NoError(t, err) + testCases = append(testCases, tc) + } + t.Logf("loaded %d test vectors from %s", len(testCases), testVectorFile) + require.Len(t, testCases, testVectorSize, "not enough test vectors found") + + // check test data with libsodium-based ed25519Verify + for _, tc := range testCases { + require.True(t, ed25519Verify(ed25519PublicKey(tc.pk), []byte(tc.m), ed25519Signature(tc.sig))) + } + + // assert signing with test vector sk produces sig + for _, tc := range testCases { + pk, sk := ed25519GenerateKeySeed(ed25519Seed(tc.seed)) + require.Equal(t, tc.pk, []byte(pk[:])) + sig := ed25519Sign(sk, []byte(tc.m)) + require.Equal(t, tc.sig, []byte(sig[:])) + } + + // test different BatchVerifier implementations and batch sizes + testVectors := make([]batchTestCase, len(testCases)) + for i, tc := range testCases { + testVectors[i] = batchTestCase{pk: tc.pk, sig: tc.sig, msg: []byte(tc.m)} + } + expectedFail := make([]bool, len(testVectors)) // all should pass + runBatchVerifierImpls(t, func(t *testing.T, makeBV func(int) BatchVerifier) { + testBatchVectors(t, makeBV, testVectors, expectedFail) + }) +} + +// testBatchVectors tests a batch of signatures with expected pass/fail results using various batch sizes +func testBatchVectors(t *testing.T, makeBV func(int) BatchVerifier, testVectors []batchTestCase, expectedFail []bool) { + require.Len(t, expectedFail, len(testVectors)) + + // shuffle the vectors so we're not always testing in the same order + rand.Shuffle(len(testVectors), func(i, j int) { + testVectors[i], testVectors[j] = testVectors[j], testVectors[i] + expectedFail[i], expectedFail[j] = expectedFail[j], expectedFail[i] + }) + + // run a single batch of test vectors and compare to expected failures + runBatch := func(t *testing.T, vecs []batchTestCase, expFail []bool) { + bv := makeBV(len(vecs)) + + for _, tv := range vecs { + bv.EnqueueSignature(SignatureVerifier(tv.pk), noHashID(tv.msg), Signature(tv.sig)) + } + + failed, err := bv.VerifyWithFeedback() + if slices.Contains(expFail, true) { + require.Error(t, err) + require.NotNil(t, failed) + require.Len(t, failed, len(vecs)) + for i := range expFail { + assert.Equal(t, expFail[i], failed[i]) + } + } else { + require.NoError(t, err) + require.Nil(t, failed) + } + } + + n := len(testVectors) + batchSizes := []int{1, 2, 4, 8, 16, 32, 64, 100, 128, 256, 512, 1024, n} + for _, batchSize := range batchSizes { + if batchSize > n { + continue + } + t.Run(fmt.Sprintf("batchSize=%d", batchSize), func(t *testing.T) { + vectorBatches := splitBatches(testVectors, batchSize) + failBatches := splitBatches(expectedFail, batchSize) + require.Equal(t, len(vectorBatches), len(failBatches)) + //t.Logf("Testing with batch size %d: %d total signatures in %d batches", batchSize, n, len(vectorBatches)) + + for i, batch := range vectorBatches { + batchExpectedFail := failBatches[i] + //t.Logf("Batch %d/%d: signatures [%d-%d), size=%d", i+1, len(vectorBatches), i*batchSize, i*batchSize+len(batch), len(batch)) + runBatch(t, batch, batchExpectedFail) + } + }) + } +} + +// splitBatches splits items into batches of the specified size +func splitBatches[T any](items []T, batchSize int) [][]T { + if batchSize <= 0 { + return nil + } + numBatches := len(items) / batchSize + if len(items)%batchSize != 0 { + numBatches++ + } + batches := make([][]T, numBatches) + + for i, item := range items { + batchIdx := i / batchSize + batches[batchIdx] = append(batches[batchIdx], item) + } + + return batches +} + +// decodeCByteArray decodes a string like "0x27,0x81," into a byte array of length n +func decodeCByteArray(hexList string, n int) []byte { + bytes := make([]byte, n) + words := strings.Split(hexList, ",") + // remove trailing empty string + if words[len(words)-1] == "" { + words = words[:len(words)-1] + } else { + panic("missing trailing comma") + } + if len(words) != n { + panic("wrong number of words") + } + for i, word := range words { + _, err := fmt.Sscanf(word, "0x%02x", &bytes[i]) + if err != nil { + panic(err) + } + } + return bytes +} + +type batchTestCaseHex struct{ pkHex, sigHex, msgHex string } +type batchTestCase struct{ pk, sig, msg []byte } + +// decodeHexTestCases converts hex-encoded test cases to byte arrays +func decodeHexTestCases(t *testing.T, hexCases []batchTestCaseHex) []batchTestCase { + cases := make([]batchTestCase, len(hexCases)) + for i, hc := range hexCases { + pk, err := hex.DecodeString(hc.pkHex) + require.NoError(t, err) + require.Len(t, pk, ed25519.PublicKeySize) + + sig, err := hex.DecodeString(hc.sigHex) + require.NoError(t, err) + require.Len(t, sig, ed25519.SignatureSize) + + msg, err := hex.DecodeString(hc.msgHex) + require.NoError(t, err) + + cases[i] = batchTestCase{pk: pk, sig: sig, msg: msg} + } + return cases +} + +// noHashID implements Hashable but returns an empty protocol.HashID for use +// with the test vectors, which should not be prefixed +type noHashID []byte + +func (n noHashID) ToBeHashed() (protocol.HashID, []byte) { return "", n } + +// Test vectors from Appendix C of "Taming the many EdDSAs" https://eprint.iacr.org/2020/1244 +var tamingEdDSAsTestVectors = []struct { + desc, msg, pk, sig string + expectedFail bool // Algorand-specific criteria +}{ + {"S = 0, small-order A, small-order R", + "8c93255d71dcab10e8f379c26200f3c7bd5f09d9bc3068d3ef4edeb4853022b6", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a0000000000000000000000000000000000000000000000000000000000000000", + true}, + {"0 < S < L, small-order A, mixed-order R", + "9bd9f44f4dcc75bd531b56b2cd280b0bb38fc1cd6d1230e14861d861de092e79", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa", + "f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43a5bb704786be79fc476f91d3f3f89b03984d8068dcf1bb7dfc6637b45450ac04", + true}, + {"0 < S < L, mixed-order A, small-order R", + "aebf3f2601a0c8c5d39cc7d8911642f740b78168218da8471772b35f9d35b9ab", + "f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa8c4bd45aecaca5b24fb97bc10ac27ac8751a7dfe1baff8b953ec9f5833ca260e", + false}, + {"0 < S < L, mixed-order A, mixed-order R", + "9bd9f44f4dcc75bd531b56b2cd280b0bb38fc1cd6d1230e14861d861de092e79", + "cdb267ce40c5cd45306fa5d2f29731459387dbf9eb933b7bd5aed9a765b88d4d", + "9046a64750444938de19f227bb80485e92b83fdb4b6506c160484c016cc1852f87909e14428a7a1d62e9f22f3d3ad7802db02eb2e688b6c52fcd6648a98bd009", + false}, + {"0 < S < L, mixed-order A, mixed-order R, SB != R + hA", + "e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec4011eaccd55b53f56c", + "cdb267ce40c5cd45306fa5d2f29731459387dbf9eb933b7bd5aed9a765b88d4d", + "160a1cb0dc9c0258cd0a7d23e94d8fa878bcb1925f2c64246b2dee1796bed5125ec6bc982a269b723e0668e540911a9a6a58921d6925e434ab10aa7940551a09", + false}, + {`0 < S < L, mixed-order A, L-order R, SB != R + hA ("#5 fails any cofactored verification that pre-reduces scalar 8h")`, + "e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec4011eaccd55b53f56c", + "cdb267ce40c5cd45306fa5d2f29731459387dbf9eb933b7bd5aed9a765b88d4d", + "21122a84e0b5fca4052f5b1235c80a537878b38f3142356b2c2384ebad4668b7e40bc836dac0f71076f9abe3a53f9c03c1ceeeddb658d0030494ace586687405", + false}, + {"S > L, L-order A, L-order R", + "85e241a07d148b41e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec40", + "442aad9f089ad9e14647b1ef9099a1ff4798d78589e66f28eca69c11f582a623", + "e96f66be976d82e60150baecff9906684aebb1ef181f67a7189ac78ea23b6c0e547f7690a0e2ddcd04d87dbc3490dc19b3b3052f7ff0538cb68afb369ba3a514", + true}, + {`S >> L, L-order A, L-order R ("#7 fails bitwise tests that S > L")`, + "85e241a07d148b41e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec40", + "442aad9f089ad9e14647b1ef9099a1ff4798d78589e66f28eca69c11f582a623", + "8ce5b96c8f26d0ab6c47958c9e68b937104cd36e13c33566acd2fe8d38aa19427e71f98a4734e74f2f13f06f97c20d58cc3f54b8bd0d272f42b695dd7e89a8c2", + true}, + {`0 < S < L, mixed-order A, small-order R ("#8-9 have non-canonical R; implementations that reduce R before hashing will accept #8 and reject #9, while those that do not will reject #8 and accept #9")`, + "9bedc267423725d473888631ebf45988bad3db83851ee85c85e241a07d148b41", + "f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03be9678ac102edcd92b0210bb34d7428d12ffc5df5f37e359941266a4e35f0f", + true}, + {`0 < S < L, mixed-order A, small-order R ("#8-9 have non-canonical R; implementations that reduce R before hashing will accept #8 and reject #9, while those that do not will reject #8 and accept #9")`, + "9bedc267423725d473888631ebf45988bad3db83851ee85c85e241a07d148b41", + "f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffca8c5b64cd208982aa38d4936621a4775aa233aa0505711d8fdcfdaa943d4908", + true}, + {`0 < S < L, small-order A, mixed-order R ("#10-11 have a non-canonical A; implementations that reduce A before hashing will accept #10 and reject #11, while those that do not will reject #10 and accept #11")`, + "e96b7021eb39c1a163b6da4e3093dcd3f21387da4cc4572be588fafae23c155b", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "a9d55260f765261eb9b84e106f665e00b867287a761990d7135963ee0a7d59dca5bb704786be79fc476f91d3f3f89b03984d8068dcf1bb7dfc6637b45450ac04", + true}, + {`0 < S < L, small-order A, mixed-order R ("#10-11 have a non-canonical A; implementations that reduce A before hashing will accept #10 and reject #11, while those that do not will reject #10 and accept #11")`, + "39a591f5321bbe07fd5a23dc2f39d025d74526615746727ceefd6e82ae65c06f", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "a9d55260f765261eb9b84e106f665e00b867287a761990d7135963ee0a7d59dca5bb704786be79fc476f91d3f3f89b03984d8068dcf1bb7dfc6637b45450ac04", + true}, +} + +// "It's 255:19AM" blog post test vectors, from the ed25519consensus package +var ed25519consensusCases = [196]struct{ pk, sig string }{ + { + "0100000000000000000000000000000000000000000000000000000000000000", + "01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0100000000000000000000000000000000000000000000000000000000000000", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0100000000000000000000000000000000000000000000000000000000000000", + "00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0100000000000000000000000000000000000000000000000000000000000000", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc050000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0100000000000000000000000000000000000000000000000000000000000000", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0100000000000000000000000000000000000000000000000000000000000000", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc850000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0100000000000000000000000000000000000000000000000000000000000000", + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0100000000000000000000000000000000000000000000000000000000000000", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0100000000000000000000000000000000000000000000000000000000000000", + "01000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0100000000000000000000000000000000000000000000000000000000000000", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0100000000000000000000000000000000000000000000000000000000000000", + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0100000000000000000000000000000000000000000000000000000000000000", + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0100000000000000000000000000000000000000000000000000000000000000", + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0100000000000000000000000000000000000000000000000000000000000000", + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a", + "01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + { + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a", + "00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + }, + { + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc050000000000000000000000000000000000000000000000000000000000000000", + }, + { + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc850000000000000000000000000000000000000000000000000000000000000000", + }, + { + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a", + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + { + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a", + "01000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + }, + { + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a", + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a", + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a", + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a", + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0000000000000000000000000000000000000000000000000000000000000080", + "01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0000000000000000000000000000000000000000000000000000000000000080", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0000000000000000000000000000000000000000000000000000000000000080", + "00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0000000000000000000000000000000000000000000000000000000000000080", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc050000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0000000000000000000000000000000000000000000000000000000000000080", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0000000000000000000000000000000000000000000000000000000000000080", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc850000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0000000000000000000000000000000000000000000000000000000000000080", + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0000000000000000000000000000000000000000000000000000000000000080", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0000000000000000000000000000000000000000000000000000000000000080", + "01000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0000000000000000000000000000000000000000000000000000000000000080", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0000000000000000000000000000000000000000000000000000000000000080", + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0000000000000000000000000000000000000000000000000000000000000080", + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0000000000000000000000000000000000000000000000000000000000000080", + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0000000000000000000000000000000000000000000000000000000000000080", + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05", + "01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + { + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05", + "00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + }, + { + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc050000000000000000000000000000000000000000000000000000000000000000", + }, + { + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc850000000000000000000000000000000000000000000000000000000000000000", + }, + { + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05", + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + { + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05", + "01000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + }, + { + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05", + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05", + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05", + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05", + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + { + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + }, + { + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc050000000000000000000000000000000000000000000000000000000000000000", + }, + { + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc850000000000000000000000000000000000000000000000000000000000000000", + }, + { + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + { + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "01000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + }, + { + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85", + "01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + { + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85", + "00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + }, + { + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc050000000000000000000000000000000000000000000000000000000000000000", + }, + { + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc850000000000000000000000000000000000000000000000000000000000000000", + }, + { + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85", + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + { + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85", + "01000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + }, + { + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85", + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85", + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85", + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85", + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0000000000000000000000000000000000000000000000000000000000000000", + "01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0000000000000000000000000000000000000000000000000000000000000000", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0000000000000000000000000000000000000000000000000000000000000000", + "00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0000000000000000000000000000000000000000000000000000000000000000", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc050000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0000000000000000000000000000000000000000000000000000000000000000", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0000000000000000000000000000000000000000000000000000000000000000", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc850000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0000000000000000000000000000000000000000000000000000000000000000", + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0000000000000000000000000000000000000000000000000000000000000000", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0000000000000000000000000000000000000000000000000000000000000000", + "01000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0000000000000000000000000000000000000000000000000000000000000000", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0000000000000000000000000000000000000000000000000000000000000000", + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0000000000000000000000000000000000000000000000000000000000000000", + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0000000000000000000000000000000000000000000000000000000000000000", + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0000000000000000000000000000000000000000000000000000000000000000", + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa", + "01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + { + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa", + "00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + }, + { + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc050000000000000000000000000000000000000000000000000000000000000000", + }, + { + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc850000000000000000000000000000000000000000000000000000000000000000", + }, + { + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa", + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + { + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa", + "01000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + }, + { + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa", + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa", + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa", + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa", + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0100000000000000000000000000000000000000000000000000000000000080", + "01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0100000000000000000000000000000000000000000000000000000000000080", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0100000000000000000000000000000000000000000000000000000000000080", + "00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0100000000000000000000000000000000000000000000000000000000000080", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc050000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0100000000000000000000000000000000000000000000000000000000000080", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0100000000000000000000000000000000000000000000000000000000000080", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc850000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0100000000000000000000000000000000000000000000000000000000000080", + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0100000000000000000000000000000000000000000000000000000000000080", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0100000000000000000000000000000000000000000000000000000000000080", + "01000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0100000000000000000000000000000000000000000000000000000000000080", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0100000000000000000000000000000000000000000000000000000000000080", + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0100000000000000000000000000000000000000000000000000000000000080", + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0100000000000000000000000000000000000000000000000000000000000080", + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "0100000000000000000000000000000000000000000000000000000000000080", + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + { + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + }, + { + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc050000000000000000000000000000000000000000000000000000000000000000", + }, + { + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc850000000000000000000000000000000000000000000000000000000000000000", + }, + { + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + { + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "01000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + }, + { + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + { + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + }, + { + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc050000000000000000000000000000000000000000000000000000000000000000", + }, + { + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc850000000000000000000000000000000000000000000000000000000000000000", + }, + { + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + { + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "01000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + }, + { + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + { + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + }, + { + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc050000000000000000000000000000000000000000000000000000000000000000", + }, + { + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc850000000000000000000000000000000000000000000000000000000000000000", + }, + { + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + { + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "01000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + }, + { + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + { + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + }, + { + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc050000000000000000000000000000000000000000000000000000000000000000", + }, + { + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc850000000000000000000000000000000000000000000000000000000000000000", + }, + { + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + { + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "01000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + }, + { + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + { + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + }, + { + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc050000000000000000000000000000000000000000000000000000000000000000", + }, + { + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc850000000000000000000000000000000000000000000000000000000000000000", + }, + { + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + { + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "01000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + }, + { + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + }, +} diff --git a/crypto/libsodium-fork/src/libsodium/crypto_sign/ed25519/ref10/batch.c b/crypto/libsodium-fork/src/libsodium/crypto_sign/ed25519/ref10/batch.c index 83f93a857d..030c64592d 100644 --- a/crypto/libsodium-fork/src/libsodium/crypto_sign/ed25519/ref10/batch.c +++ b/crypto/libsodium-fork/src/libsodium/crypto_sign/ed25519/ref10/batch.c @@ -118,7 +118,7 @@ heap_get_top2(batch_heap *heap, heap_index_t *max1, heap_index_t *max2, size_t l /* */ void ge25519_multi_scalarmult_vartime_final(ge25519_p3 *r, ge25519_p3 *point, sc25519 scalar) { - const sc25519_element_t topbit = ((sc25519_element_t)1 << (SC25519_LIMB_SIZE - 1)); + const sc25519_element_t topbit = ((sc25519_element_t)1 << (SC25519_BITS_PER_LIMB - 1)); size_t limb = limb128bits; sc25519_element_t flag; ge25519_p1p1 p1p1_r; diff --git a/crypto/onetimesig.go b/crypto/onetimesig.go index df22138c23..9d5d44379d 100644 --- a/crypto/onetimesig.go +++ b/crypto/onetimesig.go @@ -23,6 +23,7 @@ import ( "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-deadlock" + "github.com/hdevalence/ed25519consensus" ) // A OneTimeSignature is a cryptographic signature that is produced a limited @@ -375,7 +376,7 @@ func (v OneTimeSignatureVerifier) Verify(id OneTimeSignatureIdentifier, message } if !useSingleVerifierDefault { - return v.batchVerify(batchID, offsetID, message, sig) + return v.batchVerifyEd25519Consensus(batchID, offsetID, message, sig) } if !ed25519Verify(ed25519PublicKey(v), HashRep(batchID), sig.PK2Sig) { @@ -412,6 +413,14 @@ func (v OneTimeSignatureVerifier) batchVerify(batchID OneTimeSignatureSubkeyBatc return allValid } +func (v OneTimeSignatureVerifier) batchVerifyEd25519Consensus(batchID OneTimeSignatureSubkeyBatchID, offsetID OneTimeSignatureSubkeyOffsetID, message Hashable, sig OneTimeSignature) bool { + bv := ed25519consensus.NewPreallocatedBatchVerifier(3) + bv.Add(v[:], HashRep(batchID), sig.PK2Sig[:]) + bv.Add(batchID.SubKeyPK[:], HashRep(offsetID), sig.PK1Sig[:]) + bv.Add(offsetID.SubKeyPK[:], HashRep(message), sig.Sig[:]) + return bv.Verify() +} + // DeleteBeforeFineGrained deletes ephemeral keys before (but not including) the given id. func (s *OneTimeSignatureSecrets) DeleteBeforeFineGrained(current OneTimeSignatureIdentifier, numKeysPerBatch uint64) { s.mu.Lock() diff --git a/go.mod b/go.mod index c3ccdf86d4..f50967dc09 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,7 @@ require ( github.com/google/go-querystring v1.0.0 github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 + github.com/hdevalence/ed25519consensus v0.2.0 github.com/ipfs/go-log v1.0.5 github.com/ipfs/go-log/v2 v2.5.1 github.com/jmoiron/sqlx v1.2.0 @@ -63,6 +64,7 @@ require ( ) require ( + filippo.io/edwards25519 v1.0.0 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect diff --git a/go.sum b/go.sum index 0d7b065bca..26ef319213 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= +filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= +filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -259,6 +261,8 @@ github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uG github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= +github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= diff --git a/tools/block-generator/go.mod b/tools/block-generator/go.mod index 59961ea55a..567085d74f 100644 --- a/tools/block-generator/go.mod +++ b/tools/block-generator/go.mod @@ -18,6 +18,7 @@ require ( ) require ( + filippo.io/edwards25519 v1.0.0 // indirect github.com/DataDog/zstd v1.5.2 // indirect github.com/algorand/falcon v0.1.0 // indirect github.com/algorand/go-sumhash v0.1.0 // indirect @@ -64,6 +65,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/hdevalence/ed25519consensus v0.2.0 // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/ipfs/boxo v0.24.3 // indirect diff --git a/tools/block-generator/go.sum b/tools/block-generator/go.sum index d7caa73f45..c24cb3a494 100644 --- a/tools/block-generator/go.sum +++ b/tools/block-generator/go.sum @@ -6,6 +6,8 @@ dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= +filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= +filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -236,6 +238,8 @@ github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uG github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= +github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= From c1acd6c749e3c9b5780ccce3c4216e2d4d2bc463 Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Fri, 12 Sep 2025 15:32:30 -0400 Subject: [PATCH 02/11] fix comment --- crypto/batchverifier.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crypto/batchverifier.go b/crypto/batchverifier.go index b3dc5c46a2..9e0dd262a9 100644 --- a/crypto/batchverifier.go +++ b/crypto/batchverifier.go @@ -88,8 +88,7 @@ func SetEd25519BatchVerifier(useEd25519Consensus bool) { } } -// MakeBatchVerifier creates a BatchVerifier instance with the provided options. -// small-order A (public keys) while still rejecting non-canonical encodings. +// MakeBatchVerifier creates a BatchVerifier instance. func MakeBatchVerifier() BatchVerifier { return ed25519BatchVerifierFactory(minBatchVerifierAlloc) } From b809fa78a8223edef4dd07a4f7e698714d0d9bf5 Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Sun, 14 Sep 2025 20:39:32 -0400 Subject: [PATCH 03/11] fix shuffling in testBatchVectors --- crypto/gobatchverifier_test.go | 58 ++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/crypto/gobatchverifier_test.go b/crypto/gobatchverifier_test.go index 3b67ec85c1..f2858f1297 100644 --- a/crypto/gobatchverifier_test.go +++ b/crypto/gobatchverifier_test.go @@ -45,7 +45,7 @@ func TestEd25519ConsensusBatchVerifierTypes(t *testing.T) { // Test vectors for 12 edge cases listed in Appendix C of "Taming the many EdDSAs" https://eprint.iacr.org/2020/1244 // These are also checked in test_edge_cases in go-algorand/crypto/libsodium-fork/test/default/batch.c -func TestBatchVerifierLibsodiumEdgeCases(t *testing.T) { +func TestBatchVerifierTamingEdDSAsEdgeCases(t *testing.T) { partitiontest.PartitionTest(t) hexVecs := make([]batchTestCaseHex, len(tamingEdDSAsTestVectors)) @@ -75,7 +75,6 @@ func TestBatchVerifierEd25519ConsensusTestData(t *testing.T) { for i := range expectedFail { expectedFail[i] = true } - runBatchVerifierImpls(t, func(t *testing.T, makeBV func(int) BatchVerifier) { testBatchVectors(t, makeBV, decodeHexTestCases(t, hexVecs), expectedFail) }) @@ -148,53 +147,58 @@ func TestBatchVerifierLibsodiumTestData(t *testing.T) { func testBatchVectors(t *testing.T, makeBV func(int) BatchVerifier, testVectors []batchTestCase, expectedFail []bool) { require.Len(t, expectedFail, len(testVectors)) - // shuffle the vectors so we're not always testing in the same order - rand.Shuffle(len(testVectors), func(i, j int) { - testVectors[i], testVectors[j] = testVectors[j], testVectors[i] - expectedFail[i], expectedFail[j] = expectedFail[j], expectedFail[i] - }) - // run a single batch of test vectors and compare to expected failures runBatch := func(t *testing.T, vecs []batchTestCase, expFail []bool) { bv := makeBV(len(vecs)) - for _, tv := range vecs { bv.EnqueueSignature(SignatureVerifier(tv.pk), noHashID(tv.msg), Signature(tv.sig)) } - failed, err := bv.VerifyWithFeedback() - if slices.Contains(expFail, true) { + if slices.Contains(expFail, true) { // some failures expected require.Error(t, err) require.NotNil(t, failed) require.Len(t, failed, len(vecs)) for i := range expFail { assert.Equal(t, expFail[i], failed[i]) } - } else { + } else { // no failures expected require.NoError(t, err) require.Nil(t, failed) } } - n := len(testVectors) - batchSizes := []int{1, 2, 4, 8, 16, 32, 64, 100, 128, 256, 512, 1024, n} - for _, batchSize := range batchSizes { - if batchSize > n { - continue + // run all the test vectors in a single batch + t.Run("all", func(t *testing.T) { runBatch(t, testVectors, expectedFail) }) + + // split into multiple batches of different sizes, optionally shuffled + runBatchSizes := func(shuffle bool, vecs []batchTestCase, expFail []bool) { + if shuffle { + vecs, expFail = slices.Clone(vecs), slices.Clone(expFail) + rand.Shuffle(len(vecs), func(i, j int) { + vecs[i], vecs[j], expFail[i], expFail[j] = vecs[j], vecs[i], expFail[j], expFail[i] + }) } - t.Run(fmt.Sprintf("batchSize=%d", batchSize), func(t *testing.T) { - vectorBatches := splitBatches(testVectors, batchSize) - failBatches := splitBatches(expectedFail, batchSize) - require.Equal(t, len(vectorBatches), len(failBatches)) - //t.Logf("Testing with batch size %d: %d total signatures in %d batches", batchSize, n, len(vectorBatches)) - for i, batch := range vectorBatches { - batchExpectedFail := failBatches[i] - //t.Logf("Batch %d/%d: signatures [%d-%d), size=%d", i+1, len(vectorBatches), i*batchSize, i*batchSize+len(batch), len(batch)) - runBatch(t, batch, batchExpectedFail) + for _, batchSize := range []int{1, 2, 4, 8, 16, 32, 64, 100, 128, 256, 512, 1024} { + if batchSize > len(vecs) { + continue } - }) + t.Run(fmt.Sprintf("batchSize=%d", batchSize), func(t *testing.T) { + vectorBatches := splitBatches(vecs, batchSize) + failBatches := splitBatches(expFail, batchSize) + require.Equal(t, len(vectorBatches), len(failBatches)) + //t.Logf("Testing with batch size %d: %d total signatures in %d batches", batchSize, n, len(vectorBatches)) + for i, batch := range vectorBatches { + batchExpectedFail := failBatches[i] + //t.Logf("Batch %d/%d: signatures [%d-%d), size=%d", i+1, len(vectorBatches), i*batchSize, i*batchSize+len(batch), len(batch)) + runBatch(t, batch, batchExpectedFail) + } + }) + } } + + t.Run("unshuffled", func(t *testing.T) { runBatchSizes(false, testVectors, expectedFail) }) + t.Run("shuffled", func(t *testing.T) { runBatchSizes(true, testVectors, expectedFail) }) } // splitBatches splits items into batches of the specified size From f999d8b999db9802c773adb4b2d012c3aec14b68 Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Sun, 14 Sep 2025 21:16:02 -0400 Subject: [PATCH 04/11] fix lint --- crypto/onetimesig.go | 35 ++++++----------------------------- 1 file changed, 6 insertions(+), 29 deletions(-) diff --git a/crypto/onetimesig.go b/crypto/onetimesig.go index 9d5d44379d..420ae442e8 100644 --- a/crypto/onetimesig.go +++ b/crypto/onetimesig.go @@ -23,7 +23,6 @@ import ( "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-deadlock" - "github.com/hdevalence/ed25519consensus" ) // A OneTimeSignature is a cryptographic signature that is produced a limited @@ -376,7 +375,7 @@ func (v OneTimeSignatureVerifier) Verify(id OneTimeSignatureIdentifier, message } if !useSingleVerifierDefault { - return v.batchVerifyEd25519Consensus(batchID, offsetID, message, sig) + return v.batchVerify(batchID, offsetID, message, sig) } if !ed25519Verify(ed25519PublicKey(v), HashRep(batchID), sig.PK2Sig) { @@ -392,33 +391,11 @@ func (v OneTimeSignatureVerifier) Verify(id OneTimeSignatureIdentifier, message } func (v OneTimeSignatureVerifier) batchVerify(batchID OneTimeSignatureSubkeyBatchID, offsetID OneTimeSignatureSubkeyOffsetID, message Hashable, sig OneTimeSignature) bool { - // serialize encoded batchID, offsetID, message into a continuous memory buffer with the layout - // hashRep(batchID)... hashRep(offsetID)... hashRep(message)... - const estimatedSize = 256 - messageBuffer := make([]byte, 0, estimatedSize) - - messageBuffer = HashRepToBuff(batchID, messageBuffer) - batchIDLen := uint64(len(messageBuffer)) - messageBuffer = HashRepToBuff(offsetID, messageBuffer) - offsetIDLen := uint64(len(messageBuffer)) - batchIDLen - messageBuffer = HashRepToBuff(message, messageBuffer) - messageLen := uint64(len(messageBuffer)) - offsetIDLen - batchIDLen - msgLengths := []uint64{batchIDLen, offsetIDLen, messageLen} - allValid, _ := cgoBatchVerificationImpl( - messageBuffer, - msgLengths, - []PublicKey{PublicKey(v), PublicKey(batchID.SubKeyPK), PublicKey(offsetID.SubKeyPK)}, - []Signature{Signature(sig.PK2Sig), Signature(sig.PK1Sig), Signature(sig.Sig)}, - ) - return allValid -} - -func (v OneTimeSignatureVerifier) batchVerifyEd25519Consensus(batchID OneTimeSignatureSubkeyBatchID, offsetID OneTimeSignatureSubkeyOffsetID, message Hashable, sig OneTimeSignature) bool { - bv := ed25519consensus.NewPreallocatedBatchVerifier(3) - bv.Add(v[:], HashRep(batchID), sig.PK2Sig[:]) - bv.Add(batchID.SubKeyPK[:], HashRep(offsetID), sig.PK1Sig[:]) - bv.Add(offsetID.SubKeyPK[:], HashRep(message), sig.Sig[:]) - return bv.Verify() + bv := MakeBatchVerifierWithHint(3) + bv.EnqueueSignature(PublicKey(v), batchID, Signature(sig.PK2Sig)) + bv.EnqueueSignature(PublicKey(batchID.SubKeyPK), offsetID, Signature(sig.PK1Sig)) + bv.EnqueueSignature(PublicKey(offsetID.SubKeyPK), message, Signature(sig.Sig)) + return bv.Verify() == nil } // DeleteBeforeFineGrained deletes ephemeral keys before (but not including) the given id. From 9fad31773b4c5b5ee01903f75eadae0c37d5ce19 Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Wed, 17 Sep 2025 20:16:40 -0400 Subject: [PATCH 05/11] Apply suggestions from code review Co-authored-by: John Jannotti --- crypto/batchverifier.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/batchverifier.go b/crypto/batchverifier.go index 9e0dd262a9..d494f62c13 100644 --- a/crypto/batchverifier.go +++ b/crypto/batchverifier.go @@ -94,7 +94,7 @@ func MakeBatchVerifier() BatchVerifier { } // MakeBatchVerifierWithHint creates a BatchVerifier instance. This function pre-allocates -// amount of free space to enqueue signatures without expanding. +// space to enqueue signatures without expanding. func MakeBatchVerifierWithHint(hint int) BatchVerifier { return ed25519BatchVerifierFactory(hint) } From 1d306065f566c1d72edcdad02af4778b7c685cb8 Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Wed, 17 Sep 2025 22:17:45 -0400 Subject: [PATCH 06/11] add test and vectors based on go/src/crypto/ed25519/ed25519vectors_test.go --- crypto/gobatchverifier_test.go | 57 +++++++++++++++++++++++++ crypto/testdata/ed25519vectors.json.gz | Bin 0 -> 20322 bytes 2 files changed, 57 insertions(+) create mode 100644 crypto/testdata/ed25519vectors.json.gz diff --git a/crypto/gobatchverifier_test.go b/crypto/gobatchverifier_test.go index f2858f1297..ad94fb9855 100644 --- a/crypto/gobatchverifier_test.go +++ b/crypto/gobatchverifier_test.go @@ -18,8 +18,10 @@ package crypto import ( "bufio" + "compress/gzip" "crypto/ed25519" "encoding/hex" + "encoding/json" "fmt" "math/rand" "os" @@ -143,6 +145,61 @@ func TestBatchVerifierLibsodiumTestData(t *testing.T) { }) } +// based on TestEd25519Vectors from go/src/crypto/ed25519/ed25519vectors_test.go +// which uses test vectors from filippo.io/mostly-harmless/ed25519vectors +func TestBatchVerifierFilippoVectors(t *testing.T) { + var vectors []struct { + A, R, S, M string + Flags []string + } + f, err := os.Open("./testdata/ed25519vectors.json.gz") + require.NoError(t, err) + defer f.Close() + rd, err := gzip.NewReader(f) + require.NoError(t, err) + defer rd.Close() + err = json.NewDecoder(rd).Decode(&vectors) + require.NoError(t, err) + + expectedFail := make([]bool, len(vectors)) + hexVecs := make([]batchTestCaseHex, len(vectors)) + for i, v := range vectors { + for _, f := range v.Flags { + switch f { + case "LowOrderA": // reject small-order A + expectedFail[i] = true + case "NonCanonicalA", "NonCanonicalR": // reject non-canonical A or R + expectedFail[i] = true + case "LowOrderR": // small-order R allowed + case "LowOrderComponentR", "LowOrderComponentA": // torsion component allowed + case "LowOrderResidue": // cofactorless batch verification + default: + require.Fail(t, "unknown flag %q in test vector %d", f, i) + } + } + hexVecs[i] = batchTestCaseHex{pkHex: v.A, sigHex: v.R + v.S, msgHex: hex.EncodeToString([]byte(v.M))} + } + runBatchVerifierImpls(t, func(t *testing.T, makeBV func(int) BatchVerifier) { + testBatchVectors(t, makeBV, decodeHexTestCases(t, hexVecs), expectedFail) + }) + + // test isCanonicalPoint and hasSmallOrder against A and R + t.Run("ARchecks", func(t *testing.T) { + for _, v := range vectors { + A, err := hex.DecodeString(v.A) + require.NoError(t, err) + require.Equal(t, !slices.Contains(v.Flags, "NonCanonicalA"), isCanonicalPoint(A)) + require.Equal(t, slices.Contains(v.Flags, "LowOrderA"), hasSmallOrder(A)) + + R, err := hex.DecodeString(v.R) + require.NoError(t, err) + require.Equal(t, !slices.Contains(v.Flags, "NonCanonicalR"), isCanonicalPoint(R)) + require.Equal(t, slices.Contains(v.Flags, "LowOrderR"), hasSmallOrder(R)) + } + }) + +} + // testBatchVectors tests a batch of signatures with expected pass/fail results using various batch sizes func testBatchVectors(t *testing.T, makeBV func(int) BatchVerifier, testVectors []batchTestCase, expectedFail []bool) { require.Len(t, expectedFail, len(testVectors)) diff --git a/crypto/testdata/ed25519vectors.json.gz b/crypto/testdata/ed25519vectors.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..52605c9690b7af13df85d5d3d565a2b37bfdb867 GIT binary patch literal 20322 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 literal 0 HcmV?d00001 From 88a5b949040eecb00b462193ddcada2acd2adee7 Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Wed, 17 Sep 2025 22:39:53 -0400 Subject: [PATCH 07/11] CR fixes --- crypto/batchverifier.go | 2 +- crypto/batchverifier_bench_test.go | 21 +++++++++++---------- crypto/gobatchverifier.go | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/crypto/batchverifier.go b/crypto/batchverifier.go index d494f62c13..cd0168ae1f 100644 --- a/crypto/batchverifier.go +++ b/crypto/batchverifier.go @@ -101,7 +101,7 @@ func MakeBatchVerifierWithHint(hint int) BatchVerifier { func makeLibsodiumBatchVerifier(hint int) BatchVerifier { // preallocate enough storage for the expected usage. We will reallocate as needed. - if hint < minBatchVerifierAlloc { + if hint <= 0 { hint = minBatchVerifierAlloc } return &cgoBatchVerifier{ diff --git a/crypto/batchverifier_bench_test.go b/crypto/batchverifier_bench_test.go index f293d4cb75..8bcb9ff69b 100644 --- a/crypto/batchverifier_bench_test.go +++ b/crypto/batchverifier_bench_test.go @@ -44,13 +44,13 @@ func randSignedMsg(t testing.TB, r io.Reader) (SignatureVerifier, Hashable, Sign func BenchmarkBatchVerifierImpls(b *testing.B) { partitiontest.PartitionTest(b) - bN := 100 + numBatches := 100 batchSize := 64 - msgs := make([][]Hashable, bN) - pks := make([][]SignatureVerifier, bN) - sigs := make([][]Signature, bN) + msgs := make([][]Hashable, numBatches) + pks := make([][]SignatureVerifier, numBatches) + sigs := make([][]Signature, numBatches) r := cryptorand.Reader - for i := 0; i < bN; i++ { + for i := 0; i < numBatches; i++ { for j := 0; j < batchSize; j++ { pk, msg, sig := randSignedMsg(b, r) msgs[i] = append(msgs[i], msg) @@ -59,14 +59,15 @@ func BenchmarkBatchVerifierImpls(b *testing.B) { } } - b.Log("running with", b.N, "signatures in", len(msgs), "batches of", batchSize, "signatures") + b.Log("running with", b.N, "iterations using", len(msgs), "batches of", batchSize, "signatures") runImpl := func(b *testing.B, bv BatchVerifier, msgs [][]Hashable, pks [][]SignatureVerifier, sigs [][]Signature) { - b.Logf("Running %T with %d batches", bv, min(b.N, len(msgs))) + b.Logf("Running %T with %d iterations", bv, b.N) b.StartTimer() - for i := 0; i < min(b.N, len(msgs)); i++ { - for j := range msgs[i] { - bv.EnqueueSignature(pks[i][j], msgs[i][j], sigs[i][j]) + for i := 0; i < b.N; i++ { + batchIdx := i % numBatches + for j := range msgs[batchIdx] { + bv.EnqueueSignature(pks[batchIdx][j], msgs[batchIdx][j], sigs[batchIdx][j]) } require.NoError(b, bv.Verify()) } diff --git a/crypto/gobatchverifier.go b/crypto/gobatchverifier.go index 43656ee260..11f0b0621d 100644 --- a/crypto/gobatchverifier.go +++ b/crypto/gobatchverifier.go @@ -47,7 +47,7 @@ type ed25519ConsensusBatchVerifier struct { } func makeEd25519ConsensusBatchVerifier(hint int) BatchVerifier { - if hint < minBatchVerifierAlloc { + if hint <= 0 { hint = minBatchVerifierAlloc } return &ed25519ConsensusBatchVerifier{ From acc256d8d5400558f3f0b67ec26f338e896e670d Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Wed, 17 Sep 2025 22:46:00 -0400 Subject: [PATCH 08/11] CR fixes --- crypto/batchverifier_bench_test.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/crypto/batchverifier_bench_test.go b/crypto/batchverifier_bench_test.go index 8bcb9ff69b..63df5e8833 100644 --- a/crypto/batchverifier_bench_test.go +++ b/crypto/batchverifier_bench_test.go @@ -62,8 +62,7 @@ func BenchmarkBatchVerifierImpls(b *testing.B) { b.Log("running with", b.N, "iterations using", len(msgs), "batches of", batchSize, "signatures") runImpl := func(b *testing.B, bv BatchVerifier, msgs [][]Hashable, pks [][]SignatureVerifier, sigs [][]Signature) { - b.Logf("Running %T with %d iterations", bv, b.N) - b.StartTimer() + b.ResetTimer() for i := 0; i < b.N; i++ { batchIdx := i % numBatches for j := range msgs[batchIdx] { @@ -71,10 +70,7 @@ func BenchmarkBatchVerifierImpls(b *testing.B) { } require.NoError(b, bv.Verify()) } - b.StopTimer() } - b.StopTimer() - b.ResetTimer() b.Run("libsodium_single", func(b *testing.B) { bv := makeLibsodiumBatchVerifier(batchSize) From dd9c5305eebdfcaae2dbeac5581296c59822dc4d Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Wed, 17 Sep 2025 22:58:01 -0400 Subject: [PATCH 09/11] CR fixes --- crypto/gobatchverifier.go | 59 ++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 32 deletions(-) diff --git a/crypto/gobatchverifier.go b/crypto/gobatchverifier.go index 11f0b0621d..20ab92ed8a 100644 --- a/crypto/gobatchverifier.go +++ b/crypto/gobatchverifier.go @@ -140,23 +140,18 @@ func isCanonicalPoint(p []byte) bool { // Test for the two cases with a non-canonical sign bit not caught by the // non-canonical y-coordinate check above. They are points number 9 and 10 // from Table 1 of the "Taming the many EdDSAs" paper. - for _, invalidEncoding := range [][32]byte{ - { // (−0, 1) - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, - }, - { // (-0, 2^255-20) - 0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - }, - } { - if bytes.Equal(p[:], invalidEncoding[:]) { - return false - } + if bytes.Equal(p, []byte{ // (−0, 1) + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + }) || bytes.Equal(p, []byte{ // (-0, 2^255-20) + 0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + }) { + return false } return true @@ -164,40 +159,40 @@ func isCanonicalPoint(p []byte) bool { // from libsodium/crypto_core/ed25519/ref10/ed25519_ref10.c ge25519_has_small_order var smallOrderPoints = [][32]byte{ - /* 0 (order 4) */ - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0 (order 4) */ { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - /* 1 (order 1) */ - {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 1 (order 1) */ { + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* 2707385501144840649318225287225658788936804267575313519463743609750303402022 - (order 8) */ - {0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4, + (order 8) */{ + 0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98, 0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x05}, /* 55188659117513257062467267217118295137698188065244968500265048394206261417927 - (order 8) */ - {0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, + (order 8) */{ + 0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0x7a}, - /* p-1 (order 2) */ - {0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /* p-1 (order 2) */ { + 0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}, - /* p (=0, order 4) */ - {0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /* p (=0, order 4) */ { + 0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}, - /* p+1 (=1, order 1) */ - {0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /* p+1 (=1, order 1) */ { + 0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}, } // hasSmallOrder checks if a point is in the small-order blacklist. -// Based on libsodium ge25519_has_small_order. +// Based on libsodium ge25519_has_small_order, but this version is variable-time. func hasSmallOrder(p []byte) bool { if len(p) != 32 { return false From f317118dff61205851616d875e32e7f4362658d4 Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Thu, 18 Sep 2025 14:32:12 -0400 Subject: [PATCH 10/11] use [32]byte for hasSmallOrder and isCanonicalPoint --- crypto/batchverifier_bench_test.go | 6 +++--- crypto/gobatchverifier.go | 24 ++++++++---------------- crypto/gobatchverifier_test.go | 8 ++++---- 3 files changed, 15 insertions(+), 23 deletions(-) diff --git a/crypto/batchverifier_bench_test.go b/crypto/batchverifier_bench_test.go index 63df5e8833..c676a5265e 100644 --- a/crypto/batchverifier_bench_test.go +++ b/crypto/batchverifier_bench_test.go @@ -105,19 +105,19 @@ func BenchmarkCanonicalityCheck(b *testing.B) { b.Run("pubkey_check", func(b *testing.B) { for i := 0; i < b.N; i++ { - _ = isCanonicalPoint(pubkeys[i%maxN][:]) + _ = isCanonicalPoint(pubkeys[i%maxN]) } }) b.Run("signature_R_check", func(b *testing.B) { for i := 0; i < b.N; i++ { - _ = isCanonicalPoint(sigs[i%maxN][:32]) + _ = isCanonicalPoint([32]byte(sigs[i%maxN][:32])) } }) b.Run("both_checks", func(b *testing.B) { for i := 0; i < b.N; i++ { - _ = !isCanonicalPoint(pubkeys[i%maxN][:]) || !isCanonicalPoint(sigs[i%maxN][:32]) + _ = !isCanonicalPoint(pubkeys[i%maxN]) || !isCanonicalPoint([32]byte(sigs[i%maxN][:32])) } }) } diff --git a/crypto/gobatchverifier.go b/crypto/gobatchverifier.go index 20ab92ed8a..3c695eafa1 100644 --- a/crypto/gobatchverifier.go +++ b/crypto/gobatchverifier.go @@ -26,7 +26,7 @@ import ( // with additional checks to reject non-canonical encodings and small-order public keys. func ed25519ConsensusVerifySingle(publicKey []byte, message []byte, signature []byte) bool { // Check for non-canonical public key or R (first 32 bytes of signature), and reject small-order public keys - if !isCanonicalPoint(publicKey) || !isCanonicalPoint(signature[:32]) || hasSmallOrder(publicKey) { + if !isCanonicalPoint([32]byte(publicKey)) || !isCanonicalPoint([32]byte(signature[:32])) || hasSmallOrder([32]byte(publicKey)) { return false } @@ -58,7 +58,7 @@ func makeEd25519ConsensusBatchVerifier(hint int) BatchVerifier { func (b *ed25519ConsensusBatchVerifier) EnqueueSignature(sigVerifier SignatureVerifier, message Hashable, sig Signature) { msgHashRep := HashRep(message) - failedChecks := !isCanonicalPoint(sigVerifier[:]) || !isCanonicalPoint(sig[:32]) || hasSmallOrder(sigVerifier[:]) + failedChecks := !isCanonicalPoint(sigVerifier) || !isCanonicalPoint([32]byte(sig[:32])) || hasSmallOrder(sigVerifier) entry := ed25519ConsensusVerifyEntry{ msgHashRep: msgHashRep, @@ -114,11 +114,7 @@ func (b *ed25519ConsensusBatchVerifier) VerifyWithFeedback() (failed []bool, err // Check that Y is canonical, using the succeed-fast algorithm from // the "Taming the many EdDSAs" paper. -func isCanonicalY(p []byte) bool { - if len(p) != 32 { - return false - } - +func isCanonicalY(p [32]byte) bool { if p[0] < 237 { return true } @@ -132,7 +128,7 @@ func isCanonicalY(p []byte) bool { // isCanonicalPoint is a variable-time check that returns true if the // 32-byte ed25519 point encoding is canonical. -func isCanonicalPoint(p []byte) bool { +func isCanonicalPoint(p [32]byte) bool { if !isCanonicalY(p) { return false } @@ -140,17 +136,17 @@ func isCanonicalPoint(p []byte) bool { // Test for the two cases with a non-canonical sign bit not caught by the // non-canonical y-coordinate check above. They are points number 9 and 10 // from Table 1 of the "Taming the many EdDSAs" paper. - if bytes.Equal(p, []byte{ // (−0, 1) + if p == [32]byte{ // (−0, 1) 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, - }) || bytes.Equal(p, []byte{ // (-0, 2^255-20) + } || p == [32]byte{ // (-0, 2^255-20) 0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - }) { + } { return false } @@ -193,11 +189,7 @@ var smallOrderPoints = [][32]byte{ // hasSmallOrder checks if a point is in the small-order blacklist. // Based on libsodium ge25519_has_small_order, but this version is variable-time. -func hasSmallOrder(p []byte) bool { - if len(p) != 32 { - return false - } - +func hasSmallOrder(p [32]byte) bool { for _, point := range smallOrderPoints { if !bytes.Equal(p[:31], point[:31]) { continue diff --git a/crypto/gobatchverifier_test.go b/crypto/gobatchverifier_test.go index ad94fb9855..36ed799fab 100644 --- a/crypto/gobatchverifier_test.go +++ b/crypto/gobatchverifier_test.go @@ -188,13 +188,13 @@ func TestBatchVerifierFilippoVectors(t *testing.T) { for _, v := range vectors { A, err := hex.DecodeString(v.A) require.NoError(t, err) - require.Equal(t, !slices.Contains(v.Flags, "NonCanonicalA"), isCanonicalPoint(A)) - require.Equal(t, slices.Contains(v.Flags, "LowOrderA"), hasSmallOrder(A)) + require.Equal(t, !slices.Contains(v.Flags, "NonCanonicalA"), isCanonicalPoint([32]byte(A))) + require.Equal(t, slices.Contains(v.Flags, "LowOrderA"), hasSmallOrder([32]byte(A))) R, err := hex.DecodeString(v.R) require.NoError(t, err) - require.Equal(t, !slices.Contains(v.Flags, "NonCanonicalR"), isCanonicalPoint(R)) - require.Equal(t, slices.Contains(v.Flags, "LowOrderR"), hasSmallOrder(R)) + require.Equal(t, !slices.Contains(v.Flags, "NonCanonicalR"), isCanonicalPoint([32]byte(R))) + require.Equal(t, slices.Contains(v.Flags, "LowOrderR"), hasSmallOrder([32]byte(R))) } }) From 0e95eacbf0ea10432d7003df9a849a74cb9965f8 Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Thu, 18 Sep 2025 14:38:59 -0400 Subject: [PATCH 11/11] update ed25519ConsensusVerifySingle for [32]byte --- crypto/gobatchverifier.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crypto/gobatchverifier.go b/crypto/gobatchverifier.go index 3c695eafa1..46fcd0cae5 100644 --- a/crypto/gobatchverifier.go +++ b/crypto/gobatchverifier.go @@ -24,13 +24,13 @@ import ( // ed25519ConsensusVerifySingle performs single signature verification using ed25519consensus, // with additional checks to reject non-canonical encodings and small-order public keys. -func ed25519ConsensusVerifySingle(publicKey []byte, message []byte, signature []byte) bool { +func ed25519ConsensusVerifySingle(publicKey [32]byte, message []byte, signature [64]byte) bool { // Check for non-canonical public key or R (first 32 bytes of signature), and reject small-order public keys - if !isCanonicalPoint([32]byte(publicKey)) || !isCanonicalPoint([32]byte(signature[:32])) || hasSmallOrder([32]byte(publicKey)) { + if !isCanonicalPoint(publicKey) || !isCanonicalPoint([32]byte(signature[:32])) || hasSmallOrder(publicKey) { return false } - return ed25519consensus.Verify(publicKey, message, signature) + return ed25519consensus.Verify(publicKey[:], message, signature[:]) } type ed25519ConsensusVerifyEntry struct { @@ -105,7 +105,7 @@ func (b *ed25519ConsensusBatchVerifier) VerifyWithFeedback() (failed []bool, err if b.entries[i].failedChecks { failed[i] = true } else { - failed[i] = !ed25519ConsensusVerifySingle(b.entries[i].publicKey[:], b.entries[i].msgHashRep, b.entries[i].signature[:]) + failed[i] = !ed25519ConsensusVerifySingle(b.entries[i].publicKey, b.entries[i].msgHashRep, b.entries[i].signature) } }