diff --git a/crypto/batchverifier.c b/crypto/batchverifier.c new file mode 100644 index 0000000000..8fd233ab8f --- /dev/null +++ b/crypto/batchverifier.c @@ -0,0 +1,28 @@ +#include "sodium.h" +int ed25519_batch_wrapper(const unsigned char *messages1D, + const unsigned long long *mlen, + const unsigned char *publicKeys1D, + const unsigned char *signatures1D, + size_t num, + int *valid) { + int ret; + const unsigned char **messages2D, **publicKeys2D, **signatures2D; + messages2D = malloc(num * sizeof(unsigned char *)); + publicKeys2D = malloc(num * sizeof(unsigned char *)); + signatures2D = malloc(num * sizeof(unsigned char *)); + + // fill 2-D arrays for messages, pks, sigs from provided 1-D arrays + unsigned long long mpos = 0; + for (size_t i = 0; i < num; i++) { + messages2D[i] = &messages1D[mpos]; + mpos += mlen[i]; + publicKeys2D[i] = &publicKeys1D[i*crypto_sign_ed25519_PUBLICKEYBYTES]; + signatures2D[i] = &signatures1D[i*crypto_sign_ed25519_BYTES]; + } + ret = crypto_sign_ed25519_open_batch(messages2D, mlen, publicKeys2D, signatures2D, num, valid); + + free(messages2D); + free(publicKeys2D); + free(signatures2D); + return ret; +} diff --git a/crypto/batchverifier.go b/crypto/batchverifier.go index 9c14771bac..fa9bc9e02f 100644 --- a/crypto/batchverifier.go +++ b/crypto/batchverifier.go @@ -30,23 +30,28 @@ package crypto // #cgo windows,amd64 CFLAGS: -I${SRCDIR}/libs/windows/amd64/include // #cgo windows,amd64 LDFLAGS: ${SRCDIR}/libs/windows/amd64/lib/libsodium.a // #include -// #include "sodium.h" // enum { // sizeofPtr = sizeof(void*), // sizeofULongLong = sizeof(unsigned long long), // }; +// int ed25519_batch_wrapper(const unsigned char *messages1D, +// const unsigned long long *mlen, +// const unsigned char *publicKeys1D, +// const unsigned char *signatures1D, +// size_t num, +// int *valid_p); import "C" import ( "errors" - "runtime" "unsafe" ) // BatchVerifier enqueues signatures to be validated in batch. type BatchVerifier struct { - messages []Hashable // contains a slice of messages to be hashed. Each message is varible length - publicKeys []SignatureVerifier // contains a slice of public keys. Each individual public key is 32 bytes. - signatures []Signature // contains a slice of signatures keys. Each individual signature is 64 bytes. + messageHashReps []byte // contains a slice of concatenated bytes of the HashRep of the messages to be hashed. Each message is varible length + messageLens []C.ulonglong // the lengths of each message in messageHashReps + publicKeys []byte // contains a slice of concatenated public keys. Each individual public key is 32 bytes. + signatures []byte // contains a slice of concatenated signatures. Each individual signature is 64 bytes. } const minBatchVerifierAlloc = 16 @@ -75,38 +80,44 @@ func MakeBatchVerifierWithHint(hint int) *BatchVerifier { hint = minBatchVerifierAlloc } return &BatchVerifier{ - messages: make([]Hashable, 0, hint), - publicKeys: make([]SignatureVerifier, 0, hint), - signatures: make([]Signature, 0, hint), + messageHashReps: make([]byte, 0), // XXX can we get a better hint? + messageLens: make([]C.ulonglong, hint), + publicKeys: make([]byte, 0, hint*ed25519PublicKeySize), + signatures: make([]byte, 0, hint*ed25519SignatureSize), } } // EnqueueSignature enqueues a signature to be enqueued func (b *BatchVerifier) EnqueueSignature(sigVerifier SignatureVerifier, message Hashable, sig Signature) { // do we need to reallocate ? - if len(b.messages) == cap(b.messages) { + if len(b.messageLens) == cap(b.messageLens) { b.expand() } - b.messages = append(b.messages, message) - b.publicKeys = append(b.publicKeys, sigVerifier) - b.signatures = append(b.signatures, sig) + msgHashRep := HashRep(message) + b.messageHashReps = append(b.messageHashReps, msgHashRep...) + b.messageLens = append(b.messageLens, C.ulonglong(len(msgHashRep))) + b.publicKeys = append(b.publicKeys, sigVerifier[:]...) + b.signatures = append(b.signatures, sig[:]...) } func (b *BatchVerifier) expand() { - messages := make([]Hashable, len(b.messages), len(b.messages)*2) - publicKeys := make([]SignatureVerifier, len(b.publicKeys), len(b.publicKeys)*2) - signatures := make([]Signature, len(b.signatures), len(b.signatures)*2) - copy(messages, b.messages) + messageHashReps := make([]byte, len(b.messageHashReps), len(b.messageHashReps)*2) + messageLens := make([]C.ulonglong, len(b.messageLens), len(b.messageLens)*2) + publicKeys := make([]byte, len(b.publicKeys), len(b.publicKeys)*2*ed25519PublicKeySize) + signatures := make([]byte, len(b.signatures), len(b.signatures)*2*ed25519SignatureSize) + copy(messageLens, b.messageLens) + copy(messageHashReps, b.messageHashReps) copy(publicKeys, b.publicKeys) copy(signatures, b.signatures) - b.messages = messages + b.messageHashReps = messageHashReps + b.messageLens = messageLens b.publicKeys = publicKeys b.signatures = signatures } // GetNumberOfEnqueuedSignatures returns the number of signatures currently enqueued into the BatchVerifier func (b *BatchVerifier) GetNumberOfEnqueuedSignatures() int { - return len(b.messages) + return len(b.messageLens) } // Verify verifies that all the signatures are valid. in that case nil is returned @@ -123,11 +134,7 @@ func (b *BatchVerifier) VerifyWithFeedback() (failed []bool, err error) { if b.GetNumberOfEnqueuedSignatures() == 0 { return nil, nil } - var messages = make([][]byte, b.GetNumberOfEnqueuedSignatures()) - for i := range b.messages { - messages[i] = HashRep(b.messages[i]) - } - allValid, failed := batchVerificationImpl(messages, b.publicKeys, b.signatures) + allValid, failed := batchVerificationImpl(b.messageHashReps, b.messageLens, b.publicKeys, b.signatures) if allValid { return failed, nil } @@ -137,50 +144,23 @@ func (b *BatchVerifier) VerifyWithFeedback() (failed []bool, err error) { // batchVerificationImpl invokes the ed25519 batch verification algorithm. // it returns true if all the signatures were authentically signed by the owners // otherwise, returns false, and sets the indexes of the failed sigs in failed -func batchVerificationImpl(messages [][]byte, publicKeys []SignatureVerifier, signatures []Signature) (allSigsValid bool, failed []bool) { - - numberOfSignatures := len(messages) - - messagesAllocation := C.malloc(C.size_t(C.sizeofPtr * numberOfSignatures)) - messagesLenAllocation := C.malloc(C.size_t(C.sizeofULongLong * numberOfSignatures)) - publicKeysAllocation := C.malloc(C.size_t(C.sizeofPtr * numberOfSignatures)) - signaturesAllocation := C.malloc(C.size_t(C.sizeofPtr * numberOfSignatures)) - valid := C.malloc(C.size_t(C.sizeof_int * numberOfSignatures)) - - defer func() { - // release staging memory - C.free(messagesAllocation) - C.free(messagesLenAllocation) - C.free(publicKeysAllocation) - C.free(signaturesAllocation) - C.free(valid) - }() - - // load all the data pointers into the array pointers. - for i := 0; i < numberOfSignatures; i++ { - *(*uintptr)(unsafe.Pointer(uintptr(messagesAllocation) + uintptr(i*C.sizeofPtr))) = uintptr(unsafe.Pointer(&messages[i][0])) - *(*C.ulonglong)(unsafe.Pointer(uintptr(messagesLenAllocation) + uintptr(i*C.sizeofULongLong))) = C.ulonglong(len(messages[i])) - *(*uintptr)(unsafe.Pointer(uintptr(publicKeysAllocation) + uintptr(i*C.sizeofPtr))) = uintptr(unsafe.Pointer(&publicKeys[i][0])) - *(*uintptr)(unsafe.Pointer(uintptr(signaturesAllocation) + uintptr(i*C.sizeofPtr))) = uintptr(unsafe.Pointer(&signatures[i][0])) - } +func batchVerificationImpl(messageHashReps []byte, messageLens []C.ulonglong, publicKeys []byte, signatures []byte) (allSigsValid bool, failed []bool) { + numberOfSignatures := len(messageLens) + + valid := make([]C.int, numberOfSignatures) // call the batch verifier - allValid := C.crypto_sign_ed25519_open_batch( - (**C.uchar)(unsafe.Pointer(messagesAllocation)), - (*C.ulonglong)(unsafe.Pointer(messagesLenAllocation)), - (**C.uchar)(unsafe.Pointer(publicKeysAllocation)), - (**C.uchar)(unsafe.Pointer(signaturesAllocation)), - C.size_t(len(messages)), - (*C.int)(unsafe.Pointer(valid))) - - runtime.KeepAlive(messages) - runtime.KeepAlive(publicKeys) - runtime.KeepAlive(signatures) + allValid := C.ed25519_batch_wrapper( + (*C.uchar)(&messageHashReps[0]), + (*C.ulonglong)(&messageLens[0]), + (*C.uchar)(&publicKeys[0]), + (*C.uchar)(&signatures[0]), + C.size_t(numberOfSignatures), + (*C.int)(&valid[0])) failed = make([]bool, numberOfSignatures) for i := 0; i < numberOfSignatures; i++ { - cint := *(*C.int)(unsafe.Pointer(uintptr(valid) + uintptr(i*C.sizeof_int))) - failed[i] = (cint == 0) + failed[i] = (valid[i] == 0) } return allValid == 0, failed } diff --git a/crypto/batchverifier_test.go b/crypto/batchverifier_test.go index c572503ff3..c3768c5d6d 100644 --- a/crypto/batchverifier_test.go +++ b/crypto/batchverifier_test.go @@ -145,6 +145,14 @@ func BenchmarkBatchVerifierBig(b *testing.B) { } } +func (b *BatchVerifier) getSignature(i int) Signature { + if i > len(b.messageLens) { + panic("getSignature for i greater than length of messages") + } + sigbuf := b.signatures[i*ed25519SignatureSize : (i+1)*ed25519SignatureSize] + return *(*[ed25519SignatureSize]byte)(sigbuf) +} + // BenchmarkBatchVerifierBigWithInvalid builds over BenchmarkBatchVerifierBig by introducing // invalid sigs to even numbered batch sizes. This shows the impact of invalid sigs on the // performance. Basically, all the gains from batching disappear. @@ -171,7 +179,7 @@ func BenchmarkBatchVerifierBigWithInvalid(b *testing.B) { failed, err := bv.VerifyWithFeedback() if err != nil { for i, f := range failed { - if bv.signatures[i] == badSig { + if bv.getSignature(i) == badSig { require.True(b, f) } else { require.False(b, f) diff --git a/crypto/curve25519.go b/crypto/curve25519.go index 58950a3de3..64c1e5217e 100644 --- a/crypto/curve25519.go +++ b/crypto/curve25519.go @@ -70,10 +70,17 @@ func init() { type Seed ed25519Seed /* Classical signatures */ -type ed25519Signature [64]byte -type ed25519PublicKey [32]byte -type ed25519PrivateKey [64]byte -type ed25519Seed [32]byte +const ( + ed25519SignatureSize = 64 + ed25519PublicKeySize = 32 + ed25519PrivateKeySize = 64 + ed25519SeedSize = 32 +) + +type ed25519Signature [ed25519SignatureSize]byte +type ed25519PublicKey [ed25519PublicKeySize]byte +type ed25519PrivateKey [ed25519PrivateKeySize]byte +type ed25519Seed [ed25519SeedSize]byte // MasterDerivationKey is used to derive ed25519 keys for use in wallets type MasterDerivationKey [masterDerivationKeyLenBytes]byte diff --git a/crypto/onetimesig.go b/crypto/onetimesig.go index 344fd33f77..a9496398c8 100644 --- a/crypto/onetimesig.go +++ b/crypto/onetimesig.go @@ -16,6 +16,7 @@ package crypto +import "C" import ( "encoding/binary" "fmt" @@ -319,11 +320,29 @@ func (v OneTimeSignatureVerifier) Verify(id OneTimeSignatureIdentifier, message Batch: id.Batch, } - allValid, _ := batchVerificationImpl( - [][]byte{HashRep(batchID), HashRep(offsetID), HashRep(message)}, - []PublicKey{PublicKey(v), PublicKey(batchID.SubKeyPK), PublicKey(offsetID.SubKeyPK)}, - []Signature{Signature(sig.PK2Sig), Signature(sig.PK1Sig), Signature(sig.Sig)}, - ) + // Get the hash representations of the batch ID, offset ID, and message. + batchIDHashRep := HashRep(batchID) + offsetIDHashRep := HashRep(offsetID) + messageHashRep := HashRep(message) + // Append the hash representations and lengths to slices for batch verification. + msgHashReps := make([]byte, 0) + msgLens := make([]C.ulonglong, 0, 3) + msgHashReps = append(msgHashReps, batchIDHashRep...) + msgLens = append(msgLens, C.ulonglong(len(batchIDHashRep))) + msgHashReps = append(msgHashReps, offsetIDHashRep...) + msgLens = append(msgLens, C.ulonglong(len(offsetIDHashRep))) + msgHashReps = append(msgHashReps, messageHashRep...) + msgLens = append(msgLens, C.ulonglong(len(messageHashRep))) + // Append the public keys and signatures to slices for batch verification. + pks := make([]byte, 0, ed25519PublicKeySize*3) + pks = append(pks, v[:]...) + pks = append(pks, batchID.SubKeyPK[:]...) + pks = append(pks, offsetID.SubKeyPK[:]...) + sigs := make([]byte, 0, ed25519SignatureSize*3) + sigs = append(sigs, sig.PK2Sig[:]...) + sigs = append(sigs, sig.PK1Sig[:]...) + sigs = append(sigs, sig.Sig[:]...) + allValid, _ := batchVerificationImpl(msgHashReps, msgLens, pks, sigs) return allValid }