Skip to content

Commit e496f88

Browse files
Merge pull request #123 from thibault-martinez/init-transfer
initTransfer & some utils functions
2 parents 02af41d + 880ab9a commit e496f88

File tree

13 files changed

+257
-31
lines changed

13 files changed

+257
-31
lines changed

include/API/Extended.hpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,20 @@ class Extended : public Core {
332332
replayBundleResponse replayBundle(const Type::Trytes& transaction, int depth,
333333
int minWeightMagnitude);
334334

335+
/**
336+
* Prepares transfer by generating the bundle with the corresponding cosigner transactions.
337+
* Does not contain signatures.
338+
*
339+
* @param securitySum The sum of security levels used by all co-signers.
340+
* @param inputAddress Array of input addresses as well as the securitySum.
341+
* @param remainderAddress Has to be generated by the cosigners before initiating the transfer,
342+
* can be null if fully spent.
343+
* @return Bundle of transaction objects.
344+
*/
345+
std::vector<Transaction> initiateTransfer(int securitySum, const Type::Trytes& inputAddress,
346+
const Type::Trytes& remainderAddress,
347+
std::vector<Transfer>& transfers);
348+
335349
private:
336350
/**
337351
* Generates a new address.
@@ -351,6 +365,11 @@ class Extended : public Core {
351365
const std::string& seed, const std::vector<input>& inputs, Bundle& bundle,
352366
const std::vector<std::string>& signatureFragments) const;
353367

368+
/**
369+
* @return true if all transfers are valid, false otherwise
370+
*/
371+
bool isTransfersCollectionValid(const std::vector<Transfer>& transfers);
372+
354373
private:
355374
/**
356375
* crypto algorithm to be used internally

include/Crypto/Checksum.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class Checksum {
3939
public:
4040
Type::Trytes add(const Type::Trytes& address);
4141
Type::Trytes remove(const Type::Trytes& address) const;
42+
bool isValid(const Type::Trytes& address);
4243

4344
private:
4445
Type::Trytes check(const Type::Trytes& address);

include/Model/Transfer.hpp

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class Transfer {
4444
*
4545
* @return The address.
4646
*/
47-
const IOTA::Type::Trytes& getAddress();
47+
const IOTA::Type::Trytes& getAddress() const;
4848

4949
/**
5050
* Set the address.
@@ -58,7 +58,7 @@ class Transfer {
5858
*
5959
* @return The hash.
6060
*/
61-
const IOTA::Type::Trytes& getHash();
61+
const IOTA::Type::Trytes& getHash() const;
6262

6363
/**
6464
* Set the hash.
@@ -72,7 +72,7 @@ class Transfer {
7272
*
7373
* @return The persistence.
7474
*/
75-
bool getPersistence();
75+
bool getPersistence() const;
7676

7777
/**
7878
* Set the persistence.
@@ -86,7 +86,7 @@ class Transfer {
8686
*
8787
* @return The timestamp.
8888
*/
89-
const std::string& getTimestamp();
89+
const std::string& getTimestamp() const;
9090

9191
/**
9292
* Set the timestamp.
@@ -100,7 +100,7 @@ class Transfer {
100100
*
101101
* @return The value.
102102
*/
103-
int64_t getValue();
103+
int64_t getValue() const;
104104

105105
/**
106106
* Set the value.
@@ -114,7 +114,7 @@ class Transfer {
114114
*
115115
* @return The message.
116116
*/
117-
const std::string& getMessage();
117+
const std::string& getMessage() const;
118118

119119
/**
120120
* Set the message.
@@ -128,7 +128,7 @@ class Transfer {
128128
*
129129
* @return The tag.
130130
*/
131-
const std::string& getTag();
131+
const std::string& getTag() const;
132132

133133
/**
134134
* Set the tag.
@@ -137,6 +137,11 @@ class Transfer {
137137
*/
138138
void setTag(const std::string& tag);
139139

140+
/**
141+
* @return <code>true</code> if the transfer is valid; otherwise, <code>false</code>>.
142+
**/
143+
bool isValid() const;
144+
140145
private:
141146
std::string timestamp_;
142147
IOTA::Type::Trytes address_;

include/Type/Trinary.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,16 @@ bool isValidTryte(const char& tryte);
4343
*/
4444
bool isValidTrytes(const Trytes& trytes);
4545

46+
/**
47+
* @return whether the given trit is valid or not
48+
*/
49+
bool isValidTrit(const int8_t& trit);
50+
51+
/**
52+
* @return whether the given address is valid or not
53+
*/
54+
bool isValidAddress(const Trytes& s);
55+
4656
/**
4757
* Determines whether the specified array contains only valid hashes.
4858
*

include/Type/utils.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ namespace Type {
3333

3434
namespace Utils {
3535

36-
bool isValidTrit(const int8_t& trit);
36+
std::string rightPad(const std::string& s, std::size_t padLen, char padChar = ' ');
3737

3838
} // namespace Utils
3939

include/Utils/StopWatch.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ class StopWatch {
7979
*/
8080
std::chrono::hours getElapsedTimeHours();
8181

82-
private:
82+
public:
8383
/**
8484
* @return Current ts
8585
*/

include/constants.hpp

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,18 @@
2525

2626
#include <iostream>
2727

28-
const std::string TryteAlphabet = "9ABCDEFGHIJKLMNOPQRSTUVWXYZ";
29-
const unsigned int ChecksumLength = 9;
30-
const unsigned int TryteAlphabetLength = 27;
31-
const unsigned int FragmentLength = 27;
32-
const unsigned int SeedLength = 81;
33-
const unsigned int SeedLengthWithChecksum = 90;
34-
const unsigned int ByteHashLength = 48;
35-
const unsigned int TritHashLength = 243;
28+
const std::string TryteAlphabet = "9ABCDEFGHIJKLMNOPQRSTUVWXYZ";
29+
const unsigned int ChecksumLength = 9;
30+
const unsigned int TryteAlphabetLength = 27;
31+
const unsigned int FragmentLength = 27;
32+
const unsigned int TagLength = 27;
33+
const unsigned int SeedLength = 81;
34+
const unsigned int SeedLengthWithChecksum = 90;
35+
const unsigned int AddressLength = 81;
36+
const unsigned int AddressLengthWithChecksum = 90;
37+
const unsigned int ByteHashLength = 48;
38+
const unsigned int TritHashLength = 243;
39+
const unsigned int MaxTrxMsgLength = 2187;
3640

3741
const std::string EmptyHash =
3842
"999999999999999999999999999999999999999999999999999999999999999999999999999999999";

source/API/Extended.cpp

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include <Model/Signature.hpp>
3333
#include <Model/Transaction.hpp>
3434
#include <Type/Seed.hpp>
35+
#include <Type/utils.hpp>
3536

3637
namespace IOTA {
3738

@@ -639,6 +640,142 @@ Extended::replayBundle(const Type::Trytes& transaction, int depth, int minWeight
639640
return { successful, stopWatch.getElapsedTimeMiliSeconds().count() };
640641
}
641642

643+
std::vector<Transaction>
644+
Extended::initiateTransfer(int securitySum, const std::string& inputAddress,
645+
const std::string& remainderAddress, std::vector<Transfer>& transfers) {
646+
Utils::StopWatch sw;
647+
Crypto::Checksum checksum;
648+
649+
//! If message or tag is not supplied, provide it
650+
//! Also remove the checksum of the address if it's there
651+
652+
for (auto& transfer : transfers) {
653+
if (transfer.getMessage().empty()) {
654+
transfer.setMessage(Type::Utils::rightPad(transfer.getMessage(), 2187, '9'));
655+
}
656+
657+
if (transfer.getTag().empty()) {
658+
transfer.setTag(Type::Utils::rightPad(transfer.getTag(), 27, '9'));
659+
}
660+
661+
if (checksum.isValid(transfer.getAddress())) {
662+
transfer.setAddress(checksum.remove(transfer.getAddress()));
663+
}
664+
}
665+
666+
// Input validation of transfers object
667+
if (!isTransfersCollectionValid(transfers)) {
668+
throw Errors::IllegalState("Invalid transfer");
669+
}
670+
671+
//! validate input address
672+
if (!Type::isValidAddress(inputAddress)) {
673+
throw Errors::IllegalState("Invalid address");
674+
}
675+
676+
// validate remainder address
677+
if (!remainderAddress.empty() && !Type::isValidAddress(remainderAddress)) {
678+
throw Errors::IllegalState("Invalid bundle");
679+
}
680+
681+
//! Create a new bundle
682+
Bundle bundle;
683+
int totalValue = 0;
684+
std::vector<std::string> signatureFragments;
685+
std::string tag;
686+
687+
//! Iterate over all transfers, get totalValue
688+
//! and prepare the signatureFragments, message and tag
689+
for (const auto& transfer : transfers) {
690+
int signatureMessageLength = 1;
691+
692+
//! If message longer than 2187 trytes, increase signatureMessageLength (add 2nd transaction)
693+
if (transfer.getMessage().length() > MaxTrxMsgLength) {
694+
//! Get total length, message / maxLength (MaxTrxMsgLength trytes)
695+
signatureMessageLength += std::floor(transfer.getMessage().length() / MaxTrxMsgLength);
696+
697+
//! copy msg
698+
std::string msgCopy = transfer.getMessage();
699+
700+
//! While there is still a message, copy it
701+
while (!msgCopy.empty()) {
702+
std::string fragment = msgCopy.substr(0, MaxTrxMsgLength);
703+
msgCopy = msgCopy.substr(MaxTrxMsgLength, msgCopy.length());
704+
705+
// Pad remainder of fragment
706+
signatureFragments.push_back(Type::Utils::rightPad(fragment, MaxTrxMsgLength, '9'));
707+
}
708+
} else {
709+
//! Else, get single fragment with MaxTrxMsgLength of 9's trytes
710+
signatureFragments.push_back(
711+
Type::Utils::rightPad(transfer.getMessage(), MaxTrxMsgLength, '9'));
712+
}
713+
714+
//! get current timestamp in seconds
715+
long timestamp = sw.now().count();
716+
717+
//! If no tag defined, get 27 tryte tag.
718+
if (transfer.getTag().empty()) {
719+
tag = Type::Utils::rightPad(transfer.getTag(), 27, '9');
720+
}
721+
722+
//! Pad for required TagLength tryte length
723+
tag = Type::Utils::rightPad(tag, TagLength, '9');
724+
725+
//! Add first entry to the bundle
726+
bundle.addTransaction(signatureMessageLength, transfer.getAddress(), transfer.getValue(), tag,
727+
timestamp);
728+
729+
//! Sum up total value
730+
totalValue += transfer.getValue();
731+
}
732+
733+
//! Get inputs if we are sending tokens
734+
if (totalValue == 0) {
735+
throw Errors::IllegalState("Invalid value transfer");
736+
}
737+
738+
long totalBalance = 0;
739+
740+
for (const auto& balance : getBalances({ inputAddress }, 100).getBalances()) {
741+
totalBalance += std::atol(balance.c_str());
742+
}
743+
744+
// get current timestamp in seconds
745+
long timestamp = sw.now().count();
746+
747+
if (totalBalance > 0) {
748+
long toSubtract = -totalBalance;
749+
750+
//! Add input as bundle entry
751+
//! Only a single entry, signatures will be added later
752+
bundle.addTransaction(securitySum, inputAddress, toSubtract, tag, timestamp);
753+
}
754+
755+
//! Return not enough balance error
756+
if (totalValue > totalBalance) {
757+
throw Errors::IllegalState("Not enough balance");
758+
}
759+
760+
//! If there is a remainder value
761+
//! Add extra output to send remaining funds to
762+
if (totalBalance > totalValue) {
763+
long remainder = totalBalance - totalValue;
764+
765+
// Remainder bundle entry if necessary
766+
if (remainderAddress.empty()) {
767+
throw Errors::IllegalState("No remainder address defined");
768+
}
769+
770+
bundle.addTransaction(1, remainderAddress, remainder, tag, timestamp);
771+
}
772+
773+
bundle.finalize(Crypto::create(cryptoType_));
774+
bundle.addTrytes(signatureFragments);
775+
776+
return bundle.getTransactions();
777+
}
778+
642779
/*
643780
* Private methods.
644781
*/
@@ -749,6 +886,17 @@ Extended::signInputsAndReturn(const std::string& seed, const std::vector<input>&
749886
return bundleTrytes;
750887
}
751888

889+
bool
890+
Extended::isTransfersCollectionValid(const std::vector<Transfer>& transfers) {
891+
for (const auto& transfer : transfers) {
892+
if (!transfer.isValid()) {
893+
return false;
894+
}
895+
}
896+
897+
return true;
898+
}
899+
752900
} // namespace API
753901

754902
} // namespace IOTA

source/Crypto/Checksum.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ Checksum::remove(const Type::Trytes& address) const {
4646
return address.substr(0, SeedLength);
4747
}
4848

49+
bool
50+
Checksum::isValid(const Type::Trytes& addressWithChecksum) {
51+
auto addressWithoutChecksum = remove(addressWithChecksum);
52+
auto addressWithRecalculateChecksum = add(addressWithoutChecksum);
53+
54+
return addressWithRecalculateChecksum == addressWithChecksum;
55+
}
56+
4957
Type::Trytes
5058
Checksum::check(const Type::Trytes& address) {
5159
Type::Trits checksumTrits(TritHashLength);

source/Model/Bundle.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include <Crypto/SpongeFactory.hpp>
3030
#include <Model/Bundle.hpp>
3131
#include <Type/Trinary.hpp>
32+
#include <Type/utils.hpp>
3233
#include <constants.hpp>
3334

3435
Bundle::Bundle(const std::vector<Transaction>& transactions) : transactions_(transactions) {
@@ -106,10 +107,7 @@ Bundle::finalize(const std::shared_ptr<IOTA::Crypto::ISponge>& customSponge) {
106107

107108
void
108109
Bundle::addTrytes(const std::vector<std::string>& signatureFragments) {
109-
std::ostringstream ss;
110-
ss << std::setfill('9') << std::setw(2187) << "";
111-
112-
std::string emptySignatureFragment = ss.str();
110+
std::string emptySignatureFragment = IOTA::Type::Utils::rightPad("", 2187, '9');
113111

114112
for (unsigned int i = 0; i < transactions_.size(); i++) {
115113
auto& transaction = transactions_[i];

0 commit comments

Comments
 (0)