diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..1d976d6 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "external/crypto-algorithms"] + path = external/crypto-algorithms + url = https://github.com/B-Con/crypto-algorithms +[submodule "external/containers"] + path = external/containers + url = https://github.com/magestik/containers diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ee2fed3 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.3) + +project(blockchain) + +IF(NOT CMAKE_BUILD_TYPE) + SET(CMAKE_BUILD_TYPE Debug) +ENDIF() + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) +set(CMAKE_CXX_VISIBILITY_PRESET hidden) + +option(ENABLE_ASAN OFF) + +if (ENABLE_ASAN) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") +endif (ENABLE_ASAN) + +add_subdirectory(external) +add_subdirectory(src) +add_subdirectory(test) diff --git a/README.md b/README.md index 6315895..e630ee4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ # blockchain +Simple blockchain \ No newline at end of file diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt new file mode 100644 index 0000000..fe232ae --- /dev/null +++ b/external/CMakeLists.txt @@ -0,0 +1,37 @@ + +add_subdirectory(containers) + +############################################# + +# MD2 + +add_library(md2 STATIC crypto-algorithms/md2.c crypto-algorithms/md2.h) +target_include_directories(md2 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/crypto-algorithms/) + +add_executable(md2_test crypto-algorithms/md2_test.c) +target_link_libraries(md2_test PRIVATE md2) + +# MD5 + +add_library(md5 STATIC crypto-algorithms/md5.c crypto-algorithms/md5.h) +target_include_directories(md5 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/crypto-algorithms/) + +add_executable(md5_test crypto-algorithms/md5_test.c) +target_link_libraries(md5_test PRIVATE md5) + +# SHA1 + +add_library(sha1 STATIC crypto-algorithms/sha1.c crypto-algorithms/sha1.h) +target_include_directories(sha1 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/crypto-algorithms/) + +add_executable(sha1_test crypto-algorithms/sha1_test.c) +target_link_libraries(sha1_test PRIVATE sha1) + +# SHA256 + +add_library(sha256 STATIC crypto-algorithms/sha256.c crypto-algorithms/sha256.h) +target_include_directories(sha1 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/crypto-algorithms/) + +add_executable(sha256_test crypto-algorithms/sha256_test.c) +target_link_libraries(sha256_test PRIVATE sha256) + diff --git a/external/containers b/external/containers new file mode 160000 index 0000000..67fa335 --- /dev/null +++ b/external/containers @@ -0,0 +1 @@ +Subproject commit 67fa33597430cfc4d0c5e2c15fe0e26d5a7fcba3 diff --git a/external/crypto-algorithms b/external/crypto-algorithms new file mode 160000 index 0000000..cfbde48 --- /dev/null +++ b/external/crypto-algorithms @@ -0,0 +1 @@ +Subproject commit cfbde48414baacf51fc7c74f275190881f037d32 diff --git a/src/Allocator.h b/src/Allocator.h new file mode 100644 index 0000000..363423e --- /dev/null +++ b/src/Allocator.h @@ -0,0 +1,35 @@ +#pragma once + +#include // for malloc/free & realloc + +class HeapAllocator +{ +public: + + HeapAllocator(void) + { + // ... + } + + ~HeapAllocator(void) + { + // ... + } + + void * allocate(unsigned int size) + { + void * ptr = malloc(size); + return(ptr); + } + + void release(void * ptr) + { + free(ptr); + } + + void * resize(void * ptr, unsigned int size) + { + ptr = realloc(ptr, size); + return(ptr); + } +}; diff --git a/src/Block.cpp b/src/Block.cpp new file mode 100644 index 0000000..f06d691 --- /dev/null +++ b/src/Block.cpp @@ -0,0 +1 @@ +#include "Block.h" diff --git a/src/Block.h b/src/Block.h new file mode 100644 index 0000000..b6ec4c4 --- /dev/null +++ b/src/Block.h @@ -0,0 +1,109 @@ +#pragma once + +#include "base_types.h" + +template +struct BlockHeader +{ + BlockHeader(void) + { + version = 1; + timestamp = 0; + previousHash = 0; + dataHash = 0; + nonce = 0; + } + + BlockHeader(uint32_t version_, uint32_t timestamp_, const HashHeader & previousHash_, const HashData & dataHash_, uint32_t nonce_) + { + version = version_; + timestamp = timestamp_; + previousHash = previousHash_; + dataHash = dataHash_; + nonce = nonce_; + } + + uint32_t getVersion(void) const + { + return(version); + } + + uint32_t getTimestamp(void) const + { + return(timestamp); + } + + const HashHeader & getPreviousHash(void) const + { + return(previousHash); + } + + const HashData & getDataHash(void) const + { + return(dataHash); + } + + uint32_t getNonce(void) const + { + return(nonce); + } + +private: + + uint32_t version; + uint32_t timestamp; + HashHeader previousHash; + HashData dataHash; + uint32_t nonce; +}; + +template +struct Block +{ + typedef BlockHeader HEADER; + typedef HASH_HEADER HASH; + enum { SIZE = BLOCK_SIZE }; + + Block(void) : header() + { + for (int i = 0; i < SIZE; ++i) + { + data[i] = 0; + } + } + + Block(uint32_t version, uint32_t timestamp, const HASH & previousHash, uint32_t nonce, byte * data_) : header() + { + for (int i = 0; i < SIZE; ++i) + { + data[i] = data_[i]; + } + + HASH_DATA dataHash; + dataHash.initFromData(data_, SIZE); + + header = HEADER(version, timestamp, previousHash, dataHash, nonce); + } + + bool computeHash(HASH & output_hash) const + { + // it's ok to compute header hash only + // -> the header contains the data hash + return(output_hash.initFromData((byte*)&header, sizeof(HEADER))); + } + + const HEADER & getHeader(void) const + { + return(header); + } + + const byte * getData(void) const + { + return(data); + } + +private: + + HEADER header; + byte data [SIZE]; +}; diff --git a/src/Blockchain.cpp b/src/Blockchain.cpp new file mode 100644 index 0000000..bcca4a4 --- /dev/null +++ b/src/Blockchain.cpp @@ -0,0 +1,2 @@ +#include "Blockchain.h" + diff --git a/src/Blockchain.h b/src/Blockchain.h new file mode 100644 index 0000000..2677e13 --- /dev/null +++ b/src/Blockchain.h @@ -0,0 +1,176 @@ +#pragma once + +#include "base_types.h" + +#include "Block.h" + +#include + +#include "Array.h" +#include "Allocator.h" + +template +class Blockchain +{ +public: + + enum + { + INITIAL_VERSION = 1, + CURRENT_VERSION = INITIAL_VERSION, + }; + + /** + * @brief Default Constructor + */ + Blockchain(void) : difficulty(0) + { + // ... + } + + /** + * @brief Constructor with initial difficulty + * @param difficulty_ + */ + Blockchain(unsigned int difficulty_) : difficulty(difficulty_) + { + if (difficulty > BLOCK::HASH::SIZE) + { + difficulty = 0; + } + } + + /** + * @brief Get Current Hash computation difficulty + * @return + */ + inline unsigned int getCurrentDifficulty(void) const + { + return(difficulty); + } + + /** + * @brief Get Genesis Block + * @return + */ + inline const BLOCK & getGenesisBlock(void) const + { + return(*blocks[0]); + } + + /** + * @brief Get Genesis Block + * @return + */ + inline const BLOCK & getCurrentBlock(void) const + { + return(*blocks[blocks.count()-1]); + } + + /** + * @brief Create Genesis Block without data + * @param version + * @param timestamp + * @param output_block + * @return + */ + bool CreateGenesisBlock(uint32_t version, uint32_t timestamp, uint32_t nonce, BLOCK & output_block) + { + return(CreateGenesisBlock(version, timestamp, nonce, nullptr, 0, output_block)); + } + + /** + * @brief Create Genesis Block with data + * @param version + * @param timestamp + * @param output_block + * @return + */ + bool CreateGenesisBlock(uint32_t version, uint32_t timestamp, uint32_t nonce, byte * data, unsigned int dataSize, BLOCK & output_block) + { + if (version != CURRENT_VERSION) + { + return(false); + } + + typename BLOCK::HASH empty_hash; + + return(CreateBlockInternal(version, timestamp, empty_hash, nonce, data, dataSize, output_block)); + } + + /** + * @brief Create Block + * @param version + * @param timestamp + * @param previousHash + * @param nonce + * @param data + * @param dataSize + * @param output_block + * @return + */ + bool CreateBlock(uint32_t version, uint32_t timestamp, const typename BLOCK::HASH & previousHash, uint32_t nonce, byte * data, unsigned int dataSize, BLOCK & output_block) + { + if (version != CURRENT_VERSION) + { + return(false); + } + + if (data == nullptr || dataSize == 0 || dataSize > BLOCK::SIZE) + { + return(false); + } + + return(CreateBlockInternal(version, timestamp, previousHash, nonce, data, dataSize, output_block)); + } + +protected: + + /** + * @brief Create Block without checking parameters + * @param version + * @param timestamp + * @param previousHash + * @param nonce + * @param data + * @param dataSize + * @param output_block + * @return + */ + bool CreateBlockInternal(uint32_t version, uint32_t timestamp, const typename BLOCK::HASH & previousHash, uint32_t nonce, byte * data, unsigned int dataSize, BLOCK & output_block) + { + byte blockData [BLOCK::SIZE]; + memset(blockData, 0, BLOCK::SIZE); + + if (nullptr != data) + { + memcpy(blockData, data, dataSize); + } + + BLOCK newBlock(version, timestamp, previousHash, nonce, blockData); + + typename BLOCK::HASH H; + newBlock.computeHash(H); + + for (int i = 0; i < difficulty; ++i) + { + if (H[i] != 0) + { + return(false); + } + } + + output_block = newBlock; + + blocks.add(&output_block); + + return(true); + } + +private: + + unsigned int difficulty; + + Array blocks; +}; + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..dbf933a --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,5 @@ + +add_library(blockchain STATIC Block.cpp Block.h Blockchain.cpp Blockchain.h base_types.h) + +target_include_directories(blockchain PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(blockchain PUBLIC containers) diff --git a/src/base_types.h b/src/base_types.h new file mode 100644 index 0000000..1925b4f --- /dev/null +++ b/src/base_types.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +typedef uint8_t byte; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..1b7afc7 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,4 @@ + +add_executable(compute_hash compute_hash.cpp Hash.cpp Hash.h) + +target_link_libraries(compute_hash PRIVATE blockchain md5 sha1 sha256) diff --git a/test/Hash.cpp b/test/Hash.cpp new file mode 100644 index 0000000..2e1308b --- /dev/null +++ b/test/Hash.cpp @@ -0,0 +1 @@ +#include "Hash.h" diff --git a/test/Hash.h b/test/Hash.h new file mode 100644 index 0000000..1e40c8a --- /dev/null +++ b/test/Hash.h @@ -0,0 +1,187 @@ +#pragma once + +#include "base_types.h" + +extern "C" +{ +#include "md5.h" +#include "sha1.h" +#include "sha256.h" +} + +template +struct HashNull +{ + enum { SIZE = HASH_SIZE }; + + HashNull(void) + { + setBytes(0); + } + + bool initFromData(const byte * data, unsigned int size) + { + setBytes(0); + + return(true); + } + + HashNull & operator = (byte data) + { + setBytes(data); + } + + byte operator [] (unsigned int index) const + { + return(checksum[index]); + } + +protected: + + void setBytes(byte data) + { + for (int i = 0; i < SIZE; ++i) + { + checksum[i] = data; + } + } + +private: + + byte checksum [SIZE]; +}; + +struct HashMd5 +{ + enum { SIZE = 16 }; // 128 bits + + HashMd5(void) + { + setBytes(0); + } + + bool initFromData(byte * data, unsigned int size) + { + MD5_CTX context; + + md5_init(&context); + md5_update(&context, data, size); + md5_final(&context, checksum); + + return(true); + } + + HashMd5 & operator = (byte data) + { + setBytes(data); + } + + byte operator [] (unsigned int index) const + { + return(checksum[index]); + } + +protected: + + void setBytes(byte data) + { + for (int i = 0; i < SIZE; ++i) + { + checksum[i] = data; + } + } + +private: + + byte checksum [SIZE]; +}; + +struct HashSha1 +{ + enum { SIZE = 20 }; // 160 bits + + HashSha1(void) + { + setBytes(0); + } + + bool initFromData(byte * data, unsigned int size) + { + SHA1_CTX context; + + sha1_init(&context); + sha1_update(&context, data, size); + sha1_final(&context, checksum); + + return(true); + } + + HashSha1 & operator = (byte data) + { + setBytes(data); + } + + byte operator [] (unsigned int index) const + { + return(checksum[index]); + } + +protected: + + void setBytes(byte data) + { + for (int i = 0; i < SIZE; ++i) + { + checksum[i] = data; + } + } + +private: + + byte checksum [SIZE]; +}; + +struct HashSha256 +{ + enum { SIZE = 32 }; // 256 bits + + HashSha256(void) + { + setBytes(0); + } + + bool initFromData(byte * data, unsigned int size) + { + SHA256_CTX context; + + sha256_init(&context); + sha256_update(&context, data, size); + sha256_final(&context, checksum); + + return(true); + } + + HashSha256 & operator = (byte data) + { + setBytes(data); + } + + byte operator [] (unsigned int index) const + { + return(checksum[index]); + } + +protected: + + void setBytes(byte data) + { + for (int i = 0; i < SIZE; ++i) + { + checksum[i] = data; + } + } + +private: + + byte checksum [SIZE]; +}; diff --git a/test/compute_hash.cpp b/test/compute_hash.cpp new file mode 100644 index 0000000..2ca7d9f --- /dev/null +++ b/test/compute_hash.cpp @@ -0,0 +1,210 @@ +#include "Blockchain.h" + +#include "Hash.h" + +#include +#include + +typedef HashSha256 HASH_HEADER; +typedef HashMd5 HASH_DATA; + +typedef Block<1024, HASH_HEADER, HASH_DATA> BLOCK; +typedef Blockchain BLOCKCHAIN; + +const unsigned int DIFFICULTY = 2; + +static inline uint32_t getCurrentTimestamp() +{ + time_t unix_timestamp = time(nullptr); + + if (-1 == unix_timestamp) + { + return(false); + } + + uint32_t timestamp = unix_timestamp & 0xFFFFFFFF; // should be ok for now + + return(timestamp); +} + +struct MiningBlockHeader +{ + uint32_t version; + uint32_t timestamp; + HASH_HEADER previousHash; + HASH_DATA dataHash; + uint32_t nonce; + + MiningBlockHeader(const BLOCK::HASH & previousBlockHash, byte * data, unsigned int size) + { + version = 1; + timestamp = getCurrentTimestamp(); + previousHash = previousBlockHash; + if (data != nullptr) + { + dataHash.initFromData(data, size); + } + nonce = 0; + } +}; + +static void print_hash(unsigned char * data, unsigned int size) +{ + for (int i = 0; i < size; ++i) + { + printf("%02x", (unsigned)(*(data+i))); + } +} + +static bool print_block_info(const BLOCK & block) +{ + BLOCK::HASH H; + bool success = block.computeHash(H); + + if (!success) + { + return(false); + } + + printf("Block "); + print_hash((unsigned char*)&H, BLOCK::HASH::SIZE); + printf(" : \n"); + + printf("\t Version : %d\n", block.getHeader().getVersion()); + printf("\t Timestamp : %d\n", block.getHeader().getTimestamp()); + + printf("\t Previous Block : "); + print_hash((unsigned char*)&block.getHeader().getPreviousHash(), HASH_HEADER::SIZE); + printf(" \n"); + + printf("\t Data Hash : "); + print_hash((unsigned char*)&block.getHeader().getDataHash(), HASH_DATA::SIZE); + printf(" \n"); + + printf("\t Nonce : %d\n", block.getHeader().getNonce()); + + printf("\n"); + + return(true); +} + +static inline bool isHashValidForBlockchain(BLOCKCHAIN & blockchain, const BLOCK::HASH & H) +{ + for (int i = 0; i < blockchain.getCurrentDifficulty(); ++i) + { + if (H[i] != 0) + { + return(false); + } + } + + return(true); +} + +static bool ComputeNonce(BLOCKCHAIN & blockchain, MiningBlockHeader & parameters) +{ + BLOCK::HASH H; + H.initFromData((byte*)¶meters, sizeof(parameters)); + + while (!isHashValidForBlockchain(blockchain, H)) + { + ++parameters.nonce; + if (!H.initFromData((byte*)¶meters, sizeof(parameters))) + { + return(false); + } + } + + return(true); +} + +bool CreateGenesisBlock(BLOCKCHAIN & blockchain, byte * data, unsigned int size) +{ + BLOCK::HASH empty_hash; + MiningBlockHeader parameters(empty_hash, data, size); + ComputeNonce(blockchain, parameters); + + BLOCK block; + if (!blockchain.CreateGenesisBlock(parameters.version, parameters.timestamp, parameters.nonce, data, size, block)) + { + printf("Block creation failed !\n"); + return(false); + } + + BLOCK::HASH H; + bool success = block.computeHash(H); + + if (!success) + { + return(false); + } + + print_block_info(block); + + return(true); +} + +bool CreateEmptyGenesisBlock(BLOCKCHAIN & blockchain) +{ + byte no_data [BLOCK::SIZE]; + memset(no_data, 0, BLOCK::SIZE); + + return(CreateGenesisBlock(blockchain, no_data, BLOCK::SIZE)); +} + +bool AddDataToBlockChain(BLOCKCHAIN & blockchain, const BLOCK::HASH & previousBlockHash, byte * data, unsigned int size) +{ + MiningBlockHeader parameters(previousBlockHash, data, size); + ComputeNonce(blockchain, parameters); + + BLOCK block; + if (!blockchain.CreateBlock(parameters.version, parameters.timestamp, parameters.previousHash, parameters.nonce, data, size, block)) + { + printf("Block creation failed !\n"); + return(false); + } + + BLOCK::HASH H; + bool success = block.computeHash(H); + + if (!success) + { + return(false); + } + + print_block_info(block); + + return(true); +} + +int main(int argc, char ** argv) +{ + BLOCKCHAIN blockchain(DIFFICULTY); + + // + // Create Genesis Block + if (!CreateEmptyGenesisBlock(blockchain)) + { + return(-1); + } + + BLOCK::HASH H; + + const BLOCK & genesis = blockchain.getGenesisBlock(); + genesis.computeHash(H); + + // + // Create some Block + byte random_data [BLOCK::SIZE]; + memset(random_data, 0, BLOCK::SIZE); + + for (int i = 0; i < 10; ++i) + { + AddDataToBlockChain(blockchain, H, random_data, BLOCK::SIZE); + + const BLOCK & current = blockchain.getCurrentBlock(); + current.computeHash(H); + } + + return(0); +}