diff --git a/bin/main b/bin/main index 0d5b000..58cd334 100755 Binary files a/bin/main and b/bin/main differ diff --git a/run.sh b/run.sh index ee73d79..76fdf97 100755 --- a/run.sh +++ b/run.sh @@ -1,2 +1,7 @@ g++ src/main.cpp -o bin/main -std=c++17 -ljsoncpp -lssl -lcrypto -bin/main \ No newline at end of file + +if [ $? == 0 ]; then + bin/main +else + echo "Compilation failed" +fi \ No newline at end of file diff --git a/src/crypto.h b/src/crypto.h deleted file mode 100644 index 127ea14..0000000 --- a/src/crypto.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef CRYPTO_H -#define CRYPTO_H - -#include -#include -#include -#include - -uint8_t hex2int(char ch) -{ - if (ch >= '0' && ch <= '9') - return ch - '0'; - if (ch >= 'A' && ch <= 'F') - return ch - 'A' + 10; - if (ch >= 'a' && ch <= 'f') - return ch - 'a' + 10; - return -1; -} - -void sha256(const char *string, char outputBuffer[65], bool txn = false) -{ - unsigned char hash[SHA256_DIGEST_LENGTH], str[strlen(string)/2]; - for (int i = 0; i < strlen(string); i+=2) - str[i/2] = (hex2int(string[i]) << 4) + hex2int(string[i+1]); - - SHA256(str, sizeof(str), hash); - int i = 0; - for(i = 0; i < SHA256_DIGEST_LENGTH; i++) - { - sprintf(outputBuffer + (i * 2), "%02x", hash[i]); - } - outputBuffer[64] = 0; -} - -void rpmd160(const char *string, char outputBuffer[41]) -{ - unsigned char hash[RIPEMD160_DIGEST_LENGTH], str[strlen(string)/2]; - for (int i = 0; i < strlen(string); i+=2) - str[i/2] = (hex2int(string[i]) << 4) + hex2int(string[i+1]); - - RIPEMD160(str, sizeof(str), hash); - int i = 0; - for(i = 0; i < RIPEMD160_DIGEST_LENGTH; i++) - { - sprintf(outputBuffer + (i * 2), "%02x", hash[i]); - } - outputBuffer[40] = 0; -} - -#endif // CRYPTO_H \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index c8fe4b1..a1e48ea 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,6 +7,37 @@ #include "script.h" using namespace std; +set inputs_used; + +void mine() { + vector transactions; // Vector to store all transactions + + for (auto &entry : filesystem::directory_iterator("mempool")) { + ifstream txn_file(entry.path(), ifstream::binary); + Json::Value txn; + txn_file >> txn; + transactions.push_back(txn); + } + + set s; + + for (auto &txn : transactions) { + for (auto &inp : txn["vin"]) { + if (inp["prevout"]["scriptpubkey_type"] == "p2sh"){ + vector ops = getOps(inp["inner_redeemscript_asm"].asString()); + if (identifyScriptType(ops) == "Invalid") + cout << inp["inner_redeemscript_asm"].asString() << '\n' << endl; + s.insert(identifyScriptType(ops)); + } + } + } + + for (auto &i : s) { + cout << i << endl; + } +} + + int main() { // vector transactions; // Vector to store all transactions @@ -29,19 +60,26 @@ int main() { // break; // } // } - // if (check) cout << verify_txn(txn, txn_str) << '\n' << endl; + // if (check) cout << verify_txn(txn, txn_str) << endl; // // transactions.push_back(txn); // } + + // vector transactions; // Vector to store all transactions + // for (auto &entry : filesystem::directory_iterator("mempool")) { + // ifstream txn_file(entry.path(), ifstream::binary); + // Json::Value txn; + // txn_file >> txn; + // transactions.push_back(txn); + // } // set s; // for (auto &txn : transactions) { // for (auto &inp : txn["vin"]) { - // if (inp["prevout"]["scriptpubkey_type"] == "p2pkh"){ - // // cout << inp["prevout"]["scriptpubkey_asm"].asString() << endl; - // vector ops = getOps(inp["prevout"]["scriptpubkey_asm"].asString()); - // // cout << ops[0] << " " << ops[1] << " " << ops[2] << " " << ops[4] << " " << ops[5] << endl; - // s.insert(ops[0] + " " + ops[1] + " " + ops[2] + " " + ops[4] + " " + ops[5]); - // // s.insert(ops.size()); + // if (inp["prevout"]["scriptpubkey_type"] == "p2sh"){ + // vector ops = getOps(inp["inner_redeemscript_asm"].asString()); + // if (identifyScriptType(ops) == "Invalid") + // cout << inp["inner_redeemscript_asm"].asString() << '\n' << endl; + // s.insert(identifyScriptType(ops)); // } // } // } @@ -50,12 +88,76 @@ int main() { // cout << i << endl; // } + // // Test for int2hex function defined in serialize.h // cout << int2hex(uint32_t(UINT32_MAX - 255 + 97)) << endl; // Should print a in the beginning - ifstream txn_file("mempool/ff907975dc0cfa299e908e5fba6df56c764866d9a9c22828824c28b8e4511320.json"); + // ifstream txn_file("mempool/ff907975dc0cfa299e908e5fba6df56c764866d9a9c22828824c28b8e4511320.json"); + // // ifstream txn_file("mempool/fff53b0fda0ab690ddaa23c84536e0d364a736bb93137a76ebf5d78f57cdd32f.json"); + + // std::ostringstream tmp; + // tmp << txn_file.rdbuf(); + // std::string txn_str = tmp.str(); + + // Json::Value txn; + // Json::Reader reader; + // reader.parse(txn_str.c_str(), txn); + + // string ser_txn = sertialize_txn(txn); + // string ser_txn_hex = bstr2hexstr(ser_txn, ser_txn.length()); + + // char sha[SHA256_DIGEST_LENGTH]; + // SHA256((unsigned char*) ser_txn.c_str(), ser_txn.length(), (unsigned char*) sha); + // SHA256((unsigned char*) sha, SHA256_DIGEST_LENGTH, (unsigned char*) sha); + // SHA256((unsigned char*) sha, SHA256_DIGEST_LENGTH, (unsigned char*) sha); + // // cout << "SHA256: " << bstr2hexstr(sha, SHA256_DIGEST_LENGTH) << endl; + + // cout << verify_txn(txn) << endl; + + + // u_int64_t total_fees = 0; + + // for (auto &entry : filesystem::directory_iterator("mempool")) { + // ifstream txn_file(entry.path(), ifstream::binary); + // Json::Value txn; + // txn_file >> txn; + + // bool flag = false; + // for (auto &inp : txn["vin"]) { + // if (inp["prevout"]["scriptpubkey_type"] != "p2pkh"){ + // flag = true; + // break; + // } + // } + // if (flag) continue; + + // total++; + + // int64_t status = verify_txn(txn); + // if (status >= 0) { + // valid_txns++; + // total_fees += status; + // } + // else + // invalid_txns++; + // } + // cout << total << ' ' << valid_txns << ' ' << invalid_txns << endl; + + // // for (auto &i : s) { + // // cout << i << endl; + // // } + // cout << total_fees << endl; + + + + // ifstream txn_file("mempool/ff907975dc0cfa299e908e5fba6df56c764866d9a9c22828824c28b8e4511320.json"); // p2pkh + // ifstream txn_file("mempool/598ed237ba816ab2c80ba7b57cd48c3ad87a15bf33736061eb0304ee23412d85.json"); + // ifstream txn_file("mempool/fff53b0fda0ab690ddaa23c84536e0d364a736bb93137a76ebf5d78f57cdd32f.json"); // p2wpkh + // ifstream txn_file("mempool/ff7f94f1344696386e4e75e33114fd158055104793eb151e73f0b032a073b35e.json"); // p2wsh + ifstream txn_file("mempool/598ed237ba816ab2c80ba7b57cd48c3ad87a15bf33736061eb0304ee23412d85.json"); // p2sh-p2wpkh + std::ostringstream tmp; tmp << txn_file.rdbuf(); @@ -64,11 +166,22 @@ int main() { Json::Value txn; Json::Reader reader; reader.parse(txn_str.c_str(), txn); - - cout << verify_txn(txn, txn_str) << endl; - - - + + // string ser_txn = serialize_txn(txn, true); + // string ser_txn_hex = bstr2hexstr(ser_txn, ser_txn.length()); + // cout << ser_txn_hex << '\n' << endl; + + // char sha[SHA256_DIGEST_LENGTH]; + // SHA256((unsigned char*) ser_txn.c_str(), ser_txn.length(), (unsigned char*) sha); + // SHA256((unsigned char*) sha, SHA256_DIGEST_LENGTH, (unsigned char*) sha); + // for (int i = 0; i < SHA256_DIGEST_LENGTH/2; i++) + // swap(sha[i], sha[SHA256_DIGEST_LENGTH - i - 1]); + + // SHA256((unsigned char*) sha, SHA256_DIGEST_LENGTH, (unsigned char*) sha); + // cout << "Triple SHA256: " << bstr2hexstr(sha, SHA256_DIGEST_LENGTH) << endl; + + cout << txn << endl; + cout << verify_txn(txn) << endl; return 0; diff --git a/src/script.h b/src/script.h index 4af6c70..8d5e413 100644 --- a/src/script.h +++ b/src/script.h @@ -5,14 +5,22 @@ #include #include #include +#include #include -#include "crypto.h" #include "serialize.h" +#include +#include #include #include #include +#include using namespace std; +int total = 0, valid_txns = 0, invalid_txns = 0; +extern set inputs_used; +string serialized_txn = "", serialized_segwit_1 = "", serialized_segwit_inp = "", serialized_segwit_2 = ""; + + std::vector getOps(std::string asmScript) { std::vector ops; @@ -34,118 +42,449 @@ EC_POINT* Hex_to_point_NID_secp256k1(char* str) { } -bool p2pkh_verify(std::vector scriptPubKeyOps, std::vector scriptSigOps, Json::Value txn) { - std::string pubKey = scriptSigOps[3]; - std::string pkh = scriptPubKeyOps[3]; +/* + @param serialized_txn: Serialized transaction in byte string format + @param pubKey: Public key in hex string format + @param signature: Signature in hex string format +*/ +bool verify_sig (string pubKey, std::string signature) { + // return true; + serialized_txn.push_back((hex2int(signature[signature.length()-2]) << 4) + hex2int(signature[signature.length()-1])); + for (int i = 0; i < 3; i++) + serialized_txn.push_back(0); - unsigned char sha[64], rpmd[40]; - sha256(pubKey.c_str(), (char*) sha); - rpmd160((char*) sha, (char*) rpmd); - - if ((char*)rpmd == pkh) { - // Clear scriptsig and scriptsig_asm from transaction - // int idx = txn_str.find("\"scriptsig\""); - // txn_str = txn_str.substr(0, idx + 14) + txn_str.substr(idx + 228); - // idx = txn_str.find("\"scriptsig_asm\""); - // txn_str = txn_str.substr(0, idx + 18) + txn_str.substr(idx + 261); - - // int idx = txn_str.find("\"scriptsig\""); - // txn_str = txn_str.substr(0, idx-1) + txn_str.substr(idx + 237); - // idx = txn_str.find("\"scriptsig_asm\""); - // txn_str = txn_str.substr(0, idx-1) + txn_str.substr(idx + 268); - - string serialized_txn = sertialize_txn(txn, true); - cout << "Size in bytes = " << serialized_txn.length() << "\nSerialized txn in hex: " << bstr2hexstr(serialized_txn) << endl; - unsigned char str[serialized_txn.length()/2]; - for (int i = 0; i < serialized_txn.length(); i+=2) - str[i/2] = (hex2int(serialized_txn[i]) << 4) + hex2int(serialized_txn[i+1]); - - unsigned char sha2[64]; - SHA256_CTX sha256; - SHA256_Init(&sha256); - SHA256_Update(&sha256, str, sizeof(str)); - SHA256_Final(sha, &sha256); - - SHA256_Init(&sha256); - SHA256_Update(&sha256, sha, sizeof(sha)); - SHA256_Final(sha2, &sha256); + cout << "Serialized txn: " << bstr2hexstr(serialized_txn, serialized_txn.length()) << endl; - // SHA256((unsigned char*) txn_str.c_str(), sizeof(txn_str.c_str()), sha); - // SHA256(sha, sizeof(sha), sha2); + signature.pop_back(); + signature.pop_back(); - // Verify sig with pubKey and txn hash using ecdsa - char key[pubKey.length() + 1]; - sprintf(key, "%s", pubKey.c_str()); + char sig_byte[signature.length()/2]; + for (int i = 0; i < signature.length(); i+=2) + sig_byte[i/2] = (hex2int(signature[i]) << 4) + hex2int(signature[i+1]); - EC_KEY* publicKey = EC_KEY_new_by_curve_name(NID_secp256k1); - EC_KEY_set_public_key(publicKey, Hex_to_point_NID_secp256k1(key)); + char sha[SHA256_DIGEST_LENGTH]; + SHA256((unsigned char*) serialized_txn.c_str(), serialized_txn.length(), (unsigned char*) sha); + SHA256((unsigned char*) sha, SHA256_DIGEST_LENGTH, (unsigned char*) sha); - unsigned char sig[scriptSigOps[1].length()/2]; - for (int i = 0; i < scriptSigOps[1].length(); i+=2) - sig[i/2] = (hex2int(scriptSigOps[1][i]) << 4) + hex2int(scriptSigOps[1][i+1]); - - unsigned char sha_hex[sizeof(sha2)/2]; - for (int i = 0; i < sizeof(sha2); i+=2) - sha_hex[i/2] = (hex2int(sha2[i]) << 4) + hex2int(sha2[i+1]); - - if (ECDSA_verify(0, sha_hex, sizeof(sha_hex), sig, sizeof(sig), publicKey) == 1) { - cout << "Match!!" << endl; - EC_KEY_free(publicKey); // Free allocated memory - return true; - } + // Verify sig with pubKey and txn hash using ecdsa + EC_KEY* publicKey = EC_KEY_new_by_curve_name(NID_secp256k1); + EC_KEY_set_public_key(publicKey, Hex_to_point_NID_secp256k1((char*) pubKey.c_str())); + + int verified = ECDSA_verify(0, (unsigned char*) sha, SHA256_DIGEST_LENGTH, (unsigned char*) sig_byte, signature.length()/2, publicKey); + + if (verified == 1) { + // cout << "Match!!" << endl; EC_KEY_free(publicKey); // Free allocated memory + return true; + } + else if (verified == -1) { + // Get error + char errorString[256]; + ERR_error_string(ERR_get_error(), errorString); + cout << errorString << endl; + cout << bstr2hexstr(serialized_txn, serialized_txn.length()) << endl; + cout << pubKey.length() << ": " << pubKey << endl; + cout << signature.length() << ": " << signature << "\n\n" << endl; } + EC_KEY_free(publicKey); // Free allocated memory return false; } +bool exec_op (string op, stack &stk, Json::Value &txn, int &bytes_to_push) { + // if(!bytes_to_push) + // cout << op << ": "; + + if (op == "OP_0") { + stk.push("0"); + } + else if (op.substr(0, min(op.length(), (size_t) 10)) == "OP_PUSHNUM") { + // cout << "Pushing " << stoi(op.substr(11)) << " to stack" << endl; + stk.push(op.substr(11)); + } + else if (bytes_to_push > 0) { + stk.push(op.substr(0, bytes_to_push*2)); + bytes_to_push = 0; + } + else if (op.substr(0, 12) == "OP_PUSHBYTES") { + bytes_to_push = stoi(op.substr(13)); + // cout << "Pushing " << bytes_to_push << " bytes to stack" << endl; + } + else if (op.substr(0, 11) == "OP_PUSHDATA") { + bytes_to_push = -stoi(op.substr(11)); + // cout << "Pushing " << bytes_to_push << " bytes to stack" << endl; + } + // -ve value of bytes_to_push means that we have to read the first "bytes_to_push" bytes to get the number of bytes to push + else if (bytes_to_push < 0) { + int num_bytes = 0; // Number of bytes to push + for (int i=0; i<-bytes_to_push*2; i+=2) + num_bytes += ((hex2int(op[i]) << 4) + hex2int(op[i+1])) << (4*i); + + stk.push(op.substr(0, -num_bytes*2)); + bytes_to_push = 0; + } + else if (op == "OP_DUP") { + // cout << "Duplicating top stack item" << endl; + stk.push(stk.top()); + } + else if (op == "OP_HASH160") { + // cout << "Hashing the input twice: first with SHA-256 and then with RIPEMD-160" << endl; + + char top_byte[stk.top().length()/2]; + for (int i = 0; i < stk.top().length(); i+=2) + top_byte[i/2] = (hex2int(stk.top()[i]) << 4) + hex2int(stk.top()[i+1]); -bool p2sh_verify(std::vector scriptPubKeyOps, std::vector scriptSigOps, Json::Value txn) { + char sha[SHA256_DIGEST_LENGTH], rpmd[RIPEMD160_DIGEST_LENGTH]; + SHA256((unsigned char*) top_byte, sizeof(top_byte), (unsigned char*) sha); + RIPEMD160((unsigned char*) sha, SHA256_DIGEST_LENGTH, (unsigned char*) rpmd); + stk.pop(); + stk.push(bstr2hexstr(rpmd, sizeof(rpmd))); + } + else if (op == "OP_EQUALVERIFY") { + // cout << "Comparing the top two stack items. If they are not equal, the script fails" << endl; + string top = stk.top(); + stk.pop(); + if (top == stk.top()) + stk.pop(); + else { + stk.push("FALSE"); + return false; + } + } + else if (op == "OP_CHECKSIG") { + // cout << "Verifying txn against signature and public key" << endl; + string pubKey = stk.top(); + stk.pop(); + string sig = stk.top(); + stk.pop(); + if (!verify_sig(pubKey, sig)){ + stk.push("FALSE"); + return false; + } + } + else if (op == "OP_CHECKSIGVERIFY") { + // cout << "Verifying txn against signature and public key. If not valid, the script fails" << endl; + string pubKey = stk.top(); + stk.pop(); + string sig = stk.top(); + stk.pop(); + if (!verify_sig(pubKey, sig)) + return false; + } + else if (op == "OP_EQUAL") { + // cout << "Comparing the top two stack items. If they are not equal, the script fails" << endl; + string top = stk.top(); + stk.pop(); + if (top == stk.top()) { + stk.pop(); + stk.push("TRUE"); + } + else { + stk.pop(); + stk.push("FALSE"); + } + } + else if (op == "OP_VERIFY") { + // cout << "Marks transaction as invalid if top stack value is not true" << endl; + if (stk.top() == "TRUE") + stk.pop(); + else + return false; + } + else if (op == "OP_CHECKMULTISIG") { + // cout << "For each signature and public key pair, OP_CHECKSIG is executed. If all signatures are valid, the script returns true" << endl; + + int num_pubKeys = stoi(stk.top()); + stk.pop(); + vector pubKeys; + for (int i = 0; i < num_pubKeys; i++) { + pubKeys.push_back(stk.top()); + stk.pop(); + } + + int num_sigs = stoi(stk.top()); + stk.pop(); + vector sigs; + for (int i = 0; i < num_sigs; i++) { + sigs.push_back(stk.top()); + stk.pop(); + } + + int j = 0; + for (int i = 0; i < num_sigs; i++) { + // Checked all the pubKeys but still have some signatures left + if (j >= num_pubKeys) { + stk.push("FALSE"); + return false; + } + // Public key does not match the signature, so check with next public key + else if (!verify_sig(pubKeys[j], sigs[i])) { + i--; + j++; + } + else + j++; + } + } + else if (op == "OP_DROP") { + // cout << "Dropping the top stack item" << endl; + stk.pop(); + } + else if (op == "OP_DEPTH") { + // cout << "Pushing the number of stack items to stack" << endl; + stk.push(to_string(stk.size())); + } + else { + // cout << "Invalid operation" << endl; + return false; + } + return true; +} + +string identifyScriptType(std::vector &ops) { + if (ops[0] == "OP_DUP" && ops[1] == "OP_HASH160" && ops[2] == "OP_PUSHBYTES_20" && ops[4] == "OP_EQUALVERIFY" && ops[5] == "OP_CHECKSIG") + return "p2pkh"; + else if (ops[0] == "OP_HASH160" && ops[1] == "OP_PUSHBYTES_20" && ops[3] == "OP_EQUAL") + return "p2sh"; + else if (ops[0] == "OP_0" && ops[1] == "OP_PUSHBYTES_20") + return "p2wpkh"; + else if (ops[0] == "OP_0" && ops[1] == "OP_PUSHBYTES_32") + return "p2wsh"; + else if (ops[0] == "OP_PUSHNUM_1" && ops[1] == "OP_PUSHBYTES_32") + return "p2tr"; + else + return "Invalid"; +} + +bool p2pkh_verify(std::vector &scriptPubKeyOps, std::vector &scriptSigOps, Json::Value &txn) { + stack stk; + int bytes_to_push = 0; + + for (string op : scriptSigOps){ + bool return_val = exec_op(op, stk, txn, bytes_to_push); + if (!return_val) + return false; + } + + for (string op : scriptPubKeyOps){ + bool return_val = exec_op(op, stk, txn, bytes_to_push); + if (!return_val) + return false; + } + + return true; } +bool p2wpkh_verify(std::vector scriptPubKeyOps, std::vector witness, Json::Value txn) { + stack stk; + int bytes_to_push = 0; + + stk.push(witness[0]); + stk.push(witness[1]); -bool p2wpkh_verify(std::vector scriptPubKeyOps, std::vector scriptSigOps, Json::Value txn) { + bool return_val = exec_op("OP_DUP", stk, txn, bytes_to_push); + return_val = exec_op("OP_HASH160", stk, txn, bytes_to_push); + + for (string op : scriptPubKeyOps){ + // Not putting 0 on stack because it will fail OP_EQUALVERIFY + if (op == "OP_0") + continue; + return_val = exec_op(op, stk, txn, bytes_to_push); + if (!return_val) + return false; + } + + return_val = exec_op("OP_EQUALVERIFY", stk, txn, bytes_to_push); + if (!return_val) + return false; + return_val = exec_op("OP_CHECKSIG", stk, txn, bytes_to_push); + if (!return_val) + return false; + + return true; } -bool p2wsh_verify(std::vector scriptPubKeyOps, std::vector scriptSigOps, Json::Value txn) { +bool p2wsh_verify(std::vector &scriptPubKeyOps, std::vector &witness, std::vector &witnessScriptOps, Json::Value &txn) { + stack stk; + int bytes_to_push = 0; + + for (string op : scriptPubKeyOps){ + bool return_val = exec_op(op, stk, txn, bytes_to_push); + if (!return_val) + return false; + } + + stk.push(witness.back()); + + bool return_val = exec_op("OP_EQUALVERIFY", stk, txn, bytes_to_push); + if (!return_val) + return false; + + for (int i = 1; i < witness.size() - 1; i++) + stk.push(witness[i]); + for (string op : witnessScriptOps){ + return_val = exec_op(op, stk, txn, bytes_to_push); + if (!return_val) + return false; + } + + return true; +} + + +bool p2tr_verify(std::vector &scriptPubKeyOps, std::vector &scriptSigOps, Json::Value &txn) { + return true; +} + + +bool multisig_verify(std::vector &redeemScriptOps, std::vector &scriptSigOps, Json::Value &txn) { + stack stk; + int bytes_to_push = 0; + + for (string op : scriptSigOps){ + bool return_val = exec_op(op, stk, txn, bytes_to_push); + if (!return_val) + return false; + } + + for (string op : redeemScriptOps){ + bool return_val = exec_op(op, stk, txn, bytes_to_push); + if (!return_val) + return false; + } + + return true; } +bool p2sh_verify(std::vector &scriptPubKeyOps, std::vector &scriptSigOps, std::vector &redeemScriptOps, Json::Value &txn, Json::Value &inp) { + stack stk; + int bytes_to_push = 0; + + // Push values from scriptSig to stack + for (string op : scriptSigOps){ + bool return_val = exec_op(op, stk, txn, bytes_to_push); + if (!return_val) + return false; + } + + // Push values from pubKeyScript to stack + for (string op : scriptPubKeyOps){ + bool return_val = exec_op(op, stk, txn, bytes_to_push); + if (!return_val) + return false; + } -bool p2tr_verify(std::vector scriptPubKeyOps, std::vector scriptSigOps, Json::Value txn) { + if (stk.top() == "FALSE") + return false; + // Identify the type of redeem script + string redeemScriptType = identifyScriptType(redeemScriptOps); + + if (redeemScriptType == "p2wpkh") { + std::vector witness; + for (auto &wtns : inp["witness"]) + witness.push_back(wtns.asString()); + return p2wpkh_verify(redeemScriptOps, witness, txn); + } + else if (redeemScriptType == "p2wsh") { + std::vector witness; + for (auto &wtns : inp["witness"]) + witness.push_back(wtns.asString()); + std::vector redeemScriptOps = getOps(inp["redeemscript_asm"].asString()); + return p2wsh_verify(redeemScriptOps, witness, redeemScriptOps, txn); + } + // Invalid means it is a multisig script + else if (redeemScriptType == "Invalid") + return multisig_verify(redeemScriptOps, scriptSigOps, txn); + + return true; } -bool verify_txn(Json::Value &txn, std::string txn_str) { +/* +Returns -1 if the transaction is invalid +Else returns the transaction fees +*/ +int verify_txn(Json::Value &txn) { bool valid = true; + // Check if sum of input values is greater than sum of output values + uint64_t sum_input = 0, sum_output = 0; + for (auto &in : txn["vin"]) + sum_input += in["prevout"]["value"].asUInt64(); + for (auto &out : txn["vout"]) + sum_output += out["value"].asUInt64(); + if (sum_input < sum_output) { + // invalid_txns++; + return -1; + } + + // Validate each input + set txn_inputs; for (auto &inp : txn["vin"]) { + // Check if the input is already used either in any previous txn or in the current txn (double spending) + if (inputs_used.find(inp["txid"].asString()) != inputs_used.end() || txn_inputs.find(inp["txid"].asString()) != txn_inputs.end()) + return -1; + txn_inputs.insert(inp["txid"].asString()); + + // total++; std::vector scriptPubKeyOps = getOps(inp["prevout"]["scriptpubkey_asm"].asString()); - std::vector scriptSigOps = getOps(inp["scriptsig_asm"].asString()); std::string scriptPubKeyType = inp["prevout"]["scriptpubkey_type"].asString(); - if (scriptPubKeyType == "p2pkh") + // DONE + if (scriptPubKeyType == "p2pkh") { + serialized_txn = serialize_txn(txn, true); + std::vector scriptSigOps = getOps(inp["scriptsig_asm"].asString()); valid = p2pkh_verify(scriptPubKeyOps, scriptSigOps, txn); - else if (scriptPubKeyType == "p2sh") - valid = p2sh_verify(scriptPubKeyOps, scriptSigOps, txn); - else if (scriptPubKeyType == "p2wpkh") - valid = p2wpkh_verify(scriptPubKeyOps, scriptSigOps, txn); - else if (scriptPubKeyType == "p2wsh") - valid = p2wsh_verify(scriptPubKeyOps, scriptSigOps, txn); - else if (scriptPubKeyType == "p2tr") - valid = p2tr_verify(scriptPubKeyOps, scriptSigOps, txn); - else - return false; - + } + else if (scriptPubKeyType == "p2sh") { + serialized_txn = serialize_segwit_1(txn) + serialize_segwit_inp(inp) + serialize_segwit_2(txn); + std::vector scriptSigOps = getOps(inp["scriptsig_asm"].asString()); + std::vector redeemScriptOps = getOps(inp["inner_redeemscript_asm"].asString()); + valid = p2sh_verify(scriptPubKeyOps, scriptSigOps, redeemScriptOps, txn, inp); + } + // DONE + else if (scriptPubKeyType == "v0_p2wpkh") { + serialized_txn = serialize_segwit_1(txn) + serialize_segwit_inp(inp) + serialize_segwit_2(txn); + std::vector witness; + for (auto &wtns : inp["witness"]) + witness.push_back(wtns.asString()); + valid = p2wpkh_verify(scriptPubKeyOps, witness, txn); + } + else if (scriptPubKeyType == "v0_p2wsh") { + serialized_txn = serialize_segwit_1(txn) + serialize_segwit_inp(inp) + serialize_segwit_2(txn); + std::vector witness; + for (auto &wtns : inp["witness"]) + witness.push_back(wtns.asString()); + std::vector witnessScriptOps = getOps(inp["inner_witnessscript_asm"].asString()); + valid = p2wsh_verify(scriptPubKeyOps, witness, witnessScriptOps, txn); + } + // DONE + else if (scriptPubKeyType == "v1_p2tr") { + std::vector witness; + for (auto &wtns : inp["witness"]) + witness.push_back(wtns.asString()); + valid = p2tr_verify(scriptPubKeyOps, witness, txn); + } + + // if (valid) + // valid_txns++; + // else + // invalid_txns++; + if (!valid) - return false; + return -1; } - return true; + // Valid transaction, hence add the inputs to the set + for (auto &inp : txn["vin"]) + inputs_used.insert(inp["txid"].asString()); + + return sum_input - sum_output; } #endif // SCRIPT_H \ No newline at end of file diff --git a/src/serialize.h b/src/serialize.h index 759db11..e7feb60 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -3,17 +3,28 @@ #include #include +#include #include #include +#include using namespace std; -extern uint8_t hex2int(char ch); - #define IS_UINT32_T 0 #define IS_INT64_T 1 typedef basic_string ustring; +uint8_t hex2int(char ch) +{ + if (ch >= '0' && ch <= '9') + return ch - '0'; + if (ch >= 'A' && ch <= 'F') + return ch - 'A' + 10; + if (ch >= 'a' && ch <= 'f') + return ch - 'a' + 10; + return -1; +} + // JUST FOR TESTING => MAY REMOVE // Convert byte (of the form 0x0X) to hex char byte2hex(char num){ @@ -23,44 +34,52 @@ char byte2hex(char num){ // JUST FOR TESTING => MAY REMOVE // Convert byte string to hex string -string bstr2hexstr(string byte_string){ +string bstr2hexstr(string byte_string, size_t len){ string hex_string = ""; - for (int i = 0; i < byte_string.length(); i++){ + for (int i = 0; i < len; i++){ hex_string.push_back(byte2hex(((unsigned char) byte_string[i])/16)); hex_string.push_back(byte2hex(((unsigned char) byte_string[i])%16)); } return hex_string; } -string hexstr2bstr (string hex_string) { +// Convert hex string to byte string +// @param reverse: For inverting txn id for txn serialization +string hexstr2bstr (string hex_string, bool reverse = false) { string byte_string = ""; - for (int i = 0; i < hex_string.length(); i+=2) - byte_string.push_back((hex2int(hex_string[i]) << 4) + hex2int(hex_string[i+1])); + if (reverse) { + for (int i = hex_string.length() - 2; i >= 0; i-=2) + byte_string.push_back((hex2int(hex_string[i]) << 4) + hex2int(hex_string[i+1])); + } + else { + for (int i = 0; i < hex_string.length(); i+=2) + byte_string.push_back((hex2int(hex_string[i]) << 4) + hex2int(hex_string[i+1])); + } return byte_string; } -// Convert integer to hex string (binary format) -string int2hex (int64_t num, int type = IS_UINT32_T) { - string hex = ""; +// Convert integer to hex string (binary format) as little-endian +string int2bin (int64_t num, int type = IS_UINT32_T) { + string bin = ""; if (num == 0) { for (int i=0; i<4*type + 4; i++) - hex.push_back(0); - return hex; + bin.push_back(0); + return bin; } // 2's complement if (num < 0) num = (UINT64_MAX + num) + 1; - for (int i = 0; i < 4*type + 3 - log2(abs(num))/8; i++) - hex.push_back(0); while (num != 0) { - hex.push_back(abs(num) % 256); + bin.push_back(num % 256); num /= 256; } + for (int i = bin.length(); i < 4*(type + 1); i++) + bin.push_back(0); - return hex; + return bin; } // Convert integer to CompactSize uint (binary format) @@ -96,37 +115,113 @@ string int2compact (uint64_t num) { return comp; } -string sertialize_txn (Json::Value txn, bool isCleared = false) { +string serialize_txn (Json::Value txn, bool isCleared = false) { string ser_txn = ""; - ser_txn += int2hex(txn["version"].asUInt()); + ser_txn += int2bin(txn["version"].asUInt()); ser_txn += int2compact(txn["vin"].size()); for (auto &inp : txn["vin"]) { // outpoint - ser_txn += hexstr2bstr(inp["txid"].asString()); - ser_txn += int2hex(inp["vout"].asUInt()); - - if (isCleared) - ser_txn += int2compact(0); + string txid = hexstr2bstr(inp["txid"].asString()); + reverse(txid.begin(), txid.end()); + ser_txn += txid; + ser_txn += int2bin(inp["vout"].asUInt()); + + if (isCleared) { + ser_txn += int2compact(inp["prevout"]["scriptpubkey"].asString().length()/2); + ser_txn += hexstr2bstr(inp["prevout"]["scriptpubkey"].asString()); + } else { ser_txn += int2compact(inp["scriptsig"].asString().length()/2); ser_txn += hexstr2bstr(inp["scriptsig"].asString()); } - ser_txn += int2hex(inp["sequence"].asUInt()); + ser_txn += int2bin(inp["sequence"].asUInt()); } ser_txn += int2compact(txn["vout"].size()); for (auto &out : txn["vout"]) { - ser_txn += int2hex(out["value"].asInt64(), IS_INT64_T); + ser_txn += int2bin(out["value"].asInt64(), IS_INT64_T); ser_txn += int2compact(out["scriptpubkey"].asString().length()/2); ser_txn += hexstr2bstr(out["scriptpubkey"].asString()); } - ser_txn += int2hex(txn["locktime"].asUInt()); + ser_txn += int2bin(txn["locktime"].asUInt()); return ser_txn; } +string serialize_segwit_1 (Json::Value txn) { + string serialized = ""; + + serialized += int2bin(txn["version"].asUInt()); + + string inputs = ""; + for (auto &inp : txn["vin"]) { + // outpoint + string txid = hexstr2bstr(inp["txid"].asString()); + reverse(txid.begin(), txid.end()); + inputs += txid; + inputs += int2bin(inp["vout"].asUInt()); + } + char sha[SHA256_DIGEST_LENGTH]; + SHA256((unsigned char*) inputs.c_str(), inputs.length(), (unsigned char*) sha); + SHA256((unsigned char*) sha, SHA256_DIGEST_LENGTH, (unsigned char*) sha); + for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) + serialized.push_back(sha[i]); + + string sequences = ""; + for (auto &inp : txn["vin"]) { + sequences += int2bin(inp["sequence"].asUInt()); + } + SHA256((unsigned char*) sequences.c_str(), sequences.length(), (unsigned char*) sha); + SHA256((unsigned char*) sha, SHA256_DIGEST_LENGTH, (unsigned char*) sha); + for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) + serialized.push_back(sha[i]); + + return serialized; +} + + +string serialize_segwit_inp (Json::Value inp) { + string ser_inp = ""; + + ser_inp = hexstr2bstr(inp["txid"].asString()); + reverse(ser_inp.begin(), ser_inp.end()); + ser_inp += int2bin(inp["vout"].asUInt()); + + // Need to find the PKH from prevout's scriptpubkey + string scriptcode = "1976a914"; + scriptcode += inp["prevout"]["scriptpubkey"].asString().substr(4); + scriptcode += "88ac"; + ser_inp += hexstr2bstr(scriptcode); + + ser_inp += int2bin(inp["prevout"]["value"].asInt64(), IS_INT64_T); + + ser_inp += int2bin(inp["sequence"].asUInt()); + + return ser_inp; +} + +string serialize_segwit_2 (Json::Value txn) { + string serialized = ""; + + for (auto &out : txn["vout"]) { + serialized += int2bin(out["value"].asInt64(), IS_INT64_T); + serialized += int2compact(out["scriptpubkey"].asString().length()/2); + serialized += hexstr2bstr(out["scriptpubkey"].asString()); + } + char sha[SHA256_DIGEST_LENGTH]; + SHA256((unsigned char*) serialized.c_str(), serialized.length(), (unsigned char*) sha); + SHA256((unsigned char*) sha, SHA256_DIGEST_LENGTH, (unsigned char*) sha); + serialized = ""; + for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) + serialized.push_back(sha[i]); + + serialized += int2bin(txn["locktime"].asUInt()); + + return serialized; +} + #endif // SERIALIZE_H \ No newline at end of file