diff --git a/.circleci/config.yml b/.circleci/config.yml index 72e5dfa43..dfff1b10f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,6 +16,14 @@ executors: AWS_REGION: us-east-1 commands: + checkout_with_submodules: + steps: + - checkout + - run: + name: "Pull Submodules" + command: | + git submodule init + git submodule update run_test: parameters: script_path: @@ -27,7 +35,7 @@ commands: name: "Restore source code cache" keys: - go-src-v1-{{ .Revision }} - - checkout + - checkout_with_submodules - restore_cache: name: "Restore go modules cache" keys: @@ -41,7 +49,7 @@ jobs: setup_dependencies: executor: golang steps: - - checkout + - checkout_with_submodules - restore_cache: name: "Restore go modules cache" keys: @@ -107,18 +115,23 @@ jobs: name: "Restore source code cache" keys: - go-src-v1-{{ .Revision }} - - checkout + - checkout_with_submodules - restore_cache: name: "Restore go module cache" keys: - go-mod-v2-{{ checksum "go.sum" }} + - run: + name: "Build and install libsodium" + command: | + sudo apt-get update && sudo apt-get -y install libtool + make libsodium - run: name: "Run tests" command: | export VERSION="$(git describe --tags --long | sed 's/v\(.*\)/\1/')" export GO111MODULE=on mkdir -p /tmp/logs /tmp/workspace/profiles - for pkg in $(go list github.com/tendermint/tendermint/... | circleci tests split --split-by=timings); do + for pkg in $(go list ./... | circleci tests split --split-by=timings); do id=$(basename "$pkg") go test -v -timeout 5m -mod=readonly -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic "$pkg" | tee "/tmp/logs/$id-$RANDOM.log" done @@ -140,7 +153,7 @@ jobs: GOARCH: amd64 parallelism: 1 steps: - - checkout + - checkout_with_submodules - run: name: run localnet and exit on failure command: | @@ -156,42 +169,42 @@ jobs: machine: image: circleci/classic:latest steps: - - checkout + - checkout_with_submodules - run: mkdir -p $GOPATH/src/github.com/tendermint - run: ln -sf /home/circleci/project $GOPATH/src/github.com/tendermint/tendermint - run: bash test/p2p/circleci.sh - store_artifacts: path: /home/circleci/project/test/p2p/logs - upload_coverage: - executor: golang - steps: - - attach_workspace: - at: /tmp/workspace - - restore_cache: - name: "Restore source code cache" - keys: - - go-src-v1-{{ .Revision }} - - checkout - - restore_cache: - name: "Restore go module cache" - keys: - - go-mod-v2-{{ checksum "go.sum" }} - - run: - name: gather - command: | - echo "mode: atomic" > coverage.txt - for prof in $(ls /tmp/workspace/profiles/); do - tail -n +2 /tmp/workspace/profiles/"$prof" >> coverage.txt - done - - run: - name: upload - command: bash .circleci/codecov.sh -f coverage.txt +# upload_coverage: +# executor: golang +# steps: +# - attach_workspace: +# at: /tmp/workspace +# - restore_cache: +# name: "Restore source code cache" +# keys: +# - go-src-v1-{{ .Revision }} +# - checkout +# - restore_cache: +# name: "Restore go module cache" +# keys: +# - go-mod-v2-{{ checksum "go.sum" }} +# - run: +# name: gather +# command: | +# echo "mode: atomic" > coverage.txt +# for prof in $(ls /tmp/workspace/profiles/); do +# tail -n +2 /tmp/workspace/profiles/"$prof" >> coverage.txt +# done +# - run: +# name: upload +# command: bash .circleci/codecov.sh -f coverage.txt deploy_docs: executor: docs steps: - - checkout + - checkout_with_submodules - run: name: "Build docs" command: make build-docs @@ -206,7 +219,7 @@ jobs: name: "Restore source code cache" keys: - go-src-v1-{{ .Revision }} - - checkout + - checkout_with_submodules - run: name: Get next release number command: | @@ -239,7 +252,7 @@ jobs: name: "Restore source code cache" keys: - go-src-v1-{{ .Revision }} - - checkout + - checkout_with_submodules - restore_cache: name: "Restore release dependencies cache" keys: @@ -268,7 +281,7 @@ jobs: name: "Restore source code cache" keys: - go-src-v1-{{ .Revision }} - - checkout + - checkout_with_submodules - attach_workspace: at: /tmp/workspace - run: @@ -297,7 +310,7 @@ jobs: machine: image: ubuntu-1604:201903-01 steps: - - checkout + - checkout_with_submodules - attach_workspace: at: /tmp/workspace - run: @@ -316,7 +329,7 @@ jobs: steps: - attach_workspace: at: /tmp/workspace - - checkout + - checkout_with_submodules - setup_remote_docker: docker_layer_caching: true - run: @@ -351,7 +364,7 @@ jobs: GOARCH: amd64 parallelism: 1 steps: - - checkout + - checkout_with_submodules - run: name: Test RPC endpoints against swagger documentation command: | @@ -407,9 +420,9 @@ workflows: requires: - setup_dependencies - test_p2p - - upload_coverage: - requires: - - test_cover +# - upload_coverage: +# requires: +# - test_cover - reproducible_builds: filters: branches: diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..ac9144cbe --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "crypto/vrf/internal/vrf/libsodium"] + path = crypto/vrf/internal/vrf/libsodium + url = https://github.com/algorand/libsodium.git diff --git a/Makefile b/Makefile index 472b3a0ea..ddc9ef482 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ GOTOOLS = \ github.com/square/certstrap GOBIN?=${GOPATH}/bin PACKAGES=$(shell go list ./...) +SRCPATH=$(shell pwd) OUTPUT?=build/tendermint INCLUDE = -I=. -I=${GOPATH}/src -I=${GOPATH}/src/github.com/gogo/protobuf/protobuf @@ -63,6 +64,16 @@ build_abci: install_abci: @go install -mod=readonly ./abci/cmd/... +######################################## +### libsodium + +libsodium: + cd $(SRCPATH)/crypto/vrf/internal/vrf/libsodium && \ + ./autogen.sh && \ + ./configure --disable-shared --prefix="$(SRCPATH)/crypto/vrf/internal/vrf/" && \ + $(MAKE) && \ + $(MAKE) install + ######################################## ### Distribution diff --git a/crypto/vrf/internal/vrf/libsodium b/crypto/vrf/internal/vrf/libsodium new file mode 160000 index 000000000..004952bb5 --- /dev/null +++ b/crypto/vrf/internal/vrf/libsodium @@ -0,0 +1 @@ +Subproject commit 004952bb57b2a6d2c033969820c80255e8362615 diff --git a/crypto/vrf/internal/vrf/vrf.go b/crypto/vrf/internal/vrf/vrf.go new file mode 100644 index 000000000..3c50bc838 --- /dev/null +++ b/crypto/vrf/internal/vrf/vrf.go @@ -0,0 +1,122 @@ +// This vrf package makes the VRF API in Algorand's libsodium C library available to golang. +package vrf + +/* +#cgo CFLAGS: -Wall -std=c99 +#cgo CFLAGS: -I./include/ +#cgo LDFLAGS: -L./lib -lsodium +#include "sodium.h" +*/ +import "C" +import ( + "encoding/hex" + "errors" + "fmt" + "unsafe" +) + +const ( + PUBLICKEYBYTES = uint32(C.crypto_vrf_PUBLICKEYBYTES) + SECRETKEYBYTES = uint32(C.crypto_vrf_SECRETKEYBYTES) + SEEDBYTES = uint32(C.crypto_vrf_SEEDBYTES) + PROOFBYTES = uint32(C.crypto_vrf_PROOFBYTES) + OUTPUTBYTES = uint32(C.crypto_vrf_OUTPUTBYTES) + PRIMITIVE = C.crypto_vrf_PRIMITIVE +) + +// Generate an Ed25519 key pair for use with VRF. +func KeyPair() (*[PUBLICKEYBYTES]byte, *[SECRETKEYBYTES]byte) { + publicKey := [PUBLICKEYBYTES]byte{} + privateKey := [SECRETKEYBYTES]byte{} + publicKeyPtr := (*C.uchar)(unsafe.Pointer(&publicKey)) + privateKeyPtr := (*C.uchar)(unsafe.Pointer(&privateKey)) + C.crypto_vrf_keypair(publicKeyPtr, privateKeyPtr) + return &publicKey, &privateKey +} + +// Generate an Ed25519 key pair for use with VRF. Parameter `seed` means the cofactor in Curve25519 and EdDSA. +func KeyPairFromSeed(seed *[SEEDBYTES]byte) (*[PUBLICKEYBYTES]byte, *[SECRETKEYBYTES]byte) { + publicKey := [PUBLICKEYBYTES]byte{} + privateKey := [SECRETKEYBYTES]byte{} + publicKeyPtr := (*C.uchar)(unsafe.Pointer(&publicKey)) + privateKeyPtr := (*C.uchar)(unsafe.Pointer(&privateKey)) + seedPtr := (*C.uchar)(unsafe.Pointer(seed)) + C.crypto_vrf_keypair_from_seed(publicKeyPtr, privateKeyPtr, seedPtr) + return &publicKey, &privateKey +} + +// Verifies that the specified public key is valid. +func IsValidKey(publicKey *[PUBLICKEYBYTES]byte) bool { + publicKeyPtr := (*C.uchar)(unsafe.Pointer(publicKey)) + return C.crypto_vrf_is_valid_key(publicKeyPtr) != 0 +} + +// Construct a VRF proof from given secret key and message. +func Prove(privateKey *[SECRETKEYBYTES]byte, message []byte) (*[PROOFBYTES]byte, error) { + proof := [PROOFBYTES]byte{} + proofPtr := (*C.uchar)(unsafe.Pointer(&proof)) + privateKeyPtr := (*C.uchar)(unsafe.Pointer(privateKey)) + messagePtr := bytesToUnsignedCharPointer(message) + messageLen := (C.ulonglong)(len(message)) + if C.crypto_vrf_prove(proofPtr, privateKeyPtr, messagePtr, messageLen) != 0 { + return nil, errors.New(fmt.Sprintf("unable to decode the given privateKey")) + } + return &proof, nil +} + +// Verifies that proof was legitimately generated by private key for the given public key, and stores the +// VRF hash in output. Note that VRF "verify()" means the process of generating output from public key, +// proof, and message. +// https://tools.ietf.org/html/draft-irtf-cfrg-vrf-04#section-5.3 +func Verify(publicKey *[PUBLICKEYBYTES]byte, proof *[PROOFBYTES]byte, message []byte) (*[OUTPUTBYTES]byte, error) { + output := [OUTPUTBYTES]byte{} + outputPtr := (*C.uchar)(unsafe.Pointer(&output)) + publicKeyPtr := (*C.uchar)(unsafe.Pointer(publicKey)) + proofPtr := (*C.uchar)(unsafe.Pointer(proof)) + messagePtr := bytesToUnsignedCharPointer(message) + messageLen := (C.ulonglong)(len(message)) + if C.crypto_vrf_verify(outputPtr, publicKeyPtr, proofPtr, messagePtr, messageLen) != 0 { + return nil, errors.New(fmt.Sprintf( + "given public key is invalid, or the proof isn't legitimately generated for the message:"+ + " public_key=%s, proof=%s, message=%s", + hex.EncodeToString(publicKey[:]), hex.EncodeToString(proof[:]), hex.EncodeToString(message[:]))) + } + return &output, nil +} + +// Calculate the output (hash value) from the specified proof. +// In essence, this function returns a valid value if given proof is any point on the finite field. Otherwise, +// this will return an error. +func ProofToHash(proof *[PROOFBYTES]byte) (*[OUTPUTBYTES]byte, error) { + output := [OUTPUTBYTES]byte{} + outputPtr := (*C.uchar)(unsafe.Pointer(&output)) + proofPtr := (*C.uchar)(unsafe.Pointer(proof)) + if C.crypto_vrf_proof_to_hash(outputPtr, proofPtr) != 0 { + return nil, errors.New(fmt.Sprintf( + "given proof isn't legitimately generated: proof=%s", hex.EncodeToString(proof[:]))) + } + return &output, nil +} + +func SkToPk(privateKey *[SECRETKEYBYTES]byte) *[PUBLICKEYBYTES]byte { + publicKey := [PUBLICKEYBYTES]byte{} + publicKeyPtr := (*C.uchar)(unsafe.Pointer(&publicKey)) + privateKeyPtr := (*C.uchar)(unsafe.Pointer(privateKey)) + C.crypto_vrf_sk_to_pk(publicKeyPtr, privateKeyPtr) // void + return &publicKey +} + +func SkToSeed(privateKey *[SECRETKEYBYTES]byte) *[SEEDBYTES]byte { + seed := [SEEDBYTES]byte{} + seedPtr := (*C.uchar)(unsafe.Pointer(&seed)) + privateKeyPtr := (*C.uchar)(unsafe.Pointer(privateKey)) + C.crypto_vrf_sk_to_seed(seedPtr, privateKeyPtr) // void + return &seed +} + +func bytesToUnsignedCharPointer(msg []byte) *C.uchar { + if len(msg) == 0 { + return (*C.uchar)(C.NULL) + } + return (*C.uchar)(unsafe.Pointer(&msg[0])) +} diff --git a/crypto/vrf/vrf.go b/crypto/vrf/vrf.go new file mode 100644 index 000000000..05045997a --- /dev/null +++ b/crypto/vrf/vrf.go @@ -0,0 +1,66 @@ +// This vrf package makes the VRF API in Algorand's libsodium C library available to golang. +package vrf + +import ( + "github.com/tendermint/tendermint/crypto/ed25519" + vrfimpl "github.com/tendermint/tendermint/crypto/vrf/internal/vrf" + "math/big" + "unsafe" +) + +const ( + PROOFBYTES = vrfimpl.PROOFBYTES + OUTPUTBYTES = vrfimpl.OUTPUTBYTES +) + +type Proof [PROOFBYTES]byte + +type Output [OUTPUTBYTES]byte + +func newProof(bytes *[PROOFBYTES]byte) *Proof { + proof := Proof{} + copy(proof[:], bytes[:]) + return &proof +} + +func (pf *Proof) toBytes() *[PROOFBYTES]byte { + return (*[PROOFBYTES]byte)(unsafe.Pointer(pf)) +} + +func (pf *Proof) ToHash() (*Output, error) { + op, err := vrfimpl.ProofToHash(pf.toBytes()) + if err != nil { + return nil, err + } + return newOutput(op), nil +} + +func newOutput(bytes *[OUTPUTBYTES]byte) *Output { + output := Output{} + copy(output[:], bytes[:]) + return &output +} + +func (op *Output) ToInt() *big.Int { + i := big.Int{} + i.SetBytes(op[:]) + return &i +} + +func Prove(privateKey *ed25519.PrivKeyEd25519, message []byte) (*Proof, error) { + privKey := (*[vrfimpl.SECRETKEYBYTES]byte)(unsafe.Pointer(privateKey)) + pf, err := vrfimpl.Prove(privKey, message) + if err != nil { + return nil, err + } + return newProof(pf), nil +} + +func Verify(publicKey *ed25519.PubKeyEd25519, proof *Proof, message []byte) (*Output, error) { + pubKey := (*[vrfimpl.PUBLICKEYBYTES]byte)(unsafe.Pointer(publicKey)) + op, err := vrfimpl.Verify(pubKey, proof.toBytes(), message) + if err != nil { + return nil, err + } + return newOutput(op), nil +} diff --git a/crypto/vrf/vrf_test.go b/crypto/vrf/vrf_test.go new file mode 100644 index 000000000..8521a104e --- /dev/null +++ b/crypto/vrf/vrf_test.go @@ -0,0 +1,287 @@ +package vrf_test + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/crypto/vrf" + vrfimpl "github.com/tendermint/tendermint/crypto/vrf/internal/vrf" + "math/rand" + "testing" + "unsafe" +) + +func enc(s []byte) string { + return hex.EncodeToString(s) +} + +func TestConstants(t *testing.T) { + t.Logf("PUBLICKEYBYTES: %d\n", vrfimpl.PUBLICKEYBYTES) + t.Logf("SECRETKEYBYTES: %d\n", vrfimpl.SECRETKEYBYTES) + t.Logf("SEEDBYTES: %d\n", vrfimpl.SEEDBYTES) + t.Logf("PROOFBYTES: %d\n", vrfimpl.PROOFBYTES) + t.Logf("OUTPUTBYTES: %d\n", vrfimpl.OUTPUTBYTES) + t.Logf("PRIMITIVE: %s\n", vrfimpl.PRIMITIVE) + + if vrfimpl.PUBLICKEYBYTES != 32 { + t.Errorf("public key size: %d != 32\n", vrfimpl.PUBLICKEYBYTES) + } + if vrfimpl.SECRETKEYBYTES != 64 { + t.Errorf("secret key size: %d != 64\n", vrfimpl.SECRETKEYBYTES) + } + if vrfimpl.SEEDBYTES != 32 { + t.Errorf("seed size: %d != 32\n", vrfimpl.SEEDBYTES) + } + if vrfimpl.OUTPUTBYTES != 64 { + t.Errorf("output size: %d != 64\n", vrfimpl.OUTPUTBYTES) + } + if vrfimpl.PRIMITIVE != "ietfdraft03" { + t.Errorf("primitive: %s != \"ietfdraft03\"\n", vrfimpl.PRIMITIVE) + } +} + +func TestKeyPair(t *testing.T) { + var pk, sk = vrfimpl.KeyPair() + t.Logf("random public key: %s (%d bytes)\n", enc(pk[:]), len(pk)) + t.Logf("random private key: %s (%d bytes)\n", enc(sk[:]), len(sk)) + if uint32(len(pk)) != vrfimpl.PUBLICKEYBYTES { + t.Errorf("public key size: %d != %d", len(pk), vrfimpl.PUBLICKEYBYTES) + } + if uint32(len(sk)) != vrfimpl.SECRETKEYBYTES { + t.Errorf("secret key size: %d != %d", len(sk), vrfimpl.SECRETKEYBYTES) + } +} + +func TestKeyPairFromSeed(t *testing.T) { + var seed [vrfimpl.SEEDBYTES]byte + var pk, sk = vrfimpl.KeyPairFromSeed(&seed) + t.Logf("static seed: %s (%d bytes)\n", enc(seed[:]), len(seed)) + t.Logf("static public key: %s (%d bytes)\n", enc(pk[:]), len(pk)) + t.Logf("static private key: %s (%d bytes)\n", enc(sk[:]), len(sk)) + if enc(pk[:]) != "3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29" { + t.Errorf("unexpected public key: %s", enc(pk[:])) + } + if enc(sk[:]) != "0000000000000000000000000000000000000000000000000000000000000000"+ + "3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29" { + t.Errorf("unexpected private key: %s", enc(sk[:])) + } + if uint32(len(pk)) != vrfimpl.PUBLICKEYBYTES { + t.Errorf("public key size: %d != %d", len(pk), vrfimpl.PUBLICKEYBYTES) + } + if uint32(len(sk)) != vrfimpl.SECRETKEYBYTES { + t.Errorf("secret key size: %d != %d", len(sk), vrfimpl.SECRETKEYBYTES) + } + + var message [0]byte + var proof, err1 = vrfimpl.Prove(sk, message[:]) + if err1 != nil { + t.Errorf("probe failed: %s", err1) + } + t.Logf("proof: %s (%d bytes)\n", enc(proof[:]), len(proof)) + var output, err2 = vrfimpl.ProofToHash(proof) + if err2 != nil { + t.Errorf("failed to hash proof: %s", err2) + } + t.Logf("output: %s (%d bytes)\n", enc(output[:]), len(output)) +} + +func TestIsValidKey(t *testing.T) { + + // generated from KeyPair() + var pk1, _ = vrfimpl.KeyPair() + if ! vrfimpl.IsValidKey(pk1) { + t.Errorf("public key is not valid: %s", enc(pk1[:])) + } + + // generated from KeyPairFromSeed() + var seed [vrfimpl.SEEDBYTES]byte + var pk2, _ = vrfimpl.KeyPairFromSeed(&seed) + if ! vrfimpl.IsValidKey(pk2) { + t.Errorf("public key is not valid: %s", enc(pk2[:])) + } + + // zero + var zero [vrfimpl.PUBLICKEYBYTES]byte + if vrfimpl.IsValidKey(&zero) { + t.Error("recognized as valid for zero pk") + } + + // random bytes + var random [vrfimpl.PUBLICKEYBYTES]byte + var rng = rand.New(rand.NewSource(0)) + rng.Read(random[:]) + if vrfimpl.IsValidKey(&random) { + t.Errorf("recognized as valid for random pk: %s", enc(random[:])) + } +} + +func TestProveAndVerify(t *testing.T) { + message := []byte("hello, world") + + var zero [vrfimpl.SEEDBYTES]byte + var pk, sk = vrfimpl.KeyPairFromSeed(&zero) + var proof, err1 = vrfimpl.Prove(sk, message) + if err1 != nil { + t.Errorf("probe failed: %s", err1) + } + var output, err2 = vrfimpl.ProofToHash(proof) + if err2 != nil { + t.Errorf("failed to hash proof: %s", err2) + } + t.Logf("SEED[%s] -> OUTPUT[%s]\n", enc(zero[:]), enc(output[:])) + var expected, err3 = vrfimpl.Verify(pk, proof, message) + if err3 != nil { + t.Errorf("validation failed: %s", err3) + } else if bytes.Compare(expected[:], output[:]) != 0 { + t.Errorf("output not matches: %s", enc(output[:])) + } + + // essentially, the private key for ed25519 could be any value at a point on the finite field. + var invalidPrivateKey [vrfimpl.SECRETKEYBYTES]byte + for i := range invalidPrivateKey { + invalidPrivateKey[i] = 0xFF + } + var _, err4 = vrfimpl.Prove(&invalidPrivateKey, message) + if err4 == nil { + t.Errorf("Prove() with invalid private key didn't fail") + } + + // unexpected public key for Verify() + var zero3 [vrfimpl.PUBLICKEYBYTES]byte + var _, err5 = vrfimpl.Verify(&zero3, proof, message) + if err5 == nil { + t.Errorf("Verify() with zero public key didn't fail") + } + + // unexpected proof for Verify() + var zero4 [vrfimpl.PROOFBYTES]byte + var _, err6 = vrfimpl.Verify(pk, &zero4, message) + if err6 == nil { + t.Errorf("Verify() with zero proof didn't fail") + } + + // unexpected message for Verify() + var message2 = []byte("good-by world") + var output2, err7 = vrfimpl.Verify(pk, proof, message2) + if err7 == nil { + t.Errorf("Verify() success without error: %s", enc(output2[:])) + } + + // essentially, the proof for ed25519 could be any value at a point on the finite field. + var invalidProof [vrfimpl.PROOFBYTES]byte + for i := range invalidProof { + invalidProof[i] = 0xFF + } + var _, err8 = vrfimpl.ProofToHash(&invalidProof) + if err8 == nil { + t.Errorf("ProofToHash() with invalid proof didn't fail") + } +} + +func TestSkToPk(t *testing.T) { + var zero [vrfimpl.SEEDBYTES]byte + var expected, sk = vrfimpl.KeyPairFromSeed(&zero) + + var actual = vrfimpl.SkToPk(sk) + + if bytes.Compare(expected[:], actual[:]) != 0 { + t.Errorf("public key didn't match: %s != %s", enc(expected[:]), enc(actual[:])) + } +} + +func TestSkToSeed(t *testing.T) { + var zero [vrfimpl.SEEDBYTES]byte + var _, sk = vrfimpl.KeyPairFromSeed(&zero) + + var actual = vrfimpl.SkToSeed(sk) + + if bytes.Compare(zero[:], actual[:]) != 0 { + t.Errorf("seed didn't match: %s != %s", enc(zero[:]), enc(actual[:])) + } +} + +func TestToHash(t *testing.T) { + secret := [vrfimpl.SEEDBYTES]byte{} + privateKey := ed25519.GenPrivKeyFromSecret(secret[:]) + message := []byte("hello, world") + + proof, err1 := vrf.Prove(&privateKey, message) + if err1 != nil { + t.Fatalf("failed to prove: %s", err1) + } + + _, err2 := proof.ToHash() + if err2 != nil { + t.Errorf("failed to convert to hash: %s", enc(proof[:])) + } + + // check to fail for invalid proof bytes + for i := range proof { + proof[i] = 0xFF + } + op3, err3 := proof.ToHash() + if err3 == nil { + t.Errorf("unexpected hash for invalid proof: %s", enc(op3[:])) + } +} + +func TestKeyPairCompatibility(t *testing.T) { + var secret [vrfimpl.SEEDBYTES]byte + tmPrivKey := ed25519.GenPrivKeyFromSecret(secret[:]) + tmPubKey, _ := tmPrivKey.PubKey().(ed25519.PubKeyEd25519) + tmPrivKeyBytes := tmPrivKey[:] + tmPubKeyBytes := tmPubKey[:] + + var seed [vrfimpl.SEEDBYTES]byte + hashedSecret := sha256.Sum256(secret[:]) + copy(seed[:], hashedSecret[:]) + lsPubKey, lsPrivKey := vrfimpl.KeyPairFromSeed(&seed) + + if ! bytes.Equal(tmPrivKeyBytes, lsPrivKey[:]) { + t.Errorf("incompatible private key: %s != %s", + enc(tmPrivKeyBytes), enc(lsPrivKey[:])) + } + t.Logf("tendermint: private key: %s (%d bytes)\n", enc(tmPrivKeyBytes[:]), len(tmPrivKey)) + t.Logf("libsodium : private key: %s (%d bytes)\n", enc(lsPrivKey[:]), len(lsPrivKey)) + + if ! bytes.Equal(tmPubKeyBytes, lsPubKey[:]) { + t.Errorf("incompatible public key: %s != %s", enc(tmPubKeyBytes), enc(lsPubKey[:])) + } + t.Logf("tendermint: public key: %s (%d bytes)\n", enc(tmPubKeyBytes), len(tmPubKey)) + t.Logf("libsodium : public key: %s (%d bytes)\n", enc(lsPubKey[:]), len(lsPubKey)) + + pubKeyBytesPtr := (*[vrfimpl.PUBLICKEYBYTES]byte)(unsafe.Pointer(&tmPubKey)) + if ! vrfimpl.IsValidKey(pubKeyBytesPtr) { + t.Errorf("ed25519 key is not a valid public key") + } +} + +func TestProve(t *testing.T) { + secret := [vrfimpl.SEEDBYTES]byte{} + privateKey := ed25519.GenPrivKeyFromSecret(secret[:]) + publicKey, _ := privateKey.PubKey().(ed25519.PubKeyEd25519) + t.Logf("seed: %s", enc(secret[:])) + t.Logf("private key: [%s]", enc(privateKey[:])) + t.Logf("public key: [%s]", enc(publicKey[:])) + + message := []byte("hello, world") + proof, err1 := vrf.Prove(&privateKey, message) + if err1 != nil { + t.Fatalf("failed to prove: %s", err1) + } + t.Logf("proof: %s", enc(proof[:])) + + hash1, err2 := proof.ToHash() + if err2 != nil { + t.Fatalf("failed to hash: %s", err2) + } + t.Logf("hash for \"%s\": %s", message, hash1.ToInt()) + + hash2, err3 := vrf.Verify(&publicKey, proof, message) + if err3 != nil { + t.Errorf("failed to verify: %s", err3) + } else if ! bytes.Equal(hash1[:], hash2[:]) { + t.Errorf("incompatible output: %s != %s", enc(hash1[:]), enc(hash2[:])) + } +}