diff --git a/.github/workflows/ghcr.io.yml b/.github/workflows/ghcr.io.yml index b12e7f2..6c78e3e 100644 --- a/.github/workflows/ghcr.io.yml +++ b/.github/workflows/ghcr.io.yml @@ -17,6 +17,7 @@ env: on: push: branches: + - master - main - v** tags: diff --git a/.gitignore b/.gitignore index 90836e3..5ebb78a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ miniscript +miniscript.json #make miniscript.js @@ -11,3 +12,6 @@ apple-touch-icon.png #macos **.DS_Store** + +bash_profile.log +buildx.dockerfile diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..24657ef --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +github.miniscript.app \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 7a6b5b9..b65bc3e 100755 --- a/Dockerfile +++ b/Dockerfile @@ -10,15 +10,9 @@ RUN apt-get install systemd bash jq -y RUN echo $(date +%s) > updated RUN chmod +x /usr/bin/systemctl RUN echo $(date +%s) > updated -FROM systemd as pandoc -RUN apt-get install pandoc -y -RUN echo $(date +%s) > updated -FROM pandoc as docker -RUN apt-get install docker.io -y -RUN echo $(date +%s) > updated -FROM docker as clone +FROM systemd as clone RUN rm -rf /src -RUN git clone --branch v0.0.2 --depth 1 https://github.com/bitcoincore-dev/miniscript-docker /src +RUN git clone --branch v0.0.10 --depth 1 https://github.com/bitcoincore-dev/miniscript-docker /src RUN echo $(date +%s) > updated FROM clone as make WORKDIR /src @@ -26,12 +20,12 @@ RUN make miniscript RUN echo $(date +%s) > updated RUN install miniscript /usr/local/bin RUN echo $(date +%s) > updated -RUN make miniscript.js -RUN echo $(date +%s) > updated +#RUN make miniscript.js ##TODO: better buildx multiplatform building +#RUN echo $(date +%s) > updated FROM make as install RUN install ./miniscript /usr/local/bin RUN echo $(date +%s) > updated -RUN install ./miniscript-* /usr/local/bin +RUN install ./miniscript-** /usr/local/bin RUN install ./serve /usr/local/bin RUN echo $(date +%s) > updated WORKDIR /src @@ -39,5 +33,5 @@ FROM install as miniscript COPY --from=clone /src /src ENV PATH=$PATH:/usr/bin/systemctl RUN ps -p 1 -o comm= -EXPOSE 80 6102 8080 8081 +EXPOSE 80 443 6102 8080 8081 VOLUME /src diff --git a/GNUmakefile b/GNUmakefile index 3fcab0f..7de33a4 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -1,3 +1,15 @@ +## detect ARCH for buildx +ARCH :=$(shell uname -m) +export ARCH +ifeq ($(ARCH),x86_64) +TARGET :=amd64 +export TARGET +endif +ifeq ($(ARCH),arm64) +TARGET :=arm64 +export TARGET +endif + DOCKER=$(shell which docker) export DOCKER PWD=$(shell echo `pwd`) @@ -12,21 +24,32 @@ install: @rm ./miniscript || true @rm /usr/local/bin/miniscript || true @$(MAKE) miniscript + @$(MAKE) miniscript.json @install $(PWD)/miniscript /usr/local/bin/ @install $(PWD)/miniscript-* /usr/local/bin/ + @install $(PWD)/miniscript.json /usr/local/bin/ + @install $(PWD)/miniscript.json-* /usr/local/bin/ docker:docker-miniscript## docker-build +dockerx:docker-buildx## docker-buildx docker-build:## docker build -f Dockerfile -t miniscript . @./miniscript-docker start @$(DOCKER) pull ghcr.io/bitcoincore-dev/miniscript-docker:latest @$(DOCKER) build -f Dockerfile -t miniscript . +docker-buildx:## docker buildx build sequence + @./miniscript-docker start + @$(DOCKER) run --privileged --rm tonistiigi/binfmt --install all + @$(DOCKER) buildx ls + @$(DOCKER) buildx create --use --name miniscript-buildx || true + @$(DOCKER) buildx build -t miniscript --platform linux/arm64,linux/amd64 . + @$(DOCKER) buildx build -t miniscript --platform linux/$(TARGET) . --load docker-miniscript:docker-build## docker-miniscript @[[ -z "$(shell file ./miniscript | grep inux)" ]] && echo "not linux" && rm ./miniscript || echo "miniscript is built for linux" @$(DOCKER) run --rm -v $(PWD):/src --publish 80:8080 miniscript sh -c "make install" -.PHONY:test-command -test-command: - @cat $< $@ && exit; +.PHONY:miniscript-tests +miniscript-tests:## miniscript-tests + @bash $< $@ 2>/dev/null || true example-commands: @printf "\n" diff --git a/Makefile b/Makefile index 498188d..ec77ff8 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,12 @@ HEADERS := bitcoin/util/vector.h bitcoin/util/strencodings.h bitcoin/span.h bitcoin/util/spanparsing.h bitcoin/script/script.h bitcoin/script/miniscript.h compiler.h bitcoin/crypto/common.h bitcoin/serialize.h bitcoin/prevector.h bitcoin/compat/endian.h bitcoin/compat/byteswap.h bitcoin/attributes.h bitcoin/tinyformat.h bitcoin/primitives/transaction.h SOURCES := bitcoin/util/strencodings.cpp bitcoin/util/spanparsing.cpp bitcoin/script/script.cpp bitcoin/script/miniscript.cpp compiler.cpp -miniscript: $(HEADERS) $(SOURCES) main.cpp +miniscript: $(HEADERS) $(SOURCES) main.cpp## miniscript g++ -O3 -g0 -Wall -std=c++17 -march=native -flto -Ibitcoin $(SOURCES) main.cpp -o miniscript +miniscript.json: $(HEADERS) $(SOURCES) miniscript.json.cpp## miniscript.json + g++ -O3 -g0 -Wall -std=c++17 -march=native -flto -Ibitcoin $(SOURCES) miniscript.json.cpp -o miniscript.json + miniscript.js: $(HEADERS) $(SOURCES) js_bindings.cpp em++ -O3 -g0 -Wall -std=c++17 -fno-rtti -flto -Ibitcoin $(SOURCES) js_bindings.cpp -s WASM=1 -s FILESYSTEM=0 -s ENVIRONMENT=web -s DISABLE_EXCEPTION_CATCHING=0 -s EXPORTED_FUNCTIONS='["_miniscript_compile","_miniscript_analyze","_malloc","_free"]' -s EXPORTED_RUNTIME_METHODS='["cwrap","UTF8ToString"]' -o miniscript.js @@ -13,4 +16,4 @@ wrapper.dot: wrapper.txt wrapper.pdf: wrapper.dot dot -Tpdf wrapper.pdf -.PHONY:miniscript miniscript.js +.PHONY:miniscript miniscript.js miniscript.json diff --git a/README.md b/README.md index ede2d80..71edede 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ ## miniscript-docker +![miniscript-docker-web](https://github.com/bitcoincore-dev/miniscript-docker/assets/152159/287c6835-b14c-4ae5-a22b-6da9812a8f8d) + + #### Usage: ``` @@ -27,16 +30,16 @@ docker pull ghcr.io/bitcoincore-dev/miniscript-docker:latest ``` -miniscript-docker run 'and(pk(A),or(pk(B),or(9@pk(C),older(1000))))' +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/bitcoincore-dev/miniscript-docker/master/miniscript-docker)" - run 'and(pk(A),or(pk(B),or(9@pk(C),older(1000))))' ``` +![miniscript-docker-run 1](https://github.com/bitcoincore-dev/miniscript-docker/assets/152159/7f1b02db-7063-41c2-859c-33639eb82d5a) ``` -miniscript-docker run 'and_v(or_c(pk(B),or_c(pk(C),v:older(1000))),pk(A))' +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/bitcoincore-dev/miniscript-docker/master/miniscript-docker)" - run 'and_v(or_c(pk(B),or_c(pk(C),v:older(1000))),pk(A))' ``` - -EXAMPLES +![miniscript-docker-run 2 png](https://github.com/bitcoincore-dev/miniscript-docker/assets/152159/1da94b41-ad45-4bda-8113-fa7b4f1326d5) ##### miniscript-docker examples @@ -46,6 +49,16 @@ EXAMPLES ![miniscript-docker-examples](https://github.com/bitcoincore-dev/miniscript-docker/assets/152159/a9b7e0d6-bcdf-4e86-abe7-0af4dd3b887e) + +##### miniscript-docker serve + +``` +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/bitcoincore-dev/miniscript-docker/master/miniscript-docker)" - serve +``` + +![miniscript-docker-web](https://github.com/bitcoincore-dev/miniscript-docker/assets/152159/287c6835-b14c-4ae5-a22b-6da9812a8f8d) + +
Go to [the Miniscript website](http://bitcoin.sipa.be/miniscript/). diff --git a/main.cpp b/main.cpp old mode 100755 new mode 100644 index 2c0208c..32dfc67 --- a/main.cpp +++ b/main.cpp @@ -17,49 +17,13 @@ static bool run(std::string&& line, int64_t count) { miniscript::NodeRef ret; double avgcost = 0; if (Compile(Expand(line), ret, avgcost)) { - auto str = ret->ToString(COMPILER_CTX); - assert(str); - - printf( - "\n{ \"X\":\"X\",\n\"2\":\"%17.10f\",\n\"3\":\"%5i\",\n\"4\":\"%s\"\n }\n", - ret->ScriptSize() + avgcost, (int)ret->ScriptSize(), - Abbreviate(std::move(*str)).c_str() - ); - + printf("X %17.10f %5i %s %s\n", ret->ScriptSize() + avgcost, (int)ret->ScriptSize(), Abbreviate(std::move(*str)).c_str(), line.c_str()); } else if ((ret = miniscript::FromString(Expand(line), COMPILER_CTX))) { - auto ms = ret->ToString(COMPILER_CTX); - assert(ms); - printf("{ \"count\": \"%7li\",\n",(long)count); - printf("\"scriptlen\" : \"%i\",\n",(int)ret->ScriptSize()); - printf("\"maxops\" : \"%i\",\n",(int)ret->GetOps()); - printf("\"type\" : \"%s\",\n",ret->GetType() << "B"_mst ? "B" : ret->GetType() << "V"_mst ? "V" : ret->GetType() << "W"_mst ? "W" : ret->GetType() << "K"_mst ? "K" : "(invalid)"); - - printf("\"safe\" : \"%s\",\n",ret->GetType() << "s"_mst ? "yes" : "no"); - printf("\"nonmal\" : \"%s\",\n",ret->GetType() << "m"_mst ? "yes" : "no"); - printf("\"dissat\" : \"%s\",\n",ret->GetType() << "f"_mst ? "no" : ret->GetType() << "e"_mst ? "unique" : ret->GetType() << "d"_mst ? "yes" : "unknown"); - printf("\"input\" : \"%s\",\n",ret->GetType() << "z"_mst ? "0" : ret->GetType() << "o"_mst ? (ret->GetType() << "n"_mst ? "1n" : "1") : ret->GetType() << "n"_mst ? "n" : "-"); - printf("\"output\" : \"%s\",\n",ret->GetType() << "u"_mst ? "1" : "nonzero"); - printf("\"timelock_mix\" : \"%s\",\n",ret->GetType() << "k"_mst ? "no": "yes"); - printf("\"miniscript\" : \"%s\" }\n",Abbreviate(*ms).c_str()); - - - assert(ms); -// printf("%7li\nscriptlen=%i\nmaxops=%i\ntype=%s\nsafe=%s\nnonmal=%s\ndissat=%s\ninput=%s\noutput=%s\ntimelock_mix=%s\nminiscript=%s\n", -// (long)count, -// (int)ret->ScriptSize(), -// (int)ret->GetOps(), -// ret->GetType() << "B"_mst ? "B" : ret->GetType() << "V"_mst ? "V" : ret->GetType() << "W"_mst ? "W" : ret->GetType() << "K"_mst ? "K" : "(invalid)", -// ret->GetType() << "s"_mst ? "yes" : "no", -// ret->GetType() << "m"_mst ? "yes" : "no", -// ret->GetType() << "f"_mst ? "no" : ret->GetType() << "e"_mst ? "unique" : ret->GetType() << "d"_mst ? "yes" : "unknown", -// ret->GetType() << "z"_mst ? "0" : ret->GetType() << "o"_mst ? (ret->GetType() << "n"_mst ? "1n" : "1") : ret->GetType() << "n"_mst ? "n" : "-", -// ret->GetType() << "u"_mst ? "1" : "nonzero", -// ret->GetType() << "k"_mst ? "no": "yes", -// Abbreviate(*ms).c_str()); + printf("%7li scriptlen=%i maxops=%i type=%s safe=%s nonmal=%s dissat=%s input=%s output=%s timelock_mix=%s miniscript=%s\n", (long)count, (int)ret->ScriptSize(), (int)ret->GetOps(), ret->GetType() << "B"_mst ? "B" : ret->GetType() << "V"_mst ? "V" : ret->GetType() << "W"_mst ? "W" : ret->GetType() << "K"_mst ? "K" : "(invalid)", ret->GetType() << "s"_mst ? "yes" : "no", ret->GetType() << "m"_mst ? "yes" : "no", ret->GetType() << "f"_mst ? "no" : ret->GetType() << "e"_mst ? "unique" : ret->GetType() << "d"_mst ? "yes" : "unknown", ret->GetType() << "z"_mst ? "0" : ret->GetType() << "o"_mst ? (ret->GetType() << "n"_mst ? "1n" : "1") : ret->GetType() << "n"_mst ? "n" : "-", ret->GetType() << "u"_mst ? "1" : "nonzero", ret->GetType() << "k"_mst ? "no": "yes", Abbreviate(*ms).c_str()); } else { printf("Failed to parse as policy or miniscript '%s'\n", line.c_str()); } diff --git a/miniscript-docker b/miniscript-docker index ec9f609..a1138ce 100755 --- a/miniscript-docker +++ b/miniscript-docker @@ -183,11 +183,10 @@ function shell (){ docker-start docker \ run \ - -d \ -t \ -i \ --rm \ - -p 127.0.0.1:$PORT:8080/tcp \ + -p 127.0.0.1:0:8080/tcp \ -v `pwd`:/src miniscript bash } diff --git a/miniscript-test-0 b/miniscript-test-0 new file mode 100755 index 0000000..4d066b8 --- /dev/null +++ b/miniscript-test-0 @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +# +echo "or_i(and_v(v:thresh(2,pkh(M1),a:pkh(M2),a:pkh(M3)),after(1005)),and_v(v:thresh(2,pk(P1),s:pk(P2),s:pk(P3),sun:after(1002),sun:after(1003)),and_v(or_c(pk(SA1),or_c(pk(SA3),v:pkh(SA2))),thresh(2,pk(A1),s:pk(A2),s:pk(A3),sln:after(1004)))))" | miniscript diff --git a/miniscript-tests b/miniscript-tests new file mode 100755 index 0000000..a7e3a15 --- /dev/null +++ b/miniscript-tests @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +[[ -z "$(command -v miniscript-tests)" ]] && \ + install $0 /usr/local/bin || true +## add your won tests +## miniscript-test-0 +## miniscript-test-1000 +## etc.. + +TESTS=$(echo miniscript-test-{0..1000}*) +for ms in $TESTS +do + . $ms 2>/dev/null +done diff --git a/miniscript.html b/miniscript.html index 6dacea3..d043661 100644 --- a/miniscript.html +++ b/miniscript.html @@ -123,569 +123,6 @@

  • Talk about (an early version of) Miniscript at SBC'19: [video] [transcript] [slides]
  • - - - -
    -

    Policy to Miniscript compiler

    -
    - -

    Here you can see a demonstration of the P2WSH Miniscript compiler. Write a spending policy according to the instructions below, and observe how it affects the constructed Miniscript. -

    -
    - - -
    Supported policies: -
      -
    • pk(NAME): Require public key named NAME to sign. NAME can be any string up to 16 characters.
    • -
    • after(NUM), older(NUM): Require that the nLockTime/nSequence value is at least NUM. NUM cannot be 0.
    • -
    • sha256(HEX), hash256(HEX): Require that the preimage of 64-character HEX is revealed. The special value H can be used as HEX.
    • -
    • ripemd160(HEX), hash160(HEX): Require that the preimage of 40-character HEX is revealed. The special value H can be used as HEX.
    • -
    • and(POL,POL): Require that both subpolicies are satisfied.
    • -
    • or([N@]POL,[N@]POL): Require that one of the subpolicies is satisfied. The numbers N indicate the relative probability of each of the subexpressions (so 9@ is 9 times more likely than the default).
    • -
    • thresh(NUM,POL,POL,...): Require that NUM out of the following subpolicies are met (all combinations are assumed to be equally likely).
    • -
    - Fill field with: - -
    - -
    - -
    - -
    -
    - -
    -

    Analyze a Miniscript

    -
    - -

    Here you can analyze the structure of a Miniscript expression and more. -

    -
    - - - Provide a well-typed miniscript expression of type "B". -
    - -
    - -
    - -
    -
    - -
    -

    Miniscript reference

    - -
    -

    P2WSH or Tapscript?

    -

    -The Miniscript specifications vary only slightly between P2WSH and Tapscript embedded scripts. Differences are pointed -inline. If no information specific to the context is given you can assume it is valid for both P2WSH and Tapscript. -

    - -

    Translation table

    -

    -This table shows all Miniscript fragments and their associated semantics and Bitcoin Script. -Fragments that do not change the semantics of their subexpressions are called wrappers. Normal fragments use -a "fragment(arguments,...)" notation, while wrappers are written using prefixes separated from other fragments -by a colon. The colon is dropped between subsequent wrappers; e.g. dv:older(144) is the d: -wrapper applied to the v: wrapper applied to the older fragment for 144 blocks. Note how -key expressions in this table and the followings are implicitly 32 bytes "x-only" public key in Tapscript -and 33 bytes compressed public keys in P2WSH. - -

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    SemanticsMiniscript fragmentBitcoin Script
    false00
    true11
    check(key)pk_k(key)<key>
    pk_h(key)DUP HASH160 <HASH160(key)> EQUALVERIFY
    pk(key) = c:pk_k(key)<key> CHECKSIG
    pkh(key) = c:pk_h(key)DUP HASH160 <HASH160(key)> EQUALVERIFY CHECKSIG
    nSequence ≥ n (and compatible)older(n)<n> CHECKSEQUENCEVERIFY
    nLockTime ≥ n (and compatible)after(n)<n> CHECKLOCKTIMEVERIFY
    len(x) = 32 and SHA256(x) = hsha256(h)SIZE <20> EQUALVERIFY SHA256 <h> EQUAL
    len(x) = 32 and HASH256(x) = hhash256(h)SIZE <20> EQUALVERIFY HASH256 <h> EQUAL
    len(x) = 32 and RIPEMD160(x) = hripemd160(h)SIZE <20> EQUALVERIFY RIPEMD160 <h> EQUAL
    len(x) = 32 and HASH160(x) = hhash160(h)SIZE <20> EQUALVERIFY HASH160 <h> EQUAL
    (X and Y) or Zandor(X,Y,Z)[X] NOTIF [Z] ELSE [Y] ENDIF
    X and Yand_v(X,Y)[X] [Y]
    and_b(X,Y)[X] [Y] BOOLAND
    and_n(X,Y) = andor(X,Y,0)[X] NOTIF 0 ELSE [Y] ENDIF
    X or Zor_b(X,Z)[X] [Z] BOOLOR
    or_c(X,Z)[X] NOTIF [Z] ENDIF
    or_d(X,Z)[X] IFDUP NOTIF [Z] ENDIF
    or_i(X,Z)IF [X] ELSE [Z] ENDIF
    X1 + ... + Xn = kthresh(k,X1,...,Xn)[X1] [X2] ADD ... [Xn] ADD ... <k> EQUAL
    check(key1) + ... + check(keyn) = k (P2WSH only)multi(k,key1,...,keyn)<k> <key1> ... <keyn> <n> CHECKMULTISIG
    check(key1) + ... + check(keyn) = k (Tapscript only)multi_a(k,key1,...,keyn) - <key1> CHECKSIG <key2> CHECKSIGADD ... <keyn> CHECKSIGADD <k> NUMEQUAL -
    X (identities)a:XTOALTSTACK [X] FROMALTSTACK
    s:XSWAP [X]
    c:X[X] CHECKSIG
    t:X = and_v(X,1)[X] 1
    d:XDUP IF [X] ENDIF
    v:X[X] VERIFY (or VERIFY version of last opcode in [X])
    j:XSIZE 0NOTEQUAL IF [X] ENDIF
    n:X[X] 0NOTEQUAL
    l:X = or_i(0,X)IF 0 ELSE [X] ENDIF
    u:X = or_i(X,0)IF [X] ELSE 0 ENDIF
    -
    -The pk, pkh, and and_n fragments and t:, l:, and u: wrappers are syntactic sugar for other Miniscripts, as listed in the table above. -In what follows, they will not be included anymore, as their properties can be derived by looking at their expansion. -

    -

    -Hash preimages are constrained to 32 bytes to disallow various forms of griefing, including making non-standard(un-relayable) transactions, -consensus-invalid swaps across blockchains, as well as ensure that satisfaction cost can be accurately calculated. Finally, note that <20> -are in hex representation in this document. -

    - -

    Correctness properties

    - -

    -Not every Miniscript expression can be composed with every other. Some return their result by putting true or false on the stack; others can only abort or continue. -Some require subexpressions that consume an exactly known number of arguments, while others need a subexpression that has a nonzero top stack element to satisfy. To model all -these properties, we define a correctness type system for Miniscript. - - -

    -Every miniscript expression has one of four basic types:

      -
    • "B" Base expressions. These take their inputs from the top of the stack. When satisfied, they push a nonzero value of up to 4 bytes onto the stack. -When dissatisfied, they push an exact 0 onto the stack (if dissatisfaction without aborting is possible at all). -This type is used for most expressions, and required for the top level expression. An example is older(n) = <n> CHECKSEQUENCEVERIFY.
    • -
    • "V" Verify expressions. Like "B", these take their inputs from the top of the stack. Upon satisfaction however, they continue without pushing anything. They cannot be -dissatisfied (will abort instead). A "V" can be obtained using the v: wrapper on a "B" expression, or by combining other "V" expressions using and_v, or_i, or_c, or andor. -An example is v:pk(key) = <key> CHECKSIGVERIFY.
    • -
    • "K" Key expressions. They again take their inputs from the top of the stack, but instead of verifying a condition directly they always push a public key onto the stack, for -which a signature is still required to satisfy the expression. A "K" can be converted into a "B" using the c: wrapper (CHECKSIG). -An example is pk_h(key) = DUP HASH160 <Hash160(key)> EQUALVERIFY
    • -
    • "W" Wrapped expressions. They take their inputs from one below the top of the stack, and push a nonzero (in case of satisfaction) or zero (in case of dissatisfaction) -either on top of the stack, or one below. So for example a 3-input "W" would take the stack "A B C D E F" and turn it into "A B F 0" or "A B 0 F" in case of dissatisfaction, and -"A B F n" or "A B n F" in case of satisfaction (with n a nonzero value). Every "W" is either s:B (SWAP B) or a:B (TOALTSTACK B FROMALTSTACK). -An example is s:pk(key) = SWAP <key> CHECKSIG.
    • -
    - -

    -Then there are 5 type modifiers, which guarantee additional properties:

      -
    • "z" Zero-arg: this expression always consumes exactly 0 stack elements.
    • -
    • "o" One-arg: this expression always consumes exactly 1 stack element.
    • -
    • "n" Nonzero: this expression always consumes at least 1 stack element, no satisfaction for this expression requires the top input stack element to be zero.
    • -
    • "d" Dissatisfiable: a dissatisfaction for this expression can unconditionally be constructed. This implies the dissatisfaction cannot include any signature or hash preimage, and cannot rely on timelocks being satisfied.
    • -
    • "u" Unit: when satisfied, this expression will put an exact 1 on the stack (as opposed to any nonzero value).
    • -
    - -

    -This tables lists the correctness requirements for each of the Miniscript expressions, and its type properties in function of those of their subexpressions: -

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    MiniscriptRequiresTypeProperties
    0Bz; u; d
    1Bz; u
    pk_k(key)Ko; n; d; u
    pk_h(key)Kn; d; u
    older(n), after(n)1 ≤ n < 231Bz
    sha256(h)Bo; n; d; u
    ripemd160(h)Bo; n; d; u
    hash256(h)Bo; n; d; u
    hash160(h)Bo; n; d; u
    andor(X,Y,Z)X is Bdu; Y and Z are both B, K, or Vsame as Y/Zz=zXzYzZ; o=zXoYoZ or oXzYzZ; u=uYuZ; d=dZ
    and_v(X,Y)X is V; Y is B, K, or Vsame as Yz=zXzY; o=zXoY or zYoX; n=nX or zXnY; u=uY
    and_b(X,Y)X is B; Y is WBz=zXzY; o=zXoY or zYoX; n=nX or zXnY; d=dXdY; u
    or_b(X,Z)X is Bd; Z is WdBz=zXzZ; o=zXoZ or zZoX; d; u
    or_c(X,Z)X is Bdu; Z is VVz=zXzZ; o=oXzZ
    or_d(X,Z)X is Bdu; Z is BBz=zXzZ; o=oXzZ; d=dZ; u=uZ
    or_i(X,Z)both are B, K, or Vsame as X/Zo=zXzZ; u=uXuZ; d=dX or dZ
    thresh(k,X1,...,Xn)1 ≤ k ≤ n; X1 is Bdu; others are WduBz=all are z; o=all are z except one is o; d; u
    multi(k,key1,...,keyn)1 ≤ k ≤ nBn; d; u
    multi_a(k,key1,...,keyn)1 ≤ k ≤ nBd; u
    a:XX is BWd=dX; u=uX
    s:XX is BoWd=dX; u=uX
    c:XX is KBo=oX; n=nX; d=dX; u
    d:XX is VzBo; n; d; (Tapscript only) u
    v:XX is BVz=zX; o=oX; n=nX
    j:XX is BnBo=oX; n; d; u=uX
    n:XX is BBz=zX; o=oX; n=nX; d=dX; u
    -
    - - -

    Detecting timelock mixing

    -

    -The nSequence field in a transaction input, or the nLockTime field in transaction can be specified either as a time or height but not both. -Therefore it is not possible to spend scripts that require satisfaction of both, height based timelock and time based timelock of the same type. -

      -
    • "k" No timelock mixing. This expression does not contain a mix of heightlock and timelock of the same type. If the miniscript does not have the "k" property, the miniscript template will not match the user expectation of the corresponding spending policy.
    • -
    - -

    Resource limitations

    - -

    -Various types of Bitcoin Scripts have different resource limitations, either through consensus or standardness. Some of them affect otherwise valid Miniscripts:

      -
    • Scripts over 10000 bytes are invalid by consensus (bare, P2SH, P2WSH, P2SH-P2WSH). In Tapscript scripts are only implicitly bounded by the maximum standard transaction size (of 100k virtual bytes), which makes the maximum script size a bit less than 400000 bytes.
    • -
    • Scripts over 520 bytes are invalid by consensus (P2SH).
    • -
    • Script satisfactions where the total number of non-push opcodes plus the number of keys participating in all executed multis, is above 201, are invalid by consensus (bare, P2SH, P2WSH, P2SH-P2WSH).
    • -
    • Anything but pk(key) (P2PK), pkh(key) (P2PKH), and multi(k,...) up to n=3 is invalid by standardness (bare).
    • -
    • Scripts over 3600 bytes are invalid by standardness (P2WSH, P2SH-P2WSH).
    • -
    • Script satisfactions with a serialized scriptSig over 1650 bytes are invalid by standardness (P2SH).
    • -
    • Script satisfactions with a witness consisting of over 100 stack elements (excluding the script itself) are invalid by standardness (P2WSH, P2SH-P2WSH).
    • -
    • Script satisfactions that make the stack exceed 1000 elements during or before script execution are invalid by consensus (bare, P2SH, P2WSH, P2SH-P2WSH, Tapscript).
    • -
    -For P2WSH, a Miniscript whose script is larger than 3600 bytes is invalid. -For all the other limits, Miniscript makes it easy to verify they don't impact the ability to satisfy a script. Note that this is different from verifying whether the limits are never -reachable at all (which is also possible). Consider for example an or_b(X,Y) where both X and Y require a number of large multis -to be executed to satisfy. It may be the case that satisfying just one of X or Y does not exceed the ops limit, while satisfying both does. As it's never required to -satisfy both, the limit does not prevent satisfaction. - -

    -

    Security properties

    -

    -The type system above guarantees that the corresponding Bitcoin Scripts are:

      -
    • consensus and standardness complete: Assuming the resource limits listed in the previous section are not violated and there is no timelock mixing, for every set of met conditions that are permitted by the semantics, a witness can be constructed that passes Bitcoin's consensus rules and common standardness rules.
    • -
    • consensus sound: It is not possible to construct a witness that is consensus valid for a Script unless the spending conditions are met. Since standardness rules permit only a subset of consensus-valid satisfactions (by definition), this property also implies standardness soundness. -
    - - -

    -The completeness property has been extensively tested for P2WSH by verifying large numbers of random satisfactions for random Miniscript expressions against Bitcoin Core's consensus and standardness implementation. -The soundness can be reasoned about by considering all possible execution paths through each of the fragments' scripts. -

    - -

    In order for these properties to not just apply to script, but to an entire transaction, it's important that the witness commits to all data relevant for verification. In practice -this means that scripts whose conditions can be met without any digital signature are insecure. For example, if an output can be spent by simply passing a certain nLockTime -(an after(n) fragment in Miniscript) but without any digital signatures, an attacker can modify the nLockTime field in the spending transaction. - -

    -
    - -
    -

    Satisfactions and malleability

    -
    -

    Basic satisfactions

    -

    -The following table shows all valid satisfactions and dissatisfactions for every Miniscript, using satisfactions and dissatisfactions of its subexpressions. -Multiple possibilities are separated by semicolons. Some options are not actually necessary to produce correct witnesses, and are called non-canonical options. They -are listed for completeness, but marked in [grey] below. -

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    MiniscriptDissatisfactions (dsat)Satisfactions (sat)
    0-
    1-
    pk_k(key)0sig
    pk_h(key)0 keysig key
    older(n)-
    after(n)-
    sha256(h)any 32-byte vector except the preimagepreimage
    ripemd160(h)any 32-byte vector except the preimagepreimage
    hash256(h)any 32-byte vector except the preimagepreimage
    hash160(h)any 32-byte vector except the preimagepreimage
    andor(X,Y,Z)dsat(Z) dsat(X); [dsat(Y) sat(X)]sat(Y) sat(X); sat(Z) dsat(X)
    and_v(X,Y)[dsat(Y) sat(X)]sat(Y) sat(X)
    and_b(X,Y)dsat(Y) dsat(X); [sat(Y) dsat(X)]; [dsat(Y) sat(X)]sat(Y) sat(X)
    or_b(X,Z)dsat(Z) dsat(X)dsat(Z) sat(X); sat(Z) dsat(X); [sat(Z) sat(X)]
    or_c(X,Z)-sat(X); sat(Z) dsat(X)
    or_d(X,Z)dsat(Z) dsat(X)sat(X); sat(Z) dsat(X)
    or_i(X,Z)dsat(X) 1; dsat(Z) 0sat(X) 1; sat(Z) 0
    thresh(k,X1,...,Xn)All dsats [Sats/dsats with 1 ≤ #(sats) ≠ k]Sats/dsats with #(sats) = k
    multi(k,key1,...,keyn)0 0 ... 0 (k+1 times)0 sig ... sig
    multi_a(k,key1,...,keyn)0 ... 0 (n times)sig/0 with #(sig) = k and #(sigs/0) = n
    a:Xdsat(X)sat(X)
    s:Xdsat(X)sat(X)
    c:Xdsat(X)sat(X)
    d:X0sat(X) 1
    v:X-sat(X)
    j:X0; [dsat(X) (if nonzero top stack)]sat(X)
    n:Xdsat(X)sat(X)
    -
    - -

    -The correctness properties in the previous section are based on the availability of satisfactions and dissatisfactions listed above. The requirements include a "d" for every -subexpression whose dissatisfaction may be needed in building a satisfaction for the parent expression. The "d" properties themselves rely on the "d" properties of subexpressions. -An interesting property is that in a well-typed Miniscript, dissatisfying a non-"d" subexpression always causes the overall script to fail, propagating -the failure upwards to a point where it either causes the script to abort, or reach the top level. - - -

    -A conservative simplification is made here, ignoring the existence of always-true expressions like 1. This makes the typing rules incorrect in the following cases:

      -
    • or_b(X,1) and or_b(1,X) are complete when X is dissatisfiable ("d"). In that case, the condition is equivalent to 1 and is always met, and a satisfaction of the form "dsat(X)" exists. -
    • or_b(1,1) is complete, as it is equivalent to 1 and "" is a valid satisfaction.
    • -
    • and_b(X,1) and and_b(1,X) are dissatisfiable ("d") when X is. -
    • andor(1,1,Z) is complete, as it is equivalent to 1 and "" is a valid satisfaction.
    • -
    • andor(1,Y,0) is complete when Y is; it also has the same type as Y, despite 0 always being a "B". This is also dissatisfiable when Y is. -
    • The 1s items listed above can be replaced with any other always-true expression. -
    -Since all these cases of inaccurate typing involve always-true expressions that can be avoided -using strictly simpler/smaller Miniscripts, we don't consider this to be a serious restriction. For Miniscripts excluding always-true expressions (except in the and_v(X,1) code), the typing rules match the definitions above. - -

    -

    Malleability

    -

    -While following the table above to construct satisfactions is sufficient for meeting the completeness and soundness properties, it does not guarantee non-malleability. - - -

    -Malleability is the ability for a third party (not a participant in the script) to modify an existing satisfaction into another valid satisfaction. -Since Segwit, malleating a transaction no longer breaks the validity of unconfirmed descendant transactions. However, unintentional malleability may still have a number of -much weaker undesirable effects. If a witness can be stuffed with additional data, the transaction's feerate will go down, potentially to the point where its ability to -propagate and get confirmed is impacted. Additionally, malleability can be exploited to add roundtrips to BIP152 block propagation, by trying to get different miners to mine different versions -of the same transaction. Finally, malleability may interfere with the usage of hash locks as a mechanism for publishing preimages. - - -

    -Because of these reasons, Miniscript is actually designed to permit non-malleable signing. As this guarantee is restricted in non-obvious ways, let's first state the assumptions:

      -
    • The attacker does not have access to any of the private keys of public keys that participate in the Script. Participants with private keys inherently have the ability to produce different satisfactions by creating multiple signatures. While it is also interesting to study the impact rogue participants can have, we treat it as a distinct problem.
    • -
    • The attacker only has access to hash preimages that honest users have access to as well. This is a reasonable assumption because hash preimages are revealed once globally, and then available to everyone. On the other hand, making the assumption that attackers may have access to more preimages than honest users makes a large portion of scripts impossible to satisfy in a non-malleable way.
    • -
    • The attacker gets to see exactly one satisfying witness of any transaction. If he sees multiple, it becomes possible for the attacker to mix and match different satisfactions. This is very hard to reason about.
    • -
    • We restrict this analysis to scripts where no public key is repeated. If signatures constructed for one part of the script can be bound to other checks in the same script, a variant of the mixing from the previous point becomes available that is equally hard to reason about. Furthermore this situation can be avoided by using separate keys.
    • -
    - - -

    -Malleable satisfactions or dissatisfactions appear whenever options are available to attackers distinct from the one taken by honest users. This can happen for multiple reasons:

      -
    1. Two or more options for a satisfaction or dissatisfaction are listed in the table above which are both available to attackers directly. Regardless of which option is used in the honest solution, the attacker can change the solution to the other one.
    2. -
    3. Two or more options for a satisfaction or dissatisfaction are listed in the table above, only one of which is available to attackers, but the honest solution uses another one. In that case, the attacker can modify the solution to pick the one available to him.
    4. -
    5. The honest users pick a solution that contains a satisfaction which can be turned into a dissatisfaction without invalidating the overall witness. Those are called overcomplete solutions.
    6. -
    - - -

    Non-malleable satisfaction algorithm

    - -

    -Because we assume attackers never have access to private keys, we can treat any solution that includes a signature as one that is unavailable to attackers. -For others, the worst case is that the attacker has access to every solution the honest users have, but no others: for preimages this is an explicit assumption, while timelock availability is determined -by the nLockTime and nSequence fields in the transaction. As long as the overall satisfaction includes at least one signature, those values are fixed, and timelock availability is identical for attackers and honest users. - - -

    -In order to produce non-malleable satisfactions we make use of a function that returns the optimal satisfaction and dissatisfaction for a given expression (if any exist), or a special DONTUSE value, together with an optional HASSIG marker that tracks whether the solution contains at least one signature. To implement the function:

      -
    • Invoke the function recursively for all subexpressions, obtaining all their satisfactions/dissatisfactions.
    • -
    • Iterate over all the valid satisfactions/dissatisfactions in the table above (including the non-canonical ones), taking into account:
        -
      • The dissatisfactions for sha256, ripemd160, hash256, and hash160 are always malleable (reason 1), so instead use DONTUSE there.
      • -
      • The non-canonical options for and_b, or_b, and thresh are always overcomplete (reason 3), so instead use DONTUSE there as well (with HASSIG flag if the original non-canonical solution had one). -
      • The satisfactions for pk_k, pk_h, and multi can be marked HASSIG.
      • -
      • When constructing solutions by combining results for subexpressions, the result is DONTUSE if any of the constituent results is DONTUSE. Furthermore, the result gets the HASSIG tag if any of the constituents do.
      • -
    • -
    • If among all valid solutions (including DONTUSE ones) more than one does not have the HASSIG marker, return DONTUSE, as this is malleable because of reason 1.
    • -
    • If instead exactly one does not have the HASSIG marker, return that solution because of reason 2.
    • -
    • If all valid solutions have the HASSIG marker, but all of them are DONTUSE, return DONTUSE-HASSIG. The HASSIG marker is important because while this represents a choice between multiple options that would cause malleability if used, they are not available to the attacker, and we may be able to avoid them entirely still. -
    • Otherwise, all not-DONTUSE options are valid, so return the smallest one (in terms of witness size). -
    -To produce an overall satisfaction, invoke the function on the toplevel expression. If no valid satisfaction is returned, or it is DONTUSE, fail. Otherwise, if any timelocking is used in the script but the result does not have the HASSIG flag, also fail, as this represents a situation where an attacker could change the nLockTime or nSequence values to meet additional timelock conditions. Not to mention that solutions without signatures are generally insecure. If the satisfaction is both not DONTUSE and HASSIG, return it. - - -

    -The above can be used to show that no non-canonical solutions listed in the satisfaction table can occur inside non-malleable satisfactions.

      -
    • Some of the non-canonical options (the or_b, and_b, and thresh ones) are overcomplete, and thus can clearly not appear in non-malleable satisfactions.
    • -
    • The fact that non-"d" expressions cannot be dissatisfied in valid witnesses rules out the usage of the non-canonical and_v dissatisfaction.
    • -
    • "d" expressions are defined to be unconditionally dissatisfiable, which implies that for those a non-HASSIG dissatisfaction must exist. Non-HASSIG solutions - must be preferred over HASSIG ones (reason 2), and when multiple non-HASSIG ones exist, none can be used (reason 1). This lets us rule out the other non-canonical options in the table:
        -
      • j:X is always "d", its non-HASSIG dissatisfaction "0" always exists, and thus rules out any usage of "dsat(X)". -
      • If andor(X,Y,Z) is "d", a non-HASSIG dissatisfaction "dsat(Z) dsat(X)" must exist, and thus rules out any usage of "dsat(Y) sat(X)".
      • -
      • If and_b(X,Y) is "d", a non-HASSIG dissatisfaction "dsat(Y) dsat(X)" must exist, and thus rules out any usage of "dsat(Y) sat(X)" and "sat(Y) dsat(X)". Those are also overcomplete.
      • -
      • thresh(k,...) is always "d", a non-HASSIG dissatisfaction with just dissatisfactions must exist due to typing rules, and thus rules out usage of the other dissatisfactions. They are also overcomplete.
      • -
      -
    - - -

    Guaranteeing non-malleability

    - -

    -Now we have an algorithm that can find the optimal non-malleable satisfaction for any Miniscript, if it exists. But can we statically prove that such a solution always exists? -In order to do that, we add additional properties to the type system:

      -
    • "s" Signed: satisfying this expression always requires a signature (predicting whether all satisfactions will be HASSIG).
    • -
    • "f" Forced: dissatisfying this expression always requires a signature (predicting whether all dissatisfactions will be HASSIG).
    • -
    • "e" Expressive: this requires a unique unconditional dissatisfaction to exist, and forces all conditional dissatisfactions (if any) to require a signature. -
    - - -

    -The following table lists these additional properties, and the requirements for non-malleability. -

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    MiniscriptRequiresProperties
    0s, e
    1f
    pk_k(key)s, e
    pk_h(key)s, e
    older(n)f
    after(n)f
    sha256(h)
    ripemd160(h)
    hash256(h)
    hash160(h)
    andor(X,Y,Z)eX and (sX or sY or sZ)s=sZ and (sX or sY); f=fZ and (sX or fY); e=eZ and (sX or fY)
    and_v(X,Y)s=sX or sY; f=sX or fY
    and_b(X,Y)s=sX or sY; f=fXfY or sXfX or sYfY; e=eXeYsXsY
    or_b(X,Z)eXeZ and (sX or sZ)s=sXsZ; e
    or_c(X,Z)eX and (sX or sZ)s=sXsZ; f
    or_d(X,Z)eX and (sX or sZ)s=sXsZ; f=fZ; e=eZ
    or_i(X,Z)sX or sZs=sXsZ; f=fXfZ; e=eXfZ or eZfX
    thresh(k,X1,...,Xn)all are e; at most k are non-ss=at most k-1 are non-s; e=all are s
    multi(k,key1,...,keyn)s; e
    multi_a(k,key1,...,keyn)s; e
    a:Xs=sX; f=fX; e=eX
    s:Xs=sX; f=fX; e=eX
    c:Xs; f=fX; e=eX
    d:Xs=sX; e
    v:Xs=sX; f
    j:Xs=sX; e=fX
    n:Xs=sX; f=fX; e=eX
    -
    - -

    -Using the malleability type properties it is possible to determine statically whether a script can be nonmalleably satisfied under all circumstances. -In many cases it is reasonable to only accept such guaranteed-nonmalleable scripts, as unexpected behavior can occur when using other scripts. - -

    -For example, when running the non-malleable satisfaction algorithm above, adding available preimages, or increasing the nLockTime/nSequence values -actually may make it fail where it succeeded before. This is because a larger set of met conditions may mean an existing satisfaction goes from nonmalleable to malleable. -Restricting things to scripts that are guaranteed to be satisfiable in a non-malleable way avoids this problem. - -

    -When analysing Miniscripts for resource limits, restricting yourself to just non-malleable solutions (or even non-malleable scripts) also leads to tighter bounds, -as all non-canonical satisfactions and dissatisfactions can be left out of consideration. - -

    -
    - - - diff --git a/miniscript.json-test-0 b/miniscript.json-test-0 new file mode 100755 index 0000000..9b57701 --- /dev/null +++ b/miniscript.json-test-0 @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# +DOCKER=$(which docker) + +if [[ -x $DOCKER ]];then +if [ -z "$(docker images -q ghcr.io/jqliang/jq:latest 2> /dev/null)" ]; then + docker pull -q ghcr.io/jqlang/jq:latest >/dev/null + #docker run -i --rm ghcr.io/jqlang/jq -n 'range(3)' +fi +fi + +JQ=$(which jq) + +if [[ -x $JQ ]];then +echo "or_i(and_v(v:thresh(2,pkh(M1),a:pkh(M2),a:pkh(M3)),after(1005)),and_v(v:thresh(2,pk(P1),s:pk(P2),s:pk(P3),sun:after(1002),sun:after(1003)),and_v(or_c(pk(SA1),or_c(pk(SA3),v:pkh(SA2))),thresh(2,pk(A1),s:pk(A2),s:pk(A3),sln:after(1004)))))" | miniscript.json | jq +fi +if [[ ! -x $JQ ]];then +echo "or_i(and_v(v:thresh(2,pkh(M1),a:pkh(M2),a:pkh(M3)),after(1005)),and_v(v:thresh(2,pk(P1),s:pk(P2),s:pk(P3),sun:after(1002),sun:after(1003)),and_v(or_c(pk(SA1),or_c(pk(SA3),v:pkh(SA2))),thresh(2,pk(A1),s:pk(A2),s:pk(A3),sln:after(1004)))))" | miniscript.json +fi diff --git a/miniscript.json.cpp b/miniscript.json.cpp new file mode 100755 index 0000000..b67c979 --- /dev/null +++ b/miniscript.json.cpp @@ -0,0 +1,76 @@ +#include +#include +#include +#include +#include + +#include