Merge pull request #1604 from ethereum/checksums

Warn about invalid checksums of addresses.
This commit is contained in:
Alex Beregszaszi 2017-01-26 14:42:34 +00:00 committed by GitHub
commit 102fd7ee5d
15 changed files with 294 additions and 18 deletions

View File

@ -5,6 +5,7 @@ Features:
* Compiler interface: Report source location for "stack too deep" errors.
* AST: Use deterministic node identifiers.
* Type system: Introduce type identifier strings.
* Type checker: Warn about invalid checksum for addresses and deduce type from valid ones.
* Metadata: Do not include platform in the version number.
* Metadata: Add option to store sources as literal content.
* Code generator: Extract array utils into low-level functions.

View File

@ -171,6 +171,19 @@ Fixed Point Numbers
**COMING SOON...**
.. index:: address, literal;address
.. _address_literals:
Address Literals
----------------
Hexadecimal literals that pass the address checksum test, for example
``0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF`` are of ``address`` type.
Hexadecimal literals that are between 39 and 41 digits
long and do not pass the checksum test produce
a warning and are treated as regular rational number literals.
.. index:: literal, literal;rational
.. _rational_literals:

View File

@ -19,8 +19,12 @@
* @date 2014
*/
#include "CommonData.h"
#include "Exceptions.h"
#include <libdevcore/CommonData.h>
#include <libdevcore/Exceptions.h>
#include <libdevcore/SHA3.h>
#include <boost/algorithm/string.hpp>
using namespace std;
using namespace dev;
@ -95,3 +99,35 @@ bytes dev::fromHex(std::string const& _s, WhenError _throw)
}
return ret;
}
bool dev::passesAddressChecksum(string const& _str, bool _strict)
{
string s = _str.substr(0, 2) == "0x" ? _str.substr(2) : _str;
if (s.length() != 40)
return false;
if (!_strict && (
_str.find_first_of("abcdef") == string::npos ||
_str.find_first_of("ABCDEF") == string::npos
))
return true;
h256 hash = keccak256(boost::algorithm::to_lower_copy(s, std::locale::classic()));
for (size_t i = 0; i < 40; ++i)
{
char addressCharacter = s[i];
bool lowerCase;
if ('a' <= addressCharacter && addressCharacter <= 'f')
lowerCase = true;
else if ('A' <= addressCharacter && addressCharacter <= 'F')
lowerCase = false;
else
continue;
unsigned nibble = (unsigned(hash[i / 2]) >> (4 * (1 - (i % 2)))) & 0xf;
if ((nibble >= 8) == lowerCase)
return false;
}
return true;
}

View File

@ -179,4 +179,9 @@ bool contains(T const& _t, V const& _v)
return std::end(_t) != std::find(std::begin(_t), std::end(_t), _v);
}
/// @returns true iff @a _str passess the hex address checksum test.
/// @param _strict if false, hex strings with only uppercase or only lowercase letters
/// are considered valid.
bool passesAddressChecksum(std::string const& _str, bool _strict);
}

View File

@ -1565,6 +1565,16 @@ void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr)
void TypeChecker::endVisit(Literal const& _literal)
{
if (_literal.looksLikeAddress())
{
if (_literal.passesAddressChecksum())
{
_literal.annotation().type = make_shared<IntegerType>(0, IntegerType::Modifier::Address);
return;
}
else
warning(_literal.location(), "This looks like an address but has an invalid checksum.");
}
_literal.annotation().type = Type::forLiteral(_literal);
if (!_literal.annotation().type)
fatalTypeError(_literal.location(), "Invalid literal value.");

View File

@ -20,8 +20,6 @@
* Solidity abstract syntax tree.
*/
#include <algorithm>
#include <functional>
#include <libsolidity/interface/Utils.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/ASTVisitor.h>
@ -30,6 +28,11 @@
#include <libdevcore/SHA3.h>
#include <boost/algorithm/string.hpp>
#include <algorithm>
#include <functional>
using namespace std;
using namespace dev;
using namespace dev::solidity;
@ -522,3 +525,19 @@ IdentifierAnnotation& Identifier::annotation() const
m_annotation = new IdentifierAnnotation();
return static_cast<IdentifierAnnotation&>(*m_annotation);
}
bool Literal::looksLikeAddress() const
{
if (subDenomination() != SubDenomination::None)
return false;
string lit = value();
return lit.substr(0, 2) == "0x" && abs(int(lit.length()) - 42) <= 1;
}
bool Literal::passesAddressChecksum() const
{
string lit = value();
solAssert(lit.substr(0, 2) == "0x", "Expected hex prefix");
return dev::passesAddressChecksum(lit, true);
}

View File

@ -1584,6 +1584,11 @@ public:
SubDenomination subDenomination() const { return m_subDenomination; }
/// @returns true if this looks like a checksummed address.
bool looksLikeAddress() const;
/// @returns true if it passes the address checksum test.
bool passesAddressChecksum() const;
private:
Token::Value m_token;
ASTPointer<ASTString> m_value;

View File

@ -405,6 +405,14 @@ string IntegerType::toString(bool) const
return prefix + dev::toString(m_bits);
}
u256 IntegerType::literalValue(Literal const* _literal) const
{
solAssert(m_modifier == Modifier::Address, "");
solAssert(_literal, "");
solAssert(_literal->value().substr(0, 2) == "0x", "");
return u256(_literal->value());
}
TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const
{
if (

View File

@ -314,6 +314,8 @@ public:
virtual std::string toString(bool _short) const override;
virtual u256 literalValue(Literal const* _literal) const override;
virtual TypePointer encodingType() const override { return shared_from_this(); }
virtual TypePointer interfaceType(bool) const override { return shared_from_this(); }

View File

@ -1308,6 +1308,7 @@ void ExpressionCompiler::endVisit(Literal const& _literal)
{
case Type::Category::RationalNumber:
case Type::Category::Bool:
case Type::Category::Integer:
m_context << type->literalValue(&_literal);
break;
case Type::Category::StringLiteral:

View File

@ -30,20 +30,8 @@ set -e
REPO_ROOT="$(dirname "$0")"/..
# Compile all files in std and examples.
for f in "$REPO_ROOT"/std/*.sol
do
echo "Compiling $f..."
set +e
output=$("$REPO_ROOT"/build/solc/solc "$f" 2>&1)
failed=$?
# Remove the pre-release warning from the compiler output
output=$(echo "$output" | grep -v 'pre-release')
echo "$output"
set -e
test -z "$output" -a "$failed" -eq 0
done
echo "Running commandline tests..."
"$REPO_ROOT/test/cmdlineTests.sh"
# This conditional is only needed because we don't have a working Homebrew
# install for `eth` at the time of writing, so we unzip the ZIP file locally

View File

@ -436,6 +436,11 @@ bool CommandLineInterface::parseLibraryOption(string const& _input)
string addrString(lib.begin() + colon + 1, lib.end());
boost::trim(libName);
boost::trim(addrString);
if (!passesAddressChecksum(addrString, false))
{
cerr << "Invalid checksum on library address \"" << libName << "\": " << addrString << endl;
return false;
}
bytes binAddr = fromHex(addrString);
h160 address(binAddr, h160::AlignRight);
if (binAddr.size() > 20 || address == h160())

51
test/cmdlineTests.sh Executable file
View File

@ -0,0 +1,51 @@
#!/usr/bin/env bash
#------------------------------------------------------------------------------
# Bash script to run commandline Solidity tests.
#
# The documentation for solidity is hosted at:
#
# https://solidity.readthedocs.org
#
# ------------------------------------------------------------------------------
# This file is part of solidity.
#
# solidity is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# solidity is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with solidity. If not, see <http://www.gnu.org/licenses/>
#
# (c) 2016 solidity contributors.
#------------------------------------------------------------------------------
set -e
REPO_ROOT="$(dirname "$0")"/..
SOLC="$REPO_ROOT/build/solc/solc"
# Compile all files in std and examples.
for f in "$REPO_ROOT"/std/*.sol
do
echo "Compiling $f..."
set +e
output=$("$SOLC" "$f" 2>&1)
failed=$?
# Remove the pre-release warning from the compiler output
output=$(echo "$output" | grep -v 'pre-release')
echo "$output"
set -e
test -z "$output" -a "$failed" -eq 0
done
# Test library checksum
echo 'contact C {}' | "$SOLC" --link --libraries a:0x90f20564390eAe531E810af625A22f51385Cd222
! echo 'contract C {}' | "$SOLC" --link --libraries a:0x80f20564390eAe531E810af625A22f51385Cd222 2>/dev/null

View File

@ -0,0 +1,83 @@
/*
This file is part of cpp-ethereum.
cpp-ethereum is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
cpp-ethereum is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Unit tests for the address checksum.
*/
#include <libdevcore/CommonData.h>
#include "../TestHelper.h"
using namespace std;
namespace dev
{
namespace test
{
BOOST_AUTO_TEST_SUITE(Checksum)
BOOST_AUTO_TEST_CASE(regular)
{
BOOST_CHECK(passesAddressChecksum("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", true));
BOOST_CHECK(passesAddressChecksum("0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", true));
BOOST_CHECK(passesAddressChecksum("0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", true));
BOOST_CHECK(passesAddressChecksum("0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", true));
}
BOOST_AUTO_TEST_CASE(regular_negative)
{
BOOST_CHECK(!passesAddressChecksum("0x6aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", true));
BOOST_CHECK(!passesAddressChecksum("0xeB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", true));
BOOST_CHECK(!passesAddressChecksum("0xebF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", true));
BOOST_CHECK(!passesAddressChecksum("0xE1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", true));
}
BOOST_AUTO_TEST_CASE(regular_invalid_length)
{
BOOST_CHECK(passesAddressChecksum("0x9426cbfc57389778d313268E7F85F1CDc2fdad60", true));
BOOST_CHECK(!passesAddressChecksum("0x9426cbfc57389778d313268E7F85F1CDc2fdad6", true));
BOOST_CHECK(passesAddressChecksum("0x08A61851FFa4637dE289D630Ae8c5dFb0ff9171F", true));
BOOST_CHECK(!passesAddressChecksum("0x8A61851FFa4637dE289D630Ae8c5dFb0ff9171F", true));
BOOST_CHECK(passesAddressChecksum("0x00c40cC30cb4675673c9ee382de805c19734986A", true));
BOOST_CHECK(!passesAddressChecksum("0xc40cC30cb4675673c9ee382de805c19734986A", true));
BOOST_CHECK(passesAddressChecksum("0xC40CC30cb4675673C9ee382dE805c19734986a00", true));
BOOST_CHECK(!passesAddressChecksum("0xC40CC30cb4675673C9ee382dE805c19734986a", true));
}
BOOST_AUTO_TEST_CASE(homocaps_valid)
{
BOOST_CHECK(passesAddressChecksum("0x52908400098527886E0F7030069857D2E4169EE7", true));
BOOST_CHECK(passesAddressChecksum("0x8617E340B3D01FA5F11F306F4090FD50E238070D", true));
BOOST_CHECK(passesAddressChecksum("0xde709f2102306220921060314715629080e2fb77", true));
BOOST_CHECK(passesAddressChecksum("0x27b1fdb04752bbc536007a920d24acb045561c26", true));
}
BOOST_AUTO_TEST_CASE(homocaps_invalid)
{
string upper = "0x00AA0000000012400000000DDEEFF000000000BB";
BOOST_CHECK(passesAddressChecksum(upper, false));
BOOST_CHECK(!passesAddressChecksum(upper, true));
string lower = "0x11aa000000000000000d00cc00000000000000bb";
BOOST_CHECK(passesAddressChecksum(lower, false));
BOOST_CHECK(!passesAddressChecksum(lower, true));
}
BOOST_AUTO_TEST_SUITE_END()
}
}

View File

@ -4983,6 +4983,55 @@ BOOST_AUTO_TEST_CASE(constructible_internal_constructor)
success(text);
}
BOOST_AUTO_TEST_CASE(address_checksum_type_deduction)
{
char const* text = R"(
contract C {
function f() {
var x = 0xfA0bFc97E48458494Ccd857e1A85DC91F7F0046E;
x.send(2);
}
}
)";
success(text);
}
BOOST_AUTO_TEST_CASE(invalid_address_checksum)
{
char const* text = R"(
contract C {
function f() {
var x = 0xFA0bFc97E48458494Ccd857e1A85DC91F7F0046E;
}
}
)";
CHECK_WARNING(text, "checksum");
}
BOOST_AUTO_TEST_CASE(invalid_address_no_checksum)
{
char const* text = R"(
contract C {
function f() {
var x = 0xfa0bfc97e48458494ccd857e1a85dc91f7f0046e;
}
}
)";
CHECK_WARNING(text, "checksum");
}
BOOST_AUTO_TEST_CASE(invalid_address_length)
{
char const* text = R"(
contract C {
function f() {
var x = 0xA0bFc97E48458494Ccd857e1A85DC91F7F0046E;
}
}
)";
CHECK_WARNING(text, "checksum");
}
BOOST_AUTO_TEST_SUITE_END()
}