Skip to content
This repository has been archived by the owner on Sep 14, 2023. It is now read-only.

Commit

Permalink
Merge tag 'v0.19.1' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
jkawamoto committed Feb 4, 2021
2 parents 65324af + eb7b110 commit 58c80f2
Show file tree
Hide file tree
Showing 20 changed files with 382 additions and 7,171 deletions.
174 changes: 174 additions & 0 deletions ed25519hash/batch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package ed25519hash

import (
"crypto/ed25519"
"crypto/sha512"

"filippo.io/edwards25519"
"lukechampine.com/frand"
)

// VerifyBatch verifies a set of signatures. This provides a speedup of roughly
// 2x compared to verifying the signatures individually. However, if
// verification fails, the caller cannot determine which signatures were invalid
// without resorting to individual verification.
func VerifyBatch(keys []ed25519.PublicKey, hashes [][32]byte, sigs [][]byte) bool {
// The batch verification equation from the original Ed25519 paper is:
//
// [-sum(z_i * s_i)]B + sum([z_i]R_i) + sum([z_i * k_i]A_i) = 0
//
// where:
// - A_i is the verification key;
// - R_i is the signature's R value;
// - s_i is the signature's s value;
// - k_i is the hash of the message and other data;
// - z_i is a random 128-bit scalar.
//
// However, this can produce inconsistent results in the presence of
// adversarial signatures (signatures with nonzero torsion components). To
// guard against this, we multiply the whole equation by the cofactor. See
// https://hdevalence.ca/blog/2020-10-04-its-25519am for more details.

// Ultimately, we'll be computing the summation via VarTimeMultiScalarMult,
// which takes two slices: a []*Scalar and a []*Point. So we need those
// slices to contain:
//
// scalars: -sum(z_i * s_i), z_0, z_1, ... z_0*k_0, z_1*k_1, ...
// points: B, R_0, R_1, ... A_0, A_1, ...
//
// As an optimization, we allocate all of the scalar and point values
// up-front, rather than allocating each slice element individually. We also
// split these slices up into their various components to make things a bit
// more readable.
svals := make([]edwards25519.Scalar, 1+len(sigs)+len(keys))
scalars := make([]*edwards25519.Scalar, 1+len(sigs)+len(keys))
for i := range scalars {
scalars[i] = &svals[i]
}
Bcoeff := scalars[0] // z_i * s_i
Rcoeffs := scalars[1:][:len(sigs)] // z_i
Acoeffs := scalars[1+len(sigs):] // z_i * k_i

pvals := make([]edwards25519.Point, 1+len(sigs)+len(keys))
points := make([]*edwards25519.Point, 1+len(sigs)+len(keys))
for i := range points {
points[i] = &pvals[i]
}
B := points[0]
Rs := points[1:][:len(sigs)]
As := points[1+len(sigs):]

// First, set B and decompress all points R_i and A_i.
B.Set(edwards25519.NewGeneratorPoint())
for i, sig := range sigs {
if len(sig) != ed25519.SignatureSize || sig[63]&224 != 0 {
return false
} else if _, err := Rs[i].SetBytes(sig[:32]); err != nil {
return false
}
}
for i, pub := range keys {
if l := len(pub); l != ed25519.PublicKeySize {
return false
} else if _, err := As[i].SetBytes(pub); err != nil {
return false
}
}

// Next, generate the random 128-bit coefficients z_i.
buf := make([]byte, 32)
for i := range Rcoeffs {
frand.Read(buf[:16])
Rcoeffs[i].SetCanonicalBytes(buf)
}

// Compute the coefficient for B.
for i, sig := range sigs {
s, err := new(edwards25519.Scalar).SetCanonicalBytes(sig[32:])
if err != nil {
return false
}
Bcoeff.MultiplyAdd(Rcoeffs[i], s, Bcoeff) // Bcoeff += z_i * s_i
}
Bcoeff.Negate(Bcoeff) // this term is subtracted in the summation

// Compute the coefficients for each A_i.
buf = make([]byte, 96)
for i := range Acoeffs {
copy(buf[:32], sigs[i][:32])
copy(buf[32:], keys[i])
copy(buf[64:], hashes[i][:])
hram := sha512.Sum512(buf)
k := new(edwards25519.Scalar).SetUniformBytes(hram[:])
Acoeffs[i].Multiply(Rcoeffs[i], k)
}

// Multiply all the points by their coefficients, sum the results, and
// multiply by the cofactor.
sum := new(edwards25519.Point).VarTimeMultiScalarMult(scalars, points)
sum.MultByCofactor(sum)
return sum.Equal(edwards25519.NewIdentityPoint()) == 1
}

// VerifySingleKeyBatch verifies a set of signatures that were all produced by
// the same key. This provides a speedup of roughly 4x compared to verifying the
// signatures individually. However, if verification fails, the caller cannot
// determine which signatures were invalid without resorting to individual
// verification.
func VerifySingleKeyBatch(pub ed25519.PublicKey, hashes [][32]byte, sigs [][]byte) bool {
// Since we only have one A point, we can accumulate all of its coefficients
// together. That is, instead of:
//
// sum([z_i * k_i]A_i)
//
// we compute:
//
// [sum(z_i * k_i)]A

svals := make([]edwards25519.Scalar, 1+len(sigs)+1)
scalars := make([]*edwards25519.Scalar, 1+len(sigs)+1)
for i := range scalars {
scalars[i] = &svals[i]
}
Bcoeff := scalars[0]
Rcoeffs := scalars[1:][:len(sigs)]
Acoeff := scalars[1+len(sigs)]
pvals := make([]edwards25519.Point, 1+len(sigs)+1)
points := make([]*edwards25519.Point, 1+len(sigs)+1)
for i := range points {
points[i] = &pvals[i]
}
points[0].Set(edwards25519.NewGeneratorPoint())
Rs := points[1:][:len(sigs)]
A := points[1+len(sigs)]
if l := len(pub); l != ed25519.PublicKeySize {
return false
} else if _, err := A.SetBytes(pub); err != nil {
return false
}
for i, sig := range sigs {
if len(sig) != ed25519.SignatureSize || sig[63]&224 != 0 {
return false
} else if _, err := Rs[i].SetBytes(sig[:32]); err != nil {
return false
}
s, err := new(edwards25519.Scalar).SetCanonicalBytes(sig[32:])
if err != nil {
return false
}
buf := make([]byte, 96)
frand.Read(buf[:16])
Rcoeffs[i].SetCanonicalBytes(buf[:32])
Bcoeff.MultiplyAdd(Rcoeffs[i], s, Bcoeff)
copy(buf[:32], sig[:32])
copy(buf[32:], pub)
copy(buf[64:], hashes[i][:])
hram := sha512.Sum512(buf)
k := new(edwards25519.Scalar).SetUniformBytes(hram[:])
Acoeff.MultiplyAdd(Rcoeffs[i], k, Acoeff)
}
Bcoeff.Negate(Bcoeff)
sum := new(edwards25519.Point).VarTimeMultiScalarMult(scalars, points)
sum.MultByCofactor(sum)
return sum.Equal(edwards25519.NewIdentityPoint()) == 1
}
119 changes: 119 additions & 0 deletions ed25519hash/batch_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package ed25519hash

import (
"crypto/ed25519"
"fmt"
"testing"

"lukechampine.com/frand"
)

func TestVerifyBatch(t *testing.T) {
keys := make([]ed25519.PublicKey, 10)
hashes := make([][32]byte, len(keys))
sigs := make([][]byte, len(keys))
for i := range keys {
pub, priv, _ := ed25519.GenerateKey(nil)
keys[i] = pub
hashes[i] = frand.Entropy256()
sigs[i] = Sign(priv, hashes[i])
if !Verify(pub, hashes[i], sigs[i]) {
t.Fatal("individual sig failed verification")
}
}
if !VerifyBatch(keys, hashes, sigs) {
t.Fatal("signature set failed batch verification")
}

// corrupt one key/hash/sig and check that verification fails
keys[0][0] ^= 1
if VerifyBatch(keys, hashes, sigs) {
t.Error("corrupted key passed batch verification")
}
keys[0][0] ^= 1
hashes[0][0] ^= 1
if VerifyBatch(keys, hashes, sigs) {
t.Error("corrupted hash passed batch verification")
}
hashes[0][0] ^= 1
sigs[0][0] ^= 1
if VerifyBatch(keys, hashes, sigs) {
t.Error("corrupted sig passed batch verification")
}
}

func TestVerifySingleKeyBatch(t *testing.T) {
pub, priv, _ := ed25519.GenerateKey(nil)
hashes := make([][32]byte, 10)
sigs := make([][]byte, len(hashes))
for i := range sigs {
hashes[i] = frand.Entropy256()
sigs[i] = Sign(priv, hashes[i])
if !Verify(pub, hashes[i], sigs[i]) {
t.Fatal("individual sig failed verification")
}
}
if !VerifySingleKeyBatch(pub, hashes, sigs) {
t.Fatal("signature set failed batch verification")
}

// corrupt key/hash/sig and check that verification fails
pub[0] ^= 1
if VerifySingleKeyBatch(pub, hashes, sigs) {
t.Error("corrupted key passed batch verification")
}
pub[0] ^= 1
hashes[0][0] ^= 1
if VerifySingleKeyBatch(pub, hashes, sigs) {
t.Error("corrupted hash passed batch verification")
}
hashes[0][0] ^= 1
sigs[0][0] ^= 1
if VerifySingleKeyBatch(pub, hashes, sigs) {
t.Error("corrupted sig passed batch verification")
}
}

func BenchmarkVerifyBatch(b *testing.B) {
for _, n := range []int{1, 8, 64, 1024} {
b.Run(fmt.Sprint(n), func(b *testing.B) {
b.ReportAllocs()
keys := make([]ed25519.PublicKey, n)
hashes := make([][32]byte, len(keys))
sigs := make([][]byte, len(keys))
for i := range keys {
pub, priv, _ := ed25519.GenerateKey(nil)
keys[i] = pub
hashes[i] = frand.Entropy256()
sigs[i] = Sign(priv, hashes[i])
}
// NOTE: dividing by n so that metrics are per-signature
for i := 0; i < b.N/n; i++ {
if !VerifyBatch(keys, hashes, sigs) {
b.Fatal("signature set failed batch verification")
}
}
})
}
}

func BenchmarkVerifySingleKeyBatch(b *testing.B) {
pub, priv, _ := ed25519.GenerateKey(nil)
for _, n := range []int{1, 8, 64, 1024} {
b.Run(fmt.Sprint(n), func(b *testing.B) {
b.ReportAllocs()
hashes := make([][32]byte, n)
sigs := make([][]byte, n)
for i := range sigs {
hashes[i] = frand.Entropy256()
sigs[i] = Sign(priv, hashes[i])
}
// NOTE: dividing by n so that metrics are per-signature
for i := 0; i < b.N/n; i++ {
if !VerifySingleKeyBatch(pub, hashes, sigs) {
b.Fatal("signature set failed batch verification")
}
}
})
}
}
61 changes: 21 additions & 40 deletions ed25519hash/ed25519.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
"crypto/sha512"
"strconv"

"filippo.io/edwards25519"
"gitlab.com/NebulousLabs/Sia/crypto"
"lukechampine.com/us/ed25519hash/internal/edwards25519"
)

// Verify reports whether sig is a valid signature of hash by pub.
Expand All @@ -22,32 +22,26 @@ func Verify(pub ed25519.PublicKey, hash crypto.Hash, sig []byte) bool {
return false
}

var A edwards25519.ExtendedGroupElement
var publicKeyBytes [32]byte
copy(publicKeyBytes[:], pub)
if !A.FromBytes(&publicKeyBytes) {
A, err := new(edwards25519.Point).SetBytes(pub)
if err != nil {
return false
}
edwards25519.FeNeg(&A.X, &A.X)
edwards25519.FeNeg(&A.T, &A.T)
A.Negate(A)

buf := make([]byte, 96)
copy(buf[:32], sig[:32])
copy(buf[32:], pub)
copy(buf[64:], hash[:])
digest := sha512.Sum512(buf)
hramDigest := sha512.Sum512(buf)
hramDigestReduced := new(edwards25519.Scalar).SetUniformBytes(hramDigest[:])

var hReduced [32]byte
edwards25519.ScReduce(&hReduced, &digest)

var R edwards25519.ProjectiveGroupElement
var b [32]byte
copy(b[:], sig[32:])
edwards25519.GeDoubleScalarMultVartime(&R, &hReduced, &A, &b)
b, err := new(edwards25519.Scalar).SetCanonicalBytes(sig[32:])
if err != nil {
return false
}

var checkR [32]byte
R.ToBytes(&checkR)
return bytes.Equal(sig[:32], checkR[:])
encodedR := new(edwards25519.Point).VarTimeDoubleScalarBaseMult(hramDigestReduced, A, b).Bytes()
return bytes.Equal(sig[:32], encodedR)
}

// Sign signs a hash with priv.
Expand All @@ -61,40 +55,27 @@ func sign(signature []byte, priv ed25519.PrivateKey, hash crypto.Hash) []byte {
panic("ed25519: bad private key length: " + strconv.Itoa(l))
}

digest1 := sha512.Sum512(priv[:32])

var expandedSecretKey [32]byte
copy(expandedSecretKey[:], digest1[:32])
expandedSecretKey[0] &= 248
expandedSecretKey[31] &= 63
expandedSecretKey[31] |= 64
keyDigest := sha512.Sum512(priv[:32])
expandedSecretKey := new(edwards25519.Scalar).SetBytesWithClamping(keyDigest[:32])

buf := make([]byte, 96)
copy(buf[:32], digest1[32:])
copy(buf[:32], keyDigest[32:])
copy(buf[32:], hash[:])
messageDigest := sha512.Sum512(buf[:64])

var messageDigestReduced [32]byte
edwards25519.ScReduce(&messageDigestReduced, &messageDigest)
var R edwards25519.ExtendedGroupElement
edwards25519.GeScalarMultBase(&R, &messageDigestReduced)

var encodedR [32]byte
R.ToBytes(&encodedR)
messageDigestReduced := new(edwards25519.Scalar).SetUniformBytes(messageDigest[:])
encodedR := new(edwards25519.Point).ScalarBaseMult(messageDigestReduced).Bytes()

copy(buf[:32], encodedR[:])
copy(buf[32:], priv[32:])
copy(buf[64:], hash[:])
hramDigest := sha512.Sum512(buf[:96])
hramDigestReduced := new(edwards25519.Scalar).SetUniformBytes(hramDigest[:])

var hramDigestReduced [32]byte
edwards25519.ScReduce(&hramDigestReduced, &hramDigest)

var s [32]byte
edwards25519.ScMulAdd(&s, &hramDigestReduced, &expandedSecretKey, &messageDigestReduced)
s := hramDigestReduced.MultiplyAdd(hramDigestReduced, expandedSecretKey, messageDigestReduced)

copy(signature[:32], encodedR[:])
copy(signature[32:], s[:])
copy(signature[:32], encodedR)
copy(signature[32:], s.Bytes())
return signature
}

Expand Down
Loading

0 comments on commit 58c80f2

Please sign in to comment.