This commit is contained in:
nitowa
2023-08-15 22:28:03 +02:00
commit 1dae68b1c7
5529 changed files with 1659171 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
all: json-privates json-points json-ecdsa
privates: privates.cpp shared.hpp
g++ $< -L../native/secp256k1/.libs/ -lgmp -lsecp256k1 -o $@
points: points.cpp shared.hpp
g++ $< -L../native/secp256k1/.libs/ -lgmp -lsecp256k1 -o $@
ecdsa: ecdsa.cpp shared.hpp
g++ $< -L../native/secp256k1/.libs/ -lgmp -lcrypto -lssl -lsecp256k1 -o $@
clean:
rm privates points ecdsa
json-points: points
./points | jq . > ../tests/fixtures/points.json
json-privates: privates
./privates | jq . > ../tests/fixtures/privates.json
json-ecdsa: ecdsa
./ecdsa | jq . > ../tests/fixtures/ecdsa.json
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env sh
# from https://github.com/cryptocoinjs/secp256k1-node/blob/25a4b6cb567b49a40f47f50c5ca9a756f5343e4d/utils/has_lib.sh
check () {
regex="lib$1.+(so|dylib)"
# Add /sbin to path as ldconfig is located there on some systems - e.g. Debian
# (and it still can be used by unprivileged users):
PATH="$PATH:/sbin"
export PATH
# Try just checking common library locations
for dir in /lib /usr/lib /usr/local/lib /opt/local/lib /usr/lib/x86_64-linux-gnu /usr/lib/i386-linux-gnu; do
if test -d $dir; then
# shellcheck disable=SC2010
ls $dir | grep -E "$regex" && return 0
fi
done
return 1
}
check "$1" > /dev/null
if test "$?" -eq 0; then
echo true
else
echo false
fi
+134
View File
@@ -0,0 +1,134 @@
#include <iostream>
#include <tuple>
#include <vector>
#include "shared.hpp"
/////////// bitcoinjs-lib/ecdsa test fixtures
// https://github.com/bitcoinjs/bitcoinjs-lib/blob/6b3c41a06c6e38ec79dc2f3389fa2362559b4a46/test/fixtures/ecdsa.json
const auto BJS_KEYS = std::vector<std::string>({
"0000000000000000000000000000000000000000000000000000000000000001",
"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140",
"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140",
"0000000000000000000000000000000000000000000000000000000000000001",
"69ec59eaa1f4f2e36b639716b7c30ca86d9a5375c7b38d8918bd9c0ebc80ba64",
"00000000000000000000000000007246174ab1e92e9149c6e446fe194d072637",
"000000000000000000000000000000000000000000056916d0f9b31dc9b637f3",
});
const auto BJS_MESSAGES = std::vector<std::string>({
"Everything should be made as simple as possible, but not simpler.",
"Equations are more important to me, because politics is for the present, but an equation is something for eternity.",
"Not only is the Universe stranger than we think, it is stranger than we can think.",
"How wonderful that we have met with a paradox. Now we have some hope of making progress.",
"Computer science is no more about computers than astronomy is about telescopes.",
"...if you aren't, at any given time, scandalized by code you wrote five or even three years ago, you're not learning anywhere near enough",
"The question of whether computers can think is like the question of whether submarines can swim.",
});
struct S { uint8_t_32 d; uint8_t_32 m; uint8_t_64 e; std::string desc; };
auto generateSigns () {
bool ok = true;
std::vector<S> s;
size_t i = 0;
for (const auto& message : BJS_MESSAGES) {
const auto d = scalarFromHex(BJS_KEYS[i++]);
const auto hash = sha256(message);
const auto sig = _eccSign(d, hash, ok);
s.push_back({ d, hash, sig, message });
}
for (const auto& message : BJS_MESSAGES) {
const auto d = randomPrivate();
const auto hash = sha256(message);
const auto sig = _eccSign(d, hash, ok);
s.push_back({ d, hash, sig, message });
}
s.push_back({ ONE, ZERO, _eccSign(ONE, ZERO, ok), "Strange hash" });
s.push_back({ ONE, UINT256_MAX, _eccSign(ONE, UINT256_MAX, ok), "Strange hash" });
s.push_back({ GROUP_ORDER_LESS_1, ZERO, _eccSign(GROUP_ORDER_LESS_1, ZERO, ok), "Stange hash" });
s.push_back({ GROUP_ORDER_LESS_1, UINT256_MAX, _eccSign(GROUP_ORDER_LESS_1, UINT256_MAX, ok), "Strange hash" });
// fuzz
for (int i = 0; i < 2000; i++) {
const auto rkey = randomPrivate();
const auto hash = randomScalar();
auto sig = _eccSign(rkey, hash, ok);
const auto Q = _pointFromScalar<uint8_t_33>(rkey, ok);
assert(ok);
auto verified = ok;
assert(_eccVerify(Q, hash, sig) == verified);
s.push_back({ rkey, hash, sig, "" });
}
return s;
}
struct BS { uint8_t_32 d; uint8_t_32 m; std::string except; std::string desc = ""; };
auto generateBadSigns () {
std::vector<BS> bs;
for (auto x : BAD_PRIVATES) bs.push_back({ x.a, ONE, THROW_BAD_PRIVATE, x.desc });
return bs;
}
struct BV { uint8_t_vec Q; uint8_t_32 m; uint8_t_64 s; std::string except; std::string desc = ""; };
auto generateBadVerify () {
bool ok = true;
const auto G_ONE = _pointFromUInt32<uint8_t_33>(1, ok);
assert(ok);
const auto BAD_POINTS = generateBadPoints<uint8_t_65>();
const auto BAD_POINTS_C = generateBadPoints<uint8_t_33>();
std::vector<BV> bv;
for (auto x : BAD_POINTS) bv.push_back({ x.a, THREE, _signatureFromRS(ONE, ONE), THROW_BAD_POINT, x.desc });
for (auto x : BAD_POINTS_C) bv.push_back({ x.a, THREE, _signatureFromRS(ONE, ONE), THROW_BAD_POINT, x.desc });
for (auto x : BAD_SIGNATURES_VERIFY) bv.push_back({ G_ONE, THREE, x.a, "", x.desc }); // never verify, but dont throw
for (auto x : BAD_SIGNATURES) bv.push_back({ G_ONE, THREE, x.a, THROW_BAD_SIGNATURE, x.desc });
return bv;
}
template <typename T>
void dumpJSON (std::ostream& o, const T& t) {
o << jsonifyO({
jsonp("valid", jsonifyA(std::get<0>(t), [&](auto x) {
return jsonifyO({
x.desc.empty() ? "" : jsonp("description", jsonify(x.desc)),
jsonp("d", jsonify(x.d)),
jsonp("m", jsonify(x.m)),
jsonp("signature", jsonify(x.e))
});
})),
jsonp("invalid", jsonifyO({
jsonp("sign", jsonifyA(std::get<1>(t), [&](auto x) {
return jsonifyO({
x.desc.empty() ? "" : jsonp("description", jsonify(x.desc)),
jsonp("exception", jsonify(x.except)),
jsonp("d", jsonify(x.d)),
jsonp("m", jsonify(x.m))
});
})),
jsonp("verify", jsonifyA(std::get<2>(t), [&](auto x) {
return jsonifyO({
x.desc.empty() ? "" : jsonp("description", jsonify(x.desc)),
x.except.empty() ? "" : jsonp("exception", jsonify(x.except)),
jsonp("Q", jsonify(x.Q)),
jsonp("m", jsonify(x.m)),
jsonp("signature", jsonify(x.s))
});
}))
}))
});
}
int main () {
_ec_init();
const auto s = generateSigns();
const auto bs = generateBadSigns();
const auto bv = generateBadVerify();
dumpJSON(std::cout, std::make_tuple(s, bs, bv));
return 0;
}
+32
View File
@@ -0,0 +1,32 @@
// from https://github.com/dcousens/hexxer/blob/47191b839fc4bbdc60dc267d9f9673640a50c161/hexxer.hpp
#pragma once
namespace hexxer {
static const char HEX_ALPHABET[] = "0123456789abcdef";
static const int HEX_TABLE[] = {
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
0,1,2,3,4,5,6,7,8,9, // 0-9
255,255,255,255,255,255,255,
10,11,12,13,14,15, // a-f
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
10,11,12,13,14,15, // A-F
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255
};
inline auto encodeFirst (const unsigned char x) {
return HEX_ALPHABET[x >> 4];
}
inline auto encodeSecond (const unsigned char x) {
return HEX_ALPHABET[x & 0x0f];
}
inline auto decode (const char a, const char b) {
const auto ia = HEX_TABLE[static_cast<unsigned char>(a)];
const auto ib = HEX_TABLE[static_cast<unsigned char>(b)];
if (ia == 255) return 0x100;
if (ib == 255) return 0x100;
return (ia << 4) + ib;
}
}
+60
View File
@@ -0,0 +1,60 @@
#include <sstream>
#include <type_traits>
#include "hexxer.hpp"
template <typename R>
auto hexify (const R& range) {
std::stringstream ss;
for (auto& x : range) {
ss << hexxer::encodeFirst(x) << hexxer::encodeSecond(x);
}
return ss.str();
}
auto jsonify (const std::string& v) {
return "\"" + v + "\"";
}
template <typename R>
std::enable_if_t<std::is_same<typename R::value_type, uint8_t>::value, std::string>
jsonify (const R& r) {
return jsonify(hexify<R>(r));
}
auto jsonp (const std::string& k, const std::string& v) {
std::stringstream ss;
ss << "\"" << k << "\": " << v;
return ss.str();
}
auto jsonify (const bool v) {
return v ? "true" : "false";
}
template <char L = ' ', char R = ' ', typename Range, typename F>
auto jsonify_csv (const Range r, F f) {
std::stringstream ss;
size_t i = 0;
if (L != ' ') ss << L;
for (const auto& x : r) {
const auto fx = f(x);
if (fx.empty()) continue;
if (i++ > 0) ss << ',';
ss << fx;
}
if (R != ' ') ss << R;
return ss.str();
}
auto json_identity (const std::string& s) { return s; }
template <typename R = std::initializer_list<std::string>, typename F = decltype(json_identity)>
auto jsonifyO (const R& r, F f = json_identity) {
return jsonify_csv<'{', '}'>(r, f);
}
template <typename R = std::initializer_list<std::string>, typename F = decltype(json_identity)>
auto jsonifyA (const R& r, F f = json_identity) {
return jsonify_csv<'[', ']'>(r, f);
}
+326
View File
@@ -0,0 +1,326 @@
#include <iostream>
#include <tuple>
#include <vector>
#include "shared.hpp"
using V = uint8_t_vec;
// ref https://github.com/bitcoin-core/secp256k1/blob/6ad5cdb42a1a8257289a0423d644dcbdeab0f83c/src/tests.c#L2160
// iteratively verifies that (d + ...)G == (dG + ...G)
template <typename A, typename B, typename C, typename D>
void test_ec_combine (B& pa, C& pas, D& pfs) {
bool ok = true;
auto sum = ONE;
auto sumQ = _pointFromScalar<A>(sum, ok);
assert(ok);
for (int i = 1; i <= 10; i++) {
const auto d = randomPrivate();
const auto Q = _pointFromScalar<A>(d, ok);
assert(ok);
// dG + ...G
const auto V = _pointAdd<A>(sumQ, Q, ok);
assert(ok);
// (d + ...)G
const auto U = _pointAddScalar<A>(sumQ, d, ok);
assert(ok);
assert(V == U);
// (d + ...)G
sum = _privAdd(sum, d, ok);
assert(ok);
const auto R = _pointFromScalar<A>(sum, ok);
assert(ok);
assert(V == R);
pa.push_back({ sumQ, Q, V });
pas.push_back({ sumQ, d, V });
pfs.push_back({ sum, V });
sumQ = V;
}
}
struct IP { V a; bool e; std::string desc = ""; };
struct PA { V a; V b; V e; std::string except = ""; std::string desc = ""; };
struct PAS { V a; uint8_t_32 b; V e; std::string except = ""; std::string desc = ""; };
struct PC { V a; bool b; V e; std::string except = ""; std::string desc = ""; };
struct PFS { uint8_t_32 a; V e; std::string except = ""; std::string desc = ""; };
auto generate () {
using A = uint8_t_33;
bool ok = true;
const auto G_LESS_1 = _pointFromScalar<A>(GROUP_ORDER_LESS_1, ok);
const auto G_LESS_2 = _pointFromScalar<A>(GROUP_ORDER_LESS_2, ok);
const auto G_LESS_3 = _pointFromScalar<A>(GROUP_ORDER_LESS_3, ok);
const auto G_ONE = _pointFromUInt32<A>(1, ok);
const auto G_TWO = _pointFromUInt32<A>(2, ok);
const auto G_THREE = _pointFromUInt32<A>(3, ok);
const auto G_FOUR = _pointFromUInt32<A>(4, ok);
assert(ok);
const auto NULLQ = vectorify(Null<A>());
const auto BAD_POINTS_C = generateBadPoints<uint8_t_33>();
const auto BAD_POINTS = generateBadPoints<uint8_t_65>();
assert(jsonify(G_ONE) == jsonify(G)); // G == G*1 (duh)
///////////////////////////////// isPoint
std::vector<IP> ip = {
{ G, true },
{ G_ONE, true },
{ G_TWO, true },
{ G_THREE, true },
{ _pointFromX(P_LESS_1, 0x02), true, "X == P - 1" }
};
const auto _ip = ip; // prevent trashing ip while adding
for (auto& x : _ip) ip.push_back({ _pointFlip(x.a), x.e, x.desc });
for (const auto x : BAD_POINTS) ip.push_back({ x.a, false, x.desc });
for (const auto x : BAD_POINTS_C) ip.push_back({ x.a, false, x.desc });
// fuzz
for (size_t i = 0; i < 1000; ++i) {
ip.push_back({ _pointFromScalar<uint8_t_33>(randomPrivate(), ok), true }); assert(ok);
}
for (size_t i = 0; i < 1000; ++i) {
ip.push_back({ _pointFromScalar<uint8_t_65>(randomPrivate(), ok), true });
}
assert(ok);
///////////////////////////////// pointAdd
// XXX: only compressed point fixtures, flip for each combination when testing
std::vector<PA> pa = {
{ G_LESS_1, G_LESS_1, G_LESS_2 },
{ G_LESS_1, G_LESS_2, G_LESS_3 },
{ G_LESS_1, G_LESS_2, G_LESS_3 },
// https://github.com/bitcoin-core/secp256k1/blob/452d8e4d2a2f9f1b5be6b02e18f1ba102e5ca0b4/src/tests.c#L3857
{ G_ONE, G_LESS_1, NULLQ, "", "1 + -1 == 0/Infinity" },
{ G_ONE, G_LESS_2, G_LESS_1 }, // == -1
{ G_TWO, G_LESS_1, G_ONE }, // == 1
{ G_ONE, G_ONE, G_TWO, "", "1 + 1 == 2" },
{ G_ONE, G_TWO, G_THREE }
};
// fuzz
for (size_t i = 0; i < 100; ++i) {
const auto a = _pointFromScalar<A>(randomPrivate(), ok); assert(ok);
const auto b = _pointFromScalar<A>(randomPrivate(), ok); assert(ok);
const auto e = _pointAdd<A>(a, b, ok);
pa.push_back({ a, b, e });
}
///////////////////////////////// pointAddScalar
// XXX: only compressed point fixtures, flip for each combination when testing
std::vector<PAS> pas = {
{ G_LESS_1, ZERO, G_LESS_1, "", "-1 + 0 == -1" }, // #L3719
{ G_LESS_1, ONE, NULLQ, "", "-1 + 1 == 0" },
{ G_LESS_1, TWO, G_ONE },
{ G_LESS_1, THREE, G_TWO },
{ G_LESS_1, GROUP_ORDER_LESS_1, G_LESS_2 },
{ G_LESS_1, GROUP_ORDER_LESS_2, G_LESS_3 },
{ G_LESS_1, GROUP_ORDER_LESS_2, G_LESS_3 },
{ G_LESS_2, ONE, G_LESS_1 },
{ G_LESS_2, TWO, NULLQ, "", "-2 + 2 == 0" },
{ G_LESS_2, THREE, G_ONE },
{ G_ONE, GROUP_ORDER_LESS_1, NULLQ, "", "1 + -1 == 0" },
{ G_ONE, GROUP_ORDER_LESS_2, G_LESS_1, "", "1 + -2 == -1" },
{ G_TWO, GROUP_ORDER_LESS_1, G_ONE, "", "2 + -1 == 1" }
};
for (uint32_t i = 1; i < 5; ++i) {
bool ok = true;
const auto G_i = _pointFromUInt32<A>(i, ok); assert(ok);
const auto G_i_p1 = _pointFromUInt32<A>(i + 1, ok); assert(ok);
pas.push_back({ G_i, ONE, G_i_p1 });
}
///////////////////////////////// pointCompress
std::vector<PC> pc = {
{ G, true, G, "", "Generator" },
{ G, false, GU, "", "Generator (Uncompressed)" },
{ GU, true, G },
{ GU, false, GU },
};
for (auto i = 1; i < 10; ++i) {
const auto iic = vectorify(_pointFromUInt32<uint8_t_33>(i, ok)); assert(ok);
const auto ii = vectorify(_pointFromUInt32<uint8_t_65>(i, ok)); assert(ok);
pc.push_back({ iic, true, iic });
pc.push_back({ iic, false, ii });
pc.push_back({ ii, true, iic });
pc.push_back({ ii, false, ii });
}
// fuzz
for (size_t i = 0; i < 50; ++i) {
const auto ii = _pointFromScalar<uint8_t_65>(randomPrivate(), ok);
assert(ok);
uint8_t_32 iix;
std::copy(ii.begin() + 1, ii.begin() + 33, iix.begin());
const auto even = ii.at(64) % 2 == 0;
const auto iic = _pointFromX(iix, even ? 0x02 : 0x03);
pc.push_back({ iic, true, iic });
pc.push_back({ iic, false, ii });
pc.push_back({ ii, true, iic });
pc.push_back({ ii, false, ii });
}
///////////////////////////////// pointFromScalar
// XXX: only compressed point fixtures, flip for each combination when testing
std::vector<PFS> pfs = {
{ ONE, G_ONE, "", "== 1" }, // #L3153, #L3692
{ TWO, G_TWO, "", "== 2" },
{ THREE, G_THREE, "", "== 3" },
{ GROUP_ORDER_LESS_1, G_LESS_1, "", "== -1" }, // #L3171, #L3710
{ GROUP_ORDER_LESS_2, G_LESS_2, "", "== -2" },
{ GROUP_ORDER_LESS_3, G_LESS_3, "", "== -3" }
};
///////////////////////////////// pointMultiply
// XXX: only compressed point fixtures, flip for each combination when testing
std::vector<PAS> pm = {
{ G_ONE, ZERO, NULLQ, "", "1 * 0 == 0" },
{ G_ONE, ONE, G_ONE, "", "1 * 1 == 1" },
{ G_ONE, TWO, G_TWO, "", "1 * 2 == 2" },
{ G_ONE, FOUR, G_FOUR, "", "1 * 4 == 4" },
{ G_TWO, ONE, G_TWO, "", "2 * 1 == 2" },
{ G_TWO, TWO, G_FOUR, "", "2 * 2 == 4" },
{ G_FOUR, ONE, G_FOUR, "", "1 * 4 == 4" }
};
// ref https://github.com/bitcoin-core/secp256k1/blob/6ad5cdb42a1a8257289a0423d644dcbdeab0f83c/src/tests.c#L2160
test_ec_combine<A>(pa, pas, pfs);
return std::make_tuple(ip, pa, pas, pc, pfs, pm);
}
auto generateBad () {
using A = uint8_t_33;
bool ok = true;
const auto G_ONE = _pointFromUInt32<A>(1, ok);
const auto BAD_POINTS_C = generateBadPoints<uint8_t_33>();
const auto BAD_POINTS = generateBadPoints<uint8_t_65>();
assert(ok);
std::vector<PA> pa;
for (const auto x : BAD_POINTS) {
pa.push_back({ x.a, G_ONE, {}, THROW_BAD_POINT, x.desc });
pa.push_back({ G_ONE, x.a, {}, THROW_BAD_POINT, x.desc });
}
for (const auto x : BAD_POINTS_C) {
pa.push_back({ x.a, G_ONE, {}, THROW_BAD_POINT, x.desc });
pa.push_back({ G_ONE, x.a, {}, THROW_BAD_POINT, x.desc });
}
std::vector<PAS> pas;
for (const auto x : BAD_POINTS) pas.push_back({ x.a, ONE, {}, THROW_BAD_POINT, x.desc });
for (const auto x : BAD_POINTS_C) pas.push_back({ x.a, ONE, {}, THROW_BAD_POINT, x.desc });
for (const auto x : BAD_TWEAKS) pas.push_back({ G_ONE, x.a, {}, THROW_BAD_TWEAK, x.desc });
std::vector<PC> pc;
for (const auto x : BAD_POINTS) pc.push_back({ x.a, true, {}, THROW_BAD_POINT, x.desc });
for (const auto x : BAD_POINTS_C) pc.push_back({ x.a, true, {}, THROW_BAD_POINT, x.desc });
std::vector<PFS> pfs;
for (const auto x : BAD_PRIVATES) pfs.push_back({ x.a, {}, THROW_BAD_PRIVATE, x.desc });
std::vector<PAS> pm;
for (const auto x : BAD_POINTS) pm.push_back({ x.a, ONE, {}, THROW_BAD_POINT, x.desc });
for (const auto x : BAD_POINTS_C) pm.push_back({ x.a, ONE, {}, THROW_BAD_POINT, x.desc });
for (const auto x : BAD_TWEAKS) pm.push_back({ G_ONE, x.a, {}, THROW_BAD_TWEAK, x.desc });
return std::make_tuple(pa, pas, pc, pfs, pm);
}
template <typename A, typename B>
void dumpJSON (
std::ostream& o,
const A& good,
const B& bad
) {
const auto jIP = [](auto x) {
return jsonifyO({
x.desc.empty() ? "" : jsonp("description", jsonify(x.desc)),
jsonp("P", jsonify(x.a)),
jsonp("expected", jsonify(x.e))
});
};
const auto jPA = [](auto x) {
return jsonifyO({
x.desc.empty() ? "" : jsonp("description", jsonify(x.desc)),
jsonp("P", jsonify(x.a)),
jsonp("Q", jsonify(x.b)),
x.except.empty() ? jsonp("expected", isNull(x.e) ? "null" : jsonify(x.e)) : "",
x.except.empty() ? "" : jsonp("exception", jsonify(x.except)),
});
};
const auto jPAS = [](auto x) {
return jsonifyO({
x.desc.empty() ? "" : jsonp("description", jsonify(x.desc)),
jsonp("P", jsonify(x.a)),
jsonp("d", jsonify(x.b)),
x.except.empty() ? jsonp("expected", isNull(x.e) ? "null" : jsonify(x.e)) : "",
x.except.empty() ? "" : jsonp("exception", jsonify(x.except))
});
};
const auto jPC = [](auto x) {
return jsonifyO({
x.desc.empty() ? "" : jsonp("description", jsonify(x.desc)),
jsonp("P", jsonify(x.a)),
jsonp("compress", jsonify(x.b)),
x.except.empty() ? jsonp("expected", isNull(x.e) ? "null" : jsonify(x.e)) : "",
x.except.empty() ? "" : jsonp("exception", jsonify(x.except)),
});
};
const auto jPFS = [](auto x) {
return jsonifyO({
x.desc.empty() ? "" : jsonp("description", jsonify(x.desc)),
jsonp("d", jsonify(x.a)),
x.except.empty() ? jsonp("expected", isNull(x.e) ? "null" : jsonify(x.e)) : "",
x.except.empty() ? "" : jsonp("exception", jsonify(x.except)),
});
};
o << jsonifyO({
jsonp("valid", jsonifyO({
jsonp("isPoint", jsonifyA(std::get<0>(good), jIP)),
jsonp("pointAdd", jsonifyA(std::get<1>(good), jPA)),
jsonp("pointAddScalar", jsonifyA(std::get<2>(good), jPAS)),
jsonp("pointCompress", jsonifyA(std::get<3>(good), jPC)),
jsonp("pointFromScalar", jsonifyA(std::get<4>(good), jPFS)),
jsonp("pointMultiply", jsonifyA(std::get<5>(good), jPAS))
})),
jsonp("invalid", jsonifyO({
jsonp("pointAdd", jsonifyA(std::get<0>(bad), jPA)),
jsonp("pointAddScalar", jsonifyA(std::get<1>(bad), jPAS)),
jsonp("pointCompress", jsonifyA(std::get<2>(bad), jPC)),
jsonp("pointFromScalar", jsonifyA(std::get<3>(bad), jPFS)),
jsonp("pointMultiply", jsonifyA(std::get<4>(bad), jPAS))
}))
});
}
int main () {
_ec_init();
const auto a = generate();
const auto b = generateBad();
dumpJSON(std::cout, a, b);
return 0;
}
+138
View File
@@ -0,0 +1,138 @@
#include <iostream>
#include <vector>
#include "shared.hpp"
struct IP { uint8_t_32 a = {}; bool e = false; std::string desc = ""; };
struct PA { uint8_t_32 a; uint8_t_32 b; uint8_t_32 e = Null<uint8_t_32>(); std::string except = ""; std::string desc = ""; };
void generate (std::ostream& o) {
///////////////////////////////// isPrivate
// edge cases (verify)
// from https://github.com/bitcoin-core/secp256k1/blob/6ad5cdb42a1a8257289a0423d644dcbdeab0f83c/src/tests.c
std::vector<IP> ip = {
{ ZERO, false, "== 0" }, // #L3145
{ ONE, true, "== 1" }, // #L3153
{ GROUP_ORDER_LESS_1, true, "== G - 1" }, // #L3171
{ GROUP_ORDER, false, "== G" }, // #L3115
{ GROUP_ORDER_OVER_1, false, "> G" }, // #L3162
{ UINT256_MAX, false, "2^256 - 1" }, // #L3131
};
// fuzz
for (size_t i = 0; i < 1000; ++i) {
ip.push_back({ randomPrivate(), true });
}
for (size_t i = 0; i < 1000; ++i) {
const auto key = randomScalarHigh();
const auto verified = secp256k1_ec_seckey_verify(ctx, key.data());
ip.push_back({ key, verified });
}
///////////////////////////////// privateAdd
std::vector<PA> pa;
// visually inspected
// covers https://github.com/bitcoin-core/secp256k1/blob/6ad5cdb42a1a8257289a0423d644dcbdeab0f83c/src/tests.c
pa.push_back({ ONE, ZERO, ONE, "", "1 + 0 == 1" });
for (size_t i = 1; i < 5; ++i) pa.push_back({ ONE, scalarFromUInt32(i), scalarFromUInt32(1 + i) });
for (size_t i = 1; i < 5; ++i) pa.push_back({ scalarFromUInt32(i), TWO, scalarFromUInt32(i + 2) });
pa.push_back({ ONE, GROUP_ORDER_LESS_1, Null<uint8_t_32>(), "", "1 + -1 == 0" });
pa.push_back({ ONE, GROUP_ORDER_LESS_2, GROUP_ORDER_LESS_1, "", "1 + -2 == -1" });
pa.push_back({ ONE, GROUP_ORDER_LESS_3, GROUP_ORDER_LESS_2, "", "1 + -3 == -2" });
pa.push_back({ GROUP_ORDER_LESS_1, GROUP_ORDER_LESS_1, GROUP_ORDER_LESS_2 });
pa.push_back({ GROUP_ORDER_LESS_2, GROUP_ORDER_LESS_1, GROUP_ORDER_LESS_3 });
pa.push_back({ GROUP_ORDER_LESS_3, ONE, GROUP_ORDER_LESS_2 });
pa.push_back({ GROUP_ORDER_LESS_3, TWO, GROUP_ORDER_LESS_1, "", "-3 + 2 == -1" });
pa.push_back({ GROUP_ORDER_LESS_3, THREE, Null<uint8_t_32>(), "", "-3 + 3 == 0" });
// fuzz
for (size_t i = 0; i < 10000; ++i) {
const auto paPush = [&](const auto k, const auto t) {
bool ok = true;
const auto expected = _privAdd(k, t, ok);
if (ok) pa.push_back({ k, t, expected });
else pa.push_back({ k, t, Null<uint8_t_32>() });
};
paPush(randomPrivate(), randomPrivate());
paPush(randomPrivateHigh(), randomPrivateLow());
paPush(randomPrivateLow(), randomPrivateHigh());
}
std::vector<PA> paf;
for (const auto x : BAD_PRIVATES) paf.push_back({ x.a, ONE, {}, THROW_BAD_PRIVATE, x.desc });
for (const auto x : BAD_TWEAKS) paf.push_back({ ONE, x.a, {}, THROW_BAD_TWEAK, x.desc });
///////////////////////////////// privateSub
std::vector<PA> ps;
// visually inspected
// covers https://github.com/bitcoin-core/secp256k1/blob/6ad5cdb42a1a8257289a0423d644dcbdeab0f83c/src/tests.c
ps.push_back({ ONE, ZERO, ONE, "", "1 - 0 == 1" });
for (size_t i = 2; i < 7; ++i) ps.push_back({ scalarFromUInt32(i), ONE, scalarFromUInt32(i - 1) });
for (size_t i = 1; i < 10; ++i) ps.push_back({ scalarFromUInt32(10), scalarFromUInt32(i), scalarFromUInt32(10 - i) });
ps.push_back({ ONE, ONE, Null<uint8_t_32>(), "", "1 - 1 == 0" });
ps.push_back({ THREE, THREE, Null<uint8_t_32>(), "", "3 - 3 == 0" });
ps.push_back({ GROUP_ORDER_LESS_1, ONE, GROUP_ORDER_LESS_2 });
ps.push_back({ GROUP_ORDER_LESS_2, ONE, GROUP_ORDER_LESS_3 });
ps.push_back({ GROUP_ORDER_LESS_1, GROUP_ORDER_LESS_2, ONE });
ps.push_back({ GROUP_ORDER_LESS_2, GROUP_ORDER_LESS_3, ONE });
// fuzz
for (size_t i = 0; i < 1000; ++i) {
const auto psPush = [&](const auto k, const auto t) {
bool ok = true;
const auto expected = _privSub(k, t, ok);
if (ok) ps.push_back({ k, t, expected });
else ps.push_back({ k, t, Null<uint8_t_32>() });
};
psPush(randomPrivate(), randomPrivate());
psPush(randomPrivateHigh(), randomPrivateLow());
psPush(randomPrivateLow(), randomPrivateHigh());
}
std::vector<PA> psf;
for (const auto x : BAD_PRIVATES) psf.push_back({ x.a, ONE, {}, THROW_BAD_PRIVATE, x.desc });
for (const auto x : BAD_TWEAKS) psf.push_back({ ONE, x.a, {}, THROW_BAD_TWEAK, x.desc });
// dump JSON
const auto jPA = [](auto x) {
return jsonifyO({
x.desc.empty() ? "" : jsonp("description", jsonify(x.desc)),
jsonp("d", jsonify(x.a)),
jsonp("tweak", jsonify(x.b)),
x.except.empty() ? jsonp("expected", isNull(x.e) ? "null" : jsonify(x.e)) : "",
x.except.empty() ? "" : jsonp("exception", jsonify(x.except))
});
};
o << jsonifyO({
jsonp("valid", jsonifyO({
jsonp("isPrivate", jsonifyA(ip, [](auto x) {
return jsonifyO({
x.desc.empty() ? "" : jsonp("description", jsonify(x.desc)),
jsonp("d", jsonify(x.a)),
jsonp("expected", jsonify(x.e))
});
})),
jsonp("privateAdd", jsonifyA(pa, jPA)),
jsonp("privateSub", jsonifyA(ps, jPA))
})),
jsonp("invalid", jsonifyO({
jsonp("privateAdd", jsonifyA(paf, jPA)),
jsonp("privateSub", jsonifyA(psf, jPA))
}))
});
}
int main () {
_ec_init();
generate(std::cout);
return 0;
}
+359
View File
@@ -0,0 +1,359 @@
#pragma once
#include <array>
#include <cassert>
#include <iostream>
#include <openssl/sha.h>
#include <sstream>
#include <vector>
#include "../native/secp256k1/include/secp256k1.h"
#include "hexxer.hpp"
#include "json.hpp"
typedef std::array<uint8_t, 32> uint8_t_32;
typedef std::array<uint8_t, 33> uint8_t_33;
typedef std::array<uint8_t, 64> uint8_t_64;
typedef std::array<uint8_t, 65> uint8_t_65;
typedef std::vector<uint8_t> uint8_t_vec;
template <typename A>
auto vectorify (const A a) {
return uint8_t_vec(a.begin(), a.end());
}
namespace {
uint32_t s = 0xdeadbeef;
uint32_t xorshift32() {
s ^= s << 13;
s ^= s >> 17;
s ^= s << 5;
return s;
}
}
auto randomUInt8 () {
return xorshift32() % 255;
}
template <typename A>
auto random () {
A a;
for (auto& x : a) x = randomUInt8();
return a;
}
template <typename A>
auto randomHigh () {
auto x = random<A>();
for (auto i = x.size() / 2; i < x.size(); ++i) {
x.at(i) = 0xff;
}
return x;
}
template <typename A>
auto randomLow () {
auto x = random<A>();
for (auto i = 0ul; i < x.size() / 2; ++i) {
x.at(i) = 0;
}
return x;
}
template <typename A>
auto fromUInt32 (const uint32_t i) {
A x;
x.fill(0);
const auto s = x.size();
x.at(s - 4) = i >> 24;
x.at(s - 3) = i >> 16;
x.at(s - 2) = i >> 8;
x.at(s - 1) = i & 0xff;
return x;
}
auto randomScalar () { return random<uint8_t_32>(); }
auto randomScalarHigh () { return randomHigh<uint8_t_32>(); }
auto randomScalarLow () { return randomLow<uint8_t_32>(); }
auto scalarFromUInt32 (const uint32_t i) { return fromUInt32<uint8_t_32>(i); }
template <typename A>
auto fromHex (const std::string& s) {
assert(s.size() == sizeof(A) * 2);
A x;
auto i = 0;
for (auto& y : x) {
const auto a = s.at(i++);
const auto b = s.at(i++);
y = hexxer::decode(a, b);
}
return x;
}
auto pointFromHex (const std::string& s) {
if (s.size() == 66) return vectorify(fromHex<uint8_t_33>(s));
if (s.size() == 130) return vectorify(fromHex<uint8_t_65>(s));
assert(false);
}
auto scalarFromHex (const std::string& s) { return fromHex<uint8_t_32>(s); }
auto signatureFromHex (const std::string& s) { return fromHex<uint8_t_64>(s); }
secp256k1_context* ctx = nullptr;
auto randomPrivate () {
while (true) {
const auto key = randomScalar();
if (secp256k1_ec_seckey_verify(ctx, key.data())) return key;
}
}
auto randomPrivateHigh () {
while (true) {
const auto key = randomScalarHigh();
if (secp256k1_ec_seckey_verify(ctx, key.data())) return key;
}
}
auto randomPrivateLow () {
while (true) {
const auto key = randomScalarLow();
if (secp256k1_ec_seckey_verify(ctx, key.data())) return key;
}
}
// utility functions
void _ec_init () {
ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
}
auto _privAdd (uint8_t_32 key, const uint8_t_32 tweak, bool& ok) {
ok &= secp256k1_ec_privkey_tweak_add(ctx, key.data(), tweak.data());
return key;
}
auto _privSub (uint8_t_32 key, uint8_t_32 tweak, bool& ok) {
ok &= secp256k1_ec_privkey_negate(ctx, tweak.data());
ok &= secp256k1_ec_privkey_tweak_add(ctx, key.data(), tweak.data());
return key;
}
template <typename A>
uint8_t_vec _ec_pubkey_to_vec (const secp256k1_pubkey& public_key, bool& ok) {
static_assert(sizeof(A) == 33 || sizeof(A) == 65);
if (!ok) return {};
A out;
size_t outlen = out.size();
ok &= secp256k1_ec_pubkey_serialize(ctx, out.data(), &outlen, &public_key,
sizeof(A) == 33 ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED);
return vectorify<A>(out);
}
template <typename A, typename V>
auto _pointAdd (const V p, const V q, bool& ok) {
secp256k1_pubkey a, b;
ok &= secp256k1_ec_pubkey_parse(ctx, &a, p.data(), p.size());
ok &= secp256k1_ec_pubkey_parse(ctx, &b, q.data(), q.size());
const secp256k1_pubkey* points[] = { &a, &b };
secp256k1_pubkey public_key;
ok &= secp256k1_ec_pubkey_combine(ctx, &public_key, points, 2);
return _ec_pubkey_to_vec<A>(public_key, ok);
}
template <typename A, typename V>
auto _pointMul (const V p, const uint8_t_32 d, bool& ok) {
secp256k1_pubkey public_key;
ok &= secp256k1_ec_pubkey_parse(ctx, &public_key, p.data(), p.size());
ok &= secp256k1_ec_pubkey_tweak_mul(ctx, &public_key, d.data());
return _ec_pubkey_to_vec<A>(public_key, ok);
}
template <typename A, typename V>
auto _pointAddScalar (const V p, const uint8_t_32 d, bool& ok) {
secp256k1_pubkey public_key;
ok &= secp256k1_ec_pubkey_parse(ctx, &public_key, p.data(), p.size());
ok &= secp256k1_ec_pubkey_tweak_add(ctx, &public_key, d.data());
return _ec_pubkey_to_vec<A>(public_key, ok);
}
uint8_t_vec _pointFlip (const uint8_t_vec& p) {
assert(!p.empty());
secp256k1_pubkey public_key;
bool ok = secp256k1_ec_pubkey_parse(ctx, &public_key, p.data(), p.size());
assert(ok);
uint8_t_vec r;
if (p.size() == 33) r = _ec_pubkey_to_vec<uint8_t_65>(public_key, ok);
else r = _ec_pubkey_to_vec<uint8_t_33>(public_key, ok);
assert(ok);
return std::move(r);
}
template <typename A>
auto _pointFromScalar (const uint8_t_32 s, bool& ok) {
secp256k1_pubkey public_key;
ok &= secp256k1_ec_pubkey_create(ctx, &public_key, s.data());
return _ec_pubkey_to_vec<A>(public_key, ok);
}
template <typename A>
auto _pointFromUInt32 (const uint32_t i, bool& ok) {
return _pointFromScalar<A>(scalarFromUInt32(i), ok);
}
auto _pointFromX (const uint8_t_32 x, uint8_t prefix) {
uint8_t_vec p = { prefix };
p.reserve(33);
for (auto i : x) p.emplace_back(i);
return p;
}
auto _pointFromXY (const uint8_t_32 x, const uint8_t_32 y, const uint8_t prefix = 0x04) {
uint8_t_vec p = { prefix };
p.reserve(65);
for (auto i : x) p.emplace_back(i);
for (auto i : y) p.emplace_back(i);
return p;
}
auto _signatureFromRS (const uint8_t_32 r, const uint8_t_32 s) {
uint8_t_64 sig;
std::copy(r.begin(), r.end(), sig.begin());
std::copy(s.begin(), s.end(), sig.begin() + 32);
return sig;
}
auto _eccSign (const uint8_t_32 d, const uint8_t_32 message, bool& ok) {
uint8_t_64 output;
secp256k1_ecdsa_signature signature;
ok &= secp256k1_ecdsa_sign(ctx, &signature, message.data(), d.data(), nullptr, nullptr);
ok &= secp256k1_ecdsa_signature_serialize_compact(ctx, output.data(), &signature);
return output;
}
template <typename A>
auto _eccVerify (const A& p, const uint8_t_32 message, const uint8_t_64 signature) {
secp256k1_pubkey public_key;
bool ok = true;
ok &= secp256k1_ec_pubkey_parse(ctx, &public_key, p.data(), p.size());
if (!ok) return false;
secp256k1_ecdsa_signature _signature;
ok &= secp256k1_ecdsa_signature_parse_compact(ctx, &_signature, signature.data());
if (!ok) return false;
ok &= secp256k1_ecdsa_verify(ctx, &_signature, message.data(), &public_key);
return ok;
}
template <typename A>
auto sha256 (const A& m) {
uint8_t_32 h;
SHA256_CTX hctx;
SHA256_Init(&hctx);
SHA256_Update(&hctx, m.data(), m.size());
SHA256_Final(h.data(), &hctx);
return h;
}
// we use 0xfefefefefefefe.... as a null placeholder
template <typename A>
auto Null () {
A a;
a.fill(0xfe);
return a;
}
template <typename A>
auto isNull (const A& a) {
for (auto x : a) if (x != 0xfe) return false;
return true;
}
const auto ZERO = scalarFromHex("0000000000000000000000000000000000000000000000000000000000000000");
const auto ONE = scalarFromHex("0000000000000000000000000000000000000000000000000000000000000001");
const auto TWO = scalarFromHex("0000000000000000000000000000000000000000000000000000000000000002");
const auto THREE = scalarFromHex("0000000000000000000000000000000000000000000000000000000000000003");
const auto FOUR = scalarFromHex("0000000000000000000000000000000000000000000000000000000000000004");
const auto GROUP_ORDER = scalarFromHex("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141");
const auto GROUP_ORDER_LESS_3 = scalarFromHex("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413e");
const auto GROUP_ORDER_LESS_2 = scalarFromHex("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413f");
const auto GROUP_ORDER_LESS_1 = scalarFromHex("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140");
const auto GROUP_ORDER_OVER_1 = scalarFromHex("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364142");
const auto UINT256_MAX = scalarFromHex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
const auto G = pointFromHex("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798");
const auto GU = pointFromHex("0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8");
const auto P_LESS_1 = scalarFromHex("fffffffffffffffffffffffffffffffffffffffffffffffffffffffeeffffc2e");
const auto P_LESS_2 = scalarFromHex("fffffffffffffffffffffffffffffffffffffffffffffffffffffffeeffffc2d");
const auto P = scalarFromHex("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f");
const auto P_OVER_1 = scalarFromHex("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30");
template <typename A> struct B { A a; std::string desc = ""; };
const std::vector<B<uint8_t_32>> BAD_PRIVATES = {
{ ZERO, "Private key == 0" }, // #L3145, #L3684, fail, == 0
{ GROUP_ORDER, "Private key >= G" }, // #L3115, #L3670, fail, == G
{ GROUP_ORDER_OVER_1, "Private key >= G" }, // #L3162, #L3701, fail, >= G
{ UINT256_MAX, "Private key >= G" } // #L3131, #L3676, fail, > G
};
// excludes exact complement of a key, assumed to be tested elsewhere
const std::vector<B<uint8_t_32>> BAD_TWEAKS = {
{ GROUP_ORDER, "Tweak >= G" },
{ GROUP_ORDER_OVER_1, "Tweak >= G" },
{ UINT256_MAX, "Tweak >= G" }
};
const std::vector<B<uint8_t_64>> BAD_SIGNATURES_VERIFY = {
{ _signatureFromRS(ZERO, ZERO), "Invalid r, s values (== 0)" },
{ _signatureFromRS(ZERO, ONE), "Invalid r value (== 0)" },
{ _signatureFromRS(ONE, ZERO), "Invalid s value (== 0)" },
};
const std::vector<B<uint8_t_64>> BAD_SIGNATURES = {
{ _signatureFromRS(GROUP_ORDER, ONE), "Invalid r value (>= n)" },
{ _signatureFromRS(ONE, GROUP_ORDER), "Invalid s value (>= n)" }
};
// from https://github.com/cryptocoinjs/ecurve/blob/14d72f5f468d53ff33dc13c1c7af350a41d52aab/test/fixtures/point.json#L84
template <typename A = uint8_t_33>
std::vector<B<uint8_t_vec>> generateBadPoints () {
return {
{ _pointFromX(ONE, 0x01), "Bad sequence prefix" },
{ _pointFromX(ONE, 0x04), "Bad sequence prefix" },
{ _pointFromX(ONE, 0x05), "Bad sequence prefix" },
{ _pointFromX(ZERO, 0x02), "Bad X coordinate (== 0)" },
{ _pointFromX(ZERO, 0x03), "Bad X coordinate (== 0)" },
{ _pointFromX(P, 0x02), "Bad X coordinate (== P)" },
{ _pointFromX(P, 0x03), "Bad X coordinate (== P)" },
{ _pointFromX(P_LESS_2, 0x02), "Bad X coordinate (P - 2)" },
{ _pointFromX(P_LESS_2, 0x03), "Bad X coordinate (P - 2)" },
{ _pointFromX(P_OVER_1, 0x03), "Bad X coordinate (> P)" },
};
}
template <>
std::vector<B<uint8_t_vec>> generateBadPoints<uint8_t_65> () {
return {
{ _pointFromXY(ONE, ONE, 0x01), "Bad sequence prefix" },
{ _pointFromXY(ONE, ONE, 0x02), "Bad sequence prefix" },
{ _pointFromXY(ONE, ONE, 0x03), "Bad sequence prefix" },
{ _pointFromXY(ONE, ONE, 0x05), "Bad sequence prefix" },
{ _pointFromXY(ZERO, ONE), "Bad X coordinate (== 0)" },
{ _pointFromXY(ONE, ZERO), "Bad Y coordinate (== 0)" },
{ _pointFromXY(ZERO, ZERO, 0x04), "Bad X/Y coordinate (== 0)" },
{ _pointFromXY(P, ONE), "Bad X coordinate (== P)" },
{ _pointFromXY(ONE, P), "Bad Y coordinate (== P)" },
{ _pointFromXY(P_OVER_1, ONE), "Bad X coordinate (> P)" },
{ _pointFromXY(ONE, P_OVER_1), "Bad Y coordinate (> P)" },
};
}
const auto THROW_BAD_PRIVATE = "Expected Private";
const auto THROW_BAD_POINT = "Expected Point";
const auto THROW_BAD_TWEAK = "Expected Tweak";
const auto THROW_BAD_HASH = "Expected Hash";
const auto THROW_BAD_SIGNATURE = "Expected Signature";