/*++ Program name: BitDeals Module Name: Deal.cpp Notices: Apostol Core (Deal) Author: Copyright (c) Prepodobny Alen mailto: alienufo@inbox.ru mailto: ufocomp@gmail.com --*/ #include "Core.hpp" #include "Deal.hpp" //---------------------------------------------------------------------------------------------------------------------- #ifdef __cplusplus extern "C++" { #endif // __cplusplus namespace Apostol { namespace Deal { CString UTCFormat(const CString& Value) { return Value.SubString(0, 19) << " UTC"; } //-------------------------------------------------------------------------------------------------------------- CString BTCFormat(const CString& Value) { if (Value.Find(BitcoinConfig.Symbol) == CString::npos) return Value.TrimRight('0') << " " << BitcoinConfig.Symbol; return Value.TrimRight('0'); } //-------------------------------------------------------------------------------------------------------------- double BTCToDouble(const CString &Value) { const size_t Pos = Value.Find(BitcoinConfig.Symbol); if (Pos == CString::npos) return StrToDouble(Value.c_str()); return StrToDouble(Value.SubString(0, Pos).TrimRight().c_str()); } //-------------------------------------------------------------------------------------------------------------- CString DoubleToBTC(const double &Value, const CString &Format) { TCHAR Buffer[25] = {0}; FloatToStr(Value, Buffer, sizeof(Buffer), Format.c_str()); return Buffer; } //-------------------------------------------------------------------------------------------------------------- int CheckSum(const CString &Sum) { if (Sum.IsEmpty()) return 0; size_t whole = 0, fractional = 0, delimiter = 0; size_t pos = 0; TCHAR ch; ch = Sum.at(pos); while (ch != 0) { if (IsNumeral(ch)) { if (delimiter == 0) whole++; else fractional++; } else if (ch == '.') { delimiter++; } else if (ch == ' ') { break; } else { return -1; } ch = Sum.at(++pos); } if (whole == 0 || delimiter > 1 || fractional > 8) return -1; return 1; } //-------------------------------------------------------------------------------------------------------------- int VerifyDate(const CString &Date, CDateTime &Result) { if (!Date.IsEmpty()) { if (Date.Length() < 10) return 0; CString StrDate(Date); int pos = 0; TCHAR ch = StrDate.at(pos); while (ch != 0 && (IsNumeral(ch) || ((pos == 4 || pos == 7) && (ch == '-' || ch == '/')) || (pos == 10 && ch == ' ') || ((pos == 13 || pos == 16) && ch == ':'))) { ch = StrDate.at(++pos); } if (pos == 10) StrDate << " 00:00:00"; if (StrDate.Length() != 19 && StrDate.Length() != 23) { return 0; } try { if ((Result = StrToDateTimeDef(StrDate.c_str(), 0, "%04d-%02d-%02d %02d:%02d:%02d")) == 0) return -1; return 1; } catch (...) { return 0; } } return 0; } //-------------------------------------------------------------------------------------------------------------- CDateTime StringToDate(const CString &Value, const CString &Format) { return StrToDateTimeDef(Value.c_str(), 0, Format.c_str()); } //-------------------------------------------------------------------------------------------------------------- CString DateToString(CDateTime Value, const CString &Format) { TCHAR Buffer[25] = {0}; DateTimeToStr(Value, Buffer, sizeof(Buffer), Format.c_str()); return Buffer; } //-------------------------------------------------------------------------------------------------------------- //-- DealData -------------------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------------------------------- CDealOrder DealData::StringToOrder(const CString &Value) { const CString &S = Value.Lower(); if (S == "create") { return doCreate; } else if (S == "created") { return doCreated; } else if (S == "submit") { return doSubmit; } else if (S == "submitted") { return doSubmitted; } else if (S == "confirm") { return doConfirm; } else if (S == "confirmed") { return doConfirmed; } else if (S == "pay") { return doPay; } else if (S == "paid") { return doPaid; } else if (S == "complete") { return doComplete; } else if (S == "completed") { return doCompleted; } else if (S == "cancel") { return doCancel; } else if (S == "canceled") { return doCanceled; } else if (S == "execute") { return doExecute; } else if (S == "executed") { return doExecuted; } else if (S == "delete") { return doDelete; } else if (S == "deleted") { return doDeleted; } else if (S == "fail") { return doFail; } else if (S == "failed") { return doFailed; } else if (S == "feedback") { return doFeedback; } else { throw ExceptionFrm("Invalid deal order value: \"%s\".", Value.c_str()); } } //-------------------------------------------------------------------------------------------------------------- CString DealData::OrderToString(CDealOrder Order) { switch (Order) { case doCreate: return "Create"; case doCreated: return "Created"; case doSubmit: return "Submit"; case doSubmitted: return "Submitted"; case doConfirm: return "Confirm"; case doConfirmed: return "Confirmed"; case doPay: return "Pay"; case doPaid: return "Paid"; case doComplete: return "Complete"; case doCompleted: return "Completed"; case doCancel: return "Cancel"; case doCanceled: return "Canceled"; case doExecute: return "Execute"; case doExecuted: return "Executed"; case doDelete: return "Delete"; case doDeleted: return "Deleted"; case doFail: return "Fail"; case doFailed: return "Failed"; case doFeedback: return "Feedback"; default: return "Unknown"; } } //-------------------------------------------------------------------------------------------------------------- CDealType DealData::StringToType(const CString &Value) { const auto &status = Value.Lower(); if (status.Find("prepayment") != CString::npos) { return dtPrepayment; } else if (status.Find("postpayment") != CString::npos) { return dtPostpayment; } else { throw ExceptionFrm(R"(Invalid order type value: "%s".)", status.c_str()); } } //-------------------------------------------------------------------------------------------------------------- CString DealData::TypeToString(CDealType Type) { switch (Type) { case dtPrepayment: return "Prepayment"; case dtPostpayment: return "Postpayment"; default: return "Unknown"; } } //-------------------------------------------------------------------------------------------------------------- CString DealData::TypeCodeToString(const CString &Code) { if (Code == "prepayment.deal") { return {"Prepayment"}; } else if (Code == "postpayment.deal") { return {"Postpayment"}; } else { throw ExceptionFrm(R"(Invalid type code value: "%s".)", Code.c_str()); } } //-------------------------------------------------------------------------------------------------------------- CFeedBackStatus DealData::StringToFeedBackStatus(const CString &Value) { const CString &Status = Value.Lower(); if (Status == "negative") { return fsNegative; } else if (Status == "neutral") { return fsNeutral; } else if (Status == "positive") { return fsPositive; } else { throw ExceptionFrm(R"(Invalid feedback status value: "%s".)", Status.c_str()); } } //-------------------------------------------------------------------------------------------------------------- CString DealData::FeedBackStatusToString(CFeedBackStatus Status) { switch (Status) { case fsNegative: return "Negative"; case fsNeutral: return "Neutral"; case fsPositive: return "Positive"; default: return "Unknown"; } } //-------------------------------------------------------------------------------------------------------------- CString DealData::GetStringData() const { CString Data; CString S; Data = S.Format("Type: %d;", (int) Type); Data += S.Format("URL: %s;", At.c_str()); Data += S.Format("Date: %s;", Date.c_str()); Data += S.Format("Seller: %s;", Seller.Address.c_str()); Data += S.Format("Customer: %s;", Customer.Address.c_str()); Data += S.Format("Until: %s;", Payment.Until.c_str()); Data += S.Format("Sum: %s;", Payment.Sum.c_str()); Data += S.Format("LeaveBefore: %s", FeedBack.LeaveBefore.c_str()); return Data; } //-------------------------------------------------------------------------------------------------------------- //-- CRating --------------------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------------------------------- void CRating::Parse(const CString &Value) { size_t Pos = 0; TCHAR ch = Value.at(Pos++); if (Value.Size() < 4 || !IsNumeral(ch)) throw ExceptionFrm("Invalid rating value."); CString Item; while (!IsCtl(ch)) { if (ch == '+' && !Item.IsEmpty()) { Count = StrToIntDef(Item.c_str(), 0); Count++; Item.Clear(); } else if (ch == ',' && !Item.IsEmpty()) { Count = StrToIntDef(Item.c_str(), 0); Item.Clear(); } else if (ch == '%' && !Item.IsEmpty()) { Positive = StrToIntDef(Item.c_str(), 0); Item.Clear(); } else { if (!(ch == ' ' || ch == ',')) { if (!IsNumeral(ch)) throw ExceptionFrm("Invalid rating value."); Item << ch; } } ch = Value.at(Pos++); } } //-------------------------------------------------------------------------------------------------------------- //-- CDeal ----------------------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------------------------------- CDeal::CDeal() { m_Data.Order = doCreate; m_Data.FeedBack.Status = fsPositive; } //-------------------------------------------------------------------------------------------------------------- CDeal::CDeal(const YAML::Node &node) : CDeal() { Parse(node); } //-------------------------------------------------------------------------------------------------------------- CString CDeal::GetHashData() { return m_Data.GetStringData(); } //-------------------------------------------------------------------------------------------------------------- std::string CDeal::get_hash() { const std::string str(GetHashData().c_str()); return sha256(str); } //-------------------------------------------------------------------------------------------------------------- std::string CDeal::get_code() { const std::string value(GetHashData().c_str()); const auto& hex = string_to_hex(value); const config::base16 data(hex); const auto& hash = ripemd160_hash(sha256_hash(data)); return encode_base16(hash); } //-------------------------------------------------------------------------------------------------------------- CString CDeal::GetCode() { return get_code(); } //-------------------------------------------------------------------------------------------------------------- CString CDeal::GetHash() { return sha256(GetHashData()); } //-------------------------------------------------------------------------------------------------------------- wallet::ec_public CDeal::to_public_ek(uint8_t version) { const auto& hash = get_hash(); const std::string seed(hash.substr(0, 48)); const std::string salt(hash.substr(48, 16)); const auto& token = Bitcoin::token_new(hash, base16(salt)); const auto& key = Bitcoin::ek_public(token, base16(seed), version); return ek_public_to_ec(hash, key); } //-------------------------------------------------------------------------------------------------------------- wallet::payment_address CDeal::to_address_ek(uint8_t version) { const auto& hash = get_hash(); const std::string seed(hash.substr(0, 48)); const std::string salt(hash.substr(48, 16)); const auto& token = Bitcoin::token_new(hash, base16(salt)); return Bitcoin::ek_address(token, base16(seed), version); } //-------------------------------------------------------------------------------------------------------------- wallet::ec_public CDeal::to_public_hd(uint64_t prefixes) { const auto& hash = get_hash(); const auto& key = hd_new(base16(hash), prefixes); const auto& public_key = key.to_public(); return public_key.point(); } //-------------------------------------------------------------------------------------------------------------- wallet::payment_address CDeal::to_address_hd(uint64_t prefixes) { const auto& key = to_public_hd(prefixes); return key.to_payment_address(); } //-------------------------------------------------------------------------------------------------------------- std::string CDeal::get_payment_ek(const std::string &key1, const std::string &key2, std::string &key3, uint8_t version_key, uint8_t version_script) { CWitness Witness(ec_public(key1), ec_public(key2), key3.empty() ? to_public_ek(version_key) : ec_public(key3)); if (key3.empty()) key3 = Witness.keys()[2].encoded(); const auto& address = Witness.to_address(version_script); return address.encoded(); } //-------------------------------------------------------------------------------------------------------------- std::string CDeal::get_payment_hd(const std::string &key1, const std::string &key2, std::string &key3, uint64_t prefixes, uint8_t version_script) { CWitness Witness(ec_public(key1), ec_public(key2), key3.empty() ? to_public_hd(prefixes) : ec_public(key3)); if (key3.empty()) key3 = Witness.keys()[2].encoded(); const auto& address = Witness.to_address(version_script); return address.encoded(); } //-------------------------------------------------------------------------------------------------------------- std::string CDeal::get_payment_witness(const std::string &key1, const std::string &key2, std::string &key3, uint64_t prefixes) { CWitness Witness(ec_public(key1), ec_public(key2), key3.empty() ? to_public_hd(prefixes) : ec_public(key3)); if (key3.empty()) key3 = Witness.keys()[2].encoded(); return Witness.to_witness(prefixes == hd_private::mainnet ? "bc" : "tb"); } //-------------------------------------------------------------------------------------------------------------- CString CDeal::GetPaymentEK(const CString &Key1, const CString &Key2, CString &Key3, uint8_t version_key, uint8_t version_script) { std::string key3(Key3); const auto& payment = get_payment_ek(Key1.c_str(), Key2.c_str(), key3, version_key, version_script); Key3 = key3; return payment; } //-------------------------------------------------------------------------------------------------------------- CString CDeal::GetPaymentHD(const CString &Key1, const CString &Key2, CString &Key3, uint64_t prefixes, uint8_t version_script) { std::string key3(Key3); const auto& payment = get_payment_hd(Key1.c_str(), Key2.c_str(), key3, prefixes, version_script); Key3 = key3; return payment; } //-------------------------------------------------------------------------------------------------------------- CString CDeal::GetPaymentSW(const CString &Key1, const CString &Key2, CString &Key3, uint64_t prefixes) { std::string key3(Key3); const auto& payment = get_payment_witness(Key1.c_str(), Key2.c_str(), key3, prefixes); Key3 = key3; return payment; } //-------------------------------------------------------------------------------------------------------------- void CDeal::Parse(const YAML::Node &node) { const auto& deal = node["deal"]; if (deal) { m_Data.Order = CDealData::StringToOrder(deal["order"].as()); m_Data.Type = CDealData::StringToType(deal["type"].as()); m_Data.At = deal["at"].as(); const auto &date = deal["date"].as(); CDateTime Date; if (VerifyDate(date, Date) != 1) throw ExceptionFrm("Invalid deal date format: %s.", date.c_str()); m_Data.Date = DateToString(Date); const auto& seller = deal["seller"]; m_Data.Seller.Address = seller["address"].as(); if (!valid_address(m_Data.Seller.Address)) throw ExceptionFrm("Invalid seller address: %s.", m_Data.Seller.Address.c_str()); if (seller["rating"]) m_Data.Seller.Rating = seller["rating"].as(); if (seller["signature"]) m_Data.Seller.Signature = seller["signature"].as(); const auto& customer = deal["customer"]; m_Data.Customer.Address = customer["address"].as(); if (!valid_address(m_Data.Customer.Address)) throw ExceptionFrm("Invalid customer address: %s.", m_Data.Customer.Address.c_str()); if (customer["rating"]) m_Data.Customer.Rating = customer["rating"].as(); if (customer["signature"]) m_Data.Customer.Signature = customer["signature"].as(); const auto& payment = deal["payment"]; if (payment) { if (payment["address"]) m_Data.Payment.Address = payment["address"].as(); if (payment["until"]) { const auto &until = payment["until"].as(); CDateTime Until; if (VerifyDate(until, Until) != 1) throw ExceptionFrm("Invalid until date format: %s.", until.c_str()); m_Data.Payment.Until = DateToString(Until); } const auto &sum = payment["sum"].as(); if (CheckSum(sum) != 1) throw ExceptionFrm("Invalid format sum: %s.", sum.c_str()); m_Data.Payment.Sum = BTCFormat(sum); } const auto& feedback = deal["feedback"]; if (feedback) { if (feedback["leave-before"]) { const auto &leave_before = feedback["leave-before"].as(); CDateTime LeaveBefore; if (VerifyDate(leave_before, LeaveBefore) != 1) throw ExceptionFrm("Invalid leave before date format: %s.", leave_before.c_str()); m_Data.FeedBack.LeaveBefore = DateToString(LeaveBefore); } if (feedback["status"]) m_Data.FeedBack.Status = CDealData::StringToFeedBackStatus(feedback["status"].as()); if (feedback["refund"]) m_Data.FeedBack.Refund = feedback["refund"].as(); if (feedback["comments"]) m_Data.FeedBack.Comments = feedback["comments"].as(); } const auto& transaction = deal["transaction"]; if (transaction) { m_Data.Transaction.Hex = transaction["hex"].as(); } const auto& error = deal["error"]; if (error) { m_Data.Error.Message = error["message"].as(); } if (m_Data.Payment.Until.IsEmpty()) m_Data.Payment.Until = DateToString(Date + 1); // 1 day if (m_Data.FeedBack.LeaveBefore.IsEmpty()) m_Data.FeedBack.LeaveBefore = DateToString(Date + 14); // 14 day m_Data.Code = GetCode(); } else throw ExceptionFrm("Invalid YAML format: Need node \"deal\"."); } //-------------------------------------------------------------------------------------------------------------- void CDeal::SetOrder(YAML::Node &Node, CDealOrder Order) { Node["deal"]["order"] = CDealData::OrderToString(Order).c_str(); } //-------------------------------------------------------------------------------------------------------------- void CDeal::AddFeedback(YAML::Node &Node, CFeedBackStatus Status, const CString &Comments) { YAML::Node Deal = Node["deal"]; YAML::Node Feedback = Deal["feedback"]; Feedback["status"] = CDealData::FeedBackStatusToString(Status).c_str(); if (!Comments.IsEmpty()) Feedback["comments"] = Comments.c_str(); } //-------------------------------------------------------------------------------------------------------------- void CDeal::AddTransaction(YAML::Node &Node, const transaction &tx) { YAML::Node Deal = Node["deal"]; YAML::Node Transaction = Deal["transaction"]; const auto& hex = encode_base16(tx.to_data(true, true)); Transaction["hex"] = hex; DebugMessage("tx: %s\n", hex.c_str()); } //-------------------------------------------------------------------------------------------------------------- void CDeal::AddError(YAML::Node &Node, const CString &Message) { YAML::Node Deal = Node["deal"]; YAML::Node Error = Deal["error"]; Error["message"] = Message.c_str(); } } } #ifdef __cplusplus } #endif // __cplusplus