Calculating address
This commit is contained in:
parent
888e1967c4
commit
853eec6038
@ -2,7 +2,13 @@ cmake_minimum_required(VERSION 3.19)
|
|||||||
project(btcexplorer)
|
project(btcexplorer)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
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}")
|
#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)
|
||||||
@ -1,13 +1,12 @@
|
|||||||
#include "Script.h"
|
#include "Script.h"
|
||||||
|
#include "../hash.h"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <iomanip>
|
#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();) {
|
for(auto iter = data.begin(); iter != data.end();) {
|
||||||
ScriptOperation operation;
|
ScriptOperation operation;
|
||||||
uint8_t* ptr = data.data();
|
|
||||||
size_t bytes = data.size_bytes();
|
|
||||||
operation.opCode = OpCode(*iter++);
|
operation.opCode = OpCode(*iter++);
|
||||||
//std::cout << "found opcode: " << std::showbase << std::hex << std::setw(4) << (int)operation.opCode << std::dec << std::endl;
|
//std::cout << "found opcode: " << std::showbase << std::hex << std::setw(4) << (int)operation.opCode << std::dec << std::endl;
|
||||||
if(operation.opCode <= OpCode::OP_PUSHDATA4) {
|
if(operation.opCode <= OpCode::OP_PUSHDATA4) {
|
||||||
@ -20,7 +19,7 @@ Script::Script(std::span<uint8_t> data) {
|
|||||||
dataSize = *((uint16_t*)&iter[0]);
|
dataSize = *((uint16_t*)&iter[0]);
|
||||||
iter += 2;
|
iter += 2;
|
||||||
} else if(operation.opCode == OpCode::OP_PUSHDATA4) {
|
} else if(operation.opCode == OpCode::OP_PUSHDATA4) {
|
||||||
dataSize = *((uint32_t *) &iter[0]);
|
dataSize = *((uint32_t *)&iter[0]);
|
||||||
iter += 4;
|
iter += 4;
|
||||||
}
|
}
|
||||||
operation.input = std::vector(iter, iter + dataSize);
|
operation.input = std::vector(iter, iter + dataSize);
|
||||||
@ -29,14 +28,10 @@ Script::Script(std::span<uint8_t> data) {
|
|||||||
_operations.emplace_back(operation);
|
_operations.emplace_back(operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
_type = getType();
|
_type = coinbase ? ScriptType::Coinbase : type();
|
||||||
}
|
}
|
||||||
|
|
||||||
Script::Script() {
|
ScriptType Script::type() const {
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
ScriptType Script::getType() {
|
|
||||||
if(_operations[0].opCode <= OpCode::OP_PUSHDATA4 && _operations[1].opCode == OpCode::OP_CHECKSIG) {
|
if(_operations[0].opCode <= OpCode::OP_PUSHDATA4 && _operations[1].opCode == OpCode::OP_CHECKSIG) {
|
||||||
return ScriptType::P2PK;
|
return ScriptType::P2PK;
|
||||||
} else if(_operations[0].opCode == OpCode::OP_DUP
|
} else if(_operations[0].opCode == OpCode::OP_DUP
|
||||||
@ -50,3 +45,27 @@ ScriptType Script::getType() {
|
|||||||
throw std::runtime_error("Unsupported script type");
|
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 {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -5,6 +5,8 @@
|
|||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <span>
|
#include <span>
|
||||||
|
#include <string>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
enum OpCode: uint8_t {
|
enum OpCode: uint8_t {
|
||||||
// push value
|
// push value
|
||||||
@ -148,7 +150,7 @@ enum OpCode: uint8_t {
|
|||||||
};
|
};
|
||||||
|
|
||||||
enum ScriptType {
|
enum ScriptType {
|
||||||
P2PK, P2PKH
|
Unknown, Coinbase, P2PK, P2PKH
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ScriptOperation {
|
struct ScriptOperation {
|
||||||
@ -159,14 +161,16 @@ struct ScriptOperation {
|
|||||||
class Script {
|
class Script {
|
||||||
private:
|
private:
|
||||||
std::vector<ScriptOperation> _operations;
|
std::vector<ScriptOperation> _operations;
|
||||||
ScriptType _type;
|
ScriptType _type = ScriptType::Unknown;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ScriptType getType();
|
[[nodiscard]] ScriptType type() const;
|
||||||
|
[[nodiscard]] std::span<uint8_t> publicKey();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Script();
|
Script() = default;
|
||||||
explicit Script(std::span<uint8_t> data);
|
explicit Script(std::span<uint8_t> data, bool coinbase = false);
|
||||||
|
[[nodiscard]] std::string address();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //BTCEXPLORER_SCRIPT_H
|
#endif //BTCEXPLORER_SCRIPT_H
|
||||||
|
|||||||
@ -1,11 +1,15 @@
|
|||||||
#include "Transaction.h"
|
#include "Transaction.h"
|
||||||
#include "VarInt.h"
|
#include "VarInt.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
Transaction::Transaction(const std::byte *data) {
|
Transaction::Transaction(const std::byte *data) {
|
||||||
_version = *((uint32_t*)data);
|
_version = *((uint32_t*)data);
|
||||||
VarInt inputCount(data + sizeof(_version));
|
VarInt inputCount(data + sizeof(_version));
|
||||||
const std::byte* curPtr = data + sizeof(_version) + inputCount.size();
|
const std::byte* curPtr = data + sizeof(_version) + inputCount.size();
|
||||||
|
|
||||||
|
std::cout << "======== Transaction ========" << std::endl;
|
||||||
|
|
||||||
for(size_t i = 0; i < inputCount.value(); ++i) {
|
for(size_t i = 0; i < inputCount.value(); ++i) {
|
||||||
TxInput input(curPtr);
|
TxInput input(curPtr);
|
||||||
curPtr += input.size();
|
curPtr += input.size();
|
||||||
@ -17,6 +21,7 @@ Transaction::Transaction(const std::byte *data) {
|
|||||||
for(size_t i = 0; i < outputCount.value(); ++i) {
|
for(size_t i = 0; i < outputCount.value(); ++i) {
|
||||||
TxOutput output(curPtr);
|
TxOutput output(curPtr);
|
||||||
curPtr += output.size();
|
curPtr += output.size();
|
||||||
|
std::cout << "Output " << output.value()/100000000.0 << " BTC to address: " << output.address() << std::endl;
|
||||||
_outputs.emplace_back(std::move(output));
|
_outputs.emplace_back(std::move(output));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,9 @@
|
|||||||
#include "VarInt.h"
|
#include "VarInt.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
static const std::array<std::byte,TxInput::idSize> nullArray {};
|
||||||
|
|
||||||
TxInput::TxInput(const std::byte *data) {
|
TxInput::TxInput(const std::byte *data) {
|
||||||
std::copy_n(data, _txId.size(), _txId.begin());
|
std::copy_n(data, _txId.size(), _txId.begin());
|
||||||
@ -11,8 +14,11 @@ TxInput::TxInput(const std::byte *data) {
|
|||||||
VarInt scriptSigSize(data);
|
VarInt scriptSigSize(data);
|
||||||
data += scriptSigSize.size();
|
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;
|
//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();
|
data += scriptSigSize.value();
|
||||||
|
|
||||||
_sequence = *reinterpret_cast<const uint32_t*>(data);
|
_sequence = *reinterpret_cast<const uint32_t*>(data);
|
||||||
|
|||||||
@ -9,8 +9,11 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
class TxInput {
|
class TxInput {
|
||||||
|
public:
|
||||||
|
static const size_t idSize = 32;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::array<std::byte,32> _txId;
|
std::array<std::byte,idSize> _txId;
|
||||||
uint32_t _vOut;
|
uint32_t _vOut;
|
||||||
uint32_t _sequence;
|
uint32_t _sequence;
|
||||||
size_t _size;
|
size_t _size;
|
||||||
|
|||||||
@ -18,3 +18,11 @@ TxOutput::TxOutput(const std::byte *data) {
|
|||||||
size_t TxOutput::size() const {
|
size_t TxOutput::size() const {
|
||||||
return _size;
|
return _size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string TxOutput::address() const {
|
||||||
|
return _pubKeyScript->address();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t TxOutput::value() const {
|
||||||
|
return _value;
|
||||||
|
}
|
||||||
|
|||||||
@ -16,6 +16,8 @@ private:
|
|||||||
public:
|
public:
|
||||||
explicit TxOutput(const std::byte* data);
|
explicit TxOutput(const std::byte* data);
|
||||||
[[nodiscard]] size_t size() const;
|
[[nodiscard]] size_t size() const;
|
||||||
|
[[nodiscard]] uint64_t value() const;
|
||||||
|
[[nodiscard]] std::string address() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //BTCEXPLORER_TXOUTPUT_H
|
#endif //BTCEXPLORER_TXOUTPUT_H
|
||||||
|
|||||||
99
hash.h
Normal file
99
hash.h
Normal 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
|
||||||
3
main.cpp
3
main.cpp
@ -30,8 +30,11 @@ int main() {
|
|||||||
blocks.emplace_back(blockData.get(), header.blockSize);
|
blocks.emplace_back(blockData.get(), header.blockSize);
|
||||||
|
|
||||||
std::cout << "Parsed new block " << index++ << " with size: " << header.blockSize << std::endl;
|
std::cout << "Parsed new block " << index++ << " with size: " << header.blockSize << std::endl;
|
||||||
|
|
||||||
|
if(index == 1000) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto stop = std::chrono::high_resolution_clock::now();
|
auto stop = std::chrono::high_resolution_clock::now();
|
||||||
auto duration = duration_cast<std::chrono::microseconds>(stop - start);
|
auto duration = duration_cast<std::chrono::microseconds>(stop - start);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user