Status AddressDb::getNew(Address &result) { std::lock_guard<std::mutex> lock(mutex_); // Use a set to find the lowest available index: std::set<size_t> indices; for (auto &address: addresses_) if (address.second.recyclable) indices.insert(address.second.index); // The stockpile should prevent this from ever happening: if (indices.empty()) return ABC_ERROR(ABC_CC_NoAvailableAddress, "Address stockpile depleted!"); size_t index = *indices.begin(); // Verify that we can still re-derive the address: auto i = addresses_.find(mainBranch(wallet_).generate_private_key(index). address().encoded()); if (addresses_.end() == i) return ABC_ERROR(ABC_CC_Error, "Address corruption at index " + std::to_string(index)); result = i->second; return Status(); }
/** * Checks an hits key for validity. */ static Status hbitsOk(const std::string &text) { if (text.size() != 22 && text.size() != 30) return ABC_ERROR(ABC_CC_ParseError, "Wrong text length"); if (0x00 != bc::sha256_hash(bc::to_data_chunk(text + "!"))[0]) return ABC_ERROR(ABC_CC_ParseError, "Wrong text checksum"); return Status(); }
Status loginServerOtpPending(std::list<DataChunk> users, std::list<bool> &isPending) { const auto url = ABC_SERVER_ROOT "/v1/otp/pending/check"; std::string param; std::map<std::string, bool> userMap; std::list<std::string> usersEncoded; for (const auto &u : users) { std::string username = base64Encode(u); param += (username + ","); userMap[username] = false; usersEncoded.push_back(username); } JsonObject json; ABC_CHECK(json.set("l1s", param)); HttpReply reply; ABC_CHECK(AirbitzRequest().post(reply, url, json.encode())); ServerReplyJson replyJson; ABC_CHECK(replyJson.decode(reply)); JsonArray arrayJson = replyJson.results(); size_t size = arrayJson.size(); for (size_t i = 0; i < size; i++) { json_t *pJSON_Row = arrayJson[i].get(); if (!pJSON_Row || !json_is_object(pJSON_Row)) return ABC_ERROR(ABC_CC_JSONError, "Error parsing JSON array element object"); json_t *pJSON_Value = json_object_get(pJSON_Row, "login"); if (!pJSON_Value || !json_is_string(pJSON_Value)) return ABC_ERROR(ABC_CC_JSONError, "Error otp/pending/login JSON"); std::string username(json_string_value(pJSON_Value)); pJSON_Value = json_object_get(pJSON_Row, ABC_SERVER_JSON_OTP_PENDING); if (!pJSON_Value || !json_is_boolean(pJSON_Value)) return ABC_ERROR(ABC_CC_JSONError, "Error otp/pending/pending JSON"); if (json_is_true(pJSON_Value)) { userMap[username] = json_is_true(pJSON_Value); } } isPending.clear(); for (auto &username: usersEncoded) { isPending.push_back(userMap[username]); } return Status(); }
Status JsonFile::save(const std::string &filename) const { if (json_dump_file(root_, filename.c_str(), saveFlags)) return ABC_ERROR(ABC_CC_JSONError, "Cannot write JSON file " + filename); return Status(); }
Status JsonBox::decrypt(DataChunk &result, DataSlice key) { DataChunk nonce; ABC_CHECK(nonceOk()); ABC_CHECK(base16Decode(nonce, this->nonce())); DataChunk cyphertext; ABC_CHECK(cyphertextOk()); ABC_CHECK(base64Decode(cyphertext, this->cyphertext())); switch (type()) { case AES256_CBC_AIRBITZ: { ABC_CHECK_OLD(ABC_CryptoDecryptAES256Package(result, cyphertext, key, nonce, &error)); return Status(); } default: return ABC_ERROR(ABC_CC_DecryptError, "Unknown encryption type"); } }
Status chunkDecode(DataChunk &result, const std::string &in) { // The string must be a multiple of the chunk size: if (in.size() % Chars) return ABC_ERROR(ABC_CC_ParseError, "Bad encoding"); DataChunk out; out.reserve(Bytes * (in.size() / Chars)); constexpr unsigned shift = 8 * Bytes / Chars; // Bits per character uint16_t buffer = 0; // Bits waiting to be written out, MSB first int bits = 0; // Number of bits currently in the buffer auto i = in.begin(); while (i != in.end()) { // Read one character from the string: int value = Decode(*i); if (value < 0) break; ++i; // Append the bits to the buffer: buffer |= value << (16 - bits - shift); bits += shift; // Write out some bits if the buffer has a byte's worth: if (8 <= bits) { out.push_back(buffer >> 8); buffer <<= 8; bits -= 8; } }
Status HttpReply::codeOk() const { if (code < 200 || 300 <= code) return ABC_ERROR(ABC_CC_Error, "Bad HTTP status code " + std::to_string(code)); return Status(); }
Status Login::repoFind(JsonPtr &result, const std::string &type, bool create) { result = JsonPtr(); // Try 1: Use what we have: repoFindLocal(result, type).log(); // Failure is fine if (result) return Status(); // Try 2: Sync with the server: ABC_CHECK(update()); repoFindLocal(result, type).log(); // Failure is fine if (result) return Status(); // Try 3: Make a new repo: if (create) { // Make the keys: DataChunk dataKey; DataChunk syncKey; ABC_CHECK(randomData(dataKey, DATA_KEY_LENGTH)); ABC_CHECK(randomData(syncKey, SYNC_KEY_LENGTH)); AccountRepoJson repoJson; ABC_CHECK(repoJson.syncKeySet(base64Encode(syncKey))); ABC_CHECK(repoJson.dataKeySet(base64Encode(dataKey_))); // Make the metadata: DataChunk id; ABC_CHECK(randomData(id, keyIdLength)); KeyJson keyJson; ABC_CHECK(keyJson.idSet(base64Encode(id))); ABC_CHECK(keyJson.typeSet(type)); ABC_CHECK(keyJson.keysSet(repoJson)); // Encrypt the metadata: JsonBox keyBox; ABC_CHECK(keyBox.encrypt(keyJson.encode(), dataKey_)); // Push the wallet to the server: AuthJson authJson; ABC_CHECK(authJson.loginSet(*this)); ABC_CHECK(loginServerKeyAdd(authJson, keyBox, base16Encode(syncKey))); // Save to disk: LoginStashJson stashJson; ABC_CHECK(stashJson.load(paths.stashPath())); if (!stashJson.keyBoxes().ok()) ABC_CHECK(stashJson.keyBoxesSet(JsonArray())); ABC_CHECK(stashJson.keyBoxes().append(keyBox)); ABC_CHECK(stashJson.save(paths.stashPath())); result = repoJson; return Status(); } return ABC_ERROR(ABC_CC_AccountDoesNotExist, "No such repo"); }
AirbitzRequest::AirbitzRequest() { if (!status_) return; if (curl_easy_setopt(handle_, CURLOPT_SSL_CTX_FUNCTION, curlSslCallback)) status_ = ABC_ERROR(ABC_CC_Error, "cURL failed to set SSL pinning"); header("Content-Type", "application/json"); header("Authorization", API_KEY_HEADER + 15); }
Status JsonFile::load(const std::string &filename) { json_error_t error; json_t *root = json_load_file(filename.c_str(), loadFlags, &error); if (!root) return ABC_ERROR(ABC_CC_JSONError, error.text); reset(root); return Status(); }
Status JsonFile::encode(std::string &result) const { char *raw = json_dumps(root_, saveFlags); if (!raw) return ABC_ERROR(ABC_CC_JSONError, "Cannot encode JSON."); result = raw; ABC_UtilJanssonSecureFree(raw); return Status(); }
Status JsonFile::decode(const std::string &data) { json_error_t error; json_t *root = json_loadb(data.data(), data.size(), loadFlags, &error); if (!root) return ABC_ERROR(ABC_CC_JSONError, error.text); reset(root); return Status(); }
Status bridgeWatcherStart(Wallet &self) { if (watchers_.end() != watchers_.find(self.id())) return ABC_ERROR(ABC_CC_Error, "Watcher already exists for " + self.id()); watchers_[self.id()].reset(new WatcherInfo(self)); return Status(); }
static Status watcherFind(WatcherInfo *&result, Wallet &self) { std::string id = self.id(); auto row = watchers_.find(id); if (row == watchers_.end()) return ABC_ERROR(ABC_CC_Synchronizing, "Cannot find watcher for " + id); result = row->second.get(); return Status(); }
void StratumConnection::getTx( const bc::client::obelisk_codec::error_handler &onError, const bc::client::obelisk_codec::fetch_transaction_handler &onReply, const bc::hash_digest &txid) { JsonArray params; params.append(json_string(bc::encode_hash(txid).c_str())); auto errorShim = [onError](Status status) { onError(std::make_error_code(std::errc::bad_message)); }; auto decoder = [onReply](JsonPtr payload) -> Status { if (!json_is_string(payload.get())) return ABC_ERROR(ABC_CC_JSONError, "Bad reply format"); bc::data_chunk rawTx; if (!base16Decode(rawTx, json_string_value(payload.get()))) return ABC_ERROR(ABC_CC_ParseError, "Bad transaction format"); // Convert rawTx to bc::transaction_type: bc::transaction_type tx; try { auto deserial = bc::make_deserializer(rawTx.begin(), rawTx.end()); bc::satoshi_load(deserial.iterator(), deserial.end(), tx); } catch (bc::end_of_stream) { return ABC_ERROR(ABC_CC_ParseError, "Bad transaction format"); } onReply(tx); return Status(); }; sendMessage("blockchain.transaction.get", params, errorShim, decoder); }
Status AddressDb::get(Address &result, const std::string &address) { std::lock_guard<std::mutex> lock(mutex_); auto i = addresses_.find(address); if (i == addresses_.end()) return ABC_ERROR(ABC_CC_NoAvailableAddress, "No address: " + address); result = i->second; return Status(); }
Status randomData(DataChunk &result, size_t size) { DataChunk out; out.resize(size); if (!RAND_bytes(out.data(), out.size())) return ABC_ERROR(ABC_CC_Error, "Random data generation failed"); result = std::move(out); return Status(); }
static Status curlOk(CURLcode code) { if (code) { std::string message("cURL error: "); if (curl_easy_strerror(code)) message += curl_easy_strerror(code); else message += std::to_string(code); return ABC_ERROR(ABC_CC_SysError, message); } return Status(); }
Status ScryptSnrp::hash(DataChunk &result, DataSlice data, size_t size) const { DataChunk out(size); int rc = crypto_scrypt(data.data(), data.size(), salt.data(), salt.size(), n, r, p, out.data(), size); if (rc) return ABC_ERROR(ABC_CC_ScryptError, "Error calculating Scrypt hash"); result = std::move(out); return Status(); }
static Status watcherLoad(Wallet &self) { Watcher *watcher = nullptr; ABC_CHECK(watcherFind(watcher, self)); DataChunk data; ABC_CHECK(fileLoad(data, watcherPath(self))); if (!watcher->load(data)) return ABC_ERROR(ABC_CC_Error, "Unable to load serialized watcher"); return Status(); }
HttpRequest & HttpRequest::header(const std::string &key, const std::string &value) { if (!status_) return *this; std::string header = key + ": " + value; auto slist = curl_slist_append(headers_, header.c_str()); if (!slist) status_ = ABC_ERROR(ABC_CC_Error, "cURL slist error"); else headers_ = slist; return *this; }
Status currencyNumber(Currency &result, const std::string &code) { static const std::map<std::string, Currency> map { ABC_CURRENCY_LIST(CURRENCY_NUMBER_ROW) }; auto i = map.find(code); if (map.end() == i) return ABC_ERROR(ABC_CC_ParseError, "Cannot find currency code " + code); result = i->second; return Status(); }
Status currencyName(std::string &result, Currency number) { static const std::map<Currency, const char *> map { ABC_CURRENCY_LIST(CURRENCY_NAME_ROW) }; auto i = map.find(number); if (map.end() == i) return ABC_ERROR(ABC_CC_ParseError, "Cannot find currency number"); result = i->second; return Status(); }
static Status blockchainPostTx(DataSlice tx) { std::string body = "tx=" + base16Encode(tx); if (isTestnet()) return ABC_ERROR(ABC_CC_Error, "No blockchain.info testnet"); HttpReply reply; ABC_CHECK(HttpRequest(). header("Content-Type", "application/x-www-form-urlencoded"). post(reply, "https://blockchain.info/pushtx", body)); ABC_CHECK(reply.codeOk()); return Status(); }
Status pluginDataGet(const Account &account, const std::string &plugin, const std::string &key, std::string &data) { PluginDataFile json; ABC_CHECK(json.load(keyFilename(account, plugin, key), account.login.dataKey())); ABC_CHECK(json.keyOk()); ABC_CHECK(json.dataOk()); if (json.key() != key) return ABC_ERROR(ABC_CC_JSONError, "Plugin filename does not match contents"); data = json.data(); return Status(); }
Status debugLogRotate() { if (gLogFile) fclose(gLogFile); auto path = debugLogPath(); if (fileExists(path)) rename(path.c_str(), debugLogOldPath().c_str()); gLogFile = fopen(path.c_str(), "w"); if (!gLogFile) return ABC_ERROR(ABC_CC_SysError, "Cannot open " + path); return Status(); }
Status watcherBridgeRawTx(Wallet &self, const char *szTxID, DataChunk &result) { Watcher *watcher = nullptr; ABC_CHECK(watcherFind(watcher, self)); bc::hash_digest txid; if (!bc::decode_hash(txid, szTxID)) return ABC_ERROR(ABC_CC_ParseError, "Bad txid"); auto tx = watcher->find_tx(txid); result.resize(satoshi_raw_size(tx)); bc::satoshi_save(tx, result.begin()); return Status(); }
Status Login::repoFindLocal(JsonPtr &result, const std::string &type) { // If this is an Airbitz account, try the legacy `syncKey`: if (repoTypeAirbitzAccount == type) { LoginPackage loginPackage; ABC_CHECK(loginPackage.load(paths.loginPackagePath())); if (loginPackage.syncKeyBox().ok()) { DataChunk syncKey; ABC_CHECK(loginPackage.syncKeyBox().decrypt(syncKey, dataKey_)); AccountRepoJson repoJson; ABC_CHECK(repoJson.syncKeySet(base64Encode(syncKey))); ABC_CHECK(repoJson.dataKeySet(base64Encode(dataKey_))); result = repoJson; return Status(); } } // Search the on-disk array: LoginStashJson stashJson; if (stashJson.load(paths.stashPath())) { auto keyBoxesJson = stashJson.keyBoxes(); size_t keyBoxesSize = keyBoxesJson.size(); for (size_t i = 0; i < keyBoxesSize; i++) { JsonBox boxJson(keyBoxesJson[i]); DataChunk keyBytes; KeyJson keyJson; ABC_CHECK(boxJson.decrypt(keyBytes, dataKey_)); ABC_CHECK(keyJson.decode(toString(keyBytes))); if (keyJson.typeOk() && type == keyJson.type()) { result = keyJson.keys(); return Status(); } } } return ABC_ERROR(ABC_CC_AccountDoesNotExist, "No such repo"); }
void StratumConnection::getAddressHistory( const bc::client::obelisk_codec::error_handler &onError, const bc::client::obelisk_codec::fetch_history_handler &onReply, const bc::payment_address &address, size_t fromHeight) { JsonArray params; params.append(json_string(address.encoded().c_str())); auto errorShim = [onError](Status status) { onError(std::make_error_code(std::errc::bad_message)); }; auto decoder = [onReply](JsonPtr payload) -> Status { JsonArray arrayJson(payload); bc::client::history_list history; size_t size = arrayJson.size(); history.reserve(size); for (size_t i = 0; i < size; i++) { struct HistoryJson: public JsonObject { ABC_JSON_CONSTRUCTORS(HistoryJson, JsonObject) ABC_JSON_STRING(txid, "tx_hash", nullptr) ABC_JSON_INTEGER(height, "height", 0) }; HistoryJson json(arrayJson[i]); bc::hash_digest hash; if (!json.txidOk() || !bc::decode_hash(hash, json.txid())) return ABC_ERROR(ABC_CC_Error, "Bad txid"); bc::client::history_row row; row.output.hash = hash; row.output_height = json.height(); row.spend.hash = bc::null_hash; history.push_back(row); } onReply(history); return Status(); };
Status HttpRequest::init() { handle_ = curl_easy_init(); if (!handle_) return ABC_ERROR(ABC_CC_Error, "cURL failed create handle"); // Basic options: ABC_CHECK_CURL(curl_easy_setopt(handle_, CURLOPT_NOSIGNAL, 1)); ABC_CHECK_CURL(curl_easy_setopt(handle_, CURLOPT_CONNECTTIMEOUT, TIMEOUT)); const auto certPath = gContext->paths.certPath(); if (!certPath.empty()) ABC_CHECK_CURL(curl_easy_setopt(handle_, CURLOPT_CAINFO, certPath.c_str())); return Status(); }