From a41d5fe01947f2f878c055670986a165af800f9a Mon Sep 17 00:00:00 2001 From: Gavin Andresen Date: Mon, 22 Jul 2013 16:50:39 +1000 Subject: [PATCH] Payment Protocol: X509-validated payment requests Add support for a Payment Protocol to Bitcoin-Qt. Payment messages are protocol-buffer encoded and communicated over http(s), so this adds a dependency on the Google protocol buffer library, and requires Qt with OpenSSL support. --- bitcoin-qt.pro | 28 +- contrib/gitian-descriptors/README | 2 + contrib/gitian-descriptors/gitian-win32.yml | 16 +- contrib/gitian-descriptors/gitian.yml | 22 +- contrib/gitian-descriptors/protobuf-win32.yml | 37 + contrib/gitian-descriptors/qt-win32.yml | 14 +- doc/readme-qt.md | 8 +- share/qt/Info.plist | 96 ++- share/qt/protobuf.pri | 35 + src/init.cpp | 2 +- src/qt/addresstablemodel.cpp | 27 +- src/qt/bitcoin.cpp | 16 +- src/qt/bitcoinamountfield.cpp | 5 + src/qt/bitcoinamountfield.h | 3 + src/qt/bitcoingui.cpp | 20 +- src/qt/bitcoingui.h | 5 +- src/qt/forms/sendcoinsentry.ui | 792 +++++++++++++++--- src/qt/guiconstants.h | 3 + src/qt/optionsmodel.cpp | 11 + src/qt/optionsmodel.h | 1 + src/qt/paymentrequest.proto | 46 + src/qt/paymentrequestplus.cpp | 204 +++++ src/qt/paymentrequestplus.h | 41 + src/qt/paymentserver.cpp | 527 +++++++++++- src/qt/paymentserver.h | 64 +- src/qt/sendcoinsdialog.cpp | 47 +- src/qt/sendcoinsdialog.h | 3 +- src/qt/sendcoinsentry.cpp | 35 +- src/qt/sendcoinsentry.h | 14 +- src/qt/test/paymentrequestdata.h | 307 +++++++ src/qt/test/paymentservertests.cpp | 109 +++ src/qt/test/paymentservertests.h | 29 + src/qt/test/test_main.cpp | 37 +- src/qt/transactiondesc.cpp | 16 + src/qt/walletframe.cpp | 4 +- src/qt/walletframe.h | 5 +- src/qt/walletmodel.cpp | 96 ++- src/qt/walletmodel.h | 16 +- src/qt/walletstack.cpp | 4 +- src/qt/walletstack.h | 3 +- src/qt/walletview.cpp | 4 +- src/qt/walletview.h | 3 +- src/rpcdump.cpp | 4 +- src/rpcwallet.cpp | 8 +- src/wallet.cpp | 10 +- src/wallet.h | 6 +- src/walletdb.cpp | 18 + src/walletdb.h | 4 +- 48 files changed, 2466 insertions(+), 341 deletions(-) create mode 100644 contrib/gitian-descriptors/protobuf-win32.yml create mode 100644 share/qt/protobuf.pri create mode 100644 src/qt/paymentrequest.proto create mode 100644 src/qt/paymentrequestplus.cpp create mode 100644 src/qt/paymentrequestplus.h create mode 100644 src/qt/test/paymentrequestdata.h create mode 100644 src/qt/test/paymentservertests.cpp create mode 100644 src/qt/test/paymentservertests.h diff --git a/bitcoin-qt.pro b/bitcoin-qt.pro index 32cf894b7..9baa03a4e 100644 --- a/bitcoin-qt.pro +++ b/bitcoin-qt.pro @@ -16,21 +16,29 @@ CONFIG += thread # or when linking against a specific BerkelyDB version: BDB_LIB_SUFFIX=-4.8 # Dependency library locations can be customized with: -# BOOST_INCLUDE_PATH, BOOST_LIB_PATH, BDB_INCLUDE_PATH, -# BDB_LIB_PATH, OPENSSL_INCLUDE_PATH and OPENSSL_LIB_PATH respectively +# BOOST_INCLUDE_PATH BOOST_LIB_PATH, +# BDB_INCLUDE_PATH BDB_LIB_PATH, +# OPENSSL_INCLUDE_PATH OPENSSL_LIB_PATH +# PROTOBUF_INCLUDE_PATH PROTOBUF_LIB_PATH +# PROTOC : protocol buffer compiler tool OBJECTS_DIR = build MOC_DIR = build UI_DIR = build +PROTO_DIR = build +PROTO_PATH = src/qt contains(BITCOIN_QT_TEST, 1) { OBJECTS_DIR = build_test MOC_DIR = build_test UI_DIR = build_test +PROTO_DIR = build_test SOURCES += src/qt/test/test_main.cpp \ - src/qt/test/uritests.cpp -HEADERS += src/qt/test/uritests.h + src/qt/test/uritests.cpp \ + src/qt/test/paymentservertests.cpp +HEADERS += src/qt/test/uritests.h \ + src/qt/test/paymentservertests.h DEPENDPATH += src/qt/test QT += testlib TARGET = bitcoin-qt_test @@ -219,6 +227,7 @@ HEADERS += src/qt/bitcoingui.h \ src/qt/askpassphrasedialog.h \ src/protocol.h \ src/qt/notificator.h \ + src/qt/paymentrequestplus.h \ src/qt/paymentserver.h \ src/allocators.h \ src/ui_interface.h \ @@ -297,6 +306,7 @@ SOURCES += src/qt/bitcoin.cpp \ src/qt/askpassphrasedialog.cpp \ src/protocol.cpp \ src/qt/notificator.cpp \ + src/qt/paymentrequestplus.cpp \ src/qt/paymentserver.cpp \ src/qt/rpcconsole.cpp \ src/noui.cpp \ @@ -320,13 +330,15 @@ FORMS += src/qt/forms/sendcoinsdialog.ui \ src/qt/forms/optionsdialog.ui \ src/qt/forms/intro.ui +PROTOS = src/qt/paymentrequest.proto +include(share/qt/protobuf.pri) + contains(USE_QRCODE, 1) { HEADERS += src/qt/qrcodedialog.h SOURCES += src/qt/qrcodedialog.cpp FORMS += src/qt/forms/qrcodedialog.ui } - # Todo: Remove this line when switching to Qt5, as that option was removed CODECFORTR = UTF-8 @@ -420,9 +432,9 @@ macx:QMAKE_CXXFLAGS_THREAD += -pthread macx:QMAKE_INFO_PLIST = share/qt/Info.plist # Set libraries and includes at end, to use platform-defined defaults if not overridden -INCLUDEPATH += $$BOOST_INCLUDE_PATH $$BDB_INCLUDE_PATH $$OPENSSL_INCLUDE_PATH $$QRENCODE_INCLUDE_PATH -LIBS += $$join(BOOST_LIB_PATH,,-L,) $$join(BDB_LIB_PATH,,-L,) $$join(OPENSSL_LIB_PATH,,-L,) $$join(QRENCODE_LIB_PATH,,-L,) -LIBS += -lssl -lcrypto -ldb_cxx$$BDB_LIB_SUFFIX +INCLUDEPATH += $$BOOST_INCLUDE_PATH $$BDB_INCLUDE_PATH $$OPENSSL_INCLUDE_PATH $$PROTOBUF_INCLUDE_PATH $$QRENCODE_INCLUDE_PATH +LIBS += $$join(BOOST_LIB_PATH,,-L,) $$join(BDB_LIB_PATH,,-L,) $$join(OPENSSL_LIB_PATH,,-L,) $$join(PROTOBUF_LIB_PATH,,-L,) $$join(QRENCODE_LIB_PATH,,-L,) +LIBS += -lssl -lcrypto -ldb_cxx$$BDB_LIB_SUFFIX -lprotobuf # -lgdi32 has to happen after -lcrypto (see #681) win32:LIBS += -lws2_32 -lshlwapi -lmswsock -lole32 -loleaut32 -luuid -lgdi32 LIBS += -lboost_system$$BOOST_LIB_SUFFIX -lboost_filesystem$$BOOST_LIB_SUFFIX -lboost_program_options$$BOOST_LIB_SUFFIX -lboost_thread$$BOOST_THREAD_LIB_SUFFIX diff --git a/contrib/gitian-descriptors/README b/contrib/gitian-descriptors/README index 46c7668ab..f1c960ba1 100644 --- a/contrib/gitian-descriptors/README +++ b/contrib/gitian-descriptors/README @@ -32,6 +32,7 @@ Once you've got the right hardware and software: wget 'https://downloads.sourceforge.net/project/libpng/zlib/1.2.6/zlib-1.2.6.tar.gz' wget 'https://downloads.sourceforge.net/project/libpng/libpng15/older-releases/1.5.9/libpng-1.5.9.tar.gz' wget 'http://releases.qt-project.org/qt4/source/qt-everywhere-opensource-src-4.8.3.tar.gz' + wget 'http://protobuf.googlecode.com/files/protobuf-2.5.0.tar.bz2' cd ../.. cd gitian-builder @@ -50,6 +51,7 @@ Once you've got the right hardware and software: ./bin/gbuild --commit bitcoin=HEAD ../bitcoin/contrib/gitian-descriptors/boost-win32.yml ./bin/gbuild --commit bitcoin=HEAD ../bitcoin/contrib/gitian-descriptors/deps-win32.yml ./bin/gbuild --commit bitcoin=HEAD ../bitcoin/contrib/gitian-descriptors/qt-win32.yml + ./bin/gbuild --commit bitcoin=HEAD ../bitcoin/contrib/gitian-descriptors/protobuf-win32.yml # Build Win32 release: ./bin/gbuild --commit bitcoin=HEAD ../bitcoin/contrib/gitian-descriptors/gitian-win32.yml diff --git a/contrib/gitian-descriptors/gitian-win32.yml b/contrib/gitian-descriptors/gitian-win32.yml index fd3b55325..5fc5a5a16 100644 --- a/contrib/gitian-descriptors/gitian-win32.yml +++ b/contrib/gitian-descriptors/gitian-win32.yml @@ -15,16 +15,18 @@ remotes: - "url": "https://github.com/bitcoin/bitcoin.git" "dir": "bitcoin" files: -- "qt-win32-4.8.3-gitian-r1.zip" +- "qt-win32-4.8.3-gitian-r2.zip" - "boost-win32-1.50.0-gitian2.zip" - "bitcoin-deps-0.0.5.zip" +- "protobuf-win32-2.5.0-gitian-r1.zip" script: | # - mkdir $HOME/qt - cd $HOME/qt - unzip ../build/qt-win32-4.8.3-gitian-r1.zip + export QTDIR=$HOME/qt + mkdir $QTDIR + cd $QTDIR + unzip ../build/qt-win32-4.8.3-gitian-r2.zip cd $HOME/build/ - export PATH=$HOME/qt/bin/:$PATH + export PATH=$QTDIR/bin/:$PATH # mkdir boost_1_50_0 cd boost_1_50_0 @@ -41,6 +43,8 @@ script: | # unzip bitcoin-deps-0.0.5.zip # + unzip protobuf-win32-2.5.0-gitian-r1.zip + # find -type f | xargs touch --date="$REFERENCE_DATETIME" # cd bitcoin @@ -51,7 +55,7 @@ script: | export LD_PRELOAD=/usr/lib/faketime/libfaketime.so.1 export FAKETIME=$REFERENCE_DATETIME export TZ=UTC - $HOME/qt/src/bin/qmake -spec unsupported/win32-g++-cross MINIUPNPC_LIB_PATH=$HOME/build/miniupnpc MINIUPNPC_INCLUDE_PATH=$HOME/build/ BDB_LIB_PATH=$HOME/build/db-4.8.30.NC/build_unix BDB_INCLUDE_PATH=$HOME/build/db-4.8.30.NC/build_unix BOOST_LIB_PATH=$HOME/build/boost_1_50_0/stage/lib BOOST_INCLUDE_PATH=$HOME/build/boost_1_50_0 BOOST_LIB_SUFFIX=-mt-s BOOST_THREAD_LIB_SUFFIX=_win32-mt-s OPENSSL_LIB_PATH=$HOME/build/openssl-1.0.1c OPENSSL_INCLUDE_PATH=$HOME/build/openssl-1.0.1c/include QRENCODE_LIB_PATH=$HOME/build/qrencode-3.2.0/.libs QRENCODE_INCLUDE_PATH=$HOME/build/qrencode-3.2.0 USE_QRCODE=1 INCLUDEPATH=$HOME/build DEFINES=BOOST_THREAD_USE_LIB BITCOIN_NEED_QT_PLUGINS=1 QMAKE_LRELEASE=lrelease QMAKE_CXXFLAGS=-frandom-seed=bitcoin USE_BUILD_INFO=1 + $QTDIR/bin/qmake -spec unsupported/win32-g++-cross PROTOBUF_LIB_PATH=$HOME/build/protobuf-win32 PROTOBUF_INCLUDE_PATH=$HOME/build/protobuf-win32 PROTOC=$HOME/build/protobuf-win32/protoc MINIUPNPC_LIB_PATH=$HOME/build/miniupnpc MINIUPNPC_INCLUDE_PATH=$HOME/build/ BDB_LIB_PATH=$HOME/build/db-4.8.30.NC/build_unix BDB_INCLUDE_PATH=$HOME/build/db-4.8.30.NC/build_unix BOOST_LIB_PATH=$HOME/build/boost_1_50_0/stage/lib BOOST_INCLUDE_PATH=$HOME/build/boost_1_50_0 BOOST_LIB_SUFFIX=-mt-s BOOST_THREAD_LIB_SUFFIX=_win32-mt-s OPENSSL_LIB_PATH=$HOME/build/openssl-1.0.1c OPENSSL_INCLUDE_PATH=$HOME/build/openssl-1.0.1c/include QRENCODE_LIB_PATH=$HOME/build/qrencode-3.2.0/.libs QRENCODE_INCLUDE_PATH=$HOME/build/qrencode-3.2.0 USE_QRCODE=1 INCLUDEPATH=$HOME/build DEFINES=BOOST_THREAD_USE_LIB BITCOIN_NEED_QT_PLUGINS=1 QMAKE_LRELEASE=lrelease QMAKE_CXXFLAGS=-frandom-seed=bitcoin USE_BUILD_INFO=1 make $MAKEOPTS cp release/bitcoin-qt.exe $OUTDIR/ # diff --git a/contrib/gitian-descriptors/gitian.yml b/contrib/gitian-descriptors/gitian.yml index 195d0e36f..6d47a46d5 100644 --- a/contrib/gitian-descriptors/gitian.yml +++ b/contrib/gitian-descriptors/gitian.yml @@ -18,38 +18,44 @@ packages: - "unzip" - "pkg-config" - "libpng12-dev" -reference_datetime: "2011-01-30 00:00:00" +reference_datetime: "2013-06-01 00:00:00" remotes: - "url": "https://github.com/bitcoin/bitcoin.git" "dir": "bitcoin" files: - "miniupnpc-1.6.tar.gz" - "qrencode-3.2.0.tar.bz2" +- "protobuf-2.5.0.tar.bz2" script: | INSTDIR="$HOME/install" export LIBRARY_PATH="$INSTDIR/lib" # - tar xzf miniupnpc-1.6.tar.gz + tar xzfm miniupnpc-1.6.tar.gz cd miniupnpc-1.6 INSTALLPREFIX=$INSTDIR make $MAKEOPTS install cd .. # - tar xjf qrencode-3.2.0.tar.bz2 + tar xjfm qrencode-3.2.0.tar.bz2 cd qrencode-3.2.0 ./configure --prefix=$INSTDIR --enable-static --disable-shared make $MAKEOPTS install cd .. # + tar xjfm protobuf-2.5.0.tar.bz2 + cd protobuf-2.5.0 + ./configure --prefix=$INSTDIR --enable-static --disable-shared + make $MAKEOPTS install + cd .. + # cd bitcoin mkdir -p $OUTDIR/src git archive HEAD | tar -x -C $OUTDIR/src cp $OUTDIR/src/doc/README.md $OUTDIR cp $OUTDIR/src/COPYING $OUTDIR - cd src - make -f makefile.unix STATIC=1 OPENSSL_INCLUDE_PATH="$INSTDIR/include" OPENSSL_LIB_PATH="$INSTDIR/lib" $MAKEOPTS bitcoind USE_UPNP=0 DEBUGFLAGS= mkdir -p $OUTDIR/bin/$GBUILD_BITS - install -s bitcoind $OUTDIR/bin/$GBUILD_BITS - cd .. - qmake INCLUDEPATH="$INSTDIR/include" LIBS="-L$INSTDIR/lib" RELEASE=1 USE_QRCODE=1 + qmake INCLUDEPATH="$INSTDIR/include" LIBS="-L$INSTDIR/lib" PROTOC="$INSTDIR/bin/protoc" PROTOBUF_LIB_PATH="$INSTDIR/lib" PROTOBUF_INCLUDE_PATH="$INSTDIR/include" RELEASE=1 USE_QRCODE=1 make $MAKEOPTS install bitcoin-qt $OUTDIR/bin/$GBUILD_BITS + cd src + make -f makefile.unix STATIC=1 OPENSSL_INCLUDE_PATH="$INSTDIR/include" OPENSSL_LIB_PATH="$INSTDIR/lib" $MAKEOPTS bitcoind USE_UPNP=0 DEBUGFLAGS= + install -s bitcoind $OUTDIR/bin/$GBUILD_BITS diff --git a/contrib/gitian-descriptors/protobuf-win32.yml b/contrib/gitian-descriptors/protobuf-win32.yml new file mode 100644 index 000000000..492b21e7f --- /dev/null +++ b/contrib/gitian-descriptors/protobuf-win32.yml @@ -0,0 +1,37 @@ +--- +name: "protobuf-win32" +suites: +- "lucid" +architectures: +- "i386" +packages: +- "mingw32" +- "zip" +- "faketime" +reference_datetime: "2013-04-15 00:00:00" +remotes: [] +files: +- "protobuf-2.5.0.tar.bz2" +script: | + # + export LD_PRELOAD=/usr/lib/faketime/libfaketime.so.1 + export FAKETIME=$REFERENCE_DATETIME + export TZ=UTC + # + tar xjf protobuf-2.5.0.tar.bz2 + cd protobuf-2.5.0 + # First: build a native (linux) protoc + ./configure --enable-shared=no --disable-dependency-tracking + make + mkdir -p host + cp src/protoc host + # Now recompile with the mingw cross-compiler: + make distclean + ./configure --enable-shared=no --disable-dependency-tracking --with-protoc=$(pwd)/host/protoc --host=i586-mingw32msvc CXXFLAGS=-frandom-seed=11 + make + cd .. + mkdir -p protobuf-win32 + cp protobuf-2.5.0/host/protoc protobuf-win32/protoc + cp protobuf-2.5.0/src/.libs/libprotobuf.a protobuf-win32/libprotobuf.a + cp -r protobuf-2.5.0/src/google protobuf-win32/ + zip -r $OUTDIR/protobuf-win32-2.5.0-gitian-r1.zip protobuf-win32 diff --git a/contrib/gitian-descriptors/qt-win32.yml b/contrib/gitian-descriptors/qt-win32.yml index 0b711790e..7152875b8 100644 --- a/contrib/gitian-descriptors/qt-win32.yml +++ b/contrib/gitian-descriptors/qt-win32.yml @@ -12,11 +12,14 @@ reference_datetime: "2011-01-30 00:00:00" remotes: [] files: - "qt-everywhere-opensource-src-4.8.3.tar.gz" +- "bitcoin-deps-0.0.5.zip" script: | INSTDIR="$HOME/qt/" mkdir $INSTDIR - SRCDIR="$INSTDIR/src/" - mkdir $SRCDIR + # + # Need mingw-compiled openssl from bitcoin-deps: + unzip bitcoin-deps-0.0.5.zip + DEPSDIR=`pwd` # tar xzf qt-everywhere-opensource-src-4.8.3.tar.gz cd qt-everywhere-opensource-src-4.8.3 @@ -40,15 +43,14 @@ script: | #export LD_PRELOAD=/usr/lib/faketime/libfaketime.so.1 export FAKETIME=$REFERENCE_DATETIME export TZ=UTC - ./configure -prefix $INSTDIR -confirm-license -release -opensource -static -no-qt3support -xplatform unsupported/win32-g++-cross -no-multimedia -no-audio-backend -no-phonon -no-phonon-backend -no-declarative -no-script -no-scripttools -no-javascript-jit -no-webkit -no-svg -no-xmlpatterns -no-sql-sqlite -no-nis -no-cups -no-iconv -no-dbus -no-gif -no-libtiff -no-opengl -nomake examples -nomake demos -nomake docs -no-feature-style-plastique -no-feature-style-cleanlooks -no-feature-style-motif -no-feature-style-cde -no-feature-style-windowsce -no-feature-style-windowsmobile -no-feature-style-s60 + # Compile static libraries, and use statically linked openssl (-openssl-linked): + OPENSSL_LIBS="-L$DEPSDIR/openssl-1.0.1c -lssl -lcrypto -lgdi32" ./configure -prefix $INSTDIR -I $DEPSDIR/openssl-1.0.1c/include -confirm-license -release -opensource -static -no-qt3support -xplatform unsupported/win32-g++-cross -no-multimedia -no-audio-backend -no-phonon -no-phonon-backend -no-declarative -no-script -no-scripttools -no-javascript-jit -no-webkit -no-svg -no-xmlpatterns -no-sql-sqlite -no-nis -no-cups -no-iconv -no-dbus -no-gif -no-libtiff -no-opengl -nomake examples -nomake demos -nomake docs -no-feature-style-plastique -no-feature-style-cleanlooks -no-feature-style-motif -no-feature-style-cde -no-feature-style-windowsce -no-feature-style-windowsmobile -no-feature-style-s60 -openssl-linked find . -name *.prl | xargs -l sed 's|/\.||' -i find . -name *.prl | xargs -l sed 's|/$||' -i make $MAKEOPTS install - cp -a bin $SRCDIR/ cd $INSTDIR find . -name *.prl | xargs -l sed 's|/$||' -i - #sed 's|QMAKE_PRL_LIBS.*|QMAKE_PRL_LIBS = -lQtDeclarative -lQtScript -lQtSvg -lQtSql -lQtXmlPatterns -lQtGui -lgdi32 -lcomdlg32 -loleaut32 -limm32 -lwinmm -lwinspool -lmsimg32 -lQtNetwork -lQtCore -lole32 -luuid -lws2_32 -ladvapi32 -lshell32 -luser32 -lkernel32|' -i imports/Qt/labs/particles/qmlparticlesplugin.prl # as zip stores file timestamps, use faketime to intercept stat calls to set dates for all files to reference date export LD_PRELOAD=/usr/lib/faketime/libfaketime.so.1 - zip -r $OUTDIR/qt-win32-4.8.3-gitian-r1.zip * + zip -r $OUTDIR/qt-win32-4.8.3-gitian-r2.zip * diff --git a/doc/readme-qt.md b/doc/readme-qt.md index 083db3279..cbaaf645a 100644 --- a/doc/readme-qt.md +++ b/doc/readme-qt.md @@ -18,13 +18,13 @@ for Debian and Ubuntu <= 11.10 : apt-get install qt4-qmake libqt4-dev build-essential libboost-dev libboost-system-dev \ libboost-filesystem-dev libboost-program-options-dev libboost-thread-dev \ - libssl-dev libdb4.8++-dev + libssl-dev libdb4.8++-dev libprotobuf-dev protobuf-compiler for Ubuntu >= 12.04 (please read the 'Berkely DB version warning' below): apt-get install qt4-qmake libqt4-dev build-essential libboost-dev libboost-system-dev \ libboost-filesystem-dev libboost-program-options-dev libboost-thread-dev \ - libssl-dev libdb++-dev libminiupnpc-dev + libssl-dev libdb++-dev libminiupnpc-dev libprotobuf-dev protobuf-compiler For Qt 5 you need the following, otherwise you get an error with lrelease when running qmake: @@ -48,12 +48,12 @@ An executable named `bitcoin-qt` will be built. * Execute the following commands in a terminal to get the dependencies using MacPorts sudo port selfupdate - sudo port install boost db48 miniupnpc + sudo port install boost db48 miniupnpc protobuf-cpp * Execute the following commands in a terminal to get the dependencies using HomeBrew: brew update - brew install boost miniupnpc openssl berkeley-db4 + brew install boost miniupnpc openssl berkeley-db4 protobuf - If using HomeBrew, edit `bitcoin-qt.pro` to account for library location differences. There's a diff in `contrib/homebrew/bitcoin-qt-pro.patch` that shows what you need to change, or you can just patch by doing diff --git a/share/qt/Info.plist b/share/qt/Info.plist index 6f64c3164..f191402e8 100644 --- a/share/qt/Info.plist +++ b/share/qt/Info.plist @@ -2,36 +2,74 @@ - CFBundleIconFile - bitcoin.icns - CFBundlePackageType - APPL - CFBundleGetInfoString - $VERSION, Copyright © 2009-$YEAR The Bitcoin developers - CFBundleShortVersionString - $VERSION - CFBundleVersion - $VERSION - CFBundleSignature - ???? - CFBundleExecutable - Bitcoin-Qt - CFBundleIdentifier - org.bitcoinfoundation.Bitcoin-Qt - CFBundleURLTypes + CFBundleIconFile + bitcoin.icns + CFBundlePackageType + APPL + CFBundleGetInfoString + $VERSION, Copyright © 2009-$YEAR The Bitcoin developers + CFBundleShortVersionString + $VERSION + CFBundleVersion + $VERSION + CFBundleSignature + ???? + CFBundleExecutable + Bitcoin-Qt + CFBundleIdentifier + org.bitcoinfoundation.Bitcoin-Qt + + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + org.bitcoin.BitcoinPayment + CFBundleURLSchemes + + bitcoin + + + + + UTExportedTypeDeclarations + + + UTTypeIdentifier + org.bitcoin.paymentrequest + UTTypeDescription + Bitcoin payment request + UTTypeConformsTo + + public.data + + UTTypeTagSpecification + + public.mime-type + application/x-bitcoin-payment-request + public.filename-extension - - CFBundleTypeRole - Editor - CFBundleURLName - org.bitcoinfoundation.BitcoinPayment - CFBundleURLSchemes - - bitcoin - - + bitcoinpaymentrequest - NSHighResolutionCapable - + + + + + CFBundleDocumentTypes + + + CFBundleTypeRole + Editor + LSItemContentTypes + + org.bitcoin.paymentrequest + + LSHandlerRank + Owner + + + NSHighResolutionCapable + diff --git a/share/qt/protobuf.pri b/share/qt/protobuf.pri new file mode 100644 index 000000000..865fe8655 --- /dev/null +++ b/share/qt/protobuf.pri @@ -0,0 +1,35 @@ +# Based on: http://code.google.com/p/ostinato/source/browse/protobuf.pri +# +# Qt qmake integration with Google Protocol Buffers compiler protoc +# +# To compile protocol buffers with qt qmake, specify PROTOS variable and +# include this file +# +# Example: +# PROTOS = a.proto b.proto +# include(protobuf.pri) +# +# Set PROTO_PATH if you need to set the protoc --proto_path search path +# Set PROTOC to the path to the protoc compiler if it is not in your $PATH +# + +isEmpty(PROTO_DIR):PROTO_DIR = . +isEmpty(PROTOC):PROTOC = protoc + +PROTOPATHS = +for(p, PROTO_PATH):PROTOPATHS += --proto_path=$${p} + +protobuf_decl.name = protobuf header +protobuf_decl.input = PROTOS +protobuf_decl.output = $${PROTO_DIR}/${QMAKE_FILE_BASE}.pb.h +protobuf_decl.commands = $${PROTOC} --cpp_out="$${PROTO_DIR}" $${PROTOPATHS} --proto_path=${QMAKE_FILE_IN_PATH} ${QMAKE_FILE_NAME} +protobuf_decl.variable_out = GENERATED_FILES +QMAKE_EXTRA_COMPILERS += protobuf_decl + +protobuf_impl.name = protobuf implementation +protobuf_impl.input = PROTOS +protobuf_impl.output = $${PROTO_DIR}/${QMAKE_FILE_BASE}.pb.cc +protobuf_impl.depends = $${PROTO_DIR}/${QMAKE_FILE_BASE}.pb.h +protobuf_impl.commands = $$escape_expand(\\n) +protobuf_impl.variable_out = GENERATED_SOURCES +QMAKE_EXTRA_COMPILERS += protobuf_impl diff --git a/src/init.cpp b/src/init.cpp index 64a5de239..726356504 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -895,7 +895,7 @@ bool AppInit2(boost::thread_group& threadGroup) CPubKey newDefaultKey; if (pwalletMain->GetKeyFromPool(newDefaultKey, false)) { pwalletMain->SetDefaultKey(newDefaultKey); - if (!pwalletMain->SetAddressBookName(pwalletMain->vchDefaultKey.GetID(), "")) + if (!pwalletMain->SetAddressBook(pwalletMain->vchDefaultKey.GetID(), "", "receive")) strErrors << _("Cannot write default address") << "\n"; } diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index 823730c4c..dcc70222c 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -62,9 +62,19 @@ public: BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& item, wallet->mapAddressBook) { const CBitcoinAddress& address = item.first; + + AddressTableEntry::Type addressType; + const std::string& strPurpose = item.second.purpose; + if (strPurpose == "send") addressType = AddressTableEntry::Sending; + else if (strPurpose == "receive") addressType = AddressTableEntry::Receiving; + else if (strPurpose == "unknown") { + bool fMine = IsMine(*wallet, address.Get()); + addressType = (fMine ? AddressTableEntry::Receiving : AddressTableEntry::Sending); + } + else continue; // "refund" addresses aren't shown, and change addresses aren't in mapAddressBook at all. + const std::string& strName = item.second.name; - bool fMine = IsMine(*wallet, address.Get()); - cachedAddressTable.append(AddressTableEntry(fMine ? AddressTableEntry::Receiving : AddressTableEntry::Sending, + cachedAddressTable.append(AddressTableEntry(addressType, QString::fromStdString(strName), QString::fromStdString(address.ToString()))); } @@ -215,7 +225,7 @@ bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value, if(!index.isValid()) return false; AddressTableEntry *rec = static_cast(index.internalPointer()); - + std::string strPurpose = (rec->type == AddressTableEntry::Sending ? "send" : "receive"); editStatus = OK; if(role == Qt::EditRole) @@ -229,7 +239,7 @@ bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value, editStatus = NO_CHANGES; return false; } - wallet->SetAddressBookName(CBitcoinAddress(rec->address.toStdString()).Get(), value.toString().toStdString()); + wallet->SetAddressBook(CBitcoinAddress(rec->address.toStdString()).Get(), value.toString().toStdString(), strPurpose); break; case Address: // Do nothing, if old address == new address @@ -257,9 +267,9 @@ bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value, { LOCK(wallet->cs_wallet); // Remove old entry - wallet->DelAddressBookName(CBitcoinAddress(rec->address.toStdString()).Get()); + wallet->DelAddressBook(CBitcoinAddress(rec->address.toStdString()).Get()); // Add new entry with new address - wallet->SetAddressBookName(CBitcoinAddress(value.toString().toStdString()).Get(), rec->label.toStdString()); + wallet->SetAddressBook(CBitcoinAddress(value.toString().toStdString()).Get(), rec->label.toStdString(), strPurpose); } } break; @@ -368,7 +378,8 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con // Add entry { LOCK(wallet->cs_wallet); - wallet->SetAddressBookName(CBitcoinAddress(strAddress).Get(), strLabel); + wallet->SetAddressBook(CBitcoinAddress(strAddress).Get(), strLabel, + (type == Send ? "send" : "receive")); } return QString::fromStdString(strAddress); } @@ -385,7 +396,7 @@ bool AddressTableModel::removeRows(int row, int count, const QModelIndex &parent } { LOCK(wallet->cs_wallet); - wallet->DelAddressBookName(CBitcoinAddress(rec->address.toStdString()).Get()); + wallet->DelAddressBook(CBitcoinAddress(rec->address.toStdString()).Get()); } return true; } diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 5ab5d1f7e..e7cf44004 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -213,7 +213,7 @@ int main(int argc, char *argv[]) // Do this early as we don't want to bother initializing if we are just calling IPC // ... but do it after creating app and setting up translations, so errors are // translated properly. - if (PaymentServer::ipcSendCommandLine()) + if (PaymentServer::ipcSendCommandLine(argc, argv)) exit(0); // Now that translations are initialized check for errors and allow a translatable error message @@ -299,6 +299,9 @@ int main(int argc, char *argv[]) optionsModel.Upgrade(); // Must be done after AppInit2 + PaymentServer::LoadRootCAs(); + paymentServer->initNetManager(optionsModel); + if (splashref) splash.finish(&window); @@ -320,8 +323,15 @@ int main(int argc, char *argv[]) } // Now that initialization/startup is done, process any command-line - // bitcoin: URIs - QObject::connect(paymentServer, SIGNAL(receivedURI(QString)), &window, SLOT(handleURI(QString))); + // bitcoin: URIs or payment requests: + QObject::connect(paymentServer, SIGNAL(receivedPaymentRequest(SendCoinsRecipient)), + &window, SLOT(handlePaymentRequest(SendCoinsRecipient))); + QObject::connect(&walletModel, SIGNAL(coinsSent(CWallet*,SendCoinsRecipient,QByteArray)), + paymentServer, SLOT(fetchPaymentACK(CWallet*,const SendCoinsRecipient&,QByteArray))); + QObject::connect(paymentServer, SIGNAL(receivedPaymentACK(QString)), + &window, SLOT(showPaymentACK(QString))); + QObject::connect(paymentServer, SIGNAL(reportError(QString, QString, unsigned int)), + guiref, SLOT(message(QString, QString, unsigned int))); QTimer::singleShot(100, paymentServer, SLOT(uiReady())); app.exec(); diff --git a/src/qt/bitcoinamountfield.cpp b/src/qt/bitcoinamountfield.cpp index eeb6fe89b..d9d4e3b23 100644 --- a/src/qt/bitcoinamountfield.cpp +++ b/src/qt/bitcoinamountfield.cpp @@ -130,6 +130,11 @@ void BitcoinAmountField::setValue(qint64 value) setText(BitcoinUnits::format(currentUnit, value)); } +void BitcoinAmountField::setReadOnly(bool fReadeOnly) +{ + // TODO ... +} + void BitcoinAmountField::unitChanged(int idx) { // Use description tooltip for current unit for the combobox diff --git a/src/qt/bitcoinamountfield.h b/src/qt/bitcoinamountfield.h index dacbb395e..9c8be5a26 100644 --- a/src/qt/bitcoinamountfield.h +++ b/src/qt/bitcoinamountfield.h @@ -22,6 +22,9 @@ public: qint64 value(bool *valid=0) const; void setValue(qint64 value); + /** Make read-only **/ + void setReadOnly(bool fReadOnly); + /** Mark current value as invalid in UI. */ void setValid(bool valid); /** Perform input validation, mark field as invalid if entered value is not valid. */ diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 8ec2f03fa..ad32c9ea6 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -45,6 +45,7 @@ #include #if QT_VERSION < 0x050000 #include +#include #endif #include #include @@ -707,7 +708,8 @@ void BitcoinGUI::dropEvent(QDropEvent *event) QList uris = event->mimeData()->urls(); foreach(const QUrl &uri, uris) { - if (walletFrame->handleURI(uri.toString())) + SendCoinsRecipient r; + if (GUIUtil::parseBitcoinURI(uri, &r) && walletFrame->handlePaymentRequest(r)) nValidUrisFound++; } @@ -734,12 +736,18 @@ bool BitcoinGUI::eventFilter(QObject *object, QEvent *event) return QMainWindow::eventFilter(object, event); } -void BitcoinGUI::handleURI(QString strURI) +void BitcoinGUI::handlePaymentRequest(const SendCoinsRecipient& recipient) { - // URI has to be valid - if (!walletFrame->handleURI(strURI)) - message(tr("URI handling"), tr("URI can not be parsed! This can be caused by an invalid Bitcoin address or malformed URI parameters."), - CClientUIInterface::ICON_WARNING); + walletFrame->handlePaymentRequest(recipient); +} + +void BitcoinGUI::showPaymentACK(QString msg) +{ +#if QT_VERSION < 0x050000 + message(tr("Payment acknowledged"), Qt::escape(msg), CClientUIInterface::MODAL); +#else + message(tr("Payment acknowledged"), msg.toHtmlEscaped(), CClientUIInterface::MODAL); +#endif } void BitcoinGUI::setEncryptionStatus(int status) diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 6b9161539..fc25e867f 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -15,6 +15,7 @@ class TransactionView; class OverviewPage; class AddressBookPage; class SendCoinsDialog; +class SendCoinsRecipient; class SignVerifyMessageDialog; class Notificator; class RPCConsole; @@ -151,7 +152,9 @@ public slots: @param[out] payFee true to pay the fee, false to not pay the fee */ void askFee(qint64 nFeeRequired, bool *payFee); - void handleURI(QString strURI); + + void handlePaymentRequest(const SendCoinsRecipient& recipient); + void showPaymentACK(QString msg); /** Show incoming transaction notification for new transactions. */ void incomingTransaction(const QString& date, int unit, qint64 amount, const QString& type, const QString& address); diff --git a/src/qt/forms/sendcoinsentry.ui b/src/qt/forms/sendcoinsentry.ui index 28553c550..a2ef9a0a3 100644 --- a/src/qt/forms/sendcoinsentry.ui +++ b/src/qt/forms/sendcoinsentry.ui @@ -1,143 +1,685 @@ SendCoinsEntry - + 0 0 729 - 136 + 150 - Form + StackedWidget - - QFrame::StyledPanel + + false - - QFrame::Sunken + + 1 - - - 12 + + + Form - - - - A&mount: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - payAmount - - - - - - - Pay &To: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - payTo - - - - - - - - - - &Label: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - addAsLabel - - - - - - - 0 - - - - - The address to send the payment to (e.g. 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L) - - - 34 - - - - - - - Choose address from address book - - - - - - - :/icons/address-book:/icons/address-book - - - Alt+A - - - - - - - Paste address from clipboard - - - - - - - :/icons/editpaste:/icons/editpaste - - - Alt+P - - - - - - - Remove this recipient - - - - - - - :/icons/remove:/icons/remove - - - - - - - - - Enter a label for this address to add it to your address book - - - - + + QFrame::StyledPanel + + + QFrame::Sunken + + + + 12 + + + + + A&mount: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + payAmount + + + + + + + Pay &To: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + payTo + + + + + + + + + + &Label: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + addAsLabel + + + + + + + 0 + + + + + The address to send the payment to (e.g. 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L) + + + 34 + + + + + + + Choose address from address book + + + + + + + :/icons/address-book:/icons/address-book + + + Alt+A + + + + + + + Paste address from clipboard + + + + + + + :/icons/editpaste:/icons/editpaste + + + Alt+P + + + + + + + Remove this recipient + + + + + + + :/icons/remove:/icons/remove + + + + + + + + + Enter a label for this address to add it to your address book + + + + + + + + + + + + + 0 + 0 + 0 + + + + + + + 140 + 232 + 119 + + + + + + + 230 + 255 + 224 + + + + + + + 185 + 243 + 171 + + + + + + + 70 + 116 + 59 + + + + + + + 93 + 155 + 79 + + + + + + + 0 + 0 + 0 + + + + + + + 155 + 255 + 147 + + + + + + + 0 + 0 + 0 + + + + + + + 119 + 255 + 233 + + + + + + + 140 + 232 + 119 + + + + + + + 0 + 0 + 0 + + + + + + + 197 + 243 + 187 + + + + + + + 125 + 194 + 122 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + + 0 + 0 + 0 + + + + + + + 140 + 232 + 119 + + + + + + + 230 + 255 + 224 + + + + + + + 185 + 243 + 171 + + + + + + + 70 + 116 + 59 + + + + + + + 93 + 155 + 79 + + + + + + + 0 + 0 + 0 + + + + + + + 155 + 255 + 147 + + + + + + + 0 + 0 + 0 + + + + + + + 119 + 255 + 233 + + + + + + + 140 + 232 + 119 + + + + + + + 0 + 0 + 0 + + + + + + + 197 + 243 + 187 + + + + + + + 125 + 194 + 122 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + + 70 + 116 + 59 + + + + + + + 140 + 232 + 119 + + + + + + + 230 + 255 + 224 + + + + + + + 185 + 243 + 171 + + + + + + + 70 + 116 + 59 + + + + + + + 93 + 155 + 79 + + + + + + + 70 + 116 + 59 + + + + + + + 155 + 255 + 147 + + + + + + + 70 + 116 + 59 + + + + + + + 140 + 232 + 119 + + + + + + + 140 + 232 + 119 + + + + + + + 0 + 0 + 0 + + + + + + + 140 + 232 + 119 + + + + + + + 125 + 194 + 122 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + SecureSend + + + true + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + 12 + + + + + Memo: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + addAsLabel + + + + + + + A&mount: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + payAmount + + + + + + + Pay &To: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + payTo_s + + + + + + + false + + + false + + + true + + + + + + + 0 + + + + + + + + + + + message from merchant + + + + + diff --git a/src/qt/guiconstants.h b/src/qt/guiconstants.h index 92417834e..2c72c0102 100644 --- a/src/qt/guiconstants.h +++ b/src/qt/guiconstants.h @@ -28,6 +28,9 @@ static const int TOOLTIP_WRAP_THRESHOLD = 80; /* Maximum allowed URI length */ static const int MAX_URI_LENGTH = 255; +/* Maximum somewhat-sane size of a payment request file */ +static const int MAX_PAYMENT_REQUEST_SIZE = 50000; // bytes + /* QRCodeDialog -- size of exported QR Code image */ #define EXPORT_IMAGE_SIZE 256 diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 7ebe5b475..d93a60e1b 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -290,3 +290,14 @@ qint64 OptionsModel::getTransactionFee() { return nTransactionFee; } + +bool OptionsModel::getProxySettings(QString& proxyIP, quint16 &proxyPort) const +{ + std::string proxy = GetArg("-proxy", ""); + if (proxy.empty()) return false; + + CService addrProxy(proxy); + proxyIP = QString(addrProxy.ToStringIP().c_str()); + proxyPort = addrProxy.GetPort(); + return true; +} diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index d25d898d9..173db0fe0 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -49,6 +49,7 @@ public: int getDisplayUnit() { return nDisplayUnit; } bool getDisplayAddresses() { return bDisplayAddresses; } QString getLanguage() { return language; } + bool getProxySettings(QString& proxyIP, quint16 &proxyPort) const; private: int nDisplayUnit; diff --git a/src/qt/paymentrequest.proto b/src/qt/paymentrequest.proto new file mode 100644 index 000000000..b2281c4c7 --- /dev/null +++ b/src/qt/paymentrequest.proto @@ -0,0 +1,46 @@ +// +// Simple Bitcoin Payment Protocol messages +// +// Use fields 100+ for extensions; +// to avoid conflicts, register extensions at: +// https://en.bitcoin.it/wiki/Payment_Request +// + +package payments; +option java_package = "org.bitcoin.protocols.payments"; +option java_outer_classname = "Protos"; + +// Generalized form of "send payment to this/these bitcoin addresses" +message Output { + optional uint64 amount = 1 [default = 0]; // amount is integer-number-of-satoshis + required bytes script = 2; // usually one of the standard Script forms +} +message PaymentDetails { + optional string network = 1 [default = "main"]; // "main" or "test" + repeated Output outputs = 2; // Where payment should be sent + required uint64 time = 3; // Timestamp; when payment request created + optional uint64 expires = 4; // Timestamp; when this request should be considered invalid + optional string memo = 5; // Human-readable description of request for the customer + optional string payment_url = 6; // URL to send Payment and get PaymentACK + optional bytes merchant_data = 7; // Arbitrary data to include in the Payment message +} +message PaymentRequest { + optional uint32 payment_details_version = 1 [default = 1]; + optional string pki_type = 2 [default = "none"]; // none / x509+sha256 / x509+sha1 + optional bytes pki_data = 3; // depends on pki_type + required bytes serialized_payment_details = 4; // PaymentDetails + optional bytes signature = 5; // pki-dependent signature +} +message X509Certificates { + repeated bytes certificate = 1; // DER-encoded X.509 certificate chain +} +message Payment { + optional bytes merchant_data = 1; // From PaymentDetails.merchant_data + repeated bytes transactions = 2; // Signed transactions that satisfy PaymentDetails.outputs + repeated Output refund_to = 3; // Where to send refunds, if a refund is necessary + optional string memo = 4; // Human-readable message for the merchant +} +message PaymentACK { + required Payment payment = 1; // Payment message that triggered this ACK + optional string memo = 2; // human-readable message for customer +} diff --git a/src/qt/paymentrequestplus.cpp b/src/qt/paymentrequestplus.cpp new file mode 100644 index 000000000..289ddabb9 --- /dev/null +++ b/src/qt/paymentrequestplus.cpp @@ -0,0 +1,204 @@ +// +// Wraps dumb protocol buffer paymentRequest +// with some extra methods +// + +#include +#include +#include + +#include +#include + +#include + +#include "paymentrequestplus.h" + +class SSLVerifyError : public std::runtime_error +{ +public: + SSLVerifyError(std::string err) : std::runtime_error(err) { } +}; + +bool PaymentRequestPlus::parse(const QByteArray& data) +{ + bool parseOK = paymentRequest.ParseFromArray(data.data(), data.size()); + if (!parseOK) { + qDebug() << "Error parsing payment request"; + return false; + } + if (paymentRequest.payment_details_version() > 1) { + qDebug() << "Received up-version payment details, version=" << paymentRequest.payment_details_version(); + return false; + } + + parseOK = details.ParseFromString(paymentRequest.serialized_payment_details()); + if (!parseOK) + { + qDebug() << "Error parsing payment details"; + paymentRequest.Clear(); + return false; + } + return true; +} + +bool PaymentRequestPlus::SerializeToString(string* output) const +{ + return paymentRequest.SerializeToString(output); +} + +bool PaymentRequestPlus::IsInitialized() const +{ + return paymentRequest.IsInitialized(); +} + +QString PaymentRequestPlus::getPKIType() const +{ + if (!IsInitialized()) return QString("none"); + return QString::fromStdString(paymentRequest.pki_type()); +} + +bool PaymentRequestPlus::getMerchant(X509_STORE* certStore, QString& merchant) const +{ + merchant.clear(); + + if (!IsInitialized()) + return false; + + // One day we'll support more PKI types, but just + // x509 for now: + const EVP_MD* digestAlgorithm = NULL; + if (paymentRequest.pki_type() == "x509+sha256") { + digestAlgorithm = EVP_sha256(); + } + else if (paymentRequest.pki_type() == "x509+sha1") { + digestAlgorithm = EVP_sha1(); + } + else if (paymentRequest.pki_type() == "none") { + if (fDebug) qDebug() << "PaymentRequest: pki_type == none"; + return false; + } + else { + qDebug() << "PaymentRequest: unknown pki_type " << paymentRequest.pki_type().c_str(); + return false; + } + + payments::X509Certificates certChain; + if (!certChain.ParseFromString(paymentRequest.pki_data())) { + qDebug() << "PaymentRequest: error parsing pki_data"; + return false; + } + + std::vector certs; + const QDateTime currentTime = QDateTime::currentDateTime(); + for (int i = 0; i < certChain.certificate_size(); i++) { + QByteArray certData(certChain.certificate(i).data(), certChain.certificate(i).size()); + QSslCertificate qCert(certData, QSsl::Der); + if (currentTime < qCert.effectiveDate() || currentTime > qCert.expiryDate()) { + qDebug() << "PaymentRequest: certificate expired or not yet active: " << qCert; + return false; + } +#if QT_VERSION >= 0x050000 + if (qCert.isBlacklisted()) { + qDebug() << "PaymentRequest: certificate blacklisted: " << qCert; + return false; + } +#endif + const unsigned char *data = (const unsigned char *)certChain.certificate(i).data(); + X509 *cert = d2i_X509(NULL, &data, certChain.certificate(i).size()); + if (cert) + certs.push_back(cert); + } + if (certs.empty()) { + qDebug() << "PaymentRequest: empty certificate chain"; + return false; + } + + // The first cert is the signing cert, the rest are untrusted certs that chain + // to a valid root authority. OpenSSL needs them separately. + STACK_OF(X509) *chain = sk_X509_new_null(); + for (int i = certs.size()-1; i > 0; i--) { + sk_X509_push(chain, certs[i]); + } + X509 *signing_cert = certs[0]; + + // Now create a "store context", which is a single use object for checking, + // load the signing cert into it and verify. + X509_STORE_CTX *store_ctx = X509_STORE_CTX_new(); + if (!store_ctx) { + qDebug() << "PaymentRequest: error creating X509_STORE_CTX"; + return false; + } + + char *website = NULL; + bool fResult = true; + try + { + if (!X509_STORE_CTX_init(store_ctx, certStore, signing_cert, chain)) + { + int error = X509_STORE_CTX_get_error(store_ctx); + throw SSLVerifyError(X509_verify_cert_error_string(error)); + } + + // Now do the verification! + int result = X509_verify_cert(store_ctx); + if (result != 1) { + int error = X509_STORE_CTX_get_error(store_ctx); + throw SSLVerifyError(X509_verify_cert_error_string(error)); + } + X509_NAME *certname = X509_get_subject_name(signing_cert); + + // Valid cert; check signature: + payments::PaymentRequest rcopy(paymentRequest); // Copy + rcopy.set_signature(std::string("")); + std::string data_to_verify; // Everything but the signature + rcopy.SerializeToString(&data_to_verify); + + EVP_MD_CTX ctx; + EVP_PKEY *pubkey = X509_get_pubkey(signing_cert); + EVP_MD_CTX_init(&ctx); + if (!EVP_VerifyInit_ex(&ctx, digestAlgorithm, NULL) || + !EVP_VerifyUpdate(&ctx, data_to_verify.data(), data_to_verify.size()) || + !EVP_VerifyFinal(&ctx, (const unsigned char*)paymentRequest.signature().data(), paymentRequest.signature().size(), pubkey)) { + + throw SSLVerifyError("Bad signature, invalid PaymentRequest."); + } + + // OpenSSL API for getting human printable strings from certs is baroque. + int textlen = X509_NAME_get_text_by_NID(certname, NID_commonName, NULL, 0); + website = new char[textlen + 1]; + if (X509_NAME_get_text_by_NID(certname, NID_commonName, website, textlen + 1) == textlen && textlen > 0) { + merchant = website; + } + else { + throw SSLVerifyError("Bad certificate, missing common name"); + } + // TODO: detect EV certificates and set merchant = business name instead of unfriendly NID_commonName ? + } + catch (SSLVerifyError& err) + { + fResult = false; + qDebug() << "PaymentRequestPlus::getMerchant SSL err: " << err.what(); + } + + if (website) + delete[] website; + X509_STORE_CTX_free(store_ctx); + for (unsigned int i = 0; i < certs.size(); i++) + X509_free(certs[i]); + + return fResult; +} + +QList > PaymentRequestPlus::getPayTo() const +{ + QList > result; + for (int i = 0; i < details.outputs_size(); i++) + { + const unsigned char* scriptStr = (const unsigned char*)details.outputs(i).script().data(); + CScript s(scriptStr, scriptStr+details.outputs(i).script().size()); + + result.append(make_pair(s, details.outputs(i).amount())); + } + return result; +} diff --git a/src/qt/paymentrequestplus.h b/src/qt/paymentrequestplus.h new file mode 100644 index 000000000..846d256ac --- /dev/null +++ b/src/qt/paymentrequestplus.h @@ -0,0 +1,41 @@ +#ifndef PAYMENTREQUESTPLUS_H +#define PAYMENTREQUESTPLUS_H + +#include +#include +#include + +#include "base58.h" +#include "paymentrequest.pb.h" + +// +// Wraps dumb protocol buffer paymentRequest +// with extra methods +// + +class PaymentRequestPlus +{ +public: + PaymentRequestPlus() { } + + bool parse(const QByteArray& data); + bool SerializeToString(string* output) const; + + bool IsInitialized() const; + QString getPKIType() const; + // Returns true if merchant's identity is authenticated, and + // returns human-readable merchant identity in merchant + bool getMerchant(X509_STORE* certStore, QString& merchant) const; + + // Returns list of outputs, amount + QList > getPayTo() const; + + const payments::PaymentDetails& getDetails() const { return details; } + +private: + payments::PaymentRequest paymentRequest; + payments::PaymentDetails details; +}; + +#endif // PAYMENTREQUESTPLUS_H + diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp index 0d31f24a1..a9f71315a 100644 --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -2,30 +2,63 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include "paymentserver.h" - -#include "guiconstants.h" -#include "ui_interface.h" -#include "util.h" - #include #include #include +#include #include +#include #include #include +#include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #if QT_VERSION < 0x050000 #include +#else +#include #endif +#include + +#include +#include + +#include "base58.h" +#include "bitcoinunits.h" +#include "guiconstants.h" +#include "guiutil.h" +#include "optionsmodel.h" +#include "paymentserver.h" +#include "ui_interface.h" +#include "util.h" +#include "wallet.h" +#include "walletmodel.h" + using namespace boost; const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds const QString BITCOIN_IPC_PREFIX("bitcoin:"); +X509_STORE* PaymentServer::certStore = NULL; +void PaymentServer::freeCertStore() +{ + if (PaymentServer::certStore != NULL) + { + X509_STORE_free(PaymentServer::certStore); + PaymentServer::certStore = NULL; + } +} + // // Create a name that is unique for: // testnet / non-testnet @@ -45,11 +78,99 @@ static QString ipcServerName() } // -// This stores payment requests received before +// We store payment URLs and requests received before // the main GUI window is up and ready to ask the user // to send payment. + +static QList savedPaymentRequests; + +static void ReportInvalidCertificate(const QSslCertificate& cert) +{ + if (fDebug) { + qDebug() << "Invalid certificate: " << cert.subjectInfo(QSslCertificate::CommonName); + } +} + +// +// Load openSSL's list of root certificate authorities // -static QStringList savedPaymentRequests; +void PaymentServer::LoadRootCAs(X509_STORE* _store) +{ + if (PaymentServer::certStore == NULL) + atexit(PaymentServer::freeCertStore); + else + freeCertStore(); + + // Unit tests mostly use this, to pass in fake root CAs: + if (_store) + { + PaymentServer::certStore = _store; + return; + } + + // Normal execution, use either -rootcertificates or system certs: + PaymentServer::certStore = X509_STORE_new(); + + // Note: use "-system-" default here so that users can pass -rootcertificates="" + // and get 'I don't like X.509 certificates, don't trust anybody' behavior: + QString certFile = QString::fromStdString(GetArg("-rootcertificates", "-system-")); + + if (certFile.isEmpty()) + return; // Empty store + + QList certList; + + if (certFile != "-system-") + { + certList = QSslCertificate::fromPath(certFile); + // Use those certificates when fetching payment requests, too: + QSslSocket::setDefaultCaCertificates(certList); + } + else + certList = QSslSocket::systemCaCertificates (); + + int nRootCerts = 0; + const QDateTime currentTime = QDateTime::currentDateTime(); + foreach (const QSslCertificate& cert, certList) + { + if (currentTime < cert.effectiveDate() || currentTime > cert.expiryDate()) { + ReportInvalidCertificate(cert); + continue; + } +#if QT_VERSION >= 0x050000 + if (cert.isBlacklisted()) { + ReportInvalidCertificate(cert); + continue; + } +#endif + QByteArray certData = cert.toDer(); + const unsigned char *data = (const unsigned char *)certData.data(); + + X509* x509 = d2i_X509(0, &data, certData.size()); + if (x509 && X509_STORE_add_cert( PaymentServer::certStore, x509)) + { + // Note: X509_STORE_free will free the X509* objects when + // the PaymentServer is destroyed + ++nRootCerts; + } + else + { + ReportInvalidCertificate(cert); + continue; + } + } + if (fDebug) + qDebug() << "PaymentServer: loaded " << nRootCerts << " root certificates"; + + // Project for another day: + // Fetch certificate revocation lists, and add them to certStore. + // Issues to consider: + // performance (start a thread to fetch in background?) + // privacy (fetch through tor/proxy so IP address isn't revealed) + // would it be easier to just use a compiled-in blacklist? + // or use Qt's blacklist? + // "certificate stapling" with server-side caching is more efficient +} // // Sending to the server is done synchronously, at startup. @@ -57,19 +178,54 @@ static QStringList savedPaymentRequests; // and the items in savedPaymentRequest will be handled // when uiReady() is called. // -bool PaymentServer::ipcSendCommandLine() +bool PaymentServer::ipcSendCommandLine(int argc, char* argv[]) { bool fResult = false; - const QStringList& args = qApp->arguments(); - for (int i = 1; i < args.size(); i++) + for (int i = 1; i < argc; i++) { - if (!args[i].startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) + QString arg(argv[i]); + if (arg.startsWith("-")) continue; - savedPaymentRequests.append(args[i]); + + if (arg.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // bitcoin: + { + savedPaymentRequests.append(arg); + + SendCoinsRecipient r; + if (GUIUtil::parseBitcoinURI(arg, &r)) + { + CBitcoinAddress address(r.address.toStdString()); + + SelectParams(CChainParams::MAIN); + if (!address.IsValid()) + { + SelectParams(CChainParams::TESTNET); + } + } + } + else if (QFile::exists(arg)) // Filename + { + savedPaymentRequests.append(arg); + + PaymentRequestPlus request; + if (readPaymentRequest(arg, request)) + { + if (request.getDetails().network() == "main") + SelectParams(CChainParams::MAIN); + else + SelectParams(CChainParams::TESTNET); + } + } + else + { + qDebug() << "Payment request file does not exist: " << argv[i]; + // Printing to debug.log is about the best we can do here, the + // GUI hasn't started yet so we can't pop up a message box. + } } - foreach (const QString& arg, savedPaymentRequests) + foreach (const QString& r, savedPaymentRequests) { QLocalSocket* socket = new QLocalSocket(); socket->connectToServer(ipcServerName(), QIODevice::WriteOnly); @@ -79,7 +235,7 @@ bool PaymentServer::ipcSendCommandLine() QByteArray block; QDataStream out(&block, QIODevice::WriteOnly); out.setVersion(QDataStream::Qt_4_0); - out << arg; + out << r; out.device()->seek(0); socket->write(block); socket->flush(); @@ -92,50 +248,148 @@ bool PaymentServer::ipcSendCommandLine() return fResult; } -PaymentServer::PaymentServer(QApplication* parent) : QObject(parent), saveURIs(true) +PaymentServer::PaymentServer(QObject* parent, + bool startLocalServer) : QObject(parent), saveURIs(true) { + // Verify that the version of the library that we linked against is + // compatible with the version of the headers we compiled against. + GOOGLE_PROTOBUF_VERIFY_VERSION; + // Install global event filter to catch QFileOpenEvents on the mac (sent when you click bitcoin: links) - parent->installEventFilter(this); + if (parent) + parent->installEventFilter(this); QString name = ipcServerName(); // Clean up old socket leftover from a crash: QLocalServer::removeServer(name); - uriServer = new QLocalServer(this); + if (startLocalServer) + { + uriServer = new QLocalServer(this); - if (!uriServer->listen(name)) - qDebug() << tr("Cannot start bitcoin: click-to-pay handler"); - else - connect(uriServer, SIGNAL(newConnection()), this, SLOT(handleURIConnection())); + if (!uriServer->listen(name)) + qDebug() << "Cannot start bitcoin: click-to-pay handler"; + else + connect(uriServer, SIGNAL(newConnection()), this, SLOT(handleURIConnection())); + } + + // netManager is null until uiReady() is called + netManager = NULL; } -bool PaymentServer::eventFilter(QObject *object, QEvent *event) +PaymentServer::~PaymentServer() +{ + google::protobuf::ShutdownProtobufLibrary(); +} + +// +// OSX-specific way of handling bitcoin uris and +// PaymentRequest mime types +// +bool PaymentServer::eventFilter(QObject *, QEvent *event) { // clicking on bitcoin: URLs creates FileOpen events on the Mac: if (event->type() == QEvent::FileOpen) { QFileOpenEvent* fileEvent = static_cast(event); - if (!fileEvent->url().isEmpty()) - { - if (saveURIs) // Before main window is ready: - savedPaymentRequests.append(fileEvent->url().toString()); - else - emit receivedURI(fileEvent->url().toString()); - return true; - } + if (!fileEvent->file().isEmpty()) + handleURIOrFile(fileEvent->file()); + else if (!fileEvent->url().isEmpty()) + handleURIOrFile(fileEvent->url().toString()); + + return true; } return false; } +void PaymentServer::initNetManager(const OptionsModel& options) +{ + if (netManager != NULL) + delete netManager; + + // netManager is used to fetch paymentrequests given in bitcoin: URI's + netManager = new QNetworkAccessManager(this); + + // Use proxy settings from options: + QString proxyIP; + quint16 proxyPort; + if (options.getProxySettings(proxyIP, proxyPort)) + { + QNetworkProxy proxy; + proxy.setType(QNetworkProxy::Socks5Proxy); + proxy.setHostName(proxyIP); + proxy.setPort(proxyPort); + netManager->setProxy(proxy); + } + + connect(netManager, SIGNAL(finished(QNetworkReply*)), + this, SLOT(netRequestFinished(QNetworkReply*))); + connect(netManager, SIGNAL(sslErrors(QNetworkReply*, const QList &)), + this, SLOT(reportSslErrors(QNetworkReply*, const QList &))); +} + void PaymentServer::uiReady() { + assert(netManager != NULL); // Must call initNetManager before uiReady() + saveURIs = false; foreach (const QString& s, savedPaymentRequests) - emit receivedURI(s); + { + handleURIOrFile(s); + } savedPaymentRequests.clear(); } +void PaymentServer::handleURIOrFile(const QString& s) +{ + if (saveURIs) + { + savedPaymentRequests.append(s); + return; + } + + if (s.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // bitcoin: + { +#if QT_VERSION >= 0x050000 + QUrlQuery url((QUrl(s))); +#else + QUrl url(s); +#endif + if (url.hasQueryItem("request")) + { + QByteArray temp; temp.append(url.queryItemValue("request")); + QString decoded = QUrl::fromPercentEncoding(temp); + QUrl fetchUrl(decoded, QUrl::StrictMode); + + if (fDebug) qDebug() << "PaymentServer::fetchRequest " << fetchUrl; + + if (fetchUrl.isValid()) + fetchRequest(fetchUrl); + else + qDebug() << "PaymentServer: invalid url: " << fetchUrl; + return; + } + + SendCoinsRecipient recipient; + if (GUIUtil::parseBitcoinURI(s, &recipient)) + emit receivedPaymentRequest(recipient); + return; + } + + if (QFile::exists(s)) + { + PaymentRequestPlus request; + QList recipients; + if (readPaymentRequest(s, request) && processPaymentRequest(request, recipients)) { + foreach (const SendCoinsRecipient& recipient, recipients){ + emit receivedPaymentRequest(recipient); + } + } + return; + } +} + void PaymentServer::handleURIConnection() { QLocalSocket *clientConnection = uriServer->nextPendingConnection(); @@ -154,8 +408,209 @@ void PaymentServer::handleURIConnection() QString message; in >> message; - if (saveURIs) - savedPaymentRequests.append(message); - else - emit receivedURI(message); + handleURIOrFile(message); +} + +bool PaymentServer::readPaymentRequest(const QString& filename, PaymentRequestPlus& request) +{ + QFile f(filename); + if (!f.open(QIODevice::ReadOnly)) + { + qDebug() << "PaymentServer::readPaymentRequest fail to open " << filename; + return false; + } + + if (f.size() > MAX_PAYMENT_REQUEST_SIZE) + { + qDebug() << "PaymentServer::readPaymentRequest " << filename << " too large"; + return false; + } + + QByteArray data = f.readAll(); + + return request.parse(data); +} + +bool +PaymentServer::processPaymentRequest(PaymentRequestPlus& request, + QList& recipients) +{ + QList > sendingTos = request.getPayTo(); + qint64 totalAmount = 0; + foreach(const PAIRTYPE(CScript, qint64)& sendingTo, sendingTos) { + CTxOut txOut(sendingTo.second, sendingTo.first); + if (txOut.IsDust(CTransaction::nMinRelayTxFee)) { + QString message = QObject::tr("Requested payment amount (%1) too small") + .arg(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, sendingTo.second)); + qDebug() << message; + emit reportError(tr("Payment request error"), message, CClientUIInterface::MODAL); + return false; + } + + totalAmount += sendingTo.second; + } + + recipients.append(SendCoinsRecipient()); + + if (request.getMerchant(PaymentServer::certStore, recipients[0].authenticatedMerchant)) { + recipients[0].paymentRequest = request; + recipients[0].amount = totalAmount; + if (fDebug) qDebug() << "PaymentRequest from " << recipients[0].authenticatedMerchant; + } + else { + recipients.clear(); + // Insecure payment requests may turn into more than one recipient if + // the merchant is requesting payment to more than one address. + for (int i = 0; i < sendingTos.size(); i++) { + std::pair& sendingTo = sendingTos[i]; + recipients.append(SendCoinsRecipient()); + recipients[i].amount = sendingTo.second; + QString memo = QString::fromStdString(request.getDetails().memo()); +#if QT_VERSION < 0x050000 + recipients[i].label = Qt::escape(memo); +#else + recipients[i].label = memo.toHtmlEscaped(); +#endif + CTxDestination dest; + if (ExtractDestination(sendingTo.first, dest)) { + if (i == 0) // Tie request to first pay-to, we don't want multiple ACKs + recipients[i].paymentRequest = request; + recipients[i].address = QString::fromStdString(CBitcoinAddress(dest).ToString()); + if (fDebug) qDebug() << "PaymentRequest, insecure " << recipients[i].address; + } + else { + // Insecure payments to custom bitcoin addresses are not supported + // (there is no good way to tell the user where they are paying in a way + // they'd have a chance of understanding). + emit reportError(tr("Payment request error"), + tr("Insecure requests to custom payment scripts unsupported"), + CClientUIInterface::MODAL); + return false; + } + } + } + + return true; +} + +void +PaymentServer::fetchRequest(const QUrl& url) +{ + QNetworkRequest netRequest; + netRequest.setAttribute(QNetworkRequest::User, "PaymentRequest"); + netRequest.setUrl(url); + netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str()); + netManager->get(netRequest); +} + +void +PaymentServer::fetchPaymentACK(CWallet* wallet, SendCoinsRecipient recipient, QByteArray transaction) +{ + const payments::PaymentDetails& details = recipient.paymentRequest.getDetails(); + if (!details.has_payment_url()) + return; + + QNetworkRequest netRequest; + netRequest.setAttribute(QNetworkRequest::User, "PaymentACK"); + netRequest.setUrl(QString::fromStdString(details.payment_url())); + netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/bitcoin-payment"); + netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str()); + + payments::Payment payment; + payment.set_merchant_data(details.merchant_data()); + payment.add_transactions(transaction.data(), transaction.size()); + + // Create a new refund address, or re-use: + QString account = tr("Refund from") + QString(" ") + recipient.authenticatedMerchant; + std::string strAccount = account.toStdString(); + set refundAddresses = wallet->GetAccountAddresses(strAccount); + if (!refundAddresses.empty()) { + CScript s; s.SetDestination(*refundAddresses.begin()); + payments::Output* refund_to = payment.add_refund_to(); + refund_to->set_script(&s[0], s.size()); + } + else { + CPubKey newKey; + if (wallet->GetKeyFromPool(newKey, false)) { + CKeyID keyID = newKey.GetID(); + wallet->SetAddressBook(keyID, strAccount, "refund"); + + CScript s; s.SetDestination(keyID); + payments::Output* refund_to = payment.add_refund_to(); + refund_to->set_script(&s[0], s.size()); + } + else { + // This should never happen, because sending coins should have just unlocked the wallet + // and refilled the keypool + qDebug() << "Error getting refund key, refund_to not set"; + } + } + + int length = payment.ByteSize(); + netRequest.setHeader(QNetworkRequest::ContentLengthHeader, length); + QByteArray serData(length, '\0'); + if (payment.SerializeToArray(serData.data(), length)) { + netManager->post(netRequest, serData); + } + else { + // This should never happen, either: + qDebug() << "Error serializing payment message"; + } +} + +void +PaymentServer::netRequestFinished(QNetworkReply* reply) +{ + reply->deleteLater(); + if (reply->error() != QNetworkReply::NoError) + { + QString message = QObject::tr("Error communicating with %1: %2") + .arg(reply->request().url().toString()) + .arg(reply->errorString()); + qDebug() << message; + emit reportError(tr("Network request error"), message, CClientUIInterface::MODAL); + return; + } + + QByteArray data = reply->readAll(); + + QString requestType = reply->request().attribute(QNetworkRequest::User).toString(); + if (requestType == "PaymentRequest") + { + PaymentRequestPlus request; + QList recipients; + if (request.parse(data) && processPaymentRequest(request, recipients)) { + foreach (const SendCoinsRecipient& recipient, recipients){ + emit receivedPaymentRequest(recipient); + } + } + else + qDebug() << "PaymentServer::netRequestFinished: error processing PaymentRequest"; + return; + } + else if (requestType == "PaymentACK") + { + payments::PaymentACK paymentACK; + if (!paymentACK.ParseFromArray(data.data(), data.size())) + { + QString message = QObject::tr("Bad response from server %1") + .arg(reply->request().url().toString()); + qDebug() << message; + emit reportError(tr("Network request error"), message, CClientUIInterface::MODAL); + } + else { + emit receivedPaymentACK(QString::fromStdString(paymentACK.memo())); + } + } +} + +void +PaymentServer::reportSslErrors(QNetworkReply* reply, const QList &errs) +{ + QString errString; + foreach (const QSslError& err, errs) { + qDebug() << err; + errString += err.errorString() + "\n"; + } + emit reportError(tr("Network request error"), errString, CClientUIInterface::MODAL); } diff --git a/src/qt/paymentserver.h b/src/qt/paymentserver.h index 1f2fdd466..7c6b2eabf 100644 --- a/src/qt/paymentserver.h +++ b/src/qt/paymentserver.h @@ -31,37 +31,87 @@ #include #include +#include "paymentrequestplus.h" +#include "walletmodel.h" + +class CWallet; +class OptionsModel; class QApplication; +class QByteArray; class QLocalServer; +class QNetworkAccessManager; +class QNetworkReply; +class QSslError; +class QUrl; class PaymentServer : public QObject { Q_OBJECT -private: - bool saveURIs; - QLocalServer* uriServer; - public: // Returns true if there were URIs on the command line // which were successfully sent to an already-running // process. - static bool ipcSendCommandLine(); + // Note: if a payment request is given, SelectParams(MAIN/TESTNET) + // will be called so we startup in the right mode. + static bool ipcSendCommandLine(int argc, char *argv[]); - PaymentServer(QApplication* parent); + PaymentServer(QObject* parent, // parent should be QApplication object + bool startLocalServer=true); + ~PaymentServer(); + // Load root certificate authorities. Pass NULL (default) + // to read from the file specified in the -rootcertificates setting, + // or, if that's not set, to use the system default root certificates. + // If you pass in a store, you should not X509_STORE_free it: it will be + // freed either at exit or when another set of CAs are loaded. + static void LoadRootCAs(X509_STORE* store=NULL); + + // Return certificate store + static X509_STORE* getCertStore() { return certStore; } + + // Setup networking (options is used to get proxy settings) + void initNetManager(const OptionsModel& options); + + // Constructor registers this on the parent QApplication to + // receive QEvent::FileOpen events bool eventFilter(QObject *object, QEvent *event); signals: - void receivedURI(QString); + // Fired when a valid payment request is received + void receivedPaymentRequest(SendCoinsRecipient); + + // Fired when a valid PaymentACK is received + void receivedPaymentACK(QString); + + // Fired when an error should be reported to the user + void reportError(QString, QString, unsigned int); public slots: // Signal this when the main window's UI is ready // to display payment requests to the user void uiReady(); + // Submit Payment message to a merchant, get back PaymentACK: + void fetchPaymentACK(CWallet* wallet, SendCoinsRecipient recipient, QByteArray transaction); + private slots: void handleURIConnection(); + void netRequestFinished(QNetworkReply*); + void reportSslErrors(QNetworkReply*, const QList &); + +private: + static bool readPaymentRequest(const QString& filename, PaymentRequestPlus& request); + bool processPaymentRequest(PaymentRequestPlus& request, QList& recipients); + void handleURIOrFile(const QString& s); + void fetchRequest(const QUrl& url); + + bool saveURIs; // true during startup + QLocalServer* uriServer; + static X509_STORE* certStore; // Trusted root certificates + static void freeCertStore(); + + QNetworkAccessManager* netManager; // Used to fetch payment requests }; #endif // PAYMENTSERVER_H diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 2c7bab204..09d83b1a2 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -93,11 +93,26 @@ void SendCoinsDialog::on_sendButton_clicked() QStringList formatted; foreach(const SendCoinsRecipient &rcp, recipients) { + QString amount = BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, rcp.amount); + if (rcp.authenticatedMerchant.isEmpty()) + { + QString address = rcp.address; #if QT_VERSION < 0x050000 - formatted.append(tr("%1 to %2 (%3)").arg(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, rcp.amount), Qt::escape(rcp.label), rcp.address)); + QString to = Qt::escape(rcp.label); #else - formatted.append(tr("%1 to %2 (%3)").arg(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, rcp.amount), rcp.label.toHtmlEscaped(), rcp.address)); + QString to = rcp.label.toHtmlEscaped(); #endif + formatted.append(tr("%1 to %2 (%3)").arg(amount, to, address)); + } + else + { +#if QT_VERSION < 0x050000 + QString merchant = Qt::escape(rcp.authenticatedMerchant); +#else + QString merchant = rcp.authenticatedMerchant.toHtmlEscaped(); +#endif + formatted.append(tr("%1 to %2").arg(amount, merchant)); + } } fNewRecipientAllowed = false; @@ -292,20 +307,30 @@ void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv) entry->setValue(rv); } -bool SendCoinsDialog::handleURI(const QString &uri) +bool SendCoinsDialog::handlePaymentRequest(const SendCoinsRecipient &rv) { - SendCoinsRecipient rv; - // URI has to be valid - if (GUIUtil::parseBitcoinURI(uri, &rv)) - { + if (!rv.authenticatedMerchant.isEmpty()) { + // Expired payment request? + const payments::PaymentDetails& details = rv.paymentRequest.getDetails(); + if (details.has_expires() && (int64)details.expires() < GetTime()) + { + QMessageBox::warning(this, tr("Send Coins"), + tr("Payment request expired")); + return false; + } + } + else { CBitcoinAddress address(rv.address.toStdString()); - if (!address.IsValid()) + if (!address.IsValid()) { + QString strAddress(address.ToString().c_str()); + QMessageBox::warning(this, tr("Send Coins"), + tr("Invalid payment address %1").arg(strAddress)); return false; - pasteEntry(rv); - return true; + } } - return false; + pasteEntry(rv); + return true; } void SendCoinsDialog::setBalance(qint64 balance, qint64 unconfirmedBalance, qint64 immatureBalance) diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h index 043dfdcb4..e75a003ba 100644 --- a/src/qt/sendcoinsdialog.h +++ b/src/qt/sendcoinsdialog.h @@ -2,6 +2,7 @@ #define SENDCOINSDIALOG_H #include +#include namespace Ui { class SendCoinsDialog; @@ -31,7 +32,7 @@ public: void setAddress(const QString &address); void pasteEntry(const SendCoinsRecipient &rv); - bool handleURI(const QString &uri); + bool handlePaymentRequest(const SendCoinsRecipient &recipient); public slots: void clear(); diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp index 1c54850a0..75610f199 100644 --- a/src/qt/sendcoinsentry.cpp +++ b/src/qt/sendcoinsentry.cpp @@ -12,12 +12,14 @@ #include SendCoinsEntry::SendCoinsEntry(QWidget *parent) : - QFrame(parent), + QStackedWidget(parent), ui(new Ui::SendCoinsEntry), model(0) { ui->setupUi(this); + setCurrentWidget(ui->SendCoinsInsecure); + #ifdef Q_OS_MAC ui->payToLayout->setSpacing(4); #endif @@ -101,6 +103,9 @@ bool SendCoinsEntry::validate() // Check input validity bool retval = true; + if (!recipient.authenticatedMerchant.isEmpty()) + return retval; + if(!ui->payTo->hasAcceptableInput() || (model && !model->validateAddress(ui->payTo->text()))) { @@ -124,13 +129,15 @@ bool SendCoinsEntry::validate() SendCoinsRecipient SendCoinsEntry::getValue() { - SendCoinsRecipient rv; + if (!recipient.authenticatedMerchant.isEmpty()) + return recipient; - rv.address = ui->payTo->text(); - rv.label = ui->addAsLabel->text(); - rv.amount = ui->payAmount->value(); + // User-entered or non-authenticated: + recipient.address = ui->payTo->text(); + recipient.label = ui->addAsLabel->text(); + recipient.amount = ui->payAmount->value(); - return rv; + return recipient; } QWidget *SendCoinsEntry::setupTabChain(QWidget *prev) @@ -145,9 +152,22 @@ QWidget *SendCoinsEntry::setupTabChain(QWidget *prev) void SendCoinsEntry::setValue(const SendCoinsRecipient &value) { + recipient = value; + ui->payTo->setText(value.address); ui->addAsLabel->setText(value.label); ui->payAmount->setValue(value.amount); + + if (!recipient.authenticatedMerchant.isEmpty()) + { + const payments::PaymentDetails& details = value.paymentRequest.getDetails(); + + ui->payTo_s->setText(value.authenticatedMerchant); + ui->memo_s->setTextFormat(Qt::PlainText); + ui->memo_s->setText(QString::fromStdString(details.memo())); + ui->payAmount_s->setValue(value.amount); + setCurrentWidget(ui->SendCoinsSecure); + } } void SendCoinsEntry::setAddress(const QString &address) @@ -158,7 +178,7 @@ void SendCoinsEntry::setAddress(const QString &address) bool SendCoinsEntry::isClear() { - return ui->payTo->text().isEmpty(); + return ui->payTo->text().isEmpty() && ui->payTo_s->text().isEmpty(); } void SendCoinsEntry::setFocus() @@ -172,5 +192,6 @@ void SendCoinsEntry::updateDisplayUnit() { // Update payAmount with the current unit ui->payAmount->setDisplayUnit(model->getOptionsModel()->getDisplayUnit()); + ui->payAmount_s->setDisplayUnit(model->getOptionsModel()->getDisplayUnit()); } } diff --git a/src/qt/sendcoinsentry.h b/src/qt/sendcoinsentry.h index ec5f3410c..9c7bfe952 100644 --- a/src/qt/sendcoinsentry.h +++ b/src/qt/sendcoinsentry.h @@ -1,16 +1,21 @@ #ifndef SENDCOINSENTRY_H #define SENDCOINSENTRY_H -#include +#include + +#include "walletmodel.h" namespace Ui { class SendCoinsEntry; } class WalletModel; -class SendCoinsRecipient; -/** A single entry in the dialog for sending bitcoins. */ -class SendCoinsEntry : public QFrame +/** + * A single entry in the dialog for sending bitcoins. + * Stacked widget, with different UIs for payment requests + * with a strong payee identity. + */ +class SendCoinsEntry : public QStackedWidget { Q_OBJECT @@ -49,6 +54,7 @@ private slots: void updateDisplayUnit(); private: + SendCoinsRecipient recipient; Ui::SendCoinsEntry *ui; WalletModel *model; }; diff --git a/src/qt/test/paymentrequestdata.h b/src/qt/test/paymentrequestdata.h new file mode 100644 index 000000000..49558165f --- /dev/null +++ b/src/qt/test/paymentrequestdata.h @@ -0,0 +1,307 @@ +// +// Data for paymentservertests.cpp +// + +// Base64/DER-encoded fake certificate authority certificate. +// Convert pem to base64/der with: +// cat file.pem | openssl x509 -inform PEM -outform DER | openssl enc -base64 +// +// Serial Number: 10302349811211485352 (0x8ef94c91b112c0a8) +// Issuer: CN=PaymentRequest Test CA +// Subject: CN=PaymentRequest Test CA +// Not Valid After : Dec 8 16:37:24 2022 GMT +// +const char* caCert_BASE64 = +"\ +MIIB0DCCATmgAwIBAgIJAI75TJGxEsCoMA0GCSqGSIb3DQEBCwUAMCExHzAdBgNV\ +BAMTFlBheW1lbnRSZXF1ZXN0IFRlc3QgQ0EwHhcNMTIxMjEwMTYzNzI0WhcNMjIx\ +MjA4MTYzNzI0WjAhMR8wHQYDVQQDExZQYXltZW50UmVxdWVzdCBUZXN0IENBMIGf\ +MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvua59nX9radoqDYyplcns5qdVDTN1\ +7tmcGixmMYOYU3UYMU55VSsJs0dWKnMm3COQDY+N63c0XSbRqarBcsLTkaNASuPX\ +FCv1VWuEKSyy5xe4zeoDU7CVSzlxtQD9wbZW/s3ISjgaXBpwn6eVmntb0JwYxxPc\ +M1u/hrMD8BDbSQIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUA\ +A4GBADSaRgK5xe47XxycXBhHhr0Wgl4pAsFsufqA9aB9r8KNEHJ0yUvvbD/jaJJM\ +RtQcf0AJ9olzUMY4syehxbzUJP6aeXhZEYiMvdvcv9D55clq6+WLLlNT3jBgAaVn\ +p3waRjPD4bUX3nv+ojz5s4puw7Qq5QUZlhGsMzPvwDGCmZkL\ +"; + +// +// This payment request validates directly against the +// above certificate authority. +// +const char* paymentrequest1_BASE64 = +"\ +Egt4NTA5K3NoYTI1NhrxAwruAzCCAeowggFToAMCAQICAQEwDQYJKoZIhvcNAQEL\ +BQAwITEfMB0GA1UEAxMWUGF5bWVudFJlcXVlc3QgVGVzdCBDQTAeFw0xMjEyMTAx\ +NjM3MjRaFw0yMjEyMDgxNjM3MjRaMEMxGTAXBgNVBAMMEHRlc3RtZXJjaGFudC5v\ +cmcxJjAkBgNVBAoMHVBheW1lbnQgUmVxdWVzdCBUZXN0IE1lcmNoYW50MIGfMA0G\ +CSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHkMy8W1u6HsWlSqdWTmMKf54gICxNfxbY\ ++rcMtAftr62hCYx2d2QiSRd1pCUzmo12IiSX3WxSHwaTnT3MFD6jRx6+zM6XdGar\ +I2zpYle11ANzu4gAthN17uRQHV2O5QxVtzNaMdKeJLXT2L9tfEdyL++9ZUqoQmdA\ +YG9ix330hQIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4GB\ +AIkyO99KC68bi9PFRyQQ7nvn5GlQEb3Ca1bRG5+AKN9N5vc8rZ9G2hejtM8wEXni\ +eGBP+chVMsbTPEHKLrwREn7IvcyCcbAStaklPC3w0B/2idQSHskb6P3X13OR2bTH\ +a2+6wuhsOZRUrVNr24rM95DKx/eCC6JN1VW+qRPU6fqzIjQSHwiw2wYSGXapFJVg\ +igPI+6XpExtNLO/i1WFV8ZmoiKwYsuHFiwUqC1VuaXRUZXN0T25lKoABS0j59iMU\ +Uc9MdIfwsO1BskIET0eJSGNZ7eXb9N62u+qf831PMpEHkmlGpk8rHy92nPcgua/U\ +Yt8oZMn3QaTZ5A6HjJbc3A73eLylp1a0SwCl+KDMEvDQhqMn1jAVu2v92AH3uB7n\ +SiWVbw0tX/68iSQEGGfh9n6ee/8Myb3ICdw=\ +"; + +// +// Signed, but expired, merchant cert in the request +// +const char* paymentrequest2_BASE64 = +"\ +Egt4NTA5K3NoYTI1NhrsAwrpAzCCAeUwggFOoAMCAQICAQMwDQYJKoZIhvcNAQEL\ +BQAwITEfMB0GA1UEAxMWUGF5bWVudFJlcXVlc3QgVGVzdCBDQTAeFw0xMzAyMjMy\ +MTI2NDNaFw0xMzAyMjQyMTI2NDNaMD4xHDAaBgNVBAMME2V4cGlyZWRtZXJjaGFu\ +dC5vcmcxHjAcBgNVBAoMFUV4cGlyZWQgVGVzdCBNZXJjaGFudDCBnzANBgkqhkiG\ +9w0BAQEFAAOBjQAwgYkCgYEAx5DMvFtbuh7FpUqnVk5jCn+eICAsTX8W2Pq3DLQH\ +7a+toQmMdndkIkkXdaQlM5qNdiIkl91sUh8Gk509zBQ+o0cevszOl3RmqyNs6WJX\ +tdQDc7uIALYTde7kUB1djuUMVbczWjHSniS109i/bXxHci/vvWVKqEJnQGBvYsd9\ +9IUCAwEAAaMQMA4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQAaU137\ +j53rvSjlmYZpZ4RWTP7EdD6fl5ZxBeXHytN6DQL33H0eD7OFHt+ofc7E6D7keubl\ +UfCu+jOvt/MvvPUmtCI9yXZ0dNC4sjyETv+wQpxO0UNZwOM4uegdCzlo6Bi3pD4/\ +KKLdMkWuUfuPBmoammny74lZaOVr5deKXztTuCI0Eh8IsNsGEhl2qRSVYIoDyPul\ +6RMbTSzv4tVhVfGZqIisGLLhxYsFKgtVbml0VGVzdFR3byqAAXHuo4nZEPniLpkd\ +y30TkwBxVgprWJ18a9z/7Py35Qss/JMbOXbnBhJtmJCdIowHRI0aa+zqt3KKKAXi\ +mm+V4seMgxTcxMS+eDDkiTcB/RtWWSyRcS2ANjFeY0T4SLMwiCL9qWPi03hr8j96\ +tejrSPOBNSJ3Mi/q5u2Yl4gJZY2b\ +"; + +// +// 10-long chain, all intermediates valid +// +const char* paymentrequest3_BASE64 = +"\ +Egt4NTA5K3NoYTI1Nhq8JAr/AzCCAfswggFkoAMCAQICAQEwDQYJKoZIhvcNAQEL\ +BQAwPzEUMBIGA1UEAwwLdGVzdGNhOC5vcmcxJzAlBgNVBAoMHlBheW1lbnQgUmVx\ +dWVzdCBJbnRlcm1lZGlhdGUgODAeFw0xMzAyMjMyMjQyMzFaFw0yMzAyMjEyMjQy\ +MzFaMDYxGjAYBgNVBAMMEXRlc3RtZXJjaGFudDgub3JnMRgwFgYDVQQKDA9UZXN0\ +IE1lcmNoYW50IDgwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMMCHA3hiHbS\ +TKZ5K9jHRwE8NxkGp3IOx56PDB2diNkldG8XweTcRq7bBm7pdiBt4IVggtfs+6hE\ +hDYIOecyoAnVzPFTdvQ7KQdQ/fD9YLe6lk+o0edOqutPMyrxLFjSluXxEQyk7fdt\ +URloMMYfp3p1/hFCboA1rAsQ2RW38hR5AgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8w\ +DQYJKoZIhvcNAQELBQADgYEAPsdFatnc2RJSpvZsw+nCiPVsllycw5ELglq9vfJz\ +nJJucRxgzmqI2iuas1ugwbXn0BEIRLK7vMF/qBzQR6M/nTxttah+KEu+okjps9vJ\ +cIyhfTyGPC5xkHaHZ7sG+UHOFhPw0/kXn0x+pbVgBZ5315axqcp1R+DTSj/whMAr\ +n0AKiAQwggIEMIIBbaADAgECAgECMA0GCSqGSIb3DQEBCwUAMD8xFDASBgNVBAMM\ +C3Rlc3RjYTcub3JnMScwJQYDVQQKDB5QYXltZW50IFJlcXVlc3QgSW50ZXJtZWRp\ +YXRlIDcwHhcNMTMwMjIzMjI0MjMxWhcNMjMwMjIxMjI0MjMxWjA/MRQwEgYDVQQD\ +DAt0ZXN0Y2E4Lm9yZzEnMCUGA1UECgweUGF5bWVudCBSZXF1ZXN0IEludGVybWVk\ +aWF0ZSA4MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDexUFfxb1sThvabp7u\ +dZz59ciThGmmAW0nP4tjrgEACgvWIInr2dZpTHbiQNF34ycsk0le1JD93D7Qb8rd\ +25OrpaO8XS2Li2zjR9cleixXjSLwV/zv8zJ8yPl/27XL++PDTKBXVpJ8/Syp+9Ty\ +plV1BqDhqtIHb/QSHEkTQXjeYQIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqG\ +SIb3DQEBCwUAA4GBACMooQVbkbIZ2DaPwHDc4ULwguG3VI2Kzj50UdExmHtzm2S4\ +MQei+n+HEPjtJAx5OY520+10nfuP+12H2DRLQmWmdvDpeQ/Cv0yavlw4ZRejRFo7\ +KS83C0wo5rd+qTvvOmAN4UTArWkzYcEUulPdiXnRamb0WQHTeVdIbHVkMormCogE\ +MIICBDCCAW2gAwIBAgIBAjANBgkqhkiG9w0BAQsFADA/MRQwEgYDVQQDDAt0ZXN0\ +Y2E2Lm9yZzEnMCUGA1UECgweUGF5bWVudCBSZXF1ZXN0IEludGVybWVkaWF0ZSA2\ +MB4XDTEzMDIyMzIyNDIzMVoXDTIzMDIyMTIyNDIzMVowPzEUMBIGA1UEAwwLdGVz\ +dGNhNy5vcmcxJzAlBgNVBAoMHlBheW1lbnQgUmVxdWVzdCBJbnRlcm1lZGlhdGUg\ +NzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtjBRazrkebXAhXsbjimrMIRm\ +W/f9SwAHwXfc042keNtl0t2z6XE6UPcR2v/KrssXuCZgodeYxz6IM6lWosCM1xot\ +C3ChKKFBfVO30reuKBRUxXfKAFqxaG0YOAEzdZkkY9AGhqWloeSmgxpIfhInU0EF\ +JjCwrJ6IkijBatGoAAECAwEAAaMQMA4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0B\ +AQsFAAOBgQDBRTi1MolmOA0niHYX0A2lN5QWHkCfX0A7GwyoMA3dvM45m/NYd4WB\ +X+HwfnfYcI6X9jOgNo5OWmc4GGsld0HlxwMYEKISBS9PbSHPBrb3TBOlw5ztQpXZ\ +91+bOhLux52Fr03sK7v9qExmBM12M8UR2ltpzAMiUgLLMHyPfiWkvQqIBDCCAgQw\ +ggFtoAMCAQICAQIwDQYJKoZIhvcNAQELBQAwPzEUMBIGA1UEAwwLdGVzdGNhNS5v\ +cmcxJzAlBgNVBAoMHlBheW1lbnQgUmVxdWVzdCBJbnRlcm1lZGlhdGUgNTAeFw0x\ +MzAyMjMyMjQyMzBaFw0yMzAyMjEyMjQyMzBaMD8xFDASBgNVBAMMC3Rlc3RjYTYu\ +b3JnMScwJQYDVQQKDB5QYXltZW50IFJlcXVlc3QgSW50ZXJtZWRpYXRlIDYwgZ8w\ +DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANJSH3xivX1t9olIdHsznI1aE9SD7t9i\ +SZJsIB0otoETHZRVv9M9LvyzBNK98ZV+kTOlST7PJgC0d9BQM9sgYApSRq5oqKDM\ +9FXbOm/yaReAbU3mkFNFw5roTlJ5ThEy0yOGT/DS0YBRaGIvRPRj2DiqDVdCZZ+w\ +4jo1IYHkZt4FAgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQAD\ +gYEATm6+J1OmbrothO60xALKonWMBKr6hudb4amkFBqKbA9wMeM3jl+I/yKfz/Uf\ +xWuJ071IhiNv6Gxx5YwNvhUe1xMhUqHv0gpyK1Z47bD+kYS2se5sWNPNo3Y9qZDG\ +IXiGQxwHmrzaFk79Uy1xsmvsEz42w6hr25Yaw7HkIgrFveoKiAQwggIEMIIBbaAD\ +AgECAgECMA0GCSqGSIb3DQEBCwUAMD8xFDASBgNVBAMMC3Rlc3RjYTQub3JnMScw\ +JQYDVQQKDB5QYXltZW50IFJlcXVlc3QgSW50ZXJtZWRpYXRlIDQwHhcNMTMwMjIz\ +MjI0MjMwWhcNMjMwMjIxMjI0MjMwWjA/MRQwEgYDVQQDDAt0ZXN0Y2E1Lm9yZzEn\ +MCUGA1UECgweUGF5bWVudCBSZXF1ZXN0IEludGVybWVkaWF0ZSA1MIGfMA0GCSqG\ +SIb3DQEBAQUAA4GNADCBiQKBgQC7vVUFpxHzz2Tr/xij3k58s8d/BPA0R6D5RXTV\ +vmhAzc1Zuin4zUKRFs/aCj/0yED8Wu/COfNGF4tVlRNMdl9EcFsxa8XGEL4eAZa+\ +H/rOHH+7/1EINrrVWhZlUecyhilN8jmCZmqEM3ecuD0NAViqyMrgmaiFmsLoQZpE\ +GepDUQIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4GBAEdJ\ +Ss8jWiooja3WZzHXeF95QkBJNjIlpDLGcpl4opOYLSuEl9Uxp//LaQQiXuzpj4/I\ +pkWGQmMy5HOyH1lqDyiMgXpcG8PE0jEQAoEUGZ0QEqB1mZ6BCrYvmUuf/5aSVd8Y\ +6lKMR3WzFDYU9Zy0nzuHB/3nvp6MeDRQeRMtYvz4CogEMIICBDCCAW2gAwIBAgIB\ +AjANBgkqhkiG9w0BAQsFADA/MRQwEgYDVQQDDAt0ZXN0Y2EzLm9yZzEnMCUGA1UE\ +CgweUGF5bWVudCBSZXF1ZXN0IEludGVybWVkaWF0ZSAzMB4XDTEzMDIyMzIyNDIy\ +OVoXDTIzMDIyMTIyNDIyOVowPzEUMBIGA1UEAwwLdGVzdGNhNC5vcmcxJzAlBgNV\ +BAoMHlBheW1lbnQgUmVxdWVzdCBJbnRlcm1lZGlhdGUgNDCBnzANBgkqhkiG9w0B\ +AQEFAAOBjQAwgYkCgYEAxYYo3w2UXiYg6O8b4QgwN/vgreTkiW122Ep/z2TiDrhV\ +MhfOOiKdwYESPflfnXnVaQQzCGexYTQqsvqvzHSyna5hL0zPTRJxSKmTVrXRsWtp\ +dCRhjxCGipS3tlQBDi7vb+7SNRIBK4dBjjGzALNk7gMCpy+yM8f6I043jTlmGb0C\ +AwEAAaMQMA4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQDU+IQxt3Oh\ +KqaUYWC23+cB2gekvWqwMBnrCNrX/Dp+kjoJKUoR2Fs3qw53raHES4SIhpGT9l9l\ +rppNQgFe/JMHeYqOZMZO+6kuU0olJanBJ14tPIc7zlMTQ9OfmZ6v07IpyFbsQDtR\ +hpe80DpuvSFPfJ4fh0WrQf6kn3KDVpGDnAqIBDCCAgQwggFtoAMCAQICAQIwDQYJ\ +KoZIhvcNAQELBQAwPzEUMBIGA1UEAwwLdGVzdGNhMi5vcmcxJzAlBgNVBAoMHlBh\ +eW1lbnQgUmVxdWVzdCBJbnRlcm1lZGlhdGUgMjAeFw0xMzAyMjMyMjQyMjlaFw0y\ +MzAyMjEyMjQyMjlaMD8xFDASBgNVBAMMC3Rlc3RjYTMub3JnMScwJQYDVQQKDB5Q\ +YXltZW50IFJlcXVlc3QgSW50ZXJtZWRpYXRlIDMwgZ8wDQYJKoZIhvcNAQEBBQAD\ +gY0AMIGJAoGBANzgVP99Qg98e6NsKEz1v5KqRB7NTBRRsYnBvb/TSWipvMQaCYuE\ +yk1xG57x++QuASKeR3QHRQJOoAhQaj9JLUhSSv9GQ5PrFLLsOFv7L1tpzXHh2dOB\ +IW92X2yFRW2s39q+Q21yvN+N8uoKdqXhzRA+dDoXh3cavaVeHX1G+IrlAgMBAAGj\ +EDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADgYEASTwg84cX+1UhOG9s\ +ejFV3m34QuI1hPZ+qhqVJlRYUtego8Wng1BburDSwqVAv4ch2wi3c2s4e8J7AXyL\ +tzSbSQG4RN0oZi0mR8EtTTN+Mix/hBIk79dMZg85+I29uFA6Zj2d9oAhQv2qkHhc\ +6tcaheNvkQRlCyH68k3iF1Fqf+4KiAQwggIEMIIBbaADAgECAgECMA0GCSqGSIb3\ +DQEBCwUAMD8xFDASBgNVBAMMC3Rlc3RjYTEub3JnMScwJQYDVQQKDB5QYXltZW50\ +IFJlcXVlc3QgSW50ZXJtZWRpYXRlIDEwHhcNMTMwMjIzMjI0MjI5WhcNMjMwMjIx\ +MjI0MjI5WjA/MRQwEgYDVQQDDAt0ZXN0Y2EyLm9yZzEnMCUGA1UECgweUGF5bWVu\ +dCBSZXF1ZXN0IEludGVybWVkaWF0ZSAyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB\ +iQKBgQDaV8zhfyQuSf/f+fauMfgs3g/RnWy9yxxUkvQneQQPH3uZzCyk3A6q72ip\ +TtwNqiibG9455L9A7SaUjGtnpUz0NKT/VWUdqbfCl1PqXjEZbDobbAQ5hxLGOTyL\ +RQhLIcgeq2/BnmeCqHsC4md04nUp+nBo1HwKyygvK+9sMbCp/wIDAQABoxAwDjAM\ +BgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4GBACvYyE+PPmWFkbjyRu9LAt8D\ +crtyYYLRClKSg6tVvutwukLG2l//kDOohYkJtgTqr6LnCIIIwYdXN+4wxugmw4cn\ +PIZmP6kovxjhhVM95okilor1zniTAo3RN7JDIfTGNgxLdGu1btt7DOFL4zTbeSJM\ +b8M1JpPftehH+x/VLyuUCuoDMIIB5jCCAU+gAwIBAgIBBTANBgkqhkiG9w0BAQsF\ +ADAhMR8wHQYDVQQDExZQYXltZW50UmVxdWVzdCBUZXN0IENBMB4XDTEzMDIyMzIy\ +NDIyOFoXDTIzMDIyMTIyNDIyOFowPzEUMBIGA1UEAwwLdGVzdGNhMS5vcmcxJzAl\ +BgNVBAoMHlBheW1lbnQgUmVxdWVzdCBJbnRlcm1lZGlhdGUgMTCBnzANBgkqhkiG\ +9w0BAQEFAAOBjQAwgYkCgYEAo5Vy9H3nA/OOkF5Ap89yfVNSiTay/LYCaB0eALpc\ +U690U75O9Q3w2M+2AN8wpbbHsJHZMIjEeBRoQfjlYXW1ucQTxWKyT+liu0D25mGX\ +X27CBXBd4iXTxVII/iX+u3lcjORjoHOBy7QgeIDIIS9y0vYu8eArpjh7m4thrVgI\ +RtMCAwEAAaMQMA4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQB9LKcV\ +JK9sjASNzpQlpUp7nCiw5FSjVY+XMRIKK/kavzlKjZ+InsmmyRVGjDoZi9GrqG9P\ +VHgLBxi2VtVjmokZoNPqao3OfhqORAubC+JR/JLepM7aDaxDdTHVhSUk4lgNAvi2\ +6dGY7nZMsnHlPQ2tPp/HvRRiMq1oDjlylc8VTCI2Eh8IsNsGEhl2qRSVYIoDyPul\ +6RMbTSzv4tVhVfGZqIisGLLhxYsFKg1Vbml0VGVzdFRocmVlKoABn2HTsUQtMNI4\ +yNvkfkFNka3pRvTUTydJrvyfmEeLzImfM1BWddZjnywku9RToNFZZNgow5QnljmF\ +chhR/aHOuEMTxmc12K4rNlgYtHCsxLP9zd+6u0cva3TucZ6EzS8PKEib/+r12/52\ +664NuWA9WtsK7QCFrK2K95PnVCRmWl0=\ +"; + +// +// Long chain, with an invalid (expired) cert in the middle +// +const char* paymentrequest4_BASE64 = +"\ +Egt4NTA5K3NoYTI1NhqeJAr/AzCCAfswggFkoAMCAQICAQEwDQYJKoZIhvcNAQEL\ +BQAwPzEUMBIGA1UEAwwLdGVzdGNhOC5vcmcxJzAlBgNVBAoMHlBheW1lbnQgUmVx\ +dWVzdCBJbnRlcm1lZGlhdGUgODAeFw0xMzAyMjMyMjQyMzFaFw0yMzAyMjEyMjQy\ +MzFaMDYxGjAYBgNVBAMMEXRlc3RtZXJjaGFudDgub3JnMRgwFgYDVQQKDA9UZXN0\ +IE1lcmNoYW50IDgwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMMCHA3hiHbS\ +TKZ5K9jHRwE8NxkGp3IOx56PDB2diNkldG8XweTcRq7bBm7pdiBt4IVggtfs+6hE\ +hDYIOecyoAnVzPFTdvQ7KQdQ/fD9YLe6lk+o0edOqutPMyrxLFjSluXxEQyk7fdt\ +URloMMYfp3p1/hFCboA1rAsQ2RW38hR5AgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8w\ +DQYJKoZIhvcNAQELBQADgYEAPsdFatnc2RJSpvZsw+nCiPVsllycw5ELglq9vfJz\ +nJJucRxgzmqI2iuas1ugwbXn0BEIRLK7vMF/qBzQR6M/nTxttah+KEu+okjps9vJ\ +cIyhfTyGPC5xkHaHZ7sG+UHOFhPw0/kXn0x+pbVgBZ5315axqcp1R+DTSj/whMAr\ +n0AKiAQwggIEMIIBbaADAgECAgECMA0GCSqGSIb3DQEBCwUAMD8xFDASBgNVBAMM\ +C3Rlc3RjYTcub3JnMScwJQYDVQQKDB5QYXltZW50IFJlcXVlc3QgSW50ZXJtZWRp\ +YXRlIDcwHhcNMTMwMjIzMjI0MjMxWhcNMjMwMjIxMjI0MjMxWjA/MRQwEgYDVQQD\ +DAt0ZXN0Y2E4Lm9yZzEnMCUGA1UECgweUGF5bWVudCBSZXF1ZXN0IEludGVybWVk\ +aWF0ZSA4MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDexUFfxb1sThvabp7u\ +dZz59ciThGmmAW0nP4tjrgEACgvWIInr2dZpTHbiQNF34ycsk0le1JD93D7Qb8rd\ +25OrpaO8XS2Li2zjR9cleixXjSLwV/zv8zJ8yPl/27XL++PDTKBXVpJ8/Syp+9Ty\ +plV1BqDhqtIHb/QSHEkTQXjeYQIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqG\ +SIb3DQEBCwUAA4GBACMooQVbkbIZ2DaPwHDc4ULwguG3VI2Kzj50UdExmHtzm2S4\ +MQei+n+HEPjtJAx5OY520+10nfuP+12H2DRLQmWmdvDpeQ/Cv0yavlw4ZRejRFo7\ +KS83C0wo5rd+qTvvOmAN4UTArWkzYcEUulPdiXnRamb0WQHTeVdIbHVkMormCogE\ +MIICBDCCAW2gAwIBAgIBAjANBgkqhkiG9w0BAQsFADA/MRQwEgYDVQQDDAt0ZXN0\ +Y2E2Lm9yZzEnMCUGA1UECgweUGF5bWVudCBSZXF1ZXN0IEludGVybWVkaWF0ZSA2\ +MB4XDTEzMDIyMzIyNDIzMVoXDTIzMDIyMTIyNDIzMVowPzEUMBIGA1UEAwwLdGVz\ +dGNhNy5vcmcxJzAlBgNVBAoMHlBheW1lbnQgUmVxdWVzdCBJbnRlcm1lZGlhdGUg\ +NzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtjBRazrkebXAhXsbjimrMIRm\ +W/f9SwAHwXfc042keNtl0t2z6XE6UPcR2v/KrssXuCZgodeYxz6IM6lWosCM1xot\ +C3ChKKFBfVO30reuKBRUxXfKAFqxaG0YOAEzdZkkY9AGhqWloeSmgxpIfhInU0EF\ +JjCwrJ6IkijBatGoAAECAwEAAaMQMA4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0B\ +AQsFAAOBgQDBRTi1MolmOA0niHYX0A2lN5QWHkCfX0A7GwyoMA3dvM45m/NYd4WB\ +X+HwfnfYcI6X9jOgNo5OWmc4GGsld0HlxwMYEKISBS9PbSHPBrb3TBOlw5ztQpXZ\ +91+bOhLux52Fr03sK7v9qExmBM12M8UR2ltpzAMiUgLLMHyPfiWkvQqIBDCCAgQw\ +ggFtoAMCAQICAQIwDQYJKoZIhvcNAQELBQAwPzEUMBIGA1UEAwwLdGVzdGNhNS5v\ +cmcxJzAlBgNVBAoMHlBheW1lbnQgUmVxdWVzdCBJbnRlcm1lZGlhdGUgNTAeFw0x\ +MzAyMjMyMjQyMzBaFw0yMzAyMjEyMjQyMzBaMD8xFDASBgNVBAMMC3Rlc3RjYTYu\ +b3JnMScwJQYDVQQKDB5QYXltZW50IFJlcXVlc3QgSW50ZXJtZWRpYXRlIDYwgZ8w\ +DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANJSH3xivX1t9olIdHsznI1aE9SD7t9i\ +SZJsIB0otoETHZRVv9M9LvyzBNK98ZV+kTOlST7PJgC0d9BQM9sgYApSRq5oqKDM\ +9FXbOm/yaReAbU3mkFNFw5roTlJ5ThEy0yOGT/DS0YBRaGIvRPRj2DiqDVdCZZ+w\ +4jo1IYHkZt4FAgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQAD\ +gYEATm6+J1OmbrothO60xALKonWMBKr6hudb4amkFBqKbA9wMeM3jl+I/yKfz/Uf\ +xWuJ071IhiNv6Gxx5YwNvhUe1xMhUqHv0gpyK1Z47bD+kYS2se5sWNPNo3Y9qZDG\ +IXiGQxwHmrzaFk79Uy1xsmvsEz42w6hr25Yaw7HkIgrFveoK6gMwggHmMIIBT6AD\ +AgECAgEGMA0GCSqGSIb3DQEBCwUAMCExHzAdBgNVBAMTFlBheW1lbnRSZXF1ZXN0\ +IFRlc3QgQ0EwHhcNMTMwMjIzMjI1OTUxWhcNMTMwMjI0MjI1OTUxWjA/MRQwEgYD\ +VQQDDAt0ZXN0Y2E1Lm9yZzEnMCUGA1UECgweUGF5bWVudCBSZXF1ZXN0IEludGVy\ +bWVkaWF0ZSA1MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7vVUFpxHzz2Tr\ +/xij3k58s8d/BPA0R6D5RXTVvmhAzc1Zuin4zUKRFs/aCj/0yED8Wu/COfNGF4tV\ +lRNMdl9EcFsxa8XGEL4eAZa+H/rOHH+7/1EINrrVWhZlUecyhilN8jmCZmqEM3ec\ +uD0NAViqyMrgmaiFmsLoQZpEGepDUQIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0G\ +CSqGSIb3DQEBCwUAA4GBAEmcUEnhua/oiXy1fwScLgMqt+jk9mHRpE6SVsIop23Q\ +CY2JfpG6RxhMMzzzhGklEGN6cxG0HCi6B3HJx6PYrFEfTB0rW4K6m0Tvx3WpS9mN\ +uoEuJHLy18ausI/sYAPDHCL+SfBVcqorpaIG2sSpZouRBjRHAyqFAYlwlW87uq5n\ +CogEMIICBDCCAW2gAwIBAgIBAjANBgkqhkiG9w0BAQsFADA/MRQwEgYDVQQDDAt0\ +ZXN0Y2EzLm9yZzEnMCUGA1UECgweUGF5bWVudCBSZXF1ZXN0IEludGVybWVkaWF0\ +ZSAzMB4XDTEzMDIyMzIyNDIyOVoXDTIzMDIyMTIyNDIyOVowPzEUMBIGA1UEAwwL\ +dGVzdGNhNC5vcmcxJzAlBgNVBAoMHlBheW1lbnQgUmVxdWVzdCBJbnRlcm1lZGlh\ +dGUgNDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAxYYo3w2UXiYg6O8b4Qgw\ +N/vgreTkiW122Ep/z2TiDrhVMhfOOiKdwYESPflfnXnVaQQzCGexYTQqsvqvzHSy\ +na5hL0zPTRJxSKmTVrXRsWtpdCRhjxCGipS3tlQBDi7vb+7SNRIBK4dBjjGzALNk\ +7gMCpy+yM8f6I043jTlmGb0CAwEAAaMQMA4wDAYDVR0TBAUwAwEB/zANBgkqhkiG\ +9w0BAQsFAAOBgQDU+IQxt3OhKqaUYWC23+cB2gekvWqwMBnrCNrX/Dp+kjoJKUoR\ +2Fs3qw53raHES4SIhpGT9l9lrppNQgFe/JMHeYqOZMZO+6kuU0olJanBJ14tPIc7\ +zlMTQ9OfmZ6v07IpyFbsQDtRhpe80DpuvSFPfJ4fh0WrQf6kn3KDVpGDnAqIBDCC\ +AgQwggFtoAMCAQICAQIwDQYJKoZIhvcNAQELBQAwPzEUMBIGA1UEAwwLdGVzdGNh\ +Mi5vcmcxJzAlBgNVBAoMHlBheW1lbnQgUmVxdWVzdCBJbnRlcm1lZGlhdGUgMjAe\ +Fw0xMzAyMjMyMjQyMjlaFw0yMzAyMjEyMjQyMjlaMD8xFDASBgNVBAMMC3Rlc3Rj\ +YTMub3JnMScwJQYDVQQKDB5QYXltZW50IFJlcXVlc3QgSW50ZXJtZWRpYXRlIDMw\ +gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANzgVP99Qg98e6NsKEz1v5KqRB7N\ +TBRRsYnBvb/TSWipvMQaCYuEyk1xG57x++QuASKeR3QHRQJOoAhQaj9JLUhSSv9G\ +Q5PrFLLsOFv7L1tpzXHh2dOBIW92X2yFRW2s39q+Q21yvN+N8uoKdqXhzRA+dDoX\ +h3cavaVeHX1G+IrlAgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEL\ +BQADgYEASTwg84cX+1UhOG9sejFV3m34QuI1hPZ+qhqVJlRYUtego8Wng1BburDS\ +wqVAv4ch2wi3c2s4e8J7AXyLtzSbSQG4RN0oZi0mR8EtTTN+Mix/hBIk79dMZg85\ ++I29uFA6Zj2d9oAhQv2qkHhc6tcaheNvkQRlCyH68k3iF1Fqf+4KiAQwggIEMIIB\ +baADAgECAgECMA0GCSqGSIb3DQEBCwUAMD8xFDASBgNVBAMMC3Rlc3RjYTEub3Jn\ +MScwJQYDVQQKDB5QYXltZW50IFJlcXVlc3QgSW50ZXJtZWRpYXRlIDEwHhcNMTMw\ +MjIzMjI0MjI5WhcNMjMwMjIxMjI0MjI5WjA/MRQwEgYDVQQDDAt0ZXN0Y2EyLm9y\ +ZzEnMCUGA1UECgweUGF5bWVudCBSZXF1ZXN0IEludGVybWVkaWF0ZSAyMIGfMA0G\ +CSqGSIb3DQEBAQUAA4GNADCBiQKBgQDaV8zhfyQuSf/f+fauMfgs3g/RnWy9yxxU\ +kvQneQQPH3uZzCyk3A6q72ipTtwNqiibG9455L9A7SaUjGtnpUz0NKT/VWUdqbfC\ +l1PqXjEZbDobbAQ5hxLGOTyLRQhLIcgeq2/BnmeCqHsC4md04nUp+nBo1HwKyygv\ +K+9sMbCp/wIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4GB\ +ACvYyE+PPmWFkbjyRu9LAt8DcrtyYYLRClKSg6tVvutwukLG2l//kDOohYkJtgTq\ +r6LnCIIIwYdXN+4wxugmw4cnPIZmP6kovxjhhVM95okilor1zniTAo3RN7JDIfTG\ +NgxLdGu1btt7DOFL4zTbeSJMb8M1JpPftehH+x/VLyuUCuoDMIIB5jCCAU+gAwIB\ +AgIBBTANBgkqhkiG9w0BAQsFADAhMR8wHQYDVQQDExZQYXltZW50UmVxdWVzdCBU\ +ZXN0IENBMB4XDTEzMDIyMzIyNDIyOFoXDTIzMDIyMTIyNDIyOFowPzEUMBIGA1UE\ +AwwLdGVzdGNhMS5vcmcxJzAlBgNVBAoMHlBheW1lbnQgUmVxdWVzdCBJbnRlcm1l\ +ZGlhdGUgMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAo5Vy9H3nA/OOkF5A\ +p89yfVNSiTay/LYCaB0eALpcU690U75O9Q3w2M+2AN8wpbbHsJHZMIjEeBRoQfjl\ +YXW1ucQTxWKyT+liu0D25mGXX27CBXBd4iXTxVII/iX+u3lcjORjoHOBy7QgeIDI\ +IS9y0vYu8eArpjh7m4thrVgIRtMCAwEAAaMQMA4wDAYDVR0TBAUwAwEB/zANBgkq\ +hkiG9w0BAQsFAAOBgQB9LKcVJK9sjASNzpQlpUp7nCiw5FSjVY+XMRIKK/kavzlK\ +jZ+InsmmyRVGjDoZi9GrqG9PVHgLBxi2VtVjmokZoNPqao3OfhqORAubC+JR/JLe\ +pM7aDaxDdTHVhSUk4lgNAvi26dGY7nZMsnHlPQ2tPp/HvRRiMq1oDjlylc8VTCI1\ +Eh8IsNsGEhl2qRSVYIoDyPul6RMbTSzv4tVhVfGZqIisGLLhxYsFKgxVbml0VGVz\ +dEZvdXIqgAEBE1PP93Tkpif35F+dYmXn9kLA/1djcPjCs2o2rwRMM4Uk356O5dgu\ +HXQjsfdR58qZQS9CS5DAtRUf0R8+43/wijO/hb49VNaNXmY+/cPHMkahP2aV3tZi\ +FAyZblLik9A7ZvF+UsjeFQiHB5wzWQvbqk5wQ4yabHIXoYv/E0q+eQ==\ +"; + +const char* paymentrequest5_BASE64 = +"\ +Egt4NTA5K3NoYTI1NhrxAwruAzCCAeowggFToAMCAQICAQEwDQYJKoZIhvcNAQEL\ +BQAwITEfMB0GA1UEAxMWUGF5bWVudFJlcXVlc3QgVGVzdCBDQTAeFw0xMzA0MTkx\ +NzIwMDZaFw0yMzA0MTcxNzIwMDZaMEMxGTAXBgNVBAMMEHRlc3RtZXJjaGFudC5v\ +cmcxJjAkBgNVBAoMHVBheW1lbnQgUmVxdWVzdCBUZXN0IE1lcmNoYW50MIGfMA0G\ +CSqGSIb3DQEBAQUAA4GNADCBiQKBgQDhV6Yn47aEEmbl50YLvXoqGEJA51I/40wr\ +Z6VQGdXYaRqYktagrWDlgYY9h0JQ1bQhm8HgW7ju0R4NaDTXUqxg4HjprF0z3Mfm\ +/6mmebkLOOptfkVD7ceAteNI7cyuqWGIAZA7D9mV97mXoCAtTlBUycvkmoiClCCS\ +h0EpF/UTaQIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4GB\ +AGIRwW7I0QvLga+RnJoJSZNZQbtu4rQW3xmoz8WfZMBYXX3QBYg5ftycbdK+/IbP\ +qozfjGW2AS6DNArvpveSPDTK9+GJBNo1paiNtVqwXkC3Ddscv5AIms1eZGiIOQNC\ +mUvdLkpoXo48WAer3EGsZ3B15GyNEELc0q9W5yUebba1IjUSHwiw2wYSGXapFJVg\ +igPI+6XpExtNLO/i1WFV8ZmoiKwYuPvFiwUqDFVuaXRUZXN0Rml2ZSqAAXdsMgdG\ +ssymvca1S/1KeM3n8Ydi2fi1JUzAAr59xPvNJRUeqCLP9upHn5z7br3P12Oz9A20\ +5/4wL4ClPRPVnOHgij0bEg+y0tGESqmF1rfOfXDszlo2U92wCxS07kq79YAZJ1Zo\ +XYh860/Q4wvc7lfiTe+dXBzPKAKhMy91yETY\ +"; diff --git a/src/qt/test/paymentservertests.cpp b/src/qt/test/paymentservertests.cpp new file mode 100644 index 000000000..2e26ab0c9 --- /dev/null +++ b/src/qt/test/paymentservertests.cpp @@ -0,0 +1,109 @@ +#include +#include +#include +#include + +#include +#include + +#include "optionsmodel.h" +#include "paymentservertests.h" +#include "paymentrequestdata.h" +#include "util.h" + + + +X509 *parse_b64der_cert(const char* cert_data) +{ + std::vector data = DecodeBase64(cert_data); + assert(data.size() > 0); + const unsigned char* dptr = &data[0]; + X509 *cert = d2i_X509(NULL, &dptr, data.size()); + assert(cert); + return cert; +} + + +// +// Test payment request handling +// + +static SendCoinsRecipient handleRequest(PaymentServer* server, std::vector& data) +{ + RecipientCatcher sigCatcher; + QObject::connect(server, SIGNAL(receivedPaymentRequest(SendCoinsRecipient)), + &sigCatcher, SLOT(getRecipient(SendCoinsRecipient))); + + // Write data to a temp file: + QTemporaryFile f; + f.open(); + f.write((const char*)&data[0], data.size()); + f.close(); + + // Create a FileOpenEvent and send it directly to the server's event filter: + QFileOpenEvent event(f.fileName()); + server->eventFilter(NULL, &event); + + QObject::disconnect(server, SIGNAL(receivedPaymentRequest(SendCoinsRecipient)), + &sigCatcher, SLOT(getRecipient(SendCoinsRecipient))); + + // Return results from sigCatcher + return sigCatcher.recipient; +} + +void PaymentServerTests::paymentServerTests() +{ + OptionsModel optionsModel; + PaymentServer* server = new PaymentServer(NULL, false); + X509_STORE* caStore = X509_STORE_new(); + X509_STORE_add_cert(caStore, parse_b64der_cert(caCert_BASE64)); + PaymentServer::LoadRootCAs(caStore); + server->initNetManager(optionsModel); + server->uiReady(); + + // Now feed PaymentRequests to server, and observe signals it produces: + std::vector data = DecodeBase64(paymentrequest1_BASE64); + SendCoinsRecipient r = handleRequest(server, data); + QString merchant; + r.paymentRequest.getMerchant(caStore, merchant); + QCOMPARE(merchant, QString("testmerchant.org")); + + // Version of the above, with an expired certificate: + data = DecodeBase64(paymentrequest2_BASE64); + r = handleRequest(server, data); + r.paymentRequest.getMerchant(caStore, merchant); + QCOMPARE(merchant, QString("")); + + // Long certificate chain: + data = DecodeBase64(paymentrequest3_BASE64); + r = handleRequest(server, data); + r.paymentRequest.getMerchant(caStore, merchant); + QCOMPARE(merchant, QString("testmerchant8.org")); + + // Long certificate chain, with an expired certificate in the middle: + data = DecodeBase64(paymentrequest4_BASE64); + r = handleRequest(server, data); + r.paymentRequest.getMerchant(caStore, merchant); + QCOMPARE(merchant, QString("")); + + // Validly signed, but by a CA not in our root CA list: + data = DecodeBase64(paymentrequest5_BASE64); + r = handleRequest(server, data); + r.paymentRequest.getMerchant(caStore, merchant); + QCOMPARE(merchant, QString("")); + + // Try again with no root CA's, verifiedMerchant should be empty: + caStore = X509_STORE_new(); + PaymentServer::LoadRootCAs(caStore); + data = DecodeBase64(paymentrequest1_BASE64); + r = handleRequest(server, data); + r.paymentRequest.getMerchant(caStore, merchant); + QCOMPARE(merchant, QString("")); + + delete server; +} + +void RecipientCatcher::getRecipient(SendCoinsRecipient r) +{ + recipient = r; +} diff --git a/src/qt/test/paymentservertests.h b/src/qt/test/paymentservertests.h new file mode 100644 index 000000000..5aa24ebaf --- /dev/null +++ b/src/qt/test/paymentservertests.h @@ -0,0 +1,29 @@ +#ifndef PAYMENTSERVERTESTS_H +#define PAYMENTSERVERTESTS_H + +#include +#include + +#include "../paymentserver.h" + +class PaymentServerTests : public QObject +{ + Q_OBJECT + +private slots: + void paymentServerTests(); +}; + +// Dummy class to receive paymentserver signals. +// If SendCoinsRecipient was a proper QObject, then we could use +// QSignalSpy... but it's not. +class RecipientCatcher : public QObject +{ + Q_OBJECT +public slots: + void getRecipient(SendCoinsRecipient r); +public: + SendCoinsRecipient recipient; +}; + +#endif // PAYMENTSERVERTESTS_H diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp index af2d358fc..dae4e6041 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -1,16 +1,21 @@ -#include -#include - -#include "uritests.h" - -// This is all you need to run all the tests -int main(int argc, char *argv[]) -{ - bool fInvalid = false; - - URITests test1; - if (QTest::qExec(&test1) != 0) - fInvalid = true; - - return fInvalid; -} +#include +#include + +#include "uritests.h" +#include "paymentservertests.h" + +// This is all you need to run all the tests +int main(int argc, char *argv[]) +{ + bool fInvalid = false; + + URITests test1; + if (QTest::qExec(&test1) != 0) + fInvalid = true; + + PaymentServerTests test2; + if (QTest::qExec(&test2) != 0) + fInvalid = true; + + return fInvalid; +} diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index e9f3eb478..25ff3623c 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -7,6 +7,7 @@ #include "db.h" #include "ui_interface.h" #include "base58.h" +#include "paymentserver.h" #include @@ -216,6 +217,21 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) strHTML += "" + tr("Transaction ID") + ": " + wtx.GetHash().ToString().c_str() + "
"; + // + // PaymentRequest info: + // + foreach (const PAIRTYPE(string, string)& r, wtx.vOrderForm) + { + if (r.first == "PaymentRequest") + { + PaymentRequestPlus req; + req.parse(QByteArray::fromRawData(r.second.c_str(), r.second.size())); + QString merchant; + if (req.getMerchant(PaymentServer::getCertStore(), merchant)) + strHTML += "" + tr("Merchant") + ": " + GUIUtil::HtmlEscape(merchant) + "
"; + } + } + if (wtx.IsCoinBase()) strHTML += "
" + tr("Generated coins must mature 120 blocks before they can be spent. When you generated this block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, its state will change to \"not accepted\" and it won't be spendable. This may occasionally happen if another node generates a block within a few seconds of yours.") + "
"; diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp index 50c03ac62..ed2723a0f 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -52,9 +52,9 @@ void WalletFrame::removeAllWallets() walletStack->removeAllWallets(); } -bool WalletFrame::handleURI(const QString &uri) +bool WalletFrame::handlePaymentRequest(const SendCoinsRecipient &recipient) { - return walletStack->handleURI(uri); + return walletStack->handlePaymentRequest(recipient); } void WalletFrame::showOutOfSyncWarning(bool fShow) diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h index d7092f987..d5aeb6d85 100644 --- a/src/qt/walletframe.h +++ b/src/qt/walletframe.h @@ -11,6 +11,7 @@ class BitcoinGUI; class ClientModel; +class SendCoinsRecipient; class WalletModel; class WalletStack; @@ -29,7 +30,7 @@ public: void removeAllWallets(); - bool handleURI(const QString &uri); + bool handlePaymentRequest(const SendCoinsRecipient& recipient); void showOutOfSyncWarning(bool fShow); @@ -71,4 +72,4 @@ public slots: void setEncryptionStatus(); }; -#endif // WALLETFRAME_H \ No newline at end of file +#endif // WALLETFRAME_H diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index d3c3ddcab..61357647b 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -127,31 +127,60 @@ bool WalletModel::validateAddress(const QString &address) WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList &recipients) { qint64 total = 0; - QSet setAddress; - QString hex; + std::vector > vecSend; + QByteArray transaction; if(recipients.empty()) { return OK; } + QSet setAddress; // Used to detect duplicates + int nAddresses = 0; + // Pre-check input data for validity foreach(const SendCoinsRecipient &rcp, recipients) { - if(!validateAddress(rcp.address)) - { - return InvalidAddress; + if (rcp.paymentRequest.IsInitialized()) + { // PaymentRequest... + int64 subtotal = 0; + const payments::PaymentDetails& details = rcp.paymentRequest.getDetails(); + for (int i = 0; i < details.outputs_size(); i++) + { + const payments::Output& out = details.outputs(i); + if (out.amount() <= 0) continue; + subtotal += out.amount(); + const unsigned char* scriptStr = (const unsigned char*)out.script().data(); + CScript scriptPubKey(scriptStr, scriptStr+out.script().size()); + vecSend.push_back(std::pair(scriptPubKey, out.amount())); + } + if (subtotal <= 0) + { + return InvalidAmount; + } + total += subtotal; } - setAddress.insert(rcp.address); + else + { // User-entered bitcoin address / amount: + if(!validateAddress(rcp.address)) + { + return InvalidAddress; + } + if(rcp.amount <= 0) + { + return InvalidAmount; + } + setAddress.insert(rcp.address); + ++nAddresses; - if(rcp.amount <= 0) - { - return InvalidAmount; + CScript scriptPubKey; + scriptPubKey.SetDestination(CBitcoinAddress(rcp.address.toStdString()).Get()); + vecSend.push_back(std::pair(scriptPubKey, rcp.amount)); + + total += rcp.amount; } - total += rcp.amount; } - - if(recipients.size() > setAddress.size()) + if(setAddress.size() != nAddresses) { return DuplicateAddress; } @@ -169,19 +198,10 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(const QListcs_wallet); - // Sendmany - std::vector > vecSend; - foreach(const SendCoinsRecipient &rcp, recipients) - { - CScript scriptPubKey; - scriptPubKey.SetDestination(CBitcoinAddress(rcp.address.toStdString()).Get()); - vecSend.push_back(make_pair(scriptPubKey, rcp.amount)); - } - - CWalletTx wtx; CReserveKey keyChange(wallet); int64 nFeeRequired = 0; std::string strFailReason; + CWalletTx wtx; bool fCreated = wallet->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, strFailReason); if(!fCreated) @@ -194,6 +214,18 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList::iterator mi = wallet->mapAddressBook.find(dest); // Check if we have a new address or an updated label - if (mi == wallet->mapAddressBook.end() || mi->second.name != strLabel) + if (mi == wallet->mapAddressBook.end()) + { + wallet->SetAddressBook(dest, strLabel, "send"); + } + else if (mi->second.name != strLabel) { - wallet->SetAddressBookName(dest, strLabel); + wallet->SetAddressBook(dest, strLabel, ""); // "" means don't change purpose } } + emit coinsSent(wallet, rcp, transaction); } - return SendCoinsReturn(OK, 0, hex); + return SendCoinsReturn(OK, 0); } OptionsModel *WalletModel::getOptionsModel() diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index f14b09eff..8cba10f5d 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -4,6 +4,7 @@ #include #include "allocators.h" /* for SecureString */ +#include "paymentrequestplus.h" class OptionsModel; class AddressTableModel; @@ -17,9 +18,15 @@ QT_END_NAMESPACE class SendCoinsRecipient { public: + SendCoinsRecipient() : amount(0) { } + QString address; QString label; qint64 amount; + + // If from a payment request, paymentRequest.IsInitialized() will be true + PaymentRequestPlus paymentRequest; + QString authenticatedMerchant; // Empty if no authentication or invalid signature/cert/etc. }; /** Interface to Bitcoin wallet from Qt view code. */ @@ -68,12 +75,10 @@ public: struct SendCoinsReturn { SendCoinsReturn(StatusCode status, - qint64 fee=0, - QString hex=QString()): - status(status), fee(fee), hex(hex) {} + qint64 fee=0): + status(status), fee(fee) {} StatusCode status; qint64 fee; // is used in case status is "AmountWithFeeExceedsBalance" - QString hex; // is filled with the transaction hash if status is "OK" }; // Send coins to a list of recipients @@ -151,6 +156,9 @@ signals: // Asynchronous message notification void message(const QString &title, const QString &message, unsigned int style); + // Coins sent: from wallet, to recipient, in (serialized) transaction: + void coinsSent(CWallet* wallet, SendCoinsRecipient recipient, QByteArray transaction); + public slots: /* Wallet status might have changed */ void updateStatus(); diff --git a/src/qt/walletstack.cpp b/src/qt/walletstack.cpp index 9e3060e86..6cc73358a 100644 --- a/src/qt/walletstack.cpp +++ b/src/qt/walletstack.cpp @@ -54,12 +54,12 @@ void WalletStack::removeAllWallets() mapWalletViews.clear(); } -bool WalletStack::handleURI(const QString &uri) +bool WalletStack::handlePaymentRequest(const SendCoinsRecipient &recipient) { WalletView *walletView = (WalletView*)currentWidget(); if (!walletView) return false; - return walletView->handleURI(uri); + return walletView->handlePaymentRequest(recipient); } void WalletStack::showOutOfSyncWarning(bool fShow) diff --git a/src/qt/walletstack.h b/src/qt/walletstack.h index 506d595c0..5c84cb6e1 100644 --- a/src/qt/walletstack.h +++ b/src/qt/walletstack.h @@ -20,6 +20,7 @@ class TransactionView; class OverviewPage; class AddressBookPage; class SendCoinsDialog; +class SendCoinsRecipient; class SignVerifyMessageDialog; class Notificator; class RPCConsole; @@ -54,7 +55,7 @@ public: void removeAllWallets(); - bool handleURI(const QString &uri); + bool handlePaymentRequest(const SendCoinsRecipient &recipient); void showOutOfSyncWarning(bool fShow); diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index 277c05669..0a6a4028a 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -201,10 +201,10 @@ void WalletView::gotoVerifyMessageTab(QString addr) signVerifyMessageDialog->setAddress_VM(addr); } -bool WalletView::handleURI(const QString& strURI) +bool WalletView::handlePaymentRequest(const SendCoinsRecipient& recipient) { // URI has to be valid - if (sendCoinsPage->handleURI(strURI)) + if (sendCoinsPage->handlePaymentRequest(recipient)) { gotoSendCoinsPage(); return true; diff --git a/src/qt/walletview.h b/src/qt/walletview.h index 6bcd70baf..97e8f93df 100644 --- a/src/qt/walletview.h +++ b/src/qt/walletview.h @@ -16,6 +16,7 @@ class TransactionView; class OverviewPage; class AddressBookPage; class SendCoinsDialog; +class SendCoinsRecipient; class SignVerifyMessageDialog; class RPCConsole; @@ -49,7 +50,7 @@ public: */ void setWalletModel(WalletModel *walletModel); - bool handleURI(const QString &uri); + bool handlePaymentRequest(const SendCoinsRecipient& recipient); void showOutOfSyncWarning(bool fShow); diff --git a/src/rpcdump.cpp b/src/rpcdump.cpp index fde9984b0..e166f76bf 100644 --- a/src/rpcdump.cpp +++ b/src/rpcdump.cpp @@ -94,7 +94,7 @@ Value importprivkey(const Array& params, bool fHelp) LOCK2(cs_main, pwalletMain->cs_wallet); pwalletMain->MarkDirty(); - pwalletMain->SetAddressBookName(vchAddress, strLabel); + pwalletMain->SetAddressBook(vchAddress, strLabel, "receive"); // Don't throw error in case a key is already there if (pwalletMain->HaveKey(vchAddress)) @@ -172,7 +172,7 @@ Value importwallet(const Array& params, bool fHelp) } pwalletMain->mapKeyMetadata[keyid].nCreateTime = nTime; if (fLabel) - pwalletMain->SetAddressBookName(keyid, strLabel); + pwalletMain->SetAddressBook(keyid, strLabel, "receive"); nTimeBegin = std::min(nTimeBegin, nTime); } file.close(); diff --git a/src/rpcwallet.cpp b/src/rpcwallet.cpp index 78f561a0a..d07d3408b 100644 --- a/src/rpcwallet.cpp +++ b/src/rpcwallet.cpp @@ -114,7 +114,7 @@ Value getnewaddress(const Array& params, bool fHelp) throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); CKeyID keyID = newKey.GetID(); - pwalletMain->SetAddressBookName(keyID, strAccount); + pwalletMain->SetAddressBook(keyID, strAccount, "receive"); return CBitcoinAddress(keyID).ToString(); } @@ -151,7 +151,7 @@ CBitcoinAddress GetAccountAddress(string strAccount, bool bForceNew=false) if (!pwalletMain->GetKeyFromPool(account.vchPubKey, false)) throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); - pwalletMain->SetAddressBookName(account.vchPubKey.GetID(), strAccount); + pwalletMain->SetAddressBook(account.vchPubKey.GetID(), strAccount, "receive"); walletdb.WriteAccount(strAccount, account); } @@ -201,7 +201,7 @@ Value setaccount(const Array& params, bool fHelp) GetAccountAddress(strOldAccount, true); } - pwalletMain->SetAddressBookName(address.Get(), strAccount); + pwalletMain->SetAddressBook(address.Get(), strAccount, "receive"); return Value::null; } @@ -768,7 +768,7 @@ Value addmultisigaddress(const Array& params, bool fHelp) CScriptID innerID = inner.GetID(); pwalletMain->AddCScript(inner); - pwalletMain->SetAddressBookName(innerID, strAccount); + pwalletMain->SetAddressBook(innerID, strAccount, "send"); return CBitcoinAddress(innerID).ToString(); } diff --git a/src/wallet.cpp b/src/wallet.cpp index c0f2d74ca..ddfd71efd 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -496,7 +496,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn) if (GetKeyFromPool(newDefaultKey, false)) { SetDefaultKey(newDefaultKey); - SetAddressBookName(vchDefaultKey.GetID(), ""); + SetAddressBook(vchDefaultKey.GetID(), "", "receive"); } } } @@ -1457,26 +1457,28 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet) } -bool CWallet::SetAddressBookName(const CTxDestination& address, const string& strName) +bool CWallet::SetAddressBook(const CTxDestination& address, const string& strName, const string& strPurpose) { std::map::iterator mi = mapAddressBook.find(address); mapAddressBook[address].name = strName; NotifyAddressBookChanged(this, address, strName, ::IsMine(*this, address), (mi == mapAddressBook.end()) ? CT_NEW : CT_UPDATED); if (!fFileBacked) return false; + if (!strPurpose.empty() && !CWalletDB(strWalletFile).WritePurpose(CBitcoinAddress(address).ToString(), strPurpose)) + return false; return CWalletDB(strWalletFile).WriteName(CBitcoinAddress(address).ToString(), strName); } -bool CWallet::DelAddressBookName(const CTxDestination& address) +bool CWallet::DelAddressBook(const CTxDestination& address) { mapAddressBook.erase(address); NotifyAddressBookChanged(this, address, "", ::IsMine(*this, address), CT_DELETED); if (!fFileBacked) return false; + CWalletDB(strWalletFile).ErasePurpose(CBitcoinAddress(address).ToString()); return CWalletDB(strWalletFile).EraseName(CBitcoinAddress(address).ToString()); } - void CWallet::PrintWallet(const CBlock& block) { { diff --git a/src/wallet.h b/src/wallet.h index 8b9ccaf5c..d47416d27 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -69,9 +69,11 @@ class CAddressBookData { public: std::string name; + std::string purpose; CAddressBookData() { + purpose = "unknown"; } }; @@ -294,9 +296,9 @@ public: DBErrors LoadWallet(bool& fFirstRunRet); - bool SetAddressBookName(const CTxDestination& address, const std::string& strName); + bool SetAddressBook(const CTxDestination& address, const std::string& strName, const std::string& purpose); - bool DelAddressBookName(const CTxDestination& address); + bool DelAddressBook(const CTxDestination& address); void UpdatedTransaction(const uint256 &hashTx); diff --git a/src/walletdb.cpp b/src/walletdb.cpp index 6cfd394bd..554d14002 100644 --- a/src/walletdb.cpp +++ b/src/walletdb.cpp @@ -32,6 +32,18 @@ bool CWalletDB::EraseName(const string& strAddress) return Erase(make_pair(string("name"), strAddress)); } +bool CWalletDB::WritePurpose(const string& strAddress, const string& strPurpose) +{ + nWalletDBUpdated++; + return Write(make_pair(string("purpose"), strAddress), strPurpose); +} + +bool CWalletDB::ErasePurpose(const string& strPurpose) +{ + nWalletDBUpdated++; + return Erase(make_pair(string("purpose"), strPurpose)); +} + bool CWalletDB::ReadAccount(const string& strAccount, CAccount& account) { account.SetNull(); @@ -214,6 +226,12 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, ssKey >> strAddress; ssValue >> pwallet->mapAddressBook[CBitcoinAddress(strAddress).Get()].name; } + else if (strType == "purpose") + { + string strAddress; + ssKey >> strAddress; + ssValue >> pwallet->mapAddressBook[CBitcoinAddress(strAddress).Get()].purpose; + } else if (strType == "tx") { uint256 hash; diff --git a/src/walletdb.h b/src/walletdb.h index 4dfa35d82..09ebebe5a 100644 --- a/src/walletdb.h +++ b/src/walletdb.h @@ -68,9 +68,11 @@ private: void operator=(const CWalletDB&); public: bool WriteName(const std::string& strAddress, const std::string& strName); - bool EraseName(const std::string& strAddress); + bool WritePurpose(const std::string& strAddress, const std::string& purpose); + bool ErasePurpose(const std::string& strAddress); + bool WriteTx(uint256 hash, const CWalletTx& wtx) { nWalletDBUpdated++;