diff --git a/.gitignore b/.gitignore index e9e6bd8e92c..400221c8fe8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,12 +5,14 @@ src/bitcoin src/zcashd src/zcash-cli src/zcash-gtest +src/zcash-proving-service src/zcash-tx src/test/test_bitcoin # Zcash utilities src/zcash/GenerateParams src/zcash/CreateJoinSplit +src/zcash/ExampleProvingServiceClient *zcashTest.pk *zcashTest.vk diff --git a/build-aux/m4/ax_boost_regex.m4 b/build-aux/m4/ax_boost_regex.m4 new file mode 100644 index 00000000000..e2413c24fe6 --- /dev/null +++ b/build-aux/m4/ax_boost_regex.m4 @@ -0,0 +1,111 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_boost_regex.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_BOOST_REGEX +# +# DESCRIPTION +# +# Test for Regex library from the Boost C++ libraries. The macro requires +# a preceding call to AX_BOOST_BASE. Further documentation is available at +# . +# +# This macro calls: +# +# AC_SUBST(BOOST_REGEX_LIB) +# +# And sets: +# +# HAVE_BOOST_REGEX +# +# LICENSE +# +# Copyright (c) 2008 Thomas Porschberg +# Copyright (c) 2008 Michael Tindal +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 23 + +AC_DEFUN([AX_BOOST_REGEX], +[ + AC_ARG_WITH([boost-regex], + AS_HELP_STRING([--with-boost-regex@<:@=special-lib@:>@], + [use the Regex library from boost - it is possible to specify a certain library for the linker + e.g. --with-boost-regex=boost_regex-gcc-mt-d-1_33_1 ]), + [ + if test "$withval" = "no"; then + want_boost="no" + elif test "$withval" = "yes"; then + want_boost="yes" + ax_boost_user_regex_lib="" + else + want_boost="yes" + ax_boost_user_regex_lib="$withval" + fi + ], + [want_boost="yes"] + ) + + if test "x$want_boost" = "xyes"; then + AC_REQUIRE([AC_PROG_CC]) + CPPFLAGS_SAVED="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" + export CPPFLAGS + + LDFLAGS_SAVED="$LDFLAGS" + LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" + export LDFLAGS + + AC_CACHE_CHECK(whether the Boost::Regex library is available, + ax_cv_boost_regex, + [AC_LANG_PUSH([C++]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@include + ]], + [[boost::regex r(); return 0;]])], + ax_cv_boost_regex=yes, ax_cv_boost_regex=no) + AC_LANG_POP([C++]) + ]) + if test "x$ax_cv_boost_regex" = "xyes"; then + AC_DEFINE(HAVE_BOOST_REGEX,,[define if the Boost::Regex library is available]) + BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\/@:>@*//'` + if test "x$ax_boost_user_regex_lib" = "x"; then + for libextension in `ls $BOOSTLIBDIR/libboost_regex*.so* $BOOSTLIBDIR/libboost_regex*.dylib* $BOOSTLIBDIR/libboost_regex*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_regex.*\)\.so.*$;\1;' -e 's;^lib\(boost_regex.*\)\.dylib.*;\1;' -e 's;^lib\(boost_regex.*\)\.a.*$;\1;'` ; do + ax_lib=${libextension} + AC_CHECK_LIB($ax_lib, exit, + [BOOST_REGEX_LIB="-l$ax_lib"; AC_SUBST(BOOST_REGEX_LIB) link_regex="yes"; break], + [link_regex="no"]) + done + if test "x$link_regex" != "xyes"; then + for libextension in `ls $BOOSTLIBDIR/boost_regex*.dll* $BOOSTLIBDIR/boost_regex*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^\(boost_regex.*\)\.dll.*$;\1;' -e 's;^\(boost_regex.*\)\.a.*$;\1;'` ; do + ax_lib=${libextension} + AC_CHECK_LIB($ax_lib, exit, + [BOOST_REGEX_LIB="-l$ax_lib"; AC_SUBST(BOOST_REGEX_LIB) link_regex="yes"; break], + [link_regex="no"]) + done + fi + + else + for ax_lib in $ax_boost_user_regex_lib boost_regex-$ax_boost_user_regex_lib; do + AC_CHECK_LIB($ax_lib, main, + [BOOST_REGEX_LIB="-l$ax_lib"; AC_SUBST(BOOST_REGEX_LIB) link_regex="yes"; break], + [link_regex="no"]) + done + fi + if test "x$ax_lib" = "x"; then + AC_MSG_ERROR(Could not find a version of the Boost::Regex library!) + fi + if test "x$link_regex" != "xyes"; then + AC_MSG_ERROR(Could not link against $ax_lib !) + fi + fi + + CPPFLAGS="$CPPFLAGS_SAVED" + LDFLAGS="$LDFLAGS_SAVED" + fi +]) diff --git a/configure.ac b/configure.ac index b44b486a367..2e8827743bd 100644 --- a/configure.ac +++ b/configure.ac @@ -149,6 +149,18 @@ AC_ARG_ENABLE([zmq], [use_zmq=$enableval], [use_zmq=yes]) +AC_ARG_ENABLE([websocketpp], + [AS_HELP_STRING([--enable-websocketpp], + [enable WebSocket support in the proving service daemon])], + [use_websocketpp=$enableval], + [use_websocketpp=no]) + +AC_ARG_ENABLE([proving-service], + [AS_HELP_STRING([--disable-proving-service], + [disable proving service support in daemon])], + [use_proving_service=$enableval], + [use_proving_service=yes]) + AC_ARG_WITH([protoc-bindir],[AS_HELP_STRING([--with-protoc-bindir=BIN_DIR],[specify protoc bin path])], [protoc_bin_path=$withval], []) AC_ARG_ENABLE(man, @@ -228,6 +240,12 @@ AC_ARG_WITH([daemon], [build_bitcoind=$withval], [build_bitcoind=yes]) +AC_ARG_WITH([proving-service-daemon], + [AS_HELP_STRING([--with-proving-service-daemon], + [build zcash-proving-service (default=no)])], + [build_zcash_proving_service=$withval], + [build_zcash_proving_service=no]) + use_pkgconfig=yes case $host in *mingw*) @@ -574,6 +592,7 @@ AX_BOOST_FILESYSTEM AX_BOOST_PROGRAM_OPTIONS AX_BOOST_THREAD AX_BOOST_CHRONO +AX_BOOST_REGEX fi @@ -615,7 +634,7 @@ if test x$use_tests = xyes; then fi if test x$use_boost = xyes; then -BOOST_LIBS="$BOOST_LDFLAGS $BOOST_SYSTEM_LIB $BOOST_FILESYSTEM_LIB $BOOST_PROGRAM_OPTIONS_LIB $BOOST_THREAD_LIB $BOOST_CHRONO_LIB" +BOOST_LIBS="$BOOST_LDFLAGS $BOOST_SYSTEM_LIB $BOOST_FILESYSTEM_LIB $BOOST_PROGRAM_OPTIONS_LIB $BOOST_THREAD_LIB $BOOST_CHRONO_LIB $BOOST_REGEX_LIB" fi if test x$use_pkgconfig = xyes; then @@ -693,6 +712,30 @@ AC_CHECK_LIB([gmp],[[__gmpn_sub_n]],GMP_LIBS=-lgmp, [AC_MSG_ERROR(libgmp missing AC_CHECK_HEADER([gmpxx.h],,AC_MSG_ERROR(libgmpxx headers missing)) AC_CHECK_LIB([gmpxx],[main],GMPXX_LIBS=-lgmpxx, [AC_MSG_ERROR(libgmpxx missing)]) +# websocketpp is header-only, so just use autoconf +if test "x$use_websocketpp" = "xyes"; then + AC_CHECK_HEADER([websocketpp/version.hpp], + [AC_DEFINE([ENABLE_WEBSOCKET],[1],[Define to 1 to enable WebSocket functions])], + [AC_MSG_WARN([websocketpp/version.hpp not found, disabling websocket support]) + use_websocketpp=no + AC_DEFINE([ENABLE_WEBSOCKET],[0],[Define to 1 to enable WebSocket functions])]) +else + AC_DEFINE_UNQUOTED([ENABLE_WEBSOCKET],[0],[Define to 1 to enable WebSocket functions]) +fi + +if test x$use_proving_service = xyes; then + if test "x$use_zmq" = "xyes"; then + AC_DEFINE([ENABLE_PROVING_SERVICE],[1],[Define to 1 to enable proving service functions]) + else + AC_MSG_WARN([zmq not enabled, disabling proving service support]); + use_proving_service=no + AC_DEFINE([ENABLE_PROVING_SERVICE],[0],[Define to 1 to enable proving service functions]) + fi +else + AC_DEFINE_UNQUOTED([ENABLE_PROVING_SERVICE],[0],[Define to 1 to enable proving service functions]) +fi +AM_CONDITIONAL([ENABLE_PROVING_SERVICE], [test x$use_proving_service = xyes]) + RUST_LIBS="" if test x$enable_rust != xno; then RUST_LIBS="-lrustzcash" @@ -739,6 +782,20 @@ fi AC_MSG_RESULT($build_bitcoin_libs) +AC_MSG_CHECKING([whether to build zcash-proving-service]) +if test x$build_zcash_proving_service = xyes; then + if test "x$use_zmq" = "xyes" -o "x$use_websocketpp" = "xyes"; then + AC_MSG_RESULT([yes]) + BUILD_ZCASH_PROVING_SERVICE="yes" + else + AC_MSG_ERROR([zcash-proving-service requires ZMQ or WebSocket.]); + fi +else + AC_MSG_RESULT([no]) + BUILD_ZCASH_PROVING_SERVICE="" +fi +AM_CONDITIONAL([BUILD_ZCASH_PROVING_SERVICE], [test x$BUILD_ZCASH_PROVING_SERVICE = xyes]) + AC_LANG_POP if test "x$use_ccache" != "xno"; then @@ -794,6 +851,8 @@ AM_CONDITIONAL([ENABLE_ZMQ], [test "x$use_zmq" = "xyes"]) AM_CONDITIONAL([ENABLE_PROTON], [test "x$use_proton" = "xyes"]) +AM_CONDITIONAL([ENABLE_WEBSOCKET], [test "x$use_websocketpp" = "xyes"]) + AC_MSG_CHECKING([whether to build test_bitcoin]) if test x$use_tests = xyes; then AC_MSG_RESULT([yes]) diff --git a/depends/Makefile b/depends/Makefile index 897c4adabb6..50cf0f3d360 100644 --- a/depends/Makefile +++ b/depends/Makefile @@ -74,8 +74,9 @@ include packages/packages.mk rust_packages_$(NO_RUST) = $(rust_packages) wallet_packages_$(NO_WALLET) = $(wallet_packages) proton_packages_$(NO_PROTON) = $(proton_packages) +websocketpp_packages_$(NO_WEBSOCKETPP) = $(websocketpp_packages) -packages += $($(host_arch)_$(host_os)_packages) $($(host_os)_packages) $(rust_packages_) $(proton_packages_) $(wallet_packages_) +packages += $($(host_arch)_$(host_os)_packages) $($(host_os)_packages) $(rust_packages_) $(proton_packages_) $(websocketpp_packages_) $(wallet_packages_) native_packages += $($(host_arch)_$(host_os)_native_packages) $($(host_os)_native_packages) all_packages = $(packages) $(native_packages) diff --git a/depends/packages/azmq.mk b/depends/packages/azmq.mk new file mode 100644 index 00000000000..2978a9fe8e4 --- /dev/null +++ b/depends/packages/azmq.mk @@ -0,0 +1,25 @@ +package=azmq +$(package)_version=1.0 +$(package)_download_path=https://github.com/zeromq/$(package)/archive/ +$(package)_file_name=$(package)-$($(package)_version).tar.gz +$(package)_download_file=v$($(package)_version).tar.gz +$(package)_sha256_hash=c204c731bcb7810ca3a2c5515e88974ef2ff8d0589e60a897dc238b369180e7b +$(package)_dependencies=boost zeromq +$(package)_patches=libsodium.patch + +define $(package)_preprocess_cmds + patch -p1 < $($(package)_patch_dir)/libsodium.patch && \ + mkdir build +endef + +define $(package)_config_cmds + cd build; cmake .. -DCMAKE_INSTALL_PREFIX=/ -DBOOST_ROOT=$(host_prefix) -DZMQ_ROOT=$(host_prefix) +endef + +define $(package)_build_cmds + cd build; $(MAKE) VERBOSE=1 +endef + +define $(package)_stage_cmds + cd build; $(MAKE) VERBOSE=1 DESTDIR=$($(package)_staging_prefix_dir) install +endef diff --git a/depends/packages/boost.mk b/depends/packages/boost.mk index 0c545c4d596..33c67a8bee0 100644 --- a/depends/packages/boost.mk +++ b/depends/packages/boost.mk @@ -20,7 +20,7 @@ $(package)_toolset_$(host_os)=gcc $(package)_archiver_$(host_os)=$($(package)_ar) $(package)_toolset_darwin=darwin $(package)_archiver_darwin=$($(package)_libtool) -$(package)_config_libraries=chrono,filesystem,program_options,system,thread,test +$(package)_config_libraries=chrono,filesystem,program_options,system,thread,test,date_time,log,regex,random $(package)_cxxflags=-std=c++11 -fvisibility=hidden $(package)_cxxflags_linux=-fPIC endef diff --git a/depends/packages/cppzmq.mk b/depends/packages/cppzmq.mk new file mode 100644 index 00000000000..0a7a2f906cf --- /dev/null +++ b/depends/packages/cppzmq.mk @@ -0,0 +1,12 @@ +package=cppzmq +$(package)_version=4.2.2 +$(package)_download_path=https://github.com/zeromq/$(package)/archive/ +$(package)_file_name=$(package)-$($(package)_version).tar.gz +$(package)_download_file=v$($(package)_version).tar.gz +$(package)_sha256_hash=3ef50070ac5877c06c6bb25091028465020e181bbfd08f110294ed6bc419737d +$(package)_dependencies=zeromq + +define $(package)_stage_cmds + mkdir $($(package)_staging_dir)$(host_prefix)/include/ && \ + cp zmq.hpp $($(package)_staging_dir)$(host_prefix)/include/ +endef diff --git a/depends/packages/packages.mk b/depends/packages/packages.mk index 296323c8cd3..a25bb0cbd97 100644 --- a/depends/packages/packages.mk +++ b/depends/packages/packages.mk @@ -1,6 +1,7 @@ rust_packages := rust librustzcash proton_packages := proton -zcash_packages := libgmp libsodium +websocketpp_packages := websocketpp +zcash_packages := azmq cppzmq libgmp libsodium packages := boost openssl libevent zeromq $(zcash_packages) googletest native_packages := native_ccache diff --git a/depends/packages/websocketpp.mk b/depends/packages/websocketpp.mk new file mode 100644 index 00000000000..8b28e19a8bb --- /dev/null +++ b/depends/packages/websocketpp.mk @@ -0,0 +1,13 @@ +package=websocketpp +$(package)_version=0.7.0 + +$(package)_download_path=https://github.com/zaphoyd/$(package)/archive/ +$(package)_file_name=$(package)-$($(package)_version).tar.gz +$(package)_download_file=$($(package)_version).tar.gz +$(package)_sha256_hash=07b3364ad30cda022d91759d4b83ff902e1ebadb796969e58b59caa535a03923 + +define $(package)_stage_cmds + mkdir -p $($(package)_staging_dir)$(host_prefix)/include && \ + cp -a ./websocketpp $($(package)_staging_dir)$(host_prefix)/include +endef + diff --git a/depends/packages/zeromq.mk b/depends/packages/zeromq.mk index 3cca06dae08..13f573277cc 100644 --- a/depends/packages/zeromq.mk +++ b/depends/packages/zeromq.mk @@ -1,11 +1,12 @@ package=zeromq -$(package)_version=4.2.1 +$(package)_version=4.2.3 $(package)_download_path=https://github.com/zeromq/libzmq/releases/download/v$($(package)_version)/ $(package)_file_name=$(package)-$($(package)_version).tar.gz -$(package)_sha256_hash=27d1e82a099228ee85a7ddb2260f40830212402c605a4a10b5e5498a7e0e9d03 +$(package)_sha256_hash=8f1e2b2aade4dbfde98d82366d61baef2f62e812530160d2e6d0a5bb24e40bc0 +$(package)_dependencies=libsodium define $(package)_set_vars - $(package)_config_opts=--without-documentation --disable-shared --disable-curve + $(package)_config_opts=--without-documentation --disable-shared --with-libsodium $(package)_config_opts_linux=--with-pic $(package)_cxxflags=-std=c++11 endef diff --git a/depends/patches/azmq/libsodium.patch b/depends/patches/azmq/libsodium.patch new file mode 100644 index 00000000000..746c9966c8b --- /dev/null +++ b/depends/patches/azmq/libsodium.patch @@ -0,0 +1,22 @@ +diff -ur azmq-1.0-orig/CMakeLists.txt azmq-1.0/CMakeLists.txt +--- azmq-1.0-orig/CMakeLists.txt 2017-09-25 12:08:31.487969892 +0100 ++++ azmq-1.0/CMakeLists.txt 2017-09-25 12:13:45.449318390 +0100 +@@ -49,18 +49,5 @@ + ${ZeroMQ_INCLUDE_DIRS} + ${PROJECT_SOURCE_DIR}) + +-enable_testing() +- +-macro(add_catch_test name) +- if (TEST_REPORT_FORMAT) +- add_test(NAME ${name} COMMAND ${name} -r ${TEST_REPORT_FORMAT} -o "${name}.test_out.xml") +- else() +- add_test(NAME ${name} COMMAND ${name}) +- endif() +-endmacro() +- +-add_subdirectory(test) +-add_subdirectory(doc) +- + install(DIRECTORY ${PROJECT_SOURCE_DIR}/azmq + DESTINATION include) diff --git a/src/Makefile.am b/src/Makefile.am index 4d76176f304..7be894b25e0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -112,6 +112,10 @@ LIBZCASH_H = \ zcash/util.h \ zcash/Zcash.h +if ENABLE_PROVING_SERVICE +LIBZCASH_H += zcash/ProvingServiceClient.hpp +endif + .PHONY: FORCE collate-libsnark check-symbols check-security # bitcoin core # BITCOIN_CORE_H = \ @@ -434,8 +438,6 @@ zcashd_LDADD = \ $(LIBUNIVALUE) \ $(LIBBITCOIN_UTIL) \ $(LIBBITCOIN_CRYPTO) \ - $(LIBZCASH) \ - $(LIBSNARK) \ $(LIBLEVELDB) \ $(LIBMEMENV) \ $(LIBSECP256K1) @@ -449,6 +451,8 @@ zcashd_LDADD += libbitcoin_wallet.a endif zcashd_LDADD += \ + $(LIBZCASH) \ + $(LIBSNARK) \ $(BOOST_LIBS) \ $(BDB_LIBS) \ $(SSL_LIBS) \ @@ -527,6 +531,10 @@ libzcash_a_SOURCES = \ zcash/circuit/prfs.tcc \ zcash/circuit/utils.tcc +if ENABLE_PROVING_SERVICE +libzcash_a_SOURCES += zcash/ProvingServiceClient.cpp +endif + libzcash_a_CPPFLAGS = -DMULTICORE -fopenmp -fPIC -DBINARY_OUTPUT -DCURVE_ALT_BN128 -DBOOST_SPIRIT_THREADSAFE -DHAVE_BUILD_INFO -D__STDC_FORMAT_MACROS $(HARDENED_CPPFLAGS) -pipe -O1 -g -Wstack-protector -fstack-protector-all -fPIE -fvisibility=hidden -DSTATIC $(BITCOIN_INCLUDES) libzcash_a_CXXFLAGS = $(HARDENED_CXXFLAGS) -fwrapv -fno-strict-aliasing diff --git a/src/Makefile.zcash.include b/src/Makefile.zcash.include index 177931c889a..bdadc3e169d 100644 --- a/src/Makefile.zcash.include +++ b/src/Makefile.zcash.include @@ -2,6 +2,42 @@ noinst_PROGRAMS += \ zcash/GenerateParams \ zcash/CreateJoinSplit +if BUILD_ZCASH_PROVING_SERVICE +noinst_PROGRAMS += zcash-proving-service + +# proving service daemon +zcash_proving_service_SOURCES = zcash-proving-service.cpp +zcash_proving_service_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +zcash_proving_service_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +zcash_proving_service_LDADD = \ + $(LIBBITCOIN_COMMON) \ + $(LIBZCASH) \ + $(LIBSNARK) \ + $(LIBBITCOIN_UTIL) \ + $(LIBBITCOIN_CRYPTO) \ + $(BOOST_LIBS) \ + $(ZMQ_LIBS) \ + $(LIBZCASH_LIBS) +endif + +if ENABLE_PROVING_SERVICE +noinst_PROGRAMS += zcash/ExampleProvingServiceClient + +# example client for proving service +zcash_ExampleProvingServiceClient_SOURCES = zcash/ExampleProvingServiceClient.cpp +zcash_ExampleProvingServiceClient_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +zcash_ExampleProvingServiceClient_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +zcash_ExampleProvingServiceClient_LDADD = \ + $(LIBBITCOIN_COMMON) \ + $(LIBZCASH) \ + $(LIBSNARK) \ + $(LIBBITCOIN_UTIL) \ + $(LIBBITCOIN_CRYPTO) \ + $(BOOST_LIBS) \ + $(ZMQ_LIBS) \ + $(LIBZCASH_LIBS) +endif + # tool for generating our public parameters zcash_GenerateParams_SOURCES = zcash/GenerateParams.cpp zcash_GenerateParams_CPPFLAGS = $(AM_CPPFLAGS) diff --git a/src/gtest/test_joinsplit.cpp b/src/gtest/test_joinsplit.cpp index 986592e8995..40bee5b2c6a 100644 --- a/src/gtest/test_joinsplit.cpp +++ b/src/gtest/test_joinsplit.cpp @@ -55,7 +55,7 @@ void test_full_api(ZCJoinSplit* js) boost::array output_notes; // Perform the proof - proof = js->prove( + proof = js->prove(js->witness( inputs, outputs, output_notes, @@ -69,7 +69,7 @@ void test_full_api(ZCJoinSplit* js) vpub_old, vpub_new, rt - ); + )); } // Verify the transaction: @@ -130,7 +130,7 @@ void test_full_api(ZCJoinSplit* js) boost::array output_notes; // Perform the proof - proof = js->prove( + proof = js->prove(js->witness( inputs, outputs, output_notes, @@ -144,7 +144,7 @@ void test_full_api(ZCJoinSplit* js) vpub_old, vpub_new, rt - ); + )); } // Verify the transaction: @@ -182,7 +182,7 @@ void invokeAPI( boost::array output_notes; - ZCProof proof = js->prove( + auto witness = js->witness( inputs, outputs, output_notes, @@ -195,8 +195,7 @@ void invokeAPI( commitments, vpub_old, vpub_new, - rt, - false + rt ); } diff --git a/src/init.cpp b/src/init.cpp index 7df31ab1bbe..7717ac9b2bd 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -57,6 +57,7 @@ #if ENABLE_ZMQ #include "zmq/zmqnotificationinterface.h" +#include #endif #if ENABLE_PROTON @@ -406,6 +407,12 @@ std::string HelpMessage(HelpMessageMode mode) CURRENCY_UNIT, FormatMoney(CWallet::minTxFee.GetFeePerK()))); strUsage += HelpMessageOpt("-paytxfee=", strprintf(_("Fee (in %s/kB) to add to transactions you send (default: %s)"), CURRENCY_UNIT, FormatMoney(payTxFee.GetFeePerK()))); +#if ENABLE_PROVING_SERVICE + // Reword when out of experimental features + strUsage += HelpMessageOpt("-provingservice=
", _("EXPERIMENTAL: Use proving service at
for creating shielded transactions")); + strUsage += HelpMessageOpt("-provingservicepubkey=", _("EXPERIMENTAL: Public key for the proving service")); + strUsage += HelpMessageOpt("-provingserviceclientkey=", _("EXPERIMENTAL: Client key for authenticating with the proving service, if required")); +#endif strUsage += HelpMessageOpt("-rescan", _("Rescan the block chain for missing wallet transactions") + " " + _("on startup")); strUsage += HelpMessageOpt("-salvagewallet", _("Attempt to recover private keys from a corrupt wallet.dat") + " " + _("on startup")); strUsage += HelpMessageOpt("-sendfreetransactions", strprintf(_("Send transactions as zero-fee transactions if possible (default: %u)"), 0)); @@ -790,6 +797,13 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) else if (mapArgs.count("-paymentdisclosure")) { return InitError(_("Payment disclosure requires -experimentalfeatures.")); } +#if ENABLE_PROVING_SERVICE + if (mapArgs.count("-provingservice") || + mapArgs.count("-provingservicepubkey") || + mapArgs.count("-provingserviceclientkey")) { + return InitError(_("Proving service requires -experimentalfeatures.")); + } +#endif } // Set this early so that parameter interactions go to console @@ -1035,6 +1049,20 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) } } +#if ENABLE_PROVING_SERVICE + if (mapArgs.count("-provingservice")) { + if (GetArg("-provingservicepubkey", "").length() != 40) { + return InitError(_("Invalid proving service public key")); + } + auto secretKey = GetArg("-provingserviceclientkey", ""); + if (!secretKey.empty()) { + char tmp [41]; + if (secretKey.length() != 40 || zmq_curve_public(tmp, secretKey.c_str()) != 0) { + return InitError(_("Invalid proving service client key")); + } + } + } +#endif // ********************************************************* Step 4: application initialization: dir lock, daemonize, pidfile, debug log diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index 4141bea4547..45435a70ddc 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -22,7 +22,7 @@ JSDescription::JSDescription(ZCJoinSplit& params, { boost::array notes; - proof = params.prove( + witness = params.witness( inputs, outputs, notes, @@ -36,9 +36,11 @@ JSDescription::JSDescription(ZCJoinSplit& params, vpub_old, vpub_new, anchor, - computeProof, esk // payment disclosure ); + if (computeProof) { + proof = params.prove(witness); + } } JSDescription JSDescription::Randomized( diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index f9723e3992e..5f9797d7870 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -68,6 +68,9 @@ class JSDescription // This is a zk-SNARK which ensures that this JoinSplit is valid. libzcash::ZCProof proof; + // In-memory only + ZCJSProofWitness witness; + JSDescription(): vpub_old(0), vpub_new(0) { } JSDescription(ZCJoinSplit& params, diff --git a/src/test/rpc_wallet_tests.cpp b/src/test/rpc_wallet_tests.cpp index e444eb2a5ae..5099a2ceed6 100644 --- a/src/test/rpc_wallet_tests.cpp +++ b/src/test/rpc_wallet_tests.cpp @@ -1174,6 +1174,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals) info.vjsin.clear(); try { proxy.perform_joinsplit(info); + BOOST_ERROR( "Expected perform_joinsplit() to fail"); } catch (const std::runtime_error & e) { BOOST_CHECK( string(e.what()).find("error verifying joinsplit")!= string::npos); } diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index 539d5d7d660..211d51e19a2 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -99,6 +99,16 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany( throw JSONRPCError(RPC_INVALID_PARAMETER, "Minconf cannot be zero when sending from zaddr"); } +#if ENABLE_PROVING_SERVICE + provingService_.set_log_id(getId()); + provingService_.configure_from_options(); + + // Throw an error if proving service is configured and not explicitly enabled for z-addresses + if (isfromzaddr_ && provingService_.is_configured() && !provingService_.z_addresses_enabled()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Proving service is not enabled for z-addresses, for security"); + } +#endif + // Log the context info i.e. the call parameters to z_sendmany if (LogAcceptCategory("zrpcunsafe")) { LogPrint("zrpcunsafe", "%s: z_sendmany initialized (params=%s)\n", getId(), contextInfo.write()); @@ -748,19 +758,84 @@ bool AsyncRPCOperation_sendmany::main_impl() { } +void AsyncRPCOperation_sendmany::update_joinsplit_sig(CMutableTransaction& mtx) +{ + // Empty output script. + CScript scriptCode; + CTransaction signTx(mtx); + uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL); + + // Add the signature + if (!(crypto_sign_detached(&mtx.joinSplitSig[0], NULL, + dataToBeSigned.begin(), 32, + joinSplitPrivKey_ + ) == 0)) + { + throw std::runtime_error("crypto_sign_detached failed"); + } + + // Sanity check + if (!(crypto_sign_verify_detached(&mtx.joinSplitSig[0], + dataToBeSigned.begin(), 32, + mtx.joinSplitPubKey.begin() + ) == 0)) + { + throw std::runtime_error("crypto_sign_verify_detached failed"); + } +} + /** * Sign and send a raw transaction. * Raw transaction as hex string should be in object field "rawtxn" */ void AsyncRPCOperation_sendmany::sign_send_raw_transaction(UniValue obj) -{ - // Sign the raw transaction +{ UniValue rawtxnValue = find_value(obj, "rawtxn"); if (rawtxnValue.isNull()) { throw JSONRPCError(RPC_WALLET_ERROR, "Missing hex data for raw transaction"); } std::string rawtxn = rawtxnValue.get_str(); +#if ENABLE_PROVING_SERVICE + if (!testmode && provingService_.is_configured()) { + CDataStream stream(ParseHex(rawtxn), SER_NETWORK, PROTOCOL_VERSION); + CTransaction tx; + stream >> tx; + CMutableTransaction mtx(tx); + + if (mtx.vjoinsplit.size() != witnesses_.size()) { + throw std::runtime_error("Invalid usage"); + } + + auto res = provingService_.get_proofs(witnesses_); + if (!res) { + throw std::runtime_error(strprintf("Failed to fetch proofs from proving service")); + } + auto proofs = *res; + + if (mtx.vjoinsplit.size() != proofs.size()) { + throw std::runtime_error(strprintf("Proving service returned %d proofs, expected %d", proofs.size(), tx.vjoinsplit.size())); + } + + auto verifier = libzcash::ProofVerifier::Strict(); + for (size_t i = 0; i < mtx.vjoinsplit.size(); i++) { + mtx.vjoinsplit[i].proof = proofs[i]; + + if (!(mtx.vjoinsplit[i].Verify(*pzcashParams, verifier, joinSplitPubKey_))) { + throw std::runtime_error("Proving service returned invalid proof"); + } + } + + update_joinsplit_sig(mtx); + + CTransaction rawTx(mtx); + stream.clear(); + stream << rawTx; + rawtxn = HexStr(stream.begin(), stream.end()); + } +#endif + + // Sign the raw transaction UniValue params = UniValue(UniValue::VARR); params.push_back(rawtxn); UniValue signResultValue = signrawtransaction(params, false); @@ -959,7 +1034,12 @@ UniValue AsyncRPCOperation_sendmany::perform_joinsplit( FormatMoney(info.vjsout[0].value), FormatMoney(info.vjsout[1].value) ); - // Generate the proof, this can take over a minute. + // If generating the proof here, this can take over a minute. +#if ENABLE_PROVING_SERVICE + bool generateProof = !(this->testmode || provingService_.is_configured()); +#else + bool generateProof = !this->testmode; +#endif boost::array inputs {info.vjsin[0], info.vjsin[1]}; boost::array outputs @@ -979,38 +1059,25 @@ UniValue AsyncRPCOperation_sendmany::perform_joinsplit( outputMap, info.vpub_old, info.vpub_new, - !this->testmode, + generateProof, &esk); // parameter expects pointer to esk, so pass in address - { + + // If testmode is set, we want to trigger the following error (for detection in the unit test) + if (generateProof || this->testmode) { auto verifier = libzcash::ProofVerifier::Strict(); if (!(jsdesc.Verify(*pzcashParams, verifier, joinSplitPubKey_))) { throw std::runtime_error("error verifying joinsplit"); } +#if ENABLE_PROVING_SERVICE + } else if (!this->testmode) { + witnesses_.push_back(jsdesc.witness); +#endif } mtx.vjoinsplit.push_back(jsdesc); - // Empty output script. - CScript scriptCode; - CTransaction signTx(mtx); - uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL); - - // Add the signature - if (!(crypto_sign_detached(&mtx.joinSplitSig[0], NULL, - dataToBeSigned.begin(), 32, - joinSplitPrivKey_ - ) == 0)) - { - throw std::runtime_error("crypto_sign_detached failed"); - } - - // Sanity check - if (!(crypto_sign_verify_detached(&mtx.joinSplitSig[0], - dataToBeSigned.begin(), 32, - mtx.joinSplitPubKey.begin() - ) == 0)) - { - throw std::runtime_error("crypto_sign_verify_detached failed"); + if (generateProof) { + update_joinsplit_sig(mtx); } CTransaction rawTx(mtx); diff --git a/src/wallet/asyncrpcoperation_sendmany.h b/src/wallet/asyncrpcoperation_sendmany.h index 69bdbe31536..3e7c0700e0e 100644 --- a/src/wallet/asyncrpcoperation_sendmany.h +++ b/src/wallet/asyncrpcoperation_sendmany.h @@ -13,6 +13,9 @@ #include "zcash/Address.hpp" #include "wallet.h" #include "paymentdisclosure.h" +#if ENABLE_PROVING_SERVICE +#include "zcash/ProvingServiceClient.hpp" +#endif #include #include @@ -94,6 +97,11 @@ class AsyncRPCOperation_sendmany : public AsyncRPCOperation { std::vector z_inputs_; CTransaction tx_; + +#if ENABLE_PROVING_SERVICE + ProvingServiceClient provingService_; + std::vector witnesses_; +#endif void add_taddr_change_output_to_tx(CAmount amount); void add_taddr_outputs_to_tx(); @@ -114,6 +122,8 @@ class AsyncRPCOperation_sendmany : public AsyncRPCOperation { std::vector> witnesses, uint256 anchor); + void update_joinsplit_sig(CMutableTransaction& mtx); // throws exception if there was an error + void sign_send_raw_transaction(UniValue obj); // throws exception if there was an error // payment disclosure! diff --git a/src/zcash-proving-service.cpp b/src/zcash-proving-service.cpp new file mode 100644 index 00000000000..102f32b1845 --- /dev/null +++ b/src/zcash-proving-service.cpp @@ -0,0 +1,253 @@ +// Copyright (c) 2017 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "primitives/transaction.h" +#include "streams.h" +#include "util.h" +#include "version.h" +#include "zcash/JoinSplit.hpp" +#include "zcash/Proof.hpp" + +#if ENABLE_ZMQ +#include +#endif +#include +#if ENABLE_WEBSOCKET +#include +#include +#include +#endif +#include + +#include "libsnark/common/profiling.hpp" + +using namespace libzcash; + +#if ENABLE_ZMQ +std::string DEFAULT_ZMQ_BIND_ADDRESS = "tcp://*:8234"; +#endif + +#if ENABLE_WEBSOCKET +uint16_t DEFAULT_WS_BIND_PORT = 9002; +typedef websocketpp::server ws_server_t; +#endif + +uint32_t estimate_proof_time(ZCJoinSplit* p) +{ + std::cout << "Measuring time to compute one proof…" << std::flush; + struct timeval tv_start; + gettimeofday(&tv_start, 0); + + // Compute a dummy proof + uint256 anchor = ZCIncrementalMerkleTree().root(); + uint256 pubKeyHash; + JSDescription jsdesc(*p, + pubKeyHash, + anchor, + {JSInput(), JSInput()}, + {JSOutput(), JSOutput()}, + 0, + 0); + + struct timeval tv_end; + gettimeofday(&tv_end, 0); + uint32_t elapsed = tv_end.tv_sec - tv_start.tv_sec; + std::cout << " " << elapsed << " seconds" << std::endl; + return elapsed; +} + +CDataStream get_estimate(CDataStream ssRequest, uint32_t oneProof) +{ + uint32_t numProofs; + ssRequest >> numProofs; + std::cout << "Received request for " << numProofs << " proofs!" << std::endl; + + // Estimate callback + uint32_t estimate = oneProof * numProofs; + std::cout << "- Estimated proving time: " << estimate << " seconds" << std::endl; + + CDataStream ssConfirm(SER_NETWORK, PROTOCOL_VERSION); + ssConfirm << estimate; + return ssConfirm; +} + +CDataStream get_proofs(ZCJoinSplit* p, CDataStream ssWitnesses) +{ + std::vector witnesses; + ssWitnesses >> witnesses; + std::cout << "Received " << witnesses.size() << " witnesses!" << std::endl; + + std::cout << "- Running prover…" << std::flush; + std::vector proofs; + for (auto witness : witnesses) { + proofs.push_back(p->prove(witness)); + std::cout << "…" << std::flush; + } + std::cout << " Done!" << std::endl; + + CDataStream ssProofs(SER_NETWORK, PROTOCOL_VERSION); + ssProofs << proofs; + return ssProofs; +} + +CDataStream handle_request(ZCJoinSplit* p, CDataStream ssRequest, uint32_t oneProof) +{ + uint32_t protoVersion; + ssRequest >> protoVersion; + assert(protoVersion == 0); + + CDataStream ssResponse(SER_NETWORK, PROTOCOL_VERSION); + ssResponse << protoVersion; + + uint8_t msgType; + ssRequest >> msgType; + if (msgType == 0) { + ssResponse << get_estimate(ssRequest, oneProof); + } else if (msgType == 1) { + ssResponse << get_proofs(p, ssRequest); + } else { + // Error + } + + return ssResponse; +} + +#if ENABLE_ZMQ +void zmq_receive_request(ZCJoinSplit* p, azmq::rep_socket& socket, uint32_t oneProof) +{ + socket.async_receive([p, &socket, oneProof](boost::system::error_code const& ec, azmq::message& request, size_t bytes_transferred) { + const char* data = (const char*)request.data(); + CDataStream ssRequest(data, data + request.size(), SER_NETWORK, PROTOCOL_VERSION); + + auto ssResponse = handle_request(p, ssRequest, oneProof); + + std::vector serialized(ssResponse.begin(), ssResponse.end()); + socket.send(boost::asio::buffer(serialized)); + + zmq_receive_request(p, socket, oneProof); + }); +} +#endif + +#if ENABLE_WEBSOCKET +void on_websocket_message(ZCJoinSplit* p, ws_server_t* s, websocketpp::connection_hdl hdl, ws_server_t::message_ptr msg) +{ + if (msg->get_opcode() == websocketpp::frame::opcode::text) { + std::cout << "on_websocket message called with hdl: " << hdl.lock().get() + << " and text message: " << msg->get_payload() + << std::endl; + return; + } + + auto payload = msg->get_raw_payload(); + CDataStream ssWitnesses(payload.data(), payload.data() + payload.length(), SER_NETWORK, PROTOCOL_VERSION); + + auto ssProofs = get_proofs(p, ssWitnesses); + std::vector serialized(ssProofs.begin(), ssProofs.end()); + + try { + s->send(hdl, serialized.data(), serialized.size(), websocketpp::frame::opcode::binary); + } catch (const websocketpp::lib::error_code& e) { + std::cout << "Response failed because: " << e + << "(" << e.message() << ")" << std::endl; + } +} +#endif + +int main(int argc, char* argv[]) +{ + SetupEnvironment(); + + libsnark::inhibit_profiling_info = true; + libsnark::inhibit_profiling_counters = true; + + if (argc > 1 && argv[1][0] == '-' && + (argv[1][1] == '?' || argv[1][1] == 'h' || (argv[1][1] == '-' && argv[1][2] == 'h' && argv[1][3] == 'e' && + argv[1][4] == 'l' && argv[1][5] == 'p'))) { +#if (ENABLE_ZMQ) && (ENABLE_WEBSOCKET) + std::cerr << "Usage: zcash-proving-service (zmqBindAddress) (wsBindPort)" << std::endl; + std::cerr << "zmqBindAddress is a ZMQ bind address, default " << DEFAULT_ZMQ_BIND_ADDRESS << std::endl; + std::cerr << "wsBindPort is a WebSocket bind address, default " << DEFAULT_WS_BIND_PORT << std::endl; +#elif (ENABLE_ZMQ) + std::cerr << "Usage: zcash-proving-service (bindAddress)" << std::endl; + std::cerr << "bindAddress is a ZMQ bind address, default " << DEFAULT_ZMQ_BIND_ADDRESS << std::endl; +#else + std::cerr << "Usage: zcash-proving-service (bindPort)" << std::endl; + std::cerr << "bindPort is a WebSocket bind address, default " << DEFAULT_WS_BIND_PORT << std::endl; +#endif + return 1; + } + + auto p = ZCJoinSplit::Prepared((ZC_GetParamsDir() / "sprout-verifying.key").string(), + (ZC_GetParamsDir() / "sprout-proving.key").string()); + + boost::asio::io_service ios; + auto oneProof = estimate_proof_time(p); + +#if (ENABLE_ZMQ) && (ENABLE_WEBSOCKET) + size_t zmq_addr = 0; + size_t ws_port = 0; + if (argc >= 2) { + zmq_addr = 1; + } + if (argc >= 3) { + ws_port = 2; + } +#elif (ENABLE_ZMQ) + size_t zmq_addr = 0; + if (argc >= 2) { + zmq_addr = 1; + } +#else + size_t ws_port = 0; + if (argc >= 2) { + ws_port = 1; + } +#endif + +#if ENABLE_ZMQ + char publicKey[41]; + char secretKey[41]; + assert(zmq_curve_keypair(publicKey, secretKey) == 0); + + azmq::rep_socket zmq_socket(ios); + zmq_socket.set_option(azmq::socket::curve_server(1)); + zmq_socket.set_option(azmq::socket::curve_privatekey(secretKey)); + if (zmq_addr > 0) { + zmq_socket.bind(argv[zmq_addr]); + std::cout << "Listening with ZMQ on " << argv[zmq_addr] << std::endl; + } else { + zmq_socket.bind(DEFAULT_ZMQ_BIND_ADDRESS); + std::cout << "Listening with ZMQ on " << DEFAULT_ZMQ_BIND_ADDRESS << std::endl; + } + std::cout << "Proving service ZMQ public key: " << publicKey << std::endl; + + zmq_receive_request(p, zmq_socket, oneProof); +#endif + +#if ENABLE_WEBSOCKET + ws_server_t ws_server; + ws_server.init_asio(&ios); + ws_server.set_message_handler(std::bind( + &on_websocket_message, p, &ws_server, + std::placeholders::_1, std::placeholders::_2)); + if (ws_port > 0) { + std::istringstream ss(argv[ws_port]); + uint16_t port; + if (!(ss >> port)) { + std::cerr << "Invalid port " << argv[ws_port] << '\n'; + return 1; + } + ws_server.listen(port); + std::cout << "Listening with WebSocket on port " << port << std::endl; + } else { + ws_server.listen(DEFAULT_WS_BIND_PORT); + std::cout << "Listening with WebSocket on port " << DEFAULT_WS_BIND_PORT << std::endl; + } + + ws_server.start_accept(); +#endif + + ios.run(); +} diff --git a/src/zcash/ExampleProvingServiceClient.cpp b/src/zcash/ExampleProvingServiceClient.cpp new file mode 100644 index 00000000000..e1b80d3fecb --- /dev/null +++ b/src/zcash/ExampleProvingServiceClient.cpp @@ -0,0 +1,84 @@ +// Copyright (c) 2017 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "../util.h" +#include "primitives/transaction.h" +#include "streams.h" +#include "utilstrencodings.h" +#include "version.h" +#include "zcash/JoinSplit.hpp" +#include "zcash/Proof.hpp" +#include "zcash/ProvingServiceClient.hpp" + +#include + +#include "libsnark/common/profiling.hpp" + +using namespace libzcash; + +int main(int argc, char** argv) +{ + SetupEnvironment(); + + libsnark::inhibit_profiling_info = true; + libsnark::inhibit_profiling_counters = true; + + auto p = ZCJoinSplit::Prepared((ZC_GetParamsDir() / "sprout-verifying.key").string(), + (ZC_GetParamsDir() / "sprout-proving.key").string()); + + int32_t numJSDescs = 5; + if (argc > 3) { + ParseInt32(std::string(argv[3]), &numJSDescs); + } + if (argc < 3 || numJSDescs <= 0) { + std::cerr << "Usage: ExampleProvingServiceClient [provingServiceAddress] [provingServicePubKey] (numJSDescs)" << std::endl; + std::cerr << "provingServiceAddress is a ZMQ address, e.g. tcp://localhost:8234" << std::endl; + std::cerr << "provingServicePubKey is printed out by the server; surround it with single quotes in Bash" << std::endl; + std::cerr << "If provided, numJSDescs must be a positive integer." << std::endl; + return 1; + } + + if (strlen(argv[2]) != 40) { + std::cerr << "Invalid proving service public key" << std::endl; + return 1; + } + + // construct several proofs. + uint256 anchor = ZCIncrementalMerkleTree().root(); + uint256 pubKeyHash; + std::vector jsdescs; + std::vector witnesses; + for (size_t i = 0; i < numJSDescs; i++) { + JSDescription jsdesc(*p, + pubKeyHash, + anchor, + {JSInput(), JSInput()}, + {JSOutput(), JSOutput()}, + 0, + 0, + false); + jsdescs.push_back(jsdesc); + witnesses.push_back(jsdesc.witness); + } + + auto client = ProvingServiceClient("ExampleProvingServiceClient", argv[1], argv[2]); + auto res = client.get_proofs(witnesses); + if (!res) { + std::cerr << "Failed to fetch proofs" << std::endl; + return 1; + } + auto proofs = *res; + + for (size_t i = 0; i < numJSDescs; i++) { + jsdescs[i].proof = proofs[i]; + std::cout << "- Checking validity of proof " << i << "…" << std::flush; + auto verifier = ProofVerifier::Strict(); + if (jsdescs[i].Verify(*p, verifier, pubKeyHash)) { + std::cout << " Valid!" << std::endl; + } else { + std::cout << " Invalid!" << std::endl; + return 1; + } + } +} diff --git a/src/zcash/JoinSplit.cpp b/src/zcash/JoinSplit.cpp index 2685569d35d..3e29600cb4c 100644 --- a/src/zcash/JoinSplit.cpp +++ b/src/zcash/JoinSplit.cpp @@ -136,7 +136,7 @@ class JoinSplitCircuit : public JoinSplit { } } - ZCProof prove( + JSProofWitness witness( const boost::array& inputs, const boost::array& outputs, boost::array& out_notes, @@ -150,7 +150,6 @@ class JoinSplitCircuit : public JoinSplit { uint64_t vpub_old, uint64_t vpub_new, const uint256& rt, - bool computeProof, uint256 *out_esk // Payment disclosure ) { if (vpub_old > MAX_MONEY) { @@ -267,22 +266,23 @@ class JoinSplitCircuit : public JoinSplit { out_macs[i] = PRF_pk(inputs[i].key, i, h_sig); } - if (!computeProof) { - return ZCProof(); - } + return JSProofWitness( + phi, rt, h_sig, inputs, out_notes, vpub_old, vpub_new); + } + ZCProof prove(const JSProofWitness& witness) { protoboard pb; { joinsplit_gadget g(pb); g.generate_r1cs_constraints(); g.generate_r1cs_witness( - phi, - rt, - h_sig, - inputs, - out_notes, - vpub_old, - vpub_new + witness.phi, + witness.rt, + witness.h_sig, + witness.inputs, + witness.outputs, + witness.vpub_old, + witness.vpub_new ); } diff --git a/src/zcash/JoinSplit.hpp b/src/zcash/JoinSplit.hpp index 6a2d4e1f2ff..c59a8fb6547 100644 --- a/src/zcash/JoinSplit.hpp +++ b/src/zcash/JoinSplit.hpp @@ -26,6 +26,15 @@ class JSInput { Note note, SpendingKey key) : witness(witness), note(note), key(key) { } + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { + READWRITE(witness); + READWRITE(note); + READWRITE(key); + } + uint256 nullifier() const { return note.nullifier(key); } @@ -43,6 +52,44 @@ class JSOutput { Note note(const uint252& phi, const uint256& r, size_t i, const uint256& h_sig) const; }; +template +class JSProofWitness { +public: + uint252 phi; + uint256 rt; + uint256 h_sig; + boost::array inputs; + boost::array outputs; + uint64_t vpub_old; + uint64_t vpub_new; + + JSProofWitness() { } + JSProofWitness( + const uint252& phi, + const uint256& rt, + const uint256& h_sig, + const boost::array& inputs, + const boost::array& outputs, + uint64_t vpub_old, + uint64_t vpub_new) : + phi(phi), rt(rt), h_sig(h_sig), + inputs(inputs), outputs(outputs), + vpub_old(vpub_old), vpub_new(vpub_new) { } + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { + READWRITE(phi); + READWRITE(rt); + READWRITE(h_sig); + READWRITE(inputs); + READWRITE(outputs); + READWRITE(vpub_old); + READWRITE(vpub_new); + } +}; + template class JoinSplit { public: @@ -59,7 +106,7 @@ class JoinSplit { const uint256& pubKeyHash ); - virtual ZCProof prove( + virtual JSProofWitness witness( const boost::array& inputs, const boost::array& outputs, boost::array& out_notes, @@ -73,13 +120,16 @@ class JoinSplit { uint64_t vpub_old, uint64_t vpub_new, const uint256& rt, - bool computeProof = true, // For paymentdisclosure, we need to retrieve the esk. // Reference as non-const parameter with default value leads to compile error. // So use pointer for simplicity. uint256 *out_esk = nullptr ) = 0; + virtual ZCProof prove( + const JSProofWitness& witness + ) = 0; + virtual bool verify( const ZCProof& proof, ProofVerifier& verifier, @@ -99,6 +149,8 @@ class JoinSplit { } +typedef libzcash::JSProofWitness ZCJSProofWitness; typedef libzcash::JoinSplit ZCJoinSplit; diff --git a/src/zcash/Note.hpp b/src/zcash/Note.hpp index faacd2720d3..e16bc7c4504 100644 --- a/src/zcash/Note.hpp +++ b/src/zcash/Note.hpp @@ -20,6 +20,16 @@ class Note { Note(); + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { + READWRITE(a_pk); + READWRITE(value); + READWRITE(rho); + READWRITE(r); + } + uint256 cm() const; uint256 nullifier(const SpendingKey& a_sk) const; }; diff --git a/src/zcash/ProvingServiceClient.cpp b/src/zcash/ProvingServiceClient.cpp new file mode 100644 index 00000000000..558ae1d2ee0 --- /dev/null +++ b/src/zcash/ProvingServiceClient.cpp @@ -0,0 +1,123 @@ +// Copyright (c) 2017 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "ProvingServiceClient.hpp" + +#include "../util.h" +#include "streams.h" +#include "version.h" + +#include + +using namespace libzcash; + +ProvingServiceClient::ProvingServiceClient(std::string id, char* url, char* serverKey) : log_id(id), enableZAddresses(false), zmqUrl(url) +{ + assert(strlen(serverKey) == 40); + std::memcpy(zmqServerKey, serverKey, 41); + assert(zmq_curve_keypair(zmqPublicKey, zmqSecretKey) == 0); +} + +void ProvingServiceClient::configure_from_options() +{ + enableZAddresses = GetBoolArg("-i-control-the-proving-service-and-want-to-send-it-my-zkeys", false); + zmqUrl = GetArg("-provingservice", ""); + + if (!zmqUrl.empty()) { + // Parameters are checked in init.cpp + std::memcpy(zmqServerKey, GetArg("-provingservicepubkey", "").c_str(), 41); + auto secretKey = GetArg("-provingserviceclientkey", ""); + if (secretKey.empty()) { + assert(zmq_curve_keypair(zmqPublicKey, zmqSecretKey) == 0); + } else { + std::memcpy(zmqSecretKey, secretKey.c_str(), 41); + assert(zmq_curve_public(zmqPublicKey, zmqSecretKey) == 0); + } + } +} + +bool ProvingServiceClient::is_configured() +{ + return !zmqUrl.empty(); +} + +boost::optional> ProvingServiceClient::get_proofs(std::vector witnesses) +{ + zmq::context_t context(1); + zmq::socket_t socket(context, ZMQ_REQ); + socket.setsockopt(ZMQ_LINGER, 0); + socket.setsockopt(ZMQ_CURVE_SERVERKEY, zmqServerKey); + socket.setsockopt(ZMQ_CURVE_PUBLICKEY, zmqPublicKey); + socket.setsockopt(ZMQ_CURVE_SECRETKEY, zmqSecretKey); + socket.connect(zmqUrl); + + uint32_t protoVersion = 0; + std::vector items = {{socket, 0, ZMQ_POLLIN, 0}}; + uint32_t estimate; + { + CDataStream ssRequest(SER_NETWORK, PROTOCOL_VERSION); + uint32_t numWitnesses = witnesses.size(); + ssRequest << protoVersion; + ssRequest << (uint8_t)0; + ssRequest << numWitnesses; + std::vector serialized(ssRequest.begin(), ssRequest.end()); + + zmq::message_t request(serialized.size()); + memcpy(request.data(), serialized.data(), serialized.size()); + LogPrint("zrpc", "%s: Requesting estimate from proving service for %d proofs\n", log_id, numWitnesses); + socket.send(request); + + zmq::message_t reply; + LogPrint("zrpc", "%s: Waiting for time estimate…\n", log_id); + // 10s timeout on initial response + zmq::poll(items, 10 * 1000); + if (items[0].revents & ZMQ_POLLIN) { + socket.recv(&reply); + const char* data = (const char*)reply.data(); + CDataStream ssConfirm(data, data + reply.size(), SER_NETWORK, PROTOCOL_VERSION); + uint32_t confirmVersion; + ssConfirm >> confirmVersion; + assert(confirmVersion == protoVersion); + ssConfirm >> estimate; + LogPrint("zrpc", "%s: Time estimate: %d seconds\n", log_id, estimate); + } else { + LogPrint("zrpc", "%s: ERROR: Timeout waiting for time estimate"); + return boost::none; + } + } + + CDataStream ssWitnesses(SER_NETWORK, PROTOCOL_VERSION); + ssWitnesses << protoVersion; + ssWitnesses << (uint8_t)1; + ssWitnesses << witnesses; + std::vector serialized(ssWitnesses.begin(), ssWitnesses.end()); + + zmq::message_t request(serialized.size()); + memcpy(request.data(), serialized.data(), serialized.size()); + LogPrint("zrpc", "%s: Sending witnesses to proving service %s\n", log_id, zmqUrl); + socket.send(request); + + zmq::message_t reply; + LogPrint("zrpc", "%s: Waiting for proofs…\n", log_id); + // Add 5% margin to timeout for overhead + zmq::poll(items, estimate * 1050); + if (items[0].revents & ZMQ_POLLIN) { + socket.recv(&reply); + LogPrint("zrpc", "%s: Proofs received\n", log_id); + socket.close(); + } else { + LogPrint("zrpc", "%s: ERROR: Timeout waiting for time estimate"); + return boost::none; + } + + const char* data = (const char*)reply.data(); + CDataStream ssProofs(data, data + reply.size(), SER_NETWORK, PROTOCOL_VERSION); + uint32_t proofsVersion; + std::vector proofs; + ssProofs >> proofsVersion; + assert(proofsVersion == protoVersion); + ssProofs >> proofs; + + return proofs; +} diff --git a/src/zcash/ProvingServiceClient.hpp b/src/zcash/ProvingServiceClient.hpp new file mode 100644 index 00000000000..1c1c5540c20 --- /dev/null +++ b/src/zcash/ProvingServiceClient.hpp @@ -0,0 +1,50 @@ +// Copyright (c) 2017 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "zcash/JoinSplit.hpp" +#include "zcash/Proof.hpp" + +#include +#include + +using namespace libzcash; + +class ProvingServiceClient +{ +private: + std::string log_id; + bool enableZAddresses; + + std::string zmqUrl; + char zmqServerKey[41]; + char zmqPublicKey[41]; + char zmqSecretKey[41]; + +public: + ProvingServiceClient() : log_id(""), enableZAddresses(false), zmqUrl("") {} + + /** + * Create a ProvingServiceClient with a random client keypair. + */ + ProvingServiceClient(std::string id, char* url, char* serverKey); + + void set_log_id(std::string id) + { + log_id = id; + } + + /** + * Create a ProvingServiceClient using the configured options. + */ + void configure_from_options(); + + bool is_configured(); + + bool z_addresses_enabled() + { + return enableZAddresses; + } + + boost::optional> get_proofs(std::vector witnesses); +}; diff --git a/zcutil/build.sh b/zcutil/build.sh index 17f023991fc..afcfbf9dc5e 100755 --- a/zcutil/build.sh +++ b/zcutil/build.sh @@ -41,6 +41,11 @@ if [[ -z "${CXX-}" ]]; then CXX=g++ fi +# Allow users to set arbitary compile flags. Most users will not need this. +if [[ -z "${CONFIGURE_FLAGS-}" ]]; then + CONFIGURE_FLAGS="" +fi + if [ "x$*" = 'x--help' ] then cat <