Skip to content

Commit bc70ffc

Browse files
committed
test(ltc): three-tier v35 wire-compat KATs ST-2/ST-3
ST-2: v36 vote rides desired_version=VarInt(36)==0x24 CompactSize byte, visible to T-A jtoomim nodes via legacy get_desired_version_counts. ST-3: zero v36-byte leak into the v35 HashLinkType codec (no extra_data VarStr slot). Hand-transcribed expected bytes from jtoomim/p2pool-merged-v36 wire, non-circular. Both green on build/ share_test (13/13 suite clean).
1 parent a89b4af commit bc70ffc

1 file changed

Lines changed: 124 additions & 1 deletion

File tree

src/impl/ltc/test/share_test.cpp

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,127 @@ TEST(LTC_share_test, Init)
2525
std::cout << rshare.type << std::endl;
2626

2727
auto share = ltc::load_share(rshare, NetService{"0.0.0.0", 0});
28-
}
28+
}
29+
// ---------------------------------------------------------------------------
30+
// Three-tier v35-phase wire-compat KATs (KAT-form of soak gates ST-2/ST-3,
31+
// "Standing CI reduction", v35-threetier-wirecompat-design.md §5.2).
32+
//
33+
// FENCED, NON-CIRCULAR: expected bytes are hand-transcribed from the jtoomim /
34+
// frstrtr/p2pool-merged-v36 oracle wire semantics (VarIntType == Bitcoin
35+
// CompactSize, VarStrType == CompactSize-prefixed bytes, FixedStrType(N) == N
36+
// raw bytes no prefix) -- NOT a second read of the c2pool SUT serializer. A
37+
// field-order or encoding drift in ltc/share_types.hpp that silently diverges
38+
// from what a T-A (jtoomim) node round-trips fails HERE.
39+
// ST-2: the v36 vote rides desired_version = VarInt(36) == single byte 0x24
40+
// (design §1.1); pin the CompactSize codec that carries it + the
41+
// share_type outer type tag (v35 mint class 35 -> "23", v36 -> "24").
42+
// ST-3: zero v36-byte leakage into the v35 codec -- the v35 HashLinkType has
43+
// NO extra_data slot (FixedStr(0), zero bytes), whereas the v36
44+
// V36HashLinkType inserts a VarStr(extra_data) the v35 wire never carries.
45+
// ---------------------------------------------------------------------------
46+
#include <span>
47+
#include <cstdint>
48+
#include <impl/ltc/share_types.hpp>
49+
50+
namespace {
51+
52+
// FixedStrType(32) operand: raw bytes 0x00..0x1f (oracle transcription).
53+
const std::string ST_STATE_HEX =
54+
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f";
55+
56+
std::string st_state_raw()
57+
{
58+
std::string s;
59+
for (int i = 0; i < 32; ++i) s.push_back(static_cast<char>(i));
60+
return s;
61+
}
62+
63+
std::string st_to_hex(std::span<std::byte> sp)
64+
{
65+
static const char* d = "0123456789abcdef";
66+
std::string s;
67+
s.reserve(sp.size() * 2);
68+
for (std::byte b : sp) {
69+
auto v = static_cast<unsigned>(b);
70+
s.push_back(d[(v >> 4) & 0xf]);
71+
s.push_back(d[v & 0xf]);
72+
}
73+
return s;
74+
}
75+
76+
} // namespace
77+
78+
// ST-3: v35 hash_link carries NO extra_data; v36 codec adds a VarStr slot the
79+
// v35 wire never has -> any v36-byte leak into a v35 share is structural, not policy.
80+
TEST(LTC_threetier_wirecompat, ST3_no_v36_hash_link_leak)
81+
{
82+
// v35 lineage (PaddingBugfixShare): state(32) || VarInt(len); FixedStr(0)
83+
// extra_data contributes ZERO bytes. length = 300 -> CompactSize "fd2c01".
84+
{
85+
ltc::HashLinkType hl;
86+
hl.m_state = FixedStrType<32>(st_state_raw());
87+
hl.m_length = 300;
88+
PackStream ss;
89+
ss << hl;
90+
EXPECT_EQ(st_to_hex(ss.get_span()), ST_STATE_HEX + "fd2c01");
91+
EXPECT_EQ(ss.get_span().size(), 35u); // 32 + 3, nothing between state and len
92+
}
93+
// v36 struct with EMPTY extra_data still emits the VarStr length prefix "00"
94+
// -> exactly one byte more than the v35 wire; the leak is detectable.
95+
{
96+
ltc::V36HashLinkType hl;
97+
hl.m_state = FixedStrType<32>(st_state_raw());
98+
hl.m_extra_data = BaseScript(std::vector<unsigned char>{});
99+
hl.m_length = 300;
100+
PackStream ss;
101+
ss << hl;
102+
EXPECT_EQ(st_to_hex(ss.get_span()), ST_STATE_HEX + "00" + "fd2c01");
103+
EXPECT_EQ(ss.get_span().size(), 36u);
104+
}
105+
// v36 struct with non-empty extra_data: state || VarStr(aabbcc) || VarInt(len).
106+
{
107+
ltc::V36HashLinkType hl;
108+
hl.m_state = FixedStrType<32>(st_state_raw());
109+
hl.m_extra_data = BaseScript(std::vector<unsigned char>{0xaa, 0xbb, 0xcc});
110+
hl.m_length = 300;
111+
PackStream ss;
112+
ss << hl;
113+
EXPECT_EQ(st_to_hex(ss.get_span()), ST_STATE_HEX + "03aabbcc" + "fd2c01");
114+
}
115+
}
116+
117+
// ST-2: the v36 vote value (36) is the single byte 0x24 under the CompactSize
118+
// codec that carries desired_version; the share_type outer type tag agrees.
119+
TEST(LTC_threetier_wirecompat, ST2_desired_version_vote_byte)
120+
{
121+
// VarInt == Bitcoin CompactSize: pin the vote value 36 -> "24" plus the
122+
// boundary vectors, via the same VarInt codec desired_version uses.
123+
struct { uint64_t v; const char* hex; } vec[] = {
124+
{35, "23"}, {36, "24"}, {252, "fc"},
125+
{253, "fdfd00"}, {300, "fd2c01"}, {65536, "fe00000100"},
126+
};
127+
for (auto& t : vec) {
128+
ltc::HashLinkType hl;
129+
hl.m_state = FixedStrType<32>(st_state_raw());
130+
hl.m_length = t.v;
131+
PackStream ss;
132+
ss << hl;
133+
EXPECT_EQ(st_to_hex(ss.get_span()), ST_STATE_HEX + t.hex)
134+
<< "VarInt(" << t.v << ") != oracle CompactSize";
135+
}
136+
// share_type outer wrapper: VarInt(type) || VarStr(contents).
137+
// v35 mint class 35 -> "23"; v36 class -> "24" (same single-byte CompactSize
138+
// as the vote value 36) -- the tier discriminator on the wire.
139+
{
140+
chain::RawShare rs(35, BaseScript(std::vector<unsigned char>{0xde, 0xad, 0xbe, 0xef}));
141+
PackStream ss;
142+
ss << rs;
143+
EXPECT_EQ(st_to_hex(ss.get_span()), std::string("23") + "04deadbeef");
144+
}
145+
{
146+
chain::RawShare rs(36, BaseScript(std::vector<unsigned char>{0xde, 0xad, 0xbe, 0xef}));
147+
PackStream ss;
148+
ss << rs;
149+
EXPECT_EQ(st_to_hex(ss.get_span()), std::string("24") + "04deadbeef");
150+
}
151+
}

0 commit comments

Comments
 (0)