From 2443ee672d9e7e9422bcfcadb5aedf87f0085839 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 26 Jun 2022 15:05:37 +0200 Subject: -more typos --- doc/cs/ads/glossary.tex | 28 ++++++++++++++-------------- doc/cs/content/1_introduction.tex | 4 ++-- doc/cs/content/3_preliminaries.tex | 22 ++++++++++------------ doc/cs/content/4_2_specification.tex | 16 ++++++++-------- doc/cs/content/4_3_implementation.tex | 14 +++++++------- doc/cs/content/5_discussion.tex | 12 ++++++------ doc/cs/content/6_conclusion.tex | 10 +++++----- 7 files changed, 52 insertions(+), 54 deletions(-) (limited to 'doc/cs') diff --git a/doc/cs/ads/glossary.tex b/doc/cs/ads/glossary.tex index 67ff003bc..7132f89a5 100644 --- a/doc/cs/ads/glossary.tex +++ b/doc/cs/ads/glossary.tex @@ -1,12 +1,12 @@ %!TEX root = ../thesis.tex % -% vorher in Konsole folgendes aufrufen: +% vorher in Konsole folgendes aufrufen: % makeglossaries makeglossaries dokumentation.acn && makeglossaries dokumentation.glo % % -% Glossareintraege --> referenz, name, beschreibung +% Glossareintraege --> reference, name, beschreibung % Aufruf mit \gls{...} % % \newglossaryentry{non-repudiation}{name={non-repudiation},plural={non-repudiation},description={After a message is signed, one can not dispute that a message was signed}} @@ -18,36 +18,36 @@ } \newglossaryentry{25519}{ - name = {Curve25519}, - description = {A popular elliptic curve used in many cryptographic systems based on elliptic curve cryptography. See section \ref{par:curve25519}} + name = {Curve25519}, + description = {A popular elliptic curve used in many cryptographic systems based on elliptic curve cryptography. See section \ref{par:curve25519}} } \newglossaryentry{fdh}{ name = {FDH}, - description = {A Full-Domain Hash is a hash function with an image size equal to the original gorup. See section \ref{sec:rsa-fdh}}. -} + description = {A Full-Domain Hash is a hash function with an image size equal to the original gorup. See section \ref{sec:rsa-fdh}}. +} \newglossaryentry{idempotence}{ name = {idempotence}, - description = {Idempotence in the context of computer science is a property to ensure that the state of system will not change, no matter how many times the same request was made. See section \ref{abort-idempotency}} -} + description = {Idempotence in the context of computer science is a property to ensure that the state of system will not change, no matter how many times the same request was made. See section \ref{abort-idempotency}} +} \newglossaryentry{abort-idempotency}{ name = {abort-idempotency}, - description = {Abort-idempotency is a special case of \gls{idempotence}. On every step in a protocol it needs to be ensured that even on an abort, the same request always receives the same response. See section \ref{abort-idempotency}} -} + description = {Abort-idempotency is a special case of \gls{idempotence}. On every step in a protocol it needs to be ensured that even on an abort, the same request always receives the same response. See section \ref{abort-idempotency}} +} \newglossaryentry{RSABS}{ name = {RSA Blind Signatures}, - description = {Chaums Blind Signature Scheme based on RSA. See section \ref{sec:blind-rsa-sign}} + description = {Chaums Blind Signature Scheme based on RSA. See section \ref{sec:blind-rsa-sign}} } \newglossaryentry{CSBS}{ name = {Clause Blind Schnorr Signatures}, description = {A secure variant of Blind Schnorr Signature Schemes introduced in section \ref{sec:clause-blind-schnorr-sig}} -} +} % \newglossaryentry{25519}{ % name = {}, - % description = {} -% } \ No newline at end of file + % description = {} +% } diff --git a/doc/cs/content/1_introduction.tex b/doc/cs/content/1_introduction.tex index 1ed9e0589..e0fdaa018 100644 --- a/doc/cs/content/1_introduction.tex +++ b/doc/cs/content/1_introduction.tex @@ -51,7 +51,7 @@ In scope are all necessary changes on the protocol(s) and components for the fol \item design and implement a protocol where the user proves to the exchange the knowledge of the coin that is to be signed (optional) \end{itemize} -Out of scope is production readyness of the implementation. +Out of scope is production readiness of the implementation. This is because changes in the protocos and code need to be thoroughly vetted to ensure that no weaknesses or security vulnerabilities were introduced. Such an audit is out of scope for the thesis and is recommended to be performed in the future. The iOS wallet will not be considered in this work. @@ -69,4 +69,4 @@ Scope changes during the project: \item \textbf{Adjusted: } Focus is on the implementation of the exchange protocols (Withdraw, Spend, Refresh and cryptographic utilities) \item \textbf{Adjusted: } Implementation of the refresh protocol and wallet-core are nice-to-have goals \item \textbf{Removed: } The Merchant and the android wallet implementations are out of scope -\end{itemize} \ No newline at end of file +\end{itemize} diff --git a/doc/cs/content/3_preliminaries.tex b/doc/cs/content/3_preliminaries.tex index e63e65d33..7d7b7ca2f 100644 --- a/doc/cs/content/3_preliminaries.tex +++ b/doc/cs/content/3_preliminaries.tex @@ -141,7 +141,6 @@ This can be used to detect compromised signing keys or a malicious exchange. \subsection{Properties} \label{sec:taler-properties} -%Alle Taler Eigenschaften die wir angreifen wollen auflisten und bezug nehmen wie diese erreicht werden This section describes Taler's properties. \subsubsection{Free Software} @@ -299,7 +298,7 @@ If verification is successful, only Alice knows her private key and Bob uses Ali A digital signature scheme has a message space M, a signature space S and three algorithms: \begin{itemize} \item Key generation: $(pk,sk) \gets keyGen()$ - \item Signatue generation: $s \gets $sign$_sk(m)$ + \item Signature generation: $s \gets $sign$_sk(m)$ \item Verification: $ v \gets $verify$_pk(m,s)$ where $v \in {0,1}$ \end{itemize} If the result of the verification algorithm equals 1, a signature for m is called valid. @@ -783,7 +782,7 @@ A good introduction to cut and choose protocols gives the Paper from Claude Cré The expression cut-and-choose was later introduced by David Chaum in analogy to a popular cake sharing problem: Given a complete cake to be shared among two parties distrusting of each other (for reasons of serious appetite). A fair way for them to share the cake is to have one of them cut the cake in two equals hares, and let the other one choose his favourite share. - This solution guarantes that it is in the formers best interest to cut the shares as evenly as possible." + This solution guarantees that it is in the formers best interest to cut the shares as evenly as possible." } \end{center} @@ -870,10 +869,10 @@ Figure \ref{fig:withdraw-loophole-exploit} explains how such a payment would wor Note that we omitted the parts leading up to the coin creation (contract, agreement of price, number of coins and their denominations). This is how it works on a high level: \begin{enumerate} - \item The malicous merchant generates and blinds coins, which are then transmitted to the customer + \item The malicious merchant generates and blinds coins, which are then transmitted to the customer \item The customer authorizes the withdraw from his reserve by signing the blinded coins with the private key of his reserve, thus generating withdraw confirmations. - \item The withdraw confirmations are transmitted to the exchange, which generates the signatures and returns them to the malicous merchant. - \item The malicous merchant unblinds the signatures. + \item The withdraw confirmations are transmitted to the exchange, which generates the signatures and returns them to the malicious merchant. + \item The malicious merchant unblinds the signatures. He is now in possession of the coin, thus the payment is completed. \end{enumerate} @@ -882,7 +881,7 @@ This is how it works on a high level: \resizebox{1.0\textwidth}{!}{$\displaystyle \begin{array}{ l c l} % preliminaries - \textbf{Customer} & & \textbf{malicous Merchant} + \textbf{Customer} & & \textbf{malicious Merchant} \\ \text{knows:} & & \text{knows:} \\ \text{reserve keys } w_s, W_p \\ \text{denomination public key } D_p = \langle e, N \rangle & & \text{denomination public key } D_p = \langle e, N \rangle @@ -903,7 +902,7 @@ This is how it works on a high level: \\ \hline \\ - \textbf{malicous Merchant} & & \textbf{Exchange} + \textbf{malicious Merchant} & & \textbf{Exchange} \\\text{knows:} & & \text{knows:} \\& & \text{reserve public key } W_p \\ \text{denomination public key } D_p = \langle e, N \rangle & & \text{denomination keys } d_s, D_p @@ -949,7 +948,6 @@ Chapter 4.1.4 describes more general aspects as well as the contract header and \subsubsection{Spend Protocol} The payment process begins when a customer submits a shopping cart (one or more items to buy) and commits his intent to buy them. The merchant has a key pair skM, pkM of which the customer knows the public key. -% besseres Wort als commit? Note that certain details contained in contract header or deposit permission like merchant \ac{KYC} information, deposit and refund deadlines and fees are left out. The deposit state machine can be seen in figure \ref{fig:deposit:states}. \begin{figure}[htp] @@ -1033,7 +1031,7 @@ In cases where there are multiple deposit permissions (meaning that multiple coi \item Is the signature of the coin valid? \item Is $ f $ (the value to be spent) smaller or equal the residual value of the coin (check for overspending attempt)? \end{itemize} - If all checks are successful, the exchange saves the deposit record containing the deposit permission and its signature in a database, substracts the spent value from the residual value of the coin and schedules the money transfer to the merchant's account $ A_m $ (grouping payments is done to reduce payment fees). + If all checks are successful, the exchange saves the deposit record containing the deposit permission and its signature in a database, subtracts the spent value from the residual value of the coin and schedules the money transfer to the merchant's account $ A_m $ (grouping payments is done to reduce payment fees). \\The exchange calculates a deposit confirmation signature $ \sigma_{DC} $ for the deposit permission with the exchange signing private key and returns them to the merchant. \\This signature is also used to prove that a merchant was the first to receive payment from a certain coin. Without this, an evil exchange could later deny confirming a payment and claim double spending. @@ -1180,7 +1178,7 @@ The customer, which holds the old partially spend coin and knows \\$C_{old} = \t On the exchange's side various checks are done to validate the request. Detailed steps of the commit phase are shown in figure \ref{fig:refresh-part1}. - + \begin{figure} \begin{equation*} \resizebox{1.0\textwidth}{!}{$\displaystyle @@ -1464,4 +1462,4 @@ When the list of trusted auditor certs of a customer/merchant somehow can be man One attack scenario would be to attack customers/merchants with a supply-chain attack on the wallets or merchant backends' implementation. With software supply-chain attacks on the rise in 2020/21 (although the concept is not new) such an attack could have a big impact. \\ Since auditor certs are coupled with the wallet (or merchant) implementation, a bank, country, central bank or auditor will most likely publish a wallet and a merchant implementation for the corresponding Taler ecosystem. -%This would make it possible for the publisher to make changes on the Taler protocol for this specific implementation. \ No newline at end of file +%This would make it possible for the publisher to make changes on the Taler protocol for this specific implementation. diff --git a/doc/cs/content/4_2_specification.tex b/doc/cs/content/4_2_specification.tex index efe6a3c3d..fe745fc69 100644 --- a/doc/cs/content/4_2_specification.tex +++ b/doc/cs/content/4_2_specification.tex @@ -256,7 +256,7 @@ Further, the API ensures that a caller must generate two secret $r$ as in the Cl * To ensure unpredictability a new nonce should be used when a new r needs to be derived. * Uses HKDF internally. * Comment: Can be done in one HKDF shot and split output. - * + * * @param nonce is a random nonce * @param lts is a long-term-secret in form of a private key * @param[out] r array containing derived secrets r0 and r1 @@ -265,8 +265,8 @@ Further, the API ensures that a caller must generate two secret $r$ as in the Cl GNUNET_CRYPTO_cs_r_derive (const struct GNUNET_CRYPTO_CsNonce *nonce, const struct GNUNET_CRYPTO_CsPrivateKey *lts, struct GNUNET_CRYPTO_CsRSecret r[2]); - - + + /** * Extract the public R of the given secret r. * @@ -289,7 +289,7 @@ The blinding secrets are generated by a client who provides a secret as seed to * To provide abort-idempotency, blinding factors need to be derived but still need to be UNPREDICTABLE * To ensure unpredictability a new nonce has to be used. * Uses HKDF internally - * + * * @param secret is secret to derive blinding factors * @param secret_len secret length * @param[out] bs array containing the two derivedGNUNET_CRYPTO_CsBlindingSecret @@ -306,7 +306,7 @@ Further the Clause Blind Schnorr API provides an API to calculate the two blinde /** * Calculate two blinded c's * Comment: One would be insecure due to Wagner's algorithm solving ROS - * + * * @param bs array of the two blinding factor structs each containing alpha and beta * @param r_pub array of the two signer's nonce R * @param pub the public key of the signer @@ -336,7 +336,7 @@ See listing \ref{lst:crypto-sign-api}. * To ensure unpredictability a new nonce has to be used for every signature * HKDF is used internally for derivation * r0 and r1 can be derived prior by using GNUNET_CRYPTO_cs_r_derive - * + * * @param priv private key to use for the signing and as LTS in HKDF * @param r array of the two secret nonce from the signer * @param c array of the two blinded c to sign c_b @@ -370,7 +370,7 @@ GNUNET_CRYPTO_cs_unblind ( struct GNUNET_CRYPTO_CsS *signature_scalar); \end{lstlisting} -The verify API takes the message and its signature with the public key and returns GNUNET\_OK for a valid signature and GNUNET\_SYSERR otherwhise. +The verify API takes the message and its signature with the public key and returns GNUNET\_OK for a valid signature and GNUNET\_SYSERR otherwise. See listing \ref{lst:crypto-verify-api}. \begin{lstlisting}[style=bfh-c,language=C,, caption={GNUnet verify API}, label={lst:crypto-verify-api}] @@ -411,7 +411,7 @@ In crypto.c many utility functions are provided to create planchets (for planche One difference between \gls{RSABS} and \gls{CSBS} is, that the coin private key and RSA blinding secret can be created at the same point in time, since the RSA blinding secret is created randomly. However, for Clause Blind Schnorr secrets an additional step is needed, the public $R_0$ and $R_1$ are required to calculate the blinding seed to derive the secrets. -A planchet in the Clause Blind Schnorr Signature Scheme can be created as followed (implementation details ommited). +A planchet in the Clause Blind Schnorr Signature Scheme can be created as followed (implementation details omitted). \begin{enumerate} \item Create planchet with new \ac{EdDSA} private key diff --git a/doc/cs/content/4_3_implementation.tex b/doc/cs/content/4_3_implementation.tex index 07423e4e1..879e69e8f 100644 --- a/doc/cs/content/4_3_implementation.tex +++ b/doc/cs/content/4_3_implementation.tex @@ -94,8 +94,8 @@ The corresponding crypto helper, that talks with the security module, and its te \item \texttt{src/util/test\_helper\_cs.c}: Tests and benchmarks for the \gls{CSBS} crypto helper \end{itemize} % Crypto API offene Punkte: -%Input-Validierung von Punkten und Skalar -% Clamping beschreiben: https://neilmadden.blog/2020/05/28/whats-the-curve25519-clamping-all-about/ +%Input-validation of points and scalars: +% describe clamping: https://neilmadden.blog/2020/05/28/whats-the-curve25519-clamping-all-about/ % Testing: inverse operations, blinded signature test @@ -219,7 +219,7 @@ Tests for deposit are implemented here: \begin{itemize} \item \url{/src/testing/test_exchange_api.c}: Add tests (see "struct TALER\_TESTING\_Command\ spend\_cs[]") that spend \gls{CSBS} coins withdrawn in tests added for withdrawal \item \url{/src/json/json_pack.c}: Implement \gls{CSBS} case in function TALER\_JSON\_pack\_denom\_sig -\end{itemize} +\end{itemize} \section{Fixing a Minor Security Issue in Taler's RSA Blind Signature Protocols} \label{sec:taler-vuln} @@ -230,7 +230,7 @@ The issue was only in the implementation of the current RSA Blind Signature prot \label{sec:taler-vuln-desc} The redesigned \gls{CSBS} protocols already include the denomination key in the nonce check, which fixes this issue (see \ref{sec:withdraw-protocol-schnorr}). -In the case of \gls{RSABS}, the current protocol includes an \gls{idempotence} check by persisting the hash value of the blinded coin $m'$. +In the case of \gls{RSABS}, the current protocol includes an \gls{idempotence} check by persisting the hash value of the blinded coin $m'$. On a withdrawal/refresh the \gls{idempotence} check compares if the hash value of $m'$ was seen in the past and returns the 'old' signature on a match. This could lead to the following scenario: @@ -277,7 +277,7 @@ After discussing this issue with Christian Grothoff, the conclusion was to inclu return GNUNET_OK; case TALER_DENOMINATION_CS: ... - + \end{lstlisting} The issue is fixed by adding a hash of the current denomination key into the calculation of the hash used in the \gls{idempotence} check. @@ -295,7 +295,7 @@ The applied fix can be seen in listing \ref{lst:fixed-idempotence}. { struct GNUNET_HashContext *hash_context; hash_context = GNUNET_CRYPTO_hash_context_start (); - + GNUNET_CRYPTO_hash_context_read (hash_context, &denom_hash->hash, sizeof(denom_hash->hash)); @@ -312,7 +312,7 @@ The applied fix can be seen in listing \ref{lst:fixed-idempotence}. { struct GNUNET_HashContext *hash_context; hash_context = GNUNET_CRYPTO_hash_context_start (); - + GNUNET_CRYPTO_hash_context_read (hash_context, &denom_hash->hash, sizeof(denom_hash->hash)); diff --git a/doc/cs/content/5_discussion.tex b/doc/cs/content/5_discussion.tex index c68b4a79c..8381273c1 100644 --- a/doc/cs/content/5_discussion.tex +++ b/doc/cs/content/5_discussion.tex @@ -57,7 +57,7 @@ This section compares how the two schemes perform regarding CPU usage, latency, Clause Schnorr has fixed key sizes with 256 bits (32 bytes), which we compare against different RSA key sizes (1024, 2048, 3072 and 4096 bits). In terms of security, \gls{CSBS} 256 bit keys could be compared to 3072 bit RSA keys (see \url{https://www.keylength.com/} for more information). -\subsection{CPU Usage} +\subsection{CPU Usage} Various benchmarks were made on different CPU architectures. This section discusses the main results, detailed information about the performance comparison can be found in appendix \ref{chap:app-perf}. We thank the Taler team for providing measurements from additional systems and architectures. @@ -75,7 +75,7 @@ Signing and blinding operations are much faster in \gls{CSBS}, also \gls{CSBS} s \begin{bfhBox}[BFH-MediumBlue]{Setup} CPU: 8-core AMD Ryzen 7 PRO 5850U \\ OS: Ubuntu 21.10 Linux 5.13.0-25-generic \#26-Ubuntu SMP Fri Jan 7 15:48:31 UTC 2022 x86\_64 x86\_64 x86\_64 GNU/Linux \\ - libsodium version: 1.0.18-1build1 \\ + libsodium version: 1.0.18-1build1 \\ libgcrypt version: 1.8.7-5ubuntu2 \\\\ Benchmarks with other hardware setups can be found in appendix \ref{chap:app-perf}. \end{bfhBox} @@ -112,7 +112,7 @@ RSA 1024 is in some situations faster than the \gls{CSBS} implementation. Note that 1024 bit keys are not recommended for many use cases, but the highest currently known RSA factorization done is 829 bits \cite{enwiki:1055393696}. The following section \ref{sec:disc-risk} explains the risk running RSA 1024 or \gls{CSBS} denominations further.\\ The blind and unblind operations are running in a wallet implementation, therefore the comparison with RSA 1024 is very interesting for devices with less CPU power. -Comparison of such hardware can be found in appendix \ref{chap:app-perf}, these comparison results come to the same conlcusion.\\ +Comparison of such hardware can be found in appendix \ref{chap:app-perf}, these comparison results come to the same conclusion.\\ Although RSA 1024 bit is much faster in the blinding operation, \gls{CSBS} still perform better when calculating the blinding and unblinding operations together. \gls{CSBS} unblinding computes only an addition of two scalars $s + \alpha \mod p$, while RSA computes $s * r^{-1}$. To conclude, \gls{CSBS} are faster than RSA 1024 bit and provide a better level of security. @@ -205,7 +205,7 @@ The disk space comparison for a wallet can be found in \ref{tab:comp-wallet-spac These are theoretical calculations, implementations may choose to persist additional values. \end{bfhWarnBox} The reasons that \gls{CSBS} use less bandwidth is mostly because the signature/key sizes are much smaller. -The bandwith improvements for the \texttt{/keys} API is the same as specified in the table with disk space comparison \ref{tab:comp-sign-space}. +The bandwidth improvements for the \texttt{/keys} API is the same as specified in the table with disk space comparison \ref{tab:comp-sign-space}. For \gls{CSBS} many calculations are performed twice, therefore also two values are submitted. Table \ref{tab:comp-band-withd} compares the bandwidth used in a withdrawal. The 32 byte values $2 * n_w, 2 * D_p, R_0, R_1, s,W_p, c_0, c_1, \sigma_W$ as well as an integer $b$ are transmitted for \gls{CSBS}.\\ @@ -222,14 +222,14 @@ Depending on the hash size another 32 byte (or 64 byte) value is transmitted. \setupBfhTabular \begin{tabular}{lccr} \rowcolor{BFH-tablehead} - \textbf{Signature Scheme} & \textbf{Bandwith used} & \textbf{Factor} & \textbf{1M coins}\\\hline + \textbf{Signature Scheme} & \textbf{Bandwidth used} & \textbf{Factor} & \textbf{1M coins}\\\hline CS 256 bits & 356 bytes & 1x & 324 MB\\\hline RSA 1024 bit & 448 bytes & 1.3x & 448 MB \\\hline RSA 2048 bit & 832 bytes & 2.5x & 832 MB\\\hline RSA 3072 bit & 1216 bytes & 3.75x & 1216 MB\\\hline RSA 4096 bit & 1600 bytes & 4.9x & 1600 MB\\\hline \end{tabular} - \caption{Bandwith comparison withdrawal} + \caption{Bandwidth comparison withdrawal} \label{tab:comp-band-withd} \end{table} diff --git a/doc/cs/content/6_conclusion.tex b/doc/cs/content/6_conclusion.tex index c270e765a..8ee12fa5e 100644 --- a/doc/cs/content/6_conclusion.tex +++ b/doc/cs/content/6_conclusion.tex @@ -25,8 +25,8 @@ The thesis provides several results to add support for Schnorr's blind signature \end{itemize} \item Comparison and Analysis \begin{itemize} - \item Performance (speed, space, latency \& bandwith) - \item Security + \item Performance (speed, space, latency \& bandwidth) + \item Security \item Scheme Comparison \end{itemize} \item Fixing a minor security issue in Taler's current protocols @@ -47,7 +47,7 @@ This section provides an outlook on what can be done in future work. \item Evaluating \& implementing \gls{CSBS} on other curves \end{itemize} -There are some remaining protocols to implement, which were out of scope for this thesis. +There are some remaining protocols to implement, which were out of scope for this thesis. To run \gls{CSBS} in production, these protocols have to be implemented too. Further, the merchant needs to support \gls{CSBS} too. The merchant implementation can be done fast, as the merchant only verifies denomination signatures in most cases. \\ @@ -58,7 +58,7 @@ A security audit should always be made when implementing big changes like these. As mentioned in the scope section, the optional goal to find and implement a good solution for the withdraw loophole was dropped. This was due to the scope shift and because the analysis of the problem showed that finding a good solution needs more research and is a whole project in itself (see \ref{sec:scope} for more information).\\ Furthermore, \gls{CSBS} could be implemented on other curves. -For example Curve448 \cite{cryptoeprint:2015:625} could be used, as it provides 224 bits of security, wheras \gls{25519} \cite{bern:curve25519} provides about 128 bits of security. +For example Curve448 \cite{cryptoeprint:2015:625} could be used, as it provides 224 bits of security, whereas \gls{25519} \cite{bern:curve25519} provides about 128 bits of security. Curve secp256k1 could further improve \gls{CSBS} performance. While providing support for Curve448 should not be problematic, a potential implementation for secp256k1 needs further analysis (see \cite{bernlange:safecurves} and \cite{bip:schnorr-bitc} for more information). @@ -67,4 +67,4 @@ This thesis includes understanding, analyzing, integrating and implementing a re Furthermore, the implementation is done in Taler, an intuitive and modern solution for a social responsible payment system with high ethical standards. Although there was a lot of work, we enjoyed working on such a modern and very interesting topic. Especially the first successful signature verification and the signature scheme performance benchmarks motivated us to push the implementation and integration into Taler forward.\\ -We are happy to provide an implementation of a modern scheme and making it available as free software. \ No newline at end of file +We are happy to provide an implementation of a modern scheme and making it available as free software. -- cgit v1.2.3