From e47cd6fc28ab07118aa375378ff0b4236158f06c Mon Sep 17 00:00:00 2001 From: Yehonatan Buchnik Date: Mon, 3 Oct 2022 09:41:06 +0300 Subject: [PATCH 01/44] extend replica api to return some necessry objects needed for utt --- bftengine/include/bftengine/Replica.hpp | 11 +++++++++-- bftengine/src/bftengine/BFTEngine.cpp | 6 ++++++ bftengine/src/bftengine/ReplicaBase.hpp | 2 +- kvbc/include/Replica.h | 5 +++++ 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/bftengine/include/bftengine/Replica.hpp b/bftengine/include/bftengine/Replica.hpp index 82e3ce2fec..491c4418b0 100644 --- a/bftengine/include/bftengine/Replica.hpp +++ b/bftengine/include/bftengine/Replica.hpp @@ -28,7 +28,7 @@ #include "PersistentStorage.hpp" #include "IRequestHandler.hpp" #include "InternalBFTClient.hpp" - +#include "Timers.hpp" namespace concord::cron { class TicksGenerator; } @@ -38,7 +38,10 @@ class ISecretsManagerImpl; } namespace bftEngine { - +namespace impl { +class MsgsCommunicator; +class MsgHandlersRegistrator; +} // namespace impl // Possible values for 'flags' parameter enum MsgFlag : uint64_t { EMPTY_FLAGS = 0x0, @@ -135,6 +138,10 @@ class IReplica { // Returns the internal persistent storage object. virtual std::shared_ptr persistentStorage() const = 0; + + virtual std::shared_ptr getMsgsCommunicator() const { return nullptr; } + virtual std::shared_ptr getMsgHandlersRegistrator() const { return nullptr; } + virtual concordUtil::Timers *getTimers() { return nullptr; } }; } // namespace bftEngine diff --git a/bftengine/src/bftengine/BFTEngine.cpp b/bftengine/src/bftengine/BFTEngine.cpp index e86cda82c5..f9d4b198e3 100644 --- a/bftengine/src/bftengine/BFTEngine.cpp +++ b/bftengine/src/bftengine/BFTEngine.cpp @@ -69,6 +69,12 @@ class ReplicaInternal : public IReplica { std::shared_ptr internalClient() const override { return internal_client_; } + std::shared_ptr getMsgsCommunicator() const override { return replica_->getMsgsCommunicator(); } + std::shared_ptr getMsgHandlersRegistrator() const override { + return replica_->getMsgHandlersRegistrator(); + } + concordUtil::Timers *getTimers() override { return replica_->getTimers(); } + private: std::unique_ptr replica_; std::condition_variable debugWait_; diff --git a/bftengine/src/bftengine/ReplicaBase.hpp b/bftengine/src/bftengine/ReplicaBase.hpp index 74899f19b1..eb87aaa317 100644 --- a/bftengine/src/bftengine/ReplicaBase.hpp +++ b/bftengine/src/bftengine/ReplicaBase.hpp @@ -52,7 +52,7 @@ class ReplicaBase { std::shared_ptr getMsgsCommunicator() const { return msgsCommunicator_; } std::shared_ptr getMsgHandlersRegistrator() const { return msgHandlers_; } - + concordUtil::Timers* getTimers() { return &timers_; } void SetAggregator(std::shared_ptr aggregator) { if (aggregator) { aggregator_ = aggregator; diff --git a/kvbc/include/Replica.h b/kvbc/include/Replica.h index 3fe7d7055d..aec6846aa4 100644 --- a/kvbc/include/Replica.h +++ b/kvbc/include/Replica.h @@ -181,6 +181,11 @@ class Replica : public IReplica, } virtual ~Replica(); + std::shared_ptr getMsgsCommunicator() const { return m_replicaPtr->getMsgsCommunicator(); } + std::shared_ptr getMsgHandlersRegistrator() const { + return m_replicaPtr->getMsgHandlersRegistrator(); + } + concordUtil::Timers *getTimers() { return m_replicaPtr->getTimers(); } protected: RawBlock getBlockInternal(BlockId blockId) const; From 8a156f9a71d0a128263bb19695f760872e7f1108 Mon Sep 17 00:00:00 2001 From: Yehonatan Buchnik Date: Mon, 3 Oct 2022 20:34:53 +0300 Subject: [PATCH 02/44] Add INTERNAL_FLAG to message's flags --- bftengine/include/bftengine/Replica.hpp | 1 + .../src/bftengine/messages/ClientRequestMsg.cpp | 3 ++- .../signature-processor/src/sigProcessor.cpp | 16 ++++++++-------- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/bftengine/include/bftengine/Replica.hpp b/bftengine/include/bftengine/Replica.hpp index 491c4418b0..e3cf906542 100644 --- a/bftengine/include/bftengine/Replica.hpp +++ b/bftengine/include/bftengine/Replica.hpp @@ -51,6 +51,7 @@ enum MsgFlag : uint64_t { KEY_EXCHANGE_FLAG = 0x8, // TODO [TK] use reconfig_flag TICK_FLAG = 0x10, RECONFIG_FLAG = 0x20, + INTERNAL_FLAG = 0x30, PUBLISH_ON_CHAIN_OBJECT_FLAG = 0x80, CLIENTS_PUB_KEYS_FLAG = 0x100, DB_CHECKPOINT_FLAG = 0x200 diff --git a/bftengine/src/bftengine/messages/ClientRequestMsg.cpp b/bftengine/src/bftengine/messages/ClientRequestMsg.cpp index b722248323..83f3efc1d6 100644 --- a/bftengine/src/bftengine/messages/ClientRequestMsg.cpp +++ b/bftengine/src/bftengine/messages/ClientRequestMsg.cpp @@ -119,7 +119,8 @@ void ClientRequestMsg::validateImp(const ReplicasInfo& repInfo) const { } PrincipalId clientId = header->idOfClientProxy; - if ((header->flags & RECONFIG_FLAG) == 0) ConcordAssert(this->senderId() != repInfo.myId()); + if ((header->flags & RECONFIG_FLAG) == 0 || (header->flags & INTERNAL_FLAG) == 0) + ConcordAssert(this->senderId() != repInfo.myId()); /// to do - should it be just the header? auto minMsgSize = sizeof(ClientRequestMsgHeader) + header->cidLength + spanContextSize() + header->reqSignatureLength; diff --git a/utt-replica/signature-processor/src/sigProcessor.cpp b/utt-replica/signature-processor/src/sigProcessor.cpp index 7c8a77e2de..b8ba459961 100644 --- a/utt-replica/signature-processor/src/sigProcessor.cpp +++ b/utt-replica/signature-processor/src/sigProcessor.cpp @@ -17,6 +17,7 @@ #include "bftengine/ReplicaConfig.hpp" #include "bftengine/messages/MessageBase.hpp" #include "bftengine/messages/ClientRequestMsg.hpp" +#include "bftengine/Replica.hpp" #include namespace utt { @@ -190,14 +191,13 @@ void SigProcessor::publishCompleteSignature(uint64_t sig_id, auto requestSeqNum = std::chrono::duration_cast(getMonotonicTime().time_since_epoch()).count(); std::vector appClientReq = cb(sig_id, sig); - std::unique_ptr cmsg = - std::make_unique(repId_, - 0x0, - requestSeqNum, - (uint32_t)appClientReq.size(), - (const char*)appClientReq.data(), - 60000, - "new-utt-sig-" + std::to_string(sig_id)); + auto crm = std::make_unique(repId_, + bftEngine::MsgFlag::INTERNAL_FLAG, + requestSeqNum, + (uint32_t)appClientReq.size(), + (const char*)appClientReq.data(), + 60000, + "new-utt-sig-" + std::to_string(sig_id)); msgs_communicator_->getIncomingMsgsStorage()->pushExternalMsg(std::move(cmsg)); } // Called by the validating thread From e939276c7542dd4aa14ae3f08262628e06ba719a Mon Sep 17 00:00:00 2001 From: Yehonatan Buchnik Date: Thu, 6 Oct 2022 10:43:17 +0300 Subject: [PATCH 03/44] Add INTERNAL_FLAG to message's flags --- bftengine/include/bftengine/Replica.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bftengine/include/bftengine/Replica.hpp b/bftengine/include/bftengine/Replica.hpp index e3cf906542..b02bd0579b 100644 --- a/bftengine/include/bftengine/Replica.hpp +++ b/bftengine/include/bftengine/Replica.hpp @@ -51,7 +51,7 @@ enum MsgFlag : uint64_t { KEY_EXCHANGE_FLAG = 0x8, // TODO [TK] use reconfig_flag TICK_FLAG = 0x10, RECONFIG_FLAG = 0x20, - INTERNAL_FLAG = 0x30, + INTERNAL_FLAG = 0x40, PUBLISH_ON_CHAIN_OBJECT_FLAG = 0x80, CLIENTS_PUB_KEYS_FLAG = 0x100, DB_CHECKPOINT_FLAG = 0x200 From 5b61892f0e0841263b041a13a2319ab6ed9532c2 Mon Sep 17 00:00:00 2001 From: Yehonatan Buchnik Date: Thu, 6 Oct 2022 15:47:13 +0300 Subject: [PATCH 04/44] Fix internal flags issues --- bftengine/src/bftengine/ReplicaImp.cpp | 6 +++--- bftengine/src/bftengine/messages/ClientRequestMsg.cpp | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bftengine/src/bftengine/ReplicaImp.cpp b/bftengine/src/bftengine/ReplicaImp.cpp index b899f2fc10..51b0e9c510 100644 --- a/bftengine/src/bftengine/ReplicaImp.cpp +++ b/bftengine/src/bftengine/ReplicaImp.cpp @@ -439,9 +439,9 @@ void ReplicaImp::onMessage(ClientRequestMsg *m) { // check message validity const bool invalidClient = !isValidClient(clientId) && - !((repsInfo->isIdOfReplica(clientId) || repsInfo->isIdOfPeerRoReplica(clientId)) && (flags & RECONFIG_FLAG)); + !((repsInfo->isIdOfReplica(clientId) || repsInfo->isIdOfPeerRoReplica(clientId)) && (flags & RECONFIG_FLAG || flags & INTERNAL_FLAG)); const bool sentFromReplicaToNonPrimary = - !(flags & RECONFIG_FLAG) && repsInfo->isIdOfReplica(senderId) && !isCurrentPrimary(); + !(flags & RECONFIG_FLAG || flags & INTERNAL_FLAG) && repsInfo->isIdOfReplica(senderId) && !isCurrentPrimary(); if (invalidClient) { ++numInvalidClients; @@ -5487,7 +5487,7 @@ void ReplicaImp::executeRequestsInPrePrepareMsg(concordUtils::SpanWrapper &paren reqIdx++; continue; } - const bool validClient = isValidClient(clientId) || ((req.flags() & RECONFIG_FLAG) && isIdOfReplica(clientId)); + const bool validClient = isValidClient(clientId) || ((flags & RECONFIG_FLAG || flags & INTERNAL_FLAG) && isIdOfReplica(clientId)); if (!validClient) { ++numInvalidClients; LOG_WARN(CNSUS, "The client is not valid" << KVLOG(clientId)); diff --git a/bftengine/src/bftengine/messages/ClientRequestMsg.cpp b/bftengine/src/bftengine/messages/ClientRequestMsg.cpp index 83f3efc1d6..d4d2d2490d 100644 --- a/bftengine/src/bftengine/messages/ClientRequestMsg.cpp +++ b/bftengine/src/bftengine/messages/ClientRequestMsg.cpp @@ -100,7 +100,7 @@ bool ClientRequestMsg::shouldValidateAsync() const { // manner, as that will lead to overhead. Similarly, key exchanges should happen rarely, and thus we should validate // as quick as possible, in sync. const auto* header = msgBody(); - if (((header->flags & RECONFIG_FLAG) != 0) || ((header->flags & KEY_EXCHANGE_FLAG) != 0)) { + if (((header->flags & RECONFIG_FLAG) != 0) || ((header->flags & KEY_EXCHANGE_FLAG) != 0) || (header->flags & INTERNAL_FLAG) != 0) { return false; } return true; @@ -119,7 +119,7 @@ void ClientRequestMsg::validateImp(const ReplicasInfo& repInfo) const { } PrincipalId clientId = header->idOfClientProxy; - if ((header->flags & RECONFIG_FLAG) == 0 || (header->flags & INTERNAL_FLAG) == 0) + if ((header->flags & RECONFIG_FLAG) == 0 && (header->flags & INTERNAL_FLAG) == 0) ConcordAssert(this->senderId() != repInfo.myId()); /// to do - should it be just the header? @@ -136,9 +136,9 @@ void ClientRequestMsg::validateImp(const ReplicasInfo& repInfo) const { bool isIdOfExternalClient = repInfo.isIdOfExternalClient(clientId); bool doSigVerify = false; bool emptyReq = (header->requestLength == 0); - if ((header->flags & RECONFIG_FLAG) != 0 && + if (((header->flags & RECONFIG_FLAG) != 0 || (header->flags & INTERNAL_FLAG) != 0) && (repInfo.isIdOfReplica(clientId) || repInfo.isIdOfPeerRoReplica(clientId))) { - // Allow every reconfiguration message from replicas (it will be verified in the reconfiguration handler) + // Allow every reconfiguration/internal message from replicas (it will be verified in the reconfiguration handler) return; } if (!repInfo.isValidPrincipalId(clientId)) { From 8762dbf3f4735d641a3c372dc34172853347f56f Mon Sep 17 00:00:00 2001 From: Yehonatan Buchnik Date: Thu, 6 Oct 2022 16:28:33 +0300 Subject: [PATCH 05/44] fix compilation error --- bftengine/src/bftengine/ReplicaImp.cpp | 8 +++++--- bftengine/src/bftengine/messages/ClientRequestMsg.cpp | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/bftengine/src/bftengine/ReplicaImp.cpp b/bftengine/src/bftengine/ReplicaImp.cpp index 51b0e9c510..69cd113052 100644 --- a/bftengine/src/bftengine/ReplicaImp.cpp +++ b/bftengine/src/bftengine/ReplicaImp.cpp @@ -438,8 +438,8 @@ void ReplicaImp::onMessage(ClientRequestMsg *m) { // check message validity const bool invalidClient = - !isValidClient(clientId) && - !((repsInfo->isIdOfReplica(clientId) || repsInfo->isIdOfPeerRoReplica(clientId)) && (flags & RECONFIG_FLAG || flags & INTERNAL_FLAG)); + !isValidClient(clientId) && !((repsInfo->isIdOfReplica(clientId) || repsInfo->isIdOfPeerRoReplica(clientId)) && + (flags & RECONFIG_FLAG || flags & INTERNAL_FLAG)); const bool sentFromReplicaToNonPrimary = !(flags & RECONFIG_FLAG || flags & INTERNAL_FLAG) && repsInfo->isIdOfReplica(senderId) && !isCurrentPrimary(); @@ -5487,7 +5487,9 @@ void ReplicaImp::executeRequestsInPrePrepareMsg(concordUtils::SpanWrapper &paren reqIdx++; continue; } - const bool validClient = isValidClient(clientId) || ((flags & RECONFIG_FLAG || flags & INTERNAL_FLAG) && isIdOfReplica(clientId)); + const bool validClient = + isValidClient(clientId) || + ((req.flags() & RECONFIG_FLAG || req.flags() & INTERNAL_FLAG) && isIdOfReplica(clientId)); if (!validClient) { ++numInvalidClients; LOG_WARN(CNSUS, "The client is not valid" << KVLOG(clientId)); diff --git a/bftengine/src/bftengine/messages/ClientRequestMsg.cpp b/bftengine/src/bftengine/messages/ClientRequestMsg.cpp index d4d2d2490d..1b27f8164b 100644 --- a/bftengine/src/bftengine/messages/ClientRequestMsg.cpp +++ b/bftengine/src/bftengine/messages/ClientRequestMsg.cpp @@ -100,7 +100,8 @@ bool ClientRequestMsg::shouldValidateAsync() const { // manner, as that will lead to overhead. Similarly, key exchanges should happen rarely, and thus we should validate // as quick as possible, in sync. const auto* header = msgBody(); - if (((header->flags & RECONFIG_FLAG) != 0) || ((header->flags & KEY_EXCHANGE_FLAG) != 0) || (header->flags & INTERNAL_FLAG) != 0) { + if (((header->flags & RECONFIG_FLAG) != 0) || ((header->flags & KEY_EXCHANGE_FLAG) != 0) || + (header->flags & INTERNAL_FLAG) != 0) { return false; } return true; From e9dbf5929e16097e19bb5f0313b8d754a4b71b3b Mon Sep 17 00:00:00 2001 From: Yehonatan Buchnik Date: Thu, 6 Oct 2022 21:15:02 +0300 Subject: [PATCH 06/44] add pragma once --- utt/include/serialization.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utt/include/serialization.hpp b/utt/include/serialization.hpp index 41a0651a7a..16a35afc28 100644 --- a/utt/include/serialization.hpp +++ b/utt/include/serialization.hpp @@ -10,7 +10,7 @@ // notices and license terms. Your use of these subcomponents is subject to the // terms and conditions of the sub-component's license, as noted in the LICENSE // file. - +#pragma once #include #include #include From 60d15730c110a8c0f5e5b722aa4f1a6c9161f3c9 Mon Sep 17 00:00:00 2001 From: Yehonatan Buchnik Date: Wed, 12 Oct 2022 10:21:26 +0300 Subject: [PATCH 07/44] add some necessary libutt api --- utt/include/burn.hpp | 2 ++ utt/include/coin.hpp | 2 ++ utt/libutt/src/api/burn.cpp | 2 ++ utt/libutt/src/api/coin.cpp | 1 + 4 files changed, 7 insertions(+) diff --git a/utt/include/burn.hpp b/utt/include/burn.hpp index cecaf90219..68b1374ccb 100644 --- a/utt/include/burn.hpp +++ b/utt/include/burn.hpp @@ -61,6 +61,8 @@ class Burn { */ const Coin& getCoin() const; + std::string getOwnerPid() const; + public: friend class libutt::api::CoinsSigner; friend class libutt::api::Client; diff --git a/utt/include/coin.hpp b/utt/include/coin.hpp index 0896fb5f85..8c5b3f0e59 100644 --- a/utt/include/coin.hpp +++ b/utt/include/coin.hpp @@ -168,6 +168,8 @@ class Coin { */ types::CurvePoint getExpDateAsCurvePoint() const; + uint64_t getExpDate() const; + private: friend class Client; friend class operations::Burn; diff --git a/utt/libutt/src/api/burn.cpp b/utt/libutt/src/api/burn.cpp index 6d5b5e7220..90fd76db61 100644 --- a/utt/libutt/src/api/burn.cpp +++ b/utt/libutt/src/api/burn.cpp @@ -45,4 +45,6 @@ Burn& Burn::operator=(const Burn& other) { } std::string Burn::getNullifier() const { return burn_->getNullifier(); } const Coin& Burn::getCoin() const { return c_; } + +std::string Burn::getOwnerPid() const { return burn_->getOwnerPid(); } } // namespace libutt::api::operations \ No newline at end of file diff --git a/utt/libutt/src/api/coin.cpp b/utt/libutt/src/api/coin.cpp index 5f5d4d7f22..f29fddd2d8 100644 --- a/utt/libutt/src/api/coin.cpp +++ b/utt/libutt/src/api/coin.cpp @@ -102,4 +102,5 @@ void Coin::createNullifier(const UTTParams& d, const types::CurvePoint& prf) { types::CurvePoint Coin::getPidHash() const { return coin_->pid_hash.to_words(); } types::CurvePoint Coin::getSN() const { return coin_->sn.to_words(); } types::CurvePoint Coin::getExpDateAsCurvePoint() const { return coin_->exp_date.to_words(); } +uint64_t Coin::getExpDate() const { return coin_->exp_date.as_ulong(); } } // namespace libutt::api \ No newline at end of file From da746f2f716ff7d31a738bf8f00b1034a477a448 Mon Sep 17 00:00:00 2001 From: Yehonatan Buchnik Date: Wed, 19 Oct 2022 11:59:53 +0300 Subject: [PATCH 08/44] Fix an error with randomness when creating a coin by the replicas --- utt/libutt/src/Coin.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/utt/libutt/src/Coin.cpp b/utt/libutt/src/Coin.cpp index 4c8c13bb0f..a68860fb59 100644 --- a/utt/libutt/src/Coin.cpp +++ b/utt/libutt/src/Coin.cpp @@ -77,7 +77,13 @@ std::istream& operator>>(std::istream& in, libutt::Coin& c) { namespace libutt { Coin::Coin(const CommKey& ck, const Fr& sn, const Fr& val, const Fr& type, const Fr& exp_date, const Fr& pidHash) - : ck(ck), pid_hash(pidHash), sn(sn), val(val), type(type), exp_date(exp_date), t(Fr::random_element()) { + : ck(ck), + pid_hash(pidHash), + sn(sn), + val(val), + type(type), + exp_date(exp_date), + t(Fr::zero() /*When not the client creates the coin, we want to have a deterministic value for t*/) { Coin::commit(); } Coin::Coin(const CommKey& ck, @@ -103,7 +109,10 @@ Coin::Coin(const CommKey& ck, : Coin(ck, np, ask.s, sn, val, type, exp_date, ask.getPidHash()) {} bool Coin::hasValidSig(const RandSigPK& pk) const { return sig.verify(augmentComm(), pk); } -void Coin::createNullifier(const Nullifier::Params& np, const Fr& prf) { null = Nullifier(np, prf, sn, t); } +void Coin::createNullifier(const Nullifier::Params& np, const Fr& prf) { + t = Fr::random_element(); + null = Nullifier(np, prf, sn, t); +} Comm Coin::augmentComm(const CommKey& ck, const Comm& ccmTxn, const Fr& type, const Fr& exp_date) { // WARNING: Hardcoding this for now, since the code below assumes it // TODO(Safety): better ways of computing "subcommitment keys" From ae5f62e8aa34429e4258fafdf247882252dfe731 Mon Sep 17 00:00:00 2001 From: Yehonatan Buchnik Date: Wed, 19 Oct 2022 16:35:56 +0300 Subject: [PATCH 09/44] add an option to create determisitc coin commitment for coins created by replicas --- utt/include/coin.hpp | 4 +++- utt/libutt/include/utt/Coin.h | 13 +++++++---- utt/libutt/src/Coin.cpp | 41 ++++++++++++++++++++++++++++++----- utt/libutt/src/api/coin.cpp | 12 ++++++++-- 4 files changed, 58 insertions(+), 12 deletions(-) diff --git a/utt/include/coin.hpp b/utt/include/coin.hpp index 8c5b3f0e59..dd85c0a5d5 100644 --- a/utt/include/coin.hpp +++ b/utt/include/coin.hpp @@ -81,7 +81,8 @@ class Coin { const types::CurvePoint& val, const types::CurvePoint& client_id_hash, Type t, - const types::CurvePoint& expiration_date); + const types::CurvePoint& expiration_date, + bool finalize = true); Coin(); Coin(const Coin& c); Coin& operator=(const Coin& c); @@ -102,6 +103,7 @@ class Coin { * @param prf The secret client's PRF key */ void createNullifier(const UTTParams& p, const types::CurvePoint& prf); + void finalize(const UTTParams& p, const types::CurvePoint& prf); /** * @brief Check if this coin has a signature associated with it diff --git a/utt/libutt/include/utt/Coin.h b/utt/libutt/include/utt/Coin.h index e85979e4d2..5a89a99bc2 100644 --- a/utt/libutt/include/utt/Coin.h +++ b/utt/libutt/include/utt/Coin.h @@ -76,7 +76,13 @@ class Coin { // WARNING: Used only for deserialization Coin() {} // Used to create the coin's partial commitment (without nullfier) - Coin(const CommKey& ck, const Fr& sn, const Fr& val, const Fr& type, const Fr& exp_date, const Fr& pidHash); + Coin(const CommKey& ck, + const Fr& sn, + const Fr& val, + const Fr& type, + const Fr& exp_date, + const Fr& pidHash, + bool create_commitments = true); Coin(const CommKey& ck, const Nullifier::Params& np, @@ -163,14 +169,13 @@ class Coin { */ static Comm augmentComm(const CommKey& coinCK, const Comm& ccmTxn, const Fr& type, const Fr& exp_date); - protected: + public: /** * Computes the partial coin commitment: g_1^pid g_2^sn g_3^val g^r (assumes r is pre-set) and the value commitment * g_3^val g^z, by picking a random z */ void commit(); - - public: + void deterministically_commit(); /** * Call this when you need a full coin commitment to sign using RandSigSK::sign or * to verify using RandSig::verify diff --git a/utt/libutt/src/Coin.cpp b/utt/libutt/src/Coin.cpp index a68860fb59..3271df283c 100644 --- a/utt/libutt/src/Coin.cpp +++ b/utt/libutt/src/Coin.cpp @@ -76,15 +76,24 @@ std::istream& operator>>(std::istream& in, libutt::Coin& c) { } namespace libutt { -Coin::Coin(const CommKey& ck, const Fr& sn, const Fr& val, const Fr& type, const Fr& exp_date, const Fr& pidHash) +Coin::Coin(const CommKey& ck, + const Fr& sn, + const Fr& val, + const Fr& type, + const Fr& exp_date, + const Fr& pidHash, + bool create_commitments) : ck(ck), pid_hash(pidHash), sn(sn), val(val), type(type), exp_date(exp_date), - t(Fr::zero() /*When not the client creates the coin, we want to have a deterministic value for t*/) { - Coin::commit(); + t(Fr::zero() /*When the coin is not created by the client, we want to have a deterministic value for t*/) { + if (create_commitments) + commit(); + else + deterministically_commit(); } Coin::Coin(const CommKey& ck, const Nullifier::Params& np, @@ -94,9 +103,9 @@ Coin::Coin(const CommKey& ck, const Fr& type, const Fr& exp_date, const Fr& pidHash) - : ck(ck), pid_hash(pidHash), sn(sn), val(val), type(type), exp_date(exp_date), t(Fr::random_element()) { - Coin::commit(); + : ck(ck), pid_hash(pidHash), sn(sn), val(val), type(type), exp_date(exp_date) { createNullifier(np, prf); + commit(); } Coin::Coin(const CommKey& ck, @@ -133,6 +142,28 @@ Comm Coin::augmentComm(const CommKey& ck, const Comm& ccmTxn, const Fr& type, co return ccmTxn + ccmExtra; } +void Coin::deterministically_commit() { + assertGreaterThanOrEqual(ck.numMessages(), 3); + assertTrue(ck.hasCorrectG2()); + + // we first commit *partially* to the coin, but not to its type and expiration date, which will be given in plaintext + bool withG2 = true; // since we always need G2 counterparts to verify sigs + r = Fr::zero(); + ccm_txn = Comm::create(ck, {pid_hash, sn, val, Fr::zero(), Fr::zero(), r}, withG2); + assertTrue(ccm_txn.hasCorrectG2(ck)); + + // NOTE: This is a bit hard-coded. Ideally, we might've done ck_val = Params::getValCK()? + // But that couples Coin with Params. Maybe this is okay after all. + + // set the vcm commitment key to (g_3, g) + CommKey ck_val; + ck_val.g.push_back(ck.g[2]); // g_3 + ck_val.g.push_back(ck.getGen1()); // g + + z = Fr::zero(); + vcm = Comm::create(ck_val, {val, z}, false); +} + void Coin::commit() { assertGreaterThanOrEqual(ck.numMessages(), 3); assertTrue(ck.hasCorrectG2()); diff --git a/utt/libutt/src/api/coin.cpp b/utt/libutt/src/api/coin.cpp index f29fddd2d8..2472af7dc1 100644 --- a/utt/libutt/src/api/coin.cpp +++ b/utt/libutt/src/api/coin.cpp @@ -49,7 +49,8 @@ Coin::Coin(const UTTParams& d, const types::CurvePoint& val, const types::CurvePoint& pidhash, Type t, - const types::CurvePoint& exp_date) { + const types::CurvePoint& exp_date, + bool finalize) { Fr fr_sn; fr_sn.from_words(sn); Fr fr_val; @@ -60,7 +61,7 @@ Coin::Coin(const UTTParams& d, Fr pid_hash; pid_hash.from_words(pidhash); - coin_.reset(new libutt::Coin(d.getParams().ck_coin, fr_sn, fr_val, fr_type, fr_exp_date, pid_hash)); + coin_.reset(new libutt::Coin(d.getParams().ck_coin, fr_sn, fr_val, fr_type, fr_exp_date, pid_hash, finalize)); type_ = t; } Coin::Coin(const Coin& c) { @@ -94,6 +95,13 @@ void Coin::rerandomize(std::optional base_randomness) { if (base_randomness.has_value()) u_delta.from_words(*base_randomness); coin_->sig.rerandomize(coin_->r, u_delta); } +void Coin::finalize(const UTTParams& p, const types::CurvePoint& prf) { + Fr fr_prf; + fr_prf.from_words(prf); + coin_->createNullifier(p.getParams().null, fr_prf); + coin_->commit(); +} + void Coin::createNullifier(const UTTParams& d, const types::CurvePoint& prf) { Fr fr_prf; fr_prf.from_words(prf); From 9f486ff43102af6c8a7175ed7d7bd41f942a220c Mon Sep 17 00:00:00 2001 From: Evgeniy Dzhurov Date: Tue, 11 Oct 2022 14:25:36 +0300 Subject: [PATCH 10/44] Separate the wallet in its own source file --- utt/wallet-cli/CMakeLists.txt | 3 +- utt/wallet-cli/include/wallet.hpp | 45 ++++++++ utt/wallet-cli/src/main.cpp | 169 +++--------------------------- utt/wallet-cli/src/wallet.cpp | 147 ++++++++++++++++++++++++++ 4 files changed, 210 insertions(+), 154 deletions(-) create mode 100644 utt/wallet-cli/include/wallet.hpp create mode 100644 utt/wallet-cli/src/wallet.cpp diff --git a/utt/wallet-cli/CMakeLists.txt b/utt/wallet-cli/CMakeLists.txt index f8b2da2851..825b874c4c 100644 --- a/utt/wallet-cli/CMakeLists.txt +++ b/utt/wallet-cli/CMakeLists.txt @@ -2,11 +2,12 @@ add_subdirectory("proto") set(utt-wallet-cli-src src/main.cpp + src/wallet.cpp ) add_executable(utt-wallet-cli ${utt-wallet-cli-src}) -target_include_directories(utt-wallet-cli PUBLIC ../utt-client-api/include ../utt-common-api/include) +target_include_directories(utt-wallet-cli PUBLIC include/ ../utt-client-api/include ../utt-common-api/include) target_link_libraries(utt-wallet-cli PUBLIC utt-wallet-api-proto utt_client_api diff --git a/utt/wallet-cli/include/wallet.hpp b/utt/wallet-cli/include/wallet.hpp new file mode 100644 index 0000000000..a72e07897b --- /dev/null +++ b/utt/wallet-cli/include/wallet.hpp @@ -0,0 +1,45 @@ +// UTT Client API +// +// Copyright (c) 2020-2022 VMware, Inc. All Rights Reserved. +// +// This product is licensed to you under the Apache 2.0 license (the "License"). +// You may not use this product except in compliance with the Apache 2.0 +// License. +// +// This product may include a number of subcomponents with separate copyright +// notices and license terms. Your use of these subcomponents is subject to the +// terms and conditions of the sub-component's license, as noted in the LICENSE +// file. + +#pragma once + +#include +#include + +#include +#include "api.grpc.pb.h" // Generated from utt/wallet/proto/api + +#include + +class Wallet { + public: + Wallet(); + + void showUser(const std::string& userId); + void deployApp(); + void registerUser(const std::string& userId); + + private: + void connect(); + + struct DummyUserStorage : public utt::client::IUserStorage {}; + + using GrpcService = vmware::concord::utt::wallet::api::v1::WalletService::Stub; + + std::unique_ptr deployedPublicConfig_; + std::string deployedAppId_; + utt::client::TestUserPKInfrastructure pki_; + DummyUserStorage storage_; + std::map> users_; + std::unique_ptr grpc_; +}; \ No newline at end of file diff --git a/utt/wallet-cli/src/main.cpp b/utt/wallet-cli/src/main.cpp index 065b439241..8ddfa6f803 100644 --- a/utt/wallet-cli/src/main.cpp +++ b/utt/wallet-cli/src/main.cpp @@ -1,38 +1,22 @@ -#include - -#include -#include "api.grpc.pb.h" // Generated from utt/wallet/proto/api +// UTT Client API +// +// Copyright (c) 2020-2022 VMware, Inc. All Rights Reserved. +// +// This product is licensed to you under the Apache 2.0 license (the "License"). +// You may not use this product except in compliance with the Apache 2.0 +// License. +// +// This product may include a number of subcomponents with separate copyright +// notices and license terms. Your use of these subcomponents is subject to the +// terms and conditions of the sub-component's license, as noted in the LICENSE +// file. -#include "utt-client-api/ClientApi.hpp" +#include -#include #include #include -using namespace vmware::concord::utt::wallet::api::v1; - -using GrpcWalletService = std::unique_ptr; - -GrpcWalletService connectToGrpcWalletService() { - std::string grpcServerAddr = "127.0.0.1:49000"; - - std::cout << "Connecting to gRPC server at " << grpcServerAddr << " ...\n"; - - auto chan = grpc::CreateChannel(grpcServerAddr, grpc::InsecureChannelCredentials()); - - if (!chan) { - throw std::runtime_error("Failed to create gRPC channel."); - } - auto timeoutSec = std::chrono::seconds(5); - if (chan->WaitForConnected(std::chrono::system_clock::now() + timeoutSec)) { - std::cout << "Connected.\n"; - } else { - throw std::runtime_error("Failed to connect to gRPC server after " + std::to_string(timeoutSec.count()) + - " seconds."); - } - - return WalletService::NewStub(chan); -} +#include "wallet.hpp" void printHelp() { std::cout << "\nCommands:\n"; @@ -49,124 +33,6 @@ void printHelp() { // budget -- print the currently available anonymity budget (a budget is created in advance for each user) } -class Wallet { - public: - void showUser(const std::string& userId) { - auto it = users_.find(userId); - if (it == users_.end()) { - std::cout << "User '" << userId << "' does not exist.\n"; - return; - } - - std::cout << "User Id: " << it->first << '\n'; - std::cout << "Private balance: " << it->second->getBalance() << '\n'; - std::cout << "Privacy budget: " << it->second->getPrivacyBudget() << '\n'; - std::cout << "Last executed transaction number: " << it->second->getLastExecutedTxNum() << '\n'; - } - - void deployApp(GrpcWalletService& grpc) { - if (!deployedAppId_.empty()) { - std::cout << "Privacy app '" << deployedAppId_ << "' already deployed\n"; - return; - } - - // Generate a privacy config for a N=4 replica system tolerating F=1 failures - utt::client::ConfigInputParams params; - params.validatorPublicKeys = std::vector{4, "placeholderPublicKey"}; // N = 3 * F + 1 - params.threshold = 2; // F + 1 - auto config = utt::client::generateConfig(params); - if (config.empty()) throw std::runtime_error("Failed to generate a privacy app configuration!"); - - grpc::ClientContext ctx; - - DeployPrivacyAppRequest req; - req.set_config(config.data(), config.size()); - - DeployPrivacyAppResponse resp; - grpc->deployPrivacyApp(&ctx, req, &resp); - - // Note that keeping the config around in memory is just for a demo purpose and should not happen in real system - if (resp.has_err()) { - std::cout << "Failed to deploy privacy app: " << resp.err() << '\n'; - } else if (resp.app_id().empty()) { - std::cout << "Failed to deploy privacy app: empty app id!\n"; - } else { - deployedAppId_ = resp.app_id(); - // We need the public config part which can typically be obtained from the service, but we keep it for simplicity - deployedPublicConfig_ = std::make_unique(utt::client::getPublicConfig(config)); - std::cout << "Successfully deployed privacy app with id: " << deployedAppId_ << '\n'; - } - } - - void registerUser(const std::string& userId, GrpcWalletService& grpc) { - if (userId.empty()) throw std::runtime_error("Cannot register user with empty user id!"); - if (deployedAppId_.empty()) { - std::cout << "You must first deploy a privacy app to register a user. Use command 'deploy app'\n"; - return; - } - - if (users_.find(userId) != users_.end()) { - std::cout << "User '" << userId << " is already registered!\n"; - return; - } - - // Print out the list of valid user ids with pre-generated keys if we don't have a match. - // This is a temp code until we can generate keys on demand for every user id. - auto userIds = pki_.getUserIds(); - auto it = std::find(userIds.begin(), userIds.end(), userId); - if (it == userIds.end()) { - std::cout << "Please use one of the following userIds:\n["; - for (const auto& userId : userIds) std::cout << userId << ' '; - std::cout << "]\n"; - return; - } - - auto user = utt::client::createUser(userId, *deployedPublicConfig_, pki_, storage_); - if (!user) throw std::runtime_error("Failed to create user!"); - - auto userRegInput = user->getRegistrationInput(); - if (userRegInput.empty()) throw std::runtime_error("Failed to create user registration input!"); - - grpc::ClientContext ctx; - - RegisterUserRequest req; - req.set_user_id(userId); - req.set_input_rcm(userRegInput.data(), userRegInput.size()); - req.set_user_pk(user->getPK()); - - RegisterUserResponse resp; - grpc->registerUser(&ctx, req, &resp); - - if (resp.has_err()) { - std::cout << "Failed to register user: " << resp.err() << '\n'; - } else { - utt::RegistrationSig sig = std::vector(resp.signature().begin(), resp.signature().end()); - std::cout << "Got sig for registration with size: " << sig.size() << '\n'; - - utt::S2 s2 = std::vector(resp.s2().begin(), resp.s2().end()); - std::cout << "Got S2 for registration: ["; - for (const auto& val : s2) std::cout << val << ' '; - std::cout << "]\n"; - - if (!user->updateRegistration(user->getPK(), sig, s2)) { - std::cout << "Failed to update user's registration!\n"; - } else { - std::cout << "Successfully registered user with id '" << user->getUserId() << "'\n"; - users_.emplace(userId, std::move(user)); - } - } - } - - private: - struct DummyUserStorage : public utt::client::IUserStorage {}; - - std::unique_ptr deployedPublicConfig_; - std::string deployedAppId_; - utt::client::TestUserPKInfrastructure pki_; - DummyUserStorage storage_; - std::map> users_; -}; - int main(int argc, char* argv[]) { (void)argc; (void)argv; @@ -213,9 +79,6 @@ int main(int argc, char* argv[]) { // budget -- print the currently available anonymity budget (a budget is created in advance for each user) try { - auto grpc = connectToGrpcWalletService(); - if (!grpc) throw std::runtime_error("Failed to create gRPC client stub."); - utt::client::Initialize(); auto wallet = Wallet(); @@ -233,7 +96,7 @@ int main(int argc, char* argv[]) { if (cmd == "h") { printHelp(); } else if (cmd == "deploy app") { - wallet.deployApp(grpc); + wallet.deployApp(); } else { // Tokenize command std::vector cmdTokens; @@ -248,7 +111,7 @@ int main(int argc, char* argv[]) { if (cmdTokens.size() != 2) { std::cout << "Usage: specify the user id to register.\n"; } else { - wallet.registerUser(cmdTokens[1], grpc); + wallet.registerUser(cmdTokens[1]); } } else if (cmdTokens[0] == "show") { if (cmdTokens.size() != 2) { diff --git a/utt/wallet-cli/src/wallet.cpp b/utt/wallet-cli/src/wallet.cpp new file mode 100644 index 0000000000..ddccdf80de --- /dev/null +++ b/utt/wallet-cli/src/wallet.cpp @@ -0,0 +1,147 @@ +// UTT Client API +// +// Copyright (c) 2020-2022 VMware, Inc. All Rights Reserved. +// +// This product is licensed to you under the Apache 2.0 license (the "License"). +// You may not use this product except in compliance with the Apache 2.0 +// License. +// +// This product may include a number of subcomponents with separate copyright +// notices and license terms. Your use of these subcomponents is subject to the +// terms and conditions of the sub-component's license, as noted in the LICENSE +// file. + +#include "wallet.hpp" + +#include + +using namespace vmware::concord::utt::wallet::api::v1; + +Wallet::Wallet() { connect(); } + +void Wallet::connect() { + std::string grpcServerAddr = "127.0.0.1:49000"; + + std::cout << "Connecting to gRPC server at " << grpcServerAddr << " ...\n"; + + auto chan = grpc::CreateChannel(grpcServerAddr, grpc::InsecureChannelCredentials()); + + if (!chan) { + throw std::runtime_error("Failed to create gRPC channel."); + } + auto timeoutSec = std::chrono::seconds(5); + if (chan->WaitForConnected(std::chrono::system_clock::now() + timeoutSec)) { + std::cout << "Connected.\n"; + } else { + throw std::runtime_error("Failed to connect to gRPC server after " + std::to_string(timeoutSec.count()) + + " seconds."); + } + + grpc_ = WalletService::NewStub(chan); +} + +void Wallet::showUser(const std::string& userId) { + auto it = users_.find(userId); + if (it == users_.end()) { + std::cout << "User '" << userId << "' does not exist.\n"; + return; + } + + std::cout << "User Id: " << it->first << '\n'; + std::cout << "Private balance: " << it->second->getBalance() << '\n'; + std::cout << "Privacy budget: " << it->second->getPrivacyBudget() << '\n'; + std::cout << "Last executed transaction number: " << it->second->getLastExecutedTxNum() << '\n'; +} + +void Wallet::deployApp() { + if (!deployedAppId_.empty()) { + std::cout << "Privacy app '" << deployedAppId_ << "' already deployed\n"; + return; + } + + // Generate a privacy config for a N=4 replica system tolerating F=1 failures + utt::client::ConfigInputParams params; + params.validatorPublicKeys = std::vector{4, "placeholderPublicKey"}; // N = 3 * F + 1 + params.threshold = 2; // F + 1 + auto config = utt::client::generateConfig(params); + if (config.empty()) throw std::runtime_error("Failed to generate a privacy app configuration!"); + + grpc::ClientContext ctx; + + DeployPrivacyAppRequest req; + req.set_config(config.data(), config.size()); + + DeployPrivacyAppResponse resp; + grpc_->deployPrivacyApp(&ctx, req, &resp); + + // Note that keeping the config around in memory is just for a demo purpose and should not happen in real system + if (resp.has_err()) { + std::cout << "Failed to deploy privacy app: " << resp.err() << '\n'; + } else if (resp.app_id().empty()) { + std::cout << "Failed to deploy privacy app: empty app id!\n"; + } else { + deployedAppId_ = resp.app_id(); + // We need the public config part which can typically be obtained from the service, but we keep it for simplicity + deployedPublicConfig_ = std::make_unique(utt::client::getPublicConfig(config)); + std::cout << "Successfully deployed privacy app with id: " << deployedAppId_ << '\n'; + } +} + +void Wallet::registerUser(const std::string& userId) { + if (userId.empty()) throw std::runtime_error("Cannot register user with empty user id!"); + if (deployedAppId_.empty()) { + std::cout << "You must first deploy a privacy app to register a user. Use command 'deploy app'\n"; + return; + } + + if (users_.find(userId) != users_.end()) { + std::cout << "User '" << userId << " is already registered!\n"; + return; + } + + // Print out the list of valid user ids with pre-generated keys if we don't have a match. + // This is a temp code until we can generate keys on demand for every user id. + auto userIds = pki_.getUserIds(); + auto it = std::find(userIds.begin(), userIds.end(), userId); + if (it == userIds.end()) { + std::cout << "Please use one of the following userIds:\n["; + for (const auto& userId : userIds) std::cout << userId << ' '; + std::cout << "]\n"; + return; + } + + auto user = utt::client::createUser(userId, *deployedPublicConfig_, pki_, storage_); + if (!user) throw std::runtime_error("Failed to create user!"); + + auto userRegInput = user->getRegistrationInput(); + if (userRegInput.empty()) throw std::runtime_error("Failed to create user registration input!"); + + grpc::ClientContext ctx; + + RegisterUserRequest req; + req.set_user_id(userId); + req.set_input_rcm(userRegInput.data(), userRegInput.size()); + req.set_user_pk(user->getPK()); + + RegisterUserResponse resp; + grpc_->registerUser(&ctx, req, &resp); + + if (resp.has_err()) { + std::cout << "Failed to register user: " << resp.err() << '\n'; + } else { + utt::RegistrationSig sig = std::vector(resp.signature().begin(), resp.signature().end()); + std::cout << "Got sig for registration with size: " << sig.size() << '\n'; + + utt::S2 s2 = std::vector(resp.s2().begin(), resp.s2().end()); + std::cout << "Got S2 for registration: ["; + for (const auto& val : s2) std::cout << val << ' '; + std::cout << "]\n"; + + if (!user->updateRegistration(user->getPK(), sig, s2)) { + std::cout << "Failed to update user's registration!\n"; + } else { + std::cout << "Successfully registered user with id '" << user->getUserId() << "'\n"; + users_.emplace(userId, std::move(user)); + } + } +} \ No newline at end of file From 24f31865a17726844bc3582308506ab348fa2dc0 Mon Sep 17 00:00:00 2001 From: Evgeniy Dzhurov Date: Tue, 11 Oct 2022 15:36:46 +0300 Subject: [PATCH 11/44] The wallet now assumes only a single user. The user id is provided as a command line argument --- utt/wallet-cli/include/wallet.hpp | 32 +++++++++--- utt/wallet-cli/src/main.cpp | 61 ++++++++++++---------- utt/wallet-cli/src/wallet.cpp | 86 +++++++++++++------------------ 3 files changed, 95 insertions(+), 84 deletions(-) diff --git a/utt/wallet-cli/include/wallet.hpp b/utt/wallet-cli/include/wallet.hpp index a72e07897b..fdd8c7b8b1 100644 --- a/utt/wallet-cli/include/wallet.hpp +++ b/utt/wallet-cli/include/wallet.hpp @@ -23,23 +23,41 @@ class Wallet { public: - Wallet(); + Wallet(std::string userId, utt::client::IUserPKInfrastructure& pki); - void showUser(const std::string& userId); + void showInfo() const; + + /// @brief Deploy a privacy application + /// [TODO-UTT] Should be performed by an admin app void deployApp(); - void registerUser(const std::string& userId); + + /// @brief Request registration of the current user + void registerUser(); + + /// @brief Request the privacy budget. The amount of the budget is predetermined by the deployed app. + /// This operation could be performed entirely by an administrator, but we add it in the wallet + /// for demo purposes. + void requestPrivacyBudget(); private: void connect(); + // [TODO-UTT] This is a helper method to check if the user has been created successfully after generating + // a privacy app config. Since we don't have access to the public config of an already deployed privacy app + // we need to deploy it from the wallet first. This also means that currently we can test only with a single wallet + // because a second wallet would also need to deploy an app to have access to the config. This needs to be + // changed so we can deploy an app and then provide the public config to one or more wallets and create + // the users as part of initialization. + bool checkOperationl() const; + struct DummyUserStorage : public utt::client::IUserStorage {}; using GrpcService = vmware::concord::utt::wallet::api::v1::WalletService::Stub; - std::unique_ptr deployedPublicConfig_; - std::string deployedAppId_; - utt::client::TestUserPKInfrastructure pki_; + bool isOperational_ = false; DummyUserStorage storage_; - std::map> users_; + std::string userId_; + utt::client::IUserPKInfrastructure& pki_; + std::unique_ptr user_; std::unique_ptr grpc_; }; \ No newline at end of file diff --git a/utt/wallet-cli/src/main.cpp b/utt/wallet-cli/src/main.cpp index 8ddfa6f803..1774c0c89c 100644 --- a/utt/wallet-cli/src/main.cpp +++ b/utt/wallet-cli/src/main.cpp @@ -21,9 +21,8 @@ void printHelp() { std::cout << "\nCommands:\n"; std::cout << "deploy app -- generates a privacy app config and deploys it to the blockchain.\n"; - std::cout - << "register -- creates a new user and registers it in a previously deployed privacy app instance\n"; - std::cout << "show -- prints information about a user created by this wallet\n"; + std::cout << "register -- creates a new user and registers it in a previously deployed privacy app instance\n"; + std::cout << "info -- prints information about the user managed by this wallet\n"; // mint -- convert some amount of public funds (ERC20 tokens) to private funds (UTT tokens) // burn -- convert some amount of private funds (UTT tokens) to public funds (ERC20 tokens) @@ -78,10 +77,35 @@ int main(int argc, char* argv[]) { // the wallet service) private balance -- print the number of UTT tokens the user has currently (compute locally) // budget -- print the currently available anonymity budget (a budget is created in advance for each user) + if (argc != 2) { + std::cout << "Usage: specify the user id\n"; + return 0; + } + + auto userId = std::string(argv[1]); + + // Check that we have test RSA keys for this user id + // If not - print the avaialble user ids + utt::client::TestUserPKInfrastructure pki; + + // Print out the list of valid user ids with pre-generated keys if we don't have a match. + // This is a temp code until we can generate keys on demand for every user id. + auto userIds = pki.getUserIds(); + auto it = std::find(userIds.begin(), userIds.end(), userId); + if (it == userIds.end()) { + std::cout << "Use one of the following user ids: ["; + for (const auto& userId : userIds) std::cout << userId << ' '; + std::cout << "]\n"; + return 0; + } + try { utt::client::Initialize(); - auto wallet = Wallet(); + auto wallet = Wallet(userId, pki); + + std::cout << "WARNING: The wallet will not be operational until you perform the 'deploy app' command.\nThis is a " + "temporary solution and will be changed.\n"; while (true) { std::cout << "Enter command (type 'h' for commands 'Ctr-D' to quit):\n > "; @@ -97,31 +121,12 @@ int main(int argc, char* argv[]) { printHelp(); } else if (cmd == "deploy app") { wallet.deployApp(); + } else if (cmd == "register") { + wallet.registerUser(); + } else if (cmd == "info") { + wallet.showInfo(); } else { - // Tokenize command - std::vector cmdTokens; - { - std::stringstream ss(cmd); - std::string t; - while (std::getline(ss, t, ' ')) cmdTokens.emplace_back(std::move(t)); - } - if (cmdTokens.empty()) continue; - - if (cmdTokens[0] == "register") { - if (cmdTokens.size() != 2) { - std::cout << "Usage: specify the user id to register.\n"; - } else { - wallet.registerUser(cmdTokens[1]); - } - } else if (cmdTokens[0] == "show") { - if (cmdTokens.size() != 2) { - std::cout << "Usage: specify the user id to show,\n"; - } else { - wallet.showUser(cmdTokens[1]); - } - } else { - std::cout << "Unknown command '" << cmd << "'\n"; - } + std::cout << "Unknown command '" << cmd << "'\n"; } } diff --git a/utt/wallet-cli/src/wallet.cpp b/utt/wallet-cli/src/wallet.cpp index ddccdf80de..457241295d 100644 --- a/utt/wallet-cli/src/wallet.cpp +++ b/utt/wallet-cli/src/wallet.cpp @@ -17,7 +17,9 @@ using namespace vmware::concord::utt::wallet::api::v1; -Wallet::Wallet() { connect(); } +Wallet::Wallet(std::string userId, utt::client::IUserPKInfrastructure& pki) : userId_{std::move(userId)}, pki_{pki} { + connect(); +} void Wallet::connect() { std::string grpcServerAddr = "127.0.0.1:49000"; @@ -40,22 +42,18 @@ void Wallet::connect() { grpc_ = WalletService::NewStub(chan); } -void Wallet::showUser(const std::string& userId) { - auto it = users_.find(userId); - if (it == users_.end()) { - std::cout << "User '" << userId << "' does not exist.\n"; - return; - } +void Wallet::showInfo() const { + std::cout << "User Id: " << userId_ << '\n'; - std::cout << "User Id: " << it->first << '\n'; - std::cout << "Private balance: " << it->second->getBalance() << '\n'; - std::cout << "Privacy budget: " << it->second->getPrivacyBudget() << '\n'; - std::cout << "Last executed transaction number: " << it->second->getLastExecutedTxNum() << '\n'; + if (!checkOperationl()) return; + std::cout << "Private balance: " << user_->getBalance() << '\n'; + std::cout << "Privacy budget: " << user_->getPrivacyBudget() << '\n'; + std::cout << "Last executed transaction number: " << user_->getLastExecutedTxNum() << '\n'; } void Wallet::deployApp() { - if (!deployedAppId_.empty()) { - std::cout << "Privacy app '" << deployedAppId_ << "' already deployed\n"; + if (isOperational_) { + std::cout << "The wallet has already deployed an app and is operational.\n"; return; } @@ -74,54 +72,35 @@ void Wallet::deployApp() { DeployPrivacyAppResponse resp; grpc_->deployPrivacyApp(&ctx, req, &resp); - // Note that keeping the config around in memory is just for a demo purpose and should not happen in real system + // Note that keeping the config around in memory is just a temp solution and should not happen in real system if (resp.has_err()) { std::cout << "Failed to deploy privacy app: " << resp.err() << '\n'; } else if (resp.app_id().empty()) { std::cout << "Failed to deploy privacy app: empty app id!\n"; } else { - deployedAppId_ = resp.app_id(); - // We need the public config part which can typically be obtained from the service, but we keep it for simplicity - deployedPublicConfig_ = std::make_unique(utt::client::getPublicConfig(config)); - std::cout << "Successfully deployed privacy app with id: " << deployedAppId_ << '\n'; - } -} + // We need the public config part which can typically be obtained from the service, but we derive it for simplicity + auto publicConfig = utt::client::getPublicConfig(config); + std::cout << "Successfully deployed privacy app with id: " << resp.app_id() << '\n'; -void Wallet::registerUser(const std::string& userId) { - if (userId.empty()) throw std::runtime_error("Cannot register user with empty user id!"); - if (deployedAppId_.empty()) { - std::cout << "You must first deploy a privacy app to register a user. Use command 'deploy app'\n"; - return; - } - - if (users_.find(userId) != users_.end()) { - std::cout << "User '" << userId << " is already registered!\n"; - return; - } + user_ = utt::client::createUser(userId_, publicConfig, pki_, storage_); + if (!user_) throw std::runtime_error("Failed to create user!"); - // Print out the list of valid user ids with pre-generated keys if we don't have a match. - // This is a temp code until we can generate keys on demand for every user id. - auto userIds = pki_.getUserIds(); - auto it = std::find(userIds.begin(), userIds.end(), userId); - if (it == userIds.end()) { - std::cout << "Please use one of the following userIds:\n["; - for (const auto& userId : userIds) std::cout << userId << ' '; - std::cout << "]\n"; - return; + isOperational_ = true; } +} - auto user = utt::client::createUser(userId, *deployedPublicConfig_, pki_, storage_); - if (!user) throw std::runtime_error("Failed to create user!"); +void Wallet::registerUser() { + if (!checkOperationl()) return; - auto userRegInput = user->getRegistrationInput(); + auto userRegInput = user_->getRegistrationInput(); if (userRegInput.empty()) throw std::runtime_error("Failed to create user registration input!"); grpc::ClientContext ctx; RegisterUserRequest req; - req.set_user_id(userId); + req.set_user_id(userId_); req.set_input_rcm(userRegInput.data(), userRegInput.size()); - req.set_user_pk(user->getPK()); + req.set_user_pk(user_->getPK()); RegisterUserResponse resp; grpc_->registerUser(&ctx, req, &resp); @@ -137,11 +116,20 @@ void Wallet::registerUser(const std::string& userId) { for (const auto& val : s2) std::cout << val << ' '; std::cout << "]\n"; - if (!user->updateRegistration(user->getPK(), sig, s2)) { + if (!user_->updateRegistration(user_->getPK(), sig, s2)) { std::cout << "Failed to update user's registration!\n"; - } else { - std::cout << "Successfully registered user with id '" << user->getUserId() << "'\n"; - users_.emplace(userId, std::move(user)); } } +} + +void Wallet::requestPrivacyBudget() { + if (!checkOperationl()) return; +} + +bool Wallet::checkOperationl() const { + if (!isOperational_) { + std::cout << "You must first deploy a privacy app. Use command 'deploy app'\n"; + return false; + } + return true; } \ No newline at end of file From d889deb0071f9f3e32de10c66a93a389c7913619 Mon Sep 17 00:00:00 2001 From: Evgeniy Dzhurov Date: Wed, 12 Oct 2022 18:43:51 +0300 Subject: [PATCH 12/44] WIP budget, mint, transfer, burn operations --- utt/wallet-cli/include/wallet.hpp | 27 +++- utt/wallet-cli/proto/api/v1/api.proto | 126 +++++---------- utt/wallet-cli/src/main.cpp | 75 +++++++-- utt/wallet-cli/src/wallet.cpp | 212 +++++++++++++++++++++++++- 4 files changed, 330 insertions(+), 110 deletions(-) diff --git a/utt/wallet-cli/include/wallet.hpp b/utt/wallet-cli/include/wallet.hpp index fdd8c7b8b1..ec63fc07cc 100644 --- a/utt/wallet-cli/include/wallet.hpp +++ b/utt/wallet-cli/include/wallet.hpp @@ -23,7 +23,7 @@ class Wallet { public: - Wallet(std::string userId, utt::client::IUserPKInfrastructure& pki); + Wallet(std::string userId, utt::client::TestUserPKInfrastructure& pki); void showInfo() const; @@ -34,21 +34,38 @@ class Wallet { /// @brief Request registration of the current user void registerUser(); - /// @brief Request the privacy budget. The amount of the budget is predetermined by the deployed app. + /// @brief Request the creation of a privacy budget. The amount of the budget is predetermined by the deployed app. /// This operation could be performed entirely by an administrator, but we add it in the wallet /// for demo purposes. - void requestPrivacyBudget(); + void createPrivacyBudget(); + + /// @brief Requests the minting of public funds to private funds. + /// @param amount the amount of public funds + void mint(uint64_t amount); + + /// @brief Transfers the desired amount anonymously to the recipient + /// @param amount The amount to transfer + /// @param recipient The user id of the recipient + void transfer(uint64_t amount, const std::string recipient); + + /// @brief Burns the desired amount of private funds and converts them to public funds. + /// @param amount The amount of private funds to burn. + void burn(uint64_t amount); private: void connect(); + /// @brief Sync up to the last known tx number. If the last known tx number is zero (or not provided) the + /// last signed transaction number will be fetched from the system + void syncState(uint64_t lastKnownTxNum = 0); + // [TODO-UTT] This is a helper method to check if the user has been created successfully after generating // a privacy app config. Since we don't have access to the public config of an already deployed privacy app // we need to deploy it from the wallet first. This also means that currently we can test only with a single wallet // because a second wallet would also need to deploy an app to have access to the config. This needs to be // changed so we can deploy an app and then provide the public config to one or more wallets and create // the users as part of initialization. - bool checkOperationl() const; + bool checkOperational() const; struct DummyUserStorage : public utt::client::IUserStorage {}; @@ -57,7 +74,7 @@ class Wallet { bool isOperational_ = false; DummyUserStorage storage_; std::string userId_; - utt::client::IUserPKInfrastructure& pki_; + utt::client::TestUserPKInfrastructure& pki_; std::unique_ptr user_; std::unique_ptr grpc_; }; \ No newline at end of file diff --git a/utt/wallet-cli/proto/api/v1/api.proto b/utt/wallet-cli/proto/api/v1/api.proto index c91a7093c1..ec49c6752f 100644 --- a/utt/wallet-cli/proto/api/v1/api.proto +++ b/utt/wallet-cli/proto/api/v1/api.proto @@ -7,30 +7,16 @@ syntax = "proto3"; package vmware.concord.utt.wallet.api.v1; // Privacy Wallet Service Interface -// This interface follows the UTT contract interface as close as possible. -// [TODO-UTT] Consider if we should use a simplified version of this api that assumes synchronous requests -// - This would mean that a request will receive a response only when signatures are computed. service WalletService { // Temporary adding the deployment of privacy app to the wallet service // [TODO-UTT] This operation should be performed by an administrator-only tool rpc deployPrivacyApp(DeployPrivacyAppRequest) returns (DeployPrivacyAppResponse); - rpc registerUser(RegisterUserRequest) returns (RegisterUserResponse); - rpc getUserRegistration(GetUserRegistrationRequest) returns (GetUserRegistrationResponse); - - // Note: creating a public budget is done by an administrator - rpc getLatestPublicBudget(GetLatestPublicBudgetRequest) returns (GetLatestPublicBudgetResponse); - - // [TODO-UTT] Consider if we need to get the budget sig alone. - // Here is the problem: only one budget coin can exist for a user at the any time, but the signature - // is not ready at the same time as the budget coin. - // If we call getLatestPublicBudget and get back both a budget coin and a sig then we can use the coin. - // If we get just the budget coin the sig is still pending. - // But when we get the sig eventually we're not guaranteed that its for the same budget coin. - // Concurrently some administrator may have created a new budget coin. - // The safest bet is to call getLatestPublicBudgetSig until you get both a budget coin and a sig. - // Otherwise we need to verify that the coin and sig match and if not - request the coin and/or sig until they do. - rpc getLatestPublicBudgetSig(GetLatestPublicBudgetSigRequest) returns (GetLatestPublicBudgetSigResponse); + + // A non-standard way for a client to force the creation of a privacy budget. + // Usually this operation is performed by an administrator. + // The value of the returned budget is determined by the system + rpc createPrivacyBudget(CreatePrivacyBudgetRequest) returns (CreatePrivacyBudgetResponse); rpc transfer(TransferRequest) returns (TransferResponse); @@ -50,9 +36,15 @@ service WalletService { // Same as getNumOfLastSignedTranscation from spec rpc getLastSignedTxNumber(GetLastSignedTxNumberRequest) returns (GetLastSignedTxNumberResponse); - rpc getTransaction(GetTransactionRequest) returns (GetTransactionResponse); + // Fetch an already signed (completed) transaction (either mint, burn or transfer). + rpc getSignedTransaction(GetSignedTransactionRequest) returns (GetSignedTransactionResponse); +} - rpc getTransactionSig(GetTransactionSigRequest) returns (GetTransactionSigResponse); +enum TxType { + UNDEFINED = 0; + MINT = 1; + BURN = 2; + TRANSFER = 3; } message DeployPrivacyAppRequest { @@ -70,50 +62,29 @@ message RegisterUserRequest { optional bytes input_rcm = 3; // The user's input registration commitment } -// [TODO-UTT] Consider if we should include the complete result in the response -// instead of indicating success and then periodically requesting the registration data message RegisterUserResponse { - optional string err = 1; // Returns any error generated during registration - optional bytes signature = 2; // Signature on the user's registration - repeated uint64 s2 = 3; // Second part of the user's nullifier key -} - -message GetUserRegistrationRequest { - optional string user_id = 1; -} - -message GetUserRegistrationResponse { optional string err = 1; - optional string user_id = 2; - optional bytes user_pk = 3; // User's public key - optional bytes s2 = 4; // System-generated part of the user's PRF key (used for nullifiers) - optional bytes rs = 5; // A signature on the full registration commitment -} - -message GetLatestPublicBudgetRequest { - optional string user_id = 1; + optional bytes signature = 2; + repeated uint64 s2 = 3; // Second part of the user's nullifier key } -message GetLatestPublicBudgetResponse { - optional bytes budget_coin = 1; - // [TODO-UTT] Consider returning the signature in the same response as the budget coin, - // optional bytes signature = 2; // Could be empty -} - -message GetLatestPublicBudgetSigRequest { +message CreatePrivacyBudgetRequest { optional string user_id = 1; } -message GetLatestPublicBudgetSigResponse { - optional bytes signature = 1; +message CreatePrivacyBudgetResponse { + optional string err = 1; + optional bytes budget = 2; + optional bytes signature = 3; } message TransferRequest { - optional TransferTx transfer = 1; + optional bytes tx_data = 1; } message TransferResponse { - optional uint64 tx_number = 1; + optional string err = 1; + optional uint64 tx_number = 2; } message MintRequest { @@ -122,17 +93,19 @@ message MintRequest { } message MintResponse { - optional uint64 tx_number = 1; + optional string err = 1; + optional uint64 tx_number = 2; } message BurnRequest { optional string user_id = 1; - optional bytes burn_tx = 2; optional uint64 value = 3; + optional bytes tx_data = 2; } message BurnResponse { - optional uint64 tx_number = 1; + optional string err = 1; + optional uint64 tx_number = 2; } message GetLastAddedTxNumberRequest { @@ -146,43 +119,18 @@ message GetLastSignedTxNumberRequest { } message GetLastSignedTxNumberResponse { - optional uint64 tx_number = 1; -} - -message GetTransactionRequest { - optional uint64 tx_number = 1; -} - -message GetTransactionResponse { - optional uint64 tx_number = 1; - oneof tx { - TransferTx transfer = 2; - MintTx mint = 3; - BurnTx burn = 4; - } -} - -message TransferTx { - // [TODO-UTT] What else is in a transfer tx? - // -- should be mostly represented as bytes since this is an anonymous tx. - optional bytes tx = 1; -} - -message MintTx { - // [TODO-UTT] What else is in a mint tx? - optional bytes tx = 1; -} - -message BurnTx { - // [TODO-UTT] What else is in a burn tx? - optional bytes tx = 1; + optional string err = 1; + optional uint64 tx_number = 2; } -message GetTransactionSigRequest { +message GetSignedTransactionRequest { optional uint64 tx_number = 1; } -message GetTransactionSigResponse { - optional uint64 tx_number = 1; - optional bytes signatures = 2; +message GetSignedTransactionResponse { + optional string err = 1; + optional uint64 tx_number = 2; + optional TxType tx_type = 3; + optional bytes tx_data = 4; + repeated bytes sigs = 5; } \ No newline at end of file diff --git a/utt/wallet-cli/src/main.cpp b/utt/wallet-cli/src/main.cpp index 1774c0c89c..c6fa4b84e1 100644 --- a/utt/wallet-cli/src/main.cpp +++ b/utt/wallet-cli/src/main.cpp @@ -20,8 +20,10 @@ void printHelp() { std::cout << "\nCommands:\n"; - std::cout << "deploy app -- generates a privacy app config and deploys it to the blockchain.\n"; - std::cout << "register -- creates a new user and registers it in a previously deployed privacy app instance\n"; + std::cout << "deploy app -- generates a privacy app config, deploys it on the blockchain and creates a user.\n"; + std::cout << "register -- requests user registration required for spending coins\n"; + std::cout << "create budget -- requests creation of a privacy budget, the amount is decided by the system.\n"; + std::cout << "mint -- mint the requested amount of public funds.\n"; std::cout << "info -- prints information about the user managed by this wallet\n"; // mint -- convert some amount of public funds (ERC20 tokens) to private funds (UTT tokens) @@ -91,13 +93,22 @@ int main(int argc, char* argv[]) { // Print out the list of valid user ids with pre-generated keys if we don't have a match. // This is a temp code until we can generate keys on demand for every user id. auto userIds = pki.getUserIds(); - auto it = std::find(userIds.begin(), userIds.end(), userId); - if (it == userIds.end()) { - std::cout << "Use one of the following user ids: ["; - for (const auto& userId : userIds) std::cout << userId << ' '; - std::cout << "]\n"; - return 0; - } + + auto checkValidUserId = [&userIds](const std::string& userId) { + auto it = std::find(userIds.begin(), userIds.end(), userId); + + if (it == userIds.end()) { + std::cout << "Use one of the following valid user ids: ["; + for (const auto& userId : userIds) std::cout << userId << ' '; + std::cout << "]\n"; + return false; + } + + return true; + }; + + // Check that we're creating a wallet with a valid user-id + if (!checkValidUserId(userId)) return 0; try { utt::client::Initialize(); @@ -123,10 +134,54 @@ int main(int argc, char* argv[]) { wallet.deployApp(); } else if (cmd == "register") { wallet.registerUser(); + } else if (cmd == "create budget") { + wallet.createPrivacyBudget(); } else if (cmd == "info") { wallet.showInfo(); } else { - std::cout << "Unknown command '" << cmd << "'\n"; + // Tokenize params + std::vector cmdTokens; + std::string token; + while (std::getline(std::cin, token)) cmdTokens.emplace_back(token); + + if (!cmdTokens.empty()) { + if (cmdTokens[0] == "mint") { + if (cmdTokens.size() != 2) { + std::cout << "Expected the mint amount as an argument!\n"; + } else { + int amount = std::atoi(cmdTokens[1].c_str()); + if (amount <= 0) { + std::cout << "Expected a positive mint amount!\n"; + } else { + wallet.mint((uint64_t)amount); + } + } + } else if (cmdTokens[0] == "transfer") { + if (cmdTokens.size() != 3) { + std::cout << "Expected the transfer amount and recipient user id as arguments!\n"; + } else { + int amount = std::atoi(cmdTokens[1].c_str()); + if (amount <= 0) { + std::cout << "Expected a positive transfer amount!\n"; + } else if (checkValidUserId(cmdTokens[2])) { + wallet.transfer((uint64_t)amount, cmdTokens[2]); + } + } + } else if (cmdTokens[1] == "burn") { + if (cmdTokens.size() != 2) { + std::cout << "Expected the burn amount as an argument!\n"; + } else { + int amount = std::atoi(cmdTokens[1].c_str()); + if (amount <= 0) { + std::cout << "Expected a positive burn amount!\n"; + } else { + wallet.burn((uint64_t)amount); + } + } + } else { + std::cout << "Unknown command '" << cmd << "'\n"; + } + } } } diff --git a/utt/wallet-cli/src/wallet.cpp b/utt/wallet-cli/src/wallet.cpp index 457241295d..0fc66b4443 100644 --- a/utt/wallet-cli/src/wallet.cpp +++ b/utt/wallet-cli/src/wallet.cpp @@ -17,7 +17,7 @@ using namespace vmware::concord::utt::wallet::api::v1; -Wallet::Wallet(std::string userId, utt::client::IUserPKInfrastructure& pki) : userId_{std::move(userId)}, pki_{pki} { +Wallet::Wallet(std::string userId, utt::client::TestUserPKInfrastructure& pki) : userId_{std::move(userId)}, pki_{pki} { connect(); } @@ -45,7 +45,7 @@ void Wallet::connect() { void Wallet::showInfo() const { std::cout << "User Id: " << userId_ << '\n'; - if (!checkOperationl()) return; + if (!checkOperational()) return; std::cout << "Private balance: " << user_->getBalance() << '\n'; std::cout << "Privacy budget: " << user_->getPrivacyBudget() << '\n'; std::cout << "Last executed transaction number: " << user_->getLastExecutedTxNum() << '\n'; @@ -90,7 +90,7 @@ void Wallet::deployApp() { } void Wallet::registerUser() { - if (!checkOperationl()) return; + if (!checkOperational()) return; auto userRegInput = user_->getRegistrationInput(); if (userRegInput.empty()) throw std::runtime_error("Failed to create user registration input!"); @@ -122,14 +122,214 @@ void Wallet::registerUser() { } } -void Wallet::requestPrivacyBudget() { - if (!checkOperationl()) return; +void Wallet::createPrivacyBudget() { + if (!checkOperational()) return; + + grpc::ClientContext ctx; + + CreatePrivacyBudgetRequest req; + req.set_user_id(userId_); + + CreatePrivacyBudgetResponse resp; + grpc_->createPrivacyBudget(&ctx, req, &resp); + + if (resp.has_err()) { + std::cout << "Failed to create privacy budget:" << resp.err() << '\n'; + } else { + utt::PrivacyBudget budget = std::vector(resp.budget().begin(), resp.budget().end()); + utt::RegistrationSig sig = std::vector(resp.signature().begin(), resp.signature().end()); + + std::cout << "Got budget " << budget.size() << " bytes.\n"; + std::cout << "Got budget sig " << sig.size() << " bytes.\n"; + + if (!user_->updatePrivacyBudget(budget, sig)) { + std::cout << "Failed to update privacy budget!\n"; + } + } } -bool Wallet::checkOperationl() const { +void Wallet::mint(uint64_t amount) { + if (!checkOperational()) return; + + grpc::ClientContext ctx; + + MintRequest req; + req.set_user_id(userId_); + req.set_value(amount); + + MintResponse resp; + grpc_->mint(&ctx, req, &resp); + + if (resp.has_err()) { + std::cout << "Failed to mint:" << resp.err() << '\n'; + } else { + std::cout << "Successfully sent mint tx with number:" << resp.tx_number() << '\n'; + + syncState(resp.tx_number()); + } +} + +void Wallet::transfer(uint64_t amount, const std::string recipient) { + if (!checkOperational()) return; + + if (user_->getBalance() < amount) { + std::cout << "Insufficient private balance!\n"; + return; + } + + if (user_->getPrivacyBudget() < amount) { + std::cout << "Insufficient privacy budget!\n"; + return; + } + + std::cout << "Started processing " << amount << "anonymous transfer to " << recipient << "...\n"; + + // Process the transfer until we get the final transaction + // On each iteration we also sync up to the tx number of our request + while (true) { + auto result = user_->transfer(recipient, pki_.getPublicKey(recipient), amount); + + grpc::ClientContext ctx; + + TransferRequest req; + req.set_tx_data(result.requiredTx_.data_.data(), result.requiredTx_.data_.size()); + + TransferResponse resp; + grpc_->transfer(&ctx, req, &resp); + + if (resp.has_err()) { + std::cout << "Failed to transfer:" << resp.err() << '\n'; + } else { + std::cout << "Successfully sent transfer tx with number:" << resp.tx_number() << '\n'; + + syncState(resp.tx_number()); + } + + if (result.isFinal_) break; // Done + } +} + +void Wallet::burn(uint64_t amount) { + if (!checkOperational()) return; + + if (user_->getBalance() < amount) { + std::cout << "Insufficient private balance!\n"; + return; + } + + if (user_->getPrivacyBudget() < amount) { + std::cout << "Insufficient privacy budget!\n"; + return; + } + + std::cout << "Started processing " << amount << " burn...\n"; + + // Process the transfer until we get the final transaction + // On each iteration we also sync up to the tx number of our request + while (true) { + auto result = user_->burn(amount); + + grpc::ClientContext ctx; + + BurnRequest req; + req.set_tx_data(result.requiredTx_.data_.data(), result.requiredTx_.data_.size()); + + BurnResponse resp; + grpc_->burn(&ctx, req, &resp); + + if (resp.has_err()) { + std::cout << "Failed to burn:" << resp.err() << '\n'; + } else { + std::cout << "Successfully sent burn tx with number:" << resp.tx_number() << '\n'; + + syncState(resp.tx_number()); + } + + if (result.isFinal_) break; // Done + } +} + +bool Wallet::checkOperational() const { if (!isOperational_) { std::cout << "You must first deploy a privacy app. Use command 'deploy app'\n"; return false; } return true; +} + +void Wallet::syncState(uint64_t lastKnownTxNum) { + if (!checkOperational()) return; + + std::cout << "Synchronizing state...\n"; + + // Sync to latest state + if (lastKnownTxNum == 0) { + std::cout << "Last known tx number is zero (or not provided) - fetching last signed tx number...\n"; + + grpc::ClientContext ctx; + GetLastSignedTxNumberRequest req; + GetLastSignedTxNumberResponse resp; + grpc_->getLastSignedTxNumber(&ctx, req, &resp); + + if (resp.has_err()) { + std::cout << "Failed to get last signed tx number:" << resp.err() << '\n'; + } else { + std::cout << "Got last signed tx number:" << resp.tx_number() << '\n'; + lastKnownTxNum = resp.tx_number(); + } + } + + for (uint64_t txNum = user_->getLastExecutedTxNum() + 1; txNum <= lastKnownTxNum; ++txNum) { + grpc::ClientContext ctx; + + GetSignedTransactionRequest req; + req.set_tx_number(txNum); + + GetSignedTransactionResponse resp; + grpc_->getSignedTransaction(&ctx, req, &resp); + + if (resp.has_err()) { + std::cout << "Failed to get signed tx with number " << txNum << ':' << resp.err() << '\n'; + return; + } + + std::cout << "Got signed " << TxType_Name(resp.tx_type()) << " transaction.\n"; + std::cout << "Tx data: " << resp.tx_data().size() << " bytes\n"; + std::cout << "Num Sigs: " << resp.sigs_size() << '\n'; + + utt::Transaction tx; + tx.data_ = std::vector(resp.tx_data().begin(), resp.tx_data().end()); + + utt::TxOutputSigs sigs; + sigs.reserve((size_t)resp.sigs_size()); + for (const auto& sig : resp.sigs()) { + sigs.emplace_back(std::vector(sig.begin(), sig.end())); + } + + // Apply transaction + switch (resp.tx_type()) { + case TxType::MINT: { + tx.type_ = utt::Transaction::Type::Mint; + if (sigs.size() != 1) throw std::runtime_error("Expected single signature in mint tx!"); + if (!user_->updateMintTx(resp.tx_number(), tx, sigs[0])) { + throw std::runtime_error("Failed to update mint transaction!"); + } + } break; + case TxType::TRANSFER: { + tx.type_ = utt::Transaction::Type::Transfer; + if (!user_->updateTransferTx(resp.tx_number(), tx, sigs)) { + throw std::runtime_error("Failed to update transfer transaction!"); + } + } break; + case TxType::BURN: { + tx.type_ = utt::Transaction::Type::Transfer; + if (!sigs.empty()) throw std::runtime_error("Expected no signatures for burn tx!"); + if (!user_->updateBurnTx(resp.tx_number(), tx)) { + throw std::runtime_error("Failed to update burn transaction!"); + } + } break; + default: + throw std::runtime_error("Unexpected tx type!"); + } + } } \ No newline at end of file From 956d1534c9b9fd440afb7957aacbc1dd31dd0c1e Mon Sep 17 00:00:00 2001 From: Evgeniy Dzhurov Date: Thu, 13 Oct 2022 09:51:22 +0300 Subject: [PATCH 13/44] wallet fixes --- utt/wallet-cli/src/main.cpp | 122 ++++++++++++++++------------------ utt/wallet-cli/src/wallet.cpp | 5 ++ 2 files changed, 61 insertions(+), 66 deletions(-) diff --git a/utt/wallet-cli/src/main.cpp b/utt/wallet-cli/src/main.cpp index c6fa4b84e1..3fb425d46d 100644 --- a/utt/wallet-cli/src/main.cpp +++ b/utt/wallet-cli/src/main.cpp @@ -12,86 +12,75 @@ // file. #include - #include #include #include "wallet.hpp" +// [TODO-UTT] The wallet should use RocksDB to store UTT assets. +// [TODO-UTT] The wallet should use some RSA cryptography to generate public/private +// keys (used by the UTT Client Library) + +// [TODO-UTT] Initial registration +// Upon first launch (no records in RocksDB) the wallet asks the user to register +// > Choose a unique user identifier: +// After this prompt the wallet sends a registration request and waits for the response +// Upon successful registration the user can use any of the following commands. + +// [TODO-UTT] Startup Sequence +// A. Confirm registration -- check that the user is registered, otherwise go to "Initial registration". +// B. Synchronize +// 1) Ask about the latest signed transaction number and compare with +// the latest executed transaction in the wallet. Determine the range of tx numbers to be retrieved. +// 2) For each tx number to execute request the transaction and signature (can be combined) +// 3) Apply the transaction to the wallet state +// a. If it's a burn or a mint transaction matching our user-id +// b. IF it's an anonymous tx that we can claim outputs from or slash spent coins (check the nullifiers) +// [TODO-UTT] Synchronization can be optimized to require fewer requests by batching tx requests and/or filtering by +// user-id for burns and mints + +// [TODO-UTT] Periodic synchronization +// We need to periodically sync with the wallet service - we can either detect this when we send requests +// (we see that there are multiple transactions that happened before ours) or we do it periodically or before +// attempt an operation. + +// Note: Limited recovery from liveness issues +// In a single machine demo setting liveness issues will not be created due to the network, +// so we don't need to implement the full range of precautions to handle liveness issues +// such as timeouts. + +// [TODO-UTT] Commands: +// mint -- convert some amount of public funds (ERC20 tokens) to private funds (UTT tokens) +// burn -- convert some amount of private funds (UTT tokens) to public funds (ERC20 tokens) +// transfer -- transfers anonymously some amount of private funds (UTT tokens) to another user +// public balance -- print the number of ERC20 tokens the user has (needs a gRPC method to retrieve this value from +// the wallet service) private balance -- print the number of UTT tokens the user has currently (compute locally) +// budget -- print the currently available anonymity budget (a budget is created in advance for each user) + void printHelp() { std::cout << "\nCommands:\n"; - std::cout << "deploy app -- generates a privacy app config, deploys it on the blockchain and creates a user.\n"; - std::cout << "register -- requests user registration required for spending coins\n"; - std::cout << "create budget -- requests creation of a privacy budget, the amount is decided by the system.\n"; - std::cout << "mint -- mint the requested amount of public funds.\n"; - std::cout << "info -- prints information about the user managed by this wallet\n"; - - // mint -- convert some amount of public funds (ERC20 tokens) to private funds (UTT tokens) - // burn -- convert some amount of private funds (UTT tokens) to public funds (ERC20 tokens) - // transfer -- transfers anonymously some amount of private funds (UTT tokens) to another user - // public balance -- print the number of ERC20 tokens the user has (needs a gRPC method to retrieve this value from - // the wallet service) private balance -- print the number of UTT tokens the user has currently (compute locally) - // budget -- print the currently available anonymity budget (a budget is created in advance for each user) + std::cout << "deploy app -- generates a privacy app config, deploys it on the blockchain and creates a user.\n"; + std::cout << "info -- prints information about the user managed by this wallet\n"; + std::cout << "register -- requests user registration required for spending coins\n"; + std::cout << "create budget -- requests creation of a privacy budget, the amount is decided by the system.\n"; + std::cout << "mint -- mint the requested amount of public funds.\n"; + std::cout << "transfer -- transfers the specified amount to the user-id as a recipient.\n"; + std::cout << "burn -- burns the specified amount of private funds to public funds.\n"; + std::cout << '\n'; } int main(int argc, char* argv[]) { - (void)argc; - (void)argv; - - std::cout << "Sample Privacy Wallet CLI Application.\n"; - // [TODO-UTT] The wallet should use RocksDB to store UTT assets. - // [TODO-UTT] The wallet should use some RSA cryptography to generate public/private - // keys (used by the UTT Client Library) - - // [TODO-UTT] Initial registration - // Upon first launch (no records in RocksDB) the wallet asks the user to register - // > Choose a unique user identifier: - // After this prompt the wallet sends a registration request and waits for the response - // Upon successful registration the user can use any of the following commands. - - // [TODO-UTT] Startup Sequence - // A. Confirm registration -- check that the user is registered, otherwise go to "Initial registration". - // B. Synchronize - // 1) Ask about the latest signed transaction number and compare with - // the latest executed transaction in the wallet. Determine the range of tx numbers to be retrieved. - // 2) For each tx number to execute request the transaction and signature (can be combined) - // 3) Apply the transaction to the wallet state - // a. If it's a burn or a mint transaction matching our user-id - // b. IF it's an anonymous tx that we can claim outputs from or slash spent coins (check the nullifiers) - // [TODO-UTT] Synchronization can be optimized to require fewer requests by batching tx requests and/or filtering by - // user-id for burns and mints - - // [TODO-UTT] Periodic synchronization - // We need to periodically sync with the wallet service - we can either detect this when we send requests - // (we see that there are multiple transactions that happened before ours) or we do it periodically or before - // attempt an operation. - - // Note: Limited recovery from liveness issues - // In a single machine demo setting liveness issues will not be created due to the network, - // so we don't need to implement the full range of precautions to handle liveness issues - // such as timeouts. - - // [TODO-UTT] Commands: - // mint -- convert some amount of public funds (ERC20 tokens) to private funds (UTT tokens) - // burn -- convert some amount of private funds (UTT tokens) to public funds (ERC20 tokens) - // transfer -- transfers anonymously some amount of private funds (UTT tokens) to another user - // public balance -- print the number of ERC20 tokens the user has (needs a gRPC method to retrieve this value from - // the wallet service) private balance -- print the number of UTT tokens the user has currently (compute locally) - // budget -- print the currently available anonymity budget (a budget is created in advance for each user) - + if (argc != 2) { std::cout << "Usage: specify the user id\n"; return 0; } + std::cout << "Sample Privacy Wallet CLI Application.\n"; + auto userId = std::string(argv[1]); - // Check that we have test RSA keys for this user id - // If not - print the avaialble user ids utt::client::TestUserPKInfrastructure pki; - - // Print out the list of valid user ids with pre-generated keys if we don't have a match. - // This is a temp code until we can generate keys on demand for every user id. auto userIds = pki.getUserIds(); auto checkValidUserId = [&userIds](const std::string& userId) { @@ -115,11 +104,11 @@ int main(int argc, char* argv[]) { auto wallet = Wallet(userId, pki); - std::cout << "WARNING: The wallet will not be operational until you perform the 'deploy app' command.\nThis is a " + std::cout << "WARNING: The wallet will not be operational until you perform the 'deploy app' command. This is a " "temporary solution and will be changed.\n"; while (true) { - std::cout << "Enter command (type 'h' for commands 'Ctr-D' to quit):\n > "; + std::cout << "\nEnter command (type 'h' for commands 'Ctr-D' to quit):\n > "; std::string cmd; std::getline(std::cin, cmd); @@ -142,7 +131,8 @@ int main(int argc, char* argv[]) { // Tokenize params std::vector cmdTokens; std::string token; - while (std::getline(std::cin, token)) cmdTokens.emplace_back(token); + std::stringstream ss(cmd); + while (std::getline(ss, token, ' ')) cmdTokens.emplace_back(token); if (!cmdTokens.empty()) { if (cmdTokens[0] == "mint") { @@ -167,7 +157,7 @@ int main(int argc, char* argv[]) { wallet.transfer((uint64_t)amount, cmdTokens[2]); } } - } else if (cmdTokens[1] == "burn") { + } else if (cmdTokens[0] == "burn") { if (cmdTokens.size() != 2) { std::cout << "Expected the burn amount as an argument!\n"; } else { diff --git a/utt/wallet-cli/src/wallet.cpp b/utt/wallet-cli/src/wallet.cpp index 0fc66b4443..81fb3eb6da 100644 --- a/utt/wallet-cli/src/wallet.cpp +++ b/utt/wallet-cli/src/wallet.cpp @@ -172,6 +172,11 @@ void Wallet::mint(uint64_t amount) { void Wallet::transfer(uint64_t amount, const std::string recipient) { if (!checkOperational()) return; + if (userId_ == recipient) { + std::cout << "Cannot transfer to self directly!\n"; + return; + } + if (user_->getBalance() < amount) { std::cout << "Insufficient private balance!\n"; return; From b56ae6f2d26de36633b508cc04994bb0ec8e0285 Mon Sep 17 00:00:00 2001 From: Evgeniy Dzhurov Date: Thu, 13 Oct 2022 13:59:48 +0300 Subject: [PATCH 14/44] utt client api user now has more consistent error handling by throwing exceptions instead of returning a bool result. Allow updateMintTx and updateBurnTx to ignore the transaction if the owner is someone else --- utt/include/burn.hpp | 5 +- utt/libutt/include/utt/BurnOp.h | 2 +- utt/libutt/src/BurnOp.cpp | 2 +- utt/libutt/src/api/burn.cpp | 3 +- .../include/utt-client-api/User.hpp | 17 ++-- utt/utt-client-api/src/User.cpp | 94 ++++++++++--------- utt/utt-client-api/tests/TestUTTClientApi.cpp | 21 ++--- utt/wallet-cli/src/main.cpp | 1 - utt/wallet-cli/src/wallet.cpp | 20 +--- 9 files changed, 73 insertions(+), 92 deletions(-) diff --git a/utt/include/burn.hpp b/utt/include/burn.hpp index 68b1374ccb..f2a9177bbd 100644 --- a/utt/include/burn.hpp +++ b/utt/include/burn.hpp @@ -61,7 +61,10 @@ class Burn { */ const Coin& getCoin() const; - std::string getOwnerPid() const; + /** + * @brief Get the pid of the owner of the burned coin + */ + const std::string& getOwnerPid() const; public: friend class libutt::api::CoinsSigner; diff --git a/utt/libutt/include/utt/BurnOp.h b/utt/libutt/include/utt/BurnOp.h index 7749cb7d03..c9e860bfb3 100644 --- a/utt/libutt/include/utt/BurnOp.h +++ b/utt/libutt/include/utt/BurnOp.h @@ -56,7 +56,7 @@ class BurnOp { size_t getValue() const; - std::string getOwnerPid() const; + const std::string& getOwnerPid() const; std::string getNullifier() const; diff --git a/utt/libutt/src/BurnOp.cpp b/utt/libutt/src/BurnOp.cpp index a0dca3e266..a821b1a920 100644 --- a/utt/libutt/src/BurnOp.cpp +++ b/utt/libutt/src/BurnOp.cpp @@ -214,7 +214,7 @@ size_t BurnOp::getValue() const { return v; } -std::string BurnOp::getOwnerPid() const { +const std::string& BurnOp::getOwnerPid() const { InternalDataOfBurnOp* d = (InternalDataOfBurnOp*)this->p; assertTrue(d != nullptr); return d->pid; diff --git a/utt/libutt/src/api/burn.cpp b/utt/libutt/src/api/burn.cpp index 90fd76db61..23068118f6 100644 --- a/utt/libutt/src/api/burn.cpp +++ b/utt/libutt/src/api/burn.cpp @@ -45,6 +45,5 @@ Burn& Burn::operator=(const Burn& other) { } std::string Burn::getNullifier() const { return burn_->getNullifier(); } const Coin& Burn::getCoin() const { return c_; } - -std::string Burn::getOwnerPid() const { return burn_->getOwnerPid(); } +const std::string& Burn::getOwnerPid() const { return burn_->getOwnerPid(); } } // namespace libutt::api::operations \ No newline at end of file diff --git a/utt/utt-client-api/include/utt-client-api/User.hpp b/utt/utt-client-api/include/utt-client-api/User.hpp index e3a4b7ceae..8a6068ec88 100644 --- a/utt/utt-client-api/include/utt-client-api/User.hpp +++ b/utt/utt-client-api/include/utt-client-api/User.hpp @@ -54,14 +54,12 @@ class User { /// @param pk The system sent public key for the registration (must be equal to the user's public key) /// @param rs A signature on the user's registration /// @param s2 A system generated part of the user's nullifier secret key - /// @return True if the registration is accepted by the user - bool updateRegistration(const std::string& pk, const utt::RegistrationSig& rs, const utt::S2& s2); + void updateRegistration(const std::string& pk, const utt::RegistrationSig& rs, const utt::S2& s2); /// @brief Updates the privacy budget of the user /// @param token The budget token object /// @param sig The budget token signature - /// @return True if the budget token is accepted - bool updatePrivacyBudget(const utt::PrivacyBudget& budget, const utt::PrivacyBudgetSig& sig); + void updatePrivacyBudget(const utt::PrivacyBudget& budget, const utt::PrivacyBudgetSig& sig); /** * @brief Get the total value of unspent UTT tokens @@ -92,25 +90,22 @@ class User { /// @param txNum The transaction number /// @param tx A transfer transaction /// @param sigs The signatures on the transaction outputs - /// @return - bool updateTransferTx(uint64_t txNum, const utt::Transaction& tx, const utt::TxOutputSigs& sigs); + void updateTransferTx(uint64_t txNum, const utt::Transaction& tx, const utt::TxOutputSigs& sigs); /// @brief Update the user's state with the effects of a mint transaction /// @param txNum The transaction number /// @param tx A mint transaction /// @param sig The signature on the transaction output (we assume a mint tx has a single output) - /// @return - bool updateMintTx(uint64_t txNum, const utt::Transaction& tx, const utt::TxOutputSig& sig); + void updateMintTx(uint64_t txNum, const utt::Transaction& tx, const utt::TxOutputSig& sig); /// @brief Update the user's state with the effects of a burn transaction /// @param txNum The transaction number /// @param tx A burn transaction - /// @return - bool updateBurnTx(uint64_t txNum, const utt::Transaction& tx); + void updateBurnTx(uint64_t txNum, const utt::Transaction& tx); /// @brief The user records the tx as a no-op and skips it /// @param txNum - bool updateNoOp(uint64_t txNum); + void updateNoOp(uint64_t txNum); /// @brief Ask to burn some amount of tokens. This function needs to be called repeatedly until the final burn /// transaction is produced. diff --git a/utt/utt-client-api/src/User.cpp b/utt/utt-client-api/src/User.cpp index 65044e6bab..3b014415ec 100644 --- a/utt/utt-client-api/src/User.cpp +++ b/utt/utt-client-api/src/User.cpp @@ -36,6 +36,8 @@ #include "utt-client-api/PickCoins.hpp" +using namespace libutt; + namespace utt::client { struct User::Impl { @@ -239,40 +241,35 @@ UserRegistrationInput User::getRegistrationInput() const { return libutt::api::serialize(pImpl_->client_->generateInputRCM()); } -bool User::updateRegistration(const std::string& pk, const RegistrationSig& rs, const S2& s2) { - if (!pImpl_->client_) return false; - if (!(pImpl_->pk_ == pk)) return false; // Expect a matching public key - if (rs.empty() || s2.empty()) return false; +void User::updateRegistration(const std::string& pk, const RegistrationSig& rs, const S2& s2) { + if (!pImpl_->client_) throw std::runtime_error("User not initialized!"); + if (rs.empty() || s2.empty() || pk.empty()) throw std::runtime_error("updateRegistration: invalid arguments!"); + if (!(pImpl_->pk_ == pk)) throw std::runtime_error("Public key mismatch!"); // Un-blind the signature std::vector randomness = {libutt::Fr::zero().to_words(), libutt::Fr::zero().to_words()}; auto unblindedSig = libutt::api::Utils::unblindSignature(pImpl_->params_, libutt::api::Commitment::REGISTRATION, randomness, rs); - if (unblindedSig.empty()) return false; + if (unblindedSig.empty()) throw std::runtime_error("Failed to unblind reg signature!"); // [TODO-UTT] What if we already updated a registration? How do we check it? pImpl_->client_->setRCMSig(pImpl_->params_, s2, unblindedSig); - - return true; } -bool User::updatePrivacyBudget(const PrivacyBudget& budget, const PrivacyBudgetSig& sig) { - if (!pImpl_->client_) return false; +void User::updatePrivacyBudget(const PrivacyBudget& budget, const PrivacyBudgetSig& sig) { + if (!pImpl_->client_) throw std::runtime_error("User not initialized!"); auto claimedCoins = pImpl_->client_->claimCoins(libutt::api::deserialize(budget), pImpl_->params_, std::vector{sig}); // Expect a single budget token to be claimed by the user - if (claimedCoins.size() != 1) return false; - + if (claimedCoins.size() != 1) throw std::runtime_error("Expected single budget token!"); if (!pImpl_->client_->validate(claimedCoins[0])) throw std::runtime_error("Invalid initial budget coin!"); // [TODO-UTT] Requires atomic, durable write through IUserStorage pImpl_->budgetCoin_ = claimedCoins[0]; - - return true; } uint64_t User::getBalance() const { @@ -294,9 +291,10 @@ const std::string& User::getPK() const { return pImpl_->pk_; } uint64_t User::getLastExecutedTxNum() const { return pImpl_->lastExecutedTxNum_; } -bool User::updateTransferTx(uint64_t txNum, const Transaction& tx, const TxOutputSigs& sigs) { - if (tx.type_ != Transaction::Type::Transfer) return false; - if (txNum != pImpl_->lastExecutedTxNum_ + 1) return false; +void User::updateTransferTx(uint64_t txNum, const Transaction& tx, const TxOutputSigs& sigs) { + if (!pImpl_->client_) throw std::runtime_error("User not initialized!"); + if (tx.type_ != Transaction::Type::Transfer) throw std::runtime_error("Transfer tx type mismatch!"); + if (txNum != pImpl_->lastExecutedTxNum_ + 1) throw std::runtime_error("Transfer tx number is not consecutive!"); auto uttTx = libutt::api::deserialize(tx.data_); @@ -332,57 +330,61 @@ bool User::updateTransferTx(uint64_t txNum, const Transaction& tx, const TxOutpu } } } - - return true; } -bool User::updateMintTx(uint64_t txNum, const Transaction& tx, const TxOutputSig& sig) { - if (tx.type_ != Transaction::Type::Mint) return false; - if (txNum != pImpl_->lastExecutedTxNum_ + 1) return false; - pImpl_->lastExecutedTxNum_ = txNum; +void User::updateMintTx(uint64_t txNum, const Transaction& tx, const TxOutputSig& sig) { + if (!pImpl_->client_) throw std::runtime_error("User not initialized!"); + if (tx.type_ != Transaction::Type::Mint) throw std::runtime_error("Mint tx type mismatch!"); + if (txNum != pImpl_->lastExecutedTxNum_ + 1) throw std::runtime_error("Mint tx number is not consecutive!"); auto mint = libutt::api::deserialize(tx.data_); - auto claimedCoins = - pImpl_->client_->claimCoins(mint, pImpl_->params_, std::vector{sig}); + if (mint.getRecipentID() != pImpl_->client_->getPid()) { + loginfo << "Mint transaction is for different user - ignoring." << endl; + } else { + auto claimedCoins = + pImpl_->client_->claimCoins(mint, pImpl_->params_, std::vector{sig}); - // Expect a single token to be claimed by the user - if (claimedCoins.size() != 1) return false; + // Expect a single token to be claimed by the user + if (claimedCoins.size() != 1) throw std::runtime_error("Expected single coin in mint tx!"); + if (!pImpl_->client_->validate(claimedCoins[0])) throw std::runtime_error("Invalid minted coin!"); - if (!pImpl_->client_->validate(claimedCoins[0])) throw std::runtime_error("Invalid minted coin!"); + pImpl_->coins_.emplace_back(std::move(claimedCoins[0])); + } // [TODO-UTT] Requires atomic, durable write batch through IUserStorage pImpl_->lastExecutedTxNum_ = txNum; - pImpl_->coins_.emplace_back(std::move(claimedCoins[0])); - - return true; } -// [TODO-UTT] Do we actually need the whole BurnTx or we can simply use the nullifer to slash? -bool User::updateBurnTx(uint64_t txNum, const Transaction& tx) { - if (tx.type_ != Transaction::Type::Burn) return false; - if (txNum != pImpl_->lastExecutedTxNum_ + 1) return false; +// [TODO-UTT] Do we actually need the whole BurnTx or we can simply use the nullifier to slash? +void User::updateBurnTx(uint64_t txNum, const Transaction& tx) { + if (!pImpl_->client_) throw std::runtime_error("User not initialized!"); + if (tx.type_ != Transaction::Type::Burn) throw std::runtime_error("Burn tx type mismatch!"); + if (txNum != pImpl_->lastExecutedTxNum_ + 1) throw std::runtime_error("Burn tx number is not consecutive!"); auto burn = libutt::api::deserialize(tx.data_); - auto nullifier = burn.getNullifier(); - if (nullifier.empty()) return false; - auto it = std::find_if(pImpl_->coins_.begin(), pImpl_->coins_.end(), [&nullifier](const libutt::api::Coin& coin) { - return coin.getNullifier() == nullifier; - }); - if (it == pImpl_->coins_.end()) return false; + if (burn.getOwnerPid() != pImpl_->client_->getPid()) { + loginfo << "Burn transaction is for different user - ignoring." << endl; + } else { + auto nullifier = burn.getNullifier(); + if (nullifier.empty()) throw std::runtime_error("Burn tx has empty nullifier!"); + + auto it = std::find_if(pImpl_->coins_.begin(), pImpl_->coins_.end(), [&nullifier](const libutt::api::Coin& coin) { + return coin.getNullifier() == nullifier; + }); + if (it == pImpl_->coins_.end()) throw std::runtime_error("Burned token missing in wallet!"); + pImpl_->coins_.erase(it); + } // [TODO-UTT] Requires atomic, durable write batch through IUserStorage pImpl_->lastExecutedTxNum_ = txNum; - pImpl_->coins_.erase(it); - - return true; } -bool User::updateNoOp(uint64_t txNum) { - if (txNum != pImpl_->lastExecutedTxNum_ + 1) return false; +void User::updateNoOp(uint64_t txNum) { + if (!pImpl_->client_) throw std::runtime_error("User not initialized!"); + if (txNum != pImpl_->lastExecutedTxNum_ + 1) throw std::runtime_error("Noop tx number is not consecutive!"); pImpl_->lastExecutedTxNum_ = txNum; - return true; } BurnResult User::burn(uint64_t amount) const { diff --git a/utt/utt-client-api/tests/TestUTTClientApi.cpp b/utt/utt-client-api/tests/TestUTTClientApi.cpp index cd13de36aa..6bbe397c39 100644 --- a/utt/utt-client-api/tests/TestUTTClientApi.cpp +++ b/utt/utt-client-api/tests/TestUTTClientApi.cpp @@ -298,27 +298,22 @@ int main(int argc, char* argv[]) { case utt::Transaction::Type::Mint: { if (executedTx.publicUserId_ == users[i]->getUserId()) { assertTrue(executedTx.sigs_.size() == 1); - auto result = users[i]->updateMintTx(txNum, executedTx.tx_, executedTx.sigs_.front()); - assertTrue(result); + users[i]->updateMintTx(txNum, executedTx.tx_, executedTx.sigs_.front()); } else { - auto result = users[i]->updateNoOp(txNum); - assertTrue(result); + users[i]->updateNoOp(txNum); } } break; case utt::Transaction::Type::Burn: { if (executedTx.publicUserId_ == users[i]->getUserId()) { assertTrue(executedTx.sigs_.empty()); - auto result = users[i]->updateBurnTx(txNum, executedTx.tx_); - assertTrue(result); + users[i]->updateBurnTx(txNum, executedTx.tx_); } else { - auto result = users[i]->updateNoOp(txNum); - assertTrue(result); + users[i]->updateNoOp(txNum); } } break; case utt::Transaction::Type::Transfer: { assertFalse(executedTx.sigs_.empty()); - auto result = users[i]->updateTransferTx(txNum, executedTx.tx_, executedTx.sigs_); - assertTrue(result); + users[i]->updateTransferTx(txNum, executedTx.tx_, executedTx.sigs_); } break; default: assertFail("Unknown tx type!"); @@ -347,8 +342,7 @@ int main(int argc, char* argv[]) { assertFalse(resp.sig.empty()); // Note: the user's pk is usually recorded by the system and returned as part of the registration - auto result = users[i]->updateRegistration(users[i]->getPK(), resp.sig, resp.s2); - assertTrue(result); + users[i]->updateRegistration(users[i]->getPK(), resp.sig, resp.s2); } // Create budgets @@ -358,8 +352,7 @@ int main(int argc, char* argv[]) { assertFalse(resp.budget.empty()); assertFalse(resp.sig.empty()); - auto result = users[i]->updatePrivacyBudget(resp.budget, resp.sig); - assertTrue(result); + users[i]->updatePrivacyBudget(resp.budget, resp.sig); assertTrue(users[i]->getPrivacyBudget() == initialBudget[i]); } diff --git a/utt/wallet-cli/src/main.cpp b/utt/wallet-cli/src/main.cpp index 3fb425d46d..b9349b0844 100644 --- a/utt/wallet-cli/src/main.cpp +++ b/utt/wallet-cli/src/main.cpp @@ -70,7 +70,6 @@ void printHelp() { } int main(int argc, char* argv[]) { - if (argc != 2) { std::cout << "Usage: specify the user id\n"; return 0; diff --git a/utt/wallet-cli/src/wallet.cpp b/utt/wallet-cli/src/wallet.cpp index 81fb3eb6da..cc03ca6a18 100644 --- a/utt/wallet-cli/src/wallet.cpp +++ b/utt/wallet-cli/src/wallet.cpp @@ -116,9 +116,7 @@ void Wallet::registerUser() { for (const auto& val : s2) std::cout << val << ' '; std::cout << "]\n"; - if (!user_->updateRegistration(user_->getPK(), sig, s2)) { - std::cout << "Failed to update user's registration!\n"; - } + user_->updateRegistration(user_->getPK(), sig, s2); } } @@ -142,9 +140,7 @@ void Wallet::createPrivacyBudget() { std::cout << "Got budget " << budget.size() << " bytes.\n"; std::cout << "Got budget sig " << sig.size() << " bytes.\n"; - if (!user_->updatePrivacyBudget(budget, sig)) { - std::cout << "Failed to update privacy budget!\n"; - } + user_->updatePrivacyBudget(budget, sig); } } @@ -316,22 +312,16 @@ void Wallet::syncState(uint64_t lastKnownTxNum) { case TxType::MINT: { tx.type_ = utt::Transaction::Type::Mint; if (sigs.size() != 1) throw std::runtime_error("Expected single signature in mint tx!"); - if (!user_->updateMintTx(resp.tx_number(), tx, sigs[0])) { - throw std::runtime_error("Failed to update mint transaction!"); - } + user_->updateMintTx(resp.tx_number(), tx, sigs[0]); } break; case TxType::TRANSFER: { tx.type_ = utt::Transaction::Type::Transfer; - if (!user_->updateTransferTx(resp.tx_number(), tx, sigs)) { - throw std::runtime_error("Failed to update transfer transaction!"); - } + user_->updateTransferTx(resp.tx_number(), tx, sigs); } break; case TxType::BURN: { tx.type_ = utt::Transaction::Type::Transfer; if (!sigs.empty()) throw std::runtime_error("Expected no signatures for burn tx!"); - if (!user_->updateBurnTx(resp.tx_number(), tx)) { - throw std::runtime_error("Failed to update burn transaction!"); - } + user_->updateBurnTx(resp.tx_number(), tx); } break; default: throw std::runtime_error("Unexpected tx type!"); From 72912a2632c4915c1df16a4a324b7ffa5c93726d Mon Sep 17 00:00:00 2001 From: Evgeniy Dzhurov Date: Thu, 13 Oct 2022 15:10:52 +0300 Subject: [PATCH 15/44] Add support for mint transactions originating from the wallet --- .../include/utt-client-api/User.hpp | 3 +++ utt/utt-client-api/src/User.cpp | 15 +++++++++++++ utt/utt-client-api/tests/TestUTTClientApi.cpp | 22 +++++++++++++------ utt/wallet-cli/proto/api/v1/api.proto | 1 + utt/wallet-cli/src/wallet.cpp | 3 +++ 5 files changed, 37 insertions(+), 7 deletions(-) diff --git a/utt/utt-client-api/include/utt-client-api/User.hpp b/utt/utt-client-api/include/utt-client-api/User.hpp index 8a6068ec88..a57cf1f818 100644 --- a/utt/utt-client-api/include/utt-client-api/User.hpp +++ b/utt/utt-client-api/include/utt-client-api/User.hpp @@ -107,6 +107,9 @@ class User { /// @param txNum void updateNoOp(uint64_t txNum); + /// @brief Creates a transaction to mint the requested amount + utt::Transaction mint(uint64_t amount) const; + /// @brief Ask to burn some amount of tokens. This function needs to be called repeatedly until the final burn /// transaction is produced. /// @param amount The amount of private funds to burn. diff --git a/utt/utt-client-api/src/User.cpp b/utt/utt-client-api/src/User.cpp index 3b014415ec..e69e585319 100644 --- a/utt/utt-client-api/src/User.cpp +++ b/utt/utt-client-api/src/User.cpp @@ -387,6 +387,21 @@ void User::updateNoOp(uint64_t txNum) { pImpl_->lastExecutedTxNum_ = txNum; } +utt::Transaction User::mint(uint64_t amount) const { + std::stringstream ss; + ss << Fr::random_element(); + auto randomHash = ss.str(); + loginfo << "Creating a mint tx with hash: " << randomHash << endl; + + auto mint = libutt::api::operations::Mint(randomHash, amount, pImpl_->client_->getPid()); + + utt::Transaction tx; + tx.type_ = utt::Transaction::Type::Mint; + tx.data_ = libutt::api::serialize(mint); + + return tx; +} + BurnResult User::burn(uint64_t amount) const { if (!pImpl_->client_) throw std::runtime_error("User not initialized!"); if (amount == 0) throw std::runtime_error("Burn amount must be positive!"); diff --git a/utt/utt-client-api/tests/TestUTTClientApi.cpp b/utt/utt-client-api/tests/TestUTTClientApi.cpp index 6bbe397c39..3f0efdf327 100644 --- a/utt/utt-client-api/tests/TestUTTClientApi.cpp +++ b/utt/utt-client-api/tests/TestUTTClientApi.cpp @@ -169,14 +169,20 @@ struct ServerMock { return resp; } - uint64_t mint(const std::string& userId, uint64_t amount) { + uint64_t mint(const std::string& userId, uint64_t amount, const utt::Transaction& tx) { assertFalse(userId.empty()); assertTrue(amount > 0); + assertTrue(tx.type_ == utt::Transaction::Type::Mint); + assertTrue(!tx.data_.empty()); - auto pidHash = libutt::api::Utils::curvePointFromHash(userId); - auto uniqueHash = "mint|" + std::to_string(++lastTokenId_); + auto mintTx = libutt::api::deserialize(tx.data_); + assertTrue(mintTx.getRecipentID() == userId); + assertTrue(mintTx.getVal() == amount); + + //auto pidHash = libutt::api::Utils::curvePointFromHash(userId); + //auto uniqueHash = "mint|" + std::to_string(++lastTokenId_); - auto mintTx = libutt::api::operations::Mint(uniqueHash, amount, userId); + //auto mintTx = libutt::api::operations::Mint(uniqueHash, amount, userId); std::vector> shares; for (const auto& signer : coinsSigners_) { @@ -193,8 +199,9 @@ struct ServerMock { } ExecutedTx executedTx; - executedTx.tx_.type_ = utt::Transaction::Type::Mint; - executedTx.tx_.data_ = libutt::api::serialize(mintTx); + executedTx.tx_ = tx; + //executedTx.tx_.type_ = utt::Transaction::Type::Mint; + //executedTx.tx_.data_ = libutt::api::serialize(mintTx); executedTx.sigs_.emplace_back(libutt::api::Utils::aggregateSigShares(n, shareSubset)); executedTx.publicUserId_ = userId; ledger_.emplace_back(std::move(executedTx)); @@ -360,7 +367,8 @@ int main(int argc, char* argv[]) { { loginfo << "Minting tokens" << endl; for (size_t i = 0; i < C; ++i) { - auto txNum = serverMock.mint(users[i]->getUserId(), initialBalance[i]); + auto tx = users[i]->mint(initialBalance[i]); + auto txNum = serverMock.mint(users[i]->getUserId(), initialBalance[i], tx); assertTrue(txNum == serverMock.getLastExecutedTxNum()); } diff --git a/utt/wallet-cli/proto/api/v1/api.proto b/utt/wallet-cli/proto/api/v1/api.proto index ec49c6752f..c6b8355800 100644 --- a/utt/wallet-cli/proto/api/v1/api.proto +++ b/utt/wallet-cli/proto/api/v1/api.proto @@ -90,6 +90,7 @@ message TransferResponse { message MintRequest { optional string user_id = 1; optional uint64 value = 2; + optional bytes tx_data = 3; } message MintResponse { diff --git a/utt/wallet-cli/src/wallet.cpp b/utt/wallet-cli/src/wallet.cpp index cc03ca6a18..cf3a900e37 100644 --- a/utt/wallet-cli/src/wallet.cpp +++ b/utt/wallet-cli/src/wallet.cpp @@ -147,11 +147,14 @@ void Wallet::createPrivacyBudget() { void Wallet::mint(uint64_t amount) { if (!checkOperational()) return; + auto mintTx = user_->mint(amount); + grpc::ClientContext ctx; MintRequest req; req.set_user_id(userId_); req.set_value(amount); + req.set_tx_data(mintTx.data_.data(), mintTx.data_.size()); MintResponse resp; grpc_->mint(&ctx, req, &resp); From a34418fdb4469064c318ed30e5623b20db9cea5e Mon Sep 17 00:00:00 2001 From: Evgeniy Dzhurov Date: Fri, 14 Oct 2022 10:49:13 +0300 Subject: [PATCH 16/44] Add number of outputs to utt::Transaction --- utt/utt-client-api/src/User.cpp | 7 +++++ utt/utt-client-api/tests/TestUTTClientApi.cpp | 7 ----- .../include/utt-common-api/CommonApi.hpp | 2 ++ utt/wallet-cli/include/wallet.hpp | 2 +- utt/wallet-cli/proto/api/v1/api.proto | 10 ++++--- utt/wallet-cli/src/wallet.cpp | 26 +++++++++---------- 6 files changed, 29 insertions(+), 25 deletions(-) diff --git a/utt/utt-client-api/src/User.cpp b/utt/utt-client-api/src/User.cpp index e69e585319..71f152ba6f 100644 --- a/utt/utt-client-api/src/User.cpp +++ b/utt/utt-client-api/src/User.cpp @@ -93,6 +93,7 @@ utt::Transaction User::Impl::createTx_Self1to2(const libutt::api::Coin& coin, ui utt::Transaction tx; tx.type_ = utt::Transaction::Type::Transfer; tx.data_ = libutt::api::serialize(uttTx); + tx.numOutputs_ = uttTx.getNumOfOutputCoins(); return tx; } @@ -105,6 +106,7 @@ utt::Transaction User::Impl::createTx_Self2to1(const std::vector(uttTx); + tx.numOutputs_ = uttTx.getNumOfOutputCoins(); return tx; } @@ -118,6 +120,7 @@ utt::Transaction User::Impl::createTx_Self2to2(const std::vector(uttTx); + tx.numOutputs_ = uttTx.getNumOfOutputCoins(); return tx; } @@ -133,6 +136,7 @@ utt::Transaction User::Impl::createTx_1to1(const libutt::api::Coin& coin, utt::Transaction tx; tx.type_ = utt::Transaction::Type::Transfer; tx.data_ = libutt::api::serialize(uttTx); + tx.numOutputs_ = uttTx.getNumOfOutputCoins(); return tx; } @@ -150,6 +154,7 @@ utt::Transaction User::Impl::createTx_1to2(const libutt::api::Coin& coin, utt::Transaction tx; tx.type_ = utt::Transaction::Type::Transfer; tx.data_ = libutt::api::serialize(uttTx); + tx.numOutputs_ = uttTx.getNumOfOutputCoins(); return tx; } @@ -165,6 +170,7 @@ utt::Transaction User::Impl::createTx_2to1(const std::vector& utt::Transaction tx; tx.type_ = utt::Transaction::Type::Transfer; tx.data_ = libutt::api::serialize(uttTx); + tx.numOutputs_ = uttTx.getNumOfOutputCoins(); return tx; } @@ -182,6 +188,7 @@ utt::Transaction User::Impl::createTx_2to2(const std::vector& utt::Transaction tx; tx.type_ = utt::Transaction::Type::Transfer; tx.data_ = libutt::api::serialize(uttTx); + tx.numOutputs_ = uttTx.getNumOfOutputCoins(); return tx; } diff --git a/utt/utt-client-api/tests/TestUTTClientApi.cpp b/utt/utt-client-api/tests/TestUTTClientApi.cpp index 3f0efdf327..ac02686220 100644 --- a/utt/utt-client-api/tests/TestUTTClientApi.cpp +++ b/utt/utt-client-api/tests/TestUTTClientApi.cpp @@ -179,11 +179,6 @@ struct ServerMock { assertTrue(mintTx.getRecipentID() == userId); assertTrue(mintTx.getVal() == amount); - //auto pidHash = libutt::api::Utils::curvePointFromHash(userId); - //auto uniqueHash = "mint|" + std::to_string(++lastTokenId_); - - //auto mintTx = libutt::api::operations::Mint(uniqueHash, amount, userId); - std::vector> shares; for (const auto& signer : coinsSigners_) { shares.emplace_back(signer.sign(mintTx).front()); @@ -200,8 +195,6 @@ struct ServerMock { ExecutedTx executedTx; executedTx.tx_ = tx; - //executedTx.tx_.type_ = utt::Transaction::Type::Mint; - //executedTx.tx_.data_ = libutt::api::serialize(mintTx); executedTx.sigs_.emplace_back(libutt::api::Utils::aggregateSigShares(n, shareSubset)); executedTx.publicUserId_ = userId; ledger_.emplace_back(std::move(executedTx)); diff --git a/utt/utt-common-api/include/utt-common-api/CommonApi.hpp b/utt/utt-common-api/include/utt-common-api/CommonApi.hpp index 9c7d220061..bebd9d7f56 100644 --- a/utt/utt-common-api/include/utt-common-api/CommonApi.hpp +++ b/utt/utt-common-api/include/utt-common-api/CommonApi.hpp @@ -53,6 +53,8 @@ using TxOutputSigs = std::vector; struct Transaction { enum class Type { Undefined = 0, Mint = 1, Transfer = 2, Burn = 3 } type_ = Type::Undefined; std::vector data_; + uint32_t numOutputs_ = + 0; // [TODO-UTT] This can be removed if the virtual contract provides an API to get the number of outputs of a tx }; } // namespace utt \ No newline at end of file diff --git a/utt/wallet-cli/include/wallet.hpp b/utt/wallet-cli/include/wallet.hpp index ec63fc07cc..a268e9b27d 100644 --- a/utt/wallet-cli/include/wallet.hpp +++ b/utt/wallet-cli/include/wallet.hpp @@ -46,7 +46,7 @@ class Wallet { /// @brief Transfers the desired amount anonymously to the recipient /// @param amount The amount to transfer /// @param recipient The user id of the recipient - void transfer(uint64_t amount, const std::string recipient); + void transfer(uint64_t amount, const std::string& recipient); /// @brief Burns the desired amount of private funds and converts them to public funds. /// @param amount The amount of private funds to burn. diff --git a/utt/wallet-cli/proto/api/v1/api.proto b/utt/wallet-cli/proto/api/v1/api.proto index c6b8355800..d18a8197da 100644 --- a/utt/wallet-cli/proto/api/v1/api.proto +++ b/utt/wallet-cli/proto/api/v1/api.proto @@ -80,11 +80,12 @@ message CreatePrivacyBudgetResponse { message TransferRequest { optional bytes tx_data = 1; + optional uint32 num_outputs = 2; } message TransferResponse { optional string err = 1; - optional uint64 tx_number = 2; + optional uint64 last_added_tx_number = 2; } message MintRequest { @@ -95,7 +96,7 @@ message MintRequest { message MintResponse { optional string err = 1; - optional uint64 tx_number = 2; + optional uint64 last_added_tx_number = 2; } message BurnRequest { @@ -106,14 +107,15 @@ message BurnRequest { message BurnResponse { optional string err = 1; - optional uint64 tx_number = 2; + optional uint64 last_added_tx_number = 2; } message GetLastAddedTxNumberRequest { } message GetLastAddedTxNumberResponse { - optional uint64 tx_number = 1; + optional string err = 1; + optional uint64 tx_number = 2; } message GetLastSignedTxNumberRequest { diff --git a/utt/wallet-cli/src/wallet.cpp b/utt/wallet-cli/src/wallet.cpp index cf3a900e37..4e898a65a3 100644 --- a/utt/wallet-cli/src/wallet.cpp +++ b/utt/wallet-cli/src/wallet.cpp @@ -162,13 +162,13 @@ void Wallet::mint(uint64_t amount) { if (resp.has_err()) { std::cout << "Failed to mint:" << resp.err() << '\n'; } else { - std::cout << "Successfully sent mint tx with number:" << resp.tx_number() << '\n'; + std::cout << "Successfully sent mint tx. Last added tx number:" << resp.last_added_tx_number() << '\n'; - syncState(resp.tx_number()); + syncState(resp.last_added_tx_number()); } } -void Wallet::transfer(uint64_t amount, const std::string recipient) { +void Wallet::transfer(uint64_t amount, const std::string& recipient) { if (!checkOperational()) return; if (userId_ == recipient) { @@ -204,9 +204,9 @@ void Wallet::transfer(uint64_t amount, const std::string recipient) { if (resp.has_err()) { std::cout << "Failed to transfer:" << resp.err() << '\n'; } else { - std::cout << "Successfully sent transfer tx with number:" << resp.tx_number() << '\n'; + std::cout << "Successfully sent transfer tx. Last added tx number:" << resp.last_added_tx_number() << '\n'; - syncState(resp.tx_number()); + syncState(resp.last_added_tx_number()); } if (result.isFinal_) break; // Done @@ -244,9 +244,9 @@ void Wallet::burn(uint64_t amount) { if (resp.has_err()) { std::cout << "Failed to burn:" << resp.err() << '\n'; } else { - std::cout << "Successfully sent burn tx with number:" << resp.tx_number() << '\n'; + std::cout << "Successfully sent burn tx. Last added tx number:" << resp.last_added_tx_number() << '\n'; - syncState(resp.tx_number()); + syncState(resp.last_added_tx_number()); } if (result.isFinal_) break; // Done @@ -268,17 +268,17 @@ void Wallet::syncState(uint64_t lastKnownTxNum) { // Sync to latest state if (lastKnownTxNum == 0) { - std::cout << "Last known tx number is zero (or not provided) - fetching last signed tx number...\n"; + std::cout << "Last known tx number is zero (or not provided) - fetching last added tx number...\n"; grpc::ClientContext ctx; - GetLastSignedTxNumberRequest req; - GetLastSignedTxNumberResponse resp; - grpc_->getLastSignedTxNumber(&ctx, req, &resp); + GetLastAddedTxNumberRequest req; + GetLastAddedTxNumberResponse resp; + grpc_->getLastAddedTxNumber(&ctx, req, &resp); if (resp.has_err()) { - std::cout << "Failed to get last signed tx number:" << resp.err() << '\n'; + std::cout << "Failed to get last added tx number:" << resp.err() << '\n'; } else { - std::cout << "Got last signed tx number:" << resp.tx_number() << '\n'; + std::cout << "Got last added tx number:" << resp.tx_number() << '\n'; lastKnownTxNum = resp.tx_number(); } } From 7ac2db17075f209fd929b789f7236842c4afbfee Mon Sep 17 00:00:00 2001 From: Evgeniy Dzhurov Date: Sat, 15 Oct 2022 09:09:37 +0300 Subject: [PATCH 17/44] TestUTTClientApi now relies on the fact that updateBurnTx and updateMintTx ignore mints/burns intended for a different user when synchronizing --- utt/utt-client-api/tests/TestUTTClientApi.cpp | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/utt/utt-client-api/tests/TestUTTClientApi.cpp b/utt/utt-client-api/tests/TestUTTClientApi.cpp index ac02686220..4e56e37a45 100644 --- a/utt/utt-client-api/tests/TestUTTClientApi.cpp +++ b/utt/utt-client-api/tests/TestUTTClientApi.cpp @@ -51,8 +51,6 @@ struct PrivacyBudgetResponse { struct ExecutedTx { utt::Transaction tx_; utt::TxOutputSigs sigs_; - std::string publicUserId_; // Burns and mints are public transactions that expose the user id, we save it here for - // convenience }; struct ServerMock { @@ -196,13 +194,12 @@ struct ServerMock { ExecutedTx executedTx; executedTx.tx_ = tx; executedTx.sigs_.emplace_back(libutt::api::Utils::aggregateSigShares(n, shareSubset)); - executedTx.publicUserId_ = userId; ledger_.emplace_back(std::move(executedTx)); return ledger_.size(); } - uint64_t burn(const std::string& userId, const utt::Transaction& tx) { + uint64_t burn(const utt::Transaction& tx) { assertTrue(tx.type_ == utt::Transaction::Type::Burn); auto burn = libutt::api::deserialize(tx.data_); @@ -213,7 +210,6 @@ struct ServerMock { ExecutedTx executedTx; executedTx.tx_.type_ = utt::Transaction::Type::Burn; executedTx.tx_.data_ = libutt::api::serialize(burn); - executedTx.publicUserId_ = userId; ledger_.emplace_back(std::move(executedTx)); return ledger_.size(); @@ -296,20 +292,12 @@ int main(int argc, char* argv[]) { const auto& executedTx = serverMock.getExecutedTx(txNum); switch (executedTx.tx_.type_) { case utt::Transaction::Type::Mint: { - if (executedTx.publicUserId_ == users[i]->getUserId()) { - assertTrue(executedTx.sigs_.size() == 1); - users[i]->updateMintTx(txNum, executedTx.tx_, executedTx.sigs_.front()); - } else { - users[i]->updateNoOp(txNum); - } + assertTrue(executedTx.sigs_.size() == 1); + users[i]->updateMintTx(txNum, executedTx.tx_, executedTx.sigs_.front()); } break; case utt::Transaction::Type::Burn: { - if (executedTx.publicUserId_ == users[i]->getUserId()) { assertTrue(executedTx.sigs_.empty()); users[i]->updateBurnTx(txNum, executedTx.tx_); - } else { - users[i]->updateNoOp(txNum); - } } break; case utt::Transaction::Type::Transfer: { assertFalse(executedTx.sigs_.empty()); @@ -406,7 +394,7 @@ int main(int argc, char* argv[]) { auto result = users[i]->burn(balance); if (result.requiredTx_.type_ == utt::Transaction::Type::Burn) { assertTrue(result.isFinal_); - auto txNum = serverMock.burn(users[i]->getUserId(), result.requiredTx_); + auto txNum = serverMock.burn(result.requiredTx_); assertTrue(txNum == serverMock.getLastExecutedTxNum()); syncUsersWithServer(); break; // We can stop processing after burning the coin From 54de749617d45ba5de011ff3cdb32e80ba701bb1 Mon Sep 17 00:00:00 2001 From: Evgeniy Dzhurov Date: Sun, 16 Oct 2022 16:17:22 +0300 Subject: [PATCH 18/44] Multiplex multiple users in the same wallet for initial testing purposes --- utt/libutt/src/api/client.cpp | 2 + utt/utt-client-api/tests/TestUTTClientApi.cpp | 4 +- utt/wallet-cli/include/wallet.hpp | 40 +++--- utt/wallet-cli/src/main.cpp | 116 +++++++++--------- utt/wallet-cli/src/wallet.cpp | 90 +++++--------- 5 files changed, 109 insertions(+), 143 deletions(-) diff --git a/utt/libutt/src/api/client.cpp b/utt/libutt/src/api/client.cpp index bb7967f8f9..8f6fb06c82 100644 --- a/utt/libutt/src/api/client.cpp +++ b/utt/libutt/src/api/client.cpp @@ -77,6 +77,8 @@ void Client::setRCMSig(const UTTParams& d, const types::CurvePoint& s2, const ty rcm_ = Commitment(d, Commitment::Type::REGISTRATION, m, true); rcm_sig_ = sig; ask_->rs = libutt::deserialize(sig); + if (!ask_->rs.verify(*rcm_.comm_, rpk_->vk)) + throw std::runtime_error("setRCMSig - failed to verify rcm against signature!"); } std::pair Client::getRcm() const { diff --git a/utt/utt-client-api/tests/TestUTTClientApi.cpp b/utt/utt-client-api/tests/TestUTTClientApi.cpp index 4e56e37a45..f110c0762e 100644 --- a/utt/utt-client-api/tests/TestUTTClientApi.cpp +++ b/utt/utt-client-api/tests/TestUTTClientApi.cpp @@ -296,8 +296,8 @@ int main(int argc, char* argv[]) { users[i]->updateMintTx(txNum, executedTx.tx_, executedTx.sigs_.front()); } break; case utt::Transaction::Type::Burn: { - assertTrue(executedTx.sigs_.empty()); - users[i]->updateBurnTx(txNum, executedTx.tx_); + assertTrue(executedTx.sigs_.empty()); + users[i]->updateBurnTx(txNum, executedTx.tx_); } break; case utt::Transaction::Type::Transfer: { assertFalse(executedTx.sigs_.empty()); diff --git a/utt/wallet-cli/include/wallet.hpp b/utt/wallet-cli/include/wallet.hpp index a268e9b27d..cf975d2501 100644 --- a/utt/wallet-cli/include/wallet.hpp +++ b/utt/wallet-cli/include/wallet.hpp @@ -23,58 +23,48 @@ class Wallet { public: - Wallet(std::string userId, utt::client::TestUserPKInfrastructure& pki); + using Connection = std::unique_ptr; + static Connection newConnection(); - void showInfo() const; - - /// @brief Deploy a privacy application /// [TODO-UTT] Should be performed by an admin app - void deployApp(); + /// @brief Deploy a privacy application + /// @return The public configuration of the deployed application + static utt::PublicConfig deployApp(Connection& conn); + + Wallet(std::string userId, utt::client::TestUserPKInfrastructure& pki, const utt::PublicConfig& config); + + void showInfo() const; /// @brief Request registration of the current user - void registerUser(); + void registerUser(Connection& conn); /// @brief Request the creation of a privacy budget. The amount of the budget is predetermined by the deployed app. /// This operation could be performed entirely by an administrator, but we add it in the wallet /// for demo purposes. - void createPrivacyBudget(); + void createPrivacyBudget(Connection& conn); /// @brief Requests the minting of public funds to private funds. /// @param amount the amount of public funds - void mint(uint64_t amount); + void mint(Connection& conn, uint64_t amount); /// @brief Transfers the desired amount anonymously to the recipient /// @param amount The amount to transfer /// @param recipient The user id of the recipient - void transfer(uint64_t amount, const std::string& recipient); + void transfer(Connection& conn, uint64_t amount, const std::string& recipient); /// @brief Burns the desired amount of private funds and converts them to public funds. /// @param amount The amount of private funds to burn. - void burn(uint64_t amount); + void burn(Connection& conn, uint64_t amount); private: - void connect(); - /// @brief Sync up to the last known tx number. If the last known tx number is zero (or not provided) the /// last signed transaction number will be fetched from the system - void syncState(uint64_t lastKnownTxNum = 0); - - // [TODO-UTT] This is a helper method to check if the user has been created successfully after generating - // a privacy app config. Since we don't have access to the public config of an already deployed privacy app - // we need to deploy it from the wallet first. This also means that currently we can test only with a single wallet - // because a second wallet would also need to deploy an app to have access to the config. This needs to be - // changed so we can deploy an app and then provide the public config to one or more wallets and create - // the users as part of initialization. - bool checkOperational() const; + void syncState(Connection& conn, uint64_t lastKnownTxNum = 0); struct DummyUserStorage : public utt::client::IUserStorage {}; - using GrpcService = vmware::concord::utt::wallet::api::v1::WalletService::Stub; - - bool isOperational_ = false; DummyUserStorage storage_; std::string userId_; utt::client::TestUserPKInfrastructure& pki_; std::unique_ptr user_; - std::unique_ptr grpc_; }; \ No newline at end of file diff --git a/utt/wallet-cli/src/main.cpp b/utt/wallet-cli/src/main.cpp index b9349b0844..cb0d392aa8 100644 --- a/utt/wallet-cli/src/main.cpp +++ b/utt/wallet-cli/src/main.cpp @@ -59,52 +59,31 @@ void printHelp() { std::cout << "\nCommands:\n"; - std::cout << "deploy app -- generates a privacy app config, deploys it on the blockchain and creates a user.\n"; - std::cout << "info -- prints information about the user managed by this wallet\n"; - std::cout << "register -- requests user registration required for spending coins\n"; - std::cout << "create budget -- requests creation of a privacy budget, the amount is decided by the system.\n"; - std::cout << "mint -- mint the requested amount of public funds.\n"; - std::cout << "transfer -- transfers the specified amount to the user-id as a recipient.\n"; - std::cout << "burn -- burns the specified amount of private funds to public funds.\n"; + std::cout << "deploy -- generates a privacy app config, deploys it on the blockchain and creates " + "test users.\n"; + std::cout << "info -- prints information about the user managed by this wallet\n"; + std::cout << "register -- requests user registration required for spending coins\n"; + std::cout + << "create-budget -- requests creation of a privacy budget, the amount is decided by the system.\n"; + std::cout << "mint -- mint the requested amount of public funds.\n"; + std::cout << "transfer -- transfers the specified amount between users.\n"; + std::cout << "burn -- burns the specified amount of private funds to public funds.\n"; std::cout << '\n'; } int main(int argc, char* argv[]) { - if (argc != 2) { - std::cout << "Usage: specify the user id\n"; - return 0; - } - + (void)argc; + (void)argv; std::cout << "Sample Privacy Wallet CLI Application.\n"; - auto userId = std::string(argv[1]); - - utt::client::TestUserPKInfrastructure pki; - auto userIds = pki.getUserIds(); - - auto checkValidUserId = [&userIds](const std::string& userId) { - auto it = std::find(userIds.begin(), userIds.end(), userId); - - if (it == userIds.end()) { - std::cout << "Use one of the following valid user ids: ["; - for (const auto& userId : userIds) std::cout << userId << ' '; - std::cout << "]\n"; - return false; - } - - return true; - }; - - // Check that we're creating a wallet with a valid user-id - if (!checkValidUserId(userId)) return 0; + bool deployed = false; try { utt::client::Initialize(); - auto wallet = Wallet(userId, pki); + std::map wallets; - std::cout << "WARNING: The wallet will not be operational until you perform the 'deploy app' command. This is a " - "temporary solution and will be changed.\n"; + auto conn = Wallet::newConnection(); while (true) { std::cout << "\nEnter command (type 'h' for commands 'Ctr-D' to quit):\n > "; @@ -118,14 +97,23 @@ int main(int argc, char* argv[]) { if (cmd == "h") { printHelp(); - } else if (cmd == "deploy app") { - wallet.deployApp(); - } else if (cmd == "register") { - wallet.registerUser(); - } else if (cmd == "create budget") { - wallet.createPrivacyBudget(); - } else if (cmd == "info") { - wallet.showInfo(); + } else if (cmd == "deploy") { + if (deployed) { + std::cout << "The privacy app is already deployed.\n"; + } else { + auto publicConfig = Wallet::deployApp(conn); + + utt::client::TestUserPKInfrastructure pki; + auto testUserIds = pki.getUserIds(); + for (const auto& userId : testUserIds) { + std::cout << "Creating test user with id '" << userId << "'\n"; + wallets.emplace(userId, Wallet(userId, pki, publicConfig)); + } + + deployed = true; + } + } else if (!deployed) { + std::cout << "You must first deploy the privacy application. Use the 'deploy' command.\n"; } else { // Tokenize params std::vector cmdTokens; @@ -134,37 +122,55 @@ int main(int argc, char* argv[]) { while (std::getline(ss, token, ' ')) cmdTokens.emplace_back(token); if (!cmdTokens.empty()) { - if (cmdTokens[0] == "mint") { + if (cmdTokens[0] == "register") { if (cmdTokens.size() != 2) { - std::cout << "Expected the mint amount as an argument!\n"; + std::cout << "Usage: register \n"; } else { - int amount = std::atoi(cmdTokens[1].c_str()); + wallets.at(cmdTokens[1]).registerUser(conn); + } + } else if (cmdTokens[0] == "create-budget") { + if (cmdTokens.size() != 2) { + std::cout << "Usage: create-budget \n"; + } else { + wallets.at(cmdTokens[1]).createPrivacyBudget(conn); + } + } else if (cmdTokens[0] == "info") { + if (cmdTokens.size() != 2) { + std::cout << "Usage: info \n"; + } else { + wallets.at(cmdTokens[1]).showInfo(); + } + } else if (cmdTokens[0] == "mint") { + if (cmdTokens.size() != 3) { + std::cout << "Usage: mint \n"; + } else { + int amount = std::atoi(cmdTokens[2].c_str()); if (amount <= 0) { std::cout << "Expected a positive mint amount!\n"; } else { - wallet.mint((uint64_t)amount); + wallets.at(cmdTokens[1]).mint(conn, (uint64_t)amount); } } } else if (cmdTokens[0] == "transfer") { - if (cmdTokens.size() != 3) { - std::cout << "Expected the transfer amount and recipient user id as arguments!\n"; + if (cmdTokens.size() != 4) { + std::cout << "Usage: transfer \n"; } else { int amount = std::atoi(cmdTokens[1].c_str()); if (amount <= 0) { std::cout << "Expected a positive transfer amount!\n"; - } else if (checkValidUserId(cmdTokens[2])) { - wallet.transfer((uint64_t)amount, cmdTokens[2]); + } else { + wallets.at(cmdTokens[2]).transfer(conn, (uint64_t)amount, cmdTokens[3]); } } } else if (cmdTokens[0] == "burn") { - if (cmdTokens.size() != 2) { - std::cout << "Expected the burn amount as an argument!\n"; + if (cmdTokens.size() != 3) { + std::cout << "Usage: burn \n"; } else { - int amount = std::atoi(cmdTokens[1].c_str()); + int amount = std::atoi(cmdTokens[2].c_str()); if (amount <= 0) { std::cout << "Expected a positive burn amount!\n"; } else { - wallet.burn((uint64_t)amount); + wallets.at(cmdTokens[1]).burn(conn, (uint64_t)amount); } } } else { diff --git a/utt/wallet-cli/src/wallet.cpp b/utt/wallet-cli/src/wallet.cpp index 4e898a65a3..f8fc5874c6 100644 --- a/utt/wallet-cli/src/wallet.cpp +++ b/utt/wallet-cli/src/wallet.cpp @@ -17,11 +17,13 @@ using namespace vmware::concord::utt::wallet::api::v1; -Wallet::Wallet(std::string userId, utt::client::TestUserPKInfrastructure& pki) : userId_{std::move(userId)}, pki_{pki} { - connect(); +Wallet::Wallet(std::string userId, utt::client::TestUserPKInfrastructure& pki, const utt::PublicConfig& config) + : userId_{std::move(userId)}, pki_{pki} { + user_ = utt::client::createUser(userId_, config, pki_, storage_); + if (!user_) throw std::runtime_error("Failed to create user!"); } -void Wallet::connect() { +Wallet::Connection Wallet::newConnection() { std::string grpcServerAddr = "127.0.0.1:49000"; std::cout << "Connecting to gRPC server at " << grpcServerAddr << " ...\n"; @@ -39,24 +41,17 @@ void Wallet::connect() { " seconds."); } - grpc_ = WalletService::NewStub(chan); + return WalletService::NewStub(chan); } void Wallet::showInfo() const { std::cout << "User Id: " << userId_ << '\n'; - - if (!checkOperational()) return; std::cout << "Private balance: " << user_->getBalance() << '\n'; std::cout << "Privacy budget: " << user_->getPrivacyBudget() << '\n'; std::cout << "Last executed transaction number: " << user_->getLastExecutedTxNum() << '\n'; } -void Wallet::deployApp() { - if (isOperational_) { - std::cout << "The wallet has already deployed an app and is operational.\n"; - return; - } - +utt::PublicConfig Wallet::deployApp(Connection& conn) { // Generate a privacy config for a N=4 replica system tolerating F=1 failures utt::client::ConfigInputParams params; params.validatorPublicKeys = std::vector{4, "placeholderPublicKey"}; // N = 3 * F + 1 @@ -70,28 +65,19 @@ void Wallet::deployApp() { req.set_config(config.data(), config.size()); DeployPrivacyAppResponse resp; - grpc_->deployPrivacyApp(&ctx, req, &resp); + conn->deployPrivacyApp(&ctx, req, &resp); // Note that keeping the config around in memory is just a temp solution and should not happen in real system - if (resp.has_err()) { - std::cout << "Failed to deploy privacy app: " << resp.err() << '\n'; - } else if (resp.app_id().empty()) { - std::cout << "Failed to deploy privacy app: empty app id!\n"; - } else { - // We need the public config part which can typically be obtained from the service, but we derive it for simplicity - auto publicConfig = utt::client::getPublicConfig(config); - std::cout << "Successfully deployed privacy app with id: " << resp.app_id() << '\n'; + if (resp.has_err()) throw std::runtime_error("Failed to deploy privacy app: " + resp.err()); + if (resp.app_id().empty()) throw std::runtime_error("Failed to deploy privacy app: empty app id!"); - user_ = utt::client::createUser(userId_, publicConfig, pki_, storage_); - if (!user_) throw std::runtime_error("Failed to create user!"); + // We need the public config part which can typically be obtained from the service, but we derive it for simplicity + std::cout << "Successfully deployed privacy app with id: " << resp.app_id() << '\n'; - isOperational_ = true; - } + return utt::client::getPublicConfig(config); } -void Wallet::registerUser() { - if (!checkOperational()) return; - +void Wallet::registerUser(Connection& conn) { auto userRegInput = user_->getRegistrationInput(); if (userRegInput.empty()) throw std::runtime_error("Failed to create user registration input!"); @@ -103,7 +89,7 @@ void Wallet::registerUser() { req.set_user_pk(user_->getPK()); RegisterUserResponse resp; - grpc_->registerUser(&ctx, req, &resp); + conn->registerUser(&ctx, req, &resp); if (resp.has_err()) { std::cout << "Failed to register user: " << resp.err() << '\n'; @@ -120,16 +106,14 @@ void Wallet::registerUser() { } } -void Wallet::createPrivacyBudget() { - if (!checkOperational()) return; - +void Wallet::createPrivacyBudget(Connection& conn) { grpc::ClientContext ctx; CreatePrivacyBudgetRequest req; req.set_user_id(userId_); CreatePrivacyBudgetResponse resp; - grpc_->createPrivacyBudget(&ctx, req, &resp); + conn->createPrivacyBudget(&ctx, req, &resp); if (resp.has_err()) { std::cout << "Failed to create privacy budget:" << resp.err() << '\n'; @@ -144,9 +128,7 @@ void Wallet::createPrivacyBudget() { } } -void Wallet::mint(uint64_t amount) { - if (!checkOperational()) return; - +void Wallet::mint(Connection& conn, uint64_t amount) { auto mintTx = user_->mint(amount); grpc::ClientContext ctx; @@ -157,20 +139,18 @@ void Wallet::mint(uint64_t amount) { req.set_tx_data(mintTx.data_.data(), mintTx.data_.size()); MintResponse resp; - grpc_->mint(&ctx, req, &resp); + conn->mint(&ctx, req, &resp); if (resp.has_err()) { std::cout << "Failed to mint:" << resp.err() << '\n'; } else { std::cout << "Successfully sent mint tx. Last added tx number:" << resp.last_added_tx_number() << '\n'; - syncState(resp.last_added_tx_number()); + syncState(conn, resp.last_added_tx_number()); } } -void Wallet::transfer(uint64_t amount, const std::string& recipient) { - if (!checkOperational()) return; - +void Wallet::transfer(Connection& conn, uint64_t amount, const std::string& recipient) { if (userId_ == recipient) { std::cout << "Cannot transfer to self directly!\n"; return; @@ -199,23 +179,21 @@ void Wallet::transfer(uint64_t amount, const std::string& recipient) { req.set_tx_data(result.requiredTx_.data_.data(), result.requiredTx_.data_.size()); TransferResponse resp; - grpc_->transfer(&ctx, req, &resp); + conn->transfer(&ctx, req, &resp); if (resp.has_err()) { std::cout << "Failed to transfer:" << resp.err() << '\n'; } else { std::cout << "Successfully sent transfer tx. Last added tx number:" << resp.last_added_tx_number() << '\n'; - syncState(resp.last_added_tx_number()); + syncState(conn, resp.last_added_tx_number()); } if (result.isFinal_) break; // Done } } -void Wallet::burn(uint64_t amount) { - if (!checkOperational()) return; - +void Wallet::burn(Connection& conn, uint64_t amount) { if (user_->getBalance() < amount) { std::cout << "Insufficient private balance!\n"; return; @@ -239,31 +217,21 @@ void Wallet::burn(uint64_t amount) { req.set_tx_data(result.requiredTx_.data_.data(), result.requiredTx_.data_.size()); BurnResponse resp; - grpc_->burn(&ctx, req, &resp); + conn->burn(&ctx, req, &resp); if (resp.has_err()) { std::cout << "Failed to burn:" << resp.err() << '\n'; } else { std::cout << "Successfully sent burn tx. Last added tx number:" << resp.last_added_tx_number() << '\n'; - syncState(resp.last_added_tx_number()); + syncState(conn, resp.last_added_tx_number()); } if (result.isFinal_) break; // Done } } -bool Wallet::checkOperational() const { - if (!isOperational_) { - std::cout << "You must first deploy a privacy app. Use command 'deploy app'\n"; - return false; - } - return true; -} - -void Wallet::syncState(uint64_t lastKnownTxNum) { - if (!checkOperational()) return; - +void Wallet::syncState(Connection& conn, uint64_t lastKnownTxNum) { std::cout << "Synchronizing state...\n"; // Sync to latest state @@ -273,7 +241,7 @@ void Wallet::syncState(uint64_t lastKnownTxNum) { grpc::ClientContext ctx; GetLastAddedTxNumberRequest req; GetLastAddedTxNumberResponse resp; - grpc_->getLastAddedTxNumber(&ctx, req, &resp); + conn->getLastAddedTxNumber(&ctx, req, &resp); if (resp.has_err()) { std::cout << "Failed to get last added tx number:" << resp.err() << '\n'; @@ -290,7 +258,7 @@ void Wallet::syncState(uint64_t lastKnownTxNum) { req.set_tx_number(txNum); GetSignedTransactionResponse resp; - grpc_->getSignedTransaction(&ctx, req, &resp); + conn->getSignedTransaction(&ctx, req, &resp); if (resp.has_err()) { std::cout << "Failed to get signed tx with number " << txNum << ':' << resp.err() << '\n'; From 17ac7b95c61937589b9ff03d73edabd7882e4978 Mon Sep 17 00:00:00 2001 From: Evgeniy Dzhurov Date: Tue, 18 Oct 2022 09:19:50 +0300 Subject: [PATCH 19/44] Rename wallet command 'info' to 'show' --- utt/wallet-cli/src/main.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utt/wallet-cli/src/main.cpp b/utt/wallet-cli/src/main.cpp index cb0d392aa8..2da5e1baab 100644 --- a/utt/wallet-cli/src/main.cpp +++ b/utt/wallet-cli/src/main.cpp @@ -61,7 +61,7 @@ void printHelp() { std::cout << "\nCommands:\n"; std::cout << "deploy -- generates a privacy app config, deploys it on the blockchain and creates " "test users.\n"; - std::cout << "info -- prints information about the user managed by this wallet\n"; + std::cout << "show -- prints information about the user managed by this wallet\n"; std::cout << "register -- requests user registration required for spending coins\n"; std::cout << "create-budget -- requests creation of a privacy budget, the amount is decided by the system.\n"; @@ -134,9 +134,9 @@ int main(int argc, char* argv[]) { } else { wallets.at(cmdTokens[1]).createPrivacyBudget(conn); } - } else if (cmdTokens[0] == "info") { + } else if (cmdTokens[0] == "show") { if (cmdTokens.size() != 2) { - std::cout << "Usage: info \n"; + std::cout << "Usage: show \n"; } else { wallets.at(cmdTokens[1]).showInfo(); } From 66c7c2267486e678c01a35cebe74c21bf9340164 Mon Sep 17 00:00:00 2001 From: Evgeniy Dzhurov Date: Tue, 18 Oct 2022 16:20:08 +0300 Subject: [PATCH 20/44] Add a way to pre-create budget tokens in the sample wallet --- .../include/utt-client-api/User.hpp | 6 ++ utt/utt-client-api/src/User.cpp | 62 +++++++++++++++++ utt/utt-client-api/tests/TestUTTClientApi.cpp | 10 +++ utt/wallet-cli/include/wallet.hpp | 10 ++- utt/wallet-cli/src/main.cpp | 7 +- utt/wallet-cli/src/wallet.cpp | 69 +++++++++++++------ 6 files changed, 138 insertions(+), 26 deletions(-) diff --git a/utt/utt-client-api/include/utt-client-api/User.hpp b/utt/utt-client-api/include/utt-client-api/User.hpp index a57cf1f818..cb5c5b10f5 100644 --- a/utt/utt-client-api/include/utt-client-api/User.hpp +++ b/utt/utt-client-api/include/utt-client-api/User.hpp @@ -46,6 +46,9 @@ class User { User(); // Default empty user object ~User(); + /// @brief Creates a privacy budget locally - used only for testing. + void preCreatePrivacyBudget(const utt::Configuration& config, uint64_t amount); + /// @brief Creates an input registration commitment. Multiple calls generate the same object. /// @return The user's registration input object UserRegistrationInput getRegistrationInput() const; @@ -110,6 +113,9 @@ class User { /// @brief Creates a transaction to mint the requested amount utt::Transaction mint(uint64_t amount) const; + /// @brief Creates a transaction to mint the requested budget amount + utt::Transaction mintPrivacyBudget(uint64_t amount) const; + /// @brief Ask to burn some amount of tokens. This function needs to be called repeatedly until the final burn /// transaction is produced. /// @param amount The amount of private funds to burn. diff --git a/utt/utt-client-api/src/User.cpp b/utt/utt-client-api/src/User.cpp index 71f152ba6f..e301f99e70 100644 --- a/utt/utt-client-api/src/User.cpp +++ b/utt/utt-client-api/src/User.cpp @@ -409,6 +409,68 @@ utt::Transaction User::mint(uint64_t amount) const { return tx; } +void User::preCreatePrivacyBudget(const utt::Configuration& config, uint64_t amount) { + if (config.empty()) throw std::runtime_error("Privacy app config cannot be empty!"); + if (amount == 0) throw std::runtime_error("Positive privacy budget amount required!"); + + const auto& pid = pImpl_->client_->getPid(); + + loginfo << "Pre-creating " << amount << " privacy budget for '" << pid << "'\n"; + + auto uttConfig = libutt::api::deserialize(config); + + auto budget = libutt::api::operations::Budget(pImpl_->params_, *pImpl_->client_, amount, 0 /*expirationDate*/); + + // auto pidHash = libutt::api::Utils::curvePointFromHash(pImpl_->client_->getPid()); + // auto snHash = libutt::api::Utils::curvePointFromHash("budget|" + pid); + + // auto budget = libutt::api::operations::Budget(uttConfig.getPublicConfig().getParams(), snHash, pidHash, amount, 0); + + // Create coins signers from config + auto commitVerificationKey = uttConfig.getPublicConfig().getCommitVerificationKey(); + auto registrationVerificationKey = uttConfig.getPublicConfig().getRegistrationVerificationKey(); + std::map commitVerificationKeyShares; + std::map registrationVerificationKeyShares; + std::vector coinsSigners; + + for (uint16_t i = 0; i < uttConfig.getNumValidators(); ++i) { + commitVerificationKeyShares.emplace(i, uttConfig.getCommitVerificationKeyShare(i)); + registrationVerificationKeyShares.emplace(i, uttConfig.getRegistrationVerificationKeyShare(i)); + } + + // Create coins signers + for (uint16_t i = 0; i < uttConfig.getNumValidators(); ++i) { + coinsSigners.emplace_back(i, + uttConfig.getCommitSecret(i), + commitVerificationKey, + commitVerificationKeyShares, + registrationVerificationKey); + } + + // Sign budget signature + const uint16_t n = uttConfig.getNumValidators(); + const uint16_t t = uttConfig.getThreshold(); + + // We just need threshold signers to sign + std::map> shareSubset; + for (size_t idx = 0; idx < t; ++idx) { + shareSubset.emplace((uint32_t)idx, coinsSigners[idx].sign(budget).front()); + } + + auto sig = libutt::api::Utils::aggregateSigShares(n, shareSubset); + + // Claim and update + auto claimedCoins = + pImpl_->client_->claimCoins(budget, pImpl_->params_, std::vector{sig}); + + // Expect a single budget token to be claimed by the user + if (claimedCoins.size() != 1) throw std::runtime_error("Expected single budget token!"); + if (!pImpl_->client_->validate(claimedCoins[0])) throw std::runtime_error("Invalid pre-created budget coin!"); + + // [TODO-UTT] Requires atomic, durable write through IUserStorage + pImpl_->budgetCoin_ = claimedCoins[0]; +} + BurnResult User::burn(uint64_t amount) const { if (!pImpl_->client_) throw std::runtime_error("User not initialized!"); if (amount == 0) throw std::runtime_error("Burn amount must be positive!"); diff --git a/utt/utt-client-api/tests/TestUTTClientApi.cpp b/utt/utt-client-api/tests/TestUTTClientApi.cpp index f110c0762e..a293659a8c 100644 --- a/utt/utt-client-api/tests/TestUTTClientApi.cpp +++ b/utt/utt-client-api/tests/TestUTTClientApi.cpp @@ -203,6 +203,11 @@ struct ServerMock { assertTrue(tx.type_ == utt::Transaction::Type::Burn); auto burn = libutt::api::deserialize(tx.data_); + + for (const auto& signer : coinsSigners_) { + assertTrue(signer.validate(config_->getPublicConfig().getParams(), burn)); + } + auto null = burn.getNullifier(); assertTrue(nullifiers_.count(null) == 0); nullifiers_.emplace(std::move(null)); @@ -219,6 +224,11 @@ struct ServerMock { assertTrue(tx.type_ == utt::Transaction::Type::Transfer); auto uttTx = libutt::api::deserialize(tx.data_); + + for (const auto& signer : coinsSigners_) { + assertTrue(signer.validate(config_->getPublicConfig().getParams(), uttTx)); + } + for (auto&& null : uttTx.getNullifiers()) { assertTrue(nullifiers_.count(null) == 0); nullifiers_.emplace(std::move(null)); diff --git a/utt/wallet-cli/include/wallet.hpp b/utt/wallet-cli/include/wallet.hpp index cf975d2501..0c1bb3f794 100644 --- a/utt/wallet-cli/include/wallet.hpp +++ b/utt/wallet-cli/include/wallet.hpp @@ -29,11 +29,17 @@ class Wallet { /// [TODO-UTT] Should be performed by an admin app /// @brief Deploy a privacy application /// @return The public configuration of the deployed application - static utt::PublicConfig deployApp(Connection& conn); + static std::pair deployApp(Connection& conn); + + /// [TODO-UTT] Create privacy budget locally because the system can't process budget requests yet. + /// @brief Create a privacy budget locally for the user. This function is only for testing. + /// @param config A Privacy app configuration + /// @param amount The amount of privacy budget to create + void preCreatePrivacyBudget(const utt::Configuration& config, uint64_t amount); Wallet(std::string userId, utt::client::TestUserPKInfrastructure& pki, const utt::PublicConfig& config); - void showInfo() const; + void showInfo(Connection& conn); /// @brief Request registration of the current user void registerUser(Connection& conn); diff --git a/utt/wallet-cli/src/main.cpp b/utt/wallet-cli/src/main.cpp index 2da5e1baab..70ec0e11a4 100644 --- a/utt/wallet-cli/src/main.cpp +++ b/utt/wallet-cli/src/main.cpp @@ -101,13 +101,14 @@ int main(int argc, char* argv[]) { if (deployed) { std::cout << "The privacy app is already deployed.\n"; } else { - auto publicConfig = Wallet::deployApp(conn); + auto configs = Wallet::deployApp(conn); utt::client::TestUserPKInfrastructure pki; auto testUserIds = pki.getUserIds(); for (const auto& userId : testUserIds) { std::cout << "Creating test user with id '" << userId << "'\n"; - wallets.emplace(userId, Wallet(userId, pki, publicConfig)); + wallets.emplace(userId, Wallet(userId, pki, configs.second)); + wallets.at(userId).preCreatePrivacyBudget(configs.first, 10000); } deployed = true; @@ -138,7 +139,7 @@ int main(int argc, char* argv[]) { if (cmdTokens.size() != 2) { std::cout << "Usage: show \n"; } else { - wallets.at(cmdTokens[1]).showInfo(); + wallets.at(cmdTokens[1]).showInfo(conn); } } else if (cmdTokens[0] == "mint") { if (cmdTokens.size() != 3) { diff --git a/utt/wallet-cli/src/wallet.cpp b/utt/wallet-cli/src/wallet.cpp index f8fc5874c6..b6b6d21d8a 100644 --- a/utt/wallet-cli/src/wallet.cpp +++ b/utt/wallet-cli/src/wallet.cpp @@ -44,14 +44,16 @@ Wallet::Connection Wallet::newConnection() { return WalletService::NewStub(chan); } -void Wallet::showInfo() const { +void Wallet::showInfo(Connection& conn) { + syncState(conn); + std::cout << "User Id: " << userId_ << '\n'; std::cout << "Private balance: " << user_->getBalance() << '\n'; std::cout << "Privacy budget: " << user_->getPrivacyBudget() << '\n'; std::cout << "Last executed transaction number: " << user_->getLastExecutedTxNum() << '\n'; } -utt::PublicConfig Wallet::deployApp(Connection& conn) { +std::pair Wallet::deployApp(Connection& conn) { // Generate a privacy config for a N=4 replica system tolerating F=1 failures utt::client::ConfigInputParams params; params.validatorPublicKeys = std::vector{4, "placeholderPublicKey"}; // N = 3 * F + 1 @@ -74,7 +76,11 @@ utt::PublicConfig Wallet::deployApp(Connection& conn) { // We need the public config part which can typically be obtained from the service, but we derive it for simplicity std::cout << "Successfully deployed privacy app with id: " << resp.app_id() << '\n'; - return utt::client::getPublicConfig(config); + return std::pair{config, utt::client::getPublicConfig(config)}; +} + +void Wallet::preCreatePrivacyBudget(const utt::Configuration& config, uint64_t amount) { + user_->preCreatePrivacyBudget(config, amount); } void Wallet::registerUser(Connection& conn) { @@ -166,7 +172,7 @@ void Wallet::transfer(Connection& conn, uint64_t amount, const std::string& reci return; } - std::cout << "Started processing " << amount << "anonymous transfer to " << recipient << "...\n"; + std::cout << "Started processing " << amount << " anonymous transfer to " << recipient << "...\n"; // Process the transfer until we get the final transaction // On each iteration we also sync up to the tx number of our request @@ -177,6 +183,7 @@ void Wallet::transfer(Connection& conn, uint64_t amount, const std::string& reci TransferRequest req; req.set_tx_data(result.requiredTx_.data_.data(), result.requiredTx_.data_.size()); + req.set_num_outputs(result.requiredTx_.numOutputs_); TransferResponse resp; conn->transfer(&ctx, req, &resp); @@ -199,11 +206,6 @@ void Wallet::burn(Connection& conn, uint64_t amount) { return; } - if (user_->getPrivacyBudget() < amount) { - std::cout << "Insufficient privacy budget!\n"; - return; - } - std::cout << "Started processing " << amount << " burn...\n"; // Process the transfer until we get the final transaction @@ -213,21 +215,41 @@ void Wallet::burn(Connection& conn, uint64_t amount) { grpc::ClientContext ctx; - BurnRequest req; - req.set_tx_data(result.requiredTx_.data_.data(), result.requiredTx_.data_.size()); + if (result.isFinal_) { + BurnRequest req; + req.set_user_id(user_->getUserId()); + req.set_value(amount); + req.set_tx_data(result.requiredTx_.data_.data(), result.requiredTx_.data_.size()); - BurnResponse resp; - conn->burn(&ctx, req, &resp); + BurnResponse resp; + conn->burn(&ctx, req, &resp); - if (resp.has_err()) { - std::cout << "Failed to burn:" << resp.err() << '\n'; - } else { - std::cout << "Successfully sent burn tx. Last added tx number:" << resp.last_added_tx_number() << '\n'; + if (resp.has_err()) { + std::cout << "Failed to do burn:" << resp.err() << '\n'; + } else { + std::cout << "Successfully sent burn tx. Last added tx number:" << resp.last_added_tx_number() << '\n'; - syncState(conn, resp.last_added_tx_number()); - } + syncState(conn, resp.last_added_tx_number()); + } - if (result.isFinal_) break; // Done + break; // Done + } else { + TransferRequest req; + req.set_tx_data(result.requiredTx_.data_.data(), result.requiredTx_.data_.size()); + req.set_num_outputs(result.requiredTx_.numOutputs_); + TransferResponse resp; + conn->transfer(&ctx, req, &resp); + + if (resp.has_err()) { + std::cout << "Failed to do self-transfer as part of burn:" << resp.err() << '\n'; + return; + } else { + std::cout << "Successfully sent self-transfer tx as part of burn. Last added tx number:" << resp.last_added_tx_number() << '\n'; + + syncState(conn, resp.last_added_tx_number()); + } + // Continue with the next transaction in the burn process + } } } @@ -265,6 +287,11 @@ void Wallet::syncState(Connection& conn, uint64_t lastKnownTxNum) { return; } + if (!resp.has_tx_number()) { + std::cout << "Incomplete response from wallet service!\n"; + return; + } + std::cout << "Got signed " << TxType_Name(resp.tx_type()) << " transaction.\n"; std::cout << "Tx data: " << resp.tx_data().size() << " bytes\n"; std::cout << "Num Sigs: " << resp.sigs_size() << '\n'; @@ -290,7 +317,7 @@ void Wallet::syncState(Connection& conn, uint64_t lastKnownTxNum) { user_->updateTransferTx(resp.tx_number(), tx, sigs); } break; case TxType::BURN: { - tx.type_ = utt::Transaction::Type::Transfer; + tx.type_ = utt::Transaction::Type::Burn; if (!sigs.empty()) throw std::runtime_error("Expected no signatures for burn tx!"); user_->updateBurnTx(resp.tx_number(), tx); } break; From e44dbe19e8b4bbf32eb2954b0a190552c1b51d6c Mon Sep 17 00:00:00 2001 From: Evgeniy Dzhurov Date: Tue, 18 Oct 2022 20:54:07 +0300 Subject: [PATCH 21/44] Fix creation of budget locally to not happen before registration --- .../include/utt-client-api/User.hpp | 2 +- utt/utt-client-api/src/User.cpp | 20 +++----- utt/wallet-cli/include/wallet.hpp | 4 +- utt/wallet-cli/proto/api/v1/api.proto | 14 ++++++ utt/wallet-cli/src/main.cpp | 6 ++- utt/wallet-cli/src/wallet.cpp | 49 ++++++++++++++++--- 6 files changed, 70 insertions(+), 25 deletions(-) diff --git a/utt/utt-client-api/include/utt-client-api/User.hpp b/utt/utt-client-api/include/utt-client-api/User.hpp index cb5c5b10f5..cc6549b9a8 100644 --- a/utt/utt-client-api/include/utt-client-api/User.hpp +++ b/utt/utt-client-api/include/utt-client-api/User.hpp @@ -47,7 +47,7 @@ class User { ~User(); /// @brief Creates a privacy budget locally - used only for testing. - void preCreatePrivacyBudget(const utt::Configuration& config, uint64_t amount); + void createPrivacyBudgetLocal(const utt::Configuration& config, uint64_t amount); /// @brief Creates an input registration commitment. Multiple calls generate the same object. /// @return The user's registration input object diff --git a/utt/utt-client-api/src/User.cpp b/utt/utt-client-api/src/User.cpp index e301f99e70..737792f180 100644 --- a/utt/utt-client-api/src/User.cpp +++ b/utt/utt-client-api/src/User.cpp @@ -409,23 +409,16 @@ utt::Transaction User::mint(uint64_t amount) const { return tx; } -void User::preCreatePrivacyBudget(const utt::Configuration& config, uint64_t amount) { +void User::createPrivacyBudgetLocal(const utt::Configuration& config, uint64_t amount) { if (config.empty()) throw std::runtime_error("Privacy app config cannot be empty!"); if (amount == 0) throw std::runtime_error("Positive privacy budget amount required!"); const auto& pid = pImpl_->client_->getPid(); - loginfo << "Pre-creating " << amount << " privacy budget for '" << pid << "'\n"; + loginfo << "Creating " << amount << " privacy budget locally for '" << pid << "'\n"; auto uttConfig = libutt::api::deserialize(config); - auto budget = libutt::api::operations::Budget(pImpl_->params_, *pImpl_->client_, amount, 0 /*expirationDate*/); - - // auto pidHash = libutt::api::Utils::curvePointFromHash(pImpl_->client_->getPid()); - // auto snHash = libutt::api::Utils::curvePointFromHash("budget|" + pid); - - // auto budget = libutt::api::operations::Budget(uttConfig.getPublicConfig().getParams(), snHash, pidHash, amount, 0); - // Create coins signers from config auto commitVerificationKey = uttConfig.getPublicConfig().getCommitVerificationKey(); auto registrationVerificationKey = uttConfig.getPublicConfig().getRegistrationVerificationKey(); @@ -447,11 +440,14 @@ void User::preCreatePrivacyBudget(const utt::Configuration& config, uint64_t amo registrationVerificationKey); } - // Sign budget signature + // Create budget + auto budget = libutt::api::operations::Budget(pImpl_->params_, *pImpl_->client_, amount, 0 /*expirationDate*/); + + // Sign budget const uint16_t n = uttConfig.getNumValidators(); const uint16_t t = uttConfig.getThreshold(); - // We just need threshold signers to sign + // We need only 'threshold' signers to sign std::map> shareSubset; for (size_t idx = 0; idx < t; ++idx) { shareSubset.emplace((uint32_t)idx, coinsSigners[idx].sign(budget).front()); @@ -465,7 +461,7 @@ void User::preCreatePrivacyBudget(const utt::Configuration& config, uint64_t amo // Expect a single budget token to be claimed by the user if (claimedCoins.size() != 1) throw std::runtime_error("Expected single budget token!"); - if (!pImpl_->client_->validate(claimedCoins[0])) throw std::runtime_error("Invalid pre-created budget coin!"); + if (!pImpl_->client_->validate(claimedCoins[0])) throw std::runtime_error("Invalid local budget coin!"); // [TODO-UTT] Requires atomic, durable write through IUserStorage pImpl_->budgetCoin_ = claimedCoins[0]; diff --git a/utt/wallet-cli/include/wallet.hpp b/utt/wallet-cli/include/wallet.hpp index 0c1bb3f794..2e6f511ed8 100644 --- a/utt/wallet-cli/include/wallet.hpp +++ b/utt/wallet-cli/include/wallet.hpp @@ -32,10 +32,10 @@ class Wallet { static std::pair deployApp(Connection& conn); /// [TODO-UTT] Create privacy budget locally because the system can't process budget requests yet. - /// @brief Create a privacy budget locally for the user. This function is only for testing. + /// @brief Create a privacy budget locally for the user. This function is only for testing. /// @param config A Privacy app configuration /// @param amount The amount of privacy budget to create - void preCreatePrivacyBudget(const utt::Configuration& config, uint64_t amount); + void createPrivacyBudgetLocal(const utt::Configuration& config, uint64_t amount); Wallet(std::string userId, utt::client::TestUserPKInfrastructure& pki, const utt::PublicConfig& config); diff --git a/utt/wallet-cli/proto/api/v1/api.proto b/utt/wallet-cli/proto/api/v1/api.proto index d18a8197da..603d4f7d3c 100644 --- a/utt/wallet-cli/proto/api/v1/api.proto +++ b/utt/wallet-cli/proto/api/v1/api.proto @@ -38,6 +38,8 @@ service WalletService { // Fetch an already signed (completed) transaction (either mint, burn or transfer). rpc getSignedTransaction(GetSignedTransactionRequest) returns (GetSignedTransactionResponse); + + rpc getTxData(GetTxDataRequest) returns (GetTxDataResponse); } enum TxType { @@ -136,4 +138,16 @@ message GetSignedTransactionResponse { optional TxType tx_type = 3; optional bytes tx_data = 4; repeated bytes sigs = 5; + optional uint32 tx_data_size = 6; +} + +message GetTxDataRequest { + optional uint64 tx_number = 1; + optional uint32 byte_offset = 2; +} + +message GetTxDataResponse { + optional string err = 1; + optional uint64 tx_number = 2; + optional bytes tx_data = 3; } \ No newline at end of file diff --git a/utt/wallet-cli/src/main.cpp b/utt/wallet-cli/src/main.cpp index 70ec0e11a4..e65175d354 100644 --- a/utt/wallet-cli/src/main.cpp +++ b/utt/wallet-cli/src/main.cpp @@ -85,6 +85,8 @@ int main(int argc, char* argv[]) { auto conn = Wallet::newConnection(); + utt::Configuration config; + while (true) { std::cout << "\nEnter command (type 'h' for commands 'Ctr-D' to quit):\n > "; std::string cmd; @@ -102,13 +104,13 @@ int main(int argc, char* argv[]) { std::cout << "The privacy app is already deployed.\n"; } else { auto configs = Wallet::deployApp(conn); + config = std::move(configs.first); // Save the full config for creating budgets locally later utt::client::TestUserPKInfrastructure pki; auto testUserIds = pki.getUserIds(); for (const auto& userId : testUserIds) { std::cout << "Creating test user with id '" << userId << "'\n"; wallets.emplace(userId, Wallet(userId, pki, configs.second)); - wallets.at(userId).preCreatePrivacyBudget(configs.first, 10000); } deployed = true; @@ -133,7 +135,7 @@ int main(int argc, char* argv[]) { if (cmdTokens.size() != 2) { std::cout << "Usage: create-budget \n"; } else { - wallets.at(cmdTokens[1]).createPrivacyBudget(conn); + wallets.at(cmdTokens[1]).createPrivacyBudgetLocal(config, 10000); } } else if (cmdTokens[0] == "show") { if (cmdTokens.size() != 2) { diff --git a/utt/wallet-cli/src/wallet.cpp b/utt/wallet-cli/src/wallet.cpp index b6b6d21d8a..7b467243c9 100644 --- a/utt/wallet-cli/src/wallet.cpp +++ b/utt/wallet-cli/src/wallet.cpp @@ -79,8 +79,8 @@ std::pair Wallet::deployApp(Connection& c return std::pair{config, utt::client::getPublicConfig(config)}; } -void Wallet::preCreatePrivacyBudget(const utt::Configuration& config, uint64_t amount) { - user_->preCreatePrivacyBudget(config, amount); +void Wallet::createPrivacyBudgetLocal(const utt::Configuration& config, uint64_t amount) { + user_->createPrivacyBudgetLocal(config, amount); } void Wallet::registerUser(Connection& conn) { @@ -232,7 +232,7 @@ void Wallet::burn(Connection& conn, uint64_t amount) { syncState(conn, resp.last_added_tx_number()); } - break; // Done + break; // Done } else { TransferRequest req; req.set_tx_data(result.requiredTx_.data_.data(), result.requiredTx_.data_.size()); @@ -244,7 +244,8 @@ void Wallet::burn(Connection& conn, uint64_t amount) { std::cout << "Failed to do self-transfer as part of burn:" << resp.err() << '\n'; return; } else { - std::cout << "Successfully sent self-transfer tx as part of burn. Last added tx number:" << resp.last_added_tx_number() << '\n'; + std::cout << "Successfully sent self-transfer tx as part of burn. Last added tx number:" + << resp.last_added_tx_number() << '\n'; syncState(conn, resp.last_added_tx_number()); } @@ -280,7 +281,8 @@ void Wallet::syncState(Connection& conn, uint64_t lastKnownTxNum) { req.set_tx_number(txNum); GetSignedTransactionResponse resp; - conn->getSignedTransaction(&ctx, req, &resp); + auto status = conn->getSignedTransaction(&ctx, req, &resp); + std::cout << "getSignedTransaction status: " << status.error_message() << '\n'; if (resp.has_err()) { std::cout << "Failed to get signed tx with number " << txNum << ':' << resp.err() << '\n'; @@ -288,16 +290,47 @@ void Wallet::syncState(Connection& conn, uint64_t lastKnownTxNum) { } if (!resp.has_tx_number()) { - std::cout << "Incomplete response from wallet service!\n"; + std::cout << "Missing tx number in GetSignedTransactionResponse!\n"; return; } std::cout << "Got signed " << TxType_Name(resp.tx_type()) << " transaction.\n"; - std::cout << "Tx data: " << resp.tx_data().size() << " bytes\n"; + std::cout << "Tx num: " << resp.tx_number() << '\n'; + std::cout << "Tx data size: " << resp.tx_data().size() << " bytes\n"; + std::cout << "Tx data actual size: " << resp.tx_data_size() << " bytes\n"; std::cout << "Num Sigs: " << resp.sigs_size() << '\n'; utt::Transaction tx; - tx.data_ = std::vector(resp.tx_data().begin(), resp.tx_data().end()); + std::copy(resp.tx_data().begin(), resp.tx_data().end(), std::back_inserter(tx.data_)); + + auto getTxData = [](Connection& conn, uint64_t txNumber, std::vector& buff) { + grpc::ClientContext ctx; + + GetTxDataRequest req; + req.set_tx_number(txNumber); + req.set_byte_offset((uint32_t)buff.size()); + + GetTxDataResponse resp; + conn->getTxData(&ctx, req, &resp); + + if (resp.has_err()) { + throw std::runtime_error("getTxData: " + resp.err()); + } + std::cout << "got extra data:" << resp.tx_data().size() << '\n'; + + std::copy(resp.tx_data().begin(), resp.tx_data().end(), std::back_inserter(buff)); + }; + + while (true) { + if (tx.data_.size() < resp.tx_data_size()) { + std::cout << "Fetch additional tx data...\n"; + getTxData(conn, resp.tx_number(), tx.data_); + } else if (tx.data_.size() > resp.tx_data_size()) { + throw std::runtime_error("Got more bytes than the actual tx size!"); + } else { // tx size == actual size + break; // Done + } + } utt::TxOutputSigs sigs; sigs.reserve((size_t)resp.sigs_size()); From c639459e89940a94dbeb153f81e538688b18af04 Mon Sep 17 00:00:00 2001 From: Evgeniy Dzhurov Date: Wed, 19 Oct 2022 23:02:37 +0300 Subject: [PATCH 22/44] Check if the wallet exists when processing commands --- utt/wallet-cli/src/main.cpp | 43 ++++++++++++++++++++++++++++++----- utt/wallet-cli/src/wallet.cpp | 5 ++-- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/utt/wallet-cli/src/main.cpp b/utt/wallet-cli/src/main.cpp index e65175d354..1f310faff3 100644 --- a/utt/wallet-cli/src/main.cpp +++ b/utt/wallet-cli/src/main.cpp @@ -124,24 +124,43 @@ int main(int argc, char* argv[]) { std::stringstream ss(cmd); while (std::getline(ss, token, ' ')) cmdTokens.emplace_back(token); + auto getWallet = [&wallets](const std::string& userId) -> Wallet* { + auto it = wallets.find(userId); + if (it == wallets.end()) return nullptr; + return &it->second; + }; + + // [TODO-UTT] Too much nesting, should refactor validation if (!cmdTokens.empty()) { if (cmdTokens[0] == "register") { if (cmdTokens.size() != 2) { std::cout << "Usage: register \n"; } else { - wallets.at(cmdTokens[1]).registerUser(conn); + if (auto wallet = getWallet(cmdTokens[1])) { + wallet->registerUser(conn); + } else { + std::cout << "No wallet for '" << cmdTokens[1] << "'\n"; + } } } else if (cmdTokens[0] == "create-budget") { if (cmdTokens.size() != 2) { std::cout << "Usage: create-budget \n"; } else { - wallets.at(cmdTokens[1]).createPrivacyBudgetLocal(config, 10000); + if (auto wallet = getWallet(cmdTokens[1])) { + wallet->createPrivacyBudgetLocal(config, 10000); + } else { + std::cout << "No wallet for '" << cmdTokens[1] << "'\n"; + } } } else if (cmdTokens[0] == "show") { if (cmdTokens.size() != 2) { std::cout << "Usage: show \n"; } else { - wallets.at(cmdTokens[1]).showInfo(conn); + if (auto wallet = getWallet(cmdTokens[1])) { + wallet->showInfo(conn); + } else { + std::cout << "No wallet for '" << cmdTokens[1] << "'\n"; + } } } else if (cmdTokens[0] == "mint") { if (cmdTokens.size() != 3) { @@ -151,7 +170,11 @@ int main(int argc, char* argv[]) { if (amount <= 0) { std::cout << "Expected a positive mint amount!\n"; } else { - wallets.at(cmdTokens[1]).mint(conn, (uint64_t)amount); + if (auto wallet = getWallet(cmdTokens[1])) { + wallet->mint(conn, (uint64_t)amount); + } else { + std::cout << "No wallet for '" << cmdTokens[1] << "'\n"; + } } } } else if (cmdTokens[0] == "transfer") { @@ -162,7 +185,11 @@ int main(int argc, char* argv[]) { if (amount <= 0) { std::cout << "Expected a positive transfer amount!\n"; } else { - wallets.at(cmdTokens[2]).transfer(conn, (uint64_t)amount, cmdTokens[3]); + if (auto fromWallet = getWallet(cmdTokens[2])) { + fromWallet->transfer(conn, (uint64_t)amount, cmdTokens[3]); + } else { + std::cout << "No wallet for '" << cmdTokens[2] << "'\n"; + } } } } else if (cmdTokens[0] == "burn") { @@ -173,7 +200,11 @@ int main(int argc, char* argv[]) { if (amount <= 0) { std::cout << "Expected a positive burn amount!\n"; } else { - wallets.at(cmdTokens[1]).burn(conn, (uint64_t)amount); + if (auto wallet = getWallet(cmdTokens[1])) { + wallet->burn(conn, (uint64_t)amount); + } else { + std::cout << "No wallet for '" << cmdTokens[1] << "'\n"; + } } } } else { diff --git a/utt/wallet-cli/src/wallet.cpp b/utt/wallet-cli/src/wallet.cpp index 7b467243c9..985235be2d 100644 --- a/utt/wallet-cli/src/wallet.cpp +++ b/utt/wallet-cli/src/wallet.cpp @@ -46,11 +46,10 @@ Wallet::Connection Wallet::newConnection() { void Wallet::showInfo(Connection& conn) { syncState(conn); - - std::cout << "User Id: " << userId_ << '\n'; + std::cout << "\n--------- "<< userId_ << " ---------\n"; std::cout << "Private balance: " << user_->getBalance() << '\n'; std::cout << "Privacy budget: " << user_->getPrivacyBudget() << '\n'; - std::cout << "Last executed transaction number: " << user_->getLastExecutedTxNum() << '\n'; + std::cout << "Last executed tx number: " << user_->getLastExecutedTxNum() << '\n'; } std::pair Wallet::deployApp(Connection& conn) { From 8f8b87622bcaa9e418c9f3a63ec78a764af79821 Mon Sep 17 00:00:00 2001 From: Evgeniy Dzhurov Date: Thu, 20 Oct 2022 13:11:53 +0300 Subject: [PATCH 23/44] Add comment about the workaround dealing with the wallet service not returning complete messages occasionaly when getting tx data --- utt/wallet-cli/src/wallet.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/utt/wallet-cli/src/wallet.cpp b/utt/wallet-cli/src/wallet.cpp index 985235be2d..df9401cbf9 100644 --- a/utt/wallet-cli/src/wallet.cpp +++ b/utt/wallet-cli/src/wallet.cpp @@ -46,7 +46,7 @@ Wallet::Connection Wallet::newConnection() { void Wallet::showInfo(Connection& conn) { syncState(conn); - std::cout << "\n--------- "<< userId_ << " ---------\n"; + std::cout << "\n--------- " << userId_ << " ---------\n"; std::cout << "Private balance: " << user_->getBalance() << '\n'; std::cout << "Privacy budget: " << user_->getPrivacyBudget() << '\n'; std::cout << "Last executed tx number: " << user_->getLastExecutedTxNum() << '\n'; @@ -293,7 +293,7 @@ void Wallet::syncState(Connection& conn, uint64_t lastKnownTxNum) { return; } - std::cout << "Got signed " << TxType_Name(resp.tx_type()) << " transaction.\n"; + std::cout << "Got " << TxType_Name(resp.tx_type()) << " transaction.\n"; std::cout << "Tx num: " << resp.tx_number() << '\n'; std::cout << "Tx data size: " << resp.tx_data().size() << " bytes\n"; std::cout << "Tx data actual size: " << resp.tx_data_size() << " bytes\n"; @@ -320,9 +320,18 @@ void Wallet::syncState(Connection& conn, uint64_t lastKnownTxNum) { std::copy(resp.tx_data().begin(), resp.tx_data().end(), std::back_inserter(buff)); }; + // [TODO-UTT] As it seems the wallet grpc server written in node.js using grpc-js occasionally + // sends empty message (with missing fields) with no error either on the client or server. + // I have observed this mostly when fetching transactions which are a bit larger than other messages + // but not much - 20-50k. My initial suspicion was that the size of the proto message was at fault + // so I added a way to fetch the data in chunks. Each tx is cached in the wallet service and the client + // will fetch it sequentially in the next loop with 'getTxData'. This behavior could be a bug on part + // of the proto serialization in node.js or some misuse of gRPC in this client or the server. In any + // occasion this issue will plague the demo if not fixed properly, because syncing will fail and it + // needs to be retried. while (true) { if (tx.data_.size() < resp.tx_data_size()) { - std::cout << "Fetch additional tx data...\n"; + std::cout << "Tx data was incomplete - fetch additional tx data...\n"; getTxData(conn, resp.tx_number(), tx.data_); } else if (tx.data_.size() > resp.tx_data_size()) { throw std::runtime_error("Got more bytes than the actual tx size!"); From 95c257af67ceb71d17be42f650f1bc00b806bab3 Mon Sep 17 00:00:00 2001 From: Evgeniy Dzhurov Date: Fri, 21 Oct 2022 12:03:46 +0300 Subject: [PATCH 24/44] Add public balance to wallet-cli --- utt/wallet-cli/include/wallet.hpp | 1 + utt/wallet-cli/proto/api/v1/api.proto | 14 ++++++++- utt/wallet-cli/src/wallet.cpp | 45 +++++++++++++++++++++++---- 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/utt/wallet-cli/include/wallet.hpp b/utt/wallet-cli/include/wallet.hpp index 2e6f511ed8..4238edcc14 100644 --- a/utt/wallet-cli/include/wallet.hpp +++ b/utt/wallet-cli/include/wallet.hpp @@ -73,4 +73,5 @@ class Wallet { std::string userId_; utt::client::TestUserPKInfrastructure& pki_; std::unique_ptr user_; + uint64_t publicBalance_ = 0; }; \ No newline at end of file diff --git a/utt/wallet-cli/proto/api/v1/api.proto b/utt/wallet-cli/proto/api/v1/api.proto index 603d4f7d3c..d8671c9b13 100644 --- a/utt/wallet-cli/proto/api/v1/api.proto +++ b/utt/wallet-cli/proto/api/v1/api.proto @@ -40,6 +40,8 @@ service WalletService { rpc getSignedTransaction(GetSignedTransactionRequest) returns (GetSignedTransactionResponse); rpc getTxData(GetTxDataRequest) returns (GetTxDataResponse); + + rpc getPublicBalance(GetPublicBalanceRequest) returns (GetPublicBalanceResponse); } enum TxType { @@ -55,7 +57,8 @@ message DeployPrivacyAppRequest { message DeployPrivacyAppResponse { optional string err = 1; // Returns any error generated during deployment - optional string app_id = 2; // Some way to identify the deployed application + optional string privacy_contract_addr = 2; // Address of the deployed privacy contract + optional string token_contract_addr = 3; // Address of the deployed token contract } message RegisterUserRequest { @@ -150,4 +153,13 @@ message GetTxDataResponse { optional string err = 1; optional uint64 tx_number = 2; optional bytes tx_data = 3; +} + +message GetPublicBalanceRequest { + optional string user_id = 1; +} + +message GetPublicBalanceResponse { + optional string err = 1; + optional uint64 public_balance = 2; } \ No newline at end of file diff --git a/utt/wallet-cli/src/wallet.cpp b/utt/wallet-cli/src/wallet.cpp index df9401cbf9..7977267a5f 100644 --- a/utt/wallet-cli/src/wallet.cpp +++ b/utt/wallet-cli/src/wallet.cpp @@ -15,6 +15,15 @@ #include +namespace { +void printStatus(const grpc::Status& status, const char* rpcName) { + if (status.ok()) + std::cout << rpcName << " grpc status ok\n"; + else + std::cout << rpcName << " grpc status error " << status.error_code() << ": " << status.error_message() << '\n'; +} +} // namespace + using namespace vmware::concord::utt::wallet::api::v1; Wallet::Wallet(std::string userId, utt::client::TestUserPKInfrastructure& pki, const utt::PublicConfig& config) @@ -47,6 +56,7 @@ Wallet::Connection Wallet::newConnection() { void Wallet::showInfo(Connection& conn) { syncState(conn); std::cout << "\n--------- " << userId_ << " ---------\n"; + std::cout << "Public balance: " << publicBalance_ << '\n'; std::cout << "Private balance: " << user_->getBalance() << '\n'; std::cout << "Privacy budget: " << user_->getPrivacyBudget() << '\n'; std::cout << "Last executed tx number: " << user_->getLastExecutedTxNum() << '\n'; @@ -66,14 +76,16 @@ std::pair Wallet::deployApp(Connection& c req.set_config(config.data(), config.size()); DeployPrivacyAppResponse resp; - conn->deployPrivacyApp(&ctx, req, &resp); + auto status = conn->deployPrivacyApp(&ctx, req, &resp); + printStatus(status, "deployPrivacyApp"); // Note that keeping the config around in memory is just a temp solution and should not happen in real system if (resp.has_err()) throw std::runtime_error("Failed to deploy privacy app: " + resp.err()); - if (resp.app_id().empty()) throw std::runtime_error("Failed to deploy privacy app: empty app id!"); - // We need the public config part which can typically be obtained from the service, but we derive it for simplicity - std::cout << "Successfully deployed privacy app with id: " << resp.app_id() << '\n'; + std::cout << "\nDeployed privacy application\n"; + std::cout << "-----------------------------------\n"; + std::cout << "Privacy contract: " << resp.privacy_contract_addr() << '\n'; + std::cout << "Token contract: " << resp.token_contract_addr() << '\n'; return std::pair{config, utt::client::getPublicConfig(config)}; } @@ -94,7 +106,8 @@ void Wallet::registerUser(Connection& conn) { req.set_user_pk(user_->getPK()); RegisterUserResponse resp; - conn->registerUser(&ctx, req, &resp); + auto status = conn->registerUser(&ctx, req, &resp); + printStatus(status, "registerUser"); if (resp.has_err()) { std::cout << "Failed to register user: " << resp.err() << '\n'; @@ -256,6 +269,26 @@ void Wallet::burn(Connection& conn, uint64_t amount) { void Wallet::syncState(Connection& conn, uint64_t lastKnownTxNum) { std::cout << "Synchronizing state...\n"; + // Update public balance + while (true) { + grpc::ClientContext ctx; + GetPublicBalanceRequest req; + req.set_user_id(userId_); + GetPublicBalanceResponse resp; + auto status = conn->getPublicBalance(&ctx, req, &resp); + printStatus(status, "getPublicBalance"); + + if (resp.has_err()) { + std::cout << "Failed to get public balance:" << resp.err() << '\n'; + break; + } else if (!resp.has_public_balance()) { + std::cout << "retry getPublicBalance\n"; + } else { + publicBalance_ = resp.public_balance(); + break; // Done + } + } + // Sync to latest state if (lastKnownTxNum == 0) { std::cout << "Last known tx number is zero (or not provided) - fetching last added tx number...\n"; @@ -281,7 +314,7 @@ void Wallet::syncState(Connection& conn, uint64_t lastKnownTxNum) { GetSignedTransactionResponse resp; auto status = conn->getSignedTransaction(&ctx, req, &resp); - std::cout << "getSignedTransaction status: " << status.error_message() << '\n'; + printStatus(status, "getSignedTransaction"); if (resp.has_err()) { std::cout << "Failed to get signed tx with number " << txNum << ':' << resp.err() << '\n'; From 17b3d460049f28efac8012486379265a65a323d3 Mon Sep 17 00:00:00 2001 From: Evgeniy Dzhurov Date: Fri, 21 Oct 2022 14:19:00 +0300 Subject: [PATCH 25/44] Refactor wallet cli app to avoid excessive nesting when validating input. Add a simple debug output to inspect the coins of a user --- .../include/utt-client-api/User.hpp | 2 + utt/utt-client-api/src/User.cpp | 27 ++ utt/wallet-cli/include/wallet.hpp | 2 + utt/wallet-cli/src/main.cpp | 275 ++++++++++-------- utt/wallet-cli/src/wallet.cpp | 4 +- 5 files changed, 191 insertions(+), 119 deletions(-) diff --git a/utt/utt-client-api/include/utt-client-api/User.hpp b/utt/utt-client-api/include/utt-client-api/User.hpp index cc6549b9a8..189feb1d3f 100644 --- a/utt/utt-client-api/include/utt-client-api/User.hpp +++ b/utt/utt-client-api/include/utt-client-api/User.hpp @@ -132,6 +132,8 @@ class User { /// to transfer the desired amount. TransferResult transfer(const std::string& userId, const std::string& pk, uint64_t amount) const; + void debugOutput() const; + private: // Users can be created only by the top-level ClientApi functions friend std::unique_ptr createUser(const std::string& userId, diff --git a/utt/utt-client-api/src/User.cpp b/utt/utt-client-api/src/User.cpp index 737792f180..e766db77f6 100644 --- a/utt/utt-client-api/src/User.cpp +++ b/utt/utt-client-api/src/User.cpp @@ -523,4 +523,31 @@ TransferResult User::transfer(const std::string& userId, const std::string& dest } } +void User::debugOutput() const { + std::cout << "------ USER DEBUG OUTPUT START -------------\n"; + if (!pImpl_->client_) { + std::cout << "User's libutt::api::client object not initialized!\n"; + } + std::cout << "lastExecutedTxNum:" << pImpl_->lastExecutedTxNum_ << '\n'; + std::cout << "coins: [\n"; + + auto dbgOutputCoin = [](const libutt::api::Coin& coin) { + std::cout << "type: " << (coin.getType() == libutt::api::Coin::Type::Budget ? "Budget" : "Normal") << ' '; + std::cout << "value: " << coin.getVal() << ' '; + std::cout << "expire: " << coin.getExpDate() << ' '; + std::cout << "null: " << coin.getNullifier() << ' '; + std::cout << "hasSig: " << coin.hasSig() << ' '; + std::cout << '\n'; + }; + + for (const auto& coin : pImpl_->coins_) { + dbgOutputCoin(coin); + } + std::cout << "]\n"; + std::cout << "budget coin: [\n"; + if (pImpl_->budgetCoin_) dbgOutputCoin(*pImpl_->budgetCoin_); + std::cout << "]\n"; + std::cout << "------ USER DEBUG OUTPUT END -------------\n"; +} + } // namespace utt::client \ No newline at end of file diff --git a/utt/wallet-cli/include/wallet.hpp b/utt/wallet-cli/include/wallet.hpp index 4238edcc14..241adee05c 100644 --- a/utt/wallet-cli/include/wallet.hpp +++ b/utt/wallet-cli/include/wallet.hpp @@ -62,6 +62,8 @@ class Wallet { /// @param amount The amount of private funds to burn. void burn(Connection& conn, uint64_t amount); + void debugOutput() const; + private: /// @brief Sync up to the last known tx number. If the last known tx number is zero (or not provided) the /// last signed transaction number will be fetched from the system diff --git a/utt/wallet-cli/src/main.cpp b/utt/wallet-cli/src/main.cpp index 1f310faff3..dc22a604f9 100644 --- a/utt/wallet-cli/src/main.cpp +++ b/utt/wallet-cli/src/main.cpp @@ -49,14 +49,6 @@ // so we don't need to implement the full range of precautions to handle liveness issues // such as timeouts. -// [TODO-UTT] Commands: -// mint -- convert some amount of public funds (ERC20 tokens) to private funds (UTT tokens) -// burn -- convert some amount of private funds (UTT tokens) to public funds (ERC20 tokens) -// transfer -- transfers anonymously some amount of private funds (UTT tokens) to another user -// public balance -- print the number of ERC20 tokens the user has (needs a gRPC method to retrieve this value from -// the wallet service) private balance -- print the number of UTT tokens the user has currently (compute locally) -// budget -- print the currently available anonymity budget (a budget is created in advance for each user) - void printHelp() { std::cout << "\nCommands:\n"; std::cout << "deploy -- generates a privacy app config, deploys it on the blockchain and creates " @@ -71,21 +63,152 @@ void printHelp() { std::cout << '\n'; } +struct CLIApp { + Wallet::Connection conn; + utt::Configuration config; + std::map wallets; + bool deployed = false; + + void deploy() { + if (deployed) { + std::cout << "The privacy app is already deployed.\n"; + return; + } + + auto configs = Wallet::deployApp(conn); + config = std::move(configs.first); // Save the full config for creating budgets locally later + + utt::client::TestUserPKInfrastructure pki; + auto testUserIds = pki.getUserIds(); + for (const auto& userId : testUserIds) { + std::cout << "Creating test user with id '" << userId << "'\n"; + wallets.emplace(userId, Wallet(userId, pki, configs.second)); + } + deployed = true; + } + + Wallet* getWallet(const std::string& userId) { + auto it = wallets.find(userId); + if (it == wallets.end()) return nullptr; + return &it->second; + }; + + void registerCmd(const std::vector& cmdTokens) { + if (cmdTokens.size() != 2) { + std::cout << "Usage: register \n"; + return; + } + + auto wallet = getWallet(cmdTokens[1]); + if (!wallet) { + std::cout << "No wallet for '" << cmdTokens[1] << "'\n"; + return; + } + wallet->registerUser(conn); + } + + void createBudgetCmd(const std::vector& cmdTokens) { + if (cmdTokens.size() != 2) { + std::cout << "Usage: create-budget \n"; + return; + } + auto wallet = getWallet(cmdTokens[1]); + if (!wallet) { + std::cout << "No wallet for '" << cmdTokens[1] << "'\n"; + return; + } + wallet->createPrivacyBudgetLocal(config, 10000); + } + + void showCmd(const std::vector& cmdTokens) { + if (cmdTokens.size() != 2) { + std::cout << "Usage: show \n"; + return; + } + auto wallet = getWallet(cmdTokens[1]); + if (!wallet) { + std::cout << "No wallet for '" << cmdTokens[1] << "'\n"; + return; + } + wallet->showInfo(conn); + } + + void mintCmd(const std::vector& cmdTokens) { + if (cmdTokens.size() != 3) { + std::cout << "Usage: mint \n"; + return; + } + auto wallet = getWallet(cmdTokens[1]); + if (!wallet) { + std::cout << "No wallet for '" << cmdTokens[1] << "'\n"; + } + int amount = std::atoi(cmdTokens[2].c_str()); + if (amount <= 0) { + std::cout << "Expected a positive mint amount!\n"; + return; + } + wallet->mint(conn, (uint64_t)amount); + } + + void transferCmd(const std::vector& cmdTokens) { + if (cmdTokens.size() != 4) { + std::cout << "Usage: transfer \n"; + return; + } + int amount = std::atoi(cmdTokens[1].c_str()); + if (amount <= 0) { + std::cout << "Expected a positive transfer amount!\n"; + return; + } + auto fromWallet = getWallet(cmdTokens[2]); + if (!fromWallet) { + std::cout << "No wallet for '" << cmdTokens[2] << "'\n"; + } + fromWallet->transfer(conn, (uint64_t)amount, cmdTokens[3]); + } + + void burnCmd(const std::vector& cmdTokens) { + if (cmdTokens.size() != 3) { + std::cout << "Usage: burn \n"; + return; + } + int amount = std::atoi(cmdTokens[2].c_str()); + if (amount <= 0) { + std::cout << "Expected a positive burn amount!\n"; + return; + } + auto wallet = getWallet(cmdTokens[1]); + if (!wallet) { + std::cout << "No wallet for '" << cmdTokens[1] << "'\n"; + return; + } + wallet->burn(conn, (uint64_t)amount); + } + + void debugCmd(const std::vector& cmdTokens) { + if (cmdTokens.size() != 2) { + std::cout << "Usage: debug \n"; + return; + } + auto wallet = getWallet(cmdTokens[1]); + if (!wallet) { + std::cout << "No wallet for '" << cmdTokens[1] << "'\n"; + return; + } + wallet->debugOutput(); + } +}; + int main(int argc, char* argv[]) { (void)argc; (void)argv; std::cout << "Sample Privacy Wallet CLI Application.\n"; - bool deployed = false; - try { utt::client::Initialize(); - std::map wallets; - - auto conn = Wallet::newConnection(); - - utt::Configuration config; + CLIApp app; + app.conn = Wallet::newConnection(); while (true) { std::cout << "\nEnter command (type 'h' for commands 'Ctr-D' to quit):\n > "; @@ -100,22 +223,8 @@ int main(int argc, char* argv[]) { if (cmd == "h") { printHelp(); } else if (cmd == "deploy") { - if (deployed) { - std::cout << "The privacy app is already deployed.\n"; - } else { - auto configs = Wallet::deployApp(conn); - config = std::move(configs.first); // Save the full config for creating budgets locally later - - utt::client::TestUserPKInfrastructure pki; - auto testUserIds = pki.getUserIds(); - for (const auto& userId : testUserIds) { - std::cout << "Creating test user with id '" << userId << "'\n"; - wallets.emplace(userId, Wallet(userId, pki, configs.second)); - } - - deployed = true; - } - } else if (!deployed) { + app.deploy(); + } else if (!app.deployed) { std::cout << "You must first deploy the privacy application. Use the 'deploy' command.\n"; } else { // Tokenize params @@ -123,97 +232,27 @@ int main(int argc, char* argv[]) { std::string token; std::stringstream ss(cmd); while (std::getline(ss, token, ' ')) cmdTokens.emplace_back(token); + if (cmdTokens.empty()) continue; - auto getWallet = [&wallets](const std::string& userId) -> Wallet* { - auto it = wallets.find(userId); - if (it == wallets.end()) return nullptr; - return &it->second; - }; - - // [TODO-UTT] Too much nesting, should refactor validation - if (!cmdTokens.empty()) { - if (cmdTokens[0] == "register") { - if (cmdTokens.size() != 2) { - std::cout << "Usage: register \n"; - } else { - if (auto wallet = getWallet(cmdTokens[1])) { - wallet->registerUser(conn); - } else { - std::cout << "No wallet for '" << cmdTokens[1] << "'\n"; - } - } - } else if (cmdTokens[0] == "create-budget") { - if (cmdTokens.size() != 2) { - std::cout << "Usage: create-budget \n"; - } else { - if (auto wallet = getWallet(cmdTokens[1])) { - wallet->createPrivacyBudgetLocal(config, 10000); - } else { - std::cout << "No wallet for '" << cmdTokens[1] << "'\n"; - } - } - } else if (cmdTokens[0] == "show") { - if (cmdTokens.size() != 2) { - std::cout << "Usage: show \n"; - } else { - if (auto wallet = getWallet(cmdTokens[1])) { - wallet->showInfo(conn); - } else { - std::cout << "No wallet for '" << cmdTokens[1] << "'\n"; - } - } - } else if (cmdTokens[0] == "mint") { - if (cmdTokens.size() != 3) { - std::cout << "Usage: mint \n"; - } else { - int amount = std::atoi(cmdTokens[2].c_str()); - if (amount <= 0) { - std::cout << "Expected a positive mint amount!\n"; - } else { - if (auto wallet = getWallet(cmdTokens[1])) { - wallet->mint(conn, (uint64_t)amount); - } else { - std::cout << "No wallet for '" << cmdTokens[1] << "'\n"; - } - } - } - } else if (cmdTokens[0] == "transfer") { - if (cmdTokens.size() != 4) { - std::cout << "Usage: transfer \n"; - } else { - int amount = std::atoi(cmdTokens[1].c_str()); - if (amount <= 0) { - std::cout << "Expected a positive transfer amount!\n"; - } else { - if (auto fromWallet = getWallet(cmdTokens[2])) { - fromWallet->transfer(conn, (uint64_t)amount, cmdTokens[3]); - } else { - std::cout << "No wallet for '" << cmdTokens[2] << "'\n"; - } - } - } - } else if (cmdTokens[0] == "burn") { - if (cmdTokens.size() != 3) { - std::cout << "Usage: burn \n"; - } else { - int amount = std::atoi(cmdTokens[2].c_str()); - if (amount <= 0) { - std::cout << "Expected a positive burn amount!\n"; - } else { - if (auto wallet = getWallet(cmdTokens[1])) { - wallet->burn(conn, (uint64_t)amount); - } else { - std::cout << "No wallet for '" << cmdTokens[1] << "'\n"; - } - } - } - } else { - std::cout << "Unknown command '" << cmd << "'\n"; - } + if (cmdTokens[0] == "register") { + app.registerCmd(cmdTokens); + } else if (cmdTokens[0] == "create-budget") { + app.createBudgetCmd(cmdTokens); + } else if (cmdTokens[0] == "show") { + app.showCmd(cmdTokens); + } else if (cmdTokens[0] == "mint") { + app.mintCmd(cmdTokens); + } else if (cmdTokens[0] == "transfer") { + app.transferCmd(cmdTokens); + } else if (cmdTokens[0] == "burn") { + app.burnCmd(cmdTokens); + } else if (cmdTokens[0] == "debug") { + app.debugCmd(cmdTokens); + } else { + std::cout << "Unknown command '" << cmd << "'\n"; } } } - } catch (const std::runtime_error& e) { std::cout << "Error (exception): " << e.what() << '\n'; return 1; diff --git a/utt/wallet-cli/src/wallet.cpp b/utt/wallet-cli/src/wallet.cpp index 7977267a5f..31f61c7737 100644 --- a/utt/wallet-cli/src/wallet.cpp +++ b/utt/wallet-cli/src/wallet.cpp @@ -399,4 +399,6 @@ void Wallet::syncState(Connection& conn, uint64_t lastKnownTxNum) { throw std::runtime_error("Unexpected tx type!"); } } -} \ No newline at end of file +} + +void Wallet::debugOutput() const { user_->debugOutput(); } \ No newline at end of file From 2982b1e67bb4a7d2304c2ed24e3cc2e42e7affdc Mon Sep 17 00:00:00 2001 From: Evgeniy Dzhurov Date: Mon, 24 Oct 2022 18:50:30 +0300 Subject: [PATCH 26/44] Rework wallet-cli to use a bi-directional stream for all wallet requests --- utt/wallet-cli/include/wallet.hpp | 22 +- utt/wallet-cli/proto/api/v1/api.proto | 46 +++- utt/wallet-cli/src/main.cpp | 32 ++- utt/wallet-cli/src/wallet.cpp | 327 ++++++++++++-------------- 4 files changed, 221 insertions(+), 206 deletions(-) diff --git a/utt/wallet-cli/include/wallet.hpp b/utt/wallet-cli/include/wallet.hpp index 241adee05c..1b4265578a 100644 --- a/utt/wallet-cli/include/wallet.hpp +++ b/utt/wallet-cli/include/wallet.hpp @@ -21,15 +21,19 @@ #include +namespace WalletApi = vmware::concord::utt::wallet::api::v1; + class Wallet { public: - using Connection = std::unique_ptr; + using Connection = std::unique_ptr; + using Channel = std::unique_ptr>; + static Connection newConnection(); /// [TODO-UTT] Should be performed by an admin app /// @brief Deploy a privacy application /// @return The public configuration of the deployed application - static std::pair deployApp(Connection& conn); + static std::pair deployApp(Channel& chan); /// [TODO-UTT] Create privacy budget locally because the system can't process budget requests yet. /// @brief Create a privacy budget locally for the user. This function is only for testing. @@ -39,35 +43,35 @@ class Wallet { Wallet(std::string userId, utt::client::TestUserPKInfrastructure& pki, const utt::PublicConfig& config); - void showInfo(Connection& conn); + void showInfo(Channel& chan); /// @brief Request registration of the current user - void registerUser(Connection& conn); + void registerUser(Channel& chan); /// @brief Request the creation of a privacy budget. The amount of the budget is predetermined by the deployed app. /// This operation could be performed entirely by an administrator, but we add it in the wallet /// for demo purposes. - void createPrivacyBudget(Connection& conn); + void createPrivacyBudget(Channel& chan); /// @brief Requests the minting of public funds to private funds. /// @param amount the amount of public funds - void mint(Connection& conn, uint64_t amount); + void mint(Channel& chan, uint64_t amount); /// @brief Transfers the desired amount anonymously to the recipient /// @param amount The amount to transfer /// @param recipient The user id of the recipient - void transfer(Connection& conn, uint64_t amount, const std::string& recipient); + void transfer(Channel& chan, uint64_t amount, const std::string& recipient); /// @brief Burns the desired amount of private funds and converts them to public funds. /// @param amount The amount of private funds to burn. - void burn(Connection& conn, uint64_t amount); + void burn(Channel& chan, uint64_t amount); void debugOutput() const; private: /// @brief Sync up to the last known tx number. If the last known tx number is zero (or not provided) the /// last signed transaction number will be fetched from the system - void syncState(Connection& conn, uint64_t lastKnownTxNum = 0); + void syncState(Channel& chan, uint64_t lastKnownTxNum = 0); struct DummyUserStorage : public utt::client::IUserStorage {}; diff --git a/utt/wallet-cli/proto/api/v1/api.proto b/utt/wallet-cli/proto/api/v1/api.proto index d8671c9b13..8524a77ca6 100644 --- a/utt/wallet-cli/proto/api/v1/api.proto +++ b/utt/wallet-cli/proto/api/v1/api.proto @@ -39,9 +39,9 @@ service WalletService { // Fetch an already signed (completed) transaction (either mint, burn or transfer). rpc getSignedTransaction(GetSignedTransactionRequest) returns (GetSignedTransactionResponse); - rpc getTxData(GetTxDataRequest) returns (GetTxDataResponse); - rpc getPublicBalance(GetPublicBalanceRequest) returns (GetPublicBalanceResponse); + + rpc walletChannel(stream WalletRequest) returns (stream WalletResponse); } enum TxType { @@ -144,22 +144,44 @@ message GetSignedTransactionResponse { optional uint32 tx_data_size = 6; } -message GetTxDataRequest { - optional uint64 tx_number = 1; - optional uint32 byte_offset = 2; +message GetPublicBalanceRequest { + optional string user_id = 1; } -message GetTxDataResponse { +message GetPublicBalanceResponse { optional string err = 1; - optional uint64 tx_number = 2; - optional bytes tx_data = 3; + optional uint64 public_balance = 2; } -message GetPublicBalanceRequest { - optional string user_id = 1; +message WalletRequest { + oneof req { + // init + DeployPrivacyAppRequest deploy = 1; + RegisterUserRequest register_user = 2; + // transact + TransferRequest transfer = 3; + MintRequest mint = 4; + BurnRequest burn = 5; + // sync + GetPublicBalanceRequest get_public_balance = 6; + GetLastAddedTxNumberRequest get_last_added_tx_number = 7; + GetSignedTransactionRequest get_signed_tx = 8; + } } -message GetPublicBalanceResponse { +message WalletResponse { optional string err = 1; - optional uint64 public_balance = 2; + oneof resp { + // init + DeployPrivacyAppResponse deploy = 2; + RegisterUserResponse register_user = 3; + // transact + TransferResponse transfer = 4; + MintResponse mint = 5; + BurnResponse burn = 6; + // sync + GetPublicBalanceResponse get_public_balance = 7; + GetLastAddedTxNumberResponse get_last_added_tx_number = 8; + GetSignedTransactionResponse get_signed_tx = 9; + } } \ No newline at end of file diff --git a/utt/wallet-cli/src/main.cpp b/utt/wallet-cli/src/main.cpp index dc22a604f9..82a27cb4de 100644 --- a/utt/wallet-cli/src/main.cpp +++ b/utt/wallet-cli/src/main.cpp @@ -64,18 +64,37 @@ void printHelp() { } struct CLIApp { + grpc::ClientContext ctx; Wallet::Connection conn; + Wallet::Channel chan; utt::Configuration config; std::map wallets; bool deployed = false; + CLIApp() { + conn = Wallet::newConnection(); + if (!conn) throw std::runtime_error("Failed to create wallet connection!"); + + chan = conn->walletChannel(&ctx); + if (!chan) throw std::runtime_error("Failed to create wallet streaming channel!"); + } + + ~CLIApp() { + std::cout << "Closing wallet streaming channel...\n"; + chan->WritesDone(); + auto status = chan->Finish(); + std::cout << "gRPC error code: " << status.error_code() << '\n'; + std::cout << "gRPC error msg: " << status.error_message() << '\n'; + std::cout << "gRPC error details: " << status.error_details() << '\n'; + } + void deploy() { if (deployed) { std::cout << "The privacy app is already deployed.\n"; return; } - auto configs = Wallet::deployApp(conn); + auto configs = Wallet::deployApp(chan); config = std::move(configs.first); // Save the full config for creating budgets locally later utt::client::TestUserPKInfrastructure pki; @@ -104,7 +123,7 @@ struct CLIApp { std::cout << "No wallet for '" << cmdTokens[1] << "'\n"; return; } - wallet->registerUser(conn); + wallet->registerUser(chan); } void createBudgetCmd(const std::vector& cmdTokens) { @@ -130,7 +149,7 @@ struct CLIApp { std::cout << "No wallet for '" << cmdTokens[1] << "'\n"; return; } - wallet->showInfo(conn); + wallet->showInfo(chan); } void mintCmd(const std::vector& cmdTokens) { @@ -147,7 +166,7 @@ struct CLIApp { std::cout << "Expected a positive mint amount!\n"; return; } - wallet->mint(conn, (uint64_t)amount); + wallet->mint(chan, (uint64_t)amount); } void transferCmd(const std::vector& cmdTokens) { @@ -164,7 +183,7 @@ struct CLIApp { if (!fromWallet) { std::cout << "No wallet for '" << cmdTokens[2] << "'\n"; } - fromWallet->transfer(conn, (uint64_t)amount, cmdTokens[3]); + fromWallet->transfer(chan, (uint64_t)amount, cmdTokens[3]); } void burnCmd(const std::vector& cmdTokens) { @@ -182,7 +201,7 @@ struct CLIApp { std::cout << "No wallet for '" << cmdTokens[1] << "'\n"; return; } - wallet->burn(conn, (uint64_t)amount); + wallet->burn(chan, (uint64_t)amount); } void debugCmd(const std::vector& cmdTokens) { @@ -208,7 +227,6 @@ int main(int argc, char* argv[]) { utt::client::Initialize(); CLIApp app; - app.conn = Wallet::newConnection(); while (true) { std::cout << "\nEnter command (type 'h' for commands 'Ctr-D' to quit):\n > "; diff --git a/utt/wallet-cli/src/wallet.cpp b/utt/wallet-cli/src/wallet.cpp index 31f61c7737..7a48bc6310 100644 --- a/utt/wallet-cli/src/wallet.cpp +++ b/utt/wallet-cli/src/wallet.cpp @@ -15,15 +15,6 @@ #include -namespace { -void printStatus(const grpc::Status& status, const char* rpcName) { - if (status.ok()) - std::cout << rpcName << " grpc status ok\n"; - else - std::cout << rpcName << " grpc status error " << status.error_code() << ": " << status.error_message() << '\n'; -} -} // namespace - using namespace vmware::concord::utt::wallet::api::v1; Wallet::Wallet(std::string userId, utt::client::TestUserPKInfrastructure& pki, const utt::PublicConfig& config) @@ -53,8 +44,8 @@ Wallet::Connection Wallet::newConnection() { return WalletService::NewStub(chan); } -void Wallet::showInfo(Connection& conn) { - syncState(conn); +void Wallet::showInfo(Channel& chan) { + syncState(chan); std::cout << "\n--------- " << userId_ << " ---------\n"; std::cout << "Public balance: " << publicBalance_ << '\n'; std::cout << "Private balance: " << user_->getBalance() << '\n'; @@ -62,7 +53,7 @@ void Wallet::showInfo(Connection& conn) { std::cout << "Last executed tx number: " << user_->getLastExecutedTxNum() << '\n'; } -std::pair Wallet::deployApp(Connection& conn) { +std::pair Wallet::deployApp(Channel& chan) { // Generate a privacy config for a N=4 replica system tolerating F=1 failures utt::client::ConfigInputParams params; params.validatorPublicKeys = std::vector{4, "placeholderPublicKey"}; // N = 3 * F + 1 @@ -70,22 +61,25 @@ std::pair Wallet::deployApp(Connection& c auto config = utt::client::generateConfig(params); if (config.empty()) throw std::runtime_error("Failed to generate a privacy app configuration!"); - grpc::ClientContext ctx; - - DeployPrivacyAppRequest req; - req.set_config(config.data(), config.size()); + WalletRequest req; + req.mutable_deploy()->set_config(config.data(), config.size()); + chan->Write(req); - DeployPrivacyAppResponse resp; - auto status = conn->deployPrivacyApp(&ctx, req, &resp); - printStatus(status, "deployPrivacyApp"); + WalletResponse resp; + chan->Read(&resp); + if (!resp.has_deploy()) throw std::runtime_error("Expected deploy response from wallet service!"); + std::cout << "response case: " << resp.resp_case() << '\n'; + const auto& deployResp = resp.deploy(); + std::cout << "has_privacy_contract_addr:" << deployResp.has_privacy_contract_addr() << '\n'; + std::cout << "has_token_contract_addr:" << deployResp.has_token_contract_addr() << '\n'; // Note that keeping the config around in memory is just a temp solution and should not happen in real system - if (resp.has_err()) throw std::runtime_error("Failed to deploy privacy app: " + resp.err()); + if (deployResp.has_err()) throw std::runtime_error("Failed to deploy privacy app: " + resp.err()); std::cout << "\nDeployed privacy application\n"; std::cout << "-----------------------------------\n"; - std::cout << "Privacy contract: " << resp.privacy_contract_addr() << '\n'; - std::cout << "Token contract: " << resp.token_contract_addr() << '\n'; + std::cout << "Privacy contract: " << deployResp.privacy_contract_addr() << '\n'; + std::cout << "Token contract: " << deployResp.token_contract_addr() << '\n'; return std::pair{config, utt::client::getPublicConfig(config)}; } @@ -94,28 +88,29 @@ void Wallet::createPrivacyBudgetLocal(const utt::Configuration& config, uint64_t user_->createPrivacyBudgetLocal(config, amount); } -void Wallet::registerUser(Connection& conn) { +void Wallet::registerUser(Channel& chan) { auto userRegInput = user_->getRegistrationInput(); if (userRegInput.empty()) throw std::runtime_error("Failed to create user registration input!"); - grpc::ClientContext ctx; - - RegisterUserRequest req; - req.set_user_id(userId_); - req.set_input_rcm(userRegInput.data(), userRegInput.size()); - req.set_user_pk(user_->getPK()); + WalletRequest req; + auto& registerReq = *req.mutable_register_user(); + registerReq.set_user_id(userId_); + registerReq.set_input_rcm(userRegInput.data(), userRegInput.size()); + registerReq.set_user_pk(user_->getPK()); + chan->Write(req); - RegisterUserResponse resp; - auto status = conn->registerUser(&ctx, req, &resp); - printStatus(status, "registerUser"); + WalletResponse resp; + chan->Read(&resp); + if (!resp.has_register_user()) throw std::runtime_error("Expected register response from wallet service!"); + const auto& regUser = resp.register_user(); - if (resp.has_err()) { - std::cout << "Failed to register user: " << resp.err() << '\n'; + if (regUser.has_err()) { + std::cout << "Failed to register user: " << regUser.err() << '\n'; } else { - utt::RegistrationSig sig = std::vector(resp.signature().begin(), resp.signature().end()); + utt::RegistrationSig sig = std::vector(regUser.signature().begin(), regUser.signature().end()); std::cout << "Got sig for registration with size: " << sig.size() << '\n'; - utt::S2 s2 = std::vector(resp.s2().begin(), resp.s2().end()); + utt::S2 s2 = std::vector(regUser.s2().begin(), regUser.s2().end()); std::cout << "Got S2 for registration: ["; for (const auto& val : s2) std::cout << val << ' '; std::cout << "]\n"; @@ -124,51 +119,57 @@ void Wallet::registerUser(Connection& conn) { } } -void Wallet::createPrivacyBudget(Connection& conn) { - grpc::ClientContext ctx; +void Wallet::createPrivacyBudget(Channel& chan) { + (void)chan; + // [TODO-UTT] Create budget is done locally, should be done by the system + // grpc::ClientContext ctx; - CreatePrivacyBudgetRequest req; - req.set_user_id(userId_); + // CreatePrivacyBudgetRequest req; + // req.set_user_id(userId_); - CreatePrivacyBudgetResponse resp; - conn->createPrivacyBudget(&ctx, req, &resp); + // CreatePrivacyBudgetResponse resp; + // conn->createPrivacyBudget(&ctx, req, &resp); - if (resp.has_err()) { - std::cout << "Failed to create privacy budget:" << resp.err() << '\n'; - } else { - utt::PrivacyBudget budget = std::vector(resp.budget().begin(), resp.budget().end()); - utt::RegistrationSig sig = std::vector(resp.signature().begin(), resp.signature().end()); + // if (resp.has_err()) { + // std::cout << "Failed to create privacy budget:" << resp.err() << '\n'; + // } else { + // utt::PrivacyBudget budget = std::vector(resp.budget().begin(), resp.budget().end()); + // utt::RegistrationSig sig = std::vector(resp.signature().begin(), resp.signature().end()); - std::cout << "Got budget " << budget.size() << " bytes.\n"; - std::cout << "Got budget sig " << sig.size() << " bytes.\n"; + // std::cout << "Got budget " << budget.size() << " bytes.\n"; + // std::cout << "Got budget sig " << sig.size() << " bytes.\n"; - user_->updatePrivacyBudget(budget, sig); - } + // user_->updatePrivacyBudget(budget, sig); + // } } -void Wallet::mint(Connection& conn, uint64_t amount) { +void Wallet::mint(Channel& chan, uint64_t amount) { auto mintTx = user_->mint(amount); grpc::ClientContext ctx; - MintRequest req; - req.set_user_id(userId_); - req.set_value(amount); - req.set_tx_data(mintTx.data_.data(), mintTx.data_.size()); + WalletRequest req; + auto& mintReq = *req.mutable_mint(); + mintReq.set_user_id(userId_); + mintReq.set_value(amount); + mintReq.set_tx_data(mintTx.data_.data(), mintTx.data_.size()); + chan->Write(req); - MintResponse resp; - conn->mint(&ctx, req, &resp); + WalletResponse resp; + chan->Read(&resp); + if (!resp.has_mint()) throw std::runtime_error("Expected mint response from wallet service!"); + const auto& mintResp = resp.mint(); - if (resp.has_err()) { - std::cout << "Failed to mint:" << resp.err() << '\n'; + if (mintResp.has_err()) { + std::cout << "Failed to mint:" << mintResp.err() << '\n'; } else { - std::cout << "Successfully sent mint tx. Last added tx number:" << resp.last_added_tx_number() << '\n'; + std::cout << "Successfully sent mint tx. Last added tx number:" << mintResp.last_added_tx_number() << '\n'; - syncState(conn, resp.last_added_tx_number()); + syncState(chan, mintResp.last_added_tx_number()); } } -void Wallet::transfer(Connection& conn, uint64_t amount, const std::string& recipient) { +void Wallet::transfer(Channel& chan, uint64_t amount, const std::string& recipient) { if (userId_ == recipient) { std::cout << "Cannot transfer to self directly!\n"; return; @@ -191,28 +192,31 @@ void Wallet::transfer(Connection& conn, uint64_t amount, const std::string& reci while (true) { auto result = user_->transfer(recipient, pki_.getPublicKey(recipient), amount); - grpc::ClientContext ctx; - - TransferRequest req; - req.set_tx_data(result.requiredTx_.data_.data(), result.requiredTx_.data_.size()); - req.set_num_outputs(result.requiredTx_.numOutputs_); + WalletRequest req; + auto& transferReq = *req.mutable_transfer(); + transferReq.set_tx_data(result.requiredTx_.data_.data(), result.requiredTx_.data_.size()); + transferReq.set_num_outputs(result.requiredTx_.numOutputs_); + chan->Write(req); - TransferResponse resp; - conn->transfer(&ctx, req, &resp); + WalletResponse resp; + chan->Read(&resp); + if (!resp.has_transfer()) throw std::runtime_error("Expected transfer response from wallet service!"); + const auto& transferResp = resp.transfer(); - if (resp.has_err()) { + if (transferResp.has_err()) { std::cout << "Failed to transfer:" << resp.err() << '\n'; } else { - std::cout << "Successfully sent transfer tx. Last added tx number:" << resp.last_added_tx_number() << '\n'; + std::cout << "Successfully sent transfer tx. Last added tx number:" << transferResp.last_added_tx_number() + << '\n'; - syncState(conn, resp.last_added_tx_number()); + syncState(chan, transferResp.last_added_tx_number()); } if (result.isFinal_) break; // Done } } -void Wallet::burn(Connection& conn, uint64_t amount) { +void Wallet::burn(Channel& chan, uint64_t amount) { if (user_->getBalance() < amount) { std::cout << "Insufficient private balance!\n"; return; @@ -225,67 +229,73 @@ void Wallet::burn(Connection& conn, uint64_t amount) { while (true) { auto result = user_->burn(amount); - grpc::ClientContext ctx; - if (result.isFinal_) { - BurnRequest req; - req.set_user_id(user_->getUserId()); - req.set_value(amount); - req.set_tx_data(result.requiredTx_.data_.data(), result.requiredTx_.data_.size()); - - BurnResponse resp; - conn->burn(&ctx, req, &resp); - - if (resp.has_err()) { + WalletRequest req; + auto& burnReq = *req.mutable_burn(); + burnReq.set_user_id(user_->getUserId()); + burnReq.set_value(amount); + burnReq.set_tx_data(result.requiredTx_.data_.data(), result.requiredTx_.data_.size()); + chan->Write(req); + + WalletResponse resp; + chan->Read(&resp); + if (!resp.has_burn()) throw std::runtime_error("Expected burn response from wallet service!"); + const auto& burnResp = resp.transfer(); + + if (burnResp.has_err()) { std::cout << "Failed to do burn:" << resp.err() << '\n'; } else { - std::cout << "Successfully sent burn tx. Last added tx number:" << resp.last_added_tx_number() << '\n'; + std::cout << "Successfully sent burn tx. Last added tx number:" << burnResp.last_added_tx_number() << '\n'; - syncState(conn, resp.last_added_tx_number()); + syncState(chan, burnResp.last_added_tx_number()); } break; // Done } else { - TransferRequest req; - req.set_tx_data(result.requiredTx_.data_.data(), result.requiredTx_.data_.size()); - req.set_num_outputs(result.requiredTx_.numOutputs_); - TransferResponse resp; - conn->transfer(&ctx, req, &resp); - - if (resp.has_err()) { + WalletRequest req; + auto& transferReq = *req.mutable_transfer(); + transferReq.set_tx_data(result.requiredTx_.data_.data(), result.requiredTx_.data_.size()); + transferReq.set_num_outputs(result.requiredTx_.numOutputs_); + chan->Write(req); + + WalletResponse resp; + chan->Read(&resp); + if (!resp.has_transfer()) throw std::runtime_error("Expected transfer response from wallet service!"); + const auto& transferResp = resp.transfer(); + + if (transferResp.has_err()) { std::cout << "Failed to do self-transfer as part of burn:" << resp.err() << '\n'; return; } else { std::cout << "Successfully sent self-transfer tx as part of burn. Last added tx number:" - << resp.last_added_tx_number() << '\n'; + << transferResp.last_added_tx_number() << '\n'; - syncState(conn, resp.last_added_tx_number()); + syncState(chan, transferResp.last_added_tx_number()); } // Continue with the next transaction in the burn process } } } -void Wallet::syncState(Connection& conn, uint64_t lastKnownTxNum) { +void Wallet::syncState(Channel& chan, uint64_t lastKnownTxNum) { std::cout << "Synchronizing state...\n"; // Update public balance - while (true) { - grpc::ClientContext ctx; - GetPublicBalanceRequest req; - req.set_user_id(userId_); - GetPublicBalanceResponse resp; - auto status = conn->getPublicBalance(&ctx, req, &resp); - printStatus(status, "getPublicBalance"); - - if (resp.has_err()) { + { + WalletRequest req; + req.mutable_get_public_balance()->set_user_id(userId_); + chan->Write(req); + + WalletResponse resp; + chan->Read(&resp); + if (!resp.has_get_public_balance()) + throw std::runtime_error("Expected get public balance response from wallet service!"); + const auto& getPubBalanceResp = resp.get_public_balance(); + + if (getPubBalanceResp.has_err()) { std::cout << "Failed to get public balance:" << resp.err() << '\n'; - break; - } else if (!resp.has_public_balance()) { - std::cout << "retry getPublicBalance\n"; } else { - publicBalance_ = resp.public_balance(); - break; // Done + publicBalance_ = getPubBalanceResp.public_balance(); } } @@ -293,107 +303,68 @@ void Wallet::syncState(Connection& conn, uint64_t lastKnownTxNum) { if (lastKnownTxNum == 0) { std::cout << "Last known tx number is zero (or not provided) - fetching last added tx number...\n"; - grpc::ClientContext ctx; - GetLastAddedTxNumberRequest req; - GetLastAddedTxNumberResponse resp; - conn->getLastAddedTxNumber(&ctx, req, &resp); + WalletRequest req; + req.mutable_get_last_added_tx_number(); + chan->Write(req); + + WalletResponse resp; + chan->Read(&resp); + if (!resp.has_get_last_added_tx_number()) + throw std::runtime_error("Expected get last added tx number response from wallet service!"); + const auto& getLastAddedTxNumResp = resp.get_last_added_tx_number(); - if (resp.has_err()) { + if (getLastAddedTxNumResp.has_err()) { std::cout << "Failed to get last added tx number:" << resp.err() << '\n'; } else { - std::cout << "Got last added tx number:" << resp.tx_number() << '\n'; - lastKnownTxNum = resp.tx_number(); + std::cout << "Got last added tx number:" << getLastAddedTxNumResp.tx_number() << '\n'; + lastKnownTxNum = getLastAddedTxNumResp.tx_number(); } } for (uint64_t txNum = user_->getLastExecutedTxNum() + 1; txNum <= lastKnownTxNum; ++txNum) { - grpc::ClientContext ctx; + WalletRequest req; + req.mutable_get_signed_tx()->set_tx_number(txNum); + chan->Write(req); - GetSignedTransactionRequest req; - req.set_tx_number(txNum); + WalletResponse resp; + chan->Read(&resp); + if (!resp.has_get_signed_tx()) throw std::runtime_error("Expected get signed tx response from wallet service!"); + const auto& getSignedTxResp = resp.get_signed_tx(); - GetSignedTransactionResponse resp; - auto status = conn->getSignedTransaction(&ctx, req, &resp); - printStatus(status, "getSignedTransaction"); - - if (resp.has_err()) { + if (getSignedTxResp.has_err()) { std::cout << "Failed to get signed tx with number " << txNum << ':' << resp.err() << '\n'; return; } - if (!resp.has_tx_number()) { + if (!getSignedTxResp.has_tx_number()) { std::cout << "Missing tx number in GetSignedTransactionResponse!\n"; return; } - std::cout << "Got " << TxType_Name(resp.tx_type()) << " transaction.\n"; - std::cout << "Tx num: " << resp.tx_number() << '\n'; - std::cout << "Tx data size: " << resp.tx_data().size() << " bytes\n"; - std::cout << "Tx data actual size: " << resp.tx_data_size() << " bytes\n"; - std::cout << "Num Sigs: " << resp.sigs_size() << '\n'; - utt::Transaction tx; - std::copy(resp.tx_data().begin(), resp.tx_data().end(), std::back_inserter(tx.data_)); - - auto getTxData = [](Connection& conn, uint64_t txNumber, std::vector& buff) { - grpc::ClientContext ctx; - - GetTxDataRequest req; - req.set_tx_number(txNumber); - req.set_byte_offset((uint32_t)buff.size()); - - GetTxDataResponse resp; - conn->getTxData(&ctx, req, &resp); - - if (resp.has_err()) { - throw std::runtime_error("getTxData: " + resp.err()); - } - std::cout << "got extra data:" << resp.tx_data().size() << '\n'; - - std::copy(resp.tx_data().begin(), resp.tx_data().end(), std::back_inserter(buff)); - }; - - // [TODO-UTT] As it seems the wallet grpc server written in node.js using grpc-js occasionally - // sends empty message (with missing fields) with no error either on the client or server. - // I have observed this mostly when fetching transactions which are a bit larger than other messages - // but not much - 20-50k. My initial suspicion was that the size of the proto message was at fault - // so I added a way to fetch the data in chunks. Each tx is cached in the wallet service and the client - // will fetch it sequentially in the next loop with 'getTxData'. This behavior could be a bug on part - // of the proto serialization in node.js or some misuse of gRPC in this client or the server. In any - // occasion this issue will plague the demo if not fixed properly, because syncing will fail and it - // needs to be retried. - while (true) { - if (tx.data_.size() < resp.tx_data_size()) { - std::cout << "Tx data was incomplete - fetch additional tx data...\n"; - getTxData(conn, resp.tx_number(), tx.data_); - } else if (tx.data_.size() > resp.tx_data_size()) { - throw std::runtime_error("Got more bytes than the actual tx size!"); - } else { // tx size == actual size - break; // Done - } - } + std::copy(getSignedTxResp.tx_data().begin(), getSignedTxResp.tx_data().end(), std::back_inserter(tx.data_)); utt::TxOutputSigs sigs; - sigs.reserve((size_t)resp.sigs_size()); - for (const auto& sig : resp.sigs()) { + sigs.reserve((size_t)getSignedTxResp.sigs_size()); + for (const auto& sig : getSignedTxResp.sigs()) { sigs.emplace_back(std::vector(sig.begin(), sig.end())); } // Apply transaction - switch (resp.tx_type()) { + switch (getSignedTxResp.tx_type()) { case TxType::MINT: { tx.type_ = utt::Transaction::Type::Mint; if (sigs.size() != 1) throw std::runtime_error("Expected single signature in mint tx!"); - user_->updateMintTx(resp.tx_number(), tx, sigs[0]); + user_->updateMintTx(getSignedTxResp.tx_number(), tx, sigs[0]); } break; case TxType::TRANSFER: { tx.type_ = utt::Transaction::Type::Transfer; - user_->updateTransferTx(resp.tx_number(), tx, sigs); + user_->updateTransferTx(getSignedTxResp.tx_number(), tx, sigs); } break; case TxType::BURN: { tx.type_ = utt::Transaction::Type::Burn; if (!sigs.empty()) throw std::runtime_error("Expected no signatures for burn tx!"); - user_->updateBurnTx(resp.tx_number(), tx); + user_->updateBurnTx(getSignedTxResp.tx_number(), tx); } break; default: throw std::runtime_error("Unexpected tx type!"); From e36b04d7cab226853edcba2fc1e1eb2408f0e7a7 Mon Sep 17 00:00:00 2001 From: Evgeniy Dzhurov Date: Tue, 25 Oct 2022 09:59:30 +0300 Subject: [PATCH 27/44] Fix pki was referenced as a local variable. Renaming --- utt/wallet-cli/src/main.cpp | 6 +++--- utt/wallet-cli/src/wallet.cpp | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/utt/wallet-cli/src/main.cpp b/utt/wallet-cli/src/main.cpp index 82a27cb4de..5a71bab617 100644 --- a/utt/wallet-cli/src/main.cpp +++ b/utt/wallet-cli/src/main.cpp @@ -68,6 +68,7 @@ struct CLIApp { Wallet::Connection conn; Wallet::Channel chan; utt::Configuration config; + utt::client::TestUserPKInfrastructure pki; std::map wallets; bool deployed = false; @@ -97,7 +98,6 @@ struct CLIApp { auto configs = Wallet::deployApp(chan); config = std::move(configs.first); // Save the full config for creating budgets locally later - utt::client::TestUserPKInfrastructure pki; auto testUserIds = pki.getUserIds(); for (const auto& userId : testUserIds) { std::cout << "Creating test user with id '" << userId << "'\n"; @@ -112,7 +112,7 @@ struct CLIApp { return &it->second; }; - void registerCmd(const std::vector& cmdTokens) { + void registerUserCmd(const std::vector& cmdTokens) { if (cmdTokens.size() != 2) { std::cout << "Usage: register \n"; return; @@ -253,7 +253,7 @@ int main(int argc, char* argv[]) { if (cmdTokens.empty()) continue; if (cmdTokens[0] == "register") { - app.registerCmd(cmdTokens); + app.registerUserCmd(cmdTokens); } else if (cmdTokens[0] == "create-budget") { app.createBudgetCmd(cmdTokens); } else if (cmdTokens[0] == "show") { diff --git a/utt/wallet-cli/src/wallet.cpp b/utt/wallet-cli/src/wallet.cpp index 7a48bc6310..d675e621b9 100644 --- a/utt/wallet-cli/src/wallet.cpp +++ b/utt/wallet-cli/src/wallet.cpp @@ -102,15 +102,15 @@ void Wallet::registerUser(Channel& chan) { WalletResponse resp; chan->Read(&resp); if (!resp.has_register_user()) throw std::runtime_error("Expected register response from wallet service!"); - const auto& regUser = resp.register_user(); + const auto& regUserResp = resp.register_user(); - if (regUser.has_err()) { - std::cout << "Failed to register user: " << regUser.err() << '\n'; + if (regUserResp.has_err()) { + std::cout << "Failed to register user: " << regUserResp.err() << '\n'; } else { - utt::RegistrationSig sig = std::vector(regUser.signature().begin(), regUser.signature().end()); + utt::RegistrationSig sig = std::vector(regUserResp.signature().begin(), regUserResp.signature().end()); std::cout << "Got sig for registration with size: " << sig.size() << '\n'; - utt::S2 s2 = std::vector(regUser.s2().begin(), regUser.s2().end()); + utt::S2 s2 = std::vector(regUserResp.s2().begin(), regUserResp.s2().end()); std::cout << "Got S2 for registration: ["; for (const auto& val : s2) std::cout << val << ' '; std::cout << "]\n"; From fdf54e4b18296c6636bd21dfae643a4f9611e2cf Mon Sep 17 00:00:00 2001 From: Evgeniy Dzhurov Date: Tue, 25 Oct 2022 16:18:31 +0300 Subject: [PATCH 28/44] Initial split of the privacy-admin-cli application with just the deploy command from utt-wallet-cli --- utt/CMakeLists.txt | 9 ++ utt/admin-cli/CMakeLists.txt | 14 +++ utt/admin-cli/README.md | 3 + utt/admin-cli/include/admin.hpp | 42 +++++++++ utt/admin-cli/proto/CMakeLists.txt | 15 ++++ utt/admin-cli/proto/api/v1/api.proto | 47 ++++++++++ utt/admin-cli/src/admin.cpp | 94 +++++++++++++++++++ utt/admin-cli/src/main.cpp | 129 +++++++++++++++++++++++++++ 8 files changed, 353 insertions(+) create mode 100644 utt/admin-cli/CMakeLists.txt create mode 100644 utt/admin-cli/README.md create mode 100644 utt/admin-cli/include/admin.hpp create mode 100644 utt/admin-cli/proto/CMakeLists.txt create mode 100644 utt/admin-cli/proto/api/v1/api.proto create mode 100644 utt/admin-cli/src/admin.cpp create mode 100644 utt/admin-cli/src/main.cpp diff --git a/utt/CMakeLists.txt b/utt/CMakeLists.txt index 70f7673691..cacbdb4cd0 100644 --- a/utt/CMakeLists.txt +++ b/utt/CMakeLists.txt @@ -41,6 +41,12 @@ option( OFF ) +option( + BUILD_PRIVACY_ADMIN_CLI + "Enable building of admin-cli" + OFF +) + # # Configure CCache if available # @@ -186,6 +192,9 @@ add_subdirectory(libxutils) if(BUILD_WALLET_CLI) add_subdirectory(wallet-cli) endif() +if(BUILD_PRIVACY_ADMIN_CLI) + add_subdirectory(admin-cli) +endif() # [TODO-UTT] This improved api for libutt could go into its own subproject set(newutt_src diff --git a/utt/admin-cli/CMakeLists.txt b/utt/admin-cli/CMakeLists.txt new file mode 100644 index 0000000000..fb8343e09f --- /dev/null +++ b/utt/admin-cli/CMakeLists.txt @@ -0,0 +1,14 @@ +add_subdirectory("proto") + +set(privacy-admin-cli-src + src/main.cpp + src/admin.cpp +) + +add_executable(privacy-admin-cli ${privacy-admin-cli-src}) + +target_include_directories(privacy-admin-cli PUBLIC include/ ../utt-client-api/include ../utt-common-api/include) + +target_link_libraries(privacy-admin-cli PUBLIC + privacy-admin-api-proto utt_client_api +) \ No newline at end of file diff --git a/utt/admin-cli/README.md b/utt/admin-cli/README.md new file mode 100644 index 0000000000..3057de0e58 --- /dev/null +++ b/utt/admin-cli/README.md @@ -0,0 +1,3 @@ +# UTT Admin Command-Line Application + +A command-line application for a UTT admin. Uses gRPC to talk to a admin service, for which we provide the interface in [proto/api/v1/api.proto](proto/api/v1/api.proto). The actual implementation of an admin service is not provided. \ No newline at end of file diff --git a/utt/admin-cli/include/admin.hpp b/utt/admin-cli/include/admin.hpp new file mode 100644 index 0000000000..cf836b112e --- /dev/null +++ b/utt/admin-cli/include/admin.hpp @@ -0,0 +1,42 @@ +// UTT Client API +// +// Copyright (c) 2020-2022 VMware, Inc. All Rights Reserved. +// +// This product is licensed to you under the Apache 2.0 license (the "License"). +// You may not use this product except in compliance with the Apache 2.0 +// License. +// +// This product may include a number of subcomponents with separate copyright +// notices and license terms. Your use of these subcomponents is subject to the +// terms and conditions of the sub-component's license, as noted in the LICENSE +// file. + +#pragma once + +#include +#include + +#include +#include "api.grpc.pb.h" // Generated from utt/admin/proto/api + +#include + +namespace AdminApi = vmware::concord::utt::admin::api::v1; + +class Admin { + public: + using Connection = std::unique_ptr; + using Channel = std::unique_ptr>; + + static Connection newConnection(); + + /// [TODO-UTT] Should be performed by an admin app + /// @brief Deploy a privacy application + /// @return The public configuration of the deployed application + static bool deployApp(Channel& chan); + + /// @brief Request the creation of a privacy budget. The amount of the budget is predetermined by the deployed app. + /// This operation could be performed entirely by an administrator, but we add it in the admin + /// for demo purposes. + static void createPrivacyBudget(Channel& chan); +}; \ No newline at end of file diff --git a/utt/admin-cli/proto/CMakeLists.txt b/utt/admin-cli/proto/CMakeLists.txt new file mode 100644 index 0000000000..2487f51e15 --- /dev/null +++ b/utt/admin-cli/proto/CMakeLists.txt @@ -0,0 +1,15 @@ +find_package(Protobuf REQUIRED) +find_package(GRPC REQUIRED) + +include_directories(${GRPC_INCLUDE_DIR}) + +protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS ${CMAKE_CURRENT_BINARY_DIR} + api/v1/api.proto +) +grpc_generate_cpp(GRPC_SRCS GRPC_HDRS ${CMAKE_CURRENT_BINARY_DIR} + api/v1/api.proto +) + +add_library(privacy-admin-api-proto STATIC ${PROTO_SRCS} ${GRPC_SRCS}) +target_link_libraries(privacy-admin-api-proto PRIVATE protobuf::libprotobuf gRPC::grpc++) +target_include_directories(privacy-admin-api-proto PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) \ No newline at end of file diff --git a/utt/admin-cli/proto/api/v1/api.proto b/utt/admin-cli/proto/api/v1/api.proto new file mode 100644 index 0000000000..c6d336ef46 --- /dev/null +++ b/utt/admin-cli/proto/api/v1/api.proto @@ -0,0 +1,47 @@ +// Copyright 2021 VMware, all rights reserved +// +// UTT Admin's api + +syntax = "proto3"; +// [TODO-UTT] Condense this package identifier into fewer tokens? +package vmware.concord.utt.admin.api.v1; + +// Privacy Admin Service Interface +service AdminService { + rpc adminChannel(stream AdminRequest) returns (stream AdminResponse); +} + +message DeployPrivacyAppRequest { + optional bytes config = 1; +} + +message DeployPrivacyAppResponse { + optional string err = 1; // Returns any error generated during deployment + optional string privacy_contract_addr = 2; // Address of the deployed privacy contract + optional string token_contract_addr = 3; // Address of the deployed token contract +} + +message CreatePrivacyBudgetRequest { + optional string user_id = 1; +} + +message CreatePrivacyBudgetResponse { + optional string err = 1; + optional bytes budget = 2; + optional bytes signature = 3; +} + +message AdminRequest { + oneof req { + DeployPrivacyAppRequest deploy = 1; + CreatePrivacyBudgetRequest create_budget = 2; + } +} + +message AdminResponse { + optional string err = 1; + oneof resp { + DeployPrivacyAppResponse deploy = 2; + CreatePrivacyBudgetResponse create_budget = 3; + } +} \ No newline at end of file diff --git a/utt/admin-cli/src/admin.cpp b/utt/admin-cli/src/admin.cpp new file mode 100644 index 0000000000..607a666286 --- /dev/null +++ b/utt/admin-cli/src/admin.cpp @@ -0,0 +1,94 @@ +// UTT Client API +// +// Copyright (c) 2020-2022 VMware, Inc. All Rights Reserved. +// +// This product is licensed to you under the Apache 2.0 license (the "License"). +// You may not use this product except in compliance with the Apache 2.0 +// License. +// +// This product may include a number of subcomponents with separate copyright +// notices and license terms. Your use of these subcomponents is subject to the +// terms and conditions of the sub-component's license, as noted in the LICENSE +// file. + +#include "admin.hpp" + +#include + +using namespace vmware::concord::utt::admin::api::v1; + +Admin::Connection Admin::newConnection() { + std::string grpcServerAddr = "127.0.0.1:49000"; + + std::cout << "Connecting to gRPC server at " << grpcServerAddr << " ...\n"; + + auto chan = grpc::CreateChannel(grpcServerAddr, grpc::InsecureChannelCredentials()); + + if (!chan) { + throw std::runtime_error("Failed to create gRPC channel."); + } + auto timeoutSec = std::chrono::seconds(5); + if (chan->WaitForConnected(std::chrono::system_clock::now() + timeoutSec)) { + std::cout << "Connected.\n"; + } else { + throw std::runtime_error("Failed to connect to gRPC server after " + std::to_string(timeoutSec.count()) + + " seconds."); + } + + return AdminService::NewStub(chan); +} + +bool Admin::deployApp(Channel& chan) { + // Generate a privacy config for a N=4 replica system tolerating F=1 failures + utt::client::ConfigInputParams params; + params.validatorPublicKeys = std::vector{4, "placeholderPublicKey"}; // N = 3 * F + 1 + params.threshold = 2; // F + 1 + auto config = utt::client::generateConfig(params); + if (config.empty()) throw std::runtime_error("Failed to generate a privacy app configuration!"); + + AdminRequest req; + req.mutable_deploy()->set_config(config.data(), config.size()); + chan->Write(req); + + AdminResponse resp; + chan->Read(&resp); + if (!resp.has_deploy()) throw std::runtime_error("Expected deploy response from admin service!"); + std::cout << "response case: " << resp.resp_case() << '\n'; + const auto& deployResp = resp.deploy(); + std::cout << "has_privacy_contract_addr:" << deployResp.has_privacy_contract_addr() << '\n'; + std::cout << "has_token_contract_addr:" << deployResp.has_token_contract_addr() << '\n'; + + // Note that keeping the config around in memory is just a temp solution and should not happen in real system + if (deployResp.has_err()) throw std::runtime_error("Failed to deploy privacy app: " + resp.err()); + + std::cout << "\nDeployed privacy application\n"; + std::cout << "-----------------------------------\n"; + std::cout << "Privacy contract: " << deployResp.privacy_contract_addr() << '\n'; + std::cout << "Token contract: " << deployResp.token_contract_addr() << '\n'; + + return true; +} + +void Admin::createPrivacyBudget(Channel& chan) { + (void)chan; + // [TODO-UTT] Create budget is done locally, should be done by the system + // grpc::ClientContext ctx; + + // CreatePrivacyBudgetRequest req; + // req.set_user_id(userId_); + + // CreatePrivacyBudgetResponse resp; + // conn->createPrivacyBudget(&ctx, req, &resp); + + // if (resp.has_err()) { + // std::cout << "Failed to create privacy budget:" << resp.err() << '\n'; + // } else { + // utt::PrivacyBudget budget = std::vector(resp.budget().begin(), resp.budget().end()); + // utt::RegistrationSig sig = std::vector(resp.signature().begin(), resp.signature().end()); + + // std::cout << "Got budget " << budget.size() << " bytes.\n"; + // std::cout << "Got budget sig " << sig.size() << " bytes.\n"; + + // user_->updatePrivacyBudget(budget, sig); + // } +} \ No newline at end of file diff --git a/utt/admin-cli/src/main.cpp b/utt/admin-cli/src/main.cpp new file mode 100644 index 0000000000..aa23898cdc --- /dev/null +++ b/utt/admin-cli/src/main.cpp @@ -0,0 +1,129 @@ +// UTT Client API +// +// Copyright (c) 2020-2022 VMware, Inc. All Rights Reserved. +// +// This product is licensed to you under the Apache 2.0 license (the "License"). +// You may not use this product except in compliance with the Apache 2.0 +// License. +// +// This product may include a number of subcomponents with separate copyright +// notices and license terms. Your use of these subcomponents is subject to the +// terms and conditions of the sub-component's license, as noted in the LICENSE +// file. + +#include +#include +#include + +#include "admin.hpp" + +void printHelp() { + std::cout << "\nCommands:\n"; + std::cout + << "deploy -- generates a privacy app config and deploys the privacy and token contracts.\n"; + // [TODO-UTT] Admin creates a user's budget by supplying a user-id and amount from the CLI + // std::cout + // << "create-budget -- requests creation of a privacy budget, the amount is decided by the + // system.\n"; + std::cout << '\n'; +} + +struct CLIApp { + grpc::ClientContext ctx; + Admin::Connection conn; + Admin::Channel chan; + utt::Configuration config; + utt::client::TestUserPKInfrastructure pki; + bool deployed = false; + + CLIApp() { + conn = Admin::newConnection(); + if (!conn) throw std::runtime_error("Failed to create admin connection!"); + + chan = conn->adminChannel(&ctx); + if (!chan) throw std::runtime_error("Failed to create admin streaming channel!"); + } + + ~CLIApp() { + std::cout << "Closing admin streaming channel...\n"; + chan->WritesDone(); + auto status = chan->Finish(); + std::cout << "gRPC error code: " << status.error_code() << '\n'; + std::cout << "gRPC error msg: " << status.error_message() << '\n'; + std::cout << "gRPC error details: " << status.error_details() << '\n'; + } + + void deploy() { + if (deployed) { + std::cout << "The privacy app is already deployed.\n"; + return; + } + + deployed = Admin::deployApp(chan); + } + + void createBudgetCmd(const std::vector& cmdTokens) { + (void)cmdTokens; + //[TODO-UTT] Create by sending a request to the system + // if (cmdTokens.size() != 2) { + // std::cout << "Usage: create-budget \n"; + // return; + // } + // auto admin = getAdmin(cmdTokens[1]); + // if (!admin) { + // std::cout << "No admin for '" << cmdTokens[1] << "'\n"; + // return; + // } + + // admin->createPrivacyBudgetLocal(config, 10000); + } +}; + +int main(int argc, char* argv[]) { + (void)argc; + (void)argv; + std::cout << "Sample Privacy Admin CLI Application.\n"; + + try { + utt::client::Initialize(); + + CLIApp app; + + while (true) { + std::cout << "\nEnter command (type 'h' for commands 'Ctr-D' to quit):\n > "; + std::string cmd; + std::getline(std::cin, cmd); + + if (std::cin.eof()) { + std::cout << "Quitting...\n"; + break; + } + + if (cmd == "h") { + printHelp(); + } else if (cmd == "deploy") { + app.deploy(); + } else if (!app.deployed) { + std::cout << "You must first deploy the privacy application. Use the 'deploy' command.\n"; + } else { + // Tokenize params + std::vector cmdTokens; + std::string token; + std::stringstream ss(cmd); + while (std::getline(ss, token, ' ')) cmdTokens.emplace_back(token); + if (cmdTokens.empty()) continue; + + if (cmdTokens[0] == "create-budget") { + app.createBudgetCmd(cmdTokens); + } else { + std::cout << "Unknown command '" << cmd << "'\n"; + } + } + } + } catch (const std::runtime_error& e) { + std::cout << "Error (exception): " << e.what() << '\n'; + return 1; + } + + return 0; +} \ No newline at end of file From 6d85b560657e6731fd86737ab571b1048fd3d4c5 Mon Sep 17 00:00:00 2001 From: Evgeniy Dzhurov Date: Wed, 26 Oct 2022 11:44:16 +0000 Subject: [PATCH 29/44] Transform the deploy command into a configure command in the wallet-cli. Instead of generating and deploying a config, the configure command will fetch an already deployed configuration by the admin-cli --- utt/wallet-cli/include/wallet.hpp | 10 +++-- utt/wallet-cli/proto/api/v1/api.proto | 54 +++++++-------------------- utt/wallet-cli/src/main.cpp | 25 ++++++------- utt/wallet-cli/src/wallet.cpp | 37 +++++++++--------- 4 files changed, 48 insertions(+), 78 deletions(-) diff --git a/utt/wallet-cli/include/wallet.hpp b/utt/wallet-cli/include/wallet.hpp index 1b4265578a..9bbe05aeb2 100644 --- a/utt/wallet-cli/include/wallet.hpp +++ b/utt/wallet-cli/include/wallet.hpp @@ -30,10 +30,12 @@ class Wallet { static Connection newConnection(); - /// [TODO-UTT] Should be performed by an admin app - /// @brief Deploy a privacy application - /// @return The public configuration of the deployed application - static std::pair deployApp(Channel& chan); + /// @brief Get the configuration for a privacy application + /// @return The full and public configurations of the deployed application + /// [TODO-UTT] We only need the public config but the full config is returned + /// because we needed for createPrivacyBudgetLocal which should also be removed + /// in favor of a system-created budget tokens by the admin + static std::pair getConfigs(Channel& chan); /// [TODO-UTT] Create privacy budget locally because the system can't process budget requests yet. /// @brief Create a privacy budget locally for the user. This function is only for testing. diff --git a/utt/wallet-cli/proto/api/v1/api.proto b/utt/wallet-cli/proto/api/v1/api.proto index 8524a77ca6..c7ce6c964e 100644 --- a/utt/wallet-cli/proto/api/v1/api.proto +++ b/utt/wallet-cli/proto/api/v1/api.proto @@ -8,39 +8,6 @@ package vmware.concord.utt.wallet.api.v1; // Privacy Wallet Service Interface service WalletService { - // Temporary adding the deployment of privacy app to the wallet service - // [TODO-UTT] This operation should be performed by an administrator-only tool - rpc deployPrivacyApp(DeployPrivacyAppRequest) returns (DeployPrivacyAppResponse); - rpc registerUser(RegisterUserRequest) returns (RegisterUserResponse); - - // A non-standard way for a client to force the creation of a privacy budget. - // Usually this operation is performed by an administrator. - // The value of the returned budget is determined by the system - rpc createPrivacyBudget(CreatePrivacyBudgetRequest) returns (CreatePrivacyBudgetResponse); - - rpc transfer(TransferRequest) returns (TransferResponse); - - // Note: mint is supposed to convert public funds (Ether, ERC-20 tokens, etc.) to private funds (UTT tokens). - // Here we make an assumption that the wallet service keeps track of our public funds but in reality - // we need a way to use one or more Ethereum accounts representing the public funds of the user. - rpc mint(MintRequest) returns (MintResponse); - - // Note: burn is supposed to convert private funds (UTT tokens) to public funds (Ether, ERC-20 tokens, etc.). - // Here we make an assumption that the wallet service keeps track of our public funds but in reality - // we need a way to use one or more Ethereum accounts representing the public funds of the user. - rpc burn(BurnRequest) returns (BurnResponse); - - // Same as getNumOfLastAddedTranscation from spec - rpc getLastAddedTxNumber(GetLastAddedTxNumberRequest) returns (GetLastAddedTxNumberResponse); - - // Same as getNumOfLastSignedTranscation from spec - rpc getLastSignedTxNumber(GetLastSignedTxNumberRequest) returns (GetLastSignedTxNumberResponse); - - // Fetch an already signed (completed) transaction (either mint, burn or transfer). - rpc getSignedTransaction(GetSignedTransactionRequest) returns (GetSignedTransactionResponse); - - rpc getPublicBalance(GetPublicBalanceRequest) returns (GetPublicBalanceResponse); - rpc walletChannel(stream WalletRequest) returns (stream WalletResponse); } @@ -51,14 +18,19 @@ enum TxType { TRANSFER = 3; } -message DeployPrivacyAppRequest { - optional bytes config = 1; +message ConfigureRequest { } -message DeployPrivacyAppResponse { - optional string err = 1; // Returns any error generated during deployment - optional string privacy_contract_addr = 2; // Address of the deployed privacy contract - optional string token_contract_addr = 3; // Address of the deployed token contract +message ConfigureResponse { + optional string err = 1; + optional string privacy_contract_addr = 2; + optional string token_contract_addr = 3; + optional bytes public_config = 4; + + // [TODO-UTT] Remove the whole configuration. We need only the public config and not the full config. + // The full config is only needed to create budget tokens locally, but budget tokens should be + // create by a request from an admin to the system. + optional bytes config = 5; } message RegisterUserRequest { @@ -156,7 +128,7 @@ message GetPublicBalanceResponse { message WalletRequest { oneof req { // init - DeployPrivacyAppRequest deploy = 1; + ConfigureRequest configure = 1; RegisterUserRequest register_user = 2; // transact TransferRequest transfer = 3; @@ -173,7 +145,7 @@ message WalletResponse { optional string err = 1; oneof resp { // init - DeployPrivacyAppResponse deploy = 2; + ConfigureResponse configure = 2; RegisterUserResponse register_user = 3; // transact TransferResponse transfer = 4; diff --git a/utt/wallet-cli/src/main.cpp b/utt/wallet-cli/src/main.cpp index 5a71bab617..b3e4065f95 100644 --- a/utt/wallet-cli/src/main.cpp +++ b/utt/wallet-cli/src/main.cpp @@ -51,8 +51,7 @@ void printHelp() { std::cout << "\nCommands:\n"; - std::cout << "deploy -- generates a privacy app config, deploys it on the blockchain and creates " - "test users.\n"; + std::cout << "config -- configures wallets with the privacy application.\n"; std::cout << "show -- prints information about the user managed by this wallet\n"; std::cout << "register -- requests user registration required for spending coins\n"; std::cout @@ -70,7 +69,7 @@ struct CLIApp { utt::Configuration config; utt::client::TestUserPKInfrastructure pki; std::map wallets; - bool deployed = false; + bool configured = false; CLIApp() { conn = Wallet::newConnection(); @@ -89,21 +88,21 @@ struct CLIApp { std::cout << "gRPC error details: " << status.error_details() << '\n'; } - void deploy() { - if (deployed) { - std::cout << "The privacy app is already deployed.\n"; + void configure() { + if (configured) { + std::cout << "The wallets are already configured.\n"; return; } - auto configs = Wallet::deployApp(chan); - config = std::move(configs.first); // Save the full config for creating budgets locally later + auto configs = Wallet::getConfigs(chan); + config = configs.first; auto testUserIds = pki.getUserIds(); for (const auto& userId : testUserIds) { std::cout << "Creating test user with id '" << userId << "'\n"; wallets.emplace(userId, Wallet(userId, pki, configs.second)); } - deployed = true; + configured = true; } Wallet* getWallet(const std::string& userId) { @@ -240,10 +239,10 @@ int main(int argc, char* argv[]) { if (cmd == "h") { printHelp(); - } else if (cmd == "deploy") { - app.deploy(); - } else if (!app.deployed) { - std::cout << "You must first deploy the privacy application. Use the 'deploy' command.\n"; + } else if (cmd == "config") { + app.configure(); + } else if (!app.configured) { + std::cout << "You must first configure the privacy application. Use the 'config' command.\n"; } else { // Tokenize params std::vector cmdTokens; diff --git a/utt/wallet-cli/src/wallet.cpp b/utt/wallet-cli/src/wallet.cpp index d675e621b9..576bcbb41b 100644 --- a/utt/wallet-cli/src/wallet.cpp +++ b/utt/wallet-cli/src/wallet.cpp @@ -24,7 +24,7 @@ Wallet::Wallet(std::string userId, utt::client::TestUserPKInfrastructure& pki, c } Wallet::Connection Wallet::newConnection() { - std::string grpcServerAddr = "127.0.0.1:49000"; + std::string grpcServerAddr = "127.0.0.1:49001"; std::cout << "Connecting to gRPC server at " << grpcServerAddr << " ...\n"; @@ -53,35 +53,32 @@ void Wallet::showInfo(Channel& chan) { std::cout << "Last executed tx number: " << user_->getLastExecutedTxNum() << '\n'; } -std::pair Wallet::deployApp(Channel& chan) { - // Generate a privacy config for a N=4 replica system tolerating F=1 failures - utt::client::ConfigInputParams params; - params.validatorPublicKeys = std::vector{4, "placeholderPublicKey"}; // N = 3 * F + 1 - params.threshold = 2; // F + 1 - auto config = utt::client::generateConfig(params); - if (config.empty()) throw std::runtime_error("Failed to generate a privacy app configuration!"); - +std::pair Wallet::getConfigs(Channel& chan) { WalletRequest req; - req.mutable_deploy()->set_config(config.data(), config.size()); + req.mutable_configure(); chan->Write(req); WalletResponse resp; chan->Read(&resp); - if (!resp.has_deploy()) throw std::runtime_error("Expected deploy response from wallet service!"); - std::cout << "response case: " << resp.resp_case() << '\n'; - const auto& deployResp = resp.deploy(); - std::cout << "has_privacy_contract_addr:" << deployResp.has_privacy_contract_addr() << '\n'; - std::cout << "has_token_contract_addr:" << deployResp.has_token_contract_addr() << '\n'; + if (!resp.has_configure()) throw std::runtime_error("Expected configure response from wallet service!"); + const auto& configureResp = resp.configure(); // Note that keeping the config around in memory is just a temp solution and should not happen in real system - if (deployResp.has_err()) throw std::runtime_error("Failed to deploy privacy app: " + resp.err()); + if (configureResp.has_err()) throw std::runtime_error("Failed to configure: " + resp.err()); - std::cout << "\nDeployed privacy application\n"; + std::cout << "\nConfigured privacy application\n"; std::cout << "-----------------------------------\n"; - std::cout << "Privacy contract: " << deployResp.privacy_contract_addr() << '\n'; - std::cout << "Token contract: " << deployResp.token_contract_addr() << '\n'; + std::cout << "Privacy contract: " << configureResp.privacy_contract_addr() << '\n'; + std::cout << "Token contract: " << configureResp.token_contract_addr() << '\n'; + std::cout << "Full config size: " << configureResp.config().size() << '\n'; + std::cout << "Public config size: " << configureResp.public_config().size() << '\n'; + + utt::Configuration config(configureResp.config().begin(), configureResp.config().end()); + // utt::PublicConfig publicConfig(configureResp.public_config().begin(), configureResp.public_config().end()); + + auto publicConfig = utt::client::getPublicConfig(config); - return std::pair{config, utt::client::getPublicConfig(config)}; + return std::pair{std::move(config), std::move(publicConfig)}; } void Wallet::createPrivacyBudgetLocal(const utt::Configuration& config, uint64_t amount) { From a1c93be4d62a6873bf9e2684f8f3fe7afd3d0978 Mon Sep 17 00:00:00 2001 From: Evgeniy Dzhurov Date: Wed, 26 Oct 2022 12:59:40 +0000 Subject: [PATCH 30/44] Use the public config obtained from the privacy contract --- utt/wallet-cli/src/wallet.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/utt/wallet-cli/src/wallet.cpp b/utt/wallet-cli/src/wallet.cpp index 576bcbb41b..8e2b95b06d 100644 --- a/utt/wallet-cli/src/wallet.cpp +++ b/utt/wallet-cli/src/wallet.cpp @@ -74,9 +74,12 @@ std::pair Wallet::getConfigs(Channel& cha std::cout << "Public config size: " << configureResp.public_config().size() << '\n'; utt::Configuration config(configureResp.config().begin(), configureResp.config().end()); - // utt::PublicConfig publicConfig(configureResp.public_config().begin(), configureResp.public_config().end()); + utt::PublicConfig publicConfig(configureResp.public_config().begin(), configureResp.public_config().end()); - auto publicConfig = utt::client::getPublicConfig(config); + auto otherPublicConfig = utt::client::getPublicConfig(config); + if (publicConfig != otherPublicConfig) { + throw std::runtime_error("The public config doesn't correspond to the one in the full config!"); + } return std::pair{std::move(config), std::move(publicConfig)}; } From 126abb6ff936a5eb50d54903addbb44c31b6eb5a Mon Sep 17 00:00:00 2001 From: Evgeniy Dzhurov Date: Thu, 27 Oct 2022 07:21:13 +0000 Subject: [PATCH 31/44] Make wallet-cli single user by providing a user-id as an argument --- utt/wallet-cli/include/wallet.hpp | 3 + utt/wallet-cli/src/main.cpp | 157 ++++++++++++------------------ utt/wallet-cli/src/wallet.cpp | 6 ++ 3 files changed, 69 insertions(+), 97 deletions(-) diff --git a/utt/wallet-cli/include/wallet.hpp b/utt/wallet-cli/include/wallet.hpp index 9bbe05aeb2..0449269c38 100644 --- a/utt/wallet-cli/include/wallet.hpp +++ b/utt/wallet-cli/include/wallet.hpp @@ -37,6 +37,8 @@ class Wallet { /// in favor of a system-created budget tokens by the admin static std::pair getConfigs(Channel& chan); + bool isRegistered() const; + /// [TODO-UTT] Create privacy budget locally because the system can't process budget requests yet. /// @brief Create a privacy budget locally for the user. This function is only for testing. /// @param config A Privacy app configuration @@ -82,4 +84,5 @@ class Wallet { utt::client::TestUserPKInfrastructure& pki_; std::unique_ptr user_; uint64_t publicBalance_ = 0; + bool registered_ = false; }; \ No newline at end of file diff --git a/utt/wallet-cli/src/main.cpp b/utt/wallet-cli/src/main.cpp index b3e4065f95..d12cca6ff5 100644 --- a/utt/wallet-cli/src/main.cpp +++ b/utt/wallet-cli/src/main.cpp @@ -52,24 +52,24 @@ void printHelp() { std::cout << "\nCommands:\n"; std::cout << "config -- configures wallets with the privacy application.\n"; - std::cout << "show -- prints information about the user managed by this wallet\n"; + std::cout << "show -- prints information about the user managed by this wallet\n"; std::cout << "register -- requests user registration required for spending coins\n"; - std::cout - << "create-budget -- requests creation of a privacy budget, the amount is decided by the system.\n"; - std::cout << "mint -- mint the requested amount of public funds.\n"; - std::cout << "transfer -- transfers the specified amount between users.\n"; - std::cout << "burn -- burns the specified amount of private funds to public funds.\n"; + std::cout << "create-budget -- requests creation of a privacy budget, the amount is decided by the " + "system.\n"; + std::cout << "mint -- mint the requested amount of public funds.\n"; + std::cout << "transfer -- transfers the specified amount between users.\n"; + std::cout << "burn -- burns the specified amount of private funds to public funds.\n"; std::cout << '\n'; } struct CLIApp { + std::string userId; grpc::ClientContext ctx; Wallet::Connection conn; Wallet::Channel chan; utt::Configuration config; utt::client::TestUserPKInfrastructure pki; - std::map wallets; - bool configured = false; + std::unique_ptr wallet; CLIApp() { conn = Wallet::newConnection(); @@ -89,88 +89,50 @@ struct CLIApp { } void configure() { - if (configured) { - std::cout << "The wallets are already configured.\n"; + if (wallet) { + std::cout << "The wallet is already configured.\n"; return; } auto configs = Wallet::getConfigs(chan); config = configs.first; - auto testUserIds = pki.getUserIds(); - for (const auto& userId : testUserIds) { - std::cout << "Creating test user with id '" << userId << "'\n"; - wallets.emplace(userId, Wallet(userId, pki, configs.second)); - } - configured = true; + wallet = std::make_unique(userId, pki, configs.second); } - Wallet* getWallet(const std::string& userId) { - auto it = wallets.find(userId); - if (it == wallets.end()) return nullptr; - return &it->second; - }; - - void registerUserCmd(const std::vector& cmdTokens) { - if (cmdTokens.size() != 2) { - std::cout << "Usage: register \n"; - return; - } - - auto wallet = getWallet(cmdTokens[1]); - if (!wallet) { - std::cout << "No wallet for '" << cmdTokens[1] << "'\n"; - return; + void registerUserCmd() { + if (!wallet->isRegistered()) { + wallet->registerUser(chan); + wallet->showInfo(chan); + } else { + std::cout << "Wallet is already registered.\n"; } - wallet->registerUser(chan); } - void createBudgetCmd(const std::vector& cmdTokens) { - if (cmdTokens.size() != 2) { - std::cout << "Usage: create-budget \n"; - return; - } - auto wallet = getWallet(cmdTokens[1]); - if (!wallet) { - std::cout << "No wallet for '" << cmdTokens[1] << "'\n"; - return; - } + void createBudgetCmd() { wallet->createPrivacyBudgetLocal(config, 10000); - } - - void showCmd(const std::vector& cmdTokens) { - if (cmdTokens.size() != 2) { - std::cout << "Usage: show \n"; - return; - } - auto wallet = getWallet(cmdTokens[1]); - if (!wallet) { - std::cout << "No wallet for '" << cmdTokens[1] << "'\n"; - return; - } wallet->showInfo(chan); } + void showCmd() { wallet->showInfo(chan); } + void mintCmd(const std::vector& cmdTokens) { - if (cmdTokens.size() != 3) { - std::cout << "Usage: mint \n"; + if (cmdTokens.size() != 2) { + std::cout << "Usage: mint \n"; return; } - auto wallet = getWallet(cmdTokens[1]); - if (!wallet) { - std::cout << "No wallet for '" << cmdTokens[1] << "'\n"; - } - int amount = std::atoi(cmdTokens[2].c_str()); + int amount = std::atoi(cmdTokens[1].c_str()); if (amount <= 0) { std::cout << "Expected a positive mint amount!\n"; return; } wallet->mint(chan, (uint64_t)amount); + wallet->showInfo(chan); } void transferCmd(const std::vector& cmdTokens) { - if (cmdTokens.size() != 4) { - std::cout << "Usage: transfer \n"; + if (cmdTokens.size() != 3) { + std::cout << "Usage: transfer \n"; return; } int amount = std::atoi(cmdTokens[1].c_str()); @@ -178,55 +140,54 @@ struct CLIApp { std::cout << "Expected a positive transfer amount!\n"; return; } - auto fromWallet = getWallet(cmdTokens[2]); - if (!fromWallet) { - std::cout << "No wallet for '" << cmdTokens[2] << "'\n"; - } - fromWallet->transfer(chan, (uint64_t)amount, cmdTokens[3]); + wallet->transfer(chan, (uint64_t)amount, cmdTokens[2]); + wallet->showInfo(chan); } void burnCmd(const std::vector& cmdTokens) { - if (cmdTokens.size() != 3) { - std::cout << "Usage: burn \n"; + if (cmdTokens.size() != 2) { + std::cout << "Usage: burn \n"; return; } - int amount = std::atoi(cmdTokens[2].c_str()); + int amount = std::atoi(cmdTokens[1].c_str()); if (amount <= 0) { std::cout << "Expected a positive burn amount!\n"; return; } - auto wallet = getWallet(cmdTokens[1]); - if (!wallet) { - std::cout << "No wallet for '" << cmdTokens[1] << "'\n"; - return; - } wallet->burn(chan, (uint64_t)amount); + wallet->showInfo(chan); } - void debugCmd(const std::vector& cmdTokens) { - if (cmdTokens.size() != 2) { - std::cout << "Usage: debug \n"; - return; - } - auto wallet = getWallet(cmdTokens[1]); - if (!wallet) { - std::cout << "No wallet for '" << cmdTokens[1] << "'\n"; - return; - } - wallet->debugOutput(); - } + void debugCmd() { wallet->debugOutput(); } }; int main(int argc, char* argv[]) { (void)argc; (void)argv; + + if (argc != 2) { + std::cout << "Provide a user-id.\n"; + return 0; + } + + CLIApp app; + app.userId = argv[1]; + + const auto userIds = app.pki.getUserIds(); + if (std::find(userIds.begin(), userIds.end(), app.userId) == userIds.end()) { + std::cout << "Selected user id has no pre-generated keys!\n"; + std::cout << "Valid user ids: ["; + for (const auto& userId : userIds) std::cout << userId << ", "; + std::cout << "]\n"; + return 0; + } + std::cout << "Sample Privacy Wallet CLI Application.\n"; + std::cout << "UserId: " << app.userId << '\n'; try { utt::client::Initialize(); - CLIApp app; - while (true) { std::cout << "\nEnter command (type 'h' for commands 'Ctr-D' to quit):\n > "; std::string cmd; @@ -241,8 +202,8 @@ int main(int argc, char* argv[]) { printHelp(); } else if (cmd == "config") { app.configure(); - } else if (!app.configured) { - std::cout << "You must first configure the privacy application. Use the 'config' command.\n"; + } else if (!app.wallet) { + std::cout << "You must first configure the wallet. Use the 'config' command.\n"; } else { // Tokenize params std::vector cmdTokens; @@ -252,11 +213,13 @@ int main(int argc, char* argv[]) { if (cmdTokens.empty()) continue; if (cmdTokens[0] == "register") { - app.registerUserCmd(cmdTokens); + app.registerUserCmd(); + } else if (!app.wallet->isRegistered()) { + std::cout << "You must first register the user. Use the 'register' command.\n"; } else if (cmdTokens[0] == "create-budget") { - app.createBudgetCmd(cmdTokens); + app.createBudgetCmd(); } else if (cmdTokens[0] == "show") { - app.showCmd(cmdTokens); + app.showCmd(); } else if (cmdTokens[0] == "mint") { app.mintCmd(cmdTokens); } else if (cmdTokens[0] == "transfer") { @@ -264,7 +227,7 @@ int main(int argc, char* argv[]) { } else if (cmdTokens[0] == "burn") { app.burnCmd(cmdTokens); } else if (cmdTokens[0] == "debug") { - app.debugCmd(cmdTokens); + app.debugCmd(); } else { std::cout << "Unknown command '" << cmd << "'\n"; } diff --git a/utt/wallet-cli/src/wallet.cpp b/utt/wallet-cli/src/wallet.cpp index 8e2b95b06d..49e13ccaca 100644 --- a/utt/wallet-cli/src/wallet.cpp +++ b/utt/wallet-cli/src/wallet.cpp @@ -88,7 +88,11 @@ void Wallet::createPrivacyBudgetLocal(const utt::Configuration& config, uint64_t user_->createPrivacyBudgetLocal(config, amount); } +bool Wallet::isRegistered() const { return registered_; } + void Wallet::registerUser(Channel& chan) { + if (registered_) throw std::runtime_error("Wallet is already registered!"); + auto userRegInput = user_->getRegistrationInput(); if (userRegInput.empty()) throw std::runtime_error("Failed to create user registration input!"); @@ -116,6 +120,8 @@ void Wallet::registerUser(Channel& chan) { std::cout << "]\n"; user_->updateRegistration(user_->getPK(), sig, s2); + + registered_ = true; } } From 12c711066087c87a12730fba01d5df99ac640d10 Mon Sep 17 00:00:00 2001 From: Yehonatan Buchnik Date: Mon, 31 Oct 2022 11:53:08 +0200 Subject: [PATCH 32/44] Add full validation capabilites --- .../include/sigProcessor.hpp | 27 ++++-- .../signature-processor/src/sigProcessor.cpp | 85 +++++++++++++++++-- .../tests/sigProcessorTests.cpp | 9 +- 3 files changed, 103 insertions(+), 18 deletions(-) diff --git a/utt-replica/signature-processor/include/sigProcessor.hpp b/utt-replica/signature-processor/include/sigProcessor.hpp index 5d61f7d63d..4f35e612ad 100644 --- a/utt-replica/signature-processor/include/sigProcessor.hpp +++ b/utt-replica/signature-processor/include/sigProcessor.hpp @@ -36,6 +36,25 @@ class SigProcessor { */ using GenerateAppClientRequestCb = std::function(uint64_t, const std::vector&)>; + class CompleteSignatureMsg { + std::map> sigs; + std::vector full_sig; + uint32_t num_replicas{0}; + + public: + CompleteSignatureMsg(uint32_t n, const std::map>&, const std::vector&); + explicit CompleteSignatureMsg() = default; + CompleteSignatureMsg(const std::vector& buffer); + bool validate() const; + const std::vector& getFullSig() const; + /* + The serialized output is in the following format: + [num_replicas|full_sig.size()|full_sig|sigs.size()|s.size()|s] (for s in sigs) + */ + std::vector serialize() const; + CompleteSignatureMsg& deserialize(const std::vector&); + }; + static const GenerateAppClientRequestCb default_client_app_request_generator; private: @@ -136,13 +155,9 @@ class SigProcessor { /** * @brief Publishing the complete signature to the consensus * - * @param sig_id the signature id - * @param complete_signature the complete aggregated signature - * @param cb a callback that defines how the upper level generates the application request to the consensus + * @param job_entry The relevant job */ - void publishCompleteSignature(uint64_t sig_id, - const std::vector& complete_signature, - const GenerateAppClientRequestCb& cb); + void publishCompleteSignature(const SigJobEntry& job_entry); /** * @brief Handles the event of timeout for a specific job * diff --git a/utt-replica/signature-processor/src/sigProcessor.cpp b/utt-replica/signature-processor/src/sigProcessor.cpp index b8ba459961..d3acf0e255 100644 --- a/utt-replica/signature-processor/src/sigProcessor.cpp +++ b/utt-replica/signature-processor/src/sigProcessor.cpp @@ -142,8 +142,7 @@ void SigProcessor::processSignature(uint64_t sig_id, #endif // if we already have enough valid partial signatures, lets publish the complete signature and return if (entry->partial_sigs.size() == threshold_) { - auto complete_sig = libutt::api::Utils::aggregateSigShares(n_, entry->partial_sigs); - publishCompleteSignature(sig_id, complete_sig, entry->client_app_data_generator_cb_); + publishCompleteSignature(*entry); return; } } @@ -180,18 +179,19 @@ void SigProcessor::onReceivingNewPartialSig(uint64_t sig_id, // We don't care doing this part under the entry lock, because if we did reach the threshold, we won't use this // entry anymore if (entry->client_app_data_generator_cb_ != nullptr && entry->partial_sigs.size() == threshold_) { - auto sig = libutt::api::Utils::aggregateSigShares(n_, entry->partial_sigs); - publishCompleteSignature(sig_id, sig, entry->client_app_data_generator_cb_); + publishCompleteSignature(*entry); } } } -void SigProcessor::publishCompleteSignature(uint64_t sig_id, - const std::vector& sig, - const GenerateAppClientRequestCb& cb) { + +void SigProcessor::publishCompleteSignature(const SigJobEntry& job_entry) { + auto sig_id = job_entry.job_id; + auto fsig = libutt::api::Utils::aggregateSigShares(n_, job_entry.partial_sigs); + CompleteSignatureMsg msg(n_, job_entry.partial_sigs, fsig); auto requestSeqNum = std::chrono::duration_cast(getMonotonicTime().time_since_epoch()).count(); - std::vector appClientReq = cb(sig_id, sig); - auto crm = std::make_unique(repId_, + std::vector appClientReq = job_entry.client_app_data_generator_cb_(sig_id, msg.serialize()); + auto crm = new bftEngine::impl::ClientRequestMsg(repId_, bftEngine::MsgFlag::INTERNAL_FLAG, requestSeqNum, (uint32_t)appClientReq.size(), @@ -227,4 +227,71 @@ void SigProcessor::onJobTimeout(uint64_t job_id, const std::vector& sig msgs_communicator_->sendAsyncMessage((uint64_t)rid, msg.body(), msg.size()); } } + +SigProcessor::CompleteSignatureMsg::CompleteSignatureMsg(uint32_t n, + const std::map>& psigs, + const std::vector& fsig) + : sigs{psigs}, full_sig{fsig}, num_replicas{n} {} +bool SigProcessor::CompleteSignatureMsg::validate() const { + auto complete_sig = libutt::api::Utils::aggregateSigShares(num_replicas, sigs); + return full_sig == complete_sig; +} +SigProcessor::CompleteSignatureMsg::CompleteSignatureMsg(const std::vector& buffer) { deserialize(buffer); } +const std::vector& SigProcessor::CompleteSignatureMsg::getFullSig() const { return full_sig; } + +std::vector SigProcessor::CompleteSignatureMsg::serialize() const { + uint64_t loc{0}; + uint64_t fsig_size = full_sig.size(); + uint64_t psigs_size = sigs.size(); + size_t msg_size = sizeof(num_replicas) + sizeof(fsig_size) + full_sig.size(); + msg_size += sizeof(psigs_size); + for (const auto& [k, v] : sigs) msg_size += (sizeof(k) + sizeof(uint64_t) + v.size()); + std::vector ret(msg_size); + std::memcpy(ret.data(), &num_replicas, sizeof(num_replicas)); + loc += sizeof(num_replicas); + std::memcpy(ret.data() + loc, &fsig_size, sizeof(fsig_size)); + loc += sizeof(fsig_size); + std::memcpy(ret.data() + loc, full_sig.data(), full_sig.size()); + loc += full_sig.size(); + std::memcpy(ret.data() + loc, &psigs_size, sizeof(psigs_size)); + loc += sizeof(psigs_size); + for (const auto& [k, v] : sigs) { + std::memcpy(ret.data() + loc, &k, sizeof(k)); + loc += sizeof(k); + uint64_t ssize = v.size(); + std::memcpy(ret.data() + loc, &ssize, sizeof(ssize)); + loc += sizeof(ssize); + std::memcpy(ret.data() + loc, v.data(), v.size()); + loc += v.size(); + } + return ret; +} + +SigProcessor::CompleteSignatureMsg& SigProcessor::CompleteSignatureMsg::deserialize( + const std::vector& buffer) { + size_t loc{0}; + std::memcpy(&num_replicas, buffer.data(), sizeof(num_replicas)); + loc += sizeof(num_replicas); + uint64_t fsig_size{0}; + std::memcpy(&fsig_size, buffer.data() + loc, sizeof(fsig_size)); + loc += sizeof(fsig_size); + full_sig.resize(fsig_size); + std::memcpy(full_sig.data(), buffer.data() + loc, fsig_size); + loc += fsig_size; + uint64_t psigs_nums{0}; + std::memcpy(&psigs_nums, buffer.data() + loc, sizeof(psigs_nums)); + loc += sizeof(psigs_nums); + for (size_t i = 0; i < psigs_nums; i++) { + uint32_t rep_id{0}; + std::memcpy(&rep_id, buffer.data() + loc, sizeof(rep_id)); + loc += sizeof(rep_id); + uint64_t s_size{0}; + std::memcpy(&s_size, buffer.data() + loc, sizeof(s_size)); + loc += sizeof(s_size); + sigs[rep_id] = std::vector(s_size); + std::memcpy(sigs[rep_id].data(), buffer.data() + loc, s_size); + loc += s_size; + } + return *this; +} } // namespace utt \ No newline at end of file diff --git a/utt-replica/signature-processor/tests/sigProcessorTests.cpp b/utt-replica/signature-processor/tests/sigProcessorTests.cpp index 1535d4fd73..d0974ac63f 100644 --- a/utt-replica/signature-processor/tests/sigProcessorTests.cpp +++ b/utt-replica/signature-processor/tests/sigProcessorTests.cpp @@ -304,9 +304,13 @@ class test_utt_instance : public ::testing::Test { msr->registerMsgHandler(MsgCode::ClientRequest, [&, sp](bftEngine::impl::MessageBase* message) { ClientRequestMsg* msg = (ClientRequestMsg*)message; uint64_t job_id{0}; - libutt::api::types::Signature fsig(msg->requestLength() - sizeof(uint64_t)); + + std::vector cs_buffer(msg->requestLength() - sizeof(uint64_t)); std::memcpy(&job_id, msg->requestBuf(), sizeof(uint64_t)); - std::memcpy(fsig.data(), msg->requestBuf() + sizeof(uint64_t), fsig.size()); + std::memcpy(cs_buffer.data(), msg->requestBuf() + sizeof(uint64_t), cs_buffer.size()); + utt::SigProcessor::CompleteSignatureMsg cs_msg(cs_buffer); + ASSERT_TRUE(cs_msg.validate()); + auto fsig = cs_msg.getFullSig(); { std::unique_lock lk(sigs_lock); for (size_t j = 0; j < n; j++) { @@ -418,7 +422,6 @@ class utt_complete_system : public utt_system_include_budget { } std::unordered_map> coins; }; - TEST_F(test_utt_instance, test_clients_registration) { registerClients( job_id, From 70c9e368bbfcbab498966fe682746f5e895cf4fa Mon Sep 17 00:00:00 2001 From: Yehonatan Buchnik Date: Wed, 2 Nov 2022 11:34:16 +0200 Subject: [PATCH 33/44] Fix a bug where the new api transaction object exposes the input coins --- utt/include/transaction.hpp | 19 +++---------------- utt/libutt/src/api/transaction.cpp | 17 ++++++----------- utt/tests/TestSerialization.cpp | 20 ++------------------ 3 files changed, 11 insertions(+), 45 deletions(-) diff --git a/utt/include/transaction.hpp b/utt/include/transaction.hpp index 290b820682..83654518cc 100644 --- a/utt/include/transaction.hpp +++ b/utt/include/transaction.hpp @@ -70,20 +70,6 @@ class Transaction { */ std::vector getNullifiers() const; - /** - * @brief Get the transaction's input coins - * - * @return const std::vector& - */ - const std::vector& getInputCoins() const; - - /** - * @brief Get the transaction's budget coin - * - * @return std::optional - */ - std::optional getBudgetCoin() const; - /** * @brief Get the number of output coins in the transaction * @@ -91,13 +77,14 @@ class Transaction { */ uint32_t getNumOfOutputCoins() const; + bool hasBudgetCoin() const; + uint64_t getBudgetExpirationDate() const; + private: friend class libutt::api::CoinsSigner; friend class libutt::api::Client; friend std::ostream& ::operator<<(std::ostream& out, const libutt::api::operations::Transaction& tx); friend std::istream& ::operator>>(std::istream& in, libutt::api::operations::Transaction& tx); std::shared_ptr tx_; - std::vector input_coins_; - std::optional budget_coin_; }; } // namespace libutt::api::operations \ No newline at end of file diff --git a/utt/libutt/src/api/transaction.cpp b/utt/libutt/src/api/transaction.cpp index 4b02df1624..4a71bef1bb 100644 --- a/utt/libutt/src/api/transaction.cpp +++ b/utt/libutt/src/api/transaction.cpp @@ -9,15 +9,10 @@ std::ostream& operator<<(std::ostream& out, const libutt::api::operations::Transaction& tx) { out << *(tx.tx_) << std::endl; - libutt::serializeVector(out, tx.input_coins_); - out << tx.budget_coin_; return out; } std::istream& operator>>(std::istream& in, libutt::api::operations::Transaction& tx) { in >> *(tx.tx_); - libff::consume_OUTPUT_NEWLINE(in); - libutt::deserializeVector(in, tx.input_coins_); - in >> tx.budget_coin_; return in; } namespace libutt::api::operations { @@ -27,8 +22,6 @@ Transaction::Transaction(const UTTParams& d, const std::optional& bc, const std::vector>& recipients, const IEncryptor& encryptor) { - input_coins_ = coins; - budget_coin_ = bc; Fr fr_pidhash; fr_pidhash.from_words(cid.getPidHash()); Fr prf; @@ -74,13 +67,15 @@ Transaction::Transaction(const Transaction& other) { Transaction& Transaction::operator=(const Transaction& other) { if (this == &other) return *this; *tx_ = *(other.tx_); - input_coins_ = other.input_coins_; - budget_coin_ = other.budget_coin_; return *this; } std::vector Transaction::getNullifiers() const { return tx_->getNullifiers(); } -const std::vector& Transaction::getInputCoins() const { return input_coins_; } -std::optional Transaction::getBudgetCoin() const { return budget_coin_; } uint32_t Transaction::getNumOfOutputCoins() const { return (uint32_t)tx_->outs.size(); } + +bool Transaction::hasBudgetCoin() const { return tx_->ins.back().coin_type == libutt::Coin::BudgetType(); } +uint64_t Transaction::getBudgetExpirationDate() const { + if (!hasBudgetCoin()) return 0; + return tx_->ins.back().exp_date.as_ulong(); +} } // namespace libutt::api::operations \ No newline at end of file diff --git a/utt/tests/TestSerialization.cpp b/utt/tests/TestSerialization.cpp index 568f9ba73e..e462562aac 100644 --- a/utt/tests/TestSerialization.cpp +++ b/utt/tests/TestSerialization.cpp @@ -215,25 +215,9 @@ int main(int argc, char* argv[]) { // Test Transaction de/serialization std::vector serialized_tx = libutt::api::serialize(tx); auto deserialized_tx = libutt::api::deserialize(serialized_tx); - for (size_t i = 0; i < tx.getInputCoins().size(); i++) { - const auto& orig_coin = tx.getInputCoins().at(i); - const auto& des_coin = deserialized_tx.getInputCoins().at(i); - assertTrue(orig_coin.getNullifier() == des_coin.getNullifier()); - assertTrue(orig_coin.hasSig() == des_coin.hasSig()); - assertTrue(orig_coin.getType() == des_coin.getType()); - assertTrue(orig_coin.getVal() == des_coin.getVal()); - assertTrue(orig_coin.getSN() == des_coin.getSN()); - assertTrue(orig_coin.getExpDateAsCurvePoint() == des_coin.getExpDateAsCurvePoint()); - } + assertTrue(tx.hasBudgetCoin() == deserialized_tx.hasBudgetCoin()); + assertTrue(tx.getBudgetExpirationDate() == deserialized_tx.getBudgetExpirationDate()); assertTrue(tx.getNullifiers() == deserialized_tx.getNullifiers()); - auto orig_bcoin = tx.getBudgetCoin(); - auto deserialize_bcoin = deserialized_tx.getBudgetCoin(); - assertTrue(orig_bcoin->getNullifier() == deserialize_bcoin->getNullifier()); - assertTrue(orig_bcoin->hasSig() == deserialize_bcoin->hasSig()); - assertTrue(orig_bcoin->getType() == deserialize_bcoin->getType()); - assertTrue(orig_bcoin->getVal() == deserialize_bcoin->getVal()); - assertTrue(orig_bcoin->getSN() == deserialize_bcoin->getSN()); - assertTrue(orig_bcoin->getExpDateAsCurvePoint() == deserialize_bcoin->getExpDateAsCurvePoint()); return 0; } \ No newline at end of file From 916eab99e65a78ccf06f0e96c8722e7edcb5e3f9 Mon Sep 17 00:00:00 2001 From: Evgeniy Dzhurov Date: Wed, 2 Nov 2022 14:21:19 +0200 Subject: [PATCH 34/44] Cleanup admin and wallet cli outputs. Demote some loginfo to logdbg statements in libutt. Fix wallet-cli reading transfer instead of burn response (didn't affect correctness) --- utt/admin-cli/src/admin.cpp | 13 ++++++----- utt/admin-cli/src/main.cpp | 15 +++++++------ utt/libutt/src/Kzg.cpp | 2 +- utt/utt-client-api/src/User.cpp | 8 +++---- utt/wallet-cli/src/main.cpp | 11 ++++----- utt/wallet-cli/src/wallet.cpp | 40 +++++++++++++++++++-------------- 6 files changed, 49 insertions(+), 40 deletions(-) diff --git a/utt/admin-cli/src/admin.cpp b/utt/admin-cli/src/admin.cpp index 607a666286..5b59444e25 100644 --- a/utt/admin-cli/src/admin.cpp +++ b/utt/admin-cli/src/admin.cpp @@ -20,7 +20,7 @@ using namespace vmware::concord::utt::admin::api::v1; Admin::Connection Admin::newConnection() { std::string grpcServerAddr = "127.0.0.1:49000"; - std::cout << "Connecting to gRPC server at " << grpcServerAddr << " ...\n"; + std::cout << "Connecting to gRPC server at " << grpcServerAddr << "... "; auto chan = grpc::CreateChannel(grpcServerAddr, grpc::InsecureChannelCredentials()); @@ -39,6 +39,8 @@ Admin::Connection Admin::newConnection() { } bool Admin::deployApp(Channel& chan) { + std::cout << "Deploying a new privacy application...\n"; + // Generate a privacy config for a N=4 replica system tolerating F=1 failures utt::client::ConfigInputParams params; params.validatorPublicKeys = std::vector{4, "placeholderPublicKey"}; // N = 3 * F + 1 @@ -53,19 +55,18 @@ bool Admin::deployApp(Channel& chan) { AdminResponse resp; chan->Read(&resp); if (!resp.has_deploy()) throw std::runtime_error("Expected deploy response from admin service!"); - std::cout << "response case: " << resp.resp_case() << '\n'; const auto& deployResp = resp.deploy(); - std::cout << "has_privacy_contract_addr:" << deployResp.has_privacy_contract_addr() << '\n'; - std::cout << "has_token_contract_addr:" << deployResp.has_token_contract_addr() << '\n'; // Note that keeping the config around in memory is just a temp solution and should not happen in real system if (deployResp.has_err()) throw std::runtime_error("Failed to deploy privacy app: " + resp.err()); - std::cout << "\nDeployed privacy application\n"; - std::cout << "-----------------------------------\n"; + std::cout << "\nSuccessfully deployed privacy application\n"; + std::cout << "---------------------------------------------------\n"; std::cout << "Privacy contract: " << deployResp.privacy_contract_addr() << '\n'; std::cout << "Token contract: " << deployResp.token_contract_addr() << '\n'; + std::cout << "\nYou are now ready to configure wallets.\n"; + return true; } diff --git a/utt/admin-cli/src/main.cpp b/utt/admin-cli/src/main.cpp index aa23898cdc..eba6658d3f 100644 --- a/utt/admin-cli/src/main.cpp +++ b/utt/admin-cli/src/main.cpp @@ -19,8 +19,7 @@ void printHelp() { std::cout << "\nCommands:\n"; - std::cout - << "deploy -- generates a privacy app config and deploys the privacy and token contracts.\n"; + std::cout << "deploy -- generates a privacy config and deploys the privacy and token contracts.\n"; // [TODO-UTT] Admin creates a user's budget by supplying a user-id and amount from the CLI // std::cout // << "create-budget -- requests creation of a privacy budget, the amount is decided by the @@ -45,12 +44,13 @@ struct CLIApp { } ~CLIApp() { - std::cout << "Closing admin streaming channel...\n"; + std::cout << "Closing admin streaming channel... "; chan->WritesDone(); auto status = chan->Finish(); - std::cout << "gRPC error code: " << status.error_code() << '\n'; - std::cout << "gRPC error msg: " << status.error_message() << '\n'; - std::cout << "gRPC error details: " << status.error_details() << '\n'; + std::cout << " Done.\n"; + // std::cout << "gRPC error code: " << status.error_code() << '\n'; + // std::cout << "gRPC error msg: " << status.error_message() << '\n'; + // std::cout << "gRPC error details: " << status.error_details() << '\n'; } void deploy() { @@ -82,13 +82,14 @@ struct CLIApp { int main(int argc, char* argv[]) { (void)argc; (void)argv; - std::cout << "Sample Privacy Admin CLI Application.\n"; try { utt::client::Initialize(); CLIApp app; + std::cout << "\nSample Privacy Admin CLI Application.\n"; + while (true) { std::cout << "\nEnter command (type 'h' for commands 'Ctr-D' to quit):\n > "; std::string cmd; diff --git a/utt/libutt/src/Kzg.cpp b/utt/libutt/src/Kzg.cpp index 7029d173a3..dbcc4b6666 100644 --- a/utt/libutt/src/Kzg.cpp +++ b/utt/libutt/src/Kzg.cpp @@ -109,7 +109,7 @@ Params::Params(size_t q) : q(q) { int prevPct = -1; size_t c = 0; - loginfo << "Generating random q-SDH params, with q = " << q << endl; + logdbg << "Generating random q-SDH params, with q = " << q << endl; for (size_t i = 0; i <= q; i++) { g1si[i] = si * g1; g2si[i] = si * g2; diff --git a/utt/utt-client-api/src/User.cpp b/utt/utt-client-api/src/User.cpp index e766db77f6..88f713d8fe 100644 --- a/utt/utt-client-api/src/User.cpp +++ b/utt/utt-client-api/src/User.cpp @@ -347,7 +347,7 @@ void User::updateMintTx(uint64_t txNum, const Transaction& tx, const TxOutputSig auto mint = libutt::api::deserialize(tx.data_); if (mint.getRecipentID() != pImpl_->client_->getPid()) { - loginfo << "Mint transaction is for different user - ignoring." << endl; + logdbg << "Mint transaction is for different user - ignoring." << endl; } else { auto claimedCoins = pImpl_->client_->claimCoins(mint, pImpl_->params_, std::vector{sig}); @@ -372,7 +372,7 @@ void User::updateBurnTx(uint64_t txNum, const Transaction& tx) { auto burn = libutt::api::deserialize(tx.data_); if (burn.getOwnerPid() != pImpl_->client_->getPid()) { - loginfo << "Burn transaction is for different user - ignoring." << endl; + logdbg << "Burn transaction is for different user - ignoring." << endl; } else { auto nullifier = burn.getNullifier(); if (nullifier.empty()) throw std::runtime_error("Burn tx has empty nullifier!"); @@ -398,7 +398,7 @@ utt::Transaction User::mint(uint64_t amount) const { std::stringstream ss; ss << Fr::random_element(); auto randomHash = ss.str(); - loginfo << "Creating a mint tx with hash: " << randomHash << endl; + logdbg << "Creating a mint tx with hash: " << randomHash << endl; auto mint = libutt::api::operations::Mint(randomHash, amount, pImpl_->client_->getPid()); @@ -415,7 +415,7 @@ void User::createPrivacyBudgetLocal(const utt::Configuration& config, uint64_t a const auto& pid = pImpl_->client_->getPid(); - loginfo << "Creating " << amount << " privacy budget locally for '" << pid << "'\n"; + logdbg << "Creating " << amount << " privacy budget locally for '" << pid << "'\n"; auto uttConfig = libutt::api::deserialize(config); diff --git a/utt/wallet-cli/src/main.cpp b/utt/wallet-cli/src/main.cpp index d12cca6ff5..58c25a27a0 100644 --- a/utt/wallet-cli/src/main.cpp +++ b/utt/wallet-cli/src/main.cpp @@ -80,12 +80,13 @@ struct CLIApp { } ~CLIApp() { - std::cout << "Closing wallet streaming channel...\n"; + std::cout << "Closing wallet streaming channel... "; chan->WritesDone(); auto status = chan->Finish(); - std::cout << "gRPC error code: " << status.error_code() << '\n'; - std::cout << "gRPC error msg: " << status.error_message() << '\n'; - std::cout << "gRPC error details: " << status.error_details() << '\n'; + std::cout << " Done.\n"; + // std::cout << "gRPC error code: " << status.error_code() << '\n'; + // std::cout << "gRPC error msg: " << status.error_message() << '\n'; + // std::cout << "gRPC error details: " << status.error_details() << '\n'; } void configure() { @@ -182,7 +183,7 @@ int main(int argc, char* argv[]) { return 0; } - std::cout << "Sample Privacy Wallet CLI Application.\n"; + std::cout << "\nSample Privacy Wallet CLI Application.\n"; std::cout << "UserId: " << app.userId << '\n'; try { diff --git a/utt/wallet-cli/src/wallet.cpp b/utt/wallet-cli/src/wallet.cpp index 49e13ccaca..61c28f2d4e 100644 --- a/utt/wallet-cli/src/wallet.cpp +++ b/utt/wallet-cli/src/wallet.cpp @@ -26,7 +26,7 @@ Wallet::Wallet(std::string userId, utt::client::TestUserPKInfrastructure& pki, c Wallet::Connection Wallet::newConnection() { std::string grpcServerAddr = "127.0.0.1:49001"; - std::cout << "Connecting to gRPC server at " << grpcServerAddr << " ...\n"; + std::cout << "Connecting to gRPC server at " << grpcServerAddr << "... "; auto chan = grpc::CreateChannel(grpcServerAddr, grpc::InsecureChannelCredentials()); @@ -45,8 +45,9 @@ Wallet::Connection Wallet::newConnection() { } void Wallet::showInfo(Channel& chan) { + std::cout << '\n'; syncState(chan); - std::cout << "\n--------- " << userId_ << " ---------\n"; + std::cout << "--------- " << userId_ << " ---------\n"; std::cout << "Public balance: " << publicBalance_ << '\n'; std::cout << "Private balance: " << user_->getBalance() << '\n'; std::cout << "Privacy budget: " << user_->getPrivacyBudget() << '\n'; @@ -66,12 +67,13 @@ std::pair Wallet::getConfigs(Channel& cha // Note that keeping the config around in memory is just a temp solution and should not happen in real system if (configureResp.has_err()) throw std::runtime_error("Failed to configure: " + resp.err()); - std::cout << "\nConfigured privacy application\n"; - std::cout << "-----------------------------------\n"; + std::cout << "\nSuccessfully configured privacy application\n"; + std::cout << "---------------------------------------------------\n"; std::cout << "Privacy contract: " << configureResp.privacy_contract_addr() << '\n'; std::cout << "Token contract: " << configureResp.token_contract_addr() << '\n'; - std::cout << "Full config size: " << configureResp.config().size() << '\n'; - std::cout << "Public config size: " << configureResp.public_config().size() << '\n'; + + if (configureResp.config().empty()) throw std::runtime_error("The full config is empty!"); + if (configureResp.public_config().empty()) throw std::runtime_error("The public config is empty!"); utt::Configuration config(configureResp.config().begin(), configureResp.config().end()); utt::PublicConfig publicConfig(configureResp.public_config().begin(), configureResp.public_config().end()); @@ -86,6 +88,7 @@ std::pair Wallet::getConfigs(Channel& cha void Wallet::createPrivacyBudgetLocal(const utt::Configuration& config, uint64_t amount) { user_->createPrivacyBudgetLocal(config, amount); + std::cout << "Successfully created budget with value " << amount << '\n'; } bool Wallet::isRegistered() const { return registered_; } @@ -112,16 +115,16 @@ void Wallet::registerUser(Channel& chan) { std::cout << "Failed to register user: " << regUserResp.err() << '\n'; } else { utt::RegistrationSig sig = std::vector(regUserResp.signature().begin(), regUserResp.signature().end()); - std::cout << "Got sig for registration with size: " << sig.size() << '\n'; + if (sig.empty()) throw std::runtime_error("Registration signature is empty!"); utt::S2 s2 = std::vector(regUserResp.s2().begin(), regUserResp.s2().end()); - std::cout << "Got S2 for registration: ["; - for (const auto& val : s2) std::cout << val << ' '; - std::cout << "]\n"; + if (s2.empty()) throw std::runtime_error("Registration param s2 is empty!"); user_->updateRegistration(user_->getPK(), sig, s2); registered_ = true; + + std::cout << "Successfully registered user.\n"; } } @@ -191,7 +194,7 @@ void Wallet::transfer(Channel& chan, uint64_t amount, const std::string& recipie return; } - std::cout << "Started processing " << amount << " anonymous transfer to " << recipient << "...\n"; + std::cout << "Processing an anonymous transfer of " << amount << " to " << recipient << "...\n"; // Process the transfer until we get the final transaction // On each iteration we also sync up to the tx number of our request @@ -220,6 +223,8 @@ void Wallet::transfer(Channel& chan, uint64_t amount, const std::string& recipie if (result.isFinal_) break; // Done } + + std::cout << "Anonymous transfer done.\n"; } void Wallet::burn(Channel& chan, uint64_t amount) { @@ -228,7 +233,7 @@ void Wallet::burn(Channel& chan, uint64_t amount) { return; } - std::cout << "Started processing " << amount << " burn...\n"; + std::cout << "Processing a burn operation for " << amount << "...\n"; // Process the transfer until we get the final transaction // On each iteration we also sync up to the tx number of our request @@ -246,7 +251,7 @@ void Wallet::burn(Channel& chan, uint64_t amount) { WalletResponse resp; chan->Read(&resp); if (!resp.has_burn()) throw std::runtime_error("Expected burn response from wallet service!"); - const auto& burnResp = resp.transfer(); + const auto& burnResp = resp.burn(); if (burnResp.has_err()) { std::cout << "Failed to do burn:" << resp.err() << '\n'; @@ -281,10 +286,12 @@ void Wallet::burn(Channel& chan, uint64_t amount) { // Continue with the next transaction in the burn process } } + + std::cout << "Burn operation done.\n"; } void Wallet::syncState(Channel& chan, uint64_t lastKnownTxNum) { - std::cout << "Synchronizing state...\n"; + std::cout << "Synchronizing state... "; // Update public balance { @@ -307,8 +314,6 @@ void Wallet::syncState(Channel& chan, uint64_t lastKnownTxNum) { // Sync to latest state if (lastKnownTxNum == 0) { - std::cout << "Last known tx number is zero (or not provided) - fetching last added tx number...\n"; - WalletRequest req; req.mutable_get_last_added_tx_number(); chan->Write(req); @@ -322,7 +327,6 @@ void Wallet::syncState(Channel& chan, uint64_t lastKnownTxNum) { if (getLastAddedTxNumResp.has_err()) { std::cout << "Failed to get last added tx number:" << resp.err() << '\n'; } else { - std::cout << "Got last added tx number:" << getLastAddedTxNumResp.tx_number() << '\n'; lastKnownTxNum = getLastAddedTxNumResp.tx_number(); } } @@ -376,6 +380,8 @@ void Wallet::syncState(Channel& chan, uint64_t lastKnownTxNum) { throw std::runtime_error("Unexpected tx type!"); } } + + std::cout << "Ok. (Last known tx number: " << lastKnownTxNum << ")\n"; } void Wallet::debugOutput() const { user_->debugOutput(); } \ No newline at end of file From e5966447703548c655be2eba6cd6475eef078623 Mon Sep 17 00:00:00 2001 From: Yehonatan Buchnik Date: Thu, 3 Nov 2022 12:29:48 +0200 Subject: [PATCH 35/44] Improve the new utt api testing framework, integrating it with gtest and remove old files --- utt/libutt/src/api/budget.cpp | 4 +- utt/tests/CMakeLists.txt | 11 +- utt/tests/TestBudget.cpp | 80 --- utt/tests/TestBurn.cpp | 54 -- utt/tests/TestDistributedRegistration.cpp | 32 - utt/tests/TestMint.cpp | 57 -- utt/tests/TestSerialization.cpp | 223 ------- utt/tests/TestTransaction.cpp | 155 ----- .../TestTransactionWithRsaEncryption.cpp | 213 ------- utt/tests/TestUttNewApi.cpp | 559 ++++++++++++++++++ utt/tests/testUtils.hpp | 322 ++++++---- 11 files changed, 773 insertions(+), 937 deletions(-) delete mode 100644 utt/tests/TestBudget.cpp delete mode 100644 utt/tests/TestBurn.cpp delete mode 100644 utt/tests/TestDistributedRegistration.cpp delete mode 100644 utt/tests/TestMint.cpp delete mode 100644 utt/tests/TestSerialization.cpp delete mode 100644 utt/tests/TestTransaction.cpp delete mode 100644 utt/tests/TestTransactionWithRsaEncryption.cpp create mode 100644 utt/tests/TestUttNewApi.cpp diff --git a/utt/libutt/src/api/budget.cpp b/utt/libutt/src/api/budget.cpp index d601a48636..76e6ebbdf0 100644 --- a/utt/libutt/src/api/budget.cpp +++ b/utt/libutt/src/api/budget.cpp @@ -38,8 +38,8 @@ Budget::Budget(const UTTParams& d, fr_val.set_ulong(val); Fr fr_expdate; fr_expdate.set_ulong(exp_date); - coin_ = - libutt::api::Coin(d, snHash, fr_val.to_words(), pidHash, libutt::api::Coin::Type::Budget, fr_expdate.to_words()); + coin_ = libutt::api::Coin( + d, snHash, fr_val.to_words(), pidHash, libutt::api::Coin::Type::Budget, fr_expdate.to_words(), false); } libutt::api::Coin& Budget::getCoin() { return coin_; } const libutt::api::Coin& Budget::getCoin() const { return coin_; } diff --git a/utt/tests/CMakeLists.txt b/utt/tests/CMakeLists.txt index 07714ab002..3d08199552 100644 --- a/utt/tests/CMakeLists.txt +++ b/utt/tests/CMakeLists.txt @@ -1,11 +1,6 @@ +find_package(GTest REQUIRED) set(newutt_test_sources - TestDistributedRegistration.cpp - TestMint.cpp - TestBurn.cpp - TestBudget.cpp - TestTransaction.cpp - TestTransactionWithRsaEncryption.cpp - TestSerialization.cpp + TestUttNewApi.cpp ) foreach(appSrc ${newutt_test_sources}) @@ -13,7 +8,7 @@ foreach(appSrc ${newutt_test_sources}) set(appDir ../bin/test) add_executable(${appName} ${appSrc}) - target_link_libraries(${appName} PRIVATE utt_api) + target_link_libraries(${appName} PRIVATE utt_api logging GTest::Main GTest::GTest) add_test(NAME ${appName} COMMAND ${appName}) set_target_properties(${appName} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${appDir}) diff --git a/utt/tests/TestBudget.cpp b/utt/tests/TestBudget.cpp deleted file mode 100644 index f51dae7ff1..0000000000 --- a/utt/tests/TestBudget.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include "testUtils.hpp" - -#include "UTTParams.hpp" -#include "testUtils.hpp" -#include "budget.hpp" -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace libutt; -using namespace libutt::api; -using namespace libutt::api::operations; -using namespace std::chrono; -int main(int argc, char* argv[]) { - std::srand(0); - (void)argc; - (void)argv; - size_t thresh = 3; - size_t n = 4; - size_t c = 10; - auto [d, dkg, rc] = testing::init(n, thresh); - auto registrators = testing::GenerateRegistrators(n, rc); - auto banks = testing::GenerateCommitters(n, dkg, rc.toPK()); - auto clients = testing::GenerateClients(c, dkg.getPK(), rc.toPK(), rc); - for (auto& c : clients) { - testing::registerClient(d, c, registrators, thresh); - } - - for (auto& c : clients) { - std::vector> rsigs; - std::vector shares_signers; - auto budget = Budget(d, c, 1000, 123456789); - for (size_t i = 0; i < banks.size(); i++) { - rsigs.push_back(banks[i]->sign(budget).front()); - shares_signers.push_back(banks[i]->getId()); - } - for (auto& b : banks) { - for (size_t i = 0; i < rsigs.size(); i++) { - auto& sig = rsigs[i]; - auto& signer = shares_signers[i]; - assertTrue(b->validatePartialSignature(signer, sig, 0, budget)); - } - } - auto sbs = testing::getSubset((uint32_t)n, (uint32_t)thresh); - std::map> sigs; - for (auto i : sbs) { - sigs[i] = rsigs[i]; - } - auto blinded_sig = Utils::aggregateSigShares((uint32_t)n, {sigs}); - auto coin = c.claimCoins(budget, d, {blinded_sig}).front(); - assertTrue(c.validate(coin)); - } - - // Now, do the same for a budget created by the replicas. - for (auto& c : clients) { - std::vector> rsigs; - auto snHash = Fr::random_element() - .to_words(); // Assume each replica can compute the same sn using some common execution state - auto budget = Budget(d, snHash, c.getPidHash(), 1000, 123456789); - for (size_t i = 0; i < banks.size(); i++) { - rsigs.push_back(banks[i]->sign(budget).front()); - } - auto sbs = testing::getSubset((uint32_t)n, (uint32_t)thresh); - std::map> sigs; - for (auto i : sbs) { - sigs[i] = rsigs[i]; - } - auto blinded_sig = Utils::aggregateSigShares((uint32_t)n, {sigs}); - auto coin = c.claimCoins(budget, d, {blinded_sig}).front(); - coin.createNullifier(d, c.getPRFSecretKey()); - assertTrue(!coin.getNullifier().empty()); - assertTrue(c.validate(coin)); - } - return 0; -} \ No newline at end of file diff --git a/utt/tests/TestBurn.cpp b/utt/tests/TestBurn.cpp deleted file mode 100644 index af9831d948..0000000000 --- a/utt/tests/TestBurn.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#include "testUtils.hpp" - -#include "UTTParams.hpp" -#include "testUtils.hpp" -#include "mint.hpp" -#include "burn.hpp" -#include -#include -#include -#include -#include -#include -#include - -using namespace libutt; -using namespace libutt::api; -using namespace libutt::api::operations; -int main(int argc, char* argv[]) { - std::srand(0); - (void)argc; - (void)argv; - size_t thresh = 3; - size_t n = 4; - size_t c = 10; - auto [d, dkg, rc] = testing::init(n, thresh); - auto registrators = testing::GenerateRegistrators(n, rc); - auto banks = testing::GenerateCommitters(n, dkg, rc.toPK()); - auto clients = testing::GenerateClients(c, dkg.getPK(), rc.toPK(), rc); - for (auto& c : clients) { - testing::registerClient(d, c, registrators, thresh); - } - - for (auto& c : clients) { - std::vector> rsigs; - std::string simulatonOfUniqueTxHash = std::to_string(((uint64_t)rand()) * ((uint64_t)rand()) * ((uint64_t)rand())); - auto mint = Mint(simulatonOfUniqueTxHash, 100, c.getPid()); - for (size_t i = 0; i < banks.size(); i++) { - rsigs.push_back(banks[i]->sign(mint).front()); - } - auto sbs = testing::getSubset((uint32_t)n, (uint32_t)thresh); - std::map> sigs; - for (auto i : sbs) { - sigs[i] = rsigs[i]; - } - auto blinded_sig = Utils::aggregateSigShares((uint32_t)n, {sigs}); - auto coin = c.claimCoins(mint, d, {blinded_sig}).front(); - assertTrue(c.validate(coin)); - Burn b_op{d, c, coin}; - for (auto& b : banks) { - assertTrue(b->validate(d, b_op)); - } - } - return 0; -} \ No newline at end of file diff --git a/utt/tests/TestDistributedRegistration.cpp b/utt/tests/TestDistributedRegistration.cpp deleted file mode 100644 index f98c45bdd4..0000000000 --- a/utt/tests/TestDistributedRegistration.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "UTTParams.hpp" -#include "testUtils.hpp" - -#include -#include -#include -#include - -using namespace libutt; -using namespace libutt::api; - -int main(int argc, char* argv[]) { - (void)argc; - (void)argv; - size_t thresh = 3; - size_t n = 4; - size_t c = 10; - auto [d, dkg, rc] = testing::init(n, thresh); - auto registrators = testing::GenerateRegistrators(n, rc); - auto banks = testing::GenerateCommitters(n, dkg, rc.toPK()); - auto clients = testing::GenerateClients(c, dkg.getSK().toPK(), rc.toPK(), rc); - - for (auto& c : clients) { - testing::registerClient(d, c, registrators, thresh); - auto rcm_data = c.rerandomizeRcm(d); - for (auto& r : registrators) { - assertTrue(r->validateRCM(rcm_data.first, rcm_data.second)); - } - } - - return 0; -} \ No newline at end of file diff --git a/utt/tests/TestMint.cpp b/utt/tests/TestMint.cpp deleted file mode 100644 index 2e17eaf074..0000000000 --- a/utt/tests/TestMint.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "testUtils.hpp" - -#include "UTTParams.hpp" -#include "testUtils.hpp" -#include "mint.hpp" -#include -#include -#include -#include -#include -#include - -using namespace libutt; -using namespace libutt::api; -using namespace libutt::api::operations; -int main(int argc, char* argv[]) { - std::srand(0); - (void)argc; - (void)argv; - size_t thresh = 3; - size_t n = 4; - size_t c = 10; - auto [d, dkg, rc] = testing::init(n, thresh); - auto registrators = testing::GenerateRegistrators(n, rc); - auto banks = testing::GenerateCommitters(n, dkg, rc.toPK()); - auto clients = testing::GenerateClients(c, dkg.getPK(), rc.toPK(), rc); - for (auto& c : clients) { - testing::registerClient(d, c, registrators, thresh); - } - - for (auto& c : clients) { - std::vector> rsigs; - std::vector shares_signers; - std::string simulatonOfUniqueTxHash = std::to_string(((uint64_t)rand()) * ((uint64_t)rand()) * ((uint64_t)rand())); - auto mint = Mint(simulatonOfUniqueTxHash, 100, c.getPid()); - for (size_t i = 0; i < banks.size(); i++) { - rsigs.push_back(banks[i]->sign(mint).front()); - shares_signers.push_back(banks[i]->getId()); - } - for (auto& b : banks) { - for (size_t i = 0; i < rsigs.size(); i++) { - auto& sig = rsigs[i]; - auto& signer = shares_signers[i]; - assertTrue(b->validatePartialSignature(signer, sig, 0, mint)); - } - } - auto sbs = testing::getSubset((uint32_t)n, (uint32_t)thresh); - std::map> sigs; - for (auto i : sbs) { - sigs[i] = rsigs[i]; - } - auto blinded_sig = Utils::aggregateSigShares((uint32_t)n, {sigs}); - auto coin = c.claimCoins(mint, d, {blinded_sig}).front(); - assertTrue(c.validate(coin)); - } - return 0; -} \ No newline at end of file diff --git a/utt/tests/TestSerialization.cpp b/utt/tests/TestSerialization.cpp deleted file mode 100644 index e462562aac..0000000000 --- a/utt/tests/TestSerialization.cpp +++ /dev/null @@ -1,223 +0,0 @@ -#include "testUtils.hpp" -#include "coin.hpp" -#include "budget.hpp" -#include "burn.hpp" -#include "mint.hpp" -#include "commitment.hpp" -#include "transaction.hpp" -#include "config.hpp" -#include -#include -#include -#include -#include "serialization.hpp" -using namespace libutt; -using namespace libutt::api; -using namespace libutt::api::operations; - -int main(int argc, char* argv[]) { - (void)argc; - (void)argv; - - size_t thresh = 3; - size_t n = 4; - size_t c = 2; - auto [d, dkg, rc] = testing::init(n, thresh); - auto registrators = testing::GenerateRegistrators(n, rc); - auto banks = testing::GenerateCommitters(n, dkg, rc.toPK()); - auto clients = testing::GenerateClients(c, dkg.getPK(), rc.toPK(), rc); - for (auto& c : clients) { - testing::registerClient(d, c, registrators, thresh); - } - - // Test UTTParams de/serialization - std::vector serialized_params = libutt::api::serialize(d); - auto deserialized_params = libutt::api::deserialize(serialized_params); - assertTrue(deserialized_params == d); - - // Test Configuration de/serialization - { - auto config = libutt::api::Configuration((uint16_t)n, (uint16_t)thresh); - assertTrue(config.isValid()); - auto serialized_config = libutt::api::serialize(config); - auto deserialized_config = libutt::api::deserialize(serialized_config); - assertTrue(deserialized_config.isValid()); - assertTrue(deserialized_config == config); - - const auto& publicConfig = config.getPublicConfig(); - auto serialized_public_config = libutt::api::serialize(publicConfig); - auto deserialized_public_config = libutt::api::deserialize(serialized_public_config); - assertTrue(deserialized_public_config == publicConfig); - } - - Commitment rcm = clients[0].getRcm().first; - - // Test Commitment de/serialization - std::vector serialized_rcm = libutt::api::serialize(rcm); - auto deserialized_rcm = libutt::api::deserialize(serialized_rcm); - assertTrue(rcm == deserialized_rcm); - Fr fr_val; - fr_val.set_ulong(100); - - /* - We define a *complete coin* as a UTT coin that contains a nullifier. - We define an *incomplete coin* as a UTT coin that does not contain a nullifier. - */ - // Test a complete normal coin de/serialization - libutt::api::Coin c1(d, - Fr::random_element().to_words(), - Fr::random_element().to_words(), - fr_val.to_words(), - Fr::random_element().to_words(), - libutt::api::Coin::Type::Normal, - Fr::random_element().to_words()); - std::vector c1_serialized = libutt::api::serialize(c1); - libutt::api::Coin c1_deserialized = libutt::api::deserialize(c1_serialized); - assertTrue(c1.getNullifier() == c1_deserialized.getNullifier()); - assertTrue(c1.hasSig() == c1_deserialized.hasSig()); - assertTrue(c1.getType() == c1_deserialized.getType()); - assertTrue(c1.getVal() == c1_deserialized.getVal()); - assertTrue(c1.getSN() == c1_deserialized.getSN()); - assertTrue(c1.getExpDateAsCurvePoint() == c1_deserialized.getExpDateAsCurvePoint()); - - // Test an incomplete normal coin de/serialization - libutt::api::Coin c2(d, - Fr::random_element().to_words(), - fr_val.to_words(), - Fr::random_element().to_words(), - libutt::api::Coin::Type::Normal, - Fr::random_element().to_words()); - std::vector c2_serialized = libutt::api::serialize(c2); - libutt::api::Coin c2_deserialized = libutt::api::deserialize(c2_serialized); - assertTrue(c2.getNullifier() == c2_deserialized.getNullifier()); - assertTrue(c2.hasSig() == c2_deserialized.hasSig()); - assertTrue(c2.getType() == c2_deserialized.getType()); - assertTrue(c2.getVal() == c2_deserialized.getVal()); - assertTrue(c2.getSN() == c2_deserialized.getSN()); - assertTrue(c2.getExpDateAsCurvePoint() == c2_deserialized.getExpDateAsCurvePoint()); - - // Test a complete budget coin de/serialization - libutt::api::Coin c3(d, - Fr::random_element().to_words(), - fr_val.to_words(), - Fr::random_element().to_words(), - libutt::api::Coin::Type::Budget, - Fr::random_element().to_words()); - std::vector c3_serialized = libutt::api::serialize(c3); - libutt::api::Coin c3_deserialized = libutt::api::deserialize(c3_serialized); - assertTrue(c3.getNullifier() == c3_deserialized.getNullifier()); - assertTrue(c3.hasSig() == c3_deserialized.hasSig()); - assertTrue(c3.getType() == c3_deserialized.getType()); - assertTrue(c3.getVal() == c3_deserialized.getVal()); - assertTrue(c3.getSN() == c3_deserialized.getSN()); - assertTrue(c3.getExpDateAsCurvePoint() == c3_deserialized.getExpDateAsCurvePoint()); - - // Test an incomplete budget coin de/serialization - libutt::api::Coin c4(d, - Fr::random_element().to_words(), - Fr::random_element().to_words(), - fr_val.to_words(), - Fr::random_element().to_words(), - libutt::api::Coin::Type::Budget, - Fr::random_element().to_words()); - std::vector c4_serialized = libutt::api::serialize(c4); - libutt::api::Coin c4_deserialized = libutt::api::deserialize(c4_serialized); - assertTrue(c4.getNullifier() == c4_deserialized.getNullifier()); - assertTrue(c4.hasSig() == c4_deserialized.hasSig()); - assertTrue(c4.getType() == c4_deserialized.getType()); - assertTrue(c4.getVal() == c4_deserialized.getVal()); - assertTrue(c4.getSN() == c4_deserialized.getSN()); - assertTrue(c4.getExpDateAsCurvePoint() == c4_deserialized.getExpDateAsCurvePoint()); - - // Test Budget request de/serialization - libutt::api::operations::Budget b1( - d, Fr::random_element().to_words(), Fr::random_element().to_words(), 100, 987654321); - std::vector b1_serialized = libutt::api::serialize(b1); - libutt::api::operations::Budget b1_deserialized = - libutt::api::deserialize(b1_serialized); - assertTrue(b1.getHashHex() == b1_deserialized.getHashHex()); - const auto b1_coin = b1.getCoin(); - const auto c1_deserialized_coin = b1_deserialized.getCoin(); - assertTrue(b1_coin.getNullifier() == c1_deserialized_coin.getNullifier()); - assertTrue(b1_coin.hasSig() == c1_deserialized_coin.hasSig()); - assertTrue(b1_coin.getType() == c1_deserialized_coin.getType()); - assertTrue(b1_coin.getVal() == c1_deserialized_coin.getVal()); - assertTrue(b1_coin.getSN() == c1_deserialized_coin.getSN()); - assertTrue(b1_coin.getExpDateAsCurvePoint() == c1_deserialized_coin.getExpDateAsCurvePoint()); - - for (auto& c : clients) { - std::vector> rsigs; - std::string simulatonOfUniqueTxHash = std::to_string(((uint64_t)rand()) * ((uint64_t)rand()) * ((uint64_t)rand())); - auto mint = Mint(simulatonOfUniqueTxHash, 100, c.getPid()); - - // Test Mint request de/serialization - std::vector mint_serialized = libutt::api::serialize(mint); - auto mint_deserialized = libutt::api::deserialize(mint_serialized); - assertTrue(mint.getHash() == mint_deserialized.getHash()); - assertTrue(mint.getVal() == mint_deserialized.getVal()); - assertTrue(mint.getRecipentID() == mint_deserialized.getRecipentID()); - for (size_t i = 0; i < banks.size(); i++) { - rsigs.push_back(banks[i]->sign(mint).front()); - } - auto sbs = testing::getSubset((uint32_t)n, (uint32_t)thresh); - std::map> sigs; - for (auto i : sbs) { - sigs[i] = rsigs[i]; - } - auto blinded_sig = Utils::aggregateSigShares((uint32_t)n, {sigs}); - auto coin = c.claimCoins(mint, d, {blinded_sig}).front(); - libutt::api::operations::Burn b_op{d, c, coin}; - - // Test Burn request de/serialization - std::vector burn_serialized = libutt::api::serialize(b_op); - auto burn_deserialized = libutt::api::deserialize(burn_serialized); - assertTrue(b_op.getNullifier() == burn_deserialized.getNullifier()); - const auto& burn_coin = b_op.getCoin(); - const auto& burn_deserialized_coin = burn_deserialized.getCoin(); - assertTrue(burn_coin.getNullifier() == burn_deserialized_coin.getNullifier()); - assertTrue(burn_coin.hasSig() == burn_deserialized_coin.hasSig()); - assertTrue(burn_coin.getType() == burn_deserialized_coin.getType()); - assertTrue(burn_coin.getVal() == burn_deserialized_coin.getVal()); - assertTrue(burn_coin.getSN() == burn_deserialized_coin.getSN()); - assertTrue(burn_coin.getExpDateAsCurvePoint() == burn_deserialized_coin.getExpDateAsCurvePoint()); - } - const auto& client1 = clients[0]; - const auto& client2 = clients[1]; - std::vector rsigs; - std::string simulatonOfUniqueTxHash = std::to_string(((uint64_t)rand()) * ((uint64_t)rand()) * ((uint64_t)rand())); - auto mint = Mint(simulatonOfUniqueTxHash, 100, client1.getPid()); - for (size_t i = 0; i < banks.size(); i++) { - rsigs.push_back(banks[i]->sign(mint).front()); - } - auto sbs = testing::getSubset((uint32_t)n, (uint32_t)thresh); - std::map sigs; - for (auto i : sbs) { - sigs[i] = rsigs[i]; - } - auto blinded_sig = Utils::aggregateSigShares((uint32_t)n, {sigs}); - auto coin = client1.claimCoins(mint, d, {blinded_sig}).front(); - - rsigs.clear(); - auto budget = Budget(d, client1, 1000, 123456789); - for (size_t i = 0; i < banks.size(); i++) { - rsigs.push_back(banks[i]->sign(budget).front()); - } - sbs = testing::getSubset((uint32_t)n, (uint32_t)thresh); - sigs.clear(); - for (auto i : sbs) { - sigs[i] = rsigs[i]; - } - blinded_sig = Utils::aggregateSigShares((uint32_t)n, {sigs}); - auto bcoin = client1.claimCoins(budget, d, {blinded_sig}).front(); - auto encryptor = std::make_shared(rc.toPK().mpk); - Transaction tx(d, client1, {coin}, {bcoin}, {{client1.getPid(), 50}, {client2.getPid(), 50}}, *(encryptor)); - - // Test Transaction de/serialization - std::vector serialized_tx = libutt::api::serialize(tx); - auto deserialized_tx = libutt::api::deserialize(serialized_tx); - assertTrue(tx.hasBudgetCoin() == deserialized_tx.hasBudgetCoin()); - assertTrue(tx.getBudgetExpirationDate() == deserialized_tx.getBudgetExpirationDate()); - assertTrue(tx.getNullifiers() == deserialized_tx.getNullifiers()); - - return 0; -} \ No newline at end of file diff --git a/utt/tests/TestTransaction.cpp b/utt/tests/TestTransaction.cpp deleted file mode 100644 index 527e474dc8..0000000000 --- a/utt/tests/TestTransaction.cpp +++ /dev/null @@ -1,155 +0,0 @@ -#include "testUtils.hpp" - -#include "UTTParams.hpp" -#include "testUtils.hpp" -#include "mint.hpp" -#include "burn.hpp" -#include "transaction.hpp" -#include "budget.hpp" -#include "coin.hpp" -#include "types.hpp" -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -using namespace libutt; -using namespace libutt::api; -using namespace libutt::api::operations; -using namespace std::chrono; - -int main(int argc, char* argv[]) { - std::srand(0); - (void)argc; - (void)argv; - size_t thresh = 3; - size_t n = 4; - size_t c = 10; - auto [d, dkg, rc] = testing::init(n, thresh); - auto registrators = testing::GenerateRegistrators(n, rc); - auto banks = testing::GenerateCommitters(n, dkg, rc.toPK()); - auto clients = testing::GenerateClients(c, dkg.getPK(), rc.toPK(), rc); - for (auto& c : clients) { - testing::registerClient(d, c, registrators, thresh); - } - std::unordered_map> coins; - std::unordered_map bcoins; - std::unordered_map> encryptors_; - for (size_t i = 0; i < clients.size(); i++) { - encryptors_[i] = std::make_shared(rc.toPK().mpk); - } - for (auto& c : clients) { - std::vector rsigs; - auto budget = Budget(d, c, 1000, 123456789); - for (size_t i = 0; i < banks.size(); i++) { - rsigs.push_back(banks[i]->sign(budget).front()); - } - auto sbs = testing::getSubset((uint32_t)n, (uint32_t)thresh); - std::map sigs; - for (auto i : sbs) { - sigs[i] = rsigs[i]; - } - auto blinded_sig = Utils::aggregateSigShares((uint32_t)n, {sigs}); - auto coin = c.claimCoins(budget, d, {blinded_sig}).front(); - assertTrue(c.validate(coin)); - bcoins.emplace(c.getPid(), coin); - } - - for (auto& c : clients) { - std::vector rsigs; - std::string simulatonOfUniqueTxHash = std::to_string(((uint64_t)rand()) * ((uint64_t)rand()) * ((uint64_t)rand())); - auto mint = Mint(simulatonOfUniqueTxHash, 100, c.getPid()); - for (size_t i = 0; i < banks.size(); i++) { - rsigs.push_back(banks[i]->sign(mint).front()); - } - auto sbs = testing::getSubset((uint32_t)n, (uint32_t)thresh); - std::map sigs; - for (auto i : sbs) { - sigs[i] = rsigs[i]; - } - auto blinded_sig = Utils::aggregateSigShares((uint32_t)n, {sigs}); - auto coin = c.claimCoins(mint, d, {blinded_sig}).front(); - assertTrue(c.validate(coin)); - coins[c.getPid()].emplace_back(std::move(coin)); - } - - // Now, each client transfers a 50$ to its predecessor; - for (size_t i = 0; i < clients.size(); i++) { - auto& issuer = clients[i]; - auto& receiver = clients[(i + 1) % clients.size()]; - Transaction tx(d, - issuer, - {coins[issuer.getPid()].front()}, - {bcoins[issuer.getPid()]}, - {{issuer.getPid(), 50}, {receiver.getPid(), 50}}, - *(encryptors_.at((i + 1) % clients.size()))); - coins[issuer.getPid()].erase(coins[issuer.getPid()].begin()); - bcoins.erase(issuer.getPid()); - std::map> shares; - std::vector shares_signers; - for (size_t i = 0; i < banks.size(); i++) { - shares[i] = banks[i]->sign(tx); - shares_signers.push_back(banks[i]->getId()); - } - size_t num_coins = shares[0].size(); - for (size_t i = 0; i < num_coins; i++) { - std::vector share_sigs(n); - auto sbs = testing::getSubset((uint32_t)n, (uint32_t)n); - for (auto s : sbs) { - share_sigs[s] = shares[s][i]; - } - for (auto& b : banks) { - for (size_t k = 0; k < share_sigs.size(); k++) { - auto& sig = share_sigs[k]; - auto& signer = shares_signers[k]; - assertTrue(b->validatePartialSignature(signer, sig, i, tx)); - } - } - } - - std::vector sigs; - for (size_t i = 0; i < num_coins; i++) { - std::map share_sigs; - auto sbs = testing::getSubset((uint32_t)n, (uint32_t)thresh); - for (auto s : sbs) { - share_sigs[s] = shares[s][i]; - } - auto blinded_sig = Utils::aggregateSigShares((uint32_t)n, {share_sigs}); - sigs.emplace_back(std::move(blinded_sig)); - } - auto issuer_coins = issuer.claimCoins(tx, d, sigs); - for (auto& coin : issuer_coins) { - assertTrue(issuer.validate(coin)); - if (coin.getType() == api::Coin::Type::Normal) { - coins[issuer.getPid()].emplace_back(std::move(coin)); - } else { - assertTrue(bcoins.find(issuer.getPid()) == bcoins.end()); - bcoins.emplace(issuer.getPid(), coin); - } - } - auto receiver_coins = receiver.claimCoins(tx, d, sigs); - for (auto& coin : receiver_coins) { - assertTrue(receiver.validate(coin)); - if (coin.getType() == api::Coin::Type::Normal) { - coins[receiver.getPid()].emplace_back(std::move(coin)); - } else { - assertTrue(false); - } - } - } - - // At the end of these phaese, the budget of each client is 950 and it has two coins of 50 each - for (const auto& c : clients) { - assertTrue(coins[c.getPid()].size() == 2); - for (const auto& coin : coins[c.getPid()]) assertTrue(coin.getVal() == 50U); - assertTrue(bcoins[c.getPid()].getVal() == 950U); - } - return 0; -} \ No newline at end of file diff --git a/utt/tests/TestTransactionWithRsaEncryption.cpp b/utt/tests/TestTransactionWithRsaEncryption.cpp deleted file mode 100644 index 9cc1e1e89f..0000000000 --- a/utt/tests/TestTransactionWithRsaEncryption.cpp +++ /dev/null @@ -1,213 +0,0 @@ -#include "testUtils.hpp" - -#include "UTTParams.hpp" -#include "testUtils.hpp" -#include "mint.hpp" -#include "burn.hpp" -#include "transaction.hpp" -#include "budget.hpp" -#include "coin.hpp" -#include "types.hpp" -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -using namespace libutt; -using namespace libutt::api; -using namespace libutt::api::operations; -using namespace std::chrono; -std::string privatek1 = - "-----BEGIN RSA PRIVATE KEY-----\n" - "MIICXQIBAAKBgQDAyUwjK+m4EXyFKEha2NfQUY8eIqurRujNMqI1z7B3bF/8Bdg0\n" - "wffIYJUjiXo13hB6yOq+gD62BGAPRjmgReBniT3d2rLU0Z72zQ64Gof66jCGQt0W\n" - "0sfwDUv0XsfXXW9p1EGBYwIkgW6UGiABnkZcIUW4dzP2URoRCN/VIypkRwIDAQAB\n" - "AoGAa3VIvSoTAoisscQ8YHcSBIoRjiihK71AsnAQvpHfuRFthxry4qVjqgs71i0h\n" - "M7lt0iL/xePSEL7rlFf+cvnAFL4/j1R04ImBjRzWGnaNE8I7nNGGzJo9rL5I1oi3\n" - "zN2yUucTSGm7qR0MCNVy26zNmCuS/FdBPsfdZ017OTsHtPECQQDlWXAJG6nHyw2o\n" - "2cLYHzlyrrYgnWJkgFSKzr7VFNlHxfQSWXJ4zuDwhqkm3d176bVm4eHhDDv6f413\n" - "iQGraKvTAkEA1zAzpxfI7LAqd3sObWYstQb03IXE7yddMgbhoMDCT3gXhNaHKfjT\n" - "Z/GIk49jh8kyitN2FeYXXi9TiwrXStfhPQJBAMNea6ymjvstwoYKcgsOli5WG7ku\n" - "uEkqdFoGAdObvfeA7gfPgE7e1AiwfVkpd+l9TVTFqFe/xzv8+fJQmEZ+lJcCQQDN\n" - "5I7nh7h1zzEy1Qk+345TP262OT/u26kuHqtv1j+VLgDC10jIfg443D+jgITo/Tdg\n" - "4WeRGHCva3TyCtNoBxq5AkA9KZpKof4ripad4oIuCJpR/ZhQATgQwR9f+FlAxgP0\n" - "ABmBPCoxy4uGMtSBMqiiGpImbDuivYkhlBl7D8u8vn26\n" - "-----END RSA PRIVATE KEY-----\n"; -std::string publick1 = - "-----BEGIN PUBLIC KEY-----\n" - "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAyUwjK+m4EXyFKEha2NfQUY8e\n" - "IqurRujNMqI1z7B3bF/8Bdg0wffIYJUjiXo13hB6yOq+gD62BGAPRjmgReBniT3d\n" - "2rLU0Z72zQ64Gof66jCGQt0W0sfwDUv0XsfXXW9p1EGBYwIkgW6UGiABnkZcIUW4\n" - "dzP2URoRCN/VIypkRwIDAQAB\n" - "-----END PUBLIC KEY-----\n"; - -std::string privatek2 = - "-----BEGIN RSA PRIVATE KEY-----\n" - "MIICXAIBAAKBgQDMw+qeJXQ/ZxrqSqjRXoN2wFYNwhljh7RTBJuIzaqK2pDL+zaK\n" - "aREzgufG/7r2uk8njWW7EJbriLcmj1oJA913LPl4YWUEORKl7Cb6wLoPO/E5gAt1\n" - "JJ0dhDeCRU+E7vtNQ4pLyy2dYS2GHO1Tm88yuFegS3gkINPYgzNggGJ2cQIDAQAB\n" - "AoGAScyCjqTpFMDQTojB91OdBfukCClghSKvtwv+EnwtbwX/EcVkjtX3QR1485vP\n" - "goT7akHn3FfKTPFlMRyRUpZ2Bov1whQ1ztuboIonFQ7ohbDTLE3QzUv4L3e8cEbY\n" - "51MSe8tEUVRxKu53nD3asWxAi/CEyqWvRCzf4s3Q6Xw3h5ECQQD8mBT6ervLr1Qk\n" - "oKmaAuPTDyZDaSjipD0/d1p1vG8Wb8go14tq89Ts+UIWzH5aGlidxTK9j/luQqlR\n" - "YVVGNkC3AkEAz4a8jtg2++fvWT0PDz6OBfw/iHJQzSmynlzKQzpRac02UBCPo4an\n" - "o7wl9uEnucXuVpCSo0JdSf+x4r9dwmCKFwJBAPWlGNG2xicBbPzp2cZTBSheVUG9\n" - "ZOtz+bRc5/YTuJzDPI6rf4QVeH60sNbnLAGIGaHlAsFi4Jmf7nWcCIftfuUCQEbx\n" - "hJxAhetvyn7zRKatd9fL99wpWD4Ktyk0B2EcGqDUqnCMeM4qRjzPIRtYtT/oziWB\n" - "nt943HNjmeguC1tbrVkCQBMd+kbpcoFHKKrC577FM24maWRTfXJeu2/o6pxUFIUY\n" - "kzkDZ2k+FfvXWaY+N5q5bJCayor8W1QeruHzewrQmgY=\n" - "-----END RSA PRIVATE KEY-----\n"; -std::string publick2 = - "-----BEGIN PUBLIC KEY-----\n" - "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMw+qeJXQ/ZxrqSqjRXoN2wFYN\n" - "whljh7RTBJuIzaqK2pDL+zaKaREzgufG/7r2uk8njWW7EJbriLcmj1oJA913LPl4\n" - "YWUEORKl7Cb6wLoPO/E5gAt1JJ0dhDeCRU+E7vtNQ4pLyy2dYS2GHO1Tm88yuFeg\n" - "S3gkINPYgzNggGJ2cQIDAQAB\n" - "-----END PUBLIC KEY-----\n"; - -std::string privatek3 = - "-----BEGIN RSA PRIVATE KEY-----\n" - "MIICXAIBAAKBgQCnmdqW7f/rg8CiUzLugxc0jPMqzphhtl40IqINAk3pasCQsA3T\n" - "eHSa1fcKocFMY9bfQRvqiKpnK74d0V0fujFUaPIMlKMLWXEVefwe1fOYrDXOoz7N\n" - "3Go7x9u/LXwCA6HehXtIavtTPQs1yHldb/bDocEhjfGvU3TLXkAuGWsnmQIDAQAB\n" - "AoGBAJ6fRHyYICB8f7Kh35BRTYMU64fWI+5GtX3OUWTSi36g5EOL/GnqlSF94+OS\n" - "F+n+i/ycGJmuYuhmQ/bgkaxXghsDYb7fsdMJ8DEqUJAKbxeOosn8fxwmJkNAJ07J\n" - "+oAg/xkJ+ukyYnPf0P3UTuTZl0EFEpwu/vnX09QJGtuXgmQhAkEA0c0Co9MdP62r\n" - "/ybXXeqgaol2YVGzFr/bMz3hhlviV9IOGPRZmeQ8v+f1/lSsqZ8wSP5P8dkBo4UB\n" - "NSLaHAUL/QJBAMyB72EyHZUEFy3o241myqamfVtN+Dzo6qdPn/PfF/BLjwsEApCO\n" - "oUObmDDo/yiSSb00XSnn23bGYH1VJJDNJs0CQE1aG+YQ+VC4FJkfVfpvfjOpePcK\n" - "q0/w7r2mzBbAm+QrMz1qIfsGVoue12itCXgElEXlVc5iZyNF75sKvYXlKnUCQHHC\n" - "tc5zelEyfVJkff0ieQhLBOCNdtErH50Chg+6wi5BWcje6i5PqRVasEZE1etTtQEy\n" - "58Av4b0ojPQrMLP76uECQEz0c1RPDwMvznwT3BJxl8t4tixPML0nyBMD8ttnZswG\n" - "K/1CYV1uMNbchmuVQb2Kd2JyE1gQF8s3ShsbteMc5og=\n" - "-----END RSA PRIVATE KEY-----\n"; - -std::string publick3 = - "-----BEGIN PUBLIC KEY-----\n" - "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCnmdqW7f/rg8CiUzLugxc0jPMq\n" - "zphhtl40IqINAk3pasCQsA3TeHSa1fcKocFMY9bfQRvqiKpnK74d0V0fujFUaPIM\n" - "lKMLWXEVefwe1fOYrDXOoz7N3Go7x9u/LXwCA6HehXtIavtTPQs1yHldb/bDocEh\n" - "jfGvU3TLXkAuGWsnmQIDAQAB\n" - "-----END PUBLIC KEY-----\n"; - -std::vector pr_keys{privatek1, privatek2, privatek3}; -std::vector pkeys{publick1, publick2, publick3}; -int main(int argc, char* argv[]) { - std::srand(0); - (void)argc; - (void)argv; - size_t thresh = 3; - size_t n = 4; - size_t c = 3; - auto [d, dkg, rc] = testing::init(n, thresh); - auto registrators = testing::GenerateRegistrators(n, rc); - auto banks = testing::GenerateCommitters(n, dkg, rc.toPK()); - auto clients = testing::GenerateClients(c, dkg.getPK(), rc.toPK(), pr_keys); - for (auto& c : clients) { - testing::registerClient(d, c, registrators, thresh); - } - std::unordered_map> coins; - std::unordered_map bcoins; - for (auto& c : clients) { - std::vector rsigs; - auto budget = Budget(d, c, 1000, 123456789); - for (size_t i = 0; i < banks.size(); i++) { - rsigs.push_back(banks[i]->sign(budget).front()); - } - auto sbs = testing::getSubset((uint32_t)n, (uint32_t)thresh); - std::map sigs; - for (auto i : sbs) { - sigs[i] = rsigs[i]; - } - auto blinded_sig = Utils::aggregateSigShares((uint32_t)n, {sigs}); - auto coin = c.claimCoins(budget, d, {blinded_sig}).front(); - assertTrue(c.validate(coin)); - bcoins.emplace(c.getPid(), coin); - } - - for (auto& c : clients) { - std::vector rsigs; - std::string simulatonOfUniqueTxHash = std::to_string(((uint64_t)rand()) * ((uint64_t)rand()) * ((uint64_t)rand())); - auto mint = Mint(simulatonOfUniqueTxHash, 100, c.getPid()); - for (size_t i = 0; i < banks.size(); i++) { - rsigs.push_back(banks[i]->sign(mint).front()); - } - auto sbs = testing::getSubset((uint32_t)n, (uint32_t)thresh); - std::map sigs; - for (auto i : sbs) { - sigs[i] = rsigs[i]; - } - auto blinded_sig = Utils::aggregateSigShares((uint32_t)n, {sigs}); - auto coin = c.claimCoins(mint, d, {blinded_sig}).front(); - assertTrue(c.validate(coin)); - coins[c.getPid()].emplace_back(std::move(coin)); - } - - // Now, each client transfers a 50$ to its predecessor; - for (size_t i = 0; i < clients.size(); i++) { - auto& issuer = clients[i]; - auto& receiver = clients[(i + 1) % clients.size()]; - std::map tx_pub_keys; - tx_pub_keys[issuer.getPid()] = pkeys[i]; - tx_pub_keys[receiver.getPid()] = pkeys[(i + 1) % clients.size()]; - libutt::RSAEncryptor tx_encryptor(tx_pub_keys); - Transaction tx(d, - issuer, - {coins[issuer.getPid()].front()}, - {bcoins[issuer.getPid()]}, - {{issuer.getPid(), 50}, {receiver.getPid(), 50}}, - tx_encryptor); - coins[issuer.getPid()].erase(coins[issuer.getPid()].begin()); - bcoins.erase(issuer.getPid()); - std::unordered_map> shares; - for (size_t i = 0; i < banks.size(); i++) { - shares[i] = banks[i]->sign(tx); - } - size_t num_coins = shares[0].size(); - std::vector sigs; - for (size_t i = 0; i < num_coins; i++) { - std::map share_sigs; - auto sbs = testing::getSubset((uint32_t)n, (uint32_t)thresh); - for (auto s : sbs) { - share_sigs[s] = shares[s][i]; - } - auto blinded_sig = Utils::aggregateSigShares((uint32_t)n, {share_sigs}); - sigs.emplace_back(std::move(blinded_sig)); - } - auto issuer_coins = issuer.claimCoins(tx, d, sigs); - for (auto& coin : issuer_coins) { - assertTrue(issuer.validate(coin)); - if (coin.getType() == api::Coin::Type::Normal) { - coins[issuer.getPid()].emplace_back(std::move(coin)); - } else { - assertTrue(bcoins.find(issuer.getPid()) == bcoins.end()); - bcoins.emplace(issuer.getPid(), coin); - } - } - auto receiver_coins = receiver.claimCoins(tx, d, sigs); - for (auto& coin : receiver_coins) { - assertTrue(receiver.validate(coin)); - if (coin.getType() == api::Coin::Type::Normal) { - coins[receiver.getPid()].emplace_back(std::move(coin)); - } else { - assertTrue(false); - } - } - } - - // At the end of these phaese, the budget of each client is 950 and it has two coins of 50 each - for (const auto& c : clients) { - assertTrue(coins[c.getPid()].size() == 2); - for (const auto& coin : coins[c.getPid()]) assertTrue(coin.getVal() == 50U); - assertTrue(bcoins[c.getPid()].getVal() == 950U); - } - return 0; -} \ No newline at end of file diff --git a/utt/tests/TestUttNewApi.cpp b/utt/tests/TestUttNewApi.cpp new file mode 100644 index 0000000000..f6cd68cb13 --- /dev/null +++ b/utt/tests/TestUttNewApi.cpp @@ -0,0 +1,559 @@ +#include "UTTParams.hpp" +#include "testUtils.hpp" +#include "mint.hpp" +#include "budget.hpp" +#include "burn.hpp" +#include "commitment.hpp" +#include "transaction.hpp" +#include "config.hpp" +#include "serialization.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace libutt; +using namespace libutt::api; +using namespace libutt::api::operations; + +namespace { +class ibe_based_test_system : public libutt::api::testing::test_utt_instance { + protected: + void SetUp() override { libutt::api::testing::test_utt_instance::setUp(true, true); } +}; + +class ibe_based_test_system_without_registration : public libutt::api::testing::test_utt_instance { + protected: + void SetUp() override { libutt::api::testing::test_utt_instance::setUp(true, false); } +}; + +class rsa_based_test_system : public libutt::api::testing::test_utt_instance { + protected: + void SetUp() override { libutt::api::testing::test_utt_instance::setUp(false, true); } +}; + +class rsa_based_test_system_without_registration : public libutt::api::testing::test_utt_instance { + protected: + void SetUp() override { libutt::api::testing::test_utt_instance::setUp(false, false); } +}; + +class test_system_minted : public libutt::api::testing::test_utt_instance { + public: + void setUp(bool ibe) { + libutt::api::testing::test_utt_instance::setUp(ibe, true); + for (auto& c : clients) { + std::vector> rsigs; + std::string simulatonOfUniqueTxHash = + std::to_string(((uint64_t)rand()) * ((uint64_t)rand()) * ((uint64_t)rand())); + auto mint = Mint(simulatonOfUniqueTxHash, 100, c.getPid()); + for (size_t i = 0; i < banks.size(); i++) { + rsigs.push_back(banks[i]->sign(mint).front()); + } + auto sbs = getSubset((uint32_t)n, (uint32_t)thresh); + std::map> sigs; + for (auto i : sbs) { + sigs[i] = rsigs[i]; + } + auto blinded_sig = Utils::aggregateSigShares((uint32_t)n, {sigs}); + auto coin = c.claimCoins(mint, d, {blinded_sig}).front(); + ASSERT_TRUE(c.validate(coin)); + coins[c.getPid()] = {coin}; + } + + for (auto& c : clients) { + std::vector> rsigs; + std::vector shares_signers; + auto budget = Budget(d, c, 1000, 123456789); + for (size_t i = 0; i < banks.size(); i++) { + rsigs.push_back(banks[i]->sign(budget).front()); + shares_signers.push_back(banks[i]->getId()); + } + for (auto& b : banks) { + for (size_t i = 0; i < rsigs.size(); i++) { + auto& sig = rsigs[i]; + auto& signer = shares_signers[i]; + ASSERT_TRUE(b->validatePartialSignature(signer, sig, 0, budget)); + } + } + auto sbs = getSubset((uint32_t)n, (uint32_t)thresh); + std::map> sigs; + for (auto i : sbs) { + sigs[i] = rsigs[i]; + } + auto blinded_sig = Utils::aggregateSigShares((uint32_t)n, {sigs}); + auto coin = c.claimCoins(budget, d, {blinded_sig}).front(); + ASSERT_TRUE(c.validate(coin)); + bcoins[c.getPid()] = {coin}; + } + } + std::map> coins; + std::map> bcoins; +}; + +class ibe_based_test_system_minted : public test_system_minted { + protected: + void SetUp() override { test_system_minted::setUp(true); } +}; + +class ibe_based_test_system_minted_impl : public ibe_based_test_system_minted { + public: + ibe_based_test_system_minted_impl() { ibe_based_test_system_minted::SetUp(); } + void TestBody() override {} +}; + +class rsa_based_test_system_minted : public test_system_minted { + protected: + void SetUp() override { test_system_minted::setUp(false); } +}; + +TEST_F(ibe_based_test_system_without_registration, test_distributed_registration) { + for (auto& c : clients) { + registerClient(c); + auto rcm_data = c.rerandomizeRcm(d); + for (auto& r : registrators) { + ASSERT_TRUE(r->validateRCM(rcm_data.first, rcm_data.second)); + } + } +} + +TEST_F(rsa_based_test_system_without_registration, test_distributed_registration) { + for (auto& c : clients) { + registerClient(c); + auto rcm_data = c.rerandomizeRcm(d); + for (auto& r : registrators) { + ASSERT_TRUE(r->validateRCM(rcm_data.first, rcm_data.second)); + } + } +} + +TEST_F(ibe_based_test_system, test_mint) { + for (auto& c : clients) { + std::vector> rsigs; + std::vector shares_signers; + std::string simulatonOfUniqueTxHash = std::to_string(((uint64_t)rand()) * ((uint64_t)rand()) * ((uint64_t)rand())); + auto mint = Mint(simulatonOfUniqueTxHash, 100, c.getPid()); + for (size_t i = 0; i < banks.size(); i++) { + rsigs.push_back(banks[i]->sign(mint).front()); + shares_signers.push_back(banks[i]->getId()); + } + for (auto& b : banks) { + for (size_t i = 0; i < rsigs.size(); i++) { + auto& sig = rsigs[i]; + auto& signer = shares_signers[i]; + ASSERT_TRUE(b->validatePartialSignature(signer, sig, 0, mint)); + } + } + auto sbs = getSubset((uint32_t)n, (uint32_t)thresh); + std::map> sigs; + for (auto i : sbs) { + sigs[i] = rsigs[i]; + } + auto blinded_sig = Utils::aggregateSigShares((uint32_t)n, {sigs}); + auto coin = c.claimCoins(mint, d, {blinded_sig}).front(); + ASSERT_TRUE(c.validate(coin)); + } +} + +TEST_F(ibe_based_test_system, test_budget_creation_by_client) { + for (auto& c : clients) { + std::vector> rsigs; + std::vector shares_signers; + auto budget = Budget(d, c, 1000, 123456789); + for (size_t i = 0; i < banks.size(); i++) { + rsigs.push_back(banks[i]->sign(budget).front()); + shares_signers.push_back(banks[i]->getId()); + } + for (auto& b : banks) { + for (size_t i = 0; i < rsigs.size(); i++) { + auto& sig = rsigs[i]; + auto& signer = shares_signers[i]; + ASSERT_TRUE(b->validatePartialSignature(signer, sig, 0, budget)); + } + } + auto sbs = getSubset((uint32_t)n, (uint32_t)thresh); + std::map> sigs; + for (auto i : sbs) { + sigs[i] = rsigs[i]; + } + auto blinded_sig = Utils::aggregateSigShares((uint32_t)n, {sigs}); + auto coin = c.claimCoins(budget, d, {blinded_sig}).front(); + ASSERT_TRUE(c.validate(coin)); + } +} + +TEST_F(ibe_based_test_system, test_budget_creation_by_replicas) { + for (auto& c : clients) { + std::vector> rsigs; + auto snHash = Fr::random_element() + .to_words(); // Assume each replica can compute the same sn using some common execution state + for (size_t i = 0; i < banks.size(); i++) { + // Each replica creates its own version of the budget coin and sign it. We expect to have deterministic coin here + auto budget = Budget(d, snHash, c.getPidHash(), 1000, 123456789); + rsigs.push_back(banks[i]->sign(budget).front()); + } + auto budget = Budget(d, snHash, c.getPidHash(), 1000, 123456789); + auto sbs = getSubset((uint32_t)n, (uint32_t)thresh); + std::map> sigs; + for (auto i : sbs) { + sigs[i] = rsigs[i]; + } + auto blinded_sig = Utils::aggregateSigShares((uint32_t)n, {sigs}); + auto coin = c.claimCoins(budget, d, {blinded_sig}).front(); + coin.createNullifier(d, c.getPRFSecretKey()); + ASSERT_TRUE(!coin.getNullifier().empty()); + ASSERT_TRUE(c.validate(coin)); + } +} + +TEST_F(ibe_based_test_system_minted, test_valid_burn) { + for (auto& c : clients) { + Burn b_op{d, c, coins[c.getPid()].front()}; + for (auto& b : banks) { + ASSERT_TRUE(b->validate(d, b_op)); + } + } +} + +TEST_F(ibe_based_test_system_minted, test_invalid_burn) { + auto other_utt_sys = ibe_based_test_system_minted_impl(); + for (auto& c : clients) { + Burn b_op{d, c, coins[c.getPid()].front()}; + for (auto& b : other_utt_sys.banks) { + ASSERT_FALSE(b->validate(other_utt_sys.d, b_op)); + } + } +} + +TEST_F(ibe_based_test_system, test_serialization_configuration) { + auto config = libutt::api::Configuration((uint16_t)n, (uint16_t)thresh); + ASSERT_TRUE(config.isValid()); + auto serialized_config = libutt::api::serialize(config); + auto deserialized_config = libutt::api::deserialize(serialized_config); + ASSERT_TRUE(deserialized_config.isValid()); + ASSERT_TRUE(deserialized_config == config); + + const auto& publicConfig = config.getPublicConfig(); + auto serialized_public_config = libutt::api::serialize(publicConfig); + auto deserialized_public_config = libutt::api::deserialize(serialized_public_config); + ASSERT_TRUE(deserialized_public_config == publicConfig); +} + +TEST_F(ibe_based_test_system, test_serialization_global_params) { + std::vector serialized_params = libutt::api::serialize(d); + auto deserialized_params = libutt::api::deserialize(serialized_params); + ASSERT_TRUE(deserialized_params == d); +} + +TEST_F(ibe_based_test_system, test_serialization_commitment) { + Commitment rcm = clients[0].getRcm().first; + std::vector serialized_rcm = libutt::api::serialize(rcm); + auto deserialized_rcm = libutt::api::deserialize(serialized_rcm); + ASSERT_TRUE(rcm == deserialized_rcm); +} + +TEST_F(ibe_based_test_system, test_serialization_complete_coin) { + Fr fr_val; + fr_val.set_ulong(100); + libutt::api::Coin c(d, + Fr::random_element().to_words(), + Fr::random_element().to_words(), + fr_val.to_words(), + Fr::random_element().to_words(), + libutt::api::Coin::Type::Normal, + Fr::random_element().to_words()); + std::vector c_serialized = libutt::api::serialize(c); + libutt::api::Coin c_deserialized = libutt::api::deserialize(c_serialized); + assertTrue(c.getNullifier() == c_deserialized.getNullifier()); + assertTrue(c.hasSig() == c_deserialized.hasSig()); + assertTrue(c.getType() == c_deserialized.getType()); + assertTrue(c.getVal() == c_deserialized.getVal()); + assertTrue(c.getSN() == c_deserialized.getSN()); + assertTrue(c.getExpDateAsCurvePoint() == c_deserialized.getExpDateAsCurvePoint()); +} + +TEST_F(ibe_based_test_system, test_serialization_incomplete_coin) { + Fr fr_val; + fr_val.set_ulong(100); + libutt::api::Coin c(d, + Fr::random_element().to_words(), + fr_val.to_words(), + Fr::random_element().to_words(), + libutt::api::Coin::Type::Normal, + Fr::random_element().to_words()); + std::vector c_serialized = libutt::api::serialize(c); + libutt::api::Coin c_deserialized = libutt::api::deserialize(c_serialized); + assertTrue(c.getNullifier() == c_deserialized.getNullifier()); + assertTrue(c.hasSig() == c_deserialized.hasSig()); + assertTrue(c.getType() == c_deserialized.getType()); + assertTrue(c.getVal() == c_deserialized.getVal()); + assertTrue(c.getSN() == c_deserialized.getSN()); + assertTrue(c.getExpDateAsCurvePoint() == c_deserialized.getExpDateAsCurvePoint()); +} +TEST_F(ibe_based_test_system, test_serialization_complete_budget_coin) { + Fr fr_val; + fr_val.set_ulong(100); + libutt::api::Coin c(d, + Fr::random_element().to_words(), + fr_val.to_words(), + Fr::random_element().to_words(), + libutt::api::Coin::Type::Budget, + Fr::random_element().to_words()); + std::vector c_serialized = libutt::api::serialize(c); + libutt::api::Coin c_deserialized = libutt::api::deserialize(c_serialized); + assertTrue(c.getNullifier() == c_deserialized.getNullifier()); + assertTrue(c.hasSig() == c_deserialized.hasSig()); + assertTrue(c.getType() == c_deserialized.getType()); + assertTrue(c.getVal() == c_deserialized.getVal()); + assertTrue(c.getSN() == c_deserialized.getSN()); + assertTrue(c.getExpDateAsCurvePoint() == c_deserialized.getExpDateAsCurvePoint()); +} + +TEST_F(ibe_based_test_system, test_serialization_incomplete_budget_coin) { + Fr fr_val; + fr_val.set_ulong(100); + libutt::api::Coin c(d, + Fr::random_element().to_words(), + Fr::random_element().to_words(), + fr_val.to_words(), + Fr::random_element().to_words(), + libutt::api::Coin::Type::Budget, + Fr::random_element().to_words()); + std::vector c_serialized = libutt::api::serialize(c); + libutt::api::Coin c_deserialized = libutt::api::deserialize(c_serialized); + assertTrue(c.getNullifier() == c_deserialized.getNullifier()); + assertTrue(c.hasSig() == c_deserialized.hasSig()); + assertTrue(c.getType() == c_deserialized.getType()); + assertTrue(c.getVal() == c_deserialized.getVal()); + assertTrue(c.getSN() == c_deserialized.getSN()); + assertTrue(c.getExpDateAsCurvePoint() == c_deserialized.getExpDateAsCurvePoint()); +} + +TEST_F(ibe_based_test_system, test_serialization_incomplete_budget_op) { + libutt::api::operations::Budget b( + d, Fr::random_element().to_words(), Fr::random_element().to_words(), 100, 987654321); + std::vector b_serialized = libutt::api::serialize(b); + libutt::api::operations::Budget b_deserialized = + libutt::api::deserialize(b_serialized); + assertTrue(b.getHashHex() == b_deserialized.getHashHex()); + const auto b_coin = b.getCoin(); + const auto c_deserialized_coin = b_deserialized.getCoin(); + assertTrue(b_coin.getNullifier() == c_deserialized_coin.getNullifier()); + assertTrue(b_coin.hasSig() == c_deserialized_coin.hasSig()); + assertTrue(b_coin.getType() == c_deserialized_coin.getType()); + assertTrue(b_coin.getVal() == c_deserialized_coin.getVal()); + assertTrue(b_coin.getSN() == c_deserialized_coin.getSN()); + assertTrue(b_coin.getExpDateAsCurvePoint() == c_deserialized_coin.getExpDateAsCurvePoint()); +} + +TEST_F(ibe_based_test_system, test_serialization_mint_op) { + for (auto& c : clients) { + std::vector> rsigs; + std::string simulatonOfUniqueTxHash = std::to_string(((uint64_t)rand()) * ((uint64_t)rand()) * ((uint64_t)rand())); + auto mint = Mint(simulatonOfUniqueTxHash, 100, c.getPid()); + std::vector mint_serialized = libutt::api::serialize(mint); + auto mint_deserialized = libutt::api::deserialize(mint_serialized); + assertTrue(mint.getHash() == mint_deserialized.getHash()); + assertTrue(mint.getVal() == mint_deserialized.getVal()); + assertTrue(mint.getRecipentID() == mint_deserialized.getRecipentID()); + } +} + +TEST_F(ibe_based_test_system_minted, test_serialization_burn_op) { + for (auto& c : clients) { + Burn b_op{d, c, coins[c.getPid()].front()}; + std::vector burn_serialized = libutt::api::serialize(b_op); + auto burn_deserialized = libutt::api::deserialize(burn_serialized); + assertTrue(b_op.getNullifier() == burn_deserialized.getNullifier()); + const auto& burn_coin = b_op.getCoin(); + const auto& burn_deserialized_coin = burn_deserialized.getCoin(); + assertTrue(burn_coin.getNullifier() == burn_deserialized_coin.getNullifier()); + assertTrue(burn_coin.hasSig() == burn_deserialized_coin.hasSig()); + assertTrue(burn_coin.getType() == burn_deserialized_coin.getType()); + assertTrue(burn_coin.getVal() == burn_deserialized_coin.getVal()); + assertTrue(burn_coin.getSN() == burn_deserialized_coin.getSN()); + assertTrue(burn_coin.getExpDateAsCurvePoint() == burn_deserialized_coin.getExpDateAsCurvePoint()); + } +} + +TEST_F(ibe_based_test_system_minted, test_serialization_tx_op) { + const auto& client1 = clients[0]; + const auto& client2 = clients[1]; + auto coin = coins[client1.getPid()].front(); + auto bcoin = bcoins[client1.getPid()].front(); + auto encryptor = std::make_shared(rsk.toPK().mpk); + Transaction tx(d, client1, {coin}, {bcoin}, {{client1.getPid(), 50}, {client2.getPid(), 50}}, *(encryptor)); + + // Test Transaction de/serialization + std::vector serialized_tx = libutt::api::serialize(tx); + auto deserialized_tx = libutt::api::deserialize(serialized_tx); + assertTrue(tx.hasBudgetCoin() == deserialized_tx.hasBudgetCoin()); + assertTrue(tx.getBudgetExpirationDate() == deserialized_tx.getBudgetExpirationDate()); + assertTrue(tx.getNullifiers() == deserialized_tx.getNullifiers()); +} + +TEST_F(ibe_based_test_system_minted, test_transaction) { + std::unordered_map> encryptors_; + for (size_t i = 0; i < clients.size(); i++) { + encryptors_[i] = std::make_shared(rsk.toPK().mpk); + } + for (size_t i = 0; i < clients.size(); i++) { + auto& issuer = clients[i]; + auto& receiver = clients[(i + 1) % clients.size()]; + Transaction tx(d, + issuer, + {coins[issuer.getPid()].front()}, + {bcoins[issuer.getPid()].front()}, + {{issuer.getPid(), 50}, {receiver.getPid(), 50}}, + *(encryptors_.at((i + 1) % clients.size()))); + coins[issuer.getPid()].erase(coins[issuer.getPid()].begin()); + bcoins.erase(issuer.getPid()); + std::map> shares; + std::vector shares_signers; + for (size_t i = 0; i < banks.size(); i++) { + shares[i] = banks[i]->sign(tx); + shares_signers.push_back(banks[i]->getId()); + } + size_t num_coins = shares[0].size(); + for (size_t i = 0; i < num_coins; i++) { + std::vector share_sigs(n); + auto sbs = getSubset((uint32_t)n, (uint32_t)n); + for (auto s : sbs) { + share_sigs[s] = shares[s][i]; + } + for (auto& b : banks) { + for (size_t k = 0; k < share_sigs.size(); k++) { + auto& sig = share_sigs[k]; + auto& signer = shares_signers[k]; + assertTrue(b->validatePartialSignature(signer, sig, i, tx)); + } + } + } + + std::vector sigs; + for (size_t i = 0; i < num_coins; i++) { + std::map share_sigs; + auto sbs = getSubset((uint32_t)n, (uint32_t)thresh); + for (auto s : sbs) { + share_sigs[s] = shares[s][i]; + } + auto blinded_sig = Utils::aggregateSigShares((uint32_t)n, {share_sigs}); + sigs.emplace_back(std::move(blinded_sig)); + } + auto issuer_coins = issuer.claimCoins(tx, d, sigs); + for (auto& coin : issuer_coins) { + assertTrue(issuer.validate(coin)); + if (coin.getType() == api::Coin::Type::Normal) { + coins[issuer.getPid()].emplace_back(std::move(coin)); + } else { + assertTrue(bcoins.find(issuer.getPid()) == bcoins.end()); + bcoins[issuer.getPid()] = {coin}; + } + } + auto receiver_coins = receiver.claimCoins(tx, d, sigs); + for (auto& coin : receiver_coins) { + assertTrue(receiver.validate(coin)); + if (coin.getType() == api::Coin::Type::Normal) { + coins[receiver.getPid()].emplace_back(std::move(coin)); + } else { + assertTrue(false); + } + } + } + + // At the end of these phaese, the budget of each client is 950 and it has two coins of 50 each + for (const auto& c : clients) { + assertTrue(coins[c.getPid()].size() == 2); + for (const auto& coin : coins[c.getPid()]) assertTrue(coin.getVal() == 50U); + assertTrue(bcoins[c.getPid()].front().getVal() == 950U); + } +} + +TEST_F(rsa_based_test_system_minted, test_transaction) { + for (size_t i = 0; i < clients.size(); i++) { + auto& issuer = clients[i]; + auto& receiver = clients[(i + 1) % clients.size()]; + std::map tx_pub_keys; + tx_pub_keys[issuer.getPid()] = libutt::api::testing::pkeys[i]; + tx_pub_keys[receiver.getPid()] = libutt::api::testing::pkeys[(i + 1) % clients.size()]; + libutt::RSAEncryptor tx_encryptor(tx_pub_keys); + Transaction tx(d, + issuer, + {coins[issuer.getPid()].front()}, + {bcoins[issuer.getPid()].front()}, + {{issuer.getPid(), 50}, {receiver.getPid(), 50}}, + tx_encryptor); + coins[issuer.getPid()].erase(coins[issuer.getPid()].begin()); + bcoins.erase(issuer.getPid()); + std::map> shares; + std::vector shares_signers; + for (size_t i = 0; i < banks.size(); i++) { + shares[i] = banks[i]->sign(tx); + shares_signers.push_back(banks[i]->getId()); + } + size_t num_coins = shares[0].size(); + for (size_t i = 0; i < num_coins; i++) { + std::vector share_sigs(n); + auto sbs = getSubset((uint32_t)n, (uint32_t)n); + for (auto s : sbs) { + share_sigs[s] = shares[s][i]; + } + for (auto& b : banks) { + for (size_t k = 0; k < share_sigs.size(); k++) { + auto& sig = share_sigs[k]; + auto& signer = shares_signers[k]; + assertTrue(b->validatePartialSignature(signer, sig, i, tx)); + } + } + } + + std::vector sigs; + for (size_t i = 0; i < num_coins; i++) { + std::map share_sigs; + auto sbs = getSubset((uint32_t)n, (uint32_t)thresh); + for (auto s : sbs) { + share_sigs[s] = shares[s][i]; + } + auto blinded_sig = Utils::aggregateSigShares((uint32_t)n, {share_sigs}); + sigs.emplace_back(std::move(blinded_sig)); + } + auto issuer_coins = issuer.claimCoins(tx, d, sigs); + for (auto& coin : issuer_coins) { + assertTrue(issuer.validate(coin)); + if (coin.getType() == api::Coin::Type::Normal) { + coins[issuer.getPid()].emplace_back(std::move(coin)); + } else { + assertTrue(bcoins.find(issuer.getPid()) == bcoins.end()); + bcoins[issuer.getPid()] = {coin}; + } + } + auto receiver_coins = receiver.claimCoins(tx, d, sigs); + for (auto& coin : receiver_coins) { + assertTrue(receiver.validate(coin)); + if (coin.getType() == api::Coin::Type::Normal) { + coins[receiver.getPid()].emplace_back(std::move(coin)); + } else { + assertTrue(false); + } + } + } + + // At the end of these phaese, the budget of each client is 950 and it has two coins of 50 each + for (const auto& c : clients) { + assertTrue(coins[c.getPid()].size() == 2); + for (const auto& coin : coins[c.getPid()]) assertTrue(coin.getVal() == 50U); + assertTrue(bcoins[c.getPid()].front().getVal() == 950U); + } +} +} // namespace + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/utt/tests/testUtils.hpp b/utt/tests/testUtils.hpp index 9df2241dcf..c470e1a4bb 100644 --- a/utt/tests/testUtils.hpp +++ b/utt/tests/testUtils.hpp @@ -22,132 +22,228 @@ #include #include #include + +#include "gtest/gtest.h" + using namespace libutt; using namespace libutt::api; namespace libutt::api::testing { -struct GpData { - libutt::CommKey cck; - libutt::CommKey rck; -}; -std::vector getSubset(uint32_t n, uint32_t size) { - std::srand((unsigned int)std::chrono::system_clock::now().time_since_epoch().count()); - std::map ret; - for (uint32_t i = 0; i < n; i++) ret[i] = i; - for (uint32_t i = 0; i < n - size; i++) { - uint32_t index = (uint32_t)(std::rand()) % (uint32_t)(ret.size()); - ret.erase(index); - } - std::vector rret; - for (const auto& [k, v] : ret) { - (void)k; - rret.push_back(v); - } - return rret; -} -std::tuple init(size_t n, size_t thresh) { - UTTParams::BaseLibsInitData base_libs_init_data; - UTTParams::initLibs(base_libs_init_data); - auto dkg = RandSigDKG(thresh, n, Params::NumMessages); - auto rc = RegAuthSK::generateKeyAndShares(thresh, n); - GpData gp_data{dkg.getCK(), rc.ck_reg}; - UTTParams d = UTTParams::create((void*)(&gp_data)); - rc.setIBEParams(d.getParams().ibe); - return {d, dkg, rc}; -} -std::vector> GenerateRegistrators(size_t n, const RegAuthSK& rsk) { - std::vector> registrators; - std::map validation_keys; - for (size_t i = 0; i < n; i++) { - auto& sk = rsk.shares[i]; - validation_keys[(uint16_t)i] = serialize(sk.toPK()); - } - for (size_t i = 0; i < n; i++) { - registrators.push_back(std::make_shared( - (uint16_t)i, serialize(rsk.shares[i]), validation_keys, serialize(rsk.toPK()))); - } - return registrators; -} +std::string privatek1 = + "-----BEGIN RSA PRIVATE KEY-----\n" + "MIICXQIBAAKBgQDAyUwjK+m4EXyFKEha2NfQUY8eIqurRujNMqI1z7B3bF/8Bdg0\n" + "wffIYJUjiXo13hB6yOq+gD62BGAPRjmgReBniT3d2rLU0Z72zQ64Gof66jCGQt0W\n" + "0sfwDUv0XsfXXW9p1EGBYwIkgW6UGiABnkZcIUW4dzP2URoRCN/VIypkRwIDAQAB\n" + "AoGAa3VIvSoTAoisscQ8YHcSBIoRjiihK71AsnAQvpHfuRFthxry4qVjqgs71i0h\n" + "M7lt0iL/xePSEL7rlFf+cvnAFL4/j1R04ImBjRzWGnaNE8I7nNGGzJo9rL5I1oi3\n" + "zN2yUucTSGm7qR0MCNVy26zNmCuS/FdBPsfdZ017OTsHtPECQQDlWXAJG6nHyw2o\n" + "2cLYHzlyrrYgnWJkgFSKzr7VFNlHxfQSWXJ4zuDwhqkm3d176bVm4eHhDDv6f413\n" + "iQGraKvTAkEA1zAzpxfI7LAqd3sObWYstQb03IXE7yddMgbhoMDCT3gXhNaHKfjT\n" + "Z/GIk49jh8kyitN2FeYXXi9TiwrXStfhPQJBAMNea6ymjvstwoYKcgsOli5WG7ku\n" + "uEkqdFoGAdObvfeA7gfPgE7e1AiwfVkpd+l9TVTFqFe/xzv8+fJQmEZ+lJcCQQDN\n" + "5I7nh7h1zzEy1Qk+345TP262OT/u26kuHqtv1j+VLgDC10jIfg443D+jgITo/Tdg\n" + "4WeRGHCva3TyCtNoBxq5AkA9KZpKof4ripad4oIuCJpR/ZhQATgQwR9f+FlAxgP0\n" + "ABmBPCoxy4uGMtSBMqiiGpImbDuivYkhlBl7D8u8vn26\n" + "-----END RSA PRIVATE KEY-----\n"; +std::string publick1 = + "-----BEGIN PUBLIC KEY-----\n" + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAyUwjK+m4EXyFKEha2NfQUY8e\n" + "IqurRujNMqI1z7B3bF/8Bdg0wffIYJUjiXo13hB6yOq+gD62BGAPRjmgReBniT3d\n" + "2rLU0Z72zQ64Gof66jCGQt0W0sfwDUv0XsfXXW9p1EGBYwIkgW6UGiABnkZcIUW4\n" + "dzP2URoRCN/VIypkRwIDAQAB\n" + "-----END PUBLIC KEY-----\n"; -std::vector> GenerateCommitters(size_t n, const RandSigDKG& dkg, const RegAuthPK& rvk) { - std::vector> banks; - std::map share_verification_keys_; - for (size_t i = 0; i < n; i++) { - share_verification_keys_[(uint16_t)i] = serialize(dkg.skShares[i].toPK()); +std::string privatek2 = + "-----BEGIN RSA PRIVATE KEY-----\n" + "MIICXAIBAAKBgQDMw+qeJXQ/ZxrqSqjRXoN2wFYNwhljh7RTBJuIzaqK2pDL+zaK\n" + "aREzgufG/7r2uk8njWW7EJbriLcmj1oJA913LPl4YWUEORKl7Cb6wLoPO/E5gAt1\n" + "JJ0dhDeCRU+E7vtNQ4pLyy2dYS2GHO1Tm88yuFegS3gkINPYgzNggGJ2cQIDAQAB\n" + "AoGAScyCjqTpFMDQTojB91OdBfukCClghSKvtwv+EnwtbwX/EcVkjtX3QR1485vP\n" + "goT7akHn3FfKTPFlMRyRUpZ2Bov1whQ1ztuboIonFQ7ohbDTLE3QzUv4L3e8cEbY\n" + "51MSe8tEUVRxKu53nD3asWxAi/CEyqWvRCzf4s3Q6Xw3h5ECQQD8mBT6ervLr1Qk\n" + "oKmaAuPTDyZDaSjipD0/d1p1vG8Wb8go14tq89Ts+UIWzH5aGlidxTK9j/luQqlR\n" + "YVVGNkC3AkEAz4a8jtg2++fvWT0PDz6OBfw/iHJQzSmynlzKQzpRac02UBCPo4an\n" + "o7wl9uEnucXuVpCSo0JdSf+x4r9dwmCKFwJBAPWlGNG2xicBbPzp2cZTBSheVUG9\n" + "ZOtz+bRc5/YTuJzDPI6rf4QVeH60sNbnLAGIGaHlAsFi4Jmf7nWcCIftfuUCQEbx\n" + "hJxAhetvyn7zRKatd9fL99wpWD4Ktyk0B2EcGqDUqnCMeM4qRjzPIRtYtT/oziWB\n" + "nt943HNjmeguC1tbrVkCQBMd+kbpcoFHKKrC577FM24maWRTfXJeu2/o6pxUFIUY\n" + "kzkDZ2k+FfvXWaY+N5q5bJCayor8W1QeruHzewrQmgY=\n" + "-----END RSA PRIVATE KEY-----\n"; +std::string publick2 = + "-----BEGIN PUBLIC KEY-----\n" + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMw+qeJXQ/ZxrqSqjRXoN2wFYN\n" + "whljh7RTBJuIzaqK2pDL+zaKaREzgufG/7r2uk8njWW7EJbriLcmj1oJA913LPl4\n" + "YWUEORKl7Cb6wLoPO/E5gAt1JJ0dhDeCRU+E7vtNQ4pLyy2dYS2GHO1Tm88yuFeg\n" + "S3gkINPYgzNggGJ2cQIDAQAB\n" + "-----END PUBLIC KEY-----\n"; + +std::string privatek3 = + "-----BEGIN RSA PRIVATE KEY-----\n" + "MIICXAIBAAKBgQCnmdqW7f/rg8CiUzLugxc0jPMqzphhtl40IqINAk3pasCQsA3T\n" + "eHSa1fcKocFMY9bfQRvqiKpnK74d0V0fujFUaPIMlKMLWXEVefwe1fOYrDXOoz7N\n" + "3Go7x9u/LXwCA6HehXtIavtTPQs1yHldb/bDocEhjfGvU3TLXkAuGWsnmQIDAQAB\n" + "AoGBAJ6fRHyYICB8f7Kh35BRTYMU64fWI+5GtX3OUWTSi36g5EOL/GnqlSF94+OS\n" + "F+n+i/ycGJmuYuhmQ/bgkaxXghsDYb7fsdMJ8DEqUJAKbxeOosn8fxwmJkNAJ07J\n" + "+oAg/xkJ+ukyYnPf0P3UTuTZl0EFEpwu/vnX09QJGtuXgmQhAkEA0c0Co9MdP62r\n" + "/ybXXeqgaol2YVGzFr/bMz3hhlviV9IOGPRZmeQ8v+f1/lSsqZ8wSP5P8dkBo4UB\n" + "NSLaHAUL/QJBAMyB72EyHZUEFy3o241myqamfVtN+Dzo6qdPn/PfF/BLjwsEApCO\n" + "oUObmDDo/yiSSb00XSnn23bGYH1VJJDNJs0CQE1aG+YQ+VC4FJkfVfpvfjOpePcK\n" + "q0/w7r2mzBbAm+QrMz1qIfsGVoue12itCXgElEXlVc5iZyNF75sKvYXlKnUCQHHC\n" + "tc5zelEyfVJkff0ieQhLBOCNdtErH50Chg+6wi5BWcje6i5PqRVasEZE1etTtQEy\n" + "58Av4b0ojPQrMLP76uECQEz0c1RPDwMvznwT3BJxl8t4tixPML0nyBMD8ttnZswG\n" + "K/1CYV1uMNbchmuVQb2Kd2JyE1gQF8s3ShsbteMc5og=\n" + "-----END RSA PRIVATE KEY-----\n"; + +std::string publick3 = + "-----BEGIN PUBLIC KEY-----\n" + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCnmdqW7f/rg8CiUzLugxc0jPMq\n" + "zphhtl40IqINAk3pasCQsA3TeHSa1fcKocFMY9bfQRvqiKpnK74d0V0fujFUaPIM\n" + "lKMLWXEVefwe1fOYrDXOoz7N3Go7x9u/LXwCA6HehXtIavtTPQs1yHldb/bDocEh\n" + "jfGvU3TLXkAuGWsnmQIDAQAB\n" + "-----END PUBLIC KEY-----\n"; + +std::vector pr_keys{privatek1, privatek2, privatek3}; +std::vector pkeys{publick1, publick2, publick3}; + +class test_utt_instance : public ::testing::Test { + public: + protected: + struct GpData { + libutt::CommKey cck; + libutt::CommKey rck; + }; + void setUp(bool ibe, bool register_clients) { + UTTParams::BaseLibsInitData base_libs_init_data; + UTTParams::initLibs(base_libs_init_data); + auto dkg = RandSigDKG(thresh, n, Params::NumMessages); + rsk = RegAuthSK::generateKeyAndShares(thresh, n); + GpData gp_data{dkg.getCK(), rsk.ck_reg}; + d = UTTParams::create((void*)(&gp_data)); + rsk.setIBEParams(d.getParams().ibe); + rvk = rsk.toPK(); + bvk = dkg.getPK(); + + std::map validation_keys; + for (size_t i = 0; i < n; i++) { + auto& sk = rsk.shares[i]; + validation_keys[(uint16_t)i] = serialize(sk.toPK()); + } + for (size_t i = 0; i < n; i++) { + registrators.push_back(std::make_shared( + (uint16_t)i, serialize(rsk.shares[i]), validation_keys, serialize(rsk.toPK()))); + } + + std::map share_verification_keys_; + for (size_t i = 0; i < n; i++) { + share_verification_keys_[(uint16_t)i] = serialize(dkg.skShares[i].toPK()); + } + for (size_t i = 0; i < n; i++) { + banks.push_back(std::make_shared((uint16_t)i, + serialize(dkg.skShares[i]), + serialize(dkg.getPK()), + share_verification_keys_, + serialize(rvk))); + } + + if (ibe) { + GenerateIbeClients(); + } else { + GenerateRsaClients(pr_keys); + } + + if (register_clients) { + for (auto& c : clients) { + registerClient(c); + } + } } - for (size_t i = 0; i < n; i++) { - banks.push_back(std::make_shared((uint16_t)i, - serialize(dkg.skShares[i]), - serialize(dkg.getPK()), - share_verification_keys_, - serialize(rvk))); + std::vector getSubset(uint32_t n, uint32_t size) { + std::srand((unsigned int)std::chrono::system_clock::now().time_since_epoch().count()); + std::map ret; + for (uint32_t i = 0; i < n; i++) ret[i] = i; + for (uint32_t i = 0; i < n - size; i++) { + uint32_t index = (uint32_t)(std::rand()) % (uint32_t)(ret.size()); + ret.erase(index); + } + std::vector rret; + for (const auto& [k, v] : ret) { + (void)k; + rret.push_back(v); + } + return rret; } - return banks; -} -std::vector GenerateClients(size_t c, const RandSigPK& bvk, const RegAuthPK& rvk, const RegAuthSK& rsk) { - std::vector clients; - std::string bpk = libutt::serialize(bvk); - std::string rpk = libutt::serialize(rvk); - - for (size_t i = 0; i < c; i++) { - std::string pid = "client_" + std::to_string(i); - auto sk = rsk.msk.deriveEncSK(rsk.p, pid); - auto mpk = rsk.toPK().mpk; - std::string csk = libutt::serialize(sk); - std::string cmpk = libutt::serialize(mpk); - clients.push_back(Client(pid, bpk, rpk, csk, cmpk)); + void GenerateRsaClients(const std::vector& pk) { + c = pr_keys.size(); + std::vector clients; + std::string bpk = libutt::serialize(bvk); + std::string rpk = libutt::serialize(rvk); + + for (size_t i = 0; i < c; i++) { + std::string pid = "client_" + std::to_string(i); + clients.push_back(Client(pid, bpk, rpk, pk.at(i))); + } } - return clients; -} -std::vector GenerateClients(size_t c, - const RandSigPK& bvk, - const RegAuthPK& rvk, - const std::vector& pk) { - std::vector clients; - std::string bpk = libutt::serialize(bvk); - std::string rpk = libutt::serialize(rvk); + void GenerateIbeClients() { + std::string bpk = libutt::serialize(bvk); + std::string rpk = libutt::serialize(rvk); - for (size_t i = 0; i < c; i++) { - std::string pid = "client_" + std::to_string(i); - clients.push_back(Client(pid, bpk, rpk, pk.at(i))); - } - return clients; -} -void registerClient(const UTTParams& d, - Client& c, - std::vector>& registrators, - size_t thresh) { - size_t n = registrators.size(); - std::vector> shares; - std::vector shares_signers; - auto prf = c.getPRFSecretKey(); - Fr fr_s2 = Fr::random_element(); - types::CurvePoint s2; - auto rcm1 = c.generateInputRCM(); - for (auto& r : registrators) { - auto [ret_s2, sig] = r->signRCM(c.getPidHash(), fr_s2.to_words(), rcm1); - shares.push_back(sig); - shares_signers.push_back(r->getId()); - if (s2.empty()) { - s2 = ret_s2; + for (size_t i = 0; i < c; i++) { + std::string pid = "client_" + std::to_string(i); + auto sk = rsk.msk.deriveEncSK(rsk.p, pid); + auto mpk = rsk.toPK().mpk; + std::string csk = libutt::serialize(sk); + std::string cmpk = libutt::serialize(mpk); + clients.push_back(Client(pid, bpk, rpk, csk, cmpk)); } } - for (auto& r : registrators) { - for (size_t i = 0; i < shares.size(); i++) { - auto& sig = shares[i]; - auto& signer = shares_signers[i]; - assertTrue(r->validatePartialRCMSig(signer, c.getPidHash(), s2, rcm1, sig)); + + void registerClient(Client& c) { + size_t n = registrators.size(); + std::vector> shares; + std::vector shares_signers; + auto prf = c.getPRFSecretKey(); + Fr fr_s2 = Fr::random_element(); + types::CurvePoint s2; + auto rcm1 = c.generateInputRCM(); + for (auto& r : registrators) { + auto [ret_s2, sig] = r->signRCM(c.getPidHash(), fr_s2.to_words(), rcm1); + shares.push_back(sig); + shares_signers.push_back(r->getId()); + if (s2.empty()) { + s2 = ret_s2; + } } + for (auto& r : registrators) { + for (size_t i = 0; i < shares.size(); i++) { + auto& sig = shares[i]; + auto& signer = shares_signers[i]; + assertTrue(r->validatePartialRCMSig(signer, c.getPidHash(), s2, rcm1, sig)); + } + } + auto sids = getSubset((uint32_t)n, (uint32_t)thresh); + std::map> rsigs; + for (auto i : sids) { + rsigs[i] = shares[i]; + } + auto sig = Utils::aggregateSigShares((uint32_t)n, rsigs); + sig = + Utils::unblindSignature(d, Commitment::Type::REGISTRATION, {Fr::zero().to_words(), Fr::zero().to_words()}, sig); + c.setRCMSig(d, s2, sig); } - auto sids = getSubset((uint32_t)n, (uint32_t)thresh); - std::map> rsigs; - for (auto i : sids) { - rsigs[i] = shares[i]; - } - auto sig = Utils::aggregateSigShares((uint32_t)n, rsigs); - sig = Utils::unblindSignature(d, Commitment::Type::REGISTRATION, {Fr::zero().to_words(), Fr::zero().to_words()}, sig); - c.setRCMSig(d, s2, sig); -} + + public: + size_t thresh = 2; + size_t n = 4; + size_t c = 10; + libutt::RandSigPK bvk; + libutt::RegAuthPK rvk; + libutt::RegAuthSK rsk; + libutt::api::UTTParams d; + + std::vector> banks; + std::vector> registrators; + std::vector clients; +}; } // namespace libutt::api::testing \ No newline at end of file From 9044b5f9b017c5b47bcdc4141da43ba1ed02cd56 Mon Sep 17 00:00:00 2001 From: Yehonatan Buchnik Date: Thu, 3 Nov 2022 12:40:42 +0200 Subject: [PATCH 36/44] Fix clang-tidy checks --- utt-replica/signature-processor/tests/sigProcessorTests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utt-replica/signature-processor/tests/sigProcessorTests.cpp b/utt-replica/signature-processor/tests/sigProcessorTests.cpp index d0974ac63f..c51cc065b6 100644 --- a/utt-replica/signature-processor/tests/sigProcessorTests.cpp +++ b/utt-replica/signature-processor/tests/sigProcessorTests.cpp @@ -310,7 +310,7 @@ class test_utt_instance : public ::testing::Test { std::memcpy(cs_buffer.data(), msg->requestBuf() + sizeof(uint64_t), cs_buffer.size()); utt::SigProcessor::CompleteSignatureMsg cs_msg(cs_buffer); ASSERT_TRUE(cs_msg.validate()); - auto fsig = cs_msg.getFullSig(); + auto& fsig = cs_msg.getFullSig(); { std::unique_lock lk(sigs_lock); for (size_t j = 0; j < n; j++) { From d1f1a78b81643f8a32d24da82da48d8a8a0851b9 Mon Sep 17 00:00:00 2001 From: Yehonatan Buchnik Date: Thu, 3 Nov 2022 14:00:05 +0200 Subject: [PATCH 37/44] Add getter for partial signatures map from the complete signature message --- utt-replica/signature-processor/include/sigProcessor.hpp | 1 + utt-replica/signature-processor/src/sigProcessor.cpp | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/utt-replica/signature-processor/include/sigProcessor.hpp b/utt-replica/signature-processor/include/sigProcessor.hpp index 4f35e612ad..d00eb57126 100644 --- a/utt-replica/signature-processor/include/sigProcessor.hpp +++ b/utt-replica/signature-processor/include/sigProcessor.hpp @@ -46,6 +46,7 @@ class SigProcessor { explicit CompleteSignatureMsg() = default; CompleteSignatureMsg(const std::vector& buffer); bool validate() const; + const std::map>& getPartialSigs() const; const std::vector& getFullSig() const; /* The serialized output is in the following format: diff --git a/utt-replica/signature-processor/src/sigProcessor.cpp b/utt-replica/signature-processor/src/sigProcessor.cpp index d3acf0e255..d91ad623c3 100644 --- a/utt-replica/signature-processor/src/sigProcessor.cpp +++ b/utt-replica/signature-processor/src/sigProcessor.cpp @@ -238,7 +238,9 @@ bool SigProcessor::CompleteSignatureMsg::validate() const { } SigProcessor::CompleteSignatureMsg::CompleteSignatureMsg(const std::vector& buffer) { deserialize(buffer); } const std::vector& SigProcessor::CompleteSignatureMsg::getFullSig() const { return full_sig; } - +const std::map>& SigProcessor::CompleteSignatureMsg::getPartialSigs() const { + return sigs; +} std::vector SigProcessor::CompleteSignatureMsg::serialize() const { uint64_t loc{0}; uint64_t fsig_size = full_sig.size(); From df5d5be9e3435d5ac08b724be019a9632d7d8e17 Mon Sep 17 00:00:00 2001 From: Yehonatan Buchnik Date: Thu, 3 Nov 2022 14:57:15 +0200 Subject: [PATCH 38/44] complete rebase --- .../signature-processor/src/sigProcessor.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/utt-replica/signature-processor/src/sigProcessor.cpp b/utt-replica/signature-processor/src/sigProcessor.cpp index d91ad623c3..8ef23cc90a 100644 --- a/utt-replica/signature-processor/src/sigProcessor.cpp +++ b/utt-replica/signature-processor/src/sigProcessor.cpp @@ -191,14 +191,14 @@ void SigProcessor::publishCompleteSignature(const SigJobEntry& job_entry) { auto requestSeqNum = std::chrono::duration_cast(getMonotonicTime().time_since_epoch()).count(); std::vector appClientReq = job_entry.client_app_data_generator_cb_(sig_id, msg.serialize()); - auto crm = new bftEngine::impl::ClientRequestMsg(repId_, - bftEngine::MsgFlag::INTERNAL_FLAG, - requestSeqNum, - (uint32_t)appClientReq.size(), - (const char*)appClientReq.data(), - 60000, - "new-utt-sig-" + std::to_string(sig_id)); - msgs_communicator_->getIncomingMsgsStorage()->pushExternalMsg(std::move(cmsg)); + auto crm = std::make_unique(repId_, + bftEngine::MsgFlag::INTERNAL_FLAG, + requestSeqNum, + (uint32_t)appClientReq.size(), + (const char*)appClientReq.data(), + 60000, + "new-utt-sig-" + std::to_string(sig_id)); + msgs_communicator_->getIncomingMsgsStorage()->pushExternalMsg(std::move(crm)); } // Called by the validating thread void SigProcessor::onReceivingNewValidFullSig(uint64_t sig_id) { From eaaec88c99ab64cab1f7458396e29f088143771c Mon Sep 17 00:00:00 2001 From: Evgeniy Dzhurov Date: Tue, 8 Nov 2022 18:04:49 +0200 Subject: [PATCH 39/44] Add debug logs to user in utt-client-api --- utt/utt-client-api/src/User.cpp | 79 ++++++++++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 6 deletions(-) diff --git a/utt/utt-client-api/src/User.cpp b/utt/utt-client-api/src/User.cpp index 88f713d8fe..faaafb2d44 100644 --- a/utt/utt-client-api/src/User.cpp +++ b/utt/utt-client-api/src/User.cpp @@ -38,6 +38,35 @@ using namespace libutt; +#define logdbg_user logdbg << ((pImpl_->client_) ? pImpl_->client_->getPid() : "!!!uninitialized user!!!") << ' ' + +namespace { +std::string dbgPrintCoins(const std::vector& coins) { + if (coins.empty()) return "[empty]"; + std::stringstream ss; + ss << '['; + for (const auto& coin : coins) { + ss << "type:" << (coin.getType() == libutt::api::Coin::Type::Budget ? "budget" : "normal"); + ss << "|val:" << coin.getVal(); + ss << "|exp:" << coin.getExpDate(); + ss << "|null:" << coin.getNullifier() << ", "; + } + ss << ']'; + return ss.str(); +} + +std::string dbgPrintRecipients(const std::vector>& recips) { + if (recips.empty()) return "[empty]"; + std::stringstream ss; + ss << '['; + for (const auto& recip : recips) { + ss << std::get<0>(recip) << ": " << std::get<1>(recip) << ", "; + } + ss << ']'; + return ss.str(); +} +} // namespace + namespace utt::client { struct User::Impl { @@ -76,6 +105,8 @@ struct User::Impl { }; utt::Transaction User::Impl::createTx_Burn(const libutt::api::Coin& coin) { + logdbg << client_->getPid() << " creates burn tx with input coins: " << dbgPrintCoins({coin}) << endl; + utt::Transaction tx; tx.type_ = utt::Transaction::Type::Burn; auto burn = libutt::api::operations::Burn(params_, *client_, coin); @@ -88,6 +119,9 @@ utt::Transaction User::Impl::createTx_Self1to2(const libutt::api::Coin& coin, ui recip.emplace_back(client_->getPid(), amount); recip.emplace_back(client_->getPid(), coin.getVal() - amount); + logdbg << client_->getPid() << " creates self-split tx with input coins: " << dbgPrintCoins({coin}) + << " and recipients: " << dbgPrintRecipients(recip) << endl; + auto uttTx = libutt::api::operations::Transaction(params_, *client_, {coin}, std::nullopt, recip, *selfTxEncryptor_); utt::Transaction tx; @@ -101,6 +135,9 @@ utt::Transaction User::Impl::createTx_Self2to1(const std::vector> recip; recip.emplace_back(client_->getPid(), coins[0].getVal() + coins[1].getVal()); + logdbg << client_->getPid() << " creates self-merge with input coins: " << dbgPrintCoins(coins) + << " and recipients: " << dbgPrintRecipients(recip) << endl; + auto uttTx = libutt::api::operations::Transaction(params_, *client_, coins, std::nullopt, recip, *selfTxEncryptor_); utt::Transaction tx; @@ -115,6 +152,9 @@ utt::Transaction User::Impl::createTx_Self2to2(const std::vectorgetPid(), amount); recip.emplace_back(client_->getPid(), (coins[0].getVal() + coins[1].getVal()) - amount); + logdbg << client_->getPid() << " creates 2-to-2 self-split with input coins: " << dbgPrintCoins(coins) + << " and recipients: " << dbgPrintRecipients(recip) << endl; + auto uttTx = libutt::api::operations::Transaction(params_, *client_, coins, std::nullopt, recip, *selfTxEncryptor_); utt::Transaction tx; @@ -130,6 +170,9 @@ utt::Transaction User::Impl::createTx_1to1(const libutt::api::Coin& coin, std::vector> recip; recip.emplace_back(userId, coin.getVal()); + logdbg << client_->getPid() << " creates 1-to-1 transfer with input coins: " << dbgPrintCoins({coin}) + << " and recipients: " << dbgPrintRecipients(recip) << endl; + auto encryptor = createRsaEncryptorForTransferToOther(userId, pk); auto uttTx = libutt::api::operations::Transaction(params_, *client_, {coin}, budgetCoin_, recip, encryptor); @@ -148,6 +191,9 @@ utt::Transaction User::Impl::createTx_1to2(const libutt::api::Coin& coin, recip.emplace_back(userId, amount); recip.emplace_back(client_->getPid(), coin.getVal() - amount); + logdbg << client_->getPid() << " creates 1-to-2 transfer with input coins: " << dbgPrintCoins({coin}) + << " and recipients: " << dbgPrintRecipients(recip) << endl; + auto encryptor = createRsaEncryptorForTransferToOther(userId, pk); auto uttTx = libutt::api::operations::Transaction(params_, *client_, {coin}, budgetCoin_, recip, encryptor); @@ -164,6 +210,9 @@ utt::Transaction User::Impl::createTx_2to1(const std::vector& std::vector> recip; recip.emplace_back(userId, coins[0].getVal() + coins[1].getVal()); + logdbg << client_->getPid() << "creates 2-to-1 transfer with input coins: " << dbgPrintCoins(coins) + << " and recipients: " << dbgPrintRecipients(recip) << endl; + auto encryptor = createRsaEncryptorForTransferToOther(userId, pk); auto uttTx = libutt::api::operations::Transaction(params_, *client_, coins, budgetCoin_, recip, encryptor); @@ -182,6 +231,9 @@ utt::Transaction User::Impl::createTx_2to2(const std::vector& recip.emplace_back(userId, amount); recip.emplace_back(client_->getPid(), (coins[0].getVal() + coins[1].getVal()) - amount); + logdbg << client_->getPid() << " creates 2-to-2 transfer with input coins: " << dbgPrintCoins(coins) + << " and recipients: " << dbgPrintRecipients(recip) << endl; + auto encryptor = createRsaEncryptorForTransferToOther(userId, pk); auto uttTx = libutt::api::operations::Transaction(params_, *client_, coins, budgetCoin_, recip, encryptor); @@ -267,6 +319,8 @@ void User::updateRegistration(const std::string& pk, const RegistrationSig& rs, void User::updatePrivacyBudget(const PrivacyBudget& budget, const PrivacyBudgetSig& sig) { if (!pImpl_->client_) throw std::runtime_error("User not initialized!"); + logdbg_user << "updating privacy budget" << endl; + auto claimedCoins = pImpl_->client_->claimCoins(libutt::api::deserialize(budget), pImpl_->params_, std::vector{sig}); @@ -277,6 +331,8 @@ void User::updatePrivacyBudget(const PrivacyBudget& budget, const PrivacyBudgetS // [TODO-UTT] Requires atomic, durable write through IUserStorage pImpl_->budgetCoin_ = claimedCoins[0]; + + logdbg_user << "claimed budget token " << dbgPrintCoins({*pImpl_->budgetCoin_}) << endl; } uint64_t User::getBalance() const { @@ -305,6 +361,8 @@ void User::updateTransferTx(uint64_t txNum, const Transaction& tx, const TxOutpu auto uttTx = libutt::api::deserialize(tx.data_); + logdbg_user << "executing transfer tx: " << txNum << endl; + // [TODO-UTT] Requires atomic, durable write batch through IUserStorage pImpl_->lastExecutedTxNum_ = txNum; @@ -317,6 +375,7 @@ void User::updateTransferTx(uint64_t txNum, const Transaction& tx, const TxOutpu return coin.getNullifier() == null; }); if (it != pImpl_->coins_.end()) { + logdbg_user << "slashing spent coin " << dbgPrintCoins({*it}) << endl; pImpl_->coins_.erase(it); } } @@ -326,11 +385,13 @@ void User::updateTransferTx(uint64_t txNum, const Transaction& tx, const TxOutpu for (auto& coin : claimedCoins) { if (coin.getType() == libutt::api::Coin::Type::Normal) { if (!pImpl_->client_->validate(coin)) throw std::runtime_error("Invalid normal coin in transfer!"); + logdbg_user << "claimed normal coin: " << dbgPrintCoins({coin}) << endl; pImpl_->coins_.emplace_back(std::move(coin)); } else if (coin.getType() == libutt::api::Coin::Type::Budget) { // Replace budget coin if (!pImpl_->client_->validate(coin)) throw std::runtime_error("Invalid budget coin in transfer!"); if (coin.getVal() > 0) { + logdbg_user << "claimed budget coin: " << dbgPrintCoins({coin}) << endl; pImpl_->budgetCoin_ = std::move(coin); } else { pImpl_->budgetCoin_ = std::nullopt; @@ -346,8 +407,10 @@ void User::updateMintTx(uint64_t txNum, const Transaction& tx, const TxOutputSig auto mint = libutt::api::deserialize(tx.data_); + logdbg_user << "executing mint tx: " << txNum << endl; + if (mint.getRecipentID() != pImpl_->client_->getPid()) { - logdbg << "Mint transaction is for different user - ignoring." << endl; + logdbg_user << "ignores mint transaction for different user: " << mint.getRecipentID() << endl; } else { auto claimedCoins = pImpl_->client_->claimCoins(mint, pImpl_->params_, std::vector{sig}); @@ -371,8 +434,10 @@ void User::updateBurnTx(uint64_t txNum, const Transaction& tx) { auto burn = libutt::api::deserialize(tx.data_); + logdbg_user << "executing burn tx: " << txNum << endl; + if (burn.getOwnerPid() != pImpl_->client_->getPid()) { - logdbg << "Burn transaction is for different user - ignoring." << endl; + logdbg_user << "ignores burn tx for different user: " << burn.getOwnerPid() << endl; } else { auto nullifier = burn.getNullifier(); if (nullifier.empty()) throw std::runtime_error("Burn tx has empty nullifier!"); @@ -391,6 +456,9 @@ void User::updateBurnTx(uint64_t txNum, const Transaction& tx) { void User::updateNoOp(uint64_t txNum) { if (!pImpl_->client_) throw std::runtime_error("User not initialized!"); if (txNum != pImpl_->lastExecutedTxNum_ + 1) throw std::runtime_error("Noop tx number is not consecutive!"); + + logdbg_user << "executing noop tx: " << txNum << endl; + pImpl_->lastExecutedTxNum_ = txNum; } @@ -398,7 +466,8 @@ utt::Transaction User::mint(uint64_t amount) const { std::stringstream ss; ss << Fr::random_element(); auto randomHash = ss.str(); - logdbg << "Creating a mint tx with hash: " << randomHash << endl; + + logdbg_user << "creating a mint tx with random hash: " << randomHash << endl; auto mint = libutt::api::operations::Mint(randomHash, amount, pImpl_->client_->getPid()); @@ -413,9 +482,7 @@ void User::createPrivacyBudgetLocal(const utt::Configuration& config, uint64_t a if (config.empty()) throw std::runtime_error("Privacy app config cannot be empty!"); if (amount == 0) throw std::runtime_error("Positive privacy budget amount required!"); - const auto& pid = pImpl_->client_->getPid(); - - logdbg << "Creating " << amount << " privacy budget locally for '" << pid << "'\n"; + logdbg_user << "creates " << amount << " privacy budget locally" << endl; auto uttConfig = libutt::api::deserialize(config); From 304afaca4284fabcb1473885c90a4b42c6baf91f Mon Sep 17 00:00:00 2001 From: Yehonatan Buchnik Date: Wed, 9 Nov 2022 09:56:37 +0200 Subject: [PATCH 40/44] Allow transaction without budget in libutt --- .../tests/sigProcessorTests.cpp | 1 + utt/include/UTTParams.hpp | 3 + utt/include/transaction.hpp | 3 +- utt/libutt/bench/BenchTxn.cpp | 2 +- utt/libutt/include/utt/Tx.h | 15 +- utt/libutt/src/Tx.cpp | 99 ++++-- utt/libutt/src/api/UTTParams.cpp | 4 +- utt/libutt/src/api/coinsSigner.cpp | 2 +- utt/libutt/src/api/config.cpp | 1 + utt/libutt/src/api/transaction.cpp | 6 +- utt/libutt/test/TestTxn.cpp | 2 +- utt/tests/TestUttNewApi.cpp | 301 ++++++++++++------ utt/tests/testUtils.hpp | 5 +- 13 files changed, 301 insertions(+), 143 deletions(-) diff --git a/utt-replica/signature-processor/tests/sigProcessorTests.cpp b/utt-replica/signature-processor/tests/sigProcessorTests.cpp index c51cc065b6..0eba5aa626 100644 --- a/utt-replica/signature-processor/tests/sigProcessorTests.cpp +++ b/utt-replica/signature-processor/tests/sigProcessorTests.cpp @@ -130,6 +130,7 @@ class TestReceiver : public bft::communication::IReceiver { struct GpData { libutt::CommKey cck; libutt::CommKey rck; + bool budget_policy = true; }; std::tuple init(size_t n, size_t thresh) { diff --git a/utt/include/UTTParams.hpp b/utt/include/UTTParams.hpp index c074739ae0..b1ba99387e 100644 --- a/utt/include/UTTParams.hpp +++ b/utt/include/UTTParams.hpp @@ -56,10 +56,13 @@ class UTTParams { UTTParams(UTTParams&&) = default; UTTParams& operator=(UTTParams&&) = default; + bool getBudgetPolicy() const; + private: friend std::ostream& ::operator<<(std::ostream& out, const libutt::api::UTTParams& params); friend std::istream& ::operator>>(std::istream& in, libutt::api::UTTParams& params); friend bool ::operator==(const libutt::api::UTTParams& params1, const libutt::api::UTTParams& params2); std::unique_ptr params; + bool budget_policy = true; }; } // namespace libutt::api diff --git a/utt/include/transaction.hpp b/utt/include/transaction.hpp index 83654518cc..10640f79f1 100644 --- a/utt/include/transaction.hpp +++ b/utt/include/transaction.hpp @@ -57,7 +57,8 @@ class Transaction { const std::vector& input_coins, const std::optional& budget_coin, const std::vector>& recipients, - const IEncryptor& encryptor); + const IEncryptor& encryptor, + bool budget_policy = true); Transaction(); Transaction(const Transaction&); Transaction(Transaction&&) = default; diff --git a/utt/libutt/bench/BenchTxn.cpp b/utt/libutt/bench/BenchTxn.cpp index 4bad5237c7..72bd5b8e7e 100644 --- a/utt/libutt/bench/BenchTxn.cpp +++ b/utt/libutt/bench/BenchTxn.cpp @@ -113,7 +113,7 @@ class BenchTxn { // Step 2: Measure TXN validation // tqv.startLap(); - if (!tx.quickPayValidate(p, bpk, rpk)) { + if (!tx.quickPayValidate(p, bpk, rpk, true)) { testAssertFail("TXN should have QuickPay-verified"); } tqv.endLap(); diff --git a/utt/libutt/include/utt/Tx.h b/utt/libutt/include/utt/Tx.h index 8358aa2650..c6e09ee7d8 100644 --- a/utt/libutt/include/utt/Tx.h +++ b/utt/libutt/include/utt/Tx.h @@ -31,7 +31,8 @@ class Tx { public: bool isSplitOwnCoins; // true when splitting your own coins; in this case, no budget coins are given as input, to // save TXN creation & validation time - + bool budgetPolicy; + bool is_budgeted = false; Comm rcm; // commitment to sending user's registration RandSig regsig; // signature on the registration commitment 'rcm' @@ -54,8 +55,9 @@ class Tx { const std::vector& c, std::optional b, // optional budget coin const std::vector>& recip, - const RandSigPK& bpk, // only used for debugging - const RegAuthPK& rpk); // only to encrypt for the recipients + const RandSigPK& bpk, // only used for debugging + const RegAuthPK& rpk, + bool budget_policy = true); // only to encrypt for the recipients Tx(const Params& p, const Fr pidHash, @@ -68,7 +70,8 @@ class Tx { const std::vector>& recip, std::optional bpk, // only used for debugging const RandSigPK& rpk, - const IEncryptor& encryptor); // only to encrypt for the recipients + const IEncryptor& encryptor, + bool budget_policy = true); // only to encrypt for the recipients public: size_t getSize() const { @@ -92,9 +95,9 @@ class Tx { */ G1 deriveRandSigBase(size_t txoIdx) const; - bool quickPayValidate(const Params& p, const RandSigPK& bpk, const RegAuthPK& rpk) const; + bool quickPayValidate(const Params& p, const RandSigPK& bpk, const RegAuthPK& rpk, bool budget_policy) const; - bool validate(const Params& p, const RandSigPK& bpk, const RegAuthPK& rpk) const; + bool validate(const Params& p, const RandSigPK& bpk, const RegAuthPK& rpk, bool budget_policy = true) const; /** * Returns the nullifiers of all coins spent by this TXN, including the budget coin's. diff --git a/utt/libutt/src/Tx.cpp b/utt/libutt/src/Tx.cpp index 3e0c9e4c12..42cbad03fe 100644 --- a/utt/libutt/src/Tx.cpp +++ b/utt/libutt/src/Tx.cpp @@ -18,6 +18,8 @@ #include // WARNING: Include this last (see header file for details; thanks, C++) std::ostream& operator<<(std::ostream& out, const libutt::Tx& tx) { + out << tx.budgetPolicy << endl; + out << tx.is_budgeted << endl; out << tx.isSplitOwnCoins << endl; out << tx.rcm; out << tx.regsig; @@ -31,6 +33,10 @@ std::ostream& operator<<(std::ostream& out, const libutt::Tx& tx) { } std::istream& operator>>(std::istream& in, libutt::Tx& tx) { + in >> tx.budgetPolicy; + libff::consume_OUTPUT_NEWLINE(in); + in >> tx.is_budgeted; + libff::consume_OUTPUT_NEWLINE(in); in >> tx.isSplitOwnCoins; libff::consume_OUTPUT_NEWLINE(in); in >> tx.rcm; @@ -40,7 +46,8 @@ std::istream& operator>>(std::istream& in, libutt::Tx& tx) { libutt::deserializeVector(in, tx.outs); in >> tx.budget_pi; - + // If the budget policy is true, so the only reason not to have budget, is if the transaction is a split coins + // transaction. return in; } @@ -52,9 +59,21 @@ Tx::Tx(const Params& p, std::optional b, // optional budget coin const std::vector>& recip, const RandSigPK& bpk, // only used for debugging - const RegAuthPK& rpk) // only to encrypt for the recipients - : Tx{p, ask.pid_hash, ask.pid, ask.rcm, ask.rs, ask.s, c, std::move(b), recip, bpk, rpk.vk, IBEEncryptor(rpk.mpk)} { -} + const RegAuthPK& rpk, + bool budget_policy) // only to encrypt for the recipients + : Tx{p, + ask.pid_hash, + ask.pid, + ask.rcm, + ask.rs, + ask.s, + c, + std::move(b), + recip, + bpk, + rpk.vk, + IBEEncryptor(rpk.mpk), + budget_policy} {} Tx::Tx(const Params& p, const Fr pidHash, @@ -67,7 +86,9 @@ Tx::Tx(const Params& p, const std::vector>& recip, std::optional bpk, // only used for debugging const RandSigPK& rpk, - const IEncryptor& encryptor) { + const IEncryptor& encryptor, + bool budget_policy) { + budgetPolicy = budget_policy; #ifndef NDEBUG (void)bpk; #endif @@ -78,9 +99,12 @@ Tx::Tx(const Params& p, Fr pid_hash_sender = pidHash; // the (single) sender's PID hash - isSplitOwnCoins = true; // true when this TXN simply splits the sender's coins - bool isBudgeted = b.has_value(); // true when this TXN is budgeted - + isSplitOwnCoins = true; // true when this TXN simply splits the sender's coins + is_budgeted = b.has_value(); // true when this TXN is budgeted + if (!budgetPolicy && is_budgeted) { + logerror << "budget policy is false, but a budget coin was given" << endl; + throw std::runtime_error("budget policy is false, but a budget coin was given"); + } size_t totalIn = Coin::totalValue(coins); // total in value size_t totalOut = 0; // total out value size_t paidOut = 0; // total amount paid out to someone else @@ -126,7 +150,7 @@ Tx::Tx(const Params& p, throw std::runtime_error("Input and output normal coins must have same total"); } - if (isBudgeted) { + if (is_budgeted) { // if splitting your own coins, you don't need budgets if (isSplitOwnCoins) { throw std::runtime_error("You need not provide a budget coin when splitting your own coins"); @@ -189,7 +213,7 @@ Tx::Tx(const Params& p, // logtrace << "z_sum: " << z_sum << endl; // create input for budget coin too, if any - if (isBudgeted) { + if (is_budgeted) { ins.emplace_back(*b); } @@ -234,7 +258,7 @@ Tx::Tx(const Params& p, * - compute range proof for vcm_1 * - compute ctxt */ - bool icmPok = !(isBudgeted && pid_recip == pid); + bool icmPok = !(is_budgeted && pid_recip == pid); bool hasRangeProof = true; outs.emplace_back(p.getValCK(), p.getRangeProofParams(), @@ -256,7 +280,7 @@ Tx::Tx(const Params& p, /** * Step 4: Take care of budget proof, if budgeted TXN. */ - if (isBudgeted) { + if (is_budgeted) { // For the budget preservation, we have to check that: // budget_in - \sum_{j \in output coins for another} val_out(j) = budget_out Fr z_budget_recip = b->z; // the randomness of the input budget coin's 'vcm_1' @@ -315,7 +339,7 @@ Tx::Tx(const Params& p, ins[i].pi = SplitProof(p, pidHash, prf, coins.at(i), a, rcm, outsHash); } - if (isBudgeted) { + if (is_budgeted) { // logdbg << "Split proof for budget coin" << endl; ins.back().pi = SplitProof(p, pidHash, prf, *b, a, rcm, outsHash); } @@ -324,7 +348,7 @@ Tx::Tx(const Params& p, assertEqual(outs.size(), recip.size() + (b.has_value() ? 1 : 0)); } -bool Tx::quickPayValidate(const Params& p, const RandSigPK& bpk, const RegAuthPK& rpk) const { +bool Tx::quickPayValidate(const Params& p, const RandSigPK& bpk, const RegAuthPK& rpk, bool budget_policy) const { /** * TODO(Perf): Do we even need to check coinsig? * TODO(Perf): Do we even need to check regsig? @@ -334,7 +358,6 @@ bool Tx::quickPayValidate(const Params& p, const RandSigPK& bpk, const RegAuthPK /** * Step 1: Sanity check */ - bool isBudgeted = !isSplitOwnCoins; bool foundBudgetOutCoin = false, foundBudgetInCoin = false; for (auto& txout : outs) { @@ -347,17 +370,36 @@ bool Tx::quickPayValidate(const Params& p, const RandSigPK& bpk, const RegAuthPK // TODO: check coin expiration here } - if (isBudgeted != foundBudgetOutCoin) { + // whether a budget policy is true or not, a split coins transaction should not have an input budget coin + if (isSplitOwnCoins && foundBudgetInCoin) { + logerror << "spliting your own coins doesn't require a budget" << endl; + return false; + } + + if (is_budgeted != foundBudgetOutCoin) { logerror << "TX claimed to be budgeted but did not have an output budget coin" << endl; return false; } - if (isBudgeted != foundBudgetOutCoin) { + if (is_budgeted != foundBudgetOutCoin) { logerror << "TX claimed to be budgeted but did not have an output budget coin" << endl; return false; } - assertTrue(!isBudgeted || budget_pi.has_value()); + if (budget_policy) { + if (!budgetPolicy) { + logerror << "budget policy is enforced but the transaction's budget policy is false" << endl; + return false; + } + } + + if (budget_policy && !isSplitOwnCoins && !is_budgeted) { + logerror << "budget policy is enforced and budget is needed, but the transaction doesn't have a budget coin" + << endl; + return false; + } + + assertTrue(!is_budgeted || budget_pi.has_value()); /** * Step 2: Check registration authority's sig on registration commitment @@ -374,7 +416,7 @@ bool Tx::quickPayValidate(const Params& p, const RandSigPK& bpk, const RegAuthPK for (size_t i = 0; i < ins.size(); i++) { // check this is a normal coin (except for the last one if budgeted, which is allowed not to be normal) if (ins[i].coin_type != Coin::NormalType()) { - if (!isBudgeted || i != ins.size() - 1) { + if (!is_budgeted || i != ins.size() - 1) { logerror << "Expected input #" << i << " to be a normal coin" << endl; return false; } @@ -401,17 +443,14 @@ bool Tx::quickPayValidate(const Params& p, const RandSigPK& bpk, const RegAuthPK return true; } -bool Tx::validate(const Params& p, const RandSigPK& bpk, const RegAuthPK& rpk) const { - if (!quickPayValidate(p, bpk, rpk)) return false; - - bool isBudgeted = !isSplitOwnCoins; - +bool Tx::validate(const Params& p, const RandSigPK& bpk, const RegAuthPK& rpk, bool budget_policy) const { + if (!quickPayValidate(p, bpk, rpk, budget_policy)) return false; /** * Step 4: Check value preservation of normal coins. * - check the sum of in and out 'normal' coin value commitments is the same */ - size_t numNormalIn = ins.size() - (isBudgeted ? 1 : 0); - size_t numNormalOut = outs.size() - (isBudgeted ? 1 : 0); + size_t numNormalIn = ins.size() - (is_budgeted ? 1 : 0); + size_t numNormalOut = outs.size() - (is_budgeted ? 1 : 0); G1 incomms = G1::zero(), outcomms = G1::zero(); for (size_t i = 0; i < numNormalIn; i++) { @@ -434,7 +473,7 @@ bool Tx::validate(const Params& p, const RandSigPK& bpk, const RegAuthPK& rpk) c for (size_t j = 0; j < outs.size(); j++) { // check this is a normal coin (except for the last one if budget, which is allowed not to be normal) if (outs[j].coin_type != Coin::NormalType()) { - if (!isBudgeted || j != outs.size() - 1) { + if (!is_budgeted || j != outs.size() - 1) { logerror << "Expected output #" << j << " to be a normal coin" << endl; return false; } @@ -447,7 +486,7 @@ bool Tx::validate(const Params& p, const RandSigPK& bpk, const RegAuthPK& rpk) c // check recipient's identity commitment is indeed well-formed: e.g. it is not g_1^pid g_2^v g^t for v != 0 logtrace << "Checking output #" << j << "'s ZKPoK" << endl; - if (isBudgeted && budget_pi->forMeTxos.count(j) == 1) { + if (is_budgeted && budget_pi->forMeTxos.count(j) == 1) { // for budgeted TXNs, the budget proof already proves knowledge of sender-owned outputs, so this is unnecessary assertFalse(outs[j].icm_pok.has_value()); } else { @@ -465,7 +504,7 @@ bool Tx::validate(const Params& p, const RandSigPK& bpk, const RegAuthPK& rpk) c } // check range proofs on out comms (in comms are good by invariant) - // if(isBudgeted && j == outs.size() - 1) { + // if(is_budgeted && j == outs.size() - 1) { // assertFalse(outs[j].range_pi.has_value()); //} else { assertTrue(outs[j].range_pi.has_value()); @@ -481,7 +520,7 @@ bool Tx::validate(const Params& p, const RandSigPK& bpk, const RegAuthPK& rpk) c * - check pid of input budget coin matches pid of output budget coin and of normal change coins * - value preservation of budget */ - if (isBudgeted) { + if (is_budgeted) { const auto& bout = outs.back(); if (bout.coin_type != Coin::BudgetType()) { logerror << "Expected last output coin to be a budget coin" << endl; diff --git a/utt/libutt/src/api/UTTParams.cpp b/utt/libutt/src/api/UTTParams.cpp index bb39048e07..a7cf85f75f 100644 --- a/utt/libutt/src/api/UTTParams.cpp +++ b/utt/libutt/src/api/UTTParams.cpp @@ -24,6 +24,7 @@ using Fr = typename libff::default_ec_pp::Fp_type; struct GpInitData { libutt::CommKey cck; libutt::CommKey rck; + bool budget_policy; }; void UTTParams::initLibs(const UTTParams::BaseLibsInitData& init_data) { // Apparently, libff logs some extra info when computing pairings @@ -61,11 +62,12 @@ UTTParams UTTParams::create(void* initData) { GpInitData* init_data = (GpInitData*)initData; *(gp.params) = libutt::Params::random(init_data->cck); gp.params->ck_reg = init_data->rck; + gp.budget_policy = init_data->budget_policy; } return gp; } const libutt::Params& UTTParams::getParams() const { return *params; } - +bool UTTParams::getBudgetPolicy() const { return budget_policy; } UTTParams::UTTParams(const UTTParams& other) { *this = other; } UTTParams& UTTParams::operator=(const UTTParams& other) { if (this == &other) return *this; diff --git a/utt/libutt/src/api/coinsSigner.cpp b/utt/libutt/src/api/coinsSigner.cpp index 08059e98ea..88a70fef11 100644 --- a/utt/libutt/src/api/coinsSigner.cpp +++ b/utt/libutt/src/api/coinsSigner.cpp @@ -83,7 +83,7 @@ bool CoinsSigner::validate(const UTTParams& p, const operation } template <> bool CoinsSigner::validate(const UTTParams& p, const operations::Transaction& tx) const { - return tx.tx_->validate(p.getParams(), *(bvk_), *(rvk_)); + return tx.tx_->validate(p.getParams(), *(bvk_), *(rvk_), p.getBudgetPolicy()); } template <> diff --git a/utt/libutt/src/api/config.cpp b/utt/libutt/src/api/config.cpp index b056b7e0db..51b02c8de9 100644 --- a/utt/libutt/src/api/config.cpp +++ b/utt/libutt/src/api/config.cpp @@ -83,6 +83,7 @@ Configuration::Configuration(uint16_t n, uint16_t t) : Configuration() { struct CommitmentKeys { libutt::CommKey cck; libutt::CommKey rck; + bool budget_policy = true; }; CommitmentKeys commitmentKeys{dkg.getCK(), rsk.ck_reg}; pImpl_->publicConfig_.pImpl_->params_ = libutt::api::UTTParams::create((void*)(&commitmentKeys)); diff --git a/utt/libutt/src/api/transaction.cpp b/utt/libutt/src/api/transaction.cpp index 4a71bef1bb..0582bfdf11 100644 --- a/utt/libutt/src/api/transaction.cpp +++ b/utt/libutt/src/api/transaction.cpp @@ -21,7 +21,8 @@ Transaction::Transaction(const UTTParams& d, const std::vector& coins, const std::optional& bc, const std::vector>& recipients, - const IEncryptor& encryptor) { + const IEncryptor& encryptor, + bool budget_policy) { Fr fr_pidhash; fr_pidhash.from_words(cid.getPidHash()); Fr prf; @@ -57,7 +58,8 @@ Transaction::Transaction(const UTTParams& d, fr_recipients, std::nullopt, rpk.vk, - encryptor)); + encryptor, + budget_policy)); } Transaction::Transaction() { tx_.reset(new libutt::Tx()); } Transaction::Transaction(const Transaction& other) { diff --git a/utt/libutt/test/TestTxn.cpp b/utt/libutt/test/TestTxn.cpp index bb516ed702..2fbdc8b2ed 100644 --- a/utt/libutt/test/TestTxn.cpp +++ b/utt/libutt/test/TestTxn.cpp @@ -127,7 +127,7 @@ void testBudgeted2to2Txn(size_t thresh, size_t n, size_t numCycles, bool isBudge if (isQuickPay) { // check transaction validates - if (!tx.quickPayValidate(p, bpk, rpk)) { + if (!tx.quickPayValidate(p, bpk, rpk, true)) { testAssertFail("TXN should have QuickPay-verified"); } } else { diff --git a/utt/tests/TestUttNewApi.cpp b/utt/tests/TestUttNewApi.cpp index f6cd68cb13..7e87dc13d6 100644 --- a/utt/tests/TestUttNewApi.cpp +++ b/utt/tests/TestUttNewApi.cpp @@ -26,28 +26,28 @@ using namespace libutt::api::operations; namespace { class ibe_based_test_system : public libutt::api::testing::test_utt_instance { protected: - void SetUp() override { libutt::api::testing::test_utt_instance::setUp(true, true); } + void SetUp() override { libutt::api::testing::test_utt_instance::setUp(true, true, true); } }; class ibe_based_test_system_without_registration : public libutt::api::testing::test_utt_instance { protected: - void SetUp() override { libutt::api::testing::test_utt_instance::setUp(true, false); } + void SetUp() override { libutt::api::testing::test_utt_instance::setUp(true, false, true); } }; class rsa_based_test_system : public libutt::api::testing::test_utt_instance { protected: - void SetUp() override { libutt::api::testing::test_utt_instance::setUp(false, true); } + void SetUp() override { libutt::api::testing::test_utt_instance::setUp(false, true, true); } }; class rsa_based_test_system_without_registration : public libutt::api::testing::test_utt_instance { protected: - void SetUp() override { libutt::api::testing::test_utt_instance::setUp(false, false); } + void SetUp() override { libutt::api::testing::test_utt_instance::setUp(false, false, true); } }; class test_system_minted : public libutt::api::testing::test_utt_instance { public: - void setUp(bool ibe) { - libutt::api::testing::test_utt_instance::setUp(ibe, true); + void setUp(bool ibe, bool budget_policy) { + libutt::api::testing::test_utt_instance::setUp(ibe, true, budget_policy); for (auto& c : clients) { std::vector> rsigs; std::string simulatonOfUniqueTxHash = @@ -66,31 +66,32 @@ class test_system_minted : public libutt::api::testing::test_utt_instance { ASSERT_TRUE(c.validate(coin)); coins[c.getPid()] = {coin}; } - - for (auto& c : clients) { - std::vector> rsigs; - std::vector shares_signers; - auto budget = Budget(d, c, 1000, 123456789); - for (size_t i = 0; i < banks.size(); i++) { - rsigs.push_back(banks[i]->sign(budget).front()); - shares_signers.push_back(banks[i]->getId()); - } - for (auto& b : banks) { - for (size_t i = 0; i < rsigs.size(); i++) { - auto& sig = rsigs[i]; - auto& signer = shares_signers[i]; - ASSERT_TRUE(b->validatePartialSignature(signer, sig, 0, budget)); + if (budget_policy) { + for (auto& c : clients) { + std::vector> rsigs; + std::vector shares_signers; + auto budget = Budget(d, c, 1000, 123456789); + for (size_t i = 0; i < banks.size(); i++) { + rsigs.push_back(banks[i]->sign(budget).front()); + shares_signers.push_back(banks[i]->getId()); } + for (auto& b : banks) { + for (size_t i = 0; i < rsigs.size(); i++) { + auto& sig = rsigs[i]; + auto& signer = shares_signers[i]; + ASSERT_TRUE(b->validatePartialSignature(signer, sig, 0, budget)); + } + } + auto sbs = getSubset((uint32_t)n, (uint32_t)thresh); + std::map> sigs; + for (auto i : sbs) { + sigs[i] = rsigs[i]; + } + auto blinded_sig = Utils::aggregateSigShares((uint32_t)n, {sigs}); + auto coin = c.claimCoins(budget, d, {blinded_sig}).front(); + ASSERT_TRUE(c.validate(coin)); + bcoins[c.getPid()] = {coin}; } - auto sbs = getSubset((uint32_t)n, (uint32_t)thresh); - std::map> sigs; - for (auto i : sbs) { - sigs[i] = rsigs[i]; - } - auto blinded_sig = Utils::aggregateSigShares((uint32_t)n, {sigs}); - auto coin = c.claimCoins(budget, d, {blinded_sig}).front(); - ASSERT_TRUE(c.validate(coin)); - bcoins[c.getPid()] = {coin}; } } std::map> coins; @@ -99,7 +100,12 @@ class test_system_minted : public libutt::api::testing::test_utt_instance { class ibe_based_test_system_minted : public test_system_minted { protected: - void SetUp() override { test_system_minted::setUp(true); } + void SetUp() override { test_system_minted::setUp(true, true); } +}; + +class ibe_based_test_system_minted_budget_policy_disabled : public test_system_minted { + protected: + void SetUp() override { test_system_minted::setUp(true, false); } }; class ibe_based_test_system_minted_impl : public ibe_based_test_system_minted { @@ -110,7 +116,7 @@ class ibe_based_test_system_minted_impl : public ibe_based_test_system_minted { class rsa_based_test_system_minted : public test_system_minted { protected: - void SetUp() override { test_system_minted::setUp(false); } + void SetUp() override { test_system_minted::setUp(false, true); } }; TEST_F(ibe_based_test_system_without_registration, test_distributed_registration) { @@ -270,12 +276,12 @@ TEST_F(ibe_based_test_system, test_serialization_complete_coin) { Fr::random_element().to_words()); std::vector c_serialized = libutt::api::serialize(c); libutt::api::Coin c_deserialized = libutt::api::deserialize(c_serialized); - assertTrue(c.getNullifier() == c_deserialized.getNullifier()); - assertTrue(c.hasSig() == c_deserialized.hasSig()); - assertTrue(c.getType() == c_deserialized.getType()); - assertTrue(c.getVal() == c_deserialized.getVal()); - assertTrue(c.getSN() == c_deserialized.getSN()); - assertTrue(c.getExpDateAsCurvePoint() == c_deserialized.getExpDateAsCurvePoint()); + ASSERT_TRUE(c.getNullifier() == c_deserialized.getNullifier()); + ASSERT_TRUE(c.hasSig() == c_deserialized.hasSig()); + ASSERT_TRUE(c.getType() == c_deserialized.getType()); + ASSERT_TRUE(c.getVal() == c_deserialized.getVal()); + ASSERT_TRUE(c.getSN() == c_deserialized.getSN()); + ASSERT_TRUE(c.getExpDateAsCurvePoint() == c_deserialized.getExpDateAsCurvePoint()); } TEST_F(ibe_based_test_system, test_serialization_incomplete_coin) { @@ -289,12 +295,12 @@ TEST_F(ibe_based_test_system, test_serialization_incomplete_coin) { Fr::random_element().to_words()); std::vector c_serialized = libutt::api::serialize(c); libutt::api::Coin c_deserialized = libutt::api::deserialize(c_serialized); - assertTrue(c.getNullifier() == c_deserialized.getNullifier()); - assertTrue(c.hasSig() == c_deserialized.hasSig()); - assertTrue(c.getType() == c_deserialized.getType()); - assertTrue(c.getVal() == c_deserialized.getVal()); - assertTrue(c.getSN() == c_deserialized.getSN()); - assertTrue(c.getExpDateAsCurvePoint() == c_deserialized.getExpDateAsCurvePoint()); + ASSERT_TRUE(c.getNullifier() == c_deserialized.getNullifier()); + ASSERT_TRUE(c.hasSig() == c_deserialized.hasSig()); + ASSERT_TRUE(c.getType() == c_deserialized.getType()); + ASSERT_TRUE(c.getVal() == c_deserialized.getVal()); + ASSERT_TRUE(c.getSN() == c_deserialized.getSN()); + ASSERT_TRUE(c.getExpDateAsCurvePoint() == c_deserialized.getExpDateAsCurvePoint()); } TEST_F(ibe_based_test_system, test_serialization_complete_budget_coin) { Fr fr_val; @@ -307,12 +313,12 @@ TEST_F(ibe_based_test_system, test_serialization_complete_budget_coin) { Fr::random_element().to_words()); std::vector c_serialized = libutt::api::serialize(c); libutt::api::Coin c_deserialized = libutt::api::deserialize(c_serialized); - assertTrue(c.getNullifier() == c_deserialized.getNullifier()); - assertTrue(c.hasSig() == c_deserialized.hasSig()); - assertTrue(c.getType() == c_deserialized.getType()); - assertTrue(c.getVal() == c_deserialized.getVal()); - assertTrue(c.getSN() == c_deserialized.getSN()); - assertTrue(c.getExpDateAsCurvePoint() == c_deserialized.getExpDateAsCurvePoint()); + ASSERT_TRUE(c.getNullifier() == c_deserialized.getNullifier()); + ASSERT_TRUE(c.hasSig() == c_deserialized.hasSig()); + ASSERT_TRUE(c.getType() == c_deserialized.getType()); + ASSERT_TRUE(c.getVal() == c_deserialized.getVal()); + ASSERT_TRUE(c.getSN() == c_deserialized.getSN()); + ASSERT_TRUE(c.getExpDateAsCurvePoint() == c_deserialized.getExpDateAsCurvePoint()); } TEST_F(ibe_based_test_system, test_serialization_incomplete_budget_coin) { @@ -327,12 +333,12 @@ TEST_F(ibe_based_test_system, test_serialization_incomplete_budget_coin) { Fr::random_element().to_words()); std::vector c_serialized = libutt::api::serialize(c); libutt::api::Coin c_deserialized = libutt::api::deserialize(c_serialized); - assertTrue(c.getNullifier() == c_deserialized.getNullifier()); - assertTrue(c.hasSig() == c_deserialized.hasSig()); - assertTrue(c.getType() == c_deserialized.getType()); - assertTrue(c.getVal() == c_deserialized.getVal()); - assertTrue(c.getSN() == c_deserialized.getSN()); - assertTrue(c.getExpDateAsCurvePoint() == c_deserialized.getExpDateAsCurvePoint()); + ASSERT_TRUE(c.getNullifier() == c_deserialized.getNullifier()); + ASSERT_TRUE(c.hasSig() == c_deserialized.hasSig()); + ASSERT_TRUE(c.getType() == c_deserialized.getType()); + ASSERT_TRUE(c.getVal() == c_deserialized.getVal()); + ASSERT_TRUE(c.getSN() == c_deserialized.getSN()); + ASSERT_TRUE(c.getExpDateAsCurvePoint() == c_deserialized.getExpDateAsCurvePoint()); } TEST_F(ibe_based_test_system, test_serialization_incomplete_budget_op) { @@ -341,15 +347,15 @@ TEST_F(ibe_based_test_system, test_serialization_incomplete_budget_op) { std::vector b_serialized = libutt::api::serialize(b); libutt::api::operations::Budget b_deserialized = libutt::api::deserialize(b_serialized); - assertTrue(b.getHashHex() == b_deserialized.getHashHex()); + ASSERT_TRUE(b.getHashHex() == b_deserialized.getHashHex()); const auto b_coin = b.getCoin(); const auto c_deserialized_coin = b_deserialized.getCoin(); - assertTrue(b_coin.getNullifier() == c_deserialized_coin.getNullifier()); - assertTrue(b_coin.hasSig() == c_deserialized_coin.hasSig()); - assertTrue(b_coin.getType() == c_deserialized_coin.getType()); - assertTrue(b_coin.getVal() == c_deserialized_coin.getVal()); - assertTrue(b_coin.getSN() == c_deserialized_coin.getSN()); - assertTrue(b_coin.getExpDateAsCurvePoint() == c_deserialized_coin.getExpDateAsCurvePoint()); + ASSERT_TRUE(b_coin.getNullifier() == c_deserialized_coin.getNullifier()); + ASSERT_TRUE(b_coin.hasSig() == c_deserialized_coin.hasSig()); + ASSERT_TRUE(b_coin.getType() == c_deserialized_coin.getType()); + ASSERT_TRUE(b_coin.getVal() == c_deserialized_coin.getVal()); + ASSERT_TRUE(b_coin.getSN() == c_deserialized_coin.getSN()); + ASSERT_TRUE(b_coin.getExpDateAsCurvePoint() == c_deserialized_coin.getExpDateAsCurvePoint()); } TEST_F(ibe_based_test_system, test_serialization_mint_op) { @@ -359,9 +365,9 @@ TEST_F(ibe_based_test_system, test_serialization_mint_op) { auto mint = Mint(simulatonOfUniqueTxHash, 100, c.getPid()); std::vector mint_serialized = libutt::api::serialize(mint); auto mint_deserialized = libutt::api::deserialize(mint_serialized); - assertTrue(mint.getHash() == mint_deserialized.getHash()); - assertTrue(mint.getVal() == mint_deserialized.getVal()); - assertTrue(mint.getRecipentID() == mint_deserialized.getRecipentID()); + ASSERT_TRUE(mint.getHash() == mint_deserialized.getHash()); + ASSERT_TRUE(mint.getVal() == mint_deserialized.getVal()); + ASSERT_TRUE(mint.getRecipentID() == mint_deserialized.getRecipentID()); } } @@ -370,15 +376,15 @@ TEST_F(ibe_based_test_system_minted, test_serialization_burn_op) { Burn b_op{d, c, coins[c.getPid()].front()}; std::vector burn_serialized = libutt::api::serialize(b_op); auto burn_deserialized = libutt::api::deserialize(burn_serialized); - assertTrue(b_op.getNullifier() == burn_deserialized.getNullifier()); + ASSERT_TRUE(b_op.getNullifier() == burn_deserialized.getNullifier()); const auto& burn_coin = b_op.getCoin(); const auto& burn_deserialized_coin = burn_deserialized.getCoin(); - assertTrue(burn_coin.getNullifier() == burn_deserialized_coin.getNullifier()); - assertTrue(burn_coin.hasSig() == burn_deserialized_coin.hasSig()); - assertTrue(burn_coin.getType() == burn_deserialized_coin.getType()); - assertTrue(burn_coin.getVal() == burn_deserialized_coin.getVal()); - assertTrue(burn_coin.getSN() == burn_deserialized_coin.getSN()); - assertTrue(burn_coin.getExpDateAsCurvePoint() == burn_deserialized_coin.getExpDateAsCurvePoint()); + ASSERT_TRUE(burn_coin.getNullifier() == burn_deserialized_coin.getNullifier()); + ASSERT_TRUE(burn_coin.hasSig() == burn_deserialized_coin.hasSig()); + ASSERT_TRUE(burn_coin.getType() == burn_deserialized_coin.getType()); + ASSERT_TRUE(burn_coin.getVal() == burn_deserialized_coin.getVal()); + ASSERT_TRUE(burn_coin.getSN() == burn_deserialized_coin.getSN()); + ASSERT_TRUE(burn_coin.getExpDateAsCurvePoint() == burn_deserialized_coin.getExpDateAsCurvePoint()); } } @@ -393,9 +399,9 @@ TEST_F(ibe_based_test_system_minted, test_serialization_tx_op) { // Test Transaction de/serialization std::vector serialized_tx = libutt::api::serialize(tx); auto deserialized_tx = libutt::api::deserialize(serialized_tx); - assertTrue(tx.hasBudgetCoin() == deserialized_tx.hasBudgetCoin()); - assertTrue(tx.getBudgetExpirationDate() == deserialized_tx.getBudgetExpirationDate()); - assertTrue(tx.getNullifiers() == deserialized_tx.getNullifiers()); + ASSERT_TRUE(tx.hasBudgetCoin() == deserialized_tx.hasBudgetCoin()); + ASSERT_TRUE(tx.getBudgetExpirationDate() == deserialized_tx.getBudgetExpirationDate()); + ASSERT_TRUE(tx.getNullifiers() == deserialized_tx.getNullifiers()); } TEST_F(ibe_based_test_system_minted, test_transaction) { @@ -414,6 +420,9 @@ TEST_F(ibe_based_test_system_minted, test_transaction) { *(encryptors_.at((i + 1) % clients.size()))); coins[issuer.getPid()].erase(coins[issuer.getPid()].begin()); bcoins.erase(issuer.getPid()); + for (auto& b : banks) { + ASSERT_TRUE(b->validate(d, tx)); + } std::map> shares; std::vector shares_signers; for (size_t i = 0; i < banks.size(); i++) { @@ -431,7 +440,7 @@ TEST_F(ibe_based_test_system_minted, test_transaction) { for (size_t k = 0; k < share_sigs.size(); k++) { auto& sig = share_sigs[k]; auto& signer = shares_signers[k]; - assertTrue(b->validatePartialSignature(signer, sig, i, tx)); + ASSERT_TRUE(b->validatePartialSignature(signer, sig, i, tx)); } } } @@ -448,30 +457,27 @@ TEST_F(ibe_based_test_system_minted, test_transaction) { } auto issuer_coins = issuer.claimCoins(tx, d, sigs); for (auto& coin : issuer_coins) { - assertTrue(issuer.validate(coin)); + ASSERT_TRUE(issuer.validate(coin)); if (coin.getType() == api::Coin::Type::Normal) { coins[issuer.getPid()].emplace_back(std::move(coin)); } else { - assertTrue(bcoins.find(issuer.getPid()) == bcoins.end()); + ASSERT_TRUE(bcoins.find(issuer.getPid()) == bcoins.end()); bcoins[issuer.getPid()] = {coin}; } } auto receiver_coins = receiver.claimCoins(tx, d, sigs); for (auto& coin : receiver_coins) { - assertTrue(receiver.validate(coin)); - if (coin.getType() == api::Coin::Type::Normal) { - coins[receiver.getPid()].emplace_back(std::move(coin)); - } else { - assertTrue(false); - } + ASSERT_TRUE(receiver.validate(coin)); + ASSERT_TRUE(coin.getType() == api::Coin::Type::Normal); + coins[receiver.getPid()].emplace_back(std::move(coin)); } } // At the end of these phaese, the budget of each client is 950 and it has two coins of 50 each for (const auto& c : clients) { - assertTrue(coins[c.getPid()].size() == 2); - for (const auto& coin : coins[c.getPid()]) assertTrue(coin.getVal() == 50U); - assertTrue(bcoins[c.getPid()].front().getVal() == 950U); + ASSERT_TRUE(coins[c.getPid()].size() == 2); + for (const auto& coin : coins[c.getPid()]) ASSERT_TRUE(coin.getVal() == 50U); + ASSERT_TRUE(bcoins[c.getPid()].front().getVal() == 950U); } } @@ -491,6 +497,9 @@ TEST_F(rsa_based_test_system_minted, test_transaction) { tx_encryptor); coins[issuer.getPid()].erase(coins[issuer.getPid()].begin()); bcoins.erase(issuer.getPid()); + for (auto& b : banks) { + ASSERT_TRUE(b->validate(d, tx)); + } std::map> shares; std::vector shares_signers; for (size_t i = 0; i < banks.size(); i++) { @@ -508,7 +517,7 @@ TEST_F(rsa_based_test_system_minted, test_transaction) { for (size_t k = 0; k < share_sigs.size(); k++) { auto& sig = share_sigs[k]; auto& signer = shares_signers[k]; - assertTrue(b->validatePartialSignature(signer, sig, i, tx)); + ASSERT_TRUE(b->validatePartialSignature(signer, sig, i, tx)); } } } @@ -525,32 +534,128 @@ TEST_F(rsa_based_test_system_minted, test_transaction) { } auto issuer_coins = issuer.claimCoins(tx, d, sigs); for (auto& coin : issuer_coins) { - assertTrue(issuer.validate(coin)); + ASSERT_TRUE(issuer.validate(coin)); if (coin.getType() == api::Coin::Type::Normal) { coins[issuer.getPid()].emplace_back(std::move(coin)); } else { - assertTrue(bcoins.find(issuer.getPid()) == bcoins.end()); + ASSERT_TRUE(bcoins.find(issuer.getPid()) == bcoins.end()); bcoins[issuer.getPid()] = {coin}; } } auto receiver_coins = receiver.claimCoins(tx, d, sigs); for (auto& coin : receiver_coins) { - assertTrue(receiver.validate(coin)); - if (coin.getType() == api::Coin::Type::Normal) { - coins[receiver.getPid()].emplace_back(std::move(coin)); - } else { - assertTrue(false); + ASSERT_TRUE(receiver.validate(coin)); + ASSERT_TRUE(coin.getType() == api::Coin::Type::Normal); + coins[receiver.getPid()].emplace_back(std::move(coin)); + } + } + + // At the end of these phaese, the budget of each client is 950 and it has two coins of 50 each + for (const auto& c : clients) { + ASSERT_TRUE(coins[c.getPid()].size() == 2); + for (const auto& coin : coins[c.getPid()]) ASSERT_TRUE(coin.getVal() == 50U); + ASSERT_TRUE(bcoins[c.getPid()].front().getVal() == 950U); + } +} + +TEST_F(ibe_based_test_system_minted, test_transaction_without_budget) { + std::unordered_map> encryptors_; + for (size_t i = 0; i < clients.size(); i++) { + encryptors_[i] = std::make_shared(rsk.toPK().mpk); + } + for (size_t i = 0; i < clients.size(); i++) { + auto& issuer = clients[i]; + auto& receiver = clients[(i + 1) % clients.size()]; + Transaction tx(d, + issuer, + {coins[issuer.getPid()].front()}, + std::nullopt, + {{issuer.getPid(), 50}, {receiver.getPid(), 50}}, + *(encryptors_.at((i + 1) % clients.size())), + true); + for (auto& b : banks) { + ASSERT_FALSE(b->validate(d, tx)); + } + + ASSERT_ANY_THROW(Transaction(d, + issuer, + {coins[issuer.getPid()].front()}, + {bcoins[issuer.getPid()].front()}, + {{issuer.getPid(), 50}, {receiver.getPid(), 50}}, + *(encryptors_.at((i + 1) % clients.size())), + false)); + } +} + +TEST_F(ibe_based_test_system_minted_budget_policy_disabled, test_transaction) { + std::unordered_map> encryptors_; + for (size_t i = 0; i < clients.size(); i++) { + encryptors_[i] = std::make_shared(rsk.toPK().mpk); + } + for (size_t i = 0; i < clients.size(); i++) { + auto& issuer = clients[i]; + auto& receiver = clients[(i + 1) % clients.size()]; + Transaction tx(d, + issuer, + {coins[issuer.getPid()].front()}, + std::nullopt, + {{issuer.getPid(), 50}, {receiver.getPid(), 50}}, + *(encryptors_.at((i + 1) % clients.size())), + false); + coins[issuer.getPid()].erase(coins[issuer.getPid()].begin()); + std::map> shares; + std::vector shares_signers; + for (size_t i = 0; i < banks.size(); i++) { + shares[i] = banks[i]->sign(tx); + shares_signers.push_back(banks[i]->getId()); + } + size_t num_coins = shares[0].size(); + for (size_t i = 0; i < num_coins; i++) { + std::vector share_sigs(n); + auto sbs = getSubset((uint32_t)n, (uint32_t)n); + for (auto s : sbs) { + share_sigs[s] = shares[s][i]; + } + for (auto& b : banks) { + for (size_t k = 0; k < share_sigs.size(); k++) { + auto& sig = share_sigs[k]; + auto& signer = shares_signers[k]; + ASSERT_TRUE(b->validatePartialSignature(signer, sig, i, tx)); + } } } + + std::vector sigs; + for (size_t i = 0; i < num_coins; i++) { + std::map share_sigs; + auto sbs = getSubset((uint32_t)n, (uint32_t)thresh); + for (auto s : sbs) { + share_sigs[s] = shares[s][i]; + } + auto blinded_sig = Utils::aggregateSigShares((uint32_t)n, {share_sigs}); + sigs.emplace_back(std::move(blinded_sig)); + } + auto issuer_coins = issuer.claimCoins(tx, d, sigs); + for (auto& coin : issuer_coins) { + ASSERT_TRUE(issuer.validate(coin)); + ASSERT_TRUE(coin.getType() == api::Coin::Type::Normal); + coins[issuer.getPid()].emplace_back(std::move(coin)); + } + auto receiver_coins = receiver.claimCoins(tx, d, sigs); + for (auto& coin : receiver_coins) { + ASSERT_TRUE(receiver.validate(coin)); + ASSERT_TRUE(coin.getType() == api::Coin::Type::Normal); + coins[receiver.getPid()].emplace_back(std::move(coin)); + } } // At the end of these phaese, the budget of each client is 950 and it has two coins of 50 each for (const auto& c : clients) { - assertTrue(coins[c.getPid()].size() == 2); - for (const auto& coin : coins[c.getPid()]) assertTrue(coin.getVal() == 50U); - assertTrue(bcoins[c.getPid()].front().getVal() == 950U); + ASSERT_TRUE(coins[c.getPid()].size() == 2); + for (const auto& coin : coins[c.getPid()]) ASSERT_TRUE(coin.getVal() == 50U); } } + } // namespace int main(int argc, char** argv) { diff --git a/utt/tests/testUtils.hpp b/utt/tests/testUtils.hpp index c470e1a4bb..8261748ec8 100644 --- a/utt/tests/testUtils.hpp +++ b/utt/tests/testUtils.hpp @@ -111,13 +111,14 @@ class test_utt_instance : public ::testing::Test { struct GpData { libutt::CommKey cck; libutt::CommKey rck; + bool budget_policy = true; }; - void setUp(bool ibe, bool register_clients) { + void setUp(bool ibe, bool register_clients, bool budget_policy) { UTTParams::BaseLibsInitData base_libs_init_data; UTTParams::initLibs(base_libs_init_data); auto dkg = RandSigDKG(thresh, n, Params::NumMessages); rsk = RegAuthSK::generateKeyAndShares(thresh, n); - GpData gp_data{dkg.getCK(), rsk.ck_reg}; + GpData gp_data{dkg.getCK(), rsk.ck_reg, budget_policy}; d = UTTParams::create((void*)(&gp_data)); rsk.setIBEParams(d.getParams().ibe); rvk = rsk.toPK(); From db42d35511aba477dd30b401bfffc1759cd293b4 Mon Sep 17 00:00:00 2001 From: Yehonatan Buchnik Date: Wed, 9 Nov 2022 11:29:08 +0200 Subject: [PATCH 41/44] Allow transaction without budget in clientAPI --- utt/admin-cli/include/admin.hpp | 2 +- utt/admin-cli/src/admin.cpp | 3 +- utt/admin-cli/src/main.cpp | 38 ++--- utt/include/config.hpp | 2 +- utt/libutt/src/api/UTTParams.cpp | 4 + utt/libutt/src/api/config.cpp | 10 +- utt/utt-client-api/src/ClientApi.cpp | 2 +- utt/utt-client-api/src/User.cpp | 19 ++- utt/utt-client-api/tests/TestUTTClientApi.cpp | 150 +++++++++++++++++- 9 files changed, 191 insertions(+), 39 deletions(-) diff --git a/utt/admin-cli/include/admin.hpp b/utt/admin-cli/include/admin.hpp index cf836b112e..98651e49a8 100644 --- a/utt/admin-cli/include/admin.hpp +++ b/utt/admin-cli/include/admin.hpp @@ -33,7 +33,7 @@ class Admin { /// [TODO-UTT] Should be performed by an admin app /// @brief Deploy a privacy application /// @return The public configuration of the deployed application - static bool deployApp(Channel& chan); + static bool deployApp(Channel& chan, bool budget_policy = true); /// @brief Request the creation of a privacy budget. The amount of the budget is predetermined by the deployed app. /// This operation could be performed entirely by an administrator, but we add it in the admin diff --git a/utt/admin-cli/src/admin.cpp b/utt/admin-cli/src/admin.cpp index 5b59444e25..447eabf129 100644 --- a/utt/admin-cli/src/admin.cpp +++ b/utt/admin-cli/src/admin.cpp @@ -38,13 +38,14 @@ Admin::Connection Admin::newConnection() { return AdminService::NewStub(chan); } -bool Admin::deployApp(Channel& chan) { +bool Admin::deployApp(Channel& chan, bool budget_policy) { std::cout << "Deploying a new privacy application...\n"; // Generate a privacy config for a N=4 replica system tolerating F=1 failures utt::client::ConfigInputParams params; params.validatorPublicKeys = std::vector{4, "placeholderPublicKey"}; // N = 3 * F + 1 params.threshold = 2; // F + 1 + params.useBudget = budget_policy; auto config = utt::client::generateConfig(params); if (config.empty()) throw std::runtime_error("Failed to generate a privacy app configuration!"); diff --git a/utt/admin-cli/src/main.cpp b/utt/admin-cli/src/main.cpp index eba6658d3f..c0dc245c5b 100644 --- a/utt/admin-cli/src/main.cpp +++ b/utt/admin-cli/src/main.cpp @@ -19,7 +19,8 @@ void printHelp() { std::cout << "\nCommands:\n"; - std::cout << "deploy -- generates a privacy config and deploys the privacy and token contracts.\n"; + std::cout << "deploy -- generates a privacy config and deploys the privacy and token contracts. budget " + "policy is true if budget should be enforced in the system\n"; // [TODO-UTT] Admin creates a user's budget by supplying a user-id and amount from the CLI // std::cout // << "create-budget -- requests creation of a privacy budget, the amount is decided by the @@ -53,13 +54,13 @@ struct CLIApp { // std::cout << "gRPC error details: " << status.error_details() << '\n'; } - void deploy() { + void deploy(bool budget_policy) { if (deployed) { std::cout << "The privacy app is already deployed.\n"; return; } - deployed = Admin::deployApp(chan); + deployed = Admin::deployApp(chan, budget_policy); } void createBudgetCmd(const std::vector& cmdTokens) { @@ -92,8 +93,11 @@ int main(int argc, char* argv[]) { while (true) { std::cout << "\nEnter command (type 'h' for commands 'Ctr-D' to quit):\n > "; - std::string cmd; - std::getline(std::cin, cmd); + std::vector cmdTokens; + std::string token; + std::stringstream ss(cmd); + while (std::getline(ss, token, ' ')) cmdTokens.emplace_back(token); + if (cmdTokens.empty()) continue; if (std::cin.eof()) { std::cout << "Quitting...\n"; @@ -102,23 +106,19 @@ int main(int argc, char* argv[]) { if (cmd == "h") { printHelp(); - } else if (cmd == "deploy") { - app.deploy(); + } else if (cmdTokens[0] == "deploy") { + if (cmdTokens.size() == 1) { + app.deploy(true); + continue; + } + bool budget_policy = cmdTokens[1] == "on"; + app.deploy(budget_policy); } else if (!app.deployed) { std::cout << "You must first deploy the privacy application. Use the 'deploy' command.\n"; + } else if (cmdTokens[0] == "create-budget") { + app.createBudgetCmd(cmdTokens); } else { - // Tokenize params - std::vector cmdTokens; - std::string token; - std::stringstream ss(cmd); - while (std::getline(ss, token, ' ')) cmdTokens.emplace_back(token); - if (cmdTokens.empty()) continue; - - if (cmdTokens[0] == "create-budget") { - app.createBudgetCmd(cmdTokens); - } else { - std::cout << "Unknown command '" << cmd << "'\n"; - } + std::cout << "Unknown command '" << cmd << "'\n"; } } } catch (const std::runtime_error& e) { diff --git a/utt/include/config.hpp b/utt/include/config.hpp index 9329ed09f0..e94312b82b 100644 --- a/utt/include/config.hpp +++ b/utt/include/config.hpp @@ -62,7 +62,7 @@ class Configuration { /// @brief Constructs a UTT instance configuration /// @param n The number of validators for multiparty signature computation /// @param t The number of validator shares required to reconstruct a signature - Configuration(uint16_t n, uint16_t t); + Configuration(uint16_t n, uint16_t t, bool budget_policy = true); ~Configuration(); Configuration(Configuration&& o); diff --git a/utt/libutt/src/api/UTTParams.cpp b/utt/libutt/src/api/UTTParams.cpp index a7cf85f75f..4adb40cf30 100644 --- a/utt/libutt/src/api/UTTParams.cpp +++ b/utt/libutt/src/api/UTTParams.cpp @@ -6,11 +6,14 @@ #include std::ostream& operator<<(std::ostream& out, const libutt::api::UTTParams& params) { + out << params.getBudgetPolicy() << endl; out << params.getParams(); return out; } std::istream& operator>>(std::istream& in, libutt::api::UTTParams& params) { params.params.reset(new libutt::Params()); + in >> params.budget_policy; + libff::consume_OUTPUT_NEWLINE(in); in >> *(params.params); return in; } @@ -73,6 +76,7 @@ UTTParams& UTTParams::operator=(const UTTParams& other) { if (this == &other) return *this; params.reset(new libutt::Params()); *params = *(other.params); + budget_policy = other.budget_policy; return *this; } } // namespace libutt::api \ No newline at end of file diff --git a/utt/libutt/src/api/config.cpp b/utt/libutt/src/api/config.cpp index 51b02c8de9..9aa915f458 100644 --- a/utt/libutt/src/api/config.cpp +++ b/utt/libutt/src/api/config.cpp @@ -65,7 +65,7 @@ struct Configuration::Impl { Configuration::Configuration() : pImpl_{new Impl{}} {} -Configuration::Configuration(uint16_t n, uint16_t t) : Configuration() { +Configuration::Configuration(uint16_t n, uint16_t t, bool budget_policy) : Configuration() { if (n == 0 || t == 0 || t > n) throw std::runtime_error("Invalid number of validators and/or threshold"); pImpl_->n_ = n; @@ -80,13 +80,13 @@ Configuration::Configuration(uint16_t n, uint16_t t) : Configuration() { // Pass in the commitment keys to UTTParams // This struct matches the expected data structure by UTTParams::create since the method hides the actual type by // using a void* - struct CommitmentKeys { + struct GPInitData { libutt::CommKey cck; libutt::CommKey rck; - bool budget_policy = true; + bool budget_policy; }; - CommitmentKeys commitmentKeys{dkg.getCK(), rsk.ck_reg}; - pImpl_->publicConfig_.pImpl_->params_ = libutt::api::UTTParams::create((void*)(&commitmentKeys)); + GPInitData GPInitData{dkg.getCK(), rsk.ck_reg, budget_policy}; + pImpl_->publicConfig_.pImpl_->params_ = libutt::api::UTTParams::create((void*)(&GPInitData)); // For some reason we need to go back and set the IBE parameters although we might not be using IBE. rsk.setIBEParams(pImpl_->publicConfig_.pImpl_->params_.getParams().ibe); diff --git a/utt/utt-client-api/src/ClientApi.cpp b/utt/utt-client-api/src/ClientApi.cpp index f92b97995e..4384d4affd 100644 --- a/utt/utt-client-api/src/ClientApi.cpp +++ b/utt/utt-client-api/src/ClientApi.cpp @@ -59,7 +59,7 @@ Configuration generateConfig(const ConfigInputParams& inputParams) { const uint16_t n = (uint16_t)inputParams.validatorPublicKeys.size(); const uint16_t t = inputParams.threshold; - auto config = libutt::api::Configuration(n, t); + auto config = libutt::api::Configuration(n, t, inputParams.useBudget); // [TODO-UTT] Use the validator's public keys to encrypt the configuration secrets for each validator diff --git a/utt/utt-client-api/src/User.cpp b/utt/utt-client-api/src/User.cpp index faaafb2d44..74dea0745b 100644 --- a/utt/utt-client-api/src/User.cpp +++ b/utt/utt-client-api/src/User.cpp @@ -174,7 +174,8 @@ utt::Transaction User::Impl::createTx_1to1(const libutt::api::Coin& coin, << " and recipients: " << dbgPrintRecipients(recip) << endl; auto encryptor = createRsaEncryptorForTransferToOther(userId, pk); - auto uttTx = libutt::api::operations::Transaction(params_, *client_, {coin}, budgetCoin_, recip, encryptor); + auto uttTx = libutt::api::operations::Transaction( + params_, *client_, {coin}, params_.getBudgetPolicy() ? budgetCoin_ : std::nullopt, recip, encryptor); utt::Transaction tx; tx.type_ = utt::Transaction::Type::Transfer; @@ -195,7 +196,8 @@ utt::Transaction User::Impl::createTx_1to2(const libutt::api::Coin& coin, << " and recipients: " << dbgPrintRecipients(recip) << endl; auto encryptor = createRsaEncryptorForTransferToOther(userId, pk); - auto uttTx = libutt::api::operations::Transaction(params_, *client_, {coin}, budgetCoin_, recip, encryptor); + auto uttTx = libutt::api::operations::Transaction( + params_, *client_, {coin}, params_.getBudgetPolicy() ? budgetCoin_ : std::nullopt, recip, encryptor); utt::Transaction tx; tx.type_ = utt::Transaction::Type::Transfer; @@ -214,7 +216,8 @@ utt::Transaction User::Impl::createTx_2to1(const std::vector& << " and recipients: " << dbgPrintRecipients(recip) << endl; auto encryptor = createRsaEncryptorForTransferToOther(userId, pk); - auto uttTx = libutt::api::operations::Transaction(params_, *client_, coins, budgetCoin_, recip, encryptor); + auto uttTx = libutt::api::operations::Transaction( + params_, *client_, coins, params_.getBudgetPolicy() ? budgetCoin_ : std::nullopt, recip, encryptor); utt::Transaction tx; tx.type_ = utt::Transaction::Type::Transfer; @@ -235,7 +238,8 @@ utt::Transaction User::Impl::createTx_2to2(const std::vector& << " and recipients: " << dbgPrintRecipients(recip) << endl; auto encryptor = createRsaEncryptorForTransferToOther(userId, pk); - auto uttTx = libutt::api::operations::Transaction(params_, *client_, coins, budgetCoin_, recip, encryptor); + auto uttTx = libutt::api::operations::Transaction( + params_, *client_, coins, params_.getBudgetPolicy() ? budgetCoin_ : std::nullopt, recip, encryptor); utt::Transaction tx; tx.type_ = utt::Transaction::Type::Transfer; @@ -567,9 +571,10 @@ TransferResult User::transfer(const std::string& userId, const std::string& dest const uint64_t balance = getBalance(); if (balance < amount) throw std::runtime_error("User has insufficient balance!"); - const size_t budget = getPrivacyBudget(); - if (budget < amount) throw std::runtime_error("User has insufficient privacy budget!"); - + if (pImpl_->params_.getBudgetPolicy()) { + const size_t budget = getPrivacyBudget(); + if (budget < amount) throw std::runtime_error("User has insufficient privacy budget!"); + } auto pickedCoins = PickCoinsPreferExactMatch(pImpl_->coins_, amount); if (pickedCoins.empty()) throw std::runtime_error("Coin strategy didn't pick any coins!"); diff --git a/utt/utt-client-api/tests/TestUTTClientApi.cpp b/utt/utt-client-api/tests/TestUTTClientApi.cpp index a293659a8c..dfceb4b66b 100644 --- a/utt/utt-client-api/tests/TestUTTClientApi.cpp +++ b/utt/utt-client-api/tests/TestUTTClientApi.cpp @@ -265,10 +265,7 @@ struct ServerMock { } }; -int main(int argc, char* argv[]) { - (void)argc; - (void)argv; - +void testCaseWithBudgetEnforced() { // Note that this test assumes the client and server-side parts of the code work under the same initialization of // libutt. utt::client::Initialize(); @@ -421,6 +418,151 @@ int main(int argc, char* argv[]) { for (size_t i = 0; i < C; ++i) { assertTrue(users[i]->getBalance() == 0); } +} + +void testCaseWithoutBudgetEnforced() { + // Note that this test assumes the client and server-side parts of the code work under the same initialization of + // libutt. + utt::client::Initialize(); + + utt::client::ConfigInputParams cfgInputParams; + + // Create a UTT system tolerating F faulty validators + const uint16_t F = 1; + cfgInputParams.validatorPublicKeys = std::vector(3 * F + 1, "placeholderForPublicKey"); + cfgInputParams.threshold = F + 1; + cfgInputParams.useBudget = false; + // Create a new UTT instance config + auto config = utt::client::generateConfig(cfgInputParams); + + // Create a valid server-side mock based on the config + auto serverMock = ServerMock::createFromConfig(config); + + // Create new users by using the public config + utt::client::TestUserPKInfrastructure pki; + auto testUserIds = pki.getUserIds(); + const size_t C = testUserIds.size(); + loginfo << "Test users: " << C << '\n'; + assertTrue(C >= 3); // At least 3 test users expected + + std::vector> users; + + auto syncUsersWithServer = [&]() { + loginfo << "Synchronizing users with server" << endl; + for (size_t i = 0; i < C; ++i) { + for (uint64_t txNum = users[i]->getLastExecutedTxNum() + 1; txNum <= serverMock.getLastExecutedTxNum(); ++txNum) { + const auto& executedTx = serverMock.getExecutedTx(txNum); + switch (executedTx.tx_.type_) { + case utt::Transaction::Type::Mint: { + assertTrue(executedTx.sigs_.size() == 1); + users[i]->updateMintTx(txNum, executedTx.tx_, executedTx.sigs_.front()); + } break; + case utt::Transaction::Type::Burn: { + assertTrue(executedTx.sigs_.empty()); + users[i]->updateBurnTx(txNum, executedTx.tx_); + } break; + case utt::Transaction::Type::Transfer: { + assertFalse(executedTx.sigs_.empty()); + users[i]->updateTransferTx(txNum, executedTx.tx_, executedTx.sigs_); + } break; + default: + assertFail("Unknown tx type!"); + } + } + } + }; + + // Create new users by using the public config + utt::client::IUserStorage storage; + auto publicConfig = libutt::api::serialize(serverMock.config_->getPublicConfig()); + std::vector initialBalance; + + for (size_t i = 0; i < C; ++i) { + users.emplace_back(utt::client::createUser(testUserIds[i], publicConfig, pki, storage)); + initialBalance.emplace_back((i + 1) * 100); + } + // Register users + loginfo << "Registering users" << endl; + for (size_t i = 0; i < C; ++i) { + auto resp = serverMock.registerUser(users[i]->getUserId(), users[i]->getRegistrationInput()); + assertFalse(resp.s2.empty()); + assertFalse(resp.sig.empty()); + + // Note: the user's pk is usually recorded by the system and returned as part of the registration + users[i]->updateRegistration(users[i]->getPK(), resp.sig, resp.s2); + } + + // Mint test + { + loginfo << "Minting tokens" << endl; + for (size_t i = 0; i < C; ++i) { + auto tx = users[i]->mint(initialBalance[i]); + auto txNum = serverMock.mint(users[i]->getUserId(), initialBalance[i], tx); + assertTrue(txNum == serverMock.getLastExecutedTxNum()); + } + + syncUsersWithServer(); + + for (size_t i = 0; i < C; ++i) { + assertTrue(users[i]->getBalance() == initialBalance[i]); + } + } + + // Each user sends to the next one (wrapping around to the first) some amount + { + const uint64_t amount = 50; + for (size_t i = 0; i < C; ++i) { + size_t nextUserIdx = (i + 1) % C; + std::string nextUserId = "user-" + std::to_string(nextUserIdx + 1); + loginfo << "Sending " << amount << " from " << users[i]->getUserId() << " to " << nextUserId << endl; + assertTrue(amount <= users[i]->getBalance()); + auto result = users[i]->transfer(nextUserId, pki.getPublicKey(nextUserId), amount); + assertTrue(result.isFinal_); + auto txNum = serverMock.transfer(result.requiredTx_); + assertTrue(txNum == serverMock.getLastExecutedTxNum()); + } + syncUsersWithServer(); + + for (size_t i = 0; i < C; ++i) { + assertTrue(users[i]->getBalance() == + initialBalance[i]); // Unchanged - each user sent X and received X from another user + } + } + + // All users burn their private funds + loginfo << "Burning user's tokens" << endl; + for (size_t i = 0; i < C; ++i) { + const uint64_t balance = users[i]->getBalance(); + assertTrue(balance > 0); + + while (true) { + auto result = users[i]->burn(balance); + if (result.requiredTx_.type_ == utt::Transaction::Type::Burn) { + assertTrue(result.isFinal_); + auto txNum = serverMock.burn(result.requiredTx_); + assertTrue(txNum == serverMock.getLastExecutedTxNum()); + syncUsersWithServer(); + break; // We can stop processing after burning the coin + } else if (result.requiredTx_.type_ == utt::Transaction::Type::Transfer) { + assertFalse(result.isFinal_); + // We need to process a self transaction (split/merge) + auto txNum = serverMock.transfer(result.requiredTx_); + assertTrue(txNum == serverMock.getLastExecutedTxNum()); + syncUsersWithServer(); + } + } + } + + for (size_t i = 0; i < C; ++i) { + assertTrue(users[i]->getBalance() == 0); + } +} + +int main(int argc, char* argv[]) { + (void)argc; + (void)argv; + testCaseWithBudgetEnforced(); + testCaseWithoutBudgetEnforced(); return 0; } \ No newline at end of file From 6608a17c06e70a85fd173671c31cc47534d934e8 Mon Sep 17 00:00:00 2001 From: Yehonatan Buchnik Date: Wed, 9 Nov 2022 11:55:55 +0200 Subject: [PATCH 42/44] Improve the input for budget policy in the admin cli --- utt/admin-cli/src/main.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/utt/admin-cli/src/main.cpp b/utt/admin-cli/src/main.cpp index c0dc245c5b..4803fb7729 100644 --- a/utt/admin-cli/src/main.cpp +++ b/utt/admin-cli/src/main.cpp @@ -19,13 +19,16 @@ void printHelp() { std::cout << "\nCommands:\n"; - std::cout << "deploy -- generates a privacy config and deploys the privacy and token contracts. budget " - "policy is true if budget should be enforced in the system\n"; - // [TODO-UTT] Admin creates a user's budget by supplying a user-id and amount from the CLI - // std::cout - // << "create-budget -- requests creation of a privacy budget, the amount is decided by the - // system.\n"; - std::cout << '\n'; + std::cout << "deploy [budget-policy-enabled/budget-policy-disabled] -- generates a privacy config and deploys the " + "privacy and token contracts\n." + "budget-policy-disabled means that budget coin's presence won't be enforced in the system\n" + "if no input was given, the default is budget-policy-enabled" + // [TODO-UTT] Admin creates a user's budget by supplying a user-id and amount from the CLI + // std::cout + // << "create-budget -- requests creation of a privacy budget, the amount is decided by the + // system.\n"; + std::cout + << '\n'; } struct CLIApp { @@ -111,7 +114,7 @@ int main(int argc, char* argv[]) { app.deploy(true); continue; } - bool budget_policy = cmdTokens[1] == "on"; + bool budget_policy = cmdTokens[1] == "budget-policy-enabled"; app.deploy(budget_policy); } else if (!app.deployed) { std::cout << "You must first deploy the privacy application. Use the 'deploy' command.\n"; From 312455f11b5fb55837fb6710787572bec27b315b Mon Sep 17 00:00:00 2001 From: Yehonatan Buchnik Date: Wed, 9 Nov 2022 12:03:25 +0200 Subject: [PATCH 43/44] clean some redundent comments --- utt/libutt/src/Tx.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/utt/libutt/src/Tx.cpp b/utt/libutt/src/Tx.cpp index 42cbad03fe..d76cbd3f2d 100644 --- a/utt/libutt/src/Tx.cpp +++ b/utt/libutt/src/Tx.cpp @@ -46,8 +46,6 @@ std::istream& operator>>(std::istream& in, libutt::Tx& tx) { libutt::deserializeVector(in, tx.outs); in >> tx.budget_pi; - // If the budget policy is true, so the only reason not to have budget, is if the transaction is a split coins - // transaction. return in; } From c5120407c0e298eebcc2f6f88779f9837c72f86a Mon Sep 17 00:00:00 2001 From: Yehonatan Buchnik Date: Wed, 9 Nov 2022 14:10:31 +0200 Subject: [PATCH 44/44] remove defaults and beautify the code --- utt/admin-cli/include/admin.hpp | 2 +- utt/include/UTTParams.hpp | 2 +- utt/include/config.hpp | 2 +- utt/include/transaction.hpp | 3 +-- utt/libutt/bench/BenchTxn.cpp | 2 +- utt/libutt/include/utt/Tx.h | 2 +- utt/libutt/src/BurnOp.cpp | 5 ++-- utt/libutt/src/Simulation.cpp | 2 +- utt/libutt/src/Tx.cpp | 39 ++++++++++++++++++------------ utt/libutt/src/api/transaction.cpp | 5 ++-- utt/libutt/test/TestTxn.cpp | 2 +- utt/tests/TestUttNewApi.cpp | 33 +++++++++++++++++-------- 12 files changed, 60 insertions(+), 39 deletions(-) diff --git a/utt/admin-cli/include/admin.hpp b/utt/admin-cli/include/admin.hpp index 98651e49a8..ffc73cc60d 100644 --- a/utt/admin-cli/include/admin.hpp +++ b/utt/admin-cli/include/admin.hpp @@ -33,7 +33,7 @@ class Admin { /// [TODO-UTT] Should be performed by an admin app /// @brief Deploy a privacy application /// @return The public configuration of the deployed application - static bool deployApp(Channel& chan, bool budget_policy = true); + static bool deployApp(Channel& chan, bool budget_policy); /// @brief Request the creation of a privacy budget. The amount of the budget is predetermined by the deployed app. /// This operation could be performed entirely by an administrator, but we add it in the admin diff --git a/utt/include/UTTParams.hpp b/utt/include/UTTParams.hpp index b1ba99387e..acec10c7ad 100644 --- a/utt/include/UTTParams.hpp +++ b/utt/include/UTTParams.hpp @@ -57,12 +57,12 @@ class UTTParams { UTTParams& operator=(UTTParams&&) = default; bool getBudgetPolicy() const; + bool budget_policy = true; private: friend std::ostream& ::operator<<(std::ostream& out, const libutt::api::UTTParams& params); friend std::istream& ::operator>>(std::istream& in, libutt::api::UTTParams& params); friend bool ::operator==(const libutt::api::UTTParams& params1, const libutt::api::UTTParams& params2); std::unique_ptr params; - bool budget_policy = true; }; } // namespace libutt::api diff --git a/utt/include/config.hpp b/utt/include/config.hpp index e94312b82b..d79eaabce1 100644 --- a/utt/include/config.hpp +++ b/utt/include/config.hpp @@ -62,7 +62,7 @@ class Configuration { /// @brief Constructs a UTT instance configuration /// @param n The number of validators for multiparty signature computation /// @param t The number of validator shares required to reconstruct a signature - Configuration(uint16_t n, uint16_t t, bool budget_policy = true); + Configuration(uint16_t n, uint16_t t, bool budget_policy); ~Configuration(); Configuration(Configuration&& o); diff --git a/utt/include/transaction.hpp b/utt/include/transaction.hpp index 10640f79f1..83654518cc 100644 --- a/utt/include/transaction.hpp +++ b/utt/include/transaction.hpp @@ -57,8 +57,7 @@ class Transaction { const std::vector& input_coins, const std::optional& budget_coin, const std::vector>& recipients, - const IEncryptor& encryptor, - bool budget_policy = true); + const IEncryptor& encryptor); Transaction(); Transaction(const Transaction&); Transaction(Transaction&&) = default; diff --git a/utt/libutt/bench/BenchTxn.cpp b/utt/libutt/bench/BenchTxn.cpp index 72bd5b8e7e..54ec924703 100644 --- a/utt/libutt/bench/BenchTxn.cpp +++ b/utt/libutt/bench/BenchTxn.cpp @@ -119,7 +119,7 @@ class BenchTxn { tqv.endLap(); tv.startLap(); - if (!tx.validate(p, bpk, rpk)) { + if (!tx.validate(p, bpk, rpk, true)) { testAssertFail("TXN should have verified"); } tv.endLap(); diff --git a/utt/libutt/include/utt/Tx.h b/utt/libutt/include/utt/Tx.h index c6e09ee7d8..3e489da06b 100644 --- a/utt/libutt/include/utt/Tx.h +++ b/utt/libutt/include/utt/Tx.h @@ -97,7 +97,7 @@ class Tx { bool quickPayValidate(const Params& p, const RandSigPK& bpk, const RegAuthPK& rpk, bool budget_policy) const; - bool validate(const Params& p, const RandSigPK& bpk, const RegAuthPK& rpk, bool budget_policy = true) const; + bool validate(const Params& p, const RandSigPK& bpk, const RegAuthPK& rpk, bool budget_policy) const; /** * Returns the nullifiers of all coins spent by this TXN, including the budget coin's. diff --git a/utt/libutt/src/BurnOp.cpp b/utt/libutt/src/BurnOp.cpp index a821b1a920..1d440d0839 100644 --- a/utt/libutt/src/BurnOp.cpp +++ b/utt/libutt/src/BurnOp.cpp @@ -96,7 +96,8 @@ BurnOp::BurnOp(const Params& p, recip, std::move(bpk), rpk.vk, - libutt::IBEEncryptor(rpk.mpk)); + libutt::IBEEncryptor(rpk.mpk), + false); assertTrue(internalTx.outs.size() == 1); @@ -181,7 +182,7 @@ size_t BurnOp::getSize() const { bool BurnOp::validate(const Params& p, const RandSigPK& bpk, const RegAuthPK& rpk) const { InternalDataOfBurnOp* d = (InternalDataOfBurnOp*)this->p; if (d == nullptr) return false; - if (!d->tx.validate(p, bpk, rpk)) return false; + if (!d->tx.validate(p, bpk, rpk, false)) return false; if (d->tx.outs.size() != 1) return false; Fr hashPid = AddrSK::pidHash(d->pid); diff --git a/utt/libutt/src/Simulation.cpp b/utt/libutt/src/Simulation.cpp index 825550f931..2ad42fe9ce 100644 --- a/utt/libutt/src/Simulation.cpp +++ b/utt/libutt/src/Simulation.cpp @@ -51,7 +51,7 @@ Tx sendValidTxOnNetwork(const Context& ctx, const Tx& inTx) { testAssertEqual(inTx, outTx); testAssertEqual(oldHash, newHash); - if (!outTx.validate(ctx.p_, ctx.bpk_, ctx.rpk_)) { + if (!outTx.validate(ctx.p_, ctx.bpk_, ctx.rpk_, true)) { testAssertFail("TXN should have verified"); } diff --git a/utt/libutt/src/Tx.cpp b/utt/libutt/src/Tx.cpp index d76cbd3f2d..8e6e9db981 100644 --- a/utt/libutt/src/Tx.cpp +++ b/utt/libutt/src/Tx.cpp @@ -19,7 +19,6 @@ std::ostream& operator<<(std::ostream& out, const libutt::Tx& tx) { out << tx.budgetPolicy << endl; - out << tx.is_budgeted << endl; out << tx.isSplitOwnCoins << endl; out << tx.rcm; out << tx.regsig; @@ -35,8 +34,7 @@ std::ostream& operator<<(std::ostream& out, const libutt::Tx& tx) { std::istream& operator>>(std::istream& in, libutt::Tx& tx) { in >> tx.budgetPolicy; libff::consume_OUTPUT_NEWLINE(in); - in >> tx.is_budgeted; - libff::consume_OUTPUT_NEWLINE(in); + in >> tx.isSplitOwnCoins; libff::consume_OUTPUT_NEWLINE(in); in >> tx.rcm; @@ -46,6 +44,10 @@ std::istream& operator>>(std::istream& in, libutt::Tx& tx) { libutt::deserializeVector(in, tx.outs); in >> tx.budget_pi; + + for (auto& txin : tx.ins) { + tx.is_budgeted = tx.is_budgeted || (txin.coin_type == libutt::Coin::BudgetType()); + } return in; } @@ -86,7 +88,6 @@ Tx::Tx(const Params& p, const RandSigPK& rpk, const IEncryptor& encryptor, bool budget_policy) { - budgetPolicy = budget_policy; #ifndef NDEBUG (void)bpk; #endif @@ -99,10 +100,7 @@ Tx::Tx(const Params& p, isSplitOwnCoins = true; // true when this TXN simply splits the sender's coins is_budgeted = b.has_value(); // true when this TXN is budgeted - if (!budgetPolicy && is_budgeted) { - logerror << "budget policy is false, but a budget coin was given" << endl; - throw std::runtime_error("budget policy is false, but a budget coin was given"); - } + size_t totalIn = Coin::totalValue(coins); // total in value size_t totalOut = 0; // total out value size_t paidOut = 0; // total amount paid out to someone else @@ -141,7 +139,16 @@ Tx::Tx(const Params& p, forMeOutputs.insert(j); } } + budgetPolicy = isSplitOwnCoins ? false : budget_policy; + if (!budgetPolicy && is_budgeted) { + logerror << "budget policy is disabled, but a budget coin was given" << endl; + throw std::runtime_error("budget policy is false, but a budget coin was given"); + } + if (budgetPolicy && !is_budgeted) { + logerror << "budget policy is enabled, but the budget is missing" << endl; + throw std::runtime_error("budget policy is enabled, but the budget coin is missing"); + } // are you spending more than you have? if (totalIn != totalOut) { logerror << "Total-in is " << totalIn << " but total-out is " << totalOut << endl; @@ -384,18 +391,20 @@ bool Tx::quickPayValidate(const Params& p, const RandSigPK& bpk, const RegAuthPK return false; } - if (budget_policy) { - if (!budgetPolicy) { - logerror << "budget policy is enforced but the transaction's budget policy is false" << endl; - return false; - } - } - if (budget_policy && !isSplitOwnCoins && !is_budgeted) { logerror << "budget policy is enforced and budget is needed, but the transaction doesn't have a budget coin" << endl; return false; } + if (!budget_policy && !isSplitOwnCoins && is_budgeted) { + logerror << "budget policy is disabled and budget is not needed, but the transaction does have a budget coin" + << endl; + return false; + } + + if ((budget_policy && !budgetPolicy) || (!budget_policy && budgetPolicy)) { + logerror << "mismatch between transaction's budget policy and the configuration budget policy" << endl; + } assertTrue(!is_budgeted || budget_pi.has_value()); diff --git a/utt/libutt/src/api/transaction.cpp b/utt/libutt/src/api/transaction.cpp index 0582bfdf11..659ae8daeb 100644 --- a/utt/libutt/src/api/transaction.cpp +++ b/utt/libutt/src/api/transaction.cpp @@ -21,8 +21,7 @@ Transaction::Transaction(const UTTParams& d, const std::vector& coins, const std::optional& bc, const std::vector>& recipients, - const IEncryptor& encryptor, - bool budget_policy) { + const IEncryptor& encryptor) { Fr fr_pidhash; fr_pidhash.from_words(cid.getPidHash()); Fr prf; @@ -59,7 +58,7 @@ Transaction::Transaction(const UTTParams& d, std::nullopt, rpk.vk, encryptor, - budget_policy)); + d.getBudgetPolicy())); } Transaction::Transaction() { tx_.reset(new libutt::Tx()); } Transaction::Transaction(const Transaction& other) { diff --git a/utt/libutt/test/TestTxn.cpp b/utt/libutt/test/TestTxn.cpp index 2fbdc8b2ed..6de8fd7c6d 100644 --- a/utt/libutt/test/TestTxn.cpp +++ b/utt/libutt/test/TestTxn.cpp @@ -131,7 +131,7 @@ void testBudgeted2to2Txn(size_t thresh, size_t n, size_t numCycles, bool isBudge testAssertFail("TXN should have QuickPay-verified"); } } else { - if (!tx.validate(p, bpk, rpk)) { + if (!tx.validate(p, bpk, rpk, true)) { testAssertFail("TXN should have verified"); } } diff --git a/utt/tests/TestUttNewApi.cpp b/utt/tests/TestUttNewApi.cpp index 7e87dc13d6..0e1590c334 100644 --- a/utt/tests/TestUttNewApi.cpp +++ b/utt/tests/TestUttNewApi.cpp @@ -238,7 +238,7 @@ TEST_F(ibe_based_test_system_minted, test_invalid_burn) { } TEST_F(ibe_based_test_system, test_serialization_configuration) { - auto config = libutt::api::Configuration((uint16_t)n, (uint16_t)thresh); + auto config = libutt::api::Configuration((uint16_t)n, (uint16_t)thresh, true); ASSERT_TRUE(config.isValid()); auto serialized_config = libutt::api::serialize(config); auto deserialized_config = libutt::api::deserialize(serialized_config); @@ -566,24 +566,38 @@ TEST_F(ibe_based_test_system_minted, test_transaction_without_budget) { for (size_t i = 0; i < clients.size(); i++) { auto& issuer = clients[i]; auto& receiver = clients[(i + 1) % clients.size()]; + // We let the client to cheat and create a non-budgeted transaction + d.budget_policy = false; Transaction tx(d, issuer, {coins[issuer.getPid()].front()}, std::nullopt, {{issuer.getPid(), 50}, {receiver.getPid(), 50}}, - *(encryptors_.at((i + 1) % clients.size())), - true); - for (auto& b : banks) { - ASSERT_FALSE(b->validate(d, tx)); - } + *(encryptors_.at((i + 1) % clients.size()))); ASSERT_ANY_THROW(Transaction(d, issuer, {coins[issuer.getPid()].front()}, {bcoins[issuer.getPid()].front()}, {{issuer.getPid(), 50}, {receiver.getPid(), 50}}, - *(encryptors_.at((i + 1) % clients.size())), - false)); + *(encryptors_.at((i + 1) % clients.size())))); + + d.budget_policy = true; + for (auto& b : banks) { + ASSERT_FALSE(b->validate(d, tx)); + } + + Transaction tx2(d, + issuer, + {coins[issuer.getPid()].front()}, + {bcoins[issuer.getPid()].front()}, + {{issuer.getPid(), 50}, {receiver.getPid(), 50}}, + *(encryptors_.at((i + 1) % clients.size()))); + + d.budget_policy = false; + for (auto& b : banks) { + ASSERT_FALSE(b->validate(d, tx2)); + } } } @@ -600,8 +614,7 @@ TEST_F(ibe_based_test_system_minted_budget_policy_disabled, test_transaction) { {coins[issuer.getPid()].front()}, std::nullopt, {{issuer.getPid(), 50}, {receiver.getPid(), 50}}, - *(encryptors_.at((i + 1) % clients.size())), - false); + *(encryptors_.at((i + 1) % clients.size()))); coins[issuer.getPid()].erase(coins[issuer.getPid()].begin()); std::map> shares; std::vector shares_signers;