Calculating address

This commit is contained in:
Selim Mustafaev 2021-05-10 16:10:43 +03:00
parent 888e1967c4
commit 853eec6038
10 changed files with 175 additions and 20 deletions

View File

@ -2,7 +2,13 @@ cmake_minimum_required(VERSION 3.19)
project(btcexplorer)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fconcepts-diagnostics-depth=2")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
add_executable(btcexplorer main.cpp Models/Block.cpp Models/Block.h Models/Transaction.cpp Models/Transaction.h Models/VarInt.cpp Models/VarInt.h Models/TxInput.cpp Models/TxInput.h Models/TxOutput.cpp Models/TxOutput.h Models/Script.cpp Models/Script.h)
find_package(OpenSSL REQUIRED)
include_directories(OPENSSL_INCLUDE_DIR)
add_executable(btcexplorer main.cpp Models/Block.cpp Models/Block.h Models/Transaction.cpp Models/Transaction.h Models/VarInt.cpp Models/VarInt.h Models/TxInput.cpp Models/TxInput.h Models/TxOutput.cpp Models/TxOutput.h Models/Script.cpp Models/Script.h hash.h)
target_link_libraries(btcexplorer OpenSSL::Crypto)

View File

@ -1,13 +1,12 @@
#include "Script.h"
#include "../hash.h"
#include <iostream>
#include <iomanip>
Script::Script(std::span<uint8_t> data) {
Script::Script(std::span<uint8_t> data, bool coinbase) {
for(auto iter = data.begin(); iter != data.end();) {
ScriptOperation operation;
uint8_t* ptr = data.data();
size_t bytes = data.size_bytes();
operation.opCode = OpCode(*iter++);
//std::cout << "found opcode: " << std::showbase << std::hex << std::setw(4) << (int)operation.opCode << std::dec << std::endl;
if(operation.opCode <= OpCode::OP_PUSHDATA4) {
@ -20,7 +19,7 @@ Script::Script(std::span<uint8_t> data) {
dataSize = *((uint16_t*)&iter[0]);
iter += 2;
} else if(operation.opCode == OpCode::OP_PUSHDATA4) {
dataSize = *((uint32_t *) &iter[0]);
dataSize = *((uint32_t *)&iter[0]);
iter += 4;
}
operation.input = std::vector(iter, iter + dataSize);
@ -29,14 +28,10 @@ Script::Script(std::span<uint8_t> data) {
_operations.emplace_back(operation);
}
_type = getType();
_type = coinbase ? ScriptType::Coinbase : type();
}
Script::Script() {
}
ScriptType Script::getType() {
ScriptType Script::type() const {
if(_operations[0].opCode <= OpCode::OP_PUSHDATA4 && _operations[1].opCode == OpCode::OP_CHECKSIG) {
return ScriptType::P2PK;
} else if(_operations[0].opCode == OpCode::OP_DUP
@ -50,3 +45,27 @@ ScriptType Script::getType() {
throw std::runtime_error("Unsupported script type");
}
}
std::string Script::address() {
auto pubKey = publicKey();
if(!pubKey.empty()) {
auto hash160 = hash::hash160(pubKey);
std::array<uint8_t, RIPEMD160_DIGEST_LENGTH + 1> prefixedHash{};
prefixedHash[0] = 0; // TODO: this value should depend on BlockMagic
std::copy_n(hash160.begin(), hash160.size(), prefixedHash.begin() + 1);
auto secondHash = hash::sha256(prefixedHash);
std::array<uint8_t, RIPEMD160_DIGEST_LENGTH + 1 + 4> finalHash{};
std::copy(prefixedHash.begin(), prefixedHash.end(), finalHash.begin());
std::copy_n(secondHash.begin(), 4, finalHash.begin() + prefixedHash.size());
return hash::base58(finalHash);
} else {
return "";
}
}
std::span<uint8_t> Script::publicKey() {
switch (_type) {
case P2PK: return _operations[0].input;
default: return {};
}
}

View File

@ -5,6 +5,8 @@
#include <cstddef>
#include <cstdint>
#include <span>
#include <string>
#include <optional>
enum OpCode: uint8_t {
// push value
@ -148,7 +150,7 @@ enum OpCode: uint8_t {
};
enum ScriptType {
P2PK, P2PKH
Unknown, Coinbase, P2PK, P2PKH
};
struct ScriptOperation {
@ -159,14 +161,16 @@ struct ScriptOperation {
class Script {
private:
std::vector<ScriptOperation> _operations;
ScriptType _type;
ScriptType _type = ScriptType::Unknown;
private:
ScriptType getType();
[[nodiscard]] ScriptType type() const;
[[nodiscard]] std::span<uint8_t> publicKey();
public:
Script();
explicit Script(std::span<uint8_t> data);
Script() = default;
explicit Script(std::span<uint8_t> data, bool coinbase = false);
[[nodiscard]] std::string address();
};
#endif //BTCEXPLORER_SCRIPT_H

View File

@ -1,11 +1,15 @@
#include "Transaction.h"
#include "VarInt.h"
#include <iostream>
Transaction::Transaction(const std::byte *data) {
_version = *((uint32_t*)data);
VarInt inputCount(data + sizeof(_version));
const std::byte* curPtr = data + sizeof(_version) + inputCount.size();
std::cout << "======== Transaction ========" << std::endl;
for(size_t i = 0; i < inputCount.value(); ++i) {
TxInput input(curPtr);
curPtr += input.size();
@ -17,6 +21,7 @@ Transaction::Transaction(const std::byte *data) {
for(size_t i = 0; i < outputCount.value(); ++i) {
TxOutput output(curPtr);
curPtr += output.size();
std::cout << "Output " << output.value()/100000000.0 << " BTC to address: " << output.address() << std::endl;
_outputs.emplace_back(std::move(output));
}

View File

@ -2,6 +2,9 @@
#include "VarInt.h"
#include <algorithm>
#include <iostream>
#include <cstring>
static const std::array<std::byte,TxInput::idSize> nullArray {};
TxInput::TxInput(const std::byte *data) {
std::copy_n(data, _txId.size(), _txId.begin());
@ -11,8 +14,11 @@ TxInput::TxInput(const std::byte *data) {
VarInt scriptSigSize(data);
data += scriptSigSize.size();
int cmpResult = memcmp(_txId.data(), nullArray.data(), idSize);
bool coinbase = cmpResult == 0;
//std::cout << "=== Creating signature script of size: " << scriptSigSize.value() << std::endl;
_signatureScript = std::make_unique<Script>(std::span((uint8_t*)data, scriptSigSize.value()));
_signatureScript = std::make_unique<Script>(std::span((uint8_t*)data, scriptSigSize.value()), coinbase);
data += scriptSigSize.value();
_sequence = *reinterpret_cast<const uint32_t*>(data);

View File

@ -9,8 +9,11 @@
#include <memory>
class TxInput {
public:
static const size_t idSize = 32;
private:
std::array<std::byte,32> _txId;
std::array<std::byte,idSize> _txId;
uint32_t _vOut;
uint32_t _sequence;
size_t _size;

View File

@ -18,3 +18,11 @@ TxOutput::TxOutput(const std::byte *data) {
size_t TxOutput::size() const {
return _size;
}
std::string TxOutput::address() const {
return _pubKeyScript->address();
}
uint64_t TxOutput::value() const {
return _value;
}

View File

@ -16,6 +16,8 @@ private:
public:
explicit TxOutput(const std::byte* data);
[[nodiscard]] size_t size() const;
[[nodiscard]] uint64_t value() const;
[[nodiscard]] std::string address() const;
};
#endif //BTCEXPLORER_TXOUTPUT_H

99
hash.h Normal file
View File

@ -0,0 +1,99 @@
#ifndef BTCEXPLORER_HASH_H
#define BTCEXPLORER_HASH_H
#include <cstdint>
#include <array>
#include <string>
#include <concepts>
#include <openssl/sha.h>
#include <openssl/ripemd.h>
namespace hash {
static const char hex_table[16] = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
static const uint8_t base58map[] = {
'1', '2', '3', '4', '5', '6', '7', '8',
'9', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q',
'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
'h', 'i', 'j', 'k', 'm', 'n', 'o', 'p',
'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
'y', 'z' };
// Generalization of something acting as contiguous sequence of bytes
// For example: std::array, std::vector, std::span
template<typename T>
concept ByteArray = requires(T collection) {
{ collection[0] } -> std::same_as<typename T::value_type &>;
{ collection.size() } -> std::same_as<size_t>;
{ collection.data() } -> std::same_as<typename T::value_type*>;
requires sizeof(typename T::value_type) == 1;
};
template<ByteArray T>
std::string to_string(const T& data) {
std::string str(data.size()*2, ' ');
for(size_t i = 0; i < data.size(); ++i) {
str[2*i] = hex_table[(data[i] & 0xF0) >> 4];
str[2*i + 1] = hex_table[data[i] & 0x0F];
}
return str;
}
template<ByteArray T>
std::array<uint8_t,SHA256_DIGEST_LENGTH> sha256(const T& data) {
std::array<uint8_t ,SHA256_DIGEST_LENGTH> hash{};
SHA256_CTX ctx;
SHA256_Init(&ctx);
SHA256_Update(&ctx, data.data(), data.size());
SHA256_Final(hash.data(), &ctx);
return hash;
}
template<ByteArray T>
std::array<uint8_t,RIPEMD160_DIGEST_LENGTH> ripemd160(const T& data) {
std::array<uint8_t,RIPEMD160_DIGEST_LENGTH> hash{};
RIPEMD160_CTX ctx;
RIPEMD160_Init(&ctx);
RIPEMD160_Update(&ctx, data.data(), data.size());
RIPEMD160_Final(hash.data(), &ctx);
return hash;
}
template<ByteArray T>
std::array<uint8_t,RIPEMD160_DIGEST_LENGTH> hash160(const T& data) {
return ripemd160(sha256(data));
}
template<ByteArray T>
std::string base58(const T& data)
{
std::vector<uint8_t> digits((data.size() * 138 / 100) + 1);
size_t digitsLen = 1;
for (size_t i = 0; i < data.size(); i++)
{
uint32_t carry = static_cast<uint32_t>(data[i]);
for (size_t j = 0; j < digitsLen; j++)
{
carry = carry + static_cast<uint32_t>(digits[j] << 8);
digits[j] = static_cast<uint8_t>(carry % 58);
carry /= 58;
}
for (; carry; carry /= 58)
digits[digitsLen++] = static_cast<uint8_t>(carry % 58);
}
std::string result;
for (size_t i = 0; i < (data.size() - 1) && !data[i]; i++)
result.push_back(base58map[0]);
for (size_t i = 0; i < digitsLen; i++)
result.push_back(base58map[digits[digitsLen - 1 - i]]);
return result;
}
}
#endif //BTCEXPLORER_HASH_H

View File

@ -30,8 +30,11 @@ int main() {
blocks.emplace_back(blockData.get(), header.blockSize);
std::cout << "Parsed new block " << index++ << " with size: " << header.blockSize << std::endl;
if(index == 1000) {
break;
}
}
auto stop = std::chrono::high_resolution_clock::now();
auto duration = duration_cast<std::chrono::microseconds>(stop - start);