diff --git a/.gitignore b/.gitignore index e545b4f958..04c9850784 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,4 @@ server/cmd/validatemarkets client/cmd/translationsreport/translationsreport client/cmd/translationsreport/worksheets server/cmd/dexadm/dexadm +internal/libsecp256k1/secp256k1 diff --git a/internal/libsecp256k1/README.md b/internal/libsecp256k1/README.md new file mode 100644 index 0000000000..5112e5c0f6 --- /dev/null +++ b/internal/libsecp256k1/README.md @@ -0,0 +1,10 @@ +### Package libsecp256k1 + +Package libsecp256k1 includes some primative cryptographic functions needed for +working with adaptor signatures that are not currently found in golang. This imports +code from https://github.com/tecnovert/secp256k1 and uses that with cgo. Both +that library and this package are in an experimental stage. + +### Usage + +Run the `build.sh` script. Currently untested on mac and will not work on Windows. diff --git a/internal/libsecp256k1/build.sh b/internal/libsecp256k1/build.sh new file mode 100755 index 0000000000..b19343f0c7 --- /dev/null +++ b/internal/libsecp256k1/build.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +rm -fr secp256k1 +git clone https://github.com/tecnovert/secp256k1 -b anonswap_v0.2 + +cd secp256k1 +./autogen.sh +./configure --enable-module-dleag --enable-experimental --enable-module-generator --enable-module-ed25519 --enable-module-recovery --enable-module-ecdsaotves +make +cd .. diff --git a/internal/libsecp256k1/libsecp256k1.go b/internal/libsecp256k1/libsecp256k1.go new file mode 100644 index 0000000000..89bbeab13f --- /dev/null +++ b/internal/libsecp256k1/libsecp256k1.go @@ -0,0 +1,149 @@ +// This code is available on the terms of the project LICENSE.md file, +// also available online at https://blueoakcouncil.org/license/1.0.0. + +package libsecp256k1 + +/* +#cgo CFLAGS: -g -Wall +#cgo LDFLAGS: -L. -l:secp256k1/.libs/libsecp256k1.a +#include "secp256k1/include/secp256k1_dleag.h" +#include "secp256k1/include/secp256k1_ecdsaotves.h" +#include + +secp256k1_context* _ctx() { + return secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); +} +*/ +import "C" +import ( + "errors" + "unsafe" + + "decred.org/dcrdex/dex/encode" + "github.com/decred/dcrd/dcrec/edwards/v2" + "github.com/decred/dcrd/dcrec/secp256k1/v4" +) + +const ( + proofLen = 48893 + maxSigLen = 72 // The actual size is variable. + ctLen = 196 +) + +// Ed25519DleagProve creates a proof for checking a discrete logarithm is equal +// across the secp256k1 and ed25519 curves. +func Ed25519DleagProve(privKey *edwards.PrivateKey) (proof [proofLen]byte, err error) { + secpCtx := C._ctx() + defer C.free(unsafe.Pointer(secpCtx)) + nonce := [32]byte{} + copy(nonce[:], encode.RandomBytes(32)) + key := [32]byte{} + copy(key[:], privKey.Serialize()) + n := (*C.uchar)(unsafe.Pointer(&nonce)) + k := (*C.uchar)(unsafe.Pointer(&key)) + nBits := uint64(252) + nb := (*C.ulong)(unsafe.Pointer(&nBits)) + plen := C.ulong(proofLen) + p := (*C.uchar)(unsafe.Pointer(&proof)) + res := C.secp256k1_ed25519_dleag_prove(secpCtx, p, &plen, k, *nb, n) + if int(res) != 1 { + return [proofLen]byte{}, errors.New("C.secp256k1_ed25519_dleag_prove exited with error") + } + return proof, nil +} + +// Ed25519DleagVerify verifies that a descrete logarithm is equal across the +// secp256k1 and ed25519 curves. +func Ed25519DleagVerify(proof [proofLen]byte) bool { + secpCtx := C._ctx() + defer C.free(unsafe.Pointer(secpCtx)) + pl := C.ulong(proofLen) + p := (*C.uchar)(unsafe.Pointer(&proof)) + res := C.secp256k1_ed25519_dleag_verify(secpCtx, p, pl) + return res == 1 +} + +// EcdsaotvesEncSign signs the hash and returns an encrypted signature. +func EcdsaotvesEncSign(signPriv *secp256k1.PrivateKey, encPub *secp256k1.PublicKey, hash [32]byte) (cyphertext [ctLen]byte, err error) { + secpCtx := C._ctx() + defer C.free(unsafe.Pointer(secpCtx)) + privBytes := [32]byte{} + copy(privBytes[:], signPriv.Serialize()) + priv := (*C.uchar)(unsafe.Pointer(&privBytes)) + pubBytes := [33]byte{} + copy(pubBytes[:], encPub.SerializeCompressed()) + pub := (*C.uchar)(unsafe.Pointer(&pubBytes)) + h := (*C.uchar)(unsafe.Pointer(&hash)) + s := (*C.uchar)(unsafe.Pointer(&cyphertext)) + res := C.ecdsaotves_enc_sign(secpCtx, s, priv, pub, h) + if int(res) != 1 { + return [ctLen]byte{}, errors.New("C.ecdsaotves_enc_sign exited with error") + } + return cyphertext, nil +} + +// EcdsaotvesEncVerify verifies the encrypted signature. +func EcdsaotvesEncVerify(signPub, encPub *secp256k1.PublicKey, hash [32]byte, cyphertext [ctLen]byte) bool { + secpCtx := C._ctx() + defer C.free(unsafe.Pointer(secpCtx)) + signBytes := [33]byte{} + copy(signBytes[:], signPub.SerializeCompressed()) + sp := (*C.uchar)(unsafe.Pointer(&signBytes)) + encBytes := [33]byte{} + copy(encBytes[:], encPub.SerializeCompressed()) + ep := (*C.uchar)(unsafe.Pointer(&encBytes)) + h := (*C.uchar)(unsafe.Pointer(&hash)) + c := (*C.uchar)(unsafe.Pointer(&cyphertext)) + res := C.ecdsaotves_enc_verify(secpCtx, sp, ep, h, c) + return res == 1 +} + +// EcdsaotvesDecSig retrieves the signature. +func EcdsaotvesDecSig(encPriv *secp256k1.PrivateKey, cyphertext [ctLen]byte) ([]byte, error) { + secpCtx := C._ctx() + defer C.free(unsafe.Pointer(secpCtx)) + encBytes := [32]byte{} + copy(encBytes[:], encPriv.Serialize()) + ep := (*C.uchar)(unsafe.Pointer(&encBytes)) + ct := (*C.uchar)(unsafe.Pointer(&cyphertext)) + var sig [maxSigLen]byte + s := (*C.uchar)(unsafe.Pointer(&sig)) + slen := C.ulong(maxSigLen) + res := C.ecdsaotves_dec_sig(secpCtx, s, &slen, ep, ct) + if int(res) != 1 { + return nil, errors.New("C.ecdsaotves_dec_sig exited with error") + } + sigCopy := make([]byte, maxSigLen) + copy(sigCopy, sig[:]) + // Remove trailing zeros. + for i := maxSigLen - 1; i >= 0; i-- { + if sigCopy[i] != 0 { + break + } + sigCopy = sigCopy[:i] + } + return sigCopy, nil +} + +// EcdsaotvesRecEncKey retrieves the encoded private key from signature and +// cyphertext. +func EcdsaotvesRecEncKey(encPub *secp256k1.PublicKey, cyphertext [ctLen]byte, sig []byte) (encPriv *secp256k1.PrivateKey, err error) { + secpCtx := C._ctx() + defer C.free(unsafe.Pointer(secpCtx)) + pubBytes := [33]byte{} + copy(pubBytes[:], encPub.SerializeCompressed()) + ep := (*C.uchar)(unsafe.Pointer(&pubBytes)) + ct := (*C.uchar)(unsafe.Pointer(&cyphertext)) + sigCopy := [maxSigLen]byte{} + copy(sigCopy[:], sig) + s := (*C.uchar)(unsafe.Pointer(&sigCopy)) + varSigLen := len(sig) + slen := C.ulong(varSigLen) + pkBytes := [32]byte{} + pk := (*C.uchar)(unsafe.Pointer(&pkBytes)) + res := C.ecdsaotves_rec_enc_key(secpCtx, pk, ep, ct, s, slen) + if int(res) != 1 { + return nil, errors.New("C.ecdsaotves_rec_enc_key exited with error") + } + return secp256k1.PrivKeyFromBytes(pkBytes[:]), nil +} diff --git a/internal/libsecp256k1/libsecp256k1_test.go b/internal/libsecp256k1/libsecp256k1_test.go new file mode 100644 index 0000000000..4e92eb3413 --- /dev/null +++ b/internal/libsecp256k1/libsecp256k1_test.go @@ -0,0 +1,215 @@ +//go:build libsecp256k1 + +package libsecp256k1 + +import ( + "bytes" + "math/rand" + "testing" + + "github.com/decred/dcrd/dcrec/edwards/v2" + "github.com/decred/dcrd/dcrec/secp256k1/v4" +) + +func randBytes(n int) []byte { + b := make([]byte, n) + rand.Read(b) + return b +} + +func TestEd25519DleagProve(t *testing.T) { + tests := []struct { + name string + }{{ + name: "ok", + }} + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + pk, err := edwards.GeneratePrivateKey() + if err != nil { + t.Fatal(err) + } + sPk := secp256k1.PrivKeyFromBytes(pk.Serialize()) + proof, err := Ed25519DleagProve(pk) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(sPk.PubKey().SerializeCompressed(), proof[:33]) { + t.Fatal("first 33 bytes of proof not equal to secp256k1 pubkey") + } + }) + } +} + +func TestEd25519DleagVerify(t *testing.T) { + pk, err := edwards.GeneratePrivateKey() + if err != nil { + panic(err) + } + proof, err := Ed25519DleagProve(pk) + if err != nil { + panic(err) + } + tests := []struct { + name string + proof [proofLen]byte + ok bool + }{{ + name: "ok", + proof: proof, + ok: true, + }, { + name: "bad proof", + proof: func() (p [proofLen]byte) { + copy(p[:], proof[:]) + p[0] ^= p[0] + return p + }(), + }} + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ok := Ed25519DleagVerify(test.proof) + if ok != test.ok { + t.Fatalf("want %v but got %v", test.ok, ok) + } + }) + } +} + +func TestEcdsaotvesEncSign(t *testing.T) { + tests := []struct { + name string + }{{ + name: "ok", + }} + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + signPk, err := secp256k1.GeneratePrivateKey() + if err != nil { + t.Fatal(err) + } + encPk, err := secp256k1.GeneratePrivateKey() + if err != nil { + t.Fatal(err) + } + h := randBytes(32) + var hash [32]byte + copy(hash[:], h) + _, err = EcdsaotvesEncSign(signPk, encPk.PubKey(), hash) + if err != nil { + t.Fatal(err) + } + }) + } +} + +func TestEcdsaotvesEncVerify(t *testing.T) { + signPk, err := secp256k1.GeneratePrivateKey() + if err != nil { + t.Fatal(err) + } + encPk, err := secp256k1.GeneratePrivateKey() + if err != nil { + t.Fatal(err) + } + h := randBytes(32) + var hash [32]byte + copy(hash[:], h) + ct, err := EcdsaotvesEncSign(signPk, encPk.PubKey(), hash) + if err != nil { + t.Fatal(err) + } + tests := []struct { + name string + ok bool + ct [196]byte + }{{ + name: "ok", + ct: ct, + ok: true, + }, { + name: "bad sig", + ct: func() (c [ctLen]byte) { + copy(c[:], ct[:]) + c[0] ^= c[0] + return c + }(), + }} + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ok := EcdsaotvesEncVerify(signPk.PubKey(), encPk.PubKey(), hash, test.ct) + if ok != test.ok { + t.Fatalf("want %v but got %v", test.ok, ok) + } + }) + } +} + +func TestEcdsaotvesDecSig(t *testing.T) { + signPk, err := secp256k1.GeneratePrivateKey() + if err != nil { + t.Fatal(err) + } + encPk, err := secp256k1.GeneratePrivateKey() + if err != nil { + t.Fatal(err) + } + h := randBytes(32) + var hash [32]byte + copy(hash[:], h) + ct, err := EcdsaotvesEncSign(signPk, encPk.PubKey(), hash) + if err != nil { + t.Fatal(err) + } + tests := []struct { + name string + }{{ + name: "ok", + }} + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err := EcdsaotvesDecSig(encPk, ct) + if err != nil { + t.Fatal(err) + } + }) + } +} + +func TestEcdsaotvesRecEncKey(t *testing.T) { + signPk, err := secp256k1.GeneratePrivateKey() + if err != nil { + t.Fatal(err) + } + encPk, err := secp256k1.GeneratePrivateKey() + if err != nil { + t.Fatal(err) + } + h := randBytes(32) + var hash [32]byte + copy(hash[:], h) + ct, err := EcdsaotvesEncSign(signPk, encPk.PubKey(), hash) + if err != nil { + t.Fatal(err) + } + sig, err := EcdsaotvesDecSig(encPk, ct) + if err != nil { + t.Fatal(err) + } + tests := []struct { + name string + }{{ + name: "ok", + }} + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + pk, err := EcdsaotvesRecEncKey(encPk.PubKey(), ct, sig) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(pk.Serialize(), encPk.Serialize()) { + t.Fatal("private keys not equal") + } + }) + } +} diff --git a/internal/libsecp256k1/secp256k1 b/internal/libsecp256k1/secp256k1 new file mode 160000 index 0000000000..e3ebcd782a --- /dev/null +++ b/internal/libsecp256k1/secp256k1 @@ -0,0 +1 @@ +Subproject commit e3ebcd782a604f228784b10c50ffa099d9796720 diff --git a/run_tests.sh b/run_tests.sh index fa1923bb57..1ec7227cab 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -9,6 +9,12 @@ echo "Go version: $GV" # Ensure html templates pass localization. go generate -x ./client/webserver/site # no -write +cd ./internal/libsecp256k1 +./build.sh +go test -race -tags libsecp256k1 + +cd "$dir" + # list of all modules to test modules=". /dex/testing/loadbot /client/cmd/bisonw-desktop"