Skip to content

Commit 8cc3f08

Browse files
committed
btcdeb miniscript support
1 parent 0094c3c commit 8cc3f08

16 files changed

+554
-236
lines changed

Makefile.am

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ libkerl_a_SOURCES = kerl/kerl.c kerl/kerl.h
124124
btcdeb_SOURCES = \
125125
instance.h \
126126
instance.cpp \
127+
msenv.h \
128+
msenv.cpp \
127129
functions.h \
128130
functions.cpp \
129131
btcdeb.cpp \
@@ -173,6 +175,8 @@ btcc_LDADD = \
173175
test_btcdeb_SOURCES = \
174176
instance.h \
175177
instance.cpp \
178+
msenv.h \
179+
msenv.cpp \
176180
functions.h \
177181
functions.cpp \
178182
test/catch.hpp \

ansi-colors.h

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,72 @@ const std::string bg_magenta("\033[0;45m");
3232
const std::string bg_cyan ("\033[0;46m");
3333
const std::string bg_white ("\033[0;47m");
3434

35+
inline size_t length(const std::string& s) {
36+
size_t l = 0;
37+
for (size_t i = 0; s[i]; ++i) {
38+
char ch = s[i];
39+
if (ch == 033) {
40+
for (++i; s[i] != 'm'; ++i);
41+
continue;
42+
}
43+
++l;
44+
}
45+
return l;
46+
}
47+
48+
inline std::string substring(const std::string& s, size_t start, size_t length) {
49+
char buf[1024];
50+
size_t p = 0;
51+
size_t l = 0;
52+
size_t end = start + length;
53+
for (size_t i = 0; l < end && s[i]; ++i) {
54+
bool xfer = l >= start;
55+
if (s[i] == 033) {
56+
if (xfer) buf[p++] = s[i];
57+
for (++i; s[i] != 'm'; ++i) if (xfer) buf[p++] = s[i];
58+
if (xfer) buf[p++] = s[i];
59+
continue;
60+
}
61+
if (xfer) buf[p++] = s[i];
62+
++l;
63+
}
64+
buf[p] = 0;
65+
return std::string(buf) + reset;
66+
}
67+
68+
inline std::string trimmed_right(const std::string& s) {
69+
size_t len = s.length();
70+
if (len == 0) return s;
71+
size_t j;
72+
for (size_t i = len - 1; i > 1; --i) {
73+
switch (s[i]) {
74+
case ' ':
75+
case '\t':
76+
case '\n':
77+
continue;
78+
case 'm':
79+
// ansi sequence?
80+
j = i - 1;
81+
while (j > 0 && s[j] >= '0' && s[j] <= '9') --j;
82+
if (j > 0 && s[j] == ';') {
83+
--j;
84+
while (j > 0 && s[j] >= '0' && s[j] <= '9') --j;
85+
if (j > 0 && s[j] == ']') {
86+
if (s[j] == 033) {
87+
i = j;
88+
continue;
89+
}
90+
}
91+
}
92+
// nope, not an ansi sequence; we're done! fall-through
93+
default:
94+
return s.substr(0, i + 1);
95+
}
96+
}
97+
// the entire string was whitespace and/or ansi codes
98+
return "";
99+
}
100+
35101
} // namespace ansi
36102

37103
#endif // included_ansicolors_h_

approach.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ int main(int argc, char* const* argv)
5555
btc_logf("approach %d.%d.%d -- type `%s -h` for start up options\n", CLIENT_VERSION_MAJOR, CLIENT_VERSION_MINOR, CLIENT_VERSION_REVISION, argv[0]);
5656
}
5757

58-
COMPILER_CTX.SymbolicOutputs = true;
58+
COMPILER_CTX.symbolic_outputs = true;
5959

6060
char* script_str = nullptr;
6161
if (pipe_in) {

approach.functions.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ int fn_rewind(const char* arg) {
3434
}
3535

3636
int fn_script(const char* arg) {
37-
CScript script = env->m_root.m_node->ToScript(COMPILER_CTX);
37+
CScript& script = env->m_script;
3838
CScript::const_iterator it = script.begin();
3939
opcodetype opcode;
4040
std::vector<uint8_t> vchPushValue;

btcdeb.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,13 @@ int main(int argc, char* const* argv)
176176
script_str = strdup(ca.l[0]);
177177
ca.l.erase(ca.l.begin(), ca.l.begin() + 1);
178178
}
179+
180+
if (ca.m.count('P')) {
181+
if (!instance.parse_pretend_valid_expr(ca.m['P'].c_str())) {
182+
return 1;
183+
}
184+
}
185+
179186
CScript script;
180187
if (script_str) {
181188
if (instance.parse_script(script_str)) {
@@ -189,12 +196,6 @@ int main(int argc, char* const* argv)
189196

190197
instance.parse_stack_args(ca.l);
191198

192-
if (ca.m.count('P')) {
193-
if (!instance.parse_pretend_valid_expr(ca.m['P'].c_str())) {
194-
return 1;
195-
}
196-
}
197-
198199
if (instance.txin && instance.tx && ca.l.size() == 0 && instance.script.size() == 0) {
199200
if (!instance.configure_tx_txin()) return 1;
200201
}

functions.cpp

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11

2+
#include <sstream>
3+
24
#include <functions.h>
35
#include <value.h>
6+
#include <ansi-colors.h>
47

58
InterpreterEnv* env;
69
Instance instance;
@@ -105,6 +108,19 @@ void print_dualstack() {
105108
if (s.length() > rmax) rmax = s.length();
106109
r.push_back(s);
107110
}
111+
112+
if (r.size() > 0 && instance.msenv) r.push_back(""); // spacing between stack and miniscript
113+
size_t ms_start = r.size();
114+
115+
// miniscript representation
116+
if (instance.msenv) {
117+
std::istringstream ms(instance.msenv->TreeString(env->curr_op_seq));
118+
for (std::string l; std::getline(ms, l); ) {
119+
if (ansi::length(l) > rmax) rmax = ansi::length(l);
120+
r.push_back(l);
121+
}
122+
}
123+
108124
if (glmax < lmax) glmax = lmax;
109125
if (grmax < rmax) grmax = rmax;
110126
lmax = glmax; rmax = grmax;
@@ -133,8 +149,15 @@ void print_dualstack() {
133149
printf("| ");
134150
if (ri < r.size()) {
135151
auto s = r[ri++];
136-
if (s.length() > rcap) s = s.substr(0, rcap-3) + "...";
137-
printf(rfmt, s.c_str());
152+
if (ms_start > ri) {
153+
// printing stack items; right-align, no ansi
154+
if (s.length() > rcap) s = s.substr(0, rcap-3) + "...";
155+
printf(rfmt, s.c_str());
156+
} else {
157+
// printing miniscript tree; left-align, ansi enabled
158+
if (ansi::length(s) > rcap) s = ansi::substring(s, 0, rcap-3) + "...";
159+
printf("%s", s.c_str());
160+
}
138161
}
139162
printf("\n");
140163
}

instance.cpp

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,20 @@ bool Instance::parse_input_transaction(const char* txdata, int select_index) {
9797
bool Instance::parse_script(const char* script_str) {
9898
std::vector<unsigned char> scriptData = Value(script_str).data_value();
9999
script = CScript(scriptData.begin(), scriptData.end());
100-
for (const auto& keymap : COMPILER_CTX.KeyMap) {
100+
for (const auto& keymap : COMPILER_CTX.keymap) {
101101
auto cs = keymap.first.c_str();
102102
auto key = Value(std::vector<uint8_t>(keymap.second.begin(), keymap.second.end())).data;
103103
auto sig = Value((std::string("sig:") + keymap.first).c_str()).data_value();
104104
pretend_valid_map[sig] = key;
105105
pretend_valid_pubkeys.insert(key);
106106
printf("info: provide sig:%s as signature for %s [%s=%s]\n", cs, cs, HexStr(sig).c_str(), HexStr(key).c_str());
107107
}
108+
try {
109+
msenv = new MSEnv(script, true);
110+
} catch (const std::exception& ex) {
111+
printf("miniscript failed to parse script; miniscript support disabled\n");
112+
msenv = nullptr;
113+
}
108114
return script.HasValidOps();
109115
}
110116

@@ -116,31 +122,43 @@ bool Instance::parse_script(const std::vector<uint8_t>& script_data) {
116122
bool Instance::parse_pretend_valid_expr(const char* expr) {
117123
const char* p = expr;
118124
const char* c = p;
119-
valtype key;
120-
bool got_key = false;
125+
valtype sig;
126+
uint160 keyid;
127+
bool got_sig = false;
128+
COMPILER_CTX.symbolic_outputs = true;
121129
while (*c) {
122130
while (*c && *c != ',' && *c != ':') ++c;
123131
char* cs = strndup(p, c-p);
124-
valtype s = Value(cs).data_value();
132+
Value v = Value(cs);
133+
valtype s = v.data_value();
125134
free(cs);
126135
switch (*c) {
127136
case ':':
128-
if (got_key) {
137+
if (got_sig) {
129138
fprintf(stderr, "parse error (unexpected colon) near %s\n", p);
130139
return false;
131140
}
132-
key = s;
133-
got_key = true;
141+
sig = s;
142+
got_sig = true;
134143
break;
135144
case ',':
136145
case 0:
137-
if (!got_key) {
138-
fprintf(stderr, "parse error (missing key) near %s\n", p);
146+
if (!got_sig) {
147+
fprintf(stderr, "parse error (missing signature) near %s\n", p);
139148
return false;
140149
}
141-
got_key = false;
142-
pretend_valid_map[key] = s;
150+
got_sig = false;
151+
COMPILER_CTX.fake_sigs.insert(sig);
152+
v.do_hash160();
153+
keyid = uint160(v.data_value());
154+
// pretend_valid_map[sig] = s;
143155
pretend_valid_pubkeys.insert(s);
156+
CompilerContext::Key ctx_key;
157+
COMPILER_CTX.FromString(p, c, ctx_key);
158+
COMPILER_CTX.pkh_map[CKeyID(keyid)] = ctx_key;
159+
pretend_valid_pubkeys.insert(valtype(ctx_key.begin(), ctx_key.end()));
160+
// note: we override below; this may lead to issues
161+
pretend_valid_map[sig] = valtype(ctx_key.begin(), ctx_key.end());
144162
break;
145163
}
146164
p = c = c + (*c != 0);
@@ -150,7 +168,13 @@ bool Instance::parse_pretend_valid_expr(const char* expr) {
150168

151169
void Instance::parse_stack_args(const std::vector<const char*> args) {
152170
for (auto& v : args) {
153-
stack.push_back(Value(v).data_value());
171+
auto z = Value(v).data_value();
172+
stack.push_back(z);
173+
if (z.size() == 33) {
174+
// add if valid pubkey
175+
CompilerContext::Key key;
176+
COMPILER_CTX.FromPKBytes(z.begin(), z.end(), key);
177+
}
154178
}
155179
}
156180

@@ -186,7 +210,7 @@ bool Instance::step(size_t steps) {
186210
if (env->done) return false;
187211
try {
188212
if (!StepScript(*env)) return false;
189-
} catch (std::exception const& ex) {
213+
} catch (const std::exception& ex) {
190214
exception_string = ex.what();
191215
return false;
192216
}
@@ -333,7 +357,7 @@ bool Instance::configure_tx_txin() {
333357
if (scriptSig.size() > 0) {
334358
btc_segwit_logf("script sig non-empty; embedded P2SH (extracting payload)\n");
335359
// Embedded in P2SH -- payload extraction required
336-
auto it2 = scriptSig.begin();
360+
CScript::const_iterator it2 = scriptSig.begin();
337361
if (!scriptSig.GetOp(it2, opcode, pushval)) {
338362
fprintf(stderr, "can't parse sig script, or sig script ended prematurely\n");
339363
return false;
@@ -451,11 +475,29 @@ bool Instance::configure_tx_txin() {
451475
script = scriptSig;
452476
successor_script = scriptPubKey;
453477
}
478+
454479
parse_stack_args(push_del);
455480
while (!push_del.empty()) {
456481
delete push_del.back();
457482
push_del.pop_back();
458483
}
459484

485+
// extract pubkeys from script
486+
CScript::const_iterator it = script.begin();
487+
while (script.GetOp(it, opcode, pushval)) {
488+
if (pushval.size() == 33) {
489+
// add if valid pubkey
490+
CompilerContext::Key key;
491+
COMPILER_CTX.FromPKBytes(pushval.begin(), pushval.end(), key);
492+
}
493+
}
494+
495+
try {
496+
msenv = new MSEnv(successor_script, true);
497+
} catch (const std::exception& ex) {
498+
printf("miniscript failed to parse script; miniscript support disabled\n");
499+
msenv = nullptr;
500+
}
501+
460502
return true;
461503
}

instance.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@
99
#include <value.h>
1010
#include <vector>
1111
#include <map>
12+
#include <msenv.h>
1213

1314
typedef std::vector<unsigned char> valtype;
1415

1516
class Instance {
1617
public:
1718
InterpreterEnv* env;
19+
MSEnv* msenv;
1820
int count;
1921
ECCVerifyHandle evh;
2022
CTransactionRef tx;
@@ -41,6 +43,7 @@ class Instance {
4143

4244
Instance()
4345
: env(nullptr)
46+
, msenv(nullptr)
4447
, count(0)
4548
, txin_index(-1)
4649
, txin_vout_index(-1)

miniscript/compiler.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,7 @@ CostPair CalcCostPair(NodeType nt, const std::vector<const Result*>& s, double l
487487
switch (nt) {
488488
case NodeType::PK: return {73, 1};
489489
case NodeType::PK_H: return {107, 35};
490+
case NodeType::SIG: return {75, 75};
490491
case NodeType::OLDER:
491492
case NodeType::AFTER:
492493
return {0, INF};
@@ -544,6 +545,7 @@ std::pair<std::vector<double>, std::vector<double>> GetPQs(NodeType nt, double p
544545
case NodeType::HASH160:
545546
case NodeType::SHA256:
546547
case NodeType::RIPEMD160:
548+
case NodeType::SIG:
547549
return NONE;
548550
case NodeType::WRAP_A:
549551
case NodeType::WRAP_S:
@@ -611,6 +613,7 @@ const TypeFilters& GetTypeFilter(NodeType nt) {
611613
case NodeType::HASH160:
612614
case NodeType::SHA256:
613615
case NodeType::RIPEMD160:
616+
case NodeType::SIG:
614617
return FILTER_NO;
615618
case NodeType::WRAP_A: return FILTER_WRAP_A;
616619
case NodeType::WRAP_S: return FILTER_WRAP_S;

0 commit comments

Comments
 (0)