From 5640447d6e94b8aa4ad6ce5e0bc856ef0d5135a1 Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Sat, 25 Nov 2017 18:11:17 +0100 Subject: [PATCH 01/27] Base58 for stealth --- src/base58.cpp | 16 ---------------- src/base58.h | 27 +++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/base58.cpp b/src/base58.cpp index 71a555718..0d66533e9 100644 --- a/src/base58.cpp +++ b/src/base58.cpp @@ -202,22 +202,6 @@ int CBase58Data::CompareTo(const CBase58Data& b58) const return 0; } -namespace -{ -class CAnoncoinAddressVisitor : public boost::static_visitor -{ -private: - CAnoncoinAddress* addr; -public: - CAnoncoinAddressVisitor(CAnoncoinAddress* addrIn) : addr(addrIn) {} - - bool operator()(const CKeyID& id) const { return addr->Set(id); } - bool operator()(const CScriptID& id) const { return addr->Set(id); } - bool operator()(const CNoDestination& no) const { return false; } -}; - -} // anon namespace - bool CAnoncoinAddress::Set(const CKeyID& id) { SetData(Params().Base58Prefix(CChainParams::PUBKEY_ADDRESS), &id, 20); diff --git a/src/base58.h b/src/base58.h index 586fda230..1acebfc98 100644 --- a/src/base58.h +++ b/src/base58.h @@ -19,6 +19,10 @@ #include "key.h" #include "script.h" +#ifdef ENABLE_STEALTH +#include "stealth.h" +#endif + #include #include @@ -99,6 +103,20 @@ class CBase58Data * Script-hash-addresses have version 5 (or 196 testnet). * The data vector contains RIPEMD160(SHA256(cscript)), where cscript is the serialized redemption script. */ +#ifdef ENABLE_STEALTH +class CAnoncoinAddress; +class CAnoncoinAddressVisitor : public boost::static_visitor { +private: + CAnoncoinAddress *addr; +public: + CAnoncoinAddressVisitor(CAnoncoinAddress *addrIn) : addr(addrIn) { } + bool operator()(const CKeyID &id) const; + bool operator()(const CScriptID &id) const; + bool operator()(const CStealthAddress &stxAddr) const; + bool operator()(const CNoDestination &no) const; +}; +#endif + class CAnoncoinAddress : public CBase58Data { public: bool Set(const CKeyID &id); @@ -117,6 +135,15 @@ class CAnoncoinAddress : public CBase58Data { bool IsScript() const; }; +#ifdef ENABLE_STEALTH + +bool inline CAnoncoinAddressVisitor::operator()(const CKeyID &id) const { return addr->Set(id); } +bool inline CAnoncoinAddressVisitor::operator()(const CScriptID &id) const { return addr->Set(id); } +bool inline CAnoncoinAddressVisitor::operator()(const CStealthAddress &stxAddr) const { return false; } +bool inline CAnoncoinAddressVisitor::operator()(const CNoDestination &id) const { return false; } + +#endif + /** * A base58-encoded secret key */ From 487fe4634151e72504592ebf8b9ffcc481408ad7 Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Sat, 25 Nov 2017 18:11:36 +0100 Subject: [PATCH 02/27] fix chmod --- src/i2pwrapper.cpp | 0 src/i2pwrapper.h | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 src/i2pwrapper.cpp mode change 100755 => 100644 src/i2pwrapper.h diff --git a/src/i2pwrapper.cpp b/src/i2pwrapper.cpp old mode 100755 new mode 100644 diff --git a/src/i2pwrapper.h b/src/i2pwrapper.h old mode 100755 new mode 100644 From 932afa18c0354a0bf14c98e9978d69d98d86c2b8 Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Sat, 25 Nov 2017 18:12:11 +0100 Subject: [PATCH 03/27] Stealth address implementation --- src/stealth.cpp | 610 ++++++++++++++++++++++++++++++++++++++++++++++++ src/stealth.h | 111 +++++++++ 2 files changed, 721 insertions(+) create mode 100644 src/stealth.cpp create mode 100644 src/stealth.h diff --git a/src/stealth.cpp b/src/stealth.cpp new file mode 100644 index 000000000..f2ffdafc6 --- /dev/null +++ b/src/stealth.cpp @@ -0,0 +1,610 @@ +// Copyright (c) 2017 The Anoncoin developers + +#include "stealth.h" +#include "base58.h" +#include "util.h" + + +#include +#include +#include +#include +#include + +bool fTestNet = GetBoolArg("-testnet", false); +//const uint8_t stealth_version_byte = 0x2a; +const uint8_t stealth_version_byte = (!fTestNet) ? 0x17 : 0x53; + + + +bool CStealthAddress::SetEncoded(const std::string& encodedAddress) { + data_chunk raw; + + if (!DecodeBase58(encodedAddress, raw)) { + if (fDebug) + printf("CStealthAddress::SetEncoded DecodeBase58 falied.\n"); + return false; + } + + if (!VerifyChecksum(raw)) { + if (fDebug) + printf("CStealthAddress::SetEncoded verify_checksum falied.\n"); + return false; + } + + if (raw.size() < 1 + 1 + 33 + 1 + 33 + 1 + 1 + 4) { + if (fDebug) + printf("CStealthAddress::SetEncoded() too few bytes provided.\n"); + return false; + } + + + uint8_t* p = &raw[0]; + uint8_t version = *p++; + + if (version != stealth_version_byte) { + printf("CStealthAddress::SetEncoded version mismatch 0x%x != 0x%x.\n", version, stealth_version_byte); + return false; + } + + options = *p++; + + scan_pubkey.resize(33); + memcpy(&scan_pubkey[0], p, 33); + p += 33; + //uint8_t spend_pubkeys = *p++; + p++; + + spend_pubkey.resize(33); + memcpy(&spend_pubkey[0], p, 33); + + return true; +} + +std::string CStealthAddress::Encoded() const { + // https://wiki.unsystem.net/index.php/DarkWallet/Stealth#Address_format + // [version] [options] [scan_key] [N] ... [Nsigs] [prefix_length] ... + + data_chunk raw; + raw.push_back(stealth_version_byte); + + raw.push_back(options); + + raw.insert(raw.end(), scan_pubkey.begin(), scan_pubkey.end()); + raw.push_back(1); // number of spend pubkeys + raw.insert(raw.end(), spend_pubkey.begin(), spend_pubkey.end()); + raw.push_back(0); // number of signatures + raw.push_back(0); // ? + + AppendChecksum(raw); + + return EncodeBase58(raw); +} + + +uint32_t AnoncoinChecksum(uint8_t* p, uint32_t nBytes) { + if (!p || nBytes == 0) + return 0; + + uint8_t hash1[32]; + SHA256(p, nBytes, (uint8_t*)hash1); + uint8_t hash2[32]; + SHA256((uint8_t*)hash1, sizeof(hash1), (uint8_t*)hash2); + + // -- checksum is the 1st 4 bytes of the hash + uint32_t checksum = from_little_endian(&hash2[0]); + + return checksum; +} + +void AppendChecksum(data_chunk& data) { + uint32_t checksum = AnoncoinChecksum(&data[0], data.size()); + + // -- to_little_endian + std::vector tmp(4); + + //memcpy(&tmp[0], &checksum, 4); + for (int i = 0; i < 4; ++i) { + tmp[i] = checksum & 0xFF; + checksum >>= 8; + } + + data.insert(data.end(), tmp.begin(), tmp.end()); +} + +bool VerifyChecksum(const data_chunk& data) { + if (data.size() < 4) + return false; + + uint32_t checksum = from_little_endian(data.end() - 4); + + return AnoncoinChecksum((uint8_t*)&data[0], data.size()-4) == checksum; +} + + +int GenerateRandomSecret(ec_secret& out) { + RandAddSeedPerfmon(); + + static uint256 max("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140"); + static uint256 min(16000); // increase? min valid key is 1 + + uint256 test; + + int i; + // -- check max, try max 32 times + for (i = 0; i < 32; ++i) { + RAND_bytes((unsigned char*) test.begin(), 32); + if (test > min && test < max) { + memcpy(&out.e[0], test.begin(), 32); + break; + } + } + + if (i > 31) { + printf("Error: GenerateRandomSecret failed to generate a valid key.\n"); + return 1; + } + + return 0; +} + +int SecretToPublicKey(const ec_secret& secret, ec_point& out) { + // -- public key = private * G + int rv = 0; + + EC_GROUP *ecgrp = EC_GROUP_new_by_curve_name(NID_secp256k1); + + if (!ecgrp) { + printf("SecretToPublicKey(): EC_GROUP_new_by_curve_name failed.\n"); + return 1; + } + + BIGNUM* bnIn = BN_bin2bn(&secret.e[0], ec_secret_size, BN_new()); + if (!bnIn) { + EC_GROUP_free(ecgrp); + printf("SecretToPublicKey(): BN_bin2bn failed\n"); + return 1; + } + + EC_POINT* pub = EC_POINT_new(ecgrp); + + + EC_POINT_mul(ecgrp, pub, bnIn, NULL, NULL, NULL); + + BIGNUM* bnOut = EC_POINT_point2bn(ecgrp, pub, POINT_CONVERSION_COMPRESSED, BN_new(), NULL); + if (!bnOut) { + printf("SecretToPublicKey(): point2bn failed\n"); + rv = 1; + } else { + out.resize(ec_compressed_size); + if (BN_num_bytes(bnOut) != (int) ec_compressed_size || BN_bn2bin(bnOut, &out[0]) != (int) ec_compressed_size) { + printf("SecretToPublicKey(): bnOut incorrect length.\n"); + rv = 1; + } + + BN_free(bnOut); + } + + EC_GROUP_free(ecgrp); + BN_free(bnIn); + EC_POINT_free(pub); + + return rv; +} + + +int StealthSecret(ec_secret& secret, ec_point& pubkey, const ec_point& pkSpend, ec_secret& sharedSOut, ec_point& pkOut) { + /* + + send: + secret = ephem_secret, pubkey = scan_pubkey + + receive: + secret = scan_secret, pubkey = ephem_pubkey + c = H(dP) + + Q = public scan key (EC point, 33 bytes) + d = private scan key (integer, 32 bytes) + R = public spend key + f = private spend key + + Q = dG + R = fG + + Sender (has Q and R, not d or f): + + P = eG + + c = H(eQ) = H(dP) + R' = R + cG + + + Recipient gets R' and P + + test 0 and infinity? + */ + + int rv = 0; + std::vector vchOutQ; + + BN_CTX* bnCtx = NULL; + BIGNUM* bnEphem = NULL; + BIGNUM* bnQ = NULL; + EC_POINT* Q = NULL; + BIGNUM* bnOutQ = NULL; + BIGNUM* bnc = NULL; + EC_POINT* C = NULL; + BIGNUM* bnR = NULL; + EC_POINT* R = NULL; + EC_POINT* Rout = NULL; + BIGNUM* bnOutR = NULL; + + EC_GROUP* ecgrp = EC_GROUP_new_by_curve_name(NID_secp256k1); + + if (!ecgrp) { + printf("StealthSecret(): EC_GROUP_new_by_curve_name failed.\n"); + return 1; + } + + if (!(bnCtx = BN_CTX_new())) { + printf("StealthSecret(): BN_CTX_new failed.\n"); + rv = 1; + goto End; + } + + if (!(bnEphem = BN_bin2bn(&secret.e[0], ec_secret_size, BN_new()))) { + printf("StealthSecret(): bnEphem BN_bin2bn failed.\n"); + rv = 1; + goto End; + } + + if (!(bnQ = BN_bin2bn(&pubkey[0], pubkey.size(), BN_new()))) { + printf("StealthSecret(): bnQ BN_bin2bn failed\n"); + rv = 1; + goto End; + } + + if (!(Q = EC_POINT_bn2point(ecgrp, bnQ, NULL, bnCtx))) { + printf("StealthSecret(): Q EC_POINT_bn2point failed\n"); + rv = 1; + goto End; + } + + // -- eQ + // EC_POINT_mul(const EC_GROUP *group, EC_POINT *r, const BIGNUM *n, const EC_POINT *q, const BIGNUM *m, BN_CTX *ctx); + // EC_POINT_mul calculates the value generator * n + q * m and stores the result in r. The value n may be NULL in which case the result is just q * m. + if (!EC_POINT_mul(ecgrp, Q, NULL, Q, bnEphem, bnCtx)) { + printf("StealthSecret(): eQ EC_POINT_mul failed\n"); + rv = 1; + goto End; + } + + if (!(bnOutQ = EC_POINT_point2bn(ecgrp, Q, POINT_CONVERSION_COMPRESSED, BN_new(), bnCtx))) { + printf("StealthSecret(): Q EC_POINT_bn2point failed\n"); + rv = 1; + goto End; + } + + + vchOutQ.resize(ec_compressed_size); + if (BN_num_bytes(bnOutQ) != (int) ec_compressed_size || BN_bn2bin(bnOutQ, &vchOutQ[0]) != (int) ec_compressed_size) { + printf("StealthSecret(): bnOutQ incorrect length.\n"); + rv = 1; + goto End; + } + + SHA256(&vchOutQ[0], vchOutQ.size(), &sharedSOut.e[0]); + + if (!(bnc = BN_bin2bn(&sharedSOut.e[0], ec_secret_size, BN_new()))) { + printf("StealthSecret(): BN_bin2bn failed\n"); + rv = 1; + goto End; + } + + // -- cG + if (!(C = EC_POINT_new(ecgrp))) { + printf("StealthSecret(): C EC_POINT_new failed\n"); + rv = 1; + goto End; + } + + if (!EC_POINT_mul(ecgrp, C, bnc, NULL, NULL, bnCtx)) { + printf("StealthSecret(): C EC_POINT_mul failed\n"); + rv = 1; + goto End; + } + + if (!(bnR = BN_bin2bn(&pkSpend[0], pkSpend.size(), BN_new()))) { + printf("StealthSecret(): bnR BN_bin2bn failed\n"); + rv = 1; + goto End; + } + + + if (!(R = EC_POINT_bn2point(ecgrp, bnR, NULL, bnCtx))) { + printf("StealthSecret(): R EC_POINT_bn2point failed\n"); + rv = 1; + goto End; + } + + if (!EC_POINT_mul(ecgrp, C, bnc, NULL, NULL, bnCtx)) { + printf("StealthSecret(): C EC_POINT_mul failed\n"); + rv = 1; + goto End; + } + + if (!(Rout = EC_POINT_new(ecgrp))) { + printf("StealthSecret(): Rout EC_POINT_new failed\n"); + rv = 1; + goto End; + } + + if (!EC_POINT_add(ecgrp, Rout, R, C, bnCtx)) { + printf("StealthSecret(): Rout EC_POINT_add failed\n"); + rv = 1; + goto End; + } + + if (!(bnOutR = EC_POINT_point2bn(ecgrp, Rout, POINT_CONVERSION_COMPRESSED, BN_new(), bnCtx))) { + printf("StealthSecret(): Rout EC_POINT_bn2point failed\n"); + rv = 1; + goto End; + } + + + pkOut.resize(ec_compressed_size); + if (BN_num_bytes(bnOutR) != (int) ec_compressed_size || BN_bn2bin(bnOutR, &pkOut[0]) != (int) ec_compressed_size) { + printf("StealthSecret(): pkOut incorrect length.\n"); + rv = 1; + goto End; + } + + End: + if (bnOutR) BN_free(bnOutR); + if (Rout) EC_POINT_free(Rout); + if (R) EC_POINT_free(R); + if (bnR) BN_free(bnR); + if (C) EC_POINT_free(C); + if (bnc) BN_free(bnc); + if (bnOutQ) BN_free(bnOutQ); + if (Q) EC_POINT_free(Q); + if (bnQ) BN_free(bnQ); + if (bnEphem) BN_free(bnEphem); + if (bnCtx) BN_CTX_free(bnCtx); + EC_GROUP_free(ecgrp); + + return rv; +} + + +int StealthSecretSpend(ec_secret& scanSecret, ec_point& ephemPubkey, ec_secret& spendSecret, ec_secret& secretOut) { + /* + + c = H(dP) + R' = R + cG [without decrypting wallet] + = (f + c)G [after decryption of wallet] + Remember: mod curve.order, pad with 0x00s where necessary? + */ + + int rv = 0; + std::vector vchOutP; + + BN_CTX* bnCtx = NULL; + BIGNUM* bnScanSecret = NULL; + BIGNUM* bnP = NULL; + EC_POINT* P = NULL; + BIGNUM* bnOutP = NULL; + BIGNUM* bnc = NULL; + BIGNUM* bnOrder = NULL; + BIGNUM* bnSpend = NULL; + + EC_GROUP* ecgrp = EC_GROUP_new_by_curve_name(NID_secp256k1); + + if (!ecgrp) { + printf("StealthSecretSpend(): EC_GROUP_new_by_curve_name failed.\n"); + return 1; + } + + if (!(bnCtx = BN_CTX_new())) { + printf("StealthSecretSpend(): BN_CTX_new failed.\n"); + rv = 1; + goto End; + } + + if (!(bnScanSecret = BN_bin2bn(&scanSecret.e[0], ec_secret_size, BN_new()))) { + printf("StealthSecretSpend(): bnScanSecret BN_bin2bn failed.\n"); + rv = 1; + goto End; + } + + if (!(bnP = BN_bin2bn(&ephemPubkey[0], ephemPubkey.size(), BN_new()))) { + printf("StealthSecretSpend(): bnP BN_bin2bn failed\n"); + rv = 1; + goto End; + } + + if (!(P = EC_POINT_bn2point(ecgrp, bnP, NULL, bnCtx))) { + printf("StealthSecretSpend(): P EC_POINT_bn2point failed\n"); + rv = 1; + goto End; + } + + // -- dP + if (!EC_POINT_mul(ecgrp, P, NULL, P, bnScanSecret, bnCtx)) { + printf("StealthSecretSpend(): dP EC_POINT_mul failed\n"); + rv = 1; + goto End; + } + + if (!(bnOutP = EC_POINT_point2bn(ecgrp, P, POINT_CONVERSION_COMPRESSED, BN_new(), bnCtx))) { + printf("StealthSecretSpend(): P EC_POINT_bn2point failed\n"); + rv = 1; + goto End; + } + + + vchOutP.resize(ec_compressed_size); + if (BN_num_bytes(bnOutP) != (int) ec_compressed_size || BN_bn2bin(bnOutP, &vchOutP[0]) != (int) ec_compressed_size) { + printf("StealthSecretSpend(): bnOutP incorrect length.\n"); + rv = 1; + goto End; + } + + uint8_t hash1[32]; + SHA256(&vchOutP[0], vchOutP.size(), (uint8_t*)hash1); + + + if (!(bnc = BN_bin2bn(&hash1[0], 32, BN_new()))) { + printf("StealthSecretSpend(): BN_bin2bn failed\n"); + rv = 1; + goto End; + } + + if (!(bnOrder = BN_new()) || !EC_GROUP_get_order(ecgrp, bnOrder, bnCtx)) { + printf("StealthSecretSpend(): EC_GROUP_get_order failed\n"); + rv = 1; + goto End; + } + + if (!(bnSpend = BN_bin2bn(&spendSecret.e[0], ec_secret_size, BN_new()))) { + printf("StealthSecretSpend(): bnSpend BN_bin2bn failed.\n"); + rv = 1; + goto End; + } + + //if (!BN_add(r, a, b)) return 0; + //return BN_nnmod(r, r, m, ctx); + if (!BN_mod_add(bnSpend, bnSpend, bnc, bnOrder, bnCtx)) { + printf("StealthSecretSpend(): bnSpend BN_mod_add failed.\n"); + rv = 1; + goto End; + } + + if (BN_is_zero(bnSpend)) {// possible? + printf("StealthSecretSpend(): bnSpend is zero.\n"); + rv = 1; + goto End; + } + + if (BN_num_bytes(bnSpend) != (int) ec_secret_size || BN_bn2bin(bnSpend, &secretOut.e[0]) != (int) ec_secret_size) { + printf("StealthSecretSpend(): bnSpend incorrect length.\n"); + rv = 1; + goto End; + } + + End: + if (bnSpend) BN_free(bnSpend); + if (bnOrder) BN_free(bnOrder); + if (bnc) BN_free(bnc); + if (bnOutP) BN_free(bnOutP); + if (P) EC_POINT_free(P); + if (bnP) BN_free(bnP); + if (bnScanSecret) BN_free(bnScanSecret); + if (bnCtx) BN_CTX_free(bnCtx); + EC_GROUP_free(ecgrp); + + return rv; +} + + +int StealthSharedToSecretSpend(ec_secret& sharedS, ec_secret& spendSecret, ec_secret& secretOut) { + + int rv = 0; + std::vector vchOutP; + + BN_CTX* bnCtx = NULL; + BIGNUM* bnc = NULL; + BIGNUM* bnOrder = NULL; + BIGNUM* bnSpend = NULL; + + EC_GROUP* ecgrp = EC_GROUP_new_by_curve_name(NID_secp256k1); + + if (!ecgrp) { + printf("StealthSecretSpend(): EC_GROUP_new_by_curve_name failed.\n"); + return 1; + } + + if (!(bnCtx = BN_CTX_new())) { + printf("StealthSecretSpend(): BN_CTX_new failed.\n"); + rv = 1; + goto End; + } + + if (!(bnc = BN_bin2bn(&sharedS.e[0], ec_secret_size, BN_new()))) { + printf("StealthSecretSpend(): BN_bin2bn failed\n"); + rv = 1; + goto End; + } + + if (!(bnOrder = BN_new()) + || !EC_GROUP_get_order(ecgrp, bnOrder, bnCtx)) { + printf("StealthSecretSpend(): EC_GROUP_get_order failed\n"); + rv = 1; + goto End; + } + + if (!(bnSpend = BN_bin2bn(&spendSecret.e[0], ec_secret_size, BN_new()))) { + printf("StealthSecretSpend(): bnSpend BN_bin2bn failed.\n"); + rv = 1; + goto End; + } + + //if (!BN_add(r, a, b)) return 0; + //return BN_nnmod(r, r, m, ctx); + if (!BN_mod_add(bnSpend, bnSpend, bnc, bnOrder, bnCtx)) { + printf("StealthSecretSpend(): bnSpend BN_mod_add failed.\n"); + rv = 1; + goto End; + } + + if (BN_is_zero(bnSpend)) {// possible? + printf("StealthSecretSpend(): bnSpend is zero.\n"); + rv = 1; + goto End; + } + + if (BN_num_bytes(bnSpend) != (int) ec_secret_size || BN_bn2bin(bnSpend, &secretOut.e[0]) != (int) ec_secret_size) { + printf("StealthSecretSpend(): bnSpend incorrect length.\n"); + rv = 1; + goto End; + } + + End: + if (bnSpend) BN_free(bnSpend); + if (bnOrder) BN_free(bnOrder); + if (bnc) BN_free(bnc); + if (bnCtx) BN_CTX_free(bnCtx); + EC_GROUP_free(ecgrp); + + return rv; +} + +bool IsStealthAddress(const std::string& encodedAddress) { + data_chunk raw; + + if (!DecodeBase58(encodedAddress, raw)) { + printf("IsStealthAddress DecodeBase58 falied.\n"); + return false; + } + + if (!VerifyChecksum(raw)) { + printf("IsStealthAddress verify_checksum falied.\n"); + return false; + } + + if (raw.size() < 1 + 1 + 33 + 1 + 33 + 1 + 1 + 4) { + printf("IsStealthAddress too few bytes provided.\n"); + return false; + } + + + uint8_t* p = &raw[0]; + uint8_t version = *p++; + + if (version != stealth_version_byte) { + printf("IsStealthAddress version mismatch 0x%x != 0x%x.\n", version, stealth_version_byte); + return false; + } + + return true; +} diff --git a/src/stealth.h b/src/stealth.h new file mode 100644 index 000000000..cec9ec439 --- /dev/null +++ b/src/stealth.h @@ -0,0 +1,111 @@ +// Copyright (c) 2017 The Anoncoin developers + +#ifndef ANONCOIN_STEALTH_H +#define ANONCOIN_STEALTH_H + +#include "util.h" +#include "serialize.h" +#include "random.h" + +#include +#include +#include +#include + + +typedef std::vector data_chunk; + +const size_t ec_secret_size = 32; +const size_t ec_compressed_size = 33; +const size_t ec_uncompressed_size = 65; + +typedef struct ec_secret { uint8_t e[ec_secret_size]; } ec_secret; +typedef data_chunk ec_point; + +typedef uint32_t stealth_bitfield; + +struct stealth_prefix { + uint8_t number_bits; + stealth_bitfield bitfield; +}; + +template +T from_big_endian(Iterator in) { + //VERIFY_UNSIGNED(T); + T out = 0; + size_t i = sizeof(T); + while (0 < i) + out |= static_cast(*in++) << (8 * --i); + return out; +} + +template +T from_little_endian(Iterator in) { + //VERIFY_UNSIGNED(T); + T out = 0; + size_t i = 0; + while (i < sizeof(T)) + out |= static_cast(*in++) << (8 * i++); + return out; +} + +class CStealthAddress { + + public: + CStealthAddress() { + options = 0; + } + + ADD_SERIALIZE_METHODS; + + uint8_t options; + ec_point scan_pubkey; + ec_point spend_pubkey; + //std::vector spend_pubkeys; + size_t number_signatures; + stealth_prefix prefix; + + mutable std::string label; + data_chunk scan_secret; + data_chunk spend_secret; + + bool SetEncoded(const std::string& encodedAddress); + std::string Encoded() const; + + bool operator <(const CStealthAddress& y) const { + return memcmp(&scan_pubkey[0], &y.scan_pubkey[0], ec_compressed_size) < 0; + } + + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { + READWRITE(options); + READWRITE(scan_pubkey); + READWRITE(spend_pubkey); + READWRITE(label); + + READWRITE(scan_secret); + READWRITE(spend_secret); + } + + + +}; + +void AppendChecksum(data_chunk& data); + +bool VerifyChecksum(const data_chunk& data); + +int GenerateRandomSecret(ec_secret& out); + +int SecretToPublicKey(const ec_secret& secret, ec_point& out); + +int StealthSecret(ec_secret& secret, ec_point& pubkey, const ec_point& pkSpend, ec_secret& sharedSOut, ec_point& pkOut); +int StealthSecretSpend(ec_secret& scanSecret, ec_point& ephemPubkey, ec_secret& spendSecret, ec_secret& secretOut); +int StealthSharedToSecretSpend(ec_secret& sharedS, ec_secret& spendSecret, ec_secret& secretOut); + +bool IsStealthAddress(const std::string& encodedAddress); + + +#endif // STEALTH_H + From 408e695015b0605863bc28a27faf754fb2731960 Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Sat, 25 Nov 2017 18:13:18 +0100 Subject: [PATCH 04/27] added fEnforceCanonical for now --- src/init.cpp | 4 ++++ 1 file changed, 4 insertions(+) mode change 100755 => 100644 src/init.cpp diff --git a/src/init.cpp b/src/init.cpp old mode 100755 new mode 100644 index 718514e08..0823773df --- a/src/init.cpp +++ b/src/init.cpp @@ -60,6 +60,7 @@ using namespace std; CWallet* pwalletMain = NULL; #endif bool fFeeEstimatesInitialized = false; +bool fEnforceCanonical; #ifdef WIN32 // Win32 LevelDB doesn't use filedescriptors, and the ones used for @@ -343,6 +344,7 @@ std::string HelpMessage(HelpMessageMode hmm) strUsage += " -wallet= " + _("Specify wallet file (within data directory)") + " " + strprintf(_("(default: %s)"), "wallet.dat") + "\n"; strUsage += " -walletnotify= " + _("Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)") + "\n"; strUsage += " -zapwallettxes " + _("Clear list of wallet transactions (diagnostic tool; implies -rescan)") + "\n"; + strUsage += " -enforcecanonical " + _("Enforce transaction scripts to use canonical PUSH operators (default: 1)") + "\n" + #endif strUsage += "\n" + _("Debugging/Testing options:") + "\n"; @@ -668,6 +670,8 @@ bool AppInit2(boost::thread_group& threadGroup) LogPrintf("AppInit2 : parameter interaction: -zapwallettxes=1 -> setting -rescan=1\n"); } + fEnforceCanonical = GetBoolArg("-enforcecanonical", true); + // Make sure enough file descriptors are available int nBind = std::max((int)mapArgs.count("-bind") + (int)mapArgs.count("-whitebind"), 1); nMaxConnections = GetArg("-maxconnections", 125); From 0a1adcdd37ad502acbe1d4f366d598c58e75ad6f Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Sat, 25 Nov 2017 18:13:40 +0100 Subject: [PATCH 05/27] Adding random into util.h for now --- src/util.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/util.h b/src/util.h index f14f0984e..6d47e3354 100644 --- a/src/util.h +++ b/src/util.h @@ -25,6 +25,11 @@ #include #include +#ifdef ENABLE_STEALTH +#include +#include "random.h" +#endif + #ifndef WIN32 #include #include From 10139090210802aef10aa25f77526f8b460e46f6 Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Sat, 25 Nov 2017 18:14:28 +0100 Subject: [PATCH 06/27] Extending CTxDestination def --- src/keystore.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/keystore.h b/src/keystore.h index 9b056290a..8e7bffe0d 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -20,9 +20,14 @@ class CScript; * * CNoDestination: no destination set * * CKeyID: TX_PUBKEYHASH destination * * CScriptID: TX_SCRIPTHASH destination + * * CStealthAddress: A Stealth address * A CTxDestination is the internal data type encoded in a CAnoncoinAddress */ +#ifdef ENABLE_STEALTH +typedef boost::variant CTxDestination; +#else typedef boost::variant CTxDestination; +#endif /** A virtual base class for key stores */ class CKeyStore From 4937dc35f9bd572137f01a2a1c9d250e9b71e0d0 Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Sat, 25 Nov 2017 18:19:40 +0100 Subject: [PATCH 07/27] Script updates --- src/script.cpp | 21 +++++++++++++++++++++ src/script.h | 9 +++++++++ 2 files changed, 30 insertions(+) diff --git a/src/script.cpp b/src/script.cpp index 6fb5ab21d..6d6450b27 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -17,6 +17,10 @@ #include "uint256.h" #include "util.h" +#ifdef ENABLE_STEALTH +#include "stealth.h" +#endif + #include #include #include @@ -1500,6 +1504,11 @@ class CKeyStoreIsMineVisitor : public boost::static_visitor bool operator()(const CNoDestination &dest) const { return false; } bool operator()(const CKeyID &keyID) const { return keystore->HaveKey(keyID); } bool operator()(const CScriptID &scriptID) const { return keystore->HaveCScript(scriptID); } +#ifdef ENABLE_STEALTH + bool operator()(const CStealthAddress &stxAddr) const { + return stxAddr.scan_secret.size() == ec_secret_size; + } +#endif }; isminetype IsMine(const CKeyStore &keystore, const CTxDestination& dest) @@ -1653,6 +1662,12 @@ class CAffectedKeysVisitor : public boost::static_visitor { Process(script); } +#ifdef ENABLE_STEALTH + void operator()(const CStealthAddress &stxAddr) { + CScript script; + } +#endif + void operator()(const CNoDestination &none) {} }; @@ -1855,6 +1870,12 @@ class CScriptVisitor : public boost::static_visitor *script << OP_HASH160 << ToByteVector(scriptID) << OP_EQUAL; return true; } +#ifdef ENABLE_STEALTH + bool operator()(const CStealthAddress &stxAddr) const { + script->clear(); + return false; + } +#endif }; } diff --git a/src/script.h b/src/script.h index 04cd3b116..d422bf585 100644 --- a/src/script.h +++ b/src/script.h @@ -11,6 +11,10 @@ #include "script_error.h" #include "util.h" +#ifdef ENABLE_STEALTH +#include "stealth.h" +#endif + #include #include #include @@ -149,7 +153,12 @@ class CScriptID : public uint160 * * CScriptID: TX_SCRIPTHASH destination * A CTxDestination is the internal data type encoded in a CAnoncoinAddress */ + +#ifdef ENABLE_STEALTH +typedef boost::variant CTxDestination; +#else typedef boost::variant CTxDestination; +#endif const char* GetTxnOutputType(txnouttype t); From cc04d2f93aee72aac7f6b68c4bc023c875cebe02 Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Sat, 25 Nov 2017 18:22:27 +0100 Subject: [PATCH 08/27] Db update --- src/db.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/db.cpp b/src/db.cpp index c45457e34..533653b54 100644 --- a/src/db.cpp +++ b/src/db.cpp @@ -44,7 +44,7 @@ void CDBEnv::EnvShutdown() LogPrintf("CDBEnv::EnvShutdown: Error %d shutting down database environment: %s\n", ret, DbEnv::strerror(ret)); if (!fMockDb) #ifndef USING_PRE_HISTORIC_COMPILER - DbEnv(0).remove(strPath.c_str(), 0); + DbEnv((u_int32_t)0).remove(strPath.c_str(), 0); #else DbEnv((u_int32_t)0).remove(strPath.c_str(), 0); #endif @@ -60,6 +60,7 @@ void CDBEnv::Reset() CDBEnv::CDBEnv() : dbenv(NULL) { + LogPrintf("Reset triggered."); Reset(); } @@ -89,8 +90,9 @@ bool CDBEnv::Open(const boost::filesystem::path& pathIn) LogPrintf("CDBEnv::Open: LogDir=%s ErrorFile=%s\n", pathLogDir.string(), pathErrorFile.string()); unsigned int nEnvFlags = 0; - if (GetBoolArg("-privdb", true)) + if (GetBoolArg("-privdb", true)) { nEnvFlags |= DB_PRIVATE; + } dbenv->set_lg_dir(pathLogDir.string().c_str()); dbenv->set_cachesize(0, 0x100000, 1); // 1 MiB should be enough for just the wallet From 4f7a5952952b878b0f02daef83d9baf1dfbdfac4 Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Sat, 25 Nov 2017 18:22:56 +0100 Subject: [PATCH 09/27] RPC intereface update --- src/rpcclient.cpp | 12 ++ src/rpcmining.cpp | 0 src/rpcmisc.cpp | 17 +- src/rpcserver.cpp | 10 + src/rpcserver.h | 10 + src/rpcstealth.cpp | 453 +++++++++++++++++++++++++++++++++++++++++++++ src/rpcstealth.h | 24 +++ 7 files changed, 522 insertions(+), 4 deletions(-) mode change 100755 => 100644 src/rpcmining.cpp create mode 100644 src/rpcstealth.cpp create mode 100644 src/rpcstealth.h diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp index dce7a606b..38b0b473d 100644 --- a/src/rpcclient.cpp +++ b/src/rpcclient.cpp @@ -93,6 +93,17 @@ static const CRPCConvertParam vRPCConvertParams[] = { "estimatepriority", 0 }, { "prioritisetransaction", 1 }, { "prioritisetransaction", 2 }, + +#ifdef ENABLE_STEALTH + { "getnewstealthaddress", 0 }, + { "liststealthaddresses", 0 }, + { "importstealthaddress", 1 }, + { "importstealthaddress", 2 }, + { "sendtostealthaddress", 1 }, + { "clearwallettransactions", 0 }, + { "scanforalltxns", 1 }, + { "scanforstealthtxns", 1 }, +#endif // #if CLIENT_VERSION_IS_RELEASE != true { "sendalert", 2 }, { "sendalert", 3 }, @@ -105,6 +116,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "getretargetpid", 0 }, { "getretargetpid", 1 }, { "gethashmeter", 0 } + }; class CRPCConvertTable diff --git a/src/rpcmining.cpp b/src/rpcmining.cpp old mode 100755 new mode 100644 diff --git a/src/rpcmisc.cpp b/src/rpcmisc.cpp index 92dc1c909..1bcab5e26 100644 --- a/src/rpcmisc.cpp +++ b/src/rpcmisc.cpp @@ -16,6 +16,7 @@ #include "sync.h" #include "timedata.h" #ifdef ENABLE_WALLET +#include "script.h" #include "wallet.h" #include "walletdb.h" #endif @@ -112,7 +113,7 @@ Value getinfo(const Array& params, bool fHelp) } #ifdef ENABLE_WALLET -class DescribeAddressVisitor : public boost::static_visitor +class DescribeAddressVisitor : public boost::static_visitor { private: isminetype mine; @@ -120,9 +121,9 @@ class DescribeAddressVisitor : public boost::static_visitor public: DescribeAddressVisitor(isminetype mineIn) : mine(mineIn) {} - Object operator()(const CNoDestination &dest) const { return Object(); } + json_spirit::Object operator()(const CNoDestination &dest) const { return Object(); } - Object operator()(const CKeyID &keyID) const { + json_spirit::Object operator()(const CKeyID &keyID) const { Object obj; CPubKey vchPubKey; obj.push_back(Pair("isscript", false)); @@ -134,7 +135,7 @@ class DescribeAddressVisitor : public boost::static_visitor return obj; } - Object operator()(const CScriptID &scriptID) const { + json_spirit::Object operator()(const CScriptID &scriptID) const { Object obj; obj.push_back(Pair("isscript", true)); if (mine != ISMINE_NO) { @@ -155,6 +156,14 @@ class DescribeAddressVisitor : public boost::static_visitor } return obj; } + +#ifdef ENABLE_STEALTH + Object operator()(const CStealthAddress &stxAddr) const { + Object obj; + obj.push_back(Pair("todo", true)); + return obj; + } +#endif }; #endif diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index ddebfedd1..9147a6476 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -407,6 +407,16 @@ static const CRPCCommand vRPCCommands[] = { "wallet", "walletlock", &walletlock, true }, { "wallet", "walletpassphrasechange", &walletpassphrasechange, true }, { "wallet", "walletpassphrase", &walletpassphrase, true }, + +#ifdef ENABLE_STEALTH + { "stealth", "getnewstealthaddress", &getnewstealthaddress, true }, + { "stealth", "liststealthaddresses", &liststealthaddresses, true }, + { "stealth", "importstealthaddress", &importstealthaddress, true }, + { "stealth", "sendtostealthaddress", &sendtostealthaddress, false}, + { "stealth", "clearwallettransactions",&clearwallettransactions,true }, + { "stealth", "scanforalltxns", &scanforalltxns, true }, + { "stealth", "scanforstealthtxns", &scanforstealthtxns, true } +#endif #endif // ENABLE_WALLET }; diff --git a/src/rpcserver.h b/src/rpcserver.h index 08bc15783..51e457af3 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -250,6 +250,16 @@ extern json_spirit::Value getretargetpid(const json_spirit::Array& params, bool extern json_spirit::Value sendalert(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value makekeypair(const json_spirit::Array& params, bool fHelp); +#ifdef ENABLE_STEALTH +extern json_spirit::Value getnewstealthaddress(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value liststealthaddresses(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value importstealthaddress(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value sendtostealthaddress(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value clearwallettransactions(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value scanforalltxns(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value scanforstealthtxns(const json_spirit::Array& params, bool fHelp); +#endif + // in rest.cpp extern bool HTTPReq_REST(AcceptedConnection *conn, std::string& strURI, diff --git a/src/rpcstealth.cpp b/src/rpcstealth.cpp new file mode 100644 index 000000000..fcd055d58 --- /dev/null +++ b/src/rpcstealth.cpp @@ -0,0 +1,453 @@ +#include "base58.h" +#include "core_io.h" +#include "rpcserver.h" +#include "init.h" +#include "main.h" +#include "net.h" +#include "netbase.h" +#include "sync.h" +#include "timedata.h" +#include "wallet.h" +#include "walletdb.h" + +#include "stealth.h" +#include "rpcstealth.h" + +#include + +#include + +#include "json/json_spirit_utils.h" +#include "json/json_spirit_value.h" + +using namespace std; +using namespace json_spirit; + +Value getnewstealthaddress(const Array& params, bool fHelp) { + if (fHelp || params.size() > 1) + throw runtime_error( + "getnewstealthaddress [label]\n" + "Returns a new Anoncoin stealth address for receiving payments anonymously. "); + + if (pwalletMain->IsLocked()) + throw runtime_error("Failed: Wallet must be unlocked."); + + std::string sLabel; + if (params.size() > 0) + sLabel = params[0].get_str(); + + CStealthAddress sxAddr; + std::string sError; + if (!pwalletMain->NewStealthAddress(sError, sLabel, sxAddr)) + throw runtime_error(std::string("Could get new stealth address: ") + sError); + + if (!pwalletMain->AddStealthAddress(sxAddr)) + throw runtime_error("Could not save to wallet."); + + return sxAddr.Encoded(); +} + +Value liststealthaddresses(const Array& params, bool fHelp) { + if (fHelp || params.size() > 1) + throw runtime_error( + "liststealthaddresses [show_secrets=0]\n" + "List owned stealth addresses."); + + bool fShowSecrets = false; + + if (params.size() > 0) { + std::string str = params[0].get_str(); + + if (str == "0" || str == "n" || str == "no" || str == "-" || str == "false") + fShowSecrets = false; + else + fShowSecrets = true; + } + + if (fShowSecrets) { + if (pwalletMain->IsLocked()) + throw runtime_error("Failed: Wallet must be unlocked."); + } + + Object result; + + std::set::iterator it; + for (it = pwalletMain->stealthAddresses.begin(); it != pwalletMain->stealthAddresses.end(); ++it) { + if (it->scan_secret.size() < 1) + continue; // stealth address is not owned + + if (fShowSecrets) { + Object objA; + objA.push_back(Pair("Label ", it->label)); + objA.push_back(Pair("Address ", it->Encoded())); + objA.push_back(Pair("Scan Secret ", HexStr(it->scan_secret.begin(), it->scan_secret.end()))); + objA.push_back(Pair("Spend Secret ", HexStr(it->spend_secret.begin(), it->spend_secret.end()))); + result.push_back(Pair("Stealth Address", objA)); + } else { + result.push_back(Pair("Stealth Address", it->Encoded() + " - " + it->label)); + } + } + + return result; +} + +Value importstealthaddress(const Array& params, bool fHelp) { + if (fHelp || params.size() < 2) + throw runtime_error( + "importstealthaddress [label]\n" + "Import an owned stealth addresses."); + + std::string sScanSecret = params[0].get_str(); + std::string sSpendSecret = params[1].get_str(); + std::string sLabel; + + + if (params.size() > 2) { + sLabel = params[2].get_str(); + } + + std::vector vchScanSecret; + std::vector vchSpendSecret; + + if (IsHex(sScanSecret)) { + vchScanSecret = ParseHex(sScanSecret); + } else { + if (!DecodeBase58(sScanSecret, vchScanSecret)) + throw runtime_error("Could not decode scan secret as hex or base58."); + } + + if (IsHex(sSpendSecret)) { + vchSpendSecret = ParseHex(sSpendSecret); + } else { + if (!DecodeBase58(sSpendSecret, vchSpendSecret)) + throw runtime_error("Could not decode spend secret as hex or base58."); + } + + if (vchScanSecret.size() != 32) + throw runtime_error("Scan secret is not 32 bytes."); + if (vchSpendSecret.size() != 32) + throw runtime_error("Spend secret is not 32 bytes."); + + + ec_secret scan_secret; + ec_secret spend_secret; + + memcpy(&scan_secret.e[0], &vchScanSecret[0], 32); + memcpy(&spend_secret.e[0], &vchSpendSecret[0], 32); + + ec_point scan_pubkey, spend_pubkey; + if (SecretToPublicKey(scan_secret, scan_pubkey) != 0) + throw runtime_error("Could not get scan public key."); + + if (SecretToPublicKey(spend_secret, spend_pubkey) != 0) + throw runtime_error("Could not get spend public key."); + + + CStealthAddress sxAddr; + sxAddr.label = sLabel; + sxAddr.scan_pubkey = scan_pubkey; + sxAddr.spend_pubkey = spend_pubkey; + + sxAddr.scan_secret = vchScanSecret; + sxAddr.spend_secret = vchSpendSecret; + + Object result; + bool fFound = false; + // -- find if address already exists + std::set::iterator it; + for (it = pwalletMain->stealthAddresses.begin(); it != pwalletMain->stealthAddresses.end(); ++it) { + CStealthAddress &sxAddrIt = const_cast(*it); + if (sxAddrIt.scan_pubkey == sxAddr.scan_pubkey && sxAddrIt.spend_pubkey == sxAddr.spend_pubkey) { + if (sxAddrIt.scan_secret.size() < 1) { + sxAddrIt.scan_secret = sxAddr.scan_secret; + sxAddrIt.spend_secret = sxAddr.spend_secret; + fFound = true; // update stealth address with secrets + break; + } + + result.push_back(Pair("result", "Import failed - stealth address exists.")); + return result; + } + } + + if (fFound) { + result.push_back(Pair("result", "Success, updated " + sxAddr.Encoded())); + } else { + pwalletMain->stealthAddresses.insert(sxAddr); + result.push_back(Pair("result", "Success, imported " + sxAddr.Encoded())); + } + + + if (!pwalletMain->AddStealthAddress(sxAddr)) + throw runtime_error("Could not save to wallet."); + + return result; +} + + +Value sendtostealthaddress(const Array& params, bool fHelp) { + if (fHelp || params.size() < 2 || params.size() > 5) + throw runtime_error( + "sendtostealthaddress [narration] [comment] [comment-to]\n" + " is a real and is rounded to the nearest 0.000001" + + HelpRequiringPassphrase()); + + if (pwalletMain->IsLocked()) + throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please enter the wallet passphrase with walletpassphrase first."); + + std::string sEncoded = params[0].get_str(); + int64_t nAmount = AmountFromValue(params[1]); + + std::string sNarr; + if (params.size() > 2 && params[2].type() != null_type && !params[2].get_str().empty()) + sNarr = params[2].get_str(); + + if (sNarr.length() > 24) + throw runtime_error("Narration must be 24 characters or less."); + + CStealthAddress sxAddr; + Object result; + + if (!sxAddr.SetEncoded(sEncoded)) { + result.push_back(Pair("result", "Invalid Anoncoin stealth address.")); + return result; + } + + + CWalletTx wtx; + if (params.size() > 3 && params[3].type() != null_type && !params[3].get_str().empty()) + wtx.mapValue["comment"] = params[3].get_str(); + if (params.size() > 4 && params[4].type() != null_type && !params[4].get_str().empty()) + wtx.mapValue["to"] = params[4].get_str(); + + std::string sError; + if (!pwalletMain->SendStealthMoneyToDestination(sxAddr, nAmount, sNarr, wtx, sError)) + throw JSONRPCError(RPC_WALLET_ERROR, sError); + + return wtx.GetHash().GetHex(); + + result.push_back(Pair("result", "Not implemented yet.")); + + return result; +} + +Value clearwallettransactions(const Array& params, bool fHelp) { + if (fHelp || params.size() > 0) + throw runtime_error( + "clearwallettransactions \n" + "delete all transactions from wallet - reload with scanforalltxns\n" + "Warning: Backup your wallet first!"); + + + + Object result; + + uint32_t nTransactions = 0; + + char cbuf[256]; + + { + LOCK2(cs_main, pwalletMain->cs_wallet); + + CWalletDB walletdb(pwalletMain->strWalletFile); + walletdb.TxnBegin(); + Dbc* pcursor = walletdb.GetTxnCursor(); + if (!pcursor) + throw runtime_error("Cannot get wallet DB cursor"); + + Dbt datKey; + Dbt datValue; + + datKey.set_flags(DB_DBT_USERMEM); + datValue.set_flags(DB_DBT_USERMEM); + + std::vector vchKey; + std::vector vchType; + std::vector vchKeyData; + std::vector vchValueData; + + vchKeyData.resize(100); + vchValueData.resize(100); + + datKey.set_ulen(vchKeyData.size()); + datKey.set_data(&vchKeyData[0]); + + datValue.set_ulen(vchValueData.size()); + datValue.set_data(&vchValueData[0]); + + unsigned int fFlags = DB_NEXT; // same as using DB_FIRST for new cursor + while (true) { + int ret = pcursor->get(&datKey, &datValue, fFlags); + + if (ret == ENOMEM || ret == DB_BUFFER_SMALL) { + if (datKey.get_size() > datKey.get_ulen()) { + vchKeyData.resize(datKey.get_size()); + datKey.set_ulen(vchKeyData.size()); + datKey.set_data(&vchKeyData[0]); + } + + if (datValue.get_size() > datValue.get_ulen()) { + vchValueData.resize(datValue.get_size()); + datValue.set_ulen(vchValueData.size()); + datValue.set_data(&vchValueData[0]); + } + // -- try once more, when DB_BUFFER_SMALL cursor is not expected to move + ret = pcursor->get(&datKey, &datValue, fFlags); + } + + if (ret == DB_NOTFOUND) + break; + else if (datKey.get_data() == NULL || datValue.get_data() == NULL || ret != 0) { + snprintf(cbuf, sizeof(cbuf), "wallet DB error %d, %s", ret, db_strerror(ret)); + throw runtime_error(cbuf); + } + + CDataStream ssValue(SER_DISK, CLIENT_VERSION); + ssValue.SetType(SER_DISK); + ssValue.clear(); + ssValue.write((char*)datKey.get_data(), datKey.get_size()); + + ssValue >> vchType; + + + std::string strType(vchType.begin(), vchType.end()); + + //printf("strType %s\n", strType.c_str()); + + if (strType == "tx") { + uint256 hash; + ssValue >> hash; + + if ((ret = pcursor->del(0)) != 0) { + printf("Delete transaction failed %d, %s\n", ret, db_strerror(ret)); + continue; + } + + pwalletMain->mapWallet.erase(hash); + pwalletMain->NotifyTransactionChanged(pwalletMain, hash, CT_DELETED); + + nTransactions++; + } + } + pcursor->close(); + walletdb.TxnCommit(); + + + //pwalletMain->mapWallet.clear(); + } + + snprintf(cbuf, sizeof(cbuf), "Removed %u transactions.", nTransactions); + result.push_back(Pair("complete", std::string(cbuf))); + result.push_back(Pair("", "Reload with scanforstealthtxns or re-download blockchain.")); + + + return result; +} + +Value scanforalltxns(const Array& params, bool fHelp) { + if (fHelp || params.size() > 1) + throw runtime_error( + "scanforalltxns [fromHeight]\n" + "Scan blockchain for owned transactions."); + + Object result; + int32_t nFromHeight = 0; + + CBlockIndex *pindex = chainActive.Genesis(); + + + if (params.size() > 0) + nFromHeight = params[0].get_int(); + + + if (nFromHeight > 0) { + pindex = mapBlockIndex[chainActive.Tip()->GetBlockHash()]; + while (pindex->nHeight > nFromHeight + && pindex->pprev) + pindex = pindex->pprev; + } + + if (pindex == NULL) + throw runtime_error("Genesis Block is not set."); + + { + LOCK2(cs_main, pwalletMain->cs_wallet); + + pwalletMain->MarkDirty(); + + pwalletMain->ScanForWalletTransactions(pindex, true); + pwalletMain->ReacceptWalletTransactions(); + } + + result.push_back(Pair("result", "Scan complete.")); + + return result; +} + +Value scanforstealthtxns(const Array& params, bool fHelp) { + if (fHelp || params.size() > 1) + throw runtime_error( + "scanforstealthtxns [fromHeight]\n" + "Scan blockchain for owned stealth transactions."); + + Object result; + uint32_t nBlocks = 0; + uint32_t nTransactions = 0; + int32_t nFromHeight = 0; + + CBlockIndex *pindex = chainActive.Genesis(); + + + if (params.size() > 0) + nFromHeight = params[0].get_int(); + + + if (nFromHeight > 0) { + pindex = mapBlockIndex[chainActive.Tip()->GetBlockHash()]; + while (pindex->nHeight > nFromHeight + && pindex->pprev) + pindex = pindex->pprev; + } + + if (pindex == NULL) + throw runtime_error("Genesis Block is not set."); + + // -- locks in AddToWalletIfInvolvingMe + + bool fUpdate = true; // todo: make it an option? + + pwalletMain->nStealth = 0; + pwalletMain->nFoundStealth = 0; + + while (pindex) { + nBlocks++; + CBlock block; + ReadBlockFromDisk(block, pindex->GetBlockPos()); + + BOOST_FOREACH(CTransaction& tx, block.vtx) + { + std::string dummy(""); + if (!IsStandardTx(tx, dummy)) + continue; // leave out coinbase and others + nTransactions++; + + pwalletMain->AddToWalletIfInvolvingMe(tx, &block, fUpdate); + } + + pindex = pindex->pskip; + } + + printf("Scanned %u blocks, %u transactions\n", nBlocks, nTransactions); + printf("Found %u stealth transactions in blockchain.\n", pwalletMain->nStealth); + printf("Found %u new owned stealth transactions.\n", pwalletMain->nFoundStealth); + + char cbuf[256]; + snprintf(cbuf, sizeof(cbuf), "%u new stealth transactions.", pwalletMain->nFoundStealth); + + result.push_back(Pair("result", "Scan complete.")); + result.push_back(Pair("found", std::string(cbuf))); + + return result; +} + + diff --git a/src/rpcstealth.h b/src/rpcstealth.h new file mode 100644 index 000000000..b0729043d --- /dev/null +++ b/src/rpcstealth.h @@ -0,0 +1,24 @@ +#ifndef __RPCSTEALTH_H__ +#define __RPCSTEALTH_H__ +#include "rpcserver.h" + +#include + +#include + +#include "json/json_spirit_value.h" + +using namespace json_spirit; + +// main.cpp +extern bool IsStandardTx(const CTransaction& tx, std::string& reason); + +Value getnewstealthaddress(const Array& params, bool fHelp); +Value liststealthaddresses(const Array& params, bool fHelp); +Value importstealthaddress(const Array& params, bool fHelp); +Value sendtostealthaddress(const Array& params, bool fHelp); +Value clearwallettransactions(const Array& params, bool fHelp); +Value scanforalltxns(const Array& params, bool fHelp); +Value scanforstealthtxns(const Array& params, bool fHelp); + +#endif \ No newline at end of file From baf9f7762581f5d214539eda2174e7912cae10b4 Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Sat, 25 Nov 2017 18:23:45 +0100 Subject: [PATCH 10/27] main.cpp/.h updates --- src/main.cpp | 16 ++++++++++++---- src/main.h | 1 + 2 files changed, 13 insertions(+), 4 deletions(-) mode change 100755 => 100644 src/main.cpp diff --git a/src/main.cpp b/src/main.cpp old mode 100755 new mode 100644 index 98b17c5c1..23e1dcad8 --- a/src/main.cpp +++ b/src/main.cpp @@ -802,6 +802,7 @@ bool IsStandardTx(const CTransaction& tx, string& reason) } unsigned int nDataOut = 0; + unsigned int nTxnOut = 0; txnouttype whichType; BOOST_FOREACH(const CTxOut& txout, tx.vout) { if (!::IsStandard(txout.scriptPubKey, whichType)) { @@ -811,14 +812,21 @@ bool IsStandardTx(const CTransaction& tx, string& reason) if (whichType == TX_NULL_DATA) nDataOut++; - else if (txout.IsDust(minRelayTxFee)) { - reason = "dust"; - return false; + else { + if (txout.IsDust(minRelayTxFee)) { + reason = "dust"; + return false; + } + nTxnOut++; } } - +#ifdef ENABLE_STEALTH + if (nDataOut > nTxnOut) { + LogPrintf("Found %d OP_RETURNs versus %d TxOut. Not allowed, rejecting!", nDataOut, nTxnOut); +#else // only one OP_RETURN txout is permitted if (nDataOut > 1) { +#endif reason = "multi-op-return"; return false; } diff --git a/src/main.h b/src/main.h index 574819357..2cd7d5ae0 100644 --- a/src/main.h +++ b/src/main.h @@ -133,6 +133,7 @@ extern CWaitableCriticalSection csBestBlock; extern CConditionVariable cvBlockChange; extern bool fImporting; extern bool fReindex; +extern bool fEnforceCanonical; extern int nScriptCheckThreads; extern bool fTxIndex; extern bool fIsBareMultisigStd; From 90313c25753e7b0d66a5c59ebc0857497af8b44b Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Sat, 25 Nov 2017 18:24:39 +0100 Subject: [PATCH 11/27] key updates --- src/key.h | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/key.h b/src/key.h index 9ff1030bc..afe3fe82a 100644 --- a/src/key.h +++ b/src/key.h @@ -15,6 +15,23 @@ #include #include +#include // for EC_KEY definition + +// secp160k1 +// const unsigned int PRIVATE_KEY_SIZE = 192; +// const unsigned int PUBLIC_KEY_SIZE = 41; +// const unsigned int SIGNATURE_SIZE = 48; +// +// secp192k1 +// const unsigned int PRIVATE_KEY_SIZE = 222; +// const unsigned int PUBLIC_KEY_SIZE = 49; +// const unsigned int SIGNATURE_SIZE = 57; +// +// secp224k1 +// const unsigned int PRIVATE_KEY_SIZE = 250; +// const unsigned int PUBLIC_KEY_SIZE = 57; +// const unsigned int SIGNATURE_SIZE = 66; +// // secp256k1: // const unsigned int PRIVATE_KEY_SIZE = 279; // const unsigned int PUBLIC_KEY_SIZE = 65; @@ -161,12 +178,22 @@ class CPubKey { // Derive BIP32 child pubkey. bool Derive(CPubKey& pubkeyChild, unsigned char ccChild[32], unsigned int nChild, const unsigned char cc[32]) const; + + +#ifdef ENABLE_STEALTH + // Return the key as a vector of uint8_t + std::vector Raw() { return std::vector(begin(), end()); } +#endif }; // secure_allocator is defined in allocators.h // CPrivKey is a serialized private key, with all parameters included (279 bytes) typedef std::vector > CPrivKey; +#ifdef ENABLE_STEALTH +// CSecret is a serialization of just the secret parameter (32 bytes) +typedef std::vector > CSecret; +#endif /** An encapsulated private key. */ class CKey { @@ -235,7 +262,12 @@ class CKey { // Sets the secret for the key void SetSecret(const unsigned char vchIn[32], bool fCompressed = false); - +#ifdef ENABLE_STEALTH + // Construct a key from a byte vector. + void SetSecret(const std::vector &vch, bool compressed) { + Set(vch.begin(), vch.end(), compressed); + } +#endif // Initialize from a CPrivKey (serialized OpenSSL private key data). bool SetPrivKey(const CPrivKey &vchPrivKey, bool fCompressed); From c3ef039bf3285f3b41fe655086a54f1cb43f1d99 Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Sat, 25 Nov 2017 18:25:00 +0100 Subject: [PATCH 12/27] Other testnet prefix for v9 testnet --- src/chainparams.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) mode change 100755 => 100644 src/chainparams.cpp diff --git a/src/chainparams.cpp b/src/chainparams.cpp old mode 100755 new mode 100644 index 5f1a141a9..9428e1498 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -314,15 +314,15 @@ class CTestNetParams : public CMainParams { // vSeeds.push_back(CDNSSeedData("anoncoin.net", "dnsseed01.anoncoin.net")); #ifndef USING_PRE_HISTORIC_COMPILER - base58Prefixes[PUBKEY_ADDRESS] = {111}; // Anoncoin v8 compatible testnet Public keys use this value + base58Prefixes[PUBKEY_ADDRESS] = {83}; // Anoncoin v9 compatible testnet Public keys use this value base58Prefixes[SCRIPT_ADDRESS] = {196}; - base58Prefixes[SECRET_KEY] = {239}; // Anoncoin testnet secret keys start with the same prefix as the Public Key + 128 + base58Prefixes[SECRET_KEY] = {211}; // Anoncoin testnet secret keys start with the same prefix as the Public Key + 128 base58Prefixes[EXT_PUBLIC_KEY] = {0x04,0x35,0x87,0xCF}; base58Prefixes[EXT_SECRET_KEY] = {0x04,0x35,0x83,0x94}; #else - base58Prefixes[PUBKEY_ADDRESS] = list_of(111); // Anoncoin v8 compatible testnet Public keys use this value + base58Prefixes[PUBKEY_ADDRESS] = list_of(83); // Anoncoin v8 compatible testnet Public keys use this value base58Prefixes[SCRIPT_ADDRESS] = list_of(196); - base58Prefixes[SECRET_KEY] = list_of(239); // Anoncoin testnet secret keys start with the same prefix as the Public Key + 128 + base58Prefixes[SECRET_KEY] = list_of(211); // Anoncoin testnet secret keys start with the same prefix as the Public Key + 128 base58Prefixes[EXT_PUBLIC_KEY] = list_of(0x04)(0x35)(0x87)(0xCF); base58Prefixes[EXT_SECRET_KEY] = list_of(0x04)(0x35)(0x83)(0x94); #endif From 0f2b728ed94840f5aa947c50270353c7764f329d Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Sat, 25 Nov 2017 18:25:16 +0100 Subject: [PATCH 13/27] Autotools update --- configure.ac | 24 ++++++++++++++++++++++-- src/Makefile.am | 30 ++++++++++++++++++++---------- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/configure.ac b/configure.ac index b486372b7..dd88d85a2 100644 --- a/configure.ac +++ b/configure.ac @@ -86,6 +86,13 @@ AC_ARG_ENABLE([wallet], [enable_wallet=$enableval], [enable_wallet=yes]) +# Enable stealth +AC_ARG_ENABLE([stealth], + [AS_HELP_STRING([--enable-stealth], + [enable stealth (default is no)])], + [enable_stealth=$enableval], + [enable_stealth=no]) + # Enable i2psam AC_ARG_ENABLE([i2psam], [AS_HELP_STRING([--enable-i2psam], @@ -186,10 +193,13 @@ fi ## compatibility with the legacy buildsystem. ## if test "x$CXXFLAGS_overridden" = "xno"; then - CXXFLAGS="$CXXFLAGS -Wall -Wextra -Wformat -Wformat-security -Wno-unused-parameter" + CXXFLAGS="$CXXFLAGS -DENABLE_STEALTH -Wall -Wextra -Wformat -Wformat-security -Wno-unused-parameter" fi CPPFLAGS="$CPPFLAGS -DBOOST_SPIRIT_THREADSAFE -DHAVE_BUILD_INFO -D__STDC_FORMAT_MACROS" +CXXFLAGS=" -I/usr/local/opt/openssl/include $CXXFLAGS" +CPPFLAGS=" -I/usr/local/opt/openssl/include $CPPFLAGS" + AC_ARG_WITH([utils], [AS_HELP_STRING([--with-utils], [build anoncoin-cli anoncoin-tx (default=yes)])], @@ -781,6 +791,15 @@ else AC_MSG_RESULT(no) fi +dnl enable stealth +AC_MSG_CHECKING([if stealth module should be included]) +if test x$enable_stealth != xno; then + AC_MSG_RESULT(yes) + AC_DEFINE_UNQUOTED([ENABLE_STEALTH],[1],[Define to 1 to enable stealth address functions]) +else + AC_MSG_RESULT(no) +fi + dnl enable i2psam AC_MSG_CHECKING([if i2psam module should be included]) if test x$enable_i2psam != xno; then @@ -892,6 +911,7 @@ AM_CONDITIONAL([TARGET_DARWIN], [test x$TARGET_OS = xdarwin]) AM_CONDITIONAL([BUILD_DARWIN], [test x$BUILD_OS = xdarwin]) AM_CONDITIONAL([TARGET_WINDOWS], [test x$TARGET_OS = xwindows]) AM_CONDITIONAL([ENABLE_WALLET],[test x$enable_wallet = xyes]) +AM_CONDITIONAL([ENABLE_STEALTH],[test x$enable_stealth = xno]) AM_CONDITIONAL([ENABLE_I2PSAM],[test x$enable_i2psam = xyes]) AM_CONDITIONAL([ENABLE_TESTS],[test x$use_tests = xyes]) AM_CONDITIONAL([ENABLE_QT],[test x$anoncoin_enable_qt = xyes]) @@ -947,7 +967,7 @@ LIBS_TEMP="$LIBS" unset LIBS LIBS="$LIBS_TEMP" -PKGCONFIG_PATH_TEMP="$PKG_CONFIG_PATH" +PKGCONFIG_PATH_TEMP="$PKG_CONFIG_PATH:/usr/local/opt/openssl/pkgconfig" unset PKG_CONFIG_PATH PKG_CONFIG_PATH="$PKGCONFIG_PATH_TEMP" diff --git a/src/Makefile.am b/src/Makefile.am index f438f5b09..566dd0b50 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -55,10 +55,12 @@ EXTRA_LIBRARIES = \ libanoncoin_server.a \ libanoncoin_cli.a \ libanoncoin_scrypt.a + if ENABLE_WALLET -ANONCOIN_INCLUDES += $(BDB_CPPFLAGS) +ANONCOIN_INCLUDES += $(BDB_CPPFLAGS) -DENABLE_STEALTH EXTRA_LIBRARIES += libanoncoin_wallet.a endif + if ENABLE_I2PSAM EXTRA_LIBRARIES += libanoncoin_i2pnet.a endif @@ -125,12 +127,14 @@ ANONCOIN_CORE_H = \ rpcclient.h \ rpcprotocol.h \ rpcserver.h \ + rpcstealth.h \ script.h \ script_error.h \ scrypt.h \ serialize.h \ sigcache.h \ sign.h \ + stealth.h \ streams.h \ sync.h \ threadsafety.h \ @@ -166,7 +170,7 @@ obj/build.h: FORCE libanoncoin_util_a-clientversion.$(OBJEXT): obj/build.h # server: shared between anoncoind and anoncoin-qt -libanoncoin_server_a_CPPFLAGS = $(ANONCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) +libanoncoin_server_a_CPPFLAGS = $(ANONCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) -DENABLE_STEALTH libanoncoin_server_a_SOURCES = \ alert.cpp \ bloom.cpp \ @@ -194,12 +198,14 @@ libanoncoin_server_a_SOURCES = \ # wallet: shared between anoncoind and anoncoin-qt, but only linked # when wallet enabled -libanoncoin_wallet_a_CPPFLAGS = $(ANONCOIN_INCLUDES) +libanoncoin_wallet_a_CPPFLAGS = $(ANONCOIN_INCLUDES) -DENABLE_STEALTH libanoncoin_wallet_a_SOURCES = \ db.cpp \ crypter.cpp \ rpcdump.cpp \ + rpcstealth.cpp \ rpcwallet.cpp \ + stealth.cpp \ wallet.cpp \ walletdb.cpp \ $(ANONCOIN_CORE_H) @@ -232,7 +238,7 @@ univalue_libanoncoin_univalue_a_SOURCES = \ univalue/univalue.h # common: shared between anoncoind, and anoncoin-qt and non-server tools -libanoncoin_common_a_CPPFLAGS = $(ANONCOIN_INCLUDES) +libanoncoin_common_a_CPPFLAGS = $(ANONCOIN_INCLUDES) -DENABLE_STEALTH libanoncoin_common_a_SOURCES = \ addrman.cpp \ allocators.cpp \ @@ -260,7 +266,7 @@ libanoncoin_common_a_SOURCES = \ # util: shared between all executables. # This library *must* be included to make sure that the glibc # backward-compatibility objects and their sanity checks are linked. -libanoncoin_util_a_CPPFLAGS = $(ANONCOIN_INCLUDES) +libanoncoin_util_a_CPPFLAGS = $(ANONCOIN_INCLUDES) -DENABLE_STEALTH libanoncoin_util_a_SOURCES = \ compat/glibc_sanity.cpp \ compat/glibcxx_sanity.cpp \ @@ -294,7 +300,7 @@ libanoncoin_util_a_SOURCES += compat/glibcxx_compat.cpp endif # cli: shared between anoncoin-cli and anoncoin-qt -libanoncoin_cli_a_CPPFLAGS = $(ANONCOIN_INCLUDES) +libanoncoin_cli_a_CPPFLAGS = $(ANONCOIN_INCLUDES) -DENABLE_STEALTH libanoncoin_cli_a_SOURCES = \ rpcclient.cpp \ $(ANONCOIN_CORE_H) @@ -308,18 +314,22 @@ anoncoind_LDADD = \ $(LIBANONCOIN_UNIVALUE) \ $(LIBANONCOIN_UTIL) \ $(LIBANONCOIN_CRYPTO) \ - $(LIBANONCOIN_SCRYPT) \ - $(LIBLEVELDB) \ - $(LIBMEMENV) + $(LIBANONCOIN_SCRYPT) if ENABLE_WALLET anoncoind_LDADD += libanoncoin_wallet.a endif + if ENABLE_I2PSAM anoncoind_LDADD += libanoncoin_i2pnet.a endif +anoncoind_LDADD += \ + $(LIBLEVELDB) \ + $(LIBMEMENV) + anoncoind_SOURCES = anoncoind.cpp + if TARGET_WINDOWS anoncoind_SOURCES += anoncoind-res.rc endif @@ -341,7 +351,7 @@ if TARGET_WINDOWS anoncoin_cli_SOURCES += anoncoin-cli-res.rc endif -anoncoin_cli_CPPFLAGS = $(ANONCOIN_INCLUDES) +anoncoin_cli_CPPFLAGS = $(ANONCOIN_INCLUDES) -DENABLE_STEALTH anoncoin_cli_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) #anoncoin-tx binary # From 9e0b33c7da05258018e3a4000648ef457997ebb7 Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Sat, 25 Nov 2017 18:25:29 +0100 Subject: [PATCH 14/27] chmod fix --- src/addrman.h | 0 src/pow.h | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 src/addrman.h mode change 100755 => 100644 src/pow.h diff --git a/src/addrman.h b/src/addrman.h old mode 100755 new mode 100644 diff --git a/src/pow.h b/src/pow.h old mode 100755 new mode 100644 From 01baf3f2a9114e69b6debc74ffe050b0199ddc25 Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Sat, 25 Nov 2017 18:25:57 +0100 Subject: [PATCH 15/27] Making CryptedKeyMap more available for the wallet --- src/crypter.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/crypter.h b/src/crypter.h index 33d5d451d..3e622ead2 100644 --- a/src/crypter.h +++ b/src/crypter.h @@ -117,9 +117,6 @@ bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector &vchCryptedSecret); bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey); bool HaveKey(const CKeyID &address) const From fda945f09e26b30596fdb0144da799d1116722a0 Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Sat, 25 Nov 2017 18:26:14 +0100 Subject: [PATCH 16/27] Tx update --- src/transaction.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/transaction.h b/src/transaction.h index e413a1e0b..ac6d54e17 100644 --- a/src/transaction.h +++ b/src/transaction.h @@ -138,6 +138,14 @@ class CTxOut return (nValue == -1); } +#ifdef ENABLE_STEALTH + bool IsScriptOpReturn() const { + opcodetype opCode; + CScript::const_iterator itTxA = scriptPubKey.begin(); + return (scriptPubKey.GetOp(itTxA, opCode) && opCode == OP_RETURN); + } +#endif + uint256 GetHash() const; bool IsDust(CFeeRate minRelayTxFee) const From edac63ef0baf75e897247ca512f2c4b15ae1ba5e Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Sat, 25 Nov 2017 18:27:05 +0100 Subject: [PATCH 17/27] Walletdb updates --- src/walletdb.cpp | 40 +++++++++++++++++++---- src/walletdb.h | 83 +++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 109 insertions(+), 14 deletions(-) diff --git a/src/walletdb.cpp b/src/walletdb.cpp index ba7c83da0..9dcdfafda 100644 --- a/src/walletdb.cpp +++ b/src/walletdb.cpp @@ -11,6 +11,9 @@ #include "serialize.h" #include "sync.h" #include "wallet.h" +#ifndef ENABLE_STEALTH +#include "stealth.h" +#endif #include #include @@ -396,13 +399,26 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, pwallet->AddToWallet(wtx, true, NULL); //// debug print - //LogPrintf("LoadWallet %s\n", wtx.GetHash().ToString()); - //LogPrintf(" %12d %s %s %s\n", - // wtx.vout[0].nValue, - // DateTimeStrFormat("%Y-%m-%d %H:%M:%S", wtx.GetBlockTime()), - // wtx.hashBlock.ToString(), - // wtx.mapValue["message"]); + LogPrintf("LoadWallet %s\n", wtx.GetHash().ToString()); + if (fDebug) { + LogPrintf(" %12d %s %s %s\n", + wtx.vout[0].nValue, + DateTimeStrFormat("%Y-%m-%d %H:%M:%S", wtx.fTimeReceivedIsTxTime), + wtx.GetHash().ToString(), + wtx.mapValue["message"]); + } } +#ifdef ENABLE_STEALTH + if (strType == "sxAddr") { + + CStealthAddress sxAddr; + ssValue >> sxAddr; + + if (fDebug) LogPrintf("WalletDB ReadKeyValue sxAddr: %s\n", sxAddr.Encoded()); + + pwallet->stealthAddresses.insert(sxAddr); + } +#endif else if (strType == "acentry") { string strAccount; @@ -542,6 +558,18 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, (keyMeta.nCreateTime < pwallet->nTimeFirstKey)) pwallet->nTimeFirstKey = keyMeta.nCreateTime; } +#ifdef ENABLE_STEALTH + else if (strType == "sxKeyMeta") { + CKeyID keyId; + ssKey >> keyId; + CStealthKeyMetadata sxKeyMeta; + ssValue >> sxKeyMeta; + + if (fDebug) LogPrintf("WalletDB ReadKeyValue sxKeyMeta: %s\n", sxKeyMeta.pkEphem.GetHash().ToString().c_str()); + + pwallet->mapStealthKeyMeta[keyId] = sxKeyMeta; + } +#endif else if (strType == "defaultkey") { ssValue >> pwallet->vchDefaultKey; diff --git a/src/walletdb.h b/src/walletdb.h index 2b7803798..d76ff40e7 100644 --- a/src/walletdb.h +++ b/src/walletdb.h @@ -11,6 +11,11 @@ #include "key.h" #include "keystore.h" +#ifdef ENABLE_STEALTH +#include "base58.h" +#include "stealth.h" +#endif + #include #include #include @@ -39,19 +44,16 @@ enum DBErrors DB_NEED_REWRITE }; -class CKeyMetadata -{ +class CKeyMetadata { public: static const int CURRENT_VERSION=1; int nVersion; int64_t nCreateTime; // 0 means unknown - CKeyMetadata() - { + CKeyMetadata() { SetNull(); } - CKeyMetadata(int64_t nCreateTime_) - { + CKeyMetadata(int64_t nCreateTime_) { nVersion = CKeyMetadata::CURRENT_VERSION; nCreateTime = nCreateTime_; } @@ -65,13 +67,34 @@ class CKeyMetadata READWRITE(nCreateTime); } - void SetNull() - { + void SetNull() { nVersion = CKeyMetadata::CURRENT_VERSION; nCreateTime = 0; } }; +#ifdef ENABLE_STEALTH +class CStealthKeyMetadata { +// -- used to get secret for keys created by stealth transaction with wallet locked +public: + CStealthKeyMetadata() {} + + CStealthKeyMetadata(CPubKey pkEphem_, CPubKey pkScan_) : pkEphem(pkEphem_), pkScan(pkScan_) {} + + CPubKey pkEphem; + CPubKey pkScan; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { + READWRITE(pkEphem); + READWRITE(pkScan); + } + +}; +#endif + /** Access to the wallet database (wallet.dat) */ class CWalletDB : public CDB { @@ -80,6 +103,50 @@ class CWalletDB : public CDB { } +#ifdef ENABLE_STEALTH + Dbc* GetAtCursor() { + return GetCursor(); + } + + DbTxn* GetAtActiveTxn() { + return activeTxn; + } + + Dbc* GetTxnCursor() { + if (!pdb) + return NULL; + + DbTxn* ptxnid = activeTxn; // call TxnBegin first + + Dbc* pcursor = NULL; + int ret = pdb->cursor(ptxnid, &pcursor, 0); + if (ret != 0) + return NULL; + return pcursor; + } + + bool WriteStealthKeyMeta(const CKeyID& keyId, const CStealthKeyMetadata& sxKeyMeta) { + nWalletDBUpdated++; + return Write(std::make_pair(std::string("sxKeyMeta"), keyId), sxKeyMeta, true); + } + + bool EraseStealthKeyMeta(const CKeyID& keyId) { + nWalletDBUpdated++; + return Erase(std::make_pair(std::string("sxKeyMeta"), keyId)); + } + + bool WriteStealthAddress(const CStealthAddress& sxAddr) { + nWalletDBUpdated++; + + return Write(std::make_pair(std::string("sxAddr"), sxAddr.scan_pubkey), sxAddr, true); + } + + bool ReadStealthAddress(CStealthAddress& sxAddr) { + // -- set scan_pubkey before reading + return Read(std::make_pair(std::string("sxAddr"), sxAddr.scan_pubkey), sxAddr); + } +#endif + bool WriteName(const std::string& strAddress, const std::string& strName); bool EraseName(const std::string& strAddress); From 51f809a752e14aeb067ee270e33978e6724f5cde Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Sat, 25 Nov 2017 18:45:25 +0100 Subject: [PATCH 18/27] Code cleanup --- src/chainparams.cpp | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 9428e1498..2be73af53 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -177,17 +177,7 @@ class CMainParams : public CChainParams { i2pvSeeds.push_back(CDNSSeedData("a4gii55rnvv22qm2ojre2n67bzms5utr4k3ckafwjdoym2cqmv2q.b32.i2p", "a4gii55rnvv22qm2ojre2n67bzms5utr4k3ckafwjdoym2cqmv2q.b32.i2p")); // K1773R's seednode i2pvSeeds.push_back(CDNSSeedData("b7ziruwpk7g2e44xyomnc2nu5tx7bc2f2ai4dzi66uxm3bc3qttq.b32.i2p", "b7ziruwpk7g2e44xyomnc2nu5tx7bc2f2ai4dzi66uxm3bc3qttq.b32.i2p")); // K1773R's seednode (dnsseed01) i2pvSeeds.push_back(CDNSSeedData("7zbwzykhyjcmmessswamkxfyya7hioiy2oq7voaw27625qwruqia.b32.i2p", "7zbwzykhyjcmmessswamkxfyya7hioiy2oq7voaw27625qwruqia.b32.i2p")); // lunokhod's seednode - - // As of March 30, 2015 these entries were not working. CSlave 14/03/2016 Those will be deleted: -// i2pvSeeds.push_back(CDNSSeedData("ypwvq7jcu3uwyg4ufjqt4a26ca6pxdcnshv6q2okmmjsof5dxzkq.b32.i2p", "ypwvq7jcu3uwyg4ufjqt4a26ca6pxdcnshv6q2okmmjsof5dxzkq.b32.i2p")); // keystroke's seednode -// i2pvSeeds.push_back(CDNSSeedData("d46o5wddsdrvg2ywnu4o57zeloayt7oyg56adz63xukordgfommq.b32.i2p", "d46o5wddsdrvg2ywnu4o57zeloayt7oyg56adz63xukordgfommq.b32.i2p")); -// i2pvSeeds.push_back(CDNSSeedData("u2om3hgjpghqfi7yid75xdmvzlgjybstzp6mtmaxse4aztm23rwq.b32.i2p", "u2om3hgjpghqfi7yid75xdmvzlgjybstzp6mtmaxse4aztm23rwq.b32.i2p")); -// i2pvSeeds.push_back(CDNSSeedData("htigbyeisbqizn63ftqw7ggfwfeolwkb3zfxwmyffygbilwqsswq.b32.i2p", "htigbyeisbqizn63ftqw7ggfwfeolwkb3zfxwmyffygbilwqsswq.b32.i2p")); -// i2pvSeeds.push_back(CDNSSeedData("st4eyxcp73zzbpatgt26pt3rlfwb7g5ywedol65baalgpnhvzqpa.b32.i2p", "st4eyxcp73zzbpatgt26pt3rlfwb7g5ywedol65baalgpnhvzqpa.b32.i2p")); -// i2pvSeeds.push_back(CDNSSeedData("qgmxpnpujddsd5ez67p4ognqsvo64tnzdbzesezdbtb3atyoxcpq.b32.i2p", "qgmxpnpujddsd5ez67p4ognqsvo64tnzdbzesezdbtb3atyoxcpq.b32.i2p")); -// i2pvSeeds.push_back(CDNSSeedData("72vaef5cmmlcilgvpeltcp77gutsnyic2l5khsgz7kyivla5lwjq.b32.i2p", "72vaef5cmmlcilgvpeltcp77gutsnyic2l5khsgz7kyivla5lwjq.b32.i2p")); // riddler's seednode -// i2pvSeeds.push_back(CDNSSeedData("qc37luxnbh3hkihxfl2e7nwosebh5sbfvpvjqwn7c3g5kqftb5qq.b32.i2p", "qc37luxnbh3hkihxfl2e7nwosebh5sbfvpvjqwn7c3g5kqftb5qq.b32.i2p")); // psi's seednode -// + #endif // ENABLE_I2PSAM #ifndef USING_PRE_HISTORIC_COMPILER From b9e4346599886fae63b3775881b2d3624a50e10e Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Sat, 25 Nov 2017 18:51:27 +0100 Subject: [PATCH 19/27] Wallet PoC implementation --- src/wallet.cpp | 844 ++++++++++++++++++++++++++++++++++++++++++++++--- src/wallet.h | 46 ++- 2 files changed, 848 insertions(+), 42 deletions(-) diff --git a/src/wallet.cpp b/src/wallet.cpp index 29c76919a..f20d36067 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -15,6 +15,12 @@ #include "sync.h" #include "timedata.h" +#ifndef ENABLE_STEALTH +#include "leveldbwrapper.h" +#include "main.h" +#include "stealth.h" +#endif + #include #include @@ -25,6 +31,8 @@ using namespace std; +#define PRIszu "Iu" + //! //! Constants declared for global visibility in the header file, defined in this source code file. //! @@ -129,6 +137,682 @@ void CWallet::SetBestChain(const CBlockLocator& loc) walletdb.WriteBestBlock(loc); } +#ifdef ENABLE_STEALTH + + +bool CWallet::Lock() { + if (IsLocked()) + return true; + { + LOCK(cs_wallet); + CWalletDB wdb(strWalletFile); + CStealthAddress sxAddrTemp; + std::set::iterator it; + for (it = stealthAddresses.begin(); it != stealthAddresses.end(); ++it) { + if (it->scan_secret.size() < 32) + continue; + CStealthAddress &sxAddr = const_cast(*it); + if (fDebug) LogPrintf("Recrypting stealth key %s\n", sxAddr.Encoded().c_str()); + sxAddrTemp.scan_pubkey = sxAddr.scan_pubkey; + if (!wdb.ReadStealthAddress(sxAddrTemp)) { + LogPrintf("Error: Failed to read stealth key from db %s\n", sxAddr.Encoded().c_str()); + continue; + } + sxAddr.spend_secret = sxAddrTemp.spend_secret; + } + } + return true; +} + + +bool CWallet::NewStealthAddress(std::string& sError, std::string& sLabel, CStealthAddress& sxAddr) { + ec_secret scan_secret; + ec_secret spend_secret; + + if (GenerateRandomSecret(scan_secret) != 0 || GenerateRandomSecret(spend_secret) != 0) { + sError = "GenerateRandomSecret failed."; + LogPrintf("Error CWallet::NewStealthAddress - %s\n", sError); + return false; + } + + ec_point scan_pubkey, spend_pubkey; + if (SecretToPublicKey(scan_secret, scan_pubkey) != 0) { + sError = "Could not get scan public key."; + LogPrintf("Error CWallet::NewStealthAddress - %s\n", sError); + return false; + } + + if (SecretToPublicKey(spend_secret, spend_pubkey) != 0) { + sError = "Could not get spend public key."; + LogPrintf("Error CWallet::NewStealthAddress - %s\n", sError); + return false; + } + + if (fDebug) { + LogPrintf("getnewstealthaddress: "); + LogPrintf("scan_pubkey "); + for (uint32_t i = 0; i < scan_pubkey.size(); ++i) + LogPrintf("%02x", scan_pubkey[i]); + LogPrintf("\n"); + + LogPrintf("spend_pubkey "); + for (uint32_t i = 0; i < spend_pubkey.size(); ++i) + LogPrintf("%02x", spend_pubkey[i]); + LogPrintf("\n"); + } + + + sxAddr.label = sLabel; + sxAddr.scan_pubkey = scan_pubkey; + sxAddr.spend_pubkey = spend_pubkey; + + sxAddr.scan_secret.resize(32); + memcpy(&sxAddr.scan_secret[0], &scan_secret.e[0], 32); + sxAddr.spend_secret.resize(32); + memcpy(&sxAddr.spend_secret[0], &spend_secret.e[0], 32); + + return true; +} + +bool CWallet::AddStealthAddress(CStealthAddress& sxAddr) { + LOCK(cs_wallet); + + // must add before changing spend_secret + stealthAddresses.insert(sxAddr); + + bool fOwned = sxAddr.scan_secret.size() == ec_secret_size; + + CCrypter crypter; + CKeyingMaterial vMasterKey; + + if (fOwned) { + // -- owned addresses can only be added when wallet is unlocked + if (IsLocked()) { + LogPrintf("Error: CWallet::AddStealthAddress wallet must be unlocked.\n"); + stealthAddresses.erase(sxAddr); + return false; + } + + if (IsCrypted()) { + std::vector vchCryptedSecret; + CSecret vchSecret; + vchSecret.resize(32); + memcpy(&vchSecret[0], &sxAddr.spend_secret[0], 32); + + uint256 iv = Hash(sxAddr.spend_pubkey.begin(), sxAddr.spend_pubkey.end()); + if (!EncryptSecret(vMasterKey, vchSecret, iv, vchCryptedSecret)) { + LogPrintf("Error: Failed encrypting stealth key %s\n", sxAddr.Encoded()); + stealthAddresses.erase(sxAddr); + return false; + } + sxAddr.spend_secret = vchCryptedSecret; + } + } + + + bool rv = CWalletDB(strWalletFile).WriteStealthAddress(sxAddr); + // TODO: Fix notify for stealth + //if (rv) NotifyAddressBookChanged(this, sxAddr, sxAddr.label, fOwned, CT_NEW); + + return rv; +} + +bool CWallet::UnlockStealthAddresses(const CKeyingMaterial& vMasterKeyIn) { + // -- decrypt spend_secret of stealth addresses + std::set::iterator it; + for (it = stealthAddresses.begin(); it != stealthAddresses.end(); ++it) { + if (it->scan_secret.size() < 32) + continue; // stealth address is not owned + + // -- CStealthAddress are only sorted on spend_pubkey + CStealthAddress &sxAddr = const_cast(*it); + + if (fDebug) LogPrintf("Decrypting stealth key %s\n", sxAddr.Encoded()); + + CSecret vchSecret; + uint256 iv = Hash(sxAddr.spend_pubkey.begin(), sxAddr.spend_pubkey.end()); + if(!DecryptSecret(vMasterKeyIn, sxAddr.spend_secret, iv, vchSecret) || vchSecret.size() != 32) { + LogPrintf("Error: Failed decrypting stealth key %s\n", sxAddr.Encoded()); + continue; + } + + ec_secret testSecret; + memcpy(&testSecret.e[0], &vchSecret[0], 32); + ec_point pkSpendTest; + + if (SecretToPublicKey(testSecret, pkSpendTest) != 0 || pkSpendTest != sxAddr.spend_pubkey) { + LogPrintf("Error: Failed decrypting stealth key, public key mismatch %s\n", sxAddr.Encoded()); + continue; + } + + sxAddr.spend_secret.resize(32); + memcpy(&sxAddr.spend_secret[0], &vchSecret[0], 32); + } + + CryptedKeyMap mapCryptedKeys = getCryptedKeysMap(); + CryptedKeyMap::iterator mi = mapCryptedKeys.begin(); + for (; mi != mapCryptedKeys.end(); ++mi) { + CPubKey &pubKey = (*mi).second.first; + std::vector &vchCryptedSecret = (*mi).second.second; + if (vchCryptedSecret.size() != 0) + continue; + + CKeyID ckid = pubKey.GetID(); + CAnoncoinAddress addr(ckid); + + StealthKeyMetaMap::iterator mi = mapStealthKeyMeta.find(ckid); + if (mi == mapStealthKeyMeta.end()) { + LogPrintf("Error: No metadata found to add secret for %s\n", addr.ToString()); + continue; + } + + CStealthKeyMetadata& sxKeyMeta = mi->second; + + CStealthAddress sxFind; + sxFind.scan_pubkey = sxKeyMeta.pkScan.Raw(); + + std::set::iterator si = stealthAddresses.find(sxFind); + if (si == stealthAddresses.end()) { + LogPrintf("No stealth key found to add secret for %s\n", addr.ToString()); + continue; + } + + if (fDebug) LogPrintf("Expanding secret for %s\n", addr.ToString()); + + ec_secret sSpendR; + ec_secret sSpend; + ec_secret sScan; + + if (si->spend_secret.size() != ec_secret_size || si->scan_secret.size() != ec_secret_size) { + LogPrintf("Stealth address has no secret key for %s\n", addr.ToString()); + continue; + } + memcpy(&sScan.e[0], &si->scan_secret[0], ec_secret_size); + memcpy(&sSpend.e[0], &si->spend_secret[0], ec_secret_size); + + ec_point pkEphem = sxKeyMeta.pkEphem.Raw(); + if (StealthSecretSpend(sScan, pkEphem, sSpend, sSpendR) != 0) { + LogPrintf("StealthSecretSpend() failed.\n"); + continue; + } + + ec_point pkTestSpendR; + if (SecretToPublicKey(sSpendR, pkTestSpendR) != 0) { + LogPrintf("SecretToPublicKey() failed.\n"); + continue; + } + + CSecret vchSecret; + vchSecret.resize(ec_secret_size); + + memcpy(&vchSecret[0], &sSpendR.e[0], ec_secret_size); + CKey ckey; + + try { + ckey.SetPrivKey(vchSecret, true); + } catch (std::exception& e) { + LogPrintf("ckey.SetPrivKey() threw: %s.\n", e.what()); + continue; + } + + CPubKey cpkT = ckey.GetPubKey(); + + if (!cpkT.IsValid()) { + LogPrintf("cpkT is invalid.\n"); + continue; + } + + if (cpkT != pubKey) { + printf("Error: Generated secret does not match.\n"); + if (fDebug) { + LogPrintf("cpkT %s\n", HexStr(cpkT.Raw())); + LogPrintf("pubKey %s\n", HexStr(pubKey.Raw())); + } + continue; + } + + if (!ckey.IsValid()) { + LogPrintf("Reconstructed key is invalid.\n"); + continue; + } + + if (fDebug) { + CKeyID keyID = cpkT.GetID(); + CAnoncoinAddress coinAddress(keyID); + LogPrintf("Adding secret to key %s.\n", coinAddress.ToString()); + } + + if (!AddKey(ckey)) { + LogPrintf("AddKey failed.\n"); + continue; + } + + if (!CWalletDB(strWalletFile).EraseStealthKeyMeta(ckid)) + LogPrintf("EraseStealthKeyMeta failed for %s\n", addr.ToString()); + } + return true; +} + +bool CWallet::UpdateStealthAddress(std::string &addr, std::string &label, bool addIfNotExist) { + if (fDebug) LogPrintf("UpdateStealthAddress %s\n", addr); + + + CStealthAddress sxAddr; + + if (!sxAddr.SetEncoded(addr)) return false; + + std::set::iterator it; + it = stealthAddresses.find(sxAddr); + + ChangeType nMode = CT_UPDATED; + CStealthAddress sxFound; + if (it == stealthAddresses.end()) { + if (addIfNotExist) { + sxFound = sxAddr; + sxFound.label = label; + stealthAddresses.insert(sxFound); + nMode = CT_NEW; + } else { + LogPrintf("UpdateStealthAddress %s, not in set\n", addr); + return false; + } + } else { + sxFound = const_cast(*it); + + if (sxFound.label == label) { + // no change + return true; + } + + it->label = label; + + if (sxFound.scan_secret.size() == ec_secret_size) { + LogPrintf("UpdateStealthAddress: todo - update owned stealth address.\n"); + return false; + } + } + + sxFound.label = label; + + if (!CWalletDB(strWalletFile).WriteStealthAddress(sxFound)) { + LogPrintf("UpdateStealthAddress(%s) Write to db failed.\n", addr); + return false; + } + + //bool fOwned = sxFound.scan_secret.size() == ec_secret_size; + // TODO: Fix this + //NotifyAddressBookChanged(this, sxFound, sxFound.label, fOwned, nMode); + + return true; +} + +bool CWallet::CreateStealthTransaction(CScript scriptPubKey, int64_t nValue, std::vector& P, std::vector& narr, std::string& sNarr, CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, const CCoinControl* coinControl) { + vector< pair > vecSend; + vecSend.push_back(make_pair(scriptPubKey, nValue)); + + CScript scriptP = CScript() << OP_RETURN << P; + if (narr.size() > 0) + scriptP = scriptP << OP_RETURN << narr; + + vecSend.push_back(make_pair(scriptP, CAmount(0))); + + // -- shuffle inputs, change output won't mix enough as it must be not fully random for plantext narrations + std::random_shuffle(vecSend.begin(), vecSend.end()); + + std::string failReason(""); + bool rv = CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet, failReason, coinControl); + + // -- the change txn is inserted in a random pos, check here to match narr to output + + if (rv && narr.size() > 0) { + for (unsigned int k = 0; k < wtxNew.vout.size(); ++k) { + if (wtxNew.vout[k].scriptPubKey != scriptPubKey + || wtxNew.vout[k].nValue != nValue) + continue; + + char key[64]; + if (snprintf(key, sizeof(key), "n_%u", k) < 1) { + LogPrintf("CreateStealthTransaction(): Error creating narration key."); + break; + } + wtxNew.mapValue[key] = sNarr; + break; + } + } + + return rv; +} + +string CWallet::SendStealthMoney(CScript scriptPubKey, int64_t nValue, std::vector& P, std::vector& narr, std::string& sNarr, CWalletTx& wtxNew, bool fAskFee) { + CReserveKey reservekey(this); + int64_t nFeeRequired; + + if (IsLocked()) { + string strError = _("Error: Wallet locked, unable to create transaction "); + LogPrintf("SendStealthMoney() : %s", strError); + return strError; + } + if (!CreateStealthTransaction(scriptPubKey, nValue, P, narr, sNarr, wtxNew, reservekey, nFeeRequired)) { + string strError; + if (nValue + nFeeRequired > GetBalance()) + strError = strprintf(_("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds "), FormatMoney(nFeeRequired).c_str()); + else + strError = _("Error: Transaction creation failed "); + LogPrintf("SendStealthMoney() : %s", strError); + return strError; + } +/* + if (fAskFee && !uiInterface.ThreadSafeAskFee(nFeeRequired, _("Sending..."))) + return "ABORTED"; +*/ + if (!CommitTransaction(wtxNew, reservekey)) + return _("Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here."); + + return ""; +} + +bool CWallet::SendStealthMoneyToDestination(CStealthAddress& sxAddress, int64_t nValue, std::string& sNarr, CWalletTx& wtxNew, std::string& sError, bool fAskFee) +{ + // -- Check amount + if (nValue <= 0) { + sError = "Invalid amount"; + return false; + } + if (nValue + nTransactionFee > GetBalance()) { + sError = "Insufficient funds"; + return false; + } + + + ec_secret ephem_secret; + ec_secret secretShared; + ec_point pkSendTo; + ec_point ephem_pubkey; + + if (GenerateRandomSecret(ephem_secret) != 0) { + sError = "GenerateRandomSecret failed."; + return false; + } + + if (StealthSecret(ephem_secret, sxAddress.scan_pubkey, sxAddress.spend_pubkey, secretShared, pkSendTo) != 0) { + sError = "Could not generate receiving public key."; + return false; + } + + CPubKey cpkTo(pkSendTo); + if (!cpkTo.IsValid()) { + sError = "Invalid public key generated."; + return false; + } + + CKeyID ckidTo = cpkTo.GetID(); + + CAnoncoinAddress addrTo(ckidTo); + + if (SecretToPublicKey(ephem_secret, ephem_pubkey) != 0) { + sError = "Could not generate ephem public key."; + return false; + } + + if (fDebug) { + LogPrintf("Stealth send to generated pubkey x=%" PRIszu ": %s\n", pkSendTo.size(), HexStr(pkSendTo)); + LogPrintf("hash %s\n", addrTo.ToString()); + LogPrintf("ephem_pubkey x=%" PRIszu ": %s\n", ephem_pubkey.size(), HexStr(ephem_pubkey)); + } + + std::vector vchNarr; + if (sNarr.length() > 0) { + CCrypter crypter; + + CKeyingMaterial vMasterKey; + + CSecret vchSecret; + vchSecret.resize(ec_secret_size); + + crypter.SetKey(vchSecret, ephem_pubkey); + + if (!crypter.Encrypt( vMasterKey, vchNarr )) { + sError = "Narration encryption failed."; + return false; + } + + if (vchNarr.size() > 48) { + sError = "Encrypted narration is too long."; + return false; + } + } + + // -- Parse Anoncoin address + CScript scriptPubKey; + scriptPubKey.SetDestination(addrTo.Get()); + + if ((sError = SendStealthMoney(scriptPubKey, nValue, ephem_pubkey, vchNarr, sNarr, wtxNew, fAskFee)) != "") + return false; + + + return true; +} + +bool CWallet::FindStealthTransactions(const CTransaction& tx, mapValue_t& mapNarr) +{ + if (fDebug) LogPrintf("FindStealthTransactions() tx: %s\n", tx.GetHash().GetHex()); + + mapNarr.clear(); + + LOCK(cs_wallet); + ec_secret sSpendR; + ec_secret sSpend; + ec_secret sScan; + ec_secret sShared; + + ec_point pkExtracted; + + std::vector vchEphemPK; + std::vector vchDataB; + std::vector vchENarr; + opcodetype opCode; + char cbuf[256]; + + int32_t nOutputIdOuter = -1; + BOOST_FOREACH(const CTxOut& txout, tx.vout) + { + nOutputIdOuter++; + // -- for each OP_RETURN need to check all other valid outputs + + LogPrintf("txout scriptPubKey %s\n", txout.scriptPubKey.ToString()); + CScript::const_iterator itTxA = txout.scriptPubKey.begin(); + + if (!txout.scriptPubKey.GetOp(itTxA, opCode, vchEphemPK) + || opCode != OP_RETURN) + continue; + else + if (!txout.scriptPubKey.GetOp(itTxA, opCode, vchEphemPK) + || vchEphemPK.size() != 33) + { + // -- look for plaintext narrations + if (vchEphemPK.size() > 1 + && vchEphemPK[0] == 'n' + && vchEphemPK[1] == 'p') + { + if (txout.scriptPubKey.GetOp(itTxA, opCode, vchENarr) + && opCode == OP_RETURN + && txout.scriptPubKey.GetOp(itTxA, opCode, vchENarr) + && vchENarr.size() > 0) + { + std::string sNarr = std::string(vchENarr.begin(), vchENarr.end()); + + snprintf(cbuf, sizeof(cbuf), "n_%d", nOutputIdOuter-1); // plaintext narration always matches preceding value output + mapNarr[cbuf] = sNarr; + } else { + LogPrintf("Warning: FindStealthTransactions() tx: %s, Could not extract plaintext narration.\n", tx.GetHash().GetHex().c_str()); + } + } + + continue; + } + + int32_t nOutputId = -1; + nStealth++; + BOOST_FOREACH(const CTxOut& txoutB, tx.vout) + { + nOutputId++; + + if (&txoutB == &txout) + continue; + + bool txnMatch = false; // only 1 txn will match an ephem pk + LogPrintf("txoutB scriptPubKey %s\n", txoutB.scriptPubKey.ToString()); + + CTxDestination address; + if (!ExtractDestination(txoutB.scriptPubKey, address)) + continue; + + if (address.type() != typeid(CKeyID)) + continue; + + CKeyID ckidMatch = boost::get(address); + + if (HaveKey(ckidMatch)) // no point checking if already have key + continue; + + std::set::iterator it; + for (it = stealthAddresses.begin(); it != stealthAddresses.end(); ++it) { + if (it->scan_secret.size() != ec_secret_size) + continue; // Stealth Address is not owned + + LogPrintf("it->Encodeded() %s\n", it->Encoded()); + memcpy(&sScan.e[0], &it->scan_secret[0], ec_secret_size); + + if (StealthSecret(sScan, vchEphemPK, it->spend_pubkey, sShared, pkExtracted) != 0) { + LogPrintf("StealthSecret failed.\n"); + continue; + } + LogPrintf("pkExtracted %"PRIszu": %s\n", pkExtracted.size(), HexStr(pkExtracted)); + + CPubKey cpkE(pkExtracted); + + if (!cpkE.IsValid()) + continue; + CKeyID ckidE = cpkE.GetID(); + + if (ckidMatch != ckidE) + continue; + + if (fDebug) LogPrintf("Found stealth txn to address %s\n", it->Encoded()); + + if (IsLocked()) { + if (fDebug) LogPrintf("Wallet is locked, adding key without secret.\n"); + + // -- add key without secret + std::vector vchEmpty; + AddCryptedKey(cpkE, vchEmpty); + CKeyID keyId = cpkE.GetID(); + CAnoncoinAddress coinAddress(keyId); + std::string sLabel = it->Encoded(); + std::string sPurpose; + SetAddressBook(keyId, sLabel, sPurpose); + + CPubKey cpkEphem(vchEphemPK); + CPubKey cpkScan(it->scan_pubkey); + CStealthKeyMetadata lockedSkMeta(cpkEphem, cpkScan); + + if (!CWalletDB(strWalletFile).WriteStealthKeyMeta(keyId, lockedSkMeta)) + LogPrintf("WriteStealthKeyMeta failed for %s\n", coinAddress.ToString()); + + mapStealthKeyMeta[keyId] = lockedSkMeta; + nFoundStealth++; + } else { + LogPrintf("Wallet is not Locked."); + if (it->spend_secret.size() != ec_secret_size) + continue; + memcpy(&sSpend.e[0], &it->spend_secret[0], ec_secret_size); + + + if (StealthSharedToSecretSpend(sShared, sSpend, sSpendR) != 0) { + LogPrintf("StealthSharedToSecretSpend() failed.\n"); + continue; + } + + ec_point pkTestSpendR; + if (SecretToPublicKey(sSpendR, pkTestSpendR) != 0) { + LogPrintf("SecretToPublicKey() failed.\n"); + continue; + } + + CSecret vchSecret; + vchSecret.resize(ec_secret_size); + + memcpy(&vchSecret[0], &sSpendR.e[0], ec_secret_size); + CKey ckey; + + try { + ckey.Set(vchSecret.begin(), vchSecret.end(), true); + } catch (std::exception& e) { + LogPrintf("ckey.SetSecret() threw: %s.\n", e.what()); + continue; + } + + CPubKey cpkT = ckey.GetPubKey(); + if (!cpkT.IsValid()) { + LogPrintf("cpkT is invalid.\n"); + continue; + } + + if (!ckey.IsValid()) { + LogPrintf("Reconstructed key is invalid.\n"); + continue; + } + + CKeyID keyID = cpkT.GetID(); + if (fDebug) { + CAnoncoinAddress coinAddress(keyID); + LogPrintf("Adding key %s.\n", coinAddress.ToString().c_str()); + } + + if (!AddKey(ckey)) { + LogPrintf("AddKey failed.\n"); + continue; + } + + std::string sLabel = it->Encoded(); + std::string sPurpose; + SetAddressBook(keyID, sLabel, sPurpose); + nFoundStealth++; + } + + if (txout.scriptPubKey.GetOp(itTxA, opCode, vchENarr) && opCode == OP_RETURN + && txout.scriptPubKey.GetOp(itTxA, opCode, vchENarr) && vchENarr.size() > 0) { + + CKeyingMaterial vMasterKey; + CCrypter crypter; + CSecret vchSecret; + vchSecret.resize(ec_secret_size); + + crypter.SetKey(vchSecret, vchEphemPK); + std::vector vchNarr; + if (!crypter.Decrypt(vchENarr, vMasterKey)) { + LogPrintf("Decrypt narration failed.\n"); + continue; + } + std::string sNarr = std::string(vchNarr.begin(), vchNarr.end()); + + snprintf(cbuf, sizeof(cbuf), "n_%d", nOutputId); + mapNarr[cbuf] = sNarr; + } + + txnMatch = true; + break; + } + if (txnMatch) + break; + } + } + + return true; +} + +#endif + void CWallet::UpdatedTransaction( const uint256& uintTxHash ) { LOCK(cs_wallet); @@ -375,8 +1059,13 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase) return false; if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey)) continue; // try another master key +#ifndef ENABLE_STEALTH if (CCryptoKeyStore::Unlock(vMasterKey)) return true; +#else + if (CCryptoKeyStore::Unlock(vMasterKey) && UnlockStealthAddresses(vMasterKey)) + return true; +#endif } } return false; @@ -390,6 +1079,8 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, LOCK(cs_wallet); Lock(); + CWalletDB walletdb(strWalletFile); + CCrypter crypter; CKeyingMaterial vMasterKey; BOOST_FOREACH(MasterKeyMap::value_type& pMasterKey, mapMasterKeys) @@ -398,7 +1089,11 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, return false; if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey)) return false; +#ifndef ENABLE_STEALTH if (CCryptoKeyStore::Unlock(vMasterKey)) +#else + if (CCryptoKeyStore::Unlock(vMasterKey) && UnlockStealthAddresses(vMasterKey)) +#endif { int64_t nStartTime = GetTimeMillis(); crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod); @@ -416,7 +1111,7 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, return false; if (!crypter.Encrypt(vMasterKey, pMasterKey.second.vchCryptedKey)) return false; - CWalletDB(strWalletFile).WriteMasterKey(pMasterKey.first, pMasterKey.second); + walletdb.WriteMasterKey(pMasterKey.first, pMasterKey.second); if (fWasLocked) Lock(); return true; @@ -620,6 +1315,34 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) // Encryption was introduced in version 0.4.0 SetMinVersion(FEATURE_WALLETCRYPT, pwalletdbEncryption, true); +#ifdef ENABLE_STEALTH + LogPrintf("Has %d stealth addresses\n", stealthAddresses.size()); + std::set::iterator it; + for (it = stealthAddresses.begin(); it != stealthAddresses.end(); ++it) { + if (it->scan_secret.size() < 32) continue; // stealth address is not owned + + // -- CStealthAddress is only sorted on spend_pubkey + CStealthAddress &sxAddr = const_cast(*it); + + if (fDebug) LogPrintf("Encrypting stealth key %s\n", sxAddr.Encoded()); + + std::vector vchCryptedSecret; + + CSecret vchSecret; + vchSecret.resize(32); + memcpy(&vchSecret[0], &sxAddr.spend_secret[0], 32); + + uint256 iv = Hash(sxAddr.spend_pubkey.begin(), sxAddr.spend_pubkey.end()); + if (!EncryptSecret(vMasterKey, vchSecret, iv, vchCryptedSecret)) { + LogPrintf("Error: Failed encrypting stealth key %s\n", sxAddr.Encoded()); + continue; + } + + sxAddr.spend_secret = vchCryptedSecret; + if (fFileBacked) pwalletdbEncryption->WriteStealthAddress(sxAddr); + } +#endif + if (fFileBacked) { if (!pwalletdbEncryption->TxnCommit()) @@ -820,9 +1543,19 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl AssertLockHeld(cs_wallet); bool fExisted = mapWallet.count(tx.GetHash()) != 0; if (fExisted && !fUpdate) return false; - if (fExisted || IsMine(tx) || IsFromMe(tx)) - { + +#ifdef ENABLE_STEALTH + mapValue_t mapNarr; + FindStealthTransactions(tx, mapNarr); +#endif + + if (fExisted || IsMine(tx) || IsFromMe(tx)) { CWalletTx wtx(this,tx); + +#ifdef ENABLE_STEALTH + if (!mapNarr.empty()) + wtx.mapValue.insert(mapNarr.begin(), mapNarr.end()); +#endif // Get merkle branch if transaction was found in a block if (pblock) wtx.SetMerkleBranch(pblock); @@ -1324,24 +2057,48 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, set > vecSend; + vecSend.push_back(make_pair(scriptPubKey, nValue)); + + if (sNarr.length() > 0) { + std::vector vNarr(sNarr.c_str(), sNarr.c_str() + sNarr.length()); + std::vector vNDesc; + + vNDesc.resize(2); + vNDesc[0] = 'n'; + vNDesc[1] = 'p'; + CScript scriptN = CScript() << OP_RETURN << vNDesc << OP_RETURN << vNarr; + + vecSend.push_back(make_pair(scriptN, 0)); + } + + // -- CreateTransaction won't place change between value and narr output. + // narration output will be for preceding output + CAmount nFeeRet = CAmount(nTransactionFee); + bool rv = CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet, failReason, coinControl); + + // -- narration will be added to mapValue later in FindStealthTransactions From CommitTransaction + return rv; +} + +#endif bool CWallet::CreateTransaction(const vector >& vecSend, - CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, string& strFailReason, const CCoinControl* coinControl) -{ + CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, string& strFailReason, const CCoinControl* coinControl) { CAmount nValue = 0; - BOOST_FOREACH (const PAIRTYPE(CScript, CAmount)& s, vecSend) - { - if (nValue < 0) - { + BOOST_FOREACH (const PAIRTYPE(CScript, CAmount)& s, vecSend) { + if (nValue < 0) { strFailReason = _("Transaction amounts must be positive"); return false; } nValue += s.second; } - if (vecSend.empty() || nValue < 0) - { + if (vecSend.empty() || nValue < 0) { strFailReason = _("Transaction amounts must be positive"); return false; } @@ -1354,8 +2111,7 @@ bool CWallet::CreateTransaction(const vector >& vecSend, LOCK2(cs_main, cs_wallet); { nFeeRet = nTransactionFee; - while (true) - { + while (true) { txNew.vin.clear(); txNew.vout.clear(); wtxNew.fFromMe = true; @@ -1366,19 +2122,29 @@ bool CWallet::CreateTransaction(const vector >& vecSend, BOOST_FOREACH (const PAIRTYPE(CScript, CAmount)& s, vecSend) { CTxOut txout(s.second, s.first); - if (txout.IsDust(minRelayTxFee)) - { +#ifdef ENABLE_STEALTH + CScript::const_iterator itTxA = txout.scriptPubKey.begin(); + if (!txout.IsScriptOpReturn()) { + if (txout.IsDust(minRelayTxFee)) { + LogPrintf("%s\n", txout.ToString()); + strFailReason = _("Transaction amount too small"); + return false; + } + } +#else + if (txout.IsDust(minRelayTxFee)) { + LogPrintf("%s\n", txout.ToString()); strFailReason = _("Transaction amount too small"); return false; } +#endif txNew.vout.push_back(txout); } // Choose coins to use set > setCoins; CAmount nValueIn = 0; - if (!SelectCoins(nTotalValue, setCoins, nValueIn, coinControl)) - { + if (!SelectCoins(nTotalValue, setCoins, nValueIn, coinControl)) { strFailReason = _("Insufficient funds"); return false; } @@ -1403,20 +2169,17 @@ bool CWallet::CreateTransaction(const vector >& vecSend, // nFeeRet += nMoveToFee; // } - if (nChange > 0) - { + if (nChange > 0) { // Fill a vout to ourself // TODO: pass in scriptChange instead of reservekey so // change transaction isn't always pay-to-anoncoin-address CScript scriptChange; // coin control: send change to custom address - if (coinControl && !boost::get(&coinControl->destChange)) + if (coinControl && !boost::get(&coinControl->destChange)) { scriptChange.SetDestination(coinControl->destChange); - // no coin control: send change to newly generated address - else - { + } else { // Note: We use a new key here to keep it from being obvious which side is the change. // The drawback is that by not reusing a previous key, the change may be lost if a // backup is restored, if the backup doesn't have the new private key for the change. @@ -1437,13 +2200,10 @@ bool CWallet::CreateTransaction(const vector >& vecSend, // Never create dust outputs; if we would, just // add the dust to the fee. - if (newTxOut.IsDust(minRelayTxFee)) - { + if (newTxOut.IsDust(minRelayTxFee)) { nFeeRet += nChange; reservekey.ReturnKey(); - } - else - { + } else { // Insert change txn at random position: vector::iterator position = txNew.vout.begin()+GetRandInt(txNew.vout.size()+1); txNew.vout.insert(position, newTxOut); @@ -1459,8 +2219,7 @@ bool CWallet::CreateTransaction(const vector >& vecSend, // Sign int nIn = 0; BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins) - if (!SignSignature(*this, *coin.first, txNew, nIn++)) - { + if (!SignSignature(*this, *coin.first, txNew, nIn++)) { strFailReason = _("Signing transaction failed"); return false; } @@ -1470,8 +2229,7 @@ bool CWallet::CreateTransaction(const vector >& vecSend, // Limit size unsigned int nBytes = ::GetSerializeSize(*(CTransaction*)&wtxNew, SER_NETWORK, PROTOCOL_VERSION); - if (nBytes >= MAX_STANDARD_TX_SIZE) - { + if (nBytes >= MAX_STANDARD_TX_SIZE) { strFailReason = _("Transaction too large"); return false; } @@ -1481,8 +2239,7 @@ bool CWallet::CreateTransaction(const vector >& vecSend, CAmount nPayFee = nTransactionFee * (1 + (int64_t)nBytes / 1000); bool fAllowFree = AllowFree(dPriority); CAmount nMinFee = GetMinFee(wtxNew, nBytes, fAllowFree, GMF_SEND); - if (nFeeRet < max(nPayFee, nMinFee)) - { + if (nFeeRet < max(nPayFee, nMinFee)) { nFeeRet = max(nPayFee, nMinFee); continue; } @@ -1495,16 +2252,26 @@ bool CWallet::CreateTransaction(const vector >& vecSend, } bool CWallet::CreateTransaction(CScript scriptPubKey, const CAmount nValue, - CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, string& strFailReason, const CCoinControl* coinControl) -{ + CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, + string& strFailReason, const CCoinControl* coinControl) { vector< pair > vecSend; vecSend.push_back(make_pair(scriptPubKey, nValue)); return CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet, strFailReason, coinControl); } // Call after CreateTransaction unless you want to abort -bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) -{ +bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) { +#ifdef ENABLE_STEALTH + mapValue_t mapNarr; + FindStealthTransactions(wtxNew, mapNarr); + + if (!mapNarr.empty()) { + BOOST_FOREACH(const PAIRTYPE(string,string)& item, mapNarr) { + LogPrintf("CWallet::CommitTransaction: stealth: %s %s", item.first, item.second); + wtxNew.mapValue[item.first] = item.second; + } + } +#endif { LOCK2(cs_main, cs_wallet); LogPrintf( "%s : for new wtx:\n%s", __func__, wtxNew.ToString() ); @@ -1586,6 +2353,7 @@ DBErrors CWallet::LoadWallet(bool fFirstRunRet) DBErrors nLoadWalletRet = CWalletDB(strWalletFile,"cr+").LoadWallet(this); if (nLoadWalletRet == DB_NEED_REWRITE) { + LogPrintf("nLoadWalletRet == DB_NEED_REWRITE"); if (CDB::Rewrite(strWalletFile, "\x04pool")) { LOCK(cs_wallet); diff --git a/src/wallet.h b/src/wallet.h index 61081a8de..91c690cf9 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -12,6 +12,12 @@ #include "key.h" #include "keystore.h" #include "main.h" +#include "script.h" + +#ifdef ENABLE_STEALTH +#include "stealth.h" +#endif + #include "transaction.h" #include "ui_interface.h" #include "util.h" @@ -28,6 +34,11 @@ #include +#ifdef ENABLE_STEALTH +typedef std::map StealthKeyMetaMap; +#endif +typedef std::map mapValue_t; + //! Constant definitions found in the wallet source code file. //! -paytxfee default @@ -67,7 +78,6 @@ class CCoinControl; class CScript; class CWallet; -typedef std::map mapValue_t; //! Defined in wallet.cpp for use in the CAccountingEntry class serialization routines void ReadOrderPos(int64_t& nOrderPos, mapValue_t& mapValue); @@ -81,7 +91,8 @@ enum WalletFeature FEATURE_WALLETCRYPT = 40000, // wallet encryption FEATURE_COMPRPUBKEY = 60000, // compressed public keys - FEATURE_LATEST = 60000 + FEATURE_LATEST = 60000, +// FEATURE_STEALTH = 70000 // stealth addresses }; /** @@ -639,6 +650,13 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface std::set setLockedCoins; std::set setKeyPool; +#ifdef ENABLE_STEALTH + std::set stealthAddresses; + StealthKeyMetaMap mapStealthKeyMeta; + uint32_t nStealth, nFoundStealth; // for reporting, zero before use +#endif + + CPubKey vchDefaultKey; // Whoever keeps moving these dam txfee variables around, putting them in as static class members, such @@ -684,6 +702,24 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface nTimeFirstKey = 0; } + +#ifdef ENABLE_STEALTH + + bool Lock(); + bool CreateTransaction(CScript scriptPubKey, int64_t nValue, std::string& sNarr, CWalletTx& wtxNew, CReserveKey& reservekey, std::string& failReason, const CCoinControl* coinControl); + + bool NewStealthAddress(std::string& sError, std::string& sLabel, CStealthAddress& sxAddr); + bool AddStealthAddress(CStealthAddress& sxAddr); + bool UnlockStealthAddresses(const CKeyingMaterial& vMasterKeyIn); + bool UpdateStealthAddress(std::string &addr, std::string &label, bool addIfNotExist); + + bool CreateStealthTransaction(CScript scriptPubKey, int64_t nValue, std::vector& P, std::vector& narr, std::string& sNarr, CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, const CCoinControl* coinControl=NULL); + std::string SendStealthMoney(CScript scriptPubKey, int64_t nValue, std::vector& P, std::vector& narr, std::string& sNarr, CWalletTx& wtxNew, bool fAskFee=false); + bool SendStealthMoneyToDestination(CStealthAddress& sxAddress, int64_t nValue, std::string& sNarr, CWalletTx& wtxNew, std::string& sError, bool fAskFee=false); + bool FindStealthTransactions(const CTransaction& tx, mapValue_t& mapNarr); + +#endif + //! const CWalletTx* GetWalletTx(const uint256& hash) const; //! @@ -774,10 +810,12 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface CAmount GetImmatureWatchOnlyBalance() const; //! bool CreateTransaction(const std::vector >& vecSend, - CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl = NULL); + CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, + std::string& strFailReason, const CCoinControl *coinControl = NULL); //! bool CreateTransaction(CScript scriptPubKey, CAmount nValue, - CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl = NULL); + CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, + std::string& strFailReason, const CCoinControl *coinControl = NULL); //! bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey); //! From 117f5070d16e3d0ab00154efbaf252f2c9341109 Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Sat, 25 Nov 2017 18:53:37 +0100 Subject: [PATCH 20/27] Remove override --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index dd88d85a2..5dd61b7e4 100644 --- a/configure.ac +++ b/configure.ac @@ -193,7 +193,7 @@ fi ## compatibility with the legacy buildsystem. ## if test "x$CXXFLAGS_overridden" = "xno"; then - CXXFLAGS="$CXXFLAGS -DENABLE_STEALTH -Wall -Wextra -Wformat -Wformat-security -Wno-unused-parameter" + CXXFLAGS="$CXXFLAGS -Wall -Wextra -Wformat -Wformat-security -Wno-unused-parameter" fi CPPFLAGS="$CPPFLAGS -DBOOST_SPIRIT_THREADSAFE -DHAVE_BUILD_INFO -D__STDC_FORMAT_MACROS" From edde37f57182b80d4802ccf5bee0df19f4f650b6 Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Sat, 25 Nov 2017 18:54:13 +0100 Subject: [PATCH 21/27] Remove override 2 --- configure.ac | 2 -- 1 file changed, 2 deletions(-) diff --git a/configure.ac b/configure.ac index 5dd61b7e4..d74a75129 100644 --- a/configure.ac +++ b/configure.ac @@ -197,8 +197,6 @@ if test "x$CXXFLAGS_overridden" = "xno"; then fi CPPFLAGS="$CPPFLAGS -DBOOST_SPIRIT_THREADSAFE -DHAVE_BUILD_INFO -D__STDC_FORMAT_MACROS" -CXXFLAGS=" -I/usr/local/opt/openssl/include $CXXFLAGS" -CPPFLAGS=" -I/usr/local/opt/openssl/include $CPPFLAGS" AC_ARG_WITH([utils], [AS_HELP_STRING([--with-utils], From b277036bc75f83634bda77a14585b4b01a71c4bd Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Sat, 25 Nov 2017 18:56:50 +0100 Subject: [PATCH 22/27] automake cleanup --- src/Makefile.am | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 566dd0b50..76ac5de17 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -57,7 +57,7 @@ EXTRA_LIBRARIES = \ libanoncoin_scrypt.a if ENABLE_WALLET -ANONCOIN_INCLUDES += $(BDB_CPPFLAGS) -DENABLE_STEALTH +ANONCOIN_INCLUDES += $(BDB_CPPFLAGS) EXTRA_LIBRARIES += libanoncoin_wallet.a endif @@ -170,7 +170,7 @@ obj/build.h: FORCE libanoncoin_util_a-clientversion.$(OBJEXT): obj/build.h # server: shared between anoncoind and anoncoin-qt -libanoncoin_server_a_CPPFLAGS = $(ANONCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) -DENABLE_STEALTH +libanoncoin_server_a_CPPFLAGS = $(ANONCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) libanoncoin_server_a_SOURCES = \ alert.cpp \ bloom.cpp \ @@ -198,7 +198,7 @@ libanoncoin_server_a_SOURCES = \ # wallet: shared between anoncoind and anoncoin-qt, but only linked # when wallet enabled -libanoncoin_wallet_a_CPPFLAGS = $(ANONCOIN_INCLUDES) -DENABLE_STEALTH +libanoncoin_wallet_a_CPPFLAGS = $(ANONCOIN_INCLUDES) libanoncoin_wallet_a_SOURCES = \ db.cpp \ crypter.cpp \ @@ -238,7 +238,7 @@ univalue_libanoncoin_univalue_a_SOURCES = \ univalue/univalue.h # common: shared between anoncoind, and anoncoin-qt and non-server tools -libanoncoin_common_a_CPPFLAGS = $(ANONCOIN_INCLUDES) -DENABLE_STEALTH +libanoncoin_common_a_CPPFLAGS = $(ANONCOIN_INCLUDES) libanoncoin_common_a_SOURCES = \ addrman.cpp \ allocators.cpp \ @@ -266,7 +266,7 @@ libanoncoin_common_a_SOURCES = \ # util: shared between all executables. # This library *must* be included to make sure that the glibc # backward-compatibility objects and their sanity checks are linked. -libanoncoin_util_a_CPPFLAGS = $(ANONCOIN_INCLUDES) -DENABLE_STEALTH +libanoncoin_util_a_CPPFLAGS = $(ANONCOIN_INCLUDES) libanoncoin_util_a_SOURCES = \ compat/glibc_sanity.cpp \ compat/glibcxx_sanity.cpp \ @@ -300,7 +300,7 @@ libanoncoin_util_a_SOURCES += compat/glibcxx_compat.cpp endif # cli: shared between anoncoin-cli and anoncoin-qt -libanoncoin_cli_a_CPPFLAGS = $(ANONCOIN_INCLUDES) -DENABLE_STEALTH +libanoncoin_cli_a_CPPFLAGS = $(ANONCOIN_INCLUDES) libanoncoin_cli_a_SOURCES = \ rpcclient.cpp \ $(ANONCOIN_CORE_H) @@ -351,7 +351,7 @@ if TARGET_WINDOWS anoncoin_cli_SOURCES += anoncoin-cli-res.rc endif -anoncoin_cli_CPPFLAGS = $(ANONCOIN_INCLUDES) -DENABLE_STEALTH +anoncoin_cli_CPPFLAGS = $(ANONCOIN_INCLUDES) anoncoin_cli_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) #anoncoin-tx binary # From 55c75311dda5cc70968a6d83cc19e239d29573f4 Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Sat, 25 Nov 2017 18:58:33 +0100 Subject: [PATCH 23/27] Autotools cleanup --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index d74a75129..4e7d54d96 100644 --- a/configure.ac +++ b/configure.ac @@ -965,7 +965,7 @@ LIBS_TEMP="$LIBS" unset LIBS LIBS="$LIBS_TEMP" -PKGCONFIG_PATH_TEMP="$PKG_CONFIG_PATH:/usr/local/opt/openssl/pkgconfig" +PKGCONFIG_PATH_TEMP="$PKG_CONFIG_PATH" unset PKG_CONFIG_PATH PKG_CONFIG_PATH="$PKGCONFIG_PATH_TEMP" From 96a3d1bcf2d69b066cc95e28f204d9b7110ec372 Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Sat, 25 Nov 2017 23:50:34 +0100 Subject: [PATCH 24/27] RPC fixes --- src/rpcclient.cpp | 6 +++--- src/rpcstealth.cpp | 11 +++++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp index 38b0b473d..738ab55cc 100644 --- a/src/rpcclient.cpp +++ b/src/rpcclient.cpp @@ -96,13 +96,13 @@ static const CRPCConvertParam vRPCConvertParams[] = #ifdef ENABLE_STEALTH { "getnewstealthaddress", 0 }, - { "liststealthaddresses", 0 }, + { "liststealthaddresses", 2 }, { "importstealthaddress", 1 }, { "importstealthaddress", 2 }, { "sendtostealthaddress", 1 }, { "clearwallettransactions", 0 }, - { "scanforalltxns", 1 }, - { "scanforstealthtxns", 1 }, + { "scanforalltxns", 0 }, + { "scanforstealthtxns", 0 }, #endif // #if CLIENT_VERSION_IS_RELEASE != true { "sendalert", 2 }, diff --git a/src/rpcstealth.cpp b/src/rpcstealth.cpp index fcd055d58..3989146f8 100644 --- a/src/rpcstealth.cpp +++ b/src/rpcstealth.cpp @@ -69,8 +69,8 @@ Value liststealthaddresses(const Array& params, bool fHelp) { throw runtime_error("Failed: Wallet must be unlocked."); } - Object result; - + Array result; + std::set::iterator it; for (it = pwalletMain->stealthAddresses.begin(); it != pwalletMain->stealthAddresses.end(); ++it) { if (it->scan_secret.size() < 1) @@ -82,9 +82,12 @@ Value liststealthaddresses(const Array& params, bool fHelp) { objA.push_back(Pair("Address ", it->Encoded())); objA.push_back(Pair("Scan Secret ", HexStr(it->scan_secret.begin(), it->scan_secret.end()))); objA.push_back(Pair("Spend Secret ", HexStr(it->spend_secret.begin(), it->spend_secret.end()))); - result.push_back(Pair("Stealth Address", objA)); + result.push_back(objA); } else { - result.push_back(Pair("Stealth Address", it->Encoded() + " - " + it->label)); + Object objA; + objA.push_back(Pair("Label", it->label)); + objA.push_back(Pair("Stealth Address", it->Encoded())); + result.push_back(objA); } } From 79f25c19141a237efa84e27f90ce7021dfe48063 Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Sun, 26 Nov 2017 14:08:12 +0100 Subject: [PATCH 25/27] Don't allow stealth in production --- src/wallet.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/wallet.cpp b/src/wallet.cpp index f20d36067..97de8f42a 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -169,6 +169,13 @@ bool CWallet::NewStealthAddress(std::string& sError, std::string& sLabel, CSteal ec_secret scan_secret; ec_secret spend_secret; + // Temp code while not stable + bool fTestNet = GetBoolArg("-testnet", false); + if (!fTestNet) { + LogPrintf("CWallet::NewStealthAddress and other Stealth functions is not marked stable yet. Do not use this in production!\n"); + return false; + } + if (GenerateRandomSecret(scan_secret) != 0 || GenerateRandomSecret(spend_secret) != 0) { sError = "GenerateRandomSecret failed."; LogPrintf("Error CWallet::NewStealthAddress - %s\n", sError); From 11d823ef473dc28f9a2ea49631aad89c8f0562f4 Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Sun, 21 Oct 2018 22:15:47 +0200 Subject: [PATCH 26/27] Use script address prefix 15 (7) on mainnet and prefix 50 (M) on testnet --- src/chainparams.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 56cc5b382..acd60c2cd 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -170,7 +170,7 @@ class CMainParams : public CChainParams { // Because the prefix bytes are attached to a base256 encoding (ie, binary) of known width, they affect the leading cinquantoctal digit of the corresponding base58 // representation of the same number. The 23 below, is why mainnet pay-to-pubkey txouts always start with the letter A base58Prefixes[PUBKEY_ADDRESS] = {23}; // the pay-to-pubkey prefix byte - base58Prefixes[SCRIPT_ADDRESS] = {5}; // the pay-to-script-hash prefix byte + base58Prefixes[SCRIPT_ADDRESS] = {15}; // the pay-to-script-hash prefix byte base58Prefixes[SECRET_KEY] = {151}; // Anoncoin secret keys are the Public Key + 128 // What we have here are the same as Bitcoin values, and explained below as follows: // The four-byte prefixes correspond to cinquantoctal prefixes 'xpub' and 'xprv' on mainnet and 'tpub' and 'tprv' on testnet. @@ -182,7 +182,7 @@ class CMainParams : public CChainParams { #else base58Prefixes[PUBKEY_ADDRESS] = std::vector(1,23); - base58Prefixes[SCRIPT_ADDRESS] = std::vector(1,5); + base58Prefixes[SCRIPT_ADDRESS] = std::vector(1,15); base58Prefixes[SECRET_KEY] = std::vector(1,151); base58Prefixes[EXT_PUBLIC_KEY] = list_of(0x04)(0x88)(0xB2)(0x1E).convert_to_container >(); base58Prefixes[EXT_SECRET_KEY] = list_of(0x04)(0x88)(0xAD)(0xE4).convert_to_container >(); @@ -294,13 +294,13 @@ class CTestNetParams : public CMainParams { #ifdef NO_PREHISTORIC_COMPILER base58Prefixes[PUBKEY_ADDRESS] = {111}; // Anoncoin v8 compatible testnet Public keys use this value - base58Prefixes[SCRIPT_ADDRESS] = {196}; + base58Prefixes[SCRIPT_ADDRESS] = {50}; base58Prefixes[SECRET_KEY] = {211}; // Anoncoin testnet secret keys start with the same prefix as the Public Key + 128 base58Prefixes[EXT_PUBLIC_KEY] = {0x04,0x35,0x87,0xCF}; base58Prefixes[EXT_SECRET_KEY] = {0x04,0x35,0x83,0x94}; #else base58Prefixes[PUBKEY_ADDRESS] = std::vector(1,111); - base58Prefixes[SCRIPT_ADDRESS] = std::vector(1,196); + base58Prefixes[SCRIPT_ADDRESS] = std::vector(1,50); base58Prefixes[SECRET_KEY] = std::vector(1,239); base58Prefixes[EXT_PUBLIC_KEY] = boost::assign::list_of(0x04)(0x35)(0x87)(0xCF).convert_to_container >(); base58Prefixes[EXT_SECRET_KEY] = boost::assign::list_of(0x04)(0x35)(0x83)(0x94).convert_to_container >(); From 3986c43cc2fb0e81afe34f8cd9820d97fe7c1343 Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Sun, 21 Oct 2018 22:16:10 +0200 Subject: [PATCH 27/27] fix --- src/wallet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet.cpp b/src/wallet.cpp index 97de8f42a..02987862d 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -694,7 +694,7 @@ bool CWallet::FindStealthTransactions(const CTransaction& tx, mapValue_t& mapNar LogPrintf("StealthSecret failed.\n"); continue; } - LogPrintf("pkExtracted %"PRIszu": %s\n", pkExtracted.size(), HexStr(pkExtracted)); + LogPrintf("pkExtracted %" PRIszu ": %s\n", pkExtracted.size(), HexStr(pkExtracted)); CPubKey cpkE(pkExtracted);