Merge branch 'master' into age-withdraw

This commit is contained in:
Özgür Kesim 2023-05-12 13:40:22 +02:00
commit 4833234df6
Signed by: oec
GPG Key ID: 3D76A56D79EDD9D7
206 changed files with 8797 additions and 3549 deletions

@ -1 +1 @@
Subproject commit bf43b20a0362ac19bcf1bab9c33215e55d8d9f36 Subproject commit 85736484cb0da26aded705ebb1e944e8bb1b8504

View File

@ -31,7 +31,13 @@
<member kind="define"> <member kind="define">
<type>#define</type> <type>#define</type>
<name>GNUNET_TIME_UNIT_FOREVER_ABS</name> <name>GNUNET_TIME_UNIT_FOREVER_ABS</name>
<anchorfile>gnunet_util_lib.h</anchorfile> <anchorfile>gnunet_time_lib.h</anchorfile>
<arglist></arglist>
</member>
<member kind="define">
<type>#define</type>
<name>GNUNET_TIME_UNIT_ZERO_ABS</name>
<anchorfile>gnunet_time_lib.h</anchorfile>
<arglist></arglist> <arglist></arglist>
</member> </member>
</compound> </compound>

View File

@ -5,7 +5,7 @@
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
DOXYFILE_ENCODING = UTF-8 DOXYFILE_ENCODING = UTF-8
PROJECT_NAME = "GNU Taler: Exchange" PROJECT_NAME = "GNU Taler: Exchange"
PROJECT_NUMBER = 0.8.3 PROJECT_NUMBER = 0.9.3
PROJECT_LOGO = logo.svg PROJECT_LOGO = logo.svg
OUTPUT_DIRECTORY = . OUTPUT_DIRECTORY = .
CREATE_SUBDIRS = YES CREATE_SUBDIRS = YES
@ -97,59 +97,11 @@ WARN_LOGFILE =
INPUT = ../../src INPUT = ../../src
INPUT_ENCODING = UTF-8 INPUT_ENCODING = UTF-8
FILE_PATTERNS = *.c \ FILE_PATTERNS = *.c \
*.cc \ *.h
*.cxx \
*.cpp \
*.c++ \
*.d \
*.java \
*.ii \
*.ixx \
*.ipp \
*.i++ \
*.inl \
*.h \
*.hh \
*.hxx \
*.hpp \
*.h++ \
*.idl \
*.odl \
*.cs \
*.php \
*.php3 \
*.inc \
*.m \
*.mm \
*.dox \
*.py \
*.f90 \
*.f \
*.vhd \
*.vhdl \
*.C \
*.CC \
*.C++ \
*.II \
*.I++ \
*.H \
*.HH \
*.H++ \
*.CS \
*.PHP \
*.PHP3 \
*.M \
*.MM \
*.PY \
*.F90 \
*.F \
*.VHD \
*.VHDL
RECURSIVE = YES RECURSIVE = YES
EXCLUDE = EXCLUDE =
EXCLUDE_SYMLINKS = NO EXCLUDE_SYMLINKS = NO
EXCLUDE_PATTERNS = */test_* \ EXCLUDE_PATTERNS = */test_* \
*/.svn/* \
*/.git/* \ */.git/* \
*/perf_* .* \ */perf_* .* \
.* \ .* \
@ -191,7 +143,9 @@ HTML_STYLESHEET =
GENERATE_HTMLHELP = NO GENERATE_HTMLHELP = NO
GENERATE_DOCSET = NO GENERATE_DOCSET = NO
DOCSET_FEEDNAME = "GNU Taler Source Documentation" DOCSET_FEEDNAME = "GNU Taler Source Documentation"
DOCSET_BUNDLE_ID = net.taler DOCSET_BUNDLE_ID = net.taler.exchange
DOCSET_PUBLISHER_ID = net.taler
DOCSET_PUBLISHER_NAME = Taler Systems SA
HTML_DYNAMIC_SECTIONS = NO HTML_DYNAMIC_SECTIONS = NO
CHM_FILE = CHM_FILE =
HHC_LOCATION = HHC_LOCATION =

3
doc/flows/Makefile Normal file
View File

@ -0,0 +1,3 @@
all:
pdflatex main.tex
pdflatex main.tex

39
doc/flows/fees-coins.tex Normal file
View File

@ -0,0 +1,39 @@
\section{Fees per coin} \label{sec:fees:coin}
Payments with Taler are always made using coins. Each coin has a specific
denomination, and an exchange will issue coins in different denominations (in
the same currency). The fees per coin depend on the operation and the
denomination.
The primary fee to be paid is a {\bf deposit} fee that is
charged whenever a coin is fully or partially deposited
into a bank account or another wallet.
A secondary fee to be paid is a {\bf change} fee that is
charged whenever a coin partially spent and change must
be rendered.
Coins also have an {\bf expiration} date of approximately {\bf one year}.
After the expiration date, coins become worthless. Wallets that are online
{\bf three months} {\em before} a coin expires will automatically trade any
such coins for one or more fresh coins with a later expiration date. This
process is also subject to the {\bf change} fee.
\begin{table}[h!]
\caption{Fees per coin. Coin denomination values are given in units of CHF 0.01.}
\label{table:fees:coins}
\begin{center}
\begin{tabular}{l|c|r}
{\bf Denomination} & {\bf Fee type} & {\bf Amount} \\ \hline \hline
$2^{-4}-2^{ 0}$ & deposit & {\em CHF 0.00125} \\
$2^{-4}-2^{ 0}$ & change & {\em CHF 0.00125} \\
$2^{ 0}-2^{ 3}$ & deposit & {\em CHF 0.00250} \\
$2^{ 0}-2^{ 3}$ & change & {\em CHF 0.00125} \\
$2^{ 4}-2^{ 8}$ & deposit & {\em CHF 0.005} \\
$2^{ 4}-2^{ 8}$ & change & {\em CHF 0.00125} \\
$2^{ 8}-2^{12}$ & deposit & {\em CHF 0.01} \\
$2^{ 8}-2^{12}$ & change & {\em CHF 0.00125} \\
\end{tabular}
\end{center}
\end{table}

30
doc/flows/fees-wire.tex Normal file
View File

@ -0,0 +1,30 @@
\section{Fees per wire} \label{sec:fees:wire}
Wire fees apply whenever an exchange needs to initiate a wire transfer to
another bank account. Wire fees do not apply to every individual payment to a
merchant, as merchants can choose to {\em aggregate} multiple micropayments
into one large payment on the wire. Wire fees also do not apply to
wallet-to-wallet payments within the Taler system.
A {\bf wire} fee is applied when a merchant receives
an aggregated payment into their bank account.
A {\bf closing} fee is applied when a wallet fails to
withdraw coins and money has to be sent back to the
originating bank account.
\begin{table}[h!]
\caption{Table with wire fees. Wire fees are set annually.}
\label{table:fees:wire}
\begin{center}
\begin{tabular}{l|c|r}
{\bf Year} & {\bf Fee type} & {\bf Amount} \\ \hline \hline
2023 & wire & {\em CHF 0.05} \\
2023 & closing & {\em CHF 0.10} \\
2024 & wire & {\em CHF 0.05} \\
2024 & closing & {\em CHF 0.10} \\
2025 & wire & {\em CHF 0.05} \\
2025 & closing & {\em CHF 0.10} \\
\end{tabular}
\end{center}
\end{table}

52
doc/flows/int-deposit.tex Normal file
View File

@ -0,0 +1,52 @@
\section{Deposit}
\begin{figure}[h!]
\begin{sequencediagram}
\newinst{wallet}{\shortstack{Customer wallet \\
\\ \begin{tikzpicture}
\node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Wallet ID};
\end{tikzpicture}
}}
\newinst[2]{exchange}{\shortstack{Taler (exchange) \\
\\ \begin{tikzpicture}[shape aspect=.5]
\tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}}
\node at (1.5,0) {\shortstack{{{\tiny Database}}}};
\end{tikzpicture}
}}
\newinst[2]{bank}{\shortstack{Customer bank \\
\\ \begin{tikzpicture}
\node [fill=gray!20,draw=black,thick,align=center] {Checking \\ Accounts};
\end{tikzpicture}
}}
\postlevel
\begin{callself}{wallet}{Review deposit fees}{}
\end{callself}
\mess[0]{wallet}{Deposit {(Coins)}}{exchange}
\begin{sdblock}{Acceptable account?}{}
\mess[0]{exchange}{{Refuse deposit}}{wallet}
\end{sdblock}
\begin{sdblock}{KYC/AML required?}{}
\begin{callself}{exchange}{Figures~\ref{fig:proc:kyc}, \ref{fig:proc:aml}}{}
\end{callself}
\end{sdblock}
% \prelevel
% \prelevel
% \begin{sdblock}{User abort?}{}
% \mess[0]{wallet}{{Request abort}}{exchange}
% \mess[0]{exchange}{{Abort confirmation}}{wallet}
% \end{sdblock}
\mess[0]{exchange}{{Initiate transfer}}{bank}
\end{sequencediagram}
\caption{Deposit interactions between customer, Taler exchange (payment
service provider) and customer's bank.}
\label{fig:int:deposit}
\end{figure}
We do {\bf not} permit the customer to regain control over their funds {\em
unless} they pass the KYC/AML checks. The technical reason is simply that
the KYC/AML checks happen {\em after} the aggregation logic and at this point
refunds are no longer permitted. From a compliance perspective, this also
prevents malicious customers from risk-free probing of the system.

58
doc/flows/int-pay.tex Normal file
View File

@ -0,0 +1,58 @@
\section{Pay}
\begin{figure}[h!]
\begin{sequencediagram}
\newinst{wallet}{\shortstack{Customer wallet \\
\\ \begin{tikzpicture}
\node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Wallet ID};
\end{tikzpicture}
}}
\newinst[1]{merchant}{\shortstack{Merchant \\
\\ \begin{tikzpicture}[shape aspect=.5]
\tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}}
\node at (1.5,0) {\shortstack{{{\tiny Database}}}};
\end{tikzpicture}
}}
\newinst[1]{exchange}{\shortstack{Taler (exchange) \\
\\ \begin{tikzpicture}[shape aspect=.5]
\tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}}
\node at (1.5,0) {\shortstack{{{\tiny Database}}}};
\end{tikzpicture}
}}
\newinst[1]{bank}{\shortstack{Merchant bank \\
\\ \begin{tikzpicture}
\node [fill=gray!20,draw=black,thick,align=center] {Commercial \\ Accounts};
\end{tikzpicture}
}}
\postlevel
\mess[0]{wallet}{Browse catalog}{merchant}
\mess[0]{merchant}{Commercial offer}{wallet}
\begin{callself}{wallet}{Review offer}{}
\end{callself}
\mess[0]{wallet}{Send payment {(Coins)}}{merchant}
\mess[0]{merchant}{Deposit {(Coins)}}{exchange}
\begin{sdblock}{Acceptable account?}{}
\mess[0]{exchange}{{Refuse deposit}}{merchant}
\mess[0]{merchant}{{Refund purchase}}{wallet}
\end{sdblock}
\mess[0]{exchange}{{Confirm deposit}}{merchant}
\mess[0]{merchant}{Fulfill order}{wallet}
\begin{callself}{exchange}{Aggregate transactions}{}
\end{callself}
\begin{sdblock}{KYC/AML required?}{}
\begin{callself}{exchange}{Figures~\ref{fig:proc:kyc}, \ref{fig:proc:aml}}{}
\end{callself}
\end{sdblock}
\mess[0]{exchange}{{Initiate transfer}}{bank}
\end{sequencediagram}
\caption{Deposit interactions between customer, merchant,
Taler exchange (payment service provider) and merchant bank.}
\label{fig:int:pay}
\end{figure}
{\bf Internal note:} The exchange refusing a deposit immediately based on
unaccaptable merchant accounts may not be fully implemented (this is a very
recent feature, after all); especially the merchant then automatically
refunding the purchase to the customer is certainly missing. However,
the entire situation only arises when a merchant is incorrectly configured
and in violation of the terms of service.

55
doc/flows/int-pull.tex Normal file
View File

@ -0,0 +1,55 @@
\section{Pull payment (aka invoicing)}
\begin{figure}[h!]
\begin{sequencediagram}
\newinst{payer}{\shortstack{Payer \\
\\ \begin{tikzpicture}
\node [fill=gray!20,draw=black,thick,align=center] {Pre-funded \\ Wallet};
\end{tikzpicture}
}}
\newinst[2]{exchange}{\shortstack{Taler (exchange) \\
\\ \begin{tikzpicture}[shape aspect=.5]
\tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}}
\node at (1.5,0) {\shortstack{{{\tiny Database}}}};
\end{tikzpicture}
}}
\newinst[2]{payee}{\shortstack{Payee \\
\\ \begin{tikzpicture}
\node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Wallet ID};
\end{tikzpicture}
}}
\postlevel
\begin{callself}{payee}{Review pull payment fees}{}
\end{callself}
\mess[0]{payee}{{Create invoice (Wallet ID)}}{exchange}
\mess[0]{exchange}{{Invoice ready}}{payee}
\mess[0]{payee}{{Send invoice (e.g. via QR code)}}{payer}
\begin{callself}{payer}{Review invoice}{}
\end{callself}
\mess[0]{payer}{{Make payment (Coins)}}{exchange}
\begin{sdblock}{Domestic wallet?}{}
\begin{callself}{exchange}{Figure~\ref{fig:proc:domestic}}{}
\end{callself}
\end{sdblock}
\begin{sdblock}{KYC/AML required?}{}
\begin{callself}{exchange}{Figures~\ref{fig:proc:kyc}, \ref{fig:proc:aml}}{}
\end{callself}
\end{sdblock}
\mess[0]{exchange}{{Distribute digital cash}}{payee}
\end{sequencediagram}
\caption{Interactions between wallets and Taler exchange
in a pull payment.}
\label{fig:int:pull}
\end{figure}
We do {\bf not} permit the payer to regain control over their funds, once the
payment was made they are locked {\em until} the payee passes the KYC/AML
checks. We only do the AML/KYC process once the funds are locked at the
exchange. This ensures we know the actual transacted amounts (which may be
lower than the total amounts requested) and prevents risk-free probing
attacks.

47
doc/flows/int-push.tex Normal file
View File

@ -0,0 +1,47 @@
\section{Push payment}
\begin{figure}[h!]
\begin{sequencediagram}
\newinst{payer}{\shortstack{Payer \\
\\ \begin{tikzpicture}
\node [fill=gray!20,draw=black,thick,align=center] {Pre-funded \\ Wallet};
\end{tikzpicture}
}}
\newinst[2]{exchange}{\shortstack{Taler (exchange) \\
\\ \begin{tikzpicture}[shape aspect=.5]
\tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}}
\node at (1.5,0) {\shortstack{{{\tiny Database}}}};
\end{tikzpicture}
}}
\newinst[2]{payee}{\shortstack{Payee \\
\\ \begin{tikzpicture}
\node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Wallet ID};
\end{tikzpicture}
}}
\postlevel
\begin{callself}{payer}{Review push payment fees}{}
\end{callself}
\mess[0]{payer}{{Push funds (Coins)}}{exchange}
\mess[0]{payer}{{Offer payment (e.g. via QR code)}}{payee}
\begin{callself}{payee}{Review payment offer}{}
\end{callself}
\mess[0]{payee}{{Request funds (Wallet ID)}}{exchange}
\begin{sdblock}{Domestic wallet?}{}
\begin{callself}{exchange}{Figure~\ref{fig:proc:domestic}}{}
\end{callself}
\end{sdblock}
\begin{sdblock}{KYC/AML required?}{}
\begin{callself}{exchange}{Figures~\ref{fig:proc:kyc}, \ref{fig:proc:aml}}{}
\end{callself}
\end{sdblock}
\mess[0]{exchange}{{Distribute digital cash}}{payee}
% \postlevel
\begin{sdblock}{Payment offer expired?}{}
\mess[0]{exchange}{{Return funds}}{payer}
\end{sdblock}
\end{sequencediagram}
\caption{Interactions between wallets and Taler exchange
in a push payment.}
\label{fig:int:push}
\end{figure}

39
doc/flows/int-refund.tex Normal file
View File

@ -0,0 +1,39 @@
\section{Refund}
\begin{figure}[h!]
\begin{sequencediagram}
\newinst{wallet}{\shortstack{Customer wallet \\
\\ \begin{tikzpicture}
\node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Wallet ID};
\end{tikzpicture}
}}
\newinst[2]{merchant}{\shortstack{Merchant \\
\\ \begin{tikzpicture}[shape aspect=.5]
\tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}}
\node at (1.5,0) {\shortstack{{{\tiny Database}}}};
\end{tikzpicture}
}}
\newinst[2]{exchange}{\shortstack{Taler (exchange) \\
\\ \begin{tikzpicture}[shape aspect=.5]
\tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}}
\node at (1.5,0) {\shortstack{{{\tiny Database}}}};
\end{tikzpicture}
}}
\postlevel
\begin{callself}{merchant}{Initiate refund}{}
\end{callself}
\mess[0]{merchant}{{Refund offer (QR code)}}{wallet}
\mess[0]{wallet}{Download refund}{merchant}
\mess[0]{merchant}{Approve refund}{exchange}
\mess[0]{exchange}{Confirm refund}{merchant}
\mess[0]{merchant}{Return refund confirmation}{wallet}
\end{sequencediagram}
\caption{Refund processing when a merchant is unable to fulfill
a contract. Refunds must happen {\em before} the
exchange has aggregated the original transaction for
a bank transfer to the merchant. Furthermore, refunds
can only go to the customer who made the original payment
and the refund cannot exceed the amount of the original
payment.}
\label{fig:int:refund}
\end{figure}

View File

@ -0,0 +1,48 @@
\section{Shutdown}
\begin{figure}[h!]
\begin{sequencediagram}
\newinst{wallet}{\shortstack{Customer wallet \\
\\ \begin{tikzpicture}
\node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Wallet ID};
\end{tikzpicture}
}}
\newinst[2]{exchange}{\shortstack{Taler (exchange) \\
\\ \begin{tikzpicture}[shape aspect=.5]
\tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}}
\node at (1.5,0) {\shortstack{{{\tiny Database}}}};
\end{tikzpicture}
}}
\newinst[2]{bank}{\shortstack{Customer bank \\
\\ \begin{tikzpicture}
\node [fill=gray!20,draw=black,thick,align=center] {Checking \\ Accounts};
\end{tikzpicture}
}}
\postlevel
\begin{callself}{exchange}{Operator initiates shutdown}{}
\end{callself}
\mess[0]{exchange}{{Shutdown alert}}{wallet}
\begin{sdblock}{Bank account known?}{}
\begin{callself}{wallet}{Designate bank account}{}
\end{callself}
\end{sdblock}
\mess[0]{wallet}{{Deposit (Coins)}}{exchange}
\begin{sdblock}{Acceptable account?}{}
\mess[0]{exchange}{{Refuse deposit}}{wallet}
\end{sdblock}
\begin{sdblock}{KYC/AML required?}{}
\begin{callself}{exchange}{Figures~\ref{fig:proc:kyc}, \ref{fig:proc:aml}}{}
\end{callself}
\end{sdblock}
\mess[0]{exchange}{{Initiate transfer}}{bank}
\end{sequencediagram}
\caption{Shutdown interactions between customer, Taler exchange (payment
service provider) and bank.}
\label{fig:int:shutdown}
\end{figure}
KYC/AML requirements are relaxed in cases where the customer is able to
cryptographically demonstrate that they previously withdrew these coins from
the designated checking account. Thus, KYC/AML checks here primarily still
apply if the customer received the funds via P2P transfers from other wallets.

View File

@ -0,0 +1,49 @@
\section{Withdraw}
\begin{figure}[h!]
\begin{sequencediagram}
\newinst{wallet}{\shortstack{Customer wallet \\
\\ \begin{tikzpicture}
\node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Wallet ID};
\end{tikzpicture}
}}
\newinst[2]{exchange}{\shortstack{Taler (exchange) \\
\\ \begin{tikzpicture}[shape aspect=.5]
\tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}}
\node at (1.5,0) {\shortstack{{{\tiny Database}}}};
\end{tikzpicture}
}}
\newinst[2]{bank}{\shortstack{Customer bank \\
\\ \begin{tikzpicture}
\node [fill=gray!20,draw=black,thick,align=center] {Checking \\ Accounts};
\end{tikzpicture}
}}
\postlevel
\mess[0]{wallet}{Withdraw {(Amount)}}{exchange}
\mess[0]{exchange}{{Configuration (ToS, Fees)}}{wallet}
\begin{sdblock}{once}{}
\begin{callself}{wallet}{Accept ToS}{}
\end{callself}
\end{sdblock}
\begin{callself}{wallet}{Review withdraw fees}{}
\end{callself}
\mess[0]{wallet}{{Initiate transfer (Amount, Credit account, Wallet ID)}}{bank}
\mess[0]{bank}{{Credit (Wallet ID)}}{exchange}
\begin{sdblock}{Acceptable transfer?}{}
\mess[0]{exchange}{{Bounce funds}}{bank}
\end{sdblock}
\postlevel
\mess[0]{exchange}{Confirm wire transfer}{wallet}
\mess[0]{wallet}{Request digital cash}{exchange}
\mess[0]{exchange}{Distribute digital cash}{wallet}
\postlevel
\begin{sdblock}{Withdraw period expired?}{}
\mess[0]{exchange}{{Return remaining funds}}{bank}
\end{sdblock}
\end{sequencediagram}
\caption{Withdraw interactions between customer, Taler exchange (payment
service provider) and bank. The amount of digital cash distributed is
subject to limits per origin account (see Figure~\ref{fig:kyc:withdraw}).}
\label{fig:int:withdraw}
\end{figure}

57
doc/flows/kyc-balance.tex Normal file
View File

@ -0,0 +1,57 @@
\section{KYC: Balance}
Note: this process is not implemented and would require non-trivial extra work
if required.
\begin{figure}[h!]
\begin{center}
\begin{tikzpicture}[node distance=1cm,font=\sffamily,
start/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=yellow!30},
end/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=red!30},
process/.style={rectangle, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=orange!30},
failed/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=red!30},
io/.style={trapezium, trapezium left angle=70, trapezium right angle=110, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=blue!30},
decision/.style={diamond, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=green!30},
arr/.style={very thick,-latex},
every edge quotes/.style = {auto, font=\footnotesize, sloped}
]
\node (start) [start] {Start};
\node (balance) [decision,below=of start,text width=3cm] {Transaction leaves wallet balance below AML threshold?};
\node (registered) [decision,below=of balance,text width=3cm] {Wallet has been subject to KYC?};
\node (kyc) [process, below=of registered] {KYC process};
\node (aml) [process, left=of kyc] {AML process};
\node (allow) [end, right=of balance] {Allow};
\node (deny) [failed, right=of registered] {Deny};
\draw[arr] (start) -> (balance) {};
\draw[arr] (balance) -> (registered);
\draw (balance) edge["No"] (registered);
\draw[arr] (balance) -> (allow);
\draw (balance) edge["Yes"] (allow);
\draw[arr] (registered) -> (kyc);
\draw (registered) edge["No"] (kyc);
\draw[arr] (registered) -> (deny);
\draw (registered) edge["Yes"] (deny);
\draw[arr] (kyc) -> (deny);
\draw (kyc) edge["Failed"] (deny);
\draw[arr] (kyc) -> (aml);
\draw (kyc) edge["Ok"] (aml);
\draw[arr] (aml) -> (balance.west);
\draw (aml) edge["New threshold"] (balance.west);
\end{tikzpicture}
\end{center}
\caption{Regulatory process when a wallet exceeds its AML threshold.
When the transfer is denied the transaction (withdraw, P2P transfer)
is refused by the wallet.}
\end{figure}
\begin{table}[h!]
\caption{Settings for the balance trigger}
\begin{tabular}{l|l|r}
{\bf Setting} & {\bf Type} & {\bf Value} \\ \hline \hline
Default AML threshold & Amount & {\em 1000 CHF} \\
\end{tabular}
\end{table}

71
doc/flows/kyc-deposit.tex Normal file
View File

@ -0,0 +1,71 @@
\section{KYC: Deposit}
\begin{figure}[h!]
\begin{center}
\begin{tikzpicture}[node distance=1cm,font=\sffamily,
start/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=yellow!30},
end/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=red!30},
process/.style={rectangle, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=orange!30},
failed/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=red!30},
io/.style={trapezium, trapezium left angle=70, trapezium right angle=110, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=blue!30},
decision/.style={diamond, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=green!30},
arr/.style={very thick,-latex},
every edge quotes/.style = {auto, font=\footnotesize, sloped}
]
\node (start) [start] {Start};
\node (country) [decision,below=of start,text width=2.5cm] {Target account in allowed country?};
\node (amount) [decision, below=of country,text width=2.5cm] {Target account received less than KYC threshold?};
\node (kyc) [process, right=of amount] {KYC process};
\node (high) [decision, below=of amount,text width=2.5cm] {Target account received more than its AML threshold?};
\node (aml) [process, right=of high] {AML process};
\node (dummy) [below right=of aml] {};
\node (allow) [end, below right=of dummy] {Allow};
\node (deny) [failed, right=of kyc] {Deny};
\draw[arr] (start) -> (country) {};
\draw[arr] (country) -> (amount);
\draw (country) edge["Yes"] (amount);
\draw[arr] (country.east) -> (deny);
\draw (country.east) edge["No"] (deny);
\draw[arr] (amount) -> (high);
\draw (amount) edge["Yes"] (high);
\draw[arr] (amount.east) -> (kyc);
\draw (amount.east) edge["No"] (kyc);
\draw[arr] (kyc) -> (deny);
\draw (kyc) edge["Failed"] (deny);
\draw[arr] (kyc) -> (high);
\draw (kyc) edge["Succeeded"] (high);
\draw[arr] (high.south) -> (allow);
\draw (high.south) edge["Yes"] (allow);
\draw[arr] (high.east) -> (aml);
\draw (high.east) edge["No"] (aml);
\draw[arr] (aml) -> (deny);
\draw (aml) edge["Violation"] (deny);
\draw[arr] (aml) -> (allow);
\draw (aml) edge["Ok"] (allow);
\end{tikzpicture}
\end{center}
\caption{Regulatory process when depositing digital cash into a bank
account. When the transfer is denied, the money is returned to the
originating wallet.}
\end{figure}
\begin{table}[h!]
\caption{Settings for the deposit trigger}
\begin{tabular}{l|l|r}
{\bf Setting} & {\bf Type} & {\bf Value} \\ \hline \hline
Allowed bank accounts & RFC 8905 RegEx & {\em CH*} \\ \hline
KYC deposit threshold & Amount & {\em 1000 CHF} \\
Default AML deposit threshold & Amount & {\em 2500 CHF} \\
\end{tabular}
\end{table}

78
doc/flows/kyc-pull.tex Normal file
View File

@ -0,0 +1,78 @@
\section{KYC/AML: Pull Payment}
\begin{figure}[h!]
\begin{center}
\begin{tikzpicture}[node distance=0.9cm,font=\sffamily,
start/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=yellow!30},
end/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=red!30},
process/.style={rectangle, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=orange!30},
failed/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=red!30},
io/.style={trapezium, trapezium left angle=70, trapezium right angle=110, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=blue!30},
decision/.style={diamond, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=green!30},
arr/.style={very thick,-latex},
every edge quotes/.style = {auto, font=\footnotesize, sloped}
]
\node (start) [start] {Start};
\node (wallet) [decision,below=of start,text width=2.5cm] {Wallet linked to (domestic) phone number?};
\node (domestic) [process, right=of wallet] {Validate phone number};
\node (amount) [decision, below=of wallet,text width=2.5cm] {Wallet received less than KYC threshold from other wallets?};
\node (kyc) [process, right=of amount] {KYC process};
\node (high) [decision, below=of amount,text width=2.5cm] {Wallet received more than its AML threshold?};
\node (aml) [process, right=of high] {AML process};
\node (dummy) [below right=of aml] {};
\node (allow) [end, below right=of dummy] {Allow invoicing};
\node (deny) [failed, right=of kyc] {Deny};
\draw[arr] (start) -> (wallet) {};
\draw[arr] (wallet) -> (amount);
\draw (wallet) edge["Yes"] (amount);
\draw[arr] (wallet.east) -> (domestic);
\draw (wallet.east) edge["No"] (domestic);
\draw[arr] (domestic) -> (amount);
\draw (domestic) edge["Confirmed"] (amount);
\draw[arr] (domestic) -> (deny);
\draw (domestic) edge["Failed"] (deny);
\draw[arr] (amount) -> (high);
\draw (amount) edge["Yes"] (high);
\draw[arr] (amount.east) -> (kyc);
\draw (amount.east) edge["No"] (kyc);
\draw[arr] (kyc) -> (deny);
\draw (kyc) edge["Failed"] (deny);
\draw[arr] (kyc) -> (high);
\draw (kyc) edge["Succeeded"] (high);
\draw[arr] (high.south) -> (allow);
\draw (high.south) edge["Yes"] (allow);
\draw[arr] (high.east) -> (aml);
\draw (high.east) edge["No"] (aml);
\draw[arr] (aml) -> (deny);
\draw (aml) edge["Violation"] (deny);
\draw[arr] (aml) -> (allow);
\draw (aml) edge["Ok"] (allow);
\end{tikzpicture}
\end{center}
\caption{Regulatory process when receiving payments from another wallet.
The threshold depends on the risk profile from the KYC process.
When invoicing is denied the wallet cannot generate the invoice.}
\end{figure}
\begin{table}[h!]
\caption{Settings for the pull payment trigger}
\begin{tabular}{l|l|r}
{\bf Setting} & {\bf Type} & {\bf Value} \\ \hline \hline
Permitted phone numbers & Dialing prefix & {\em +41} \\
P2P KYC threshold & Amount & {\em 100 CHF} \\
Default P2P AML threshold & Amount & {\em 1000 CHF} \\
\end{tabular}
\end{table}

79
doc/flows/kyc-push.tex Normal file
View File

@ -0,0 +1,79 @@
\section{KYC/AML: Push Payment}
\begin{figure}[h!]
\begin{center}
\begin{tikzpicture}[node distance=0.9cm,font=\sffamily,
start/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=yellow!30},
end/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=red!30},
process/.style={rectangle, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=orange!30},
failed/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=red!30},
io/.style={trapezium, trapezium left angle=70, trapezium right angle=110, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=blue!30},
decision/.style={diamond, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=green!30},
arr/.style={very thick,-latex},
every edge quotes/.style = {auto, font=\footnotesize, sloped}
]
\node (start) [start] {Start};
\node (wallet) [decision,below=of start,text width=2.5cm] {Wallet linked to (domestic) phone number?};
\node (domestic) [process, right=of wallet] {Validate phone number};
\node (amount) [decision, below=of wallet,text width=2.5cm] {Wallet received less than KYC threshold from other wallets?};
\node (kyc) [process, right=of amount] {KYC process};
\node (high) [decision, below=of amount,text width=2.5cm] {Wallet received more than its AML threshold?};
\node (aml) [process, right=of high] {AML process};
\node (dummy) [below right=of aml] {};
\node (allow) [end, below right=of dummy] {Allow};
\node (deny) [failed, right=of kyc] {Deny};
\draw[arr] (start) -> (wallet) {};
\draw[arr] (wallet) -> (amount);
\draw (wallet) edge["Yes"] (amount);
\draw[arr] (wallet.east) -> (domestic);
\draw (wallet.east) edge["No"] (domestic);
\draw[arr] (domestic) -> (amount);
\draw (domestic) edge["Confirmed"] (amount);
\draw[arr] (domestic) -> (deny);
\draw (domestic) edge["Failed"] (deny);
\draw[arr] (amount) -> (high);
\draw (amount) edge["Yes"] (high);
\draw[arr] (amount.east) -> (kyc);
\draw (amount.east) edge["No"] (kyc);
\draw[arr] (kyc) -> (deny);
\draw (kyc) edge["Failed"] (deny);
\draw[arr] (kyc) -> (high);
\draw (kyc) edge["Succeeded"] (high);
\draw[arr] (high.south) -> (allow);
\draw (high.south) edge["Yes"] (allow);
\draw[arr] (high.east) -> (aml);
\draw (high.east) edge["No"] (aml);
\draw[arr] (aml) -> (deny);
\draw (aml) edge["Violation"] (deny);
\draw[arr] (aml) -> (allow);
\draw (aml) edge["Ok"] (allow);
\end{tikzpicture}
\end{center}
\caption{Regulatory process when receiving payments from another wallet.
The threshold depends on the risk profile from the KYC process.
When the transfer is denied the money is (eventually) returned to
the originating wallet.}
\end{figure}
\begin{table}[h!]
\caption{Settings for the push payment trigger}
\begin{tabular}{l|l|r}
{\bf Setting} & {\bf Type} & {\bf Value} \\ \hline \hline
Permitted phone numbers & Dialing prefix & {\em +41} \\
P2P KYC threshold & Amount & {\em 100 CHF} \\
Default P2P AML threshold & Amount & {\em 1000 CHF} \\
\end{tabular}
\end{table}

View File

@ -0,0 +1,45 @@
\section{KYC: Withdraw}
\begin{figure}[h!]
\begin{center}
\begin{tikzpicture}[node distance=1cm,font=\sffamily,
start/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=yellow!30},
end/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=red!30},
process/.style={rectangle, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=orange!30},
failed/.style={rectangle, rounded corners, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=red!30},
io/.style={trapezium, trapezium left angle=70, trapezium right angle=110, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=blue!30},
decision/.style={diamond, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=green!30},
arr/.style={very thick,-latex},
every edge quotes/.style = {auto, font=\footnotesize, sloped}
]
\node (start) [start] {Start};
\node (country) [decision,below=of start,text width=3cm] {Wire transfer originates from allowed country?};
\node (amount) [decision, below=of country,text width=3cm] {Transferred less than maximum amount from origin account over last month?};
\node (allow) [end, below=of amount] {Allow};
\node (deny) [failed, right=of allow] {Deny};
\draw[arr] (start) -> (country) {};
\draw[arr] (country) -> (amount);
\draw (country) edge["Yes"] (amount);
\draw[arr] (country.east) -> (deny);
\draw (country.east) edge["No"] (deny);
\draw[arr] (amount) -> (allow);
\draw (amount) edge["Yes"] (allow);
\draw[arr] (amount.east) -> (deny);
\draw (amount.east) edge["No"] (deny);
\end{tikzpicture}
\end{center}
\caption{Regulatory process when withdrawing digital cash from a
bank account.
When the transfer is denied the money is (eventually) returned to
the originating bank account.}
\label{fig:kyc:withdraw}
\end{figure}
\begin{table}[h!]
\caption{Settings for the withdraw trigger}
\begin{tabular}{l|l|r}
{\bf Setting} & {\bf Type} & {\bf Value} \\ \hline \hline
Allowed bank accounts & RFC 8905 RegEx & {\em CH*} \\ \hline
Monthly withdraw maximum & Amount & {\em 1000 CHF} \\
\end{tabular}
\end{table}

159
doc/flows/main.tex Normal file
View File

@ -0,0 +1,159 @@
\documentclass[10pt,a4paper,oneside]{book}
\usepackage[utf8]{inputenc}
\usepackage{url}
\usepackage{graphicx}
\usepackage{hyperref}
\usepackage{qrcode}
\usepackage{pgf-umlsd}
\usepackage{tikz}
\usetikzlibrary{shapes,arrows}
\usetikzlibrary{positioning}
\usetikzlibrary{calc}
\usetikzlibrary{quotes}
\author{Christian Grothoff}
\title{Flows in the GNU Taler System}
\begin{document}
\tableofcontents
\chapter{Interactions} \label{chap:interactions}
This chapter introduces the main payment interactions in the GNU Taler payment
system. For each interaction, we introduce the parties involved and in which
order they interact and how. In each interaction it is possible that the
Taler exchange needs to trigger a compliance process. These regulatory
riggers are described in more detail in Chapter~\ref{chap:triggers}.
The main interactions of the system are:
\begin{description}
\item[withdraw] a customer withdraws digital cash to their wallet
\item[deposit] a customer returns digital cash into their bank account
\item[pay] a customer pays into bank account of a merchant
\item[refund] a merchant decides to return funds to a customer
\item[push] a customer sends a payment to another wallet
\item[pull] a customer requests a payment from another wallet (effectively sending an invoice)
\item[shutdown] the Taler payment system operator informs the customers that the system is being shut down for good
\end{description}
Taler has no accounts (this is digital cash) and thus there is no ``opening''
or ``closing'' of accounts. The equivalent of ``opening'' an account is thus
to withdraw digital cash. The equivalent of ``closing'' an account is to
either (1) deposit the funds explicitly into a bank account, or (2) the coins
will expire if the wallet was lost (including long-term offline or
uninstalled). Finally, if a wallet remains (occasionally) online but a user
does simply not spend the coins will (3) diminish in value from the change
fees (see Section~\ref{sec:fees:coin}) that apply to prevent the coins from
expiring outright.
The following sections describe the respective processes for each of these
interactions.
\include{int-withdraw}
\include{int-deposit}
\include{int-pay}
\include{int-refund}
\include{int-push}
\include{int-pull}
\include{int-shutdown}
\chapter{Regulatory Triggers} \label{chap:triggers}
In this chapter we show decision diagrams for regulatory processes of the
various core operations of the GNU Taler payment system. In each case, the
{\bf start} state refers to one of the interactions described in the previous
chapter. The payment system will then use the process to arrive at an {\bf
allow} decision which permits the transaction to go through, or at a {\bf
deny} decision which ensures that the funds are not moved.
The specific {\em decisions} (in green) depend on the risk profile and the
regulatory environment. The tables in each section list the specific values
that are to be configured.
There are five types if interactions that can trigger regulatory processes:
\begin{description}
\item[withdraw] a customer withdraws digital cash from their {\bf bank account}
\item[deposit] a merchant's {\bf bank account} is designated to receive a payment in digital cash
\item[push] a {\bf wallet} accepts a payment from another wallet
\item[pull] a {\bf wallet} requests a payment from another wallet
\item[balance] a withdraw or P2P payment causes the balance of a {\bf wallet} to exceed a given threshold
\end{description}
We note in bold the {\bf anchor} for the regulator process. The anchor is used
to link the interaction to an identity. Once an identity has been established
for a particular anchor, that link is considered established for all types of
activities involving that anchor. A wallet is uniquely identified in the
system by its unique cryptographic key. A bank account is uniquely identified
in the system by its (RFC 8905) bank routing data (usually including BIC, IBAN
and account owner name).
The KYC and AML processes themselves are described in
Chapter~\ref{chap:regproc}.
\include{kyc-withdraw}
\include{kyc-deposit}
\include{kyc-push}
\include{kyc-pull}
\include{kyc-balance}
\chapter{Regulatory Processes} \label{chap:regproc}
This chapter describes the interactions between the customer, exchange and
organizations or staff assisting with regulatory processes designed to ensure
that customers are residents in the area of operation of the payment service
provider, are properly identified, and do not engage in money laundering.
The three main regulatory processes are:
\begin{description}
\item[domestic check] This process establishes that a user is generally
eligible to use the payment system. The process checks that the user has an
eligible address, but stops short of establishing the user's identity.
\item[kyc] This process establishes a user's legal identity, possibly
using external providers to review documents and check against blacklists.
\item[aml] The AML process reviews suspicious payment activities for
money laundering. Here AML staff reviews all collected information.
\end{description}
\include{proc-domestic}
\include{proc-kyc}
\include{proc-aml}
\chapter{Fees} \label{chap:fees}
The business model for operating a Taler exchange is to charge transaction
fees. Fees are charged on certain operations by the exchange. There are two
types of fees, {\bf wire fees} and {\bf coin fees}. This chapter describes
the fee structure.
Fixed, amount-independent {\bf wire fees} are charged on wire transfers using
the core banking system. Details on wire fees are described in
Section~\ref{sec:fees:wire}.
Coin fees are more complex, as they do not exactly follow neither the usual
percentage of volume model of other payment systems. Instead, coin fees are
applied per coin, resulting in a {\em logarithmic} fee structure. As a
result, the effective fee {\em percentage} for tiny transactions is high (for
example 50\% for transactions of 0.0025 CHF) while the effective fee
percentage for large transactions is nominal (for example $\approx$ 0.05\% for
transactions of $\approx$ 40 CHF). Details on coin fees are described in
Section~\ref{sec:fees:coin}.
Fees are configurable (and that fee types beyond those described here are
supported by the software). Thus, the specific fees may be adjusted in the
future based on business decisions. However, changes to the fees are never
retroactively applied to coins already in circulation. Wire fees that have
been publicly announced for a particular time period also cannot be changed.
Finally, any change to the terms of service must also be explicitly accepted
by the users before they withdraw additional funds.
\include{fees-wire}
\include{fees-coins}
%\include{fees-other}
\end{document}

47
doc/flows/proc-aml.tex Normal file
View File

@ -0,0 +1,47 @@
\section{AML process}
\begin{figure}[h!]
\begin{sequencediagram}
\newinst{wallet}{\shortstack{Customer \\
\\ \begin{tikzpicture}
\node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Action};
\end{tikzpicture}
}}
\newinst[2]{exchange}{\shortstack{Taler (exchange) \\
\\ \begin{tikzpicture}[shape aspect=.5]
\tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}}
\node at (1.5,0) {\shortstack{{{\tiny Database}}}};
\end{tikzpicture}
}}
\newinst[2]{staff}{\shortstack{AML staff \\
\\ \begin{tikzpicture}
\node [fill=gray!20,draw=black,thick,align=center] { Access \\ Token};
\end{tikzpicture}
}}
\postlevel
\mess[0]{wallet}{{Initial action}}{exchange}
\begin{callself}{exchange}{Establish AML requirement}{}
\end{callself}
\begin{callself}{exchange}{Queue AML task}{}
\end{callself}
\mess[0]{exchange}{Wait for AML}{wallet}
\mess[0]{staff}{Request AML work}{exchange}
\mess[0]{exchange}{{Open AML task(s)}}{staff}
\mess[0]{staff}{Request details}{exchange}
\mess[0]{exchange}{KYC/AML data}{staff}
\begin{callself}{staff}{Review and decide}{}
\end{callself}
\mess[0]{staff}{{Decision documentation}}{exchange}
\mess[0]{exchange}{AML decision}{wallet}
\mess[0]{wallet}{{Retry action}}{exchange}
\end{sequencediagram}
\caption{Deposit interactions between customer, Taler exchange (payment
service provider) and the AML staff. The process can be
triggered by various {\em actions} described in Chapter~\ref{chap:triggers}.
AML staff interactions are cryptographically secured and
decisions and the provided reasoning are archived by the exchange.
AML staff may interact with the customer (out-of-band)
in its decision process.
}
\label{fig:proc:aml}
\end{figure}

View File

@ -0,0 +1,66 @@
\section{Domestic wallet check}
\begin{figure}[h!]
\begin{sequencediagram}
\newinst{wallet}{\shortstack{Customer wallet \\
\\ \begin{tikzpicture}
\node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Wallet ID};
\end{tikzpicture}
}}
\newinst[2]{exchange}{\shortstack{Taler (exchange) \\
\\ \begin{tikzpicture}[shape aspect=.5]
\tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}}
\node at (1.5,0) {\shortstack{{{\tiny Database}}}};
\end{tikzpicture}
}}
\newinst[2]{sms}{\shortstack{Address validator}}
\postlevel
\mess[0]{wallet}{{P2P payment (Wallet ID)}}{exchange}
\begin{callself}{exchange}{New wallet?}{}
\end{callself}
\mess[0]{exchange}{Request address validation}{sms}
\mess[0]{sms}{Validation process ID}{exchange}
\mess[0]{exchange}{Request address validation}{wallet}
\mess[0]{wallet}{Send address}{sms}
\mess[0]{sms}{{Send confirmation code (to address)}}{wallet}
\mess[0]{wallet}{Supply confirmation code}{sms}
\mess[0]{sms}{{Confirmed customer address}}{exchange}
\mess[0]{exchange}{{Confirm completion}}{wallet}
\mess[0]{wallet}{{Retry action}}{exchange}
\end{sequencediagram}
\caption{Deposit interactions between customer, Taler exchange (payment
service provider) and external address validation service. The process can be
triggered by wallet-to-wallet (P2P) payments described in Chapter~\ref{chap:triggers}.}
\label{fig:proc:domestic}
\end{figure}
Our users have to accept the terms of service which restrict the use of the
service to domestic customers. For interactions with the core banking system,
this simply means that we only accept payments from or to domestic bank
accounts. For P2P payments between wallets, we require that the wallets are
controlled by a domestic entity. We define domestic entities as those that
are able to receive messages at a domestic address. Two types of addresses are
supported:
\begin{itemize}
\item Control over a domestic {\bf mobile phone number} is established
by sending an SMS message with a confirmation code to the MSIN.
\item Control over a domestic {\bf postal address} is established by
sending a letter with a confirmation code to the address.
\end{itemize}
Depending on the type of address, a validation has a limited validity period,
as shown in Table~\ref{table:proc:domestic}. When the validity period is
over, a wallet has to re-do the address validation before they can receive any
further funds through the service.
\begin{table}[h!]
\caption{Restrictions on address validations}
\label{table:proc:domestic}
\begin{tabular}{l|l|r}
{\bf Type} & {\bf Validity period} & {\bf Restricted to} \\ \hline \hline
Mobile phone number & 12 months & {\em +41} \\
Postal address & 36 months & {\em Switzerland} \\
\end{tabular}
\end{table}

88
doc/flows/proc-kyc.tex Normal file
View File

@ -0,0 +1,88 @@
\section{KYC process}
\begin{figure}[h!]
\begin{sequencediagram}
\newinst{wallet}{\shortstack{Customer \\
\\ \begin{tikzpicture}
\node [fill=gray!20,draw=black,thick,align=center] { Unique \\ Action};
\end{tikzpicture}
}}
\newinst[2]{exchange}{\shortstack{Taler (exchange) \\
\\ \begin{tikzpicture}[shape aspect=.5]
\tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}}
\node at (1.5,0) {\shortstack{{{\tiny Database}}}};
\end{tikzpicture}
}}
\newinst[2]{kyc}{\shortstack{KYC provider \\
\\ \begin{tikzpicture}[shape aspect=.5]
\tikzset{every node/.style={cylinder,shape border rotate=90, draw,fill=gray!25}}
\node at (1.5,0) {\shortstack{{{\tiny Database}}}};
\end{tikzpicture}
}}
\postlevel
\mess[0]{wallet}{{Initial action}}{exchange}
\begin{callself}{exchange}{Establish KYC requirement}{}
\end{callself}
\mess[0]{exchange}{Request new KYC process}{kyc}
\mess[0]{kyc}{{Process identifier (PI)}}{exchange}
\mess[0]{exchange}{{KYC required (PI)}}{wallet}
\mess[0]{wallet}{{KYC start (PI)}}{kyc}
\mess[0]{kyc}{{Request identity documentation}}{wallet}
\mess[0]{wallet}{{Upload identity documentation}}{kyc}
\begin{callself}{kyc}{Validate documentation}{}
\end{callself}
\mess[0]{kyc}{{Share documentation (PI)}}{exchange}
\mess[0]{kyc}{{Confirm completion}}{wallet}
\mess[0]{wallet}{{Retry action}}{exchange}
\end{sequencediagram}
\caption{Deposit interactions between customer, Taler exchange (payment
service provider) and external KYC provider. The process can be
triggered by various {\em actions} described in Chapter~\ref{chap:triggers}.}
\label{fig:proc:kyc}
\end{figure}
At the beginning of the KYC process, the user needs to specify
whether they are an {\bf individual} or a {\bf business}. This
then determines which types of attributes are collected in the
KYC process (Table~\ref{table:proc:kyc:individual} vs.
Table~\ref{table:proc:kyc:business}).
\begin{table}
\caption{Information collected for individuals}
\label{table:proc:kyc:individual}
\begin{center}
\begin{tabular}{l|c|r}
{\bf Type} & {\bf Required} & {\bf Example} \\ \hline \hline
Surname & yes & Mustermann \\
First name(s) & yes & Max \\
Date of birth & yes & 1.1.1980 \\
Nationality & yes & Swiss \\
Actual address of domicile & yes & Seestrasse 3, 8008 Zuerich \\
Phone number & no & +41-123456789 \\
E-mail & no & me@example.com \\
Identification document & yes & JPG image \\
\end{tabular}
\end{center}
\end{table}
\begin{table}
\caption{Information collected for businesses}
\label{table:proc:kyc:business}
\begin{center}
\begin{tabular}{l|c|r}
{\bf Type} & {\bf Required} & {\bf Example} \\ \hline \hline
Company name & yes & Mega AG \\
Registered office & yes & Seestrasse 4, 8008 Zuerich \\
Company identification document & yes & PDF file \\ \hline
Contact person name & yes & Max Mustermann \\
Phone number & no & +41-123456789 \\
E-mail & yes & me@example.com \\
Identification document & yes & JPG image \\
Date of birth & yes & 1.1.1980 \\
Nationality & yes & Swiss \\ \hline
Power of attorney arrangement & yes & PDF file \\
\end{tabular}
\end{center}
\end{table}

View File

@ -193,7 +193,7 @@ echo " DONE"
echo -n "Setting up merchant" echo -n "Setting up merchant"
curl -H "Content-Type: application/json" -X POST -d '{"auth":{"method":"external"},"payto_uris":["payto://x-taler-bank/localhost/43"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' http://localhost:9966/management/instances curl -H "Content-Type: application/json" -X POST -d '{"auth":{"method":"external"},"accounts":[{"payto_uri":"payto://x-taler-bank/localhost/43"}],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' http://localhost:9966/management/instances
echo " DONE" echo " DONE"

View File

@ -398,7 +398,7 @@ echo " DONE"
echo -n "Setting up merchant" echo -n "Setting up merchant"
curl -H "Content-Type: application/json" -X POST -d '{"auth":{"method":"external"},"payto_uris":["payto://iban/SANDBOXX/DE474361?receiver-name=Merchant43"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' http://localhost:9966/management/instances curl -H "Content-Type: application/json" -X POST -d '{"auth":{"method":"external"},"accounts":[{"payto_uri":"payto://iban/SANDBOXX/DE474361?receiver-name=Merchant43"}],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' http://localhost:9966/management/instances
echo " DONE" echo " DONE"

View File

@ -400,7 +400,7 @@ echo " DONE"
# Setup merchant # Setup merchant
echo -n "Setting up merchant" echo -n "Setting up merchant"
curl -H "Content-Type: application/json" -X POST -d '{"auth": {"method": "external"}, "payto_uris":["payto://iban/SANDBOXX/DE474361?receiver-name=Merchant43"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' http://localhost:9966/management/instances curl -H "Content-Type: application/json" -X POST -d '{"auth": {"method": "external"}, "accounts":[{"payto_uri":"payto://iban/SANDBOXX/DE474361?receiver-name=Merchant43"}],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' http://localhost:9966/management/instances
# run wallet CLI # run wallet CLI

View File

@ -605,6 +605,9 @@ main (int argc,
level, level,
NULL)); NULL));
GNUNET_free (level); GNUNET_free (level);
/* suppress compiler warnings... */
GNUNET_assert (NULL != src_cfgfile);
GNUNET_assert (NULL != dst_cfgfile);
if (0 == strcmp (src_cfgfile, if (0 == strcmp (src_cfgfile,
dst_cfgfile)) dst_cfgfile))
{ {

View File

@ -172,9 +172,9 @@ coin_history_index (const struct TALER_CoinSpendPublicKeyP *coin_pub)
{ {
uint32_t i; uint32_t i;
memcpy (&i, GNUNET_memcpy (&i,
coin_pub, coin_pub,
sizeof (i)); sizeof (i));
return i % MAX_COIN_HISTORIES; return i % MAX_COIN_HISTORIES;
} }

View File

@ -674,12 +674,12 @@ hash_rc (const char *receiver_account,
size_t slen = strlen (receiver_account); size_t slen = strlen (receiver_account);
char buf[sizeof (struct TALER_WireTransferIdentifierRawP) + slen]; char buf[sizeof (struct TALER_WireTransferIdentifierRawP) + slen];
memcpy (buf, GNUNET_memcpy (buf,
wtid, wtid,
sizeof (*wtid)); sizeof (*wtid));
memcpy (&buf[sizeof (*wtid)], GNUNET_memcpy (&buf[sizeof (*wtid)],
receiver_account, receiver_account,
slen); slen);
GNUNET_CRYPTO_hash (buf, GNUNET_CRYPTO_hash (buf,
sizeof (buf), sizeof (buf),
key); key);
@ -1483,10 +1483,10 @@ history_debit_cb (void *cls,
switch (dhr->http_status) switch (dhr->http_status)
{ {
case MHD_HTTP_OK: case MHD_HTTP_OK:
for (unsigned int i = 0; i<dhr->details.success.details_length; i++) for (unsigned int i = 0; i<dhr->details.ok.details_length; i++)
{ {
const struct TALER_BANK_DebitDetails *dd const struct TALER_BANK_DebitDetails *dd
= &dhr->details.success.details[i]; = &dhr->details.ok.details[i];
GNUNET_log (GNUNET_ERROR_TYPE_INFO, GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Analyzing bank DEBIT at %s of %s with WTID %s\n", "Analyzing bank DEBIT at %s of %s with WTID %s\n",
GNUNET_TIME_timestamp2s (dd->execution_date), GNUNET_TIME_timestamp2s (dd->execution_date),
@ -1504,9 +1504,9 @@ history_debit_cb (void *cls,
roi->details.execution_date = dd->execution_date; roi->details.execution_date = dd->execution_date;
roi->details.wtid = dd->wtid; roi->details.wtid = dd->wtid;
roi->details.credit_account_uri = (const char *) &roi[1]; roi->details.credit_account_uri = (const char *) &roi[1];
memcpy (&roi[1], GNUNET_memcpy (&roi[1],
dd->credit_account_uri, dd->credit_account_uri,
slen); slen);
if (GNUNET_OK != if (GNUNET_OK !=
GNUNET_CONTAINER_multihashmap_put (out_map, GNUNET_CONTAINER_multihashmap_put (out_map,
&roi->subject_hash, &roi->subject_hash,
@ -1678,9 +1678,9 @@ reserve_in_cb (void *cls,
rii->details.execution_date = execution_date; rii->details.execution_date = execution_date;
rii->details.reserve_pub = *reserve_pub; rii->details.reserve_pub = *reserve_pub;
rii->details.debit_account_uri = (const char *) &rii[1]; rii->details.debit_account_uri = (const char *) &rii[1];
memcpy (&rii[1], GNUNET_memcpy (&rii[1],
sender_account_details, sender_account_details,
slen); slen);
GNUNET_CRYPTO_hash (&wire_reference, GNUNET_CRYPTO_hash (&wire_reference,
sizeof (uint64_t), sizeof (uint64_t),
&rii->row_off_hash); &rii->row_off_hash);
@ -1978,10 +1978,10 @@ history_credit_cb (void *cls,
switch (chr->http_status) switch (chr->http_status)
{ {
case MHD_HTTP_OK: case MHD_HTTP_OK:
for (unsigned int i = 0; i<chr->details.success.details_length; i++) for (unsigned int i = 0; i<chr->details.ok.details_length; i++)
{ {
const struct TALER_BANK_CreditDetails *cd const struct TALER_BANK_CreditDetails *cd
= &chr->details.success.details[i]; = &chr->details.ok.details[i];
if (! analyze_credit (wa, if (! analyze_credit (wa,
cd)) cd))

View File

@ -131,8 +131,8 @@ parse_account_history (struct TALER_BANK_CreditHistoryHandle *hh,
return GNUNET_SYSERR; return GNUNET_SYSERR;
} }
} }
chr.details.success.details_length = len; chr.details.ok.details_length = len;
chr.details.success.details = cd; chr.details.ok.details = cd;
hh->hcb (hh->hcb_cls, hh->hcb (hh->hcb_cls,
&chr); &chr);
} }

View File

@ -133,8 +133,8 @@ parse_account_history (struct TALER_BANK_DebitHistoryHandle *hh,
return GNUNET_SYSERR; return GNUNET_SYSERR;
} }
} }
dhr.details.success.details_length = len; dhr.details.ok.details_length = len;
dhr.details.success.details = dd; dhr.details.ok.details = dd;
hh->hcb (hh->hcb_cls, hh->hcb (hh->hcb_cls,
&dhr); &dhr);
} }

View File

@ -99,12 +99,12 @@ TALER_BANK_prepare_transfer (
wp->account_len = htonl ((uint32_t) d_len); wp->account_len = htonl ((uint32_t) d_len);
wp->exchange_url_len = htonl ((uint32_t) u_len); wp->exchange_url_len = htonl ((uint32_t) u_len);
end = (char *) &wp[1]; end = (char *) &wp[1];
memcpy (end, GNUNET_memcpy (end,
destination_account_payto_uri, destination_account_payto_uri,
d_len); d_len);
memcpy (end + d_len, GNUNET_memcpy (end + d_len,
exchange_base_url, exchange_base_url,
u_len); u_len);
*buf = (char *) wp; *buf = (char *) wp;
} }

View File

@ -1389,9 +1389,9 @@ make_transfer (
if (NULL != timestamp) if (NULL != timestamp)
*timestamp = t->date; *timestamp = t->date;
t->type = T_DEBIT; t->type = T_DEBIT;
memcpy (t->subject.debit.exchange_base_url, GNUNET_memcpy (t->subject.debit.exchange_base_url,
exchange_base_url, exchange_base_url,
url_len); url_len);
t->subject.debit.wtid = *subject; t->subject.debit.wtid = *subject;
if (NULL == request_uid) if (NULL == request_uid)
GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_NONCE, GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_NONCE,

View File

@ -179,10 +179,10 @@ credit_history_cb (void *cls,
global_ret = 0; global_ret = 0;
break; break;
case MHD_HTTP_OK: case MHD_HTTP_OK:
for (unsigned int i = 0; i<reply->details.success.details_length; i++) for (unsigned int i = 0; i<reply->details.ok.details_length; i++)
{ {
const struct TALER_BANK_CreditDetails *cd = const struct TALER_BANK_CreditDetails *cd =
&reply->details.success.details[i]; &reply->details.ok.details[i];
/* If credit/debit accounts were specified, use as a filter */ /* If credit/debit accounts were specified, use as a filter */
if ( (NULL != credit_account) && if ( (NULL != credit_account) &&
@ -279,10 +279,10 @@ debit_history_cb (void *cls,
global_ret = 0; global_ret = 0;
break; break;
case MHD_HTTP_OK: case MHD_HTTP_OK:
for (unsigned int i = 0; i<reply->details.success.details_length; i++) for (unsigned int i = 0; i<reply->details.ok.details_length; i++)
{ {
const struct TALER_BANK_DebitDetails *dd = const struct TALER_BANK_DebitDetails *dd =
&reply->details.success.details[i]; &reply->details.ok.details[i];
/* If credit/debit accounts were specified, use as a filter */ /* If credit/debit accounts were specified, use as a filter */
if ( (NULL != credit_account) && if ( (NULL != credit_account) &&

View File

@ -644,26 +644,19 @@ do_upload (char *const *args)
* a particular exchange and what keys the exchange is using. * a particular exchange and what keys the exchange is using.
* *
* @param cls closure with the `char **` remaining args * @param cls closure with the `char **` remaining args
* @param hr HTTP response data * @param kr response data
* @param keys information about the various keys used
* by the exchange, NULL if /keys failed
* @param compat protocol compatibility information
*/ */
static void static void
keys_cb ( keys_cb (
void *cls, void *cls,
const struct TALER_EXCHANGE_HttpResponse *hr, const struct TALER_EXCHANGE_KeysResponse *kr)
const struct TALER_EXCHANGE_Keys *keys,
enum TALER_EXCHANGE_VersionCompatibility compat)
{ {
char *const *args = cls; char *const *args = cls;
(void) keys; switch (kr->hr.http_status)
(void) compat;
switch (hr->http_status)
{ {
case MHD_HTTP_OK: case MHD_HTTP_OK:
if (! json_is_object (hr->reply)) if (! json_is_object (kr->hr.reply))
{ {
GNUNET_break (0); GNUNET_break (0);
TALER_EXCHANGE_disconnect (exchange); TALER_EXCHANGE_disconnect (exchange);
@ -676,9 +669,9 @@ keys_cb (
default: default:
fprintf (stderr, fprintf (stderr,
"Failed to download keys: %s (HTTP status: %u/%u)\n", "Failed to download keys: %s (HTTP status: %u/%u)\n",
hr->hint, kr->hr.hint,
hr->http_status, kr->hr.http_status,
(unsigned int) hr->ec); (unsigned int) kr->hr.ec);
TALER_EXCHANGE_disconnect (exchange); TALER_EXCHANGE_disconnect (exchange);
exchange = NULL; exchange = NULL;
test_shutdown (); test_shutdown ();
@ -689,7 +682,7 @@ keys_cb (
GNUNET_JSON_pack_string ("operation", GNUNET_JSON_pack_string ("operation",
OP_INPUT_KEYS), OP_INPUT_KEYS),
GNUNET_JSON_pack_object_incref ("arguments", GNUNET_JSON_pack_object_incref ("arguments",
(json_t *) hr->reply)); (json_t *) kr->hr.reply));
if (NULL == args[0]) if (NULL == args[0])
{ {
json_dumpf (in, json_dumpf (in,

View File

@ -24,6 +24,8 @@
#include "taler_json_lib.h" #include "taler_json_lib.h"
#include "taler_exchange_service.h" #include "taler_exchange_service.h"
#include "taler_extensions.h" #include "taler_extensions.h"
#include <regex.h>
/** /**
* Name of the input for the 'sign' and 'show' operation. * Name of the input for the 'sign' and 'show' operation.
@ -1119,14 +1121,15 @@ load_offline_key (int do_create)
* Function called with information about the post revocation operation result. * Function called with information about the post revocation operation result.
* *
* @param cls closure with a `struct DenomRevocationRequest` * @param cls closure with a `struct DenomRevocationRequest`
* @param hr HTTP response data * @param dr response data
*/ */
static void static void
denom_revocation_cb ( denom_revocation_cb (
void *cls, void *cls,
const struct TALER_EXCHANGE_HttpResponse *hr) const struct TALER_EXCHANGE_ManagementRevokeDenominationResponse *dr)
{ {
struct DenomRevocationRequest *drr = cls; struct DenomRevocationRequest *drr = cls;
const struct TALER_EXCHANGE_HttpResponse *hr = &dr->hr;
if (MHD_HTTP_NO_CONTENT != hr->http_status) if (MHD_HTTP_NO_CONTENT != hr->http_status)
{ {
@ -1208,14 +1211,15 @@ upload_denom_revocation (const char *exchange_url,
* Function called with information about the post revocation operation result. * Function called with information about the post revocation operation result.
* *
* @param cls closure with a `struct SignkeyRevocationRequest` * @param cls closure with a `struct SignkeyRevocationRequest`
* @param hr HTTP response data * @param sr response data
*/ */
static void static void
signkey_revocation_cb ( signkey_revocation_cb (
void *cls, void *cls,
const struct TALER_EXCHANGE_HttpResponse *hr) const struct TALER_EXCHANGE_ManagementRevokeSigningKeyResponse *sr)
{ {
struct SignkeyRevocationRequest *srr = cls; struct SignkeyRevocationRequest *srr = cls;
const struct TALER_EXCHANGE_HttpResponse *hr = &sr->hr;
if (MHD_HTTP_NO_CONTENT != hr->http_status) if (MHD_HTTP_NO_CONTENT != hr->http_status)
{ {
@ -1489,13 +1493,14 @@ upload_auditor_del (const char *exchange_url,
* Function called with information about the post wire add operation result. * Function called with information about the post wire add operation result.
* *
* @param cls closure with a `struct WireAddRequest` * @param cls closure with a `struct WireAddRequest`
* @param hr HTTP response data * @param wer response data
*/ */
static void static void
wire_add_cb (void *cls, wire_add_cb (void *cls,
const struct TALER_EXCHANGE_HttpResponse *hr) const struct TALER_EXCHANGE_ManagementWireEnableResponse *wer)
{ {
struct WireAddRequest *war = cls; struct WireAddRequest *war = cls;
const struct TALER_EXCHANGE_HttpResponse *hr = &wer->hr;
if (MHD_HTTP_NO_CONTENT != hr->http_status) if (MHD_HTTP_NO_CONTENT != hr->http_status)
{ {
@ -1533,10 +1538,21 @@ upload_wire_add (const char *exchange_url,
struct GNUNET_TIME_Timestamp start_time; struct GNUNET_TIME_Timestamp start_time;
struct WireAddRequest *war; struct WireAddRequest *war;
const char *err_name; const char *err_name;
const char *conversion_url = NULL;
json_t *debit_restrictions;
json_t *credit_restrictions;
unsigned int err_line; unsigned int err_line;
struct GNUNET_JSON_Specification spec[] = { struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("payto_uri", GNUNET_JSON_spec_string ("payto_uri",
&payto_uri), &payto_uri),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("conversion_url",
&conversion_url),
NULL),
GNUNET_JSON_spec_json ("debit_restrictions",
&debit_restrictions),
GNUNET_JSON_spec_json ("credit_restrictions",
&credit_restrictions),
GNUNET_JSON_spec_timestamp ("validity_start", GNUNET_JSON_spec_timestamp ("validity_start",
&start_time), &start_time),
GNUNET_JSON_spec_fixed_auto ("master_sig_add", GNUNET_JSON_spec_fixed_auto ("master_sig_add",
@ -1561,6 +1577,7 @@ upload_wire_add (const char *exchange_url,
stderr, stderr,
JSON_INDENT (2)); JSON_INDENT (2));
global_ret = EXIT_FAILURE; global_ret = EXIT_FAILURE;
GNUNET_JSON_parse_free (spec);
test_shutdown (); test_shutdown ();
return; return;
} }
@ -1574,6 +1591,7 @@ upload_wire_add (const char *exchange_url,
"payto:// URI `%s' is malformed\n", "payto:// URI `%s' is malformed\n",
payto_uri); payto_uri);
global_ret = EXIT_FAILURE; global_ret = EXIT_FAILURE;
GNUNET_JSON_parse_free (spec);
test_shutdown (); test_shutdown ();
return; return;
} }
@ -1588,6 +1606,7 @@ upload_wire_add (const char *exchange_url,
"payto URI is malformed: %s\n", "payto URI is malformed: %s\n",
msg); msg);
GNUNET_free (msg); GNUNET_free (msg);
GNUNET_JSON_parse_free (spec);
test_shutdown (); test_shutdown ();
global_ret = EXIT_INVALIDARGUMENT; global_ret = EXIT_INVALIDARGUMENT;
return; return;
@ -1599,6 +1618,9 @@ upload_wire_add (const char *exchange_url,
TALER_EXCHANGE_management_enable_wire (ctx, TALER_EXCHANGE_management_enable_wire (ctx,
exchange_url, exchange_url,
payto_uri, payto_uri,
conversion_url,
debit_restrictions,
credit_restrictions,
start_time, start_time,
&master_sig_add, &master_sig_add,
&master_sig_wire, &master_sig_wire,
@ -1607,6 +1629,7 @@ upload_wire_add (const char *exchange_url,
GNUNET_CONTAINER_DLL_insert (war_head, GNUNET_CONTAINER_DLL_insert (war_head,
war_tail, war_tail,
war); war);
GNUNET_JSON_parse_free (spec);
} }
@ -1614,13 +1637,14 @@ upload_wire_add (const char *exchange_url,
* Function called with information about the post wire del operation result. * Function called with information about the post wire del operation result.
* *
* @param cls closure with a `struct WireDelRequest` * @param cls closure with a `struct WireDelRequest`
* @param hr HTTP response data * @param wdres response data
*/ */
static void static void
wire_del_cb (void *cls, wire_del_cb (void *cls,
const struct TALER_EXCHANGE_HttpResponse *hr) const struct TALER_EXCHANGE_ManagementWireDisableResponse *wdres)
{ {
struct WireDelRequest *wdr = cls; struct WireDelRequest *wdr = cls;
const struct TALER_EXCHANGE_HttpResponse *hr = &wdres->hr;
if (MHD_HTTP_NO_CONTENT != hr->http_status) if (MHD_HTTP_NO_CONTENT != hr->http_status)
{ {
@ -1927,14 +1951,15 @@ upload_global_fee (const char *exchange_url,
* Function called with information about the drain profits operation. * Function called with information about the drain profits operation.
* *
* @param cls closure with a `struct DrainProfitsRequest` * @param cls closure with a `struct DrainProfitsRequest`
* @param hr HTTP response data * @param mdr response data
*/ */
static void static void
drain_profits_cb ( drain_profits_cb (
void *cls, void *cls,
const struct TALER_EXCHANGE_HttpResponse *hr) const struct TALER_EXCHANGE_ManagementDrainResponse *mdr)
{ {
struct DrainProfitsRequest *dpr = cls; struct DrainProfitsRequest *dpr = cls;
const struct TALER_EXCHANGE_HttpResponse *hr = &mdr->hr;
if (MHD_HTTP_NO_CONTENT != hr->http_status) if (MHD_HTTP_NO_CONTENT != hr->http_status)
{ {
@ -2033,14 +2058,15 @@ upload_drain (const char *exchange_url,
* Function called with information about the post upload keys operation result. * Function called with information about the post upload keys operation result.
* *
* @param cls closure with a `struct UploadKeysRequest` * @param cls closure with a `struct UploadKeysRequest`
* @param hr HTTP response data * @param mr response data
*/ */
static void static void
keys_cb ( keys_cb (
void *cls, void *cls,
const struct TALER_EXCHANGE_HttpResponse *hr) const struct TALER_EXCHANGE_ManagementPostKeysResponse *mr)
{ {
struct UploadKeysRequest *ukr = cls; struct UploadKeysRequest *ukr = cls;
const struct TALER_EXCHANGE_HttpResponse *hr = &mr->hr;
if (MHD_HTTP_NO_CONTENT != hr->http_status) if (MHD_HTTP_NO_CONTENT != hr->http_status)
{ {
@ -2206,14 +2232,15 @@ upload_keys (const char *exchange_url,
* Function called with information about the post upload extensions operation result. * Function called with information about the post upload extensions operation result.
* *
* @param cls closure with a `struct UploadExtensionsRequest` * @param cls closure with a `struct UploadExtensionsRequest`
* @param hr HTTP response data * @param er response data
*/ */
static void static void
extensions_cb ( extensions_cb (
void *cls, void *cls,
const struct TALER_EXCHANGE_HttpResponse *hr) const struct TALER_EXCHANGE_ManagementPostExtensionsResponse *er)
{ {
struct UploadExtensionsRequest *uer = cls; struct UploadExtensionsRequest *uer = cls;
const struct TALER_EXCHANGE_HttpResponse *hr = &er->hr;
if (MHD_HTTP_NO_CONTENT != hr->http_status) if (MHD_HTTP_NO_CONTENT != hr->http_status)
{ {
@ -2447,14 +2474,15 @@ add_partner (const char *exchange_url,
* Function called with information about the AML officer update operation. * Function called with information about the AML officer update operation.
* *
* @param cls closure with a `struct AmlStaffRequest` * @param cls closure with a `struct AmlStaffRequest`
* @param hr HTTP response data * @param ar response data
*/ */
static void static void
update_aml_officer_cb ( update_aml_officer_cb (
void *cls, void *cls,
const struct TALER_EXCHANGE_HttpResponse *hr) const struct TALER_EXCHANGE_ManagementUpdateAmlOfficerResponse *ar)
{ {
struct AmlStaffRequest *asr = cls; struct AmlStaffRequest *asr = cls;
const struct TALER_EXCHANGE_HttpResponse *hr = &ar->hr;
if (MHD_HTTP_NO_CONTENT != hr->http_status) if (MHD_HTTP_NO_CONTENT != hr->http_status)
{ {
@ -2949,6 +2977,96 @@ do_del_auditor (char *const *args)
} }
/**
* Parse account restriction.
*
* @param args the array of command-line arguments to process next
* @param[in,out] restrictions JSON array to update
* @return -1 on error, otherwise number of arguments from @a args that were used
*/
static int
parse_restriction (char *const *args,
json_t *restrictions)
{
if (NULL == args[0])
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Restriction TYPE argument missing\n");
return -1;
}
if (0 == strcmp (args[0],
"deny"))
{
GNUNET_assert (0 ==
json_array_append_new (
restrictions,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("type",
"deny"))));
return 1;
}
if (0 == strcmp (args[0],
"regex"))
{
json_t *i18n;
json_error_t err;
if ( (NULL == args[1]) ||
(NULL == args[2]) ||
(NULL == args[3]) )
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Mandatory arguments for restriction of type `regex' missing (REGEX, HINT, HINT-I18 required)\n");
return -1;
}
{
regex_t ex;
if (0 != regcomp (&ex,
args[1],
REG_NOSUB | REG_EXTENDED))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Invalid regular expression `%s'\n",
args[1]);
return -1;
}
regfree (&ex);
}
i18n = json_loads (args[3],
JSON_REJECT_DUPLICATES,
&err);
if (NULL == i18n)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Invalid JSON for restriction of type `regex': `%s` at %d\n",
args[3],
err.position);
return -1;
}
GNUNET_assert (0 ==
json_array_append_new (
restrictions,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("type",
"regex"),
GNUNET_JSON_pack_string ("regex",
args[1]),
GNUNET_JSON_pack_string ("human_hint",
args[2]),
GNUNET_JSON_pack_object_steal ("human_hint_i18n",
i18n)
)));
return 4;
}
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Restriction TYPE `%s' unsupported\n",
args[0]);
return -1;
}
/** /**
* Add wire account. * Add wire account.
* *
@ -2961,6 +3079,10 @@ do_add_wire (char *const *args)
struct TALER_MasterSignatureP master_sig_add; struct TALER_MasterSignatureP master_sig_add;
struct TALER_MasterSignatureP master_sig_wire; struct TALER_MasterSignatureP master_sig_wire;
struct GNUNET_TIME_Timestamp now; struct GNUNET_TIME_Timestamp now;
const char *conversion_url = NULL;
json_t *debit_restrictions;
json_t *credit_restrictions;
unsigned int num_args = 1;
if (NULL != in) if (NULL != in)
{ {
@ -3011,24 +3133,101 @@ do_add_wire (char *const *args)
} }
GNUNET_free (wire_method); GNUNET_free (wire_method);
} }
debit_restrictions = json_array ();
GNUNET_assert (NULL != debit_restrictions);
credit_restrictions = json_array ();
GNUNET_assert (NULL != credit_restrictions);
while (NULL != args[num_args])
{
if (0 == strcmp (args[num_args],
"conversion-url"))
{
num_args++;
conversion_url = args[num_args];
if (NULL == conversion_url)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"'conversion-url' requires an argument\n");
global_ret = EXIT_INVALIDARGUMENT;
test_shutdown ();
json_decref (debit_restrictions);
json_decref (credit_restrictions);
return;
}
num_args++;
continue;
}
if (0 == strcmp (args[num_args],
"credit-restriction"))
{
int iret;
num_args++;
iret = parse_restriction (&args[num_args],
credit_restrictions);
if (iret <= 0)
{
global_ret = EXIT_INVALIDARGUMENT;
test_shutdown ();
json_decref (debit_restrictions);
json_decref (credit_restrictions);
return;
}
num_args += iret;
continue;
}
if (0 == strcmp (args[num_args],
"debit-restriction"))
{
int iret;
num_args++;
iret = parse_restriction (&args[num_args],
debit_restrictions);
if (iret <= 0)
{
global_ret = EXIT_INVALIDARGUMENT;
test_shutdown ();
json_decref (debit_restrictions);
json_decref (credit_restrictions);
return;
}
num_args += iret;
continue;
}
break;
}
TALER_exchange_offline_wire_add_sign (args[0], TALER_exchange_offline_wire_add_sign (args[0],
conversion_url,
debit_restrictions,
credit_restrictions,
now, now,
&master_priv, &master_priv,
&master_sig_add); &master_sig_add);
TALER_exchange_wire_signature_make (args[0], TALER_exchange_wire_signature_make (args[0],
conversion_url,
debit_restrictions,
credit_restrictions,
&master_priv, &master_priv,
&master_sig_wire); &master_sig_wire);
output_operation (OP_ENABLE_WIRE, output_operation (OP_ENABLE_WIRE,
GNUNET_JSON_PACK ( GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("payto_uri", GNUNET_JSON_pack_string ("payto_uri",
args[0]), args[0]),
GNUNET_JSON_pack_array_steal ("debit_restrictions",
debit_restrictions),
GNUNET_JSON_pack_array_steal ("credit_restrictions",
credit_restrictions),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("conversion_url",
conversion_url)),
GNUNET_JSON_pack_timestamp ("validity_start", GNUNET_JSON_pack_timestamp ("validity_start",
now), now),
GNUNET_JSON_pack_data_auto ("master_sig_add", GNUNET_JSON_pack_data_auto ("master_sig_add",
&master_sig_add), &master_sig_add),
GNUNET_JSON_pack_data_auto ("master_sig_wire", GNUNET_JSON_pack_data_auto ("master_sig_wire",
&master_sig_wire))); &master_sig_wire)));
next (args + 1); next (args + num_args);
} }
@ -3643,18 +3842,15 @@ enable_aml_staff (char *const *args)
* whether there are subsequent commands). * whether there are subsequent commands).
* *
* @param cls closure with the `char **` remaining args * @param cls closure with the `char **` remaining args
* @param hr HTTP response data * @param mgr response data
* @param keys information about the various keys used
* by the exchange, NULL if /management/keys failed
*/ */
static void static void
download_cb (void *cls, download_cb (void *cls,
const struct TALER_EXCHANGE_HttpResponse *hr, const struct TALER_EXCHANGE_ManagementGetKeysResponse *mgr)
const struct TALER_EXCHANGE_FutureKeys *keys)
{ {
char *const *args = cls; char *const *args = cls;
const struct TALER_EXCHANGE_HttpResponse *hr = &mgr->hr;
(void) keys;
mgkh = NULL; mgkh = NULL;
switch (hr->http_status) switch (hr->http_status)
{ {
@ -5045,7 +5241,7 @@ work (void *cls)
{ {
.name = "enable-account", .name = "enable-account",
.help = .help =
"enable wire account of the exchange (payto-URI must be given as argument)", "enable wire account of the exchange (payto-URI must be given as argument; for optional argument see man page)",
.cb = &do_add_wire .cb = &do_add_wire
}, },
{ {

View File

@ -312,10 +312,10 @@ expired_reserve_cb (void *cls,
memset (&wtid, memset (&wtid,
0, 0,
sizeof (wtid)); sizeof (wtid));
memcpy (&wtid, GNUNET_memcpy (&wtid,
reserve_pub, reserve_pub,
GNUNET_MIN (sizeof (wtid), GNUNET_MIN (sizeof (wtid),
sizeof (*reserve_pub))); sizeof (*reserve_pub)));
qs = db_plugin->insert_reserve_closed (db_plugin->cls, qs = db_plugin->insert_reserve_closed (db_plugin->cls,
reserve_pub, reserve_pub,
now, now,

View File

@ -542,7 +542,6 @@ handle_get_aml (struct TEH_RequestContext *rc,
TALER_EC_GENERIC_DB_FETCH_FAILED, TALER_EC_GENERIC_DB_FETCH_FAILED,
NULL); NULL);
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection, return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_FORBIDDEN, MHD_HTTP_FORBIDDEN,
TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_ACCESS_DENIED, TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_ACCESS_DENIED,
@ -932,9 +931,9 @@ proceed_with_handler (struct TEH_RequestContext *rc,
/* Parse command-line arguments */ /* Parse command-line arguments */
/* make a copy of 'url' because 'strtok_r()' will modify */ /* make a copy of 'url' because 'strtok_r()' will modify */
memcpy (d, GNUNET_memcpy (d,
url, url,
ulen); ulen);
i = 0; i = 0;
args[i++] = strtok_r (d, "/", &sp); args[i++] = strtok_r (d, "/", &sp);
while ( (NULL != args[i - 1]) && while ( (NULL != args[i - 1]) &&
@ -1617,33 +1616,8 @@ handle_mhd_request (void *cls,
if (0 == strcasecmp (method, if (0 == strcasecmp (method,
MHD_HTTP_METHOD_POST)) MHD_HTTP_METHOD_POST))
{ {
const char *cl; TALER_MHD_check_content_length (connection,
TALER_MHD_REQUEST_BUFFER_MAX);
/* Maybe check for maximum upload size
and refuse requests if they are just too big. */
cl = MHD_lookup_connection_value (connection,
MHD_HEADER_KIND,
MHD_HTTP_HEADER_CONTENT_LENGTH);
if (NULL != cl)
{
unsigned long long cv;
char dummy;
if (1 != sscanf (cl,
"%llu%c",
&cv,
&dummy))
{
/* Not valid HTTP request, just close connection. */
GNUNET_break_op (0);
return MHD_NO;
}
if (cv > TALER_MHD_REQUEST_BUFFER_MAX)
{
GNUNET_break_op (0);
return TALER_MHD_reply_request_too_large (connection);
}
}
} }
} }
@ -2215,6 +2189,7 @@ do_shutdown (void *cls)
mhd = TALER_MHD_daemon_stop (); mhd = TALER_MHD_daemon_stop ();
TEH_resume_keys_requests (true); TEH_resume_keys_requests (true);
TEH_deposits_get_cleanup ();
TEH_reserves_get_cleanup (); TEH_reserves_get_cleanup ();
TEH_purses_get_cleanup (); TEH_purses_get_cleanup ();
TEH_kyc_check_cleanup (); TEH_kyc_check_cleanup ();

View File

@ -22,6 +22,8 @@
#include <gnunet/gnunet_util_lib.h> #include <gnunet/gnunet_util_lib.h>
#include <jansson.h> #include <jansson.h>
#include <microhttpd.h> #include <microhttpd.h>
#include "taler-exchange-httpd_metrics.h"
#include "taler_exchangedb_plugin.h"
#include "taler_mhd_lib.h" #include "taler_mhd_lib.h"
#include "taler-exchange-httpd_mhd.h" #include "taler-exchange-httpd_mhd.h"
#include "taler-exchange-httpd_age-withdraw_reveal.h" #include "taler-exchange-httpd_age-withdraw_reveal.h"
@ -387,12 +389,10 @@ denomination_is_valid (
struct TEH_DenominationKey *dks, struct TEH_DenominationKey *dks,
MHD_RESULT *result) MHD_RESULT *result)
{ {
dks = TEH_keys_denomination_by_hash2 ( dks = TEH_keys_denomination_by_hash2 (ksh,
ksh, denom_h,
denom_h, connection,
connection, result);
result);
if (NULL == dks) if (NULL == dks)
{ {
/* The denomination doesn't exist */ /* The denomination doesn't exist */
@ -635,7 +635,7 @@ verify_commitment_and_max_age (
} }
else else
{ {
/* FIXME:oec: Refactor this block out into its own function */ /* FIXME[oec] Refactor this block out into its own function */
size_t j = (TALER_CNC_KAPPA - 1) * c + k; /* Index into disclosed_coin_secrets[] */ size_t j = (TALER_CNC_KAPPA - 1) * c + k; /* Index into disclosed_coin_secrets[] */
const struct TALER_PlanchetMasterSecretP *secret; const struct TALER_PlanchetMasterSecretP *secret;
@ -784,6 +784,43 @@ verify_commitment_and_max_age (
} }
/**
* @brief Send a response for "/age-withdraw/$RCH/reveal"
*
* @param connection The http connection to the client to send the response to
* @param num_coins Number of new coins with age restriction for which we reveal data
* @param awrcs array of @a num_coins signatures revealed
* @return a MHD result code
*/
static MHD_RESULT
reply_age_withdraw_reveal_success (
struct MHD_Connection *connection,
unsigned int num_coins,
const struct TALER_EXCHANGEDB_AgeWithdrawRevealedCoin *awrcs)
{
json_t *list = json_array ();
GNUNET_assert (NULL != list);
for (unsigned int index = 0;
index < num_coins;
index++)
{
json_t *obj = GNUNET_JSON_PACK (
TALER_JSON_pack_blinded_denom_sig ("ev_sig",
&awrcs[index].coin_sig));
GNUNET_assert (0 ==
json_array_append_new (list,
obj));
}
return TALER_MHD_REPLY_JSON_PACK (
connection,
MHD_HTTP_OK,
GNUNET_JSON_pack_array_steal ("ev_sigs",
list));
}
/** /**
* @brief Signs and persists the undisclosed coins * @brief Signs and persists the undisclosed coins
* *
@ -796,7 +833,7 @@ verify_commitment_and_max_age (
* @return GNUNET_OK on success, GNUNET_SYSERR otherwise * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
*/ */
static enum GNUNET_GenericReturnValue static enum GNUNET_GenericReturnValue
finalize_age_withdraw_and_sign ( sign_and_finalize_age_withdraw (
struct MHD_Connection *connection, struct MHD_Connection *connection,
const struct TALER_AgeWithdrawCommitmentHashP *h_commitment, const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
const uint32_t num_coins, const uint32_t num_coins,
@ -806,7 +843,9 @@ finalize_age_withdraw_and_sign (
{ {
enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
struct TEH_CoinSignData csds[num_coins]; struct TEH_CoinSignData csds[num_coins];
struct TALER_BlindedDenominationSignature bss[num_coins]; struct TALER_BlindedDenominationSignature bds[num_coins];
struct TALER_EXCHANGEDB_AgeWithdrawRevealedCoin awrcs[num_coins];
enum GNUNET_DB_QueryStatus qs;
for (uint32_t i = 0; i<num_coins; i++) for (uint32_t i = 0; i<num_coins; i++)
{ {
@ -814,13 +853,13 @@ finalize_age_withdraw_and_sign (
csds[i].bp = &coin_evs[i]; csds[i].bp = &coin_evs[i];
} }
/* First, sign the the blinded coins */ /* Sign the the blinded coins first */
{ {
enum TALER_ErrorCode ec; enum TALER_ErrorCode ec;
ec = TEH_keys_denomination_batch_sign (csds, ec = TEH_keys_denomination_batch_sign (csds,
num_coins, num_coins,
false, false,
bss); bds);
if (TALER_EC_NONE != ec) if (TALER_EC_NONE != ec)
{ {
GNUNET_break (0); GNUNET_break (0);
@ -831,12 +870,107 @@ finalize_age_withdraw_and_sign (
} }
} }
/* TODO[oec]: GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
* - in a transaction: save the coins. "Signatures ready, starting DB interaction\n");
* - add signature response
*/
#pragma message "FIXME[oec]: implement finalize_age_withdraw_and_sign" /* Prepare the data for insertion */
for (uint32_t i = 0; i<num_coins; i++)
{
TALER_coin_ev_hash (&coin_evs[i],
csds[i].h_denom_pub,
&awrcs[i].h_coin_ev);
awrcs[i].h_denom_pub = *csds[i].h_denom_pub;
awrcs[i].coin_sig = bds[i];
}
/* Persist operation result in DB, transactionally */
for (unsigned int r = 0; r < MAX_TRANSACTION_COMMIT_RETRIES; r++)
{
bool changed = false;
/* Transaction start */
if (GNUNET_OK !=
TEH_plugin->start (TEH_plugin->cls,
"insert_age_withdraw_reveal batch"))
{
GNUNET_break (0);
ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_START_FAILED,
NULL);
goto cleanup;
}
qs = TEH_plugin->insert_age_withdraw_reveal (TEH_plugin->cls,
h_commitment,
num_coins,
awrcs);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
TEH_plugin->rollback (TEH_plugin->cls);
continue;
}
else if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_break (0);
TEH_plugin->rollback (TEH_plugin->cls);
ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_STORE_FAILED,
"insert_age_withdraw_reveal");
goto cleanup;
}
changed = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
/* Commit the transaction */
qs = TEH_plugin->commit (TEH_plugin->cls);
if (qs >= 0)
{
if (changed)
TEH_METRICS_num_success[TEH_MT_SUCCESS_AGE_WITHDRAW_REVEAL]++;
break; /* success */
}
else if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_break (0);
TEH_plugin->rollback (TEH_plugin->cls);
ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_COMMIT_FAILED,
NULL);
goto cleanup;
}
else
{
TEH_plugin->rollback (TEH_plugin->cls);
}
} /* end of retry */
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
GNUNET_break (0);
TEH_plugin->rollback (TEH_plugin->cls);
ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_SOFT_FAILURE,
NULL);
goto cleanup;
}
/* Generate final (positive) response */
ret = reply_age_withdraw_reveal_success (connection,
num_coins,
awrcs);
cleanup:
GNUNET_break (GNUNET_OK != ret);
/* Free resources */
for (unsigned int i = 0; i<num_coins; i++)
TALER_blinded_denom_sig_free (&awrcs[i].coin_sig);
return ret; return ret;
} }
@ -922,7 +1056,7 @@ TEH_handler_age_withdraw_reveal (
break; break;
/* Finally, sign and persist the coins */ /* Finally, sign and persist the coins */
if (GNUNET_OK != finalize_age_withdraw_and_sign ( if (GNUNET_OK != sign_and_finalize_age_withdraw (
rc->connection, rc->connection,
&actx.commitment.h_commitment, &actx.commitment.h_commitment,
actx.num_coins, actx.num_coins,

View File

@ -41,7 +41,7 @@
* *
* Returned via both /config and /keys endpoints. * Returned via both /config and /keys endpoints.
*/ */
#define EXCHANGE_PROTOCOL_VERSION "14:0:2" #define EXCHANGE_PROTOCOL_VERSION "15:0:0"
/** /**

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER This file is part of TALER
Copyright (C) 2014-2017, 2021 Taler Systems SA Copyright (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software terms of the GNU Affero General Public License as published by the Free Software
@ -23,6 +23,7 @@
#include <jansson.h> #include <jansson.h>
#include <microhttpd.h> #include <microhttpd.h>
#include <pthread.h> #include <pthread.h>
#include "taler_dbevents.h"
#include "taler_json_lib.h" #include "taler_json_lib.h"
#include "taler_mhd_lib.h" #include "taler_mhd_lib.h"
#include "taler_signatures.h" #include "taler_signatures.h"
@ -37,6 +38,26 @@
struct DepositWtidContext struct DepositWtidContext
{ {
/**
* Kept in a DLL.
*/
struct DepositWtidContext *next;
/**
* Kept in a DLL.
*/
struct DepositWtidContext *prev;
/**
* Context for the request we are processing.
*/
struct TEH_RequestContext *rc;
/**
* Subscription for the database event we are waiting for.
*/
struct GNUNET_DB_EventHandler *eh;
/** /**
* Hash over the proposal data of the contract for which this deposit is made. * Hash over the proposal data of the contract for which this deposit is made.
*/ */
@ -64,6 +85,12 @@ struct DepositWtidContext
*/ */
struct TALER_WireTransferIdentifierRawP wtid; struct TALER_WireTransferIdentifierRawP wtid;
/**
* Signature by the merchant.
*/
struct TALER_MerchantSignatureP merchant_sig;
/** /**
* Set by #handle_wtid data to the coin's contribution to the wire transfer. * Set by #handle_wtid data to the coin's contribution to the wire transfer.
*/ */
@ -79,6 +106,11 @@ struct DepositWtidContext
*/ */
struct GNUNET_TIME_Timestamp execution_time; struct GNUNET_TIME_Timestamp execution_time;
/**
* Timeout of the request, for long-polling.
*/
struct GNUNET_TIME_Absolute timeout;
/** /**
* Set by #handle_wtid to the coin contribution to the transaction * Set by #handle_wtid to the coin contribution to the transaction
* (that is, @e coin_contribution minus @e coin_fee). * (that is, @e coin_contribution minus @e coin_fee).
@ -101,9 +133,45 @@ struct DepositWtidContext
* Set to #GNUNET_SYSERR if there was a serious error. * Set to #GNUNET_SYSERR if there was a serious error.
*/ */
enum GNUNET_GenericReturnValue pending; enum GNUNET_GenericReturnValue pending;
/**
* #GNUNET_YES if we were suspended, #GNUNET_SYSERR
* if we were woken up due to shutdown.
*/
enum GNUNET_GenericReturnValue suspended;
}; };
/**
* Head of DLL of suspended requests.
*/
static struct DepositWtidContext *dwc_head;
/**
* Tail of DLL of suspended requests.
*/
static struct DepositWtidContext *dwc_tail;
void
TEH_deposits_get_cleanup ()
{
struct DepositWtidContext *n;
for (struct DepositWtidContext *ctx = dwc_head;
NULL != ctx;
ctx = n)
{
n = ctx->next;
GNUNET_assert (GNUNET_YES == ctx->suspended);
ctx->suspended = GNUNET_SYSERR;
MHD_resume_connection (ctx->rc->connection);
GNUNET_CONTAINER_DLL_remove (dwc_head,
dwc_tail,
ctx);
}
}
/** /**
* A merchant asked for details about a deposit. Provide * A merchant asked for details about a deposit. Provide
* them. Generates the 200 reply. * them. Generates the 200 reply.
@ -227,34 +295,97 @@ deposits_get_transaction (void *cls,
} }
/**
* Function called on events received from Postgres.
* Wakes up long pollers.
*
* @param cls the `struct DepositWtidContext *`
* @param extra additional event data provided
* @param extra_size number of bytes in @a extra
*/
static void
db_event_cb (void *cls,
const void *extra,
size_t extra_size)
{
struct DepositWtidContext *ctx = cls;
struct GNUNET_AsyncScopeSave old_scope;
(void) extra;
(void) extra_size;
if (GNUNET_NO != ctx->suspended)
return; /* might get multiple wake-up events */
GNUNET_CONTAINER_DLL_remove (dwc_head,
dwc_tail,
ctx);
GNUNET_async_scope_enter (&ctx->rc->async_scope_id,
&old_scope);
TEH_check_invariants ();
ctx->suspended = GNUNET_NO;
MHD_resume_connection (ctx->rc->connection);
TALER_MHD_daemon_trigger ();
TEH_check_invariants ();
GNUNET_async_scope_restore (&old_scope);
}
/** /**
* Lookup and return the wire transfer identifier. * Lookup and return the wire transfer identifier.
* *
* @param connection the MHD connection to handle
* @param ctx context of the signed request to execute * @param ctx context of the signed request to execute
* @return MHD result code * @return MHD result code
*/ */
static MHD_RESULT static MHD_RESULT
handle_track_transaction_request ( handle_track_transaction_request (
struct MHD_Connection *connection,
struct DepositWtidContext *ctx) struct DepositWtidContext *ctx)
{ {
MHD_RESULT mhd_ret; struct MHD_Connection *connection = ctx->rc->connection;
if (GNUNET_OK != if ( (GNUNET_TIME_absolute_is_future (ctx->timeout)) &&
TEH_DB_run_transaction (connection, (NULL == ctx->eh) )
"handle deposits GET", {
TEH_MT_REQUEST_OTHER, struct TALER_CoinDepositEventP rep = {
&mhd_ret, .header.size = htons (sizeof (rep)),
&deposits_get_transaction, .header.type = htons (TALER_DBEVENT_EXCHANGE_DEPOSIT_STATUS_CHANGED),
ctx)) .merchant_pub = ctx->merchant
return mhd_ret; };
ctx->eh = TEH_plugin->event_listen (
TEH_plugin->cls,
GNUNET_TIME_absolute_get_remaining (ctx->timeout),
&rep.header,
&db_event_cb,
ctx);
}
{
MHD_RESULT mhd_ret;
if (GNUNET_OK !=
TEH_DB_run_transaction (connection,
"handle deposits GET",
TEH_MT_REQUEST_OTHER,
&mhd_ret,
&deposits_get_transaction,
ctx))
return mhd_ret;
}
if (GNUNET_SYSERR == ctx->pending) if (GNUNET_SYSERR == ctx->pending)
return TALER_MHD_reply_with_error (connection, return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR, MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_INVARIANT_FAILURE, TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
"wire fees exceed aggregate in database"); "wire fees exceed aggregate in database");
if (ctx->pending) if (GNUNET_YES == ctx->pending)
{
if ( (GNUNET_TIME_absolute_is_future (ctx->timeout)) &&
(GNUNET_NO == ctx->suspended) )
{
GNUNET_CONTAINER_DLL_insert (dwc_head,
dwc_tail,
ctx);
ctx->suspended = GNUNET_YES;
MHD_suspend_connection (connection);
return MHD_YES;
}
return TALER_MHD_REPLY_JSON_PACK ( return TALER_MHD_REPLY_JSON_PACK (
connection, connection,
MHD_HTTP_ACCEPTED, MHD_HTTP_ACCEPTED,
@ -270,94 +401,118 @@ handle_track_transaction_request (
ctx->kyc.ok), ctx->kyc.ok),
GNUNET_JSON_pack_timestamp ("execution_time", GNUNET_JSON_pack_timestamp ("execution_time",
ctx->execution_time)); ctx->execution_time));
}
return reply_deposit_details (connection, return reply_deposit_details (connection,
ctx); ctx);
} }
/**
* Function called to clean up a context.
*
* @param rc request context with data to clean up
*/
static void
dwc_cleaner (struct TEH_RequestContext *rc)
{
struct DepositWtidContext *ctx = rc->rh_ctx;
GNUNET_assert (GNUNET_NO == ctx->suspended);
if (NULL != ctx->eh)
{
TEH_plugin->event_listen_cancel (TEH_plugin->cls,
ctx->eh);
ctx->eh = NULL;
}
GNUNET_free (ctx);
}
MHD_RESULT MHD_RESULT
TEH_handler_deposits_get (struct TEH_RequestContext *rc, TEH_handler_deposits_get (struct TEH_RequestContext *rc,
const char *const args[4]) const char *const args[4])
{ {
enum GNUNET_GenericReturnValue res; struct DepositWtidContext *ctx = rc->rh_ctx;
struct TALER_MerchantSignatureP merchant_sig;
struct DepositWtidContext ctx;
if (GNUNET_OK != if (NULL == ctx)
GNUNET_STRINGS_string_to_data (args[0],
strlen (args[0]),
&ctx.h_wire,
sizeof (ctx.h_wire)))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_WIRE,
args[0]);
}
if (GNUNET_OK !=
GNUNET_STRINGS_string_to_data (args[1],
strlen (args[1]),
&ctx.merchant,
sizeof (ctx.merchant)))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_MERCHANT_PUB,
args[1]);
}
if (GNUNET_OK !=
GNUNET_STRINGS_string_to_data (args[2],
strlen (args[2]),
&ctx.h_contract_terms,
sizeof (ctx.h_contract_terms)))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_CONTRACT_TERMS,
args[2]);
}
if (GNUNET_OK !=
GNUNET_STRINGS_string_to_data (args[3],
strlen (args[3]),
&ctx.coin_pub,
sizeof (ctx.coin_pub)))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_COIN_PUB,
args[3]);
}
res = TALER_MHD_parse_request_arg_data (rc->connection,
"merchant_sig",
&merchant_sig,
sizeof (merchant_sig));
if (GNUNET_SYSERR == res)
return MHD_NO; /* internal error */
if (GNUNET_NO == res)
return MHD_YES; /* parse error */
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
{ {
ctx = GNUNET_new (struct DepositWtidContext);
ctx->rc = rc;
rc->rh_ctx = ctx;
rc->rh_cleaner = &dwc_cleaner;
if (GNUNET_OK != if (GNUNET_OK !=
TALER_merchant_deposit_verify (&ctx.merchant, GNUNET_STRINGS_string_to_data (args[0],
&ctx.coin_pub, strlen (args[0]),
&ctx.h_contract_terms, &ctx->h_wire,
&ctx.h_wire, sizeof (ctx->h_wire)))
&merchant_sig))
{ {
GNUNET_break_op (0); GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection, return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_FORBIDDEN, MHD_HTTP_BAD_REQUEST,
TALER_EC_EXCHANGE_DEPOSITS_GET_MERCHANT_SIGNATURE_INVALID, TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_WIRE,
NULL); args[0]);
}
if (GNUNET_OK !=
GNUNET_STRINGS_string_to_data (args[1],
strlen (args[1]),
&ctx->merchant,
sizeof (ctx->merchant)))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_MERCHANT_PUB,
args[1]);
}
if (GNUNET_OK !=
GNUNET_STRINGS_string_to_data (args[2],
strlen (args[2]),
&ctx->h_contract_terms,
sizeof (ctx->h_contract_terms)))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_CONTRACT_TERMS,
args[2]);
}
if (GNUNET_OK !=
GNUNET_STRINGS_string_to_data (args[3],
strlen (args[3]),
&ctx->coin_pub,
sizeof (ctx->coin_pub)))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_COIN_PUB,
args[3]);
}
TALER_MHD_parse_request_arg_auto_t (rc->connection,
"merchant_sig",
&ctx->merchant_sig);
TALER_MHD_parse_request_timeout (rc->connection,
&ctx->timeout);
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
{
if (GNUNET_OK !=
TALER_merchant_deposit_verify (&ctx->merchant,
&ctx->coin_pub,
&ctx->h_contract_terms,
&ctx->h_wire,
&ctx->merchant_sig))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_FORBIDDEN,
TALER_EC_EXCHANGE_DEPOSITS_GET_MERCHANT_SIGNATURE_INVALID,
NULL);
}
} }
} }
return handle_track_transaction_request (rc->connection, return handle_track_transaction_request (ctx);
&ctx);
} }

View File

@ -26,6 +26,13 @@
#include "taler-exchange-httpd.h" #include "taler-exchange-httpd.h"
/**
* Resume long pollers on GET /deposits.
*/
void
TEH_deposits_get_cleanup (void);
/** /**
* Handle a "/deposits/$H_WIRE/$MERCHANT_PUB/$H_CONTRACT_TERMS/$COIN_PUB" * Handle a "/deposits/$H_WIRE/$MERCHANT_PUB/$H_CONTRACT_TERMS/$COIN_PUB"
* request. * request.

View File

@ -2377,9 +2377,10 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
} }
GNUNET_CONTAINER_multihashmap_iterator_destroy (iter); GNUNET_CONTAINER_multihashmap_iterator_destroy (iter);
GNUNET_CONTAINER_multihashmap_destroy (denominations_by_group);
} }
GNUNET_CONTAINER_multihashmap_destroy (denominations_by_group);
} }
GNUNET_CONTAINER_heap_destroy (heap); GNUNET_CONTAINER_heap_destroy (heap);

View File

@ -520,34 +520,8 @@ TEH_handler_kyc_check (
"usertype"); "usertype");
} }
{ TALER_MHD_parse_request_timeout (rc->connection,
const char *ts; &kyp->timeout);
ts = MHD_lookup_connection_value (rc->connection,
MHD_GET_ARGUMENT_KIND,
"timeout_ms");
if (NULL != ts)
{
char dummy;
unsigned long long tms;
if (1 !=
sscanf (ts,
"%llu%c",
&tms,
&dummy))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"timeout_ms");
}
kyp->timeout = GNUNET_TIME_relative_to_absolute (
GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
tms));
}
}
} }
if ( (NULL == kyp->eh) && if ( (NULL == kyp->eh) &&

View File

@ -297,7 +297,6 @@ TEH_handler_kyc_proof (
{ {
struct KycProofContext *kpc = rc->rh_ctx; struct KycProofContext *kpc = rc->rh_ctx;
const char *provider_section_or_logic = args[0]; const char *provider_section_or_logic = args[0];
const char *h_payto;
if (NULL == kpc) if (NULL == kpc)
{ {
@ -310,33 +309,13 @@ TEH_handler_kyc_proof (
TALER_EC_GENERIC_ENDPOINT_UNKNOWN, TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
"'/kyc-proof/$PROVIDER_SECTION?state=$H_PAYTO' required"); "'/kyc-proof/$PROVIDER_SECTION?state=$H_PAYTO' required");
} }
h_payto = MHD_lookup_connection_value (rc->connection,
MHD_GET_ARGUMENT_KIND,
"state");
if (NULL == h_payto)
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MISSING,
"h_payto");
}
kpc = GNUNET_new (struct KycProofContext); kpc = GNUNET_new (struct KycProofContext);
kpc->rc = rc; kpc->rc = rc;
rc->rh_ctx = kpc; rc->rh_ctx = kpc;
rc->rh_cleaner = &clean_kpc; rc->rh_cleaner = &clean_kpc;
if (GNUNET_OK != TALER_MHD_parse_request_arg_auto_t (rc->connection,
GNUNET_STRINGS_string_to_data (h_payto, "state",
strlen (h_payto), &kpc->h_payto);
&kpc->h_payto,
sizeof (kpc->h_payto)))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"h_payto");
}
if (GNUNET_OK != if (GNUNET_OK !=
TALER_KYCLOGIC_lookup_logic (provider_section_or_logic, TALER_KYCLOGIC_lookup_logic (provider_section_or_logic,
&kpc->logic, &kpc->logic,

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER This file is part of TALER
Copyright (C) 2020 Taler Systems SA Copyright (C) 2020-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software terms of the GNU Affero General Public License as published by the Free Software
@ -114,6 +114,9 @@ del_wire (void *cls,
} }
qs = TEH_plugin->update_wire (TEH_plugin->cls, qs = TEH_plugin->update_wire (TEH_plugin->cls,
awc->payto_uri, awc->payto_uri,
NULL,
NULL,
NULL,
awc->validity_end, awc->validity_end,
false); false);
if (qs < 0) if (qs < 0)

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER This file is part of TALER
Copyright (C) 2020 Taler Systems SA Copyright (C) 2020-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software terms of the GNU Affero General Public License as published by the Free Software
@ -54,6 +54,21 @@ struct AddWireContext
*/ */
const char *payto_uri; const char *payto_uri;
/**
* (optional) address of a conversion service for this account.
*/
const char *conversion_url;
/**
* Restrictions imposed when crediting this account.
*/
json_t *credit_restrictions;
/**
* Restrictions imposed when debiting this account.
*/
json_t *debit_restrictions;
/** /**
* Timestamp for checking against replay attacks. * Timestamp for checking against replay attacks.
*/ */
@ -114,11 +129,17 @@ add_wire (void *cls,
if (0 == qs) if (0 == qs)
qs = TEH_plugin->insert_wire (TEH_plugin->cls, qs = TEH_plugin->insert_wire (TEH_plugin->cls,
awc->payto_uri, awc->payto_uri,
awc->conversion_url,
awc->debit_restrictions,
awc->credit_restrictions,
awc->validity_start, awc->validity_start,
&awc->master_sig_wire); &awc->master_sig_wire);
else else
qs = TEH_plugin->update_wire (TEH_plugin->cls, qs = TEH_plugin->update_wire (TEH_plugin->cls,
awc->payto_uri, awc->payto_uri,
awc->conversion_url,
awc->debit_restrictions,
awc->credit_restrictions,
awc->validity_start, awc->validity_start,
true); true);
if (qs < 0) if (qs < 0)
@ -141,7 +162,9 @@ TEH_handler_management_post_wire (
struct MHD_Connection *connection, struct MHD_Connection *connection,
const json_t *root) const json_t *root)
{ {
struct AddWireContext awc; struct AddWireContext awc = {
.conversion_url = NULL
};
struct GNUNET_JSON_Specification spec[] = { struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("master_sig_wire", GNUNET_JSON_spec_fixed_auto ("master_sig_wire",
&awc.master_sig_wire), &awc.master_sig_wire),
@ -149,6 +172,14 @@ TEH_handler_management_post_wire (
&awc.master_sig_add), &awc.master_sig_add),
GNUNET_JSON_spec_string ("payto_uri", GNUNET_JSON_spec_string ("payto_uri",
&awc.payto_uri), &awc.payto_uri),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("conversion_url",
&awc.conversion_url),
NULL),
GNUNET_JSON_spec_json ("credit_restrictions",
&awc.credit_restrictions),
GNUNET_JSON_spec_json ("debit_restrictions",
&awc.debit_restrictions),
GNUNET_JSON_spec_timestamp ("validity_start", GNUNET_JSON_spec_timestamp ("validity_start",
&awc.validity_start), &awc.validity_start),
GNUNET_JSON_spec_end () GNUNET_JSON_spec_end ()
@ -179,17 +210,22 @@ TEH_handler_management_post_wire (
MHD_HTTP_BAD_REQUEST, MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PAYTO_URI_MALFORMED, TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
msg); msg);
GNUNET_JSON_parse_free (spec);
GNUNET_free (msg); GNUNET_free (msg);
return ret; return ret;
} }
} }
if (GNUNET_OK != if (GNUNET_OK !=
TALER_exchange_offline_wire_add_verify (awc.payto_uri, TALER_exchange_offline_wire_add_verify (awc.payto_uri,
awc.conversion_url,
awc.debit_restrictions,
awc.credit_restrictions,
awc.validity_start, awc.validity_start,
&TEH_master_public_key, &TEH_master_public_key,
&awc.master_sig_add)) &awc.master_sig_add))
{ {
GNUNET_break_op (0); GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_with_error ( return TALER_MHD_reply_with_error (
connection, connection,
MHD_HTTP_FORBIDDEN, MHD_HTTP_FORBIDDEN,
@ -199,10 +235,14 @@ TEH_handler_management_post_wire (
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
if (GNUNET_OK != if (GNUNET_OK !=
TALER_exchange_wire_signature_check (awc.payto_uri, TALER_exchange_wire_signature_check (awc.payto_uri,
awc.conversion_url,
awc.debit_restrictions,
awc.credit_restrictions,
&TEH_master_public_key, &TEH_master_public_key,
&awc.master_sig_wire)) &awc.master_sig_wire))
{ {
GNUNET_break_op (0); GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_with_error ( return TALER_MHD_reply_with_error (
connection, connection,
MHD_HTTP_FORBIDDEN, MHD_HTTP_FORBIDDEN,
@ -218,6 +258,7 @@ TEH_handler_management_post_wire (
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"payto:// URI `%s' is malformed\n", "payto:// URI `%s' is malformed\n",
awc.payto_uri); awc.payto_uri);
GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_with_error ( return TALER_MHD_reply_with_error (
connection, connection,
MHD_HTTP_BAD_REQUEST, MHD_HTTP_BAD_REQUEST,
@ -237,6 +278,7 @@ TEH_handler_management_post_wire (
&ret, &ret,
&add_wire, &add_wire,
&awc); &awc);
GNUNET_JSON_parse_free (spec);
if (GNUNET_SYSERR == res) if (GNUNET_SYSERR == res)
return ret; return ret;
} }

View File

@ -61,7 +61,8 @@ enum TEH_MetricTypeSuccess
TEH_MT_SUCCESS_BATCH_WITHDRAW = 3, TEH_MT_SUCCESS_BATCH_WITHDRAW = 3,
TEH_MT_SUCCESS_MELT = 4, TEH_MT_SUCCESS_MELT = 4,
TEH_MT_SUCCESS_REFRESH_REVEAL = 5, TEH_MT_SUCCESS_REFRESH_REVEAL = 5,
TEH_MT_SUCCESS_COUNT = 6 /* MUST BE LAST! */ TEH_MT_SUCCESS_AGE_WITHDRAW_REVEAL = 6,
TEH_MT_SUCCESS_COUNT = 7 /* MUST BE LAST! */
}; };
/** /**

View File

@ -57,29 +57,9 @@ TEH_handler_purses_delete (
TALER_EC_EXCHANGE_GENERIC_PURSE_PUB_MALFORMED, TALER_EC_EXCHANGE_GENERIC_PURSE_PUB_MALFORMED,
args[0]); args[0]);
} }
{ TALER_MHD_parse_request_header_auto_t (connection,
const char *sig; "Taler-Purse-Signature",
&purse_sig);
sig = MHD_lookup_connection_value (connection,
MHD_HEADER_KIND,
"Taler-Purse-Signature");
if ( (NULL == sig) ||
(GNUNET_OK !=
GNUNET_STRINGS_string_to_data (sig,
strlen (sig),
&purse_sig,
sizeof (purse_sig))) )
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
(NULL == sig)
? TALER_EC_GENERIC_PARAMETER_MISSING
: TALER_EC_GENERIC_PARAMETER_MALFORMED,
"Taler-Purse-Signature");
}
}
if (GNUNET_OK != if (GNUNET_OK !=
TALER_wallet_purse_delete_verify (&purse_pub, TALER_wallet_purse_delete_verify (&purse_pub,
&purse_sig)) &purse_sig))

View File

@ -243,36 +243,8 @@ TEH_handler_purses_get (struct TEH_RequestContext *rc,
args[1]); args[1]);
} }
{ TALER_MHD_parse_request_timeout (rc->connection,
const char *long_poll_timeout_ms; &gc->timeout);
long_poll_timeout_ms
= MHD_lookup_connection_value (rc->connection,
MHD_GET_ARGUMENT_KIND,
"timeout_ms");
if (NULL != long_poll_timeout_ms)
{
unsigned int timeout_ms;
char dummy;
struct GNUNET_TIME_Relative timeout;
if (1 != sscanf (long_poll_timeout_ms,
"%u%c",
&timeout_ms,
&dummy))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"timeout_ms (must be non-negative number)");
}
timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
timeout_ms);
gc->timeout = GNUNET_TIME_relative_to_absolute (timeout);
}
}
if ( (GNUNET_TIME_absolute_is_future (gc->timeout)) && if ( (GNUNET_TIME_absolute_is_future (gc->timeout)) &&
(NULL == gc->eh) ) (NULL == gc->eh) )
{ {

View File

@ -773,12 +773,17 @@ clean_age:
NULL); NULL);
goto cleanup; goto cleanup;
} }
for (unsigned int i = 0; i<rctx->num_fresh_coins; i++) for (unsigned int i = 0; i<rctx->num_fresh_coins; i++)
{
rrcs[i].coin_sig = bss[i]; rrcs[i].coin_sig = bss[i];
rrcs[i].blinded_planchet = rcds[i].blinded_planchet;
}
} }
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Signatures ready, starting DB interaction\n"); "Signatures ready, starting DB interaction\n");
for (unsigned int r = 0; r<MAX_TRANSACTION_COMMIT_RETRIES; r++) for (unsigned int r = 0; r<MAX_TRANSACTION_COMMIT_RETRIES; r++)
{ {
bool changed; bool changed;
@ -795,12 +800,7 @@ clean_age:
NULL); NULL);
goto cleanup; goto cleanup;
} }
for (unsigned int i = 0; i<rctx->num_fresh_coins; i++)
{
struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i];
rrc->blinded_planchet = rcds[i].blinded_planchet;
}
qs = TEH_plugin->insert_refresh_reveal ( qs = TEH_plugin->insert_refresh_reveal (
TEH_plugin->cls, TEH_plugin->cls,
melt_serial_id, melt_serial_id,

View File

@ -52,8 +52,12 @@ struct ReservePoller
struct MHD_Connection *connection; struct MHD_Connection *connection;
/** /**
* Subscription for the database event we are * Our request context.
* waiting for. */
struct TEH_RequestContext *rc;
/**
* Subscription for the database event we are waiting for.
*/ */
struct GNUNET_DB_EventHandler *eh; struct GNUNET_DB_EventHandler *eh;
@ -154,6 +158,8 @@ db_event_cb (void *cls,
(void) extra_size; (void) extra_size;
if (! rp->suspended) if (! rp->suspended)
return; /* might get multiple wake-up events */ return; /* might get multiple wake-up events */
GNUNET_async_scope_enter (&rp->rc->async_scope_id,
&old_scope);
TEH_check_invariants (); TEH_check_invariants ();
rp->suspended = false; rp->suspended = false;
MHD_resume_connection (rp->connection); MHD_resume_connection (rp->connection);
@ -171,11 +177,9 @@ TEH_handler_reserves_get (struct TEH_RequestContext *rc,
if (NULL == rp) if (NULL == rp)
{ {
struct GNUNET_TIME_Relative timeout
= GNUNET_TIME_UNIT_ZERO;
rp = GNUNET_new (struct ReservePoller); rp = GNUNET_new (struct ReservePoller);
rp->connection = rc->connection; rp->connection = rc->connection;
rp->rc = rc;
rc->rh_ctx = rp; rc->rh_ctx = rp;
rc->rh_cleaner = &rp_cleanup; rc->rh_cleaner = &rp_cleanup;
GNUNET_CONTAINER_DLL_insert (rp_head, GNUNET_CONTAINER_DLL_insert (rp_head,
@ -193,34 +197,8 @@ TEH_handler_reserves_get (struct TEH_RequestContext *rc,
TALER_EC_GENERIC_RESERVE_PUB_MALFORMED, TALER_EC_GENERIC_RESERVE_PUB_MALFORMED,
args[0]); args[0]);
} }
{ TALER_MHD_parse_request_timeout (rc->connection,
const char *long_poll_timeout_ms; &rp->timeout);
long_poll_timeout_ms
= MHD_lookup_connection_value (rc->connection,
MHD_GET_ARGUMENT_KIND,
"timeout_ms");
if (NULL != long_poll_timeout_ms)
{
unsigned int timeout_ms;
char dummy;
if (1 != sscanf (long_poll_timeout_ms,
"%u%c",
&timeout_ms,
&dummy))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"timeout_ms (must be non-negative number)");
}
timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
timeout_ms);
}
}
rp->timeout = GNUNET_TIME_relative_to_absolute (timeout);
} }
if ( (GNUNET_TIME_absolute_is_future (rp->timeout)) && if ( (GNUNET_TIME_absolute_is_future (rp->timeout)) &&

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER This file is part of TALER
Copyright (C) 2015-2022 Taler Systems SA Copyright (C) 2015-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software terms of the GNU Affero General Public License as published by the Free Software
@ -224,12 +224,18 @@ TEH_wire_done ()
* *
* @param cls a `json_t *` object to expand with wire account details * @param cls a `json_t *` object to expand with wire account details
* @param payto_uri the exchange bank account URI to add * @param payto_uri the exchange bank account URI to add
* @param conversion_url URL of a conversion service, NULL if there is no conversion
* @param debit_restrictions JSON array with debit restrictions on the account
* @param credit_restrictions JSON array with credit restrictions on the account
* @param master_sig master key signature affirming that this is a bank * @param master_sig master key signature affirming that this is a bank
* account of the exchange (of purpose #TALER_SIGNATURE_MASTER_WIRE_DETAILS) * account of the exchange (of purpose #TALER_SIGNATURE_MASTER_WIRE_DETAILS)
*/ */
static void static void
add_wire_account (void *cls, add_wire_account (void *cls,
const char *payto_uri, const char *payto_uri,
const char *conversion_url,
const json_t *debit_restrictions,
const json_t *credit_restrictions,
const struct TALER_MasterSignatureP *master_sig) const struct TALER_MasterSignatureP *master_sig)
{ {
json_t *a = cls; json_t *a = cls;
@ -240,6 +246,13 @@ add_wire_account (void *cls,
GNUNET_JSON_PACK ( GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("payto_uri", GNUNET_JSON_pack_string ("payto_uri",
payto_uri), payto_uri),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("conversion_url",
conversion_url)),
GNUNET_JSON_pack_array_incref ("debit_restrictions",
(json_t *) debit_restrictions),
GNUNET_JSON_pack_array_incref ("credit_restrictions",
(json_t *) credit_restrictions),
GNUNET_JSON_pack_data_auto ("master_sig", GNUNET_JSON_pack_data_auto ("master_sig",
master_sig)))) master_sig))))
{ {
@ -462,6 +475,8 @@ build_wire_state (void)
wsh->wire_reply = TALER_MHD_MAKE_JSON_PACK ( wsh->wire_reply = TALER_MHD_MAKE_JSON_PACK (
GNUNET_JSON_pack_array_steal ("accounts", GNUNET_JSON_pack_array_steal ("accounts",
wire_accounts_array), wire_accounts_array),
GNUNET_JSON_pack_array_steal ("wads", /* #7271 */
json_array ()),
GNUNET_JSON_pack_object_steal ("fees", GNUNET_JSON_pack_object_steal ("fees",
wire_fee_object), wire_fee_object),
GNUNET_JSON_pack_data_auto ("master_public_key", GNUNET_JSON_pack_data_auto ("master_public_key",

View File

@ -563,9 +563,9 @@ wire_prepare_cb (void *cls,
} }
wpd = GNUNET_malloc (sizeof (struct WirePrepareData) wpd = GNUNET_malloc (sizeof (struct WirePrepareData)
+ buf_size); + buf_size);
memcpy (&wpd[1], GNUNET_memcpy (&wpd[1],
buf, buf,
buf_size); buf_size);
wpd->buf_size = buf_size; wpd->buf_size = buf_size;
wpd->row_id = rowid; wpd->row_id = rowid;
GNUNET_CONTAINER_DLL_insert (wpd_head, GNUNET_CONTAINER_DLL_insert (wpd_head,

View File

@ -731,8 +731,8 @@ history_cb (void *cls,
{ {
case MHD_HTTP_OK: case MHD_HTTP_OK:
process_reply (wrap_size, process_reply (wrap_size,
reply->details.success.details, reply->details.ok.details,
reply->details.success.details_length); reply->details.ok.details_length);
return; return;
case MHD_HTTP_NO_CONTENT: case MHD_HTTP_NO_CONTENT:
transaction_completed (); transaction_completed ();

View File

@ -12,4 +12,5 @@ test-exchangedb-batch-reserves-in-insert-postgres
test-exchangedb-by-j-postgres test-exchangedb-by-j-postgres
test-exchangedb-populate-link-data-postgres test-exchangedb-populate-link-data-postgres
test-exchangedb-populate-ready-deposit-postgres test-exchangedb-populate-ready-deposit-postgres
test-exchangedb-populate-select-refunds-by-coin-postgres test-exchangedb-populate-select-refunds-by-coin-postgres
exchange-0004.sql

View File

@ -14,24 +14,24 @@
-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> -- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-- --
CREATE FUNCTION create_table_withdraw_age_commitments( CREATE FUNCTION create_table_age_withdraw_commitments(
IN partition_suffix VARCHAR DEFAULT NULL IN partition_suffix VARCHAR DEFAULT NULL
) )
RETURNS VOID RETURNS VOID
LANGUAGE plpgsql LANGUAGE plpgsql
AS $$ AS $$
DECLARE DECLARE
table_name VARCHAR DEFAULT 'withdraw_age_commitments'; table_name VARCHAR DEFAULT 'age_withdraw_commitments';
BEGIN BEGIN
PERFORM create_partitioned_table( PERFORM create_partitioned_table(
'CREATE TABLE %I' 'CREATE TABLE %I'
'(withdraw_age_commitment_id BIGINT GENERATED BY DEFAULT AS IDENTITY' '(age_withdraw_commitment_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
',h_commitment BYTEA PRIMARY KEY CHECK (LENGTH(h_commitment)=64)' ',h_commitment BYTEA CHECK (LENGTH(h_commitment)=64)'
',amount_with_fee_val INT8 NOT NULL' ',amount_with_fee_val INT8 NOT NULL'
',amount_with_fee_frac INT4 NOT NULL' ',amount_with_fee_frac INT4 NOT NULL'
',max_age INT2 NOT NULL' ',max_age INT2 NOT NULL'
',reserve_pub BYTEA NOT NULL CHECK (LENGTH(reserve_pub)=32)' ',reserve_pub BYTEA CHECK (LENGTH(reserve_pub)=32)'
',reserve_sig BYTEA CHECK (LENGTH(reserve_sig)=64)' ',reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)'
',noreveal_index INT4 NOT NULL' ',noreveal_index INT4 NOT NULL'
') %s ;' ') %s ;'
,table_name ,table_name
@ -77,66 +77,58 @@ END
$$; $$;
CREATE FUNCTION constrain_table_withdraw_age_commitments( CREATE FUNCTION constrain_table_age_withdraw_commitments(
IN partition_suffix VARCHAR IN partition_suffix VARCHAR
) )
RETURNS void RETURNS void
LANGUAGE plpgsql LANGUAGE plpgsql
AS $$ AS $$
DECLARE DECLARE
table_name VARCHAR DEFAULT 'withdraw_age_commitments'; table_name VARCHAR DEFAULT 'age_withdraw_commitments';
BEGIN BEGIN
table_name = concat_ws('_', table_name, partition_suffix); table_name = concat_ws('_', table_name, partition_suffix);
EXECUTE FORMAT ( EXECUTE FORMAT (
'ALTER TABLE ' || table_name || 'ALTER TABLE ' || table_name ||
' ADD PRIMARY KEY (h_commitment, reserve_pub);' ' ADD PRIMARY KEY (h_commitment);'
); );
EXECUTE FORMAT ( EXECUTE FORMAT (
'ALTER TABLE ' || table_name || 'ALTER TABLE ' || table_name ||
' ADD CONSTRAINT ' || table_name || '_withdraw_age_commitment_id_key' ' ADD CONSTRAINT ' || table_name || '_h_commitment_reserve_pub_key'
' UNIQUE (withdraw_age_commitment_id);' ' UNIQUE (h_commitment, reserve_pub);'
);
EXECUTE FORMAT (
'ALTER TABLE ' || table_name ||
' ADD CONSTRAINT ' || table_name || '_age_withdraw_commitment_id_key'
' UNIQUE (age_withdraw_commitment_id);'
); );
END END
$$; $$;
CREATE FUNCTION foreign_table_withdraw_age_commitments() CREATE FUNCTION foreign_table_age_withdraw_commitments()
RETURNS void RETURNS void
LANGUAGE plpgsql LANGUAGE plpgsql
AS $$ AS $$
DECLARE DECLARE
table_name VARCHAR DEFAULT 'withdraw_age_commitments'; table_name VARCHAR DEFAULT 'age_withdraw_commitments';
BEGIN BEGIN
EXECUTE FORMAT ( EXECUTE FORMAT (
'ALTER TABLE ' || table_name || 'ALTER TABLE ' || table_name ||
' ADD CONSTRAINT ' || table_name || '_foreign_reserve_pub' ' ADD CONSTRAINT ' || table_name || '_foreign_reserve_pub'
' FOREIGN KEY (reserve_pub)' ' FOREIGN KEY (reserve_pub)'
' REFERENCES reserves (reserve_pub) ON DELETE CASCADE;' ' REFERENCES reserves(reserve_pub) ON DELETE CASCADE;'
); );
END END
$$; $$;
INSERT INTO exchange_tables INSERT INTO exchange_tables
(name (name
,version ,version
,action ,action
,partitioned ,partitioned
,by_range) ,by_range)
VALUES VALUES
('withdraw_age_commitments' ('age_withdraw_commitments', 'exchange-0003', 'create', TRUE ,FALSE),
,'exchange-0003' ('age_withdraw_commitments', 'exchange-0003', 'constrain',TRUE ,FALSE),
,'create' ('age_withdraw_commitments', 'exchange-0003', 'foreign', TRUE ,FALSE);
,TRUE
,FALSE),
('withdraw_age_commitments'
,'exchange-0003'
,'constrain'
,TRUE
,FALSE),
('withdraw_age_commitments'
,'exchange-0003'
,'foreign'
,TRUE
,FALSE);

View File

@ -14,25 +14,24 @@
-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> -- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-- --
CREATE FUNCTION create_table_withdraw_age_revealed_coins( CREATE FUNCTION create_table_age_withdraw_revealed_coins(
IN partition_suffix VARCHAR DEFAULT NULL IN partition_suffix VARCHAR DEFAULT NULL
) )
RETURNS VOID RETURNS VOID
LANGUAGE plpgsql LANGUAGE plpgsql
AS $$ AS $$
DECLARE DECLARE
table_name VARCHAR DEFAULT 'withdraw_age_revealed_coins'; table_name VARCHAR DEFAULT 'age_withdraw_revealed_coins';
BEGIN BEGIN
PERFORM create_partitioned_table( PERFORM create_partitioned_table(
'CREATE TABLE %I' 'CREATE TABLE %I'
'(withdraw_age_revealed_coins_id BIGINT GENERATED BY DEFAULT AS IDENTITY' -- UNIQUE '(age_withdraw_revealed_coins_id BIGINT GENERATED BY DEFAULT AS IDENTITY' -- UNIQUE
',h_commitment BYTEA NOT NULL CHECK (LENGTH(h_commitment)=64)' ',h_commitment BYTEA NOT NULL CHECK (LENGTH(h_commitment)=64)'
',freshcoin_index INT4 NOT NULL' ',freshcoin_index INT4 NOT NULL'
',denominations_serial INT8 NOT NULL' ',denominations_serial INT8 NOT NULL'
',coin_ev BYTEA NOT NULL' ',coin_ev BYTEA NOT NULL'
',h_coin_ev BYTEA CHECK (LENGTH(h_coin_ev)=64)' ',h_coin_ev BYTEA CHECK (LENGTH(h_coin_ev)=64)'
',ev_sig BYTEA NOT NULL' ',ev_sig BYTEA NOT NULL'
',ewv BYTEA NOT NULL'
') %s ;' ') %s ;'
,table_name ,table_name
,'PARTITION BY HASH (h_commitment)' ,'PARTITION BY HASH (h_commitment)'
@ -79,30 +78,24 @@ BEGIN
,table_name ,table_name
,partition_suffix ,partition_suffix
); );
PERFORM comment_partitioned_column(
'Exchange contributed values in the creation of the fresh coin (see /csr)'
,'ewv'
,table_name
,partition_suffix
);
END END
$$; $$;
CREATE FUNCTION constrain_table_withdraw_age_revealed_coins( CREATE FUNCTION constrain_table_age_withdraw_revealed_coins(
IN partition_suffix VARCHAR IN partition_suffix VARCHAR
) )
RETURNS void RETURNS void
LANGUAGE plpgsql LANGUAGE plpgsql
AS $$ AS $$
DECLARE DECLARE
table_name VARCHAR DEFAULT 'withdraw_age_revealed_coins'; table_name VARCHAR DEFAULT 'age_withdraw_revealed_coins';
BEGIN BEGIN
table_name = concat_ws('_', table_name, partition_suffix); table_name = concat_ws('_', table_name, partition_suffix);
EXECUTE FORMAT ( EXECUTE FORMAT (
'ALTER TABLE ' || table_name || 'ALTER TABLE ' || table_name ||
' ADD CONSTRAINT ' || table_name || '_withdraw_age_revealed_coins_id_key' ' ADD CONSTRAINT ' || table_name || '_age_withdraw_revealed_coins_id_key'
' UNIQUE (withdraw_age_revealed_coins_id);' ' UNIQUE (age_withdraw_revealed_coins_id);'
); );
EXECUTE FORMAT ( EXECUTE FORMAT (
'ALTER TABLE ' || table_name || 'ALTER TABLE ' || table_name ||
@ -112,18 +105,18 @@ BEGIN
END END
$$; $$;
CREATE FUNCTION foreign_table_withdraw_age_revealed_coins() CREATE FUNCTION foreign_table_age_withdraw_revealed_coins()
RETURNS void RETURNS void
LANGUAGE plpgsql LANGUAGE plpgsql
AS $$ AS $$
DECLARE DECLARE
table_name VARCHAR DEFAULT 'withdraw_age_revealed_coins'; table_name VARCHAR DEFAULT 'age_withdraw_revealed_coins';
BEGIN BEGIN
EXECUTE FORMAT ( EXECUTE FORMAT (
'ALTER TABLE ' || table_name || 'ALTER TABLE ' || table_name ||
' ADD CONSTRAINT ' || table_name || '_foreign_h_commitment' ' ADD CONSTRAINT ' || table_name || '_foreign_h_commitment'
' FOREIGN KEY (h_commitment)' ' FOREIGN KEY (h_commitment)'
' REFERENCES withdraw_age_commitments (h_commitment) ON DELETE CASCADE;' ' REFERENCES age_withdraw_commitments (h_commitment) ON DELETE CASCADE;'
); );
EXECUTE FORMAT ( EXECUTE FORMAT (
'ALTER TABLE ' || table_name || 'ALTER TABLE ' || table_name ||
@ -142,17 +135,17 @@ INSERT INTO exchange_tables
,partitioned ,partitioned
,by_range) ,by_range)
VALUES VALUES
('withdraw_age_revealed_coins' ('age_withdraw_revealed_coins'
,'exchange-0003' ,'exchange-0003'
,'create' ,'create'
,TRUE ,TRUE
,FALSE), ,FALSE),
('withdraw_age_revealed_coins' ('age_withdraw_revealed_coins'
,'exchange-0003' ,'exchange-0003'
,'constrain' ,'constrain'
,TRUE ,TRUE
,FALSE), ,FALSE),
('withdraw_age_revealed_coins' ('age_withdraw_revealed_coins'
,'exchange-0003' ,'exchange-0003'
,'foreign' ,'foreign'
,TRUE ,TRUE

View File

@ -0,0 +1,26 @@
--
-- This file is part of TALER
-- Copyright (C) 2023 Taler Systems SA
--
-- TALER is free software; you can redistribute it and/or modify it under the
-- terms of the GNU General Public License as published by the Free Software
-- Foundation; either version 3, or (at your option) any later version.
--
-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License along with
-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
--
ALTER TABLE wire_accounts
ADD COLUMN conversion_url VARCHAR DEFAULT (NULL),
ADD COLUMN debit_restrictions VARCHAR DEFAULT (NULL),
ADD COLUMN credit_restrictions VARCHAR DEFAULT (NULL);
COMMENT ON COLUMN wire_accounts.conversion_url
IS 'URL of a currency conversion service if conversion is needed when this account is used; NULL if there is no conversion.';
COMMENT ON COLUMN wire_accounts.debit_restrictions
IS 'JSON array describing restrictions imposed when debiting this account. Empty for no restrictions, NULL if account was migrated from previous database revision or account is disabled.';
COMMENT ON COLUMN wire_accounts.credit_restrictions
IS 'JSON array describing restrictions imposed when crediting this account. Empty for no restrictions, NULL if account was migrated from previous database revision or account is disabled.';

View File

@ -20,7 +20,9 @@ sqlinputs = \
0002-*.sql \ 0002-*.sql \
exchange-0002.sql.in \ exchange-0002.sql.in \
0003-*.sql \ 0003-*.sql \
exchange-0003.sql.in exchange-0003.sql.in \
0004-*.sql \
exchange-0004.sql.in
sql_DATA = \ sql_DATA = \
benchmark-0001.sql \ benchmark-0001.sql \
@ -28,6 +30,7 @@ sql_DATA = \
exchange-0001.sql \ exchange-0001.sql \
exchange-0002.sql \ exchange-0002.sql \
exchange-0003.sql \ exchange-0003.sql \
exchange-0004.sql \
drop.sql \ drop.sql \
procedures.sql procedures.sql
@ -39,7 +42,8 @@ BUILT_SOURCES = \
CLEANFILES = \ CLEANFILES = \
exchange-0002.sql \ exchange-0002.sql \
exchange-0003.sql exchange-0003.sql \
exchange-0004.sql
procedures.sql: procedures.sql.in exchange_do_*.sql procedures.sql: procedures.sql.in exchange_do_*.sql
chmod +w $@ || true chmod +w $@ || true
@ -56,6 +60,11 @@ exchange-0003.sql: exchange-0003.sql.in 0003-*.sql
gcc -E -P -undef - < exchange-0003.sql.in 2>/dev/null | sed -e "s/--.*//" | awk 'NF' - >$@ gcc -E -P -undef - < exchange-0003.sql.in 2>/dev/null | sed -e "s/--.*//" | awk 'NF' - >$@
chmod ugo-w $@ chmod ugo-w $@
exchange-0004.sql: exchange-0004.sql.in 0004-*.sql
chmod +w $@ || true
gcc -E -P -undef - < exchange-0004.sql.in 2>/dev/null | sed -e "s/--.*//" | awk 'NF' - >$@
chmod ugo-w $@
EXTRA_DIST = \ EXTRA_DIST = \
exchangedb.conf \ exchangedb.conf \
exchangedb-postgres.conf \ exchangedb-postgres.conf \

View File

@ -169,9 +169,9 @@ bem_insert (struct GNUNET_PQ_Context *conn,
GNUNET_CRYPTO_hash (&b, GNUNET_CRYPTO_hash (&b,
sizeof (b), sizeof (b),
&hc); &hc);
memcpy (&ihc, GNUNET_memcpy (&ihc,
&hc, &hc,
sizeof (ihc)); sizeof (ihc));
{ {
struct GNUNET_PQ_QueryParam params[] = { struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (&hc), GNUNET_PQ_query_param_auto_from_type (&hc),
@ -265,9 +265,9 @@ bem_select (struct GNUNET_PQ_Context *conn,
GNUNET_CRYPTO_hash (&b, GNUNET_CRYPTO_hash (&b,
sizeof (b), sizeof (b),
&hc); &hc);
memcpy (&ihc, GNUNET_memcpy (&ihc,
&hc, &hc,
sizeof (ihc)); sizeof (ihc));
{ {
struct GNUNET_PQ_QueryParam params[] = { struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint32 (&ihc), GNUNET_PQ_query_param_uint32 (&ihc),

View File

@ -21,6 +21,7 @@ BEGIN;
SELECT _v.unregister_patch('exchange-0001'); SELECT _v.unregister_patch('exchange-0001');
SELECT _v.unregister_patch('exchange-0002'); SELECT _v.unregister_patch('exchange-0002');
SELECT _v.unregister_patch('exchange-0003'); SELECT _v.unregister_patch('exchange-0003');
SELECT _v.unregister_patch('exchange-0004');
DROP SCHEMA exchange CASCADE; DROP SCHEMA exchange CASCADE;

View File

@ -25,6 +25,8 @@ SET search_path TO exchange;
#include "0003-aml_status.sql" #include "0003-aml_status.sql"
#include "0003-aml_staff.sql" #include "0003-aml_staff.sql"
#include "0003-aml_history.sql" #include "0003-aml_history.sql"
#include "0003-age_withdraw_commitments.sql"
#include "0003-age_withdraw_reveals.sql"
COMMIT; COMMIT;

View File

@ -0,0 +1,24 @@
--
-- This file is part of TALER
-- Copyright (C) 2014--2023 Taler Systems SA
--
-- TALER is free software; you can redistribute it and/or modify it under the
-- terms of the GNU General Public License as published by the Free Software
-- Foundation; either version 3, or (at your option) any later version.
--
-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License along with
-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
--
BEGIN;
SELECT _v.register_patch('exchange-0004', NULL, NULL);
SET search_path TO exchange;
#include "0004-wire_accounts.sql"
COMMIT;

View File

@ -22,6 +22,7 @@
#include "taler_error_codes.h" #include "taler_error_codes.h"
#include "taler_dbevents.h" #include "taler_dbevents.h"
#include "taler_pq_lib.h" #include "taler_pq_lib.h"
#include "pg_event_notify.h"
#include "pg_aggregate.h" #include "pg_aggregate.h"
#include "pg_helper.h" #include "pg_helper.h"
@ -35,34 +36,12 @@ TEH_PG_aggregate (
{ {
struct PostgresClosure *pg = cls; struct PostgresClosure *pg = cls;
struct GNUNET_TIME_Absolute now = {0}; struct GNUNET_TIME_Absolute now = {0};
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_absolute_time (&now),
GNUNET_PQ_query_param_auto_from_type (merchant_pub),
GNUNET_PQ_query_param_auto_from_type (h_payto),
GNUNET_PQ_query_param_auto_from_type (wtid),
GNUNET_PQ_query_param_end
};
uint64_t sum_deposit_value; uint64_t sum_deposit_value;
uint64_t sum_deposit_frac; uint64_t sum_deposit_frac;
uint64_t sum_refund_value; uint64_t sum_refund_value;
uint64_t sum_refund_frac; uint64_t sum_refund_frac;
uint64_t sum_fee_value; uint64_t sum_fee_value;
uint64_t sum_fee_frac; uint64_t sum_fee_frac;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_uint64 ("sum_deposit_value",
&sum_deposit_value),
GNUNET_PQ_result_spec_uint64 ("sum_deposit_fraction",
&sum_deposit_frac),
GNUNET_PQ_result_spec_uint64 ("sum_refund_value",
&sum_refund_value),
GNUNET_PQ_result_spec_uint64 ("sum_refund_fraction",
&sum_refund_frac),
GNUNET_PQ_result_spec_uint64 ("sum_fee_value",
&sum_fee_value),
GNUNET_PQ_result_spec_uint64 ("sum_fee_fraction",
&sum_fee_frac),
GNUNET_PQ_result_spec_end
};
enum GNUNET_DB_QueryStatus qs; enum GNUNET_DB_QueryStatus qs;
struct TALER_Amount sum_deposit; struct TALER_Amount sum_deposit;
struct TALER_Amount sum_refund; struct TALER_Amount sum_refund;
@ -71,8 +50,6 @@ TEH_PG_aggregate (
now = GNUNET_TIME_absolute_round_down (GNUNET_TIME_absolute_get (), now = GNUNET_TIME_absolute_round_down (GNUNET_TIME_absolute_get (),
pg->aggregator_shift); pg->aggregator_shift);
/* Used in #postgres_aggregate() */
PREPARE (pg, PREPARE (pg,
"aggregate", "aggregate",
"WITH dep AS (" /* restrict to our merchant and account and mark as done */ "WITH dep AS (" /* restrict to our merchant and account and mark as done */
@ -148,11 +125,35 @@ TEH_PG_aggregate (
" FULL OUTER JOIN ref ON (FALSE)" /* We just want all sums */ " FULL OUTER JOIN ref ON (FALSE)" /* We just want all sums */
" FULL OUTER JOIN fees ON (FALSE);"); " FULL OUTER JOIN fees ON (FALSE);");
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_absolute_time (&now),
GNUNET_PQ_query_param_auto_from_type (merchant_pub),
GNUNET_PQ_query_param_auto_from_type (h_payto),
GNUNET_PQ_query_param_auto_from_type (wtid),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_uint64 ("sum_deposit_value",
&sum_deposit_value),
GNUNET_PQ_result_spec_uint64 ("sum_deposit_fraction",
&sum_deposit_frac),
GNUNET_PQ_result_spec_uint64 ("sum_refund_value",
&sum_refund_value),
GNUNET_PQ_result_spec_uint64 ("sum_refund_fraction",
&sum_refund_frac),
GNUNET_PQ_result_spec_uint64 ("sum_fee_value",
&sum_fee_value),
GNUNET_PQ_result_spec_uint64 ("sum_fee_fraction",
&sum_fee_frac),
GNUNET_PQ_result_spec_end
};
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"aggregate", "aggregate",
params, params,
rs); rs);
}
if (qs < 0) if (qs < 0)
{ {
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
@ -165,6 +166,18 @@ TEH_PG_aggregate (
total)); total));
return qs; return qs;
} }
{
struct TALER_CoinDepositEventP rep = {
.header.size = htons (sizeof (rep)),
.header.type = htons (TALER_DBEVENT_EXCHANGE_DEPOSIT_STATUS_CHANGED),
.merchant_pub = *merchant_pub
};
TEH_PG_event_notify (pg,
&rep.header,
NULL,
0);
}
GNUNET_assert (GNUNET_OK == GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (pg->currency, TALER_amount_set_zero (pg->currency,
&sum_deposit)); &sum_deposit));

View File

@ -69,7 +69,7 @@ TEH_PG_get_age_withdraw_info (
",amount_with_fee_val" ",amount_with_fee_val"
",amount_with_fee_frac" ",amount_with_fee_frac"
",noreveal_index" ",noreveal_index"
" FROM withdraw_age_commitments" " FROM age_withdraw_commitments"
" WHERE reserve_pub=$1 and h_commitment=$2;"); " WHERE reserve_pub=$1 and h_commitment=$2;");
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"get_age_withdraw_info", "get_age_withdraw_info",

View File

@ -66,10 +66,25 @@ get_wire_accounts_cb (void *cls,
for (unsigned int i = 0; i < num_results; i++) for (unsigned int i = 0; i < num_results; i++)
{ {
char *payto_uri; char *payto_uri;
char *conversion_url = NULL;
json_t *debit_restrictions = NULL;
json_t *credit_restrictions = NULL;
struct TALER_MasterSignatureP master_sig; struct TALER_MasterSignatureP master_sig;
struct GNUNET_PQ_ResultSpec rs[] = { struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_string ("payto_uri", GNUNET_PQ_result_spec_string ("payto_uri",
&payto_uri), &payto_uri),
GNUNET_PQ_result_spec_allow_null (
GNUNET_PQ_result_spec_string ("conversion_url",
&conversion_url),
NULL),
GNUNET_PQ_result_spec_allow_null (
TALER_PQ_result_spec_json ("debit_restrictions",
&debit_restrictions),
NULL),
GNUNET_PQ_result_spec_allow_null (
TALER_PQ_result_spec_json ("credit_restrictions",
&credit_restrictions),
NULL),
GNUNET_PQ_result_spec_auto_from_type ("master_sig", GNUNET_PQ_result_spec_auto_from_type ("master_sig",
&master_sig), &master_sig),
GNUNET_PQ_result_spec_end GNUNET_PQ_result_spec_end
@ -84,8 +99,21 @@ get_wire_accounts_cb (void *cls,
ctx->status = GNUNET_SYSERR; ctx->status = GNUNET_SYSERR;
return; return;
} }
if (NULL == debit_restrictions)
{
debit_restrictions = json_array ();
GNUNET_assert (NULL != debit_restrictions);
}
if (NULL == credit_restrictions)
{
credit_restrictions = json_array ();
GNUNET_assert (NULL != credit_restrictions);
}
ctx->cb (ctx->cb_cls, ctx->cb (ctx->cb_cls,
payto_uri, payto_uri,
conversion_url,
debit_restrictions,
credit_restrictions,
&master_sig); &master_sig);
GNUNET_PQ_cleanup_result (rs); GNUNET_PQ_cleanup_result (rs);
} }
@ -112,6 +140,9 @@ TEH_PG_get_wire_accounts (void *cls,
"get_wire_accounts", "get_wire_accounts",
"SELECT" "SELECT"
" payto_uri" " payto_uri"
",conversion_url"
",debit_restrictions"
",credit_restrictions"
",master_sig" ",master_sig"
" FROM wire_accounts" " FROM wire_accounts"
" WHERE is_active"); " WHERE is_active");
@ -123,5 +154,4 @@ TEH_PG_get_wire_accounts (void *cls,
if (GNUNET_OK != ctx.status) if (GNUNET_OK != ctx.status)
return GNUNET_DB_STATUS_HARD_ERROR; return GNUNET_DB_STATUS_HARD_ERROR;
return qs; return qs;
} }

View File

@ -42,8 +42,8 @@ TEH_PG_insert_age_withdraw_reveal (
/* TODO */ /* TODO */
#if 0 #if 0
PREPARE (pg, PREPARE (pg,
"insert_withdraw_age_revealed_coin", "insert_age_withdraw_revealed_coin",
"INSERT INTO withdraw_age_reveals " "INSERT INTO age_withdraw_reveals "
"(h_commitment " "(h_commitment "
",freshcoin_index " ",freshcoin_index "
",denominations_serial " ",denominations_serial "

View File

@ -2062,39 +2062,39 @@ irbt_cb_table_purse_deletion (struct PostgresClosure *pg,
/** /**
* Function called with withdraw_age_commitments records to insert into table. * Function called with age_withdraw_commitments records to insert into table.
* *
* @param pg plugin context * @param pg plugin context
* @param td record to insert * @param td record to insert
*/ */
static enum GNUNET_DB_QueryStatus static enum GNUNET_DB_QueryStatus
irbt_cb_table_withdraw_age_commitments (struct PostgresClosure *pg, irbt_cb_table_age_withdraw_commitments (struct PostgresClosure *pg,
const struct const struct
TALER_EXCHANGEDB_TableData *td) TALER_EXCHANGEDB_TableData *td)
{ {
struct GNUNET_PQ_QueryParam params[] = { struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&td->serial), GNUNET_PQ_query_param_uint64 (&td->serial),
GNUNET_PQ_query_param_auto_from_type ( GNUNET_PQ_query_param_auto_from_type (
&td->details.withdraw_age_commitments.h_commitment), &td->details.age_withdraw_commitments.h_commitment),
TALER_PQ_query_param_amount ( TALER_PQ_query_param_amount (
&td->details.withdraw_age_commitments.amount_with_fee), &td->details.age_withdraw_commitments.amount_with_fee),
GNUNET_PQ_query_param_uint16 ( GNUNET_PQ_query_param_uint16 (
&td->details.withdraw_age_commitments.max_age), &td->details.age_withdraw_commitments.max_age),
GNUNET_PQ_query_param_auto_from_type ( GNUNET_PQ_query_param_auto_from_type (
&td->details.withdraw_age_commitments.reserve_pub), &td->details.age_withdraw_commitments.reserve_pub),
GNUNET_PQ_query_param_auto_from_type ( GNUNET_PQ_query_param_auto_from_type (
&td->details.withdraw_age_commitments.reserve_sig), &td->details.age_withdraw_commitments.reserve_sig),
GNUNET_PQ_query_param_uint32 ( GNUNET_PQ_query_param_uint32 (
&td->details.withdraw_age_commitments.noreveal_index), &td->details.age_withdraw_commitments.noreveal_index),
GNUNET_PQ_query_param_absolute_time ( GNUNET_PQ_query_param_absolute_time (
&td->details.withdraw_age_commitments.timestamp), &td->details.age_withdraw_commitments.timestamp),
GNUNET_PQ_query_param_end GNUNET_PQ_query_param_end
}; };
PREPARE (pg, PREPARE (pg,
"insert_into_table_withdraw_age_commitments", "insert_into_table_age_withdraw_commitments",
"INSERT INTO withdraw_age_commitments" "INSERT INTO age_withdraw_commitments"
"(withdraw_age_commitment_id" "(age_withdraw_commitment_id"
",h_commitment" ",h_commitment"
",amount_with_fee_val" ",amount_with_fee_val"
",amount_with_fee_frac" ",amount_with_fee_frac"
@ -2106,19 +2106,19 @@ irbt_cb_table_withdraw_age_commitments (struct PostgresClosure *pg,
") VALUES " ") VALUES "
"($1, $2, $3, $4, $5, $6, $7, $8, $9);"); "($1, $2, $3, $4, $5, $6, $7, $8, $9);");
return GNUNET_PQ_eval_prepared_non_select (pg->conn, return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_into_table_withdraw_age_commitments", "insert_into_table_age_withdraw_commitments",
params); params);
} }
/** /**
* Function called with withdraw_age_revealed_coins records to insert into table. * Function called with age_withdraw_revealed_coins records to insert into table.
* *
* @param pg plugin context * @param pg plugin context
* @param td record to insert * @param td record to insert
*/ */
static enum GNUNET_DB_QueryStatus static enum GNUNET_DB_QueryStatus
irbt_cb_table_withdraw_age_revealed_coins (struct PostgresClosure *pg, irbt_cb_table_age_withdraw_revealed_coins (struct PostgresClosure *pg,
const struct const struct
TALER_EXCHANGEDB_TableData *td) TALER_EXCHANGEDB_TableData *td)
{ {
@ -2126,26 +2126,24 @@ irbt_cb_table_withdraw_age_revealed_coins (struct PostgresClosure *pg,
struct GNUNET_PQ_QueryParam params[] = { struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&td->serial), GNUNET_PQ_query_param_uint64 (&td->serial),
GNUNET_PQ_query_param_auto_from_type ( GNUNET_PQ_query_param_auto_from_type (
&td->details.withdraw_age_revealed_coins.h_commitment), &td->details.age_withdraw_revealed_coins.h_commitment),
GNUNET_PQ_query_param_uint32 ( GNUNET_PQ_query_param_uint32 (
&td->details.withdraw_age_revealed_coins.freshcoin_index), &td->details.age_withdraw_revealed_coins.freshcoin_index),
GNUNET_PQ_query_param_uint64 ( GNUNET_PQ_query_param_uint64 (
&td->details.withdraw_age_revealed_coins.denominations_serial), &td->details.age_withdraw_revealed_coins.denominations_serial),
GNUNET_PQ_query_param_fixed_size ( GNUNET_PQ_query_param_fixed_size (
td->details.withdraw_age_revealed_coins.coin_ev, td->details.age_withdraw_revealed_coins.coin_ev,
td->details.withdraw_age_revealed_coins.coin_ev_size), td->details.age_withdraw_revealed_coins.coin_ev_size),
GNUNET_PQ_query_param_auto_from_type (&h_coin_ev), GNUNET_PQ_query_param_auto_from_type (&h_coin_ev),
TALER_PQ_query_param_blinded_denom_sig ( TALER_PQ_query_param_blinded_denom_sig (
&td->details.withdraw_age_revealed_coins.ev_sig), &td->details.age_withdraw_revealed_coins.ev_sig),
TALER_PQ_query_param_exchange_withdraw_values (
&td->details.withdraw_age_revealed_coins.ewv),
GNUNET_PQ_query_param_end GNUNET_PQ_query_param_end
}; };
PREPARE (pg, PREPARE (pg,
"insert_into_table_withdraw_age_revealed_coins", "insert_into_table_age_withdraw_revealed_coins",
"INSERT INTO withdraw_age_revealed_coins" "INSERT INTO age_withdraw_revealed_coins"
"(withdraw_age_revealed_coins_id" "(age_withdraw_revealed_coins_id"
",h_commitment" ",h_commitment"
",freshcoin_index" ",freshcoin_index"
",denominations_serial" ",denominations_serial"
@ -2156,12 +2154,12 @@ irbt_cb_table_withdraw_age_revealed_coins (struct PostgresClosure *pg,
") VALUES " ") VALUES "
"($1, $2, $3, $4, $5, $6, $7, $8);"); "($1, $2, $3, $4, $5, $6, $7, $8);");
GNUNET_CRYPTO_hash (td->details.withdraw_age_revealed_coins.coin_ev, GNUNET_CRYPTO_hash (td->details.age_withdraw_revealed_coins.coin_ev,
td->details.withdraw_age_revealed_coins.coin_ev_size, td->details.age_withdraw_revealed_coins.coin_ev_size,
&h_coin_ev); &h_coin_ev);
return GNUNET_PQ_eval_prepared_non_select (pg->conn, return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_into_table_withdraw_age_revealed_coins", "insert_into_table_age_withdraw_revealed_coins",
params); params);
} }
@ -2314,10 +2312,10 @@ TEH_PG_insert_records_by_table (void *cls,
rh = &irbt_cb_table_purse_deletion; rh = &irbt_cb_table_purse_deletion;
break; break;
case TALER_EXCHANGEDB_RT_WITHDRAW_AGE_COMMITMENTS: case TALER_EXCHANGEDB_RT_WITHDRAW_AGE_COMMITMENTS:
rh = &irbt_cb_table_withdraw_age_commitments; rh = &irbt_cb_table_age_withdraw_commitments;
break; break;
case TALER_EXCHANGEDB_RT_WITHDRAW_AGE_REVEALED_COINS: case TALER_EXCHANGEDB_RT_WITHDRAW_AGE_REVEALED_COINS:
rh = &irbt_cb_table_withdraw_age_revealed_coins; rh = &irbt_cb_table_age_withdraw_revealed_coins;
break; break;
} }
if (NULL == rh) if (NULL == rh)

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER This file is part of TALER
Copyright (C) 2022 Taler Systems SA Copyright (C) 2022, 2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software terms of the GNU General Public License as published by the Free Software
@ -29,12 +29,20 @@
enum GNUNET_DB_QueryStatus enum GNUNET_DB_QueryStatus
TEH_PG_insert_wire (void *cls, TEH_PG_insert_wire (void *cls,
const char *payto_uri, const char *payto_uri,
const char *conversion_url,
json_t *debit_restrictions,
json_t *credit_restrictions,
struct GNUNET_TIME_Timestamp start_date, struct GNUNET_TIME_Timestamp start_date,
const struct TALER_MasterSignatureP *master_sig) const struct TALER_MasterSignatureP *master_sig)
{ {
struct PostgresClosure *pg = cls; struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = { struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (payto_uri), GNUNET_PQ_query_param_string (payto_uri),
NULL == conversion_url
? GNUNET_PQ_query_param_null ()
: GNUNET_PQ_query_param_string (conversion_url),
TALER_PQ_query_param_json (debit_restrictions),
TALER_PQ_query_param_json (credit_restrictions),
GNUNET_PQ_query_param_auto_from_type (master_sig), GNUNET_PQ_query_param_auto_from_type (master_sig),
GNUNET_PQ_query_param_timestamp (&start_date), GNUNET_PQ_query_param_timestamp (&start_date),
GNUNET_PQ_query_param_end GNUNET_PQ_query_param_end
@ -44,11 +52,14 @@ TEH_PG_insert_wire (void *cls,
"insert_wire", "insert_wire",
"INSERT INTO wire_accounts " "INSERT INTO wire_accounts "
"(payto_uri" "(payto_uri"
",conversion_url"
",debit_restrictions"
",credit_restrictions"
",master_sig" ",master_sig"
",is_active" ",is_active"
",last_change" ",last_change"
") VALUES " ") VALUES "
"($1, $2, true, $3);"); "($1, $2, $3, $4, $5, true, $6);");
return GNUNET_PQ_eval_prepared_non_select (pg->conn, return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_wire", "insert_wire",
params); params);

View File

@ -29,6 +29,9 @@
* *
* @param cls closure * @param cls closure
* @param payto_uri wire account of the exchange * @param payto_uri wire account of the exchange
* @param conversion_url URL of a conversion service, NULL if there is no conversion
* @param debit_restrictions JSON array with debit restrictions on the account
* @param credit_restrictions JSON array with credit restrictions on the account
* @param start_date date when the account was added by the offline system * @param start_date date when the account was added by the offline system
* (only to be used for replay detection) * (only to be used for replay detection)
* @param master_sig public signature affirming the existence of the account, * @param master_sig public signature affirming the existence of the account,
@ -38,6 +41,9 @@
enum GNUNET_DB_QueryStatus enum GNUNET_DB_QueryStatus
TEH_PG_insert_wire (void *cls, TEH_PG_insert_wire (void *cls,
const char *payto_uri, const char *payto_uri,
const char *conversion_url,
json_t *debit_restrictions,
json_t *credit_restrictions,
struct GNUNET_TIME_Timestamp start_date, struct GNUNET_TIME_Timestamp start_date,
const struct TALER_MasterSignatureP *master_sig); const struct TALER_MasterSignatureP *master_sig);

View File

@ -1118,9 +1118,9 @@ lrbt_cb_table_refresh_transfer_keys (void *cls,
ctx->error = true; ctx->error = true;
return; return;
} }
memcpy (&td.details.refresh_transfer_keys.tprivs[0], GNUNET_memcpy (&td.details.refresh_transfer_keys.tprivs[0],
tpriv, tpriv,
tpriv_size); tpriv_size);
ctx->cb (ctx->cb_cls, ctx->cb (ctx->cb_cls,
&td); &td);
GNUNET_PQ_cleanup_result (rs); GNUNET_PQ_cleanup_result (rs);
@ -2767,14 +2767,14 @@ lrbt_cb_table_purse_deletion (void *cls,
/** /**
* Function called with withdraw_age_commitments table entries. * Function called with age_withdraw_commitments table entries.
* *
* @param cls closure * @param cls closure
* @param result the postgres result * @param result the postgres result
* @param num_results the number of results in @a result * @param num_results the number of results in @a result
*/ */
static void static void
lrbt_cb_table_withdraw_age_commitments (void *cls, lrbt_cb_table_age_withdraw_commitments (void *cls,
PGresult *result, PGresult *result,
unsigned int num_results) unsigned int num_results)
{ {
@ -2788,26 +2788,26 @@ lrbt_cb_table_withdraw_age_commitments (void *cls,
{ {
struct GNUNET_PQ_ResultSpec rs[] = { struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_uint64 ( GNUNET_PQ_result_spec_uint64 (
"withdraw_age_commitment_id", "age_withdraw_commitment_id",
&td.serial), &td.serial),
GNUNET_PQ_result_spec_auto_from_type ( GNUNET_PQ_result_spec_auto_from_type (
"h_commitment", "h_commitment",
&td.details.withdraw_age_commitments.h_commitment), &td.details.age_withdraw_commitments.h_commitment),
GNUNET_PQ_result_spec_uint16 ( GNUNET_PQ_result_spec_uint16 (
"max_age", "max_age",
&td.details.withdraw_age_commitments.max_age), &td.details.age_withdraw_commitments.max_age),
TALER_PQ_RESULT_SPEC_AMOUNT ( TALER_PQ_RESULT_SPEC_AMOUNT (
"amount_with_fee", "amount_with_fee",
&td.details.withdraw_age_commitments.amount_with_fee), &td.details.age_withdraw_commitments.amount_with_fee),
GNUNET_PQ_result_spec_auto_from_type ( GNUNET_PQ_result_spec_auto_from_type (
"reserve_pub", "reserve_pub",
&td.details.withdraw_age_commitments.reserve_pub), &td.details.age_withdraw_commitments.reserve_pub),
GNUNET_PQ_result_spec_auto_from_type ( GNUNET_PQ_result_spec_auto_from_type (
"reserve_sig", "reserve_sig",
&td.details.withdraw_age_commitments.reserve_sig), &td.details.age_withdraw_commitments.reserve_sig),
GNUNET_PQ_result_spec_uint32 ( GNUNET_PQ_result_spec_uint32 (
"noreveal_index", "noreveal_index",
&td.details.withdraw_age_commitments.noreveal_index), &td.details.age_withdraw_commitments.noreveal_index),
GNUNET_PQ_result_spec_end GNUNET_PQ_result_spec_end
}; };
@ -2828,14 +2828,14 @@ lrbt_cb_table_withdraw_age_commitments (void *cls,
/** /**
* Function called with withdraw_age_revealed_coins table entries. * Function called with age_withdraw_revealed_coins table entries.
* *
* @param cls closure * @param cls closure
* @param result the postgres result * @param result the postgres result
* @param num_results the number of results in @a result * @param num_results the number of results in @a result
*/ */
static void static void
lrbt_cb_table_withdraw_age_revealed_coins (void *cls, lrbt_cb_table_age_withdraw_revealed_coins (void *cls,
PGresult *result, PGresult *result,
unsigned int num_results) unsigned int num_results)
{ {
@ -2848,22 +2848,22 @@ lrbt_cb_table_withdraw_age_revealed_coins (void *cls,
{ {
struct GNUNET_PQ_ResultSpec rs[] = { struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_uint64 ( GNUNET_PQ_result_spec_uint64 (
"withdraw_age_revealed_coins_id", "age_withdraw_revealed_coins_id",
&td.serial), &td.serial),
GNUNET_PQ_result_spec_auto_from_type ( GNUNET_PQ_result_spec_auto_from_type (
"h_commitment", "h_commitment",
&td.details.withdraw_age_revealed_coins.h_commitment), &td.details.age_withdraw_revealed_coins.h_commitment),
GNUNET_PQ_result_spec_uint32 ( GNUNET_PQ_result_spec_uint32 (
"freshcoin_index", "freshcoin_index",
&td.details.withdraw_age_revealed_coins.freshcoin_index), &td.details.age_withdraw_revealed_coins.freshcoin_index),
GNUNET_PQ_result_spec_uint64 ( GNUNET_PQ_result_spec_uint64 (
"denominations_serial", "denominations_serial",
&td.details.withdraw_age_revealed_coins.denominations_serial), &td.details.age_withdraw_revealed_coins.denominations_serial),
/* Note: h_coin_ev is recalculated */ /* Note: h_coin_ev is recalculated */
GNUNET_PQ_result_spec_variable_size ( GNUNET_PQ_result_spec_variable_size (
"coin_ev", "coin_ev",
(void **) &td.details.withdraw_age_revealed_coins.coin_ev, (void **) &td.details.age_withdraw_revealed_coins.coin_ev,
&td.details.withdraw_age_revealed_coins.coin_ev_size), &td.details.age_withdraw_revealed_coins.coin_ev_size),
TALER_PQ_result_spec_blinded_denom_sig ( TALER_PQ_result_spec_blinded_denom_sig (
"ev_sig", "ev_sig",
&td.details.refresh_revealed_coins.ev_sig), &td.details.refresh_revealed_coins.ev_sig),
@ -3598,9 +3598,9 @@ TEH_PG_lookup_records_by_table (void *cls,
rh = &lrbt_cb_table_purse_deletion; rh = &lrbt_cb_table_purse_deletion;
break; break;
case TALER_EXCHANGEDB_RT_WITHDRAW_AGE_COMMITMENTS: case TALER_EXCHANGEDB_RT_WITHDRAW_AGE_COMMITMENTS:
XPREPARE ("select_above_serial_by_table_withdraw_age_commitments", XPREPARE ("select_above_serial_by_table_age_withdraw_commitments",
"SELECT" "SELECT"
" withdraw_age_commitment_id" " age_withdraw_commitment_id"
",h_commitment" ",h_commitment"
",amount_with_fee_val" ",amount_with_fee_val"
",amount_with_fee_frac" ",amount_with_fee_frac"
@ -3608,15 +3608,15 @@ TEH_PG_lookup_records_by_table (void *cls,
",reserve_pub" ",reserve_pub"
",reserve_sig" ",reserve_sig"
",noreveal_index" ",noreveal_index"
" FROM withdraw_age_commitments" " FROM age_withdraw_commitments"
" WHERE withdraw_age_commitment_id > $1" " WHERE age_withdraw_commitment_id > $1"
" ORDER BY withdraw_age_commitment_id ASC;"); " ORDER BY age_withdraw_commitment_id ASC;");
rh = &lrbt_cb_table_withdraw_age_commitments; rh = &lrbt_cb_table_age_withdraw_commitments;
break; break;
case TALER_EXCHANGEDB_RT_WITHDRAW_AGE_REVEALED_COINS: case TALER_EXCHANGEDB_RT_WITHDRAW_AGE_REVEALED_COINS:
XPREPARE ("select_above_serial_by_table_withdraw_age_revealed_coins", XPREPARE ("select_above_serial_by_table_age_withdraw_revealed_coins",
"SELECT" "SELECT"
" withdraw_age_revealed_coins_serial_id" " age_withdraw_revealed_coins_serial_id"
",h_commitment" ",h_commitment"
",freshcoin_index" ",freshcoin_index"
",denominations_serial" ",denominations_serial"
@ -3624,10 +3624,10 @@ TEH_PG_lookup_records_by_table (void *cls,
",h_coin_ev" ",h_coin_ev"
",ev_sig" ",ev_sig"
",ewv" ",ewv"
" FROM withdraw_age_revealed_coins" " FROM age_withdraw_revealed_coins"
" WHERE withdraw_age_revealed_coins_serial_id > $1" " WHERE age_withdraw_revealed_coins_serial_id > $1"
" ORDER BY withdraw_age_revealed_coins_serial_id ASC;"); " ORDER BY age_withdraw_revealed_coins_serial_id ASC;");
rh = &lrbt_cb_table_withdraw_age_revealed_coins; rh = &lrbt_cb_table_age_withdraw_revealed_coins;
break; break;
} }
if (NULL == rh) if (NULL == rh)

View File

@ -427,22 +427,22 @@ TEH_PG_lookup_serial_by_table (void *cls,
statement = "select_serial_by_table_purse_deletion"; statement = "select_serial_by_table_purse_deletion";
break; break;
case TALER_EXCHANGEDB_RT_WITHDRAW_AGE_COMMITMENTS: case TALER_EXCHANGEDB_RT_WITHDRAW_AGE_COMMITMENTS:
XPREPARE ("select_serial_by_table_withdraw_age_commitments", XPREPARE ("select_serial_by_table_age_withdraw_commitments",
"SELECT" "SELECT"
" withdraw_age_commitment_id AS serial" " age_withdraw_commitment_id AS serial"
" FROM withdraw_age_commitments" " FROM age_withdraw_commitments"
" ORDER BY withdraw_age_commitment_id DESC" " ORDER BY age_withdraw_commitment_id DESC"
" LIMIT 1;"); " LIMIT 1;");
statement = "select_serial_by_table_withdraw_age_commitments"; statement = "select_serial_by_table_age_withdraw_commitments";
break; break;
case TALER_EXCHANGEDB_RT_WITHDRAW_AGE_REVEALED_COINS: case TALER_EXCHANGEDB_RT_WITHDRAW_AGE_REVEALED_COINS:
XPREPARE ("select_serial_by_table_withdraw_age_revealed_coins", XPREPARE ("select_serial_by_table_age_withdraw_revealed_coins",
"SELECT" "SELECT"
" withdraw_age_revealed_coins_id AS serial" " age_withdraw_revealed_coins_id AS serial"
" FROM withdraw_age_revealed_coins" " FROM age_withdraw_revealed_coins"
" ORDER BY withdraw_age_revealed_coins_id DESC" " ORDER BY age_withdraw_revealed_coins_id DESC"
" LIMIT 1;"); " LIMIT 1;");
statement = "select_serial_by_table_withdraw_age_revealed_coins"; statement = "select_serial_by_table_age_withdraw_revealed_coins";
break; break;
} }
if (NULL == statement) if (NULL == statement)

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER This file is part of TALER
Copyright (C) 2022 Taler Systems SA Copyright (C) 2022, 2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software terms of the GNU General Public License as published by the Free Software
@ -29,6 +29,9 @@
enum GNUNET_DB_QueryStatus enum GNUNET_DB_QueryStatus
TEH_PG_update_wire (void *cls, TEH_PG_update_wire (void *cls,
const char *payto_uri, const char *payto_uri,
const char *conversion_url,
json_t *debit_restrictions,
json_t *credit_restrictions,
struct GNUNET_TIME_Timestamp change_date, struct GNUNET_TIME_Timestamp change_date,
bool enabled) bool enabled)
{ {
@ -36,17 +39,28 @@ TEH_PG_update_wire (void *cls,
struct GNUNET_PQ_QueryParam params[] = { struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (payto_uri), GNUNET_PQ_query_param_string (payto_uri),
GNUNET_PQ_query_param_bool (enabled), GNUNET_PQ_query_param_bool (enabled),
NULL == conversion_url
? GNUNET_PQ_query_param_null ()
: GNUNET_PQ_query_param_string (conversion_url),
enabled
? TALER_PQ_query_param_json (debit_restrictions)
: GNUNET_PQ_query_param_null (),
enabled
? TALER_PQ_query_param_json (credit_restrictions)
: GNUNET_PQ_query_param_null (),
GNUNET_PQ_query_param_timestamp (&change_date), GNUNET_PQ_query_param_timestamp (&change_date),
GNUNET_PQ_query_param_end GNUNET_PQ_query_param_end
}; };
/* used in #postgres_update_wire() */
PREPARE (pg, PREPARE (pg,
"update_wire", "update_wire",
"UPDATE wire_accounts" "UPDATE wire_accounts"
" SET" " SET"
" is_active=$2" " is_active=$2"
" ,last_change=$3" " ,conversion_url=$3"
" ,debit_restrictions=$4"
" ,credit_restrictions=$5"
" ,last_change=$6"
" WHERE payto_uri=$1"); " WHERE payto_uri=$1");
return GNUNET_PQ_eval_prepared_non_select (pg->conn, return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"update_wire", "update_wire",

View File

@ -24,11 +24,16 @@
#include "taler_util.h" #include "taler_util.h"
#include "taler_json_lib.h" #include "taler_json_lib.h"
#include "taler_exchangedb_plugin.h" #include "taler_exchangedb_plugin.h"
/** /**
* Update information about a wire account of the exchange. * Update information about a wire account of the exchange.
* *
* @param cls closure * @param cls closure
* @param payto_uri account the update is about * @param payto_uri account the update is about
* @param conversion_url URL of a conversion service, NULL if there is no conversion
* @param debit_restrictions JSON array with debit restrictions on the account; NULL allowed if not @a enabled
* @param credit_restrictions JSON array with credit restrictions on the account; NULL allowed if not @a enabled
* @param change_date date when the account status was last changed * @param change_date date when the account status was last changed
* (only to be used for replay detection) * (only to be used for replay detection)
* @param enabled true to enable, false to disable (the actual change) * @param enabled true to enable, false to disable (the actual change)
@ -37,6 +42,9 @@
enum GNUNET_DB_QueryStatus enum GNUNET_DB_QueryStatus
TEH_PG_update_wire (void *cls, TEH_PG_update_wire (void *cls,
const char *payto_uri, const char *payto_uri,
const char *conversion_url,
json_t *debit_restrictions,
json_t *credit_restrictions,
struct GNUNET_TIME_Timestamp change_date, struct GNUNET_TIME_Timestamp change_date,
bool enabled); bool enabled);

View File

@ -207,10 +207,10 @@ libtaler_extension_age_restriction_init (void *arg)
return NULL; return NULL;
} }
mask.bits = TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_MASK; if (NULL == groups)
groups = GNUNET_strdup (TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_GROUPS);
if ((groups != NULL) && if (GNUNET_OK != TALER_parse_age_group_string (groups, &mask))
(GNUNET_OK != TALER_parse_age_group_string (groups, &mask)))
{ {
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"[age restriction] couldn't parse age groups: '%s'\n", "[age restriction] couldn't parse age groups: '%s'\n",

View File

@ -111,6 +111,7 @@ struct TALER_BANK_AdminAddIncomingHandle;
* @param timestamp time when the transaction was made. * @param timestamp time when the transaction was made.
* @param json detailed response from the HTTPD, or NULL if reply was not in JSON * @param json detailed response from the HTTPD, or NULL if reply was not in JSON
*/ */
// FIXME: bad API
typedef void typedef void
(*TALER_BANK_AdminAddIncomingCallback) ( (*TALER_BANK_AdminAddIncomingCallback) (
void *cls, void *cls,
@ -199,6 +200,7 @@ struct TALER_BANK_TransferHandle;
* @param row_id unique ID of the wire transfer in the bank's records * @param row_id unique ID of the wire transfer in the bank's records
* @param timestamp when did the transaction go into effect * @param timestamp when did the transaction go into effect
*/ */
// FIXME: bad API
typedef void typedef void
(*TALER_BANK_TransferCallback)( (*TALER_BANK_TransferCallback)(
void *cls, void *cls,
@ -337,7 +339,7 @@ struct TALER_BANK_CreditHistoryResponse
*/ */
unsigned int details_length; unsigned int details_length;
} success; } ok;
} details; } details;
@ -493,7 +495,7 @@ struct TALER_BANK_DebitHistoryResponse
*/ */
unsigned int details_length; unsigned int details_length;
} success; } ok;
} details; } details;

View File

@ -5512,6 +5512,9 @@ TALER_exchange_offline_global_fee_verify (
* Create wire account addition signature. * Create wire account addition signature.
* *
* @param payto_uri bank account * @param payto_uri bank account
* @param conversion_url URL of the conversion service, or NULL if none
* @param debit_restrictions JSON encoding of debit restrictions on the account; see AccountRestriction in the spec
* @param credit_restrictions JSON encoding of credit restrictions on the account; see AccountRestriction in the spec
* @param now timestamp to use for the signature (rounded) * @param now timestamp to use for the signature (rounded)
* @param master_priv private key to sign with * @param master_priv private key to sign with
* @param[out] master_sig where to write the signature * @param[out] master_sig where to write the signature
@ -5519,6 +5522,9 @@ TALER_exchange_offline_global_fee_verify (
void void
TALER_exchange_offline_wire_add_sign ( TALER_exchange_offline_wire_add_sign (
const char *payto_uri, const char *payto_uri,
const char *conversion_url,
const json_t *debit_restrictions,
const json_t *credit_restrictions,
struct GNUNET_TIME_Timestamp now, struct GNUNET_TIME_Timestamp now,
const struct TALER_MasterPrivateKeyP *master_priv, const struct TALER_MasterPrivateKeyP *master_priv,
struct TALER_MasterSignatureP *master_sig); struct TALER_MasterSignatureP *master_sig);
@ -5528,6 +5534,9 @@ TALER_exchange_offline_wire_add_sign (
* Verify wire account addition signature. * Verify wire account addition signature.
* *
* @param payto_uri bank account * @param payto_uri bank account
* @param conversion_url URL of the conversion service, or NULL if none
* @param debit_restrictions JSON encoding of debit restrictions on the account; see AccountRestriction in the spec
* @param credit_restrictions JSON encoding of credit restrictions on the account; see AccountRestriction in the spec
* @param sign_time timestamp when signature was created * @param sign_time timestamp when signature was created
* @param master_pub public key to verify against * @param master_pub public key to verify against
* @param master_sig the signature the signature * @param master_sig the signature the signature
@ -5536,6 +5545,9 @@ TALER_exchange_offline_wire_add_sign (
enum GNUNET_GenericReturnValue enum GNUNET_GenericReturnValue
TALER_exchange_offline_wire_add_verify ( TALER_exchange_offline_wire_add_verify (
const char *payto_uri, const char *payto_uri,
const char *conversion_url,
const json_t *debit_restrictions,
const json_t *credit_restrictions,
struct GNUNET_TIME_Timestamp sign_time, struct GNUNET_TIME_Timestamp sign_time,
const struct TALER_MasterPublicKeyP *master_pub, const struct TALER_MasterPublicKeyP *master_pub,
const struct TALER_MasterSignatureP *master_sig); const struct TALER_MasterSignatureP *master_sig);
@ -5578,6 +5590,9 @@ TALER_exchange_offline_wire_del_verify (
* Check the signature in @a master_sig. * Check the signature in @a master_sig.
* *
* @param payto_uri URI that is signed * @param payto_uri URI that is signed
* @param conversion_url URL of the conversion service, or NULL if none
* @param debit_restrictions JSON encoding of debit restrictions on the account; see AccountRestriction in the spec
* @param credit_restrictions JSON encoding of credit restrictions on the account; see AccountRestriction in the spec
* @param master_pub master public key of the exchange * @param master_pub master public key of the exchange
* @param master_sig signature of the exchange * @param master_sig signature of the exchange
* @return #GNUNET_OK if signature is valid * @return #GNUNET_OK if signature is valid
@ -5585,6 +5600,9 @@ TALER_exchange_offline_wire_del_verify (
enum GNUNET_GenericReturnValue enum GNUNET_GenericReturnValue
TALER_exchange_wire_signature_check ( TALER_exchange_wire_signature_check (
const char *payto_uri, const char *payto_uri,
const char *conversion_url,
const json_t *debit_restrictions,
const json_t *credit_restrictions,
const struct TALER_MasterPublicKeyP *master_pub, const struct TALER_MasterPublicKeyP *master_pub,
const struct TALER_MasterSignatureP *master_sig); const struct TALER_MasterSignatureP *master_sig);
@ -5593,12 +5611,18 @@ TALER_exchange_wire_signature_check (
* Create a signed wire statement for the given account. * Create a signed wire statement for the given account.
* *
* @param payto_uri account specification * @param payto_uri account specification
* @param conversion_url URL of the conversion service, or NULL if none
* @param debit_restrictions JSON encoding of debit restrictions on the account; see AccountRestriction in the spec
* @param credit_restrictions JSON encoding of credit restrictions on the account; see AccountRestriction in the spec
* @param master_priv private key to sign with * @param master_priv private key to sign with
* @param[out] master_sig where to write the signature * @param[out] master_sig where to write the signature
*/ */
void void
TALER_exchange_wire_signature_make ( TALER_exchange_wire_signature_make (
const char *payto_uri, const char *payto_uri,
const char *conversion_url,
const json_t *debit_restrictions,
const json_t *credit_restrictions,
const struct TALER_MasterPrivateKeyP *master_priv, const struct TALER_MasterPrivateKeyP *master_priv,
struct TALER_MasterSignatureP *master_sig); struct TALER_MasterSignatureP *master_sig);

File diff suppressed because it is too large Load Diff

View File

@ -149,7 +149,25 @@ struct TALER_EXCHANGEDB_DenominationKeyInformation
GNUNET_NETWORK_STRUCT_BEGIN GNUNET_NETWORK_STRUCT_BEGIN
/** /**
* Signature of events signalling a reserve got funding. * Events signalling that a coin deposit status
* changed.
*/
struct TALER_CoinDepositEventP
{
/**
* Of type #TALER_DBEVENT_EXCHANGE_DEPOSIT_STATUS_CHANGED.
*/
struct GNUNET_DB_EventHeaderP header;
/**
* Public key of the merchant.
*/
struct TALER_MerchantPublicKeyP merchant_pub;
};
/**
* Events signalling a reserve got funding.
*/ */
struct TALER_ReserveEventP struct TALER_ReserveEventP
{ {
@ -760,7 +778,7 @@ struct TALER_EXCHANGEDB_TableData
struct TALER_ReserveSignatureP reserve_sig; struct TALER_ReserveSignatureP reserve_sig;
uint32_t noreveal_index; uint32_t noreveal_index;
struct GNUNET_TIME_Absolute timestamp; struct GNUNET_TIME_Absolute timestamp;
} withdraw_age_commitments; } age_withdraw_commitments;
struct struct
{ {
@ -769,10 +787,9 @@ struct TALER_EXCHANGEDB_TableData
uint64_t denominations_serial; uint64_t denominations_serial;
void *coin_ev; void *coin_ev;
size_t coin_ev_size; size_t coin_ev_size;
struct TALER_ExchangeWithdrawValues ewv;
// h_coin_ev omitted, to be recomputed! // h_coin_ev omitted, to be recomputed!
struct TALER_BlindedDenominationSignature ev_sig; struct TALER_BlindedDenominationSignature ev_sig;
} withdraw_age_revealed_coins; } age_withdraw_revealed_coins;
} details; } details;
@ -1200,8 +1217,9 @@ struct TALER_EXCHANGEDB_AgeWithdrawCommitment
struct TALER_ReservePublicKeyP reserve_pub; struct TALER_ReservePublicKeyP reserve_pub;
/** /**
* Signature confirming the age withdrawal, matching @e reserve_pub, @e * Signature confirming the age withdrawal commitment, matching @e
* maximum_age_group and @e h_commitment and @e total_amount_with_fee. * reserve_pub, @e maximum_age_group and @e h_commitment and @e
* total_amount_with_fee.
*/ */
struct TALER_ReserveSignatureP reserve_sig; struct TALER_ReserveSignatureP reserve_sig;
@ -2737,6 +2755,28 @@ struct TALER_EXCHANGEDB_CsRevealFreshCoinData
uint32_t coin_off; uint32_t coin_off;
}; };
/**
* Information about a coin that was revealed to the exchange
* during reveal.
*/
struct TALER_EXCHANGEDB_AgeWithdrawRevealedCoin
{
/**
* Hash of the public denomination key of the coin.
*/
struct TALER_DenominationHashP h_denom_pub;
/**
* Signature generated by the exchange over the coin (in blinded format).
*/
struct TALER_BlindedDenominationSignature coin_sig;
/**
* Blinded hash of the new coin
*/
struct TALER_BlindedCoinHashP h_coin_ev;
};
/** /**
* Generic KYC status for some operation. * Generic KYC status for some operation.
@ -2859,6 +2899,9 @@ typedef enum GNUNET_GenericReturnValue
* *
* @param cls closure * @param cls closure
* @param payto_uri the exchange bank account URI * @param payto_uri the exchange bank account URI
* @param conversion_url URL of a conversion service, NULL if there is no conversion
* @param debit_restrictions JSON array with debit restrictions on the account
* @param credit_restrictions JSON array with credit restrictions on the account
* @param master_sig master key signature affirming that this is a bank * @param master_sig master key signature affirming that this is a bank
* account of the exchange (of purpose #TALER_SIGNATURE_MASTER_WIRE_DETAILS) * account of the exchange (of purpose #TALER_SIGNATURE_MASTER_WIRE_DETAILS)
*/ */
@ -2866,6 +2909,9 @@ typedef void
(*TALER_EXCHANGEDB_WireAccountCallback)( (*TALER_EXCHANGEDB_WireAccountCallback)(
void *cls, void *cls,
const char *payto_uri, const char *payto_uri,
const char *conversion_url,
const json_t *debit_restrictions,
const json_t *credit_restrictions,
const struct TALER_MasterSignatureP *master_sig); const struct TALER_MasterSignatureP *master_sig);
@ -3810,19 +3856,18 @@ struct TALER_EXCHANGEDB_Plugin
* age restriction enabled in a given age-withdraw operation and the relevant * age restriction enabled in a given age-withdraw operation and the relevant
* information we learned or created in the reveal steop * information we learned or created in the reveal steop
* *
* @param cls the `struct PostgresClosure` with the plugin-specific state * @param cls The `struct PostgresClosure` with the plugin-specific state
* @param h_commitment The hash of the original age-withdraw commitment, which is a key into the withdraw_age_commitments table * @param h_commitment The hash of the original age-withdraw commitment, which is a key into the age_withdraw_commitments table
* @param num_coins number of coins to generate, size of the @a coin_evs array * @param num_awrcs Number of coins to generate, size of the @a coin_evs array
* TODO: oec * @param awrcs Array of @a num_awrcs information about coins to be created
* @return query execution status * @return query execution status
*/ */
enum GNUNET_DB_QueryStatus enum GNUNET_DB_QueryStatus
(*insert_age_withdraw_reveal)( (*insert_age_withdraw_reveal)(
void *cls, void *cls,
uint64_t h_commitment, const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
uint32_t num_coins uint32_t num_awrcs,
/* TODO: oec */ const struct TALER_EXCHANGEDB_AgeWithdrawRevealedCoin *awrcs);
);
/** /**
* Lookup in the database for the fresh coins with age-restriction that * Lookup in the database for the fresh coins with age-restriction that
@ -5523,6 +5568,9 @@ struct TALER_EXCHANGEDB_Plugin
* *
* @param cls closure * @param cls closure
* @param payto_uri wire account of the exchange * @param payto_uri wire account of the exchange
* @param conversion_url URL of a conversion service, NULL if there is no conversion
* @param debit_restrictions JSON array with debit restrictions on the account
* @param credit_restrictions JSON array with credit restrictions on the account
* @param start_date date when the account was added by the offline system * @param start_date date when the account was added by the offline system
* (only to be used for replay detection) * (only to be used for replay detection)
* @param master_sig public signature affirming the existence of the account, * @param master_sig public signature affirming the existence of the account,
@ -5532,6 +5580,9 @@ struct TALER_EXCHANGEDB_Plugin
enum GNUNET_DB_QueryStatus enum GNUNET_DB_QueryStatus
(*insert_wire)(void *cls, (*insert_wire)(void *cls,
const char *payto_uri, const char *payto_uri,
const char *conversion_url,
json_t *debit_restrictions,
json_t *credit_restrictions,
struct GNUNET_TIME_Timestamp start_date, struct GNUNET_TIME_Timestamp start_date,
const struct TALER_MasterSignatureP *master_sig); const struct TALER_MasterSignatureP *master_sig);
@ -5541,6 +5592,9 @@ struct TALER_EXCHANGEDB_Plugin
* *
* @param cls closure * @param cls closure
* @param payto_uri account the update is about * @param payto_uri account the update is about
* @param conversion_url URL of a conversion service, NULL if there is no conversion
* @param debit_restrictions JSON array with debit restrictions on the account; NULL allowed if not @a enabled
* @param credit_restrictions JSON array with credit restrictions on the account; NULL allowed if not @a enabled
* @param change_date date when the account status was last changed * @param change_date date when the account status was last changed
* (only to be used for replay detection) * (only to be used for replay detection)
* @param enabled true to enable, false to disable (the actual change) * @param enabled true to enable, false to disable (the actual change)
@ -5549,6 +5603,9 @@ struct TALER_EXCHANGEDB_Plugin
enum GNUNET_DB_QueryStatus enum GNUNET_DB_QueryStatus
(*update_wire)(void *cls, (*update_wire)(void *cls,
const char *payto_uri, const char *payto_uri,
const char *conversion_url,
json_t *debit_restrictions,
json_t *credit_restrictions,
struct GNUNET_TIME_Timestamp change_date, struct GNUNET_TIME_Timestamp change_date,
bool enabled); bool enabled);

View File

@ -344,10 +344,6 @@ TALER_extensions_verify_manifests_signature (
* The default age mask represents the age groups * The default age mask represents the age groups
* 0-7, 8-9, 10-11, 12-13, 14-15, 16-17, 18-20, 21-... * 0-7, 8-9, 10-11, 12-13, 14-15, 16-17, 18-20, 21-...
*/ */
#define TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_MASK (1 | 1 << 8 | 1 << 10 \
| 1 << 12 | 1 << 14 \
| 1 << 16 | 1 << 18 \
| 1 << 21)
#define TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_GROUPS "8:10:12:14:16:18:21" #define TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_GROUPS "8:10:12:14:16:18:21"

View File

@ -674,33 +674,6 @@ TALER_JSON_merchant_wire_signature_hash (const json_t *wire_s,
struct TALER_MerchantWireHashP *hc); struct TALER_MerchantWireHashP *hc);
/**
* Check the signature in @a wire_s. Also performs rudimentary
* checks on the account data *if* supported.
*
* @param wire_s signed wire information of an exchange
* @param master_pub master public key of the exchange
* @return #GNUNET_OK if signature is valid
*/
enum GNUNET_GenericReturnValue
TALER_JSON_exchange_wire_signature_check (
const json_t *wire_s,
const struct TALER_MasterPublicKeyP *master_pub);
/**
* Create a signed wire statement for the given account.
*
* @param payto_uri account specification
* @param master_priv private key to sign with
* @return NULL if @a payto_uri is malformed
*/
json_t *
TALER_JSON_exchange_wire_signature_make (
const char *payto_uri,
const struct TALER_MasterPrivateKeyP *master_priv);
/** /**
* Extract a string from @a object under the field @a field, but respecting * Extract a string from @a object under the field @a field, but respecting
* the Taler i18n rules and the language preferences expressed in @a * the Taler i18n rules and the language preferences expressed in @a

View File

@ -437,7 +437,47 @@ TALER_MHD_parse_json_array (struct MHD_Connection *connection,
/** /**
* Extract fixed-size base32crockford encoded data from request. * Extract optional "timeout_ms" argument from request.
*
* @param connection the MHD connection
* @param[out] expiration set to #GNUNET_TIME_UNIT_ZERO_ABS if there was no timeout,
* the current time plus the value given under "timeout_ms" otherwise
* @return #GNUNET_OK on success, #GNUNET_NO if an
* error was returned on @a connection (caller should return #MHD_YES) and
* #GNUNET_SYSERR if we failed to return an error (caller should return #MHD_NO)
*/
enum GNUNET_GenericReturnValue
TALER_MHD_parse_request_arg_timeout (struct MHD_Connection *connection,
struct GNUNET_TIME_Absolute *expiration);
/**
* Extract optional "timeout_ms" argument from request.
* Macro that *returns* #MHD_YES/#MHD_NO if the "timeout_ms"
* argument existed but failed to parse.
*
* @param connection the MHD connection
* @param[out] expiration set to #GNUNET_TIME_UNIT_ZERO_ABS if there was no timeout,
* the current time plus the value given under "timeout_ms" otherwise
*/
#define TALER_MHD_parse_request_timeout(connection,expiration) \
do { \
switch (TALER_MHD_parse_request_arg_timeout (connection, \
expiration)) \
{ \
case GNUNET_SYSERR: \
GNUNET_break (0); \
return MHD_NO; \
case GNUNET_NO: \
GNUNET_break_op (0); \
case GNUNET_OK: \
break; \
} \
} while (0)
/**
* Extract fixed-size base32crockford encoded data from request argument.
* *
* Queues an error response to the connection if the parameter is missing or * Queues an error response to the connection if the parameter is missing or
* invalid. * invalid.
@ -446,16 +486,195 @@ TALER_MHD_parse_json_array (struct MHD_Connection *connection,
* @param param_name the name of the parameter with the key * @param param_name the name of the parameter with the key
* @param[out] out_data pointer to store the result * @param[out] out_data pointer to store the result
* @param out_size expected size of @a out_data * @param out_size expected size of @a out_data
* @param[out] present set to true if argument was found
* @return * @return
* #GNUNET_YES if the the argument is present * #GNUNET_YES if the the argument is present
* #GNUNET_NO if the argument is absent or malformed * #GNUNET_NO if the argument is malformed
* #GNUNET_SYSERR on internal error (error response could not be sent) * #GNUNET_SYSERR on internal error (error response could not be sent)
*/ */
enum GNUNET_GenericReturnValue enum GNUNET_GenericReturnValue
TALER_MHD_parse_request_arg_data (struct MHD_Connection *connection, TALER_MHD_parse_request_arg_data (struct MHD_Connection *connection,
const char *param_name, const char *param_name,
void *out_data, void *out_data,
size_t out_size); size_t out_size,
bool *present);
/**
* Extract fixed-size base32crockford encoded data from request header.
*
* Queues an error response to the connection if the parameter is missing or
* invalid.
*
* @param connection the MHD connection
* @param header_name the name of the HTTP header with the value
* @param[out] out_data pointer to store the result
* @param out_size expected size of @a out_data
* @param[out] present set to true if argument was found
* @return
* #GNUNET_YES if the the argument is present
* #GNUNET_NO if the argument is malformed
* #GNUNET_SYSERR on internal error (error response could not be sent)
*/
enum GNUNET_GenericReturnValue
TALER_MHD_parse_request_header_data (struct MHD_Connection *connection,
const char *header_name,
void *out_data,
size_t out_size,
bool *present);
/**
* Extract fixed-size base32crockford encoded data from request.
*
* @param connection the MHD connection
* @param name the name of the parameter with the key
* @param[out] val pointer to store the result, type must determine size
* @param[in,out] required pass true to require presence of this argument; if 'false'
* set to true if the argument was found
* @return
* #GNUNET_YES if the the argument is present
* #GNUNET_NO if the argument is absent or malformed
* #GNUNET_SYSERR on internal error (error response could not be sent)
*/
#define TALER_MHD_parse_request_arg_auto(connection,name,val,required) \
do { \
bool p; \
switch (TALER_MHD_parse_request_arg_data (connection, name, \
val, sizeof (*val), &p)) \
{ \
case GNUNET_SYSERR: \
GNUNET_break (0); \
return MHD_NO; \
case GNUNET_NO: \
GNUNET_break_op (0); \
return MHD_YES; \
case GNUNET_OK: \
if (required & (! p)) \
return TALER_MHD_reply_with_error ( \
connection, \
MHD_HTTP_BAD_REQUEST, \
TALER_EC_GENERIC_PARAMETER_MISSING, \
name); \
required = p; \
break; \
} \
} while (0)
/**
* Extract required fixed-size base32crockford encoded data from request.
*
* @param connection the MHD connection
* @param name the name of the parameter with the key
* @param[out] val pointer to store the result, type must determine size
* @return
* #GNUNET_YES if the the argument is present
* #GNUNET_NO if the argument is absent or malformed
* #GNUNET_SYSERR on internal error (error response could not be sent)
*/
#define TALER_MHD_parse_request_arg_auto_t(connection,name,val) \
do { \
bool b = true; \
TALER_MHD_parse_request_arg_auto (connection,name,val,b); \
} while (0)
/**
* Extract fixed-size base32crockford encoded data from request.
*
* @param connection the MHD connection
* @param name the name of the header with the key
* @param[out] val pointer to store the result, type must determine size
* @param[in,out] required pass true to require presence of this argument; if 'false'
* set to true if the argument was found
* @return
* #GNUNET_YES if the the argument is present
* #GNUNET_NO if the argument is absent or malformed
* #GNUNET_SYSERR on internal error (error response could not be sent)
*/
#define TALER_MHD_parse_request_header_auto(connection,name,val,required) \
do { \
bool p; \
switch (TALER_MHD_parse_request_header_data (connection, name, \
val, sizeof (*val), &p)) \
{ \
case GNUNET_SYSERR: \
GNUNET_break (0); \
return MHD_NO; \
case GNUNET_NO: \
GNUNET_break_op (0); \
return MHD_YES; \
case GNUNET_OK: \
if (required & (! p)) \
return TALER_MHD_reply_with_error ( \
connection, \
MHD_HTTP_BAD_REQUEST, \
TALER_EC_GENERIC_PARAMETER_MISSING, \
name); \
required = p; \
break; \
} \
} while (0)
/**
* Extract required fixed-size base32crockford encoded data from request.
*
* @param connection the MHD connection
* @param name the name of the header with the key
* @param[out] val pointer to store the result, type must determine size
* @return
* #GNUNET_YES if the the argument is present
* #GNUNET_NO if the argument is absent or malformed
* #GNUNET_SYSERR on internal error (error response could not be sent)
*/
#define TALER_MHD_parse_request_header_auto_t(connection,name,val) \
do { \
bool b = true; \
TALER_MHD_parse_request_header_auto (connection,name,val,b); \
} while (0)
/**
* Check that the 'Content-Length' header is giving
* a length below @a max_len. If not, return an
* appropriate error response and return the
* correct #MHD_YES/#MHD_NO value from this function.
*
* @param connection the MHD connection
* @param max_len maximum allowed content length
* @return
* #GNUNET_YES if the the argument is present
* #GNUNET_NO if the argument is absent or malformed
* #GNUNET_SYSERR on internal error (error response could not be sent)
*/
enum GNUNET_GenericReturnValue
TALER_MHD_check_content_length_ (struct MHD_Connection *connection,
unsigned long long max_len);
/**
* Check that the 'Content-Length' header is giving
* a length below @a max_len. If not, return an
* appropriate error response and return the
* correct #MHD_YES/#MHD_NO value from this function.
*
* @param connection the MHD connection
* @param max_len maximum allowed content length
*/
#define TALER_MHD_check_content_length(connection,max_len) \
do { \
switch (TALER_MHD_check_content_length_ (connection, max_len)) \
{ \
case GNUNET_SYSERR: \
GNUNET_break (0); \
return MHD_NO; \
case GNUNET_NO: \
GNUNET_break_op (0); \
return MHD_YES; \
case GNUNET_OK: \
break; \
} \
} while (0)
/** /**

View File

@ -22,7 +22,7 @@
#define TALER_TEMPLATING_LIB_H #define TALER_TEMPLATING_LIB_H
#include <microhttpd.h> #include <microhttpd.h>
#include "taler_mhd_lib.h"
/** /**
* Fill in Mustach template @a tmpl using the data from @a root * Fill in Mustach template @a tmpl using the data from @a root
@ -91,6 +91,26 @@ TALER_TEMPLATING_reply (struct MHD_Connection *connection,
const char *taler_uri, const char *taler_uri,
const json_t *root); const json_t *root);
/**
* Load a @a template and substitute an error message based on @a ec and @a
* detail, returning the result to the @a connection with the given @a
* http_status code.
*
* @param connection the connection we act upon
* @param template_basename basename of the template to load
* @param http_status code to use on success
* @param ec error code to return
* @param detail optional text to add to the template
* @return #MHD_YES on success, #MHD_NO to just close the connection
*/
MHD_RESULT
TALER_TEMPLATING_reply_error (struct MHD_Connection *connection,
const char *template_basename,
unsigned int http_status,
enum TALER_ErrorCode ec,
const char *detail);
/** /**
* Preload templates. * Preload templates.
* *

View File

@ -147,15 +147,11 @@ TALER_TESTING_prepare_exchange (const char *config_filename,
* *
* @param cls closure, typically, the "run" method containing * @param cls closure, typically, the "run" method containing
* all the commands to be run, and a closure for it. * all the commands to be run, and a closure for it.
* @param hr http response details * @param kr response details
* @param keys the exchange's keys.
* @param compat protocol compatibility information.
*/ */
void void
TALER_TESTING_cert_cb (void *cls, TALER_TESTING_cert_cb (void *cls,
const struct TALER_EXCHANGE_HttpResponse *hr, const struct TALER_EXCHANGE_KeysResponse *kr);
const struct TALER_EXCHANGE_Keys *keys,
enum TALER_EXCHANGE_VersionCompatibility compat);
/** /**
@ -1242,6 +1238,36 @@ TALER_TESTING_cmd_exec_wirewatch (const char *label,
const char *config_filename); const char *config_filename);
/**
* Request URL via "wget".
*
* @param label command label.
* @param url URL to fetch
* @return the command.
*/
struct TALER_TESTING_Command
TALER_TESTING_cmd_exec_wget (const char *label,
const char *url);
/**
* Request fetch-transactions via "wget".
*
* @param label command label.
* @param username username to use
* @param password password to use
* @param bank_base_url base URL of the nexus
* @param account_id account to fetch transactions for
* @return the command.
*/
struct TALER_TESTING_Command
TALER_TESTING_cmd_nexus_fetch_transactions (const char *label,
const char *username,
const char *password,
const char *bank_base_url,
const char *account_id);
/** /**
* Make a "expire" CMD. * Make a "expire" CMD.
* *

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER This file is part of TALER
Copyright (C) 2014-2021 Taler Systems SA Copyright (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software terms of the GNU General Public License as published by the Free Software
@ -229,6 +229,16 @@ void
TALER_OS_init (void); TALER_OS_init (void);
/**
* Re-encode string at @a inp to match RFC 8785 (section 3.2.2.2).
*
* @param[in,out] inp pointer to string to re-encode
* @return number of bytes in resulting @a inp
*/
size_t
TALER_rfc8785encode (char **inp);
/** /**
* URL-encode a string according to rfc3986. * URL-encode a string according to rfc3986.
* *
@ -559,6 +569,58 @@ enum GNUNET_GenericReturnValue
TALER_JSON_parse_age_groups (const json_t *root, TALER_JSON_parse_age_groups (const json_t *root,
struct TALER_AgeMask *mask); struct TALER_AgeMask *mask);
/**
* Handle to an external process that will assist
* with some JSON-to-JSON conversion.
*/
struct TALER_JSON_ExternalConversion;
/**
* Type of a callback that receives a JSON @a result.
*
* @param cls closure
* @param status_type how did the process die
* @param code termination status code from the process
* @param result some JSON result, NULL if we failed to get an JSON output
*/
typedef void
(*TALER_JSON_JsonCallback) (void *cls,
enum GNUNET_OS_ProcessStatusType status_type,
unsigned long code,
const json_t *result);
/**
* Launch some external helper @a binary to convert some @a input
* and eventually call @a cb with the result.
*
* @param input JSON to serialize and pass to the helper process
* @param cb function to call on the result
* @param cb_cls closure for @a cb
* @param binary name of the binary to execute
* @param ... NULL-terminated list of arguments for the @a binary,
* usually starting with again the name of the binary
* @return handle to cancel the operation (and kill the helper)
*/
struct TALER_JSON_ExternalConversion *
TALER_JSON_external_conversion_start (const json_t *input,
TALER_JSON_JsonCallback cb,
void *cb_cls,
const char *binary,
...);
/**
* Abort external conversion, killing the process and preventing
* the callback from being called. Must not be called after the
* callback was invoked.
*
* @param[in] ec external conversion handle to cancel
*/
void
TALER_JSON_external_conversion_stop (
struct TALER_JSON_ExternalConversion *ec);
#undef __TALER_UTIL_LIB_H_INSIDE__ #undef __TALER_UTIL_LIB_H_INSIDE__
#endif #endif

View File

@ -28,12 +28,10 @@ libtalerjson_la_LIBADD = \
$(XLIB) $(XLIB)
TESTS = \ TESTS = \
test_json \ test_json
test_json_wire
check_PROGRAMS= \ check_PROGRAMS= \
test_json \ test_json
test_json_wire
test_json_SOURCES = \ test_json_SOURCES = \
test_json.c test_json.c
@ -43,13 +41,3 @@ test_json_LDADD = \
$(top_builddir)/src/util/libtalerutil.la \ $(top_builddir)/src/util/libtalerutil.la \
-lgnunetutil \ -lgnunetutil \
-ljansson -ljansson
test_json_wire_SOURCES = \
test_json_wire.c
test_json_wire_LDADD = \
$(top_builddir)/src/json/libtalerjson.la \
-lgnunetjson \
$(top_builddir)/src/util/libtalerutil.la \
-lgnunetutil \
-ljansson

View File

@ -61,170 +61,6 @@ contains_real (const json_t *json)
} }
/**
* Dump character in the low range into @a buf
* following RFC 8785.
*
* @param[in,out] buf buffer to modify
* @param val value to dump
*/
static void
lowdump (struct GNUNET_Buffer *buf,
unsigned char val)
{
char scratch[7];
switch (val)
{
case 0x8:
GNUNET_buffer_write (buf,
"\\b",
2);
break;
case 0x9:
GNUNET_buffer_write (buf,
"\\t",
2);
break;
case 0xA:
GNUNET_buffer_write (buf,
"\\n",
2);
break;
case 0xC:
GNUNET_buffer_write (buf,
"\\f",
2);
break;
case 0xD:
GNUNET_buffer_write (buf,
"\\r",
2);
break;
default:
GNUNET_snprintf (scratch,
sizeof (scratch),
"\\u%04x",
(unsigned int) val);
GNUNET_buffer_write (buf,
scratch,
6);
break;
}
}
/**
* Re-encode string at @a inp to match RFC 8785 (section 3.2.2.2).
*
* @param[in,out] inp pointer to string to re-encode
* @return number of bytes in resulting @a inp
*/
static size_t
rfc8785encode (char **inp)
{
struct GNUNET_Buffer buf = { 0 };
size_t left = strlen (*inp) + 1;
size_t olen;
char *in = *inp;
const char *pos = in;
GNUNET_buffer_prealloc (&buf,
left + 40);
buf.warn_grow = 0; /* disable, + 40 is just a wild guess */
while (1)
{
int mbl = u8_mblen ((unsigned char *) pos,
left);
unsigned char val;
if (0 == mbl)
break;
val = (unsigned char) *pos;
if ( (1 == mbl) &&
(val <= 0x1F) )
{
/* Should not happen, as input is produced by
* JSON stringification */
GNUNET_break (0);
lowdump (&buf,
val);
}
else if ( (1 == mbl) && ('\\' == *pos) )
{
switch (*(pos + 1))
{
case '\\':
mbl = 2;
GNUNET_buffer_write (&buf,
pos,
mbl);
break;
case 'u':
{
unsigned int num;
uint32_t n32;
unsigned char res[8];
size_t rlen;
GNUNET_assert ( (1 ==
sscanf (pos + 2,
"%4x",
&num)) ||
(1 ==
sscanf (pos + 2,
"%4X",
&num)) );
mbl = 6;
n32 = (uint32_t) num;
rlen = sizeof (res);
u32_to_u8 (&n32,
1,
res,
&rlen);
if ( (1 == rlen) &&
(res[0] <= 0x1F) )
{
lowdump (&buf,
res[0]);
}
else
{
GNUNET_buffer_write (&buf,
(const char *) res,
rlen);
}
}
break;
default:
mbl = 2;
GNUNET_buffer_write (&buf,
pos,
mbl);
break;
}
}
else
{
GNUNET_buffer_write (&buf,
pos,
mbl);
}
left -= mbl;
pos += mbl;
}
/* 0-terminate buffer */
GNUNET_buffer_write (&buf,
"",
1);
GNUNET_free (in);
*inp = GNUNET_buffer_reap (&buf,
&olen);
return olen;
}
/** /**
* Dump the @a json to a string and hash it. * Dump the @a json to a string and hash it.
* *
@ -262,7 +98,7 @@ dump_and_hash (const json_t *json,
GNUNET_break (0); GNUNET_break (0);
return GNUNET_SYSERR; return GNUNET_SYSERR;
} }
len = rfc8785encode (&wire_enc); len = TALER_rfc8785encode (&wire_enc);
if (NULL == salt) if (NULL == salt)
{ {
GNUNET_CRYPTO_hash (wire_enc, GNUNET_CRYPTO_hash (wire_enc,
@ -819,6 +655,7 @@ parse_path (json_t *obj,
json_t *next_obj = NULL; json_t *next_obj = NULL;
char *next_dot; char *next_dot;
GNUNET_assert (NULL != id); /* make stupid compiler happy */
if (NULL == next_id) if (NULL == next_id)
{ {
cb (cb_cls, cb (cb_cls,
@ -1031,7 +868,7 @@ TALER_JSON_canonicalize (const json_t *input)
GNUNET_break (0); GNUNET_break (0);
return NULL; return NULL;
} }
rfc8785encode (&wire_enc); TALER_rfc8785encode (&wire_enc);
return wire_enc; return wire_enc;
} }

View File

@ -70,80 +70,6 @@ TALER_JSON_merchant_wire_signature_hash (const json_t *wire_s,
} }
enum GNUNET_GenericReturnValue
TALER_JSON_exchange_wire_signature_check (
const json_t *wire_s,
const struct TALER_MasterPublicKeyP *master_pub)
{
const char *payto_uri;
struct TALER_MasterSignatureP master_sig;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("payto_uri",
&payto_uri),
GNUNET_JSON_spec_fixed_auto ("master_sig",
&master_sig),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (wire_s,
spec,
NULL, NULL))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
{
char *err;
err = TALER_payto_validate (payto_uri);
if (NULL != err)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"URI `%s' ill-formed: %s\n",
payto_uri,
err);
GNUNET_free (err);
return GNUNET_SYSERR;
}
}
return TALER_exchange_wire_signature_check (payto_uri,
master_pub,
&master_sig);
}
json_t *
TALER_JSON_exchange_wire_signature_make (
const char *payto_uri,
const struct TALER_MasterPrivateKeyP *master_priv)
{
struct TALER_MasterSignatureP master_sig;
char *err;
if (NULL !=
(err = TALER_payto_validate (payto_uri)))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Invalid payto URI `%s': %s\n",
payto_uri,
err);
GNUNET_free (err);
return NULL;
}
TALER_exchange_wire_signature_make (payto_uri,
master_priv,
&master_sig);
return GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("payto_uri",
payto_uri),
GNUNET_JSON_pack_data_auto ("master_sig",
&master_sig));
}
char * char *
TALER_JSON_wire_to_payto (const json_t *wire_s) TALER_JSON_wire_to_payto (const json_t *wire_s)
{ {

View File

@ -1,80 +0,0 @@
/*
This file is part of TALER
(C) 2015, 2016 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* @file json/test_json_wire.c
* @brief Tests for Taler-specific crypto logic
* @author Christian Grothoff <christian@grothoff.org>
*/
#include "platform.h"
#include "taler_util.h"
#include "taler_json_lib.h"
int
main (int argc,
const char *const argv[])
{
struct TALER_MasterPublicKeyP master_pub;
struct TALER_MasterPrivateKeyP master_priv;
json_t *wire_xtalerbank;
json_t *wire_iban;
const char *payto_xtalerbank = "payto://x-taler-bank/42";
const char *payto_iban =
"payto://iban/BIC-TO-BE-SKIPPED/DE89370400440532013000?receiver-name=Test";
char *p_xtalerbank;
char *p_iban;
(void) argc;
(void) argv;
GNUNET_log_setup ("test-json-wire",
"WARNING",
NULL);
GNUNET_CRYPTO_eddsa_key_create (&master_priv.eddsa_priv);
GNUNET_CRYPTO_eddsa_key_get_public (&master_priv.eddsa_priv,
&master_pub.eddsa_pub);
wire_xtalerbank = TALER_JSON_exchange_wire_signature_make (payto_xtalerbank,
&master_priv);
wire_iban = TALER_JSON_exchange_wire_signature_make (payto_iban,
&master_priv);
if (NULL == wire_iban)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Could not parse payto/IBAN (%s) into 'wire object'\n",
payto_iban);
return 1;
}
p_xtalerbank = TALER_JSON_wire_to_payto (wire_xtalerbank);
p_iban = TALER_JSON_wire_to_payto (wire_iban);
GNUNET_assert (0 == strcmp (p_xtalerbank, payto_xtalerbank));
GNUNET_assert (0 == strcmp (p_iban, payto_iban));
GNUNET_free (p_xtalerbank);
GNUNET_free (p_iban);
GNUNET_assert (GNUNET_OK ==
TALER_JSON_exchange_wire_signature_check (wire_xtalerbank,
&master_pub));
GNUNET_assert (GNUNET_OK ==
TALER_JSON_exchange_wire_signature_check (wire_iban,
&master_pub));
json_decref (wire_xtalerbank);
json_decref (wire_iban);
return 0;
}
/* end of test_json_wire.c */

View File

@ -16,7 +16,11 @@ pkgcfg_DATA = \
EXTRA_DIST = \ EXTRA_DIST = \
$(pkgcfg_DATA) \ $(pkgcfg_DATA) \
sample.conf sample.conf \
persona-sample-reply.json
bin_SCRIPTS = \
taler-exchange-kyc-persona-converter.sh
lib_LTLIBRARIES = \ lib_LTLIBRARIES = \
libtalerkyclogic.la libtalerkyclogic.la

View File

@ -29,6 +29,10 @@ KYC_PERSONA_SUBDOMAIN = taler
# Authentication token to use. # Authentication token to use.
KYC_PERSONA_AUTH_TOKEN = persona_sandbox_42 KYC_PERSONA_AUTH_TOKEN = persona_sandbox_42
# Program that converts Persona KYC data into the
# GNU Taler format.
KYC_PERSONA_CONVERTER_HELPER = taler-exchange-kyc-persona-converter.sh
# Form to use. # Form to use.
KYC_PERSONA_TEMPLATE_ID = itempl_Uj6Xxxxx KYC_PERSONA_TEMPLATE_ID = itempl_Uj6Xxxxx

View File

@ -780,10 +780,11 @@ TALER_KYCLOGIC_kyc_init (const struct GNUNET_CONFIGURATION_Handle *cfg)
TALER_KYCLOGIC_kyc_done (); TALER_KYCLOGIC_kyc_done ();
return GNUNET_SYSERR; return GNUNET_SYSERR;
} }
qsort (kyc_triggers, if (0 != num_kyc_triggers)
num_kyc_triggers, qsort (kyc_triggers,
sizeof (struct TALER_KYCLOGIC_KycTrigger *), num_kyc_triggers,
&sort_by_timeframe); sizeof (struct TALER_KYCLOGIC_KycTrigger *),
&sort_by_timeframe);
return GNUNET_OK; return GNUNET_OK;
} }

View File

@ -490,8 +490,6 @@ initiate_task (void *cls)
struct PluginState *ps = pd->ps; struct PluginState *ps = pd->ps;
char *hps; char *hps;
char *url; char *url;
char *redirect_uri;
char *redirect_uri_encoded;
char legi_s[42]; char legi_s[42];
ih->task = NULL; ih->task = NULL;
@ -501,19 +499,27 @@ initiate_task (void *cls)
(unsigned long long) ih->legitimization_uuid); (unsigned long long) ih->legitimization_uuid);
hps = GNUNET_STRINGS_data_to_string_alloc (&ih->h_payto, hps = GNUNET_STRINGS_data_to_string_alloc (&ih->h_payto,
sizeof (ih->h_payto)); sizeof (ih->h_payto));
GNUNET_asprintf (&redirect_uri, {
"%skyc-proof/%s?state=%s", char *redirect_uri_encoded;
ps->exchange_base_url,
pd->section, {
hps); char *redirect_uri;
redirect_uri_encoded = TALER_urlencode (redirect_uri);
GNUNET_free (redirect_uri); GNUNET_asprintf (&redirect_uri,
GNUNET_asprintf (&url, "%skyc-proof/%s?state=%s",
"%s?response_type=code&client_id=%s&redirect_uri=%s", ps->exchange_base_url,
pd->login_url, pd->section,
pd->client_id, hps);
redirect_uri_encoded); redirect_uri_encoded = TALER_urlencode (redirect_uri);
GNUNET_free (redirect_uri_encoded); GNUNET_free (redirect_uri);
}
GNUNET_asprintf (&url,
"%s?response_type=code&client_id=%s&redirect_uri=%s",
pd->login_url,
pd->client_id,
redirect_uri_encoded);
GNUNET_free (redirect_uri_encoded);
}
/* FIXME-API: why do we *redirect* the client here, /* FIXME-API: why do we *redirect* the client here,
instead of making the HTTP request *ourselves* instead of making the HTTP request *ourselves*
and forwarding the response? This prevents us and forwarding the response? This prevents us
@ -582,6 +588,37 @@ oauth2_initiate_cancel (struct TALER_KYCLOGIC_InitiateHandle *ih)
} }
/**
* Cancel KYC proof.
*
* @param[in] ph handle of operation to cancel
*/
static void
oauth2_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph)
{
if (NULL != ph->task)
{
GNUNET_SCHEDULER_cancel (ph->task);
ph->task = NULL;
}
if (NULL != ph->job)
{
GNUNET_CURL_job_cancel (ph->job);
ph->job = NULL;
}
if (NULL != ph->response)
{
MHD_destroy_response (ph->response);
ph->response = NULL;
}
GNUNET_free (ph->provider_user_id);
if (NULL != ph->attributes)
json_decref (ph->attributes);
GNUNET_free (ph->post_body);
GNUNET_free (ph);
}
/** /**
* Function called to asynchronously return the final * Function called to asynchronously return the final
* result to the callback. * result to the callback.
@ -602,10 +639,8 @@ return_proof_response (void *cls)
ph->attributes, ph->attributes,
ph->http_status, ph->http_status,
ph->response); ph->response);
GNUNET_free (ph->provider_user_id); ph->response = NULL; /*Ownership passed to 'ph->cb'!*/
if (NULL != ph->attributes) oauth2_proof_cancel (ph);
json_decref (ph->attributes);
GNUNET_free (ph);
} }
@ -1101,7 +1136,6 @@ oauth2_proof (void *cls,
1)); 1));
{ {
char *client_id; char *client_id;
char *redirect_uri;
char *client_secret; char *client_secret;
char *authorization_code; char *authorization_code;
char *redirect_uri_encoded; char *redirect_uri_encoded;
@ -1109,13 +1143,17 @@ oauth2_proof (void *cls,
hps = GNUNET_STRINGS_data_to_string_alloc (&ph->h_payto, hps = GNUNET_STRINGS_data_to_string_alloc (&ph->h_payto,
sizeof (ph->h_payto)); sizeof (ph->h_payto));
GNUNET_asprintf (&redirect_uri, {
"%skyc-proof/%s?state=%s", char *redirect_uri;
ps->exchange_base_url,
pd->section, GNUNET_asprintf (&redirect_uri,
hps); "%skyc-proof/%s?state=%s",
redirect_uri_encoded = TALER_urlencode (redirect_uri); ps->exchange_base_url,
GNUNET_free (redirect_uri); pd->section,
hps);
redirect_uri_encoded = TALER_urlencode (redirect_uri);
GNUNET_free (redirect_uri);
}
GNUNET_assert (NULL != redirect_uri_encoded); GNUNET_assert (NULL != redirect_uri_encoded);
client_id = curl_easy_escape (ph->eh, client_id = curl_easy_escape (ph->eh,
pd->client_id, pd->client_id,
@ -1164,34 +1202,6 @@ oauth2_proof (void *cls,
} }
/**
* Cancel KYC proof.
*
* @param[in] ph handle of operation to cancel
*/
static void
oauth2_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph)
{
if (NULL != ph->task)
{
GNUNET_SCHEDULER_cancel (ph->task);
ph->task = NULL;
}
if (NULL != ph->job)
{
GNUNET_CURL_job_cancel (ph->job);
ph->job = NULL;
}
if (NULL != ph->response)
{
MHD_destroy_response (ph->response);
ph->response = NULL;
}
GNUNET_free (ph->post_body);
GNUNET_free (ph);
}
/** /**
* Function to asynchronously return the 404 not found * Function to asynchronously return the 404 not found
* page for the webhook. * page for the webhook.

View File

@ -111,6 +111,12 @@ struct TALER_KYCLOGIC_ProviderDetails
*/ */
char *subdomain; char *subdomain;
/**
* Name of the program we use to convert outputs
* from Persona into our JSON inputs.
*/
char *conversion_binary;
/** /**
* Where to redirect the client upon completion. * Where to redirect the client upon completion.
*/ */
@ -230,6 +236,12 @@ struct TALER_KYCLOGIC_ProofHandle
*/ */
char *url; char *url;
/**
* Handle to an external process that converts the
* Persona response to our internal format.
*/
struct TALER_JSON_ExternalConversion *ec;
/** /**
* Hash of the payto:// URI we are checking the KYC for. * Hash of the payto:// URI we are checking the KYC for.
*/ */
@ -246,6 +258,11 @@ struct TALER_KYCLOGIC_ProofHandle
*/ */
char *provider_user_id; char *provider_user_id;
/**
* Account ID from the service.
*/
char *account_id;
/** /**
* Inquiry ID at the provider. * Inquiry ID at the provider.
*/ */
@ -294,6 +311,11 @@ struct TALER_KYCLOGIC_WebhookHandle
*/ */
char *inquiry_id; char *inquiry_id;
/**
* Account ID from the service.
*/
char *account_id;
/** /**
* URL of the cURL request. * URL of the cURL request.
*/ */
@ -315,6 +337,12 @@ struct TALER_KYCLOGIC_WebhookHandle
*/ */
const char *template_id; const char *template_id;
/**
* Handle to an external process that converts the
* Persona response to our internal format.
*/
struct TALER_JSON_ExternalConversion *ec;
/** /**
* Our account ID. * Our account ID.
*/ */
@ -344,6 +372,7 @@ persona_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd)
GNUNET_free (pd->auth_token); GNUNET_free (pd->auth_token);
GNUNET_free (pd->template_id); GNUNET_free (pd->template_id);
GNUNET_free (pd->subdomain); GNUNET_free (pd->subdomain);
GNUNET_free (pd->conversion_binary);
GNUNET_free (pd->salt); GNUNET_free (pd->salt);
GNUNET_free (pd->section); GNUNET_free (pd->section);
GNUNET_free (pd->post_kyc_redirect_url); GNUNET_free (pd->post_kyc_redirect_url);
@ -418,6 +447,18 @@ persona_load_configuration (void *cls,
persona_unload_configuration (pd); persona_unload_configuration (pd);
return NULL; return NULL;
} }
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (ps->cfg,
provider_section_name,
"KYC_PERSONA_CONVERTER_HELPER",
&pd->conversion_binary))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
provider_section_name,
"KYC_PERSONA_CONVERTER_HELPER");
persona_unload_configuration (pd);
return NULL;
}
if (GNUNET_OK != if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (ps->cfg, GNUNET_CONFIGURATION_get_value_string (ps->cfg,
provider_section_name, provider_section_name,
@ -838,8 +879,14 @@ persona_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph)
GNUNET_CURL_job_cancel (ph->job); GNUNET_CURL_job_cancel (ph->job);
ph->job = NULL; ph->job = NULL;
} }
if (NULL != ph->ec)
{
TALER_JSON_external_conversion_stop (ph->ec);
ph->ec = NULL;
}
GNUNET_free (ph->url); GNUNET_free (ph->url);
GNUNET_free (ph->provider_user_id); GNUNET_free (ph->provider_user_id);
GNUNET_free (ph->account_id);
GNUNET_free (ph->inquiry_id); GNUNET_free (ph->inquiry_id);
GNUNET_free (ph); GNUNET_free (ph);
} }
@ -922,161 +969,6 @@ proof_reply_error (struct TALER_KYCLOGIC_ProofHandle *ph,
} }
/**
* Convert KYC attribute data from Persona response.
*
* @param attr json array with Persona attribute data
* @return KYC attribute data
*/
static json_t *
convert_attributes (const json_t *attr)
{
const char *country_code = NULL;
const char *name_first = NULL;
const char *name_middle = NULL;
const char *name_last = NULL;
const char *address_street_1 = NULL;
const char *address_street_2 = NULL;
const char *address_city = NULL;
const char *address_postal_code = NULL;
const char *birthdate = NULL;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("country-code",
&country_code),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("name-first",
&name_first),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("name-middle",
&name_middle),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("name-last",
&name_last),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("address-street-1",
&address_street_1),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("address-street-2",
&address_street_2),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("address-city",
&address_city),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("address-postal-code",
&address_postal_code),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("birthdate",
&birthdate),
NULL),
GNUNET_JSON_spec_end ()
};
json_t *ret;
if (GNUNET_OK !=
GNUNET_JSON_parse (attr,
spec,
NULL, NULL))
{
GNUNET_break (0);
json_dumpf (attr,
stderr,
JSON_INDENT (2));
return NULL;
}
{
char *name = NULL;
char *street = NULL;
char *city = NULL;
if ( (NULL != name_last) ||
(NULL != name_first) ||
(NULL != name_middle) )
{
GNUNET_asprintf (&name,
"%s, %s %s",
(NULL != name_last)
? name_last
: "",
(NULL != name_first)
? name_first
: "",
(NULL != name_middle)
? name_middle
: "");
}
if ( (NULL != address_city) ||
(NULL != address_postal_code) )
{
GNUNET_asprintf (&city,
"%s%s%s %s",
(NULL != country_code)
? country_code
: "",
(NULL != country_code)
? "-"
: "",
(NULL != address_postal_code)
? address_postal_code
: "",
(NULL != address_city)
? address_city
: "");
}
if ( (NULL != address_street_1) ||
(NULL != address_street_2) )
{
GNUNET_asprintf (&street,
"%s%s%s",
(NULL != address_street_1)
? address_street_1
: "",
( (NULL != address_street_1) &&
(NULL != address_street_2) )
? "\n"
: "",
(NULL != address_street_2)
? address_street_2
: "");
}
ret = GNUNET_JSON_PACK (
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string (
TALER_ATTRIBUTE_BIRTHDATE,
birthdate)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string (
TALER_ATTRIBUTE_FULL_NAME,
name)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string (
TALER_ATTRIBUTE_ADDRESS_STREET,
street)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string (
TALER_ATTRIBUTE_ADDRESS_CITY,
city)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string (
TALER_ATTRIBUTE_RESIDENCES,
country_code))
);
GNUNET_free (street);
GNUNET_free (city);
GNUNET_free (name);
}
return ret;
}
/** /**
* Return a response for the @a ph request indicating a * Return a response for the @a ph request indicating a
* protocol violation by the Persona server. * protocol violation by the Persona server.
@ -1115,6 +1007,86 @@ return_invalid_response (struct TALER_KYCLOGIC_ProofHandle *ph,
} }
/**
* Start the external conversion helper.
*
* @param pd configuration details
* @param attr attributes to give to the helper
* @param cb function to call with the result
* @param cb_cls closure for @a cb
* @return handle for the helper
*/
static struct TALER_JSON_ExternalConversion *
start_conversion (const struct TALER_KYCLOGIC_ProviderDetails *pd,
const json_t *attr,
TALER_JSON_JsonCallback cb,
void *cb_cls)
{
return TALER_JSON_external_conversion_start (
attr,
cb,
cb_cls,
pd->conversion_binary,
pd->conversion_binary,
"-a",
pd->auth_token,
NULL
);
}
/**
* Type of a callback that receives a JSON @a result.
*
* @param cls closure with a `struct TALER_KYCLOGIC_ProofHandle *`
* @param status_type how did the process die
* @param code termination status code from the process
* @param attr result some JSON result, NULL if we failed to get an JSON output
*/
static void
proof_post_conversion_cb (void *cls,
enum GNUNET_OS_ProcessStatusType status_type,
unsigned long code,
const json_t *attr)
{
struct TALER_KYCLOGIC_ProofHandle *ph = cls;
struct MHD_Response *resp;
struct GNUNET_TIME_Absolute expiration;
ph->ec = NULL;
if ( (NULL == attr) ||
(0 != code) )
{
GNUNET_break_op (0);
return_invalid_response (ph,
MHD_HTTP_OK,
ph->inquiry_id,
"converter",
NULL);
persona_proof_cancel (ph);
return;
}
expiration = GNUNET_TIME_relative_to_absolute (ph->pd->validity);
resp = MHD_create_response_from_buffer (0,
"",
MHD_RESPMEM_PERSISTENT);
GNUNET_break (MHD_YES ==
MHD_add_response_header (resp,
MHD_HTTP_HEADER_LOCATION,
ph->pd->post_kyc_redirect_url));
TALER_MHD_add_global_headers (resp);
ph->cb (ph->cb_cls,
TALER_KYCLOGIC_STATUS_SUCCESS,
ph->account_id,
ph->inquiry_id,
expiration,
attr,
MHD_HTTP_SEE_OTHER,
resp);
persona_proof_cancel (ph);
}
/** /**
* Function called when we're done processing the * Function called when we're done processing the
* HTTP "/api/v1/inquiries/{inquiry-id}" request. * HTTP "/api/v1/inquiries/{inquiry-id}" request.
@ -1283,46 +1255,15 @@ handle_proof_finished (void *cls,
data); data);
break; break;
} }
ph->account_id = GNUNET_strdup (account_id);
{ ph->ec = start_conversion (ph->pd,
struct MHD_Response *resp; j,
struct GNUNET_TIME_Absolute expiration; &proof_post_conversion_cb,
json_t *attr; ph);
attr = convert_attributes (attributes);
if (NULL == attr)
{
GNUNET_break_op (0);
return_invalid_response (ph,
response_code,
inquiry_id,
"data-relationships-account-data-id",
data);
break;
}
expiration = GNUNET_TIME_relative_to_absolute (ph->pd->validity);
resp = MHD_create_response_from_buffer (0,
"",
MHD_RESPMEM_PERSISTENT);
GNUNET_break (MHD_YES ==
MHD_add_response_header (resp,
MHD_HTTP_HEADER_LOCATION,
ph->pd->post_kyc_redirect_url));
TALER_MHD_add_global_headers (resp);
ph->cb (ph->cb_cls,
TALER_KYCLOGIC_STATUS_SUCCESS,
account_id,
inquiry_id,
expiration,
attr,
MHD_HTTP_SEE_OTHER,
resp);
json_decref (attr);
}
GNUNET_JSON_parse_free (ispec); GNUNET_JSON_parse_free (ispec);
} }
GNUNET_JSON_parse_free (spec); GNUNET_JSON_parse_free (spec);
break; return; /* continued in proof_post_conversion_cb */
} }
case MHD_HTTP_BAD_REQUEST: case MHD_HTTP_BAD_REQUEST:
case MHD_HTTP_NOT_FOUND: case MHD_HTTP_NOT_FOUND:
@ -1580,6 +1521,12 @@ persona_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh)
GNUNET_CURL_job_cancel (wh->job); GNUNET_CURL_job_cancel (wh->job);
wh->job = NULL; wh->job = NULL;
} }
if (NULL != wh->ec)
{
TALER_JSON_external_conversion_stop (wh->ec);
wh->ec = NULL;
}
GNUNET_free (wh->account_id);
GNUNET_free (wh->inquiry_id); GNUNET_free (wh->inquiry_id);
GNUNET_free (wh->url); GNUNET_free (wh->url);
GNUNET_free (wh); GNUNET_free (wh);
@ -1650,6 +1597,32 @@ webhook_reply_error (struct TALER_KYCLOGIC_WebhookHandle *wh,
} }
/**
* Type of a callback that receives a JSON @a result.
*
* @param cls closure with a `struct TALER_KYCLOGIC_WebhookHandle *`
* @param status_type how did the process die
* @param code termination status code from the process
* @param attr some JSON result, NULL if we failed to get an JSON output
*/
static void
webhook_post_conversion_cb (void *cls,
enum GNUNET_OS_ProcessStatusType status_type,
unsigned long code,
const json_t *attr)
{
struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
wh->ec = NULL;
webhook_generic_reply (wh,
TALER_KYCLOGIC_STATUS_SUCCESS,
wh->account_id,
wh->inquiry_id,
attr,
MHD_HTTP_OK);
}
/** /**
* Function called when we're done processing the * Function called when we're done processing the
* HTTP "/api/v1/inquiries/{inquiry_id}" request. * HTTP "/api/v1/inquiries/{inquiry_id}" request.
@ -1723,7 +1696,6 @@ handle_webhook_finished (void *cls,
NULL), NULL),
GNUNET_JSON_spec_end () GNUNET_JSON_spec_end ()
}; };
json_t *attr;
if (GNUNET_OK != if (GNUNET_OK !=
GNUNET_JSON_parse (attributes, GNUNET_JSON_parse (attributes,
@ -1807,19 +1779,15 @@ handle_webhook_finished (void *cls,
MHD_HTTP_BAD_GATEWAY); MHD_HTTP_BAD_GATEWAY);
break; break;
} }
wh->account_id = GNUNET_strdup (account_id);
attr = convert_attributes (attributes); wh->ec = start_conversion (wh->pd,
webhook_generic_reply (wh, j,
TALER_KYCLOGIC_STATUS_SUCCESS, &webhook_post_conversion_cb,
account_id, wh);
inquiry_id,
attr,
MHD_HTTP_OK);
json_decref (attr);
GNUNET_JSON_parse_free (ispec); GNUNET_JSON_parse_free (ispec);
} }
GNUNET_JSON_parse_free (spec); GNUNET_JSON_parse_free (spec);
break; return; /* continued in webhook_post_conversion_cb */
} }
case MHD_HTTP_BAD_REQUEST: case MHD_HTTP_BAD_REQUEST:
case MHD_HTTP_NOT_FOUND: case MHD_HTTP_NOT_FOUND:

View File

@ -0,0 +1,54 @@
#!/bin/bash
# This file is in the public domain.
#
# This code converts (some of) the JSON output from Persona into the GNU Taler
# specific KYC attribute data (again in JSON format). We may need to download
# and inline file data in the process, for authorization pass "-a" with the
# respective bearer token.
#
# Die if anything goes wrong.
set -eu
# Parse command-line options
while getopts ':a:' OPTION; do
case "$OPTION" in
a)
TOKEN="$OPTARG"
;;
?)
echo "Unrecognized command line option"
exit 1
;;
esac
done
# First, extract everything from stdin.
J=$(jq '{"first":.data.attributes."name-first","middle":.data.attributes."name-middle","last":.data.attributes."name-last","cc":.data.attributes.fields."address-country-code".value,"birthdate":.data.attributes.birthdate,"city":.data.attributes."address-city","postcode":.data.attributes."address-postal-code","street-1":.data.attributes."address-street-1","street-2":.data.attributes."address-street-2","address-subdivision":.data.attributes."address-subdivision","identification-number":.data.attributes."identification-number","photo":.included[]|select(.type=="verification/government-id")|.attributes|select(.status=="passed")|."front-photo-url"}')
# Next, combine some fields into larger values.
FULLNAME=$(echo "$J" | jq -r '[.first,.middle,.last]|join(" ")')
STREET=$(echo $J | jq -r '[."street-1",."street-2"]|join(" ")')
CITY=$(echo $J | jq -r '[.postcode,.city,."address-subdivision,.cc"]|join(" ")')
# Download and base32-encode the photo
PHOTO_URL=$(echo "$J" | jq -r '.photo')
PHOTO_FILE=$(mktemp -t tmp.XXXXXXXXXX)
if [ -z "${TOKEN:-}" ]
then
wget -q --output-document=- "$PHOTO_URL" | gnunet-base32 > ${PHOTO_FILE}
else
wget -q --output-document=- --header "Authorization: Bearer $TOKEN" "$PHOTO_URL" | gnunet-base32 > ${PHOTO_FILE}
fi
# Combine into final result.
echo "$J" | jq \
--arg full_name "${FULLNAME}" \
--arg street "${STREET}" \
--arg city "${CITY}" \
--rawfile photo "${PHOTO_FILE}" \
'{$full_name,$street,$city,"birthdate":.birthdate,"residences":.cc,"identification_number":."identification-number",$photo}'
exit 0

View File

@ -990,9 +990,9 @@ proceed_with_handler (struct TEKT_RequestContext *rc,
/* Parse command-line arguments */ /* Parse command-line arguments */
/* make a copy of 'url' because 'strtok_r()' will modify */ /* make a copy of 'url' because 'strtok_r()' will modify */
memcpy (d, GNUNET_memcpy (d,
url, url,
ulen); ulen);
i = 0; i = 0;
args[i++] = strtok_r (d, "/", &sp); args[i++] = strtok_r (d, "/", &sp);
while ( (NULL != args[i - 1]) && while ( (NULL != args[i - 1]) &&

View File

@ -247,7 +247,7 @@ handle_deposit_finished (void *cls,
&dh->exchange_pub), &dh->exchange_pub),
GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("transaction_base_url", GNUNET_JSON_spec_string ("transaction_base_url",
&dr.details.success.transaction_base_url), &dr.details.ok.transaction_base_url),
NULL), NULL),
GNUNET_JSON_spec_timestamp ("exchange_timestamp", GNUNET_JSON_spec_timestamp ("exchange_timestamp",
&dh->exchange_timestamp), &dh->exchange_timestamp),
@ -332,7 +332,7 @@ handle_deposit_finished (void *cls,
GNUNET_break_op (0); GNUNET_break_op (0);
dr.hr.http_status = 0; dr.hr.http_status = 0;
dr.hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE; dr.hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE;
GNUNET_JSON_parse_free (spec); GNUNET_JSON_parse_free (spec);
break; break;
} }
} }
@ -341,10 +341,10 @@ handle_deposit_finished (void *cls,
dh); dh);
GNUNET_JSON_parse_free (spec); GNUNET_JSON_parse_free (spec);
} }
dr.details.success.exchange_sigs = dh->exchange_sigs; dr.details.ok.exchange_sigs = dh->exchange_sigs;
dr.details.success.exchange_pub = &dh->exchange_pub; dr.details.ok.exchange_pub = &dh->exchange_pub;
dr.details.success.deposit_timestamp = dh->exchange_timestamp; dr.details.ok.deposit_timestamp = dh->exchange_timestamp;
dr.details.success.num_signatures = dh->num_cdds; dr.details.ok.num_signatures = dh->num_cdds;
break; break;
case MHD_HTTP_BAD_REQUEST: case MHD_HTTP_BAD_REQUEST:
/* This should never happen, either us or the exchange is buggy /* This should never happen, either us or the exchange is buggy

View File

@ -144,37 +144,34 @@ struct TALER_EXCHANGE_BatchWithdrawHandle
* HTTP /reserves/$RESERVE_PUB/batch-withdraw request. * HTTP /reserves/$RESERVE_PUB/batch-withdraw request.
* *
* @param cls the `struct TALER_EXCHANGE_BatchWithdrawHandle` * @param cls the `struct TALER_EXCHANGE_BatchWithdrawHandle`
* @param hr HTTP response data * @param bw2r response data
* @param blind_sigs array of blind signatures over the coins, NULL on error
* @param blind_sigs_length length of the @a blind_sigs array
*/ */
static void static void
handle_reserve_batch_withdraw_finished ( handle_reserve_batch_withdraw_finished (
void *cls, void *cls,
const struct TALER_EXCHANGE_HttpResponse *hr, const struct TALER_EXCHANGE_BatchWithdraw2Response *bw2r)
const struct TALER_BlindedDenominationSignature *blind_sigs,
unsigned int blind_sigs_length)
{ {
struct TALER_EXCHANGE_BatchWithdrawHandle *wh = cls; struct TALER_EXCHANGE_BatchWithdrawHandle *wh = cls;
struct TALER_EXCHANGE_BatchWithdrawResponse wr = { struct TALER_EXCHANGE_BatchWithdrawResponse wr = {
.hr = *hr .hr = bw2r->hr
}; };
struct TALER_EXCHANGE_PrivateCoinDetails coins[wh->num_coins]; struct TALER_EXCHANGE_PrivateCoinDetails coins[GNUNET_NZL (wh->num_coins)];
wh->wh2 = NULL; wh->wh2 = NULL;
memset (coins, memset (coins,
0, 0,
sizeof (coins)); sizeof (coins));
if (blind_sigs_length != wh->num_coins) switch (bw2r->hr.http_status)
{
GNUNET_break_op (0);
wr.hr.http_status = 0;
wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
}
switch (hr->http_status)
{ {
case MHD_HTTP_OK: case MHD_HTTP_OK:
{ {
if (bw2r->details.ok.blind_sigs_length != wh->num_coins)
{
GNUNET_break_op (0);
wr.hr.http_status = 0;
wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
break;
}
for (unsigned int i = 0; i<wh->num_coins; i++) for (unsigned int i = 0; i<wh->num_coins; i++)
{ {
struct CoinData *cd = &wh->coins[i]; struct CoinData *cd = &wh->coins[i];
@ -183,7 +180,7 @@ handle_reserve_batch_withdraw_finished (
if (GNUNET_OK != if (GNUNET_OK !=
TALER_planchet_to_coin (&cd->pk.key, TALER_planchet_to_coin (&cd->pk.key,
&blind_sigs[i], &bw2r->details.ok.blind_sigs[i],
&cd->bks, &cd->bks,
&cd->priv, &cd->priv,
cd->ach, cd->ach,
@ -200,8 +197,8 @@ handle_reserve_batch_withdraw_finished (
coin->sig = fc.sig; coin->sig = fc.sig;
coin->exchange_vals = cd->alg_values; coin->exchange_vals = cd->alg_values;
} }
wr.details.success.coins = coins; wr.details.ok.coins = coins;
wr.details.success.num_coins = wh->num_coins; wr.details.ok.num_coins = wh->num_coins;
break; break;
} }
case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
@ -217,7 +214,7 @@ handle_reserve_batch_withdraw_finished (
}; };
if (GNUNET_OK != if (GNUNET_OK !=
GNUNET_JSON_parse (hr->reply, GNUNET_JSON_parse (bw2r->hr.reply,
spec, spec,
NULL, NULL)) NULL, NULL))
{ {
@ -289,7 +286,7 @@ withdraw_cs_stage_two_callback (
switch (csrr->hr.http_status) switch (csrr->hr.http_status)
{ {
case MHD_HTTP_OK: case MHD_HTTP_OK:
cd->alg_values = csrr->details.success.alg_values; cd->alg_values = csrr->details.ok.alg_values;
TALER_planchet_setup_coin_priv (&cd->ps, TALER_planchet_setup_coin_priv (&cd->ps,
&cd->alg_values, &cd->alg_values,
&cd->priv); &cd->priv);

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER This file is part of TALER
Copyright (C) 2014-2022 Taler Systems SA Copyright (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software terms of the GNU General Public License as published by the Free Software
@ -108,9 +108,9 @@ reserve_batch_withdraw_ok (struct TALER_EXCHANGE_BatchWithdraw2Handle *wh,
"ev_sigs"); "ev_sigs");
const json_t *j; const json_t *j;
unsigned int index; unsigned int index;
struct TALER_EXCHANGE_HttpResponse hr = { struct TALER_EXCHANGE_BatchWithdraw2Response bwr = {
.reply = json, .hr.reply = json,
.http_status = MHD_HTTP_OK .hr.http_status = MHD_HTTP_OK
}; };
if ( (NULL == ja) || if ( (NULL == ja) ||
@ -141,10 +141,10 @@ reserve_batch_withdraw_ok (struct TALER_EXCHANGE_BatchWithdraw2Handle *wh,
} }
/* signature is valid, return it to the application */ /* signature is valid, return it to the application */
bwr.details.ok.blind_sigs = blind_sigs;
bwr.details.ok.blind_sigs_length = wh->num_coins;
wh->cb (wh->cb_cls, wh->cb (wh->cb_cls,
&hr, &bwr);
blind_sigs,
wh->num_coins);
/* make sure callback isn't called again after return */ /* make sure callback isn't called again after return */
wh->cb = NULL; wh->cb = NULL;
for (unsigned int i = 0; i<wh->num_coins; i++) for (unsigned int i = 0; i<wh->num_coins; i++)
@ -264,16 +264,16 @@ handle_reserve_batch_withdraw_finished (void *cls,
{ {
struct TALER_EXCHANGE_BatchWithdraw2Handle *wh = cls; struct TALER_EXCHANGE_BatchWithdraw2Handle *wh = cls;
const json_t *j = response; const json_t *j = response;
struct TALER_EXCHANGE_HttpResponse hr = { struct TALER_EXCHANGE_BatchWithdraw2Response bwr = {
.reply = j, .hr.reply = j,
.http_status = (unsigned int) response_code .hr.http_status = (unsigned int) response_code
}; };
wh->job = NULL; wh->job = NULL;
switch (response_code) switch (response_code)
{ {
case 0: case 0:
hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; bwr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break; break;
case MHD_HTTP_OK: case MHD_HTTP_OK:
if (GNUNET_OK != if (GNUNET_OK !=
@ -281,8 +281,8 @@ handle_reserve_batch_withdraw_finished (void *cls,
j)) j))
{ {
GNUNET_break_op (0); GNUNET_break_op (0);
hr.http_status = 0; bwr.hr.http_status = 0;
hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; bwr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
break; break;
} }
GNUNET_assert (NULL == wh->cb); GNUNET_assert (NULL == wh->cb);
@ -304,8 +304,8 @@ handle_reserve_batch_withdraw_finished (void *cls,
NULL, NULL)) NULL, NULL))
{ {
GNUNET_break_op (0); GNUNET_break_op (0);
hr.http_status = 0; bwr.hr.http_status = 0;
hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; bwr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
break; break;
} }
} }
@ -313,24 +313,24 @@ handle_reserve_batch_withdraw_finished (void *cls,
case MHD_HTTP_BAD_REQUEST: case MHD_HTTP_BAD_REQUEST:
/* This should never happen, either us or the exchange is buggy /* This should never happen, either us or the exchange is buggy
(or API version conflict); just pass JSON reply to the application */ (or API version conflict); just pass JSON reply to the application */
hr.ec = TALER_JSON_get_error_code (j); bwr.hr.ec = TALER_JSON_get_error_code (j);
hr.hint = TALER_JSON_get_error_hint (j); bwr.hr.hint = TALER_JSON_get_error_hint (j);
break; break;
case MHD_HTTP_FORBIDDEN: case MHD_HTTP_FORBIDDEN:
GNUNET_break_op (0); GNUNET_break_op (0);
/* Nothing really to verify, exchange says one of the signatures is /* Nothing really to verify, exchange says one of the signatures is
invalid; as we checked them, this should never happen, we invalid; as we checked them, this should never happen, we
should pass the JSON reply to the application */ should pass the JSON reply to the application */
hr.ec = TALER_JSON_get_error_code (j); bwr.hr.ec = TALER_JSON_get_error_code (j);
hr.hint = TALER_JSON_get_error_hint (j); bwr.hr.hint = TALER_JSON_get_error_hint (j);
break; break;
case MHD_HTTP_NOT_FOUND: case MHD_HTTP_NOT_FOUND:
/* Nothing really to verify, the exchange basically just says /* Nothing really to verify, the exchange basically just says
that it doesn't know this reserve. Can happen if we that it doesn't know this reserve. Can happen if we
query before the wire transfer went through. query before the wire transfer went through.
We should simply pass the JSON reply to the application. */ We should simply pass the JSON reply to the application. */
hr.ec = TALER_JSON_get_error_code (j); bwr.hr.ec = TALER_JSON_get_error_code (j);
hr.hint = TALER_JSON_get_error_hint (j); bwr.hr.hint = TALER_JSON_get_error_hint (j);
break; break;
case MHD_HTTP_CONFLICT: case MHD_HTTP_CONFLICT:
/* The exchange says that the reserve has insufficient funds; /* The exchange says that the reserve has insufficient funds;
@ -340,13 +340,13 @@ handle_reserve_batch_withdraw_finished (void *cls,
j)) j))
{ {
GNUNET_break_op (0); GNUNET_break_op (0);
hr.http_status = 0; bwr.hr.http_status = 0;
hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; bwr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
} }
else else
{ {
hr.ec = TALER_JSON_get_error_code (j); bwr.hr.ec = TALER_JSON_get_error_code (j);
hr.hint = TALER_JSON_get_error_hint (j); bwr.hr.hint = TALER_JSON_get_error_hint (j);
} }
break; break;
case MHD_HTTP_GONE: case MHD_HTTP_GONE:
@ -354,32 +354,30 @@ handle_reserve_batch_withdraw_finished (void *cls,
/* Note: one might want to check /keys for revocation /* Note: one might want to check /keys for revocation
signature here, alas tricky in case our /keys signature here, alas tricky in case our /keys
is outdated => left to clients */ is outdated => left to clients */
hr.ec = TALER_JSON_get_error_code (j); bwr.hr.ec = TALER_JSON_get_error_code (j);
hr.hint = TALER_JSON_get_error_hint (j); bwr.hr.hint = TALER_JSON_get_error_hint (j);
break; break;
case MHD_HTTP_INTERNAL_SERVER_ERROR: case MHD_HTTP_INTERNAL_SERVER_ERROR:
/* Server had an internal issue; we should retry, but this API /* Server had an internal issue; we should retry, but this API
leaves this to the application */ leaves this to the application */
hr.ec = TALER_JSON_get_error_code (j); bwr.hr.ec = TALER_JSON_get_error_code (j);
hr.hint = TALER_JSON_get_error_hint (j); bwr.hr.hint = TALER_JSON_get_error_hint (j);
break; break;
default: default:
/* unexpected response code */ /* unexpected response code */
GNUNET_break_op (0); GNUNET_break_op (0);
hr.ec = TALER_JSON_get_error_code (j); bwr.hr.ec = TALER_JSON_get_error_code (j);
hr.hint = TALER_JSON_get_error_hint (j); bwr.hr.hint = TALER_JSON_get_error_hint (j);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d for exchange batch withdraw\n", "Unexpected response code %u/%d for exchange batch withdraw\n",
(unsigned int) response_code, (unsigned int) response_code,
(int) hr.ec); (int) bwr.hr.ec);
break; break;
} }
if (NULL != wh->cb) if (NULL != wh->cb)
{ {
wh->cb (wh->cb_cls, wh->cb (wh->cb_cls,
&hr, &bwr);
NULL,
0);
wh->cb = NULL; wh->cb = NULL;
} }
TALER_EXCHANGE_batch_withdraw2_cancel (wh); TALER_EXCHANGE_batch_withdraw2_cancel (wh);

View File

@ -2194,4 +2194,196 @@ TALER_EXCHANGE_verify_deposit_signature_ (
} }
/**
* Parse account restriction in @a jrest into @a rest.
*
* @param jresta array of account restrictions in JSON
* @param[out] resta_len set to length of @a resta
* @param[out] resta account restriction array to set
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
parse_restrictions (const json_t *jresta,
unsigned int *resta_len,
struct TALER_EXCHANGE_AccountRestriction **resta)
{
if (! json_is_array (jresta))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
*resta_len = json_array_size (jresta);
if (0 == *resta_len)
{
/* no restrictions, perfectly OK */
*resta = NULL;
return GNUNET_OK;
}
*resta = GNUNET_new_array (*resta_len,
struct TALER_EXCHANGE_AccountRestriction);
for (unsigned int i = 0; i<*resta_len; i++)
{
const json_t *jr = json_array_get (jresta,
i);
struct TALER_EXCHANGE_AccountRestriction *ar = &(*resta)[i];
const char *type = json_string_value (json_object_get (jr,
"type"));
if (NULL == type)
{
GNUNET_break (0);
goto fail;
}
if (0 == strcmp (type,
"deny"))
{
ar->type = TALER_EXCHANGE_AR_DENY;
continue;
}
if (0 == strcmp (type,
"regex"))
{
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string (
"payto_regex",
&ar->details.regex.posix_egrep),
GNUNET_JSON_spec_string (
"human_hint",
&ar->details.regex.human_hint),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_object_const (
"human_hint_i18n",
&ar->details.regex.human_hint_i18n),
NULL),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (jr,
spec,
NULL, NULL))
{
/* bogus reply */
GNUNET_break_op (0);
goto fail;
}
ar->type = TALER_EXCHANGE_AR_REGEX;
continue;
}
/* unsupported type */
GNUNET_break (0);
return GNUNET_SYSERR;
}
return GNUNET_OK;
fail:
GNUNET_free (*resta);
*resta_len = 0;
return GNUNET_SYSERR;
}
enum GNUNET_GenericReturnValue
TALER_EXCHANGE_parse_accounts (const struct TALER_MasterPublicKeyP *master_pub,
const json_t *accounts,
struct TALER_EXCHANGE_WireAccount was[],
unsigned int was_length)
{
memset (was,
0,
sizeof (struct TALER_EXCHANGE_WireAccount) * was_length);
GNUNET_assert (was_length ==
json_array_size (accounts));
for (unsigned int i = 0;
i<was_length;
i++)
{
struct TALER_EXCHANGE_WireAccount *wa = &was[i];
json_t *credit_restrictions;
json_t *debit_restrictions;
struct GNUNET_JSON_Specification spec_account[] = {
GNUNET_JSON_spec_string ("payto_uri",
&wa->payto_uri),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("conversion_url",
&wa->conversion_url),
NULL),
GNUNET_JSON_spec_json ("credit_restrictions",
&credit_restrictions),
GNUNET_JSON_spec_json ("debit_restrictions",
&debit_restrictions),
GNUNET_JSON_spec_fixed_auto ("master_sig",
&wa->master_sig),
GNUNET_JSON_spec_end ()
};
json_t *account;
account = json_array_get (accounts,
i);
if (GNUNET_OK !=
GNUNET_JSON_parse (account,
spec_account,
NULL, NULL))
{
/* bogus reply */
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
{
char *err;
err = TALER_payto_validate (wa->payto_uri);
if (NULL != err)
{
GNUNET_break_op (0);
GNUNET_free (err);
return GNUNET_SYSERR;
}
}
if ( (NULL != master_pub) &&
(GNUNET_OK !=
TALER_exchange_wire_signature_check (wa->payto_uri,
wa->conversion_url,
debit_restrictions,
credit_restrictions,
master_pub,
&wa->master_sig)) )
{
/* bogus reply */
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
if ( (GNUNET_OK !=
parse_restrictions (credit_restrictions,
&wa->credit_restrictions_length,
&wa->credit_restrictions)) ||
(GNUNET_OK !=
parse_restrictions (debit_restrictions,
&wa->debit_restrictions_length,
&wa->debit_restrictions)) )
{
/* bogus reply */
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
GNUNET_JSON_parse_free (spec_account);
} /* end 'for all accounts */
return GNUNET_OK;
}
void
TALER_EXCHANGE_free_accounts (struct TALER_EXCHANGE_WireAccount *was,
unsigned int was_len)
{
for (unsigned int i = 0; i<was_len; i++)
{
struct TALER_EXCHANGE_WireAccount *wa = &was[i];
GNUNET_free (wa->credit_restrictions);
GNUNET_free (wa->debit_restrictions);
}
}
/* end of exchange_api_common.c */ /* end of exchange_api_common.c */

Some files were not shown because too many files have changed in this diff Show More