Browse Source

Merge pull request #2539 from gavinandresen/paymentrequest

Payment Protocol Work
0.10
Gavin Andresen 12 years ago
parent
commit
e62f8d72f3
  1. 28
      bitcoin-qt.pro
  2. 2
      contrib/gitian-descriptors/README
  3. 16
      contrib/gitian-descriptors/gitian-win32.yml
  4. 22
      contrib/gitian-descriptors/gitian.yml
  5. 37
      contrib/gitian-descriptors/protobuf-win32.yml
  6. 14
      contrib/gitian-descriptors/qt-win32.yml
  7. 8
      doc/readme-qt.md
  8. 96
      share/qt/Info.plist
  9. 35
      share/qt/protobuf.pri
  10. 13
      src/bitcoind.cpp
  11. 2
      src/chainparams.h
  12. 5
      src/init.cpp
  13. 35
      src/qt/addresstablemodel.cpp
  14. 85
      src/qt/bitcoin.cpp
  15. 5
      src/qt/bitcoinamountfield.cpp
  16. 3
      src/qt/bitcoinamountfield.h
  17. 20
      src/qt/bitcoingui.cpp
  18. 5
      src/qt/bitcoingui.h
  19. 792
      src/qt/forms/sendcoinsentry.ui
  20. 3
      src/qt/guiconstants.h
  21. 8
      src/qt/guiutil.cpp
  22. 3
      src/qt/guiutil.h
  23. 11
      src/qt/optionsmodel.cpp
  24. 1
      src/qt/optionsmodel.h
  25. 46
      src/qt/paymentrequest.proto
  26. 204
      src/qt/paymentrequestplus.cpp
  27. 41
      src/qt/paymentrequestplus.h
  28. 527
      src/qt/paymentserver.cpp
  29. 64
      src/qt/paymentserver.h
  30. 47
      src/qt/sendcoinsdialog.cpp
  31. 3
      src/qt/sendcoinsdialog.h
  32. 56
      src/qt/sendcoinsentry.cpp
  33. 14
      src/qt/sendcoinsentry.h
  34. 3
      src/qt/splashscreen.cpp
  35. 307
      src/qt/test/paymentrequestdata.h
  36. 109
      src/qt/test/paymentservertests.cpp
  37. 29
      src/qt/test/paymentservertests.h
  38. 37
      src/qt/test/test_main.cpp
  39. 32
      src/qt/transactiondesc.cpp
  40. 4
      src/qt/walletframe.cpp
  41. 5
      src/qt/walletframe.h
  42. 98
      src/qt/walletmodel.cpp
  43. 16
      src/qt/walletmodel.h
  44. 4
      src/qt/walletstack.cpp
  45. 3
      src/qt/walletstack.h
  46. 4
      src/qt/walletview.cpp
  47. 3
      src/qt/walletview.h
  48. 6
      src/rpcdump.cpp
  49. 2
      src/rpcrawtransaction.cpp
  50. 50
      src/rpcwallet.cpp
  51. 29
      src/util.cpp
  52. 31
      src/wallet.cpp
  53. 21
      src/wallet.h
  54. 20
      src/walletdb.cpp
  55. 4
      src/walletdb.h

28
bitcoin-qt.pro

@ -16,21 +16,29 @@ CONFIG += thread @@ -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 \ @@ -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 \ @@ -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 \ @@ -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 @@ -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

2
contrib/gitian-descriptors/README

@ -32,6 +32,7 @@ Once you've got the right hardware and software: @@ -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: @@ -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

16
contrib/gitian-descriptors/gitian-win32.yml

@ -15,16 +15,18 @@ remotes: @@ -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: | @@ -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: | @@ -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/
#

22
contrib/gitian-descriptors/gitian.yml

@ -18,38 +18,44 @@ packages: @@ -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

37
contrib/gitian-descriptors/protobuf-win32.yml

@ -0,0 +1,37 @@ @@ -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

14
contrib/gitian-descriptors/qt-win32.yml

@ -12,11 +12,14 @@ reference_datetime: "2011-01-30 00:00:00" @@ -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: | @@ -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 *

8
doc/readme-qt.md

@ -18,13 +18,13 @@ for Debian and Ubuntu <= 11.10 : @@ -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. @@ -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

96
share/qt/Info.plist

@ -2,36 +2,74 @@ @@ -2,36 +2,74 @@
<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
<plist version="0.9">
<dict>
<key>CFBundleIconFile</key>
<string>bitcoin.icns</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleGetInfoString</key>
<string>$VERSION, Copyright © 2009-$YEAR The Bitcoin developers</string>
<key>CFBundleShortVersionString</key>
<string>$VERSION</string>
<key>CFBundleVersion</key>
<string>$VERSION</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleExecutable</key>
<string>Bitcoin-Qt</string>
<key>CFBundleIdentifier</key>
<string>org.bitcoinfoundation.Bitcoin-Qt</string>
<key>CFBundleURLTypes</key>
<key>CFBundleIconFile</key>
<string>bitcoin.icns</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleGetInfoString</key>
<string>$VERSION, Copyright © 2009-$YEAR The Bitcoin developers</string>
<key>CFBundleShortVersionString</key>
<string>$VERSION</string>
<key>CFBundleVersion</key>
<string>$VERSION</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleExecutable</key>
<string>Bitcoin-Qt</string>
<key>CFBundleIdentifier</key>
<string>org.bitcoinfoundation.Bitcoin-Qt</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>org.bitcoin.BitcoinPayment</string>
<key>CFBundleURLSchemes</key>
<array>
<string>bitcoin</string>
</array>
</dict>
</array>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeIdentifier</key>
<string>org.bitcoin.paymentrequest</string>
<key>UTTypeDescription</key>
<string>Bitcoin payment request</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.mime-type</key>
<string>application/x-bitcoin-payment-request</string>
<key>public.filename-extension</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>org.bitcoinfoundation.BitcoinPayment</string>
<key>CFBundleURLSchemes</key>
<array>
<string>bitcoin</string>
</array>
</dict>
<string>bitcoinpaymentrequest</string>
</array>
<key>NSHighResolutionCapable</key>
<true/>
</dict>
</dict>
</array>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSItemContentTypes</key>
<array>
<string>org.bitcoin.paymentrequest</string>
</array>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
</array>
<key>NSHighResolutionCapable</key>
<true/>
</dict>
</plist>

35
share/qt/protobuf.pri

@ -0,0 +1,35 @@ @@ -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

13
src/bitcoind.cpp

@ -39,10 +39,15 @@ bool AppInit(int argc, char* argv[]) @@ -39,10 +39,15 @@ bool AppInit(int argc, char* argv[])
ParseParameters(argc, argv);
if (!boost::filesystem::is_directory(GetDataDir(false)))
{
fprintf(stderr, "Error: Specified directory does not exist\n");
Shutdown();
fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", mapArgs["-datadir"].c_str());
return false;
}
ReadConfigFile(mapArgs, mapMultiArgs);
// Check for -testnet or -regtest parameter (TestNet() calls are only valid after this clause)
if (!SelectParamsFromCommandLine()) {
fprintf(stderr, "Error: Invalid combination of -regtest and -testnet.\n");
return false;
}
if (mapArgs.count("-?") || mapArgs.count("--help"))
{
@ -67,10 +72,6 @@ bool AppInit(int argc, char* argv[]) @@ -67,10 +72,6 @@ bool AppInit(int argc, char* argv[])
if (fCommandLine)
{
if (!SelectParamsFromCommandLine()) {
fprintf(stderr, "Error: invalid combination of -regtest and -testnet.\n");
return false;
}
int ret = CommandLineRPC(argc, argv);
exit(ret);
}

2
src/chainparams.h

@ -39,6 +39,8 @@ public: @@ -39,6 +39,8 @@ public:
MAIN,
TESTNET,
REGTEST,
MAX_NETWORK_TYPES
};
enum Base58Type {

5
src/init.cpp

@ -373,9 +373,6 @@ bool AppInit2(boost::thread_group& threadGroup) @@ -373,9 +373,6 @@ bool AppInit2(boost::thread_group& threadGroup)
// ********************************************************* Step 2: parameter interactions
Checkpoints::fEnabled = GetBoolArg("-checkpoints", true);
if (!SelectParamsFromCommandLine()) {
return InitError("Invalid combination of -testnet and -regtest.");
}
if (mapArgs.count("-bind")) {
// when specifying an explicit binding address, you want to listen on it
@ -898,7 +895,7 @@ bool AppInit2(boost::thread_group& threadGroup) @@ -898,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";
}

35
src/qt/addresstablemodel.cpp

@ -59,12 +59,22 @@ public: @@ -59,12 +59,22 @@ public:
cachedAddressTable.clear();
{
LOCK(wallet->cs_wallet);
BOOST_FOREACH(const PAIRTYPE(CTxDestination, std::string)& item, wallet->mapAddressBook)
BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& item, wallet->mapAddressBook)
{
const CBitcoinAddress& address = item.first;
const std::string& strName = item.second;
bool fMine = IsMine(*wallet, address.Get());
cachedAddressTable.append(AddressTableEntry(fMine ? AddressTableEntry::Receiving : AddressTableEntry::Sending,
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;
cachedAddressTable.append(AddressTableEntry(addressType,
QString::fromStdString(strName),
QString::fromStdString(address.ToString())));
}
@ -215,7 +225,7 @@ bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value, @@ -215,7 +225,7 @@ bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value,
if(!index.isValid())
return false;
AddressTableEntry *rec = static_cast<AddressTableEntry*>(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, @@ -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, @@ -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 @@ -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 @@ -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;
}
@ -397,10 +408,10 @@ QString AddressTableModel::labelForAddress(const QString &address) const @@ -397,10 +408,10 @@ QString AddressTableModel::labelForAddress(const QString &address) const
{
LOCK(wallet->cs_wallet);
CBitcoinAddress address_parsed(address.toStdString());
std::map<CTxDestination, std::string>::iterator mi = wallet->mapAddressBook.find(address_parsed.Get());
std::map<CTxDestination, CAddressBookData>::iterator mi = wallet->mapAddressBook.find(address_parsed.Get());
if (mi != wallet->mapAddressBook.end())
{
return QString::fromStdString(mi->second);
return QString::fromStdString(mi->second.name);
}
}
return QString();

85
src/qt/bitcoin.cpp

@ -151,13 +151,39 @@ static void initTranslations(QTranslator &qtTranslatorBase, QTranslator &qtTrans @@ -151,13 +151,39 @@ static void initTranslations(QTranslator &qtTranslatorBase, QTranslator &qtTrans
QApplication::installTranslator(&translator);
}
/* qDebug() message handler --> debug.log */
#if QT_VERSION < 0x050000
void DebugMessageHandler(QtMsgType type, const char * msg)
{
OutputDebugStringF("%s\n", msg);
}
#else
void DebugMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString &msg)
{
OutputDebugStringF("%s\n", qPrintable(msg));
}
#endif
#ifndef BITCOIN_QT_TEST
int main(int argc, char *argv[])
{
bool fMissingDatadir = false;
bool fSelParFromCLFailed = false;
fHaveGUI = true;
// Command-line options take precedence:
ParseParameters(argc, argv);
// ... then bitcoin.conf:
if (!boost::filesystem::is_directory(GetDataDir(false))) {
fMissingDatadir = true;
} else {
ReadConfigFile(mapArgs, mapMultiArgs);
}
// Check for -testnet or -regtest parameter (TestNet() calls are only valid after this clause)
if (!SelectParamsFromCommandLine()) {
fSelParFromCLFailed = true;
}
#if QT_VERSION < 0x050000
// Internal string conversion is all UTF-8
@ -175,7 +201,7 @@ int main(int argc, char *argv[]) @@ -175,7 +201,7 @@ int main(int argc, char *argv[])
// as it is used to locate QSettings)
QApplication::setOrganizationName("Bitcoin");
QApplication::setOrganizationDomain("bitcoin.org");
if (GetBoolArg("-testnet", false)) // Separate UI settings for testnet
if (TestNet()) // Separate UI settings for testnet
QApplication::setApplicationName("Bitcoin-Qt-testnet");
else
QApplication::setApplicationName("Bitcoin-Qt");
@ -184,28 +210,34 @@ int main(int argc, char *argv[]) @@ -184,28 +210,34 @@ int main(int argc, char *argv[])
QTranslator qtTranslatorBase, qtTranslator, translatorBase, translator;
initTranslations(qtTranslatorBase, qtTranslator, translatorBase, translator);
// User language is set up: pick a data directory
Intro::pickDataDirectory();
// Do this early as we don't want to bother initializing if we are just calling IPC
// ... but do it after creating app, so QCoreApplication::arguments is initialized:
if (PaymentServer::ipcSendCommandLine())
// ... but do it after creating app and setting up translations, so errors are
// translated properly.
if (PaymentServer::ipcSendCommandLine(argc, argv))
exit(0);
PaymentServer* paymentServer = new PaymentServer(&app);
// Install global event filter that makes sure that long tooltips can be word-wrapped
app.installEventFilter(new GUIUtil::ToolTipToRichTextFilter(TOOLTIP_WRAP_THRESHOLD, &app));
// ... then bitcoin.conf:
if (!boost::filesystem::is_directory(GetDataDir(false)))
{
// Now that translations are initialized check for errors and allow a translatable error message
if (fMissingDatadir) {
QMessageBox::critical(0, QObject::tr("Bitcoin"),
QObject::tr("Error: Specified data directory \"%1\" does not exist.").arg(QString::fromStdString(mapArgs["-datadir"])));
return 1;
}
ReadConfigFile(mapArgs, mapMultiArgs);
else if (fSelParFromCLFailed) {
QMessageBox::critical(0, QObject::tr("Bitcoin"), QObject::tr("Error: Invalid combination of -regtest and -testnet."));
return 1;
}
// ... then GUI settings:
// Start up the payment server early, too, so impatient users that click on
// bitcoin: links repeatedly have their payment requests routed to this process:
PaymentServer* paymentServer = new PaymentServer(&app);
// User language is set up: pick a data directory
Intro::pickDataDirectory();
// Install global event filter that makes sure that long tooltips can be word-wrapped
app.installEventFilter(new GUIUtil::ToolTipToRichTextFilter(TOOLTIP_WRAP_THRESHOLD, &app));
// ... now GUI settings:
OptionsModel optionsModel;
// Subscribe to global signals from core
@ -223,6 +255,13 @@ int main(int argc, char *argv[]) @@ -223,6 +255,13 @@ int main(int argc, char *argv[])
return 1;
}
// Install qDebug() message handler to route to debug.log:
#if QT_VERSION < 0x050000
qInstallMsgHandler(DebugMessageHandler);
#else
qInstallMessageHandler(DebugMessageHandler);
#endif
SplashScreen splash(QPixmap(), 0);
if (GetBoolArg("-splash", true) && !GetBoolArg("-min", false))
{
@ -245,7 +284,7 @@ int main(int argc, char *argv[]) @@ -245,7 +284,7 @@ int main(int argc, char *argv[])
boost::thread_group threadGroup;
BitcoinGUI window(GetBoolArg("-testnet", false), 0);
BitcoinGUI window(TestNet(), 0);
guiref = &window;
QTimer* pollShutdownTimer = new QTimer(guiref);
@ -260,6 +299,9 @@ int main(int argc, char *argv[]) @@ -260,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);
@ -281,8 +323,15 @@ int main(int argc, char *argv[]) @@ -281,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();

5
src/qt/bitcoinamountfield.cpp

@ -130,6 +130,11 @@ void BitcoinAmountField::setValue(qint64 value) @@ -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

3
src/qt/bitcoinamountfield.h

@ -22,6 +22,9 @@ public: @@ -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. */

20
src/qt/bitcoingui.cpp

@ -45,6 +45,7 @@ @@ -45,6 +45,7 @@
#include <QDragEnterEvent>
#if QT_VERSION < 0x050000
#include <QUrl>
#include <QTextDocument>
#endif
#include <QMimeData>
#include <QStyle>
@ -707,7 +708,8 @@ void BitcoinGUI::dropEvent(QDropEvent *event) @@ -707,7 +708,8 @@ void BitcoinGUI::dropEvent(QDropEvent *event)
QList<QUrl> 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) @@ -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)

5
src/qt/bitcoingui.h

@ -15,6 +15,7 @@ class TransactionView; @@ -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: @@ -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);

792
src/qt/forms/sendcoinsentry.ui

@ -1,143 +1,685 @@ @@ -1,143 +1,685 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SendCoinsEntry</class>
<widget class="QFrame" name="SendCoinsEntry">
<widget class="QStackedWidget" name="SendCoinsEntry">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>729</width>
<height>136</height>
<height>150</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
<string>StackedWidget</string>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
<property name="currentIndex">
<number>1</number>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="spacing">
<number>12</number>
<widget class="QFrame" name="SendCoinsInsecure">
<property name="windowTitle">
<string>Form</string>
</property>
<item row="5" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>A&amp;mount:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>payAmount</cstring>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Pay &amp;To:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>payTo</cstring>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="BitcoinAmountField" name="payAmount"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>&amp;Label:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>addAsLabel</cstring>
</property>
</widget>
</item>
<item row="3" column="1">
<layout class="QHBoxLayout" name="payToLayout">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QValidatedLineEdit" name="payTo">
<property name="toolTip">
<string>The address to send the payment to (e.g. 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L)</string>
</property>
<property name="maxLength">
<number>34</number>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="addressBookButton">
<property name="toolTip">
<string>Choose address from address book</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../bitcoin.qrc">
<normaloff>:/icons/address-book</normaloff>:/icons/address-book</iconset>
</property>
<property name="shortcut">
<string>Alt+A</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="pasteButton">
<property name="toolTip">
<string>Paste address from clipboard</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../bitcoin.qrc">
<normaloff>:/icons/editpaste</normaloff>:/icons/editpaste</iconset>
</property>
<property name="shortcut">
<string>Alt+P</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="deleteButton">
<property name="toolTip">
<string>Remove this recipient</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../bitcoin.qrc">
<normaloff>:/icons/remove</normaloff>:/icons/remove</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item row="4" column="1">
<widget class="QValidatedLineEdit" name="addAsLabel">
<property name="toolTip">
<string>Enter a label for this address to add it to your address book</string>
</property>
</widget>
</item>
</layout>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="spacing">
<number>12</number>
</property>
<item row="5" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>A&amp;mount:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>payAmount</cstring>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Pay &amp;To:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>payTo</cstring>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="BitcoinAmountField" name="payAmount"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>&amp;Label:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>addAsLabel</cstring>
</property>
</widget>
</item>
<item row="3" column="1">
<layout class="QHBoxLayout" name="payToLayout">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QValidatedLineEdit" name="payTo">
<property name="toolTip">
<string>The address to send the payment to (e.g. 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L)</string>
</property>
<property name="maxLength">
<number>34</number>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="addressBookButton">
<property name="toolTip">
<string>Choose address from address book</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../bitcoin.qrc">
<normaloff>:/icons/address-book</normaloff>:/icons/address-book</iconset>
</property>
<property name="shortcut">
<string>Alt+A</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="pasteButton">
<property name="toolTip">
<string>Paste address from clipboard</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../bitcoin.qrc">
<normaloff>:/icons/editpaste</normaloff>:/icons/editpaste</iconset>
</property>
<property name="shortcut">
<string>Alt+P</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="deleteButton">
<property name="toolTip">
<string>Remove this recipient</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../bitcoin.qrc">
<normaloff>:/icons/remove</normaloff>:/icons/remove</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item row="4" column="1">
<widget class="QValidatedLineEdit" name="addAsLabel">
<property name="toolTip">
<string>Enter a label for this address to add it to your address book</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QFrame" name="SendCoinsSecure">
<property name="palette">
<palette>
<active>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="Button">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>140</red>
<green>232</green>
<blue>119</blue>
</color>
</brush>
</colorrole>
<colorrole role="Light">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>230</red>
<green>255</green>
<blue>224</blue>
</color>
</brush>
</colorrole>
<colorrole role="Midlight">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>185</red>
<green>243</green>
<blue>171</blue>
</color>
</brush>
</colorrole>
<colorrole role="Dark">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>70</red>
<green>116</green>
<blue>59</blue>
</color>
</brush>
</colorrole>
<colorrole role="Mid">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>93</red>
<green>155</green>
<blue>79</blue>
</color>
</brush>
</colorrole>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="BrightText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>155</red>
<green>255</green>
<blue>147</blue>
</color>
</brush>
</colorrole>
<colorrole role="ButtonText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>119</red>
<green>255</green>
<blue>233</blue>
</color>
</brush>
</colorrole>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>140</red>
<green>232</green>
<blue>119</blue>
</color>
</brush>
</colorrole>
<colorrole role="Shadow">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="AlternateBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>197</red>
<green>243</green>
<blue>187</blue>
</color>
</brush>
</colorrole>
<colorrole role="NoRole">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>125</red>
<green>194</green>
<blue>122</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>220</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="Button">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>140</red>
<green>232</green>
<blue>119</blue>
</color>
</brush>
</colorrole>
<colorrole role="Light">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>230</red>
<green>255</green>
<blue>224</blue>
</color>
</brush>
</colorrole>
<colorrole role="Midlight">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>185</red>
<green>243</green>
<blue>171</blue>
</color>
</brush>
</colorrole>
<colorrole role="Dark">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>70</red>
<green>116</green>
<blue>59</blue>
</color>
</brush>
</colorrole>
<colorrole role="Mid">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>93</red>
<green>155</green>
<blue>79</blue>
</color>
</brush>
</colorrole>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="BrightText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>155</red>
<green>255</green>
<blue>147</blue>
</color>
</brush>
</colorrole>
<colorrole role="ButtonText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>119</red>
<green>255</green>
<blue>233</blue>
</color>
</brush>
</colorrole>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>140</red>
<green>232</green>
<blue>119</blue>
</color>
</brush>
</colorrole>
<colorrole role="Shadow">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="AlternateBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>197</red>
<green>243</green>
<blue>187</blue>
</color>
</brush>
</colorrole>
<colorrole role="NoRole">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>125</red>
<green>194</green>
<blue>122</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>220</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>70</red>
<green>116</green>
<blue>59</blue>
</color>
</brush>
</colorrole>
<colorrole role="Button">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>140</red>
<green>232</green>
<blue>119</blue>
</color>
</brush>
</colorrole>
<colorrole role="Light">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>230</red>
<green>255</green>
<blue>224</blue>
</color>
</brush>
</colorrole>
<colorrole role="Midlight">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>185</red>
<green>243</green>
<blue>171</blue>
</color>
</brush>
</colorrole>
<colorrole role="Dark">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>70</red>
<green>116</green>
<blue>59</blue>
</color>
</brush>
</colorrole>
<colorrole role="Mid">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>93</red>
<green>155</green>
<blue>79</blue>
</color>
</brush>
</colorrole>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>70</red>
<green>116</green>
<blue>59</blue>
</color>
</brush>
</colorrole>
<colorrole role="BrightText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>155</red>
<green>255</green>
<blue>147</blue>
</color>
</brush>
</colorrole>
<colorrole role="ButtonText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>70</red>
<green>116</green>
<blue>59</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>140</red>
<green>232</green>
<blue>119</blue>
</color>
</brush>
</colorrole>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>140</red>
<green>232</green>
<blue>119</blue>
</color>
</brush>
</colorrole>
<colorrole role="Shadow">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="AlternateBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>140</red>
<green>232</green>
<blue>119</blue>
</color>
</brush>
</colorrole>
<colorrole role="NoRole">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>125</red>
<green>194</green>
<blue>122</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>220</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="windowTitle">
<string>SecureSend</string>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<layout class="QGridLayout" name="gridLayout_s">
<property name="spacing">
<number>12</number>
</property>
<item row="4" column="0">
<widget class="QLabel" name="label_s4">
<property name="text">
<string>Memo:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>addAsLabel</cstring>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_s1">
<property name="text">
<string>A&amp;mount:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>payAmount</cstring>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_s2">
<property name="text">
<string>Pay &amp;To:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>payTo_s</cstring>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="BitcoinAmountField" name="payAmount_s">
<property name="enabled">
<bool>false</bool>
</property>
<property name="acceptDrops">
<bool>false</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="2">
<layout class="QHBoxLayout" name="payToLayout_s">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="payTo_s">
</widget>
</item>
</layout>
</item>
<item row="4" column="2">
<widget class="QLabel" name="memo_s">
<property name="text">
<string>message from merchant</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>

3
src/qt/guiconstants.h

@ -28,6 +28,9 @@ static const int TOOLTIP_WRAP_THRESHOLD = 80; @@ -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

8
src/qt/guiutil.cpp

@ -148,6 +148,14 @@ bool parseBitcoinURI(QString uri, SendCoinsRecipient *out) @@ -148,6 +148,14 @@ bool parseBitcoinURI(QString uri, SendCoinsRecipient *out)
return parseBitcoinURI(uriInstance, out);
}
bool isDust(const QString& address, qint64 amount)
{
CTxDestination dest = CBitcoinAddress(address.toStdString()).Get();
CScript script; script.SetDestination(dest);
CTxOut txOut(amount, script);
return txOut.IsDust(CTransaction::nMinRelayTxFee);
}
QString HtmlEscape(const QString& str, bool fMultiLine)
{
#if QT_VERSION < 0x050000

3
src/qt/guiutil.h

@ -36,6 +36,9 @@ namespace GUIUtil @@ -36,6 +36,9 @@ namespace GUIUtil
bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out);
bool parseBitcoinURI(QString uri, SendCoinsRecipient *out);
// Returns true if given address+amount meets "dust" definition
bool isDust(const QString& address, qint64 amount);
// HTML escaping for rich text controls
QString HtmlEscape(const QString& str, bool fMultiLine=false);
QString HtmlEscape(const std::string& str, bool fMultiLine=false);

11
src/qt/optionsmodel.cpp

@ -290,3 +290,14 @@ qint64 OptionsModel::getTransactionFee() @@ -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;
}

1
src/qt/optionsmodel.h

@ -49,6 +49,7 @@ public: @@ -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;

46
src/qt/paymentrequest.proto

@ -0,0 +1,46 @@ @@ -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
}

204
src/qt/paymentrequestplus.cpp

@ -0,0 +1,204 @@ @@ -0,0 +1,204 @@
//
// Wraps dumb protocol buffer paymentRequest
// with some extra methods
//
#include <QDateTime>
#include <QDebug>
#include <QSslCertificate>
#include <openssl/x509.h>
#include <openssl/x509_vfy.h>
#include <stdexcept>
#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<X509*> 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<std::pair<CScript,qint64> > PaymentRequestPlus::getPayTo() const
{
QList<std::pair<CScript,qint64> > 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;
}

41
src/qt/paymentrequestplus.h

@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
#ifndef PAYMENTREQUESTPLUS_H
#define PAYMENTREQUESTPLUS_H
#include <QByteArray>
#include <QList>
#include <QString>
#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<std::pair<CScript,qint64> > getPayTo() const;
const payments::PaymentDetails& getDetails() const { return details; }
private:
payments::PaymentRequest paymentRequest;
payments::PaymentDetails details;
};
#endif // PAYMENTREQUESTPLUS_H

527
src/qt/paymentserver.cpp

@ -2,30 +2,63 @@ @@ -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 <QApplication>
#include <QByteArray>
#include <QDataStream>
#include <QDateTime>
#include <QDebug>
#include <QFile>
#include <QFileOpenEvent>
#include <QHash>
#include <QList>
#include <QLocalServer>
#include <QLocalSocket>
#include <QStringList>
#include <QTextDocument>
#include <QNetworkAccessManager>
#include <QNetworkProxy>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QSslCertificate>
#include <QSslError>
#include <QSslSocket>
#if QT_VERSION < 0x050000
#include <QUrl>
#else
#include <QUrlQuery>
#endif
#include <cstdlib>
#include <openssl/x509.h>
#include <openssl/x509_vfy.h>
#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() @@ -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<QString> 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<QSslCertificate> 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; @@ -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() @@ -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() @@ -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<QFileOpenEvent*>(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<QSslError> &)),
this, SLOT(reportSslErrors(QNetworkReply*, const QList<QSslError> &)));
}
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<SendCoinsRecipient> 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() @@ -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<SendCoinsRecipient>& recipients)
{
QList<std::pair<CScript,qint64> > 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<CScript, qint64>& 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<CTxDestination> 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<SendCoinsRecipient> 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<QSslError> &errs)
{
QString errString;
foreach (const QSslError& err, errs) {
qDebug() << err;
errString += err.errorString() + "\n";
}
emit reportError(tr("Network request error"), errString, CClientUIInterface::MODAL);
}

64
src/qt/paymentserver.h

@ -31,37 +31,87 @@ @@ -31,37 +31,87 @@
#include <QObject>
#include <QString>
#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<QSslError> &);
private:
static bool readPaymentRequest(const QString& filename, PaymentRequestPlus& request);
bool processPaymentRequest(PaymentRequestPlus& request, QList<SendCoinsRecipient>& 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

47
src/qt/sendcoinsdialog.cpp

@ -93,11 +93,26 @@ void SendCoinsDialog::on_sendButton_clicked() @@ -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("<b>%1</b> 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("<b>%1</b> to %2 (%3)").arg(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, rcp.amount), rcp.label.toHtmlEscaped(), rcp.address));
QString to = rcp.label.toHtmlEscaped();
#endif
formatted.append(tr("<b>%1</b> 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("<b>%1</b> to %2").arg(amount, merchant));
}
}
fNewRecipientAllowed = false;
@ -292,20 +307,30 @@ void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv) @@ -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)

3
src/qt/sendcoinsdialog.h

@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
#define SENDCOINSDIALOG_H
#include <QDialog>
#include <QVariant>
namespace Ui {
class SendCoinsDialog;
@ -31,7 +32,7 @@ public: @@ -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();

56
src/qt/sendcoinsentry.cpp

@ -12,12 +12,14 @@ @@ -12,12 +12,14 @@
#include <QClipboard>
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,24 +103,24 @@ bool SendCoinsEntry::validate() @@ -101,24 +103,24 @@ bool SendCoinsEntry::validate()
// Check input validity
bool retval = true;
if(!ui->payAmount->validate())
if (!recipient.authenticatedMerchant.isEmpty())
return retval;
if(!ui->payTo->hasAcceptableInput() ||
(model && !model->validateAddress(ui->payTo->text())))
{
ui->payTo->setValid(false);
retval = false;
}
else
if(!ui->payAmount->validate())
{
if(ui->payAmount->value() <= 0)
{
// Cannot send 0 coins or less
ui->payAmount->setValid(false);
retval = false;
}
retval = false;
}
if(!ui->payTo->hasAcceptableInput() ||
(model && !model->validateAddress(ui->payTo->text())))
{
ui->payTo->setValid(false);
// Reject dust outputs:
if (retval && GUIUtil::isDust(ui->payTo->text(), ui->payAmount->value())) {
ui->payAmount->setValid(false);
retval = false;
}
@ -127,13 +129,15 @@ bool SendCoinsEntry::validate() @@ -127,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)
@ -148,9 +152,22 @@ QWidget *SendCoinsEntry::setupTabChain(QWidget *prev) @@ -148,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)
@ -161,7 +178,7 @@ void SendCoinsEntry::setAddress(const QString &address) @@ -161,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()
@ -175,5 +192,6 @@ void SendCoinsEntry::updateDisplayUnit() @@ -175,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());
}
}

14
src/qt/sendcoinsentry.h

@ -1,16 +1,21 @@ @@ -1,16 +1,21 @@
#ifndef SENDCOINSENTRY_H
#define SENDCOINSENTRY_H
#include <QFrame>
#include <QStackedWidget>
#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: @@ -49,6 +54,7 @@ private slots:
void updateDisplayUnit();
private:
SendCoinsRecipient recipient;
Ui::SendCoinsEntry *ui;
WalletModel *model;
};

3
src/qt/splashscreen.cpp

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
#include "splashscreen.h"
#include "clientversion.h"
#include "util.h"
#include "chainparams.h"
#include <QApplication>
#include <QPainter>
@ -26,7 +27,7 @@ SplashScreen::SplashScreen(const QPixmap &pixmap, Qt::WindowFlags f) : @@ -26,7 +27,7 @@ SplashScreen::SplashScreen(const QPixmap &pixmap, Qt::WindowFlags f) :
// load the bitmap for writing some text over it
QPixmap newPixmap;
if(GetBoolArg("-testnet", false)) {
if(TestNet()) {
newPixmap = QPixmap(":/images/splash_testnet");
}
else {

307
src/qt/test/paymentrequestdata.h

@ -0,0 +1,307 @@ @@ -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\
";

109
src/qt/test/paymentservertests.cpp

@ -0,0 +1,109 @@ @@ -0,0 +1,109 @@
#include <QCoreApplication>
#include <QDebug>
#include <QTemporaryFile>
#include <QVariant>
#include <openssl/x509.h>
#include <openssl/x509_vfy.h>
#include "optionsmodel.h"
#include "paymentservertests.h"
#include "paymentrequestdata.h"
#include "util.h"
X509 *parse_b64der_cert(const char* cert_data)
{
std::vector<unsigned char> 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<unsigned char>& 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<unsigned char> 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;
}

29
src/qt/test/paymentservertests.h

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
#ifndef PAYMENTSERVERTESTS_H
#define PAYMENTSERVERTESTS_H
#include <QTest>
#include <QObject>
#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

37
src/qt/test/test_main.cpp

@ -1,16 +1,21 @@ @@ -1,16 +1,21 @@
#include <QTest>
#include <QObject>
#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 <QTest>
#include <QObject>
#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;
}

32
src/qt/transactiondesc.cpp

@ -7,6 +7,7 @@ @@ -7,6 +7,7 @@
#include "db.h"
#include "ui_interface.h"
#include "base58.h"
#include "paymentserver.h"
#include <string>
@ -88,8 +89,8 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) @@ -88,8 +89,8 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx)
strHTML += "<b>" + tr("From") + ":</b> " + tr("unknown") + "<br>";
strHTML += "<b>" + tr("To") + ":</b> ";
strHTML += GUIUtil::HtmlEscape(CBitcoinAddress(address).ToString());
if (!wallet->mapAddressBook[address].empty())
strHTML += " (" + tr("own address") + ", " + tr("label") + ": " + GUIUtil::HtmlEscape(wallet->mapAddressBook[address]) + ")";
if (!wallet->mapAddressBook[address].name.empty())
strHTML += " (" + tr("own address") + ", " + tr("label") + ": " + GUIUtil::HtmlEscape(wallet->mapAddressBook[address].name) + ")";
else
strHTML += " (" + tr("own address") + ")";
strHTML += "<br>";
@ -110,8 +111,8 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) @@ -110,8 +111,8 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx)
std::string strAddress = wtx.mapValue["to"];
strHTML += "<b>" + tr("To") + ":</b> ";
CTxDestination dest = CBitcoinAddress(strAddress).Get();
if (wallet->mapAddressBook.count(dest) && !wallet->mapAddressBook[dest].empty())
strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[dest]) + " ";
if (wallet->mapAddressBook.count(dest) && !wallet->mapAddressBook[dest].name.empty())
strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[dest].name) + " ";
strHTML += GUIUtil::HtmlEscape(strAddress) + "<br>";
}
@ -167,8 +168,8 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) @@ -167,8 +168,8 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx)
if (ExtractDestination(txout.scriptPubKey, address))
{
strHTML += "<b>" + tr("To") + ":</b> ";
if (wallet->mapAddressBook.count(address) && !wallet->mapAddressBook[address].empty())
strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[address]) + " ";
if (wallet->mapAddressBook.count(address) && !wallet->mapAddressBook[address].name.empty())
strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[address].name) + " ";
strHTML += GUIUtil::HtmlEscape(CBitcoinAddress(address).ToString());
strHTML += "<br>";
}
@ -216,6 +217,21 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) @@ -216,6 +217,21 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx)
strHTML += "<b>" + tr("Transaction ID") + ":</b> " + wtx.GetHash().ToString().c_str() + "<br>";
//
// 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 += "<b>" + tr("Merchant") + ":</b> " + GUIUtil::HtmlEscape(merchant) + "<br>";
}
}
if (wtx.IsCoinBase())
strHTML += "<br>" + 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.") + "<br>";
@ -254,8 +270,8 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) @@ -254,8 +270,8 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx)
CTxDestination address;
if (ExtractDestination(vout.scriptPubKey, address))
{
if (wallet->mapAddressBook.count(address) && !wallet->mapAddressBook[address].empty())
strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[address]) + " ";
if (wallet->mapAddressBook.count(address) && !wallet->mapAddressBook[address].name.empty())
strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[address].name) + " ";
strHTML += QString::fromStdString(CBitcoinAddress(address).ToString());
}
strHTML = strHTML + " " + tr("Amount") + "=" + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, vout.nValue);

4
src/qt/walletframe.cpp

@ -52,9 +52,9 @@ void WalletFrame::removeAllWallets() @@ -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)

5
src/qt/walletframe.h

@ -11,6 +11,7 @@ @@ -11,6 +11,7 @@
class BitcoinGUI;
class ClientModel;
class SendCoinsRecipient;
class WalletModel;
class WalletStack;
@ -29,7 +30,7 @@ public: @@ -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: @@ -71,4 +72,4 @@ public slots:
void setEncryptionStatus();
};
#endif // WALLETFRAME_H
#endif // WALLETFRAME_H

98
src/qt/walletmodel.cpp

@ -127,31 +127,60 @@ bool WalletModel::validateAddress(const QString &address) @@ -127,31 +127,60 @@ bool WalletModel::validateAddress(const QString &address)
WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList<SendCoinsRecipient> &recipients)
{
qint64 total = 0;
QSet<QString> setAddress;
QString hex;
std::vector<std::pair<CScript, int64> > vecSend;
QByteArray transaction;
if(recipients.empty())
{
return OK;
}
QSet<QString> 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<CScript, int64>(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<CScript, int64>(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 QList<SendCoinsRecipie @@ -169,19 +198,10 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList<SendCoinsRecipie
{
LOCK2(cs_main, wallet->cs_wallet);
// Sendmany
std::vector<std::pair<CScript, int64> > 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<SendCoinsRecipie @@ -194,6 +214,18 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList<SendCoinsRecipie
CClientUIInterface::MSG_ERROR);
return TransactionCreationFailed;
}
// Store PaymentRequests in wtx.vOrderForm in wallet.
foreach(const SendCoinsRecipient &rcp, recipients)
{
if (rcp.paymentRequest.IsInitialized())
{
std::string key("PaymentRequest");
std::string value;
rcp.paymentRequest.SerializeToString(&value);
wtx.vOrderForm.push_back(make_pair(key, value));
}
}
if(!uiInterface.ThreadSafeAskFee(nFeeRequired))
{
return Aborted;
@ -202,10 +234,15 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList<SendCoinsRecipie @@ -202,10 +234,15 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList<SendCoinsRecipie
{
return TransactionCommitFailed;
}
hex = QString::fromStdString(wtx.GetHash().GetHex());
CTransaction* t = (CTransaction*)&wtx;
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << *t;
transaction.append(&(ssTx[0]), ssTx.size());
}
// Add addresses / update labels that we've sent to to the address book
// Add addresses / update labels that we've sent to to the address book,
// and emit coinsSent signal
foreach(const SendCoinsRecipient &rcp, recipients)
{
std::string strAddress = rcp.address.toStdString();
@ -214,17 +251,22 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList<SendCoinsRecipie @@ -214,17 +251,22 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList<SendCoinsRecipie
{
LOCK(wallet->cs_wallet);
std::map<CTxDestination, std::string>::iterator mi = wallet->mapAddressBook.find(dest);
std::map<CTxDestination, CAddressBookData>::iterator mi = wallet->mapAddressBook.find(dest);
// Check if we have a new address or an updated label
if (mi == wallet->mapAddressBook.end() || mi->second != 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()

16
src/qt/walletmodel.h

@ -4,6 +4,7 @@ @@ -4,6 +4,7 @@
#include <QObject>
#include "allocators.h" /* for SecureString */
#include "paymentrequestplus.h"
class OptionsModel;
class AddressTableModel;
@ -17,9 +18,15 @@ QT_END_NAMESPACE @@ -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: @@ -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: @@ -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();

4
src/qt/walletstack.cpp

@ -54,12 +54,12 @@ void WalletStack::removeAllWallets() @@ -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)

3
src/qt/walletstack.h

@ -20,6 +20,7 @@ class TransactionView; @@ -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: @@ -54,7 +55,7 @@ public:
void removeAllWallets();
bool handleURI(const QString &uri);
bool handlePaymentRequest(const SendCoinsRecipient &recipient);
void showOutOfSyncWarning(bool fShow);

4
src/qt/walletview.cpp

@ -201,10 +201,10 @@ void WalletView::gotoVerifyMessageTab(QString addr) @@ -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;

3
src/qt/walletview.h

@ -16,6 +16,7 @@ class TransactionView; @@ -16,6 +16,7 @@ class TransactionView;
class OverviewPage;
class AddressBookPage;
class SendCoinsDialog;
class SendCoinsRecipient;
class SignVerifyMessageDialog;
class RPCConsole;
@ -49,7 +50,7 @@ public: @@ -49,7 +50,7 @@ public:
*/
void setWalletModel(WalletModel *walletModel);
bool handleURI(const QString &uri);
bool handlePaymentRequest(const SendCoinsRecipient& recipient);
void showOutOfSyncWarning(bool fShow);

6
src/rpcdump.cpp

@ -94,7 +94,7 @@ Value importprivkey(const Array& params, bool fHelp) @@ -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) @@ -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();
@ -255,7 +255,7 @@ Value dumpwallet(const Array& params, bool fHelp) @@ -255,7 +255,7 @@ Value dumpwallet(const Array& params, bool fHelp)
CKey key;
if (pwalletMain->GetKey(keyid, key)) {
if (pwalletMain->mapAddressBook.count(keyid)) {
file << strprintf("%s %s label=%s # addr=%s\n", CBitcoinSecret(key).ToString().c_str(), strTime.c_str(), EncodeDumpString(pwalletMain->mapAddressBook[keyid]).c_str(), strAddr.c_str());
file << strprintf("%s %s label=%s # addr=%s\n", CBitcoinSecret(key).ToString().c_str(), strTime.c_str(), EncodeDumpString(pwalletMain->mapAddressBook[keyid].name).c_str(), strAddr.c_str());
} else if (setKeyPool.count(keyid)) {
file << strprintf("%s %s reserve=1 # addr=%s\n", CBitcoinSecret(key).ToString().c_str(), strTime.c_str(), strAddr.c_str());
} else {

2
src/rpcrawtransaction.cpp

@ -229,7 +229,7 @@ Value listunspent(const Array& params, bool fHelp) @@ -229,7 +229,7 @@ Value listunspent(const Array& params, bool fHelp)
{
entry.push_back(Pair("address", CBitcoinAddress(address).ToString()));
if (pwalletMain->mapAddressBook.count(address))
entry.push_back(Pair("account", pwalletMain->mapAddressBook[address]));
entry.push_back(Pair("account", pwalletMain->mapAddressBook[address].name));
}
entry.push_back(Pair("scriptPubKey", HexStr(pk.begin(), pk.end())));
if (pk.IsPayToScriptHash())

50
src/rpcwallet.cpp

@ -114,7 +114,7 @@ Value getnewaddress(const Array& params, bool fHelp) @@ -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) @@ -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);
}
@ -196,12 +196,12 @@ Value setaccount(const Array& params, bool fHelp) @@ -196,12 +196,12 @@ Value setaccount(const Array& params, bool fHelp)
// Detect when changing the account of an address that is the 'unused current key' of another account:
if (pwalletMain->mapAddressBook.count(address.Get()))
{
string strOldAccount = pwalletMain->mapAddressBook[address.Get()];
string strOldAccount = pwalletMain->mapAddressBook[address.Get()].name;
if (address == GetAccountAddress(strOldAccount))
GetAccountAddress(strOldAccount, true);
}
pwalletMain->SetAddressBookName(address.Get(), strAccount);
pwalletMain->SetAddressBook(address.Get(), strAccount, "receive");
return Value::null;
}
@ -219,9 +219,9 @@ Value getaccount(const Array& params, bool fHelp) @@ -219,9 +219,9 @@ Value getaccount(const Array& params, bool fHelp)
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
string strAccount;
map<CTxDestination, string>::iterator mi = pwalletMain->mapAddressBook.find(address.Get());
if (mi != pwalletMain->mapAddressBook.end() && !(*mi).second.empty())
strAccount = (*mi).second;
map<CTxDestination, CAddressBookData>::iterator mi = pwalletMain->mapAddressBook.find(address.Get());
if (mi != pwalletMain->mapAddressBook.end() && !(*mi).second.name.empty())
strAccount = (*mi).second.name;
return strAccount;
}
@ -237,10 +237,10 @@ Value getaddressesbyaccount(const Array& params, bool fHelp) @@ -237,10 +237,10 @@ Value getaddressesbyaccount(const Array& params, bool fHelp)
// Find all addresses that have the given account
Array ret;
BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, string)& item, pwalletMain->mapAddressBook)
BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, CAddressBookData)& item, pwalletMain->mapAddressBook)
{
const CBitcoinAddress& address = item.first;
const string& strName = item.second;
const string& strName = item.second.name;
if (strName == strAccount)
ret.push_back(address.ToString());
}
@ -301,7 +301,7 @@ Value listaddressgroupings(const Array& params, bool fHelp) @@ -301,7 +301,7 @@ Value listaddressgroupings(const Array& params, bool fHelp)
{
LOCK(pwalletMain->cs_wallet);
if (pwalletMain->mapAddressBook.find(CBitcoinAddress(address).Get()) != pwalletMain->mapAddressBook.end())
addressInfo.push_back(pwalletMain->mapAddressBook.find(CBitcoinAddress(address).Get())->second);
addressInfo.push_back(pwalletMain->mapAddressBook.find(CBitcoinAddress(address).Get())->second.name);
}
jsonGrouping.push_back(addressInfo);
}
@ -421,17 +421,6 @@ Value getreceivedbyaddress(const Array& params, bool fHelp) @@ -421,17 +421,6 @@ Value getreceivedbyaddress(const Array& params, bool fHelp)
}
void GetAccountAddresses(string strAccount, set<CTxDestination>& setAddress)
{
BOOST_FOREACH(const PAIRTYPE(CTxDestination, string)& item, pwalletMain->mapAddressBook)
{
const CTxDestination& address = item.first;
const string& strName = item.second;
if (strName == strAccount)
setAddress.insert(address);
}
}
Value getreceivedbyaccount(const Array& params, bool fHelp)
{
if (fHelp || params.size() < 1 || params.size() > 2)
@ -446,8 +435,7 @@ Value getreceivedbyaccount(const Array& params, bool fHelp) @@ -446,8 +435,7 @@ Value getreceivedbyaccount(const Array& params, bool fHelp)
// Get the set of pub keys assigned to account
string strAccount = AccountFromValue(params[0]);
set<CTxDestination> setAddress;
GetAccountAddresses(strAccount, setAddress);
set<CTxDestination> setAddress = pwalletMain->GetAccountAddresses(strAccount);
// Tally
int64 nAmount = 0;
@ -780,7 +768,7 @@ Value addmultisigaddress(const Array& params, bool fHelp) @@ -780,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();
}
@ -862,10 +850,10 @@ Value ListReceived(const Array& params, bool fByAccounts) @@ -862,10 +850,10 @@ Value ListReceived(const Array& params, bool fByAccounts)
// Reply
Array ret;
map<string, tallyitem> mapAccountTally;
BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, string)& item, pwalletMain->mapAddressBook)
BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, CAddressBookData)& item, pwalletMain->mapAddressBook)
{
const CBitcoinAddress& address = item.first;
const string& strAccount = item.second;
const string& strAccount = item.second.name;
map<CBitcoinAddress, tallyitem>::iterator it = mapTally.find(address);
if (it == mapTally.end() && !fIncludeEmpty)
continue;
@ -988,7 +976,7 @@ void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDe @@ -988,7 +976,7 @@ void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDe
{
string account;
if (pwalletMain->mapAddressBook.count(r.first))
account = pwalletMain->mapAddressBook[r.first];
account = pwalletMain->mapAddressBook[r.first].name;
if (fAllAccounts || (account == strAccount))
{
Object entry;
@ -1101,9 +1089,9 @@ Value listaccounts(const Array& params, bool fHelp) @@ -1101,9 +1089,9 @@ Value listaccounts(const Array& params, bool fHelp)
nMinDepth = params[0].get_int();
map<string, int64> mapAccountBalances;
BOOST_FOREACH(const PAIRTYPE(CTxDestination, string)& entry, pwalletMain->mapAddressBook) {
BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& entry, pwalletMain->mapAddressBook) {
if (IsMine(*pwalletMain, entry.first)) // This address belongs to me
mapAccountBalances[entry.second] = 0;
mapAccountBalances[entry.second.name] = 0;
}
for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it)
@ -1121,7 +1109,7 @@ Value listaccounts(const Array& params, bool fHelp) @@ -1121,7 +1109,7 @@ Value listaccounts(const Array& params, bool fHelp)
{
BOOST_FOREACH(const PAIRTYPE(CTxDestination, int64)& r, listReceived)
if (pwalletMain->mapAddressBook.count(r.first))
mapAccountBalances[pwalletMain->mapAddressBook[r.first]] += r.second;
mapAccountBalances[pwalletMain->mapAddressBook[r.first].name] += r.second;
else
mapAccountBalances[""] += r.second;
}
@ -1470,7 +1458,7 @@ Value validateaddress(const Array& params, bool fHelp) @@ -1470,7 +1458,7 @@ Value validateaddress(const Array& params, bool fHelp)
ret.insert(ret.end(), detail.begin(), detail.end());
}
if (pwalletMain->mapAddressBook.count(dest))
ret.push_back(Pair("account", pwalletMain->mapAddressBook[dest]));
ret.push_back(Pair("account", pwalletMain->mapAddressBook[dest].name));
}
return ret;
}

29
src/util.cpp

@ -8,6 +8,7 @@ @@ -8,6 +8,7 @@
#ifdef __linux__
#define _POSIX_C_SOURCE 200112L
#endif
#include <algorithm>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/resource.h>
@ -83,7 +84,6 @@ bool fNoListen = false; @@ -83,7 +84,6 @@ bool fNoListen = false;
bool fLogTimestamps = false;
CMedianFilter<int64> vTimeOffsets(200,0);
volatile bool fReopenDebugLog = false;
bool fCachedPath[2] = {false, false};
// Init OpenSSL library multithreading support
static CCriticalSection** ppmutexOpenSSL;
@ -1043,22 +1043,25 @@ boost::filesystem::path GetDefaultDataDir() @@ -1043,22 +1043,25 @@ boost::filesystem::path GetDefaultDataDir()
#endif
}
static boost::filesystem::path pathCached[CChainParams::MAX_NETWORK_TYPES+1];
static CCriticalSection csPathCached;
const boost::filesystem::path &GetDataDir(bool fNetSpecific)
{
namespace fs = boost::filesystem;
static fs::path pathCached[2];
static CCriticalSection csPathCached;
LOCK(csPathCached);
int nNet = CChainParams::MAX_NETWORK_TYPES;
if (fNetSpecific) nNet = Params().NetworkID();
fs::path &path = pathCached[fNetSpecific];
fs::path &path = pathCached[nNet];
// This can be called during exceptions by printf, so we cache the
// value so we don't have to do memory allocations after that.
if (fCachedPath[fNetSpecific])
if (!path.empty())
return path;
LOCK(csPathCached);
if (mapArgs.count("-datadir")) {
path = fs::system_complete(mapArgs["-datadir"]);
if (!fs::is_directory(path)) {
@ -1073,10 +1076,15 @@ const boost::filesystem::path &GetDataDir(bool fNetSpecific) @@ -1073,10 +1076,15 @@ const boost::filesystem::path &GetDataDir(bool fNetSpecific)
fs::create_directories(path);
fCachedPath[fNetSpecific] = true;
return path;
}
void ClearDatadirCache()
{
std::fill(&pathCached[0], &pathCached[CChainParams::MAX_NETWORK_TYPES+1],
boost::filesystem::path());
}
boost::filesystem::path GetConfigFile()
{
boost::filesystem::path pathConfigFile(GetArg("-conf", "bitcoin.conf"));
@ -1091,9 +1099,6 @@ void ReadConfigFile(map<string, string>& mapSettingsRet, @@ -1091,9 +1099,6 @@ void ReadConfigFile(map<string, string>& mapSettingsRet,
if (!streamConfig.good())
return; // No bitcoin.conf file is OK
// clear path cache after loading config file
fCachedPath[0] = fCachedPath[1] = false;
set<string> setOptions;
setOptions.insert("*");
@ -1109,6 +1114,8 @@ void ReadConfigFile(map<string, string>& mapSettingsRet, @@ -1109,6 +1114,8 @@ void ReadConfigFile(map<string, string>& mapSettingsRet,
}
mapMultiSettingsRet[strKey].push_back(it->value[0]);
}
// If datadir is changed in .conf file:
ClearDatadirCache();
}
boost::filesystem::path GetPidFile()

31
src/wallet.cpp

@ -496,7 +496,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn) @@ -496,7 +496,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn)
if (GetKeyFromPool(newDefaultKey, false))
{
SetDefaultKey(newDefaultKey);
SetAddressBookName(vchDefaultKey.GetID(), "");
SetAddressBook(vchDefaultKey.GetID(), "", "receive");
}
}
}
@ -717,8 +717,8 @@ void CWalletTx::GetAccountAmounts(const string& strAccount, int64& nReceived, @@ -717,8 +717,8 @@ void CWalletTx::GetAccountAmounts(const string& strAccount, int64& nReceived,
{
if (pwallet->mapAddressBook.count(r.first))
{
map<CTxDestination, string>::const_iterator mi = pwallet->mapAddressBook.find(r.first);
if (mi != pwallet->mapAddressBook.end() && (*mi).second == strAccount)
map<CTxDestination, CAddressBookData>::const_iterator mi = pwallet->mapAddressBook.find(r.first);
if (mi != pwallet->mapAddressBook.end() && (*mi).second.name == strAccount)
nReceived += r.second;
}
else if (strAccount.empty())
@ -1457,26 +1457,28 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet) @@ -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<CTxDestination, std::string>::iterator mi = mapAddressBook.find(address);
mapAddressBook[address] = strName;
std::map<CTxDestination, CAddressBookData>::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)
{
{
@ -1812,6 +1814,19 @@ set< set<CTxDestination> > CWallet::GetAddressGroupings() @@ -1812,6 +1814,19 @@ set< set<CTxDestination> > CWallet::GetAddressGroupings()
return ret;
}
set<CTxDestination> CWallet::GetAccountAddresses(string strAccount) const
{
set<CTxDestination> result;
BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& item, mapAddressBook)
{
const CTxDestination& address = item.first;
const string& strName = item.second.name;
if (strName == strAccount)
result.insert(address);
}
return result;
}
bool CReserveKey::GetReservedKey(CPubKey& pubkey)
{
if (nIndex == -1)

21
src/wallet.h

@ -64,6 +64,19 @@ public: @@ -64,6 +64,19 @@ public:
)
};
/** Address book data */
class CAddressBookData
{
public:
std::string name;
std::string purpose;
CAddressBookData()
{
purpose = "unknown";
}
};
/** A CWallet is an extension of a keystore, which also maintains a set of transactions and balances,
* and provides the ability to create new transactions.
*/
@ -124,7 +137,7 @@ public: @@ -124,7 +137,7 @@ public:
int64 nOrderPosNext;
std::map<uint256, int> mapRequestCount;
std::map<CTxDestination, std::string> mapAddressBook;
std::map<CTxDestination, CAddressBookData> mapAddressBook;
CPubKey vchDefaultKey;
@ -214,6 +227,8 @@ public: @@ -214,6 +227,8 @@ public:
std::set< std::set<CTxDestination> > GetAddressGroupings();
std::map<CTxDestination, int64> GetAddressBalances();
std::set<CTxDestination> GetAccountAddresses(std::string strAccount) const;
bool IsMine(const CTxIn& txin) const;
int64 GetDebit(const CTxIn& txin) const;
bool IsMine(const CTxOut& txout) const
@ -281,9 +296,9 @@ public: @@ -281,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);

20
src/walletdb.cpp

@ -32,6 +32,18 @@ bool CWalletDB::EraseName(const string& strAddress) @@ -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();
@ -212,7 +224,13 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, @@ -212,7 +224,13 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
{
string strAddress;
ssKey >> strAddress;
ssValue >> pwallet->mapAddressBook[CBitcoinAddress(strAddress).Get()];
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")
{

4
src/walletdb.h

@ -68,9 +68,11 @@ private: @@ -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++;

Loading…
Cancel
Save