Skip to content

Instantly share code, notes, and snippets.

@lukem512
Last active November 17, 2018 18:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lukem512/6382f9f2f8446f3e35cf to your computer and use it in GitHub Desktop.
Save lukem512/6382f9f2f8446f3e35cf to your computer and use it in GitHub Desktop.
Ban address in Bitcoin
// Check that the destination is not banned
bool IsDestinationBanned(CScript scriptPubKey)
{
txnouttype typeRet;
vector<CTxDestination> addressRet;
int nRequiredRet;
if (!ExtractDestinations(scriptPubKey, typeRet, addressRet, nRequiredRet))
// Could not extract destinations
return true;
BOOST_FOREACH(CTxDestination address, addressRet)
BOOST_FOREACH(std::string bannedAddress, bannedAddresses)
if (address == CBitcoinAddress(bannedAddress).Get())
// Destination address is banned
return true;
return false;
}
bool CTransaction::IsInputBanned(const CTxIn& input, CCoinsViewCache &mapInputs) const
{
// Determine script type
const CTxOut& prev = GetOutputFor(input, mapInputs);
const CScript& prevScript = prev.scriptPubKey;
vector<vector<unsigned char> > vSolutions;
txnouttype whichType;
if (!Solver(prevScript, whichType, vSolutions))
{
printf("IsInputBanned() : Solver returned false\n");
return true;
}
// Evaluate P2PKH script
// <sig> <pubkey>
if (whichType == TX_PUBKEYHASH)
{
std::vector<std::vector<unsigned char> > stack;
CTransaction txTo;
unsigned int nIn, flags, nHashType;
if (!EvalScript(stack, input.scriptSig, txTo, nIn, flags, nHashType))
{
printf("IsInputBanned() : EvalScript returned false\n");
return true;
}
// Expose pubkey
vector<unsigned char>& vchPubKey = stack.at(stack.size()+(-1));
// Take pubkey and find address
CPubKey pubkey(vchPubKey);
if (!pubkey.IsValid())
{
printf("IsInputBanned() : pubKey is not valid\n");
return true;
}
CBitcoinAddress address;
address.Set(pubkey.GetID());
// Check address against blacklist
BOOST_FOREACH(std::string bannedAddress, bannedAddresses)
{
if (address.Get() == CBitcoinAddress(bannedAddress).Get())
{
printf("IsInputBanned() : sender address %s is BANNED\n", address.ToString().c_str());
return true;
}
}
}
// Not banned!
return false;
}
bool CTransaction::CheckTransaction() const
{
// Basic checks that don't depend on any context
if (vin.empty())
return DoS(10, error("CTransaction::CheckTransaction() : vin empty"));
if (vout.empty())
return DoS(10, error("CTransaction::CheckTransaction() : vout empty"));
// Size limits
if (::GetSerializeSize(*this, SER_NETWORK, PROTOCOL_VERSION) > MAX_BLOCK_SIZE)
return DoS(100, error("CTransaction::CheckTransaction() : size limits failed"));
// Check for negative or overflow output values
int64 nValueOut = 0;
for (unsigned int i = 0; i < vout.size(); i++)
{
const CTxOut& txout = vout[i];
if (txout.IsEmpty() && !IsCoinBase() && !IsCoinStake())
return DoS(100, error("CTransaction::CheckTransaction() : txout empty for user transaction"));
// ppcoin: enforce minimum output amount
if ((!txout.IsEmpty()) && txout.nValue < MIN_TXOUT_AMOUNT)
return DoS(100, error("CTransaction::CheckTransaction() : txout.nValue below minimum"));
if (txout.nValue > MAX_MONEY)
return DoS(100, error("CTransaction::CheckTransaction() : txout.nValue too high"));
nValueOut += txout.nValue;
if (!MoneyRange(nValueOut))
return DoS(100, error("CTransaction::CheckTransaction() : txout total out of range"));
// Ensure destination isn't banned address
if (IsDestinationBanned(txout))
return state.DoS(100, error("CTransaction::CheckTransaction() : destination address is banned"));
}
// Check for duplicate inputs
set<COutPoint> vInOutPoints;
BOOST_FOREACH(const CTxIn& txin, vin)
{
if (vInOutPoints.count(txin.prevout))
return false;
vInOutPoints.insert(txin.prevout);
}
if (IsCoinBase())
{
if (vin[0].scriptSig.size() < 2 || vin[0].scriptSig.size() > 100)
return DoS(100, error("CTransaction::CheckTransaction() : coinbase script size"));
}
else
{
BOOST_FOREACH(const CTxIn& txin, vin)
{
if (txin.prevout.IsNull())
return DoS(10, error("CTransaction::CheckTransaction() : prevout is null"));
// Check for banned sender address
if (IsSenderBanned(txin))
return state.DoS(100, error("CTransaction::CheckTransaction() : banned address in input"))
}
}
return true;
}
bool CTransaction::CheckInputs(CValidationState &state, CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, std::vector<CScriptCheck> *pvChecks) const
{
if (!IsCoinBase())
{
if (pvChecks)
pvChecks->reserve(vin.size());
// This doesn't trigger the DoS code on purpose; if it did, it would make it easier
// for an attacker to attempt to split the network.
if (!HaveInputs(inputs))
return state.Invalid(error("CheckInputs() : %s inputs unavailable", GetHash().ToString().c_str()));
// While checking, GetBestBlock() refers to the parent block.
// This is also true for mempool checks.
int nSpendHeight = inputs.GetBestBlock()->nHeight + 1;
int64 nValueIn = 0;
int64 nFees = 0;
for (unsigned int i = 0; i < vin.size(); i++)
{
const COutPoint &prevout = vin[i].prevout;
const CCoins &coins = inputs.GetCoins(prevout.hash);
// If prev is coinbase, check that it's matured
if (coins.IsCoinBase()) {
if (nSpendHeight - coins.nHeight < COINBASE_MATURITY)
return state.Invalid(error("CheckInputs() : tried to spend coinbase at depth %d", nSpendHeight - coins.nHeight));
}
// Check for negative or overflow input values
nValueIn += coins.vout[prevout.n].nValue;
if (!MoneyRange(coins.vout[prevout.n].nValue) || !MoneyRange(nValueIn))
return state.DoS(100, error("CheckInputs() : txin values out of range"));
// Check for banned inputs
if (IsInputBanned(vin[i], inputs))
return state.DoS(100, error("CheckInputs() : input banned"));
}
if (nValueIn < GetValueOut())
return state.DoS(100, error("CheckInputs() : %s value in < value out", GetHash().ToString().c_str()));
// Tally transaction fees
int64 nTxFee = nValueIn - GetValueOut();
if (nTxFee < 0)
return state.DoS(100, error("CheckInputs() : %s nTxFee < 0", GetHash().ToString().c_str()));
nFees += nTxFee;
if (!MoneyRange(nFees))
return state.DoS(100, error("CheckInputs() : nFees out of range"));
// The first loop above does all the inexpensive checks.
// Only if ALL inputs pass do we perform expensive ECDSA signature checks.
// Helps prevent CPU exhaustion attacks.
// Skip ECDSA signature verification when connecting blocks
// before the last block chain checkpoint. This is safe because block merkle hashes are
// still computed and checked, and any change will be caught at the next checkpoint.
if (fScriptChecks) {
for (unsigned int i = 0; i < vin.size(); i++) {
const COutPoint &prevout = vin[i].prevout;
const CCoins &coins = inputs.GetCoins(prevout.hash);
// Verify signature
CScriptCheck check(coins, *this, i, flags, 0);
if (pvChecks) {
pvChecks->push_back(CScriptCheck());
check.swap(pvChecks->back());
} else if (!check()) {
if (flags & SCRIPT_VERIFY_STRICTENC) {
// For now, check whether the failure was caused by non-canonical
// encodings or not; if so, don't trigger DoS protection.
CScriptCheck check(coins, *this, i, flags & (~SCRIPT_VERIFY_STRICTENC), 0);
if (check())
return state.Invalid();
}
return state.DoS(100,false);
}
}
}
}
return true;
}
// Banned addresses
static const std::string bannedAddresses[] = {
"1HB5XMLmzFVj8ALj6mfBsbifRoD4miY36v"
};
// Declare beneath CTxOut declaration
bool IsDestinationBanned(CScript scriptPubKey);
bool IsDestinationBanned(CTxOut txout);
class CTransaction
{
public:
// Check whether an input is on the blacklist
bool IsInputBanned(const CTxIn& input, CCoinsViewCache &mapInputs) const;
};
bool CWallet::CreateTransaction(const vector<pair<CScript, int64> >& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet, const CCoinControl* coinControl)
{
int64 nValue = 0;
BOOST_FOREACH (const PAIRTYPE(CScript, int64)& s, vecSend)
{
if (nValue < 0)
return false;
nValue += s.second;
// Ensure destination isn't banned address
if (IsDestinationBanned(s.first))
{
strFailReason = _("Destination address is banned");
return false;
}
}
if (vecSend.empty() || nValue < 0)
return false;
wtxNew.BindWallet(this);
{
LOCK2(cs_main, cs_wallet);
// txdb must be opened before the mapWallet lock
CTxDB txdb("r");
{
nFeeRet = nTransactionFee;
loop
{
wtxNew.vin.clear();
wtxNew.vout.clear();
wtxNew.fFromMe = true;
int64 nTotalValue = nValue + nFeeRet;
double dPriority = 0;
// vouts to the payees
BOOST_FOREACH (const PAIRTYPE(CScript, int64)& s, vecSend)
wtxNew.vout.push_back(CTxOut(s.second, s.first));
// Choose coins to use
set<pair<const CWalletTx*,unsigned int> > setCoins;
int64 nValueIn = 0;
if (!SelectCoins(nTotalValue, wtxNew.nTime, setCoins, nValueIn, coinControl))
return false;
BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins)
{
int64 nCredit = pcoin.first->vout[pcoin.second].nValue;
dPriority += (double)nCredit * pcoin.first->GetDepthInMainChain();
}
int64 nChange = nValueIn - nValue - nFeeRet;
// if sub-cent change is required, the fee must be raised to at least MIN_TX_FEE
// or until nChange becomes zero
// NOTE: this depends on the exact behaviour of GetMinFee
if (nFeeRet < MIN_TX_FEE && nChange > 0 && nChange < CENT)
{
int64 nMoveToFee = min(nChange, MIN_TX_FEE - nFeeRet);
nChange -= nMoveToFee;
nFeeRet += nMoveToFee;
}
// ppcoin: sub-cent change is moved to fee
if (nChange > 0 && nChange < MIN_TXOUT_AMOUNT)
{
nFeeRet += nChange;
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-bitcoin-address
CScript scriptChange;
// coin control: send change to custom address
if (coinControl && !boost::get<CNoDestination>(&coinControl->destChange))
scriptChange.SetDestination(coinControl->destChange);
// no coin control: send change to newly generated address
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.
// If we reused the old key, it would be possible to add code to look for and
// rediscover unknown transactions that were written with keys of ours to recover
// post-backup change.
// Reserve a new key pair from key pool
CPubKey vchPubKey = reservekey.GetReservedKey();
scriptChange.SetDestination(vchPubKey.GetID());
}
// Insert change txn at random position:
vector<CTxOut>::iterator position = wtxNew.vout.begin()+GetRandInt(wtxNew.vout.size());
wtxNew.vout.insert(position, CTxOut(nChange, scriptChange));
}
else
reservekey.ReturnKey();
// Fill vin
BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins)
wtxNew.vin.push_back(CTxIn(coin.first->GetHash(),coin.second));
// Sign
int nIn = 0;
BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins)
if (!SignSignature(*this, *coin.first, wtxNew, nIn++))
return false;
// Limit size
unsigned int nBytes = ::GetSerializeSize(*(CTransaction*)&wtxNew, SER_NETWORK, PROTOCOL_VERSION);
if (nBytes >= MAX_BLOCK_SIZE_GEN/5)
return false;
dPriority /= nBytes;
// Check that enough fee is included
int64 nPayFee = nTransactionFee * (1 + (int64)nBytes / 1000);
int64 nMinFee = wtxNew.GetMinFee(1, false, GMF_SEND, nBytes);
if (nFeeRet < max(nPayFee, nMinFee))
{
nFeeRet = max(nPayFee, nMinFee);
continue;
}
// Fill vtxPrev by copying from previous transactions vtxPrev
wtxNew.AddSupportingTransactions(txdb);
wtxNew.fTimeReceivedIsTxTime = true;
break;
}
}
}
return true;
}
@lukem512
Copy link
Author

CWallet::CreateTransaction() and CTransaction::CheckTransaction() contain a check for a banned destination. This check is the function IsDestinationBanned() which can be passed a CTxOut or a CScript scriptPubKey.

CTransaction::CheckInputs() contains a check for a banned input. This check is the funtion CTransaction::IsInputBanned() which is passed a CTxOut. This only checks pay-to-pubkeyhash inputs as, currently, the vast majority of inputs are of this type. If other input types need to be blocked then these must be handled separately.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment