Browse Source

WIP

analysis
Pieter Wuille 5 years ago
parent
commit
cedb4e58b8
  1. 40
      compiler.cpp
  2. 118
      index.html
  3. 29
      js_bindings.cpp

40
compiler.cpp

@ -820,13 +820,13 @@ std::string Disassembler(CScript::const_iterator& it, CScript::const_iterator en
last_space = ret.size() - 1;
}
if (data.size() == 20) {
if (data == std::vector<unsigned char>(20, 0x88)) {
if (data == std::vector<unsigned char>(20, 0x99)) {
ret += "<h>";
} else if (data[0] == 'P' && data[1] == 'K' && data[2] == 'h') {
while (data.size() && data.back() == 0) data.pop_back();
ret += "<HASH160(" + std::string((const char*)data.data() + 3, data.size() - 3) + ")>";
}
} else if (data.size() == 32 && data == std::vector<unsigned char>(32, 0x99)) {
} else if (data.size() == 32 && data == std::vector<unsigned char>(32, 0x88)) {
ret += "<H>";
} else if (data.size() == 33 && data[0] == 2 && data[1] == 'P' && data[2] == 'K' && data[3] == 'b') {
while (data.size() && data.back() == 0) data.pop_back();
@ -936,28 +936,48 @@ bool Compile(const std::string& policy, miniscript::NodeRef<std::string>& ret, d
std::string Expand(std::string str) {
while (true) {
auto pos = str.find("(H)");
auto pos = str.find("sha256(H)");
if (pos == std::string::npos) break;
str.replace(pos, 3, "(8888888888888888888888888888888888888888888888888888888888888888)");
str.replace(pos, 9, "sha256(8888888888888888888888888888888888888888888888888888888888888888)");
}
while (true) {
auto pos = str.find("(h)");
auto pos = str.find("hash256(H)");
if (pos == std::string::npos) break;
str.replace(pos, 3, "(9999999999999999999999999999999999999999)");
str.replace(pos, 10, "hash256(8888888888888888888888888888888888888888888888888888888888888888)");
}
while (true) {
auto pos = str.find("ripemd160(H)");
if (pos == std::string::npos) break;
str.replace(pos, 12, "(9999999999999999999999999999999999999999)");
}
while (true) {
auto pos = str.find("hash160(H)");
if (pos == std::string::npos) break;
str.replace(pos, 10, "(9999999999999999999999999999999999999999)");
}
return str;
}
std::string Abbreviate(std::string str) {
while (true) {
auto pos = str.find("(8888888888888888888888888888888888888888888888888888888888888888)");
auto pos = str.find("sha256(8888888888888888888888888888888888888888888888888888888888888888)");
if (pos == std::string::npos) break;
str.replace(pos, 72, "sha256(H)");
}
while (true) {
auto pos = str.find("hash256(8888888888888888888888888888888888888888888888888888888888888888)");
if (pos == std::string::npos) break;
str.replace(pos, 73, "hash256(H)");
}
while (true) {
auto pos = str.find("ripemd160(9999999999999999999999999999999999999999)");
if (pos == std::string::npos) break;
str.replace(pos, 66, "(H)");
str.replace(pos, 51, "ripemd160(H)");
}
while (true) {
auto pos = str.find("(9999999999999999999999999999999999999999)");
auto pos = str.find("hash160(9999999999999999999999999999999999999999)");
if (pos == std::string::npos) break;
str.replace(pos, 42, "(h)");
str.replace(pos, 49, "hash160(H)");
}
return str;
}

118
index.html

@ -21,32 +21,50 @@
<body>
<script src="miniscript.js"></script>
<script>
em_miniscript_compile = Module.cwrap('miniscript_compile', 'none', ['string', 'number', 'number', 'number', 'number']);
em_miniscript_analyze = Module.cwrap('miniscript_analyze', 'none', ['string', 'number', 'number']);
function htmlEscape(str) {
return String(str)
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
em_miniscript_compile = Module.cwrap('miniscript_compile', 'none', ['string', 'number', 'number', 'number', 'number', 'number', 'number']);
em_miniscript_analyze = Module.cwrap('miniscript_analyze', 'none', ['string', 'number', 'number', 'number', 'number']);
function miniscript_compile() {
document.getElementById("descout").value = "Compiling...";
document.getElementById("spendout").innerHTML = "Compiling...";
document.getElementById("out").innerHTML = "Compiling...";
window.setTimeout(function() {
src = document.getElementById("source").value;
var descout = Module._malloc(1000);
var spendout = Module._malloc(50000);
em_miniscript_compile(src, descout, 1000, spendout, 50000);
document.getElementById("descout").value = Module.UTF8ToString(descout);
document.getElementById("spendout").innerHTML = Module.UTF8ToString(spendout);
Module._free(descout)
Module._free(spendout)
var msout = Module._malloc(10000);
var costout = Module._malloc(500);
var asmout = Module._malloc(100000);
em_miniscript_compile(src, msout, 10000, costout, 500, asmout, 100000);
document.getElementById("analyze_ms").value = Module.UTF8ToString(msout);
document.getElementById("analyze_out").innerHTML = "";
document.getElementById("out").innerHTML =
"<p></p><p><b>Miniscript output:</b></p><p><code>" + htmlEscape(Module.UTF8ToString(msout)) + "</code></p>" +
"<p><b>Spending cost analysis</b></p><p>" + Module.UTF8ToString(costout) + "</p>" +
"<p><b>Resulting script structure</b></p><p><samp><pre>" + htmlEscape(Module.UTF8ToString(asmout)) + "</pre></samp></p>";
Module._free(msout)
Module._free(costout)
Module._free(asmout)
});
}
function miniscript_analyze() {
document.getElementById("spendout").innerHTML = "Analyzing...";
document.getElementById("analyze_out").innerHTML = "Analyzing...";
window.setTimeout(function() {
src = document.getElementById("descout").value;
var spendout = Module._malloc(50000);
em_miniscript_analyze(src, spendout, 50000);
document.getElementById("spendout").innerHTML = Module.UTF8ToString(spendout);
Module._free(spendout)
src = document.getElementById("analyze_ms").value;
var analyze_out = Module._malloc(50000);
var asmout = Module._malloc(100000);
em_miniscript_analyze(src, analyze_out, 50000, asmout, 100000);
document.getElementById("analyze_out").innerHTML =
"<p></p><p><b>Analysis</b></p><p>" + Module.UTF8ToString(analyze_out) + "</p><p><small>Hover over the tree elements for more details</small></p>" +
"<p><b>Resulting script structure</b></p><p><samp><pre>" + htmlEscape(Module.UTF8ToString(asmout)) + "</pre></samp></p>";
Module._free(analyze_out)
Module._free(asmout)
});
}
@ -86,6 +104,34 @@ rely on Segwit-specific rules. Furthermore, the implemented policy compilers ass
</div>
</div>
<div class="card mb-3 text-left">
<h3 class="card-header">Policy to Miniscript compiler</h3>
<div class="card-block">
<p>Here you can see a demonstration of the Miniscript compiler. Write a spending policy following the instructions below, and observe how it affects the constructed Miniscript.</p>
<form>
<div class="form-group">
<label for="source">Policy</label>
<textarea class="form-control" id="source" rows="1">and(pk(A),or(pk(B),or(9@pk(C),older(1000))))</textarea>
<small class="form-text test-muted">Supported expressions:
<ul>
<li><samp>pk(<em>NAME</em>)</samp>: Require public key named <em>NAME</em> to sign. <em>NAME</em> can be any string up to 16 characters.</li>
<li><samp>after(<em>NUM</em>)</samp>, <samp>older(<em>NUM</em>)</samp>: Require that the <samp>nLockTime</samp>/<samp>nSequence</samp> value is at least <em>NUM</em>. <em>NUM</em> cannot be 0.</li>
<li><samp>sha256(<em>HEX</em>)</samp>, <samp>hash256(<em>HEX</em>)</samp>: Require that the preimage of 64-character <em>HEX</em> is revealed. The special value <samp>H</samp> can be used as <em>HEX</em>.</li>
<li><samp>ripemd160(<em>HEX</em>)</samp>, <samp>hash160(<em>HEX</em>)</samp>: Require that the preimage of 40-character <em>HEX</em> is revealed. The special value <samp>H</samp> can be used as <em>HEX</em>.</li>
<li><samp>and(<em>EXPR</em>,<em>EXPR</em>)</samp>: Require that both subexpressions are satisfied.</li>
<li><samp>or([<em>N</em>@]<em>EXPR</em>,[<em>N</em>@]<em>EXPR</em>)</samp>: Require that one of the subexpressions is satisfied. The numbers N indicate the relative probability of each of the subexpressions.</li>
<li><samp>thresh(<em>NUM</em>,<em>EXPR</em>,<em>EXPR</em>,...)</samp>: Require that NUM out of the following subexpressions are satisfied (all combinations are assumed to be equally likely).</li>
</ul></small>
</div>
<button type="button" class="btn btn-primary" onclick="miniscript_compile();">Compile</button>
</form>
<div id="out"></div>
</div>
</div>
<div class="card mb-3 text-left">
<h3 class="card-header">Miniscript reference</h3>
<div class="card-block">
@ -537,41 +583,23 @@ Some examples from this table explained:<ul>
<div class="card mb-3 text-left">
<h3 class="card-header">Compiler demo</h3>
<h3 class="card-header">Analyze a Miniscript</h3>
<div class="card-block">
<textarea rows="1" cols="100" id="source">
and(pk(C),or(pk(C),or(9@pk(C),older(1000))))
</textarea><br/>
<button type="button" onclick="miniscript_compile();">Compile</button>
Supported elements:
<ul>
<li><b>pk(HEX)</b>: Require public key HEX to sign.</li>
<li><b>time(NUMBER)</b>: Require that a relative time NUMBER has passed since creating the output.</li>
<li><b>hash(HEX)</b>: Require that the SHA256 preimage of HEX is revealed.</li>
<li><b>and(EXPR,EXPR)</b>: Require that both subexpressions are satisfied.</li>
<li><b>or([N@]EXPR,[N@]EXPR)</b>: Require that one of the subexpressions is satisfied. The numbers N indicate the relative probability of each of the subexpressions.</li>
<li><b>thresh(NUM,EXPR,EXPR,...)</b>: Require that NUM out of the following subexpressions are satisfied.</li>
</ul>
Shorthands:
<ul>
<li><b>C</b>: A dummy compressed public key (usable as argument to pk or multi)</li>
<li><b>U</b>: A dummy uncompressed public key (usable as argument to pk or multi)</li>
<li><b>H</b>: A dummy hash (usable as argument to hash)</li>
</ul>
<form>
<div class="form-group">
<label for="analyze_ms">Miniscript</label>
<textarea class="form-control" id="analyze_ms" rows="1">and_v(vc:pk(K),c:pk(A))</textarea>
<small class="form-text test-muted">Provide a well-typed miniscript expression of type "B".</small>
</div>
<button type="button" class="btn btn-primary" onclick="miniscript_analyze();">Analyze</button>
</form>
<hr/>
<b>Miniscript output</b><br/>
<textarea rows="4" cols="100" id="descout"></textarea>
<button type="button" onclick="miniscript_analyze();">Analyze</button>
<div id="analyze_out"></div>
<hr/>
<b>Analysis</b><br/>
<a id="spendout"></a>
</div>
</div>
</div>
</body>
</html>

29
js_bindings.cpp

@ -14,7 +14,7 @@ void Output(const std::string& str, char* out, int outlen) {
out[maxlen] = 0;
}
std::string Props(const miniscript::NodeRef<CompilerKey>& node, std::string in) {
std::string Props(const miniscript::NodeRef<std::string>& node, std::string in) {
std::string ret = "<span title=\"type: ";
if (node->GetType() == ""_mst) {
ret += "[invalid]";
@ -38,7 +38,7 @@ std::string Props(const miniscript::NodeRef<CompilerKey>& node, std::string in)
return std::move(ret) + "\">" + std::move(in) + "</span>";
}
std::string Analyze(const miniscript::NodeRef<CompilerKey>& node) {
std::string Analyze(const miniscript::NodeRef<std::string>& node) {
switch (node->nodetype) {
case miniscript::NodeType::PK: return Props(node, "pk(" + COMPILER_CTX.ToString(node->keys[0]) + ")");
case miniscript::NodeType::PK_H: return Props(node, "pk_h(" + COMPILER_CTX.ToString(node->keys[0]) + ")");
@ -79,39 +79,46 @@ std::string Analyze(const miniscript::NodeRef<CompilerKey>& node) {
extern "C" {
void miniscript_compile(const char* desc, char* descout, int descoutlen, char* costout, int costoutlen) {
void miniscript_compile(const char* desc, char* msout, int msoutlen, char* costout, int costoutlen, char* asmout, int asmoutlen) {
try {
std::string str(desc);
str.erase(str.find_last_not_of(" \n\r\t") + 1);
miniscript::NodeRef<CompilerKey> ret;
miniscript::NodeRef<std::string> ret;
double avgcost;
if (!Compile(Expand(str), ret, avgcost)) {
Output("[compile error]", descout, descoutlen);
Output("[compile error]", msout, msoutlen);
Output("[compile error]", costout, costoutlen);
Output("[compile error]", asmout, asmoutlen);
return;
}
Output(Abbreviate(ret->ToString(COMPILER_CTX)), descout, descoutlen);
std::string coststr = "Size: " + std::to_string(ret->ScriptSize()) + " bytes script + " + std::to_string(avgcost) + " bytes input = " + std::to_string(ret->ScriptSize() + avgcost) + " bytes<br/><ul><li>" + Analyze(ret) + "</li></lu>";
Output(Abbreviate(ret->ToString(COMPILER_CTX)), msout, msoutlen);
std::string coststr = "<ul><li>Script: " + std::to_string(ret->ScriptSize()) + " WU</li><li>Input:" + std::to_string(avgcost) + " WU</li><li>Total: " + std::to_string(ret->ScriptSize() + avgcost) + " WU</li></ul>";
Output(coststr, costout, costoutlen);
Output(Disassemble(ret->ToScript(COMPILER_CTX)), asmout, asmoutlen);
} catch (const std::exception& e) {
Output("[exception: " + std::string(e.what()) + "]", descout, descoutlen);
Output("[exception: " + std::string(e.what()) + "]", msout, msoutlen);
Output("", costout, costoutlen);
Output("", asmout, asmoutlen);
}
}
void miniscript_analyze(const char* ms, char* costout, int costoutlen) {
void miniscript_analyze(const char* ms, char* costout, int costoutlen, char* asmout, int asmoutlen) {
try {
std::string str(ms);
str.erase(str.find_last_not_of(" \n\r\t") + 1);
miniscript::NodeRef<CompilerKey> ret;
miniscript::NodeRef<std::string> ret;
ret = miniscript::FromString(Expand(str), COMPILER_CTX);
if (!ret) {
Output("[analysis error]", costout, costoutlen);
Output("[analysis error]", asmout, asmoutlen);
return;
}
std::string coststr = "Size: " + std::to_string(ret->ScriptSize()) + " bytes script<ul><li>" + Analyze(ret) + "</li>";
std::string coststr = "Size: " + std::to_string(ret->ScriptSize()) + " bytes script<ul><li>" + Analyze(ret) + "</li></ul>";
Output(coststr, costout, costoutlen);
Output(Disassemble(ret->ToScript(COMPILER_CTX)), asmout, asmoutlen);
} catch (const std::exception& e) {
Output("[exception: " + std::string(e.what()) + "]", costout, costoutlen);
Output("", asmout, asmoutlen);
}
}

Loading…
Cancel
Save