mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 03:52:49 +01:00
Payment Protocol: X509-validated payment requests
Add support for a Payment Protocol to Bitcoin-Qt. Payment messages are protocol-buffer encoded and communicated over http(s), so this adds a dependency on the Google protocol buffer library, and requires Qt with OpenSSL support.
This commit is contained in:
parent
47d0534368
commit
a41d5fe019
@ -16,21 +16,29 @@ CONFIG += thread
|
||||
# or when linking against a specific BerkelyDB version: BDB_LIB_SUFFIX=-4.8
|
||||
|
||||
# Dependency library locations can be customized with:
|
||||
# BOOST_INCLUDE_PATH, BOOST_LIB_PATH, BDB_INCLUDE_PATH,
|
||||
# BDB_LIB_PATH, OPENSSL_INCLUDE_PATH and OPENSSL_LIB_PATH respectively
|
||||
# BOOST_INCLUDE_PATH BOOST_LIB_PATH,
|
||||
# BDB_INCLUDE_PATH BDB_LIB_PATH,
|
||||
# OPENSSL_INCLUDE_PATH OPENSSL_LIB_PATH
|
||||
# PROTOBUF_INCLUDE_PATH PROTOBUF_LIB_PATH
|
||||
# PROTOC : protocol buffer compiler tool
|
||||
|
||||
OBJECTS_DIR = build
|
||||
MOC_DIR = build
|
||||
UI_DIR = build
|
||||
PROTO_DIR = build
|
||||
PROTO_PATH = src/qt
|
||||
|
||||
contains(BITCOIN_QT_TEST, 1) {
|
||||
OBJECTS_DIR = build_test
|
||||
MOC_DIR = build_test
|
||||
UI_DIR = build_test
|
||||
PROTO_DIR = build_test
|
||||
|
||||
SOURCES += src/qt/test/test_main.cpp \
|
||||
src/qt/test/uritests.cpp
|
||||
HEADERS += src/qt/test/uritests.h
|
||||
src/qt/test/uritests.cpp \
|
||||
src/qt/test/paymentservertests.cpp
|
||||
HEADERS += src/qt/test/uritests.h \
|
||||
src/qt/test/paymentservertests.h
|
||||
DEPENDPATH += src/qt/test
|
||||
QT += testlib
|
||||
TARGET = bitcoin-qt_test
|
||||
@ -219,6 +227,7 @@ HEADERS += src/qt/bitcoingui.h \
|
||||
src/qt/askpassphrasedialog.h \
|
||||
src/protocol.h \
|
||||
src/qt/notificator.h \
|
||||
src/qt/paymentrequestplus.h \
|
||||
src/qt/paymentserver.h \
|
||||
src/allocators.h \
|
||||
src/ui_interface.h \
|
||||
@ -297,6 +306,7 @@ SOURCES += src/qt/bitcoin.cpp \
|
||||
src/qt/askpassphrasedialog.cpp \
|
||||
src/protocol.cpp \
|
||||
src/qt/notificator.cpp \
|
||||
src/qt/paymentrequestplus.cpp \
|
||||
src/qt/paymentserver.cpp \
|
||||
src/qt/rpcconsole.cpp \
|
||||
src/noui.cpp \
|
||||
@ -320,13 +330,15 @@ FORMS += src/qt/forms/sendcoinsdialog.ui \
|
||||
src/qt/forms/optionsdialog.ui \
|
||||
src/qt/forms/intro.ui
|
||||
|
||||
PROTOS = src/qt/paymentrequest.proto
|
||||
include(share/qt/protobuf.pri)
|
||||
|
||||
contains(USE_QRCODE, 1) {
|
||||
HEADERS += src/qt/qrcodedialog.h
|
||||
SOURCES += src/qt/qrcodedialog.cpp
|
||||
FORMS += src/qt/forms/qrcodedialog.ui
|
||||
}
|
||||
|
||||
|
||||
# Todo: Remove this line when switching to Qt5, as that option was removed
|
||||
CODECFORTR = UTF-8
|
||||
|
||||
@ -420,9 +432,9 @@ macx:QMAKE_CXXFLAGS_THREAD += -pthread
|
||||
macx:QMAKE_INFO_PLIST = share/qt/Info.plist
|
||||
|
||||
# Set libraries and includes at end, to use platform-defined defaults if not overridden
|
||||
INCLUDEPATH += $$BOOST_INCLUDE_PATH $$BDB_INCLUDE_PATH $$OPENSSL_INCLUDE_PATH $$QRENCODE_INCLUDE_PATH
|
||||
LIBS += $$join(BOOST_LIB_PATH,,-L,) $$join(BDB_LIB_PATH,,-L,) $$join(OPENSSL_LIB_PATH,,-L,) $$join(QRENCODE_LIB_PATH,,-L,)
|
||||
LIBS += -lssl -lcrypto -ldb_cxx$$BDB_LIB_SUFFIX
|
||||
INCLUDEPATH += $$BOOST_INCLUDE_PATH $$BDB_INCLUDE_PATH $$OPENSSL_INCLUDE_PATH $$PROTOBUF_INCLUDE_PATH $$QRENCODE_INCLUDE_PATH
|
||||
LIBS += $$join(BOOST_LIB_PATH,,-L,) $$join(BDB_LIB_PATH,,-L,) $$join(OPENSSL_LIB_PATH,,-L,) $$join(PROTOBUF_LIB_PATH,,-L,) $$join(QRENCODE_LIB_PATH,,-L,)
|
||||
LIBS += -lssl -lcrypto -ldb_cxx$$BDB_LIB_SUFFIX -lprotobuf
|
||||
# -lgdi32 has to happen after -lcrypto (see #681)
|
||||
win32:LIBS += -lws2_32 -lshlwapi -lmswsock -lole32 -loleaut32 -luuid -lgdi32
|
||||
LIBS += -lboost_system$$BOOST_LIB_SUFFIX -lboost_filesystem$$BOOST_LIB_SUFFIX -lboost_program_options$$BOOST_LIB_SUFFIX -lboost_thread$$BOOST_THREAD_LIB_SUFFIX
|
||||
|
@ -32,6 +32,7 @@ Once you've got the right hardware and software:
|
||||
wget 'https://downloads.sourceforge.net/project/libpng/zlib/1.2.6/zlib-1.2.6.tar.gz'
|
||||
wget 'https://downloads.sourceforge.net/project/libpng/libpng15/older-releases/1.5.9/libpng-1.5.9.tar.gz'
|
||||
wget 'http://releases.qt-project.org/qt4/source/qt-everywhere-opensource-src-4.8.3.tar.gz'
|
||||
wget 'http://protobuf.googlecode.com/files/protobuf-2.5.0.tar.bz2'
|
||||
cd ../..
|
||||
|
||||
cd gitian-builder
|
||||
@ -50,6 +51,7 @@ Once you've got the right hardware and software:
|
||||
./bin/gbuild --commit bitcoin=HEAD ../bitcoin/contrib/gitian-descriptors/boost-win32.yml
|
||||
./bin/gbuild --commit bitcoin=HEAD ../bitcoin/contrib/gitian-descriptors/deps-win32.yml
|
||||
./bin/gbuild --commit bitcoin=HEAD ../bitcoin/contrib/gitian-descriptors/qt-win32.yml
|
||||
./bin/gbuild --commit bitcoin=HEAD ../bitcoin/contrib/gitian-descriptors/protobuf-win32.yml
|
||||
|
||||
# Build Win32 release:
|
||||
./bin/gbuild --commit bitcoin=HEAD ../bitcoin/contrib/gitian-descriptors/gitian-win32.yml
|
||||
|
@ -15,16 +15,18 @@ remotes:
|
||||
- "url": "https://github.com/bitcoin/bitcoin.git"
|
||||
"dir": "bitcoin"
|
||||
files:
|
||||
- "qt-win32-4.8.3-gitian-r1.zip"
|
||||
- "qt-win32-4.8.3-gitian-r2.zip"
|
||||
- "boost-win32-1.50.0-gitian2.zip"
|
||||
- "bitcoin-deps-0.0.5.zip"
|
||||
- "protobuf-win32-2.5.0-gitian-r1.zip"
|
||||
script: |
|
||||
#
|
||||
mkdir $HOME/qt
|
||||
cd $HOME/qt
|
||||
unzip ../build/qt-win32-4.8.3-gitian-r1.zip
|
||||
export QTDIR=$HOME/qt
|
||||
mkdir $QTDIR
|
||||
cd $QTDIR
|
||||
unzip ../build/qt-win32-4.8.3-gitian-r2.zip
|
||||
cd $HOME/build/
|
||||
export PATH=$HOME/qt/bin/:$PATH
|
||||
export PATH=$QTDIR/bin/:$PATH
|
||||
#
|
||||
mkdir boost_1_50_0
|
||||
cd boost_1_50_0
|
||||
@ -41,6 +43,8 @@ script: |
|
||||
#
|
||||
unzip bitcoin-deps-0.0.5.zip
|
||||
#
|
||||
unzip protobuf-win32-2.5.0-gitian-r1.zip
|
||||
#
|
||||
find -type f | xargs touch --date="$REFERENCE_DATETIME"
|
||||
#
|
||||
cd bitcoin
|
||||
@ -51,7 +55,7 @@ script: |
|
||||
export LD_PRELOAD=/usr/lib/faketime/libfaketime.so.1
|
||||
export FAKETIME=$REFERENCE_DATETIME
|
||||
export TZ=UTC
|
||||
$HOME/qt/src/bin/qmake -spec unsupported/win32-g++-cross MINIUPNPC_LIB_PATH=$HOME/build/miniupnpc MINIUPNPC_INCLUDE_PATH=$HOME/build/ BDB_LIB_PATH=$HOME/build/db-4.8.30.NC/build_unix BDB_INCLUDE_PATH=$HOME/build/db-4.8.30.NC/build_unix BOOST_LIB_PATH=$HOME/build/boost_1_50_0/stage/lib BOOST_INCLUDE_PATH=$HOME/build/boost_1_50_0 BOOST_LIB_SUFFIX=-mt-s BOOST_THREAD_LIB_SUFFIX=_win32-mt-s OPENSSL_LIB_PATH=$HOME/build/openssl-1.0.1c OPENSSL_INCLUDE_PATH=$HOME/build/openssl-1.0.1c/include QRENCODE_LIB_PATH=$HOME/build/qrencode-3.2.0/.libs QRENCODE_INCLUDE_PATH=$HOME/build/qrencode-3.2.0 USE_QRCODE=1 INCLUDEPATH=$HOME/build DEFINES=BOOST_THREAD_USE_LIB BITCOIN_NEED_QT_PLUGINS=1 QMAKE_LRELEASE=lrelease QMAKE_CXXFLAGS=-frandom-seed=bitcoin USE_BUILD_INFO=1
|
||||
$QTDIR/bin/qmake -spec unsupported/win32-g++-cross PROTOBUF_LIB_PATH=$HOME/build/protobuf-win32 PROTOBUF_INCLUDE_PATH=$HOME/build/protobuf-win32 PROTOC=$HOME/build/protobuf-win32/protoc MINIUPNPC_LIB_PATH=$HOME/build/miniupnpc MINIUPNPC_INCLUDE_PATH=$HOME/build/ BDB_LIB_PATH=$HOME/build/db-4.8.30.NC/build_unix BDB_INCLUDE_PATH=$HOME/build/db-4.8.30.NC/build_unix BOOST_LIB_PATH=$HOME/build/boost_1_50_0/stage/lib BOOST_INCLUDE_PATH=$HOME/build/boost_1_50_0 BOOST_LIB_SUFFIX=-mt-s BOOST_THREAD_LIB_SUFFIX=_win32-mt-s OPENSSL_LIB_PATH=$HOME/build/openssl-1.0.1c OPENSSL_INCLUDE_PATH=$HOME/build/openssl-1.0.1c/include QRENCODE_LIB_PATH=$HOME/build/qrencode-3.2.0/.libs QRENCODE_INCLUDE_PATH=$HOME/build/qrencode-3.2.0 USE_QRCODE=1 INCLUDEPATH=$HOME/build DEFINES=BOOST_THREAD_USE_LIB BITCOIN_NEED_QT_PLUGINS=1 QMAKE_LRELEASE=lrelease QMAKE_CXXFLAGS=-frandom-seed=bitcoin USE_BUILD_INFO=1
|
||||
make $MAKEOPTS
|
||||
cp release/bitcoin-qt.exe $OUTDIR/
|
||||
#
|
||||
|
@ -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
Normal file
37
contrib/gitian-descriptors/protobuf-win32.yml
Normal file
@ -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
|
@ -12,11 +12,14 @@ reference_datetime: "2011-01-30 00:00:00"
|
||||
remotes: []
|
||||
files:
|
||||
- "qt-everywhere-opensource-src-4.8.3.tar.gz"
|
||||
- "bitcoin-deps-0.0.5.zip"
|
||||
script: |
|
||||
INSTDIR="$HOME/qt/"
|
||||
mkdir $INSTDIR
|
||||
SRCDIR="$INSTDIR/src/"
|
||||
mkdir $SRCDIR
|
||||
#
|
||||
# Need mingw-compiled openssl from bitcoin-deps:
|
||||
unzip bitcoin-deps-0.0.5.zip
|
||||
DEPSDIR=`pwd`
|
||||
#
|
||||
tar xzf qt-everywhere-opensource-src-4.8.3.tar.gz
|
||||
cd qt-everywhere-opensource-src-4.8.3
|
||||
@ -40,15 +43,14 @@ script: |
|
||||
#export LD_PRELOAD=/usr/lib/faketime/libfaketime.so.1
|
||||
export FAKETIME=$REFERENCE_DATETIME
|
||||
export TZ=UTC
|
||||
./configure -prefix $INSTDIR -confirm-license -release -opensource -static -no-qt3support -xplatform unsupported/win32-g++-cross -no-multimedia -no-audio-backend -no-phonon -no-phonon-backend -no-declarative -no-script -no-scripttools -no-javascript-jit -no-webkit -no-svg -no-xmlpatterns -no-sql-sqlite -no-nis -no-cups -no-iconv -no-dbus -no-gif -no-libtiff -no-opengl -nomake examples -nomake demos -nomake docs -no-feature-style-plastique -no-feature-style-cleanlooks -no-feature-style-motif -no-feature-style-cde -no-feature-style-windowsce -no-feature-style-windowsmobile -no-feature-style-s60
|
||||
# Compile static libraries, and use statically linked openssl (-openssl-linked):
|
||||
OPENSSL_LIBS="-L$DEPSDIR/openssl-1.0.1c -lssl -lcrypto -lgdi32" ./configure -prefix $INSTDIR -I $DEPSDIR/openssl-1.0.1c/include -confirm-license -release -opensource -static -no-qt3support -xplatform unsupported/win32-g++-cross -no-multimedia -no-audio-backend -no-phonon -no-phonon-backend -no-declarative -no-script -no-scripttools -no-javascript-jit -no-webkit -no-svg -no-xmlpatterns -no-sql-sqlite -no-nis -no-cups -no-iconv -no-dbus -no-gif -no-libtiff -no-opengl -nomake examples -nomake demos -nomake docs -no-feature-style-plastique -no-feature-style-cleanlooks -no-feature-style-motif -no-feature-style-cde -no-feature-style-windowsce -no-feature-style-windowsmobile -no-feature-style-s60 -openssl-linked
|
||||
find . -name *.prl | xargs -l sed 's|/\.||' -i
|
||||
find . -name *.prl | xargs -l sed 's|/$||' -i
|
||||
make $MAKEOPTS install
|
||||
cp -a bin $SRCDIR/
|
||||
cd $INSTDIR
|
||||
find . -name *.prl | xargs -l sed 's|/$||' -i
|
||||
#sed 's|QMAKE_PRL_LIBS.*|QMAKE_PRL_LIBS = -lQtDeclarative -lQtScript -lQtSvg -lQtSql -lQtXmlPatterns -lQtGui -lgdi32 -lcomdlg32 -loleaut32 -limm32 -lwinmm -lwinspool -lmsimg32 -lQtNetwork -lQtCore -lole32 -luuid -lws2_32 -ladvapi32 -lshell32 -luser32 -lkernel32|' -i imports/Qt/labs/particles/qmlparticlesplugin.prl
|
||||
|
||||
# as zip stores file timestamps, use faketime to intercept stat calls to set dates for all files to reference date
|
||||
export LD_PRELOAD=/usr/lib/faketime/libfaketime.so.1
|
||||
zip -r $OUTDIR/qt-win32-4.8.3-gitian-r1.zip *
|
||||
zip -r $OUTDIR/qt-win32-4.8.3-gitian-r2.zip *
|
||||
|
@ -18,13 +18,13 @@ for Debian and Ubuntu <= 11.10 :
|
||||
|
||||
apt-get install qt4-qmake libqt4-dev build-essential libboost-dev libboost-system-dev \
|
||||
libboost-filesystem-dev libboost-program-options-dev libboost-thread-dev \
|
||||
libssl-dev libdb4.8++-dev
|
||||
libssl-dev libdb4.8++-dev libprotobuf-dev protobuf-compiler
|
||||
|
||||
for Ubuntu >= 12.04 (please read the 'Berkely DB version warning' below):
|
||||
|
||||
apt-get install qt4-qmake libqt4-dev build-essential libboost-dev libboost-system-dev \
|
||||
libboost-filesystem-dev libboost-program-options-dev libboost-thread-dev \
|
||||
libssl-dev libdb++-dev libminiupnpc-dev
|
||||
libssl-dev libdb++-dev libminiupnpc-dev libprotobuf-dev protobuf-compiler
|
||||
|
||||
For Qt 5 you need the following, otherwise you get an error with lrelease when running qmake:
|
||||
|
||||
@ -48,12 +48,12 @@ An executable named `bitcoin-qt` will be built.
|
||||
* Execute the following commands in a terminal to get the dependencies using MacPorts
|
||||
|
||||
sudo port selfupdate
|
||||
sudo port install boost db48 miniupnpc
|
||||
sudo port install boost db48 miniupnpc protobuf-cpp
|
||||
|
||||
* Execute the following commands in a terminal to get the dependencies using HomeBrew:
|
||||
|
||||
brew update
|
||||
brew install boost miniupnpc openssl berkeley-db4
|
||||
brew install boost miniupnpc openssl berkeley-db4 protobuf
|
||||
|
||||
- If using HomeBrew, edit `bitcoin-qt.pro` to account for library location differences. There's a diff in `contrib/homebrew/bitcoin-qt-pro.patch` that shows what you need to change, or you can just patch by doing
|
||||
|
||||
|
@ -18,19 +18,57 @@
|
||||
<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.bitcoinfoundation.BitcoinPayment</string>
|
||||
<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>
|
||||
<string>bitcoinpaymentrequest</string>
|
||||
</array>
|
||||
</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>
|
||||
|
35
share/qt/protobuf.pri
Normal file
35
share/qt/protobuf.pri
Normal file
@ -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
|
@ -895,7 +895,7 @@ bool AppInit2(boost::thread_group& threadGroup)
|
||||
CPubKey newDefaultKey;
|
||||
if (pwalletMain->GetKeyFromPool(newDefaultKey, false)) {
|
||||
pwalletMain->SetDefaultKey(newDefaultKey);
|
||||
if (!pwalletMain->SetAddressBookName(pwalletMain->vchDefaultKey.GetID(), ""))
|
||||
if (!pwalletMain->SetAddressBook(pwalletMain->vchDefaultKey.GetID(), "", "receive"))
|
||||
strErrors << _("Cannot write default address") << "\n";
|
||||
}
|
||||
|
||||
|
@ -62,9 +62,19 @@ public:
|
||||
BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& item, wallet->mapAddressBook)
|
||||
{
|
||||
const CBitcoinAddress& address = item.first;
|
||||
const std::string& strName = item.second.name;
|
||||
|
||||
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());
|
||||
cachedAddressTable.append(AddressTableEntry(fMine ? AddressTableEntry::Receiving : AddressTableEntry::Sending,
|
||||
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,
|
||||
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,
|
||||
editStatus = NO_CHANGES;
|
||||
return false;
|
||||
}
|
||||
wallet->SetAddressBookName(CBitcoinAddress(rec->address.toStdString()).Get(), value.toString().toStdString());
|
||||
wallet->SetAddressBook(CBitcoinAddress(rec->address.toStdString()).Get(), value.toString().toStdString(), strPurpose);
|
||||
break;
|
||||
case Address:
|
||||
// Do nothing, if old address == new address
|
||||
@ -257,9 +267,9 @@ bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value,
|
||||
{
|
||||
LOCK(wallet->cs_wallet);
|
||||
// Remove old entry
|
||||
wallet->DelAddressBookName(CBitcoinAddress(rec->address.toStdString()).Get());
|
||||
wallet->DelAddressBook(CBitcoinAddress(rec->address.toStdString()).Get());
|
||||
// Add new entry with new address
|
||||
wallet->SetAddressBookName(CBitcoinAddress(value.toString().toStdString()).Get(), rec->label.toStdString());
|
||||
wallet->SetAddressBook(CBitcoinAddress(value.toString().toStdString()).Get(), rec->label.toStdString(), strPurpose);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -368,7 +378,8 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con
|
||||
// Add entry
|
||||
{
|
||||
LOCK(wallet->cs_wallet);
|
||||
wallet->SetAddressBookName(CBitcoinAddress(strAddress).Get(), strLabel);
|
||||
wallet->SetAddressBook(CBitcoinAddress(strAddress).Get(), strLabel,
|
||||
(type == Send ? "send" : "receive"));
|
||||
}
|
||||
return QString::fromStdString(strAddress);
|
||||
}
|
||||
@ -385,7 +396,7 @@ bool AddressTableModel::removeRows(int row, int count, const QModelIndex &parent
|
||||
}
|
||||
{
|
||||
LOCK(wallet->cs_wallet);
|
||||
wallet->DelAddressBookName(CBitcoinAddress(rec->address.toStdString()).Get());
|
||||
wallet->DelAddressBook(CBitcoinAddress(rec->address.toStdString()).Get());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -213,7 +213,7 @@ int main(int argc, char *argv[])
|
||||
// Do this early as we don't want to bother initializing if we are just calling IPC
|
||||
// ... but do it after creating app and setting up translations, so errors are
|
||||
// translated properly.
|
||||
if (PaymentServer::ipcSendCommandLine())
|
||||
if (PaymentServer::ipcSendCommandLine(argc, argv))
|
||||
exit(0);
|
||||
|
||||
// Now that translations are initialized check for errors and allow a translatable error message
|
||||
@ -299,6 +299,9 @@ int main(int argc, char *argv[])
|
||||
|
||||
optionsModel.Upgrade(); // Must be done after AppInit2
|
||||
|
||||
PaymentServer::LoadRootCAs();
|
||||
paymentServer->initNetManager(optionsModel);
|
||||
|
||||
if (splashref)
|
||||
splash.finish(&window);
|
||||
|
||||
@ -320,8 +323,15 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
|
||||
// Now that initialization/startup is done, process any command-line
|
||||
// bitcoin: URIs
|
||||
QObject::connect(paymentServer, SIGNAL(receivedURI(QString)), &window, SLOT(handleURI(QString)));
|
||||
// bitcoin: URIs or payment requests:
|
||||
QObject::connect(paymentServer, SIGNAL(receivedPaymentRequest(SendCoinsRecipient)),
|
||||
&window, SLOT(handlePaymentRequest(SendCoinsRecipient)));
|
||||
QObject::connect(&walletModel, SIGNAL(coinsSent(CWallet*,SendCoinsRecipient,QByteArray)),
|
||||
paymentServer, SLOT(fetchPaymentACK(CWallet*,const SendCoinsRecipient&,QByteArray)));
|
||||
QObject::connect(paymentServer, SIGNAL(receivedPaymentACK(QString)),
|
||||
&window, SLOT(showPaymentACK(QString)));
|
||||
QObject::connect(paymentServer, SIGNAL(reportError(QString, QString, unsigned int)),
|
||||
guiref, SLOT(message(QString, QString, unsigned int)));
|
||||
QTimer::singleShot(100, paymentServer, SLOT(uiReady()));
|
||||
|
||||
app.exec();
|
||||
|
@ -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
|
||||
|
@ -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. */
|
||||
|
@ -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)
|
||||
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)
|
||||
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)
|
||||
|
@ -15,6 +15,7 @@ class TransactionView;
|
||||
class OverviewPage;
|
||||
class AddressBookPage;
|
||||
class SendCoinsDialog;
|
||||
class SendCoinsRecipient;
|
||||
class SignVerifyMessageDialog;
|
||||
class Notificator;
|
||||
class RPCConsole;
|
||||
@ -151,7 +152,9 @@ public slots:
|
||||
@param[out] payFee true to pay the fee, false to not pay the fee
|
||||
*/
|
||||
void askFee(qint64 nFeeRequired, bool *payFee);
|
||||
void handleURI(QString strURI);
|
||||
|
||||
void handlePaymentRequest(const SendCoinsRecipient& recipient);
|
||||
void showPaymentACK(QString msg);
|
||||
|
||||
/** Show incoming transaction notification for new transactions. */
|
||||
void incomingTransaction(const QString& date, int unit, qint64 amount, const QString& type, const QString& address);
|
||||
|
@ -1,15 +1,25 @@
|
||||
<?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>StackedWidget</string>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<widget class="QFrame" name="SendCoinsInsecure">
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
@ -139,6 +149,538 @@
|
||||
</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&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 &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>
|
||||
<class>BitcoinAmountField</class>
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
Normal file
46
src/qt/paymentrequest.proto
Normal file
@ -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
Normal file
204
src/qt/paymentrequestplus.cpp
Normal file
@ -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
Normal file
41
src/qt/paymentrequestplus.h
Normal file
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
//
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
static QStringList savedPaymentRequests;
|
||||
// Load openSSL's list of root certificate authorities
|
||||
//
|
||||
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;
|
||||
// and the items in savedPaymentRequest will be handled
|
||||
// when uiReady() is called.
|
||||
//
|
||||
bool PaymentServer::ipcSendCommandLine()
|
||||
bool PaymentServer::ipcSendCommandLine(int argc, char* argv[])
|
||||
{
|
||||
bool fResult = false;
|
||||
|
||||
const QStringList& args = qApp->arguments();
|
||||
for (int i = 1; i < args.size(); i++)
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
if (!args[i].startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive))
|
||||
QString arg(argv[i]);
|
||||
if (arg.startsWith("-"))
|
||||
continue;
|
||||
savedPaymentRequests.append(args[i]);
|
||||
|
||||
if (arg.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // bitcoin:
|
||||
{
|
||||
savedPaymentRequests.append(arg);
|
||||
|
||||
SendCoinsRecipient r;
|
||||
if (GUIUtil::parseBitcoinURI(arg, &r))
|
||||
{
|
||||
CBitcoinAddress address(r.address.toStdString());
|
||||
|
||||
SelectParams(CChainParams::MAIN);
|
||||
if (!address.IsValid())
|
||||
{
|
||||
SelectParams(CChainParams::TESTNET);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (QFile::exists(arg)) // Filename
|
||||
{
|
||||
savedPaymentRequests.append(arg);
|
||||
|
||||
PaymentRequestPlus request;
|
||||
if (readPaymentRequest(arg, request))
|
||||
{
|
||||
if (request.getDetails().network() == "main")
|
||||
SelectParams(CChainParams::MAIN);
|
||||
else
|
||||
SelectParams(CChainParams::TESTNET);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Payment request file does not exist: " << argv[i];
|
||||
// Printing to debug.log is about the best we can do here, the
|
||||
// GUI hasn't started yet so we can't pop up a message box.
|
||||
}
|
||||
}
|
||||
|
||||
foreach (const QString& arg, savedPaymentRequests)
|
||||
foreach (const QString& r, savedPaymentRequests)
|
||||
{
|
||||
QLocalSocket* socket = new QLocalSocket();
|
||||
socket->connectToServer(ipcServerName(), QIODevice::WriteOnly);
|
||||
@ -79,7 +235,7 @@ bool PaymentServer::ipcSendCommandLine()
|
||||
QByteArray block;
|
||||
QDataStream out(&block, QIODevice::WriteOnly);
|
||||
out.setVersion(QDataStream::Qt_4_0);
|
||||
out << arg;
|
||||
out << r;
|
||||
out.device()->seek(0);
|
||||
socket->write(block);
|
||||
socket->flush();
|
||||
@ -92,9 +248,15 @@ 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)
|
||||
if (parent)
|
||||
parent->installEventFilter(this);
|
||||
|
||||
QString name = ipcServerName();
|
||||
@ -102,40 +264,132 @@ PaymentServer::PaymentServer(QApplication* parent) : QObject(parent), saveURIs(t
|
||||
// Clean up old socket leftover from a crash:
|
||||
QLocalServer::removeServer(name);
|
||||
|
||||
if (startLocalServer)
|
||||
{
|
||||
uriServer = new QLocalServer(this);
|
||||
|
||||
if (!uriServer->listen(name))
|
||||
qDebug() << tr("Cannot start bitcoin: click-to-pay handler");
|
||||
qDebug() << "Cannot start bitcoin: click-to-pay handler";
|
||||
else
|
||||
connect(uriServer, SIGNAL(newConnection()), this, SLOT(handleURIConnection()));
|
||||
}
|
||||
|
||||
bool PaymentServer::eventFilter(QObject *object, QEvent *event)
|
||||
// netManager is null until uiReady() is called
|
||||
netManager = NULL;
|
||||
}
|
||||
|
||||
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());
|
||||
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()
|
||||
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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
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())
|
||||
{
|
||||
CBitcoinAddress address(rv.address.toStdString());
|
||||
if (!address.IsValid())
|
||||
QMessageBox::warning(this, tr("Send Coins"),
|
||||
tr("Payment request expired"));
|
||||
return false;
|
||||
pasteEntry(rv);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
CBitcoinAddress address(rv.address.toStdString());
|
||||
if (!address.IsValid()) {
|
||||
QString strAddress(address.ToString().c_str());
|
||||
QMessageBox::warning(this, tr("Send Coins"),
|
||||
tr("Invalid payment address %1").arg(strAddress));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
pasteEntry(rv);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SendCoinsDialog::setBalance(qint64 balance, qint64 unconfirmedBalance, qint64 immatureBalance)
|
||||
|
@ -2,6 +2,7 @@
|
||||
#define SENDCOINSDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QVariant>
|
||||
|
||||
namespace Ui {
|
||||
class SendCoinsDialog;
|
||||
@ -31,7 +32,7 @@ public:
|
||||
|
||||
void setAddress(const QString &address);
|
||||
void pasteEntry(const SendCoinsRecipient &rv);
|
||||
bool handleURI(const QString &uri);
|
||||
bool handlePaymentRequest(const SendCoinsRecipient &recipient);
|
||||
|
||||
public slots:
|
||||
void clear();
|
||||
|
@ -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,6 +103,9 @@ bool SendCoinsEntry::validate()
|
||||
// Check input validity
|
||||
bool retval = true;
|
||||
|
||||
if (!recipient.authenticatedMerchant.isEmpty())
|
||||
return retval;
|
||||
|
||||
if(!ui->payTo->hasAcceptableInput() ||
|
||||
(model && !model->validateAddress(ui->payTo->text())))
|
||||
{
|
||||
@ -124,13 +129,15 @@ bool SendCoinsEntry::validate()
|
||||
|
||||
SendCoinsRecipient SendCoinsEntry::getValue()
|
||||
{
|
||||
SendCoinsRecipient rv;
|
||||
if (!recipient.authenticatedMerchant.isEmpty())
|
||||
return recipient;
|
||||
|
||||
rv.address = ui->payTo->text();
|
||||
rv.label = ui->addAsLabel->text();
|
||||
rv.amount = ui->payAmount->value();
|
||||
// User-entered or non-authenticated:
|
||||
recipient.address = ui->payTo->text();
|
||||
recipient.label = ui->addAsLabel->text();
|
||||
recipient.amount = ui->payAmount->value();
|
||||
|
||||
return rv;
|
||||
return recipient;
|
||||
}
|
||||
|
||||
QWidget *SendCoinsEntry::setupTabChain(QWidget *prev)
|
||||
@ -145,9 +152,22 @@ QWidget *SendCoinsEntry::setupTabChain(QWidget *prev)
|
||||
|
||||
void SendCoinsEntry::setValue(const SendCoinsRecipient &value)
|
||||
{
|
||||
recipient = value;
|
||||
|
||||
ui->payTo->setText(value.address);
|
||||
ui->addAsLabel->setText(value.label);
|
||||
ui->payAmount->setValue(value.amount);
|
||||
|
||||
if (!recipient.authenticatedMerchant.isEmpty())
|
||||
{
|
||||
const payments::PaymentDetails& details = value.paymentRequest.getDetails();
|
||||
|
||||
ui->payTo_s->setText(value.authenticatedMerchant);
|
||||
ui->memo_s->setTextFormat(Qt::PlainText);
|
||||
ui->memo_s->setText(QString::fromStdString(details.memo()));
|
||||
ui->payAmount_s->setValue(value.amount);
|
||||
setCurrentWidget(ui->SendCoinsSecure);
|
||||
}
|
||||
}
|
||||
|
||||
void SendCoinsEntry::setAddress(const QString &address)
|
||||
@ -158,7 +178,7 @@ void SendCoinsEntry::setAddress(const QString &address)
|
||||
|
||||
bool SendCoinsEntry::isClear()
|
||||
{
|
||||
return ui->payTo->text().isEmpty();
|
||||
return ui->payTo->text().isEmpty() && ui->payTo_s->text().isEmpty();
|
||||
}
|
||||
|
||||
void SendCoinsEntry::setFocus()
|
||||
@ -172,5 +192,6 @@ void SendCoinsEntry::updateDisplayUnit()
|
||||
{
|
||||
// Update payAmount with the current unit
|
||||
ui->payAmount->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
|
||||
ui->payAmount_s->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
|
||||
}
|
||||
}
|
||||
|
@ -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:
|
||||
void updateDisplayUnit();
|
||||
|
||||
private:
|
||||
SendCoinsRecipient recipient;
|
||||
Ui::SendCoinsEntry *ui;
|
||||
WalletModel *model;
|
||||
};
|
||||
|
307
src/qt/test/paymentrequestdata.h
Normal file
307
src/qt/test/paymentrequestdata.h
Normal file
@ -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
Normal file
109
src/qt/test/paymentservertests.cpp
Normal file
@ -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
Normal file
29
src/qt/test/paymentservertests.h
Normal file
@ -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
|
@ -2,6 +2,7 @@
|
||||
#include <QObject>
|
||||
|
||||
#include "uritests.h"
|
||||
#include "paymentservertests.h"
|
||||
|
||||
// This is all you need to run all the tests
|
||||
int main(int argc, char *argv[])
|
||||
@ -12,5 +13,9 @@ int main(int argc, char *argv[])
|
||||
if (QTest::qExec(&test1) != 0)
|
||||
fInvalid = true;
|
||||
|
||||
PaymentServerTests test2;
|
||||
if (QTest::qExec(&test2) != 0)
|
||||
fInvalid = true;
|
||||
|
||||
return fInvalid;
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "db.h"
|
||||
#include "ui_interface.h"
|
||||
#include "base58.h"
|
||||
#include "paymentserver.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
@ -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>";
|
||||
|
||||
|
@ -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)
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
class BitcoinGUI;
|
||||
class ClientModel;
|
||||
class SendCoinsRecipient;
|
||||
class WalletModel;
|
||||
class WalletStack;
|
||||
|
||||
@ -29,7 +30,7 @@ public:
|
||||
|
||||
void removeAllWallets();
|
||||
|
||||
bool handleURI(const QString &uri);
|
||||
bool handlePaymentRequest(const SendCoinsRecipient& recipient);
|
||||
|
||||
void showOutOfSyncWarning(bool fShow);
|
||||
|
||||
|
@ -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 (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;
|
||||
}
|
||||
else
|
||||
{ // User-entered bitcoin address / amount:
|
||||
if(!validateAddress(rcp.address))
|
||||
{
|
||||
return InvalidAddress;
|
||||
}
|
||||
setAddress.insert(rcp.address);
|
||||
|
||||
if(rcp.amount <= 0)
|
||||
{
|
||||
return InvalidAmount;
|
||||
}
|
||||
setAddress.insert(rcp.address);
|
||||
++nAddresses;
|
||||
|
||||
CScript scriptPubKey;
|
||||
scriptPubKey.SetDestination(CBitcoinAddress(rcp.address.toStdString()).Get());
|
||||
vecSend.push_back(std::pair<CScript, int64>(scriptPubKey, 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
|
||||
{
|
||||
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
|
||||
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
|
||||
{
|
||||
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();
|
||||
@ -217,14 +254,19 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList<SendCoinsRecipie
|
||||
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.name != strLabel)
|
||||
if (mi == wallet->mapAddressBook.end())
|
||||
{
|
||||
wallet->SetAddressBookName(dest, strLabel);
|
||||
wallet->SetAddressBook(dest, strLabel, "send");
|
||||
}
|
||||
else if (mi->second.name != 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()
|
||||
|
@ -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
|
||||
class SendCoinsRecipient
|
||||
{
|
||||
public:
|
||||
SendCoinsRecipient() : amount(0) { }
|
||||
|
||||
QString address;
|
||||
QString label;
|
||||
qint64 amount;
|
||||
|
||||
// If from a payment request, paymentRequest.IsInitialized() will be true
|
||||
PaymentRequestPlus paymentRequest;
|
||||
QString authenticatedMerchant; // Empty if no authentication or invalid signature/cert/etc.
|
||||
};
|
||||
|
||||
/** Interface to Bitcoin wallet from Qt view code. */
|
||||
@ -68,12 +75,10 @@ public:
|
||||
struct SendCoinsReturn
|
||||
{
|
||||
SendCoinsReturn(StatusCode status,
|
||||
qint64 fee=0,
|
||||
QString hex=QString()):
|
||||
status(status), fee(fee), hex(hex) {}
|
||||
qint64 fee=0):
|
||||
status(status), fee(fee) {}
|
||||
StatusCode status;
|
||||
qint64 fee; // is used in case status is "AmountWithFeeExceedsBalance"
|
||||
QString hex; // is filled with the transaction hash if status is "OK"
|
||||
};
|
||||
|
||||
// Send coins to a list of recipients
|
||||
@ -151,6 +156,9 @@ signals:
|
||||
// Asynchronous message notification
|
||||
void message(const QString &title, const QString &message, unsigned int style);
|
||||
|
||||
// Coins sent: from wallet, to recipient, in (serialized) transaction:
|
||||
void coinsSent(CWallet* wallet, SendCoinsRecipient recipient, QByteArray transaction);
|
||||
|
||||
public slots:
|
||||
/* Wallet status might have changed */
|
||||
void updateStatus();
|
||||
|
@ -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)
|
||||
|
@ -20,6 +20,7 @@ class TransactionView;
|
||||
class OverviewPage;
|
||||
class AddressBookPage;
|
||||
class SendCoinsDialog;
|
||||
class SendCoinsRecipient;
|
||||
class SignVerifyMessageDialog;
|
||||
class Notificator;
|
||||
class RPCConsole;
|
||||
@ -54,7 +55,7 @@ public:
|
||||
|
||||
void removeAllWallets();
|
||||
|
||||
bool handleURI(const QString &uri);
|
||||
bool handlePaymentRequest(const SendCoinsRecipient &recipient);
|
||||
|
||||
void showOutOfSyncWarning(bool fShow);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -16,6 +16,7 @@ class TransactionView;
|
||||
class OverviewPage;
|
||||
class AddressBookPage;
|
||||
class SendCoinsDialog;
|
||||
class SendCoinsRecipient;
|
||||
class SignVerifyMessageDialog;
|
||||
class RPCConsole;
|
||||
|
||||
@ -49,7 +50,7 @@ public:
|
||||
*/
|
||||
void setWalletModel(WalletModel *walletModel);
|
||||
|
||||
bool handleURI(const QString &uri);
|
||||
bool handlePaymentRequest(const SendCoinsRecipient& recipient);
|
||||
|
||||
void showOutOfSyncWarning(bool fShow);
|
||||
|
||||
|
@ -94,7 +94,7 @@ Value importprivkey(const Array& params, bool fHelp)
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
pwalletMain->MarkDirty();
|
||||
pwalletMain->SetAddressBookName(vchAddress, strLabel);
|
||||
pwalletMain->SetAddressBook(vchAddress, strLabel, "receive");
|
||||
|
||||
// Don't throw error in case a key is already there
|
||||
if (pwalletMain->HaveKey(vchAddress))
|
||||
@ -172,7 +172,7 @@ Value importwallet(const Array& params, bool fHelp)
|
||||
}
|
||||
pwalletMain->mapKeyMetadata[keyid].nCreateTime = nTime;
|
||||
if (fLabel)
|
||||
pwalletMain->SetAddressBookName(keyid, strLabel);
|
||||
pwalletMain->SetAddressBook(keyid, strLabel, "receive");
|
||||
nTimeBegin = std::min(nTimeBegin, nTime);
|
||||
}
|
||||
file.close();
|
||||
|
@ -114,7 +114,7 @@ Value getnewaddress(const Array& params, bool fHelp)
|
||||
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
|
||||
CKeyID keyID = newKey.GetID();
|
||||
|
||||
pwalletMain->SetAddressBookName(keyID, strAccount);
|
||||
pwalletMain->SetAddressBook(keyID, strAccount, "receive");
|
||||
|
||||
return CBitcoinAddress(keyID).ToString();
|
||||
}
|
||||
@ -151,7 +151,7 @@ CBitcoinAddress GetAccountAddress(string strAccount, bool bForceNew=false)
|
||||
if (!pwalletMain->GetKeyFromPool(account.vchPubKey, false))
|
||||
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
|
||||
|
||||
pwalletMain->SetAddressBookName(account.vchPubKey.GetID(), strAccount);
|
||||
pwalletMain->SetAddressBook(account.vchPubKey.GetID(), strAccount, "receive");
|
||||
walletdb.WriteAccount(strAccount, account);
|
||||
}
|
||||
|
||||
@ -201,7 +201,7 @@ Value setaccount(const Array& params, bool fHelp)
|
||||
GetAccountAddress(strOldAccount, true);
|
||||
}
|
||||
|
||||
pwalletMain->SetAddressBookName(address.Get(), strAccount);
|
||||
pwalletMain->SetAddressBook(address.Get(), strAccount, "receive");
|
||||
|
||||
return Value::null;
|
||||
}
|
||||
@ -768,7 +768,7 @@ Value addmultisigaddress(const Array& params, bool fHelp)
|
||||
CScriptID innerID = inner.GetID();
|
||||
pwalletMain->AddCScript(inner);
|
||||
|
||||
pwalletMain->SetAddressBookName(innerID, strAccount);
|
||||
pwalletMain->SetAddressBook(innerID, strAccount, "send");
|
||||
return CBitcoinAddress(innerID).ToString();
|
||||
}
|
||||
|
||||
|
@ -496,7 +496,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn)
|
||||
if (GetKeyFromPool(newDefaultKey, false))
|
||||
{
|
||||
SetDefaultKey(newDefaultKey);
|
||||
SetAddressBookName(vchDefaultKey.GetID(), "");
|
||||
SetAddressBook(vchDefaultKey.GetID(), "", "receive");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1457,26 +1457,28 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
|
||||
}
|
||||
|
||||
|
||||
bool CWallet::SetAddressBookName(const CTxDestination& address, const string& strName)
|
||||
bool CWallet::SetAddressBook(const CTxDestination& address, const string& strName, const string& strPurpose)
|
||||
{
|
||||
std::map<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)
|
||||
{
|
||||
{
|
||||
|
@ -69,9 +69,11 @@ class CAddressBookData
|
||||
{
|
||||
public:
|
||||
std::string name;
|
||||
std::string purpose;
|
||||
|
||||
CAddressBookData()
|
||||
{
|
||||
purpose = "unknown";
|
||||
}
|
||||
};
|
||||
|
||||
@ -294,9 +296,9 @@ public:
|
||||
|
||||
DBErrors LoadWallet(bool& fFirstRunRet);
|
||||
|
||||
bool SetAddressBookName(const CTxDestination& address, const std::string& strName);
|
||||
bool SetAddressBook(const CTxDestination& address, const std::string& strName, const std::string& purpose);
|
||||
|
||||
bool DelAddressBookName(const CTxDestination& address);
|
||||
bool DelAddressBook(const CTxDestination& address);
|
||||
|
||||
void UpdatedTransaction(const uint256 &hashTx);
|
||||
|
||||
|
@ -32,6 +32,18 @@ bool CWalletDB::EraseName(const string& strAddress)
|
||||
return Erase(make_pair(string("name"), strAddress));
|
||||
}
|
||||
|
||||
bool CWalletDB::WritePurpose(const string& strAddress, const string& strPurpose)
|
||||
{
|
||||
nWalletDBUpdated++;
|
||||
return Write(make_pair(string("purpose"), strAddress), strPurpose);
|
||||
}
|
||||
|
||||
bool CWalletDB::ErasePurpose(const string& strPurpose)
|
||||
{
|
||||
nWalletDBUpdated++;
|
||||
return Erase(make_pair(string("purpose"), strPurpose));
|
||||
}
|
||||
|
||||
bool CWalletDB::ReadAccount(const string& strAccount, CAccount& account)
|
||||
{
|
||||
account.SetNull();
|
||||
@ -214,6 +226,12 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||
ssKey >> strAddress;
|
||||
ssValue >> pwallet->mapAddressBook[CBitcoinAddress(strAddress).Get()].name;
|
||||
}
|
||||
else if (strType == "purpose")
|
||||
{
|
||||
string strAddress;
|
||||
ssKey >> strAddress;
|
||||
ssValue >> pwallet->mapAddressBook[CBitcoinAddress(strAddress).Get()].purpose;
|
||||
}
|
||||
else if (strType == "tx")
|
||||
{
|
||||
uint256 hash;
|
||||
|
@ -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…
Reference in New Issue
Block a user