From d4e820b56364d4fe85414ef8a148644b53d69781 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Tue, 26 Mar 2019 20:17:20 +0900 Subject: [PATCH 01/40] Add Miniscript folder as separate dll --- NBitcoin/Miniscript/AssemblyInfo.fs | 41 + NBitcoin/Miniscript/Contract.fs | 5 + NBitcoin/Miniscript/Library.fs | 5 + NBitcoin/Miniscript/Miniscript.fs | 37 + NBitcoin/Miniscript/Miniscript.fsproj | 25 + NBitcoin/Miniscript/MiniscriptAST.fs | 562 ++++++++++ NBitcoin/Miniscript/MiniscriptCompiler.fs | 1087 +++++++++++++++++++ NBitcoin/Miniscript/MiniscriptDecompiler.fs | 652 +++++++++++ NBitcoin/Miniscript/MiniscriptParser.fs | 141 +++ NBitcoin/Miniscript/Program.fs | 8 + NBitcoin/Miniscript/Satisfy.fs | 322 ++++++ NBitcoin/Miniscript/Utils/Lib.fs | 34 + NBitcoin/Miniscript/Utils/Parser.fs | 167 +++ NBitcoin/NBitcoin.csproj | 9 + 14 files changed, 3095 insertions(+) create mode 100644 NBitcoin/Miniscript/AssemblyInfo.fs create mode 100644 NBitcoin/Miniscript/Contract.fs create mode 100644 NBitcoin/Miniscript/Library.fs create mode 100644 NBitcoin/Miniscript/Miniscript.fs create mode 100644 NBitcoin/Miniscript/Miniscript.fsproj create mode 100644 NBitcoin/Miniscript/MiniscriptAST.fs create mode 100644 NBitcoin/Miniscript/MiniscriptCompiler.fs create mode 100644 NBitcoin/Miniscript/MiniscriptDecompiler.fs create mode 100644 NBitcoin/Miniscript/MiniscriptParser.fs create mode 100644 NBitcoin/Miniscript/Program.fs create mode 100644 NBitcoin/Miniscript/Satisfy.fs create mode 100644 NBitcoin/Miniscript/Utils/Lib.fs create mode 100644 NBitcoin/Miniscript/Utils/Parser.fs diff --git a/NBitcoin/Miniscript/AssemblyInfo.fs b/NBitcoin/Miniscript/AssemblyInfo.fs new file mode 100644 index 0000000000..6a8ef90560 --- /dev/null +++ b/NBitcoin/Miniscript/AssemblyInfo.fs @@ -0,0 +1,41 @@ +// Auto-Generated by FAKE; do not edit +namespace System + +open System.Reflection + +[] +[] +[] +[] +[] +[] +[] +[] +do () + +module internal AssemblyVersionInformation = + [] + let AssemblyTitle = "Miniscript" + + [] + let AssemblyProduct = "Miniscript" + + [] + let AssemblyVersion = "0.1.0" + + [] + let AssemblyMetadata_ReleaseDate = "2017-03-17T00:00:00.0000000" + + [] + let AssemblyFileVersion = "0.1.0" + + [] + let AssemblyInformationalVersion = "0.1.0" + + [] + let AssemblyMetadata_ReleaseChannel = "release" + + [] + let AssemblyMetadata_GitHash = "bb8964b54bee133e9af64d316dc2cfee16df7f72" diff --git a/NBitcoin/Miniscript/Contract.fs b/NBitcoin/Miniscript/Contract.fs new file mode 100644 index 0000000000..bfe5ce47cc --- /dev/null +++ b/NBitcoin/Miniscript/Contract.fs @@ -0,0 +1,5 @@ +module FNBitcoin.Contract + + +type ScriptBuilder() = + member this.Hoge () = () \ No newline at end of file diff --git a/NBitcoin/Miniscript/Library.fs b/NBitcoin/Miniscript/Library.fs new file mode 100644 index 0000000000..9cf038ea98 --- /dev/null +++ b/NBitcoin/Miniscript/Library.fs @@ -0,0 +1,5 @@ +namespace FNBitcoin + +module Say = + let nothing name = name |> ignore + let hello name = sprintf "Hello %s" name diff --git a/NBitcoin/Miniscript/Miniscript.fs b/NBitcoin/Miniscript/Miniscript.fs new file mode 100644 index 0000000000..8a77d436f7 --- /dev/null +++ b/NBitcoin/Miniscript/Miniscript.fs @@ -0,0 +1,37 @@ +namespace FNBitcoin.MiniScript + +open FNBitcoin.MiniScriptAST +open FNBitcoin.MiniScriptDecompiler +open NBitcoin + +/// wrapper for top-level AST +type MiniScript = MiniScript of AST + +module MiniScript = + let fromAST (t : AST) : Result = + match t.castT() with + | Ok t -> Ok(MiniScript(TTree t)) + | o -> Error (sprintf "AST was not top-level (T) representation\n%A" o) + + let fromASTUnsafe(t: AST) = + match fromAST t with + | Ok t -> t + | Error e -> failwith e + + let toAST (m : MiniScript) = + match m with + | MiniScript a -> a + + let fromScriptUnsafe (s : NBitcoin.Script) = + let res = parseScriptUnsafe s + match fromAST res with + | Ok r -> r + | Error e -> failwith e + + let toScript (m : MiniScript) : Script = + let ast = toAST m + ast.ToScript() + +type MiniScript with + member this.ToScript() = MiniScript.toScript this + member this.ToAST() = MiniScript.toAST this diff --git a/NBitcoin/Miniscript/Miniscript.fsproj b/NBitcoin/Miniscript/Miniscript.fsproj new file mode 100644 index 0000000000..a70374533d --- /dev/null +++ b/NBitcoin/Miniscript/Miniscript.fsproj @@ -0,0 +1,25 @@ + + + netcoreapp2.1;netstandard2.0 + + + true + true + + + + + + + + + + + + + + + + + + diff --git a/NBitcoin/Miniscript/MiniscriptAST.fs b/NBitcoin/Miniscript/MiniscriptAST.fs new file mode 100644 index 0000000000..f78add4349 --- /dev/null +++ b/NBitcoin/Miniscript/MiniscriptAST.fs @@ -0,0 +1,562 @@ +module FNBitcoin.MiniScriptAST + +open NBitcoin +open FNBitcoin.Utils +open System.Text + +// TODO: Use unativeint instead of uint? + +/// "E"xpression. takes more than one inputs from the stack, if it satisfies the condition, +/// It will leave 1 onto the stack, otherwise leave 0 +/// E and W are the only type which is able to dissatisfy without failing the whole script. +type E = + | CheckSig of PubKey + | CheckMultiSig of uint32 * PubKey [] + | Time of LockTime + | Threshold of (uint32 * E * W []) + | ParallelAnd of (E * W) + | CascadeAnd of (E * F) + | ParallelOr of (E * W) + | CascadeOr of (E * E) + | SwitchOrLeft of (E * F) + | SwitchOrRight of (E * F) + | Likely of F + | Unlikely of F + +/// "W"rapped. say top level element is `X`, then consume items from the next element. +/// and leave one of [1,X] [X,1] if it satisfied the condition. otherwise +/// leave [0,X] or [X,0] onto the stack. +and W = + | CheckSig of PubKey + | HashEqual of uint256 + | Time of LockTime + | CastE of E + +/// "Q"ueue. Similar to F, but leaves public key buffer on the stack instead of 1 +and Q = + | Pubkey of PubKey + | And of (V * Q) + | Or of (Q * Q) + + +/// "F"orced. Similar to T, but always leaves 1 on the stack. +and F = + | CheckSig of PubKey + | CheckMultiSig of uint32 * PubKey [] + | Time of LockTime + | HashEqual of uint256 + | Threshold of (uint32 * E * W []) + | And of (V * F) + | CascadeOr of (E * V) + | SwitchOr of (F * F) + | SwitchOrV of (V * V) + | DelayedOr of (Q * Q) + +/// "V"erify. Similar to the T, but does not leave anything on the stack +and V = + | CheckSig of PubKey + | CheckMultiSig of uint32 * PubKey [] + | Time of LockTime + | HashEqual of uint256 + | Threshold of (uint32 * E * W []) + | And of (V * V) + | CascadeOr of (E * V) + | SwitchOr of (V * V) + | SwitchOrT of (T * T) + | DelayedOr of (Q * Q) + +/// "T"opLevel representation. Must be satisfied, and leave zero (or non-zero) value onto the stack +and T = + | Time of LockTime + | HashEqual of uint256 + | And of (V * T) + | ParallelOr of (E * W) + | CascadeOr of (E * T) + | CascadeOrV of (E * V) + | SwitchOr of (T * T) + | SwitchOrV of (V * V) + | DelayedOr of (Q * Q) + | CastE of E + +type AST = + | ETree of E + | QTree of Q + | WTree of W + | FTree of F + | VTree of V + | TTree of T + +type ASTType = + | EExpr + | QExpr + | WExpr + | FExpr + | VExpr + | TExpr + +let private EncodeUint (n: uint32) = + Op.GetPushOp(int64 n).ToString() + +let private EncodeInt (n: int32) = + Op.GetPushOp(int64 n).ToString() + +type E with + + member this.print() = + match this with + | CheckSig pk -> sprintf "E.pk(%s)" (pk.ToHex()) + | CheckMultiSig(m, pks) -> + sprintf "E.multi(%d,%s)" m + (pks + |> Array.fold (fun acc k -> sprintf "%s,%s" acc (k.ToString())) + "") + | Time t -> sprintf "E.time(%s)" (t.ToString()) + | Threshold(num, e, ws) -> + sprintf "E.thres(%d,%s,%s)" num (e.print()) + (ws + |> Array.fold (fun acc w -> sprintf "%s,%s" acc (w.print())) "") + | ParallelAnd(e, w) -> sprintf "E.and_p(%s,%s)" (e.print()) (w.print()) + | CascadeAnd(e, f) -> sprintf "E.and_c(%s,%s)" (e.print()) (f.print()) + | ParallelOr(e, w) -> sprintf "E.or_p(%s,%s)" (e.print()) (w.print()) + | CascadeOr(e, e2) -> sprintf "E.or_c(%s,%s)" (e.print()) (e2.print()) + | SwitchOrLeft(e, f) -> sprintf "E.or_s(%s,%s)" (e.print()) (f.print()) + | SwitchOrRight(e, f) -> sprintf "E.or_r(%s,%s)" (e.print()) (f.print()) + | Likely f -> sprintf "E.lift_l(%s)" (f.print()) + | Unlikely f -> sprintf "E.lift_u(%s)" (f.print()) + + member this.Serialize(sb : StringBuilder) : StringBuilder = + match this with + | CheckSig pk -> sb.AppendFormat(" {0} OP_CHECKSIG", pk) + | CheckMultiSig(m, pks) -> + sb.AppendFormat(" {0}", (EncodeUint m)) |> ignore + for pk in pks do + do sb.AppendFormat(" {0}", (pk.ToHex())) |> ignore + sb.AppendFormat(" {0} OP_CHECKMULTISIG", EncodeInt(pks.Length)) |> ignore + sb + | Time t -> + sb.AppendFormat(" OP_DUP OP_IF {0} OP_CSV OP_DROP OP_ENDIF", EncodeUint(!> t)) + | Threshold(k, e, ws) -> + e.Serialize(sb) |> ignore + for w in ws do + w.Serialize(sb) |> ignore + sb.Append(" OP_ADD") |> ignore + sb.AppendFormat(" {0} OP_EQUAL", (EncodeUint k)) + | ParallelAnd(l, r) -> + l.Serialize(sb) |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_BOOLAND") + | CascadeAnd(l, r) -> + l.Serialize(sb) |> ignore + sb.Append(" OP_NOTIF 0 OP_ELSE") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF") + | ParallelOr(l, r) -> + l.Serialize(sb) |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_BOOLOR") + | CascadeOr(l, r) -> + l.Serialize(sb) |> ignore + sb.Append(" OP_IFDUP OP_NOTIF") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF") + | SwitchOrLeft(l, r) -> + sb.Append(" OP_IF") |> ignore + l.Serialize(sb) |> ignore + sb.Append(" OP_ELSE") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF") + | SwitchOrRight(l, r) -> + sb.Append(" OP_NOTIF") |> ignore + l.Serialize(sb) |> ignore + sb.Append(" OP_ELSE") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF") + | Likely(f) -> + sb.Append(" OP_NOTIF") |> ignore + f.Serialize(sb) |> ignore + sb.Append(" OP_ELSE 0 OP_ENDIF") + | Unlikely(f) -> + sb.Append(" OP_IF") |> ignore + f.Serialize(sb) |> ignore + sb.Append(" OP_ELSE 0 OP_ENDIF") + + member this.toE() = this + member this.toT() = + match this with + | ParallelOr(l, r) -> T.ParallelOr(l, r) + | x -> T.CastE(x) + +and Q with + + member this.print() = + match this with + | Pubkey p -> sprintf "Q.pk(%s)" (p.ToString()) + | And(v, q) -> sprintf "Q.and(%s,%s)" (v.print()) (q.print()) + | Or(q1, q2) -> sprintf "Q.or(%s,%s)" (q1.print()) (q2.print()) + + member this.Serialize(sb : StringBuilder) : StringBuilder = + match this with + | Pubkey pk -> sb.AppendFormat(" {0}", (pk.ToHex())) + | And(l, r) -> + l.Serialize(sb) |> ignore + r.Serialize(sb) + | Or(l, r) -> + sb.Append(" OP_IF") |> ignore + l.Serialize(sb) |> ignore + sb.Append(" OP_ELSE") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF") + +and W with + + member this.print() = + match this with + | CheckSig pk -> sprintf "W.pk(%s)" (pk.ToString()) + | HashEqual u -> sprintf "W.hash(%s)" (u.ToString()) + | Time t -> sprintf "W.time(%s)" (t.ToString()) + | CastE e -> e.print() + + member this.Serialize(sb : StringBuilder) : StringBuilder = + match this with + | CheckSig pk -> + sb.Append(" OP_SWAP") |> ignore + sb.AppendFormat(" {0}", (pk.ToHex())) |> ignore + sb.Append(" OP_CHECKSIG") + | HashEqual h -> + sb.Append + (sprintf " OP_SWAP OP_SIZE OP_0NOTEQUAL OP_IF OP_SIZE %s OP_EQUALVERIFY OP_SHA256" + (EncodeInt 32)) + |> ignore + sb.AppendFormat(" {0}", h.ToString()) |> ignore + sb.Append(" OP_EQUALVERIFY 1 OP_ENDIF") + | Time t -> + sb.AppendFormat + (" OP_SWAP OP_DUP OP_IF {0} OP_CSV OP_DROP OP_ENDIF", (EncodeUint (!> t))) + | CastE e -> + sb.Append(" OP_TOALTSTACK") |> ignore + e.Serialize(sb) |> ignore + sb.Append(" OP_FROMALTSTACK") + +and F with + + member this.print() = + match this with + | CheckSig pk -> sprintf "F.pk(%s)" (pk.ToString()) + | CheckMultiSig(m, pks) -> + sprintf "F.multi(%d,%s)" m + (pks + |> Array.fold (fun acc k -> sprintf "%s,%s" acc (k.ToString())) + "") + | Time t -> sprintf "F.time(%s)" (t.ToString()) + | HashEqual h -> sprintf "F.hash(%s)" (h.ToString()) + | Threshold(num, e, ws) -> + sprintf "F.thres(%d,%s,%s)" num (e.print()) + (ws + |> Array.fold (fun acc w -> sprintf "%s,%s" acc (w.print())) "") + | And(l, r) -> sprintf "F.and(%s,%s)" (l.print()) (r.print()) + | CascadeOr(l, r) -> sprintf "F.or_v(%s,%s)" (l.print()) (r.print()) + | SwitchOr(l, r) -> sprintf "F.or_s(%s,%s)" (l.print()) (r.print()) + | SwitchOrV(l, r) -> sprintf "F.or_a(%s,%s)" (l.print()) (r.print()) + | DelayedOr(l, r) -> sprintf "F.or_d(%s,%s)" (l.print()) (r.print()) + + member this.toE() = this + + member this.toT() = + match this with + | CascadeOr(l, r) -> T.CascadeOrV(l, r) + | SwitchOrV(l, r) -> T.SwitchOrV(l, r) + | x -> failwith (sprintf "%s is not a T" (x.print())) + + member this.Serialize(sb : StringBuilder) : StringBuilder = + match this with + | CheckSig pk -> + sb.AppendFormat(" {0} OP_CHECKSIGVERIFY 1", (pk.ToHex())) + | CheckMultiSig(m, pks) -> + sb.AppendFormat(" {0}", (EncodeUint m)) |> ignore + for pk in pks do + sb.AppendFormat(" {0}", (pk.ToHex())) |> ignore + sb.AppendFormat(" {0} OP_CHECKMULTISIGVERIFY 1", (EncodeInt pks.Length)) + | Time t -> sb.AppendFormat(" {0} OP_CSV OP_0NOTEQUAL", (EncodeUint (!> t))) + | HashEqual h -> + sb.AppendFormat + (" OP_SIZE 20 OP_EQUALVERIFY OP_SHA256 {0} OP_EQUALVERIFY 1", h) + | Threshold(k, e, ws) -> + e.Serialize(sb) |> ignore + for w in ws do + w.Serialize(sb) |> ignore + sb.Append(" OP_ADD") |> ignore + sb.AppendFormat(" {0} OP_EQUALVERIFY 1", (EncodeUint k)) + | And(l, r) -> + l.Serialize(sb) |> ignore + r.Serialize(sb) + | SwitchOr(l, r) -> + sb.AppendFormat(" OP_IF") |> ignore + l.Serialize(sb) |> ignore + sb.Append(" OP_ELSE") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF") + | SwitchOrV(l, r) -> + sb.AppendFormat(" OP_IF") |> ignore + l.Serialize(sb) |> ignore + sb.Append(" OP_ELSE") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF 1") + | CascadeOr(l, r) -> + l.Serialize(sb) |> ignore + sb.Append(" OP_NOTIF") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF 1") + | DelayedOr(l, r) -> + sb.Append(" OP_IF") |> ignore + l.Serialize(sb) |> ignore + sb.Append(" OP_ELSE") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF OP_CHECKSIGVERIFY 1") + +and V with + + member this.print() = + match this with + | CheckSig pk -> sprintf "V.pk(%s)" (pk.ToString()) + | CheckMultiSig(m, pks) -> + sprintf "V.multi(%d,%s)" m + (pks + |> Array.fold (fun acc k -> sprintf "%s,%s" acc (k.ToString())) + "") + | Time t -> sprintf "V.time(%s)" (t.ToString()) + | HashEqual h -> sprintf "V.hash(%s)" (h.ToString()) + | Threshold(num, e, ws) -> + sprintf "V.thres(%d,%s,%s)" num (e.print()) + (ws + |> Array.fold (fun acc w -> sprintf "%s,%s" acc (w.print())) "") + | And(l, r) -> sprintf "V.and(%s,%s)" (l.print()) (r.print()) + | CascadeOr(l, r) -> sprintf "V.or_v(%s,%s)" (l.print()) (r.print()) + | SwitchOr(l, r) -> sprintf "V.or_s(%s,%s)" (l.print()) (r.print()) + | SwitchOrT(l, r) -> sprintf "V.or_a(%s,%s)" (l.print()) (r.print()) + | DelayedOr(l, r) -> sprintf "V.or_d(%s,%s)" (l.print()) (r.print()) + + member this.Serialize(sb : StringBuilder) : StringBuilder = + match this with + | CheckSig pk -> + sb.AppendFormat(" {0} OP_CHECKSIGVERIFY ", (pk.ToHex())) + | CheckMultiSig(m, pks) -> + sb.AppendFormat(" {0}", (EncodeUint m)) |> ignore + for pk in pks do + sb.AppendFormat(" {0}", (pk.ToHex())) |> ignore + sb.AppendFormat(" {0} OP_CHECKMULTISIGVERIFY", (EncodeInt pks.Length)) + | Time t -> sb.AppendFormat(" {0} OP_CSV OP_DROP", (EncodeUint (!> t))) + | HashEqual h -> + sb.AppendFormat + (" OP_SIZE 20 OP_EQUALVERIFY OP_SHA256 {0} OP_EQUALVERIFY", h) + | Threshold(k, e, ws) -> + e.Serialize(sb) |> ignore + for w in ws do + w.Serialize(sb) |> ignore + sb.Append(" OP_ADD") |> ignore + sb.AppendFormat(" {0} OP_EQUALVERIFY", (EncodeUint k)) + | And(l, r) -> + l.Serialize(sb) |> ignore + r.Serialize(sb) + | SwitchOr(l, r) -> + sb.AppendFormat(" OP_IF") |> ignore + l.Serialize(sb) |> ignore + sb.Append(" OP_ELSE") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF") + | SwitchOrT(l, r) -> + sb.AppendFormat(" OP_IF") |> ignore + l.Serialize(sb) |> ignore + sb.Append(" OP_ELSE") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF OP_VERIFY") + | CascadeOr(l, r) -> + l.Serialize(sb) |> ignore + sb.Append(" OP_NOTIF") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF") + | DelayedOr(l, r) -> + sb.Append(" OP_IF") |> ignore + l.Serialize(sb) |> ignore + sb.Append(" OP_ELSE") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF OP_CHECKSIGVERIFY") + +and T with + + member this.print() = + match this with + | Time t -> sprintf "T.time(%s)" (t.ToString()) + | HashEqual h -> sprintf "T.hash(%s)" (h.ToString()) + | And(l, r) -> sprintf "T.and_p(%s,%s)" (l.print()) (r.print()) + | ParallelOr(l, r) -> sprintf "T.or_vp(%s,%s)" (l.print()) (r.print()) + | CascadeOr(l, r) -> sprintf "T.or_c(%s,%s)" (l.print()) (r.print()) + | CascadeOrV(l, r) -> sprintf "T.or_v(%s,%s)" (l.print()) (r.print()) + | SwitchOr(l, r) -> sprintf "T.or_s(%s,%s)" (l.print()) (r.print()) + | SwitchOrV(l, r) -> sprintf "T.or_a(%s,%s)" (l.print()) (r.print()) + | DelayedOr(l, r) -> sprintf "T.or_d(%s,%s)" (l.print()) (r.print()) + | CastE e -> sprintf "T.%s" (e.print()) + + member this.Serialize(sb : StringBuilder) : StringBuilder = + match this with + | Time t -> sb.AppendFormat(" {0} OP_CSV", (EncodeUint (!> t))) + | HashEqual h -> + sb.AppendFormat + (" OP_SIZE 20 OP_EQUALVERIFY OP_SHA256 {0} OP_EQUAL", h) + | And(l, r) -> + l.Serialize(sb) |> ignore + r.Serialize(sb) + | ParallelOr(l, r) -> + l.Serialize(sb) |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_BOOLOR") + | CascadeOr(l, r) -> + l.Serialize(sb) |> ignore + sb.Append(" OP_IFDUP OP_NOTIF") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF") + | CascadeOrV(l, r) -> + l.Serialize(sb) |> ignore + sb.Append(" OP_NOTIF") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF 1") + | SwitchOr(l, r) -> + sb.AppendFormat(" OP_IF") |> ignore + l.Serialize(sb) |> ignore + sb.Append(" OP_ELSE") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF") + | SwitchOrV(l, r) -> + sb.AppendFormat(" OP_IF") |> ignore + l.Serialize(sb) |> ignore + sb.Append(" OP_ELSE") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF 1") + | DelayedOr(l, r) -> + sb.Append(" OP_IF") |> ignore + l.Serialize(sb) |> ignore + sb.Append(" OP_ELSE") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF OP_CHECKSIG") + | CastE e -> e.Serialize(sb) + +type AST with + + member this.Print() = + match this with + | ETree e -> e.print() + | QTree q -> q.print() + | WTree w -> w.print() + | FTree f -> f.print() + | VTree v -> v.print() + | TTree t -> t.print() + + member this.ToScript() = + let sb = StringBuilder() + match this with + | ETree e -> + let s = e.Serialize(sb) + NBitcoin.Script(s.ToString()) + | QTree q -> + let s = q.Serialize(sb) + NBitcoin.Script(s.ToString()) + | WTree w -> + let s = w.Serialize(sb) + NBitcoin.Script(s.ToString()) + | FTree f -> + let s = f.Serialize(sb) + NBitcoin.Script(s.ToString()) + | VTree v -> + let s = v.Serialize(sb) + NBitcoin.Script(s.ToString()) + | TTree t -> + let s = t.Serialize(sb) + let str = s.ToString() + NBitcoin.Script(str) + member this.GetASTType() = + match this with + | ETree _ -> EExpr + | QTree _ -> QExpr + | WTree _ -> WExpr + | FTree _ -> FExpr + | VTree _ -> VExpr + | TTree _ -> TExpr + + member this.IsT() = + match this with + | ETree _ + | TTree _ -> true + | FTree f -> + match f with + | F.CascadeOr _ + | F.SwitchOrV _ -> true + | _ -> false + | _ -> false + + member this.castT() : Result = + match this with + | TTree t -> Ok t + | FTree f -> + match f with + | F.CascadeOr(l, r) -> Ok(T.CascadeOrV(l, r)) + | F.SwitchOrV(l, r) -> Ok(T.SwitchOrV(l, r)) + | _ -> Error(sprintf "failed to cast %s" (this.Print())) + | ETree e -> + match e with + | E.ParallelOr(l, r) -> Ok(T.ParallelOr(l, r)) + | otherE -> Ok(T.CastE(otherE)) + | _ -> Error(sprintf "failed to cast %s" (this.Print())) + + member this.castE() : Result = + match this with + | ETree e -> Ok e + | _ -> Error(sprintf "failed to cast %s" (this.Print())) + + member this.castQ() : Result = + match this with + | QTree q -> Ok q + | _ -> Error(sprintf "failed to cast %s" (this.Print())) + + member this.castW() : Result = + match this with + | WTree w -> Ok w + | _ -> Error(sprintf "failed to cast %s" (this.Print())) + + member this.castF() : Result = + match this with + | FTree f -> Ok f + | _ -> Error(sprintf "failed to cast %s" (this.Print())) + + member this.castV() : Result = + match this with + | VTree v -> Ok v + | _ -> Error(sprintf "failed to cast %s" (this.Print())) + + member this.castTUnsafe() : T = + match this.castT() with + | Ok t -> t + | Error s -> failwith s + + member this.castEUnsafe() : E = + match this.castE() with + | Ok e -> e + | Error s -> failwith s + + member this.castQUnsafe() : Q = + match this.castQ() with + | Ok q -> q + | Error s -> failwith s + + member this.castWUnsafe() : W = + match this.castW() with + | Ok w -> w + | Error s -> failwith s + + member this.castFUnsafe() : F = + match this.castF() with + | Ok f -> f + | Error s -> failwith s + + member this.castVUnsafe() : V = + match this.castV() with + | Ok v -> v + | Error s -> failwith s diff --git a/NBitcoin/Miniscript/MiniscriptCompiler.fs b/NBitcoin/Miniscript/MiniscriptCompiler.fs new file mode 100644 index 0000000000..fb5a5cbad4 --- /dev/null +++ b/NBitcoin/Miniscript/MiniscriptCompiler.fs @@ -0,0 +1,1087 @@ +module FNBitcoin.MiniScriptCompiler + +open NBitcoin +open FNBitcoin.Utils +open FNBitcoin.MiniScriptParser +open FNBitcoin.MiniScript +open MiniScriptAST + +type CompiledNode = + | Pk of NBitcoin.PubKey + | Multi of uint32 * PubKey [] + | Hash of uint256 + | Time of LockTime + | Threshold of uint32 * CompiledNode [] + | And of left : CompiledNode * right : CompiledNode + | Or of left : CompiledNode * right : CompiledNode * leftProb : float * rightProb : float + +type Cost = + { ast : AST + pkCost : uint32 + satCost : float + dissatCost : float } + +/// Intermediary value before computing parent Cost +type CostTriple = + { parent : AST + left : Cost + right : Cost + /// In case of F ast, we can tell the compiler that + /// it can be combined as an E expression in two ways. + /// This is equivalent to `->` in this macro + /// ref: https://github.com/apoelstra/rust-miniscript/blob/ac36d4bacd6440458a57b4bd2013ea1c27058709/src/policy/compiler.rs#L333 + condCombine : bool } + +module Cost = + /// Casts F -> E + let likely (fcost : Cost) : Cost = + { ast = ETree(E.Likely(fcost.ast.castFUnsafe())) + pkCost = fcost.pkCost + 4u + satCost = fcost.satCost * 1.0 + dissatCost = 2.0 } + + let unlikely (fcost : Cost) : Cost = + { ast = ETree(E.Unlikely(fcost.ast.castFUnsafe())) + pkCost = fcost.pkCost + 4u + satCost = fcost.satCost * 2.0 + dissatCost = 1.0 } + + let fromPairToTCost (left : Cost) (right : Cost) (newAst : T) + (lweight : float) (rweight : float) = + match newAst with + | T.Time _ | T.HashEqual _ | T.CastE _ -> failwith "unreachable" + | T.And _ -> + { ast = TTree newAst + pkCost = left.pkCost + right.pkCost + satCost = left.satCost + right.satCost + dissatCost = 0.0 } + | T.ParallelOr _ -> + { ast = TTree newAst + pkCost = left.pkCost + right.pkCost + 1u + satCost = + (left.satCost + right.dissatCost) * lweight + + (right.satCost + left.dissatCost) * rweight + dissatCost = 0.0 } + | T.CascadeOr _ | T.CascadeOrV _ -> + { ast = TTree newAst + pkCost = left.pkCost + right.pkCost + 3u + satCost = + left.satCost * lweight + + (right.satCost + left.dissatCost) * rweight + dissatCost = 0.0 } + | T.SwitchOr _ -> + { ast = TTree newAst + pkCost = left.pkCost + right.pkCost + 3u + satCost = + (left.satCost + 2.0) * lweight + + (right.satCost + 1.0) * rweight + dissatCost = 0.0 } + | T.SwitchOrV _ -> + { ast = TTree newAst + pkCost = left.pkCost + right.pkCost + 4u + satCost = + (left.satCost + 2.0) * lweight + + (right.satCost + 1.0) * rweight + dissatCost = 0.0 } + | T.DelayedOr _ -> + { ast = TTree newAst + pkCost = left.pkCost + right.pkCost + 4u + satCost = + 72.0 + (left.satCost + 2.0) * lweight + + (right.satCost + 1.0) * rweight + dissatCost = 0.0 } + + let fromPairToVCost (left : Cost) (right : Cost) (newAst : V) + (lweight : float) (rweight : float) = + match newAst with + | V.CheckSig _ | V.CheckMultiSig _ | V.Time _ | V.HashEqual _ | V.Threshold _ -> + failwith "unreachable" + | V.And _ -> + { ast = VTree newAst + pkCost = left.pkCost + right.pkCost + satCost = left.satCost + right.satCost + dissatCost = 0.0 } + | V.CascadeOr _ -> + { ast = VTree newAst + pkCost = left.pkCost + right.pkCost + 2u + satCost = + (left.satCost * lweight) + + (right.satCost + left.dissatCost) * rweight + dissatCost = 0.0 } + | V.SwitchOr _ -> + { ast = VTree newAst + pkCost = left.pkCost + right.pkCost + 3u + satCost = + (left.satCost + 2.0) * lweight + + (right.satCost + 1.0) * rweight + dissatCost = 0.0 } + | V.SwitchOrT _ -> + { ast = VTree newAst + pkCost = left.pkCost + right.pkCost + 4u + satCost = + (left.satCost + 2.0) * lweight + + (right.satCost + 1.0) * rweight + dissatCost = 0.0 } + | V.DelayedOr _ -> + { ast = VTree newAst + pkCost = left.pkCost + right.pkCost + 4u + satCost = + (72.0 + left.satCost + 2.0) * lweight + + (72.0 + right.satCost + 1.0) * rweight + dissatCost = 0.0 } + + let fromPairToFCost (left : Cost) (right : Cost) (newAst : F) + (lweight : float) (rweight : float) = + match newAst with + | F.CheckSig _ | F.CheckMultiSig _ | F.Time _ | F.HashEqual _ | F.Threshold _ -> + failwith "unreachable" + | F.And _ -> + { ast = FTree newAst + pkCost = left.pkCost + right.pkCost + satCost = left.satCost + right.satCost + dissatCost = 0.0 } + | F.CascadeOr _ -> + { ast = FTree newAst + pkCost = left.pkCost + right.pkCost + 3u + satCost = + left.satCost * lweight + + (right.satCost + left.dissatCost) * rweight + dissatCost = 0.0 } + | F.SwitchOr _ -> + { ast = FTree newAst + pkCost = left.pkCost + right.pkCost + 3u + satCost = + (left.satCost + 2.0) * lweight + + (right.satCost + 1.0) * rweight + dissatCost = 0.0 } + | F.SwitchOrV _ -> + { ast = FTree newAst + pkCost = left.pkCost + right.pkCost + 4u + satCost = + (left.satCost + 2.0) * lweight + + (right.satCost + 1.0) * rweight + dissatCost = 0.0 } + | F.DelayedOr _ -> + { ast = FTree newAst + pkCost = left.pkCost + right.pkCost + 5u + satCost = + 72.0 + (left.satCost + 2.0) * lweight + + (right.satCost + 1.0) * rweight + dissatCost = 0.0 } + + let fromPairToECost (left : Cost) (right : Cost) (newAst : E) + (lweight : float) (rweight : float) = + let pkCost = left.pkCost + right.pkCost + match newAst with + | E.CheckSig _ | E.CheckMultiSig _ | E.Time _ | E.Threshold _ | E.Likely _ | E.Unlikely _ | E.ParallelAnd _ -> + { ast = ETree newAst + pkCost = pkCost + 1u + satCost = left.satCost + right.satCost + dissatCost = left.dissatCost + right.dissatCost } + | E.CascadeAnd _ -> + { ast = ETree newAst + pkCost = pkCost + 4u + satCost = left.satCost + right.satCost + dissatCost = left.dissatCost } + | E.ParallelOr _ -> + { ast = ETree newAst + pkCost = pkCost + 1u + satCost = + left.satCost * lweight + + (right.satCost + left.dissatCost) * rweight + dissatCost = left.dissatCost + right.dissatCost } + | E.CascadeOr _ -> + { ast = ETree newAst + pkCost = left.pkCost + right.pkCost + 3u + satCost = + left.satCost * lweight + + (right.satCost + left.dissatCost) * rweight + dissatCost = left.dissatCost + right.dissatCost } + | E.SwitchOrLeft _ -> + { ast = ETree newAst + pkCost = pkCost + 3u + satCost = + (left.satCost + 2.0) * lweight + + (right.satCost + 1.0) * rweight + dissatCost = left.dissatCost + 2.0 } + | E.SwitchOrRight _ -> + { ast = ETree newAst + pkCost = pkCost + 3u + satCost = + (left.satCost + 1.0) * lweight + + (right.satCost + 2.0) * rweight + dissatCost = left.dissatCost + 1.0 } + + // TODO: Consider about treating swap case (eft <=> right) here. + let fromTriple (triple : CostTriple) (lweight : float) (rweight : float) : Cost [] = + match triple.parent with + | TTree t -> + fromPairToTCost triple.left triple.right t lweight rweight + |> Array.singleton + | ETree e -> + fromPairToECost triple.left triple.right e lweight rweight + |> Array.singleton + | FTree f -> + match triple.condCombine with + | (false) -> + fromPairToFCost triple.left triple.right f lweight rweight + |> Array.singleton + | (true) -> + let costBeforeCast = + fromPairToFCost triple.left triple.right f lweight rweight + [| likely (costBeforeCast) + unlikely (costBeforeCast) |] + | VTree v -> + fromPairToVCost triple.left triple.right v lweight rweight + |> Array.singleton + + let min_cost (a : Cost, b : Cost, p_sat : float, p_dissat : float) = + let weight_one = + (float a.pkCost) + p_sat * a.satCost + p_dissat * a.dissatCost + let weight_two = + (float b.pkCost) + p_sat * b.satCost + p_dissat * a.dissatCost + if weight_one < weight_two then a + else if weight_one > weight_two then b + else if a.satCost < b.satCost then a + else b + + let fold_costs (p_sat : float) (p_dissat : float) (cs : Cost []) = + cs + |> Array.toList + |> List.reduce (fun a b -> min_cost (a, b, p_sat, p_dissat)) + + // equivalent to rules! macro in rust-miniscript + let getMinimumCost (triples : CostTriple []) (p_sat) (p_dissat) + (lweight : float) (rweight : float) : Cost = + triples + |> Array.map (fun p -> fromTriple p 0.0 0.0) + |> Array.concat + |> fold_costs p_sat p_dissat + +module CompiledNode = + /// bytes length when a number is encoded as bitcoin CScriptNum + let private scriptNumCost n = + if n <= 16u then 1u + else if n < 0x100u then 2u + else if n < 0x10000u then 3u + else if n < 0x1000000u then 4u + else 5u + + let private minCost (one : Cost, two : Cost, p_sat : float, p_dissat) = + let weight_one = + (float one.pkCost) + p_sat * one.satCost + p_dissat * one.dissatCost + let weight_two = + (float two.pkCost) + p_sat * two.satCost + p_dissat * two.dissatCost + if weight_one < weight_two then one + else if weight_two < weight_one then one + else if one.satCost < two.satCost then one + else two + + let private getPkCost m (pks : PubKey []) = + match (m > 16u, pks.Length > 16) with + | (true, true) -> 4 + | (true, false) -> 3 + | (false, true) -> 3 + | (false, false) -> 2 + + let rec fromPolicy (p : Policy) : CompiledNode = + match p with + | Key k -> Pk k + | Policy.Multi(m, pks) -> Multi(m, pks) + | Policy.Hash h -> Hash h + | Policy.Time t -> Time t + | Policy.Threshold(n, subexprs) -> + let ps = subexprs |> Array.map fromPolicy + Threshold(n, ps) + | Policy.And(e1, e2) -> And(fromPolicy e1, fromPolicy e2) + | Policy.Or(e1, e2) -> Or(fromPolicy e1, fromPolicy e2, 0.5, 0.5) + | Policy.AsymmetricOr(e1, e2) -> + Or(fromPolicy e1, fromPolicy e2, 127.0 / 128.0, 1.0 / 128.0) + + // TODO: cache + let rec best_t (node : CompiledNode, p_sat : float, p_dissat : float) : Cost = + match node with + | Pk _ | Multi _ | Threshold _ -> + let e = best_e (node, p_sat, p_dissat) + { ast = TTree(T.CastE(e.ast.castEUnsafe())) + pkCost = e.pkCost + satCost = e.satCost + dissatCost = 0.0 } + | Time t -> + let num_cost = scriptNumCost (!> t) + { ast = TTree(T.Time t) + pkCost = 1u + uint32 num_cost + satCost = 0.0 + dissatCost = 0.0 } + | Hash h -> + { ast = TTree(T.HashEqual h) + pkCost = 39u + satCost = 33.0 + dissatCost = 0.0 } + | And(l, r) -> + let vl = best_v (l, p_sat, 0.0) + let vr = best_v (r, p_sat, 0.0) + let tl = best_t (l, p_sat, 0.0) + let tr = best_t (r, p_sat, 0.0) + + let possibleCases = + [| { parent = + TTree + (T.And(vl.ast.castVUnsafe(), tr.ast.castTUnsafe())) + left = vl + right = tr + condCombine = false } + { parent = + TTree + (T.And(vr.ast.castVUnsafe(), tl.ast.castTUnsafe())) + left = vl + right = tr + condCombine = false } |] + Cost.getMinimumCost possibleCases p_sat p_dissat 0.0 0.0 + | Or(l, r, lweight, rweight) -> + let le = best_e (l, (p_sat * lweight), (p_sat * rweight)) + let re = best_e (r, (p_sat * rweight), (p_sat * lweight)) + let lw = best_w (l, (p_sat * lweight), (p_sat * rweight)) + let rw = best_w (r, (p_sat * rweight), (p_sat * lweight)) + let lt = best_t (l, (p_sat * lweight), 0.0) + let rt = best_t (r, (p_sat * lweight), 0.0) + let lv = best_v (l, (p_sat * lweight), 0.0) + let rv = best_v (r, (p_sat * lweight), 0.0) + let maybelq = best_q (l, (p_sat * lweight), 0.0) + let mayberq = best_q (r, (p_sat * lweight), 0.0) + + let possibleCases = + [| { parent = + TTree + (T.ParallelOr + (le.ast.castEUnsafe(), rw.ast.castWUnsafe())) + left = le + right = rw + condCombine = false } + { parent = + TTree + (T.ParallelOr + (re.ast.castEUnsafe(), lw.ast.castWUnsafe())) + left = re + right = lw + condCombine = false } + { parent = + TTree + (T.CascadeOr + (le.ast.castEUnsafe(), rt.ast.castTUnsafe())) + left = le + right = rt + condCombine = false } + { parent = + TTree + (T.CascadeOr + (re.ast.castEUnsafe(), lt.ast.castTUnsafe())) + left = re + right = lt + condCombine = false } + { parent = + TTree + (T.CascadeOrV + (le.ast.castEUnsafe(), rv.ast.castVUnsafe())) + left = le + right = rv + condCombine = false } + { parent = + TTree + (T.CascadeOrV + (re.ast.castEUnsafe(), lv.ast.castVUnsafe())) + left = re + right = lv + condCombine = false } + { parent = + TTree + (T.SwitchOr + (lt.ast.castTUnsafe(), rt.ast.castTUnsafe())) + left = lt + right = rt + condCombine = false } + { parent = + TTree + (T.SwitchOr + (rt.ast.castTUnsafe(), lt.ast.castTUnsafe())) + left = rt + right = lt + condCombine = false } + { parent = + TTree + (T.SwitchOrV + (lv.ast.castVUnsafe(), rv.ast.castVUnsafe())) + left = lv + right = rv + condCombine = false } + { parent = + TTree + (T.SwitchOrV + (rv.ast.castVUnsafe(), lv.ast.castVUnsafe())) + left = rv + right = lv + condCombine = false } |] + + let casesWithQ = + match maybelq, mayberq with + | Some lq, Some rq -> + Array.append possibleCases [| { parent = + TTree + (T.DelayedOr + (lq.ast.castQUnsafe + (), + rq.ast.castQUnsafe + ())) + left = lq + right = rq + condCombine = false } |] + | _ -> possibleCases + + Cost.getMinimumCost casesWithQ p_sat 0.0 lweight rweight + + and best_e (node : CompiledNode, p_sat : float, p_dissat : float) : Cost = + match node with + | Pk k -> + { ast = ETree(E.CheckSig k) + pkCost = 35u + satCost = 72.0 + dissatCost = 1.0 } + | Multi(m, pks) -> + let num_cost = getPkCost m pks + + let options = + [ { ast = ETree(E.CheckMultiSig(m, pks)) + pkCost = uint32 (num_cost + 34 * pks.Length + 1) + satCost = 2.0 + dissatCost = 1.0 } ] + if not (p_dissat > 0.0) then options.[0] + else + let bestf = best_f (node, p_sat, 0.0) + + let options2 = + [ Cost.likely (bestf) + Cost.unlikely (bestf) ] + List.concat [ options; options2 ] + |> List.toArray + |> Cost.fold_costs p_sat p_dissat + | Time n -> + let num_cost = scriptNumCost (!> n) + { ast = ETree(E.Time n) + pkCost = 5u + num_cost + satCost = 2.0 + dissatCost = 1.0 } + | Hash h -> + let fcost = best_f (node, p_sat, p_dissat) + minCost (Cost.likely fcost, Cost.unlikely fcost, p_sat, p_dissat) + | And(l, r) -> + let le = best_e (l, p_sat, p_dissat) + let re = best_e (r, p_sat, p_dissat) + let lw = best_w (l, p_sat, p_dissat) + let rw = best_w (r, p_sat, p_dissat) + let lf = best_f (l, p_sat, 0.0) + let rf = best_f (r, p_sat, 0.0) + let lv = best_v (l, p_sat, 0.0) + let rv = best_v (r, p_sat, 0.0) + + let possibleCases = + [| { parent = + ETree + (E.ParallelAnd + (le.ast.castEUnsafe(), rw.ast.castWUnsafe())) + left = le + right = rw + condCombine = false } + { parent = + ETree + (E.ParallelAnd + (re.ast.castEUnsafe(), lw.ast.castWUnsafe())) + left = re + right = lw + condCombine = false } + { parent = + ETree + (E.CascadeAnd + (le.ast.castEUnsafe(), rf.ast.castFUnsafe())) + left = le + right = rf + condCombine = false } + { parent = + ETree + (E.CascadeAnd + (re.ast.castEUnsafe(), lf.ast.castFUnsafe())) + left = re + right = lf + condCombine = false } + { parent = + FTree + (F.And(lv.ast.castVUnsafe(), rf.ast.castFUnsafe())) + left = lv + right = rf + condCombine = true } + { parent = + FTree + (F.And(rv.ast.castVUnsafe(), lf.ast.castFUnsafe())) + left = rv + right = lf + condCombine = true } |] + Cost.getMinimumCost possibleCases p_sat p_dissat 0.5 0.5 + | Or(l, r, lweight, rweight) -> + let le_par = + best_e (l, (p_sat * lweight), (p_dissat + p_sat * rweight)) + let re_par = + best_e (r, (p_sat * lweight), (p_dissat + p_sat * rweight)) + let lw_par = + best_w (l, (p_sat * lweight), (p_dissat + p_sat * rweight)) + let rw_par = + best_w (r, (p_sat * lweight), (p_dissat + p_sat * rweight)) + let le_cas = best_e (l, (p_sat * lweight), (p_dissat)) + let re_cas = best_e (r, (p_sat * lweight), (p_dissat)) + let le_cond_par = best_e (l, (p_sat * lweight), (p_sat * rweight)) + let re_cond_par = best_e (r, (p_sat * lweight), (p_sat * lweight)) + let lv = best_v (l, (p_sat * lweight), 0.0) + let rv = best_v (r, (p_sat * rweight), 0.0) + let lf = best_f (l, (p_sat * lweight), 0.0) + let rf = best_f (r, (p_sat * rweight), 0.0) + let maybelq = best_q (l, (p_sat * lweight), 0.0) + let mayberq = best_q (r, (p_sat * rweight), 0.0) + + let possibleCases = + [| { parent = + ETree + (E.ParallelOr + (le_par.ast.castEUnsafe(), + rw_par.ast.castWUnsafe())) + left = le_par + right = rw_par + condCombine = false } + { parent = + ETree + (E.ParallelOr + (re_par.ast.castEUnsafe(), + lw_par.ast.castWUnsafe())) + left = re_par + right = lw_par + condCombine = false } + { parent = + ETree + (E.CascadeOr + (le_par.ast.castEUnsafe(), + re_cas.ast.castEUnsafe())) + left = le_par + right = re_cas + condCombine = false } + { parent = + ETree + (E.CascadeOr + (re_par.ast.castEUnsafe(), + le_cas.ast.castEUnsafe())) + left = re_par + right = le_cas + condCombine = false } + { parent = + ETree + (E.SwitchOrLeft + (le_cas.ast.castEUnsafe(), + rf.ast.castFUnsafe())) + left = le_cas + right = rf + condCombine = false } + { parent = + ETree + (E.SwitchOrLeft + (re_cas.ast.castEUnsafe(), + lf.ast.castFUnsafe())) + left = re_cas + right = lf + condCombine = false } + { parent = + ETree + (E.SwitchOrRight + (le_cas.ast.castEUnsafe(), + rf.ast.castFUnsafe())) + left = le_cas + right = rf + condCombine = false } + { parent = + ETree + (E.SwitchOrRight + (re_cas.ast.castEUnsafe(), + lf.ast.castFUnsafe())) + left = re_cas + right = lf + condCombine = false } + { parent = + FTree + (F.CascadeOr + (le_cas.ast.castEUnsafe(), + rv.ast.castVUnsafe())) + left = le_cas + right = rv + condCombine = true } + { parent = + FTree + (F.CascadeOr + (re_cas.ast.castEUnsafe(), + lv.ast.castVUnsafe())) + left = re_cas + right = lv + condCombine = true } + { parent = + FTree + (F.SwitchOr + (lf.ast.castFUnsafe(), rf.ast.castFUnsafe())) + left = lf + right = rf + condCombine = true } + { parent = + FTree + (F.SwitchOr + (rf.ast.castFUnsafe(), lf.ast.castFUnsafe())) + left = rf + right = lf + condCombine = true } + { parent = + FTree + (F.SwitchOrV + (lv.ast.castVUnsafe(), rv.ast.castVUnsafe())) + left = lv + right = rv + condCombine = true } + { parent = + FTree + (F.SwitchOrV + (rv.ast.castVUnsafe(), lv.ast.castVUnsafe())) + left = rv + right = lv + condCombine = true } |] + + let casesWithQ = + match maybelq, mayberq with + | Some lq, Some rq -> + Array.append possibleCases [| { parent = + FTree + (F.DelayedOr + (lq.ast.castQUnsafe + (), + rq.ast.castQUnsafe + ())) + left = lq + right = rq + condCombine = true } + { parent = + FTree + (F.DelayedOr + (rq.ast.castQUnsafe + (), + lq.ast.castQUnsafe + ())) + left = rq + right = lq + condCombine = true } |] + | _ -> possibleCases + + Cost.getMinimumCost casesWithQ p_sat p_dissat lweight rweight + | Threshold(n, subs) -> + let num_cost = scriptNumCost n + let avgCost = float n / float subs.Length + let e = + best_e + (subs.[0], (p_sat * avgCost), + (p_dissat + p_sat * (1.0 - avgCost))) + let ws = + subs + |> Array.map + (fun s -> + best_w + (s, (p_sat * avgCost), + (p_dissat + p_sat * (1.0 - avgCost)))) + let pk_cost = + ws + |> Array.fold (fun acc w -> acc + w.pkCost) + (1u + num_cost + e.pkCost) + let sat_cost = + ws |> Array.fold (fun acc w -> acc + w.satCost) e.satCost + let dissat_cost = + ws |> Array.fold (fun acc w -> acc + w.dissatCost) e.dissatCost + let wsast = ws |> Array.map (fun w -> w.ast.castWUnsafe()) + + let cond = + { ast = ETree(E.Threshold(n, e.ast.castEUnsafe(), wsast)) + pkCost = pk_cost + satCost = sat_cost + dissatCost = dissat_cost } + + let f = best_f (node, p_sat, 0.0) + let cond1 = Cost.likely (f) + let cond2 = Cost.unlikely (f) + let nonCond = Cost.min_cost (cond1, cond2, p_sat, p_dissat) + Cost.min_cost (cond, nonCond, p_sat, p_dissat) + + and best_q (node : CompiledNode, p_sat : float, p_dissat : float) : Cost option = + match node with + | Pk pk -> + { ast = QTree(Q.Pubkey(pk)) + pkCost = 34u + satCost = 0.0 + dissatCost = 0.0 } + |> Some + | And(l, r) -> + let maybelq = best_q (l, p_sat, p_dissat) + let mayberq = best_q (r, p_sat, p_dissat) + + let cost v q = + { ast = QTree(Q.And(v.ast.castVUnsafe(), q.ast.castQUnsafe())) + pkCost = v.pkCost + q.pkCost + satCost = v.satCost + q.satCost + dissatCost = 0.0 } + + let op = + match maybelq, mayberq with + | None, Some rq -> + let lv = best_v (l, p_sat, p_dissat) + [| cost lv rq |] + | Some lq, None -> + let rv = best_v (r, p_sat, p_dissat) + [| cost rv lq |] + | Some lq, Some rq -> + let lv = best_v (l, p_sat, p_dissat) + let rv = best_v (r, p_sat, p_dissat) + [| cost lv rq + cost rv lq |] + | None, None -> [||] + + if op.Length = 0 then None + else + op + |> Cost.fold_costs p_sat p_dissat + |> Some + | Or(l, r, lweight, rweight) -> + let maybelq = best_q (l, (p_sat * lweight), 0.0) + let mayberq = best_q (r, (p_sat + rweight), 0.0) + match maybelq, mayberq with + | Some lq, Some rq -> + [| { ast = + QTree(Q.Or(lq.ast.castQUnsafe(), rq.ast.castQUnsafe())) + pkCost = lq.pkCost + rq.pkCost + 3u + satCost = + lweight * (2.0 + lq.satCost) + + rweight * (1.0 + rq.satCost) + dissatCost = 0.0 } + { ast = + QTree(Q.Or(rq.ast.castQUnsafe(), lq.ast.castQUnsafe())) + pkCost = rq.pkCost + lq.pkCost + 3u + satCost = + lweight * (1.0 + lq.satCost) + + rweight * (2.0 + rq.satCost) + dissatCost = 0.0 } |] + |> Cost.fold_costs p_sat p_dissat + |> Some + | _ -> None + | _ -> None + + and best_w (node : CompiledNode, p_sat : float, p_dissat : float) : Cost = + match node with + | Pk k -> + { ast = WTree(W.CheckSig(k)) + pkCost = 36u + satCost = 72.0 + dissatCost = 1.0 } + | Time t -> + let num_cost = scriptNumCost (!> t) + { ast = WTree(W.Time(t)) + pkCost = 6u + num_cost + satCost = 2.0 + dissatCost = 1.0 } + | Hash h -> + { ast = WTree(W.HashEqual(h)) + pkCost = 45u + satCost = 33.0 + dissatCost = 1.0 } + | _ -> + let c = best_e (node, p_sat, p_dissat) + { c with ast = WTree(W.CastE(c.ast.castEUnsafe())) + pkCost = c.pkCost + 2u } + + and best_f (node : CompiledNode, p_sat : float, p_dissat : float) : Cost = + match node with + | Pk k -> + { ast = FTree(F.CheckSig(k)) + pkCost = 36u + satCost = 72.0 + dissatCost = 1.0 } + | Multi(m, pks) -> + let num_cost = getPkCost m pks + { ast = FTree(F.CheckMultiSig(m, pks)) + pkCost = uint32 (num_cost + 34 * pks.Length) + 2u + satCost = 1.0 + 72.0 * float m + dissatCost = 0.0 } + | Time t -> + let num_cost = scriptNumCost (!> t) + { ast = FTree(F.Time(t)) + pkCost = 2u + num_cost + satCost = 0.0 + dissatCost = 0.0 } + | Hash h -> + { ast = FTree(F.HashEqual(h)) + pkCost = 40u + satCost = 33.0 + dissatCost = 0.0 } + | And(l, r) -> + let vl = best_v (l, p_sat, 0.0) + let vr = best_v (r, p_sat, 0.0) + let fl = best_f (l, p_sat, 0.0) + let fr = best_f (r, p_sat, 0.0) + + let possibleCases = + [| { parent = + FTree + (F.And(vl.ast.castVUnsafe(), fr.ast.castFUnsafe())) + left = vl + right = fr + condCombine = false } + { parent = + FTree + (F.And(vr.ast.castVUnsafe(), fl.ast.castFUnsafe())) + left = vr + right = fl + condCombine = false } |] + Cost.getMinimumCost possibleCases p_sat 0.0 0.5 0.5 + | Or(l, r, lweight, rweight) -> + let le_par = best_e (l, (p_sat * lweight), (p_sat + rweight)) + let re_par = best_e (r, (p_sat * rweight), (p_sat * lweight)) + let lf = best_f (l, (p_sat * lweight), 0.0) + let rf = best_f (r, (p_sat * rweight), 0.0) + let lv = best_v (l, (p_sat * lweight), 0.0) + let rv = best_v (r, (p_sat * rweight), 0.0) + let maybelq = best_q (l, (p_sat * lweight), 0.0) + let mayberq = best_q (r, (p_sat * rweight), 0.0) + + let possibleCases = + [| { parent = + FTree + (F.CascadeOr + (le_par.ast.castEUnsafe(), + rv.ast.castVUnsafe())) + left = le_par + right = rv + condCombine = false } + { parent = + FTree + (F.CascadeOr + (re_par.ast.castEUnsafe(), + lv.ast.castVUnsafe())) + left = re_par + right = lv + condCombine = false } + { parent = + FTree + (F.SwitchOr + (lf.ast.castFUnsafe(), rf.ast.castFUnsafe())) + left = lf + right = rf + condCombine = false } + { parent = + FTree + (F.SwitchOr + (rf.ast.castFUnsafe(), lf.ast.castFUnsafe())) + left = rf + right = lf + condCombine = false } + { parent = + FTree + (F.SwitchOrV + (lv.ast.castVUnsafe(), rv.ast.castVUnsafe())) + left = lv + right = rv + condCombine = false } + { parent = + FTree + (F.SwitchOrV + (rv.ast.castVUnsafe(), lv.ast.castVUnsafe())) + left = rv + right = lv + condCombine = false } |] + + let casesWithQ = + match maybelq, mayberq with + | Some lq, Some rq -> + Array.append possibleCases [| { parent = + FTree + (F.DelayedOr + (lq.ast.castQUnsafe + (), + rq.ast.castQUnsafe + ())) + left = lq + right = rq + condCombine = false } |] + | _ -> possibleCases + + Cost.getMinimumCost casesWithQ p_sat 0.0 lweight rweight + | Threshold(n, subs) -> + let num_cost = scriptNumCost n + let avg_cost = float n / float subs.Length + let e = + best_e + (subs.[0], (p_sat * avg_cost), + (p_dissat + p_sat * (1.0 - avg_cost))) + let ws = + subs + |> Array.map + (fun s -> + best_w + (s, (p_sat * avg_cost), + (p_dissat + p_sat * (1.0 - avg_cost)))) + let pk_cost = + ws + |> Array.fold (fun acc w -> acc + w.pkCost + 1u) + (2u + num_cost + e.pkCost) + let sat_cost = + ws |> Array.fold (fun acc w -> acc + w.satCost) e.satCost + let dissat_cost = + ws |> Array.fold (fun acc w -> acc + w.dissatCost) e.dissatCost + let wsast = ws |> Array.map (fun w -> w.ast.castWUnsafe()) + { ast = FTree(F.Threshold(n, e.ast.castEUnsafe(), wsast)) + pkCost = pk_cost + satCost = sat_cost * avg_cost + dissat_cost * (1.0 - avg_cost) + dissatCost = 0.0 } + + and best_v (node : CompiledNode, p_sat : float, p_dissat : float) : Cost = + match node with + | Pk k -> + { ast = VTree(V.CheckSig(k)) + pkCost = 35u + satCost = 0.0 + dissatCost = 0.0 } + | Multi(m, pks) -> + let num_cost = getPkCost m pks + { ast = VTree(V.CheckMultiSig(m, pks)) + pkCost = uint32 (num_cost + 34 * pks.Length + 1) + satCost = 1.0 + 72.0 * float m + dissatCost = 0.0 } + | Time t -> + let num_cost = scriptNumCost (!> t) + { ast = VTree(V.Time(t)) + pkCost = 2u + num_cost + satCost = 0.0 + dissatCost = 0.0 } + | Hash h -> + { ast = VTree(V.HashEqual(h)) + pkCost = 39u + satCost = 33.0 + dissatCost = 0.0 } + | And(l, r) -> + let lv = best_v (l, p_sat, 0.0) + let rv = best_v (r, p_sat, 0.0) + { ast = VTree(V.And(lv.ast.castVUnsafe(), rv.ast.castVUnsafe())) + pkCost = lv.pkCost + rv.pkCost + satCost = lv.satCost + rv.satCost + dissatCost = 0.0 } + | Or(l, r, lweight, rweight) -> + let le_par = best_e (l, (p_sat * lweight), (p_sat * rweight)) + let re_par = best_e (r, (p_sat * rweight), (p_sat * lweight)) + let lt = best_t (l, (p_sat * lweight), 0.0) + let rt = best_t (r, (p_sat * rweight), 0.0) + let lv = best_v (l, (p_sat * lweight), 0.0) + let rv = best_v (r, (p_sat * rweight), 0.0) + let maybelq = best_q (l, (p_sat * lweight), 0.0) + let mayberq = best_q (r, (p_sat * rweight), 0.0) + + let possibleCases = + [| { parent = + VTree + (V.CascadeOr + (le_par.ast.castEUnsafe(), + rv.ast.castVUnsafe())) + left = le_par + right = rv + condCombine = false } + { parent = + VTree + (V.CascadeOr + (re_par.ast.castEUnsafe(), + lv.ast.castVUnsafe())) + left = re_par + right = lv + condCombine = false } + { parent = + VTree + (V.SwitchOr + (lv.ast.castVUnsafe(), rv.ast.castVUnsafe())) + left = lv + right = rv + condCombine = false } + { parent = + VTree + (V.SwitchOr + (rv.ast.castVUnsafe(), lv.ast.castVUnsafe())) + left = rv + right = lv + condCombine = false } + { parent = + VTree + (V.SwitchOrT + (lt.ast.castTUnsafe(), rt.ast.castTUnsafe())) + left = lt + right = rt + condCombine = false } + { parent = + VTree + (V.SwitchOrT + (rt.ast.castTUnsafe(), lt.ast.castTUnsafe())) + left = rt + right = lt + condCombine = false } |] + + let casesWithQ = + match maybelq, mayberq with + | Some lq, Some rq -> + Array.append possibleCases [| { parent = + VTree + (V.DelayedOr + (lq.ast.castQUnsafe + (), + rq.ast.castQUnsafe + ())) + left = lq + right = rq + condCombine = false } |] + | _ -> possibleCases + + Cost.getMinimumCost casesWithQ p_sat 0.0 lweight rweight + | Threshold(n, subs) -> + let num_cost = scriptNumCost n + let avg_cost = float n / float subs.Length + let e = + best_e + (subs.[0], (p_sat * avg_cost), (p_sat * (1.0 - avg_cost))) + let ws = + subs + |> Array.map + (fun s -> + best_w + (s, (p_sat * avg_cost), (p_sat * (1.0 - avg_cost)))) + let pk_cost = + ws + |> Array.fold (fun acc w -> acc + w.pkCost + 1u) + (1u + num_cost + e.pkCost) + let sat_cost = + ws |> Array.fold (fun acc w -> acc + w.satCost) e.satCost + let dissat_cost = + ws |> Array.fold (fun acc w -> acc + w.dissatCost) e.dissatCost + let wsast = ws |> Array.map (fun w -> w.ast.castWUnsafe()) + { ast = VTree(V.Threshold(n, e.ast.castEUnsafe(), wsast)) + pkCost = pk_cost + satCost = sat_cost * avg_cost + dissat_cost * (1.0 - avg_cost) + dissatCost = 0.0 } + +type CompiledNode with + static member fromPolicy (p : Policy) = CompiledNode.fromPolicy p + member this.compile() = + let node = CompiledNode.best_t (this, 1.0, 0.0) + MiniScript.fromAST (node.ast) + + member this.compileUnsafe() = + match this.compile() with + | Ok miniscript -> miniscript + | Error e -> failwith e diff --git a/NBitcoin/Miniscript/MiniscriptDecompiler.fs b/NBitcoin/Miniscript/MiniscriptDecompiler.fs new file mode 100644 index 0000000000..0a3001d2a0 --- /dev/null +++ b/NBitcoin/Miniscript/MiniscriptDecompiler.fs @@ -0,0 +1,652 @@ +module FNBitcoin.MiniScriptDecompiler + +open NBitcoin +open System +open FNBitcoin.Utils.Parser +open MiniScriptAST + +/// Subset of Bitcoin Script which is used in Miniscript +type Token = + | BoolAnd + | BoolOr + | Add + | Equal + | EqualVerify + | CheckSig + | CheckSigVerify + | CheckMultiSig + | CheckMultiSigVerify + | CheckSequenceVerify + | FromAltStack + | ToAltStack + | Drop + | Dup + | If + | IfDup + | NotIf + | Else + | EndIf + | ZeroNotEqual + | Size + | Swap + | Tuck + | Verify + | Hash160 + | Sha256 + | Number of uint32 + | Hash160Hash of uint160 + | Sha256Hash of uint256 + | Pk of NBitcoin.PubKey + | Any + +type TokenCategory = + | BoolAnd + | BoolOr + | Add + | Equal + | EqualVerify + | CheckSig + | CheckSigVerify + | CheckMultiSig + | CheckMultiSigVerify + | CheckSequenceVerify + | FromAltStack + | ToAltStack + | Drop + | Dup + | If + | IfDup + | NotIf + | Else + | EndIf + | ZeroNotEqual + | Size + | Swap + | Tuck + | Verify + | Hash160 + | Sha256 + | Number + | Hash160Hash + | Sha256Hash + | Pk + | Any + +type ParseException(msg, ex : exn) = + inherit Exception(msg, ex) + new(msg) = ParseException(msg, null) + +type Token with + member this.GetItem() = + match this with + | Number n -> box n |> Some + | Hash160Hash h -> box h |> Some + | Sha256Hash h -> box h |> Some + | Pk pk -> box pk |> Some + | _ -> None + member this.GetItemUnsafe() = + match this with + | Number n -> n :> obj + | Hash160Hash h -> h :> obj + | Sha256Hash h -> h :> obj + | Pk pk -> pk :> obj + | i -> failwith (sprintf "failed to get item from %A" i) + + // usual reflection is not working for extracing name of each case. So we need this. + member this.GetCategory() = + match this with + | BoolAnd -> TokenCategory.BoolAnd + | BoolOr -> TokenCategory.BoolOr + | Add -> TokenCategory.Add + | Equal -> TokenCategory.Equal + | EqualVerify -> TokenCategory.EqualVerify + | CheckSig -> TokenCategory.CheckSig + | CheckSigVerify -> TokenCategory.CheckSigVerify + | CheckMultiSig -> TokenCategory.CheckMultiSig + | CheckMultiSigVerify -> TokenCategory.CheckMultiSigVerify + | CheckSequenceVerify -> TokenCategory.CheckSequenceVerify + | FromAltStack -> TokenCategory.FromAltStack + | ToAltStack -> TokenCategory.ToAltStack + | Drop -> TokenCategory.Drop + | Dup -> TokenCategory.Dup + | If -> TokenCategory.If + | IfDup -> TokenCategory.IfDup + | NotIf -> TokenCategory.NotIf + | Else -> TokenCategory.Else + | EndIf -> TokenCategory.EndIf + | ZeroNotEqual -> TokenCategory.ZeroNotEqual + | Size -> TokenCategory.Size + | Swap -> TokenCategory.Swap + | Tuck -> TokenCategory.Tuck + | Verify -> TokenCategory.Verify + | Hash160 -> TokenCategory.Hash160 + | Sha256 -> TokenCategory.Sha256 + | Number _ -> TokenCategory.Number + | Hash160Hash _ -> TokenCategory.Hash160Hash + | Sha256Hash _ -> TokenCategory.Sha256Hash + | Pk _ -> TokenCategory.Pk + | Any -> TokenCategory.Any + +let private tryGetItemFromOp (op: Op) = + let size = op.PushData.Length + match size with + | 20 -> Ok(Token.Hash160Hash(uint160 (op.PushData, false))) + | 32 -> Ok(Token.Sha256Hash(uint256 (op.PushData, false))) + | 33 -> + try + Ok(Token.Pk(NBitcoin.PubKey(op.PushData))) + with :? FormatException as ex -> + Error(ParseException("Invalid Public Key", ex)) + | _ -> + match op.GetInt().HasValue with + | true -> + let v = op.GetInt().Value + /// no need to check v >= 0 since it is checked in NBitcoin side + Ok(Token.Number(uint32 v)) + | false -> + Error(ParseException(sprintf "Invalid push with Opcode %O" op)) + +let private castOpToToken (op : Op) : Result = + match (op.Code) with + | OpcodeType.OP_BOOLAND -> Ok(Token.BoolAnd) + | OpcodeType.OP_BOOLOR -> Ok(Token.BoolOr) + | OpcodeType.OP_EQUAL -> Ok(Token.Equal) + | OpcodeType.OP_EQUALVERIFY -> Ok(Token.EqualVerify) + | OpcodeType.OP_CHECKSIG -> Ok(Token.CheckSig) + | OpcodeType.OP_CHECKSIGVERIFY -> Ok(Token.CheckSigVerify) + | OpcodeType.OP_CHECKMULTISIG -> Ok(Token.CheckMultiSig) + | OpcodeType.OP_CHECKMULTISIGVERIFY -> Ok(Token.CheckMultiSigVerify) + | OpcodeType.OP_CHECKSEQUENCEVERIFY -> Ok(Token.CheckSequenceVerify) + | OpcodeType.OP_FROMALTSTACK -> Ok(Token.FromAltStack) + | OpcodeType.OP_TOALTSTACK -> Ok(Token.ToAltStack) + | OpcodeType.OP_DROP -> Ok(Token.Drop) + | OpcodeType.OP_DUP -> Ok(Token.Dup) + | OpcodeType.OP_IF -> Ok(Token.If) + | OpcodeType.OP_IFDUP -> Ok(Token.IfDup) + | OpcodeType.OP_NOTIF -> Ok(Token.NotIf) + | OpcodeType.OP_ELSE -> Ok(Token.Else) + | OpcodeType.OP_ENDIF -> Ok(Token.EndIf) + | OpcodeType.OP_0NOTEQUAL -> Ok(Token.ZeroNotEqual) + | OpcodeType.OP_SIZE -> Ok(Token.Size) + | OpcodeType.OP_SWAP -> Ok(Token.Swap) + | OpcodeType.OP_TUCK -> Ok(Token.Tuck) + | OpcodeType.OP_VERIFY -> Ok(Token.Verify) + | OpcodeType.OP_HASH160 -> Ok(Token.Hash160) + | OpcodeType.OP_SHA256 -> Ok(Token.Sha256) + | OpcodeType.OP_ADD -> Ok(Token.Add) + | OpcodeType.OP_0 -> Ok(Token.Number 0u) + | OpcodeType.OP_1 -> Ok(Token.Number 1u) + | OpcodeType.OP_2 -> Ok(Token.Number 2u) + | OpcodeType.OP_3 -> Ok(Token.Number 3u) + | OpcodeType.OP_4 -> Ok(Token.Number 4u) + | OpcodeType.OP_5 -> Ok(Token.Number 5u) + | OpcodeType.OP_6 -> Ok(Token.Number 6u) + | OpcodeType.OP_7 -> Ok(Token.Number 7u) + | OpcodeType.OP_8 -> Ok(Token.Number 8u) + | OpcodeType.OP_9 -> Ok(Token.Number 9u) + | OpcodeType.OP_10 -> Ok(Token.Number 10u) + | OpcodeType.OP_11 -> Ok(Token.Number 11u) + | OpcodeType.OP_12 -> Ok(Token.Number 12u) + | OpcodeType.OP_13 -> Ok(Token.Number 13u) + | OpcodeType.OP_14 -> Ok(Token.Number 14u) + | OpcodeType.OP_15 -> Ok(Token.Number 15u) + | OpcodeType.OP_16 -> Ok(Token.Number 16u) + | otherOp when (byte 0x01) <= (byte otherOp) && (byte otherOp) < (byte 0x4B) -> + tryGetItemFromOp op + | otherOp when (byte 0x4B) <= (byte otherOp) -> + Error(ParseException(sprintf "MiniScript does not support pushdata bigger than 33. Got %s" (otherOp.ToString()))) + | unknown -> + Error(ParseException(sprintf "Unknown Opcode to MiniScript %s" (unknown.ToString()))) + +type State = { + ops: Op[] + position: int +} + +type TokenParser = Parser + +let nextToken state = + if state.ops.Length - 1 < state.position then + state, None + else + let newState = { state with position = state.position + 1 } + let tk = state.ops.[state.position] + newState, Some(tk) + + +module TokenParser = + let pToken (cat: TokenCategory) = + let name = sprintf "pToken %A" cat + let innerFn state = + if state.position < 0 then + Error(name, "no more input", 0) + else + let pos = state.position + let ops = state.ops.[pos] + let r = castOpToToken ops + match r with + | Error pex -> + let msg = sprintf "opcode %s is not supported by MiniScript %s" ops.Name pex.Message + Error(name, msg, pos) + | Ok actualToken -> + let actualCat = actualToken.GetCategory() + if cat = Any || cat = actualCat then + let newState = { state with position=state.position - 1 } + let item = actualToken.GetItem() + Ok (actualToken.GetItem(), newState) + else + let msg = sprintf "token is not the one expected \nactual: %A\nexpected: %A" actualCat cat + Error(name, msg, pos) + {parseFn=innerFn; name=name} + let mutable pENoPostProcess, pENoPostProcessImpl = createParserForwardedToRef() + + let mutable pW, pWImpl = createParserForwardedToRef() + let mutable pE, pEImpl = createParserForwardedToRef() + let mutable pV, pVImpl = createParserForwardedToRef() + let mutable pQ, pQImpl = createParserForwardedToRef() + let mutable pT, pTImpl = createParserForwardedToRef() + let mutable pF, pFImpl = createParserForwardedToRef() + + // ---- common helpers ---- + let private pTime1 = (pToken EndIf) + >>. (pToken (Drop)) + >>. (pToken (CheckSequenceVerify)) + >>. (pToken Number) + .>> (pToken If) .>> (pToken Dup) + + // TODO: restrict to only specific number + let private pNumberN n = + let numberValidateParser (maybeNumberObj: obj option) = + let name = sprintf "number validator %d" n + let innerFn state = + let actual = maybeNumberObj.Value :?> uint32 + if actual = n then + Ok(n, state) + else + let msg = sprintf "failed in number validation\nexpected: %d\nactual: %d" n actual + Error(name, msg, state.position) + + {parseFn=innerFn;name=name} + (pToken Number) >>= numberValidateParser + + let private multisigBind (expectedType: ASTType) (nAndPks: obj option * obj option list, maybeMObj: obj option) = + let n = (fst nAndPks).Value :?> uint32 + let pks = (snd nAndPks) + |> List.rev + |> List.toArray + |> Array.map(fun pkobj -> pkobj.Value :?> PubKey) + let m = maybeMObj.Value :?> uint32 + let name = sprintf "Parser for Multisig of type %A" expectedType + let innerFn (state: State) = + if pks.Length = (int n) then + match expectedType with + | EExpr -> Ok(ETree(E.CheckMultiSig(m, pks)), state) + | VExpr -> Ok(VTree(V.CheckMultiSig(m, pks)), state) + | _ -> failwith "unreachable!" + else + let msg = (sprintf "Invalid Multisig Script\nn was %d but actual pubkey length was %d" n pks.Length) + Error(name, msg, state.position) + + {parseFn=innerFn; name=name} + + + // ---- W --------- + let pWCheckSig = (pToken CheckSig) + >>. (pToken Pk) .>> (pToken Swap) + |>> fun maybePKObj -> WTree(W.CheckSig (maybePKObj.Value :?> NBitcoin.PubKey)) + "Parser W.Checksig" + + let pWTime = (pTime1 + .>> (pToken Swap) + |>> fun o -> WTree(W.Time(LockTime(o.Value :?> uint32)))) + "Parser W.Time" + + let pWCastE = (pToken FromAltStack) + >>. (pE) .>> (pToken ToAltStack) + |>> fun expr -> + WTree(W.CastE(expr.castEUnsafe())) + + let pWHashEqual = (pToken EndIf >>. pF .>> pToken If .>> pToken ZeroNotEqual .>> pToken Size .>> pToken Swap) + >>=( + fun ast -> + let name = "pWHashEqualValidator" + let innerFn state = + match ast.castF() with + | Ok fexpr -> + match fexpr with + | F.HashEqual hash -> + Ok(WTree(W.HashEqual(hash)), state) + | e -> + let msg = sprintf "unexpected expr\nexpected: F.HashEqual\nactual: %A" e + Error(name, msg, state.position) + | Error e -> failwith "unreachable" + {parseFn=innerFn; name=name} + ) + + // ---- E --------- + let pEParallelAnd = ((pToken BoolAnd) + >>. pW .>>. pE + |>> fun (astW, astE) -> + ETree(E.ParallelAnd(astE.castEUnsafe(), astW.castWUnsafe()))) + "Parser E.ParallelAnd" + + let pEParallelOr = ((pToken BoolOr) + >>. pW .>>. pE + |>> fun (astW, astE) -> + ETree(E.ParallelOr(astE.castEUnsafe(), astW.castWUnsafe()))) + "Parser E.ParallelAnd" + + let pEThreshold = (((pToken Equal) >>. (pToken Number)) + .>>. (many1 (pToken Add >>. pW)) + .>>. (pENoPostProcess) + |>> fun (kws, east) -> + let k = (fst kws).Value :?> uint32 + let e = east.castEUnsafe() + let ws = (snd kws) + |> List.toArray + |> Array.rev + |> Array.map(fun ast -> ast.castWUnsafe()) + ETree(E.Threshold(k, e, ws)) + ) "Parser E.Threshold" + + let pECheckSig = (pToken CheckSig) + >>. (pToken Pk) + |>> fun maybePKObj -> ETree(E.CheckSig (maybePKObj.Value :?> NBitcoin.PubKey)) + "Parser E.Checksig" + + let pECheckMultisig = (pToken CheckMultiSig) >>. (pToken Number) + .>>. (many1 (pToken Pk)) + .>>. (pToken Number) + >>= multisigBind EExpr + + let pETime = pWTime + <|> (pTime1 |>> fun maybeNumberObj -> ETree(E.Time(LockTime(maybeNumberObj.Value :?> uint32)))) + + + let private pLikelyPrefix = (pToken EndIf) >>. pNumberN(0u) >>. pToken Else >>. pF + + let pEUnlikely = pLikelyPrefix + .>> pToken If + |>> fun (fexpr) -> ETree(E.Unlikely(fexpr.castFUnsafe())) + + let pELikely = pLikelyPrefix + .>> pToken NotIf + |>> fun (fexpr) -> ETree(E.Likely(fexpr.castFUnsafe())) + + let pECascadeAnd = (pToken EndIf) >>. pF .>> pToken Else + .>>. ((pNumberN 0u) >>. (pToken NotIf) >>. pE) + |>> fun (rightF, leftE) -> + ETree(E.CascadeAnd(leftE.castEUnsafe(), rightF.castFUnsafe())) + + let pESwitchOrLeft = ((pToken EndIf) >>. pF .>> pToken Else) + .>>. ((pE) .>> pToken If) + |>> fun (rightF, leftE) -> + ETree(E.SwitchOrLeft(leftE.castEUnsafe(), rightF.castFUnsafe())) + + let pESwitchOrRight = (pToken EndIf >>. pF .>> pToken Else) + .>>. (pE .>> pToken NotIf) + |>> fun (rightF, leftE) -> + ETree(E.SwitchOrRight(leftE.castEUnsafe(), rightF.castFUnsafe())) + + // ---- V ------- + let pVDelayedOr = (((pToken CheckSigVerify) + >>. (pToken EndIf) >>. pQ) .>>. (pToken Else >>. pQ .>> pToken If) + |>> fun (q1, q2) -> + VTree(V.DelayedOr(q2.castQUnsafe(), q1.castQUnsafe())) + ) "P.VDelayedOr" + + let pVHashEqual = ((pToken EqualVerify) >>. ((pToken Sha256Hash) + .>> (pToken Sha256) .>> (pToken EqualVerify) .>> (pNumberN 32u) .>> (pToken Size)) + |>> (fun maybeHashObj -> + let hash = maybeHashObj.Value :?> uint256 + VTree(V.HashEqual(hash)) + ) + ) "Parser pVHashEqual" + + let pVThreshold = ((pToken EqualVerify) >>. (pToken Number)) + .>>. (many1 (pToken Add >>. pW)) + .>>. (pE) + |>> fun (kws, east) -> + let k = (fst kws).Value :?> uint32 + let e = east.castEUnsafe() + let ws = (snd kws) + |> List.toArray + |> Array.rev + |> Array.map(fun ast -> ast.castWUnsafe()) + VTree(V.Threshold(k, e, ws)) + + let pVCheckSig = ((pToken CheckSigVerify) + >>. (pToken Pk) + |>> fun maybePkObj -> VTree(V.CheckSig(maybePkObj.Value :?> PubKey)) + ) "Parser pVCheckSig" + + let pVCheckMultisig = (pToken CheckMultiSigVerify) + >>. (pToken Number) + .>>. (many1 (pToken Pk)) + .>>. (pToken Number) + >>= multisigBind VExpr + + let pVTime = pToken Drop >>. pToken CheckSequenceVerify >>. pToken Number + |>> fun maybeNumberObj -> + let n = maybeNumberObj.Value :?> uint32 + VTree(V.Time(LockTime(n))) + + let pVSwitchOr = (pToken EndIf >>. pV .>> pToken Else) + .>>. (pV .>> pToken If) + |>> fun (rightV, leftV) -> + VTree(V.SwitchOr(leftV.castVUnsafe(), rightV.castVUnsafe())) + + let pVCascadeOr = (pToken EndIf >>. pV .>> pToken NotIf) + .>>. pE + |>> fun (rightV, leftE) -> + VTree(V.CascadeOr(leftE.castEUnsafe(), rightV.castVUnsafe())) + + let pVSwitchOrT = (pToken Verify >>. pToken EndIf >>. pT .>> pToken Else) + .>>. (pT .>> pToken If) + |>> fun (rightT, leftT) -> + VTree(V.SwitchOrT(leftT.castTUnsafe(), rightT.castTUnsafe())) + + // ---- Q ------- + let pQPubKey = ((pToken Pk) + |>> fun pk -> QTree(Q.Pubkey(pk.Value :?> NBitcoin.PubKey)) + ) "P.QPubKey" + + + let pQOr = ((pToken EndIf) >>. pQ) + .>>. ((pToken Else) >>. pQ .>> pToken(If)) + |>> fun (l, r) -> QTree(Q.Or(r.castQUnsafe(), l.castQUnsafe())) + // ---- T ------- + + let pTHashEqual = ((pToken Equal + >>. pToken Sha256Hash + .>> pToken Sha256 + .>> pToken EqualVerify + .>> pNumberN 32u + .>> pToken Size) + |>> fun maybeHash -> TTree(T.HashEqual(maybeHash.Value :?> uint256))) + "Parser T.HashEqual" + + let pTDelayedOr = ((pToken CheckSig) >>. (pToken EndIf) + >>. pQ .>>. (pToken Else >>. pQ .>> pToken If) + |>> fun (q1, q2) -> TTree(T.DelayedOr(q2.castQUnsafe(), q2.castQUnsafe())) + ) "Parser T.DelayedOr" + + let pTTime = ((pToken CheckSequenceVerify) >>. (pToken Number) + |>> fun (maybeNumberObj) -> + let n = maybeNumberObj.Value :?> uint32 + TTree(T.Time(LockTime(n))) + ) "Parser T.Time" + + let pTSwitchOr = ((pToken EndIf >>. pT .>> pToken Else) + .>>. (pT .>> pToken If) + |>> fun (rightT, leftT) -> + TTree(T.SwitchOr(leftT.castTUnsafe(), rightT.castTUnsafe())) + ) "Parser T.SwitchOr" + + let pTCascadeOr = (pToken EndIf >>. pT .>> pToken NotIf .>> pToken IfDup) + .>>. pE + |>> fun (rightT, leftE) -> + TTree(T.CascadeOr(leftE.castEUnsafe(), rightT.castTUnsafe())) + // ---- F ------- + let pFTime = (pToken ZeroNotEqual) + >>. (pToken CheckSequenceVerify) + >>. (pToken Number) + |>> fun (maybeNumberObj) -> + let n = maybeNumberObj.Value :?> uint32 + FTree(F.Time(LockTime(n))) + + let pFSwitchOr = ((pToken EndIf) >>. pF .>> pToken Else) + .>>. (pF .>> pToken If) + |>> fun (rightF, leftF) -> + FTree(F.SwitchOr(leftF.castFUnsafe(), rightF.castFUnsafe())) + + let pFFromV = (pNumberN 1u >>. pV) + >>=( + fun ast -> + let name = "pFFromV" + let innerFn state = + match ast.castVUnsafe() with + | V.CheckSig pk -> + Ok(FTree(F.CheckSig(pk)), state) + | V.CheckMultiSig (m, pks) -> + Ok(FTree(F.CheckMultiSig(m, pks)), state) + | V.HashEqual hash -> + Ok(FTree(F.HashEqual(hash)), state) + | V.Threshold(k, e, ws)-> + Ok(FTree(F.Threshold(k, e, ws)), state) + | V.CascadeOr(l, r)-> + Ok(FTree(F.CascadeOr(l, r)), state) + | V.SwitchOr(l, r)-> + Ok(FTree(F.SwitchOrV(l, r)), state) + | V.DelayedOr(l, r)-> + Ok(FTree(F.DelayedOr(l, r)), state) + | e -> + let msg = sprintf "unexpected expr\nactual: %A" e + Error(name, msg, state.position) + {parseFn=innerFn; name=name} + ) + + // ---- Composition ---- + let mutable SubExpressionParser, SubExpressionParserImpl = createParserForwardedToRef() + let private shouldPostProcess(info: AST * State) = + let ast = fst info + let state = snd info + if state.position = -1 then + Ok(false) + else + /// If last opcode is a certain one, no need for post processing. + let checkLastOp state = + let lastOp = state.ops.[state.position] + let lastToken = castOpToToken lastOp + match lastToken with + | Error e -> Error ("PostProcess", + sprintf "Unexpected Exception in post process\nerror: %A" e, + 0) + | Ok(Token.If) + | Ok(Token.NotIf) + | Ok(Token.Else) -> Ok(false) + | Ok(Token.ToAltStack) -> Ok(false) + | _ -> Ok(true) + + match ast.GetASTType() with + | TExpr + | VExpr + | EExpr + | QExpr + | FExpr -> + checkLastOp state + | _ -> Ok(false) + + let postProcess (ast: AST) = + let name = "postProcess" + let innerFn state = + match shouldPostProcess(ast, state) with + | Error e -> + Error e + | Ok(false) -> + Ok((ast), state) + | Ok(true) -> + let rightAST = ast + + match run SubExpressionParser state with + | Error e -> + Error e + | Ok result -> + let leftAST, state = result + let leftV = leftAST.castVUnsafe() + match (rightAST.GetASTType()) with + | TExpr -> Ok(TTree(T.And(leftV, rightAST.castTUnsafe())), state) + | EExpr -> + Ok(TTree(T.And(leftV, rightAST.castTUnsafe())), state) + | QExpr -> Ok(QTree(Q.And(leftV, rightAST.castQUnsafe())), state) + | FExpr -> + match rightAST.castT() with + | Ok t -> Ok(TTree(t), state) + | Error _ -> Ok(FTree(F.And(leftV, rightAST.castFUnsafe())), state) + | VExpr -> Ok(VTree(V.And(leftV, rightAST.castVUnsafe())), state) + | _ -> failwith "unreachable" + + {parseFn=innerFn; name = name} + + /// validate AST is a specific type + let pTryCastToType (expected: ASTType) (ast: AST) = + let name = "pIsTypeOf" + let innerFn state = + if ast.GetASTType() = expected then + Ok(ast, state) + else if expected = TExpr && ast.IsT() then + Ok(TTree(ast.castTUnsafe()), state) + else + let msg = sprintf "AST is not the expected type\nexpected: %A\nactual: %A" expected ast + Error(name, msg, state.position) + {parseFn=innerFn; name=name} + + do pENoPostProcessImpl := choice [ + pECheckSig + pEParallelAnd + pEParallelOr + pEThreshold + pECheckMultisig + pETime + pESwitchOrLeft + pESwitchOrRight + pELikely + pEUnlikely + pECascadeAnd + ] + + do SubExpressionParserImpl := (choice [ + pWCheckSig; pWTime; pWCastE; pWHashEqual + pENoPostProcess + pVDelayedOr + pVHashEqual + pVThreshold + pVCheckSig + pVCheckMultisig + pVTime + pVSwitchOr + pVCascadeOr + pVSwitchOrT + pQPubKey; pQOr + pTHashEqual; pTDelayedOr; pTTime; pTSwitchOr; pTCascadeOr + pFTime + pFSwitchOr + pFFromV + ] >>= postProcess) "SubexpressionParser" + + do pWImpl := SubExpressionParser >>= pTryCastToType WExpr + do pEImpl := SubExpressionParser >>= pTryCastToType EExpr + do pVImpl := SubExpressionParser >>= pTryCastToType VExpr + do pQImpl := SubExpressionParser >>= pTryCastToType QExpr + do pTImpl := SubExpressionParser >>= pTryCastToType TExpr + do pFImpl := SubExpressionParser >>= pTryCastToType FExpr + +let parseScript (sc: Script) = + let ops = (sc.ToOps() |> Seq.toArray) + let initialState = {ops=ops; position=ops.Length - 1} + run TokenParser.SubExpressionParser initialState |> Result.map(fst) + +let parseScriptUnsafe sc = + match parseScript sc with + | Ok r -> r + | Error e -> failwith (printParserError e) diff --git a/NBitcoin/Miniscript/MiniscriptParser.fs b/NBitcoin/Miniscript/MiniscriptParser.fs new file mode 100644 index 0000000000..eef94ae397 --- /dev/null +++ b/NBitcoin/Miniscript/MiniscriptParser.fs @@ -0,0 +1,141 @@ +module FNBitcoin.MiniScriptParser + +open NBitcoin +open System.Text.RegularExpressions +open System + +type Policy = + | Key of PubKey + | Multi of uint32 * PubKey [] + | Hash of uint256 + | Time of NBitcoin.LockTime + | Threshold of uint32 * Policy [] + | And of Policy * Policy + | Or of Policy * Policy + | AsymmetricOr of Policy * Policy + override this.ToString() = + match this with + | Key k1 -> sprintf "pk(%s)" (string (k1.ToHex())) + | Multi(m, klist) -> + klist + |> Seq.map (fun k -> string (k.ToHex())) + |> Seq.reduce (fun a b -> sprintf "%s,%s" a b) + |> sprintf "multi(%d,%s)" m + | Hash h -> sprintf "hash(%s)" (string (h.ToString())) + | Time t -> sprintf "time(%s)" (string (t.ToString())) + | Threshold(m, plist) -> + plist + |> Array.map (fun p -> p.ToString()) + |> Array.reduce (fun a b -> sprintf "%s,%s" a b) + |> sprintf "thres(%d,%s)" m + | And(p1, p2) -> sprintf "and(%s,%s)" (p1.ToString()) (p2.ToString()) + | Or(p1, p2) -> sprintf "or(%s,%s)" (p1.ToString()) (p2.ToString()) + | AsymmetricOr(p1, p2) -> + sprintf "aor(%s,%s)" (p1.ToString()) (p2.ToString()) + + +// parser +let quoted = Regex(@"\((.*)\)") + +let rec (|SurroundedByBrackets|_|) (s : string) = + let s2 = s.Trim() + if s2.StartsWith("(") && s2.EndsWith(")") then + Some(s2.TrimStart('(').TrimEnd(')')) + else None + +let (|Expression|_|) (prefix : string) (s : string) = + let s = s.Trim() + if s.StartsWith(prefix) then Some(s.Substring(prefix.Length)) + else None + +let (|PubKeyPattern|_|) (s : string) = + try + Some(PubKey(s)) + with :? FormatException as ex -> None + +let (|PubKeysPattern|_|) (s : string) = + let s = s.Trim().Split(',') + match UInt32.TryParse(s.[0]) with + | (false, _) -> None + | (true, i) -> + try + let pks = + s.[1..s.Length - 1] |> Array.map (fun hex -> PubKey(hex.Trim())) + Some(i, pks) + with :? FormatException -> None + +let (|Hash|_|) (s : string) = + try + Some(uint256 (s.Trim())) + with :? FormatException -> None + +let (|Time|_|) (s : string) = + try + Some(uint32 (s.Trim())) + with :? FormatException -> None + +// Split with "," but only when not surroounded by parenthesis +let rec safeSplit (s : string) (acc : string list) (index : int) (openNum : int) + (currentChunk : char []) = + if s.Length = index then + let lastChunk = String.Concat(Array.append currentChunk [| ')' |]) + let lastAcc = List.append acc [ lastChunk ] + lastAcc |> List.toArray + else + let c = s.[index] + if c = '(' then + let newChunk = Array.append currentChunk [| c |] + safeSplit s acc (index + 1) (openNum + 1) newChunk + elif c = ')' then + let newChunk = Array.append currentChunk [| c |] + safeSplit s acc (index + 1) (openNum - 1) newChunk + elif openNum = 0 && (c = ',') then + let newElement = String.Concat(currentChunk) + let newAcc = List.append acc [ newElement ] + safeSplit s newAcc (index + 1) (openNum) [||] + else + let newChunk = Array.append currentChunk [| c |] + safeSplit s acc (index + 1) (openNum) newChunk + +let rec (|Policy|_|) s = + let s = Regex.Replace(s, @"[|\s|\n|\r\n]+", "") + match s with + | Expression "pk" (SurroundedByBrackets(PubKeyPattern pk)) -> Some(Key pk) + | Expression "multi" (SurroundedByBrackets(PubKeysPattern pks)) -> + Multi((fst pks), (snd pks)) |> Some + | Expression "hash" (SurroundedByBrackets(Hash hash)) -> Some(Hash hash) + | Expression "time" (SurroundedByBrackets(Time t)) -> Some(Time(LockTime(t))) + // recursive matches + | Expression "thres" (SurroundedByBrackets(Threshold thres)) -> + Some(Threshold(thres)) + | Expression "and" (SurroundedByBrackets(And(expr1, expr2))) -> + And(expr1, expr2) |> Some + | Expression "or" (SurroundedByBrackets(Or(expr1, expr2))) -> + Or(expr1, expr2) |> Some + | Expression "aor" (SurroundedByBrackets(AsymmetricOr(expr1, expr2))) -> + AsymmetricOr(expr1, expr2) |> Some + | _ -> None + +and (|Threshold|_|) (s : string) = + let s = safeSplit s [] 0 0 [||] + let thresholdStr = s.[0] + match UInt32.TryParse(thresholdStr) with + | (true, threshold) -> + let subPolicy = s.[1..s.Length - 1] |> Array.choose ((|Policy|_|)) + if subPolicy.Length <> s.Length - 1 then None + else Some(threshold, subPolicy) + | (false, _) -> None + +and (|And|_|) (s : string) = twoSubExpressions s + +and (|Or|_|) (s : string) = twoSubExpressions s + +and (|AsymmetricOr|_|) (s : string) = twoSubExpressions s + +and twoSubExpressions (s : string) = + let s = safeSplit s [] 0 0 [||] + if s.Length <> 2 then None + else + let subPolicies = s |> Array.choose ((|Policy|_|)) + if subPolicies.Length <> s.Length then None + else Some(subPolicies.[0], subPolicies.[1]) diff --git a/NBitcoin/Miniscript/Program.fs b/NBitcoin/Miniscript/Program.fs new file mode 100644 index 0000000000..09b1452193 --- /dev/null +++ b/NBitcoin/Miniscript/Program.fs @@ -0,0 +1,8 @@ +// Learn more about F# at http://fsharp.org + +open System + +[] +let main argv = + printfn "Hello World from F#!" + 0 // return an integer exit code diff --git a/NBitcoin/Miniscript/Satisfy.fs b/NBitcoin/Miniscript/Satisfy.fs new file mode 100644 index 0000000000..6ff91d36f6 --- /dev/null +++ b/NBitcoin/Miniscript/Satisfy.fs @@ -0,0 +1,322 @@ +namespace FNBitcoin.Satisfy + +module Satisfy = + open FNBitcoin.MiniScriptAST + open FNBitcoin.Utils + open NBitcoin + open System + + type SignatureProvider = PubKey -> TransactionSignature option + type PreImageHash = PreImagehash of uint256 + type PreImage = PreImage of uint256 + type PreImageProvider = PreImageHash -> PreImage + + type ProviderSet = (SignatureProvider * PreImageProvider * TimeSpan) + + type CSVOffset = BlockHeight of uint32 | UnixTime of DateTimeOffset + type FailureCase = + | MissingSig of PubKey list + | NotMatured of CSVOffset + | LockTimeTypeMismatch + | Nested of FailureCase list + + type SatisfiedItem = + | PreImage of byte[] + | Signature of TransactionSignature + | RawPush of byte[] + + type SatisfactionResult = Result + + let satisfyCost (res: SatisfiedItem list): int = failwith "" + + let (>>=) xR f = Result.bind f xR + + // ------- helpers -------- + let satisfyCheckSig (keyFn: SignatureProvider) k = + match keyFn k with + | None -> Error (MissingSig [k]) + | Some(txSig) -> Ok([Signature(txSig)]) + + let satisfyCheckMultisig (keyFn: SignatureProvider) (m, pks) = + let maybeSigList = pks + |> Array.map(keyFn) + |> Array.toList + + let sigList = maybeSigList |> List.choose(id) |> List.map(Signature) + + if sigList.Length >= (int32 m) then + Ok(sigList) + else + let sigNotFoundPks = maybeSigList + |> List.zip (pks |> Array.toList) + |> List.choose(fun (pk, maybeSig) -> + if maybeSig.IsNone then Some(pk) else None) + Error(MissingSig(sigNotFoundPks)) + + let satisfyHashEqual (hashFn: PreImageProvider) h = + failwith "" + + let satisfyCSV (age: LockTime) (t: LockTime) = + let offset = t.Value - age.Value + if + (age.IsHeightLock && t.IsHeightLock) + then + if (offset > 0u) then + Error(NotMatured(BlockHeight offset)) + else + Ok([]) + else if + (age.IsTimeLock && t.IsTimeLock) + then + if (offset > 0u) then + Error(NotMatured(UnixTime(DateTimeOffset.FromUnixTimeSeconds(int64 (offset))))) + else + Ok([]) + else + Error(LockTimeTypeMismatch) + + let rec satisfyThreshold (keyFn, hashFn, age) (k, e, ws): SatisfactionResult = + let flatten l = List.collect id l + let wsList = ws |> Array.toList + + let wResult = wsList + |> List.rev + |> List.map(satisfyW(keyFn, hashFn, age)) + let wOkList = wResult + |> List.filter(fun wr -> match wr with | Ok w -> true;| _ -> false) + |> List.map(fun wr -> match wr with | Ok w -> w; | _ -> failwith "unreachable") + + let wErrorList = wResult + |> List.filter(fun wr -> match wr with | Error w -> true;| _ -> false) + |> List.map(fun wr -> match wr with | Error e -> e; | _ -> failwith "unreachable") + + let eResult = satisfyE (keyFn, hashFn, age) e |> List.singleton + let eOkList = eResult + |> List.filter(fun wr -> match wr with | Ok w -> true;| _ -> false) + |> List.map(fun wr -> match wr with | Ok w -> w; | _ -> failwith "unreachable") + + let eErrorList = eResult + |> List.filter(fun wr -> match wr with | Error w -> true;| _ -> false) + |> List.map(fun wr -> match wr with | Error e -> e; | _ -> failwith "unreachable") + + + let satisfiedTotal = wOkList.Length + eOkList.Length + + if satisfiedTotal >= (int k) then + let dissatisfiedW = List.zip wsList wResult + |> List.choose(fun (w, wr) -> match wr with | Error _ -> Some(w); | _ -> None) + |> List.map(dissatisfyW) + let dissatisfiedE = match eResult.[0] with | Error _ -> [dissatisfyE e] | Ok _ -> [] + Ok(flatten (wOkList @ eOkList @ dissatisfiedW @ dissatisfiedE)) + else + Error(Nested(wErrorList @ eErrorList)) + + and satisfyAST (keyFn, hashFn, age) (ast: AST) = + match ast.GetASTType() with + | EExpr -> satisfyE (keyFn, hashFn, age) (ast.castEUnsafe()) + | FExpr -> satisfyF (keyFn, hashFn, age) (ast.castFUnsafe()) + | WExpr -> satisfyW (keyFn, hashFn, age) (ast.castWUnsafe()) + | QExpr -> satisfyQ (keyFn, hashFn, age) (ast.castQUnsafe()) + | TExpr -> satisfyT (keyFn, hashFn, age) (ast.castTUnsafe()) + | VExpr -> satisfyV (keyFn, hashFn, age) (ast.castVUnsafe()) + + and dissatisfyAST (ast: AST) = + match ast.GetASTType() with + | EExpr -> dissatisfyE (ast.castEUnsafe()) + | WExpr -> dissatisfyW (ast.castWUnsafe()) + | _ -> failwith "unreachable" + + and satisfyParallelOr providers (l: AST, r: AST) = + match (satisfyAST providers l), (satisfyAST providers r) with + | Ok(lItems), Ok (rItems) -> // return the one has less cost + let lDissat = dissatisfyAST l + let rDissat = dissatisfyAST r + if (satisfyCost rDissat + satisfyCost lItems <= satisfyCost rItems + satisfyCost lDissat) then + Ok(rDissat @ lItems) + else + Ok(lDissat @ rItems) + | Ok(lItems), Error _ -> + let rDissat = dissatisfyAST r + Ok(lItems @ rDissat) + | Error _, Ok(rItems) -> + let lDissat = dissatisfyAST l + Ok(rItems @ lDissat) + | Error e1, Error e2 -> Error(Nested([e1; e2])) + + and satisfyCascadeOr providers (l, r) = + match (satisfyAST providers l), (satisfyAST providers r) with + | Error e, Error _ -> Error e + | Ok lItems, Error _ -> Ok(lItems) + | Error _, Ok rItems -> + let lDissat = dissatisfyAST l + Ok(rItems @ lDissat) + | Ok lItems, Ok rItems -> + let lDissat = dissatisfyAST l + if satisfyCost lItems <= satisfyCost rItems + satisfyCost lDissat then + Ok(lItems) + else + Ok(rItems) + + and satisfySwitchOr providers (l, r) = + match (satisfyAST providers l), (satisfyAST providers r) with + | Error e, Error _ -> Error e + | Ok lItems, Error _ -> Ok(lItems @ [RawPush([|byte 1|])]) + | Error e, Ok rItems -> Ok(rItems @ [RawPush([|byte 0|])]) + | Ok lItems, Ok rItems -> // return the one has less cost + if satisfyCost(lItems) + 2 <= satisfyCost rItems + 1 then + Ok(lItems @ [RawPush([|byte 1|])]) + else + Ok(rItems @ [RawPush([|byte 0|])]) + + and satisfyE providers (e: E) = + let (keyFn, hashFn, age) = providers + match e with + | E.CheckSig k -> satisfyCheckSig keyFn k + | E.CheckMultiSig(m, pks) -> satisfyCheckMultisig keyFn (m, pks) + | E.Time t -> satisfyCSV age t + | E.Threshold i -> + satisfyThreshold providers i + | E.ParallelAnd(e, w) -> + satisfyE (keyFn, hashFn, age) e + >>= (fun eitem -> satisfyW(keyFn, hashFn, age) w >>= (fun witem -> Ok(eitem @ witem))) + | E.CascadeAnd(e, f) -> + satisfyE (keyFn, hashFn, age) e + >>= (fun eitem -> satisfyF(keyFn, hashFn, age) f >>= (fun fitem -> Ok(eitem @ fitem))) + | E.ParallelOr(e, w) -> satisfyParallelOr (keyFn, hashFn, age) (ETree(e), WTree(w)) + | E.CascadeOr(e1, e2) -> satisfyCascadeOr (keyFn, hashFn, age) (ETree(e1), ETree(e2)) + | E.SwitchOrLeft(e, f) -> satisfySwitchOr (keyFn, hashFn, age) (ETree(e), FTree(f)) + | E.SwitchOrRight(e, f) -> satisfySwitchOr (keyFn, hashFn, age) (ETree(e), FTree(f)) + | E.Likely f -> + satisfyF (keyFn, hashFn, age) f |> Result.map(fun items -> items @ [RawPush([||])]) + | E.Unlikely f -> + satisfyF (keyFn, hashFn, age) f |> Result.map(fun items -> items @ [RawPush([|byte 1|])]) + + and satisfyW providers w: SatisfactionResult = + let keyFn, hashFn, age = providers + match w with + | W.CheckSig pk -> satisfyCheckSig keyFn pk + | W.HashEqual h -> satisfyHashEqual hashFn h + | W.Time t -> satisfyCSV age t |> Result.map(fun items -> items @ [RawPush([| byte 1 |])]) + | W.CastE e -> satisfyE providers e + + and satisfyT providers t = + let (keyFn, hashFn, age) = providers + match t with + | T.Time t -> Ok([RawPush([||])]) + | T.HashEqual h -> satisfyHashEqual hashFn h + | T.And(v, t) -> + let rRes = satisfyT providers t + let lRes = satisfyV providers v + rRes >>= (fun rItems -> lRes >>= fun(lItems) -> Ok(rItems @ lItems)) + | T.ParallelOr(e, w) -> satisfyParallelOr providers (ETree(e), WTree(w)) + | T.CascadeOr(e, t) -> satisfyCascadeOr providers (ETree(e), TTree(t)) + | T.CascadeOrV(e, v) -> satisfyCascadeOr providers (ETree(e), VTree(v)) + | T.SwitchOr(t1, t2) -> satisfySwitchOr providers (TTree(t1), TTree(t2)) + | T.SwitchOrV(v1, v2) -> satisfySwitchOr providers (VTree(v1), VTree(v2)) + | T.DelayedOr(q1, q2) -> satisfySwitchOr providers (QTree(q1), QTree(q2)) + | T.CastE e -> satisfyE providers e + + and satisfyQ providers q = + let keyFn, hashFn, age = providers + match q with + | Q.Pubkey pk -> satisfyCheckSig (keyFn) pk + | Q.And(l, r) -> + let rRes = satisfyQ providers r + let lRes = satisfyV providers l + rRes >>= (fun rItems -> lRes >>= fun(lItems) -> Ok(rItems @ lItems)) + | Q.Or(l, r) -> satisfySwitchOr providers (QTree(l), QTree(r)) + + and satisfyF providers f = + let (keyFn, hashFn, age) = providers + match f with + | F.CheckSig pk -> satisfyCheckSig keyFn pk + | F.CheckMultiSig(m, pks) -> satisfyCheckMultisig keyFn (m, pks) + | F.Time t -> satisfyCSV age t + | F.HashEqual h -> satisfyHashEqual hashFn h + | F.Threshold i -> satisfyThreshold providers i + | F.And(v ,f) -> + let rRes = satisfyF providers f + let lRes = satisfyV providers v + rRes >>= (fun rItems -> lRes >>= fun(lItems) -> Ok(rItems @ lItems)) + | F.CascadeOr(e, v) -> satisfyCascadeOr providers (ETree(e), VTree(v)) + | F.SwitchOr(f1, f2) -> satisfySwitchOr providers (FTree(f1), FTree(f2)) + | F.SwitchOrV(v1, v2) -> satisfySwitchOr providers (VTree(v1), VTree(v2)) + | F.DelayedOr(q1, q2) -> satisfySwitchOr providers (QTree(q1), QTree(q2)) + + and satisfyV providers v = + let (keyFn, hashFn, age) = providers + match v with + | V.CheckSig pk -> satisfyCheckSig keyFn pk + | V.CheckMultiSig (m, pks) -> satisfyCheckMultisig keyFn (m, pks) + | V.Time t -> satisfyCSV age t + | V.HashEqual h -> satisfyHashEqual hashFn h + | V.Threshold i -> satisfyThreshold providers i + | V.And(v1, v2) -> + let rRes = satisfyV providers v2 + let lRes = satisfyV providers v1 + rRes >>= (fun rItems -> lRes >>= fun(lItems) -> Ok(rItems @ lItems)) + | V.SwitchOr (v1, v2) -> satisfySwitchOr providers (VTree(v1), VTree(v2)) + | V.SwitchOrT (t1, t2) -> satisfySwitchOr providers (TTree(t1), TTree(t2)) + | V.CascadeOr(e, v) -> satisfyCascadeOr providers (ETree(e), VTree(v)) + | V.DelayedOr(q1, q2) -> satisfySwitchOr providers (QTree(q1), QTree(q2)) + + and dissatisfyE (e: E): SatisfiedItem list = + match e with + | E.CheckSig pk -> [RawPush([| byte 0 |])] + | E.CheckMultiSig (m, pks) -> [RawPush[| byte 0 |]; RawPush[| byte(m + 1u)|]] + | E.Time t -> [RawPush([| byte 0 |])] + | E.Threshold (_, e, ws) -> + let wDissat = ws |> Array.toList |> List.rev |> List.map(dissatisfyW) |> List.collect id + let eDissat = dissatisfyE e + wDissat @ eDissat + | E.ParallelAnd (e, w) -> + (dissatisfyW w) @ (dissatisfyE e) + | E.CascadeAnd (e, _) -> + (dissatisfyE e) + | E.ParallelOr (e, w) -> + (dissatisfyW w) @ (dissatisfyE e) + | E.CascadeOr (e, e2) -> + (dissatisfyE e2) @ (dissatisfyE e) + | E.SwitchOrLeft (e, _) -> + (dissatisfyE e) @ [RawPush[| byte 1 |]] + | E.SwitchOrRight (e, _) -> + (dissatisfyE e) @ [RawPush[||]] + | E.Likely f -> [RawPush[| byte 1 |]] + | E.Unlikely f -> [RawPush[||]] + + and dissatisfyW (w: W): SatisfiedItem list = + match w with + | W.CheckSig _ -> [RawPush[||]] + | W.HashEqual _ -> [RawPush[||]] + | W.Time _ -> [RawPush[||]] + | W.CastE e -> dissatisfyE e + + // ---------- types ------- + type E with + member this.Satisfy(keyFn: SignatureProvider, + hashFn: PreImageProvider, + age: LockTime): SatisfactionResult = satisfyE(keyFn, hashFn, age) this + + member this.Disatisfy(): SatisfiedItem list = dissatisfyE this + + type T with + member this.Satisfy(keyFn: SignatureProvider, + hashFn: PreImageProvider, + age: LockTime): SatisfactionResult = satisfyT(keyFn, hashFn, age) this + type W with + member this.Satisfy(keyFn: SignatureProvider, + hashFn: PreImageProvider, + age: LockTime): SatisfactionResult = satisfyW(keyFn, hashFn, age) this + member this.Disatisfy(): SatisfiedItem list = dissatisfyW this + type Q with + member this.Satisfy(keyFn: SignatureProvider, + hashFn: PreImageProvider, + age: LockTime): SatisfactionResult = satisfyQ(keyFn, hashFn, age) this + type F with + member this.Satisfy(keyFn: SignatureProvider, + hashFn: PreImageProvider, + age: LockTime): SatisfactionResult = satisfyF(keyFn, hashFn, age) this + type V with + member this.Satisfy(keyFn: SignatureProvider, + hashFn: PreImageProvider, + age: LockTime): SatisfactionResult = satisfyV(keyFn, hashFn, age) this diff --git a/NBitcoin/Miniscript/Utils/Lib.fs b/NBitcoin/Miniscript/Utils/Lib.fs new file mode 100644 index 0000000000..a37c586b13 --- /dev/null +++ b/NBitcoin/Miniscript/Utils/Lib.fs @@ -0,0 +1,34 @@ +namespace FNBitcoin.Utils + +[] +module Utils = + let inline (!>) (x:^a) : ^b = ((^a or ^b) : (static member op_Implicit : ^a -> ^b )x ) + + let resultFolder (acc : Result<'a seq, 'b>) + (item : Result<'a, 'c>) = + match acc, item with + | Ok x, Ok y -> + Ok(seq { + yield! x + yield y + }) + | Error x, Ok y -> Error x + | Ok x, Error y -> Error y + | Error x, Error y -> Error(y.ToString() + x.ToString()) + + + module List = + let rec traverseResult f list = + let (>>=) x f = Result.bind f x + let retn = Ok + let cons head tail = head :: tail + + let initState = retn [] + let folder head tail = + f head >>= (fun h -> + tail >>= (fun t -> + retn (cons h t) + ) + ) + List.foldBack folder list initState + let sequenceResult list = traverseResult id list \ No newline at end of file diff --git a/NBitcoin/Miniscript/Utils/Parser.fs b/NBitcoin/Miniscript/Utils/Parser.fs new file mode 100644 index 0000000000..2b4d72c185 --- /dev/null +++ b/NBitcoin/Miniscript/Utils/Parser.fs @@ -0,0 +1,167 @@ +namespace FNBitcoin.Utils + +module Parser = + type ErrorMessage = string + type ParserName = string + type Position = int + + type ParserError = ParserName * ErrorMessage * Position + + let printParserError (pe: ParserError) = + let (name, msg, pos) = pe + sprintf "name: %s\nmsg: %s\nposition %d" name msg pos + + type ParserResult<'a> = Result<'a, ParserError> + + type Parser<'a, 'u> = { + parseFn: 'u -> ParserResult<'a * 'u> + name: ParserName + } + type Parser<'a> = Parser<'a, unit> + + + // combinators for parser. 1: Monad law + let bindP f p = + let innerFn input = + let r1 = p.parseFn input + match r1 with + | Ok(item, remainingInput) -> + let p2 = f item + p2.parseFn remainingInput + | Error e -> Error e + {parseFn=innerFn; name="unknown"} + + let (>>=) p f = bindP f p + + let returnP x = + let name = sprintf "%A" x + let innerFn input = + Ok (x, input) + {parseFn=innerFn; name=name} + + // 2: Functor law + let mapP f = + bindP (f >> returnP) + + let () = mapP + let (|>>) x f = mapP f x + + // 3: Applicatives + let applyP fP xP = + fP >>= (fun f -> + xP >>= (fun x -> returnP (f x))) + + let (<*>) = applyP + let lift2 f xP yP = + returnP f <*> xP <*> yP + + + // 4: parser specific things + /// get the label from a parser + let getName (parser) = + // get label + parser.name + + /// update the label in the parser + let setName parser newName = + // change the inner function to use the new label + let newInnerFn input = + let result = parser.parseFn input + match result with + | Error (oldLabel,err,pos) -> + // if Failure, return new label + Error (newName,err,pos) + | ok -> ok + // return the Parser + {parseFn=newInnerFn; name=newName} + + /// infix version of setLabel + let ( ) = setName + + let andThen (p1) (p2) = + let l = sprintf "%s andThen %s" (getName p1) (getName p2) + p1 >>= (fun p1R -> + p2 >>= (fun p2R -> + returnP (p1R, p2R) + )) l + + let (.>>.) = andThen + + let run p input = + p.parseFn input + + let orElse p1 p2 = + let name = sprintf "%s orElse %s" (getName p1) (getName p2) + let innerFn input = + let r1 = p1.parseFn input + match r1 with + | Ok _ -> r1 + | Error e -> + let r2 = p2.parseFn input + r2 + {parseFn=innerFn; name=name} + + let (<|>) = orElse + + let choice listOfParsers = + List.reduce (<|>) listOfParsers + + let rec sequence parserlist = + let cons head tail = head::tail + let consP = lift2 cons + match parserlist with + | [] -> returnP [] + | head::tail -> + consP head (sequence tail) + + // parse zero or more occurancs o the specified parser + let rec star p input = + let firstResult = p.parseFn input + match firstResult with + | Error (_, _, _) -> ([], input) + | Ok (firstValue, inputAfterFirstPlace) -> + let (subsequenceValues, remainingInput) = + star p inputAfterFirstPlace + let values = firstValue::subsequenceValues + (values, remainingInput) + + // zero or more occurances + let many p = + let name = sprintf "many %s" (getName p) + let rec innerFn input = + Ok(star p input) + {parseFn=innerFn; name=name} + + // one or more + let many1 p = + let name = sprintf "many1 %s" (getName p) + p >>= (fun head -> + many p >>= (fun tail -> + returnP (head::tail) + )) name + + let opt p = + let name = sprintf "opt %s" (getName p) + let some = p |>> Some + let none = returnP None + (some <|> none) name + + let (.>>) p1 p2 = + p1 .>>. p2 + |> mapP (fun (a, b) -> a) + + let (>>.) p1 p2 = + p1 .>>. p2 + |> mapP (fun (a, b) -> b) + + let createParserForwardedToRef<'a, 'u>() = + let dummyParser = + let innerFn input : ParserResult<'a * 'u> = failwith "unfixed forwarded parser" + {parseFn=innerFn; name="unknown"} + let parserRef = ref dummyParser + let innerFn input = + (!parserRef).parseFn input + let wrapperParser = {parseFn=innerFn; name="unknown"} + wrapperParser, parserRef + + \ No newline at end of file diff --git a/NBitcoin/NBitcoin.csproj b/NBitcoin/NBitcoin.csproj index ed48c17c92..dc71e9f73f 100644 --- a/NBitcoin/NBitcoin.csproj +++ b/NBitcoin/NBitcoin.csproj @@ -93,4 +93,13 @@ bin\$(Configuration)\$(TargetFramework)\NBitcoin.xml + + + .\NBitcoin.nuspec + $(NuspecProperties);id=$(AssemblyName) + $(NuspecProperties);config=$(Configuration) + $(NuspecProperties);version=$(Version) + $(NuspecProperties);description=$(Description) + $(NuspecProperties);authors=$(Company) + From f254c5baa7d76b84d1cdda4b6a314443379cdb58 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Wed, 27 Mar 2019 05:12:43 +0900 Subject: [PATCH 02/40] Move Miniscript to independent Directory --- {NBitcoin/Miniscript => NBitcoin.Miniscript}/AssemblyInfo.fs | 0 {NBitcoin/Miniscript => NBitcoin.Miniscript}/Contract.fs | 0 {NBitcoin/Miniscript => NBitcoin.Miniscript}/Library.fs | 0 {NBitcoin/Miniscript => NBitcoin.Miniscript}/Miniscript.fs | 0 .../Miniscript => NBitcoin.Miniscript}/Miniscript.fsproj | 5 ++++- .../Miniscript => NBitcoin.Miniscript}/MiniscriptAST.fs | 0 .../Miniscript => NBitcoin.Miniscript}/MiniscriptCompiler.fs | 0 .../MiniscriptDecompiler.fs | 0 .../Miniscript => NBitcoin.Miniscript}/MiniscriptParser.fs | 0 {NBitcoin/Miniscript => NBitcoin.Miniscript}/Program.fs | 0 {NBitcoin/Miniscript => NBitcoin.Miniscript}/Satisfy.fs | 0 {NBitcoin/Miniscript => NBitcoin.Miniscript}/Utils/Lib.fs | 0 {NBitcoin/Miniscript => NBitcoin.Miniscript}/Utils/Parser.fs | 0 NBitcoin/NBitcoin.csproj | 2 +- 14 files changed, 5 insertions(+), 2 deletions(-) rename {NBitcoin/Miniscript => NBitcoin.Miniscript}/AssemblyInfo.fs (100%) rename {NBitcoin/Miniscript => NBitcoin.Miniscript}/Contract.fs (100%) rename {NBitcoin/Miniscript => NBitcoin.Miniscript}/Library.fs (100%) rename {NBitcoin/Miniscript => NBitcoin.Miniscript}/Miniscript.fs (100%) rename {NBitcoin/Miniscript => NBitcoin.Miniscript}/Miniscript.fsproj (84%) rename {NBitcoin/Miniscript => NBitcoin.Miniscript}/MiniscriptAST.fs (100%) rename {NBitcoin/Miniscript => NBitcoin.Miniscript}/MiniscriptCompiler.fs (100%) rename {NBitcoin/Miniscript => NBitcoin.Miniscript}/MiniscriptDecompiler.fs (100%) rename {NBitcoin/Miniscript => NBitcoin.Miniscript}/MiniscriptParser.fs (100%) rename {NBitcoin/Miniscript => NBitcoin.Miniscript}/Program.fs (100%) rename {NBitcoin/Miniscript => NBitcoin.Miniscript}/Satisfy.fs (100%) rename {NBitcoin/Miniscript => NBitcoin.Miniscript}/Utils/Lib.fs (100%) rename {NBitcoin/Miniscript => NBitcoin.Miniscript}/Utils/Parser.fs (100%) diff --git a/NBitcoin/Miniscript/AssemblyInfo.fs b/NBitcoin.Miniscript/AssemblyInfo.fs similarity index 100% rename from NBitcoin/Miniscript/AssemblyInfo.fs rename to NBitcoin.Miniscript/AssemblyInfo.fs diff --git a/NBitcoin/Miniscript/Contract.fs b/NBitcoin.Miniscript/Contract.fs similarity index 100% rename from NBitcoin/Miniscript/Contract.fs rename to NBitcoin.Miniscript/Contract.fs diff --git a/NBitcoin/Miniscript/Library.fs b/NBitcoin.Miniscript/Library.fs similarity index 100% rename from NBitcoin/Miniscript/Library.fs rename to NBitcoin.Miniscript/Library.fs diff --git a/NBitcoin/Miniscript/Miniscript.fs b/NBitcoin.Miniscript/Miniscript.fs similarity index 100% rename from NBitcoin/Miniscript/Miniscript.fs rename to NBitcoin.Miniscript/Miniscript.fs diff --git a/NBitcoin/Miniscript/Miniscript.fsproj b/NBitcoin.Miniscript/Miniscript.fsproj similarity index 84% rename from NBitcoin/Miniscript/Miniscript.fsproj rename to NBitcoin.Miniscript/Miniscript.fsproj index a70374533d..5db59797d9 100644 --- a/NBitcoin/Miniscript/Miniscript.fsproj +++ b/NBitcoin.Miniscript/Miniscript.fsproj @@ -20,6 +20,9 @@ - + + + + diff --git a/NBitcoin/Miniscript/MiniscriptAST.fs b/NBitcoin.Miniscript/MiniscriptAST.fs similarity index 100% rename from NBitcoin/Miniscript/MiniscriptAST.fs rename to NBitcoin.Miniscript/MiniscriptAST.fs diff --git a/NBitcoin/Miniscript/MiniscriptCompiler.fs b/NBitcoin.Miniscript/MiniscriptCompiler.fs similarity index 100% rename from NBitcoin/Miniscript/MiniscriptCompiler.fs rename to NBitcoin.Miniscript/MiniscriptCompiler.fs diff --git a/NBitcoin/Miniscript/MiniscriptDecompiler.fs b/NBitcoin.Miniscript/MiniscriptDecompiler.fs similarity index 100% rename from NBitcoin/Miniscript/MiniscriptDecompiler.fs rename to NBitcoin.Miniscript/MiniscriptDecompiler.fs diff --git a/NBitcoin/Miniscript/MiniscriptParser.fs b/NBitcoin.Miniscript/MiniscriptParser.fs similarity index 100% rename from NBitcoin/Miniscript/MiniscriptParser.fs rename to NBitcoin.Miniscript/MiniscriptParser.fs diff --git a/NBitcoin/Miniscript/Program.fs b/NBitcoin.Miniscript/Program.fs similarity index 100% rename from NBitcoin/Miniscript/Program.fs rename to NBitcoin.Miniscript/Program.fs diff --git a/NBitcoin/Miniscript/Satisfy.fs b/NBitcoin.Miniscript/Satisfy.fs similarity index 100% rename from NBitcoin/Miniscript/Satisfy.fs rename to NBitcoin.Miniscript/Satisfy.fs diff --git a/NBitcoin/Miniscript/Utils/Lib.fs b/NBitcoin.Miniscript/Utils/Lib.fs similarity index 100% rename from NBitcoin/Miniscript/Utils/Lib.fs rename to NBitcoin.Miniscript/Utils/Lib.fs diff --git a/NBitcoin/Miniscript/Utils/Parser.fs b/NBitcoin.Miniscript/Utils/Parser.fs similarity index 100% rename from NBitcoin/Miniscript/Utils/Parser.fs rename to NBitcoin.Miniscript/Utils/Parser.fs diff --git a/NBitcoin/NBitcoin.csproj b/NBitcoin/NBitcoin.csproj index dc71e9f73f..9d4957fffb 100644 --- a/NBitcoin/NBitcoin.csproj +++ b/NBitcoin/NBitcoin.csproj @@ -20,7 +20,7 @@ true - net461;net452;netstandard1.3;netstandard1.1;netcoreapp2.1;netstandard2.0 + netstandard1.3;netstandard1.1;netcoreapp2.1;netstandard2.0 netstandard2.0 $(TargetFrameworkOverride) 1591;1573;1572;1584;1570;3021 From 0a641bef9e16a0bcf424f6240e95a1c56468207d Mon Sep 17 00:00:00 2001 From: joemphilips Date: Wed, 27 Mar 2019 05:22:59 +0900 Subject: [PATCH 03/40] Stop using the work FNBitcoin --- NBitcoin.Miniscript/Contract.fs | 2 +- NBitcoin.Miniscript/Library.fs | 2 +- NBitcoin.Miniscript/Miniscript.fs | 6 +- NBitcoin.Miniscript/MiniscriptAST.fs | 1073 +++++----- NBitcoin.Miniscript/MiniscriptCompiler.fs | 2145 ++++++++++--------- NBitcoin.Miniscript/MiniscriptDecompiler.fs | 6 +- NBitcoin.Miniscript/MiniscriptParser.fs | 265 +-- NBitcoin.Miniscript/Satisfy.fs | 6 +- NBitcoin.Miniscript/Utils/Lib.fs | 2 +- NBitcoin.Miniscript/Utils/Parser.fs | 2 +- 10 files changed, 1756 insertions(+), 1753 deletions(-) diff --git a/NBitcoin.Miniscript/Contract.fs b/NBitcoin.Miniscript/Contract.fs index bfe5ce47cc..0a76ace110 100644 --- a/NBitcoin.Miniscript/Contract.fs +++ b/NBitcoin.Miniscript/Contract.fs @@ -1,4 +1,4 @@ -module FNBitcoin.Contract +namespace NBitcoin.Miniscript.Contract type ScriptBuilder() = diff --git a/NBitcoin.Miniscript/Library.fs b/NBitcoin.Miniscript/Library.fs index 9cf038ea98..588410c945 100644 --- a/NBitcoin.Miniscript/Library.fs +++ b/NBitcoin.Miniscript/Library.fs @@ -1,4 +1,4 @@ -namespace FNBitcoin +namespace NBitcoin.Miniscript module Say = let nothing name = name |> ignore diff --git a/NBitcoin.Miniscript/Miniscript.fs b/NBitcoin.Miniscript/Miniscript.fs index 8a77d436f7..063f5f892a 100644 --- a/NBitcoin.Miniscript/Miniscript.fs +++ b/NBitcoin.Miniscript/Miniscript.fs @@ -1,7 +1,7 @@ -namespace FNBitcoin.MiniScript +namespace NBitcoin.Miniscript -open FNBitcoin.MiniScriptAST -open FNBitcoin.MiniScriptDecompiler +open NBitcoin.Miniscript.AST +open NBitcoin.Miniscript.Decompiler open NBitcoin /// wrapper for top-level AST diff --git a/NBitcoin.Miniscript/MiniscriptAST.fs b/NBitcoin.Miniscript/MiniscriptAST.fs index f78add4349..3061620a35 100644 --- a/NBitcoin.Miniscript/MiniscriptAST.fs +++ b/NBitcoin.Miniscript/MiniscriptAST.fs @@ -1,562 +1,563 @@ -module FNBitcoin.MiniScriptAST +namespace NBitcoin.Miniscript open NBitcoin -open FNBitcoin.Utils +open NBitcoin.Miniscript.Utils open System.Text -// TODO: Use unativeint instead of uint? +module AST = + // TODO: Use unativeint instead of uint? -/// "E"xpression. takes more than one inputs from the stack, if it satisfies the condition, -/// It will leave 1 onto the stack, otherwise leave 0 -/// E and W are the only type which is able to dissatisfy without failing the whole script. -type E = - | CheckSig of PubKey - | CheckMultiSig of uint32 * PubKey [] - | Time of LockTime - | Threshold of (uint32 * E * W []) - | ParallelAnd of (E * W) - | CascadeAnd of (E * F) - | ParallelOr of (E * W) - | CascadeOr of (E * E) - | SwitchOrLeft of (E * F) - | SwitchOrRight of (E * F) - | Likely of F - | Unlikely of F + /// "E"xpression. takes more than one inputs from the stack, if it satisfies the condition, + /// It will leave 1 onto the stack, otherwise leave 0 + /// E and W are the only type which is able to dissatisfy without failing the whole script. + type E = + | CheckSig of PubKey + | CheckMultiSig of uint32 * PubKey [] + | Time of LockTime + | Threshold of (uint32 * E * W []) + | ParallelAnd of (E * W) + | CascadeAnd of (E * F) + | ParallelOr of (E * W) + | CascadeOr of (E * E) + | SwitchOrLeft of (E * F) + | SwitchOrRight of (E * F) + | Likely of F + | Unlikely of F -/// "W"rapped. say top level element is `X`, then consume items from the next element. -/// and leave one of [1,X] [X,1] if it satisfied the condition. otherwise -/// leave [0,X] or [X,0] onto the stack. -and W = - | CheckSig of PubKey - | HashEqual of uint256 - | Time of LockTime - | CastE of E + /// "W"rapped. say top level element is `X`, then consume items from the next element. + /// and leave one of [1,X] [X,1] if it satisfied the condition. otherwise + /// leave [0,X] or [X,0] onto the stack. + and W = + | CheckSig of PubKey + | HashEqual of uint256 + | Time of LockTime + | CastE of E -/// "Q"ueue. Similar to F, but leaves public key buffer on the stack instead of 1 -and Q = - | Pubkey of PubKey - | And of (V * Q) - | Or of (Q * Q) + /// "Q"ueue. Similar to F, but leaves public key buffer on the stack instead of 1 + and Q = + | Pubkey of PubKey + | And of (V * Q) + | Or of (Q * Q) -/// "F"orced. Similar to T, but always leaves 1 on the stack. -and F = - | CheckSig of PubKey - | CheckMultiSig of uint32 * PubKey [] - | Time of LockTime - | HashEqual of uint256 - | Threshold of (uint32 * E * W []) - | And of (V * F) - | CascadeOr of (E * V) - | SwitchOr of (F * F) - | SwitchOrV of (V * V) - | DelayedOr of (Q * Q) + /// "F"orced. Similar to T, but always leaves 1 on the stack. + and F = + | CheckSig of PubKey + | CheckMultiSig of uint32 * PubKey [] + | Time of LockTime + | HashEqual of uint256 + | Threshold of (uint32 * E * W []) + | And of (V * F) + | CascadeOr of (E * V) + | SwitchOr of (F * F) + | SwitchOrV of (V * V) + | DelayedOr of (Q * Q) -/// "V"erify. Similar to the T, but does not leave anything on the stack -and V = - | CheckSig of PubKey - | CheckMultiSig of uint32 * PubKey [] - | Time of LockTime - | HashEqual of uint256 - | Threshold of (uint32 * E * W []) - | And of (V * V) - | CascadeOr of (E * V) - | SwitchOr of (V * V) - | SwitchOrT of (T * T) - | DelayedOr of (Q * Q) + /// "V"erify. Similar to the T, but does not leave anything on the stack + and V = + | CheckSig of PubKey + | CheckMultiSig of uint32 * PubKey [] + | Time of LockTime + | HashEqual of uint256 + | Threshold of (uint32 * E * W []) + | And of (V * V) + | CascadeOr of (E * V) + | SwitchOr of (V * V) + | SwitchOrT of (T * T) + | DelayedOr of (Q * Q) -/// "T"opLevel representation. Must be satisfied, and leave zero (or non-zero) value onto the stack -and T = - | Time of LockTime - | HashEqual of uint256 - | And of (V * T) - | ParallelOr of (E * W) - | CascadeOr of (E * T) - | CascadeOrV of (E * V) - | SwitchOr of (T * T) - | SwitchOrV of (V * V) - | DelayedOr of (Q * Q) - | CastE of E + /// "T"opLevel representation. Must be satisfied, and leave zero (or non-zero) value onto the stack + and T = + | Time of LockTime + | HashEqual of uint256 + | And of (V * T) + | ParallelOr of (E * W) + | CascadeOr of (E * T) + | CascadeOrV of (E * V) + | SwitchOr of (T * T) + | SwitchOrV of (V * V) + | DelayedOr of (Q * Q) + | CastE of E -type AST = - | ETree of E - | QTree of Q - | WTree of W - | FTree of F - | VTree of V - | TTree of T + type AST = + | ETree of E + | QTree of Q + | WTree of W + | FTree of F + | VTree of V + | TTree of T -type ASTType = - | EExpr - | QExpr - | WExpr - | FExpr - | VExpr - | TExpr + type ASTType = + | EExpr + | QExpr + | WExpr + | FExpr + | VExpr + | TExpr -let private EncodeUint (n: uint32) = - Op.GetPushOp(int64 n).ToString() + let private EncodeUint (n: uint32) = + Op.GetPushOp(int64 n).ToString() -let private EncodeInt (n: int32) = - Op.GetPushOp(int64 n).ToString() + let private EncodeInt (n: int32) = + Op.GetPushOp(int64 n).ToString() -type E with - - member this.print() = - match this with - | CheckSig pk -> sprintf "E.pk(%s)" (pk.ToHex()) - | CheckMultiSig(m, pks) -> - sprintf "E.multi(%d,%s)" m - (pks - |> Array.fold (fun acc k -> sprintf "%s,%s" acc (k.ToString())) - "") - | Time t -> sprintf "E.time(%s)" (t.ToString()) - | Threshold(num, e, ws) -> - sprintf "E.thres(%d,%s,%s)" num (e.print()) - (ws - |> Array.fold (fun acc w -> sprintf "%s,%s" acc (w.print())) "") - | ParallelAnd(e, w) -> sprintf "E.and_p(%s,%s)" (e.print()) (w.print()) - | CascadeAnd(e, f) -> sprintf "E.and_c(%s,%s)" (e.print()) (f.print()) - | ParallelOr(e, w) -> sprintf "E.or_p(%s,%s)" (e.print()) (w.print()) - | CascadeOr(e, e2) -> sprintf "E.or_c(%s,%s)" (e.print()) (e2.print()) - | SwitchOrLeft(e, f) -> sprintf "E.or_s(%s,%s)" (e.print()) (f.print()) - | SwitchOrRight(e, f) -> sprintf "E.or_r(%s,%s)" (e.print()) (f.print()) - | Likely f -> sprintf "E.lift_l(%s)" (f.print()) - | Unlikely f -> sprintf "E.lift_u(%s)" (f.print()) - - member this.Serialize(sb : StringBuilder) : StringBuilder = - match this with - | CheckSig pk -> sb.AppendFormat(" {0} OP_CHECKSIG", pk) - | CheckMultiSig(m, pks) -> - sb.AppendFormat(" {0}", (EncodeUint m)) |> ignore - for pk in pks do - do sb.AppendFormat(" {0}", (pk.ToHex())) |> ignore - sb.AppendFormat(" {0} OP_CHECKMULTISIG", EncodeInt(pks.Length)) |> ignore - sb - | Time t -> - sb.AppendFormat(" OP_DUP OP_IF {0} OP_CSV OP_DROP OP_ENDIF", EncodeUint(!> t)) - | Threshold(k, e, ws) -> - e.Serialize(sb) |> ignore - for w in ws do - w.Serialize(sb) |> ignore - sb.Append(" OP_ADD") |> ignore - sb.AppendFormat(" {0} OP_EQUAL", (EncodeUint k)) - | ParallelAnd(l, r) -> - l.Serialize(sb) |> ignore - r.Serialize(sb) |> ignore - sb.Append(" OP_BOOLAND") - | CascadeAnd(l, r) -> - l.Serialize(sb) |> ignore - sb.Append(" OP_NOTIF 0 OP_ELSE") |> ignore - r.Serialize(sb) |> ignore - sb.Append(" OP_ENDIF") - | ParallelOr(l, r) -> - l.Serialize(sb) |> ignore - r.Serialize(sb) |> ignore - sb.Append(" OP_BOOLOR") - | CascadeOr(l, r) -> - l.Serialize(sb) |> ignore - sb.Append(" OP_IFDUP OP_NOTIF") |> ignore - r.Serialize(sb) |> ignore - sb.Append(" OP_ENDIF") - | SwitchOrLeft(l, r) -> - sb.Append(" OP_IF") |> ignore - l.Serialize(sb) |> ignore - sb.Append(" OP_ELSE") |> ignore - r.Serialize(sb) |> ignore - sb.Append(" OP_ENDIF") - | SwitchOrRight(l, r) -> - sb.Append(" OP_NOTIF") |> ignore - l.Serialize(sb) |> ignore - sb.Append(" OP_ELSE") |> ignore - r.Serialize(sb) |> ignore - sb.Append(" OP_ENDIF") - | Likely(f) -> - sb.Append(" OP_NOTIF") |> ignore - f.Serialize(sb) |> ignore - sb.Append(" OP_ELSE 0 OP_ENDIF") - | Unlikely(f) -> - sb.Append(" OP_IF") |> ignore - f.Serialize(sb) |> ignore - sb.Append(" OP_ELSE 0 OP_ENDIF") - - member this.toE() = this - member this.toT() = - match this with - | ParallelOr(l, r) -> T.ParallelOr(l, r) - | x -> T.CastE(x) + type E with + + member this.print() = + match this with + | CheckSig pk -> sprintf "E.pk(%s)" (pk.ToHex()) + | CheckMultiSig(m, pks) -> + sprintf "E.multi(%d,%s)" m + (pks + |> Array.fold (fun acc k -> sprintf "%s,%s" acc (k.ToString())) + "") + | Time t -> sprintf "E.time(%s)" (t.ToString()) + | Threshold(num, e, ws) -> + sprintf "E.thres(%d,%s,%s)" num (e.print()) + (ws + |> Array.fold (fun acc w -> sprintf "%s,%s" acc (w.print())) "") + | ParallelAnd(e, w) -> sprintf "E.and_p(%s,%s)" (e.print()) (w.print()) + | CascadeAnd(e, f) -> sprintf "E.and_c(%s,%s)" (e.print()) (f.print()) + | ParallelOr(e, w) -> sprintf "E.or_p(%s,%s)" (e.print()) (w.print()) + | CascadeOr(e, e2) -> sprintf "E.or_c(%s,%s)" (e.print()) (e2.print()) + | SwitchOrLeft(e, f) -> sprintf "E.or_s(%s,%s)" (e.print()) (f.print()) + | SwitchOrRight(e, f) -> sprintf "E.or_r(%s,%s)" (e.print()) (f.print()) + | Likely f -> sprintf "E.lift_l(%s)" (f.print()) + | Unlikely f -> sprintf "E.lift_u(%s)" (f.print()) + + member this.Serialize(sb : StringBuilder) : StringBuilder = + match this with + | CheckSig pk -> sb.AppendFormat(" {0} OP_CHECKSIG", pk) + | CheckMultiSig(m, pks) -> + sb.AppendFormat(" {0}", (EncodeUint m)) |> ignore + for pk in pks do + do sb.AppendFormat(" {0}", (pk.ToHex())) |> ignore + sb.AppendFormat(" {0} OP_CHECKMULTISIG", EncodeInt(pks.Length)) |> ignore + sb + | Time t -> + sb.AppendFormat(" OP_DUP OP_IF {0} OP_CSV OP_DROP OP_ENDIF", EncodeUint(!> t)) + | Threshold(k, e, ws) -> + e.Serialize(sb) |> ignore + for w in ws do + w.Serialize(sb) |> ignore + sb.Append(" OP_ADD") |> ignore + sb.AppendFormat(" {0} OP_EQUAL", (EncodeUint k)) + | ParallelAnd(l, r) -> + l.Serialize(sb) |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_BOOLAND") + | CascadeAnd(l, r) -> + l.Serialize(sb) |> ignore + sb.Append(" OP_NOTIF 0 OP_ELSE") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF") + | ParallelOr(l, r) -> + l.Serialize(sb) |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_BOOLOR") + | CascadeOr(l, r) -> + l.Serialize(sb) |> ignore + sb.Append(" OP_IFDUP OP_NOTIF") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF") + | SwitchOrLeft(l, r) -> + sb.Append(" OP_IF") |> ignore + l.Serialize(sb) |> ignore + sb.Append(" OP_ELSE") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF") + | SwitchOrRight(l, r) -> + sb.Append(" OP_NOTIF") |> ignore + l.Serialize(sb) |> ignore + sb.Append(" OP_ELSE") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF") + | Likely(f) -> + sb.Append(" OP_NOTIF") |> ignore + f.Serialize(sb) |> ignore + sb.Append(" OP_ELSE 0 OP_ENDIF") + | Unlikely(f) -> + sb.Append(" OP_IF") |> ignore + f.Serialize(sb) |> ignore + sb.Append(" OP_ELSE 0 OP_ENDIF") + + member this.toE() = this + member this.toT() = + match this with + | ParallelOr(l, r) -> T.ParallelOr(l, r) + | x -> T.CastE(x) -and Q with - - member this.print() = - match this with - | Pubkey p -> sprintf "Q.pk(%s)" (p.ToString()) - | And(v, q) -> sprintf "Q.and(%s,%s)" (v.print()) (q.print()) - | Or(q1, q2) -> sprintf "Q.or(%s,%s)" (q1.print()) (q2.print()) - - member this.Serialize(sb : StringBuilder) : StringBuilder = - match this with - | Pubkey pk -> sb.AppendFormat(" {0}", (pk.ToHex())) - | And(l, r) -> - l.Serialize(sb) |> ignore - r.Serialize(sb) - | Or(l, r) -> - sb.Append(" OP_IF") |> ignore - l.Serialize(sb) |> ignore - sb.Append(" OP_ELSE") |> ignore - r.Serialize(sb) |> ignore - sb.Append(" OP_ENDIF") + and Q with + + member this.print() = + match this with + | Pubkey p -> sprintf "Q.pk(%s)" (p.ToString()) + | And(v, q) -> sprintf "Q.and(%s,%s)" (v.print()) (q.print()) + | Or(q1, q2) -> sprintf "Q.or(%s,%s)" (q1.print()) (q2.print()) + + member this.Serialize(sb : StringBuilder) : StringBuilder = + match this with + | Pubkey pk -> sb.AppendFormat(" {0}", (pk.ToHex())) + | And(l, r) -> + l.Serialize(sb) |> ignore + r.Serialize(sb) + | Or(l, r) -> + sb.Append(" OP_IF") |> ignore + l.Serialize(sb) |> ignore + sb.Append(" OP_ELSE") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF") -and W with - - member this.print() = - match this with - | CheckSig pk -> sprintf "W.pk(%s)" (pk.ToString()) - | HashEqual u -> sprintf "W.hash(%s)" (u.ToString()) - | Time t -> sprintf "W.time(%s)" (t.ToString()) - | CastE e -> e.print() - - member this.Serialize(sb : StringBuilder) : StringBuilder = - match this with - | CheckSig pk -> - sb.Append(" OP_SWAP") |> ignore - sb.AppendFormat(" {0}", (pk.ToHex())) |> ignore - sb.Append(" OP_CHECKSIG") - | HashEqual h -> - sb.Append - (sprintf " OP_SWAP OP_SIZE OP_0NOTEQUAL OP_IF OP_SIZE %s OP_EQUALVERIFY OP_SHA256" - (EncodeInt 32)) - |> ignore - sb.AppendFormat(" {0}", h.ToString()) |> ignore - sb.Append(" OP_EQUALVERIFY 1 OP_ENDIF") - | Time t -> - sb.AppendFormat - (" OP_SWAP OP_DUP OP_IF {0} OP_CSV OP_DROP OP_ENDIF", (EncodeUint (!> t))) - | CastE e -> - sb.Append(" OP_TOALTSTACK") |> ignore - e.Serialize(sb) |> ignore - sb.Append(" OP_FROMALTSTACK") - -and F with - - member this.print() = - match this with - | CheckSig pk -> sprintf "F.pk(%s)" (pk.ToString()) - | CheckMultiSig(m, pks) -> - sprintf "F.multi(%d,%s)" m - (pks - |> Array.fold (fun acc k -> sprintf "%s,%s" acc (k.ToString())) - "") - | Time t -> sprintf "F.time(%s)" (t.ToString()) - | HashEqual h -> sprintf "F.hash(%s)" (h.ToString()) - | Threshold(num, e, ws) -> - sprintf "F.thres(%d,%s,%s)" num (e.print()) - (ws - |> Array.fold (fun acc w -> sprintf "%s,%s" acc (w.print())) "") - | And(l, r) -> sprintf "F.and(%s,%s)" (l.print()) (r.print()) - | CascadeOr(l, r) -> sprintf "F.or_v(%s,%s)" (l.print()) (r.print()) - | SwitchOr(l, r) -> sprintf "F.or_s(%s,%s)" (l.print()) (r.print()) - | SwitchOrV(l, r) -> sprintf "F.or_a(%s,%s)" (l.print()) (r.print()) - | DelayedOr(l, r) -> sprintf "F.or_d(%s,%s)" (l.print()) (r.print()) - - member this.toE() = this - - member this.toT() = - match this with - | CascadeOr(l, r) -> T.CascadeOrV(l, r) - | SwitchOrV(l, r) -> T.SwitchOrV(l, r) - | x -> failwith (sprintf "%s is not a T" (x.print())) - - member this.Serialize(sb : StringBuilder) : StringBuilder = - match this with - | CheckSig pk -> - sb.AppendFormat(" {0} OP_CHECKSIGVERIFY 1", (pk.ToHex())) - | CheckMultiSig(m, pks) -> - sb.AppendFormat(" {0}", (EncodeUint m)) |> ignore - for pk in pks do + and W with + + member this.print() = + match this with + | CheckSig pk -> sprintf "W.pk(%s)" (pk.ToString()) + | HashEqual u -> sprintf "W.hash(%s)" (u.ToString()) + | Time t -> sprintf "W.time(%s)" (t.ToString()) + | CastE e -> e.print() + + member this.Serialize(sb : StringBuilder) : StringBuilder = + match this with + | CheckSig pk -> + sb.Append(" OP_SWAP") |> ignore sb.AppendFormat(" {0}", (pk.ToHex())) |> ignore - sb.AppendFormat(" {0} OP_CHECKMULTISIGVERIFY 1", (EncodeInt pks.Length)) - | Time t -> sb.AppendFormat(" {0} OP_CSV OP_0NOTEQUAL", (EncodeUint (!> t))) - | HashEqual h -> - sb.AppendFormat - (" OP_SIZE 20 OP_EQUALVERIFY OP_SHA256 {0} OP_EQUALVERIFY 1", h) - | Threshold(k, e, ws) -> - e.Serialize(sb) |> ignore - for w in ws do - w.Serialize(sb) |> ignore - sb.Append(" OP_ADD") |> ignore - sb.AppendFormat(" {0} OP_EQUALVERIFY 1", (EncodeUint k)) - | And(l, r) -> - l.Serialize(sb) |> ignore - r.Serialize(sb) - | SwitchOr(l, r) -> - sb.AppendFormat(" OP_IF") |> ignore - l.Serialize(sb) |> ignore - sb.Append(" OP_ELSE") |> ignore - r.Serialize(sb) |> ignore - sb.Append(" OP_ENDIF") - | SwitchOrV(l, r) -> - sb.AppendFormat(" OP_IF") |> ignore - l.Serialize(sb) |> ignore - sb.Append(" OP_ELSE") |> ignore - r.Serialize(sb) |> ignore - sb.Append(" OP_ENDIF 1") - | CascadeOr(l, r) -> - l.Serialize(sb) |> ignore - sb.Append(" OP_NOTIF") |> ignore - r.Serialize(sb) |> ignore - sb.Append(" OP_ENDIF 1") - | DelayedOr(l, r) -> - sb.Append(" OP_IF") |> ignore - l.Serialize(sb) |> ignore - sb.Append(" OP_ELSE") |> ignore - r.Serialize(sb) |> ignore - sb.Append(" OP_ENDIF OP_CHECKSIGVERIFY 1") + sb.Append(" OP_CHECKSIG") + | HashEqual h -> + sb.Append + (sprintf " OP_SWAP OP_SIZE OP_0NOTEQUAL OP_IF OP_SIZE %s OP_EQUALVERIFY OP_SHA256" + (EncodeInt 32)) + |> ignore + sb.AppendFormat(" {0}", h.ToString()) |> ignore + sb.Append(" OP_EQUALVERIFY 1 OP_ENDIF") + | Time t -> + sb.AppendFormat + (" OP_SWAP OP_DUP OP_IF {0} OP_CSV OP_DROP OP_ENDIF", (EncodeUint (!> t))) + | CastE e -> + sb.Append(" OP_TOALTSTACK") |> ignore + e.Serialize(sb) |> ignore + sb.Append(" OP_FROMALTSTACK") -and V with - - member this.print() = - match this with - | CheckSig pk -> sprintf "V.pk(%s)" (pk.ToString()) - | CheckMultiSig(m, pks) -> - sprintf "V.multi(%d,%s)" m - (pks - |> Array.fold (fun acc k -> sprintf "%s,%s" acc (k.ToString())) - "") - | Time t -> sprintf "V.time(%s)" (t.ToString()) - | HashEqual h -> sprintf "V.hash(%s)" (h.ToString()) - | Threshold(num, e, ws) -> - sprintf "V.thres(%d,%s,%s)" num (e.print()) - (ws - |> Array.fold (fun acc w -> sprintf "%s,%s" acc (w.print())) "") - | And(l, r) -> sprintf "V.and(%s,%s)" (l.print()) (r.print()) - | CascadeOr(l, r) -> sprintf "V.or_v(%s,%s)" (l.print()) (r.print()) - | SwitchOr(l, r) -> sprintf "V.or_s(%s,%s)" (l.print()) (r.print()) - | SwitchOrT(l, r) -> sprintf "V.or_a(%s,%s)" (l.print()) (r.print()) - | DelayedOr(l, r) -> sprintf "V.or_d(%s,%s)" (l.print()) (r.print()) - - member this.Serialize(sb : StringBuilder) : StringBuilder = - match this with - | CheckSig pk -> - sb.AppendFormat(" {0} OP_CHECKSIGVERIFY ", (pk.ToHex())) - | CheckMultiSig(m, pks) -> - sb.AppendFormat(" {0}", (EncodeUint m)) |> ignore - for pk in pks do - sb.AppendFormat(" {0}", (pk.ToHex())) |> ignore - sb.AppendFormat(" {0} OP_CHECKMULTISIGVERIFY", (EncodeInt pks.Length)) - | Time t -> sb.AppendFormat(" {0} OP_CSV OP_DROP", (EncodeUint (!> t))) - | HashEqual h -> - sb.AppendFormat - (" OP_SIZE 20 OP_EQUALVERIFY OP_SHA256 {0} OP_EQUALVERIFY", h) - | Threshold(k, e, ws) -> - e.Serialize(sb) |> ignore - for w in ws do - w.Serialize(sb) |> ignore - sb.Append(" OP_ADD") |> ignore - sb.AppendFormat(" {0} OP_EQUALVERIFY", (EncodeUint k)) - | And(l, r) -> - l.Serialize(sb) |> ignore - r.Serialize(sb) - | SwitchOr(l, r) -> - sb.AppendFormat(" OP_IF") |> ignore - l.Serialize(sb) |> ignore - sb.Append(" OP_ELSE") |> ignore - r.Serialize(sb) |> ignore - sb.Append(" OP_ENDIF") - | SwitchOrT(l, r) -> - sb.AppendFormat(" OP_IF") |> ignore - l.Serialize(sb) |> ignore - sb.Append(" OP_ELSE") |> ignore - r.Serialize(sb) |> ignore - sb.Append(" OP_ENDIF OP_VERIFY") - | CascadeOr(l, r) -> - l.Serialize(sb) |> ignore - sb.Append(" OP_NOTIF") |> ignore - r.Serialize(sb) |> ignore - sb.Append(" OP_ENDIF") - | DelayedOr(l, r) -> - sb.Append(" OP_IF") |> ignore - l.Serialize(sb) |> ignore - sb.Append(" OP_ELSE") |> ignore - r.Serialize(sb) |> ignore - sb.Append(" OP_ENDIF OP_CHECKSIGVERIFY") + and F with + + member this.print() = + match this with + | CheckSig pk -> sprintf "F.pk(%s)" (pk.ToString()) + | CheckMultiSig(m, pks) -> + sprintf "F.multi(%d,%s)" m + (pks + |> Array.fold (fun acc k -> sprintf "%s,%s" acc (k.ToString())) + "") + | Time t -> sprintf "F.time(%s)" (t.ToString()) + | HashEqual h -> sprintf "F.hash(%s)" (h.ToString()) + | Threshold(num, e, ws) -> + sprintf "F.thres(%d,%s,%s)" num (e.print()) + (ws + |> Array.fold (fun acc w -> sprintf "%s,%s" acc (w.print())) "") + | And(l, r) -> sprintf "F.and(%s,%s)" (l.print()) (r.print()) + | CascadeOr(l, r) -> sprintf "F.or_v(%s,%s)" (l.print()) (r.print()) + | SwitchOr(l, r) -> sprintf "F.or_s(%s,%s)" (l.print()) (r.print()) + | SwitchOrV(l, r) -> sprintf "F.or_a(%s,%s)" (l.print()) (r.print()) + | DelayedOr(l, r) -> sprintf "F.or_d(%s,%s)" (l.print()) (r.print()) + + member this.toE() = this + + member this.toT() = + match this with + | CascadeOr(l, r) -> T.CascadeOrV(l, r) + | SwitchOrV(l, r) -> T.SwitchOrV(l, r) + | x -> failwith (sprintf "%s is not a T" (x.print())) + + member this.Serialize(sb : StringBuilder) : StringBuilder = + match this with + | CheckSig pk -> + sb.AppendFormat(" {0} OP_CHECKSIGVERIFY 1", (pk.ToHex())) + | CheckMultiSig(m, pks) -> + sb.AppendFormat(" {0}", (EncodeUint m)) |> ignore + for pk in pks do + sb.AppendFormat(" {0}", (pk.ToHex())) |> ignore + sb.AppendFormat(" {0} OP_CHECKMULTISIGVERIFY 1", (EncodeInt pks.Length)) + | Time t -> sb.AppendFormat(" {0} OP_CSV OP_0NOTEQUAL", (EncodeUint (!> t))) + | HashEqual h -> + sb.AppendFormat + (" OP_SIZE 20 OP_EQUALVERIFY OP_SHA256 {0} OP_EQUALVERIFY 1", h) + | Threshold(k, e, ws) -> + e.Serialize(sb) |> ignore + for w in ws do + w.Serialize(sb) |> ignore + sb.Append(" OP_ADD") |> ignore + sb.AppendFormat(" {0} OP_EQUALVERIFY 1", (EncodeUint k)) + | And(l, r) -> + l.Serialize(sb) |> ignore + r.Serialize(sb) + | SwitchOr(l, r) -> + sb.AppendFormat(" OP_IF") |> ignore + l.Serialize(sb) |> ignore + sb.Append(" OP_ELSE") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF") + | SwitchOrV(l, r) -> + sb.AppendFormat(" OP_IF") |> ignore + l.Serialize(sb) |> ignore + sb.Append(" OP_ELSE") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF 1") + | CascadeOr(l, r) -> + l.Serialize(sb) |> ignore + sb.Append(" OP_NOTIF") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF 1") + | DelayedOr(l, r) -> + sb.Append(" OP_IF") |> ignore + l.Serialize(sb) |> ignore + sb.Append(" OP_ELSE") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF OP_CHECKSIGVERIFY 1") -and T with - - member this.print() = - match this with - | Time t -> sprintf "T.time(%s)" (t.ToString()) - | HashEqual h -> sprintf "T.hash(%s)" (h.ToString()) - | And(l, r) -> sprintf "T.and_p(%s,%s)" (l.print()) (r.print()) - | ParallelOr(l, r) -> sprintf "T.or_vp(%s,%s)" (l.print()) (r.print()) - | CascadeOr(l, r) -> sprintf "T.or_c(%s,%s)" (l.print()) (r.print()) - | CascadeOrV(l, r) -> sprintf "T.or_v(%s,%s)" (l.print()) (r.print()) - | SwitchOr(l, r) -> sprintf "T.or_s(%s,%s)" (l.print()) (r.print()) - | SwitchOrV(l, r) -> sprintf "T.or_a(%s,%s)" (l.print()) (r.print()) - | DelayedOr(l, r) -> sprintf "T.or_d(%s,%s)" (l.print()) (r.print()) - | CastE e -> sprintf "T.%s" (e.print()) - - member this.Serialize(sb : StringBuilder) : StringBuilder = - match this with - | Time t -> sb.AppendFormat(" {0} OP_CSV", (EncodeUint (!> t))) - | HashEqual h -> - sb.AppendFormat - (" OP_SIZE 20 OP_EQUALVERIFY OP_SHA256 {0} OP_EQUAL", h) - | And(l, r) -> - l.Serialize(sb) |> ignore - r.Serialize(sb) - | ParallelOr(l, r) -> - l.Serialize(sb) |> ignore - r.Serialize(sb) |> ignore - sb.Append(" OP_BOOLOR") - | CascadeOr(l, r) -> - l.Serialize(sb) |> ignore - sb.Append(" OP_IFDUP OP_NOTIF") |> ignore - r.Serialize(sb) |> ignore - sb.Append(" OP_ENDIF") - | CascadeOrV(l, r) -> - l.Serialize(sb) |> ignore - sb.Append(" OP_NOTIF") |> ignore - r.Serialize(sb) |> ignore - sb.Append(" OP_ENDIF 1") - | SwitchOr(l, r) -> - sb.AppendFormat(" OP_IF") |> ignore - l.Serialize(sb) |> ignore - sb.Append(" OP_ELSE") |> ignore - r.Serialize(sb) |> ignore - sb.Append(" OP_ENDIF") - | SwitchOrV(l, r) -> - sb.AppendFormat(" OP_IF") |> ignore - l.Serialize(sb) |> ignore - sb.Append(" OP_ELSE") |> ignore - r.Serialize(sb) |> ignore - sb.Append(" OP_ENDIF 1") - | DelayedOr(l, r) -> - sb.Append(" OP_IF") |> ignore - l.Serialize(sb) |> ignore - sb.Append(" OP_ELSE") |> ignore - r.Serialize(sb) |> ignore - sb.Append(" OP_ENDIF OP_CHECKSIG") - | CastE e -> e.Serialize(sb) + and V with + + member this.print() = + match this with + | CheckSig pk -> sprintf "V.pk(%s)" (pk.ToString()) + | CheckMultiSig(m, pks) -> + sprintf "V.multi(%d,%s)" m + (pks + |> Array.fold (fun acc k -> sprintf "%s,%s" acc (k.ToString())) + "") + | Time t -> sprintf "V.time(%s)" (t.ToString()) + | HashEqual h -> sprintf "V.hash(%s)" (h.ToString()) + | Threshold(num, e, ws) -> + sprintf "V.thres(%d,%s,%s)" num (e.print()) + (ws + |> Array.fold (fun acc w -> sprintf "%s,%s" acc (w.print())) "") + | And(l, r) -> sprintf "V.and(%s,%s)" (l.print()) (r.print()) + | CascadeOr(l, r) -> sprintf "V.or_v(%s,%s)" (l.print()) (r.print()) + | SwitchOr(l, r) -> sprintf "V.or_s(%s,%s)" (l.print()) (r.print()) + | SwitchOrT(l, r) -> sprintf "V.or_a(%s,%s)" (l.print()) (r.print()) + | DelayedOr(l, r) -> sprintf "V.or_d(%s,%s)" (l.print()) (r.print()) + + member this.Serialize(sb : StringBuilder) : StringBuilder = + match this with + | CheckSig pk -> + sb.AppendFormat(" {0} OP_CHECKSIGVERIFY ", (pk.ToHex())) + | CheckMultiSig(m, pks) -> + sb.AppendFormat(" {0}", (EncodeUint m)) |> ignore + for pk in pks do + sb.AppendFormat(" {0}", (pk.ToHex())) |> ignore + sb.AppendFormat(" {0} OP_CHECKMULTISIGVERIFY", (EncodeInt pks.Length)) + | Time t -> sb.AppendFormat(" {0} OP_CSV OP_DROP", (EncodeUint (!> t))) + | HashEqual h -> + sb.AppendFormat + (" OP_SIZE 20 OP_EQUALVERIFY OP_SHA256 {0} OP_EQUALVERIFY", h) + | Threshold(k, e, ws) -> + e.Serialize(sb) |> ignore + for w in ws do + w.Serialize(sb) |> ignore + sb.Append(" OP_ADD") |> ignore + sb.AppendFormat(" {0} OP_EQUALVERIFY", (EncodeUint k)) + | And(l, r) -> + l.Serialize(sb) |> ignore + r.Serialize(sb) + | SwitchOr(l, r) -> + sb.AppendFormat(" OP_IF") |> ignore + l.Serialize(sb) |> ignore + sb.Append(" OP_ELSE") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF") + | SwitchOrT(l, r) -> + sb.AppendFormat(" OP_IF") |> ignore + l.Serialize(sb) |> ignore + sb.Append(" OP_ELSE") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF OP_VERIFY") + | CascadeOr(l, r) -> + l.Serialize(sb) |> ignore + sb.Append(" OP_NOTIF") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF") + | DelayedOr(l, r) -> + sb.Append(" OP_IF") |> ignore + l.Serialize(sb) |> ignore + sb.Append(" OP_ELSE") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF OP_CHECKSIGVERIFY") -type AST with - - member this.Print() = - match this with - | ETree e -> e.print() - | QTree q -> q.print() - | WTree w -> w.print() - | FTree f -> f.print() - | VTree v -> v.print() - | TTree t -> t.print() - - member this.ToScript() = - let sb = StringBuilder() - match this with - | ETree e -> - let s = e.Serialize(sb) - NBitcoin.Script(s.ToString()) - | QTree q -> - let s = q.Serialize(sb) - NBitcoin.Script(s.ToString()) - | WTree w -> - let s = w.Serialize(sb) - NBitcoin.Script(s.ToString()) - | FTree f -> - let s = f.Serialize(sb) - NBitcoin.Script(s.ToString()) - | VTree v -> - let s = v.Serialize(sb) - NBitcoin.Script(s.ToString()) - | TTree t -> - let s = t.Serialize(sb) - let str = s.ToString() - NBitcoin.Script(str) - member this.GetASTType() = - match this with - | ETree _ -> EExpr - | QTree _ -> QExpr - | WTree _ -> WExpr - | FTree _ -> FExpr - | VTree _ -> VExpr - | TTree _ -> TExpr + and T with + + member this.print() = + match this with + | Time t -> sprintf "T.time(%s)" (t.ToString()) + | HashEqual h -> sprintf "T.hash(%s)" (h.ToString()) + | And(l, r) -> sprintf "T.and_p(%s,%s)" (l.print()) (r.print()) + | ParallelOr(l, r) -> sprintf "T.or_vp(%s,%s)" (l.print()) (r.print()) + | CascadeOr(l, r) -> sprintf "T.or_c(%s,%s)" (l.print()) (r.print()) + | CascadeOrV(l, r) -> sprintf "T.or_v(%s,%s)" (l.print()) (r.print()) + | SwitchOr(l, r) -> sprintf "T.or_s(%s,%s)" (l.print()) (r.print()) + | SwitchOrV(l, r) -> sprintf "T.or_a(%s,%s)" (l.print()) (r.print()) + | DelayedOr(l, r) -> sprintf "T.or_d(%s,%s)" (l.print()) (r.print()) + | CastE e -> sprintf "T.%s" (e.print()) + + member this.Serialize(sb : StringBuilder) : StringBuilder = + match this with + | Time t -> sb.AppendFormat(" {0} OP_CSV", (EncodeUint (!> t))) + | HashEqual h -> + sb.AppendFormat + (" OP_SIZE 20 OP_EQUALVERIFY OP_SHA256 {0} OP_EQUAL", h) + | And(l, r) -> + l.Serialize(sb) |> ignore + r.Serialize(sb) + | ParallelOr(l, r) -> + l.Serialize(sb) |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_BOOLOR") + | CascadeOr(l, r) -> + l.Serialize(sb) |> ignore + sb.Append(" OP_IFDUP OP_NOTIF") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF") + | CascadeOrV(l, r) -> + l.Serialize(sb) |> ignore + sb.Append(" OP_NOTIF") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF 1") + | SwitchOr(l, r) -> + sb.AppendFormat(" OP_IF") |> ignore + l.Serialize(sb) |> ignore + sb.Append(" OP_ELSE") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF") + | SwitchOrV(l, r) -> + sb.AppendFormat(" OP_IF") |> ignore + l.Serialize(sb) |> ignore + sb.Append(" OP_ELSE") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF 1") + | DelayedOr(l, r) -> + sb.Append(" OP_IF") |> ignore + l.Serialize(sb) |> ignore + sb.Append(" OP_ELSE") |> ignore + r.Serialize(sb) |> ignore + sb.Append(" OP_ENDIF OP_CHECKSIG") + | CastE e -> e.Serialize(sb) - member this.IsT() = - match this with - | ETree _ - | TTree _ -> true - | FTree f -> - match f with - | F.CascadeOr _ - | F.SwitchOrV _ -> true + type AST with + + member this.Print() = + match this with + | ETree e -> e.print() + | QTree q -> q.print() + | WTree w -> w.print() + | FTree f -> f.print() + | VTree v -> v.print() + | TTree t -> t.print() + + member this.ToScript() = + let sb = StringBuilder() + match this with + | ETree e -> + let s = e.Serialize(sb) + NBitcoin.Script(s.ToString()) + | QTree q -> + let s = q.Serialize(sb) + NBitcoin.Script(s.ToString()) + | WTree w -> + let s = w.Serialize(sb) + NBitcoin.Script(s.ToString()) + | FTree f -> + let s = f.Serialize(sb) + NBitcoin.Script(s.ToString()) + | VTree v -> + let s = v.Serialize(sb) + NBitcoin.Script(s.ToString()) + | TTree t -> + let s = t.Serialize(sb) + let str = s.ToString() + NBitcoin.Script(str) + member this.GetASTType() = + match this with + | ETree _ -> EExpr + | QTree _ -> QExpr + | WTree _ -> WExpr + | FTree _ -> FExpr + | VTree _ -> VExpr + | TTree _ -> TExpr + + member this.IsT() = + match this with + | ETree _ + | TTree _ -> true + | FTree f -> + match f with + | F.CascadeOr _ + | F.SwitchOrV _ -> true + | _ -> false | _ -> false - | _ -> false - member this.castT() : Result = - match this with - | TTree t -> Ok t - | FTree f -> - match f with - | F.CascadeOr(l, r) -> Ok(T.CascadeOrV(l, r)) - | F.SwitchOrV(l, r) -> Ok(T.SwitchOrV(l, r)) + member this.castT() : Result = + match this with + | TTree t -> Ok t + | FTree f -> + match f with + | F.CascadeOr(l, r) -> Ok(T.CascadeOrV(l, r)) + | F.SwitchOrV(l, r) -> Ok(T.SwitchOrV(l, r)) + | _ -> Error(sprintf "failed to cast %s" (this.Print())) + | ETree e -> + match e with + | E.ParallelOr(l, r) -> Ok(T.ParallelOr(l, r)) + | otherE -> Ok(T.CastE(otherE)) + | _ -> Error(sprintf "failed to cast %s" (this.Print())) + + member this.castE() : Result = + match this with + | ETree e -> Ok e + | _ -> Error(sprintf "failed to cast %s" (this.Print())) + + member this.castQ() : Result = + match this with + | QTree q -> Ok q + | _ -> Error(sprintf "failed to cast %s" (this.Print())) + + member this.castW() : Result = + match this with + | WTree w -> Ok w + | _ -> Error(sprintf "failed to cast %s" (this.Print())) + + member this.castF() : Result = + match this with + | FTree f -> Ok f + | _ -> Error(sprintf "failed to cast %s" (this.Print())) + + member this.castV() : Result = + match this with + | VTree v -> Ok v | _ -> Error(sprintf "failed to cast %s" (this.Print())) - | ETree e -> - match e with - | E.ParallelOr(l, r) -> Ok(T.ParallelOr(l, r)) - | otherE -> Ok(T.CastE(otherE)) - | _ -> Error(sprintf "failed to cast %s" (this.Print())) - - member this.castE() : Result = - match this with - | ETree e -> Ok e - | _ -> Error(sprintf "failed to cast %s" (this.Print())) - - member this.castQ() : Result = - match this with - | QTree q -> Ok q - | _ -> Error(sprintf "failed to cast %s" (this.Print())) - - member this.castW() : Result = - match this with - | WTree w -> Ok w - | _ -> Error(sprintf "failed to cast %s" (this.Print())) - - member this.castF() : Result = - match this with - | FTree f -> Ok f - | _ -> Error(sprintf "failed to cast %s" (this.Print())) - - member this.castV() : Result = - match this with - | VTree v -> Ok v - | _ -> Error(sprintf "failed to cast %s" (this.Print())) - - member this.castTUnsafe() : T = - match this.castT() with - | Ok t -> t - | Error s -> failwith s - - member this.castEUnsafe() : E = - match this.castE() with - | Ok e -> e - | Error s -> failwith s - - member this.castQUnsafe() : Q = - match this.castQ() with - | Ok q -> q - | Error s -> failwith s - - member this.castWUnsafe() : W = - match this.castW() with - | Ok w -> w - | Error s -> failwith s - - member this.castFUnsafe() : F = - match this.castF() with - | Ok f -> f - | Error s -> failwith s - - member this.castVUnsafe() : V = - match this.castV() with - | Ok v -> v - | Error s -> failwith s + + member this.castTUnsafe() : T = + match this.castT() with + | Ok t -> t + | Error s -> failwith s + + member this.castEUnsafe() : E = + match this.castE() with + | Ok e -> e + | Error s -> failwith s + + member this.castQUnsafe() : Q = + match this.castQ() with + | Ok q -> q + | Error s -> failwith s + + member this.castWUnsafe() : W = + match this.castW() with + | Ok w -> w + | Error s -> failwith s + + member this.castFUnsafe() : F = + match this.castF() with + | Ok f -> f + | Error s -> failwith s + + member this.castVUnsafe() : V = + match this.castV() with + | Ok v -> v + | Error s -> failwith s diff --git a/NBitcoin.Miniscript/MiniscriptCompiler.fs b/NBitcoin.Miniscript/MiniscriptCompiler.fs index fb5a5cbad4..7ce8e1f424 100644 --- a/NBitcoin.Miniscript/MiniscriptCompiler.fs +++ b/NBitcoin.Miniscript/MiniscriptCompiler.fs @@ -1,1087 +1,1088 @@ -module FNBitcoin.MiniScriptCompiler +namespace NBitcoin.Miniscript open NBitcoin -open FNBitcoin.Utils -open FNBitcoin.MiniScriptParser -open FNBitcoin.MiniScript -open MiniScriptAST +open NBitcoin.Miniscript.Utils +open NBitcoin.Miniscript.MiniscriptParser +open Miniscript.AST -type CompiledNode = - | Pk of NBitcoin.PubKey - | Multi of uint32 * PubKey [] - | Hash of uint256 - | Time of LockTime - | Threshold of uint32 * CompiledNode [] - | And of left : CompiledNode * right : CompiledNode - | Or of left : CompiledNode * right : CompiledNode * leftProb : float * rightProb : float +module Compiler = + type CompiledNode = + | Pk of NBitcoin.PubKey + | Multi of uint32 * PubKey [] + | Hash of uint256 + | Time of LockTime + | Threshold of uint32 * CompiledNode [] + | And of left : CompiledNode * right : CompiledNode + | Or of left : CompiledNode * right : CompiledNode * leftProb : float * rightProb : float -type Cost = - { ast : AST - pkCost : uint32 - satCost : float - dissatCost : float } + type Cost = + { ast : AST + pkCost : uint32 + satCost : float + dissatCost : float } -/// Intermediary value before computing parent Cost -type CostTriple = - { parent : AST - left : Cost - right : Cost - /// In case of F ast, we can tell the compiler that - /// it can be combined as an E expression in two ways. - /// This is equivalent to `->` in this macro - /// ref: https://github.com/apoelstra/rust-miniscript/blob/ac36d4bacd6440458a57b4bd2013ea1c27058709/src/policy/compiler.rs#L333 - condCombine : bool } + /// Intermediary value before computing parent Cost + type CostTriple = + { parent : AST + left : Cost + right : Cost + /// In case of F ast, we can tell the compiler that + /// it can be combined as an E expression in two ways. + /// This is equivalent to `->` in this macro + /// ref: https://github.com/apoelstra/rust-miniscript/blob/ac36d4bacd6440458a57b4bd2013ea1c27058709/src/policy/compiler.rs#L333 + condCombine : bool } -module Cost = - /// Casts F -> E - let likely (fcost : Cost) : Cost = - { ast = ETree(E.Likely(fcost.ast.castFUnsafe())) - pkCost = fcost.pkCost + 4u - satCost = fcost.satCost * 1.0 - dissatCost = 2.0 } - - let unlikely (fcost : Cost) : Cost = - { ast = ETree(E.Unlikely(fcost.ast.castFUnsafe())) - pkCost = fcost.pkCost + 4u - satCost = fcost.satCost * 2.0 - dissatCost = 1.0 } - - let fromPairToTCost (left : Cost) (right : Cost) (newAst : T) - (lweight : float) (rweight : float) = - match newAst with - | T.Time _ | T.HashEqual _ | T.CastE _ -> failwith "unreachable" - | T.And _ -> - { ast = TTree newAst - pkCost = left.pkCost + right.pkCost - satCost = left.satCost + right.satCost - dissatCost = 0.0 } - | T.ParallelOr _ -> - { ast = TTree newAst - pkCost = left.pkCost + right.pkCost + 1u - satCost = - (left.satCost + right.dissatCost) * lweight - + (right.satCost + left.dissatCost) * rweight - dissatCost = 0.0 } - | T.CascadeOr _ | T.CascadeOrV _ -> - { ast = TTree newAst - pkCost = left.pkCost + right.pkCost + 3u - satCost = - left.satCost * lweight - + (right.satCost + left.dissatCost) * rweight - dissatCost = 0.0 } - | T.SwitchOr _ -> - { ast = TTree newAst - pkCost = left.pkCost + right.pkCost + 3u - satCost = - (left.satCost + 2.0) * lweight - + (right.satCost + 1.0) * rweight - dissatCost = 0.0 } - | T.SwitchOrV _ -> - { ast = TTree newAst - pkCost = left.pkCost + right.pkCost + 4u - satCost = - (left.satCost + 2.0) * lweight - + (right.satCost + 1.0) * rweight - dissatCost = 0.0 } - | T.DelayedOr _ -> - { ast = TTree newAst - pkCost = left.pkCost + right.pkCost + 4u - satCost = - 72.0 + (left.satCost + 2.0) * lweight - + (right.satCost + 1.0) * rweight - dissatCost = 0.0 } - - let fromPairToVCost (left : Cost) (right : Cost) (newAst : V) - (lweight : float) (rweight : float) = - match newAst with - | V.CheckSig _ | V.CheckMultiSig _ | V.Time _ | V.HashEqual _ | V.Threshold _ -> - failwith "unreachable" - | V.And _ -> - { ast = VTree newAst - pkCost = left.pkCost + right.pkCost - satCost = left.satCost + right.satCost - dissatCost = 0.0 } - | V.CascadeOr _ -> - { ast = VTree newAst - pkCost = left.pkCost + right.pkCost + 2u - satCost = - (left.satCost * lweight) - + (right.satCost + left.dissatCost) * rweight - dissatCost = 0.0 } - | V.SwitchOr _ -> - { ast = VTree newAst - pkCost = left.pkCost + right.pkCost + 3u - satCost = - (left.satCost + 2.0) * lweight - + (right.satCost + 1.0) * rweight - dissatCost = 0.0 } - | V.SwitchOrT _ -> - { ast = VTree newAst - pkCost = left.pkCost + right.pkCost + 4u - satCost = - (left.satCost + 2.0) * lweight - + (right.satCost + 1.0) * rweight - dissatCost = 0.0 } - | V.DelayedOr _ -> - { ast = VTree newAst - pkCost = left.pkCost + right.pkCost + 4u - satCost = - (72.0 + left.satCost + 2.0) * lweight - + (72.0 + right.satCost + 1.0) * rweight - dissatCost = 0.0 } - - let fromPairToFCost (left : Cost) (right : Cost) (newAst : F) - (lweight : float) (rweight : float) = - match newAst with - | F.CheckSig _ | F.CheckMultiSig _ | F.Time _ | F.HashEqual _ | F.Threshold _ -> - failwith "unreachable" - | F.And _ -> - { ast = FTree newAst - pkCost = left.pkCost + right.pkCost - satCost = left.satCost + right.satCost - dissatCost = 0.0 } - | F.CascadeOr _ -> - { ast = FTree newAst - pkCost = left.pkCost + right.pkCost + 3u - satCost = - left.satCost * lweight - + (right.satCost + left.dissatCost) * rweight - dissatCost = 0.0 } - | F.SwitchOr _ -> - { ast = FTree newAst - pkCost = left.pkCost + right.pkCost + 3u - satCost = - (left.satCost + 2.0) * lweight - + (right.satCost + 1.0) * rweight - dissatCost = 0.0 } - | F.SwitchOrV _ -> - { ast = FTree newAst - pkCost = left.pkCost + right.pkCost + 4u - satCost = - (left.satCost + 2.0) * lweight - + (right.satCost + 1.0) * rweight - dissatCost = 0.0 } - | F.DelayedOr _ -> - { ast = FTree newAst - pkCost = left.pkCost + right.pkCost + 5u - satCost = - 72.0 + (left.satCost + 2.0) * lweight - + (right.satCost + 1.0) * rweight - dissatCost = 0.0 } - - let fromPairToECost (left : Cost) (right : Cost) (newAst : E) - (lweight : float) (rweight : float) = - let pkCost = left.pkCost + right.pkCost - match newAst with - | E.CheckSig _ | E.CheckMultiSig _ | E.Time _ | E.Threshold _ | E.Likely _ | E.Unlikely _ | E.ParallelAnd _ -> - { ast = ETree newAst - pkCost = pkCost + 1u - satCost = left.satCost + right.satCost - dissatCost = left.dissatCost + right.dissatCost } - | E.CascadeAnd _ -> - { ast = ETree newAst - pkCost = pkCost + 4u - satCost = left.satCost + right.satCost - dissatCost = left.dissatCost } - | E.ParallelOr _ -> - { ast = ETree newAst - pkCost = pkCost + 1u - satCost = - left.satCost * lweight - + (right.satCost + left.dissatCost) * rweight - dissatCost = left.dissatCost + right.dissatCost } - | E.CascadeOr _ -> - { ast = ETree newAst - pkCost = left.pkCost + right.pkCost + 3u - satCost = - left.satCost * lweight - + (right.satCost + left.dissatCost) * rweight - dissatCost = left.dissatCost + right.dissatCost } - | E.SwitchOrLeft _ -> - { ast = ETree newAst - pkCost = pkCost + 3u - satCost = - (left.satCost + 2.0) * lweight - + (right.satCost + 1.0) * rweight - dissatCost = left.dissatCost + 2.0 } - | E.SwitchOrRight _ -> - { ast = ETree newAst - pkCost = pkCost + 3u - satCost = - (left.satCost + 1.0) * lweight - + (right.satCost + 2.0) * rweight - dissatCost = left.dissatCost + 1.0 } - - // TODO: Consider about treating swap case (eft <=> right) here. - let fromTriple (triple : CostTriple) (lweight : float) (rweight : float) : Cost [] = - match triple.parent with - | TTree t -> - fromPairToTCost triple.left triple.right t lweight rweight - |> Array.singleton - | ETree e -> - fromPairToECost triple.left triple.right e lweight rweight - |> Array.singleton - | FTree f -> - match triple.condCombine with - | (false) -> - fromPairToFCost triple.left triple.right f lweight rweight + module Cost = + /// Casts F -> E + let likely (fcost : Cost) : Cost = + { ast = ETree(E.Likely(fcost.ast.castFUnsafe())) + pkCost = fcost.pkCost + 4u + satCost = fcost.satCost * 1.0 + dissatCost = 2.0 } + + let unlikely (fcost : Cost) : Cost = + { ast = ETree(E.Unlikely(fcost.ast.castFUnsafe())) + pkCost = fcost.pkCost + 4u + satCost = fcost.satCost * 2.0 + dissatCost = 1.0 } + + let fromPairToTCost (left : Cost) (right : Cost) (newAst : T) + (lweight : float) (rweight : float) = + match newAst with + | T.Time _ | T.HashEqual _ | T.CastE _ -> failwith "unreachable" + | T.And _ -> + { ast = TTree newAst + pkCost = left.pkCost + right.pkCost + satCost = left.satCost + right.satCost + dissatCost = 0.0 } + | T.ParallelOr _ -> + { ast = TTree newAst + pkCost = left.pkCost + right.pkCost + 1u + satCost = + (left.satCost + right.dissatCost) * lweight + + (right.satCost + left.dissatCost) * rweight + dissatCost = 0.0 } + | T.CascadeOr _ | T.CascadeOrV _ -> + { ast = TTree newAst + pkCost = left.pkCost + right.pkCost + 3u + satCost = + left.satCost * lweight + + (right.satCost + left.dissatCost) * rweight + dissatCost = 0.0 } + | T.SwitchOr _ -> + { ast = TTree newAst + pkCost = left.pkCost + right.pkCost + 3u + satCost = + (left.satCost + 2.0) * lweight + + (right.satCost + 1.0) * rweight + dissatCost = 0.0 } + | T.SwitchOrV _ -> + { ast = TTree newAst + pkCost = left.pkCost + right.pkCost + 4u + satCost = + (left.satCost + 2.0) * lweight + + (right.satCost + 1.0) * rweight + dissatCost = 0.0 } + | T.DelayedOr _ -> + { ast = TTree newAst + pkCost = left.pkCost + right.pkCost + 4u + satCost = + 72.0 + (left.satCost + 2.0) * lweight + + (right.satCost + 1.0) * rweight + dissatCost = 0.0 } + + let fromPairToVCost (left : Cost) (right : Cost) (newAst : V) + (lweight : float) (rweight : float) = + match newAst with + | V.CheckSig _ | V.CheckMultiSig _ | V.Time _ | V.HashEqual _ | V.Threshold _ -> + failwith "unreachable" + | V.And _ -> + { ast = VTree newAst + pkCost = left.pkCost + right.pkCost + satCost = left.satCost + right.satCost + dissatCost = 0.0 } + | V.CascadeOr _ -> + { ast = VTree newAst + pkCost = left.pkCost + right.pkCost + 2u + satCost = + (left.satCost * lweight) + + (right.satCost + left.dissatCost) * rweight + dissatCost = 0.0 } + | V.SwitchOr _ -> + { ast = VTree newAst + pkCost = left.pkCost + right.pkCost + 3u + satCost = + (left.satCost + 2.0) * lweight + + (right.satCost + 1.0) * rweight + dissatCost = 0.0 } + | V.SwitchOrT _ -> + { ast = VTree newAst + pkCost = left.pkCost + right.pkCost + 4u + satCost = + (left.satCost + 2.0) * lweight + + (right.satCost + 1.0) * rweight + dissatCost = 0.0 } + | V.DelayedOr _ -> + { ast = VTree newAst + pkCost = left.pkCost + right.pkCost + 4u + satCost = + (72.0 + left.satCost + 2.0) * lweight + + (72.0 + right.satCost + 1.0) * rweight + dissatCost = 0.0 } + + let fromPairToFCost (left : Cost) (right : Cost) (newAst : F) + (lweight : float) (rweight : float) = + match newAst with + | F.CheckSig _ | F.CheckMultiSig _ | F.Time _ | F.HashEqual _ | F.Threshold _ -> + failwith "unreachable" + | F.And _ -> + { ast = FTree newAst + pkCost = left.pkCost + right.pkCost + satCost = left.satCost + right.satCost + dissatCost = 0.0 } + | F.CascadeOr _ -> + { ast = FTree newAst + pkCost = left.pkCost + right.pkCost + 3u + satCost = + left.satCost * lweight + + (right.satCost + left.dissatCost) * rweight + dissatCost = 0.0 } + | F.SwitchOr _ -> + { ast = FTree newAst + pkCost = left.pkCost + right.pkCost + 3u + satCost = + (left.satCost + 2.0) * lweight + + (right.satCost + 1.0) * rweight + dissatCost = 0.0 } + | F.SwitchOrV _ -> + { ast = FTree newAst + pkCost = left.pkCost + right.pkCost + 4u + satCost = + (left.satCost + 2.0) * lweight + + (right.satCost + 1.0) * rweight + dissatCost = 0.0 } + | F.DelayedOr _ -> + { ast = FTree newAst + pkCost = left.pkCost + right.pkCost + 5u + satCost = + 72.0 + (left.satCost + 2.0) * lweight + + (right.satCost + 1.0) * rweight + dissatCost = 0.0 } + + let fromPairToECost (left : Cost) (right : Cost) (newAst : E) + (lweight : float) (rweight : float) = + let pkCost = left.pkCost + right.pkCost + match newAst with + | E.CheckSig _ | E.CheckMultiSig _ | E.Time _ | E.Threshold _ | E.Likely _ | E.Unlikely _ | E.ParallelAnd _ -> + { ast = ETree newAst + pkCost = pkCost + 1u + satCost = left.satCost + right.satCost + dissatCost = left.dissatCost + right.dissatCost } + | E.CascadeAnd _ -> + { ast = ETree newAst + pkCost = pkCost + 4u + satCost = left.satCost + right.satCost + dissatCost = left.dissatCost } + | E.ParallelOr _ -> + { ast = ETree newAst + pkCost = pkCost + 1u + satCost = + left.satCost * lweight + + (right.satCost + left.dissatCost) * rweight + dissatCost = left.dissatCost + right.dissatCost } + | E.CascadeOr _ -> + { ast = ETree newAst + pkCost = left.pkCost + right.pkCost + 3u + satCost = + left.satCost * lweight + + (right.satCost + left.dissatCost) * rweight + dissatCost = left.dissatCost + right.dissatCost } + | E.SwitchOrLeft _ -> + { ast = ETree newAst + pkCost = pkCost + 3u + satCost = + (left.satCost + 2.0) * lweight + + (right.satCost + 1.0) * rweight + dissatCost = left.dissatCost + 2.0 } + | E.SwitchOrRight _ -> + { ast = ETree newAst + pkCost = pkCost + 3u + satCost = + (left.satCost + 1.0) * lweight + + (right.satCost + 2.0) * rweight + dissatCost = left.dissatCost + 1.0 } + + // TODO: Consider about treating swap case (eft <=> right) here. + let fromTriple (triple : CostTriple) (lweight : float) (rweight : float) : Cost [] = + match triple.parent with + | TTree t -> + fromPairToTCost triple.left triple.right t lweight rweight + |> Array.singleton + | ETree e -> + fromPairToECost triple.left triple.right e lweight rweight |> Array.singleton - | (true) -> - let costBeforeCast = - fromPairToFCost triple.left triple.right f lweight rweight - [| likely (costBeforeCast) - unlikely (costBeforeCast) |] - | VTree v -> - fromPairToVCost triple.left triple.right v lweight rweight - |> Array.singleton - - let min_cost (a : Cost, b : Cost, p_sat : float, p_dissat : float) = - let weight_one = - (float a.pkCost) + p_sat * a.satCost + p_dissat * a.dissatCost - let weight_two = - (float b.pkCost) + p_sat * b.satCost + p_dissat * a.dissatCost - if weight_one < weight_two then a - else if weight_one > weight_two then b - else if a.satCost < b.satCost then a - else b - - let fold_costs (p_sat : float) (p_dissat : float) (cs : Cost []) = - cs - |> Array.toList - |> List.reduce (fun a b -> min_cost (a, b, p_sat, p_dissat)) - - // equivalent to rules! macro in rust-miniscript - let getMinimumCost (triples : CostTriple []) (p_sat) (p_dissat) - (lweight : float) (rweight : float) : Cost = - triples - |> Array.map (fun p -> fromTriple p 0.0 0.0) - |> Array.concat - |> fold_costs p_sat p_dissat + | FTree f -> + match triple.condCombine with + | (false) -> + fromPairToFCost triple.left triple.right f lweight rweight + |> Array.singleton + | (true) -> + let costBeforeCast = + fromPairToFCost triple.left triple.right f lweight rweight + [| likely (costBeforeCast) + unlikely (costBeforeCast) |] + | VTree v -> + fromPairToVCost triple.left triple.right v lweight rweight + |> Array.singleton + + let min_cost (a : Cost, b : Cost, p_sat : float, p_dissat : float) = + let weight_one = + (float a.pkCost) + p_sat * a.satCost + p_dissat * a.dissatCost + let weight_two = + (float b.pkCost) + p_sat * b.satCost + p_dissat * a.dissatCost + if weight_one < weight_two then a + else if weight_one > weight_two then b + else if a.satCost < b.satCost then a + else b + + let fold_costs (p_sat : float) (p_dissat : float) (cs : Cost []) = + cs + |> Array.toList + |> List.reduce (fun a b -> min_cost (a, b, p_sat, p_dissat)) + + // equivalent to rules! macro in rust-miniscript + let getMinimumCost (triples : CostTriple []) (p_sat) (p_dissat) + (lweight : float) (rweight : float) : Cost = + triples + |> Array.map (fun p -> fromTriple p 0.0 0.0) + |> Array.concat + |> fold_costs p_sat p_dissat -module CompiledNode = - /// bytes length when a number is encoded as bitcoin CScriptNum - let private scriptNumCost n = - if n <= 16u then 1u - else if n < 0x100u then 2u - else if n < 0x10000u then 3u - else if n < 0x1000000u then 4u - else 5u - - let private minCost (one : Cost, two : Cost, p_sat : float, p_dissat) = - let weight_one = - (float one.pkCost) + p_sat * one.satCost + p_dissat * one.dissatCost - let weight_two = - (float two.pkCost) + p_sat * two.satCost + p_dissat * two.dissatCost - if weight_one < weight_two then one - else if weight_two < weight_one then one - else if one.satCost < two.satCost then one - else two - - let private getPkCost m (pks : PubKey []) = - match (m > 16u, pks.Length > 16) with - | (true, true) -> 4 - | (true, false) -> 3 - | (false, true) -> 3 - | (false, false) -> 2 - - let rec fromPolicy (p : Policy) : CompiledNode = - match p with - | Key k -> Pk k - | Policy.Multi(m, pks) -> Multi(m, pks) - | Policy.Hash h -> Hash h - | Policy.Time t -> Time t - | Policy.Threshold(n, subexprs) -> - let ps = subexprs |> Array.map fromPolicy - Threshold(n, ps) - | Policy.And(e1, e2) -> And(fromPolicy e1, fromPolicy e2) - | Policy.Or(e1, e2) -> Or(fromPolicy e1, fromPolicy e2, 0.5, 0.5) - | Policy.AsymmetricOr(e1, e2) -> - Or(fromPolicy e1, fromPolicy e2, 127.0 / 128.0, 1.0 / 128.0) - - // TODO: cache - let rec best_t (node : CompiledNode, p_sat : float, p_dissat : float) : Cost = - match node with - | Pk _ | Multi _ | Threshold _ -> - let e = best_e (node, p_sat, p_dissat) - { ast = TTree(T.CastE(e.ast.castEUnsafe())) - pkCost = e.pkCost - satCost = e.satCost - dissatCost = 0.0 } - | Time t -> - let num_cost = scriptNumCost (!> t) - { ast = TTree(T.Time t) - pkCost = 1u + uint32 num_cost - satCost = 0.0 - dissatCost = 0.0 } - | Hash h -> - { ast = TTree(T.HashEqual h) - pkCost = 39u - satCost = 33.0 - dissatCost = 0.0 } - | And(l, r) -> - let vl = best_v (l, p_sat, 0.0) - let vr = best_v (r, p_sat, 0.0) - let tl = best_t (l, p_sat, 0.0) - let tr = best_t (r, p_sat, 0.0) - - let possibleCases = - [| { parent = - TTree - (T.And(vl.ast.castVUnsafe(), tr.ast.castTUnsafe())) - left = vl - right = tr - condCombine = false } - { parent = - TTree - (T.And(vr.ast.castVUnsafe(), tl.ast.castTUnsafe())) - left = vl - right = tr - condCombine = false } |] - Cost.getMinimumCost possibleCases p_sat p_dissat 0.0 0.0 - | Or(l, r, lweight, rweight) -> - let le = best_e (l, (p_sat * lweight), (p_sat * rweight)) - let re = best_e (r, (p_sat * rweight), (p_sat * lweight)) - let lw = best_w (l, (p_sat * lweight), (p_sat * rweight)) - let rw = best_w (r, (p_sat * rweight), (p_sat * lweight)) - let lt = best_t (l, (p_sat * lweight), 0.0) - let rt = best_t (r, (p_sat * lweight), 0.0) - let lv = best_v (l, (p_sat * lweight), 0.0) - let rv = best_v (r, (p_sat * lweight), 0.0) - let maybelq = best_q (l, (p_sat * lweight), 0.0) - let mayberq = best_q (r, (p_sat * lweight), 0.0) - - let possibleCases = - [| { parent = - TTree - (T.ParallelOr - (le.ast.castEUnsafe(), rw.ast.castWUnsafe())) - left = le - right = rw - condCombine = false } - { parent = - TTree - (T.ParallelOr - (re.ast.castEUnsafe(), lw.ast.castWUnsafe())) - left = re - right = lw - condCombine = false } - { parent = - TTree - (T.CascadeOr - (le.ast.castEUnsafe(), rt.ast.castTUnsafe())) - left = le - right = rt - condCombine = false } - { parent = - TTree - (T.CascadeOr - (re.ast.castEUnsafe(), lt.ast.castTUnsafe())) - left = re - right = lt - condCombine = false } - { parent = - TTree - (T.CascadeOrV - (le.ast.castEUnsafe(), rv.ast.castVUnsafe())) - left = le - right = rv - condCombine = false } - { parent = - TTree - (T.CascadeOrV - (re.ast.castEUnsafe(), lv.ast.castVUnsafe())) - left = re - right = lv - condCombine = false } - { parent = - TTree - (T.SwitchOr - (lt.ast.castTUnsafe(), rt.ast.castTUnsafe())) - left = lt - right = rt - condCombine = false } - { parent = - TTree - (T.SwitchOr - (rt.ast.castTUnsafe(), lt.ast.castTUnsafe())) - left = rt - right = lt - condCombine = false } - { parent = - TTree - (T.SwitchOrV - (lv.ast.castVUnsafe(), rv.ast.castVUnsafe())) - left = lv - right = rv - condCombine = false } - { parent = - TTree - (T.SwitchOrV - (rv.ast.castVUnsafe(), lv.ast.castVUnsafe())) - left = rv - right = lv - condCombine = false } |] - - let casesWithQ = - match maybelq, mayberq with - | Some lq, Some rq -> - Array.append possibleCases [| { parent = - TTree - (T.DelayedOr - (lq.ast.castQUnsafe - (), - rq.ast.castQUnsafe - ())) - left = lq - right = rq - condCombine = false } |] - | _ -> possibleCases - - Cost.getMinimumCost casesWithQ p_sat 0.0 lweight rweight - - and best_e (node : CompiledNode, p_sat : float, p_dissat : float) : Cost = - match node with - | Pk k -> - { ast = ETree(E.CheckSig k) - pkCost = 35u - satCost = 72.0 - dissatCost = 1.0 } - | Multi(m, pks) -> - let num_cost = getPkCost m pks - - let options = - [ { ast = ETree(E.CheckMultiSig(m, pks)) - pkCost = uint32 (num_cost + 34 * pks.Length + 1) - satCost = 2.0 - dissatCost = 1.0 } ] - if not (p_dissat > 0.0) then options.[0] - else - let bestf = best_f (node, p_sat, 0.0) + module CompiledNode = + /// bytes length when a number is encoded as bitcoin CScriptNum + let private scriptNumCost n = + if n <= 16u then 1u + else if n < 0x100u then 2u + else if n < 0x10000u then 3u + else if n < 0x1000000u then 4u + else 5u + + let private minCost (one : Cost, two : Cost, p_sat : float, p_dissat) = + let weight_one = + (float one.pkCost) + p_sat * one.satCost + p_dissat * one.dissatCost + let weight_two = + (float two.pkCost) + p_sat * two.satCost + p_dissat * two.dissatCost + if weight_one < weight_two then one + else if weight_two < weight_one then one + else if one.satCost < two.satCost then one + else two + + let private getPkCost m (pks : PubKey []) = + match (m > 16u, pks.Length > 16) with + | (true, true) -> 4 + | (true, false) -> 3 + | (false, true) -> 3 + | (false, false) -> 2 + + let rec fromPolicy (p : Policy) : CompiledNode = + match p with + | Key k -> Pk k + | Policy.Multi(m, pks) -> Multi(m, pks) + | Policy.Hash h -> Hash h + | Policy.Time t -> Time t + | Policy.Threshold(n, subexprs) -> + let ps = subexprs |> Array.map fromPolicy + Threshold(n, ps) + | Policy.And(e1, e2) -> And(fromPolicy e1, fromPolicy e2) + | Policy.Or(e1, e2) -> Or(fromPolicy e1, fromPolicy e2, 0.5, 0.5) + | Policy.AsymmetricOr(e1, e2) -> + Or(fromPolicy e1, fromPolicy e2, 127.0 / 128.0, 1.0 / 128.0) + + // TODO: cache + let rec best_t (node : CompiledNode, p_sat : float, p_dissat : float) : Cost = + match node with + | Pk _ | Multi _ | Threshold _ -> + let e = best_e (node, p_sat, p_dissat) + { ast = TTree(T.CastE(e.ast.castEUnsafe())) + pkCost = e.pkCost + satCost = e.satCost + dissatCost = 0.0 } + | Time t -> + let num_cost = scriptNumCost (!> t) + { ast = TTree(T.Time t) + pkCost = 1u + uint32 num_cost + satCost = 0.0 + dissatCost = 0.0 } + | Hash h -> + { ast = TTree(T.HashEqual h) + pkCost = 39u + satCost = 33.0 + dissatCost = 0.0 } + | And(l, r) -> + let vl = best_v (l, p_sat, 0.0) + let vr = best_v (r, p_sat, 0.0) + let tl = best_t (l, p_sat, 0.0) + let tr = best_t (r, p_sat, 0.0) - let options2 = - [ Cost.likely (bestf) - Cost.unlikely (bestf) ] - List.concat [ options; options2 ] - |> List.toArray - |> Cost.fold_costs p_sat p_dissat - | Time n -> - let num_cost = scriptNumCost (!> n) - { ast = ETree(E.Time n) - pkCost = 5u + num_cost - satCost = 2.0 - dissatCost = 1.0 } - | Hash h -> - let fcost = best_f (node, p_sat, p_dissat) - minCost (Cost.likely fcost, Cost.unlikely fcost, p_sat, p_dissat) - | And(l, r) -> - let le = best_e (l, p_sat, p_dissat) - let re = best_e (r, p_sat, p_dissat) - let lw = best_w (l, p_sat, p_dissat) - let rw = best_w (r, p_sat, p_dissat) - let lf = best_f (l, p_sat, 0.0) - let rf = best_f (r, p_sat, 0.0) - let lv = best_v (l, p_sat, 0.0) - let rv = best_v (r, p_sat, 0.0) - - let possibleCases = - [| { parent = - ETree - (E.ParallelAnd - (le.ast.castEUnsafe(), rw.ast.castWUnsafe())) - left = le - right = rw - condCombine = false } - { parent = - ETree - (E.ParallelAnd - (re.ast.castEUnsafe(), lw.ast.castWUnsafe())) - left = re - right = lw - condCombine = false } - { parent = - ETree - (E.CascadeAnd - (le.ast.castEUnsafe(), rf.ast.castFUnsafe())) - left = le - right = rf - condCombine = false } - { parent = - ETree - (E.CascadeAnd - (re.ast.castEUnsafe(), lf.ast.castFUnsafe())) - left = re - right = lf - condCombine = false } - { parent = - FTree - (F.And(lv.ast.castVUnsafe(), rf.ast.castFUnsafe())) - left = lv - right = rf - condCombine = true } - { parent = - FTree - (F.And(rv.ast.castVUnsafe(), lf.ast.castFUnsafe())) - left = rv - right = lf - condCombine = true } |] - Cost.getMinimumCost possibleCases p_sat p_dissat 0.5 0.5 - | Or(l, r, lweight, rweight) -> - let le_par = - best_e (l, (p_sat * lweight), (p_dissat + p_sat * rweight)) - let re_par = - best_e (r, (p_sat * lweight), (p_dissat + p_sat * rweight)) - let lw_par = - best_w (l, (p_sat * lweight), (p_dissat + p_sat * rweight)) - let rw_par = - best_w (r, (p_sat * lweight), (p_dissat + p_sat * rweight)) - let le_cas = best_e (l, (p_sat * lweight), (p_dissat)) - let re_cas = best_e (r, (p_sat * lweight), (p_dissat)) - let le_cond_par = best_e (l, (p_sat * lweight), (p_sat * rweight)) - let re_cond_par = best_e (r, (p_sat * lweight), (p_sat * lweight)) - let lv = best_v (l, (p_sat * lweight), 0.0) - let rv = best_v (r, (p_sat * rweight), 0.0) - let lf = best_f (l, (p_sat * lweight), 0.0) - let rf = best_f (r, (p_sat * rweight), 0.0) - let maybelq = best_q (l, (p_sat * lweight), 0.0) - let mayberq = best_q (r, (p_sat * rweight), 0.0) - - let possibleCases = - [| { parent = - ETree - (E.ParallelOr - (le_par.ast.castEUnsafe(), - rw_par.ast.castWUnsafe())) - left = le_par - right = rw_par - condCombine = false } - { parent = - ETree - (E.ParallelOr - (re_par.ast.castEUnsafe(), - lw_par.ast.castWUnsafe())) - left = re_par - right = lw_par - condCombine = false } - { parent = - ETree - (E.CascadeOr - (le_par.ast.castEUnsafe(), - re_cas.ast.castEUnsafe())) - left = le_par - right = re_cas - condCombine = false } - { parent = - ETree - (E.CascadeOr - (re_par.ast.castEUnsafe(), - le_cas.ast.castEUnsafe())) - left = re_par - right = le_cas - condCombine = false } - { parent = - ETree - (E.SwitchOrLeft - (le_cas.ast.castEUnsafe(), - rf.ast.castFUnsafe())) - left = le_cas - right = rf - condCombine = false } - { parent = - ETree - (E.SwitchOrLeft - (re_cas.ast.castEUnsafe(), - lf.ast.castFUnsafe())) - left = re_cas - right = lf - condCombine = false } - { parent = - ETree - (E.SwitchOrRight - (le_cas.ast.castEUnsafe(), - rf.ast.castFUnsafe())) - left = le_cas - right = rf - condCombine = false } - { parent = - ETree - (E.SwitchOrRight - (re_cas.ast.castEUnsafe(), - lf.ast.castFUnsafe())) - left = re_cas - right = lf - condCombine = false } - { parent = - FTree - (F.CascadeOr - (le_cas.ast.castEUnsafe(), - rv.ast.castVUnsafe())) - left = le_cas - right = rv - condCombine = true } - { parent = - FTree - (F.CascadeOr - (re_cas.ast.castEUnsafe(), - lv.ast.castVUnsafe())) - left = re_cas - right = lv - condCombine = true } - { parent = - FTree - (F.SwitchOr - (lf.ast.castFUnsafe(), rf.ast.castFUnsafe())) - left = lf - right = rf - condCombine = true } - { parent = - FTree - (F.SwitchOr - (rf.ast.castFUnsafe(), lf.ast.castFUnsafe())) - left = rf - right = lf - condCombine = true } - { parent = - FTree - (F.SwitchOrV - (lv.ast.castVUnsafe(), rv.ast.castVUnsafe())) - left = lv - right = rv - condCombine = true } - { parent = - FTree - (F.SwitchOrV - (rv.ast.castVUnsafe(), lv.ast.castVUnsafe())) - left = rv - right = lv - condCombine = true } |] - - let casesWithQ = - match maybelq, mayberq with - | Some lq, Some rq -> - Array.append possibleCases [| { parent = - FTree - (F.DelayedOr - (lq.ast.castQUnsafe - (), - rq.ast.castQUnsafe - ())) - left = lq - right = rq - condCombine = true } - { parent = - FTree - (F.DelayedOr - (rq.ast.castQUnsafe - (), - lq.ast.castQUnsafe - ())) - left = rq - right = lq - condCombine = true } |] - | _ -> possibleCases - - Cost.getMinimumCost casesWithQ p_sat p_dissat lweight rweight - | Threshold(n, subs) -> - let num_cost = scriptNumCost n - let avgCost = float n / float subs.Length - let e = - best_e - (subs.[0], (p_sat * avgCost), - (p_dissat + p_sat * (1.0 - avgCost))) - let ws = - subs - |> Array.map - (fun s -> - best_w - (s, (p_sat * avgCost), - (p_dissat + p_sat * (1.0 - avgCost)))) - let pk_cost = - ws - |> Array.fold (fun acc w -> acc + w.pkCost) - (1u + num_cost + e.pkCost) - let sat_cost = - ws |> Array.fold (fun acc w -> acc + w.satCost) e.satCost - let dissat_cost = - ws |> Array.fold (fun acc w -> acc + w.dissatCost) e.dissatCost - let wsast = ws |> Array.map (fun w -> w.ast.castWUnsafe()) - - let cond = - { ast = ETree(E.Threshold(n, e.ast.castEUnsafe(), wsast)) - pkCost = pk_cost - satCost = sat_cost - dissatCost = dissat_cost } - - let f = best_f (node, p_sat, 0.0) - let cond1 = Cost.likely (f) - let cond2 = Cost.unlikely (f) - let nonCond = Cost.min_cost (cond1, cond2, p_sat, p_dissat) - Cost.min_cost (cond, nonCond, p_sat, p_dissat) - - and best_q (node : CompiledNode, p_sat : float, p_dissat : float) : Cost option = - match node with - | Pk pk -> - { ast = QTree(Q.Pubkey(pk)) - pkCost = 34u - satCost = 0.0 - dissatCost = 0.0 } - |> Some - | And(l, r) -> - let maybelq = best_q (l, p_sat, p_dissat) - let mayberq = best_q (r, p_sat, p_dissat) - - let cost v q = - { ast = QTree(Q.And(v.ast.castVUnsafe(), q.ast.castQUnsafe())) - pkCost = v.pkCost + q.pkCost - satCost = v.satCost + q.satCost + let possibleCases = + [| { parent = + TTree + (T.And(vl.ast.castVUnsafe(), tr.ast.castTUnsafe())) + left = vl + right = tr + condCombine = false } + { parent = + TTree + (T.And(vr.ast.castVUnsafe(), tl.ast.castTUnsafe())) + left = vl + right = tr + condCombine = false } |] + Cost.getMinimumCost possibleCases p_sat p_dissat 0.0 0.0 + | Or(l, r, lweight, rweight) -> + let le = best_e (l, (p_sat * lweight), (p_sat * rweight)) + let re = best_e (r, (p_sat * rweight), (p_sat * lweight)) + let lw = best_w (l, (p_sat * lweight), (p_sat * rweight)) + let rw = best_w (r, (p_sat * rweight), (p_sat * lweight)) + let lt = best_t (l, (p_sat * lweight), 0.0) + let rt = best_t (r, (p_sat * lweight), 0.0) + let lv = best_v (l, (p_sat * lweight), 0.0) + let rv = best_v (r, (p_sat * lweight), 0.0) + let maybelq = best_q (l, (p_sat * lweight), 0.0) + let mayberq = best_q (r, (p_sat * lweight), 0.0) + + let possibleCases = + [| { parent = + TTree + (T.ParallelOr + (le.ast.castEUnsafe(), rw.ast.castWUnsafe())) + left = le + right = rw + condCombine = false } + { parent = + TTree + (T.ParallelOr + (re.ast.castEUnsafe(), lw.ast.castWUnsafe())) + left = re + right = lw + condCombine = false } + { parent = + TTree + (T.CascadeOr + (le.ast.castEUnsafe(), rt.ast.castTUnsafe())) + left = le + right = rt + condCombine = false } + { parent = + TTree + (T.CascadeOr + (re.ast.castEUnsafe(), lt.ast.castTUnsafe())) + left = re + right = lt + condCombine = false } + { parent = + TTree + (T.CascadeOrV + (le.ast.castEUnsafe(), rv.ast.castVUnsafe())) + left = le + right = rv + condCombine = false } + { parent = + TTree + (T.CascadeOrV + (re.ast.castEUnsafe(), lv.ast.castVUnsafe())) + left = re + right = lv + condCombine = false } + { parent = + TTree + (T.SwitchOr + (lt.ast.castTUnsafe(), rt.ast.castTUnsafe())) + left = lt + right = rt + condCombine = false } + { parent = + TTree + (T.SwitchOr + (rt.ast.castTUnsafe(), lt.ast.castTUnsafe())) + left = rt + right = lt + condCombine = false } + { parent = + TTree + (T.SwitchOrV + (lv.ast.castVUnsafe(), rv.ast.castVUnsafe())) + left = lv + right = rv + condCombine = false } + { parent = + TTree + (T.SwitchOrV + (rv.ast.castVUnsafe(), lv.ast.castVUnsafe())) + left = rv + right = lv + condCombine = false } |] + + let casesWithQ = + match maybelq, mayberq with + | Some lq, Some rq -> + Array.append possibleCases [| { parent = + TTree + (T.DelayedOr + (lq.ast.castQUnsafe + (), + rq.ast.castQUnsafe + ())) + left = lq + right = rq + condCombine = false } |] + | _ -> possibleCases + + Cost.getMinimumCost casesWithQ p_sat 0.0 lweight rweight + + and best_e (node : CompiledNode, p_sat : float, p_dissat : float) : Cost = + match node with + | Pk k -> + { ast = ETree(E.CheckSig k) + pkCost = 35u + satCost = 72.0 + dissatCost = 1.0 } + | Multi(m, pks) -> + let num_cost = getPkCost m pks + + let options = + [ { ast = ETree(E.CheckMultiSig(m, pks)) + pkCost = uint32 (num_cost + 34 * pks.Length + 1) + satCost = 2.0 + dissatCost = 1.0 } ] + if not (p_dissat > 0.0) then options.[0] + else + let bestf = best_f (node, p_sat, 0.0) + + let options2 = + [ Cost.likely (bestf) + Cost.unlikely (bestf) ] + List.concat [ options; options2 ] + |> List.toArray + |> Cost.fold_costs p_sat p_dissat + | Time n -> + let num_cost = scriptNumCost (!> n) + { ast = ETree(E.Time n) + pkCost = 5u + num_cost + satCost = 2.0 + dissatCost = 1.0 } + | Hash h -> + let fcost = best_f (node, p_sat, p_dissat) + minCost (Cost.likely fcost, Cost.unlikely fcost, p_sat, p_dissat) + | And(l, r) -> + let le = best_e (l, p_sat, p_dissat) + let re = best_e (r, p_sat, p_dissat) + let lw = best_w (l, p_sat, p_dissat) + let rw = best_w (r, p_sat, p_dissat) + let lf = best_f (l, p_sat, 0.0) + let rf = best_f (r, p_sat, 0.0) + let lv = best_v (l, p_sat, 0.0) + let rv = best_v (r, p_sat, 0.0) + + let possibleCases = + [| { parent = + ETree + (E.ParallelAnd + (le.ast.castEUnsafe(), rw.ast.castWUnsafe())) + left = le + right = rw + condCombine = false } + { parent = + ETree + (E.ParallelAnd + (re.ast.castEUnsafe(), lw.ast.castWUnsafe())) + left = re + right = lw + condCombine = false } + { parent = + ETree + (E.CascadeAnd + (le.ast.castEUnsafe(), rf.ast.castFUnsafe())) + left = le + right = rf + condCombine = false } + { parent = + ETree + (E.CascadeAnd + (re.ast.castEUnsafe(), lf.ast.castFUnsafe())) + left = re + right = lf + condCombine = false } + { parent = + FTree + (F.And(lv.ast.castVUnsafe(), rf.ast.castFUnsafe())) + left = lv + right = rf + condCombine = true } + { parent = + FTree + (F.And(rv.ast.castVUnsafe(), lf.ast.castFUnsafe())) + left = rv + right = lf + condCombine = true } |] + Cost.getMinimumCost possibleCases p_sat p_dissat 0.5 0.5 + | Or(l, r, lweight, rweight) -> + let le_par = + best_e (l, (p_sat * lweight), (p_dissat + p_sat * rweight)) + let re_par = + best_e (r, (p_sat * lweight), (p_dissat + p_sat * rweight)) + let lw_par = + best_w (l, (p_sat * lweight), (p_dissat + p_sat * rweight)) + let rw_par = + best_w (r, (p_sat * lweight), (p_dissat + p_sat * rweight)) + let le_cas = best_e (l, (p_sat * lweight), (p_dissat)) + let re_cas = best_e (r, (p_sat * lweight), (p_dissat)) + let le_cond_par = best_e (l, (p_sat * lweight), (p_sat * rweight)) + let re_cond_par = best_e (r, (p_sat * lweight), (p_sat * lweight)) + let lv = best_v (l, (p_sat * lweight), 0.0) + let rv = best_v (r, (p_sat * rweight), 0.0) + let lf = best_f (l, (p_sat * lweight), 0.0) + let rf = best_f (r, (p_sat * rweight), 0.0) + let maybelq = best_q (l, (p_sat * lweight), 0.0) + let mayberq = best_q (r, (p_sat * rweight), 0.0) + + let possibleCases = + [| { parent = + ETree + (E.ParallelOr + (le_par.ast.castEUnsafe(), + rw_par.ast.castWUnsafe())) + left = le_par + right = rw_par + condCombine = false } + { parent = + ETree + (E.ParallelOr + (re_par.ast.castEUnsafe(), + lw_par.ast.castWUnsafe())) + left = re_par + right = lw_par + condCombine = false } + { parent = + ETree + (E.CascadeOr + (le_par.ast.castEUnsafe(), + re_cas.ast.castEUnsafe())) + left = le_par + right = re_cas + condCombine = false } + { parent = + ETree + (E.CascadeOr + (re_par.ast.castEUnsafe(), + le_cas.ast.castEUnsafe())) + left = re_par + right = le_cas + condCombine = false } + { parent = + ETree + (E.SwitchOrLeft + (le_cas.ast.castEUnsafe(), + rf.ast.castFUnsafe())) + left = le_cas + right = rf + condCombine = false } + { parent = + ETree + (E.SwitchOrLeft + (re_cas.ast.castEUnsafe(), + lf.ast.castFUnsafe())) + left = re_cas + right = lf + condCombine = false } + { parent = + ETree + (E.SwitchOrRight + (le_cas.ast.castEUnsafe(), + rf.ast.castFUnsafe())) + left = le_cas + right = rf + condCombine = false } + { parent = + ETree + (E.SwitchOrRight + (re_cas.ast.castEUnsafe(), + lf.ast.castFUnsafe())) + left = re_cas + right = lf + condCombine = false } + { parent = + FTree + (F.CascadeOr + (le_cas.ast.castEUnsafe(), + rv.ast.castVUnsafe())) + left = le_cas + right = rv + condCombine = true } + { parent = + FTree + (F.CascadeOr + (re_cas.ast.castEUnsafe(), + lv.ast.castVUnsafe())) + left = re_cas + right = lv + condCombine = true } + { parent = + FTree + (F.SwitchOr + (lf.ast.castFUnsafe(), rf.ast.castFUnsafe())) + left = lf + right = rf + condCombine = true } + { parent = + FTree + (F.SwitchOr + (rf.ast.castFUnsafe(), lf.ast.castFUnsafe())) + left = rf + right = lf + condCombine = true } + { parent = + FTree + (F.SwitchOrV + (lv.ast.castVUnsafe(), rv.ast.castVUnsafe())) + left = lv + right = rv + condCombine = true } + { parent = + FTree + (F.SwitchOrV + (rv.ast.castVUnsafe(), lv.ast.castVUnsafe())) + left = rv + right = lv + condCombine = true } |] + + let casesWithQ = + match maybelq, mayberq with + | Some lq, Some rq -> + Array.append possibleCases [| { parent = + FTree + (F.DelayedOr + (lq.ast.castQUnsafe + (), + rq.ast.castQUnsafe + ())) + left = lq + right = rq + condCombine = true } + { parent = + FTree + (F.DelayedOr + (rq.ast.castQUnsafe + (), + lq.ast.castQUnsafe + ())) + left = rq + right = lq + condCombine = true } |] + | _ -> possibleCases + + Cost.getMinimumCost casesWithQ p_sat p_dissat lweight rweight + | Threshold(n, subs) -> + let num_cost = scriptNumCost n + let avgCost = float n / float subs.Length + let e = + best_e + (subs.[0], (p_sat * avgCost), + (p_dissat + p_sat * (1.0 - avgCost))) + let ws = + subs + |> Array.map + (fun s -> + best_w + (s, (p_sat * avgCost), + (p_dissat + p_sat * (1.0 - avgCost)))) + let pk_cost = + ws + |> Array.fold (fun acc w -> acc + w.pkCost) + (1u + num_cost + e.pkCost) + let sat_cost = + ws |> Array.fold (fun acc w -> acc + w.satCost) e.satCost + let dissat_cost = + ws |> Array.fold (fun acc w -> acc + w.dissatCost) e.dissatCost + let wsast = ws |> Array.map (fun w -> w.ast.castWUnsafe()) + + let cond = + { ast = ETree(E.Threshold(n, e.ast.castEUnsafe(), wsast)) + pkCost = pk_cost + satCost = sat_cost + dissatCost = dissat_cost } + + let f = best_f (node, p_sat, 0.0) + let cond1 = Cost.likely (f) + let cond2 = Cost.unlikely (f) + let nonCond = Cost.min_cost (cond1, cond2, p_sat, p_dissat) + Cost.min_cost (cond, nonCond, p_sat, p_dissat) + + and best_q (node : CompiledNode, p_sat : float, p_dissat : float) : Cost option = + match node with + | Pk pk -> + { ast = QTree(Q.Pubkey(pk)) + pkCost = 34u + satCost = 0.0 dissatCost = 0.0 } - - let op = - match maybelq, mayberq with - | None, Some rq -> - let lv = best_v (l, p_sat, p_dissat) - [| cost lv rq |] - | Some lq, None -> - let rv = best_v (r, p_sat, p_dissat) - [| cost rv lq |] - | Some lq, Some rq -> - let lv = best_v (l, p_sat, p_dissat) - let rv = best_v (r, p_sat, p_dissat) - [| cost lv rq - cost rv lq |] - | None, None -> [||] - - if op.Length = 0 then None - else - op - |> Cost.fold_costs p_sat p_dissat |> Some - | Or(l, r, lweight, rweight) -> - let maybelq = best_q (l, (p_sat * lweight), 0.0) - let mayberq = best_q (r, (p_sat + rweight), 0.0) - match maybelq, mayberq with - | Some lq, Some rq -> - [| { ast = - QTree(Q.Or(lq.ast.castQUnsafe(), rq.ast.castQUnsafe())) - pkCost = lq.pkCost + rq.pkCost + 3u - satCost = - lweight * (2.0 + lq.satCost) - + rweight * (1.0 + rq.satCost) - dissatCost = 0.0 } - { ast = - QTree(Q.Or(rq.ast.castQUnsafe(), lq.ast.castQUnsafe())) - pkCost = rq.pkCost + lq.pkCost + 3u - satCost = - lweight * (1.0 + lq.satCost) - + rweight * (2.0 + rq.satCost) - dissatCost = 0.0 } |] - |> Cost.fold_costs p_sat p_dissat - |> Some - | _ -> None - | _ -> None - - and best_w (node : CompiledNode, p_sat : float, p_dissat : float) : Cost = - match node with - | Pk k -> - { ast = WTree(W.CheckSig(k)) - pkCost = 36u - satCost = 72.0 - dissatCost = 1.0 } - | Time t -> - let num_cost = scriptNumCost (!> t) - { ast = WTree(W.Time(t)) - pkCost = 6u + num_cost - satCost = 2.0 - dissatCost = 1.0 } - | Hash h -> - { ast = WTree(W.HashEqual(h)) - pkCost = 45u - satCost = 33.0 - dissatCost = 1.0 } - | _ -> - let c = best_e (node, p_sat, p_dissat) - { c with ast = WTree(W.CastE(c.ast.castEUnsafe())) - pkCost = c.pkCost + 2u } - - and best_f (node : CompiledNode, p_sat : float, p_dissat : float) : Cost = - match node with - | Pk k -> - { ast = FTree(F.CheckSig(k)) - pkCost = 36u - satCost = 72.0 - dissatCost = 1.0 } - | Multi(m, pks) -> - let num_cost = getPkCost m pks - { ast = FTree(F.CheckMultiSig(m, pks)) - pkCost = uint32 (num_cost + 34 * pks.Length) + 2u - satCost = 1.0 + 72.0 * float m - dissatCost = 0.0 } - | Time t -> - let num_cost = scriptNumCost (!> t) - { ast = FTree(F.Time(t)) - pkCost = 2u + num_cost - satCost = 0.0 - dissatCost = 0.0 } - | Hash h -> - { ast = FTree(F.HashEqual(h)) - pkCost = 40u - satCost = 33.0 - dissatCost = 0.0 } - | And(l, r) -> - let vl = best_v (l, p_sat, 0.0) - let vr = best_v (r, p_sat, 0.0) - let fl = best_f (l, p_sat, 0.0) - let fr = best_f (r, p_sat, 0.0) - - let possibleCases = - [| { parent = - FTree - (F.And(vl.ast.castVUnsafe(), fr.ast.castFUnsafe())) - left = vl - right = fr - condCombine = false } - { parent = - FTree - (F.And(vr.ast.castVUnsafe(), fl.ast.castFUnsafe())) - left = vr - right = fl - condCombine = false } |] - Cost.getMinimumCost possibleCases p_sat 0.0 0.5 0.5 - | Or(l, r, lweight, rweight) -> - let le_par = best_e (l, (p_sat * lweight), (p_sat + rweight)) - let re_par = best_e (r, (p_sat * rweight), (p_sat * lweight)) - let lf = best_f (l, (p_sat * lweight), 0.0) - let rf = best_f (r, (p_sat * rweight), 0.0) - let lv = best_v (l, (p_sat * lweight), 0.0) - let rv = best_v (r, (p_sat * rweight), 0.0) - let maybelq = best_q (l, (p_sat * lweight), 0.0) - let mayberq = best_q (r, (p_sat * rweight), 0.0) - - let possibleCases = - [| { parent = - FTree - (F.CascadeOr - (le_par.ast.castEUnsafe(), - rv.ast.castVUnsafe())) - left = le_par - right = rv - condCombine = false } - { parent = - FTree - (F.CascadeOr - (re_par.ast.castEUnsafe(), - lv.ast.castVUnsafe())) - left = re_par - right = lv - condCombine = false } - { parent = - FTree - (F.SwitchOr - (lf.ast.castFUnsafe(), rf.ast.castFUnsafe())) - left = lf - right = rf - condCombine = false } - { parent = - FTree - (F.SwitchOr - (rf.ast.castFUnsafe(), lf.ast.castFUnsafe())) - left = rf - right = lf - condCombine = false } - { parent = - FTree - (F.SwitchOrV - (lv.ast.castVUnsafe(), rv.ast.castVUnsafe())) - left = lv - right = rv - condCombine = false } - { parent = - FTree - (F.SwitchOrV - (rv.ast.castVUnsafe(), lv.ast.castVUnsafe())) - left = rv - right = lv - condCombine = false } |] - - let casesWithQ = - match maybelq, mayberq with - | Some lq, Some rq -> - Array.append possibleCases [| { parent = - FTree - (F.DelayedOr - (lq.ast.castQUnsafe - (), - rq.ast.castQUnsafe - ())) - left = lq - right = rq - condCombine = false } |] - | _ -> possibleCases - - Cost.getMinimumCost casesWithQ p_sat 0.0 lweight rweight - | Threshold(n, subs) -> - let num_cost = scriptNumCost n - let avg_cost = float n / float subs.Length - let e = - best_e - (subs.[0], (p_sat * avg_cost), - (p_dissat + p_sat * (1.0 - avg_cost))) - let ws = - subs - |> Array.map - (fun s -> - best_w - (s, (p_sat * avg_cost), - (p_dissat + p_sat * (1.0 - avg_cost)))) - let pk_cost = - ws - |> Array.fold (fun acc w -> acc + w.pkCost + 1u) - (2u + num_cost + e.pkCost) - let sat_cost = - ws |> Array.fold (fun acc w -> acc + w.satCost) e.satCost - let dissat_cost = - ws |> Array.fold (fun acc w -> acc + w.dissatCost) e.dissatCost - let wsast = ws |> Array.map (fun w -> w.ast.castWUnsafe()) - { ast = FTree(F.Threshold(n, e.ast.castEUnsafe(), wsast)) - pkCost = pk_cost - satCost = sat_cost * avg_cost + dissat_cost * (1.0 - avg_cost) - dissatCost = 0.0 } - - and best_v (node : CompiledNode, p_sat : float, p_dissat : float) : Cost = - match node with - | Pk k -> - { ast = VTree(V.CheckSig(k)) - pkCost = 35u - satCost = 0.0 - dissatCost = 0.0 } - | Multi(m, pks) -> - let num_cost = getPkCost m pks - { ast = VTree(V.CheckMultiSig(m, pks)) - pkCost = uint32 (num_cost + 34 * pks.Length + 1) - satCost = 1.0 + 72.0 * float m - dissatCost = 0.0 } - | Time t -> - let num_cost = scriptNumCost (!> t) - { ast = VTree(V.Time(t)) - pkCost = 2u + num_cost - satCost = 0.0 - dissatCost = 0.0 } - | Hash h -> - { ast = VTree(V.HashEqual(h)) - pkCost = 39u - satCost = 33.0 - dissatCost = 0.0 } - | And(l, r) -> - let lv = best_v (l, p_sat, 0.0) - let rv = best_v (r, p_sat, 0.0) - { ast = VTree(V.And(lv.ast.castVUnsafe(), rv.ast.castVUnsafe())) - pkCost = lv.pkCost + rv.pkCost - satCost = lv.satCost + rv.satCost - dissatCost = 0.0 } - | Or(l, r, lweight, rweight) -> - let le_par = best_e (l, (p_sat * lweight), (p_sat * rweight)) - let re_par = best_e (r, (p_sat * rweight), (p_sat * lweight)) - let lt = best_t (l, (p_sat * lweight), 0.0) - let rt = best_t (r, (p_sat * rweight), 0.0) - let lv = best_v (l, (p_sat * lweight), 0.0) - let rv = best_v (r, (p_sat * rweight), 0.0) - let maybelq = best_q (l, (p_sat * lweight), 0.0) - let mayberq = best_q (r, (p_sat * rweight), 0.0) - - let possibleCases = - [| { parent = - VTree - (V.CascadeOr - (le_par.ast.castEUnsafe(), - rv.ast.castVUnsafe())) - left = le_par - right = rv - condCombine = false } - { parent = - VTree - (V.CascadeOr - (re_par.ast.castEUnsafe(), - lv.ast.castVUnsafe())) - left = re_par - right = lv - condCombine = false } - { parent = - VTree - (V.SwitchOr - (lv.ast.castVUnsafe(), rv.ast.castVUnsafe())) - left = lv - right = rv - condCombine = false } - { parent = - VTree - (V.SwitchOr - (rv.ast.castVUnsafe(), lv.ast.castVUnsafe())) - left = rv - right = lv - condCombine = false } - { parent = - VTree - (V.SwitchOrT - (lt.ast.castTUnsafe(), rt.ast.castTUnsafe())) - left = lt - right = rt - condCombine = false } - { parent = - VTree - (V.SwitchOrT - (rt.ast.castTUnsafe(), lt.ast.castTUnsafe())) - left = rt - right = lt - condCombine = false } |] - - let casesWithQ = + | And(l, r) -> + let maybelq = best_q (l, p_sat, p_dissat) + let mayberq = best_q (r, p_sat, p_dissat) + + let cost v q = + { ast = QTree(Q.And(v.ast.castVUnsafe(), q.ast.castQUnsafe())) + pkCost = v.pkCost + q.pkCost + satCost = v.satCost + q.satCost + dissatCost = 0.0 } + + let op = + match maybelq, mayberq with + | None, Some rq -> + let lv = best_v (l, p_sat, p_dissat) + [| cost lv rq |] + | Some lq, None -> + let rv = best_v (r, p_sat, p_dissat) + [| cost rv lq |] + | Some lq, Some rq -> + let lv = best_v (l, p_sat, p_dissat) + let rv = best_v (r, p_sat, p_dissat) + [| cost lv rq + cost rv lq |] + | None, None -> [||] + + if op.Length = 0 then None + else + op + |> Cost.fold_costs p_sat p_dissat + |> Some + | Or(l, r, lweight, rweight) -> + let maybelq = best_q (l, (p_sat * lweight), 0.0) + let mayberq = best_q (r, (p_sat + rweight), 0.0) match maybelq, mayberq with | Some lq, Some rq -> - Array.append possibleCases [| { parent = - VTree - (V.DelayedOr - (lq.ast.castQUnsafe - (), - rq.ast.castQUnsafe - ())) - left = lq - right = rq - condCombine = false } |] - | _ -> possibleCases - - Cost.getMinimumCost casesWithQ p_sat 0.0 lweight rweight - | Threshold(n, subs) -> - let num_cost = scriptNumCost n - let avg_cost = float n / float subs.Length - let e = - best_e - (subs.[0], (p_sat * avg_cost), (p_sat * (1.0 - avg_cost))) - let ws = - subs - |> Array.map - (fun s -> - best_w - (s, (p_sat * avg_cost), (p_sat * (1.0 - avg_cost)))) - let pk_cost = - ws - |> Array.fold (fun acc w -> acc + w.pkCost + 1u) - (1u + num_cost + e.pkCost) - let sat_cost = - ws |> Array.fold (fun acc w -> acc + w.satCost) e.satCost - let dissat_cost = - ws |> Array.fold (fun acc w -> acc + w.dissatCost) e.dissatCost - let wsast = ws |> Array.map (fun w -> w.ast.castWUnsafe()) - { ast = VTree(V.Threshold(n, e.ast.castEUnsafe(), wsast)) - pkCost = pk_cost - satCost = sat_cost * avg_cost + dissat_cost * (1.0 - avg_cost) - dissatCost = 0.0 } + [| { ast = + QTree(Q.Or(lq.ast.castQUnsafe(), rq.ast.castQUnsafe())) + pkCost = lq.pkCost + rq.pkCost + 3u + satCost = + lweight * (2.0 + lq.satCost) + + rweight * (1.0 + rq.satCost) + dissatCost = 0.0 } + { ast = + QTree(Q.Or(rq.ast.castQUnsafe(), lq.ast.castQUnsafe())) + pkCost = rq.pkCost + lq.pkCost + 3u + satCost = + lweight * (1.0 + lq.satCost) + + rweight * (2.0 + rq.satCost) + dissatCost = 0.0 } |] + |> Cost.fold_costs p_sat p_dissat + |> Some + | _ -> None + | _ -> None + + and best_w (node : CompiledNode, p_sat : float, p_dissat : float) : Cost = + match node with + | Pk k -> + { ast = WTree(W.CheckSig(k)) + pkCost = 36u + satCost = 72.0 + dissatCost = 1.0 } + | Time t -> + let num_cost = scriptNumCost (!> t) + { ast = WTree(W.Time(t)) + pkCost = 6u + num_cost + satCost = 2.0 + dissatCost = 1.0 } + | Hash h -> + { ast = WTree(W.HashEqual(h)) + pkCost = 45u + satCost = 33.0 + dissatCost = 1.0 } + | _ -> + let c = best_e (node, p_sat, p_dissat) + { c with ast = WTree(W.CastE(c.ast.castEUnsafe())) + pkCost = c.pkCost + 2u } + + and best_f (node : CompiledNode, p_sat : float, p_dissat : float) : Cost = + match node with + | Pk k -> + { ast = FTree(F.CheckSig(k)) + pkCost = 36u + satCost = 72.0 + dissatCost = 1.0 } + | Multi(m, pks) -> + let num_cost = getPkCost m pks + { ast = FTree(F.CheckMultiSig(m, pks)) + pkCost = uint32 (num_cost + 34 * pks.Length) + 2u + satCost = 1.0 + 72.0 * float m + dissatCost = 0.0 } + | Time t -> + let num_cost = scriptNumCost (!> t) + { ast = FTree(F.Time(t)) + pkCost = 2u + num_cost + satCost = 0.0 + dissatCost = 0.0 } + | Hash h -> + { ast = FTree(F.HashEqual(h)) + pkCost = 40u + satCost = 33.0 + dissatCost = 0.0 } + | And(l, r) -> + let vl = best_v (l, p_sat, 0.0) + let vr = best_v (r, p_sat, 0.0) + let fl = best_f (l, p_sat, 0.0) + let fr = best_f (r, p_sat, 0.0) + + let possibleCases = + [| { parent = + FTree + (F.And(vl.ast.castVUnsafe(), fr.ast.castFUnsafe())) + left = vl + right = fr + condCombine = false } + { parent = + FTree + (F.And(vr.ast.castVUnsafe(), fl.ast.castFUnsafe())) + left = vr + right = fl + condCombine = false } |] + Cost.getMinimumCost possibleCases p_sat 0.0 0.5 0.5 + | Or(l, r, lweight, rweight) -> + let le_par = best_e (l, (p_sat * lweight), (p_sat + rweight)) + let re_par = best_e (r, (p_sat * rweight), (p_sat * lweight)) + let lf = best_f (l, (p_sat * lweight), 0.0) + let rf = best_f (r, (p_sat * rweight), 0.0) + let lv = best_v (l, (p_sat * lweight), 0.0) + let rv = best_v (r, (p_sat * rweight), 0.0) + let maybelq = best_q (l, (p_sat * lweight), 0.0) + let mayberq = best_q (r, (p_sat * rweight), 0.0) + + let possibleCases = + [| { parent = + FTree + (F.CascadeOr + (le_par.ast.castEUnsafe(), + rv.ast.castVUnsafe())) + left = le_par + right = rv + condCombine = false } + { parent = + FTree + (F.CascadeOr + (re_par.ast.castEUnsafe(), + lv.ast.castVUnsafe())) + left = re_par + right = lv + condCombine = false } + { parent = + FTree + (F.SwitchOr + (lf.ast.castFUnsafe(), rf.ast.castFUnsafe())) + left = lf + right = rf + condCombine = false } + { parent = + FTree + (F.SwitchOr + (rf.ast.castFUnsafe(), lf.ast.castFUnsafe())) + left = rf + right = lf + condCombine = false } + { parent = + FTree + (F.SwitchOrV + (lv.ast.castVUnsafe(), rv.ast.castVUnsafe())) + left = lv + right = rv + condCombine = false } + { parent = + FTree + (F.SwitchOrV + (rv.ast.castVUnsafe(), lv.ast.castVUnsafe())) + left = rv + right = lv + condCombine = false } |] + + let casesWithQ = + match maybelq, mayberq with + | Some lq, Some rq -> + Array.append possibleCases [| { parent = + FTree + (F.DelayedOr + (lq.ast.castQUnsafe + (), + rq.ast.castQUnsafe + ())) + left = lq + right = rq + condCombine = false } |] + | _ -> possibleCases + + Cost.getMinimumCost casesWithQ p_sat 0.0 lweight rweight + | Threshold(n, subs) -> + let num_cost = scriptNumCost n + let avg_cost = float n / float subs.Length + let e = + best_e + (subs.[0], (p_sat * avg_cost), + (p_dissat + p_sat * (1.0 - avg_cost))) + let ws = + subs + |> Array.map + (fun s -> + best_w + (s, (p_sat * avg_cost), + (p_dissat + p_sat * (1.0 - avg_cost)))) + let pk_cost = + ws + |> Array.fold (fun acc w -> acc + w.pkCost + 1u) + (2u + num_cost + e.pkCost) + let sat_cost = + ws |> Array.fold (fun acc w -> acc + w.satCost) e.satCost + let dissat_cost = + ws |> Array.fold (fun acc w -> acc + w.dissatCost) e.dissatCost + let wsast = ws |> Array.map (fun w -> w.ast.castWUnsafe()) + { ast = FTree(F.Threshold(n, e.ast.castEUnsafe(), wsast)) + pkCost = pk_cost + satCost = sat_cost * avg_cost + dissat_cost * (1.0 - avg_cost) + dissatCost = 0.0 } + + and best_v (node : CompiledNode, p_sat : float, p_dissat : float) : Cost = + match node with + | Pk k -> + { ast = VTree(V.CheckSig(k)) + pkCost = 35u + satCost = 0.0 + dissatCost = 0.0 } + | Multi(m, pks) -> + let num_cost = getPkCost m pks + { ast = VTree(V.CheckMultiSig(m, pks)) + pkCost = uint32 (num_cost + 34 * pks.Length + 1) + satCost = 1.0 + 72.0 * float m + dissatCost = 0.0 } + | Time t -> + let num_cost = scriptNumCost (!> t) + { ast = VTree(V.Time(t)) + pkCost = 2u + num_cost + satCost = 0.0 + dissatCost = 0.0 } + | Hash h -> + { ast = VTree(V.HashEqual(h)) + pkCost = 39u + satCost = 33.0 + dissatCost = 0.0 } + | And(l, r) -> + let lv = best_v (l, p_sat, 0.0) + let rv = best_v (r, p_sat, 0.0) + { ast = VTree(V.And(lv.ast.castVUnsafe(), rv.ast.castVUnsafe())) + pkCost = lv.pkCost + rv.pkCost + satCost = lv.satCost + rv.satCost + dissatCost = 0.0 } + | Or(l, r, lweight, rweight) -> + let le_par = best_e (l, (p_sat * lweight), (p_sat * rweight)) + let re_par = best_e (r, (p_sat * rweight), (p_sat * lweight)) + let lt = best_t (l, (p_sat * lweight), 0.0) + let rt = best_t (r, (p_sat * rweight), 0.0) + let lv = best_v (l, (p_sat * lweight), 0.0) + let rv = best_v (r, (p_sat * rweight), 0.0) + let maybelq = best_q (l, (p_sat * lweight), 0.0) + let mayberq = best_q (r, (p_sat * rweight), 0.0) + + let possibleCases = + [| { parent = + VTree + (V.CascadeOr + (le_par.ast.castEUnsafe(), + rv.ast.castVUnsafe())) + left = le_par + right = rv + condCombine = false } + { parent = + VTree + (V.CascadeOr + (re_par.ast.castEUnsafe(), + lv.ast.castVUnsafe())) + left = re_par + right = lv + condCombine = false } + { parent = + VTree + (V.SwitchOr + (lv.ast.castVUnsafe(), rv.ast.castVUnsafe())) + left = lv + right = rv + condCombine = false } + { parent = + VTree + (V.SwitchOr + (rv.ast.castVUnsafe(), lv.ast.castVUnsafe())) + left = rv + right = lv + condCombine = false } + { parent = + VTree + (V.SwitchOrT + (lt.ast.castTUnsafe(), rt.ast.castTUnsafe())) + left = lt + right = rt + condCombine = false } + { parent = + VTree + (V.SwitchOrT + (rt.ast.castTUnsafe(), lt.ast.castTUnsafe())) + left = rt + right = lt + condCombine = false } |] + + let casesWithQ = + match maybelq, mayberq with + | Some lq, Some rq -> + Array.append possibleCases [| { parent = + VTree + (V.DelayedOr + (lq.ast.castQUnsafe + (), + rq.ast.castQUnsafe + ())) + left = lq + right = rq + condCombine = false } |] + | _ -> possibleCases + + Cost.getMinimumCost casesWithQ p_sat 0.0 lweight rweight + | Threshold(n, subs) -> + let num_cost = scriptNumCost n + let avg_cost = float n / float subs.Length + let e = + best_e + (subs.[0], (p_sat * avg_cost), (p_sat * (1.0 - avg_cost))) + let ws = + subs + |> Array.map + (fun s -> + best_w + (s, (p_sat * avg_cost), (p_sat * (1.0 - avg_cost)))) + let pk_cost = + ws + |> Array.fold (fun acc w -> acc + w.pkCost + 1u) + (1u + num_cost + e.pkCost) + let sat_cost = + ws |> Array.fold (fun acc w -> acc + w.satCost) e.satCost + let dissat_cost = + ws |> Array.fold (fun acc w -> acc + w.dissatCost) e.dissatCost + let wsast = ws |> Array.map (fun w -> w.ast.castWUnsafe()) + { ast = VTree(V.Threshold(n, e.ast.castEUnsafe(), wsast)) + pkCost = pk_cost + satCost = sat_cost * avg_cost + dissat_cost * (1.0 - avg_cost) + dissatCost = 0.0 } -type CompiledNode with - static member fromPolicy (p : Policy) = CompiledNode.fromPolicy p - member this.compile() = - let node = CompiledNode.best_t (this, 1.0, 0.0) - MiniScript.fromAST (node.ast) + type CompiledNode with + static member fromPolicy (p : Policy) = CompiledNode.fromPolicy p + member this.compile() = + let node = CompiledNode.best_t (this, 1.0, 0.0) + MiniScript.fromAST (node.ast) - member this.compileUnsafe() = - match this.compile() with - | Ok miniscript -> miniscript - | Error e -> failwith e + member this.compileUnsafe() = + match this.compile() with + | Ok miniscript -> miniscript + | Error e -> failwith e + \ No newline at end of file diff --git a/NBitcoin.Miniscript/MiniscriptDecompiler.fs b/NBitcoin.Miniscript/MiniscriptDecompiler.fs index 0a3001d2a0..3948ed70fa 100644 --- a/NBitcoin.Miniscript/MiniscriptDecompiler.fs +++ b/NBitcoin.Miniscript/MiniscriptDecompiler.fs @@ -1,9 +1,9 @@ -module FNBitcoin.MiniScriptDecompiler +module NBitcoin.Miniscript.Decompiler open NBitcoin open System -open FNBitcoin.Utils.Parser -open MiniScriptAST +open NBitcoin.Miniscript.Utils.Parser +open Miniscript.AST /// Subset of Bitcoin Script which is used in Miniscript type Token = diff --git a/NBitcoin.Miniscript/MiniscriptParser.fs b/NBitcoin.Miniscript/MiniscriptParser.fs index eef94ae397..1bbda847c7 100644 --- a/NBitcoin.Miniscript/MiniscriptParser.fs +++ b/NBitcoin.Miniscript/MiniscriptParser.fs @@ -1,141 +1,142 @@ -module FNBitcoin.MiniScriptParser +namespace NBitcoin.Miniscript open NBitcoin open System.Text.RegularExpressions open System -type Policy = - | Key of PubKey - | Multi of uint32 * PubKey [] - | Hash of uint256 - | Time of NBitcoin.LockTime - | Threshold of uint32 * Policy [] - | And of Policy * Policy - | Or of Policy * Policy - | AsymmetricOr of Policy * Policy - override this.ToString() = - match this with - | Key k1 -> sprintf "pk(%s)" (string (k1.ToHex())) - | Multi(m, klist) -> - klist - |> Seq.map (fun k -> string (k.ToHex())) - |> Seq.reduce (fun a b -> sprintf "%s,%s" a b) - |> sprintf "multi(%d,%s)" m - | Hash h -> sprintf "hash(%s)" (string (h.ToString())) - | Time t -> sprintf "time(%s)" (string (t.ToString())) - | Threshold(m, plist) -> - plist - |> Array.map (fun p -> p.ToString()) - |> Array.reduce (fun a b -> sprintf "%s,%s" a b) - |> sprintf "thres(%d,%s)" m - | And(p1, p2) -> sprintf "and(%s,%s)" (p1.ToString()) (p2.ToString()) - | Or(p1, p2) -> sprintf "or(%s,%s)" (p1.ToString()) (p2.ToString()) - | AsymmetricOr(p1, p2) -> - sprintf "aor(%s,%s)" (p1.ToString()) (p2.ToString()) - - -// parser -let quoted = Regex(@"\((.*)\)") - -let rec (|SurroundedByBrackets|_|) (s : string) = - let s2 = s.Trim() - if s2.StartsWith("(") && s2.EndsWith(")") then - Some(s2.TrimStart('(').TrimEnd(')')) - else None - -let (|Expression|_|) (prefix : string) (s : string) = - let s = s.Trim() - if s.StartsWith(prefix) then Some(s.Substring(prefix.Length)) - else None - -let (|PubKeyPattern|_|) (s : string) = - try - Some(PubKey(s)) - with :? FormatException as ex -> None - -let (|PubKeysPattern|_|) (s : string) = - let s = s.Trim().Split(',') - match UInt32.TryParse(s.[0]) with - | (false, _) -> None - | (true, i) -> +module MiniscriptParser = + type Policy = + | Key of PubKey + | Multi of uint32 * PubKey [] + | Hash of uint256 + | Time of NBitcoin.LockTime + | Threshold of uint32 * Policy [] + | And of Policy * Policy + | Or of Policy * Policy + | AsymmetricOr of Policy * Policy + override this.ToString() = + match this with + | Key k1 -> sprintf "pk(%s)" (string (k1.ToHex())) + | Multi(m, klist) -> + klist + |> Seq.map (fun k -> string (k.ToHex())) + |> Seq.reduce (fun a b -> sprintf "%s,%s" a b) + |> sprintf "multi(%d,%s)" m + | Hash h -> sprintf "hash(%s)" (string (h.ToString())) + | Time t -> sprintf "time(%s)" (string (t.ToString())) + | Threshold(m, plist) -> + plist + |> Array.map (fun p -> p.ToString()) + |> Array.reduce (fun a b -> sprintf "%s,%s" a b) + |> sprintf "thres(%d,%s)" m + | And(p1, p2) -> sprintf "and(%s,%s)" (p1.ToString()) (p2.ToString()) + | Or(p1, p2) -> sprintf "or(%s,%s)" (p1.ToString()) (p2.ToString()) + | AsymmetricOr(p1, p2) -> + sprintf "aor(%s,%s)" (p1.ToString()) (p2.ToString()) + + + // parser + let quoted = Regex(@"\((.*)\)") + + let rec (|SurroundedByBrackets|_|) (s : string) = + let s2 = s.Trim() + if s2.StartsWith("(") && s2.EndsWith(")") then + Some(s2.TrimStart('(').TrimEnd(')')) + else None + + let (|Expression|_|) (prefix : string) (s : string) = + let s = s.Trim() + if s.StartsWith(prefix) then Some(s.Substring(prefix.Length)) + else None + + let (|PubKeyPattern|_|) (s : string) = try - let pks = - s.[1..s.Length - 1] |> Array.map (fun hex -> PubKey(hex.Trim())) - Some(i, pks) + Some(PubKey(s)) + with :? FormatException as ex -> None + + let (|PubKeysPattern|_|) (s : string) = + let s = s.Trim().Split(',') + match UInt32.TryParse(s.[0]) with + | (false, _) -> None + | (true, i) -> + try + let pks = + s.[1..s.Length - 1] |> Array.map (fun hex -> PubKey(hex.Trim())) + Some(i, pks) + with :? FormatException -> None + + let (|Hash|_|) (s : string) = + try + Some(uint256 (s.Trim())) with :? FormatException -> None -let (|Hash|_|) (s : string) = - try - Some(uint256 (s.Trim())) - with :? FormatException -> None - -let (|Time|_|) (s : string) = - try - Some(uint32 (s.Trim())) - with :? FormatException -> None - -// Split with "," but only when not surroounded by parenthesis -let rec safeSplit (s : string) (acc : string list) (index : int) (openNum : int) - (currentChunk : char []) = - if s.Length = index then - let lastChunk = String.Concat(Array.append currentChunk [| ')' |]) - let lastAcc = List.append acc [ lastChunk ] - lastAcc |> List.toArray - else - let c = s.[index] - if c = '(' then - let newChunk = Array.append currentChunk [| c |] - safeSplit s acc (index + 1) (openNum + 1) newChunk - elif c = ')' then - let newChunk = Array.append currentChunk [| c |] - safeSplit s acc (index + 1) (openNum - 1) newChunk - elif openNum = 0 && (c = ',') then - let newElement = String.Concat(currentChunk) - let newAcc = List.append acc [ newElement ] - safeSplit s newAcc (index + 1) (openNum) [||] + let (|Time|_|) (s : string) = + try + Some(uint32 (s.Trim())) + with :? FormatException -> None + + // Split with "," but only when not surroounded by parenthesis + let rec safeSplit (s : string) (acc : string list) (index : int) (openNum : int) + (currentChunk : char []) = + if s.Length = index then + let lastChunk = String.Concat(Array.append currentChunk [| ')' |]) + let lastAcc = List.append acc [ lastChunk ] + lastAcc |> List.toArray + else + let c = s.[index] + if c = '(' then + let newChunk = Array.append currentChunk [| c |] + safeSplit s acc (index + 1) (openNum + 1) newChunk + elif c = ')' then + let newChunk = Array.append currentChunk [| c |] + safeSplit s acc (index + 1) (openNum - 1) newChunk + elif openNum = 0 && (c = ',') then + let newElement = String.Concat(currentChunk) + let newAcc = List.append acc [ newElement ] + safeSplit s newAcc (index + 1) (openNum) [||] + else + let newChunk = Array.append currentChunk [| c |] + safeSplit s acc (index + 1) (openNum) newChunk + + let rec (|Policy|_|) s = + let s = Regex.Replace(s, @"[|\s|\n|\r\n]+", "") + match s with + | Expression "pk" (SurroundedByBrackets(PubKeyPattern pk)) -> Some(Key pk) + | Expression "multi" (SurroundedByBrackets(PubKeysPattern pks)) -> + Multi((fst pks), (snd pks)) |> Some + | Expression "hash" (SurroundedByBrackets(Hash hash)) -> Some(Hash hash) + | Expression "time" (SurroundedByBrackets(Time t)) -> Some(Time(LockTime(t))) + // recursive matches + | Expression "thres" (SurroundedByBrackets(Threshold thres)) -> + Some(Threshold(thres)) + | Expression "and" (SurroundedByBrackets(And(expr1, expr2))) -> + And(expr1, expr2) |> Some + | Expression "or" (SurroundedByBrackets(Or(expr1, expr2))) -> + Or(expr1, expr2) |> Some + | Expression "aor" (SurroundedByBrackets(AsymmetricOr(expr1, expr2))) -> + AsymmetricOr(expr1, expr2) |> Some + | _ -> None + + and (|Threshold|_|) (s : string) = + let s = safeSplit s [] 0 0 [||] + let thresholdStr = s.[0] + match UInt32.TryParse(thresholdStr) with + | (true, threshold) -> + let subPolicy = s.[1..s.Length - 1] |> Array.choose ((|Policy|_|)) + if subPolicy.Length <> s.Length - 1 then None + else Some(threshold, subPolicy) + | (false, _) -> None + + and (|And|_|) (s : string) = twoSubExpressions s + + and (|Or|_|) (s : string) = twoSubExpressions s + + and (|AsymmetricOr|_|) (s : string) = twoSubExpressions s + + and twoSubExpressions (s : string) = + let s = safeSplit s [] 0 0 [||] + if s.Length <> 2 then None else - let newChunk = Array.append currentChunk [| c |] - safeSplit s acc (index + 1) (openNum) newChunk - -let rec (|Policy|_|) s = - let s = Regex.Replace(s, @"[|\s|\n|\r\n]+", "") - match s with - | Expression "pk" (SurroundedByBrackets(PubKeyPattern pk)) -> Some(Key pk) - | Expression "multi" (SurroundedByBrackets(PubKeysPattern pks)) -> - Multi((fst pks), (snd pks)) |> Some - | Expression "hash" (SurroundedByBrackets(Hash hash)) -> Some(Hash hash) - | Expression "time" (SurroundedByBrackets(Time t)) -> Some(Time(LockTime(t))) - // recursive matches - | Expression "thres" (SurroundedByBrackets(Threshold thres)) -> - Some(Threshold(thres)) - | Expression "and" (SurroundedByBrackets(And(expr1, expr2))) -> - And(expr1, expr2) |> Some - | Expression "or" (SurroundedByBrackets(Or(expr1, expr2))) -> - Or(expr1, expr2) |> Some - | Expression "aor" (SurroundedByBrackets(AsymmetricOr(expr1, expr2))) -> - AsymmetricOr(expr1, expr2) |> Some - | _ -> None - -and (|Threshold|_|) (s : string) = - let s = safeSplit s [] 0 0 [||] - let thresholdStr = s.[0] - match UInt32.TryParse(thresholdStr) with - | (true, threshold) -> - let subPolicy = s.[1..s.Length - 1] |> Array.choose ((|Policy|_|)) - if subPolicy.Length <> s.Length - 1 then None - else Some(threshold, subPolicy) - | (false, _) -> None - -and (|And|_|) (s : string) = twoSubExpressions s - -and (|Or|_|) (s : string) = twoSubExpressions s - -and (|AsymmetricOr|_|) (s : string) = twoSubExpressions s - -and twoSubExpressions (s : string) = - let s = safeSplit s [] 0 0 [||] - if s.Length <> 2 then None - else - let subPolicies = s |> Array.choose ((|Policy|_|)) - if subPolicies.Length <> s.Length then None - else Some(subPolicies.[0], subPolicies.[1]) + let subPolicies = s |> Array.choose ((|Policy|_|)) + if subPolicies.Length <> s.Length then None + else Some(subPolicies.[0], subPolicies.[1]) diff --git a/NBitcoin.Miniscript/Satisfy.fs b/NBitcoin.Miniscript/Satisfy.fs index 6ff91d36f6..f8dc9ef244 100644 --- a/NBitcoin.Miniscript/Satisfy.fs +++ b/NBitcoin.Miniscript/Satisfy.fs @@ -1,8 +1,8 @@ -namespace FNBitcoin.Satisfy +namespace NBitcoin.Miniscript.Satisfy module Satisfy = - open FNBitcoin.MiniScriptAST - open FNBitcoin.Utils + open NBitcoin.Miniscript.AST + open NBitcoin.Miniscript.Utils open NBitcoin open System diff --git a/NBitcoin.Miniscript/Utils/Lib.fs b/NBitcoin.Miniscript/Utils/Lib.fs index a37c586b13..930f0f2185 100644 --- a/NBitcoin.Miniscript/Utils/Lib.fs +++ b/NBitcoin.Miniscript/Utils/Lib.fs @@ -1,4 +1,4 @@ -namespace FNBitcoin.Utils +namespace NBitcoin.Miniscript.Utils [] module Utils = diff --git a/NBitcoin.Miniscript/Utils/Parser.fs b/NBitcoin.Miniscript/Utils/Parser.fs index 2b4d72c185..7e568bc1d4 100644 --- a/NBitcoin.Miniscript/Utils/Parser.fs +++ b/NBitcoin.Miniscript/Utils/Parser.fs @@ -1,4 +1,4 @@ -namespace FNBitcoin.Utils +namespace NBitcoin.Miniscript.Utils module Parser = type ErrorMessage = string From 788d4595069e16757fcc1d4d65034fa10255a69c Mon Sep 17 00:00:00 2001 From: joemphilips Date: Wed, 27 Mar 2019 06:09:25 +0900 Subject: [PATCH 04/40] Fromat: Fix some casing --- NBitcoin.Miniscript/Miniscript.fs | 2 +- NBitcoin.Miniscript/MiniscriptAST.fs | 174 ++++----- NBitcoin.Miniscript/MiniscriptCompiler.fs | 387 ++++++++++---------- NBitcoin.Miniscript/MiniscriptDecompiler.fs | 63 ++-- NBitcoin.Miniscript/Satisfy.fs | 16 +- NBitcoin.Miniscript/Utils/Parser.fs | 4 +- 6 files changed, 322 insertions(+), 324 deletions(-) diff --git a/NBitcoin.Miniscript/Miniscript.fs b/NBitcoin.Miniscript/Miniscript.fs index 063f5f892a..6a685a0b37 100644 --- a/NBitcoin.Miniscript/Miniscript.fs +++ b/NBitcoin.Miniscript/Miniscript.fs @@ -9,7 +9,7 @@ type MiniScript = MiniScript of AST module MiniScript = let fromAST (t : AST) : Result = - match t.castT() with + match t.CastT() with | Ok t -> Ok(MiniScript(TTree t)) | o -> Error (sprintf "AST was not top-level (T) representation\n%A" o) diff --git a/NBitcoin.Miniscript/MiniscriptAST.fs b/NBitcoin.Miniscript/MiniscriptAST.fs index 3061620a35..72a5afbc2f 100644 --- a/NBitcoin.Miniscript/MiniscriptAST.fs +++ b/NBitcoin.Miniscript/MiniscriptAST.fs @@ -95,15 +95,15 @@ module AST = | VExpr | TExpr - let private EncodeUint (n: uint32) = + let private encodeUint (n: uint32) = Op.GetPushOp(int64 n).ToString() - let private EncodeInt (n: int32) = + let private encodeInt (n: int32) = Op.GetPushOp(int64 n).ToString() type E with - member this.print() = + member this.Print() = match this with | CheckSig pk -> sprintf "E.pk(%s)" (pk.ToHex()) | CheckMultiSig(m, pks) -> @@ -113,35 +113,35 @@ module AST = "") | Time t -> sprintf "E.time(%s)" (t.ToString()) | Threshold(num, e, ws) -> - sprintf "E.thres(%d,%s,%s)" num (e.print()) + sprintf "E.thres(%d,%s,%s)" num (e.Print()) (ws - |> Array.fold (fun acc w -> sprintf "%s,%s" acc (w.print())) "") - | ParallelAnd(e, w) -> sprintf "E.and_p(%s,%s)" (e.print()) (w.print()) - | CascadeAnd(e, f) -> sprintf "E.and_c(%s,%s)" (e.print()) (f.print()) - | ParallelOr(e, w) -> sprintf "E.or_p(%s,%s)" (e.print()) (w.print()) - | CascadeOr(e, e2) -> sprintf "E.or_c(%s,%s)" (e.print()) (e2.print()) - | SwitchOrLeft(e, f) -> sprintf "E.or_s(%s,%s)" (e.print()) (f.print()) - | SwitchOrRight(e, f) -> sprintf "E.or_r(%s,%s)" (e.print()) (f.print()) - | Likely f -> sprintf "E.lift_l(%s)" (f.print()) - | Unlikely f -> sprintf "E.lift_u(%s)" (f.print()) + |> Array.fold (fun acc w -> sprintf "%s,%s" acc (w.Print())) "") + | ParallelAnd(e, w) -> sprintf "E.and_p(%s,%s)" (e.Print()) (w.Print()) + | CascadeAnd(e, f) -> sprintf "E.and_c(%s,%s)" (e.Print()) (f.Print()) + | ParallelOr(e, w) -> sprintf "E.or_p(%s,%s)" (e.Print()) (w.Print()) + | CascadeOr(e, e2) -> sprintf "E.or_c(%s,%s)" (e.Print()) (e2.Print()) + | SwitchOrLeft(e, f) -> sprintf "E.or_s(%s,%s)" (e.Print()) (f.Print()) + | SwitchOrRight(e, f) -> sprintf "E.or_r(%s,%s)" (e.Print()) (f.Print()) + | Likely f -> sprintf "E.lift_l(%s)" (f.Print()) + | Unlikely f -> sprintf "E.lift_u(%s)" (f.Print()) member this.Serialize(sb : StringBuilder) : StringBuilder = match this with | CheckSig pk -> sb.AppendFormat(" {0} OP_CHECKSIG", pk) | CheckMultiSig(m, pks) -> - sb.AppendFormat(" {0}", (EncodeUint m)) |> ignore + sb.AppendFormat(" {0}", (encodeUint m)) |> ignore for pk in pks do do sb.AppendFormat(" {0}", (pk.ToHex())) |> ignore - sb.AppendFormat(" {0} OP_CHECKMULTISIG", EncodeInt(pks.Length)) |> ignore + sb.AppendFormat(" {0} OP_CHECKMULTISIG", encodeInt(pks.Length)) |> ignore sb | Time t -> - sb.AppendFormat(" OP_DUP OP_IF {0} OP_CSV OP_DROP OP_ENDIF", EncodeUint(!> t)) + sb.AppendFormat(" OP_DUP OP_IF {0} OP_CSV OP_DROP OP_ENDIF", encodeUint(!> t)) | Threshold(k, e, ws) -> e.Serialize(sb) |> ignore for w in ws do w.Serialize(sb) |> ignore sb.Append(" OP_ADD") |> ignore - sb.AppendFormat(" {0} OP_EQUAL", (EncodeUint k)) + sb.AppendFormat(" {0} OP_EQUAL", (encodeUint k)) | ParallelAnd(l, r) -> l.Serialize(sb) |> ignore r.Serialize(sb) |> ignore @@ -181,19 +181,19 @@ module AST = f.Serialize(sb) |> ignore sb.Append(" OP_ELSE 0 OP_ENDIF") - member this.toE() = this - member this.toT() = + member this.ToE() = this + member this.ToT() = match this with | ParallelOr(l, r) -> T.ParallelOr(l, r) | x -> T.CastE(x) and Q with - member this.print() = + member this.Print() = match this with | Pubkey p -> sprintf "Q.pk(%s)" (p.ToString()) - | And(v, q) -> sprintf "Q.and(%s,%s)" (v.print()) (q.print()) - | Or(q1, q2) -> sprintf "Q.or(%s,%s)" (q1.print()) (q2.print()) + | And(v, q) -> sprintf "Q.and(%s,%s)" (v.Print()) (q.Print()) + | Or(q1, q2) -> sprintf "Q.or(%s,%s)" (q1.Print()) (q2.Print()) member this.Serialize(sb : StringBuilder) : StringBuilder = match this with @@ -210,12 +210,12 @@ module AST = and W with - member this.print() = + member this.Print() = match this with | CheckSig pk -> sprintf "W.pk(%s)" (pk.ToString()) | HashEqual u -> sprintf "W.hash(%s)" (u.ToString()) | Time t -> sprintf "W.time(%s)" (t.ToString()) - | CastE e -> e.print() + | CastE e -> e.Print() member this.Serialize(sb : StringBuilder) : StringBuilder = match this with @@ -226,13 +226,13 @@ module AST = | HashEqual h -> sb.Append (sprintf " OP_SWAP OP_SIZE OP_0NOTEQUAL OP_IF OP_SIZE %s OP_EQUALVERIFY OP_SHA256" - (EncodeInt 32)) + (encodeInt 32)) |> ignore sb.AppendFormat(" {0}", h.ToString()) |> ignore sb.Append(" OP_EQUALVERIFY 1 OP_ENDIF") | Time t -> sb.AppendFormat - (" OP_SWAP OP_DUP OP_IF {0} OP_CSV OP_DROP OP_ENDIF", (EncodeUint (!> t))) + (" OP_SWAP OP_DUP OP_IF {0} OP_CSV OP_DROP OP_ENDIF", (encodeUint (!> t))) | CastE e -> sb.Append(" OP_TOALTSTACK") |> ignore e.Serialize(sb) |> ignore @@ -240,7 +240,7 @@ module AST = and F with - member this.print() = + member this.Print() = match this with | CheckSig pk -> sprintf "F.pk(%s)" (pk.ToString()) | CheckMultiSig(m, pks) -> @@ -251,33 +251,33 @@ module AST = | Time t -> sprintf "F.time(%s)" (t.ToString()) | HashEqual h -> sprintf "F.hash(%s)" (h.ToString()) | Threshold(num, e, ws) -> - sprintf "F.thres(%d,%s,%s)" num (e.print()) + sprintf "F.thres(%d,%s,%s)" num (e.Print()) (ws - |> Array.fold (fun acc w -> sprintf "%s,%s" acc (w.print())) "") - | And(l, r) -> sprintf "F.and(%s,%s)" (l.print()) (r.print()) - | CascadeOr(l, r) -> sprintf "F.or_v(%s,%s)" (l.print()) (r.print()) - | SwitchOr(l, r) -> sprintf "F.or_s(%s,%s)" (l.print()) (r.print()) - | SwitchOrV(l, r) -> sprintf "F.or_a(%s,%s)" (l.print()) (r.print()) - | DelayedOr(l, r) -> sprintf "F.or_d(%s,%s)" (l.print()) (r.print()) + |> Array.fold (fun acc w -> sprintf "%s,%s" acc (w.Print())) "") + | And(l, r) -> sprintf "F.and(%s,%s)" (l.Print()) (r.Print()) + | CascadeOr(l, r) -> sprintf "F.or_v(%s,%s)" (l.Print()) (r.Print()) + | SwitchOr(l, r) -> sprintf "F.or_s(%s,%s)" (l.Print()) (r.Print()) + | SwitchOrV(l, r) -> sprintf "F.or_a(%s,%s)" (l.Print()) (r.Print()) + | DelayedOr(l, r) -> sprintf "F.or_d(%s,%s)" (l.Print()) (r.Print()) - member this.toE() = this + member this.ToE() = this - member this.toT() = + member this.ToT() = match this with | CascadeOr(l, r) -> T.CascadeOrV(l, r) | SwitchOrV(l, r) -> T.SwitchOrV(l, r) - | x -> failwith (sprintf "%s is not a T" (x.print())) + | x -> failwith (sprintf "%s is not a T" (x.Print())) member this.Serialize(sb : StringBuilder) : StringBuilder = match this with | CheckSig pk -> sb.AppendFormat(" {0} OP_CHECKSIGVERIFY 1", (pk.ToHex())) | CheckMultiSig(m, pks) -> - sb.AppendFormat(" {0}", (EncodeUint m)) |> ignore + sb.AppendFormat(" {0}", (encodeUint m)) |> ignore for pk in pks do sb.AppendFormat(" {0}", (pk.ToHex())) |> ignore - sb.AppendFormat(" {0} OP_CHECKMULTISIGVERIFY 1", (EncodeInt pks.Length)) - | Time t -> sb.AppendFormat(" {0} OP_CSV OP_0NOTEQUAL", (EncodeUint (!> t))) + sb.AppendFormat(" {0} OP_CHECKMULTISIGVERIFY 1", (encodeInt pks.Length)) + | Time t -> sb.AppendFormat(" {0} OP_CSV OP_0NOTEQUAL", (encodeUint (!> t))) | HashEqual h -> sb.AppendFormat (" OP_SIZE 20 OP_EQUALVERIFY OP_SHA256 {0} OP_EQUALVERIFY 1", h) @@ -286,7 +286,7 @@ module AST = for w in ws do w.Serialize(sb) |> ignore sb.Append(" OP_ADD") |> ignore - sb.AppendFormat(" {0} OP_EQUALVERIFY 1", (EncodeUint k)) + sb.AppendFormat(" {0} OP_EQUALVERIFY 1", (encodeUint k)) | And(l, r) -> l.Serialize(sb) |> ignore r.Serialize(sb) @@ -316,7 +316,7 @@ module AST = and V with - member this.print() = + member this.Print() = match this with | CheckSig pk -> sprintf "V.pk(%s)" (pk.ToString()) | CheckMultiSig(m, pks) -> @@ -327,25 +327,25 @@ module AST = | Time t -> sprintf "V.time(%s)" (t.ToString()) | HashEqual h -> sprintf "V.hash(%s)" (h.ToString()) | Threshold(num, e, ws) -> - sprintf "V.thres(%d,%s,%s)" num (e.print()) + sprintf "V.thres(%d,%s,%s)" num (e.Print()) (ws - |> Array.fold (fun acc w -> sprintf "%s,%s" acc (w.print())) "") - | And(l, r) -> sprintf "V.and(%s,%s)" (l.print()) (r.print()) - | CascadeOr(l, r) -> sprintf "V.or_v(%s,%s)" (l.print()) (r.print()) - | SwitchOr(l, r) -> sprintf "V.or_s(%s,%s)" (l.print()) (r.print()) - | SwitchOrT(l, r) -> sprintf "V.or_a(%s,%s)" (l.print()) (r.print()) - | DelayedOr(l, r) -> sprintf "V.or_d(%s,%s)" (l.print()) (r.print()) + |> Array.fold (fun acc w -> sprintf "%s,%s" acc (w.Print())) "") + | And(l, r) -> sprintf "V.and(%s,%s)" (l.Print()) (r.Print()) + | CascadeOr(l, r) -> sprintf "V.or_v(%s,%s)" (l.Print()) (r.Print()) + | SwitchOr(l, r) -> sprintf "V.or_s(%s,%s)" (l.Print()) (r.Print()) + | SwitchOrT(l, r) -> sprintf "V.or_a(%s,%s)" (l.Print()) (r.Print()) + | DelayedOr(l, r) -> sprintf "V.or_d(%s,%s)" (l.Print()) (r.Print()) member this.Serialize(sb : StringBuilder) : StringBuilder = match this with | CheckSig pk -> sb.AppendFormat(" {0} OP_CHECKSIGVERIFY ", (pk.ToHex())) | CheckMultiSig(m, pks) -> - sb.AppendFormat(" {0}", (EncodeUint m)) |> ignore + sb.AppendFormat(" {0}", (encodeUint m)) |> ignore for pk in pks do sb.AppendFormat(" {0}", (pk.ToHex())) |> ignore - sb.AppendFormat(" {0} OP_CHECKMULTISIGVERIFY", (EncodeInt pks.Length)) - | Time t -> sb.AppendFormat(" {0} OP_CSV OP_DROP", (EncodeUint (!> t))) + sb.AppendFormat(" {0} OP_CHECKMULTISIGVERIFY", (encodeInt pks.Length)) + | Time t -> sb.AppendFormat(" {0} OP_CSV OP_DROP", (encodeUint (!> t))) | HashEqual h -> sb.AppendFormat (" OP_SIZE 20 OP_EQUALVERIFY OP_SHA256 {0} OP_EQUALVERIFY", h) @@ -354,7 +354,7 @@ module AST = for w in ws do w.Serialize(sb) |> ignore sb.Append(" OP_ADD") |> ignore - sb.AppendFormat(" {0} OP_EQUALVERIFY", (EncodeUint k)) + sb.AppendFormat(" {0} OP_EQUALVERIFY", (encodeUint k)) | And(l, r) -> l.Serialize(sb) |> ignore r.Serialize(sb) @@ -384,22 +384,22 @@ module AST = and T with - member this.print() = + member this.Print() = match this with | Time t -> sprintf "T.time(%s)" (t.ToString()) | HashEqual h -> sprintf "T.hash(%s)" (h.ToString()) - | And(l, r) -> sprintf "T.and_p(%s,%s)" (l.print()) (r.print()) - | ParallelOr(l, r) -> sprintf "T.or_vp(%s,%s)" (l.print()) (r.print()) - | CascadeOr(l, r) -> sprintf "T.or_c(%s,%s)" (l.print()) (r.print()) - | CascadeOrV(l, r) -> sprintf "T.or_v(%s,%s)" (l.print()) (r.print()) - | SwitchOr(l, r) -> sprintf "T.or_s(%s,%s)" (l.print()) (r.print()) - | SwitchOrV(l, r) -> sprintf "T.or_a(%s,%s)" (l.print()) (r.print()) - | DelayedOr(l, r) -> sprintf "T.or_d(%s,%s)" (l.print()) (r.print()) - | CastE e -> sprintf "T.%s" (e.print()) + | And(l, r) -> sprintf "T.and_p(%s,%s)" (l.Print()) (r.Print()) + | ParallelOr(l, r) -> sprintf "T.or_vp(%s,%s)" (l.Print()) (r.Print()) + | CascadeOr(l, r) -> sprintf "T.or_c(%s,%s)" (l.Print()) (r.Print()) + | CascadeOrV(l, r) -> sprintf "T.or_v(%s,%s)" (l.Print()) (r.Print()) + | SwitchOr(l, r) -> sprintf "T.or_s(%s,%s)" (l.Print()) (r.Print()) + | SwitchOrV(l, r) -> sprintf "T.or_a(%s,%s)" (l.Print()) (r.Print()) + | DelayedOr(l, r) -> sprintf "T.or_d(%s,%s)" (l.Print()) (r.Print()) + | CastE e -> sprintf "T.%s" (e.Print()) member this.Serialize(sb : StringBuilder) : StringBuilder = match this with - | Time t -> sb.AppendFormat(" {0} OP_CSV", (EncodeUint (!> t))) + | Time t -> sb.AppendFormat(" {0} OP_CSV", (encodeUint (!> t))) | HashEqual h -> sb.AppendFormat (" OP_SIZE 20 OP_EQUALVERIFY OP_SHA256 {0} OP_EQUAL", h) @@ -444,12 +444,12 @@ module AST = member this.Print() = match this with - | ETree e -> e.print() - | QTree q -> q.print() - | WTree w -> w.print() - | FTree f -> f.print() - | VTree v -> v.print() - | TTree t -> t.print() + | ETree e -> e.Print() + | QTree q -> q.Print() + | WTree w -> w.Print() + | FTree f -> f.Print() + | VTree v -> v.Print() + | TTree t -> t.Print() member this.ToScript() = let sb = StringBuilder() @@ -493,7 +493,7 @@ module AST = | _ -> false | _ -> false - member this.castT() : Result = + member this.CastT() : Result = match this with | TTree t -> Ok t | FTree f -> @@ -507,57 +507,57 @@ module AST = | otherE -> Ok(T.CastE(otherE)) | _ -> Error(sprintf "failed to cast %s" (this.Print())) - member this.castE() : Result = + member this.CastE() : Result = match this with | ETree e -> Ok e | _ -> Error(sprintf "failed to cast %s" (this.Print())) - member this.castQ() : Result = + member this.CastQ() : Result = match this with | QTree q -> Ok q | _ -> Error(sprintf "failed to cast %s" (this.Print())) - member this.castW() : Result = + member this.CastW() : Result = match this with | WTree w -> Ok w | _ -> Error(sprintf "failed to cast %s" (this.Print())) - member this.castF() : Result = + member this.CastF() : Result = match this with | FTree f -> Ok f | _ -> Error(sprintf "failed to cast %s" (this.Print())) - member this.castV() : Result = + member this.CastV() : Result = match this with | VTree v -> Ok v | _ -> Error(sprintf "failed to cast %s" (this.Print())) - member this.castTUnsafe() : T = - match this.castT() with + member this.CastTUnsafe() : T = + match this.CastT() with | Ok t -> t | Error s -> failwith s - member this.castEUnsafe() : E = - match this.castE() with + member this.CastEUnsafe() : E = + match this.CastE() with | Ok e -> e | Error s -> failwith s - member this.castQUnsafe() : Q = - match this.castQ() with + member this.CastQUnsafe() : Q = + match this.CastQ() with | Ok q -> q | Error s -> failwith s - member this.castWUnsafe() : W = - match this.castW() with + member this.CastWUnsafe() : W = + match this.CastW() with | Ok w -> w | Error s -> failwith s - member this.castFUnsafe() : F = - match this.castF() with + member this.CastFUnsafe() : F = + match this.CastF() with | Ok f -> f | Error s -> failwith s - member this.castVUnsafe() : V = - match this.castV() with + member this.CastVUnsafe() : V = + match this.CastV() with | Ok v -> v | Error s -> failwith s diff --git a/NBitcoin.Miniscript/MiniscriptCompiler.fs b/NBitcoin.Miniscript/MiniscriptCompiler.fs index 7ce8e1f424..c526b7b30d 100644 --- a/NBitcoin.Miniscript/MiniscriptCompiler.fs +++ b/NBitcoin.Miniscript/MiniscriptCompiler.fs @@ -35,13 +35,13 @@ module Compiler = module Cost = /// Casts F -> E let likely (fcost : Cost) : Cost = - { ast = ETree(E.Likely(fcost.ast.castFUnsafe())) + { ast = ETree(E.Likely(fcost.ast.CastFUnsafe())) pkCost = fcost.pkCost + 4u satCost = fcost.satCost * 1.0 dissatCost = 2.0 } let unlikely (fcost : Cost) : Cost = - { ast = ETree(E.Unlikely(fcost.ast.castFUnsafe())) + { ast = ETree(E.Unlikely(fcost.ast.CastFUnsafe())) pkCost = fcost.pkCost + 4u satCost = fcost.satCost * 2.0 dissatCost = 1.0 } @@ -234,29 +234,28 @@ module Compiler = | VTree v -> fromPairToVCost triple.left triple.right v lweight rweight |> Array.singleton - - let min_cost (a : Cost, b : Cost, p_sat : float, p_dissat : float) = - let weight_one = + | _ -> failwith "unreachable" + let minCost (a : Cost, b : Cost, p_sat : float, p_dissat : float) = + let weightOne = (float a.pkCost) + p_sat * a.satCost + p_dissat * a.dissatCost - let weight_two = + let weightTwo = (float b.pkCost) + p_sat * b.satCost + p_dissat * a.dissatCost - if weight_one < weight_two then a - else if weight_one > weight_two then b + if weightOne < weightTwo then a + else if weightOne > weightTwo then b else if a.satCost < b.satCost then a else b - let fold_costs (p_sat : float) (p_dissat : float) (cs : Cost []) = + let foldCosts (p_sat : float) (p_dissat : float) (cs : Cost []) = cs |> Array.toList - |> List.reduce (fun a b -> min_cost (a, b, p_sat, p_dissat)) + |> List.reduce (fun a b -> minCost (a, b, p_sat, p_dissat)) // equivalent to rules! macro in rust-miniscript - let getMinimumCost (triples : CostTriple []) (p_sat) (p_dissat) + let getMinimumCost (triples : CostTriple []) (pSat) (pDissat) (lweight : float) (rweight : float) : Cost = triples - |> Array.map (fun p -> fromTriple p 0.0 0.0) - |> Array.concat - |> fold_costs p_sat p_dissat + |> Array.collect (fun p -> fromTriple p 0.0 0.0) + |> foldCosts pSat pDissat module CompiledNode = /// bytes length when a number is encoded as bitcoin CScriptNum @@ -268,12 +267,12 @@ module Compiler = else 5u let private minCost (one : Cost, two : Cost, p_sat : float, p_dissat) = - let weight_one = + let weightOne = (float one.pkCost) + p_sat * one.satCost + p_dissat * one.dissatCost - let weight_two = + let weightTwo = (float two.pkCost) + p_sat * two.satCost + p_dissat * two.dissatCost - if weight_one < weight_two then one - else if weight_two < weight_one then one + if weightOne < weightTwo then one + else if weightTwo < weightOne then one else if one.satCost < two.satCost then one else two @@ -299,11 +298,11 @@ module Compiler = Or(fromPolicy e1, fromPolicy e2, 127.0 / 128.0, 1.0 / 128.0) // TODO: cache - let rec best_t (node : CompiledNode, p_sat : float, p_dissat : float) : Cost = + let rec bestT (node : CompiledNode, p_sat : float, p_dissat : float) : Cost = match node with | Pk _ | Multi _ | Threshold _ -> - let e = best_e (node, p_sat, p_dissat) - { ast = TTree(T.CastE(e.ast.castEUnsafe())) + let e = bestE (node, p_sat, p_dissat) + { ast = TTree(T.CastE(e.ast.CastEUnsafe())) pkCost = e.pkCost satCost = e.satCost dissatCost = 0.0 } @@ -319,105 +318,105 @@ module Compiler = satCost = 33.0 dissatCost = 0.0 } | And(l, r) -> - let vl = best_v (l, p_sat, 0.0) - let vr = best_v (r, p_sat, 0.0) - let tl = best_t (l, p_sat, 0.0) - let tr = best_t (r, p_sat, 0.0) + let vl = bestV (l, p_sat, 0.0) + let vr = bestV (r, p_sat, 0.0) + let tl = bestT (l, p_sat, 0.0) + let tr = bestT (r, p_sat, 0.0) let possibleCases = [| { parent = TTree - (T.And(vl.ast.castVUnsafe(), tr.ast.castTUnsafe())) + (T.And(vl.ast.CastVUnsafe(), tr.ast.CastTUnsafe())) left = vl right = tr condCombine = false } { parent = TTree - (T.And(vr.ast.castVUnsafe(), tl.ast.castTUnsafe())) + (T.And(vr.ast.CastVUnsafe(), tl.ast.CastTUnsafe())) left = vl right = tr condCombine = false } |] Cost.getMinimumCost possibleCases p_sat p_dissat 0.0 0.0 | Or(l, r, lweight, rweight) -> - let le = best_e (l, (p_sat * lweight), (p_sat * rweight)) - let re = best_e (r, (p_sat * rweight), (p_sat * lweight)) - let lw = best_w (l, (p_sat * lweight), (p_sat * rweight)) - let rw = best_w (r, (p_sat * rweight), (p_sat * lweight)) - let lt = best_t (l, (p_sat * lweight), 0.0) - let rt = best_t (r, (p_sat * lweight), 0.0) - let lv = best_v (l, (p_sat * lweight), 0.0) - let rv = best_v (r, (p_sat * lweight), 0.0) - let maybelq = best_q (l, (p_sat * lweight), 0.0) - let mayberq = best_q (r, (p_sat * lweight), 0.0) - + let le = bestE (l, (p_sat * lweight), (p_sat * rweight)) + let re = bestE (r, (p_sat * rweight), (p_sat * lweight)) + let lw = bestW (l, (p_sat * lweight), (p_sat * rweight)) + let rw = bestW (r, (p_sat * rweight), (p_sat * lweight)) + let lt = bestT (l, (p_sat * lweight), 0.0) + let rt = bestT (r, (p_sat * lweight), 0.0) + let lv = bestV (l, (p_sat * lweight), 0.0) + let rv = bestV (r, (p_sat * lweight), 0.0) + let maybelq = bestQ (l, (p_sat * lweight), 0.0) + let mayberq = bestQ (r, (p_sat * lweight), 0.0) + let possibleCases = [| { parent = TTree (T.ParallelOr - (le.ast.castEUnsafe(), rw.ast.castWUnsafe())) + (le.ast.CastEUnsafe(), rw.ast.CastWUnsafe())) left = le right = rw condCombine = false } { parent = TTree (T.ParallelOr - (re.ast.castEUnsafe(), lw.ast.castWUnsafe())) + (re.ast.CastEUnsafe(), lw.ast.CastWUnsafe())) left = re right = lw condCombine = false } { parent = TTree (T.CascadeOr - (le.ast.castEUnsafe(), rt.ast.castTUnsafe())) + (le.ast.CastEUnsafe(), rt.ast.CastTUnsafe())) left = le right = rt condCombine = false } { parent = TTree (T.CascadeOr - (re.ast.castEUnsafe(), lt.ast.castTUnsafe())) + (re.ast.CastEUnsafe(), lt.ast.CastTUnsafe())) left = re right = lt condCombine = false } { parent = TTree (T.CascadeOrV - (le.ast.castEUnsafe(), rv.ast.castVUnsafe())) + (le.ast.CastEUnsafe(), rv.ast.CastVUnsafe())) left = le right = rv condCombine = false } { parent = TTree (T.CascadeOrV - (re.ast.castEUnsafe(), lv.ast.castVUnsafe())) + (re.ast.CastEUnsafe(), lv.ast.CastVUnsafe())) left = re right = lv condCombine = false } { parent = TTree (T.SwitchOr - (lt.ast.castTUnsafe(), rt.ast.castTUnsafe())) + (lt.ast.CastTUnsafe(), rt.ast.CastTUnsafe())) left = lt right = rt condCombine = false } { parent = TTree (T.SwitchOr - (rt.ast.castTUnsafe(), lt.ast.castTUnsafe())) + (rt.ast.CastTUnsafe(), lt.ast.CastTUnsafe())) left = rt right = lt condCombine = false } { parent = TTree (T.SwitchOrV - (lv.ast.castVUnsafe(), rv.ast.castVUnsafe())) + (lv.ast.CastVUnsafe(), rv.ast.CastVUnsafe())) left = lv right = rv condCombine = false } { parent = TTree (T.SwitchOrV - (rv.ast.castVUnsafe(), lv.ast.castVUnsafe())) + (rv.ast.CastVUnsafe(), lv.ast.CastVUnsafe())) left = rv right = lv condCombine = false } |] @@ -428,9 +427,9 @@ module Compiler = Array.append possibleCases [| { parent = TTree (T.DelayedOr - (lq.ast.castQUnsafe + (lq.ast.CastQUnsafe (), - rq.ast.castQUnsafe + rq.ast.CastQUnsafe ())) left = lq right = rq @@ -439,7 +438,7 @@ module Compiler = Cost.getMinimumCost casesWithQ p_sat 0.0 lweight rweight - and best_e (node : CompiledNode, p_sat : float, p_dissat : float) : Cost = + and bestE (node : CompiledNode, p_sat : float, p_dissat : float) : Cost = match node with | Pk k -> { ast = ETree(E.CheckSig k) @@ -456,14 +455,14 @@ module Compiler = dissatCost = 1.0 } ] if not (p_dissat > 0.0) then options.[0] else - let bestf = best_f (node, p_sat, 0.0) + let bestf = bestF (node, p_sat, 0.0) let options2 = [ Cost.likely (bestf) Cost.unlikely (bestf) ] List.concat [ options; options2 ] |> List.toArray - |> Cost.fold_costs p_sat p_dissat + |> Cost.foldCosts p_sat p_dissat | Time n -> let num_cost = scriptNumCost (!> n) { ast = ETree(E.Time n) @@ -471,186 +470,186 @@ module Compiler = satCost = 2.0 dissatCost = 1.0 } | Hash h -> - let fcost = best_f (node, p_sat, p_dissat) + let fcost = bestF (node, p_sat, p_dissat) minCost (Cost.likely fcost, Cost.unlikely fcost, p_sat, p_dissat) | And(l, r) -> - let le = best_e (l, p_sat, p_dissat) - let re = best_e (r, p_sat, p_dissat) - let lw = best_w (l, p_sat, p_dissat) - let rw = best_w (r, p_sat, p_dissat) - let lf = best_f (l, p_sat, 0.0) - let rf = best_f (r, p_sat, 0.0) - let lv = best_v (l, p_sat, 0.0) - let rv = best_v (r, p_sat, 0.0) + let le = bestE (l, p_sat, p_dissat) + let re = bestE (r, p_sat, p_dissat) + let lw = bestW (l, p_sat, p_dissat) + let rw = bestW (r, p_sat, p_dissat) + let lf = bestF (l, p_sat, 0.0) + let rf = bestF (r, p_sat, 0.0) + let lv = bestV (l, p_sat, 0.0) + let rv = bestV (r, p_sat, 0.0) let possibleCases = [| { parent = ETree (E.ParallelAnd - (le.ast.castEUnsafe(), rw.ast.castWUnsafe())) + (le.ast.CastEUnsafe(), rw.ast.CastWUnsafe())) left = le right = rw condCombine = false } { parent = ETree (E.ParallelAnd - (re.ast.castEUnsafe(), lw.ast.castWUnsafe())) + (re.ast.CastEUnsafe(), lw.ast.CastWUnsafe())) left = re right = lw condCombine = false } { parent = ETree (E.CascadeAnd - (le.ast.castEUnsafe(), rf.ast.castFUnsafe())) + (le.ast.CastEUnsafe(), rf.ast.CastFUnsafe())) left = le right = rf condCombine = false } { parent = ETree (E.CascadeAnd - (re.ast.castEUnsafe(), lf.ast.castFUnsafe())) + (re.ast.CastEUnsafe(), lf.ast.CastFUnsafe())) left = re right = lf condCombine = false } { parent = FTree - (F.And(lv.ast.castVUnsafe(), rf.ast.castFUnsafe())) + (F.And(lv.ast.CastVUnsafe(), rf.ast.CastFUnsafe())) left = lv right = rf condCombine = true } { parent = FTree - (F.And(rv.ast.castVUnsafe(), lf.ast.castFUnsafe())) + (F.And(rv.ast.CastVUnsafe(), lf.ast.CastFUnsafe())) left = rv right = lf condCombine = true } |] Cost.getMinimumCost possibleCases p_sat p_dissat 0.5 0.5 | Or(l, r, lweight, rweight) -> let le_par = - best_e (l, (p_sat * lweight), (p_dissat + p_sat * rweight)) + bestE (l, (p_sat * lweight), (p_dissat + p_sat * rweight)) let re_par = - best_e (r, (p_sat * lweight), (p_dissat + p_sat * rweight)) + bestE (r, (p_sat * lweight), (p_dissat + p_sat * rweight)) let lw_par = - best_w (l, (p_sat * lweight), (p_dissat + p_sat * rweight)) + bestW (l, (p_sat * lweight), (p_dissat + p_sat * rweight)) let rw_par = - best_w (r, (p_sat * lweight), (p_dissat + p_sat * rweight)) - let le_cas = best_e (l, (p_sat * lweight), (p_dissat)) - let re_cas = best_e (r, (p_sat * lweight), (p_dissat)) - let le_cond_par = best_e (l, (p_sat * lweight), (p_sat * rweight)) - let re_cond_par = best_e (r, (p_sat * lweight), (p_sat * lweight)) - let lv = best_v (l, (p_sat * lweight), 0.0) - let rv = best_v (r, (p_sat * rweight), 0.0) - let lf = best_f (l, (p_sat * lweight), 0.0) - let rf = best_f (r, (p_sat * rweight), 0.0) - let maybelq = best_q (l, (p_sat * lweight), 0.0) - let mayberq = best_q (r, (p_sat * rweight), 0.0) + bestW (r, (p_sat * lweight), (p_dissat + p_sat * rweight)) + let le_cas = bestE (l, (p_sat * lweight), (p_dissat)) + let re_cas = bestE (r, (p_sat * lweight), (p_dissat)) + let le_cond_par = bestE (l, (p_sat * lweight), (p_sat * rweight)) + let re_cond_par = bestE (r, (p_sat * lweight), (p_sat * lweight)) + let lv = bestV (l, (p_sat * lweight), 0.0) + let rv = bestV (r, (p_sat * rweight), 0.0) + let lf = bestV (l, (p_sat * lweight), 0.0) + let rf = bestF (r, (p_sat * rweight), 0.0) + let maybelq = bestQ (l, (p_sat * lweight), 0.0) + let mayberq = bestQ (r, (p_sat * rweight), 0.0) let possibleCases = [| { parent = ETree (E.ParallelOr - (le_par.ast.castEUnsafe(), - rw_par.ast.castWUnsafe())) + (le_par.ast.CastEUnsafe(), + rw_par.ast.CastWUnsafe())) left = le_par right = rw_par condCombine = false } { parent = ETree (E.ParallelOr - (re_par.ast.castEUnsafe(), - lw_par.ast.castWUnsafe())) + (re_par.ast.CastEUnsafe(), + lw_par.ast.CastWUnsafe())) left = re_par right = lw_par condCombine = false } { parent = ETree (E.CascadeOr - (le_par.ast.castEUnsafe(), - re_cas.ast.castEUnsafe())) + (le_par.ast.CastEUnsafe(), + re_cas.ast.CastEUnsafe())) left = le_par right = re_cas condCombine = false } { parent = ETree (E.CascadeOr - (re_par.ast.castEUnsafe(), - le_cas.ast.castEUnsafe())) + (re_par.ast.CastEUnsafe(), + le_cas.ast.CastEUnsafe())) left = re_par right = le_cas condCombine = false } { parent = ETree (E.SwitchOrLeft - (le_cas.ast.castEUnsafe(), - rf.ast.castFUnsafe())) + (le_cas.ast.CastEUnsafe(), + rf.ast.CastFUnsafe())) left = le_cas right = rf condCombine = false } { parent = ETree (E.SwitchOrLeft - (re_cas.ast.castEUnsafe(), - lf.ast.castFUnsafe())) + (re_cas.ast.CastEUnsafe(), + lf.ast.CastFUnsafe())) left = re_cas right = lf condCombine = false } { parent = ETree (E.SwitchOrRight - (le_cas.ast.castEUnsafe(), - rf.ast.castFUnsafe())) + (le_cas.ast.CastEUnsafe(), + rf.ast.CastFUnsafe())) left = le_cas right = rf condCombine = false } { parent = ETree (E.SwitchOrRight - (re_cas.ast.castEUnsafe(), - lf.ast.castFUnsafe())) + (re_cas.ast.CastEUnsafe(), + lf.ast.CastFUnsafe())) left = re_cas right = lf condCombine = false } { parent = FTree (F.CascadeOr - (le_cas.ast.castEUnsafe(), - rv.ast.castVUnsafe())) + (le_cas.ast.CastEUnsafe(), + rv.ast.CastVUnsafe())) left = le_cas right = rv condCombine = true } { parent = FTree (F.CascadeOr - (re_cas.ast.castEUnsafe(), - lv.ast.castVUnsafe())) + (re_cas.ast.CastEUnsafe(), + lv.ast.CastVUnsafe())) left = re_cas right = lv condCombine = true } { parent = FTree (F.SwitchOr - (lf.ast.castFUnsafe(), rf.ast.castFUnsafe())) + (lf.ast.CastFUnsafe(), rf.ast.CastFUnsafe())) left = lf right = rf condCombine = true } { parent = FTree (F.SwitchOr - (rf.ast.castFUnsafe(), lf.ast.castFUnsafe())) + (rf.ast.CastFUnsafe(), lf.ast.CastFUnsafe())) left = rf right = lf condCombine = true } { parent = FTree (F.SwitchOrV - (lv.ast.castVUnsafe(), rv.ast.castVUnsafe())) + (lv.ast.CastVUnsafe(), rv.ast.CastVUnsafe())) left = lv right = rv condCombine = true } { parent = FTree (F.SwitchOrV - (rv.ast.castVUnsafe(), lv.ast.castVUnsafe())) + (rv.ast.CastVUnsafe(), lv.ast.CastVUnsafe())) left = rv right = lv condCombine = true } |] @@ -661,9 +660,9 @@ module Compiler = Array.append possibleCases [| { parent = FTree (F.DelayedOr - (lq.ast.castQUnsafe + (lq.ast.CastQUnsafe (), - rq.ast.castQUnsafe + rq.ast.CastQUnsafe ())) left = lq right = rq @@ -671,9 +670,9 @@ module Compiler = { parent = FTree (F.DelayedOr - (rq.ast.castQUnsafe + (rq.ast.CastQUnsafe (), - lq.ast.castQUnsafe + lq.ast.CastQUnsafe ())) left = rq right = lq @@ -685,14 +684,14 @@ module Compiler = let num_cost = scriptNumCost n let avgCost = float n / float subs.Length let e = - best_e + bestE (subs.[0], (p_sat * avgCost), (p_dissat + p_sat * (1.0 - avgCost))) let ws = subs |> Array.map (fun s -> - best_w + bestW (s, (p_sat * avgCost), (p_dissat + p_sat * (1.0 - avgCost)))) let pk_cost = @@ -703,21 +702,21 @@ module Compiler = ws |> Array.fold (fun acc w -> acc + w.satCost) e.satCost let dissat_cost = ws |> Array.fold (fun acc w -> acc + w.dissatCost) e.dissatCost - let wsast = ws |> Array.map (fun w -> w.ast.castWUnsafe()) + let wsast = ws |> Array.map (fun w -> w.ast.CastWUnsafe()) let cond = - { ast = ETree(E.Threshold(n, e.ast.castEUnsafe(), wsast)) + { ast = ETree(E.Threshold(n, e.ast.CastEUnsafe(), wsast)) pkCost = pk_cost satCost = sat_cost dissatCost = dissat_cost } - let f = best_f (node, p_sat, 0.0) + let f = bestF (node, p_sat, 0.0) let cond1 = Cost.likely (f) let cond2 = Cost.unlikely (f) - let nonCond = Cost.min_cost (cond1, cond2, p_sat, p_dissat) - Cost.min_cost (cond, nonCond, p_sat, p_dissat) + let nonCond = Cost.minCost (cond1, cond2, p_sat, p_dissat) + Cost.minCost (cond, nonCond, p_sat, p_dissat) - and best_q (node : CompiledNode, p_sat : float, p_dissat : float) : Cost option = + and bestQ (node : CompiledNode, p_sat : float, p_dissat : float) : Cost option = match node with | Pk pk -> { ast = QTree(Q.Pubkey(pk)) @@ -726,11 +725,11 @@ module Compiler = dissatCost = 0.0 } |> Some | And(l, r) -> - let maybelq = best_q (l, p_sat, p_dissat) - let mayberq = best_q (r, p_sat, p_dissat) + let maybelq = bestQ (l, p_sat, p_dissat) + let mayberq = bestQ (r, p_sat, p_dissat) let cost v q = - { ast = QTree(Q.And(v.ast.castVUnsafe(), q.ast.castQUnsafe())) + { ast = QTree(Q.And(v.ast.CastVUnsafe(), q.ast.CastQUnsafe())) pkCost = v.pkCost + q.pkCost satCost = v.satCost + q.satCost dissatCost = 0.0 } @@ -738,14 +737,14 @@ module Compiler = let op = match maybelq, mayberq with | None, Some rq -> - let lv = best_v (l, p_sat, p_dissat) + let lv = bestV (l, p_sat, p_dissat) [| cost lv rq |] | Some lq, None -> - let rv = best_v (r, p_sat, p_dissat) + let rv = bestV (r, p_sat, p_dissat) [| cost rv lq |] | Some lq, Some rq -> - let lv = best_v (l, p_sat, p_dissat) - let rv = best_v (r, p_sat, p_dissat) + let lv = bestV (l, p_sat, p_dissat) + let rv = bestV (r, p_sat, p_dissat) [| cost lv rq cost rv lq |] | None, None -> [||] @@ -753,33 +752,33 @@ module Compiler = if op.Length = 0 then None else op - |> Cost.fold_costs p_sat p_dissat + |> Cost.foldCosts p_sat p_dissat |> Some | Or(l, r, lweight, rweight) -> - let maybelq = best_q (l, (p_sat * lweight), 0.0) - let mayberq = best_q (r, (p_sat + rweight), 0.0) + let maybelq = bestQ (l, (p_sat * lweight), 0.0) + let mayberq = bestQ (r, (p_sat + rweight), 0.0) match maybelq, mayberq with | Some lq, Some rq -> [| { ast = - QTree(Q.Or(lq.ast.castQUnsafe(), rq.ast.castQUnsafe())) + QTree(Q.Or(lq.ast.CastQUnsafe(), rq.ast.CastQUnsafe())) pkCost = lq.pkCost + rq.pkCost + 3u satCost = lweight * (2.0 + lq.satCost) + rweight * (1.0 + rq.satCost) dissatCost = 0.0 } { ast = - QTree(Q.Or(rq.ast.castQUnsafe(), lq.ast.castQUnsafe())) + QTree(Q.Or(rq.ast.CastQUnsafe(), lq.ast.CastQUnsafe())) pkCost = rq.pkCost + lq.pkCost + 3u satCost = lweight * (1.0 + lq.satCost) + rweight * (2.0 + rq.satCost) dissatCost = 0.0 } |] - |> Cost.fold_costs p_sat p_dissat + |> Cost.foldCosts p_sat p_dissat |> Some | _ -> None | _ -> None - and best_w (node : CompiledNode, p_sat : float, p_dissat : float) : Cost = + and bestW (node : CompiledNode, p_sat : float, p_dissat : float) : Cost = match node with | Pk k -> { ast = WTree(W.CheckSig(k)) @@ -798,11 +797,11 @@ module Compiler = satCost = 33.0 dissatCost = 1.0 } | _ -> - let c = best_e (node, p_sat, p_dissat) - { c with ast = WTree(W.CastE(c.ast.castEUnsafe())) + let c = bestE (node, p_sat, p_dissat) + { c with ast = WTree(W.CastE(c.ast.CastEUnsafe())) pkCost = c.pkCost + 2u } - and best_f (node : CompiledNode, p_sat : float, p_dissat : float) : Cost = + and bestF (node : CompiledNode, p_sat : float, p_dissat : float) : Cost = match node with | Pk k -> { ast = FTree(F.CheckSig(k)) @@ -827,77 +826,77 @@ module Compiler = satCost = 33.0 dissatCost = 0.0 } | And(l, r) -> - let vl = best_v (l, p_sat, 0.0) - let vr = best_v (r, p_sat, 0.0) - let fl = best_f (l, p_sat, 0.0) - let fr = best_f (r, p_sat, 0.0) + let vl = bestV (l, p_sat, 0.0) + let vr = bestV (r, p_sat, 0.0) + let fl = bestF (l, p_sat, 0.0) + let fr = bestF (r, p_sat, 0.0) let possibleCases = [| { parent = FTree - (F.And(vl.ast.castVUnsafe(), fr.ast.castFUnsafe())) + (F.And(vl.ast.CastVUnsafe(), fr.ast.CastFUnsafe())) left = vl right = fr condCombine = false } { parent = FTree - (F.And(vr.ast.castVUnsafe(), fl.ast.castFUnsafe())) + (F.And(vr.ast.CastVUnsafe(), fl.ast.CastFUnsafe())) left = vr right = fl condCombine = false } |] Cost.getMinimumCost possibleCases p_sat 0.0 0.5 0.5 | Or(l, r, lweight, rweight) -> - let le_par = best_e (l, (p_sat * lweight), (p_sat + rweight)) - let re_par = best_e (r, (p_sat * rweight), (p_sat * lweight)) - let lf = best_f (l, (p_sat * lweight), 0.0) - let rf = best_f (r, (p_sat * rweight), 0.0) - let lv = best_v (l, (p_sat * lweight), 0.0) - let rv = best_v (r, (p_sat * rweight), 0.0) - let maybelq = best_q (l, (p_sat * lweight), 0.0) - let mayberq = best_q (r, (p_sat * rweight), 0.0) + let le_par = bestE (l, (p_sat * lweight), (p_sat + rweight)) + let re_par = bestE (r, (p_sat * rweight), (p_sat * lweight)) + let lf = bestF (l, (p_sat * lweight), 0.0) + let rf = bestF (r, (p_sat * rweight), 0.0) + let lv = bestV (l, (p_sat * lweight), 0.0) + let rv = bestV (r, (p_sat * rweight), 0.0) + let maybelq = bestQ (l, (p_sat * lweight), 0.0) + let mayberq = bestQ (r, (p_sat * rweight), 0.0) let possibleCases = [| { parent = FTree (F.CascadeOr - (le_par.ast.castEUnsafe(), - rv.ast.castVUnsafe())) + (le_par.ast.CastEUnsafe(), + rv.ast.CastVUnsafe())) left = le_par right = rv condCombine = false } { parent = FTree (F.CascadeOr - (re_par.ast.castEUnsafe(), - lv.ast.castVUnsafe())) + (re_par.ast.CastEUnsafe(), + lv.ast.CastVUnsafe())) left = re_par right = lv condCombine = false } { parent = FTree (F.SwitchOr - (lf.ast.castFUnsafe(), rf.ast.castFUnsafe())) + (lf.ast.CastFUnsafe(), rf.ast.CastFUnsafe())) left = lf right = rf condCombine = false } { parent = FTree (F.SwitchOr - (rf.ast.castFUnsafe(), lf.ast.castFUnsafe())) + (rf.ast.CastFUnsafe(), lf.ast.CastFUnsafe())) left = rf right = lf condCombine = false } { parent = FTree (F.SwitchOrV - (lv.ast.castVUnsafe(), rv.ast.castVUnsafe())) + (lv.ast.CastVUnsafe(), rv.ast.CastVUnsafe())) left = lv right = rv condCombine = false } { parent = FTree (F.SwitchOrV - (rv.ast.castVUnsafe(), lv.ast.castVUnsafe())) + (rv.ast.CastVUnsafe(), lv.ast.CastVUnsafe())) left = rv right = lv condCombine = false } |] @@ -908,9 +907,9 @@ module Compiler = Array.append possibleCases [| { parent = FTree (F.DelayedOr - (lq.ast.castQUnsafe + (lq.ast.CastQUnsafe (), - rq.ast.castQUnsafe + rq.ast.CastQUnsafe ())) left = lq right = rq @@ -922,14 +921,14 @@ module Compiler = let num_cost = scriptNumCost n let avg_cost = float n / float subs.Length let e = - best_e + bestE (subs.[0], (p_sat * avg_cost), (p_dissat + p_sat * (1.0 - avg_cost))) let ws = subs |> Array.map (fun s -> - best_w + bestW (s, (p_sat * avg_cost), (p_dissat + p_sat * (1.0 - avg_cost)))) let pk_cost = @@ -940,13 +939,13 @@ module Compiler = ws |> Array.fold (fun acc w -> acc + w.satCost) e.satCost let dissat_cost = ws |> Array.fold (fun acc w -> acc + w.dissatCost) e.dissatCost - let wsast = ws |> Array.map (fun w -> w.ast.castWUnsafe()) - { ast = FTree(F.Threshold(n, e.ast.castEUnsafe(), wsast)) + let wsast = ws |> Array.map (fun w -> w.ast.CastWUnsafe()) + { ast = FTree(F.Threshold(n, e.ast.CastEUnsafe(), wsast)) pkCost = pk_cost satCost = sat_cost * avg_cost + dissat_cost * (1.0 - avg_cost) dissatCost = 0.0 } - and best_v (node : CompiledNode, p_sat : float, p_dissat : float) : Cost = + and bestV (node : CompiledNode, p_sat : float, p_dissat : float) : Cost = match node with | Pk k -> { ast = VTree(V.CheckSig(k)) @@ -971,64 +970,64 @@ module Compiler = satCost = 33.0 dissatCost = 0.0 } | And(l, r) -> - let lv = best_v (l, p_sat, 0.0) - let rv = best_v (r, p_sat, 0.0) - { ast = VTree(V.And(lv.ast.castVUnsafe(), rv.ast.castVUnsafe())) + let lv = bestV (l, p_sat, 0.0) + let rv = bestV (r, p_sat, 0.0) + { ast = VTree(V.And(lv.ast.CastVUnsafe(), rv.ast.CastVUnsafe())) pkCost = lv.pkCost + rv.pkCost satCost = lv.satCost + rv.satCost dissatCost = 0.0 } | Or(l, r, lweight, rweight) -> - let le_par = best_e (l, (p_sat * lweight), (p_sat * rweight)) - let re_par = best_e (r, (p_sat * rweight), (p_sat * lweight)) - let lt = best_t (l, (p_sat * lweight), 0.0) - let rt = best_t (r, (p_sat * rweight), 0.0) - let lv = best_v (l, (p_sat * lweight), 0.0) - let rv = best_v (r, (p_sat * rweight), 0.0) - let maybelq = best_q (l, (p_sat * lweight), 0.0) - let mayberq = best_q (r, (p_sat * rweight), 0.0) + let le_par = bestE (l, (p_sat * lweight), (p_sat * rweight)) + let re_par = bestE (r, (p_sat * rweight), (p_sat * lweight)) + let lt = bestT (l, (p_sat * lweight), 0.0) + let rt = bestT (r, (p_sat * rweight), 0.0) + let lv = bestV (l, (p_sat * lweight), 0.0) + let rv = bestV (r, (p_sat * rweight), 0.0) + let maybelq = bestQ (l, (p_sat * lweight), 0.0) + let mayberq = bestQ (r, (p_sat * rweight), 0.0) let possibleCases = [| { parent = VTree (V.CascadeOr - (le_par.ast.castEUnsafe(), - rv.ast.castVUnsafe())) + (le_par.ast.CastEUnsafe(), + rv.ast.CastVUnsafe())) left = le_par right = rv condCombine = false } { parent = VTree (V.CascadeOr - (re_par.ast.castEUnsafe(), - lv.ast.castVUnsafe())) + (re_par.ast.CastEUnsafe(), + lv.ast.CastVUnsafe())) left = re_par right = lv condCombine = false } { parent = VTree (V.SwitchOr - (lv.ast.castVUnsafe(), rv.ast.castVUnsafe())) + (lv.ast.CastVUnsafe(), rv.ast.CastVUnsafe())) left = lv right = rv condCombine = false } { parent = VTree (V.SwitchOr - (rv.ast.castVUnsafe(), lv.ast.castVUnsafe())) + (rv.ast.CastVUnsafe(), lv.ast.CastVUnsafe())) left = rv right = lv condCombine = false } { parent = VTree (V.SwitchOrT - (lt.ast.castTUnsafe(), rt.ast.castTUnsafe())) + (lt.ast.CastTUnsafe(), rt.ast.CastTUnsafe())) left = lt right = rt condCombine = false } { parent = VTree (V.SwitchOrT - (rt.ast.castTUnsafe(), lt.ast.castTUnsafe())) + (rt.ast.CastTUnsafe(), lt.ast.CastTUnsafe())) left = rt right = lt condCombine = false } |] @@ -1039,9 +1038,9 @@ module Compiler = Array.append possibleCases [| { parent = VTree (V.DelayedOr - (lq.ast.castQUnsafe + (lq.ast.CastQUnsafe (), - rq.ast.castQUnsafe + rq.ast.CastQUnsafe ())) left = lq right = rq @@ -1053,13 +1052,13 @@ module Compiler = let num_cost = scriptNumCost n let avg_cost = float n / float subs.Length let e = - best_e + bestE (subs.[0], (p_sat * avg_cost), (p_sat * (1.0 - avg_cost))) let ws = subs |> Array.map (fun s -> - best_w + bestW (s, (p_sat * avg_cost), (p_sat * (1.0 - avg_cost)))) let pk_cost = ws @@ -1069,20 +1068,20 @@ module Compiler = ws |> Array.fold (fun acc w -> acc + w.satCost) e.satCost let dissat_cost = ws |> Array.fold (fun acc w -> acc + w.dissatCost) e.dissatCost - let wsast = ws |> Array.map (fun w -> w.ast.castWUnsafe()) - { ast = VTree(V.Threshold(n, e.ast.castEUnsafe(), wsast)) + let wsast = ws |> Array.map (fun w -> w.ast.CastWUnsafe()) + { ast = VTree(V.Threshold(n, e.ast.CastEUnsafe(), wsast)) pkCost = pk_cost satCost = sat_cost * avg_cost + dissat_cost * (1.0 - avg_cost) dissatCost = 0.0 } type CompiledNode with - static member fromPolicy (p : Policy) = CompiledNode.fromPolicy p - member this.compile() = - let node = CompiledNode.best_t (this, 1.0, 0.0) + static member FromPolicy (p : Policy) = CompiledNode.fromPolicy p + member this.Compile() = + let node = CompiledNode.bestT (this, 1.0, 0.0) MiniScript.fromAST (node.ast) - member this.compileUnsafe() = - match this.compile() with + member this.CompileUnsafe() = + match this.Compile() with | Ok miniscript -> miniscript | Error e -> failwith e \ No newline at end of file diff --git a/NBitcoin.Miniscript/MiniscriptDecompiler.fs b/NBitcoin.Miniscript/MiniscriptDecompiler.fs index 3948ed70fa..6f51fadb9d 100644 --- a/NBitcoin.Miniscript/MiniscriptDecompiler.fs +++ b/NBitcoin.Miniscript/MiniscriptDecompiler.fs @@ -232,7 +232,6 @@ module TokenParser = let actualCat = actualToken.GetCategory() if cat = Any || cat = actualCat then let newState = { state with position=state.position - 1 } - let item = actualToken.GetItem() Ok (actualToken.GetItem(), newState) else let msg = sprintf "token is not the one expected \nactual: %A\nexpected: %A" actualCat cat @@ -304,14 +303,14 @@ module TokenParser = let pWCastE = (pToken FromAltStack) >>. (pE) .>> (pToken ToAltStack) |>> fun expr -> - WTree(W.CastE(expr.castEUnsafe())) + WTree(W.CastE(expr.CastEUnsafe())) let pWHashEqual = (pToken EndIf >>. pF .>> pToken If .>> pToken ZeroNotEqual .>> pToken Size .>> pToken Swap) >>=( fun ast -> let name = "pWHashEqualValidator" let innerFn state = - match ast.castF() with + match ast.CastF() with | Ok fexpr -> match fexpr with | F.HashEqual hash -> @@ -327,13 +326,13 @@ module TokenParser = let pEParallelAnd = ((pToken BoolAnd) >>. pW .>>. pE |>> fun (astW, astE) -> - ETree(E.ParallelAnd(astE.castEUnsafe(), astW.castWUnsafe()))) + ETree(E.ParallelAnd(astE.CastEUnsafe(), astW.CastWUnsafe()))) "Parser E.ParallelAnd" let pEParallelOr = ((pToken BoolOr) >>. pW .>>. pE |>> fun (astW, astE) -> - ETree(E.ParallelOr(astE.castEUnsafe(), astW.castWUnsafe()))) + ETree(E.ParallelOr(astE.CastEUnsafe(), astW.CastWUnsafe()))) "Parser E.ParallelAnd" let pEThreshold = (((pToken Equal) >>. (pToken Number)) @@ -341,11 +340,11 @@ module TokenParser = .>>. (pENoPostProcess) |>> fun (kws, east) -> let k = (fst kws).Value :?> uint32 - let e = east.castEUnsafe() + let e = east.CastEUnsafe() let ws = (snd kws) |> List.toArray |> Array.rev - |> Array.map(fun ast -> ast.castWUnsafe()) + |> Array.map(fun ast -> ast.CastWUnsafe()) ETree(E.Threshold(k, e, ws)) ) "Parser E.Threshold" @@ -367,32 +366,32 @@ module TokenParser = let pEUnlikely = pLikelyPrefix .>> pToken If - |>> fun (fexpr) -> ETree(E.Unlikely(fexpr.castFUnsafe())) + |>> fun (fexpr) -> ETree(E.Unlikely(fexpr.CastFUnsafe())) let pELikely = pLikelyPrefix .>> pToken NotIf - |>> fun (fexpr) -> ETree(E.Likely(fexpr.castFUnsafe())) + |>> fun (fexpr) -> ETree(E.Likely(fexpr.CastFUnsafe())) let pECascadeAnd = (pToken EndIf) >>. pF .>> pToken Else .>>. ((pNumberN 0u) >>. (pToken NotIf) >>. pE) |>> fun (rightF, leftE) -> - ETree(E.CascadeAnd(leftE.castEUnsafe(), rightF.castFUnsafe())) + ETree(E.CascadeAnd(leftE.CastEUnsafe(), rightF.CastFUnsafe())) let pESwitchOrLeft = ((pToken EndIf) >>. pF .>> pToken Else) .>>. ((pE) .>> pToken If) |>> fun (rightF, leftE) -> - ETree(E.SwitchOrLeft(leftE.castEUnsafe(), rightF.castFUnsafe())) + ETree(E.SwitchOrLeft(leftE.CastEUnsafe(), rightF.CastFUnsafe())) let pESwitchOrRight = (pToken EndIf >>. pF .>> pToken Else) .>>. (pE .>> pToken NotIf) |>> fun (rightF, leftE) -> - ETree(E.SwitchOrRight(leftE.castEUnsafe(), rightF.castFUnsafe())) + ETree(E.SwitchOrRight(leftE.CastEUnsafe(), rightF.CastFUnsafe())) // ---- V ------- let pVDelayedOr = (((pToken CheckSigVerify) >>. (pToken EndIf) >>. pQ) .>>. (pToken Else >>. pQ .>> pToken If) |>> fun (q1, q2) -> - VTree(V.DelayedOr(q2.castQUnsafe(), q1.castQUnsafe())) + VTree(V.DelayedOr(q2.CastQUnsafe(), q1.CastQUnsafe())) ) "P.VDelayedOr" let pVHashEqual = ((pToken EqualVerify) >>. ((pToken Sha256Hash) @@ -408,11 +407,11 @@ module TokenParser = .>>. (pE) |>> fun (kws, east) -> let k = (fst kws).Value :?> uint32 - let e = east.castEUnsafe() + let e = east.CastEUnsafe() let ws = (snd kws) |> List.toArray |> Array.rev - |> Array.map(fun ast -> ast.castWUnsafe()) + |> Array.map(fun ast -> ast.CastWUnsafe()) VTree(V.Threshold(k, e, ws)) let pVCheckSig = ((pToken CheckSigVerify) @@ -434,17 +433,17 @@ module TokenParser = let pVSwitchOr = (pToken EndIf >>. pV .>> pToken Else) .>>. (pV .>> pToken If) |>> fun (rightV, leftV) -> - VTree(V.SwitchOr(leftV.castVUnsafe(), rightV.castVUnsafe())) + VTree(V.SwitchOr(leftV.CastVUnsafe(), rightV.CastVUnsafe())) let pVCascadeOr = (pToken EndIf >>. pV .>> pToken NotIf) .>>. pE |>> fun (rightV, leftE) -> - VTree(V.CascadeOr(leftE.castEUnsafe(), rightV.castVUnsafe())) + VTree(V.CascadeOr(leftE.CastEUnsafe(), rightV.CastVUnsafe())) let pVSwitchOrT = (pToken Verify >>. pToken EndIf >>. pT .>> pToken Else) .>>. (pT .>> pToken If) |>> fun (rightT, leftT) -> - VTree(V.SwitchOrT(leftT.castTUnsafe(), rightT.castTUnsafe())) + VTree(V.SwitchOrT(leftT.CastTUnsafe(), rightT.CastTUnsafe())) // ---- Q ------- let pQPubKey = ((pToken Pk) @@ -454,7 +453,7 @@ module TokenParser = let pQOr = ((pToken EndIf) >>. pQ) .>>. ((pToken Else) >>. pQ .>> pToken(If)) - |>> fun (l, r) -> QTree(Q.Or(r.castQUnsafe(), l.castQUnsafe())) + |>> fun (l, r) -> QTree(Q.Or(r.CastQUnsafe(), l.CastQUnsafe())) // ---- T ------- let pTHashEqual = ((pToken Equal @@ -468,7 +467,7 @@ module TokenParser = let pTDelayedOr = ((pToken CheckSig) >>. (pToken EndIf) >>. pQ .>>. (pToken Else >>. pQ .>> pToken If) - |>> fun (q1, q2) -> TTree(T.DelayedOr(q2.castQUnsafe(), q2.castQUnsafe())) + |>> fun (q1, q2) -> TTree(T.DelayedOr(q2.CastQUnsafe(), q2.CastQUnsafe())) ) "Parser T.DelayedOr" let pTTime = ((pToken CheckSequenceVerify) >>. (pToken Number) @@ -480,13 +479,13 @@ module TokenParser = let pTSwitchOr = ((pToken EndIf >>. pT .>> pToken Else) .>>. (pT .>> pToken If) |>> fun (rightT, leftT) -> - TTree(T.SwitchOr(leftT.castTUnsafe(), rightT.castTUnsafe())) + TTree(T.SwitchOr(leftT.CastTUnsafe(), rightT.CastTUnsafe())) ) "Parser T.SwitchOr" let pTCascadeOr = (pToken EndIf >>. pT .>> pToken NotIf .>> pToken IfDup) .>>. pE |>> fun (rightT, leftE) -> - TTree(T.CascadeOr(leftE.castEUnsafe(), rightT.castTUnsafe())) + TTree(T.CascadeOr(leftE.CastEUnsafe(), rightT.CastTUnsafe())) // ---- F ------- let pFTime = (pToken ZeroNotEqual) >>. (pToken CheckSequenceVerify) @@ -498,14 +497,14 @@ module TokenParser = let pFSwitchOr = ((pToken EndIf) >>. pF .>> pToken Else) .>>. (pF .>> pToken If) |>> fun (rightF, leftF) -> - FTree(F.SwitchOr(leftF.castFUnsafe(), rightF.castFUnsafe())) + FTree(F.SwitchOr(leftF.CastFUnsafe(), rightF.CastFUnsafe())) let pFFromV = (pNumberN 1u >>. pV) >>=( fun ast -> let name = "pFFromV" let innerFn state = - match ast.castVUnsafe() with + match ast.CastVUnsafe() with | V.CheckSig pk -> Ok(FTree(F.CheckSig(pk)), state) | V.CheckMultiSig (m, pks) -> @@ -573,17 +572,17 @@ module TokenParser = Error e | Ok result -> let leftAST, state = result - let leftV = leftAST.castVUnsafe() + let leftV = leftAST.CastVUnsafe() match (rightAST.GetASTType()) with - | TExpr -> Ok(TTree(T.And(leftV, rightAST.castTUnsafe())), state) + | TExpr -> Ok(TTree(T.And(leftV, rightAST.CastTUnsafe())), state) | EExpr -> - Ok(TTree(T.And(leftV, rightAST.castTUnsafe())), state) - | QExpr -> Ok(QTree(Q.And(leftV, rightAST.castQUnsafe())), state) + Ok(TTree(T.And(leftV, rightAST.CastTUnsafe())), state) + | QExpr -> Ok(QTree(Q.And(leftV, rightAST.CastQUnsafe())), state) | FExpr -> - match rightAST.castT() with + match rightAST.CastT() with | Ok t -> Ok(TTree(t), state) - | Error _ -> Ok(FTree(F.And(leftV, rightAST.castFUnsafe())), state) - | VExpr -> Ok(VTree(V.And(leftV, rightAST.castVUnsafe())), state) + | Error _ -> Ok(FTree(F.And(leftV, rightAST.CastFUnsafe())), state) + | VExpr -> Ok(VTree(V.And(leftV, rightAST.CastVUnsafe())), state) | _ -> failwith "unreachable" {parseFn=innerFn; name = name} @@ -595,7 +594,7 @@ module TokenParser = if ast.GetASTType() = expected then Ok(ast, state) else if expected = TExpr && ast.IsT() then - Ok(TTree(ast.castTUnsafe()), state) + Ok(TTree(ast.CastTUnsafe()), state) else let msg = sprintf "AST is not the expected type\nexpected: %A\nactual: %A" expected ast Error(name, msg, state.position) diff --git a/NBitcoin.Miniscript/Satisfy.fs b/NBitcoin.Miniscript/Satisfy.fs index f8dc9ef244..ce3b032968 100644 --- a/NBitcoin.Miniscript/Satisfy.fs +++ b/NBitcoin.Miniscript/Satisfy.fs @@ -113,17 +113,17 @@ module Satisfy = and satisfyAST (keyFn, hashFn, age) (ast: AST) = match ast.GetASTType() with - | EExpr -> satisfyE (keyFn, hashFn, age) (ast.castEUnsafe()) - | FExpr -> satisfyF (keyFn, hashFn, age) (ast.castFUnsafe()) - | WExpr -> satisfyW (keyFn, hashFn, age) (ast.castWUnsafe()) - | QExpr -> satisfyQ (keyFn, hashFn, age) (ast.castQUnsafe()) - | TExpr -> satisfyT (keyFn, hashFn, age) (ast.castTUnsafe()) - | VExpr -> satisfyV (keyFn, hashFn, age) (ast.castVUnsafe()) + | EExpr -> satisfyE (keyFn, hashFn, age) (ast.CastEUnsafe()) + | FExpr -> satisfyF (keyFn, hashFn, age) (ast.CastFUnsafe()) + | WExpr -> satisfyW (keyFn, hashFn, age) (ast.CastWUnsafe()) + | QExpr -> satisfyQ (keyFn, hashFn, age) (ast.CastQUnsafe()) + | TExpr -> satisfyT (keyFn, hashFn, age) (ast.CastTUnsafe()) + | VExpr -> satisfyV (keyFn, hashFn, age) (ast.CastVUnsafe()) and dissatisfyAST (ast: AST) = match ast.GetASTType() with - | EExpr -> dissatisfyE (ast.castEUnsafe()) - | WExpr -> dissatisfyW (ast.castWUnsafe()) + | EExpr -> dissatisfyE (ast.CastEUnsafe()) + | WExpr -> dissatisfyW (ast.CastWUnsafe()) | _ -> failwith "unreachable" and satisfyParallelOr providers (l: AST, r: AST) = diff --git a/NBitcoin.Miniscript/Utils/Parser.fs b/NBitcoin.Miniscript/Utils/Parser.fs index 7e568bc1d4..57fffb0738 100644 --- a/NBitcoin.Miniscript/Utils/Parser.fs +++ b/NBitcoin.Miniscript/Utils/Parser.fs @@ -49,7 +49,7 @@ module Parser = // 3: Applicatives let applyP fP xP = fP >>= (fun f -> - xP >>= (fun x -> returnP (f x))) + xP >>= (f >> returnP)) let (<*>) = applyP let lift2 f xP yP = @@ -118,7 +118,7 @@ module Parser = let rec star p input = let firstResult = p.parseFn input match firstResult with - | Error (_, _, _) -> ([], input) + | Error _ -> ([], input) | Ok (firstValue, inputAfterFirstPlace) -> let (subsequenceValues, remainingInput) = star p inputAfterFirstPlace From a53651ed5ea8ab853d7a4e908fce4ff915a0bf93 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Wed, 27 Mar 2019 21:10:09 +0900 Subject: [PATCH 05/40] fixup! Move Miniscript to independent Directory --- NBitcoin/NBitcoin.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NBitcoin/NBitcoin.csproj b/NBitcoin/NBitcoin.csproj index 9d4957fffb..dc71e9f73f 100644 --- a/NBitcoin/NBitcoin.csproj +++ b/NBitcoin/NBitcoin.csproj @@ -20,7 +20,7 @@ true - netstandard1.3;netstandard1.1;netcoreapp2.1;netstandard2.0 + net461;net452;netstandard1.3;netstandard1.1;netcoreapp2.1;netstandard2.0 netstandard2.0 $(TargetFrameworkOverride) 1591;1573;1572;1584;1570;3021 From 3d2abb3dc07f7b93b526fd0cdd54b9d3a6fd096f Mon Sep 17 00:00:00 2001 From: joemphilips Date: Wed, 3 Apr 2019 11:01:14 +0900 Subject: [PATCH 06/40] pack Miniscript without editing .nuspec manually --- NBitcoin/NBitcoin.csproj | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/NBitcoin/NBitcoin.csproj b/NBitcoin/NBitcoin.csproj index dc71e9f73f..521575ae67 100644 --- a/NBitcoin/NBitcoin.csproj +++ b/NBitcoin/NBitcoin.csproj @@ -1,5 +1,5 @@  - + Metaco SA Copyright © Metaco SA 2017 @@ -20,7 +20,7 @@ true - net461;net452;netstandard1.3;netstandard1.1;netcoreapp2.1;netstandard2.0 + netcoreapp2.1;netstandard2.0 netstandard2.0 $(TargetFrameworkOverride) 1591;1573;1572;1584;1570;3021 @@ -94,12 +94,31 @@ bin\$(Configuration)\$(TargetFramework)\NBitcoin.xml - - .\NBitcoin.nuspec - $(NuspecProperties);id=$(AssemblyName) - $(NuspecProperties);config=$(Configuration) - $(NuspecProperties);version=$(Version) - $(NuspecProperties);description=$(Description) - $(NuspecProperties);authors=$(Company) - + + + + + + + + + + + From c76955f82c72683a523e7dc58f6c1c9f0d465ff6 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Wed, 3 Apr 2019 11:34:06 +0900 Subject: [PATCH 07/40] pack Miniscript without editing .nuspec manually. --- NBitcoin.Miniscript/Miniscript.fsproj | 4 ++- NBitcoin/NBitcoin.csproj | 48 ++++++++++++--------------- pack.sh | 14 ++++++++ 3 files changed, 39 insertions(+), 27 deletions(-) create mode 100644 pack.sh diff --git a/NBitcoin.Miniscript/Miniscript.fsproj b/NBitcoin.Miniscript/Miniscript.fsproj index 5db59797d9..252c6a6477 100644 --- a/NBitcoin.Miniscript/Miniscript.fsproj +++ b/NBitcoin.Miniscript/Miniscript.fsproj @@ -1,6 +1,6 @@  - netcoreapp2.1;netstandard2.0 + net461;netcoreapp2.1;netstandard2.0 true @@ -25,4 +25,6 @@ + + diff --git a/NBitcoin/NBitcoin.csproj b/NBitcoin/NBitcoin.csproj index 521575ae67..c8898c21f4 100644 --- a/NBitcoin/NBitcoin.csproj +++ b/NBitcoin/NBitcoin.csproj @@ -94,31 +94,27 @@ bin\$(Configuration)\$(TargetFramework)\NBitcoin.xml - - - - - - - - - - + + + true + + + + true + Always + lib\netstandard2.0 + + + true + Always + lib\netcoreapp2.1 + + + true + Always + lib\net461 + + diff --git a/pack.sh b/pack.sh new file mode 100644 index 0000000000..f61fc6aaa4 --- /dev/null +++ b/pack.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -u + +readonly VERSION="1.0" +if [[ "$(uname)" == 'Darwin' ]]; then + readonly SCRIPT_DIR_PATH=$(dirname $(greadlink -f $0)) +else + readonly SCRIPT_DIR_PATH=$(dirname $(readlink -f $0)) +fi + +cd $SCRIPT_DIR_PATH + +dotnet build NBitcoin.Miniscript -c Release +dotnet pack NBitcoin -c Release From 47e3a46d5fadebe895651eaf0e6592374f889f6f Mon Sep 17 00:00:00 2001 From: joemphilips Date: Wed, 3 Apr 2019 21:46:13 +0900 Subject: [PATCH 08/40] Start Writing Tests for Miniscript --- NBitcoin.Miniscript.Tests/AssemblyInfo.fs | 41 ++ NBitcoin.Miniscript.Tests/Contract.fs | 28 ++ .../FNBitcoin.Tests.fsproj | 29 ++ NBitcoin.Miniscript.Tests/Generators/Lib.fs | 48 +++ .../Generators/NBitcoin.fs | 11 + .../Generators/Policy.fs | 58 +++ .../Generators/Primitives.fs | 9 + NBitcoin.Miniscript.Tests/Main.fs | 6 + .../MiniScriptCompilerTests.fs | 39 ++ .../MiniScriptDecompilerTests.fs | 358 ++++++++++++++++++ .../MiniScriptParserTests.fs | 99 +++++ NBitcoin.Miniscript.Tests/Tests.fs | 2 + NBitcoin.Miniscript.Tests/fsc.props | 21 + NBitcoin.Miniscript.Tests/netfx.props | 27 ++ NBitcoin.Miniscript/fsc.props | 21 + NBitcoin.Miniscript/netfx.props | 27 ++ 16 files changed, 824 insertions(+) create mode 100644 NBitcoin.Miniscript.Tests/AssemblyInfo.fs create mode 100644 NBitcoin.Miniscript.Tests/Contract.fs create mode 100644 NBitcoin.Miniscript.Tests/FNBitcoin.Tests.fsproj create mode 100644 NBitcoin.Miniscript.Tests/Generators/Lib.fs create mode 100644 NBitcoin.Miniscript.Tests/Generators/NBitcoin.fs create mode 100644 NBitcoin.Miniscript.Tests/Generators/Policy.fs create mode 100644 NBitcoin.Miniscript.Tests/Generators/Primitives.fs create mode 100644 NBitcoin.Miniscript.Tests/Main.fs create mode 100644 NBitcoin.Miniscript.Tests/MiniScriptCompilerTests.fs create mode 100644 NBitcoin.Miniscript.Tests/MiniScriptDecompilerTests.fs create mode 100644 NBitcoin.Miniscript.Tests/MiniScriptParserTests.fs create mode 100644 NBitcoin.Miniscript.Tests/Tests.fs create mode 100644 NBitcoin.Miniscript.Tests/fsc.props create mode 100644 NBitcoin.Miniscript.Tests/netfx.props create mode 100644 NBitcoin.Miniscript/fsc.props create mode 100644 NBitcoin.Miniscript/netfx.props diff --git a/NBitcoin.Miniscript.Tests/AssemblyInfo.fs b/NBitcoin.Miniscript.Tests/AssemblyInfo.fs new file mode 100644 index 0000000000..a93ffd02f5 --- /dev/null +++ b/NBitcoin.Miniscript.Tests/AssemblyInfo.fs @@ -0,0 +1,41 @@ +// Auto-Generated by FAKE; do not edit +namespace System + +open System.Reflection + +[] +[] +[] +[] +[] +[] +[] +[] +do () + +module internal AssemblyVersionInformation = + [] + let AssemblyTitle = "FNBitcoin.Tests" + + [] + let AssemblyProduct = "FNBitcoin" + + [] + let AssemblyVersion = "0.1.0" + + [] + let AssemblyMetadata_ReleaseDate = "2017-03-17T00:00:00.0000000" + + [] + let AssemblyFileVersion = "0.1.0" + + [] + let AssemblyInformationalVersion = "0.1.0" + + [] + let AssemblyMetadata_ReleaseChannel = "release" + + [] + let AssemblyMetadata_GitHash = "bb8964b54bee133e9af64d316dc2cfee16df7f72" diff --git a/NBitcoin.Miniscript.Tests/Contract.fs b/NBitcoin.Miniscript.Tests/Contract.fs new file mode 100644 index 0000000000..c397c895b9 --- /dev/null +++ b/NBitcoin.Miniscript.Tests/Contract.fs @@ -0,0 +1,28 @@ +module ContractTests + +open Expecto +open FNBitcoin.Contract +open FsCheck +open NBitcoin +(* +[] +let contractTests = + testList "Contracts" [ + testProperty "Multisig" <| fun (m: PositiveInt) (n: PositiveInt) -> + if (m < n) && n.Get < 16 then + let pubkeys = seq { for i in 0..n.Get do + yield Key().PubKey } + |> Seq.toArray + let verifiableScript = Multisig(m.Get, pubkeys) + let res = verify { + let! v = verifiableScript + return v + } + match res with + | Verified s -> Expect.isTrue true "ok" + | Failed s -> Expect.isTrue false "failed" + else + Expect.isTrue true "skip" + ] + +*) diff --git a/NBitcoin.Miniscript.Tests/FNBitcoin.Tests.fsproj b/NBitcoin.Miniscript.Tests/FNBitcoin.Tests.fsproj new file mode 100644 index 0000000000..74927e02b7 --- /dev/null +++ b/NBitcoin.Miniscript.Tests/FNBitcoin.Tests.fsproj @@ -0,0 +1,29 @@ + + + Exe + netcoreapp2.1;netcoreapp2.2;net461 + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NBitcoin.Miniscript.Tests/Generators/Lib.fs b/NBitcoin.Miniscript.Tests/Generators/Lib.fs new file mode 100644 index 0000000000..1b960fb2e8 --- /dev/null +++ b/NBitcoin.Miniscript.Tests/Generators/Lib.fs @@ -0,0 +1,48 @@ +namespace NBitcoin.Miniscript.Tests.Generators + +open FsCheck +open NBitcoin.Miniscript.MiniscriptParser +open NBitcoin.Miniscript.Tests.Generators.Policy + +type Generators = + static member Policy() : Arbitrary = // policy |> Arb.fromGen + { new Arbitrary() with + override this.Generator = policy + // TODO: This shrinker is far from ideal + // 1. nested shrinking does not work well + // 2. Must use Seq instead of List + override this.Shrinker(p: Policy) = + let rec shrinkPolicy p = + match p with + | Key k -> [] + | Multi(m, pks) -> [Multi(1u, pks.[0..0]); Policy.Key pks.[0]] + | Policy.Hash h -> [] + | Policy.Time t -> [] + | Policy.Threshold (k, ps) -> + let shrinkThres (k, (ps: Policy[])) = + let k2 = if k = 1u then k else k - 1u + let ps2 = Arb.shrink(ps) + ps2 |> Seq.toList |> List.map(fun p -> Policy.Threshold(k2, p)) + let subexpr = ps |> Array.toList + if ps.Length = 1 then subexpr else shrinkThres(k, ps) + | Policy.And(p1, p2) -> + let shrinkedAnd = shrinkNested Policy.And p1 p2 + List.concat[shrinkedAnd; [p1; p2;]] + | Policy.Or(p1, p2) -> + let shrinkedOr = shrinkNested Policy.Or p1 p2 + List.concat[shrinkedOr; [p1; p2;]] + | Policy.AsymmetricOr(p1, p2) -> + let shrinkedAOr = shrinkNested Policy.AsymmetricOr p1 p2 + List.concat[shrinkedAOr; [p1; p2;]] + + /// Helper for shrinking nested types + and shrinkNested expectedType p1 p2 = + let shrinkedSub1 = shrinkPolicy p1 + let shrinkedSub2 = shrinkPolicy p2 + shrinkedSub1 + |> List.collect(fun p1e -> shrinkedSub2 + |> List.map(fun p2e -> p1e, p2e)) + |> List.map expectedType + + shrinkPolicy p |> List.toSeq + } diff --git a/NBitcoin.Miniscript.Tests/Generators/NBitcoin.fs b/NBitcoin.Miniscript.Tests/Generators/NBitcoin.fs new file mode 100644 index 0000000000..e1bf9b9e68 --- /dev/null +++ b/NBitcoin.Miniscript.Tests/Generators/NBitcoin.fs @@ -0,0 +1,11 @@ +namespace NBitcoin.Miniscript.Tests.Generators + +module internal NBitcoin = + open FsCheck + open NBitcoin.Miniscript.Tests.Generators.Primitives + + let pubKeyGen = + let k = NBitcoin.Key() // prioritize speed for randomness + Gen.constant (k) |> Gen.map (fun k -> k.PubKey) + + let uint256Gen = bytesOfNGen 32 |> Gen.map uint256 diff --git a/NBitcoin.Miniscript.Tests/Generators/Policy.fs b/NBitcoin.Miniscript.Tests/Generators/Policy.fs new file mode 100644 index 0000000000..14d35b94e1 --- /dev/null +++ b/NBitcoin.Miniscript.Tests/Generators/Policy.fs @@ -0,0 +1,58 @@ +namespace NBitcoin.Miniscript.Tests.Generators + +module internal Policy = + open FsCheck + open NBitcoin.Miniscript.MiniscriptParser + open NBitcoin.Miniscript.Tests.Generators.NBitcoin + + let multiContentsGen = gen { let! n = Gen.choose (1, 20) |> Gen.map uint32 + let! subN = Gen.choose ((int n), 20) + let! subs = Gen.arrayOfLength subN pubKeyGen + return (n, subs) } + + let nonRecursivePolicyGen : Gen = + Gen.frequency [ (2, Gen.map Key pubKeyGen) + + (1, + Gen.map (fun (num, pks) -> Multi(num, pks)) + multiContentsGen) + (2, Gen.map Hash uint256Gen) + (2, Gen.map Time Arb.generate) ] + + let policy = + let rec policy' s = + match s with + | 0 -> nonRecursivePolicyGen + | n when n > 0 -> + let subPolicyGen = policy' (n / 2) + Gen.frequency [ (2, nonRecursivePolicyGen) + (3, recursivePolicyGen subPolicyGen) ] + | _ -> invalidArg "s" "Only positive arguments are allowed!" + + and recursivePolicyGen (subPolicyGen : Gen) = + Gen.oneof + [ Gen.map (fun (t, ps) -> Threshold(t, ps)) + (thresholdContentsGen subPolicyGen) + + Gen.map2 (fun subP1 subP2 -> And(subP1, subP2)) subPolicyGen + subPolicyGen + + Gen.map2 (fun subP1 subP2 -> Or(subP1, subP2)) subPolicyGen + subPolicyGen + + Gen.map2 (fun subP1 subP2 -> AsymmetricOr(subP1, subP2)) + subPolicyGen subPolicyGen ] + + and thresholdContentsGen (subGen : Gen<_>) = gen { let! n = Gen.choose + (1, 6) + |> Gen.map + uint32 + let! subN = Gen.choose + ((int + n), + 6) + let! subs = Gen.arrayOfLength + subN + subGen + return (n, subs) } + Gen.sized policy' diff --git a/NBitcoin.Miniscript.Tests/Generators/Primitives.fs b/NBitcoin.Miniscript.Tests/Generators/Primitives.fs new file mode 100644 index 0000000000..81282073c8 --- /dev/null +++ b/NBitcoin.Miniscript.Tests/Generators/Primitives.fs @@ -0,0 +1,9 @@ +namespace NBitcoin.Miniscript.Tests.Generators + +module internal Primitives = + open FsCheck + + let byteGen = Gen.choose (0, 127) |> Gen.map byte + let bytesGen = Gen.listOf byteGen + let nonEmptyBytesGen = Gen.nonEmptyListOf byteGen + let bytesOfNGen n = Gen.arrayOfLength n byteGen diff --git a/NBitcoin.Miniscript.Tests/Main.fs b/NBitcoin.Miniscript.Tests/Main.fs new file mode 100644 index 0000000000..9506f7c20b --- /dev/null +++ b/NBitcoin.Miniscript.Tests/Main.fs @@ -0,0 +1,6 @@ +module ExpectoTemplate + +open Expecto + +[] +let main argv = Tests.runTestsInAssembly defaultConfig argv diff --git a/NBitcoin.Miniscript.Tests/MiniScriptCompilerTests.fs b/NBitcoin.Miniscript.Tests/MiniScriptCompilerTests.fs new file mode 100644 index 0000000000..6cbda60e0a --- /dev/null +++ b/NBitcoin.Miniscript.Tests/MiniScriptCompilerTests.fs @@ -0,0 +1,39 @@ +module MiniScriptCompilerTests + +open Expecto +open Expecto.Logging +open Expecto.Logging.Message +open NBitcoin.Miniscript.Tests.Generators +open NBitcoin.Miniscript.Compiler +open NBitcoin.Miniscript.MiniscriptParser + +let logger = Log.create "MiniscriptCompiler" + +let config = + { FsCheckConfig.defaultConfig with arbitrary = [ typeof ] + maxTest = 500 + endSize = 32 + receivedArgs = + fun _ name no args -> + logger.debugWithBP + (eventX + "For {test} {no}, generated {args}" + >> setField "test" name + >> setField "no" no + >> setField "args" args) } + +[] +let tests = + testList "miniscript compiler" [ testPropertyWithConfig config + "should compile arbitrary input" <| fun (p : Policy) -> + let node = CompiledNode.fromPolicy (p) + let t = node.compile() + Expect.isOk t + + testPropertyWithConfig config + "Should compile arbitrary input to actual bitcoin script" <| fun (p: Policy) -> + let m = match CompiledNode.fromPolicy(p).compile() with + | Ok miniscript -> miniscript + | Result.Error e -> failwith e + Expect.isNotNull (m.ToScript()) "script was empty" + ] diff --git a/NBitcoin.Miniscript.Tests/MiniScriptDecompilerTests.fs b/NBitcoin.Miniscript.Tests/MiniScriptDecompilerTests.fs new file mode 100644 index 0000000000..844fe6821a --- /dev/null +++ b/NBitcoin.Miniscript.Tests/MiniScriptDecompilerTests.fs @@ -0,0 +1,358 @@ +module MiniScriptDecompilerTests + +open Expecto +open Expecto.Logging +open NBitcoin +open NBitcoin.Miniscript.Utils +open NBitcoin.Miniscript.MiniscriptParser +open NBitcoin.Miniscript.Tests.Generators +open NBitcoin.Miniscript.AST +open NBitcoin.Miniscript +open NBitcoin.Miniscript.Utils.Parser +open NBitcoin.Miniscript.Compiler +open NBitcoin.Miniscript.Decompiler + +let logger = Log.create "MiniscriptDeCompiler" +let keys = + [ "028c28a97bf8298bc0d23d8c749452a32e694b65e30a9472a3954ab30fe5324caa"; + "03ab1ac1872a38a2f196bed5a6047f0da2c8130fe8de49fc4d5dfb201f7611d8e2"; + "039729247032c0dfcf45b4841fcd72f6e9a2422631fc3466cf863e87154754dd40"; + "032564fe9b5beef82d3703a607253f31ef8ea1b365772df434226aee642651b3fa"; + "0289637f97580a796e050791ad5a2f27af1803645d95df021a3c2d82eb8c2ca7ff" ] + +let keysList = + keys + |> List.map (PubKey) + |> List.toArray + +let longKeysList = keysList.[0] |> Array.replicate 20 +// --------- AST <-> Script --------- +let checkParseResult res expected = + match res with + | Ok (ast) -> Expect.equal ast expected "failed to deserialize properly" + | Result.Error e -> + let name, msg, pos = e + failwithf "name: %s\nmsg: %s\npos: %d" name msg pos + +[] +let tests = + testList "Decompiler" [ testCase "case1" <| fun _ -> + let pk = PubKey(keys.[0]) + let pk2 = PubKey(keys.[1]) + let boolAndWE = ETree( + E.ParallelAnd( + E.CheckSig(pk), W.Time(!> 1u)) + ) + let sc = boolAndWE.ToScript() + let res = FNBitcoin.MiniScriptDecompiler.parseScript sc + checkParseResult res boolAndWE + + testCase "case2" <| fun _ -> + + let pk = PubKey(keys.[0]) + let pk2 = PubKey(keys.[1]) + let delayedOrV = VTree(V.DelayedOr(Q.Pubkey(pk), Q.Pubkey(pk2))) + let sc = delayedOrV.ToScript() + let res = FNBitcoin.MiniScriptDecompiler.parseScript sc + checkParseResult res delayedOrV + + testCase "Should pass the testcase in rust-miniscript" <| fun _ -> + + let roundtrip (miniscriptResult : Result) + (s : Script) = + match miniscriptResult with + | Ok tree -> + let ser = tree.ToScript() + Expect.equal ser s + "Serialized Miniscript does not match expected script" + let deser = + MiniScript.fromScriptUnsafe s + Expect.equal deser tree + "deserialized script does not match expected MiniScript" + | Result.Error e -> failwith e + + let r1 = + MiniScript.fromAST + (AST.TTree + (T.CastE + (E.CheckSig + (PubKey + (keys.[0]))))) + let s1 = + Script + (sprintf "%s %s" + (keys.[0].ToString()) + "OP_CHECKSIG") + roundtrip r1 s1 + let r2 = + MiniScript.fromAST + (AST.TTree + (T.CastE + (E.CheckMultiSig + (3u, keysList)))) + let s2 = + Script + (sprintf + "OP_3 %s %s %s %s %s OP_5 OP_CHECKMULTISIG" + keys.[0] keys.[1] + keys.[2] keys.[3] + keys.[4]) + roundtrip r2 s2 + + let r3_partial = + MiniScript.fromAST(TTree(T.And + (V.CheckMultiSig + (2u, + keysList.[2..3]), + T.Time(!> 10000u)) + )) + + let policy3_2 = + sprintf + "2 %s %s 2 OP_CHECKMULTISIGVERIFY" + keys.[2] keys.[3] + + let s3_partial = Script(sprintf "%s 1027 OP_CSV" policy3_2) + roundtrip r3_partial s3_partial + + // Liquid policy + let r3 = + MiniScript.fromAST + (AST.TTree + (T.CascadeOr + (E.CheckMultiSig + (2u, + keysList.[0..1]), + T.And + (V.CheckMultiSig + (2u, + keysList.[2..3]), + T.Time + (!> 10000u))))) + let policy3_1 = + sprintf + "2 %s %s 2 OP_CHECKMULTISIG" + keys.[0] keys.[1] + let tmp = sprintf "%s OP_IFDUP OP_NOTIF %s 1027 OP_CSV OP_ENDIF" + policy3_1 policy3_2 + let s3 = + Script(tmp) + roundtrip r3 s3 + + let r4 = + MiniScript.fromAST + (TTree(T.Time(!> 921u))) + let s4 = Script("9903 OP_CSV") + roundtrip r4 s4 + + let r5 = MiniScript.fromAST (TTree( + T.SwitchOrV( + V.CheckSig(keysList.[0]), + V.And( + V.CheckSig(keysList.[1]), + V.CheckSig(keysList.[2]) + ) + ))) + + let scriptStr = sprintf "OP_IF %s OP_CHECKSIGVERIFY OP_ELSE %s OP_CHECKSIGVERIFY %s OP_CHECKSIGVERIFY OP_ENDIF 1" + keys.[0] keys.[1] keys.[2] + let s5 = Script(scriptStr) + roundtrip r5 s5 + + ] + +// --------- converting all the way down to ---- +// --------- Policy <-> AST <-> Script --------- +let config = + { FsCheckConfig.defaultConfig with arbitrary = [ typeof ] + maxTest = 30 + endSize = 32 } + +let roundTripFromMiniScript (m: MiniScript) = + let sc = m.ToScript() + let m2 = MiniScript.fromScriptUnsafe sc + Expect.equal m2 m "failed" + +let roundtrip p = + let m = CompiledNode.fromPolicy(p).compileUnsafe() + roundTripFromMiniScript m + +let hash = uint256.Parse("59141e52303a755307114c2a5e6823010b3f1d586216742f396d4b06106e222c") + +[] +let tests2 = + testList "Should convert Policy <-> AST <-> Script" [ + /// This test did good job for finding some bugs. + /// But however, some cases are unfixable so leave it as pending test. + /// specifically, the case is when there is a nested `and`. + /// `and(and(1, 2), 3)` is semantically equal to `and(1, and(2, 3))` + /// But the assertion will fail, so leave it untested. + // TODO: (Ideally, we should have `CustomComparison` for AST and Policy) + ptestPropertyWithConfig config "Every possible MiniScript" <| fun (p: Policy) -> + roundtrip p + testCase "Case found by property tests: 1" <| fun _ -> + let input = Policy.Or(Key(keysList.[0]), Policy.And(Policy.Time(!> 2u), Policy.Time(!> 1u))) + let m = CompiledNode.fromPolicy(input).compileUnsafe() + let sc = m.ToScript() + let customParser = TokenParser.pT + let ops = sc.ToOps() |> Seq.toArray + let customState = {ops=ops; position=ops.Length - 1} + let m2 = run customParser customState + Expect.isOk m2 "failed" + + testCase "Case found by property tests: 2" <| fun _ -> + let input = MiniScript.fromASTUnsafe(TTree(T.HashEqual(hash))) + roundTripFromMiniScript input + + testCase "Case found by property tests: 3" <| fun _ -> + let input = MiniScript.fromASTUnsafe( + TTree(T.And(V.Time(1u), T.Time(1u)))) + roundTripFromMiniScript input + + testCase "Case found by property tests: 4" <| fun _ -> + let input = MiniScript.fromASTUnsafe(TTree( + T.CastE( + E.Threshold( + 1u, + E.CheckSig(keysList.[0]), + [| W.CheckSig(keysList.[0]) |] + ) + ) + ) + ) + roundTripFromMiniScript input + testCase "Case found by property tests: 5" <| fun _ -> + let input = MiniScript.fromASTUnsafe(TTree( + T.CastE( + E.Likely( + F.Threshold( + 1u, + E.CheckSig(keysList.[0]), + [| W.CheckSig(keysList.[0]) |] + ) + ) + )) + ) + roundTripFromMiniScript input + testCase "Case found by property tests: 6" <| fun _ -> + let input = MiniScript.fromASTUnsafe(TTree( + T.And( + V.CheckMultiSig(1u, longKeysList), + T.Time(!> 1u) + ))) + roundTripFromMiniScript input + testCase "Case found by property tests: 7" <| fun _ -> + let input = MiniScript.fromASTUnsafe(TTree( + T.CastE( + E.SwitchOrRight(E.Time(!> 1u), F.Time(!> 1u)) + ))) + roundTripFromMiniScript input + + testCase "Case found by property tests: 8" <| fun _ -> + let input = MiniScript.fromASTUnsafe(TTree( + T.And( + V.Time(!> 2u), + T.CastE(E.Threshold( + 1u, + E.Time(!> 4u), + [|W.Time(!> 5u)|] + )) + ) + )) + + roundTripFromMiniScript input + testCase "Case found by property tests: 9" <| fun _ -> + let input = MiniScript.fromASTUnsafe(TTree( + T.CastE( + E.Likely(F.And(V.Time(!> 2u), F.Time(!> 2u))) + ) + )) + + roundTripFromMiniScript input + ptestCase "Can NOT handle nested And" <| fun _ -> + let input = MiniScript.fromASTUnsafe(TTree( + T.And(V.Time(!> 3u), T.And(V.Time(!> 4u), T.Time(!> 4u))) + )) + + roundTripFromMiniScript input + ] + +let roundtripParserAndAST (parser: Parser<_, _>) (ast: AST) = + let sc = ast.ToScript() + let ops = sc.ToOps() |> Seq.toArray + let initialState = {ops=ops;position=ops.Length - 1} + match run parser initialState with + | Ok r -> Expect.equal ast (fst r) "AST is not equal" + | Result.Error e -> failwithf "%A" e + +[] +let deserializationTestWithParser = + testList "deserialization test with parser" [ + testCase "Case found by property tests: 5_2" <| fun _ -> + let input = + ETree( + E.Likely( + F.Time(!> 1u) + ) + ) + let parser = TokenParser.pE + roundtripParserAndAST parser input + testCase "Case found by property tests: 5_3" <| fun _ -> + let input = + FTree(F.Threshold( + 1u, + E.CheckSig(keysList.[0]), + [| W.CheckSig(keysList.[0]) |] + )) + let parser = TokenParser.pF + roundtripParserAndAST parser input + testCase "Case found by property tests: 6_1" <| fun _ -> + let input = + VTree(V.CheckMultiSig(1u, longKeysList)) + let parser = TokenParser.pV + roundtripParserAndAST parser input + testCase "Case found by property tests: 7_1" <| fun _ -> + let input = + WTree(W.CastE(E.CheckSig(keysList.[0]))) + let parser = TokenParser.pW + roundtripParserAndAST parser input + + testCase "Case found by property tests: 7_2" <| fun _ -> + let input = + WTree(W.CastE(E.SwitchOrRight(E.Time(!> 1u), F.Time(!> 1u)))) + + let parser = TokenParser.pW + roundtripParserAndAST parser input + testCase "Case found by property tests: 8_1" <| fun _ -> + let input = + FTree(F.SwitchOr(F.Time(!> 1u), F.Time(!> 1u))) + + let parser = TokenParser.pF + roundtripParserAndAST parser input + testCase "Case found by property tests: 8_2" <| fun _ -> + let input = + VTree(V.SwitchOr(V.Time(!> 1u), V.Time(!> 1u))) + + let parser = TokenParser.pV + roundtripParserAndAST parser input + + let input = + TTree(T.SwitchOr(T.Time(!> 1u), T.Time(!> 1u))) + let parser = TokenParser.pT + roundtripParserAndAST parser input + + let input = + VTree(V.SwitchOrT(T.Time(!> 1u), T.Time(!> 1u))) + let parser = TokenParser.pV + roundtripParserAndAST parser input + testCase "Case found by property tests: 8_3" <| fun _ -> + let input = + ETree(E.SwitchOrRight(E.Time(!> 1u), F.Time(!> 1u))) + let parser = TokenParser.pE + roundtripParserAndAST parser input + testCase "Case found by property tests: 9_2" <| fun _ -> + let input = + FTree(F.And(V.Time(!> 2u), F.Time(!> 2u))) + let parser = TokenParser.pF + roundtripParserAndAST parser input + ] \ No newline at end of file diff --git a/NBitcoin.Miniscript.Tests/MiniScriptParserTests.fs b/NBitcoin.Miniscript.Tests/MiniScriptParserTests.fs new file mode 100644 index 0000000000..b4eb12b18b --- /dev/null +++ b/NBitcoin.Miniscript.Tests/MiniScriptParserTests.fs @@ -0,0 +1,99 @@ +module MiniScriptParserTests + +open NBitcoin.Miniscript.MiniscriptParser +open Expecto +open Expecto.Logging +open Expecto.Logging.Message +open NBitcoin.Miniscript.Tests.Generators + +let logger = Log.create "MiniscriptParser" +let pk1Str = + "0225629523a16986d3bf575e1163f7dff024d734ad1e656b55ba94e41ede2fdfb6" +let pk2Str = + "03b0ad2ab4133717f26f3a50dfb3a0df0664c88045a1b80005aac88284003a98d3" +let pk3Str = + "02717a8c5d9fc77bc12cfe1171f51c5e5178048a5b8f66ca11088dced56d8bf469" + +let check = + function + | Policy p -> () + | _ -> failwith "Failed to parse policy" + +let config = + { FsCheckConfig.defaultConfig with arbitrary = [ typeof ] + maxTest = 30 + endSize = 128 + receivedArgs = + fun _ name no args -> + logger.debugWithBP + (eventX + "For {test} {no}, generated {args}" + >> setField "test" name + >> setField "no" no + >> setField "args" args) } + +[] +let tests = + testList "miniscript parser tests" + [ testCase "Should print" + <| fun _ -> + let pk1, pk2, pk3 = + NBitcoin.PubKey(pk1Str), NBitcoin.PubKey(pk2Str), + NBitcoin.PubKey(pk3Str) + let testdata1 : Policy = + And + (Key(pk1), + Or + (Multi(1u, [| pk2; pk3 |]), + AsymmetricOr(Key(pk1), Time(1000u)))) + let actual = testdata1.ToString() + let expected = + sprintf + "and(pk(%s),or(multi(1,%s,%s),aor(pk(%s),time(1000))))" + pk1Str pk2Str pk3Str pk1Str + Expect.equal actual expected + "Policy.print did not work as expected" + testCase "ParseTest1" <| fun _ -> + let d1 = sprintf "pk(%s)" pk1Str + check d1 + let d2 = sprintf "multi(1,%s,%s)" pk1Str pk2Str + check d2 + let d3 = sprintf "hash(%s)" (NBitcoin.uint256().ToString()) + check d3 + let d4 = "time(1000)" + check d4 + let d5 = sprintf "thres(2,%s,%s,%s)" d1 d2 d3 + check d5 + let d6 = sprintf "and(%s,%s)" d2 d3 + check d6 + let d7 = sprintf "or(%s, %s)" d4 d5 + check d7 + let d8 = sprintf "aor(%s,%s)" d6 d7 + check d8 + testCase "parsing input with noise" <| fun _ -> + let dataWithWhiteSpace = + sprintf + "thres ( 2 , and (pk ( %s ) , aor( multi (2, %s , %s ) , time ( 1000)) ), pk(%s))" + pk1Str pk1Str pk1Str pk2Str + check dataWithWhiteSpace + let dataWithNewLine = + sprintf + "thres ( \r\n2 , and \n(pk ( \n%s ) , aor( multi \n(2, %s ,%s )\n, time ( 1000)) ), pk(%s))" + pk1Str pk1Str pk1Str pk2Str + check dataWithNewLine + testCase "bidirectional conversion" <| fun _ -> + let data = + sprintf + "thres(2,and(pk(%s),aor(multi(2,%s,%s),time(1000))),pk(%s))" + pk1Str pk1Str pk1Str pk2Str + + let data2 = + match data with + | Policy p -> p.ToString() + | _ -> failwith "Failed to parse policy" + Expect.equal data data2 "Could not parse symmetrically" + + testPropertyWithConfig config "Should convert <-> " <| fun (p : Policy) -> + match p.ToString() with + | Policy p2 -> Expect.equal p p2 + | _ -> failwith "Failed to convert bidirectionally" ] diff --git a/NBitcoin.Miniscript.Tests/Tests.fs b/NBitcoin.Miniscript.Tests/Tests.fs new file mode 100644 index 0000000000..1673813524 --- /dev/null +++ b/NBitcoin.Miniscript.Tests/Tests.fs @@ -0,0 +1,2 @@ +module Tests + diff --git a/NBitcoin.Miniscript.Tests/fsc.props b/NBitcoin.Miniscript.Tests/fsc.props new file mode 100644 index 0000000000..3fb4e62948 --- /dev/null +++ b/NBitcoin.Miniscript.Tests/fsc.props @@ -0,0 +1,21 @@ + + + + + true + true + true + + + C:\Program Files (x86)\Microsoft SDKs\F#\4.1\Framework\v4.0 + fsc.exe + + + /Library/Frameworks/Mono.framework/Versions/Current/Commands + fsharpc + + + /usr/bin + fsharpc + + diff --git a/NBitcoin.Miniscript.Tests/netfx.props b/NBitcoin.Miniscript.Tests/netfx.props new file mode 100644 index 0000000000..c410c1964f --- /dev/null +++ b/NBitcoin.Miniscript.Tests/netfx.props @@ -0,0 +1,27 @@ + + + + + + true + + + /Library/Frameworks/Mono.framework/Versions/Current/lib/mono + /usr/lib/mono + /usr/local/lib/mono + + + $(BaseFrameworkPathOverrideForMono)/4.5-api + $(BaseFrameworkPathOverrideForMono)/4.5.1-api + $(BaseFrameworkPathOverrideForMono)/4.5.2-api + $(BaseFrameworkPathOverrideForMono)/4.6-api + $(BaseFrameworkPathOverrideForMono)/4.6.1-api + $(BaseFrameworkPathOverrideForMono)/4.6.2-api + $(BaseFrameworkPathOverrideForMono)/4.7-api + $(BaseFrameworkPathOverrideForMono)/4.7.1-api + true + + + $(FrameworkPathOverride)/Facades;$(AssemblySearchPaths) + + diff --git a/NBitcoin.Miniscript/fsc.props b/NBitcoin.Miniscript/fsc.props new file mode 100644 index 0000000000..3fb4e62948 --- /dev/null +++ b/NBitcoin.Miniscript/fsc.props @@ -0,0 +1,21 @@ + + + + + true + true + true + + + C:\Program Files (x86)\Microsoft SDKs\F#\4.1\Framework\v4.0 + fsc.exe + + + /Library/Frameworks/Mono.framework/Versions/Current/Commands + fsharpc + + + /usr/bin + fsharpc + + diff --git a/NBitcoin.Miniscript/netfx.props b/NBitcoin.Miniscript/netfx.props new file mode 100644 index 0000000000..c410c1964f --- /dev/null +++ b/NBitcoin.Miniscript/netfx.props @@ -0,0 +1,27 @@ + + + + + + true + + + /Library/Frameworks/Mono.framework/Versions/Current/lib/mono + /usr/lib/mono + /usr/local/lib/mono + + + $(BaseFrameworkPathOverrideForMono)/4.5-api + $(BaseFrameworkPathOverrideForMono)/4.5.1-api + $(BaseFrameworkPathOverrideForMono)/4.5.2-api + $(BaseFrameworkPathOverrideForMono)/4.6-api + $(BaseFrameworkPathOverrideForMono)/4.6.1-api + $(BaseFrameworkPathOverrideForMono)/4.6.2-api + $(BaseFrameworkPathOverrideForMono)/4.7-api + $(BaseFrameworkPathOverrideForMono)/4.7.1-api + true + + + $(FrameworkPathOverride)/Facades;$(AssemblySearchPaths) + + From 3c3227e540fc555440a47708d9d25b1b4369b5c2 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Wed, 3 Apr 2019 21:50:41 +0900 Subject: [PATCH 09/40] Update csproj a bit --- NBitcoin/NBitcoin.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NBitcoin/NBitcoin.csproj b/NBitcoin/NBitcoin.csproj index c8898c21f4..6ddee08112 100644 --- a/NBitcoin/NBitcoin.csproj +++ b/NBitcoin/NBitcoin.csproj @@ -20,7 +20,7 @@ true - netcoreapp2.1;netstandard2.0 + net461;net452;netstandard1.3;netstandard1.1;netcoreapp2.1;netstandard2.0 netstandard2.0 $(TargetFrameworkOverride) 1591;1573;1572;1584;1570;3021 @@ -96,7 +96,7 @@ - true + true From d43bf48d1e90d17829d44a0708d3f44769094cb3 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Wed, 3 Apr 2019 22:13:39 +0900 Subject: [PATCH 10/40] Include Miniscript to solution --- ...Tests.fsproj => NBitcoin.Miniscript.Tests.fsproj} | 0 ...{Miniscript.fsproj => NBitcoin.Miniscript.fsproj} | 0 NBitcoin.sln | 12 ++++++++++++ 3 files changed, 12 insertions(+) rename NBitcoin.Miniscript.Tests/{FNBitcoin.Tests.fsproj => NBitcoin.Miniscript.Tests.fsproj} (100%) rename NBitcoin.Miniscript/{Miniscript.fsproj => NBitcoin.Miniscript.fsproj} (100%) diff --git a/NBitcoin.Miniscript.Tests/FNBitcoin.Tests.fsproj b/NBitcoin.Miniscript.Tests/NBitcoin.Miniscript.Tests.fsproj similarity index 100% rename from NBitcoin.Miniscript.Tests/FNBitcoin.Tests.fsproj rename to NBitcoin.Miniscript.Tests/NBitcoin.Miniscript.Tests.fsproj diff --git a/NBitcoin.Miniscript/Miniscript.fsproj b/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj similarity index 100% rename from NBitcoin.Miniscript/Miniscript.fsproj rename to NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj diff --git a/NBitcoin.sln b/NBitcoin.sln index 65922f5a63..73fbfdd3af 100644 --- a/NBitcoin.sln +++ b/NBitcoin.sln @@ -20,6 +20,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NBitcoin.TestFramework", "N EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NBitcoin.Bench", "NBitcoin.Bench\NBitcoin.Bench.csproj", "{153FEA8A-FA98-4ADD-8570-5FD88631C489}" EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "NBitcoin.Miniscript.Tests", "NBitcoin.Miniscript.Tests\NBitcoin.Miniscript.Tests.fsproj", "{89E2F073-7558-4F48-AF7C-05C9EEC850A2}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "NBitcoin.Miniscript", "NBitcoin.Miniscript\NBitcoin.Miniscript.fsproj", "{F00D2B30-F9DA-40AC-AC4C-B1765A2FD7BA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -46,6 +50,14 @@ Global {153FEA8A-FA98-4ADD-8570-5FD88631C489}.Debug|Any CPU.Build.0 = Debug|Any CPU {153FEA8A-FA98-4ADD-8570-5FD88631C489}.Release|Any CPU.ActiveCfg = Release|Any CPU {153FEA8A-FA98-4ADD-8570-5FD88631C489}.Release|Any CPU.Build.0 = Release|Any CPU + {89E2F073-7558-4F48-AF7C-05C9EEC850A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {89E2F073-7558-4F48-AF7C-05C9EEC850A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {89E2F073-7558-4F48-AF7C-05C9EEC850A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {89E2F073-7558-4F48-AF7C-05C9EEC850A2}.Release|Any CPU.Build.0 = Release|Any CPU + {F00D2B30-F9DA-40AC-AC4C-B1765A2FD7BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F00D2B30-F9DA-40AC-AC4C-B1765A2FD7BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F00D2B30-F9DA-40AC-AC4C-B1765A2FD7BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F00D2B30-F9DA-40AC-AC4C-B1765A2FD7BA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 50f9de34b2ee53e2762500584592404b8e31adab Mon Sep 17 00:00:00 2001 From: joemphilips Date: Wed, 3 Apr 2019 22:56:53 +0900 Subject: [PATCH 11/40] Fix compile error in Miniscript.Tests --- NBitcoin.Miniscript.Tests/Contract.fs | 1 - NBitcoin.Miniscript.Tests/Generators/NBitcoin.fs | 2 +- NBitcoin.Miniscript.Tests/Generators/Policy.fs | 2 +- NBitcoin.Miniscript.Tests/MiniScriptCompilerTests.fs | 4 ++-- .../MiniScriptDecompilerTests.fs | 12 ++++++------ NBitcoin.Miniscript.Tests/MiniScriptParserTests.fs | 3 ++- .../NBitcoin.Miniscript.Tests.fsproj | 3 ++- 7 files changed, 14 insertions(+), 13 deletions(-) diff --git a/NBitcoin.Miniscript.Tests/Contract.fs b/NBitcoin.Miniscript.Tests/Contract.fs index c397c895b9..e6348406d7 100644 --- a/NBitcoin.Miniscript.Tests/Contract.fs +++ b/NBitcoin.Miniscript.Tests/Contract.fs @@ -1,7 +1,6 @@ module ContractTests open Expecto -open FNBitcoin.Contract open FsCheck open NBitcoin (* diff --git a/NBitcoin.Miniscript.Tests/Generators/NBitcoin.fs b/NBitcoin.Miniscript.Tests/Generators/NBitcoin.fs index e1bf9b9e68..2ce4de08f2 100644 --- a/NBitcoin.Miniscript.Tests/Generators/NBitcoin.fs +++ b/NBitcoin.Miniscript.Tests/Generators/NBitcoin.fs @@ -8,4 +8,4 @@ module internal NBitcoin = let k = NBitcoin.Key() // prioritize speed for randomness Gen.constant (k) |> Gen.map (fun k -> k.PubKey) - let uint256Gen = bytesOfNGen 32 |> Gen.map uint256 + let uint256Gen = bytesOfNGen 32 |> Gen.map NBitcoin.uint256 diff --git a/NBitcoin.Miniscript.Tests/Generators/Policy.fs b/NBitcoin.Miniscript.Tests/Generators/Policy.fs index 14d35b94e1..314d856f7e 100644 --- a/NBitcoin.Miniscript.Tests/Generators/Policy.fs +++ b/NBitcoin.Miniscript.Tests/Generators/Policy.fs @@ -17,7 +17,7 @@ module internal Policy = Gen.map (fun (num, pks) -> Multi(num, pks)) multiContentsGen) (2, Gen.map Hash uint256Gen) - (2, Gen.map Time Arb.generate) ] + (2, Arb.generate |> Gen.map NBitcoin.LockTime |> Gen.map(Time)) ] let policy = let rec policy' s = diff --git a/NBitcoin.Miniscript.Tests/MiniScriptCompilerTests.fs b/NBitcoin.Miniscript.Tests/MiniScriptCompilerTests.fs index 6cbda60e0a..ccca4484d6 100644 --- a/NBitcoin.Miniscript.Tests/MiniScriptCompilerTests.fs +++ b/NBitcoin.Miniscript.Tests/MiniScriptCompilerTests.fs @@ -27,12 +27,12 @@ let tests = testList "miniscript compiler" [ testPropertyWithConfig config "should compile arbitrary input" <| fun (p : Policy) -> let node = CompiledNode.fromPolicy (p) - let t = node.compile() + let t = node.Compile() Expect.isOk t testPropertyWithConfig config "Should compile arbitrary input to actual bitcoin script" <| fun (p: Policy) -> - let m = match CompiledNode.fromPolicy(p).compile() with + let m = match CompiledNode.fromPolicy(p).Compile() with | Ok miniscript -> miniscript | Result.Error e -> failwith e Expect.isNotNull (m.ToScript()) "script was empty" diff --git a/NBitcoin.Miniscript.Tests/MiniScriptDecompilerTests.fs b/NBitcoin.Miniscript.Tests/MiniScriptDecompilerTests.fs index 844fe6821a..8f8b3d3d1b 100644 --- a/NBitcoin.Miniscript.Tests/MiniScriptDecompilerTests.fs +++ b/NBitcoin.Miniscript.Tests/MiniScriptDecompilerTests.fs @@ -44,7 +44,7 @@ let tests = E.CheckSig(pk), W.Time(!> 1u)) ) let sc = boolAndWE.ToScript() - let res = FNBitcoin.MiniScriptDecompiler.parseScript sc + let res = Miniscript.Decompiler.parseScript sc checkParseResult res boolAndWE testCase "case2" <| fun _ -> @@ -53,7 +53,7 @@ let tests = let pk2 = PubKey(keys.[1]) let delayedOrV = VTree(V.DelayedOr(Q.Pubkey(pk), Q.Pubkey(pk2))) let sc = delayedOrV.ToScript() - let res = FNBitcoin.MiniScriptDecompiler.parseScript sc + let res = Miniscript.Decompiler.parseScript sc checkParseResult res delayedOrV testCase "Should pass the testcase in rust-miniscript" <| fun _ -> @@ -174,7 +174,7 @@ let roundTripFromMiniScript (m: MiniScript) = Expect.equal m2 m "failed" let roundtrip p = - let m = CompiledNode.fromPolicy(p).compileUnsafe() + let m = CompiledNode.fromPolicy(p).CompileUnsafe() roundTripFromMiniScript m let hash = uint256.Parse("59141e52303a755307114c2a5e6823010b3f1d586216742f396d4b06106e222c") @@ -187,12 +187,12 @@ let tests2 = /// specifically, the case is when there is a nested `and`. /// `and(and(1, 2), 3)` is semantically equal to `and(1, and(2, 3))` /// But the assertion will fail, so leave it untested. - // TODO: (Ideally, we should have `CustomComparison` for AST and Policy) + // TODO: (Ideally, we should have stomComparison` for AST and Policy) ptestPropertyWithConfig config "Every possible MiniScript" <| fun (p: Policy) -> roundtrip p testCase "Case found by property tests: 1" <| fun _ -> let input = Policy.Or(Key(keysList.[0]), Policy.And(Policy.Time(!> 2u), Policy.Time(!> 1u))) - let m = CompiledNode.fromPolicy(input).compileUnsafe() + let m = CompiledNode.fromPolicy(input).CompileUnsafe() let sc = m.ToScript() let customParser = TokenParser.pT let ops = sc.ToOps() |> Seq.toArray @@ -206,7 +206,7 @@ let tests2 = testCase "Case found by property tests: 3" <| fun _ -> let input = MiniScript.fromASTUnsafe( - TTree(T.And(V.Time(1u), T.Time(1u)))) + TTree(T.And(V.Time(LockTime(1u)), T.Time(LockTime(1u))))) roundTripFromMiniScript input testCase "Case found by property tests: 4" <| fun _ -> diff --git a/NBitcoin.Miniscript.Tests/MiniScriptParserTests.fs b/NBitcoin.Miniscript.Tests/MiniScriptParserTests.fs index b4eb12b18b..b3317d099e 100644 --- a/NBitcoin.Miniscript.Tests/MiniScriptParserTests.fs +++ b/NBitcoin.Miniscript.Tests/MiniScriptParserTests.fs @@ -5,6 +5,7 @@ open Expecto open Expecto.Logging open Expecto.Logging.Message open NBitcoin.Miniscript.Tests.Generators +open NBitcoin.Miniscript.Utils let logger = Log.create "MiniscriptParser" let pk1Str = @@ -45,7 +46,7 @@ let tests = (Key(pk1), Or (Multi(1u, [| pk2; pk3 |]), - AsymmetricOr(Key(pk1), Time(1000u)))) + AsymmetricOr(Key(pk1), Time(!> 1000u)))) let actual = testdata1.ToString() let expected = sprintf diff --git a/NBitcoin.Miniscript.Tests/NBitcoin.Miniscript.Tests.fsproj b/NBitcoin.Miniscript.Tests/NBitcoin.Miniscript.Tests.fsproj index 74927e02b7..073fda347b 100644 --- a/NBitcoin.Miniscript.Tests/NBitcoin.Miniscript.Tests.fsproj +++ b/NBitcoin.Miniscript.Tests/NBitcoin.Miniscript.Tests.fsproj @@ -20,10 +20,11 @@ - + + From 2d6a63d9e349c212601d04ebfcf7dd1d64529cb5 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Thu, 4 Apr 2019 13:23:57 +0900 Subject: [PATCH 12/40] Fix every compile warning when testing Miniscript --- .../MiniScriptDecompilerTests.fs | 164 ++++++++++-------- .../NBitcoin.Miniscript.Tests.fsproj | 2 +- NBitcoin.Miniscript/MiniscriptParser.fs | 2 +- NBitcoin.Miniscript/Utils/Lib.fs | 4 +- 4 files changed, 91 insertions(+), 81 deletions(-) diff --git a/NBitcoin.Miniscript.Tests/MiniScriptDecompilerTests.fs b/NBitcoin.Miniscript.Tests/MiniScriptDecompilerTests.fs index 8f8b3d3d1b..9e55f54461 100644 --- a/NBitcoin.Miniscript.Tests/MiniScriptDecompilerTests.fs +++ b/NBitcoin.Miniscript.Tests/MiniScriptDecompilerTests.fs @@ -39,10 +39,7 @@ let tests = testList "Decompiler" [ testCase "case1" <| fun _ -> let pk = PubKey(keys.[0]) let pk2 = PubKey(keys.[1]) - let boolAndWE = ETree( - E.ParallelAnd( - E.CheckSig(pk), W.Time(!> 1u)) - ) + let boolAndWE = ETree(E.ParallelAnd(E.CheckSig(pk), W.Time(!> 1u))) let sc = boolAndWE.ToScript() let res = Miniscript.Decompiler.parseScript sc checkParseResult res boolAndWE @@ -99,21 +96,25 @@ let tests = keys.[4]) roundtrip r2 s2 - let r3_partial = - MiniScript.fromAST(TTree(T.And - (V.CheckMultiSig - (2u, - keysList.[2..3]), - T.Time(!> 10000u)) - )) - - let policy3_2 = + let r3Partial = + MiniScript.fromAST( + TTree( + T.And( + V.CheckMultiSig( + 2u, + keysList.[2..3]), + T.Time(!> 10000u) + ) + ) + ) + + let policy32 = sprintf "2 %s %s 2 OP_CHECKMULTISIGVERIFY" keys.[2] keys.[3] - let s3_partial = Script(sprintf "%s 1027 OP_CSV" policy3_2) - roundtrip r3_partial s3_partial + let s3Partial = Script(sprintf "%s 1027 OP_CSV" policy32) + roundtrip r3Partial s3Partial // Liquid policy let r3 = @@ -129,12 +130,12 @@ let tests = keysList.[2..3]), T.Time (!> 10000u))))) - let policy3_1 = + let policy31 = sprintf "2 %s %s 2 OP_CHECKMULTISIG" keys.[0] keys.[1] let tmp = sprintf "%s OP_IFDUP OP_NOTIF %s 1027 OP_CSV OP_ENDIF" - policy3_1 policy3_2 + policy31 policy32 let s3 = Script(tmp) roundtrip r3 s3 @@ -146,13 +147,15 @@ let tests = roundtrip r4 s4 let r5 = MiniScript.fromAST (TTree( - T.SwitchOrV( - V.CheckSig(keysList.[0]), - V.And( - V.CheckSig(keysList.[1]), - V.CheckSig(keysList.[2]) - ) - ))) + T.SwitchOrV( + V.CheckSig(keysList.[0]), + V.And( + V.CheckSig(keysList.[1]), + V.CheckSig(keysList.[2]) + ) + ) + ) + ) let scriptStr = sprintf "OP_IF %s OP_CHECKSIGVERIFY OP_ELSE %s OP_CHECKSIGVERIFY %s OP_CHECKSIGVERIFY OP_ENDIF 1" keys.[0] keys.[1] keys.[2] @@ -188,8 +191,7 @@ let tests2 = /// `and(and(1, 2), 3)` is semantically equal to `and(1, and(2, 3))` /// But the assertion will fail, so leave it untested. // TODO: (Ideally, we should have stomComparison` for AST and Policy) - ptestPropertyWithConfig config "Every possible MiniScript" <| fun (p: Policy) -> - roundtrip p + ptestPropertyWithConfig config "Every possible MiniScript" <| roundtrip testCase "Case found by property tests: 1" <| fun _ -> let input = Policy.Or(Key(keysList.[0]), Policy.And(Policy.Time(!> 2u), Policy.Time(!> 1u))) let m = CompiledNode.fromPolicy(input).CompileUnsafe() @@ -205,74 +207,81 @@ let tests2 = roundTripFromMiniScript input testCase "Case found by property tests: 3" <| fun _ -> - let input = MiniScript.fromASTUnsafe( - TTree(T.And(V.Time(LockTime(1u)), T.Time(LockTime(1u))))) + let input = MiniScript.fromASTUnsafe(TTree(T.And(V.Time(LockTime(1u)), T.Time(LockTime(1u))))) roundTripFromMiniScript input testCase "Case found by property tests: 4" <| fun _ -> let input = MiniScript.fromASTUnsafe(TTree( - T.CastE( - E.Threshold( - 1u, - E.CheckSig(keysList.[0]), - [| W.CheckSig(keysList.[0]) |] - ) - ) - ) - ) + T.CastE( + E.Threshold( + 1u, + E.CheckSig(keysList.[0]), + [| W.CheckSig(keysList.[0]) |] + ) + ) + ) + ) roundTripFromMiniScript input testCase "Case found by property tests: 5" <| fun _ -> let input = MiniScript.fromASTUnsafe(TTree( - T.CastE( - E.Likely( - F.Threshold( - 1u, - E.CheckSig(keysList.[0]), - [| W.CheckSig(keysList.[0]) |] - ) - ) - )) - ) + T.CastE( + E.Likely( + F.Threshold( + 1u, + E.CheckSig(keysList.[0]), + [| W.CheckSig(keysList.[0]) |] + ) + ) + ) + ) + ) roundTripFromMiniScript input testCase "Case found by property tests: 6" <| fun _ -> - let input = MiniScript.fromASTUnsafe(TTree( - T.And( - V.CheckMultiSig(1u, longKeysList), - T.Time(!> 1u) - ))) + let input = MiniScript.fromASTUnsafe(TTree(T.And( + V.CheckMultiSig(1u, longKeysList), + T.Time(!> 1u) + ))) roundTripFromMiniScript input testCase "Case found by property tests: 7" <| fun _ -> let input = MiniScript.fromASTUnsafe(TTree( - T.CastE( - E.SwitchOrRight(E.Time(!> 1u), F.Time(!> 1u)) - ))) + T.CastE( + E.SwitchOrRight(E.Time(!> 1u), F.Time(!> 1u)) + ) + ) + ) roundTripFromMiniScript input - testCase "Case found by property tests: 8" <| fun _ -> let input = MiniScript.fromASTUnsafe(TTree( - T.And( - V.Time(!> 2u), - T.CastE(E.Threshold( - 1u, - E.Time(!> 4u), - [|W.Time(!> 5u)|] - )) - ) - )) + T.And( + V.Time(!> 2u), + T.CastE( + E.Threshold( + 1u, + E.Time(!> 4u), + [|W.Time(!> 5u)|] + )) + ) + )) roundTripFromMiniScript input testCase "Case found by property tests: 9" <| fun _ -> let input = MiniScript.fromASTUnsafe(TTree( - T.CastE( - E.Likely(F.And(V.Time(!> 2u), F.Time(!> 2u))) - ) - )) + T.CastE( + E.Likely(F.And(V.Time(!> 2u), F.Time(!> 2u))) + ) + )) roundTripFromMiniScript input ptestCase "Can NOT handle nested And" <| fun _ -> let input = MiniScript.fromASTUnsafe(TTree( - T.And(V.Time(!> 3u), T.And(V.Time(!> 4u), T.Time(!> 4u))) - )) + T.And( + V.Time(!> 3u), + T.And( + V.Time(!> 4u), + T.Time(!> 4u)) + ) + ) + ) roundTripFromMiniScript input ] @@ -298,12 +307,13 @@ let deserializationTestWithParser = let parser = TokenParser.pE roundtripParserAndAST parser input testCase "Case found by property tests: 5_3" <| fun _ -> - let input = - FTree(F.Threshold( - 1u, - E.CheckSig(keysList.[0]), - [| W.CheckSig(keysList.[0]) |] - )) + let input = FTree( + F.Threshold( + 1u, + E.CheckSig(keysList.[0]), + [| W.CheckSig(keysList.[0]) |] + ) + ) let parser = TokenParser.pF roundtripParserAndAST parser input testCase "Case found by property tests: 6_1" <| fun _ -> diff --git a/NBitcoin.Miniscript.Tests/NBitcoin.Miniscript.Tests.fsproj b/NBitcoin.Miniscript.Tests/NBitcoin.Miniscript.Tests.fsproj index 073fda347b..b6f3b5bba1 100644 --- a/NBitcoin.Miniscript.Tests/NBitcoin.Miniscript.Tests.fsproj +++ b/NBitcoin.Miniscript.Tests/NBitcoin.Miniscript.Tests.fsproj @@ -23,7 +23,7 @@ - + diff --git a/NBitcoin.Miniscript/MiniscriptParser.fs b/NBitcoin.Miniscript/MiniscriptParser.fs index 1bbda847c7..cbadbcb57f 100644 --- a/NBitcoin.Miniscript/MiniscriptParser.fs +++ b/NBitcoin.Miniscript/MiniscriptParser.fs @@ -23,7 +23,7 @@ module MiniscriptParser = |> Seq.reduce (fun a b -> sprintf "%s,%s" a b) |> sprintf "multi(%d,%s)" m | Hash h -> sprintf "hash(%s)" (string (h.ToString())) - | Time t -> sprintf "time(%s)" (string (t.ToString())) + | Time t -> sprintf "time(%d)" (t.Value) | Threshold(m, plist) -> plist |> Array.map (fun p -> p.ToString()) diff --git a/NBitcoin.Miniscript/Utils/Lib.fs b/NBitcoin.Miniscript/Utils/Lib.fs index 930f0f2185..c35b7047bd 100644 --- a/NBitcoin.Miniscript/Utils/Lib.fs +++ b/NBitcoin.Miniscript/Utils/Lib.fs @@ -4,7 +4,7 @@ namespace NBitcoin.Miniscript.Utils module Utils = let inline (!>) (x:^a) : ^b = ((^a or ^b) : (static member op_Implicit : ^a -> ^b )x ) - let resultFolder (acc : Result<'a seq, 'b>) + let resultFolder (acc : Result<'a seq, 'c>) (item : Result<'a, 'c>) = match acc, item with | Ok x, Ok y -> @@ -14,7 +14,7 @@ module Utils = }) | Error x, Ok y -> Error x | Ok x, Error y -> Error y - | Error x, Error y -> Error(y.ToString() + x.ToString()) + | Error x, Error y -> Error (x) module List = From d6b8c42279504ed5404b6e37d09436eb790423cb Mon Sep 17 00:00:00 2001 From: joemphilips Date: Thu, 4 Apr 2019 14:35:02 +0900 Subject: [PATCH 13/40] Fix bug in all Miniscript Test --- NBitcoin.Miniscript.Tests/MiniScriptCompilerTests.fs | 4 +--- NBitcoin.Miniscript/MiniscriptCompiler.fs | 2 +- NBitcoin.Miniscript/Utils/Lib.fs | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/NBitcoin.Miniscript.Tests/MiniScriptCompilerTests.fs b/NBitcoin.Miniscript.Tests/MiniScriptCompilerTests.fs index ccca4484d6..fa88f168d8 100644 --- a/NBitcoin.Miniscript.Tests/MiniScriptCompilerTests.fs +++ b/NBitcoin.Miniscript.Tests/MiniScriptCompilerTests.fs @@ -32,8 +32,6 @@ let tests = testPropertyWithConfig config "Should compile arbitrary input to actual bitcoin script" <| fun (p: Policy) -> - let m = match CompiledNode.fromPolicy(p).Compile() with - | Ok miniscript -> miniscript - | Result.Error e -> failwith e + let m = CompiledNode.fromPolicy(p).CompileUnsafe() Expect.isNotNull (m.ToScript()) "script was empty" ] diff --git a/NBitcoin.Miniscript/MiniscriptCompiler.fs b/NBitcoin.Miniscript/MiniscriptCompiler.fs index c526b7b30d..51a0c77508 100644 --- a/NBitcoin.Miniscript/MiniscriptCompiler.fs +++ b/NBitcoin.Miniscript/MiniscriptCompiler.fs @@ -539,7 +539,7 @@ module Compiler = let re_cond_par = bestE (r, (p_sat * lweight), (p_sat * lweight)) let lv = bestV (l, (p_sat * lweight), 0.0) let rv = bestV (r, (p_sat * rweight), 0.0) - let lf = bestV (l, (p_sat * lweight), 0.0) + let lf = bestF (l, (p_sat * lweight), 0.0) let rf = bestF (r, (p_sat * rweight), 0.0) let maybelq = bestQ (l, (p_sat * lweight), 0.0) let mayberq = bestQ (r, (p_sat * rweight), 0.0) diff --git a/NBitcoin.Miniscript/Utils/Lib.fs b/NBitcoin.Miniscript/Utils/Lib.fs index c35b7047bd..751962f49d 100644 --- a/NBitcoin.Miniscript/Utils/Lib.fs +++ b/NBitcoin.Miniscript/Utils/Lib.fs @@ -12,11 +12,11 @@ module Utils = yield! x yield y }) - | Error x, Ok y -> Error x | Ok x, Error y -> Error y - | Error x, Error y -> Error (x) + | Error x, _ -> Error x + [] module List = let rec traverseResult f list = let (>>=) x f = Result.bind f x From 0684c0fcdcc8b3fdc9da000b74e0fdf2f3d00be6 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Thu, 4 Apr 2019 22:22:52 +0900 Subject: [PATCH 14/40] Refactor Miniscript * Rename `Policy` -> `AbstractPolicy` * Model `ProviderSet` member as optional * Reorganize Miniscript as facade module. --- NBitcoin.Miniscript.Tests/Generators/Lib.fs | 30 ++-- .../Generators/Policy.fs | 6 +- .../MiniScriptCompilerTests.fs | 8 +- .../MiniScriptDecompilerTests.fs | 14 +- .../MiniScriptParserTests.fs | 10 +- .../NBitcoin.Miniscript.Tests.fsproj | 2 +- NBitcoin.Miniscript.Tests/SatisfyTests.fs | 14 ++ NBitcoin.Miniscript.Tests/Tests.fs | 2 - NBitcoin.Miniscript/AssemblyInfo.fs | 1 + NBitcoin.Miniscript/Miniscript.fs | 11 ++ NBitcoin.Miniscript/MiniscriptCompiler.fs | 25 ++- NBitcoin.Miniscript/MiniscriptParser.fs | 17 +- .../NBitcoin.Miniscript.fsproj | 6 +- NBitcoin.Miniscript/Satisfy.fs | 164 ++++++++++-------- 14 files changed, 182 insertions(+), 128 deletions(-) create mode 100644 NBitcoin.Miniscript.Tests/SatisfyTests.fs delete mode 100644 NBitcoin.Miniscript.Tests/Tests.fs diff --git a/NBitcoin.Miniscript.Tests/Generators/Lib.fs b/NBitcoin.Miniscript.Tests/Generators/Lib.fs index 1b960fb2e8..2c9b781b5f 100644 --- a/NBitcoin.Miniscript.Tests/Generators/Lib.fs +++ b/NBitcoin.Miniscript.Tests/Generators/Lib.fs @@ -5,34 +5,34 @@ open NBitcoin.Miniscript.MiniscriptParser open NBitcoin.Miniscript.Tests.Generators.Policy type Generators = - static member Policy() : Arbitrary = // policy |> Arb.fromGen - { new Arbitrary() with + static member Policy() : Arbitrary = // policy |> Arb.fromGen + { new Arbitrary() with override this.Generator = policy // TODO: This shrinker is far from ideal // 1. nested shrinking does not work well // 2. Must use Seq instead of List - override this.Shrinker(p: Policy) = + override this.Shrinker(p: AbstractPolicy) = let rec shrinkPolicy p = match p with | Key k -> [] - | Multi(m, pks) -> [Multi(1u, pks.[0..0]); Policy.Key pks.[0]] - | Policy.Hash h -> [] - | Policy.Time t -> [] - | Policy.Threshold (k, ps) -> - let shrinkThres (k, (ps: Policy[])) = + | Multi(m, pks) -> [Multi(1u, pks.[0..0]); AbstractPolicy.Key pks.[0]] + | AbstractPolicy.Hash h -> [] + | AbstractPolicy.Time t -> [] + | AbstractPolicy.Threshold (k, ps) -> + let shrinkThres (k, (ps: AbstractPolicy[])) = let k2 = if k = 1u then k else k - 1u let ps2 = Arb.shrink(ps) - ps2 |> Seq.toList |> List.map(fun p -> Policy.Threshold(k2, p)) + ps2 |> Seq.toList |> List.map(fun p -> AbstractPolicy.Threshold(k2, p)) let subexpr = ps |> Array.toList if ps.Length = 1 then subexpr else shrinkThres(k, ps) - | Policy.And(p1, p2) -> - let shrinkedAnd = shrinkNested Policy.And p1 p2 + | AbstractPolicy.And(p1, p2) -> + let shrinkedAnd = shrinkNested AbstractPolicy.And p1 p2 List.concat[shrinkedAnd; [p1; p2;]] - | Policy.Or(p1, p2) -> - let shrinkedOr = shrinkNested Policy.Or p1 p2 + | AbstractPolicy.Or(p1, p2) -> + let shrinkedOr = shrinkNested AbstractPolicy.Or p1 p2 List.concat[shrinkedOr; [p1; p2;]] - | Policy.AsymmetricOr(p1, p2) -> - let shrinkedAOr = shrinkNested Policy.AsymmetricOr p1 p2 + | AbstractPolicy.AsymmetricOr(p1, p2) -> + let shrinkedAOr = shrinkNested AbstractPolicy.AsymmetricOr p1 p2 List.concat[shrinkedAOr; [p1; p2;]] /// Helper for shrinking nested types diff --git a/NBitcoin.Miniscript.Tests/Generators/Policy.fs b/NBitcoin.Miniscript.Tests/Generators/Policy.fs index 314d856f7e..7763d20705 100644 --- a/NBitcoin.Miniscript.Tests/Generators/Policy.fs +++ b/NBitcoin.Miniscript.Tests/Generators/Policy.fs @@ -9,8 +9,8 @@ module internal Policy = let! subN = Gen.choose ((int n), 20) let! subs = Gen.arrayOfLength subN pubKeyGen return (n, subs) } - - let nonRecursivePolicyGen : Gen = + + let nonRecursivePolicyGen : Gen = Gen.frequency [ (2, Gen.map Key pubKeyGen) (1, @@ -29,7 +29,7 @@ module internal Policy = (3, recursivePolicyGen subPolicyGen) ] | _ -> invalidArg "s" "Only positive arguments are allowed!" - and recursivePolicyGen (subPolicyGen : Gen) = + and recursivePolicyGen (subPolicyGen : Gen) = Gen.oneof [ Gen.map (fun (t, ps) -> Threshold(t, ps)) (thresholdContentsGen subPolicyGen) diff --git a/NBitcoin.Miniscript.Tests/MiniScriptCompilerTests.fs b/NBitcoin.Miniscript.Tests/MiniScriptCompilerTests.fs index fa88f168d8..c22b46d4f6 100644 --- a/NBitcoin.Miniscript.Tests/MiniScriptCompilerTests.fs +++ b/NBitcoin.Miniscript.Tests/MiniScriptCompilerTests.fs @@ -25,13 +25,13 @@ let config = [] let tests = testList "miniscript compiler" [ testPropertyWithConfig config - "should compile arbitrary input" <| fun (p : Policy) -> + "should compile arbitrary input" <| fun (p : AbstractPolicy) -> let node = CompiledNode.fromPolicy (p) let t = node.Compile() - Expect.isOk t + Expect.isOk (t.CastT()) testPropertyWithConfig config - "Should compile arbitrary input to actual bitcoin script" <| fun (p: Policy) -> - let m = CompiledNode.fromPolicy(p).CompileUnsafe() + "Should compile arbitrary input to actual bitcoin script" <| fun (p: AbstractPolicy) -> + let m = CompiledNode.fromPolicy(p).Compile() Expect.isNotNull (m.ToScript()) "script was empty" ] diff --git a/NBitcoin.Miniscript.Tests/MiniScriptDecompilerTests.fs b/NBitcoin.Miniscript.Tests/MiniScriptDecompilerTests.fs index 9e55f54461..476bb98d81 100644 --- a/NBitcoin.Miniscript.Tests/MiniScriptDecompilerTests.fs +++ b/NBitcoin.Miniscript.Tests/MiniScriptDecompilerTests.fs @@ -177,8 +177,8 @@ let roundTripFromMiniScript (m: MiniScript) = Expect.equal m2 m "failed" let roundtrip p = - let m = CompiledNode.fromPolicy(p).CompileUnsafe() - roundTripFromMiniScript m + let m = CompiledNode.fromPolicy(p).Compile() + roundTripFromMiniScript (MiniScript.fromASTUnsafe(m)) let hash = uint256.Parse("59141e52303a755307114c2a5e6823010b3f1d586216742f396d4b06106e222c") @@ -193,8 +193,14 @@ let tests2 = // TODO: (Ideally, we should have stomComparison` for AST and Policy) ptestPropertyWithConfig config "Every possible MiniScript" <| roundtrip testCase "Case found by property tests: 1" <| fun _ -> - let input = Policy.Or(Key(keysList.[0]), Policy.And(Policy.Time(!> 2u), Policy.Time(!> 1u))) - let m = CompiledNode.fromPolicy(input).CompileUnsafe() + let input = AbstractPolicy.Or( + Key(keysList.[0]), + AbstractPolicy.And( + AbstractPolicy.Time(!> 2u), + AbstractPolicy.Time(!> 1u) + ) + ) + let m = CompiledNode.fromPolicy(input).Compile() let sc = m.ToScript() let customParser = TokenParser.pT let ops = sc.ToOps() |> Seq.toArray diff --git a/NBitcoin.Miniscript.Tests/MiniScriptParserTests.fs b/NBitcoin.Miniscript.Tests/MiniScriptParserTests.fs index b3317d099e..e35513c9be 100644 --- a/NBitcoin.Miniscript.Tests/MiniScriptParserTests.fs +++ b/NBitcoin.Miniscript.Tests/MiniScriptParserTests.fs @@ -17,7 +17,7 @@ let pk3Str = let check = function - | Policy p -> () + | AbstractPolicy p -> () | _ -> failwith "Failed to parse policy" let config = @@ -41,7 +41,7 @@ let tests = let pk1, pk2, pk3 = NBitcoin.PubKey(pk1Str), NBitcoin.PubKey(pk2Str), NBitcoin.PubKey(pk3Str) - let testdata1 : Policy = + let testdata1 : AbstractPolicy = And (Key(pk1), Or @@ -90,11 +90,11 @@ let tests = let data2 = match data with - | Policy p -> p.ToString() + | AbstractPolicy p -> p.ToString() | _ -> failwith "Failed to parse policy" Expect.equal data data2 "Could not parse symmetrically" - testPropertyWithConfig config "Should convert <-> " <| fun (p : Policy) -> + testPropertyWithConfig config "Should convert <-> " <| fun (p : AbstractPolicy) -> match p.ToString() with - | Policy p2 -> Expect.equal p p2 + | AbstractPolicy p2 -> Expect.equal p p2 | _ -> failwith "Failed to convert bidirectionally" ] diff --git a/NBitcoin.Miniscript.Tests/NBitcoin.Miniscript.Tests.fsproj b/NBitcoin.Miniscript.Tests/NBitcoin.Miniscript.Tests.fsproj index b6f3b5bba1..53a03e1384 100644 --- a/NBitcoin.Miniscript.Tests/NBitcoin.Miniscript.Tests.fsproj +++ b/NBitcoin.Miniscript.Tests/NBitcoin.Miniscript.Tests.fsproj @@ -13,7 +13,7 @@ - + diff --git a/NBitcoin.Miniscript.Tests/SatisfyTests.fs b/NBitcoin.Miniscript.Tests/SatisfyTests.fs new file mode 100644 index 0000000000..e510ed32e1 --- /dev/null +++ b/NBitcoin.Miniscript.Tests/SatisfyTests.fs @@ -0,0 +1,14 @@ +module SatisfyTests + +open Expecto +open NBitcoin.Miniscript + +[] +let tests = + testList "SatisfyTests" [ + testCase "case 1" <| fun _ -> + let key = NBitcoin.Key() + let scriptStr = sprintf "and(pk(%s), time(%d))" (key.PubKey.ToString()) 10000u + let ms = MiniScript.parseUnsafe scriptStr + () + ] diff --git a/NBitcoin.Miniscript.Tests/Tests.fs b/NBitcoin.Miniscript.Tests/Tests.fs deleted file mode 100644 index 1673813524..0000000000 --- a/NBitcoin.Miniscript.Tests/Tests.fs +++ /dev/null @@ -1,2 +0,0 @@ -module Tests - diff --git a/NBitcoin.Miniscript/AssemblyInfo.fs b/NBitcoin.Miniscript/AssemblyInfo.fs index 6a8ef90560..bdd66da60e 100644 --- a/NBitcoin.Miniscript/AssemblyInfo.fs +++ b/NBitcoin.Miniscript/AssemblyInfo.fs @@ -11,6 +11,7 @@ open System.Reflection [] [] [] +[] [] do () diff --git a/NBitcoin.Miniscript/Miniscript.fs b/NBitcoin.Miniscript/Miniscript.fs index 6a685a0b37..02c7c742bd 100644 --- a/NBitcoin.Miniscript/Miniscript.fs +++ b/NBitcoin.Miniscript/Miniscript.fs @@ -2,6 +2,7 @@ namespace NBitcoin.Miniscript open NBitcoin.Miniscript.AST open NBitcoin.Miniscript.Decompiler +open NBitcoin.Miniscript.Compiler open NBitcoin /// wrapper for top-level AST @@ -17,6 +18,16 @@ module MiniScript = match fromAST t with | Ok t -> t | Error e -> failwith e + + let parse (s: string) = + match s with + | AbstractPolicy p -> (CompiledNode.FromPolicy p).Compile() |> fromAST + | _ -> Error("failed to parse String policy") + + let parseUnsafe (s: string) = + match parse s with + | Ok m -> m + | Error e -> failwith e let toAST (m : MiniScript) = match m with diff --git a/NBitcoin.Miniscript/MiniscriptCompiler.fs b/NBitcoin.Miniscript/MiniscriptCompiler.fs index 51a0c77508..e468205f56 100644 --- a/NBitcoin.Miniscript/MiniscriptCompiler.fs +++ b/NBitcoin.Miniscript/MiniscriptCompiler.fs @@ -283,18 +283,18 @@ module Compiler = | (false, true) -> 3 | (false, false) -> 2 - let rec fromPolicy (p : Policy) : CompiledNode = + let rec fromPolicy (p : AbstractPolicy) : CompiledNode = match p with | Key k -> Pk k - | Policy.Multi(m, pks) -> Multi(m, pks) - | Policy.Hash h -> Hash h - | Policy.Time t -> Time t - | Policy.Threshold(n, subexprs) -> + | AbstractPolicy.Multi(m, pks) -> Multi(m, pks) + | AbstractPolicy.Hash h -> Hash h + | AbstractPolicy.Time t -> Time t + | AbstractPolicy.Threshold(n, subexprs) -> let ps = subexprs |> Array.map fromPolicy Threshold(n, ps) - | Policy.And(e1, e2) -> And(fromPolicy e1, fromPolicy e2) - | Policy.Or(e1, e2) -> Or(fromPolicy e1, fromPolicy e2, 0.5, 0.5) - | Policy.AsymmetricOr(e1, e2) -> + | AbstractPolicy.And(e1, e2) -> And(fromPolicy e1, fromPolicy e2) + | AbstractPolicy.Or(e1, e2) -> Or(fromPolicy e1, fromPolicy e2, 0.5, 0.5) + | AbstractPolicy.AsymmetricOr(e1, e2) -> Or(fromPolicy e1, fromPolicy e2, 127.0 / 128.0, 1.0 / 128.0) // TODO: cache @@ -1075,13 +1075,8 @@ module Compiler = dissatCost = 0.0 } type CompiledNode with - static member FromPolicy (p : Policy) = CompiledNode.fromPolicy p + static member FromPolicy (p : AbstractPolicy) = CompiledNode.fromPolicy p member this.Compile() = let node = CompiledNode.bestT (this, 1.0, 0.0) - MiniScript.fromAST (node.ast) - - member this.CompileUnsafe() = - match this.Compile() with - | Ok miniscript -> miniscript - | Error e -> failwith e + node.ast \ No newline at end of file diff --git a/NBitcoin.Miniscript/MiniscriptParser.fs b/NBitcoin.Miniscript/MiniscriptParser.fs index cbadbcb57f..33f64103ff 100644 --- a/NBitcoin.Miniscript/MiniscriptParser.fs +++ b/NBitcoin.Miniscript/MiniscriptParser.fs @@ -4,16 +4,17 @@ open NBitcoin open System.Text.RegularExpressions open System +[] module MiniscriptParser = - type Policy = + type AbstractPolicy = | Key of PubKey | Multi of uint32 * PubKey [] | Hash of uint256 | Time of NBitcoin.LockTime - | Threshold of uint32 * Policy [] - | And of Policy * Policy - | Or of Policy * Policy - | AsymmetricOr of Policy * Policy + | Threshold of uint32 * AbstractPolicy [] + | And of AbstractPolicy * AbstractPolicy + | Or of AbstractPolicy * AbstractPolicy + | AsymmetricOr of AbstractPolicy * AbstractPolicy override this.ToString() = match this with | Key k1 -> sprintf "pk(%s)" (string (k1.ToHex())) @@ -98,7 +99,7 @@ module MiniscriptParser = let newChunk = Array.append currentChunk [| c |] safeSplit s acc (index + 1) (openNum) newChunk - let rec (|Policy|_|) s = + let rec (|AbstractPolicy|_|) s = let s = Regex.Replace(s, @"[|\s|\n|\r\n]+", "") match s with | Expression "pk" (SurroundedByBrackets(PubKeyPattern pk)) -> Some(Key pk) @@ -122,7 +123,7 @@ module MiniscriptParser = let thresholdStr = s.[0] match UInt32.TryParse(thresholdStr) with | (true, threshold) -> - let subPolicy = s.[1..s.Length - 1] |> Array.choose ((|Policy|_|)) + let subPolicy = s.[1..s.Length - 1] |> Array.choose ((|AbstractPolicy|_|)) if subPolicy.Length <> s.Length - 1 then None else Some(threshold, subPolicy) | (false, _) -> None @@ -137,6 +138,6 @@ module MiniscriptParser = let s = safeSplit s [] 0 0 [||] if s.Length <> 2 then None else - let subPolicies = s |> Array.choose ((|Policy|_|)) + let subPolicies = s |> Array.choose ((|AbstractPolicy|_|)) if subPolicies.Length <> s.Length then None else Some(subPolicies.[0], subPolicies.[1]) diff --git a/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj b/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj index 252c6a6477..5e1e1ac157 100644 --- a/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj +++ b/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj @@ -1,6 +1,6 @@  - net461;netcoreapp2.1;netstandard2.0 + net461;netcoreapp2.1;netstandard2.0 true @@ -13,8 +13,8 @@ - + @@ -27,4 +27,4 @@ - + \ No newline at end of file diff --git a/NBitcoin.Miniscript/Satisfy.fs b/NBitcoin.Miniscript/Satisfy.fs index ce3b032968..4c07841adf 100644 --- a/NBitcoin.Miniscript/Satisfy.fs +++ b/NBitcoin.Miniscript/Satisfy.fs @@ -5,13 +5,14 @@ module Satisfy = open NBitcoin.Miniscript.Utils open NBitcoin open System + open System.Runtime.InteropServices type SignatureProvider = PubKey -> TransactionSignature option type PreImageHash = PreImagehash of uint256 type PreImage = PreImage of uint256 type PreImageProvider = PreImageHash -> PreImage - type ProviderSet = (SignatureProvider * PreImageProvider * TimeSpan) + type ProviderSet = (SignatureProvider option * PreImageProvider option * LockTime option) type CSVOffset = BlockHeight of uint32 | UnixTime of DateTimeOffset type FailureCase = @@ -19,6 +20,7 @@ module Satisfy = | NotMatured of CSVOffset | LockTimeTypeMismatch | Nested of FailureCase list + | CurrentTimeNotSpecified type SatisfiedItem = | PreImage of byte[] @@ -32,31 +34,37 @@ module Satisfy = let (>>=) xR f = Result.bind f xR // ------- helpers -------- - let satisfyCheckSig (keyFn: SignatureProvider) k = - match keyFn k with - | None -> Error (MissingSig [k]) - | Some(txSig) -> Ok([Signature(txSig)]) - - let satisfyCheckMultisig (keyFn: SignatureProvider) (m, pks) = - let maybeSigList = pks - |> Array.map(keyFn) - |> Array.toList - - let sigList = maybeSigList |> List.choose(id) |> List.map(Signature) - - if sigList.Length >= (int32 m) then - Ok(sigList) - else - let sigNotFoundPks = maybeSigList - |> List.zip (pks |> Array.toList) - |> List.choose(fun (pk, maybeSig) -> - if maybeSig.IsNone then Some(pk) else None) - Error(MissingSig(sigNotFoundPks)) + let satisfyCheckSig (maybeKeyFn: SignatureProvider option) k = + match maybeKeyFn with + | None -> Error(MissingSig([k])) + | Some keyFn -> + match keyFn k with + | None -> Error (MissingSig [k]) + | Some(txSig) -> Ok([Signature(txSig)]) + + let satisfyCheckMultisig (maybeKeyFn: SignatureProvider option) (m, pks) = + match maybeKeyFn with + | None -> Error(MissingSig(pks |> List.ofArray)) + | Some keyFn -> + let maybeSigList = pks + |> Array.map(keyFn) + |> Array.toList + + let sigList = maybeSigList |> List.choose(id) |> List.map(Signature) + + if sigList.Length >= (int32 m) then + Ok(sigList) + else + let sigNotFoundPks = maybeSigList + |> List.zip (pks |> Array.toList) + |> List.choose(fun (pk, maybeSig) -> + if maybeSig.IsNone then Some(pk) else None) + Error(MissingSig(sigNotFoundPks)) - let satisfyHashEqual (hashFn: PreImageProvider) h = + let satisfyHashEqual (maybeHashFn: PreImageProvider option) h = failwith "" - let satisfyCSV (age: LockTime) (t: LockTime) = + let satisfyCSVCore (age: LockTime) (t: LockTime) = let offset = t.Value - age.Value if (age.IsHeightLock && t.IsHeightLock) @@ -75,13 +83,19 @@ module Satisfy = else Error(LockTimeTypeMismatch) - let rec satisfyThreshold (keyFn, hashFn, age) (k, e, ws): SatisfactionResult = + let satisfyCSV (age: LockTime option) (t: LockTime) = + match age with + | None -> Error(CurrentTimeNotSpecified) + | Some a -> satisfyCSVCore a t + + let rec satisfyThreshold (providers) (k, e, ws): SatisfactionResult = + let keyFn, hashFn, age = providers let flatten l = List.collect id l let wsList = ws |> Array.toList let wResult = wsList |> List.rev - |> List.map(satisfyW(keyFn, hashFn, age)) + |> List.map(satisfyW providers) let wOkList = wResult |> List.filter(fun wr -> match wr with | Ok w -> true;| _ -> false) |> List.map(fun wr -> match wr with | Ok w -> w; | _ -> failwith "unreachable") @@ -111,14 +125,14 @@ module Satisfy = else Error(Nested(wErrorList @ eErrorList)) - and satisfyAST (keyFn, hashFn, age) (ast: AST) = + and satisfyAST providers (ast: AST) = match ast.GetASTType() with - | EExpr -> satisfyE (keyFn, hashFn, age) (ast.CastEUnsafe()) - | FExpr -> satisfyF (keyFn, hashFn, age) (ast.CastFUnsafe()) - | WExpr -> satisfyW (keyFn, hashFn, age) (ast.CastWUnsafe()) - | QExpr -> satisfyQ (keyFn, hashFn, age) (ast.CastQUnsafe()) - | TExpr -> satisfyT (keyFn, hashFn, age) (ast.CastTUnsafe()) - | VExpr -> satisfyV (keyFn, hashFn, age) (ast.CastVUnsafe()) + | EExpr -> satisfyE providers (ast.CastEUnsafe()) + | FExpr -> satisfyF providers (ast.CastFUnsafe()) + | WExpr -> satisfyW providers (ast.CastWUnsafe()) + | QExpr -> satisfyQ providers (ast.CastQUnsafe()) + | TExpr -> satisfyT providers (ast.CastTUnsafe()) + | VExpr -> satisfyV providers (ast.CastVUnsafe()) and dissatisfyAST (ast: AST) = match ast.GetASTType() with @@ -168,8 +182,8 @@ module Satisfy = else Ok(rItems @ [RawPush([|byte 0|])]) - and satisfyE providers (e: E) = - let (keyFn, hashFn, age) = providers + and satisfyE (providers: ProviderSet) (e: E) = + let keyFn, hashFn, age = providers match e with | E.CheckSig k -> satisfyCheckSig keyFn k | E.CheckMultiSig(m, pks) -> satisfyCheckMultisig keyFn (m, pks) @@ -177,29 +191,31 @@ module Satisfy = | E.Threshold i -> satisfyThreshold providers i | E.ParallelAnd(e, w) -> - satisfyE (keyFn, hashFn, age) e - >>= (fun eitem -> satisfyW(keyFn, hashFn, age) w >>= (fun witem -> Ok(eitem @ witem))) + satisfyE providers e + >>= (fun eitem -> satisfyW providers w >>= (fun witem -> Ok(eitem @ witem))) | E.CascadeAnd(e, f) -> - satisfyE (keyFn, hashFn, age) e - >>= (fun eitem -> satisfyF(keyFn, hashFn, age) f >>= (fun fitem -> Ok(eitem @ fitem))) - | E.ParallelOr(e, w) -> satisfyParallelOr (keyFn, hashFn, age) (ETree(e), WTree(w)) - | E.CascadeOr(e1, e2) -> satisfyCascadeOr (keyFn, hashFn, age) (ETree(e1), ETree(e2)) - | E.SwitchOrLeft(e, f) -> satisfySwitchOr (keyFn, hashFn, age) (ETree(e), FTree(f)) - | E.SwitchOrRight(e, f) -> satisfySwitchOr (keyFn, hashFn, age) (ETree(e), FTree(f)) + satisfyE providers e + >>= (fun eitem -> satisfyF providers f >>= (fun fitem -> Ok(eitem @ fitem))) + | E.ParallelOr(e, w) -> satisfyParallelOr providers (ETree(e), WTree(w)) + | E.CascadeOr(e1, e2) -> satisfyCascadeOr providers (ETree(e1), ETree(e2)) + | E.SwitchOrLeft(e, f) -> satisfySwitchOr providers (ETree(e), FTree(f)) + | E.SwitchOrRight(e, f) -> satisfySwitchOr providers (ETree(e), FTree(f)) | E.Likely f -> - satisfyF (keyFn, hashFn, age) f |> Result.map(fun items -> items @ [RawPush([||])]) + satisfyF providers f |> Result.map(fun items -> items @ [RawPush([||])]) | E.Unlikely f -> - satisfyF (keyFn, hashFn, age) f |> Result.map(fun items -> items @ [RawPush([|byte 1|])]) + satisfyF providers f |> Result.map(fun items -> items @ [RawPush([|byte 1|])]) - and satisfyW providers w: SatisfactionResult = + and satisfyW (providers: ProviderSet) w: SatisfactionResult = let keyFn, hashFn, age = providers match w with | W.CheckSig pk -> satisfyCheckSig keyFn pk | W.HashEqual h -> satisfyHashEqual hashFn h - | W.Time t -> satisfyCSV age t |> Result.map(fun items -> items @ [RawPush([| byte 1 |])]) + | W.Time t -> + satisfyCSV age t |> Result.map(fun items -> items @ [RawPush([| byte 1 |])]) | W.CastE e -> satisfyE providers e + | _ -> Ok [] - and satisfyT providers t = + and satisfyT (providers) t = let (keyFn, hashFn, age) = providers match t with | T.Time t -> Ok([RawPush([||])]) @@ -216,8 +232,8 @@ module Satisfy = | T.DelayedOr(q1, q2) -> satisfySwitchOr providers (QTree(q1), QTree(q2)) | T.CastE e -> satisfyE providers e - and satisfyQ providers q = - let keyFn, hashFn, age = providers + and satisfyQ (providers) q = + let (keyFn, hashFn, age) = providers match q with | Q.Pubkey pk -> satisfyCheckSig (keyFn) pk | Q.And(l, r) -> @@ -226,7 +242,7 @@ module Satisfy = rRes >>= (fun rItems -> lRes >>= fun(lItems) -> Ok(rItems @ lItems)) | Q.Or(l, r) -> satisfySwitchOr providers (QTree(l), QTree(r)) - and satisfyF providers f = + and satisfyF (providers) f = let (keyFn, hashFn, age) = providers match f with | F.CheckSig pk -> satisfyCheckSig keyFn pk @@ -293,30 +309,42 @@ module Satisfy = // ---------- types ------- type E with - member this.Satisfy(keyFn: SignatureProvider, - hashFn: PreImageProvider, - age: LockTime): SatisfactionResult = satisfyE(keyFn, hashFn, age) this + member this.Satisfy([]?keyFn: SignatureProvider, + []?hashFn: PreImageProvider, + []?age: LockTime): SatisfactionResult = + let providers = ProviderSet(keyFn, hashFn, age) + satisfyE providers this member this.Disatisfy(): SatisfiedItem list = dissatisfyE this type T with - member this.Satisfy(keyFn: SignatureProvider, - hashFn: PreImageProvider, - age: LockTime): SatisfactionResult = satisfyT(keyFn, hashFn, age) this + member this.Satisfy([]?keyFn: SignatureProvider, + []?hashFn: PreImageProvider, + []?age: LockTime): SatisfactionResult = + let providers = ProviderSet(keyFn, hashFn, age) + satisfyT providers this type W with - member this.Satisfy(keyFn: SignatureProvider, - hashFn: PreImageProvider, - age: LockTime): SatisfactionResult = satisfyW(keyFn, hashFn, age) this + member this.Satisfy([]?keyFn: SignatureProvider, + []?hashFn: PreImageProvider, + []?age: LockTime): SatisfactionResult = + let providers = ProviderSet(keyFn, hashFn, age) + satisfyW providers this member this.Disatisfy(): SatisfiedItem list = dissatisfyW this type Q with - member this.Satisfy(keyFn: SignatureProvider, - hashFn: PreImageProvider, - age: LockTime): SatisfactionResult = satisfyQ(keyFn, hashFn, age) this + member this.Satisfy([]?keyFn: SignatureProvider, + []?hashFn: PreImageProvider, + []?age: LockTime): SatisfactionResult = + let providers = ProviderSet(keyFn, hashFn, age) + satisfyQ providers this type F with - member this.Satisfy(keyFn: SignatureProvider, - hashFn: PreImageProvider, - age: LockTime): SatisfactionResult = satisfyF(keyFn, hashFn, age) this + member this.Satisfy([]?keyFn: SignatureProvider, + []?hashFn: PreImageProvider, + []?age: LockTime): SatisfactionResult = + let providers = ProviderSet(keyFn, hashFn, age) + satisfyF providers this type V with - member this.Satisfy(keyFn: SignatureProvider, - hashFn: PreImageProvider, - age: LockTime): SatisfactionResult = satisfyV(keyFn, hashFn, age) this + member this.Satisfy([]?keyFn: SignatureProvider, + []?hashFn: PreImageProvider, + []?age: LockTime): SatisfactionResult = + let providers = ProviderSet(keyFn, hashFn, age) + satisfyV providers this From 1a2677d8cd683d8bb562d2c5ff4428555696e13a Mon Sep 17 00:00:00 2001 From: joemphilips Date: Thu, 4 Apr 2019 22:30:18 +0900 Subject: [PATCH 15/40] Rename MiniScript -> Miniscript --- .../MiniScriptDecompilerTests.fs | 40 +++++++++---------- NBitcoin.Miniscript.Tests/SatisfyTests.fs | 2 +- NBitcoin.Miniscript/Miniscript.fs | 23 ++++++----- NBitcoin.Miniscript/MiniscriptDecompiler.fs | 6 +-- 4 files changed, 36 insertions(+), 35 deletions(-) diff --git a/NBitcoin.Miniscript.Tests/MiniScriptDecompilerTests.fs b/NBitcoin.Miniscript.Tests/MiniScriptDecompilerTests.fs index 476bb98d81..8c423cf491 100644 --- a/NBitcoin.Miniscript.Tests/MiniScriptDecompilerTests.fs +++ b/NBitcoin.Miniscript.Tests/MiniScriptDecompilerTests.fs @@ -55,7 +55,7 @@ let tests = testCase "Should pass the testcase in rust-miniscript" <| fun _ -> - let roundtrip (miniscriptResult : Result) + let roundtrip (miniscriptResult : Result) (s : Script) = match miniscriptResult with | Ok tree -> @@ -63,13 +63,13 @@ let tests = Expect.equal ser s "Serialized Miniscript does not match expected script" let deser = - MiniScript.fromScriptUnsafe s + Miniscript.fromScriptUnsafe s Expect.equal deser tree "deserialized script does not match expected MiniScript" | Result.Error e -> failwith e let r1 = - MiniScript.fromAST + Miniscript.fromAST (AST.TTree (T.CastE (E.CheckSig @@ -82,7 +82,7 @@ let tests = "OP_CHECKSIG") roundtrip r1 s1 let r2 = - MiniScript.fromAST + Miniscript.fromAST (AST.TTree (T.CastE (E.CheckMultiSig @@ -97,7 +97,7 @@ let tests = roundtrip r2 s2 let r3Partial = - MiniScript.fromAST( + Miniscript.fromAST( TTree( T.And( V.CheckMultiSig( @@ -118,7 +118,7 @@ let tests = // Liquid policy let r3 = - MiniScript.fromAST + Miniscript.fromAST (AST.TTree (T.CascadeOr (E.CheckMultiSig @@ -141,12 +141,12 @@ let tests = roundtrip r3 s3 let r4 = - MiniScript.fromAST + Miniscript.fromAST (TTree(T.Time(!> 921u))) let s4 = Script("9903 OP_CSV") roundtrip r4 s4 - let r5 = MiniScript.fromAST (TTree( + let r5 = Miniscript.fromAST (TTree( T.SwitchOrV( V.CheckSig(keysList.[0]), V.And( @@ -171,14 +171,14 @@ let config = maxTest = 30 endSize = 32 } -let roundTripFromMiniScript (m: MiniScript) = +let roundTripFromMiniScript (m: Miniscript) = let sc = m.ToScript() - let m2 = MiniScript.fromScriptUnsafe sc + let m2 = Miniscript.fromScriptUnsafe sc Expect.equal m2 m "failed" let roundtrip p = let m = CompiledNode.fromPolicy(p).Compile() - roundTripFromMiniScript (MiniScript.fromASTUnsafe(m)) + roundTripFromMiniScript (Miniscript.fromASTUnsafe(m)) let hash = uint256.Parse("59141e52303a755307114c2a5e6823010b3f1d586216742f396d4b06106e222c") @@ -209,15 +209,15 @@ let tests2 = Expect.isOk m2 "failed" testCase "Case found by property tests: 2" <| fun _ -> - let input = MiniScript.fromASTUnsafe(TTree(T.HashEqual(hash))) + let input = Miniscript.fromASTUnsafe(TTree(T.HashEqual(hash))) roundTripFromMiniScript input testCase "Case found by property tests: 3" <| fun _ -> - let input = MiniScript.fromASTUnsafe(TTree(T.And(V.Time(LockTime(1u)), T.Time(LockTime(1u))))) + let input = Miniscript.fromASTUnsafe(TTree(T.And(V.Time(LockTime(1u)), T.Time(LockTime(1u))))) roundTripFromMiniScript input testCase "Case found by property tests: 4" <| fun _ -> - let input = MiniScript.fromASTUnsafe(TTree( + let input = Miniscript.fromASTUnsafe(TTree( T.CastE( E.Threshold( 1u, @@ -229,7 +229,7 @@ let tests2 = ) roundTripFromMiniScript input testCase "Case found by property tests: 5" <| fun _ -> - let input = MiniScript.fromASTUnsafe(TTree( + let input = Miniscript.fromASTUnsafe(TTree( T.CastE( E.Likely( F.Threshold( @@ -243,13 +243,13 @@ let tests2 = ) roundTripFromMiniScript input testCase "Case found by property tests: 6" <| fun _ -> - let input = MiniScript.fromASTUnsafe(TTree(T.And( + let input = Miniscript.fromASTUnsafe(TTree(T.And( V.CheckMultiSig(1u, longKeysList), T.Time(!> 1u) ))) roundTripFromMiniScript input testCase "Case found by property tests: 7" <| fun _ -> - let input = MiniScript.fromASTUnsafe(TTree( + let input = Miniscript.fromASTUnsafe(TTree( T.CastE( E.SwitchOrRight(E.Time(!> 1u), F.Time(!> 1u)) ) @@ -257,7 +257,7 @@ let tests2 = ) roundTripFromMiniScript input testCase "Case found by property tests: 8" <| fun _ -> - let input = MiniScript.fromASTUnsafe(TTree( + let input = Miniscript.fromASTUnsafe(TTree( T.And( V.Time(!> 2u), T.CastE( @@ -271,7 +271,7 @@ let tests2 = roundTripFromMiniScript input testCase "Case found by property tests: 9" <| fun _ -> - let input = MiniScript.fromASTUnsafe(TTree( + let input = Miniscript.fromASTUnsafe(TTree( T.CastE( E.Likely(F.And(V.Time(!> 2u), F.Time(!> 2u))) ) @@ -279,7 +279,7 @@ let tests2 = roundTripFromMiniScript input ptestCase "Can NOT handle nested And" <| fun _ -> - let input = MiniScript.fromASTUnsafe(TTree( + let input = Miniscript.fromASTUnsafe(TTree( T.And( V.Time(!> 3u), T.And( diff --git a/NBitcoin.Miniscript.Tests/SatisfyTests.fs b/NBitcoin.Miniscript.Tests/SatisfyTests.fs index e510ed32e1..d5519bb075 100644 --- a/NBitcoin.Miniscript.Tests/SatisfyTests.fs +++ b/NBitcoin.Miniscript.Tests/SatisfyTests.fs @@ -9,6 +9,6 @@ let tests = testCase "case 1" <| fun _ -> let key = NBitcoin.Key() let scriptStr = sprintf "and(pk(%s), time(%d))" (key.PubKey.ToString()) 10000u - let ms = MiniScript.parseUnsafe scriptStr + let ms = Miniscript.parseUnsafe scriptStr () ] diff --git a/NBitcoin.Miniscript/Miniscript.fs b/NBitcoin.Miniscript/Miniscript.fs index 02c7c742bd..9470cb84cf 100644 --- a/NBitcoin.Miniscript/Miniscript.fs +++ b/NBitcoin.Miniscript/Miniscript.fs @@ -6,12 +6,13 @@ open NBitcoin.Miniscript.Compiler open NBitcoin /// wrapper for top-level AST -type MiniScript = MiniScript of AST +type Miniscript = Miniscript of AST -module MiniScript = - let fromAST (t : AST) : Result = +[] +module Miniscript = + let fromAST (t : AST) : Result = match t.CastT() with - | Ok t -> Ok(MiniScript(TTree t)) + | Ok t -> Ok(Miniscript(TTree t)) | o -> Error (sprintf "AST was not top-level (T) representation\n%A" o) let fromASTUnsafe(t: AST) = @@ -28,10 +29,10 @@ module MiniScript = match parse s with | Ok m -> m | Error e -> failwith e - - let toAST (m : MiniScript) = + + let toAST (m : Miniscript) = match m with - | MiniScript a -> a + | Miniscript a -> a let fromScriptUnsafe (s : NBitcoin.Script) = let res = parseScriptUnsafe s @@ -39,10 +40,10 @@ module MiniScript = | Ok r -> r | Error e -> failwith e - let toScript (m : MiniScript) : Script = + let toScript (m : Miniscript) : Script = let ast = toAST m ast.ToScript() -type MiniScript with - member this.ToScript() = MiniScript.toScript this - member this.ToAST() = MiniScript.toAST this +type Miniscript with + member this.ToScript() = Miniscript.toScript this + member this.ToAST() = Miniscript.toAST this diff --git a/NBitcoin.Miniscript/MiniscriptDecompiler.fs b/NBitcoin.Miniscript/MiniscriptDecompiler.fs index 6f51fadb9d..ceef5fec2c 100644 --- a/NBitcoin.Miniscript/MiniscriptDecompiler.fs +++ b/NBitcoin.Miniscript/MiniscriptDecompiler.fs @@ -194,9 +194,9 @@ let private castOpToToken (op : Op) : Result = | otherOp when (byte 0x01) <= (byte otherOp) && (byte otherOp) < (byte 0x4B) -> tryGetItemFromOp op | otherOp when (byte 0x4B) <= (byte otherOp) -> - Error(ParseException(sprintf "MiniScript does not support pushdata bigger than 33. Got %s" (otherOp.ToString()))) + Error(ParseException(sprintf "Miniscript does not support pushdata bigger than 33. Got %s" (otherOp.ToString()))) | unknown -> - Error(ParseException(sprintf "Unknown Opcode to MiniScript %s" (unknown.ToString()))) + Error(ParseException(sprintf "Unknown Opcode to Miniscript %s" (unknown.ToString()))) type State = { ops: Op[] @@ -226,7 +226,7 @@ module TokenParser = let r = castOpToToken ops match r with | Error pex -> - let msg = sprintf "opcode %s is not supported by MiniScript %s" ops.Name pex.Message + let msg = sprintf "opcode %s is not supported by Miniscript %s" ops.Name pex.Message Error(name, msg, pos) | Ok actualToken -> let actualCat = actualToken.GetCategory() From 7ea97cac1c436d7f30493916acb134ae149b64d4 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Thu, 4 Apr 2019 22:42:32 +0900 Subject: [PATCH 16/40] Small refactor --- NBitcoin.Miniscript/Miniscript.fs | 11 ++++++++--- NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj | 2 +- NBitcoin.Miniscript/Satisfy.fs | 15 +++++++-------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/NBitcoin.Miniscript/Miniscript.fs b/NBitcoin.Miniscript/Miniscript.fs index 9470cb84cf..20a46c1bfe 100644 --- a/NBitcoin.Miniscript/Miniscript.fs +++ b/NBitcoin.Miniscript/Miniscript.fs @@ -3,16 +3,17 @@ namespace NBitcoin.Miniscript open NBitcoin.Miniscript.AST open NBitcoin.Miniscript.Decompiler open NBitcoin.Miniscript.Compiler +open NBitcoin.Miniscript.Satisfy open NBitcoin /// wrapper for top-level AST -type Miniscript = Miniscript of AST +type Miniscript = Miniscript of T [] module Miniscript = let fromAST (t : AST) : Result = match t.CastT() with - | Ok t -> Ok(Miniscript(TTree t)) + | Ok t -> Ok(Miniscript(t)) | o -> Error (sprintf "AST was not top-level (T) representation\n%A" o) let fromASTUnsafe(t: AST) = @@ -32,7 +33,7 @@ module Miniscript = let toAST (m : Miniscript) = match m with - | Miniscript a -> a + | Miniscript a -> TTree(a) let fromScriptUnsafe (s : NBitcoin.Script) = let res = parseScriptUnsafe s @@ -44,6 +45,10 @@ module Miniscript = let ast = toAST m ast.ToScript() + let satisfy (Miniscript t) (providers: ProviderSet) = + satisfyT (providers) t + type Miniscript with member this.ToScript() = Miniscript.toScript this member this.ToAST() = Miniscript.toAST this + member this.SatisFy(providers) = Miniscript.satisfy this providers diff --git a/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj b/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj index 5e1e1ac157..d24e3a4e11 100644 --- a/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj +++ b/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj @@ -14,8 +14,8 @@ - + diff --git a/NBitcoin.Miniscript/Satisfy.fs b/NBitcoin.Miniscript/Satisfy.fs index 4c07841adf..e5e7f33965 100644 --- a/NBitcoin.Miniscript/Satisfy.fs +++ b/NBitcoin.Miniscript/Satisfy.fs @@ -1,4 +1,4 @@ -namespace NBitcoin.Miniscript.Satisfy +namespace NBitcoin.Miniscript module Satisfy = open NBitcoin.Miniscript.AST @@ -213,7 +213,6 @@ module Satisfy = | W.Time t -> satisfyCSV age t |> Result.map(fun items -> items @ [RawPush([| byte 1 |])]) | W.CastE e -> satisfyE providers e - | _ -> Ok [] and satisfyT (providers) t = let (keyFn, hashFn, age) = providers @@ -312,7 +311,7 @@ module Satisfy = member this.Satisfy([]?keyFn: SignatureProvider, []?hashFn: PreImageProvider, []?age: LockTime): SatisfactionResult = - let providers = ProviderSet(keyFn, hashFn, age) + let providers = (keyFn, hashFn, age) satisfyE providers this member this.Disatisfy(): SatisfiedItem list = dissatisfyE this @@ -321,30 +320,30 @@ module Satisfy = member this.Satisfy([]?keyFn: SignatureProvider, []?hashFn: PreImageProvider, []?age: LockTime): SatisfactionResult = - let providers = ProviderSet(keyFn, hashFn, age) + let providers = (keyFn, hashFn, age) satisfyT providers this type W with member this.Satisfy([]?keyFn: SignatureProvider, []?hashFn: PreImageProvider, []?age: LockTime): SatisfactionResult = - let providers = ProviderSet(keyFn, hashFn, age) + let providers = (keyFn, hashFn, age) satisfyW providers this member this.Disatisfy(): SatisfiedItem list = dissatisfyW this type Q with member this.Satisfy([]?keyFn: SignatureProvider, []?hashFn: PreImageProvider, []?age: LockTime): SatisfactionResult = - let providers = ProviderSet(keyFn, hashFn, age) + let providers = (keyFn, hashFn, age) satisfyQ providers this type F with member this.Satisfy([]?keyFn: SignatureProvider, []?hashFn: PreImageProvider, []?age: LockTime): SatisfactionResult = - let providers = ProviderSet(keyFn, hashFn, age) + let providers = (keyFn, hashFn, age) satisfyF providers this type V with member this.Satisfy([]?keyFn: SignatureProvider, []?hashFn: PreImageProvider, []?age: LockTime): SatisfactionResult = - let providers = ProviderSet(keyFn, hashFn, age) + let providers = (keyFn, hashFn, age) satisfyV providers this From 930d210e602ce275162e1b49d6ebadce8957d913 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Fri, 5 Apr 2019 00:49:31 +0900 Subject: [PATCH 17/40] separate FSharp test and CSharp test for Miniscript --- .../CSharp/MiniscriptPSBTTests.cs | 24 +++++++++++ .../NBitcoin.Miniscript.Tests.CSharp.csproj | 20 +++++++++ NBitcoin.Miniscript.Tests/CSharp/UnitTest1.cs | 14 +++++++ .../{ => FSharp}/AssemblyInfo.fs | 0 .../{ => FSharp}/Contract.fs | 0 .../{ => FSharp}/Generators/Lib.fs | 0 .../{ => FSharp}/Generators/NBitcoin.fs | 0 .../{ => FSharp}/Generators/Policy.fs | 0 .../{ => FSharp}/Generators/Primitives.fs | 0 .../{ => FSharp}/Main.fs | 0 .../{ => FSharp}/MiniScriptCompilerTests.fs | 0 .../{ => FSharp}/MiniScriptDecompilerTests.fs | 0 .../{ => FSharp}/MiniScriptParserTests.fs | 0 .../NBitcoin.Miniscript.Tests.FSharp.fsproj} | 4 +- .../FSharp/SatisfyTests.fs | 30 +++++++++++++ .../{ => FSharp}/fsc.props | 0 .../{ => FSharp}/netfx.props | 0 NBitcoin.Miniscript.Tests/SatisfyTests.fs | 14 ------- NBitcoin.Miniscript/Miniscript.fs | 9 +++- NBitcoin.Miniscript/Satisfy.fs | 42 ------------------- NBitcoin.sln | 24 ++++++++--- 21 files changed, 116 insertions(+), 65 deletions(-) create mode 100644 NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs create mode 100644 NBitcoin.Miniscript.Tests/CSharp/NBitcoin.Miniscript.Tests.CSharp.csproj create mode 100644 NBitcoin.Miniscript.Tests/CSharp/UnitTest1.cs rename NBitcoin.Miniscript.Tests/{ => FSharp}/AssemblyInfo.fs (100%) rename NBitcoin.Miniscript.Tests/{ => FSharp}/Contract.fs (100%) rename NBitcoin.Miniscript.Tests/{ => FSharp}/Generators/Lib.fs (100%) rename NBitcoin.Miniscript.Tests/{ => FSharp}/Generators/NBitcoin.fs (100%) rename NBitcoin.Miniscript.Tests/{ => FSharp}/Generators/Policy.fs (100%) rename NBitcoin.Miniscript.Tests/{ => FSharp}/Generators/Primitives.fs (100%) rename NBitcoin.Miniscript.Tests/{ => FSharp}/Main.fs (100%) rename NBitcoin.Miniscript.Tests/{ => FSharp}/MiniScriptCompilerTests.fs (100%) rename NBitcoin.Miniscript.Tests/{ => FSharp}/MiniScriptDecompilerTests.fs (100%) rename NBitcoin.Miniscript.Tests/{ => FSharp}/MiniScriptParserTests.fs (100%) rename NBitcoin.Miniscript.Tests/{NBitcoin.Miniscript.Tests.fsproj => FSharp/NBitcoin.Miniscript.Tests.FSharp.fsproj} (87%) create mode 100644 NBitcoin.Miniscript.Tests/FSharp/SatisfyTests.fs rename NBitcoin.Miniscript.Tests/{ => FSharp}/fsc.props (100%) rename NBitcoin.Miniscript.Tests/{ => FSharp}/netfx.props (100%) delete mode 100644 NBitcoin.Miniscript.Tests/SatisfyTests.fs diff --git a/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs b/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs new file mode 100644 index 0000000000..d261cfa73a --- /dev/null +++ b/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs @@ -0,0 +1,24 @@ +using System; +using NBitcoin.Miniscript; +using NBitcoin; +using Xunit; +namespace NBitcoin.Tests +{ + public class MiniscriptPSBTTests + { + public MiniscriptPSBTTests() + { + } + + private TransactionSignature DummyKeyFn(PubKey key) => null; + public void ShouldSatisfyMiniscript() + { + var key = new NBitcoin.Key(); + var scriptStr = $"and(pk({key.PubKey}), time({new LockTime(10000)}))"; + var ms = NBitcoin.Miniscript.Miniscript.parseUnsafe(scriptStr); + Assert.NotNull(ms); + + var r1 = ms.Satisfy(DummyKeyFn, null, null); + } + } +} diff --git a/NBitcoin.Miniscript.Tests/CSharp/NBitcoin.Miniscript.Tests.CSharp.csproj b/NBitcoin.Miniscript.Tests/CSharp/NBitcoin.Miniscript.Tests.CSharp.csproj new file mode 100644 index 0000000000..ae7c6a5b7a --- /dev/null +++ b/NBitcoin.Miniscript.Tests/CSharp/NBitcoin.Miniscript.Tests.CSharp.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp2.2 + + false + + + + + + + + + + + + + + diff --git a/NBitcoin.Miniscript.Tests/CSharp/UnitTest1.cs b/NBitcoin.Miniscript.Tests/CSharp/UnitTest1.cs new file mode 100644 index 0000000000..19a07c12a3 --- /dev/null +++ b/NBitcoin.Miniscript.Tests/CSharp/UnitTest1.cs @@ -0,0 +1,14 @@ +using System; +using Xunit; + +namespace CSharp +{ + public class UnitTest1 + { + [Fact] + public void Test1() + { + + } + } +} diff --git a/NBitcoin.Miniscript.Tests/AssemblyInfo.fs b/NBitcoin.Miniscript.Tests/FSharp/AssemblyInfo.fs similarity index 100% rename from NBitcoin.Miniscript.Tests/AssemblyInfo.fs rename to NBitcoin.Miniscript.Tests/FSharp/AssemblyInfo.fs diff --git a/NBitcoin.Miniscript.Tests/Contract.fs b/NBitcoin.Miniscript.Tests/FSharp/Contract.fs similarity index 100% rename from NBitcoin.Miniscript.Tests/Contract.fs rename to NBitcoin.Miniscript.Tests/FSharp/Contract.fs diff --git a/NBitcoin.Miniscript.Tests/Generators/Lib.fs b/NBitcoin.Miniscript.Tests/FSharp/Generators/Lib.fs similarity index 100% rename from NBitcoin.Miniscript.Tests/Generators/Lib.fs rename to NBitcoin.Miniscript.Tests/FSharp/Generators/Lib.fs diff --git a/NBitcoin.Miniscript.Tests/Generators/NBitcoin.fs b/NBitcoin.Miniscript.Tests/FSharp/Generators/NBitcoin.fs similarity index 100% rename from NBitcoin.Miniscript.Tests/Generators/NBitcoin.fs rename to NBitcoin.Miniscript.Tests/FSharp/Generators/NBitcoin.fs diff --git a/NBitcoin.Miniscript.Tests/Generators/Policy.fs b/NBitcoin.Miniscript.Tests/FSharp/Generators/Policy.fs similarity index 100% rename from NBitcoin.Miniscript.Tests/Generators/Policy.fs rename to NBitcoin.Miniscript.Tests/FSharp/Generators/Policy.fs diff --git a/NBitcoin.Miniscript.Tests/Generators/Primitives.fs b/NBitcoin.Miniscript.Tests/FSharp/Generators/Primitives.fs similarity index 100% rename from NBitcoin.Miniscript.Tests/Generators/Primitives.fs rename to NBitcoin.Miniscript.Tests/FSharp/Generators/Primitives.fs diff --git a/NBitcoin.Miniscript.Tests/Main.fs b/NBitcoin.Miniscript.Tests/FSharp/Main.fs similarity index 100% rename from NBitcoin.Miniscript.Tests/Main.fs rename to NBitcoin.Miniscript.Tests/FSharp/Main.fs diff --git a/NBitcoin.Miniscript.Tests/MiniScriptCompilerTests.fs b/NBitcoin.Miniscript.Tests/FSharp/MiniScriptCompilerTests.fs similarity index 100% rename from NBitcoin.Miniscript.Tests/MiniScriptCompilerTests.fs rename to NBitcoin.Miniscript.Tests/FSharp/MiniScriptCompilerTests.fs diff --git a/NBitcoin.Miniscript.Tests/MiniScriptDecompilerTests.fs b/NBitcoin.Miniscript.Tests/FSharp/MiniScriptDecompilerTests.fs similarity index 100% rename from NBitcoin.Miniscript.Tests/MiniScriptDecompilerTests.fs rename to NBitcoin.Miniscript.Tests/FSharp/MiniScriptDecompilerTests.fs diff --git a/NBitcoin.Miniscript.Tests/MiniScriptParserTests.fs b/NBitcoin.Miniscript.Tests/FSharp/MiniScriptParserTests.fs similarity index 100% rename from NBitcoin.Miniscript.Tests/MiniScriptParserTests.fs rename to NBitcoin.Miniscript.Tests/FSharp/MiniScriptParserTests.fs diff --git a/NBitcoin.Miniscript.Tests/NBitcoin.Miniscript.Tests.fsproj b/NBitcoin.Miniscript.Tests/FSharp/NBitcoin.Miniscript.Tests.FSharp.fsproj similarity index 87% rename from NBitcoin.Miniscript.Tests/NBitcoin.Miniscript.Tests.fsproj rename to NBitcoin.Miniscript.Tests/FSharp/NBitcoin.Miniscript.Tests.FSharp.fsproj index 53a03e1384..bbdae1ea4b 100644 --- a/NBitcoin.Miniscript.Tests/NBitcoin.Miniscript.Tests.fsproj +++ b/NBitcoin.Miniscript.Tests/FSharp/NBitcoin.Miniscript.Tests.FSharp.fsproj @@ -19,8 +19,8 @@ - - + + diff --git a/NBitcoin.Miniscript.Tests/FSharp/SatisfyTests.fs b/NBitcoin.Miniscript.Tests/FSharp/SatisfyTests.fs new file mode 100644 index 0000000000..1981741539 --- /dev/null +++ b/NBitcoin.Miniscript.Tests/FSharp/SatisfyTests.fs @@ -0,0 +1,30 @@ +module SatisfyTests + +open Expecto +open NBitcoin +open NBitcoin.Miniscript + +[] +let tests = + testList "SatisfyTests" [ + ftestCase "case 1" <| fun _ -> + let key = NBitcoin.Key() + let scriptStr = sprintf "and(pk(%s), time(%d))" (key.PubKey.ToString()) 10000u + let ms = Miniscript.parseUnsafe scriptStr + let t = ms.ToAST().CastTUnsafe() + + let dummyKeyFn pk = None + let r1 = Satisfy.satisfyT (dummyKeyFn |> Some, None, None) t + Expect.isError r1 "should not satisfy with dummy function" + + let dummySig = TransactionSignature.Empty + + let keyFn (pk: PubKey) = if pk.Equals(key.PubKey) then Some(dummySig) else None + let r2 = Satisfy.satisfyT (Some keyFn, None, None) t + Expect.isError r2 "should not satisfy the time" + + let dummyAge = LockTime 10001 + let r3 = Satisfy.satisfyT (Some keyFn, None, Some dummyAge) t + + Expect.isOk r3 "could not satisfy" + ] diff --git a/NBitcoin.Miniscript.Tests/fsc.props b/NBitcoin.Miniscript.Tests/FSharp/fsc.props similarity index 100% rename from NBitcoin.Miniscript.Tests/fsc.props rename to NBitcoin.Miniscript.Tests/FSharp/fsc.props diff --git a/NBitcoin.Miniscript.Tests/netfx.props b/NBitcoin.Miniscript.Tests/FSharp/netfx.props similarity index 100% rename from NBitcoin.Miniscript.Tests/netfx.props rename to NBitcoin.Miniscript.Tests/FSharp/netfx.props diff --git a/NBitcoin.Miniscript.Tests/SatisfyTests.fs b/NBitcoin.Miniscript.Tests/SatisfyTests.fs deleted file mode 100644 index d5519bb075..0000000000 --- a/NBitcoin.Miniscript.Tests/SatisfyTests.fs +++ /dev/null @@ -1,14 +0,0 @@ -module SatisfyTests - -open Expecto -open NBitcoin.Miniscript - -[] -let tests = - testList "SatisfyTests" [ - testCase "case 1" <| fun _ -> - let key = NBitcoin.Key() - let scriptStr = sprintf "and(pk(%s), time(%d))" (key.PubKey.ToString()) 10000u - let ms = Miniscript.parseUnsafe scriptStr - () - ] diff --git a/NBitcoin.Miniscript/Miniscript.fs b/NBitcoin.Miniscript/Miniscript.fs index 20a46c1bfe..f832409b80 100644 --- a/NBitcoin.Miniscript/Miniscript.fs +++ b/NBitcoin.Miniscript/Miniscript.fs @@ -1,5 +1,8 @@ namespace NBitcoin.Miniscript +open System +open System.Runtime.InteropServices + open NBitcoin.Miniscript.AST open NBitcoin.Miniscript.Decompiler open NBitcoin.Miniscript.Compiler @@ -51,4 +54,8 @@ module Miniscript = type Miniscript with member this.ToScript() = Miniscript.toScript this member this.ToAST() = Miniscript.toAST this - member this.SatisFy(providers) = Miniscript.satisfy this providers + member this.Satisfy(nullableKeyFn: SignatureProvider option, + hashFn: PreImageProvider option, + age: LockTime option) = + let keyFn = if box nullableKeyFn = null then None else nullableKeyFn + Miniscript.satisfy this (keyFn, hashFn, age) diff --git a/NBitcoin.Miniscript/Satisfy.fs b/NBitcoin.Miniscript/Satisfy.fs index e5e7f33965..6ed3527f88 100644 --- a/NBitcoin.Miniscript/Satisfy.fs +++ b/NBitcoin.Miniscript/Satisfy.fs @@ -5,7 +5,6 @@ module Satisfy = open NBitcoin.Miniscript.Utils open NBitcoin open System - open System.Runtime.InteropServices type SignatureProvider = PubKey -> TransactionSignature option type PreImageHash = PreImagehash of uint256 @@ -306,44 +305,3 @@ module Satisfy = | W.Time _ -> [RawPush[||]] | W.CastE e -> dissatisfyE e - // ---------- types ------- - type E with - member this.Satisfy([]?keyFn: SignatureProvider, - []?hashFn: PreImageProvider, - []?age: LockTime): SatisfactionResult = - let providers = (keyFn, hashFn, age) - satisfyE providers this - - member this.Disatisfy(): SatisfiedItem list = dissatisfyE this - - type T with - member this.Satisfy([]?keyFn: SignatureProvider, - []?hashFn: PreImageProvider, - []?age: LockTime): SatisfactionResult = - let providers = (keyFn, hashFn, age) - satisfyT providers this - type W with - member this.Satisfy([]?keyFn: SignatureProvider, - []?hashFn: PreImageProvider, - []?age: LockTime): SatisfactionResult = - let providers = (keyFn, hashFn, age) - satisfyW providers this - member this.Disatisfy(): SatisfiedItem list = dissatisfyW this - type Q with - member this.Satisfy([]?keyFn: SignatureProvider, - []?hashFn: PreImageProvider, - []?age: LockTime): SatisfactionResult = - let providers = (keyFn, hashFn, age) - satisfyQ providers this - type F with - member this.Satisfy([]?keyFn: SignatureProvider, - []?hashFn: PreImageProvider, - []?age: LockTime): SatisfactionResult = - let providers = (keyFn, hashFn, age) - satisfyF providers this - type V with - member this.Satisfy([]?keyFn: SignatureProvider, - []?hashFn: PreImageProvider, - []?age: LockTime): SatisfactionResult = - let providers = (keyFn, hashFn, age) - satisfyV providers this diff --git a/NBitcoin.sln b/NBitcoin.sln index 73fbfdd3af..3b8e340633 100644 --- a/NBitcoin.sln +++ b/NBitcoin.sln @@ -20,10 +20,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NBitcoin.TestFramework", "N EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NBitcoin.Bench", "NBitcoin.Bench\NBitcoin.Bench.csproj", "{153FEA8A-FA98-4ADD-8570-5FD88631C489}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "NBitcoin.Miniscript.Tests", "NBitcoin.Miniscript.Tests\NBitcoin.Miniscript.Tests.fsproj", "{89E2F073-7558-4F48-AF7C-05C9EEC850A2}" -EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "NBitcoin.Miniscript", "NBitcoin.Miniscript\NBitcoin.Miniscript.fsproj", "{F00D2B30-F9DA-40AC-AC4C-B1765A2FD7BA}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NBitcoin.Miniscript.Tests", "NBitcoin.Miniscript.Tests", "{961EC18E-0301-4917-B91C-EBE7AFF6F7A7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NBitcoin.Miniscript.Tests.CSharp", "NBitcoin.Miniscript.Tests\CSharp\NBitcoin.Miniscript.Tests.CSharp.csproj", "{C890F563-03B6-43AB-83CF-E7EF865DC9F6}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "NBitcoin.Miniscript.Tests.FSharp", "NBitcoin.Miniscript.Tests\FSharp\NBitcoin.Miniscript.Tests.FSharp.fsproj", "{59132A30-5C07-4617-836C-36D8CF621C6E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -50,14 +54,18 @@ Global {153FEA8A-FA98-4ADD-8570-5FD88631C489}.Debug|Any CPU.Build.0 = Debug|Any CPU {153FEA8A-FA98-4ADD-8570-5FD88631C489}.Release|Any CPU.ActiveCfg = Release|Any CPU {153FEA8A-FA98-4ADD-8570-5FD88631C489}.Release|Any CPU.Build.0 = Release|Any CPU - {89E2F073-7558-4F48-AF7C-05C9EEC850A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {89E2F073-7558-4F48-AF7C-05C9EEC850A2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {89E2F073-7558-4F48-AF7C-05C9EEC850A2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {89E2F073-7558-4F48-AF7C-05C9EEC850A2}.Release|Any CPU.Build.0 = Release|Any CPU {F00D2B30-F9DA-40AC-AC4C-B1765A2FD7BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F00D2B30-F9DA-40AC-AC4C-B1765A2FD7BA}.Debug|Any CPU.Build.0 = Debug|Any CPU {F00D2B30-F9DA-40AC-AC4C-B1765A2FD7BA}.Release|Any CPU.ActiveCfg = Release|Any CPU {F00D2B30-F9DA-40AC-AC4C-B1765A2FD7BA}.Release|Any CPU.Build.0 = Release|Any CPU + {C890F563-03B6-43AB-83CF-E7EF865DC9F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C890F563-03B6-43AB-83CF-E7EF865DC9F6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C890F563-03B6-43AB-83CF-E7EF865DC9F6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C890F563-03B6-43AB-83CF-E7EF865DC9F6}.Release|Any CPU.Build.0 = Release|Any CPU + {59132A30-5C07-4617-836C-36D8CF621C6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {59132A30-5C07-4617-836C-36D8CF621C6E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {59132A30-5C07-4617-836C-36D8CF621C6E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {59132A30-5C07-4617-836C-36D8CF621C6E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -65,4 +73,8 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A1246380-43E4-4710-99A7-F7524458155E} EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {C890F563-03B6-43AB-83CF-E7EF865DC9F6} = {961EC18E-0301-4917-B91C-EBE7AFF6F7A7} + {59132A30-5C07-4617-836C-36D8CF621C6E} = {961EC18E-0301-4917-B91C-EBE7AFF6F7A7} + EndGlobalSection EndGlobal From 1483d9873b7f0bf368d3d14d8adb341ed6e07acd Mon Sep 17 00:00:00 2001 From: joemphilips Date: Fri, 5 Apr 2019 14:10:41 +0900 Subject: [PATCH 18/40] Rebame Miniscript.dll -> NBitcoin.Miniscript.dll --- NBitcoin.Miniscript.Tests/FSharp/AssemblyInfo.fs | 8 ++++---- NBitcoin.Miniscript/AssemblyInfo.fs | 8 ++++---- NBitcoin.Miniscript/Miniscript.fs | 1 - NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj | 2 +- NBitcoin/NBitcoin.csproj | 8 ++++---- 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/NBitcoin.Miniscript.Tests/FSharp/AssemblyInfo.fs b/NBitcoin.Miniscript.Tests/FSharp/AssemblyInfo.fs index a93ffd02f5..a80a2cc957 100644 --- a/NBitcoin.Miniscript.Tests/FSharp/AssemblyInfo.fs +++ b/NBitcoin.Miniscript.Tests/FSharp/AssemblyInfo.fs @@ -3,8 +3,8 @@ namespace System open System.Reflection -[] -[] +[] +[] [] [] @@ -17,10 +17,10 @@ do () module internal AssemblyVersionInformation = [] - let AssemblyTitle = "FNBitcoin.Tests" + let AssemblyTitle = "NBitcoin.Miniscript.Tests.FSharp" [] - let AssemblyProduct = "FNBitcoin" + let AssemblyProduct = "NBitcoin.Miniscript.Tests.FSharp" [] let AssemblyVersion = "0.1.0" diff --git a/NBitcoin.Miniscript/AssemblyInfo.fs b/NBitcoin.Miniscript/AssemblyInfo.fs index bdd66da60e..a47d9e01f0 100644 --- a/NBitcoin.Miniscript/AssemblyInfo.fs +++ b/NBitcoin.Miniscript/AssemblyInfo.fs @@ -3,8 +3,8 @@ namespace System open System.Reflection -[] -[] +[] +[] [] [] @@ -18,10 +18,10 @@ do () module internal AssemblyVersionInformation = [] - let AssemblyTitle = "Miniscript" + let AssemblyTitle = "NBitcoin.Miniscript" [] - let AssemblyProduct = "Miniscript" + let AssemblyProduct = "NBitcoin.Miniscript" [] let AssemblyVersion = "0.1.0" diff --git a/NBitcoin.Miniscript/Miniscript.fs b/NBitcoin.Miniscript/Miniscript.fs index f832409b80..3c51508a65 100644 --- a/NBitcoin.Miniscript/Miniscript.fs +++ b/NBitcoin.Miniscript/Miniscript.fs @@ -12,7 +12,6 @@ open NBitcoin /// wrapper for top-level AST type Miniscript = Miniscript of T -[] module Miniscript = let fromAST (t : AST) : Result = match t.CastT() with diff --git a/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj b/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj index d24e3a4e11..eed5f51f74 100644 --- a/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj +++ b/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj @@ -27,4 +27,4 @@ - \ No newline at end of file + diff --git a/NBitcoin/NBitcoin.csproj b/NBitcoin/NBitcoin.csproj index 6ddee08112..25579948e6 100644 --- a/NBitcoin/NBitcoin.csproj +++ b/NBitcoin/NBitcoin.csproj @@ -96,21 +96,21 @@ - true + true - + true Always lib\netstandard2.0 + Include="..\NBitcoin.Miniscript\bin\$(Configuration)\netcoreapp2.1\NBitcoin.Miniscript.dll"> true Always lib\netcoreapp2.1 - + true Always lib\net461 From 80217a5a427d31965ecdda0dfbcb1663e57af418 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Sat, 6 Apr 2019 16:23:53 +0900 Subject: [PATCH 19/40] Restrict public-facing api in Miniscript as strict as possible --- .../CSharp/MiniscriptPSBTTests.cs | 25 +++++---- .../NBitcoin.Miniscript.Tests.CSharp.csproj | 11 +++- .../FSharp/MiniScriptDecompilerTests.fs | 5 +- .../FSharp/SatisfyTests.fs | 19 ++++++- NBitcoin.Miniscript/AssemblyInfo.fs | 4 ++ NBitcoin.Miniscript/Miniscript.fs | 56 ++++++++++++------- NBitcoin.Miniscript/MiniscriptAST.fs | 2 +- NBitcoin.Miniscript/MiniscriptCompiler.fs | 2 +- NBitcoin.Miniscript/MiniscriptDecompiler.fs | 10 ++-- .../NBitcoin.Miniscript.fsproj | 4 +- NBitcoin.Miniscript/Satisfy.fs | 48 ++++++++-------- 11 files changed, 122 insertions(+), 64 deletions(-) diff --git a/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs b/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs index d261cfa73a..ce722026ec 100644 --- a/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs +++ b/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs @@ -1,24 +1,29 @@ using System; -using NBitcoin.Miniscript; -using NBitcoin; +using System.Collections.Generic; using Xunit; -namespace NBitcoin.Tests + +namespace NBitcoin.Miniscript.Tests.CSharp { public class MiniscriptPSBTTests { + private Key privKey { get; set; } public MiniscriptPSBTTests() { + privkey = new NBitcoin.Key(); } - private TransactionSignature DummyKeyFn(PubKey key) => null; + [Fact] public void ShouldSatisfyMiniscript() { - var key = new NBitcoin.Key(); - var scriptStr = $"and(pk({key.PubKey}), time({new LockTime(10000)}))"; - var ms = NBitcoin.Miniscript.Miniscript.parseUnsafe(scriptStr); - Assert.NotNull(ms); + var scriptStr = $"and(pk({privKey.PubKey}), time({10000}))"; + var ms = Miniscript.parseUnsafe(scriptStr); + Assert.NotNull(ms); + + var sigDict = new Dictionary(); + var r1 = ms.Satisfy(sigDict); - var r1 = ms.Satisfy(DummyKeyFn, null, null); - } + + Assert. + } } } diff --git a/NBitcoin.Miniscript.Tests/CSharp/NBitcoin.Miniscript.Tests.CSharp.csproj b/NBitcoin.Miniscript.Tests/CSharp/NBitcoin.Miniscript.Tests.CSharp.csproj index ae7c6a5b7a..44df840b4d 100644 --- a/NBitcoin.Miniscript.Tests/CSharp/NBitcoin.Miniscript.Tests.CSharp.csproj +++ b/NBitcoin.Miniscript.Tests/CSharp/NBitcoin.Miniscript.Tests.CSharp.csproj @@ -1,20 +1,27 @@ - netcoreapp2.2 + netcoreapp2.1; false + - + + + + + + ..\..\NBitcoin.Miniscript\bin\Debug\netcoreapp2.1\NBitcoin.Miniscript.dll + diff --git a/NBitcoin.Miniscript.Tests/FSharp/MiniScriptDecompilerTests.fs b/NBitcoin.Miniscript.Tests/FSharp/MiniScriptDecompilerTests.fs index 8c423cf491..dd23a6f7aa 100644 --- a/NBitcoin.Miniscript.Tests/FSharp/MiniScriptDecompilerTests.fs +++ b/NBitcoin.Miniscript.Tests/FSharp/MiniScriptDecompilerTests.fs @@ -6,8 +6,9 @@ open NBitcoin open NBitcoin.Miniscript.Utils open NBitcoin.Miniscript.MiniscriptParser open NBitcoin.Miniscript.Tests.Generators -open NBitcoin.Miniscript.AST open NBitcoin.Miniscript +open NBitcoin.Miniscript.AST +open NBitcoin.Miniscript.Miniscript open NBitcoin.Miniscript.Utils.Parser open NBitcoin.Miniscript.Compiler open NBitcoin.Miniscript.Decompiler @@ -292,7 +293,7 @@ let tests2 = roundTripFromMiniScript input ] -let roundtripParserAndAST (parser: Parser<_, _>) (ast: AST) = +let private roundtripParserAndAST (parser: Parser<_, _>) (ast: AST) = let sc = ast.ToScript() let ops = sc.ToOps() |> Seq.toArray let initialState = {ops=ops;position=ops.Length - 1} diff --git a/NBitcoin.Miniscript.Tests/FSharp/SatisfyTests.fs b/NBitcoin.Miniscript.Tests/FSharp/SatisfyTests.fs index 1981741539..c69c9162a2 100644 --- a/NBitcoin.Miniscript.Tests/FSharp/SatisfyTests.fs +++ b/NBitcoin.Miniscript.Tests/FSharp/SatisfyTests.fs @@ -7,7 +7,7 @@ open NBitcoin.Miniscript [] let tests = testList "SatisfyTests" [ - ftestCase "case 1" <| fun _ -> + testCase "case 1" <| fun _ -> let key = NBitcoin.Key() let scriptStr = sprintf "and(pk(%s), time(%d))" (key.PubKey.ToString()) 10000u let ms = Miniscript.parseUnsafe scriptStr @@ -26,5 +26,22 @@ let tests = let dummyAge = LockTime 10001 let r3 = Satisfy.satisfyT (Some keyFn, None, Some dummyAge) t + Expect.isOk r3 "could not satisfy" + + ftestCase "case 1 using facade" <| fun _ -> + let key = NBitcoin.Key() + let scriptStr = sprintf "and(pk(%s), time(%d))" (key.PubKey.ToString()) 10000u + let ms = Miniscript.parseUnsafe scriptStr + let dummyKeyFn pk = None + let r1 = ms.Satisfy(?keyFn=Some(dummyKeyFn)) + let dummySig = TransactionSignature.Empty + + let keyFn (pk: PubKey) = if pk.Equals(key.PubKey) then Some(dummySig) else None + let r2 = ms.Satisfy(?keyFn=Some keyFn) + Expect.isError r2 "should not satisfy the time" + + let dummyAge = LockTime 10001u + let r3 = ms.Satisfy(?keyFn=Some keyFn, ?age=Some dummyAge) + Expect.isOk r3 "could not satisfy" ] diff --git a/NBitcoin.Miniscript/AssemblyInfo.fs b/NBitcoin.Miniscript/AssemblyInfo.fs index a47d9e01f0..48805d010d 100644 --- a/NBitcoin.Miniscript/AssemblyInfo.fs +++ b/NBitcoin.Miniscript/AssemblyInfo.fs @@ -2,6 +2,7 @@ namespace System open System.Reflection +open System.Runtime.CompilerServices [] [] @@ -14,6 +15,9 @@ open System.Reflection [] [] +[] +[] + do () module internal AssemblyVersionInformation = diff --git a/NBitcoin.Miniscript/Miniscript.fs b/NBitcoin.Miniscript/Miniscript.fs index 3c51508a65..e8797d80ee 100644 --- a/NBitcoin.Miniscript/Miniscript.fs +++ b/NBitcoin.Miniscript/Miniscript.fs @@ -1,6 +1,6 @@ namespace NBitcoin.Miniscript -open System +open System.Collections.Generic open System.Runtime.InteropServices open NBitcoin.Miniscript.AST @@ -10,51 +10,69 @@ open NBitcoin.Miniscript.Satisfy open NBitcoin /// wrapper for top-level AST -type Miniscript = Miniscript of T -module Miniscript = - let fromAST (t : AST) : Result = +module public Miniscript = + type Miniscript = private Miniscript of T + + let internal fromAST (t : AST) : Result = match t.CastT() with | Ok t -> Ok(Miniscript(t)) | o -> Error (sprintf "AST was not top-level (T) representation\n%A" o) - let fromASTUnsafe(t: AST) = + let internal fromASTUnsafe(t: AST) = match fromAST t with | Ok t -> t | Error e -> failwith e - let parse (s: string) = + [] + let public parse (s: string) = match s with | AbstractPolicy p -> (CompiledNode.FromPolicy p).Compile() |> fromAST | _ -> Error("failed to parse String policy") - let parseUnsafe (s: string) = + [] + let public parseUnsafe (s: string) = match parse s with | Ok m -> m | Error e -> failwith e - let toAST (m : Miniscript) = + let internal toAST (m : Miniscript) = match m with | Miniscript a -> TTree(a) - let fromScriptUnsafe (s : NBitcoin.Script) = + [] + let public fromScriptUnsafe (s : NBitcoin.Script) = let res = parseScriptUnsafe s match fromAST res with | Ok r -> r | Error e -> failwith e - let toScript (m : Miniscript) : Script = + let public toScript (m : Miniscript) : Script = let ast = toAST m ast.ToScript() - let satisfy (Miniscript t) (providers: ProviderSet) = + let public Satisfy (Miniscript t) (providers: ProviderSet) = satisfyT (providers) t -type Miniscript with - member this.ToScript() = Miniscript.toScript this - member this.ToAST() = Miniscript.toAST this - member this.Satisfy(nullableKeyFn: SignatureProvider option, - hashFn: PreImageProvider option, - age: LockTime option) = - let keyFn = if box nullableKeyFn = null then None else nullableKeyFn - Miniscript.satisfy this (keyFn, hashFn, age) + let private dictToFn (d: IDictionary<_ ,_>) k = + match d.TryGetValue k with + | (true, v) -> Some v + | (false, _) -> None + + type Miniscript with + member this.ToScript() = toScript this + member internal this.ToAST() = toAST this + /// Facade for F# + member this.Satisfy(?keyFn: SignatureProvider, + ?hashFn: PreImageProvider, + ?age: LockTime) = + Satisfy this (keyFn, hashFn, age) + + /// Facade for C# + member this.Satisfy([)>] sigDict: IDictionary, + [)>] hashDict: IDictionary, + [] age: uint32) = + let keyFn = if sigDict = null then None else Some(dictToFn sigDict) + let hashFn = if hashDict = null then None else Some(dictToFn hashDict) + let age2 = if age = 0u then None else Some(LockTime(age)) + this.Satisfy(?keyFn=keyFn, ?hashFn=hashFn, ?age=age2) \ No newline at end of file diff --git a/NBitcoin.Miniscript/MiniscriptAST.fs b/NBitcoin.Miniscript/MiniscriptAST.fs index 72a5afbc2f..d9deaff2a6 100644 --- a/NBitcoin.Miniscript/MiniscriptAST.fs +++ b/NBitcoin.Miniscript/MiniscriptAST.fs @@ -4,7 +4,7 @@ open NBitcoin open NBitcoin.Miniscript.Utils open System.Text -module AST = +module internal AST = // TODO: Use unativeint instead of uint? /// "E"xpression. takes more than one inputs from the stack, if it satisfies the condition, diff --git a/NBitcoin.Miniscript/MiniscriptCompiler.fs b/NBitcoin.Miniscript/MiniscriptCompiler.fs index e468205f56..807c602018 100644 --- a/NBitcoin.Miniscript/MiniscriptCompiler.fs +++ b/NBitcoin.Miniscript/MiniscriptCompiler.fs @@ -5,7 +5,7 @@ open NBitcoin.Miniscript.Utils open NBitcoin.Miniscript.MiniscriptParser open Miniscript.AST -module Compiler = +module internal Compiler = type CompiledNode = | Pk of NBitcoin.PubKey | Multi of uint32 * PubKey [] diff --git a/NBitcoin.Miniscript/MiniscriptDecompiler.fs b/NBitcoin.Miniscript/MiniscriptDecompiler.fs index ceef5fec2c..944ceda9d3 100644 --- a/NBitcoin.Miniscript/MiniscriptDecompiler.fs +++ b/NBitcoin.Miniscript/MiniscriptDecompiler.fs @@ -7,6 +7,7 @@ open Miniscript.AST /// Subset of Bitcoin Script which is used in Miniscript type Token = + private | BoolAnd | BoolOr | Add @@ -40,6 +41,7 @@ type Token = | Any type TokenCategory = + private | BoolAnd | BoolOr | Add @@ -203,7 +205,7 @@ type State = { position: int } -type TokenParser = Parser +type private TokenParser = Parser let nextToken state = if state.ops.Length - 1 < state.position then @@ -214,7 +216,7 @@ let nextToken state = newState, Some(tk) -module TokenParser = +module internal TokenParser = let pToken (cat: TokenCategory) = let name = sprintf "pToken %A" cat let innerFn state = @@ -640,12 +642,12 @@ module TokenParser = do pTImpl := SubExpressionParser >>= pTryCastToType TExpr do pFImpl := SubExpressionParser >>= pTryCastToType FExpr -let parseScript (sc: Script) = +let internal parseScript (sc: Script) = let ops = (sc.ToOps() |> Seq.toArray) let initialState = {ops=ops; position=ops.Length - 1} run TokenParser.SubExpressionParser initialState |> Result.map(fst) -let parseScriptUnsafe sc = +let internal parseScriptUnsafe sc = match parseScript sc with | Ok r -> r | Error e -> failwith (printParserError e) diff --git a/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj b/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj index eed5f51f74..8d7673d343 100644 --- a/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj +++ b/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj @@ -20,10 +20,10 @@ - + - + diff --git a/NBitcoin.Miniscript/Satisfy.fs b/NBitcoin.Miniscript/Satisfy.fs index 6ed3527f88..ffe2724b05 100644 --- a/NBitcoin.Miniscript/Satisfy.fs +++ b/NBitcoin.Miniscript/Satisfy.fs @@ -1,32 +1,36 @@ namespace NBitcoin.Miniscript -module Satisfy = - open NBitcoin.Miniscript.AST - open NBitcoin.Miniscript.Utils - open NBitcoin - open System +open NBitcoin +open System + +type SignatureProvider = PubKey -> TransactionSignature option +type PreImageHash = uint256 +type PreImage = uint256 +type PreImageProvider = PreImageHash -> PreImage option - type SignatureProvider = PubKey -> TransactionSignature option - type PreImageHash = PreImagehash of uint256 - type PreImage = PreImage of uint256 - type PreImageProvider = PreImageHash -> PreImage +type ProviderSet = (SignatureProvider option * PreImageProvider option * LockTime option) - type ProviderSet = (SignatureProvider option * PreImageProvider option * LockTime option) +type CSVOffset = BlockHeight of uint32 | UnixTime of DateTimeOffset +type FailureCase = + | MissingSig of PubKey list + | NotMatured of CSVOffset + | LockTimeTypeMismatch + | Nested of FailureCase list + | CurrentTimeNotSpecified - type CSVOffset = BlockHeight of uint32 | UnixTime of DateTimeOffset - type FailureCase = - | MissingSig of PubKey list - | NotMatured of CSVOffset - | LockTimeTypeMismatch - | Nested of FailureCase list - | CurrentTimeNotSpecified +type SatisfiedItem = + | PreImage of byte[] + | Signature of TransactionSignature + | RawPush of byte[] - type SatisfiedItem = - | PreImage of byte[] - | Signature of TransactionSignature - | RawPush of byte[] +type SatisfactionResult = Result - type SatisfactionResult = Result + +module internal Satisfy = + open NBitcoin.Miniscript.AST + open NBitcoin.Miniscript.Utils + open NBitcoin + open System let satisfyCost (res: SatisfiedItem list): int = failwith "" From 1d6803b94aa3e2d026c0f0d5a1c975fa4b59889e Mon Sep 17 00:00:00 2001 From: joemphilips Date: Sun, 7 Apr 2019 01:46:52 +0900 Subject: [PATCH 20/40] Enable to pass `Func` to `Satisfy` --- .../CSharp/MiniscriptPSBTTests.cs | 44 ++++++-- .../NBitcoin.Miniscript.Tests.CSharp.csproj | 18 ++-- NBitcoin.Miniscript.Tests/CSharp/Utils.cs | 101 ++++++++++++++++++ NBitcoin.Miniscript/Miniscript.fs | 17 ++- .../NBitcoin.Miniscript.fsproj | 1 + NBitcoin.Miniscript/Utils/FuncConversion.fs | 8 ++ 6 files changed, 172 insertions(+), 17 deletions(-) create mode 100644 NBitcoin.Miniscript.Tests/CSharp/Utils.cs create mode 100644 NBitcoin.Miniscript/Utils/FuncConversion.fs diff --git a/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs b/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs index ce722026ec..a200c79fc5 100644 --- a/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs +++ b/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs @@ -1,29 +1,61 @@ using System; using System.Collections.Generic; using Xunit; +using NBitcoin.BIP174; +using Microsoft.FSharp.Core; namespace NBitcoin.Miniscript.Tests.CSharp { public class MiniscriptPSBTTests { - private Key privKey { get; set; } + private Key[] privKeys { get; } + public Network Network { get; } + public MiniscriptPSBTTests() { - privkey = new NBitcoin.Key(); + privKeys = new[] { new Key(), new Key(), new Key(), new Key() }; + Network = Network.Main; + } + + private TransactionSignature GetDummySig() + { + var hash = new uint256(); + var ecdsa = privKeys[0].Sign(hash); + return new TransactionSignature(ecdsa, SigHash.All); } [Fact] - public void ShouldSatisfyMiniscript() + public void ShouldSatisfyMiniscript() { - var scriptStr = $"and(pk({privKey.PubKey}), time({10000}))"; - var ms = Miniscript.parseUnsafe(scriptStr); + var policyStr = $"aor(and(pk({privKeys[0].PubKey}), time({10000})), multi(2, {privKeys[0].PubKey}, {privKeys[1].PubKey})"; + var ms = Miniscript.ParseUnsafe(policyStr); Assert.NotNull(ms); + // Giving empty dict. var sigDict = new Dictionary(); var r1 = ms.Satisfy(sigDict); + // Assert.NotNull(r1); + Console.WriteLine(r1); + // Giving lambda + var someSig = FSharpOption.Some(GetDummySig()); + var r2 = ms.Satisfy(pk => pk == privKeys[0].PubKey ? someSig : null); + // Assert.NotNull(r2); + Console.WriteLine(r2); + } - Assert. + [Fact] + public void ShouldSatisfyPSBT() + { + var policyStr = $"aor(and(pk({privKeys[0].PubKey}), time({10000})), multi(2, {privKeys[0].PubKey}, {privKeys[1].PubKey})"; + var ms = Miniscript.ParseUnsafe(policyStr); + var script = ms.ToScript(); + var funds = Utils.CreateDummyFunds(Network, privKeys, script); + var tx = Utils.CreateTxToSpendFunds(funds, privKeys, script, false, false); + var psbt = PSBT.FromTransaction(tx) + .AddTransactions(funds) + .AddScript(script); + // psbt.Satisfy(); } } } diff --git a/NBitcoin.Miniscript.Tests/CSharp/NBitcoin.Miniscript.Tests.CSharp.csproj b/NBitcoin.Miniscript.Tests/CSharp/NBitcoin.Miniscript.Tests.CSharp.csproj index 44df840b4d..4e0d357e0b 100644 --- a/NBitcoin.Miniscript.Tests/CSharp/NBitcoin.Miniscript.Tests.CSharp.csproj +++ b/NBitcoin.Miniscript.Tests/CSharp/NBitcoin.Miniscript.Tests.CSharp.csproj @@ -1,7 +1,7 @@ - netcoreapp2.1; + net461;netcoreapp2.2; false @@ -13,15 +13,15 @@ - - - + + + - - - - ..\..\NBitcoin.Miniscript\bin\Debug\netcoreapp2.1\NBitcoin.Miniscript.dll - + + + + ..\..\NBitcoin.Miniscript\bin\Debug\netcoreapp2.1\NBitcoin.Miniscript.dll + diff --git a/NBitcoin.Miniscript.Tests/CSharp/Utils.cs b/NBitcoin.Miniscript.Tests/CSharp/Utils.cs new file mode 100644 index 0000000000..11ad2656d2 --- /dev/null +++ b/NBitcoin.Miniscript.Tests/CSharp/Utils.cs @@ -0,0 +1,101 @@ +using System; +using NBitcoin; +using System.Collections.Generic; +using System.Linq; + +namespace NBitcoin.Miniscript.Tests.CSharp +{ + /// + /// Copied and tweaked from `NBitcoin.Tests.PSBTTests` . + /// It could possibly reference the method directly, but we prefered to keep the libraries separated. + /// + public class Utils + { + public Utils() + { + } + static internal ICoin[] DummyFundsToCoins(IEnumerable txs, Script redeem, Key key) + { + var barecoins = txs.SelectMany(tx => tx.Outputs.AsCoins()).ToArray(); + var coins = new ICoin[barecoins.Length]; + coins[0] = barecoins[0]; + coins[1] = barecoins[1]; + coins[2] = redeem != null ? new ScriptCoin(barecoins[2], redeem) : barecoins[2]; // p2sh + coins[3] = redeem != null ? new ScriptCoin(barecoins[3], redeem) : barecoins[3]; // p2wsh + coins[4] = key != null ? new ScriptCoin(barecoins[4], key.PubKey.WitHash.ScriptPubKey) : barecoins[4]; // p2sh-p2wpkh + coins[5] = redeem != null ? new ScriptCoin(barecoins[5], redeem) : barecoins[5]; // p2sh-p2wsh + return coins; + } + + static internal Transaction CreateTxToSpendFunds( + Transaction[] funds, + Key[] keys, + Script redeem, + bool withScript, + bool sign + ) + { + var tx = Network.Main.CreateTransaction(); + tx.Inputs.Add(new OutPoint(funds[0].GetHash(), 0)); // p2pkh + tx.Inputs.Add(new OutPoint(funds[0].GetHash(), 1)); // p2wpkh + tx.Inputs.Add(new OutPoint(funds[1].GetHash(), 0)); // p2sh + tx.Inputs.Add(new OutPoint(funds[2].GetHash(), 0)); // p2wsh + tx.Inputs.Add(new OutPoint(funds[3].GetHash(), 0)); // p2sh-p2wpkh + tx.Inputs.Add(new OutPoint(funds[4].GetHash(), 0)); // p2sh-p2wsh + + var dummyOut = new TxOut(Money.Coins(0.599m), keys[0]); + tx.Outputs.Add(dummyOut); + + if (withScript) + { + // OP_0 + three empty signatures + var emptySigPush = new Script(OpcodeType.OP_0, OpcodeType.OP_0, OpcodeType.OP_0, OpcodeType.OP_0); + tx.Inputs[0].ScriptSig = PayToPubkeyHashTemplate.Instance.GenerateScriptSig(null, keys[0].PubKey); + tx.Inputs[1].WitScript = PayToWitPubKeyHashTemplate.Instance.GenerateWitScript(null, keys[0].PubKey); + tx.Inputs[2].ScriptSig = emptySigPush + Op.GetPushOp(redeem.ToBytes()); + tx.Inputs[3].WitScript = PayToWitScriptHashTemplate.Instance.GenerateWitScript(emptySigPush, redeem); + tx.Inputs[4].ScriptSig = new Script(Op.GetPushOp(keys[0].PubKey.WitHash.ScriptPubKey.ToBytes())); + tx.Inputs[4].WitScript = PayToWitPubKeyHashTemplate.Instance.GenerateWitScript(null, keys[0].PubKey); + tx.Inputs[5].ScriptSig = new Script(Op.GetPushOp(redeem.WitHash.ScriptPubKey.ToBytes())); + tx.Inputs[5].WitScript = PayToWitScriptHashTemplate.Instance.GenerateWitScript(emptySigPush, redeem); + } + + if (sign) + { + tx.Sign(keys, DummyFundsToCoins(funds, redeem, keys[0])); + } + return tx; + } + + static public Transaction[] CreateDummyFunds(Network network, Key[] keyForOutput, Script redeem) + { + // 1. p2pkh and p2wpkh + var tx1 = network.CreateTransaction(); + tx1.Inputs.Add(TxIn.CreateCoinbase(200)); + tx1.Outputs.Add(new TxOut(Money.Coins(0.1m), keyForOutput[0].PubKey.Hash)); + tx1.Outputs.Add(new TxOut(Money.Coins(0.1m), keyForOutput[0].PubKey.WitHash)); + + // 2. p2sh-multisig + var tx2 = network.CreateTransaction(); + tx2.Inputs.Add(TxIn.CreateCoinbase(200)); + tx2.Outputs.Add(new TxOut(Money.Coins(0.1m), redeem.Hash)); + + // 3. p2wsh + var tx3 = network.CreateTransaction(); + tx3.Inputs.Add(TxIn.CreateCoinbase(200)); + tx3.Outputs.Add(new TxOut(Money.Coins(0.1m), redeem.WitHash)); + + // 4. p2sh-p2wpkh + var tx4 = network.CreateTransaction(); + tx4.Inputs.Add(TxIn.CreateCoinbase(200)); + tx4.Outputs.Add(new TxOut(Money.Coins(0.1m), keyForOutput[0].PubKey.WitHash.ScriptPubKey.Hash)); + + // 5. p2sh-p2wsh + var tx5 = network.CreateTransaction(); + tx5.Inputs.Add(TxIn.CreateCoinbase(200)); + tx5.Outputs.Add(new TxOut(Money.Coins(0.1m), redeem.WitHash.ScriptPubKey.Hash.ScriptPubKey)); + return new Transaction[] { tx1, tx2, tx3, tx4, tx5 }; + + } + } +} diff --git a/NBitcoin.Miniscript/Miniscript.fs b/NBitcoin.Miniscript/Miniscript.fs index e8797d80ee..ed48be2462 100644 --- a/NBitcoin.Miniscript/Miniscript.fs +++ b/NBitcoin.Miniscript/Miniscript.fs @@ -1,5 +1,6 @@ namespace NBitcoin.Miniscript +open System open System.Collections.Generic open System.Runtime.InteropServices @@ -59,6 +60,10 @@ module public Miniscript = | (true, v) -> Some v | (false, _) -> None + let private toFSharpFunc<'TIn, 'TOut> (f: Func<'TIn, 'TOut>) = + fun input -> + let v = f.Invoke(input) + if isNull (box v) then None else Some v type Miniscript with member this.ToScript() = toScript this member internal this.ToAST() = toAST this @@ -69,10 +74,18 @@ module public Miniscript = Satisfy this (keyFn, hashFn, age) /// Facade for C# + member this.Satisfy([)>] keyFn: Func, + [)>] hashFn: Func, + [] age: uint32) = + let maybeFsharpKeyFn = if isNull keyFn then None else Some(toFSharpFunc(keyFn)) + let maybeFsharpHashFn = if isNull hashFn then None else Some(toFSharpFunc(hashFn)) + let maybeAge = if age = 0u then None else Some(LockTime(age)) + this.Satisfy(?keyFn=maybeFsharpKeyFn, ?hashFn=maybeFsharpHashFn, ?age=maybeAge) + member this.Satisfy([)>] sigDict: IDictionary, [)>] hashDict: IDictionary, [] age: uint32) = - let keyFn = if sigDict = null then None else Some(dictToFn sigDict) - let hashFn = if hashDict = null then None else Some(dictToFn hashDict) + let keyFn = if isNull sigDict then None else Some(dictToFn sigDict) + let hashFn = if isNull hashDict then None else Some(dictToFn hashDict) let age2 = if age = 0u then None else Some(LockTime(age)) this.Satisfy(?keyFn=keyFn, ?hashFn=hashFn, ?age=age2) \ No newline at end of file diff --git a/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj b/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj index 8d7673d343..d5bfcd59b4 100644 --- a/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj +++ b/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj @@ -9,6 +9,7 @@ + diff --git a/NBitcoin.Miniscript/Utils/FuncConversion.fs b/NBitcoin.Miniscript/Utils/FuncConversion.fs new file mode 100644 index 0000000000..5cc228891e --- /dev/null +++ b/NBitcoin.Miniscript/Utils/FuncConversion.fs @@ -0,0 +1,8 @@ +namespace NBitcoin.Miniscript.Utils +open System +open System.Runtime.CompilerServices + +[] +module FuncExtension = + type public CSharpFun = + static member internal ToFSharpFunc<'a> (action: Action<'a>) = fun a -> action.Invoke(a) From cb21a500e6ecf1881cf79612bf8124f8f8589c26 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Sun, 7 Apr 2019 02:57:00 +0900 Subject: [PATCH 21/40] Fix bug when satisfying LockTime --- .../CSharp/MiniscriptPSBTTests.cs | 11 +++++--- NBitcoin.Miniscript/Miniscript.fs | 1 + NBitcoin.Miniscript/Satisfy.fs | 26 ++++++++++--------- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs b/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs index a200c79fc5..1d7f5e2d9a 100644 --- a/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs +++ b/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs @@ -38,10 +38,13 @@ public void ShouldSatisfyMiniscript() Console.WriteLine(r1); // Giving lambda - var someSig = FSharpOption.Some(GetDummySig()); - var r2 = ms.Satisfy(pk => pk == privKeys[0].PubKey ? someSig : null); - // Assert.NotNull(r2); - Console.WriteLine(r2); + Func dummyKeyFn = pk => pk == privKeys[0].PubKey ? GetDummySig() : null; + var r2 = ms.Satisfy(dummyKeyFn); + Assert.True(r2.IsError); + + var r3 = ms.Satisfy(dummyKeyFn, null, 10001u); + + Assert.True(r3.IsOk); } [Fact] diff --git a/NBitcoin.Miniscript/Miniscript.fs b/NBitcoin.Miniscript/Miniscript.fs index ed48be2462..a6c34e02ae 100644 --- a/NBitcoin.Miniscript/Miniscript.fs +++ b/NBitcoin.Miniscript/Miniscript.fs @@ -82,6 +82,7 @@ module public Miniscript = let maybeAge = if age = 0u then None else Some(LockTime(age)) this.Satisfy(?keyFn=maybeFsharpKeyFn, ?hashFn=maybeFsharpHashFn, ?age=maybeAge) + /// Facade for C# (Might be unnecessary) member this.Satisfy([)>] sigDict: IDictionary, [)>] hashDict: IDictionary, [] age: uint32) = diff --git a/NBitcoin.Miniscript/Satisfy.fs b/NBitcoin.Miniscript/Satisfy.fs index ffe2724b05..279cf4b667 100644 --- a/NBitcoin.Miniscript/Satisfy.fs +++ b/NBitcoin.Miniscript/Satisfy.fs @@ -13,13 +13,14 @@ type ProviderSet = (SignatureProvider option * PreImageProvider option * LockTim type CSVOffset = BlockHeight of uint32 | UnixTime of DateTimeOffset type FailureCase = | MissingSig of PubKey list + | MissingHash of uint256 | NotMatured of CSVOffset | LockTimeTypeMismatch | Nested of FailureCase list | CurrentTimeNotSpecified type SatisfiedItem = - | PreImage of byte[] + | PreImage of uint256 | Signature of TransactionSignature | RawPush of byte[] @@ -65,21 +66,22 @@ module internal Satisfy = Error(MissingSig(sigNotFoundPks)) let satisfyHashEqual (maybeHashFn: PreImageProvider option) h = - failwith "" + match maybeHashFn with + | None -> Error(MissingHash(h)) + | Some fn -> + match fn h with + | None -> Error(MissingHash h) + | Some v -> Ok([PreImage(v)]) let satisfyCSVCore (age: LockTime) (t: LockTime) = - let offset = t.Value - age.Value - if - (age.IsHeightLock && t.IsHeightLock) - then - if (offset > 0u) then - Error(NotMatured(BlockHeight offset)) + let offset = (int32 t.Value) - (int32 age.Value) + if (age.IsHeightLock && t.IsHeightLock) then + if (offset > 0) then + Error(NotMatured(BlockHeight (uint32 offset))) else Ok([]) - else if - (age.IsTimeLock && t.IsTimeLock) - then - if (offset > 0u) then + else if (age.IsTimeLock && t.IsTimeLock) then + if (offset > 0) then Error(NotMatured(UnixTime(DateTimeOffset.FromUnixTimeSeconds(int64 (offset))))) else Ok([]) From 55b2e3d4acdf4ca1cc83b4907cc774f3ef25f64a Mon Sep 17 00:00:00 2001 From: joemphilips Date: Mon, 8 Apr 2019 21:02:00 +0900 Subject: [PATCH 22/40] Update MiniscriptPSBTTests.cs * Tweak public api visibility in Miniscript. * Add Unsafe version to Miniscript.Satisfy * Implement satisfyCost in `Satisfy.fs` s Please enter the commit message for your changes. Lines starting --- .../CSharp/MiniscriptPSBTTests.cs | 43 +++++++----- .../FSharp/Generators/Lib.fs | 2 +- .../FSharp/Generators/Policy.fs | 2 +- .../FSharp/MiniScriptCompilerTests.fs | 1 + .../FSharp/MiniScriptParserTests.fs | 1 + .../FSharp/SatisfyTests.fs | 5 +- NBitcoin.Miniscript/Miniscript.fs | 67 +++++++++++++------ NBitcoin.Miniscript/MiniscriptParser.fs | 59 ++++++++-------- .../NBitcoin.Miniscript.fsproj | 1 + NBitcoin.Miniscript/PSBTExtension.fs | 10 +++ NBitcoin.Miniscript/Satisfy.fs | 9 ++- 11 files changed, 130 insertions(+), 70 deletions(-) create mode 100644 NBitcoin.Miniscript/PSBTExtension.fs diff --git a/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs b/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs index 1d7f5e2d9a..f206357b75 100644 --- a/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs +++ b/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs @@ -2,7 +2,9 @@ using System.Collections.Generic; using Xunit; using NBitcoin.BIP174; -using Microsoft.FSharp.Core; +using NBitcoin.Miniscript; +using static NBitcoin.Miniscript.AbstractPolicy; +using System.Linq; namespace NBitcoin.Miniscript.Tests.CSharp { @@ -28,30 +30,41 @@ private TransactionSignature GetDummySig() public void ShouldSatisfyMiniscript() { var policyStr = $"aor(and(pk({privKeys[0].PubKey}), time({10000})), multi(2, {privKeys[0].PubKey}, {privKeys[1].PubKey})"; - var ms = Miniscript.ParseUnsafe(policyStr); + var ms = Miniscript.FromStringUnsafe(policyStr); Assert.NotNull(ms); - // Giving empty dict. - var sigDict = new Dictionary(); - var r1 = ms.Satisfy(sigDict); - // Assert.NotNull(r1); - Console.WriteLine(r1); + // We can write AbstractPolicy directly instead of using string representation. + var pubKeys = privKeys.Select(p => p.PubKey).Take(2).ToArray(); + var policy = new AsymmetricOr( + new And( + new AbstractPolicy.Key(privKeys[0].PubKey), + new Time(new LockTime(10000)) + ), + new Multi(2, pubKeys) + ); + // And it is EqualityComparable by default. :) + var msFromPolicy = Miniscript.FromPolicyUnsafe(policy); + Assert.Equal(ms, msFromPolicy); - // Giving lambda - Func dummyKeyFn = pk => pk == privKeys[0].PubKey ? GetDummySig() : null; - var r2 = ms.Satisfy(dummyKeyFn); - Assert.True(r2.IsError); - - var r3 = ms.Satisfy(dummyKeyFn, null, 10001u); + Func dummySignatureProvider = + pk => pk == privKeys[0].PubKey ? GetDummySig() : null; + Assert.Throws(() => ms.SatisfyUnsafe(dummySignatureProvider)); + Assert.Throws(() => ms.SatisfyUnsafe(dummySignatureProvider, null, 9999u)); + var r3 = ms.Satisfy(dummySignatureProvider, null, 10000u); Assert.True(r3.IsOk); + + Func dummySignatureProvider2 = + pk => (pk == privKeys[0].PubKey || pk == privKeys[1].PubKey) ? GetDummySig() : null; + var r5 = ms.Satisfy(dummySignatureProvider2); + Assert.True(r5.IsOk); } [Fact] public void ShouldSatisfyPSBT() { - var policyStr = $"aor(and(pk({privKeys[0].PubKey}), time({10000})), multi(2, {privKeys[0].PubKey}, {privKeys[1].PubKey})"; - var ms = Miniscript.ParseUnsafe(policyStr); + var policyStr = $"aor(and(pk({privKeys[0].PubKey}), time({10000})), multi(2, {privKeys[0].PubKey}, {privKeys[1].PubKey}))"; + var ms = Miniscript.FromStringUnsafe(policyStr); var script = ms.ToScript(); var funds = Utils.CreateDummyFunds(Network, privKeys, script); var tx = Utils.CreateTxToSpendFunds(funds, privKeys, script, false, false); diff --git a/NBitcoin.Miniscript.Tests/FSharp/Generators/Lib.fs b/NBitcoin.Miniscript.Tests/FSharp/Generators/Lib.fs index 2c9b781b5f..483b63d8aa 100644 --- a/NBitcoin.Miniscript.Tests/FSharp/Generators/Lib.fs +++ b/NBitcoin.Miniscript.Tests/FSharp/Generators/Lib.fs @@ -1,7 +1,7 @@ namespace NBitcoin.Miniscript.Tests.Generators open FsCheck -open NBitcoin.Miniscript.MiniscriptParser +open NBitcoin.Miniscript open NBitcoin.Miniscript.Tests.Generators.Policy type Generators = diff --git a/NBitcoin.Miniscript.Tests/FSharp/Generators/Policy.fs b/NBitcoin.Miniscript.Tests/FSharp/Generators/Policy.fs index 7763d20705..d94ee0d9f7 100644 --- a/NBitcoin.Miniscript.Tests/FSharp/Generators/Policy.fs +++ b/NBitcoin.Miniscript.Tests/FSharp/Generators/Policy.fs @@ -2,7 +2,7 @@ namespace NBitcoin.Miniscript.Tests.Generators module internal Policy = open FsCheck - open NBitcoin.Miniscript.MiniscriptParser + open NBitcoin.Miniscript open NBitcoin.Miniscript.Tests.Generators.NBitcoin let multiContentsGen = gen { let! n = Gen.choose (1, 20) |> Gen.map uint32 diff --git a/NBitcoin.Miniscript.Tests/FSharp/MiniScriptCompilerTests.fs b/NBitcoin.Miniscript.Tests/FSharp/MiniScriptCompilerTests.fs index c22b46d4f6..5fadf13817 100644 --- a/NBitcoin.Miniscript.Tests/FSharp/MiniScriptCompilerTests.fs +++ b/NBitcoin.Miniscript.Tests/FSharp/MiniScriptCompilerTests.fs @@ -4,6 +4,7 @@ open Expecto open Expecto.Logging open Expecto.Logging.Message open NBitcoin.Miniscript.Tests.Generators +open NBitcoin.Miniscript open NBitcoin.Miniscript.Compiler open NBitcoin.Miniscript.MiniscriptParser diff --git a/NBitcoin.Miniscript.Tests/FSharp/MiniScriptParserTests.fs b/NBitcoin.Miniscript.Tests/FSharp/MiniScriptParserTests.fs index e35513c9be..1e0f62d0e5 100644 --- a/NBitcoin.Miniscript.Tests/FSharp/MiniScriptParserTests.fs +++ b/NBitcoin.Miniscript.Tests/FSharp/MiniScriptParserTests.fs @@ -1,5 +1,6 @@ module MiniScriptParserTests +open NBitcoin.Miniscript open NBitcoin.Miniscript.MiniscriptParser open Expecto open Expecto.Logging diff --git a/NBitcoin.Miniscript.Tests/FSharp/SatisfyTests.fs b/NBitcoin.Miniscript.Tests/FSharp/SatisfyTests.fs index c69c9162a2..f325cc9f8e 100644 --- a/NBitcoin.Miniscript.Tests/FSharp/SatisfyTests.fs +++ b/NBitcoin.Miniscript.Tests/FSharp/SatisfyTests.fs @@ -3,6 +3,7 @@ module SatisfyTests open Expecto open NBitcoin open NBitcoin.Miniscript +open NBitcoin.Miniscript [] let tests = @@ -10,7 +11,7 @@ let tests = testCase "case 1" <| fun _ -> let key = NBitcoin.Key() let scriptStr = sprintf "and(pk(%s), time(%d))" (key.PubKey.ToString()) 10000u - let ms = Miniscript.parseUnsafe scriptStr + let ms = Miniscript.fromStringUnsafe scriptStr let t = ms.ToAST().CastTUnsafe() let dummyKeyFn pk = None @@ -31,7 +32,7 @@ let tests = ftestCase "case 1 using facade" <| fun _ -> let key = NBitcoin.Key() let scriptStr = sprintf "and(pk(%s), time(%d))" (key.PubKey.ToString()) 10000u - let ms = Miniscript.parseUnsafe scriptStr + let ms = Miniscript.fromStringUnsafe scriptStr let dummyKeyFn pk = None let r1 = ms.Satisfy(?keyFn=Some(dummyKeyFn)) let dummySig = TransactionSignature.Empty diff --git a/NBitcoin.Miniscript/Miniscript.fs b/NBitcoin.Miniscript/Miniscript.fs index a6c34e02ae..70699fceb8 100644 --- a/NBitcoin.Miniscript/Miniscript.fs +++ b/NBitcoin.Miniscript/Miniscript.fs @@ -8,10 +8,22 @@ open NBitcoin.Miniscript.AST open NBitcoin.Miniscript.Decompiler open NBitcoin.Miniscript.Compiler open NBitcoin.Miniscript.Satisfy +open NBitcoin.Miniscript.MiniscriptParser open NBitcoin -/// wrapper for top-level AST +/// Exception types to enable consumer to use try-catch style handling instead of `Result` +/// Why we define it here instead of putting it into `Utis` ? +/// Because a code for core logics should never throw `Exception` and instead use Result, +/// And we must basically restrict public-facing interfaces to this file. +type MiniscriptException(msg: string, ex: exn) = + inherit Exception(msg, ex) + new (msg) = MiniscriptException(msg, null) + +type MiniscriptSatisfyException(reason: FailureCase, ex: exn) = + inherit MiniscriptException(sprintf "Failed to satisfy, got: %A" reason, ex) + new (reason) = MiniscriptSatisfyException(reason, null) +/// wrapper for top-level AST module public Miniscript = type Miniscript = private Miniscript of T @@ -25,18 +37,29 @@ module public Miniscript = | Ok t -> t | Error e -> failwith e - [] - let public parse (s: string) = + [] + let public fromPolicy(p: AbstractPolicy) = + (CompiledNode.FromPolicy p).Compile() |> fromAST + + [] + let public fromPolicyUnsafe(p: AbstractPolicy) = + match fromPolicy p with + | Ok p -> p + | Error e -> failwith e + + [] + let public fromString (s: string) = match s with - | AbstractPolicy p -> (CompiledNode.FromPolicy p).Compile() |> fromAST + | AbstractPolicy p -> fromPolicy p | _ -> Error("failed to parse String policy") - [] - let public parseUnsafe (s: string) = - match parse s with + [] + let public fromStringUnsafe (s: string) = + match fromString s with | Ok m -> m | Error e -> failwith e + let internal toAST (m : Miniscript) = match m with | Miniscript a -> TTree(a) @@ -48,11 +71,12 @@ module public Miniscript = | Ok r -> r | Error e -> failwith e - let public toScript (m : Miniscript) : Script = + let private toScript (m : Miniscript) : Script = let ast = toAST m ast.ToScript() - let public Satisfy (Miniscript t) (providers: ProviderSet) = + [] + let public satisfy (Miniscript t) (providers: ProviderSet) = satisfyT (providers) t let private dictToFn (d: IDictionary<_ ,_>) k = @@ -71,9 +95,23 @@ module public Miniscript = member this.Satisfy(?keyFn: SignatureProvider, ?hashFn: PreImageProvider, ?age: LockTime) = - Satisfy this (keyFn, hashFn, age) + satisfy this (keyFn, hashFn, age) + member this.SatisfyUnsafe(?keyFn: SignatureProvider, + ?hashFn: PreImageProvider, + ?age: LockTime) = + match satisfy this (keyFn, hashFn, age) with + | Ok item -> item + | Error e -> raise (MiniscriptSatisfyException(e)) /// Facade for C# + member this.SatisfyUnsafe([)>] keyFn: Func, + [)>] hashFn: Func, + [] age: uint32) = + let maybeFsharpKeyFn = if isNull keyFn then None else Some(toFSharpFunc(keyFn)) + let maybeFsharpHashFn = if isNull hashFn then None else Some(toFSharpFunc(hashFn)) + let maybeAge = if age = 0u then None else Some(LockTime(age)) + this.SatisfyUnsafe(?keyFn=maybeFsharpKeyFn, ?hashFn=maybeFsharpHashFn, ?age=maybeAge) + member this.Satisfy([)>] keyFn: Func, [)>] hashFn: Func, [] age: uint32) = @@ -81,12 +119,3 @@ module public Miniscript = let maybeFsharpHashFn = if isNull hashFn then None else Some(toFSharpFunc(hashFn)) let maybeAge = if age = 0u then None else Some(LockTime(age)) this.Satisfy(?keyFn=maybeFsharpKeyFn, ?hashFn=maybeFsharpHashFn, ?age=maybeAge) - - /// Facade for C# (Might be unnecessary) - member this.Satisfy([)>] sigDict: IDictionary, - [)>] hashDict: IDictionary, - [] age: uint32) = - let keyFn = if isNull sigDict then None else Some(dictToFn sigDict) - let hashFn = if isNull hashDict then None else Some(dictToFn hashDict) - let age2 = if age = 0u then None else Some(LockTime(age)) - this.Satisfy(?keyFn=keyFn, ?hashFn=hashFn, ?age=age2) \ No newline at end of file diff --git a/NBitcoin.Miniscript/MiniscriptParser.fs b/NBitcoin.Miniscript/MiniscriptParser.fs index 33f64103ff..42c12270d9 100644 --- a/NBitcoin.Miniscript/MiniscriptParser.fs +++ b/NBitcoin.Miniscript/MiniscriptParser.fs @@ -4,37 +4,36 @@ open NBitcoin open System.Text.RegularExpressions open System -[] -module MiniscriptParser = - type AbstractPolicy = - | Key of PubKey - | Multi of uint32 * PubKey [] - | Hash of uint256 - | Time of NBitcoin.LockTime - | Threshold of uint32 * AbstractPolicy [] - | And of AbstractPolicy * AbstractPolicy - | Or of AbstractPolicy * AbstractPolicy - | AsymmetricOr of AbstractPolicy * AbstractPolicy - override this.ToString() = - match this with - | Key k1 -> sprintf "pk(%s)" (string (k1.ToHex())) - | Multi(m, klist) -> - klist - |> Seq.map (fun k -> string (k.ToHex())) - |> Seq.reduce (fun a b -> sprintf "%s,%s" a b) - |> sprintf "multi(%d,%s)" m - | Hash h -> sprintf "hash(%s)" (string (h.ToString())) - | Time t -> sprintf "time(%d)" (t.Value) - | Threshold(m, plist) -> - plist - |> Array.map (fun p -> p.ToString()) - |> Array.reduce (fun a b -> sprintf "%s,%s" a b) - |> sprintf "thres(%d,%s)" m - | And(p1, p2) -> sprintf "and(%s,%s)" (p1.ToString()) (p2.ToString()) - | Or(p1, p2) -> sprintf "or(%s,%s)" (p1.ToString()) (p2.ToString()) - | AsymmetricOr(p1, p2) -> - sprintf "aor(%s,%s)" (p1.ToString()) (p2.ToString()) +type AbstractPolicy = + | Key of PubKey + | Multi of uint32 * PubKey [] + | Hash of uint256 + | Time of NBitcoin.LockTime + | Threshold of uint32 * AbstractPolicy [] + | And of AbstractPolicy * AbstractPolicy + | Or of AbstractPolicy * AbstractPolicy + | AsymmetricOr of AbstractPolicy * AbstractPolicy + override this.ToString() = + match this with + | Key k1 -> sprintf "pk(%s)" (string (k1.ToHex())) + | Multi(m, klist) -> + klist + |> Seq.map (fun k -> string (k.ToHex())) + |> Seq.reduce (fun a b -> sprintf "%s,%s" a b) + |> sprintf "multi(%d,%s)" m + | Hash h -> sprintf "hash(%s)" (string (h.ToString())) + | Time t -> sprintf "time(%d)" (t.Value) + | Threshold(m, plist) -> + plist + |> Array.map (fun p -> p.ToString()) + |> Array.reduce (fun a b -> sprintf "%s,%s" a b) + |> sprintf "thres(%d,%s)" m + | And(p1, p2) -> sprintf "and(%s,%s)" (p1.ToString()) (p2.ToString()) + | Or(p1, p2) -> sprintf "or(%s,%s)" (p1.ToString()) (p2.ToString()) + | AsymmetricOr(p1, p2) -> + sprintf "aor(%s,%s)" (p1.ToString()) (p2.ToString()) +module MiniscriptParser = // parser let quoted = Regex(@"\((.*)\)") diff --git a/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj b/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj index d5bfcd59b4..7475338079 100644 --- a/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj +++ b/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj @@ -17,6 +17,7 @@ + diff --git a/NBitcoin.Miniscript/PSBTExtension.fs b/NBitcoin.Miniscript/PSBTExtension.fs new file mode 100644 index 0000000000..64a78b1ff5 --- /dev/null +++ b/NBitcoin.Miniscript/PSBTExtension.fs @@ -0,0 +1,10 @@ +namespace NBitcoin.Miniscript +open System +open System.Runtime.CompilerServices +open NBitcoin.BIP174 + +[] +type PSBTExtension = + [] + static member Finalize(psbt: PSBT) = + () \ No newline at end of file diff --git a/NBitcoin.Miniscript/Satisfy.fs b/NBitcoin.Miniscript/Satisfy.fs index 279cf4b667..381dd00f13 100644 --- a/NBitcoin.Miniscript/Satisfy.fs +++ b/NBitcoin.Miniscript/Satisfy.fs @@ -23,17 +23,22 @@ type SatisfiedItem = | PreImage of uint256 | Signature of TransactionSignature | RawPush of byte[] + with member this.ToBytes(): byte array = + match this with + | RawPush i -> i + | PreImage i -> i.ToBytes() + | Signature i -> i.ToBytes() type SatisfactionResult = Result - module internal Satisfy = open NBitcoin.Miniscript.AST open NBitcoin.Miniscript.Utils open NBitcoin open System - let satisfyCost (res: SatisfiedItem list): int = failwith "" + let satisfyCost (res: SatisfiedItem list): int = + res |> List.fold(fun a b -> 1 + b.ToBytes().Length + a) 0 let (>>=) xR f = Result.bind f xR From 2da2ff30b5975099cd0384a43fc63c5085f3f3c1 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Mon, 8 Apr 2019 21:06:59 +0900 Subject: [PATCH 23/40] Remove all unnecessary files from Miniscript --- NBitcoin.Miniscript.Tests/FSharp/Contract.fs | 27 ------------------- .../NBitcoin.Miniscript.Tests.FSharp.fsproj | 1 - NBitcoin.Miniscript/Contract.fs | 5 ---- NBitcoin.Miniscript/Library.fs | 5 ---- .../NBitcoin.Miniscript.fsproj | 2 -- NBitcoin.Miniscript/Program.fs | 8 ------ 6 files changed, 48 deletions(-) delete mode 100644 NBitcoin.Miniscript.Tests/FSharp/Contract.fs delete mode 100644 NBitcoin.Miniscript/Contract.fs delete mode 100644 NBitcoin.Miniscript/Library.fs delete mode 100644 NBitcoin.Miniscript/Program.fs diff --git a/NBitcoin.Miniscript.Tests/FSharp/Contract.fs b/NBitcoin.Miniscript.Tests/FSharp/Contract.fs deleted file mode 100644 index e6348406d7..0000000000 --- a/NBitcoin.Miniscript.Tests/FSharp/Contract.fs +++ /dev/null @@ -1,27 +0,0 @@ -module ContractTests - -open Expecto -open FsCheck -open NBitcoin -(* -[] -let contractTests = - testList "Contracts" [ - testProperty "Multisig" <| fun (m: PositiveInt) (n: PositiveInt) -> - if (m < n) && n.Get < 16 then - let pubkeys = seq { for i in 0..n.Get do - yield Key().PubKey } - |> Seq.toArray - let verifiableScript = Multisig(m.Get, pubkeys) - let res = verify { - let! v = verifiableScript - return v - } - match res with - | Verified s -> Expect.isTrue true "ok" - | Failed s -> Expect.isTrue false "failed" - else - Expect.isTrue true "skip" - ] - -*) diff --git a/NBitcoin.Miniscript.Tests/FSharp/NBitcoin.Miniscript.Tests.FSharp.fsproj b/NBitcoin.Miniscript.Tests/FSharp/NBitcoin.Miniscript.Tests.FSharp.fsproj index bbdae1ea4b..1550bcc077 100644 --- a/NBitcoin.Miniscript.Tests/FSharp/NBitcoin.Miniscript.Tests.FSharp.fsproj +++ b/NBitcoin.Miniscript.Tests/FSharp/NBitcoin.Miniscript.Tests.FSharp.fsproj @@ -9,7 +9,6 @@ - diff --git a/NBitcoin.Miniscript/Contract.fs b/NBitcoin.Miniscript/Contract.fs deleted file mode 100644 index 0a76ace110..0000000000 --- a/NBitcoin.Miniscript/Contract.fs +++ /dev/null @@ -1,5 +0,0 @@ -namespace NBitcoin.Miniscript.Contract - - -type ScriptBuilder() = - member this.Hoge () = () \ No newline at end of file diff --git a/NBitcoin.Miniscript/Library.fs b/NBitcoin.Miniscript/Library.fs deleted file mode 100644 index 588410c945..0000000000 --- a/NBitcoin.Miniscript/Library.fs +++ /dev/null @@ -1,5 +0,0 @@ -namespace NBitcoin.Miniscript - -module Say = - let nothing name = name |> ignore - let hello name = sprintf "Hello %s" name diff --git a/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj b/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj index 7475338079..3a6b28955f 100644 --- a/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj +++ b/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj @@ -18,8 +18,6 @@ - - diff --git a/NBitcoin.Miniscript/Program.fs b/NBitcoin.Miniscript/Program.fs deleted file mode 100644 index 09b1452193..0000000000 --- a/NBitcoin.Miniscript/Program.fs +++ /dev/null @@ -1,8 +0,0 @@ -// Learn more about F# at http://fsharp.org - -open System - -[] -let main argv = - printfn "Hello World from F#!" - 0 // return an integer exit code From c871a27333b345794b9d884a21507d01b3550dfd Mon Sep 17 00:00:00 2001 From: joemphilips Date: Mon, 8 Apr 2019 21:18:21 +0900 Subject: [PATCH 24/40] Fix bug in E.Or --- NBitcoin.Miniscript/MiniscriptCompiler.fs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NBitcoin.Miniscript/MiniscriptCompiler.fs b/NBitcoin.Miniscript/MiniscriptCompiler.fs index 807c602018..cdcdebe494 100644 --- a/NBitcoin.Miniscript/MiniscriptCompiler.fs +++ b/NBitcoin.Miniscript/MiniscriptCompiler.fs @@ -612,7 +612,7 @@ module internal Compiler = { parent = FTree (F.CascadeOr - (le_cas.ast.CastEUnsafe(), + (le_cond_par.ast.CastEUnsafe(), rv.ast.CastVUnsafe())) left = le_cas right = rv @@ -620,7 +620,7 @@ module internal Compiler = { parent = FTree (F.CascadeOr - (re_cas.ast.CastEUnsafe(), + (re_cond_par.ast.CastEUnsafe(), lv.ast.CastVUnsafe())) left = re_cas right = lv From d645d9d2ee05883a526467998981244551cde041 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Wed, 10 Apr 2019 05:14:05 +0900 Subject: [PATCH 25/40] Update Miniscript.PSBTExtensions * Move every PSBT-related tests to NBitcoin.Tests.Miniscript * Add reference from NBitcoin.Tests.Miniscript to NBitcoin.Tests|TestFramework * Append TargetFrameworks `netcoreapp2.1;netcoreapp2.2` to TestFramework * Rewrite PSBT.Finalize as an extension method in NBitcoin.Miniscript --- NBitcoin.Miniscript.Tests/CSharp/AssertEx.cs | 50 +++ .../CSharp/MiniscriptPSBTTests.cs | 2 +- .../NBitcoin.Miniscript.Tests.CSharp.csproj | 7 + .../CSharp}/PSBTTests.cs | 6 +- .../CSharp/RPCClientTests.cs | 313 ++++++++++++++++++ .../CSharp}/data/psbt.json | 0 .../FSharp/SatisfyTests.fs | 20 +- NBitcoin.Miniscript/Miniscript.fs | 9 +- NBitcoin.Miniscript/PSBTExtension.fs | 160 ++++++++- NBitcoin.Miniscript/Satisfy.fs | 2 + NBitcoin.Miniscript/Utils/Lib.fs | 7 +- .../NBitcoin.TestFramework.csproj | 2 +- NBitcoin.Tests/Comparer.cs | 14 - NBitcoin.Tests/NBitcoin.Tests.csproj | 3 - NBitcoin.Tests/PSBTComparer.cs | 12 + .../PropertyTest/PSBTSerializationTest.cs | 1 - NBitcoin.Tests/RPCClientTests.cs | 294 ---------------- NBitcoin/BIP174/PartiallySignedTransaction.cs | 5 +- NBitcoin/Properties/AssemblyInfo.cs | 2 + 19 files changed, 579 insertions(+), 330 deletions(-) create mode 100644 NBitcoin.Miniscript.Tests/CSharp/AssertEx.cs rename {NBitcoin.Tests => NBitcoin.Miniscript.Tests/CSharp}/PSBTTests.cs (99%) create mode 100644 NBitcoin.Miniscript.Tests/CSharp/RPCClientTests.cs rename {NBitcoin.Tests => NBitcoin.Miniscript.Tests/CSharp}/data/psbt.json (100%) delete mode 100644 NBitcoin.Tests/Comparer.cs create mode 100644 NBitcoin.Tests/PSBTComparer.cs diff --git a/NBitcoin.Miniscript.Tests/CSharp/AssertEx.cs b/NBitcoin.Miniscript.Tests/CSharp/AssertEx.cs new file mode 100644 index 0000000000..33048e9ac1 --- /dev/null +++ b/NBitcoin.Miniscript.Tests/CSharp/AssertEx.cs @@ -0,0 +1,50 @@ +using NBitcoin.Crypto; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace NBitcoin.Miniscript.Tests.CSharp +{ + class AssertEx + { + [DebuggerHidden] + internal static void Error(string msg) + { + Assert.False(true, msg); + } + [DebuggerHidden] + internal static void Equal(T actual, T expected) + { + Assert.Equal(expected, actual); + } + [DebuggerHidden] + internal static void CollectionEquals(T[] actual, T[] expected) + { + if(actual.Length != expected.Length) + Assert.False(true, "Actual.Length(" + actual.Length + ") != Expected.Length(" + expected.Length + ")"); + + for(int i = 0; i < actual.Length; i++) + { + if(!Object.Equals(actual[i], expected[i])) + Assert.False(true, "Actual[" + i + "](" + actual[i] + ") != Expected[" + i + "](" + expected[i] + ")"); + } + } + + [DebuggerHidden] + internal static void StackEquals(ContextStack stack1, ContextStack stack2) + { + var hash1 = stack1.Select(o => Hashes.Hash256(o)).ToArray(); + var hash2 = stack2.Select(o => Hashes.Hash256(o)).ToArray(); + AssertEx.CollectionEquals(hash1, hash2); + } + + internal static void CollectionEquals(System.Collections.BitArray bitArray, int p) + { + throw new NotImplementedException(); + } + } +} diff --git a/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs b/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs index f206357b75..5a8d348548 100644 --- a/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs +++ b/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs @@ -42,7 +42,7 @@ public void ShouldSatisfyMiniscript() ), new Multi(2, pubKeys) ); - // And it is EqualityComparable by default. :) + // And it is EqualityComparable by default. 🎉 var msFromPolicy = Miniscript.FromPolicyUnsafe(policy); Assert.Equal(ms, msFromPolicy); diff --git a/NBitcoin.Miniscript.Tests/CSharp/NBitcoin.Miniscript.Tests.CSharp.csproj b/NBitcoin.Miniscript.Tests/CSharp/NBitcoin.Miniscript.Tests.CSharp.csproj index 4e0d357e0b..0830109c98 100644 --- a/NBitcoin.Miniscript.Tests/CSharp/NBitcoin.Miniscript.Tests.CSharp.csproj +++ b/NBitcoin.Miniscript.Tests/CSharp/NBitcoin.Miniscript.Tests.CSharp.csproj @@ -15,6 +15,8 @@ + + @@ -24,4 +26,9 @@ + + + PreserveNewest + + diff --git a/NBitcoin.Tests/PSBTTests.cs b/NBitcoin.Miniscript.Tests/CSharp/PSBTTests.cs similarity index 99% rename from NBitcoin.Tests/PSBTTests.cs rename to NBitcoin.Miniscript.Tests/CSharp/PSBTTests.cs index 47edb2cdaa..3c8f36c285 100644 --- a/NBitcoin.Tests/PSBTTests.cs +++ b/NBitcoin.Miniscript.Tests/CSharp/PSBTTests.cs @@ -1,5 +1,7 @@ +using static NBitcoin.Utils; using NBitcoin.BIP174; using Xunit; +using NBitcoin.Tests; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System.IO; @@ -7,11 +9,11 @@ using NBitcoin.DataEncoders; using System.Collections.Generic; using System.Linq; -using static NBitcoin.Tests.Comparer; using Xunit.Abstractions; -namespace NBitcoin.Tests +namespace NBitcoin.Miniscript.Tests.CSharp { + public class PSBTTests { private readonly ITestOutputHelper Output; diff --git a/NBitcoin.Miniscript.Tests/CSharp/RPCClientTests.cs b/NBitcoin.Miniscript.Tests/CSharp/RPCClientTests.cs new file mode 100644 index 0000000000..74ea6c57b8 --- /dev/null +++ b/NBitcoin.Miniscript.Tests/CSharp/RPCClientTests.cs @@ -0,0 +1,313 @@ +using Xunit; +using NBitcoin; +using NBitcoin.Tests; +using NBitcoin.RPC; +using NBitcoin.BIP174; +using System; +using System.Linq; +using System.Collections.Generic; +using Xunit.Abstractions; + +namespace NBitcoin.Miniscript.Tests.CSharp +{ + public class RPCClientTests + { + internal PSBTComparer PSBTComparerInstance { get; } + public ITestOutputHelper Output { get; } + + public RPCClientTests(ITestOutputHelper output) + { + PSBTComparerInstance = new PSBTComparer(); + + Output = output; + } + [Fact] + public void ShouldCreatePSBTAcceptableByRPCAsExpected() + { + using (var builder = NodeBuilderEx.Create()) + { + var node = builder.CreateNode(); + node.Start(); + var client = node.CreateRPCClient(); + + var keys = new Key[] { new Key(), new Key(), new Key() }; + var redeem = PayToMultiSigTemplate.Instance.GenerateScriptPubKey(3, keys.Select(ki => ki.PubKey).ToArray()); + var funds = PSBTTests.CreateDummyFunds(Network.TestNet, keys, redeem); + + // case1: PSBT from already fully signed tx + var tx = PSBTTests.CreateTxToSpendFunds(funds, keys, redeem, true, true); + // PSBT without previous outputs but with finalized_script_witness will throw an error. + var psbt = PSBT.FromTransaction(tx.Clone(), true); + Assert.Throws(() => psbt.ToBase64()); + + // after adding coins, will not throw an error. + psbt.AddCoins(funds.SelectMany(f => f.Outputs.AsCoins()).ToArray()); + CheckPSBTIsAcceptableByRealRPC(psbt.ToBase64(), client); + + // but if we use rpc to convert tx to psbt, it will discard input scriptSig and ScriptWitness. + // So it will be acceptable by any other rpc. + psbt = PSBT.FromTransaction(tx.Clone()); + CheckPSBTIsAcceptableByRealRPC(psbt.ToBase64(), client); + + // case2: PSBT from tx with script (but without signatures) + tx = PSBTTests.CreateTxToSpendFunds(funds, keys, redeem, true, false); + psbt = PSBT.FromTransaction(tx, true); + // it has witness_script but has no prevout so it will throw an error. + Assert.Throws(() => psbt.ToBase64()); + // after adding coins, will not throw error. + psbt.AddCoins(funds.SelectMany(f => f.Outputs.AsCoins()).ToArray()); + CheckPSBTIsAcceptableByRealRPC(psbt.ToBase64(), client); + + // case3: PSBT from tx without script nor signatures. + tx = PSBTTests.CreateTxToSpendFunds(funds, keys, redeem, false, false); + psbt = PSBT.FromTransaction(tx, true); + // This time, it will not throw an error at the first place. + // Since sanity check for witness input will not complain about witness-script-without-witnessUtxo + CheckPSBTIsAcceptableByRealRPC(psbt.ToBase64(), client); + + var dummyKey = new Key(); + var dummyScript = new Script("OP_DUP " + "OP_HASH160 " + Op.GetPushOp(dummyKey.PubKey.Hash.ToBytes()) + " OP_EQUALVERIFY"); + + // even after adding coins and scripts ... + var psbtWithCoins = psbt.Clone().AddCoins(funds.SelectMany(f => f.Outputs.AsCoins()).ToArray()); + CheckPSBTIsAcceptableByRealRPC(psbtWithCoins.ToBase64(), client); + psbtWithCoins.AddScript(redeem); + CheckPSBTIsAcceptableByRealRPC(psbtWithCoins.ToBase64(), client); + var tmp = psbtWithCoins.Clone().AddScript(dummyScript); // should not change with dummyScript + Assert.Equal(psbtWithCoins, tmp, PSBTComparerInstance); + // or txs and scripts. + var psbtWithTXs = psbt.Clone().AddTransactions(funds); + CheckPSBTIsAcceptableByRealRPC(psbtWithTXs.ToBase64(), client); + psbtWithTXs.AddScript(redeem); + CheckPSBTIsAcceptableByRealRPC(psbtWithTXs.ToBase64(), client); + tmp = psbtWithTXs.Clone().AddScript(dummyScript); + Assert.Equal(psbtWithTXs, tmp, PSBTComparerInstance); + + // Let's don't forget about hd KeyPath + psbtWithTXs.AddKeyPath(keys[0].PubKey, Tuple.Create((uint)1234, KeyPath.Parse("m/1'/2/3"))); + psbtWithTXs.AddPathTo(3, keys[1].PubKey, 4321, KeyPath.Parse("m/3'/2/1")); + psbtWithTXs.AddPathTo(0, keys[1].PubKey, 4321, KeyPath.Parse("m/3'/2/1"), false); + CheckPSBTIsAcceptableByRealRPC(psbtWithTXs.ToBase64(), client); + + // What about after adding some signatures? + psbtWithTXs.SignAll(keys); + CheckPSBTIsAcceptableByRealRPC(psbtWithTXs.ToBase64(), client); + tmp = psbtWithTXs.Clone().SignAll(dummyKey); // Try signing with unrelated key should not change anything + Assert.Equal(psbtWithTXs, tmp, PSBTComparerInstance); + // And finalization? + psbtWithTXs.Finalize(); + CheckPSBTIsAcceptableByRealRPC(psbtWithTXs.ToBase64(), client); + } + return; + } + + /// + /// Just Check if the psbt is acceptable by bitcoin core rpc. + /// + /// + /// + private void CheckPSBTIsAcceptableByRealRPC(string base64, RPCClient client) + => client.SendCommand(RPCOperations.decodepsbt, base64); + + [Fact] + public void ShouldWalletProcessPSBTAndExtractMempoolAcceptableTX() + { + using (var builder = NodeBuilderEx.Create()) + { + var node = builder.CreateNode(); + node.Start(); + + var client = node.CreateRPCClient(); + + // ensure the wallet has whole kinds of coins ... + var addr = client.GetNewAddress(); + client.GenerateToAddress(101, addr); + addr = client.GetNewAddress(new GetNewAddressRequest() { AddressType = AddressType.Bech32 }); + client.SendToAddress(addr, Money.Coins(15)); + addr = client.GetNewAddress(new GetNewAddressRequest() { AddressType = AddressType.P2SHSegwit }); + client.SendToAddress(addr, Money.Coins(15)); + var tmpaddr = new Key(); + client.GenerateToAddress(1, tmpaddr.PubKey.GetAddress(node.Network)); + + // case 1: irrelevant psbt. + var keys = new Key[] { new Key(), new Key(), new Key() }; + var redeem = PayToMultiSigTemplate.Instance.GenerateScriptPubKey(3, keys.Select(ki => ki.PubKey).ToArray()); + var funds = PSBTTests.CreateDummyFunds(Network.TestNet, keys, redeem); + var tx = PSBTTests.CreateTxToSpendFunds(funds, keys, redeem, true, true); + var psbt = PSBT.FromTransaction(tx, true) + .AddTransactions(funds) + .AddScript(redeem); + var case1Result = client.WalletProcessPSBT(psbt); + // nothing must change for the psbt unrelated to the wallet. + Assert.Equal(psbt, case1Result.PSBT, PSBTComparerInstance); + + // case 2: psbt relevant to the wallet. (but already finalized) + var kOut = new Key(); + tx = builder.Network.CreateTransaction(); + tx.Outputs.Add(new TxOut(Money.Coins(45), kOut)); // This has to be big enough since the wallet must use whole kinds of address. + var fundTxResult = client.FundRawTransaction(tx); + Assert.Equal(3, fundTxResult.Transaction.Inputs.Count); + var psbtFinalized = PSBT.FromTransaction(fundTxResult.Transaction, true); + var result = client.WalletProcessPSBT(psbtFinalized, false); + Assert.False(result.PSBT.CanExtractTX()); + result = client.WalletProcessPSBT(psbtFinalized, true); + Assert.True(result.PSBT.CanExtractTX()); + + // case 3a: psbt relevant to the wallet (and not finalized) + var spendableCoins = client.ListUnspent().Where(c => c.IsSpendable).Select(c => c.AsCoin()); + tx = builder.Network.CreateTransaction(); + foreach (var coin in spendableCoins) + tx.Inputs.Add(coin.Outpoint); + tx.Outputs.Add(new TxOut(Money.Coins(45), kOut)); + var psbtUnFinalized = PSBT.FromTransaction(tx, true); + + var type = SigHash.All; + // unsigned + result = client.WalletProcessPSBT(psbtUnFinalized, false, type, bip32derivs: true); + Assert.False(result.Complete); + Assert.False(result.PSBT.CanExtractTX()); + var ex2 = Assert.Throws( + () => result.PSBT.Finalize() + ); + var errors2 = ex2.InnerExceptions; + Assert.NotEmpty(errors2); + foreach (var psbtin in result.PSBT.Inputs) + { + Assert.Equal(SigHash.Undefined, psbtin.SighashType); + Assert.NotEmpty(psbtin.HDKeyPaths); + } + + // signed + result = client.WalletProcessPSBT(psbtUnFinalized, true, type); + // does not throw + result.PSBT.Finalize(); + + var txResult = result.PSBT.ExtractTX(); + var acceptResult = client.TestMempoolAccept(txResult, true); + Assert.True(acceptResult.IsAllowed, acceptResult.RejectReason); + } + } + + // refs: https://github.com/bitcoin/bitcoin/blob/df73c23f5fac031cc9b2ec06a74275db5ea322e3/doc/psbt.md#workflows + // with 2 difference. + // 1. one user (David) do not use bitcoin core (only NBitcoin) + // 2. 4-of-4 instead of 2-of-3 + // 3. In version 0.17, `importmulti` can not handle witness script so only p2sh are considered here. TODO: fix + [Fact] + public void ShouldPerformMultisigProcessingWithCore() + { + using (var builder = NodeBuilderEx.Create()) + { + if (!builder.NodeImplementation.Version.Contains("0.17")) + throw new Exception("Test must be updated!"); + var nodeAlice = builder.CreateNode(); + var nodeBob = builder.CreateNode(); + var nodeCarol = builder.CreateNode(); + var nodeFunder = builder.CreateNode(); + var david = new Key(); + builder.StartAll(); + + // prepare multisig script and watch with node. + var nodes = new CoreNode[] { nodeAlice, nodeBob, nodeCarol }; + var clients = nodes.Select(n => n.CreateRPCClient()).ToArray(); + var addresses = clients.Select(c => c.GetNewAddress()); + var addrInfos = addresses.Select((a, i) => clients[i].GetAddressInfo(a)); + var pubkeys = new List { david.PubKey }; + pubkeys.AddRange(addrInfos.Select(i => i.PubKey).ToArray()); + var script = PayToMultiSigTemplate.Instance.GenerateScriptPubKey(4, pubkeys.ToArray()); + var aMultiP2SH = script.Hash.ScriptPubKey; + // var aMultiP2WSH = script.WitHash.ScriptPubKey; + // var aMultiP2SH_P2WSH = script.WitHash.ScriptPubKey.Hash.ScriptPubKey; + var multiAddresses = new BitcoinAddress[] { aMultiP2SH.GetDestinationAddress(builder.Network) }; + var importMultiObject = new ImportMultiAddress[] { + new ImportMultiAddress() + { + ScriptPubKey = new ImportMultiAddress.ScriptPubKeyObject(multiAddresses[0]), + RedeemScript = script.ToHex(), + Internal = true, + }, + /* + new ImportMultiAddress() + { + ScriptPubKey = new ImportMultiAddress.ScriptPubKeyObject(aMultiP2WSH), + RedeemScript = script.ToHex(), + Internal = true, + }, + new ImportMultiAddress() + { + ScriptPubKey = new ImportMultiAddress.ScriptPubKeyObject(aMultiP2SH_P2WSH), + RedeemScript = script.WitHash.ScriptPubKey.ToHex(), + Internal = true, + }, + new ImportMultiAddress() + { + ScriptPubKey = new ImportMultiAddress.ScriptPubKeyObject(aMultiP2SH_P2WSH), + RedeemScript = script.ToHex(), + Internal = true, + } + */ + }; + + for (var i = 0; i < clients.Length; i++) + { + var c = clients[i]; + Output.WriteLine($"Importing for {i}"); + c.ImportMulti(importMultiObject, false); + } + + // pay from funder + nodeFunder.Generate(103); + var funderClient = nodeFunder.CreateRPCClient(); + funderClient.SendToAddress(aMultiP2SH, Money.Coins(40)); + // funderClient.SendToAddress(aMultiP2WSH, Money.Coins(40)); + // funderClient.SendToAddress(aMultiP2SH_P2WSH, Money.Coins(40)); + nodeFunder.Generate(1); + foreach (var n in nodes) + { + nodeFunder.Sync(n, true); + } + + // pay from multisig address + // first carol creates psbt + var carol = clients[2]; + // check if we have enough balance + var info = carol.GetBlockchainInfoAsync().Result; + Assert.Equal((ulong)104, info.Blocks); + var balance = carol.GetBalance(0, true); + // Assert.Equal(Money.Coins(120), balance); + Assert.Equal(Money.Coins(40), balance); + + var aSend = new Key().PubKey.GetAddress(nodeAlice.Network); + var outputs = new Dictionary(); + outputs.Add(aSend, Money.Coins(10)); + var fundOptions = new FundRawTransactionOptions() { SubtractFeeFromOutputs = new int[] { 0 }, IncludeWatching = true }; + PSBT psbt = carol.WalletCreateFundedPSBT(null, outputs, 0, fundOptions).PSBT; + psbt = carol.WalletProcessPSBT(psbt).PSBT; + + // second, Bob checks and process psbt. + var bob = clients[1]; + Assert.Contains(multiAddresses, a => + psbt.Inputs.Any(psbtin => psbtin.WitnessUtxo?.ScriptPubKey == a.ScriptPubKey) || + psbt.Inputs.Any(psbtin => (bool)psbtin.NonWitnessUtxo?.Outputs.Any(o => a.ScriptPubKey == o.ScriptPubKey)) + ); + var psbt1 = bob.WalletProcessPSBT(psbt.Clone()).PSBT; + + // at the same time, David may do the ; + psbt.SignAll(david); + var alice = clients[0]; + var psbt2 = alice.WalletProcessPSBT(psbt).PSBT; + + // not enough signatures + Assert.Throws(() => psbt.Finalize()); + + // So let's combine. + var psbtCombined = psbt1.Combine(psbt2); + + // Finally, anyone can finalize and broadcast the psbt. + var tx = psbtCombined.Finalize().ExtractTX(); + var result = alice.TestMempoolAccept(tx); + Assert.True(result.IsAllowed, result.RejectReason); + } + } + } + } \ No newline at end of file diff --git a/NBitcoin.Tests/data/psbt.json b/NBitcoin.Miniscript.Tests/CSharp/data/psbt.json similarity index 100% rename from NBitcoin.Tests/data/psbt.json rename to NBitcoin.Miniscript.Tests/CSharp/data/psbt.json diff --git a/NBitcoin.Miniscript.Tests/FSharp/SatisfyTests.fs b/NBitcoin.Miniscript.Tests/FSharp/SatisfyTests.fs index f325cc9f8e..f90c9047ef 100644 --- a/NBitcoin.Miniscript.Tests/FSharp/SatisfyTests.fs +++ b/NBitcoin.Miniscript.Tests/FSharp/SatisfyTests.fs @@ -4,11 +4,12 @@ open Expecto open NBitcoin open NBitcoin.Miniscript open NBitcoin.Miniscript +open System.Linq [] let tests = - testList "SatisfyTests" [ - testCase "case 1" <| fun _ -> + testList "Miniscript.Satisfy" [ + testCase "Should Satisfy simple script" <| fun _ -> let key = NBitcoin.Key() let scriptStr = sprintf "and(pk(%s), time(%d))" (key.PubKey.ToString()) 10000u let ms = Miniscript.fromStringUnsafe scriptStr @@ -29,7 +30,7 @@ let tests = Expect.isOk r3 "could not satisfy" - ftestCase "case 1 using facade" <| fun _ -> + testCase "Should Satisfy simple script from facade" <| fun _ -> let key = NBitcoin.Key() let scriptStr = sprintf "and(pk(%s), time(%d))" (key.PubKey.ToString()) 10000u let ms = Miniscript.fromStringUnsafe scriptStr @@ -45,4 +46,17 @@ let tests = let r3 = ms.Satisfy(?keyFn=Some keyFn, ?age=Some dummyAge) Expect.isOk r3 "could not satisfy" + + testCase "Should satisfy script generated from templates" <| fun _ -> + let roundtrip sc (ks: Key list) = + let ms = Miniscript.fromScriptUnsafe(sc) + let dummySig = TransactionSignature.Empty + let keyFn pk = if ((ks |> List.map(fun k -> k.PubKey)) |> List.contains(pk)) then Some dummySig else None + ms.SatisfyUnsafe(?keyFn=Some keyFn) |> ignore + () + + let k1, k2 = NBitcoin.Key(), NBitcoin.Key() + let pk1, pk2 = (k1.PubKey), (k2.PubKey) + let p2pkh = PayToPubkeyHashTemplate.Instance.GenerateScriptPubKey(pk1) + roundtrip p2pkh [k1] ] diff --git a/NBitcoin.Miniscript/Miniscript.fs b/NBitcoin.Miniscript/Miniscript.fs index 70699fceb8..424152a6c6 100644 --- a/NBitcoin.Miniscript/Miniscript.fs +++ b/NBitcoin.Miniscript/Miniscript.fs @@ -64,11 +64,14 @@ module public Miniscript = match m with | Miniscript a -> TTree(a) + [] + let public fromScript (s : NBitcoin.Script) = + parseScript s |> Result.mapError(fun e -> e.ToString()) >>= fromAST + [] let public fromScriptUnsafe (s : NBitcoin.Script) = - let res = parseScriptUnsafe s - match fromAST res with - | Ok r -> r + match fromScript s with + | Ok res -> res | Error e -> failwith e let private toScript (m : Miniscript) : Script = diff --git a/NBitcoin.Miniscript/PSBTExtension.fs b/NBitcoin.Miniscript/PSBTExtension.fs index 64a78b1ff5..c8815d68c8 100644 --- a/NBitcoin.Miniscript/PSBTExtension.fs +++ b/NBitcoin.Miniscript/PSBTExtension.fs @@ -1,10 +1,164 @@ namespace NBitcoin.Miniscript open System +open System.Linq open System.Runtime.CompilerServices +open System.Runtime.InteropServices +open NBitcoin +open NBitcoin.Miniscript.Utils open NBitcoin.BIP174 +type PSBTFinalizationException(msg: string, ex: exn) = + inherit System.Exception(msg, ex) + new (msg) = PSBTFinalizationException(msg, null) + [] type PSBTExtension = - [] - static member Finalize(psbt: PSBT) = - () \ No newline at end of file + static member private keyFn (sc: Script) (pk: PubKey): TransactionSignature = + if PayToPubkeyHashTemplate.Instance.CheckScriptPubKey(sc) then + null + else + null + + static member private tryCheckWitness + (hashFn: Func) + (age: uint32) + (psbt: PSBT) + (sigHash) + (dummyTX: Transaction) + (index: int) + (prevOut: TxOut) + (ctx: ScriptEvaluationContext) + (isP2SH: bool) + (spk: Script): Result = + let psbtin = psbt.Inputs.[index] + if PayToWitPubKeyHashTemplate.Instance.CheckScriptPubKey(spk) then + let sigPair = psbtin.PartialSigs.First() + let txSig = TransactionSignature(snd sigPair.Value, sigHash) + dummyTX.Inputs.[index].WitScript <- PayToWitPubKeyHashTemplate.Instance.GenerateWitScript(txSig, fst sigPair.Value) + let ss = if isP2SH then Script(Op.GetPushOp(spk.ToBytes())) else Script.Empty + if not (ctx.VerifyScript(ss, dummyTX, index, prevOut)) then + let errorMsg = sprintf "Script verification failed for p2wpkh %s" (ctx.Error.ToString()) + Error(PSBTFinalizationException(errorMsg)) + else + psbt.Inputs.[index].FinalScriptSig <- ss + psbt.Inputs.[index].FinalScriptWitness <- dummyTX.Inputs.[index].WitScript + Ok(psbt) + // p2wsh + else if PayToWitScriptHashTemplate.Instance.CheckScriptPubKey(spk) then + match Miniscript.fromScript psbtin.WitnessScript with + | Error msg -> + let errorMsg = "Failed to parse p2wsh as a Miniscript: " + msg + Error(PSBTFinalizationException(errorMsg)) + | Ok ms -> + match ms.Satisfy(PSBTExtension.keyFn psbtin.WitnessScript, hashFn, age) with + | Error fCase -> + let msg = sprintf "Failed to satisfy p2wsh script: %A" fCase + Error(PSBTFinalizationException(msg)) + | Ok items -> + let pushes = items |> List.toArray |> Array.map(fun i -> i.ToPushOps()) + let ss = if isP2SH then Script(Op.GetPushOp(spk.ToBytes())) else Script.Empty + dummyTX.Inputs.[index].WitScript <- PayToWitScriptHashTemplate.Instance.GenerateWitScript(pushes, psbtin.WitnessScript) + if not (ctx.VerifyScript(ss, dummyTX, index, prevOut)) then + let msg = sprintf "Script verification failed for p2wsh %s" (ctx.Error.ToString()) + Error (PSBTFinalizationException(msg)) + else + psbt.Inputs.[index].FinalScriptWitness <- dummyTX.Inputs.[index].WitScript + psbt.Inputs.[index].FinalScriptSig <- ss + Ok(psbt) + else + Error(PSBTFinalizationException("Unknown type of script")) + + static member private isBareP2SH (psbtin: PSBTInput) = + (isNull psbtin.WitnessScript |> not) && (PayToWitTemplate.Instance.CheckScriptPubKey(psbtin.RedeemScript) |> not) + + [] + static member FinalizeIndex(psbt: PSBT, + index: int, + [)>] hashFn: Func, + [] age: uint32) = + let psbtin = psbt.Inputs.[index] + let txin: TxIn = psbt.tx.Inputs.[index] + if psbtin.IsFinalized() then + Ok(psbt) + else if isNull (psbtin.GetOutput(txin.PrevOut)) then + Error(PSBTFinalizationException("Can not fiinlize PSBTInput without utxo")) + else + let prevOut: TxOut = psbtin.GetOutput(txin.PrevOut) + let dummyTX = psbt.tx.Clone() + let sigHash = if psbtin.SighashType = SigHash.Undefined then SigHash.All else psbtin.SighashType + let mutable context = ScriptEvaluationContext() + context.SigHash <- sigHash + + let spk = prevOut.ScriptPubKey + let tryCheckWitness = PSBTExtension.tryCheckWitness hashFn age psbt sigHash dummyTX index prevOut context + if (PayToPubkeyHashTemplate.Instance.CheckScriptPubKey(spk)) then + let sigPair = psbtin.PartialSigs.First() + let txSig = TransactionSignature(snd sigPair.Value, sigHash) + let ss = PayToPubkeyHashTemplate.Instance.GenerateScriptSig(txSig, fst sigPair.Value) + if context.VerifyScript(ss, dummyTX, index, prevOut) then + psbtin.FinalScriptSig <- ss + Ok(psbt) + else + let errorMsg = sprintf "Script verification failed for p2pkh %s" (context.Error.ToString()) + Error(PSBTFinalizationException(errorMsg)) + else if spk.IsPayToScriptHash then + if PSBTExtension.isBareP2SH psbtin then + match Miniscript.fromScript psbtin.RedeemScript with + | Error msg -> + let msg = "Failed to parse p2sh as a Miniscript: " + msg + Error(PSBTFinalizationException(msg)) + | Ok ms -> + match ms.Satisfy(PSBTExtension.keyFn psbtin.RedeemScript, hashFn, age) with + | Error fCase -> + let msg = sprintf "Failed to satisfy p2sh redeem script: %A" fCase + Error(PSBTFinalizationException(msg)) + | Ok items -> + let pushes = items |> List.toArray |> Array.map(fun i -> i.ToPushOps()) + let ss = PayToScriptHashTemplate.Instance.GenerateScriptSig(pushes, psbtin.RedeemScript) + if context.VerifyScript(ss, dummyTX, index, prevOut) then + psbtin.FinalScriptSig <- ss + Ok(psbt) + else + let msg = sprintf "Script verification failed for p2sh %s" (context.Error.ToString()) + Error (PSBTFinalizationException(msg)) + else + tryCheckWitness true (psbtin.RedeemScript) + else + tryCheckWitness false (spk) + + + [] + static member FinalizeIndexUnsafe(psbt: PSBT, + index: int, + [)>] hashFn: Func, + [] age: uint32) = + + match psbt.FinalizeIndex(index, hashFn, age) with + | Ok psbt -> psbt + | Error e -> raise e + + // Finalize all inputs. + [] + static member Finalize(psbt: PSBT, + [)>] hashFn: Func, + [] age: uint32) = + let inline resultFolder (acc) (r): Result = + match acc, r with + | Error e1 , Error e2 -> Error((AggregateException(seq [(e1 :> exn); (e2 :> exn)])) :> exn) + | Error e, Ok _ -> Error e + | Ok _, Error e -> Error e + | Ok _, Ok psbt2 -> Ok psbt2 + + let r = seq { 0 .. psbt.Inputs.Count - 1 } + |> Seq.map(fun i -> psbt.FinalizeIndex(i, hashFn, age)) + |> Seq.map(Result.mapError(fun e -> e :> exn)) + |> Seq.reduce resultFolder + |> Result.mapError(fun e -> PSBTFinalizationException("Failed to finalize psbt", e)) + r + [] + static member FinalizeUnsafe(psbt: PSBT, + [)>] hashFn: Func, + [] age: uint32) = + match psbt.Finalize(hashFn, age) with + | Ok psbt -> psbt + | Error e -> raise e \ No newline at end of file diff --git a/NBitcoin.Miniscript/Satisfy.fs b/NBitcoin.Miniscript/Satisfy.fs index 381dd00f13..36f6886985 100644 --- a/NBitcoin.Miniscript/Satisfy.fs +++ b/NBitcoin.Miniscript/Satisfy.fs @@ -28,6 +28,8 @@ type SatisfiedItem = | RawPush i -> i | PreImage i -> i.ToBytes() | Signature i -> i.ToBytes() + member this.ToPushOps(): Op = + Op.GetPushOp(this.ToBytes()) type SatisfactionResult = Result diff --git a/NBitcoin.Miniscript/Utils/Lib.fs b/NBitcoin.Miniscript/Utils/Lib.fs index 751962f49d..8e060df1b3 100644 --- a/NBitcoin.Miniscript/Utils/Lib.fs +++ b/NBitcoin.Miniscript/Utils/Lib.fs @@ -2,10 +2,10 @@ namespace NBitcoin.Miniscript.Utils [] module Utils = + open System let inline (!>) (x:^a) : ^b = ((^a or ^b) : (static member op_Implicit : ^a -> ^b )x ) - let resultFolder (acc : Result<'a seq, 'c>) - (item : Result<'a, 'c>) = + let resultFolder (acc : Result<'a seq, _>) (item : Result<'a, _>) = match acc, item with | Ok x, Ok y -> Ok(seq { @@ -13,7 +13,8 @@ module Utils = yield y }) | Ok x, Error y -> Error y - | Error x, _ -> Error x + | Error x, Ok y -> Error x + | Error x, Error y -> Error((AggregateException([|x; y|]) :> exn)) [] diff --git a/NBitcoin.TestFramework/NBitcoin.TestFramework.csproj b/NBitcoin.TestFramework/NBitcoin.TestFramework.csproj index fecdc402dc..ab79835ae3 100644 --- a/NBitcoin.TestFramework/NBitcoin.TestFramework.csproj +++ b/NBitcoin.TestFramework/NBitcoin.TestFramework.csproj @@ -2,7 +2,7 @@ 1.6.35 - netstandard1.6;net452;netstandard2.0 + netstandard1.6;net452;netstandard2.0;netcoreapp2.1;netcoreapp2.2 netstandard2.0 $(TargetFrameworkOverride) NBitcoin.TestFramework diff --git a/NBitcoin.Tests/Comparer.cs b/NBitcoin.Tests/Comparer.cs deleted file mode 100644 index fe85b93ecc..0000000000 --- a/NBitcoin.Tests/Comparer.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; -using NBitcoin.BIP174; - -namespace NBitcoin.Tests -{ - public static class Comparer - { - public class PSBTComparer : EqualityComparer - { - public override bool Equals(PSBT a, PSBT b) => a.Equals(b); - public override int GetHashCode(PSBT psbt) => psbt.GetHashCode(); - } - } -} \ No newline at end of file diff --git a/NBitcoin.Tests/NBitcoin.Tests.csproj b/NBitcoin.Tests/NBitcoin.Tests.csproj index 433aa1e31b..ea6746eed0 100644 --- a/NBitcoin.Tests/NBitcoin.Tests.csproj +++ b/NBitcoin.Tests/NBitcoin.Tests.csproj @@ -97,9 +97,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest diff --git a/NBitcoin.Tests/PSBTComparer.cs b/NBitcoin.Tests/PSBTComparer.cs new file mode 100644 index 0000000000..97ce81993e --- /dev/null +++ b/NBitcoin.Tests/PSBTComparer.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using NBitcoin.BIP174; + +namespace NBitcoin.Tests +{ + public class PSBTComparer : EqualityComparer + { + public override bool Equals(PSBT a, PSBT b) => a.Equals(b); + public override int GetHashCode(PSBT psbt) => psbt.GetHashCode(); + } + +} \ No newline at end of file diff --git a/NBitcoin.Tests/PropertyTest/PSBTSerializationTest.cs b/NBitcoin.Tests/PropertyTest/PSBTSerializationTest.cs index 8378e5224f..ae30ddc70f 100644 --- a/NBitcoin.Tests/PropertyTest/PSBTSerializationTest.cs +++ b/NBitcoin.Tests/PropertyTest/PSBTSerializationTest.cs @@ -8,7 +8,6 @@ using System; using System.Collections.Generic; using System.Linq; -using static NBitcoin.Tests.Comparer; namespace NBitcoin.Tests.PropertyTest { diff --git a/NBitcoin.Tests/RPCClientTests.cs b/NBitcoin.Tests/RPCClientTests.cs index 01b5919f2f..0bed829847 100644 --- a/NBitcoin.Tests/RPCClientTests.cs +++ b/NBitcoin.Tests/RPCClientTests.cs @@ -19,7 +19,6 @@ using NBitcoin.BIP174; using FsCheck; using NBitcoin.Tests.Generators; -using static NBitcoin.Tests.Comparer; namespace NBitcoin.Tests { @@ -31,14 +30,10 @@ public class RPCClientTests { const string TestAccount = "NBitcoin.RPCClientTests"; - public PSBTComparer PSBTComparerInstance { get; } public ITestOutputHelper Output { get; } public RPCClientTests(ITestOutputHelper output) { - Arb.Register(); - Arb.Register(); - PSBTComparerInstance = new PSBTComparer(); Output = output; } @@ -1289,295 +1284,6 @@ public async Task CanGenerateBlocks() } } - [Fact] - public void ShouldCreatePSBTAcceptableByRPCAsExpected() - { - using (var builder = NodeBuilderEx.Create()) - { - var node = builder.CreateNode(); - node.Start(); - var client = node.CreateRPCClient(); - - var keys = new Key[] {new Key(), new Key(), new Key() }; - var redeem = PayToMultiSigTemplate.Instance.GenerateScriptPubKey(3, keys.Select(ki => ki.PubKey).ToArray()); - var funds = PSBTTests.CreateDummyFunds(Network.TestNet, keys, redeem); - - // case1: PSBT from already fully signed tx - var tx = PSBTTests.CreateTxToSpendFunds(funds, keys, redeem, true, true); - // PSBT without previous outputs but with finalized_script_witness will throw an error. - var psbt = PSBT.FromTransaction(tx.Clone(), true); - Assert.Throws(() => psbt.ToBase64()); - - // after adding coins, will not throw an error. - psbt.AddCoins(funds.SelectMany(f => f.Outputs.AsCoins()).ToArray()); - CheckPSBTIsAcceptableByRealRPC(psbt.ToBase64(), client); - - // but if we use rpc to convert tx to psbt, it will discard input scriptSig and ScriptWitness. - // So it will be acceptable by any other rpc. - psbt = PSBT.FromTransaction(tx.Clone()); - CheckPSBTIsAcceptableByRealRPC(psbt.ToBase64(), client); - - // case2: PSBT from tx with script (but without signatures) - tx = PSBTTests.CreateTxToSpendFunds(funds, keys, redeem, true, false); - psbt = PSBT.FromTransaction(tx, true); - // it has witness_script but has no prevout so it will throw an error. - Assert.Throws(() => psbt.ToBase64()); - // after adding coins, will not throw error. - psbt.AddCoins(funds.SelectMany(f => f.Outputs.AsCoins()).ToArray()); - CheckPSBTIsAcceptableByRealRPC(psbt.ToBase64(), client); - - // case3: PSBT from tx without script nor signatures. - tx = PSBTTests.CreateTxToSpendFunds(funds, keys, redeem, false, false); - psbt = PSBT.FromTransaction(tx, true); - // This time, it will not throw an error at the first place. - // Since sanity check for witness input will not complain about witness-script-without-witnessUtxo - CheckPSBTIsAcceptableByRealRPC(psbt.ToBase64(), client); - - var dummyKey = new Key(); - var dummyScript = new Script("OP_DUP " + "OP_HASH160 " + Op.GetPushOp(dummyKey.PubKey.Hash.ToBytes()) + " OP_EQUALVERIFY"); - - // even after adding coins and scripts ... - var psbtWithCoins = psbt.Clone().AddCoins(funds.SelectMany(f => f.Outputs.AsCoins()).ToArray()); - CheckPSBTIsAcceptableByRealRPC(psbtWithCoins.ToBase64(), client); - psbtWithCoins.AddScript(redeem); - CheckPSBTIsAcceptableByRealRPC(psbtWithCoins.ToBase64(), client); - var tmp = psbtWithCoins.Clone().AddScript(dummyScript); // should not change with dummyScript - Assert.Equal(psbtWithCoins, tmp, PSBTComparerInstance); - // or txs and scripts. - var psbtWithTXs = psbt.Clone().AddTransactions(funds); - CheckPSBTIsAcceptableByRealRPC(psbtWithTXs.ToBase64(), client); - psbtWithTXs.AddScript(redeem); - CheckPSBTIsAcceptableByRealRPC(psbtWithTXs.ToBase64(), client); - tmp = psbtWithTXs.Clone().AddScript(dummyScript); - Assert.Equal(psbtWithTXs, tmp, PSBTComparerInstance); - - // Let's don't forget about hd KeyPath - psbtWithTXs.AddKeyPath(keys[0].PubKey, Tuple.Create((uint)1234, KeyPath.Parse("m/1'/2/3"))); - psbtWithTXs.AddPathTo(3, keys[1].PubKey, 4321, KeyPath.Parse("m/3'/2/1")); - psbtWithTXs.AddPathTo(0, keys[1].PubKey, 4321, KeyPath.Parse("m/3'/2/1"), false); - CheckPSBTIsAcceptableByRealRPC(psbtWithTXs.ToBase64(), client); - - // What about after adding some signatures? - psbtWithTXs.SignAll(keys); - CheckPSBTIsAcceptableByRealRPC(psbtWithTXs.ToBase64(), client); - tmp = psbtWithTXs.Clone().SignAll(dummyKey); // Try signing with unrelated key should not change anything - Assert.Equal(psbtWithTXs, tmp, PSBTComparerInstance); - // And finalization? - psbtWithTXs.Finalize(); - CheckPSBTIsAcceptableByRealRPC(psbtWithTXs.ToBase64(), client); - } - return; - } - - /// - /// Just Check if the psbt is acceptable by bitcoin core rpc. - /// - /// - /// - private void CheckPSBTIsAcceptableByRealRPC(string base64, RPCClient client) - => client.SendCommand(RPCOperations.decodepsbt, base64); - - [Fact] - public void ShouldWalletProcessPSBTAndExtractMempoolAcceptableTX() - { - using (var builder = NodeBuilderEx.Create()) - { - var node = builder.CreateNode(); - node.Start(); - - var client = node.CreateRPCClient(); - - // ensure the wallet has whole kinds of coins ... - var addr = client.GetNewAddress(); - client.GenerateToAddress(101, addr); - addr = client.GetNewAddress(new GetNewAddressRequest() { AddressType = AddressType.Bech32 }); - client.SendToAddress(addr, Money.Coins(15)); - addr = client.GetNewAddress(new GetNewAddressRequest() { AddressType = AddressType.P2SHSegwit }); - client.SendToAddress(addr, Money.Coins(15)); - var tmpaddr = new Key(); - client.GenerateToAddress(1, tmpaddr.PubKey.GetAddress(node.Network)); - - // case 1: irrelevant psbt. - var keys = new Key[] {new Key(), new Key(), new Key() }; - var redeem = PayToMultiSigTemplate.Instance.GenerateScriptPubKey(3, keys.Select(ki => ki.PubKey).ToArray()); - var funds = PSBTTests.CreateDummyFunds(Network.TestNet, keys, redeem); - var tx = PSBTTests.CreateTxToSpendFunds(funds, keys, redeem, true, true); - var psbt = PSBT.FromTransaction(tx, true) - .AddTransactions(funds) - .AddScript(redeem); - var case1Result = client.WalletProcessPSBT(psbt); - // nothing must change for the psbt unrelated to the wallet. - Assert.Equal(psbt, case1Result.PSBT, PSBTComparerInstance); - - // case 2: psbt relevant to the wallet. (but already finalized) - var kOut = new Key(); - tx = builder.Network.CreateTransaction(); - tx.Outputs.Add(new TxOut(Money.Coins(45), kOut)); // This has to be big enough since the wallet must use whole kinds of address. - var fundTxResult = client.FundRawTransaction(tx); - Assert.Equal(3, fundTxResult.Transaction.Inputs.Count); - var psbtFinalized = PSBT.FromTransaction(fundTxResult.Transaction, true); - var result = client.WalletProcessPSBT(psbtFinalized, false); - Assert.False(result.PSBT.CanExtractTX()); - result = client.WalletProcessPSBT(psbtFinalized, true); - Assert.True(result.PSBT.CanExtractTX()); - - // case 3a: psbt relevant to the wallet (and not finalized) - var spendableCoins = client.ListUnspent().Where(c => c.IsSpendable).Select(c => c.AsCoin()); - tx = builder.Network.CreateTransaction(); - foreach (var coin in spendableCoins) - tx.Inputs.Add(coin.Outpoint); - tx.Outputs.Add(new TxOut(Money.Coins(45), kOut)); - var psbtUnFinalized = PSBT.FromTransaction(tx, true); - - var type = SigHash.All; - // unsigned - result = client.WalletProcessPSBT(psbtUnFinalized, false, type, bip32derivs: true); - Assert.False(result.Complete); - Assert.False(result.PSBT.CanExtractTX()); - var ex2 = Assert.Throws( - () => result.PSBT.Finalize() - ); - var errors2 = ex2.InnerExceptions; - Assert.NotEmpty(errors2); - foreach (var psbtin in result.PSBT.Inputs) - { - Assert.Equal(SigHash.Undefined, psbtin.SighashType); - Assert.NotEmpty(psbtin.HDKeyPaths); - } - - // signed - result = client.WalletProcessPSBT(psbtUnFinalized, true, type); - // does not throw - result.PSBT.Finalize(); - - var txResult = result.PSBT.ExtractTX(); - var acceptResult = client.TestMempoolAccept(txResult, true); - Assert.True(acceptResult.IsAllowed, acceptResult.RejectReason); - } - } - - // refs: https://github.com/bitcoin/bitcoin/blob/df73c23f5fac031cc9b2ec06a74275db5ea322e3/doc/psbt.md#workflows - // with 2 difference. - // 1. one user (David) do not use bitcoin core (only NBitcoin) - // 2. 4-of-4 instead of 2-of-3 - // 3. In version 0.17, `importmulti` can not handle witness script so only p2sh are considered here. TODO: fix - [Fact] - public void ShouldPerformMultisigProcessingWithCore() - { - using (var builder = NodeBuilderEx.Create()) - { - if (!builder.NodeImplementation.Version.Contains("0.17")) - throw new Exception("Test must be updated!"); - var nodeAlice = builder.CreateNode(); - var nodeBob = builder.CreateNode(); - var nodeCarol = builder.CreateNode(); - var nodeFunder = builder.CreateNode(); - var david = new Key(); - builder.StartAll(); - - // prepare multisig script and watch with node. - var nodes = new CoreNode[]{nodeAlice, nodeBob, nodeCarol}; - var clients = nodes.Select(n => n.CreateRPCClient()).ToArray(); - var addresses = clients.Select(c => c.GetNewAddress()); - var addrInfos = addresses.Select((a, i) => clients[i].GetAddressInfo(a)); - var pubkeys = new List { david.PubKey }; - pubkeys.AddRange(addrInfos.Select(i => i.PubKey).ToArray()); - var script = PayToMultiSigTemplate.Instance.GenerateScriptPubKey(4, pubkeys.ToArray()); - var aMultiP2SH = script.Hash.ScriptPubKey; - // var aMultiP2WSH = script.WitHash.ScriptPubKey; - // var aMultiP2SH_P2WSH = script.WitHash.ScriptPubKey.Hash.ScriptPubKey; - var multiAddresses = new BitcoinAddress[] { aMultiP2SH.GetDestinationAddress(builder.Network) }; - var importMultiObject = new ImportMultiAddress[] { - new ImportMultiAddress() - { - ScriptPubKey = new ImportMultiAddress.ScriptPubKeyObject(multiAddresses[0]), - RedeemScript = script.ToHex(), - Internal = true, - }, - /* - new ImportMultiAddress() - { - ScriptPubKey = new ImportMultiAddress.ScriptPubKeyObject(aMultiP2WSH), - RedeemScript = script.ToHex(), - Internal = true, - }, - new ImportMultiAddress() - { - ScriptPubKey = new ImportMultiAddress.ScriptPubKeyObject(aMultiP2SH_P2WSH), - RedeemScript = script.WitHash.ScriptPubKey.ToHex(), - Internal = true, - }, - new ImportMultiAddress() - { - ScriptPubKey = new ImportMultiAddress.ScriptPubKeyObject(aMultiP2SH_P2WSH), - RedeemScript = script.ToHex(), - Internal = true, - } - */ - }; - - for (var i = 0; i < clients.Length; i++) - { - var c = clients[i]; - Output.WriteLine($"Importing for {i}"); - c.ImportMulti(importMultiObject, false); - } - - // pay from funder - nodeFunder.Generate(103); - var funderClient = nodeFunder.CreateRPCClient(); - funderClient.SendToAddress(aMultiP2SH, Money.Coins(40)); - // funderClient.SendToAddress(aMultiP2WSH, Money.Coins(40)); - // funderClient.SendToAddress(aMultiP2SH_P2WSH, Money.Coins(40)); - nodeFunder.Generate(1); - foreach (var n in nodes) - { - nodeFunder.Sync(n, true); - } - - // pay from multisig address - // first carol creates psbt - var carol = clients[2]; - // check if we have enough balance - var info = carol.GetBlockchainInfoAsync().Result; - Assert.Equal((ulong)104, info.Blocks); - var balance = carol.GetBalance(0, true); - // Assert.Equal(Money.Coins(120), balance); - Assert.Equal(Money.Coins(40), balance); - - var aSend = new Key().PubKey.GetAddress(nodeAlice.Network); - var outputs = new Dictionary(); - outputs.Add(aSend, Money.Coins(10)); - var fundOptions = new FundRawTransactionOptions() { SubtractFeeFromOutputs = new int[] {0}, IncludeWatching = true }; - PSBT psbt = carol.WalletCreateFundedPSBT(null, outputs, 0, fundOptions).PSBT; - psbt = carol.WalletProcessPSBT(psbt).PSBT; - - // second, Bob checks and process psbt. - var bob = clients[1]; - Assert.Contains(multiAddresses, a => - psbt.Inputs.Any(psbtin => psbtin.WitnessUtxo?.ScriptPubKey == a.ScriptPubKey) || - psbt.Inputs.Any(psbtin => (bool)psbtin.NonWitnessUtxo?.Outputs.Any(o => a.ScriptPubKey == o.ScriptPubKey)) - ); - var psbt1 = bob.WalletProcessPSBT(psbt.Clone()).PSBT; - - // at the same time, David may do the ; - psbt.SignAll(david); - var alice = clients[0]; - var psbt2 = alice.WalletProcessPSBT(psbt).PSBT; - - // not enough signatures - Assert.Throws(() => psbt.Finalize()); - - // So let's combine. - var psbtCombined = psbt1.Combine(psbt2); - - // Finally, anyone can finalize and broadcast the psbt. - var tx = psbtCombined.Finalize().ExtractTX(); - var result = alice.TestMempoolAccept(tx); - Assert.True(result.IsAllowed, result.RejectReason); - } - } - [Fact] /// diff --git a/NBitcoin/BIP174/PartiallySignedTransaction.cs b/NBitcoin/BIP174/PartiallySignedTransaction.cs index 520c08fac1..1cc9c6ed81 100755 --- a/NBitcoin/BIP174/PartiallySignedTransaction.cs +++ b/NBitcoin/BIP174/PartiallySignedTransaction.cs @@ -6,6 +6,7 @@ using NBitcoin.Crypto; using NBitcoin.DataEncoders; +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("NBitcoin.Miniscript")] namespace NBitcoin.BIP174 { using HDKeyPathKVMap = SortedDictionary>; @@ -560,7 +561,7 @@ internal void Finalize(Transaction tx, int index) if (prevout == null) throw new InvalidOperationException("Can not finalize PSBTInput without utxo"); - var dummyTx = tx.Clone(); // Since to run VerifyScript for witness input, we must modify tx. + var dummyTx = tx.Clone(); // Because we must modify tx to run VerifyScript for witness input. var context = new ScriptEvaluationContext() { SigHash = sighash_type == 0 ? SigHash.All : sighash_type}; var nextScript = prevout.ScriptPubKey; @@ -1392,7 +1393,7 @@ private void Initialize() /// /// It tries to preserve signatures and scripts from ScriptSig (or Witness Script) iff preserveInputProp is true. /// Due to policy in sanity checking, input with witness script and without witness_utxo can not be serialized. - /// So if you specify true, make sure you will run `TryAddTransaction` or `AddCoins` right after this and give witness_utxo to the input. + /// So if you specify true, make sure you will run `AddTransactions` or `AddCoins` right after this and give witness_utxo to the input. /// /// PSBT public static PSBT FromTransaction(Transaction tx, bool preserveInputProp = false) => new PSBT(tx, preserveInputProp); diff --git a/NBitcoin/Properties/AssemblyInfo.cs b/NBitcoin/Properties/AssemblyInfo.cs index 543bcdf56f..03ab7bdf09 100644 --- a/NBitcoin/Properties/AssemblyInfo.cs +++ b/NBitcoin/Properties/AssemblyInfo.cs @@ -8,6 +8,8 @@ // associated with an assembly. [assembly: InternalsVisibleTo("NBitcoin.Tests")] [assembly: InternalsVisibleTo("NBitcoin.Altcoins")] +[assembly: InternalsVisibleTo("NBitcoin.Miniscript")] +[assembly: InternalsVisibleTo("NBitcoin.Miniscript.Tests.CSharp")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from From 39e35342f1cfe7c154c01c75e54c7e5ae819c960 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Wed, 10 Apr 2019 19:08:42 +0900 Subject: [PATCH 26/40] Fix bug in Satisfy --- NBitcoin.Miniscript/Satisfy.fs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/NBitcoin.Miniscript/Satisfy.fs b/NBitcoin.Miniscript/Satisfy.fs index 36f6886985..2d5c213863 100644 --- a/NBitcoin.Miniscript/Satisfy.fs +++ b/NBitcoin.Miniscript/Satisfy.fs @@ -64,7 +64,7 @@ module internal Satisfy = let sigList = maybeSigList |> List.choose(id) |> List.map(Signature) if sigList.Length >= (int32 m) then - Ok(sigList) + Ok([RawPush [||]] @ sigList) else let sigNotFoundPks = maybeSigList |> List.zip (pks |> Array.toList) @@ -187,12 +187,12 @@ module internal Satisfy = match (satisfyAST providers l), (satisfyAST providers r) with | Error e, Error _ -> Error e | Ok lItems, Error _ -> Ok(lItems @ [RawPush([|byte 1|])]) - | Error e, Ok rItems -> Ok(rItems @ [RawPush([|byte 0|])]) + | Error e, Ok rItems -> Ok(rItems @ [RawPush([||])]) | Ok lItems, Ok rItems -> // return the one has less cost if satisfyCost(lItems) + 2 <= satisfyCost rItems + 1 then Ok(lItems @ [RawPush([|byte 1|])]) else - Ok(rItems @ [RawPush([|byte 0|])]) + Ok(rItems @ [RawPush([||])]) and satisfyE (providers: ProviderSet) (e: E) = let keyFn, hashFn, age = providers @@ -289,9 +289,9 @@ module internal Satisfy = and dissatisfyE (e: E): SatisfiedItem list = match e with - | E.CheckSig pk -> [RawPush([| byte 0 |])] + | E.CheckSig pk -> [RawPush([||])] | E.CheckMultiSig (m, pks) -> [RawPush[| byte 0 |]; RawPush[| byte(m + 1u)|]] - | E.Time t -> [RawPush([| byte 0 |])] + | E.Time t -> [RawPush([||])] | E.Threshold (_, e, ws) -> let wDissat = ws |> Array.toList |> List.rev |> List.map(dissatisfyW) |> List.collect id let eDissat = dissatisfyE e From 88c8ee5fc57c8a17bac43eda9cdf06e055ac524c Mon Sep 17 00:00:00 2001 From: joemphilips Date: Wed, 10 Apr 2019 19:09:44 +0900 Subject: [PATCH 27/40] Delete PSBT.Finalize() and reimplement it as Extension --- NBitcoin.Miniscript.Tests/CSharp/PSBTTests.cs | 8 +- .../CSharp/RPCClientTests.cs | 10 +- .../FSharp/MiniScriptDecompilerTests.fs | 5 + NBitcoin.Miniscript/PSBTExtension.fs | 58 +++++--- NBitcoin/BIP174/PartiallySignedTransaction.cs | 140 +----------------- NBitcoin/Properties/AssemblyInfo.cs | 1 + 6 files changed, 53 insertions(+), 169 deletions(-) diff --git a/NBitcoin.Miniscript.Tests/CSharp/PSBTTests.cs b/NBitcoin.Miniscript.Tests/CSharp/PSBTTests.cs index 3c8f36c285..85cec5e69e 100644 --- a/NBitcoin.Miniscript.Tests/CSharp/PSBTTests.cs +++ b/NBitcoin.Miniscript.Tests/CSharp/PSBTTests.cs @@ -148,7 +148,7 @@ public void CanUpdate() Assert.Single(signedPSBTWithCoins.Inputs[4].PartialSigs); Assert.Single(signedPSBTWithCoins.Inputs[5].PartialSigs); var ex = Assert.Throws(() => - signedPSBTWithCoins.Finalize() + signedPSBTWithCoins.FinalizeUnsafe() ); var finalizationErrors = ex.InnerExceptions; // Only p2wpkh and p2sh-p2wpkh will succeed. @@ -193,7 +193,7 @@ public void CanUpdate() Assert.False(whollySignedPSBT.CanExtractTX()); - var finalizedPSBT = whollySignedPSBT.Finalize(); + var finalizedPSBT = whollySignedPSBT.FinalizeUnsafe(); Assert.True(finalizedPSBT.CanExtractTX()); var finalTX = finalizedPSBT.ExtractTX(); @@ -230,7 +230,7 @@ public void ShouldCaptureExceptionInFinalization() var tx = CreateTxToSpendFunds(funds, keys, redeem, false, false); var psbt = PSBT.FromTransaction(tx); - var ex = Assert.Throws(() => psbt.Finalize()); + var ex = Assert.Throws(() => psbt.FinalizeUnsafe()); var errors = ex.InnerExceptions; Assert.Equal(6, errors.Count); } @@ -379,7 +379,7 @@ public void ShouldPassTheLongestTestInBIP174() expected = PSBT.Parse((string)testcase["psbtcombined"]); Assert.Equal(expected, combined); - var finalized = psbt.Finalize(); + var finalized = psbt.FinalizeUnsafe(); expected = PSBT.Parse((string)testcase["psbtfinalized"]); Assert.Equal(expected, finalized); diff --git a/NBitcoin.Miniscript.Tests/CSharp/RPCClientTests.cs b/NBitcoin.Miniscript.Tests/CSharp/RPCClientTests.cs index 74ea6c57b8..b7c7f1bc7b 100644 --- a/NBitcoin.Miniscript.Tests/CSharp/RPCClientTests.cs +++ b/NBitcoin.Miniscript.Tests/CSharp/RPCClientTests.cs @@ -95,7 +95,7 @@ public void ShouldCreatePSBTAcceptableByRPCAsExpected() tmp = psbtWithTXs.Clone().SignAll(dummyKey); // Try signing with unrelated key should not change anything Assert.Equal(psbtWithTXs, tmp, PSBTComparerInstance); // And finalization? - psbtWithTXs.Finalize(); + psbtWithTXs.FinalizeUnsafe(); CheckPSBTIsAcceptableByRealRPC(psbtWithTXs.ToBase64(), client); } return; @@ -167,7 +167,7 @@ public void ShouldWalletProcessPSBTAndExtractMempoolAcceptableTX() Assert.False(result.Complete); Assert.False(result.PSBT.CanExtractTX()); var ex2 = Assert.Throws( - () => result.PSBT.Finalize() + () => result.PSBT.FinalizeUnsafe() ); var errors2 = ex2.InnerExceptions; Assert.NotEmpty(errors2); @@ -180,7 +180,7 @@ public void ShouldWalletProcessPSBTAndExtractMempoolAcceptableTX() // signed result = client.WalletProcessPSBT(psbtUnFinalized, true, type); // does not throw - result.PSBT.Finalize(); + result.PSBT.FinalizeUnsafe(); var txResult = result.PSBT.ExtractTX(); var acceptResult = client.TestMempoolAccept(txResult, true); @@ -298,13 +298,13 @@ public void ShouldPerformMultisigProcessingWithCore() var psbt2 = alice.WalletProcessPSBT(psbt).PSBT; // not enough signatures - Assert.Throws(() => psbt.Finalize()); + Assert.Throws(() => psbt.FinalizeUnsafe()); // So let's combine. var psbtCombined = psbt1.Combine(psbt2); // Finally, anyone can finalize and broadcast the psbt. - var tx = psbtCombined.Finalize().ExtractTX(); + var tx = psbtCombined.FinalizeUnsafe().ExtractTX(); var result = alice.TestMempoolAccept(tx); Assert.True(result.IsAllowed, result.RejectReason); } diff --git a/NBitcoin.Miniscript.Tests/FSharp/MiniScriptDecompilerTests.fs b/NBitcoin.Miniscript.Tests/FSharp/MiniScriptDecompilerTests.fs index dd23a6f7aa..a2812cdf11 100644 --- a/NBitcoin.Miniscript.Tests/FSharp/MiniScriptDecompilerTests.fs +++ b/NBitcoin.Miniscript.Tests/FSharp/MiniScriptDecompilerTests.fs @@ -54,6 +54,11 @@ let tests = let res = Miniscript.Decompiler.parseScript sc checkParseResult res delayedOrV + testCase "Should decompile Multisig from template" <| fun _ -> + let sc = PayToMultiSigTemplate.Instance.GenerateScriptPubKey(2, keysList) + let ms = Miniscript.Decompiler.parseScriptUnsafe sc + () + testCase "Should pass the testcase in rust-miniscript" <| fun _ -> let roundtrip (miniscriptResult : Result) diff --git a/NBitcoin.Miniscript/PSBTExtension.fs b/NBitcoin.Miniscript/PSBTExtension.fs index c8815d68c8..6ffa2b00cc 100644 --- a/NBitcoin.Miniscript/PSBTExtension.fs +++ b/NBitcoin.Miniscript/PSBTExtension.fs @@ -1,9 +1,9 @@ -namespace NBitcoin.Miniscript +namespace NBitcoin open System open System.Linq open System.Runtime.CompilerServices open System.Runtime.InteropServices -open NBitcoin +open NBitcoin.Miniscript open NBitcoin.Miniscript.Utils open NBitcoin.BIP174 @@ -13,11 +13,11 @@ type PSBTFinalizationException(msg: string, ex: exn) = [] type PSBTExtension = - static member private keyFn (sc: Script) (pk: PubKey): TransactionSignature = - if PayToPubkeyHashTemplate.Instance.CheckScriptPubKey(sc) then - null - else - null + static member private keyFn (psbtin: PSBTInput) (sc: Script) (pk: PubKey): TransactionSignature = + let sigHash = if psbtin.SighashType = SigHash.Undefined then SigHash.All else psbtin.SighashType + match psbtin.PartialSigs.TryGetValue(pk.Hash) with + | (true, sigPair) -> TransactionSignature(snd sigPair, sigHash) + | (false, _) -> null static member private tryCheckWitness (hashFn: Func) @@ -42,6 +42,7 @@ type PSBTExtension = else psbt.Inputs.[index].FinalScriptSig <- ss psbt.Inputs.[index].FinalScriptWitness <- dummyTX.Inputs.[index].WitScript + psbt.Inputs.[index].ClearForFinalize() Ok(psbt) // p2wsh else if PayToWitScriptHashTemplate.Instance.CheckScriptPubKey(spk) then @@ -50,7 +51,7 @@ type PSBTExtension = let errorMsg = "Failed to parse p2wsh as a Miniscript: " + msg Error(PSBTFinalizationException(errorMsg)) | Ok ms -> - match ms.Satisfy(PSBTExtension.keyFn psbtin.WitnessScript, hashFn, age) with + match ms.Satisfy(PSBTExtension.keyFn psbtin psbtin.WitnessScript, hashFn, age) with | Error fCase -> let msg = sprintf "Failed to satisfy p2wsh script: %A" fCase Error(PSBTFinalizationException(msg)) @@ -59,17 +60,22 @@ type PSBTExtension = let ss = if isP2SH then Script(Op.GetPushOp(spk.ToBytes())) else Script.Empty dummyTX.Inputs.[index].WitScript <- PayToWitScriptHashTemplate.Instance.GenerateWitScript(pushes, psbtin.WitnessScript) if not (ctx.VerifyScript(ss, dummyTX, index, prevOut)) then - let msg = sprintf "Script verification failed for p2wsh %s" (ctx.Error.ToString()) + let msg = sprintf "Script verification failed for following p2wsh;\nErrorCode: %s\nScript:%s\nPushItems: %A" + (ctx.Error.ToString()) + (psbtin.RedeemScript.ToString()) + items Error (PSBTFinalizationException(msg)) else psbt.Inputs.[index].FinalScriptWitness <- dummyTX.Inputs.[index].WitScript psbt.Inputs.[index].FinalScriptSig <- ss + psbt.Inputs.[index].ClearForFinalize() Ok(psbt) else - Error(PSBTFinalizationException("Unknown type of script")) + let msg = sprintf "Unknown type of script %s" (spk.ToString()) + Error(PSBTFinalizationException(msg)) static member private isBareP2SH (psbtin: PSBTInput) = - (isNull psbtin.WitnessScript |> not) && (PayToWitTemplate.Instance.CheckScriptPubKey(psbtin.RedeemScript) |> not) + (isNull psbtin.WitnessScript) && (PayToWitTemplate.Instance.CheckScriptPubKey(psbtin.RedeemScript) |> not) [] static member FinalizeIndex(psbt: PSBT, @@ -91,16 +97,20 @@ type PSBTExtension = let spk = prevOut.ScriptPubKey let tryCheckWitness = PSBTExtension.tryCheckWitness hashFn age psbt sigHash dummyTX index prevOut context + + // p2pkh if (PayToPubkeyHashTemplate.Instance.CheckScriptPubKey(spk)) then let sigPair = psbtin.PartialSigs.First() let txSig = TransactionSignature(snd sigPair.Value, sigHash) let ss = PayToPubkeyHashTemplate.Instance.GenerateScriptSig(txSig, fst sigPair.Value) - if context.VerifyScript(ss, dummyTX, index, prevOut) then - psbtin.FinalScriptSig <- ss - Ok(psbt) - else + if not (context.VerifyScript(ss, dummyTX, index, prevOut)) then let errorMsg = sprintf "Script verification failed for p2pkh %s" (context.Error.ToString()) Error(PSBTFinalizationException(errorMsg)) + else + psbtin.FinalScriptSig <- ss + psbt.Inputs.[index].ClearForFinalize() + Ok(psbt) + // p2sh else if spk.IsPayToScriptHash then if PSBTExtension.isBareP2SH psbtin then match Miniscript.fromScript psbtin.RedeemScript with @@ -108,22 +118,28 @@ type PSBTExtension = let msg = "Failed to parse p2sh as a Miniscript: " + msg Error(PSBTFinalizationException(msg)) | Ok ms -> - match ms.Satisfy(PSBTExtension.keyFn psbtin.RedeemScript, hashFn, age) with + match ms.Satisfy(PSBTExtension.keyFn psbtin psbtin.RedeemScript, hashFn, age) with | Error fCase -> let msg = sprintf "Failed to satisfy p2sh redeem script: %A" fCase Error(PSBTFinalizationException(msg)) | Ok items -> let pushes = items |> List.toArray |> Array.map(fun i -> i.ToPushOps()) let ss = PayToScriptHashTemplate.Instance.GenerateScriptSig(pushes, psbtin.RedeemScript) - if context.VerifyScript(ss, dummyTX, index, prevOut) then + if not (context.VerifyScript(ss, dummyTX, index, prevOut)) then + let msg = sprintf "Script verification failed for following p2sh;\nErrorCode: %s\nScript:%s\nPushItems: %A" + (context.Error.ToString()) + (psbtin.RedeemScript.ToString()) + items + Error (PSBTFinalizationException(msg)) + else psbtin.FinalScriptSig <- ss + psbt.Inputs.[index].ClearForFinalize() Ok(psbt) - else - let msg = sprintf "Script verification failed for p2sh %s" (context.Error.ToString()) - Error (PSBTFinalizationException(msg)) else + // p2sh-p2wpkh, p2sh-p2wsh tryCheckWitness true (psbtin.RedeemScript) else + // p2wpkh, p2wsh tryCheckWitness false (spk) @@ -153,7 +169,7 @@ type PSBTExtension = |> Seq.map(fun i -> psbt.FinalizeIndex(i, hashFn, age)) |> Seq.map(Result.mapError(fun e -> e :> exn)) |> Seq.reduce resultFolder - |> Result.mapError(fun e -> PSBTFinalizationException("Failed to finalize psbt", e)) + |> Result.mapError(fun e -> PSBTFinalizationException("Failed to finalize PSBTInput", e)) r [] static member FinalizeUnsafe(psbt: PSBT, diff --git a/NBitcoin/BIP174/PartiallySignedTransaction.cs b/NBitcoin/BIP174/PartiallySignedTransaction.cs index 1cc9c6ed81..7d8706ec26 100755 --- a/NBitcoin/BIP174/PartiallySignedTransaction.cs +++ b/NBitcoin/BIP174/PartiallySignedTransaction.cs @@ -6,7 +6,6 @@ using NBitcoin.Crypto; using NBitcoin.DataEncoders; -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("NBitcoin.Miniscript")] namespace NBitcoin.BIP174 { using HDKeyPathKVMap = SortedDictionary>; @@ -549,115 +548,11 @@ internal bool Sign(int index, Transaction tx, Key[] keys, bool UseLowR = true) public bool IsFinalized() => final_script_sig != null || final_script_witness != null; - internal void Finalize(Transaction tx, int index) - { - if (tx == null) - throw new ArgumentNullException(nameof(tx)); - - if (IsFinalized()) - return; - - var prevout = GetOutput(tx.Inputs[index].PrevOut); - if (prevout == null) - throw new InvalidOperationException("Can not finalize PSBTInput without utxo"); - - var dummyTx = tx.Clone(); // Because we must modify tx to run VerifyScript for witness input. - var context = new ScriptEvaluationContext() { SigHash = sighash_type == 0 ? SigHash.All : sighash_type}; - var nextScript = prevout.ScriptPubKey; - - // 1. p2pkh - if (PayToPubkeyHashTemplate.Instance.CheckScriptPubKey(nextScript)) - { - var sigPair = partial_sigs.First(); - var txSig = new TransactionSignature(sigPair.Value.Item2, sighash_type == 0 ? SigHash.All : (SigHash)sighash_type); - var ss = PayToPubkeyHashTemplate.Instance.GenerateScriptSig(txSig, sigPair.Value.Item1); - if (!context.VerifyScript(ss, dummyTx, index, prevout)) - throw new InvalidOperationException($"Failed to verify script in p2pkh! {context.Error}"); - final_script_sig = ss; - } - - // 2. p2sh - else if (nextScript.IsPayToScriptHash) - { - // bare p2sh - if (witness_script == null && !PayToWitTemplate.Instance.CheckScriptPubKey(redeem_script)) - { - var pushes = GetPushItems(redeem_script); - var ss = PayToScriptHashTemplate.Instance.GenerateScriptSig(pushes, redeem_script); - if (!context.VerifyScript(ss, dummyTx, index, prevout)) - throw new InvalidOperationException($"Failed to verify script in p2sh! {context.Error}"); - final_script_sig = ss; - } - // Why not create `final_script_sig` here? because if the following code throws an error, it will be left out dirty. - nextScript = redeem_script; - } - - // 3. p2wpkh - if (PayToWitPubKeyHashTemplate.Instance.CheckScriptPubKey(nextScript)) - { - var sigPair = partial_sigs.First(); - var txSig = new TransactionSignature(sigPair.Value.Item2, sighash_type == 0 ? SigHash.All : (SigHash)sighash_type); - dummyTx.Inputs[index].WitScript = PayToWitPubKeyHashTemplate.Instance.GenerateWitScript(txSig, sigPair.Value.Item1); - Script ss = null; - if (prevout.ScriptPubKey.IsPayToScriptHash) - ss = new Script(Op.GetPushOp(redeem_script.ToBytes())); - if (!context.VerifyScript(ss ?? Script.Empty, dummyTx, index, prevout)) - throw new InvalidOperationException($"Failed to verify script in p2wpkh! {context.Error}"); - - final_script_witness = dummyTx.Inputs[index].WitScript; - final_script_sig = ss; - } - - // 4. p2wsh - else if (PayToWitScriptHashTemplate.Instance.CheckScriptPubKey(nextScript)) - { - var pushes = GetPushItems(witness_script); - dummyTx.Inputs[index].WitScript = PayToWitScriptHashTemplate.Instance.GenerateWitScript(pushes, witness_script); - Script ss = null; - if (prevout.ScriptPubKey.IsPayToScriptHash) - ss = new Script(Op.GetPushOp(redeem_script.ToBytes())); - if (!context.VerifyScript(ss ?? Script.Empty, dummyTx, index, prevout)) - throw new InvalidOperationException($"Failed to verify script in p2wsh! {context.Error}"); - - final_script_witness = dummyTx.Inputs[index].WitScript; - final_script_sig = ss; - } - if (IsFinalized()) - ClearForFinalize(); - } - - /// - /// conovert partial sigs to suitable form for ScriptSig (or Witness). - /// This will preserve the ordering of redeem script even if it did not follow bip67. - /// - /// - /// - private Op[] GetPushItems(Script redeem) - { - if (PayToMultiSigTemplate.Instance.CheckScriptPubKey(redeem)) - { - var sigPushes = new List { OpcodeType.OP_0 }; - foreach (var pk in redeem.GetAllPubKeys()) - { - if (!partial_sigs.TryGetValue(pk.Hash, out var sigPair)) - continue; - var txSig = new TransactionSignature(sigPair.Item2, sighash_type == 0 ? SigHash.All : (SigHash)sighash_type); - sigPushes.Add(Op.GetPushOp(txSig.ToBytes())); - } - // check sig is more than m in case of p2multisig. - var multiSigParam = PayToMultiSigTemplate.Instance.ExtractScriptPubKeyParameters(redeem); - var numSigs = sigPushes.Count - 1; - if (multiSigParam != null && numSigs < multiSigParam.SignatureCount) - throw new InvalidOperationException("Not enough signatures to finalize."); - return sigPushes.ToArray(); - } - throw new InvalidOperationException("PSBT does not know how to finalize this type of script!"); - } /// /// This will not clear utxos since tx extractor might want to check the validity /// - private void ClearForFinalize() + internal void ClearForFinalize() { this.redeem_script = null; this.witness_script = null; @@ -1502,39 +1397,6 @@ public PSBT CoinJoin(PSBT other) return result; } - /// - /// If this method throws an error, that is a bug. - /// - /// - /// - private PSBT Finalize(out InvalidOperationException[] errors) - { - var elist = new List (); - for (var i = 0; i < Inputs.Count; i++) - { - var psbtin = Inputs[i]; - try - { - psbtin.Finalize(tx, i); - } - catch (InvalidOperationException e) - { - var exception = new InvalidOperationException($"Failed to finalize in input {i}", e); - elist.Add(exception); - } - } - errors = elist.ToArray(); - return this; - } - - public PSBT Finalize() - { - Finalize(out var errors); - if (errors.Length != 0) - throw new AggregateException(errors); - return this; - } - /// /// Test vector in the bip174 specify to use a signer which follows RFC 6979. /// So we must sign without [LowR value assured way](https://github.com/MetacoSA/NBitcoin/pull/510) diff --git a/NBitcoin/Properties/AssemblyInfo.cs b/NBitcoin/Properties/AssemblyInfo.cs index 03ab7bdf09..d3172fac57 100644 --- a/NBitcoin/Properties/AssemblyInfo.cs +++ b/NBitcoin/Properties/AssemblyInfo.cs @@ -10,6 +10,7 @@ [assembly: InternalsVisibleTo("NBitcoin.Altcoins")] [assembly: InternalsVisibleTo("NBitcoin.Miniscript")] [assembly: InternalsVisibleTo("NBitcoin.Miniscript.Tests.CSharp")] +[assembly: InternalsVisibleTo("NBitcoin.Miniscript.Tests.FSharp")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from From 872fc0908e702a0cc460250172980fc29973b7a0 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Thu, 11 Apr 2019 01:09:44 +0900 Subject: [PATCH 28/40] Fix all bug in finalization --- .../CSharp/RPCClientTests.cs | 2 +- NBitcoin.Miniscript/PSBTExtension.fs | 61 +++++++++++-------- 2 files changed, 38 insertions(+), 25 deletions(-) diff --git a/NBitcoin.Miniscript.Tests/CSharp/RPCClientTests.cs b/NBitcoin.Miniscript.Tests/CSharp/RPCClientTests.cs index b7c7f1bc7b..6c437ad14c 100644 --- a/NBitcoin.Miniscript.Tests/CSharp/RPCClientTests.cs +++ b/NBitcoin.Miniscript.Tests/CSharp/RPCClientTests.cs @@ -298,7 +298,7 @@ public void ShouldPerformMultisigProcessingWithCore() var psbt2 = alice.WalletProcessPSBT(psbt).PSBT; // not enough signatures - Assert.Throws(() => psbt.FinalizeUnsafe()); + Assert.Throws(() => psbt.FinalizeIndexUnsafe(0)); // So let's combine. var psbtCombined = psbt1.Combine(psbt2); diff --git a/NBitcoin.Miniscript/PSBTExtension.fs b/NBitcoin.Miniscript/PSBTExtension.fs index 6ffa2b00cc..ae105ed399 100644 --- a/NBitcoin.Miniscript/PSBTExtension.fs +++ b/NBitcoin.Miniscript/PSBTExtension.fs @@ -1,6 +1,7 @@ namespace NBitcoin open System open System.Linq +open System.Collections.Generic open System.Runtime.CompilerServices open System.Runtime.InteropServices open NBitcoin.Miniscript @@ -19,6 +20,13 @@ type PSBTExtension = | (true, sigPair) -> TransactionSignature(snd sigPair, sigHash) | (false, _) -> null + static member private getSig (partialSigs: IDictionary<_, _>) = + try + partialSigs.First() |> Some + with + | :? InvalidOperationException as e -> + None + static member private tryCheckWitness (hashFn: Func) (age: uint32) @@ -32,18 +40,20 @@ type PSBTExtension = (spk: Script): Result = let psbtin = psbt.Inputs.[index] if PayToWitPubKeyHashTemplate.Instance.CheckScriptPubKey(spk) then - let sigPair = psbtin.PartialSigs.First() - let txSig = TransactionSignature(snd sigPair.Value, sigHash) - dummyTX.Inputs.[index].WitScript <- PayToWitPubKeyHashTemplate.Instance.GenerateWitScript(txSig, fst sigPair.Value) - let ss = if isP2SH then Script(Op.GetPushOp(spk.ToBytes())) else Script.Empty - if not (ctx.VerifyScript(ss, dummyTX, index, prevOut)) then - let errorMsg = sprintf "Script verification failed for p2wpkh %s" (ctx.Error.ToString()) - Error(PSBTFinalizationException(errorMsg)) - else - psbt.Inputs.[index].FinalScriptSig <- ss - psbt.Inputs.[index].FinalScriptWitness <- dummyTX.Inputs.[index].WitScript - psbt.Inputs.[index].ClearForFinalize() - Ok(psbt) + match PSBTExtension.getSig psbtin.PartialSigs with + | None -> Error(PSBTFinalizationException("No signature for p2pkh")) + | Some sigPair -> + let txSig = TransactionSignature(snd sigPair.Value, sigHash) + dummyTX.Inputs.[index].WitScript <- PayToWitPubKeyHashTemplate.Instance.GenerateWitScript(txSig, fst sigPair.Value) + let ss = if isP2SH then Script(Op.GetPushOp(spk.ToBytes())) else Script.Empty + if not (ctx.VerifyScript(ss, dummyTX, index, prevOut)) then + let errorMsg = sprintf "Script verification failed for p2wpkh %s" (ctx.Error.ToString()) + Error(PSBTFinalizationException(errorMsg)) + else + psbt.Inputs.[index].FinalScriptSig <- ss + psbt.Inputs.[index].FinalScriptWitness <- dummyTX.Inputs.[index].WitScript + psbt.Inputs.[index].ClearForFinalize() + Ok(psbt) // p2wsh else if PayToWitScriptHashTemplate.Instance.CheckScriptPubKey(spk) then match Miniscript.fromScript psbtin.WitnessScript with @@ -101,15 +111,18 @@ type PSBTExtension = // p2pkh if (PayToPubkeyHashTemplate.Instance.CheckScriptPubKey(spk)) then let sigPair = psbtin.PartialSigs.First() - let txSig = TransactionSignature(snd sigPair.Value, sigHash) - let ss = PayToPubkeyHashTemplate.Instance.GenerateScriptSig(txSig, fst sigPair.Value) - if not (context.VerifyScript(ss, dummyTX, index, prevOut)) then - let errorMsg = sprintf "Script verification failed for p2pkh %s" (context.Error.ToString()) - Error(PSBTFinalizationException(errorMsg)) - else - psbtin.FinalScriptSig <- ss - psbt.Inputs.[index].ClearForFinalize() - Ok(psbt) + match PSBTExtension.getSig psbtin.PartialSigs with + | None -> Error(PSBTFinalizationException("No signature for p2pkh")) + | Some sigPair -> + let txSig = TransactionSignature(snd sigPair.Value, sigHash) + let ss = PayToPubkeyHashTemplate.Instance.GenerateScriptSig(txSig, fst sigPair.Value) + if not (context.VerifyScript(ss, dummyTX, index, prevOut)) then + let errorMsg = sprintf "Script verification failed for p2pkh %s" (context.Error.ToString()) + Error(PSBTFinalizationException(errorMsg)) + else + psbtin.FinalScriptSig <- ss + psbt.Inputs.[index].ClearForFinalize() + Ok(psbt) // p2sh else if spk.IsPayToScriptHash then if PSBTExtension.isBareP2SH psbtin then @@ -160,16 +173,16 @@ type PSBTExtension = [] age: uint32) = let inline resultFolder (acc) (r): Result = match acc, r with - | Error e1 , Error e2 -> Error((AggregateException(seq [(e1 :> exn); (e2 :> exn)])) :> exn) + | Error e1 , Error e2 -> Error(e1 @ e2) | Error e, Ok _ -> Error e | Ok _, Error e -> Error e | Ok _, Ok psbt2 -> Ok psbt2 let r = seq { 0 .. psbt.Inputs.Count - 1 } |> Seq.map(fun i -> psbt.FinalizeIndex(i, hashFn, age)) - |> Seq.map(Result.mapError(fun e -> e :> exn)) + |> Seq.map(Result.mapError(fun e -> [e])) |> Seq.reduce resultFolder - |> Result.mapError(fun e -> PSBTFinalizationException("Failed to finalize PSBTInput", e)) + |> Result.mapError(fun es -> AggregateException(es |> List.map(fun e -> e :> exn))) r [] static member FinalizeUnsafe(psbt: PSBT, From 7e036ac4322e812c7436387ce1859cd1ae19d445 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Thu, 11 Apr 2019 16:46:42 +0900 Subject: [PATCH 29/40] Tweak *.[c|f]sproj files --- NBitcoin.Altcoins/NBitcoin.Altcoins.csproj | 4 ++-- .../CSharp/NBitcoin.Miniscript.Tests.CSharp.csproj | 10 ++++------ .../FSharp/NBitcoin.Miniscript.Tests.FSharp.fsproj | 2 +- .../NBitcoin.TestFramework.csproj | 2 +- NBitcoin.Tests/NBitcoin.Tests.csproj | 2 +- NBitcoin/NBitcoin.csproj | 2 +- pack.sh | 14 -------------- 7 files changed, 10 insertions(+), 26 deletions(-) delete mode 100644 pack.sh diff --git a/NBitcoin.Altcoins/NBitcoin.Altcoins.csproj b/NBitcoin.Altcoins/NBitcoin.Altcoins.csproj index 7c63ce347a..21621b9ed7 100644 --- a/NBitcoin.Altcoins/NBitcoin.Altcoins.csproj +++ b/NBitcoin.Altcoins/NBitcoin.Altcoins.csproj @@ -12,7 +12,7 @@ 1.0.1.42 - net461;net452;netstandard1.3;netcoreapp2.1;netstandard2.0 + net452;net461;netstandard1.3;netcoreapp2.1;netstandard2.0 netstandard2.0 $(TargetFrameworkOverride) 1591;1573;1572;1584;1570;3021 @@ -34,4 +34,4 @@ true bin\Release\NBitcoin.Altcoins.XML - \ No newline at end of file + diff --git a/NBitcoin.Miniscript.Tests/CSharp/NBitcoin.Miniscript.Tests.CSharp.csproj b/NBitcoin.Miniscript.Tests/CSharp/NBitcoin.Miniscript.Tests.CSharp.csproj index 0830109c98..db2b8d8a4d 100644 --- a/NBitcoin.Miniscript.Tests/CSharp/NBitcoin.Miniscript.Tests.CSharp.csproj +++ b/NBitcoin.Miniscript.Tests/CSharp/NBitcoin.Miniscript.Tests.CSharp.csproj @@ -1,7 +1,7 @@ - net461;netcoreapp2.2; + net461;netstandard2.0;netcoreapp2.1; false @@ -17,13 +17,11 @@ - + + - - - ..\..\NBitcoin.Miniscript\bin\Debug\netcoreapp2.1\NBitcoin.Miniscript.dll - + diff --git a/NBitcoin.Miniscript.Tests/FSharp/NBitcoin.Miniscript.Tests.FSharp.fsproj b/NBitcoin.Miniscript.Tests/FSharp/NBitcoin.Miniscript.Tests.FSharp.fsproj index 1550bcc077..d99b173f8c 100644 --- a/NBitcoin.Miniscript.Tests/FSharp/NBitcoin.Miniscript.Tests.FSharp.fsproj +++ b/NBitcoin.Miniscript.Tests/FSharp/NBitcoin.Miniscript.Tests.FSharp.fsproj @@ -1,7 +1,7 @@ Exe - netcoreapp2.1;netcoreapp2.2;net461 + netstandard2.0;netcoreapp2.1;net461 diff --git a/NBitcoin.TestFramework/NBitcoin.TestFramework.csproj b/NBitcoin.TestFramework/NBitcoin.TestFramework.csproj index ab79835ae3..ccc7ccdb35 100644 --- a/NBitcoin.TestFramework/NBitcoin.TestFramework.csproj +++ b/NBitcoin.TestFramework/NBitcoin.TestFramework.csproj @@ -2,7 +2,7 @@ 1.6.35 - netstandard1.6;net452;netstandard2.0;netcoreapp2.1;netcoreapp2.2 + netstandard1.6;net452;netstandard2.0;netcoreapp2.1 netstandard2.0 $(TargetFrameworkOverride) NBitcoin.TestFramework diff --git a/NBitcoin.Tests/NBitcoin.Tests.csproj b/NBitcoin.Tests/NBitcoin.Tests.csproj index ea6746eed0..654c17f847 100644 --- a/NBitcoin.Tests/NBitcoin.Tests.csproj +++ b/NBitcoin.Tests/NBitcoin.Tests.csproj @@ -6,7 +6,7 @@ The C# Bitcoin Library - net461;netcoreapp2.1 + net461;netstandard2.0;netcoreapp2.1 netcoreapp2.1 diff --git a/NBitcoin/NBitcoin.csproj b/NBitcoin/NBitcoin.csproj index 25579948e6..58f7d7a6ce 100644 --- a/NBitcoin/NBitcoin.csproj +++ b/NBitcoin/NBitcoin.csproj @@ -20,7 +20,7 @@ true - net461;net452;netstandard1.3;netstandard1.1;netcoreapp2.1;netstandard2.0 + net452;net461;netstandard1.1;netstandard1.3;netstandard2.0;netcoreapp2.1 netstandard2.0 $(TargetFrameworkOverride) 1591;1573;1572;1584;1570;3021 diff --git a/pack.sh b/pack.sh deleted file mode 100644 index f61fc6aaa4..0000000000 --- a/pack.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -set -u - -readonly VERSION="1.0" -if [[ "$(uname)" == 'Darwin' ]]; then - readonly SCRIPT_DIR_PATH=$(dirname $(greadlink -f $0)) -else - readonly SCRIPT_DIR_PATH=$(dirname $(readlink -f $0)) -fi - -cd $SCRIPT_DIR_PATH - -dotnet build NBitcoin.Miniscript -c Release -dotnet pack NBitcoin -c Release From 67588fd0847eb7e6a8ef54681ba7e2bc830c0b99 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Thu, 11 Apr 2019 17:29:52 +0900 Subject: [PATCH 30/40] Delete useless file in Miniscript.Tests.CSharp --- NBitcoin.Miniscript.Tests/CSharp/UnitTest1.cs | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 NBitcoin.Miniscript.Tests/CSharp/UnitTest1.cs diff --git a/NBitcoin.Miniscript.Tests/CSharp/UnitTest1.cs b/NBitcoin.Miniscript.Tests/CSharp/UnitTest1.cs deleted file mode 100644 index 19a07c12a3..0000000000 --- a/NBitcoin.Miniscript.Tests/CSharp/UnitTest1.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using Xunit; - -namespace CSharp -{ - public class UnitTest1 - { - [Fact] - public void Test1() - { - - } - } -} From 4f9a80f3692da3ca7aa0e5357a4e26513be46f70 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Thu, 11 Apr 2019 18:22:23 +0900 Subject: [PATCH 31/40] Add description about netfx.props --- NBitcoin.Miniscript.Tests/FSharp/netfx.props | 2 ++ NBitcoin.Miniscript/netfx.props | 2 ++ 2 files changed, 4 insertions(+) diff --git a/NBitcoin.Miniscript.Tests/FSharp/netfx.props b/NBitcoin.Miniscript.Tests/FSharp/netfx.props index c410c1964f..12a67e1e0c 100644 --- a/NBitcoin.Miniscript.Tests/FSharp/netfx.props +++ b/NBitcoin.Miniscript.Tests/FSharp/netfx.props @@ -1,5 +1,7 @@ + + diff --git a/NBitcoin.Miniscript/netfx.props b/NBitcoin.Miniscript/netfx.props index c410c1964f..12a67e1e0c 100644 --- a/NBitcoin.Miniscript/netfx.props +++ b/NBitcoin.Miniscript/netfx.props @@ -1,5 +1,7 @@ + + From ff38b8d712c49dac86bc65987cdd703a8b55ca8d Mon Sep 17 00:00:00 2001 From: joemphilips Date: Thu, 11 Apr 2019 19:12:08 +0900 Subject: [PATCH 32/40] Remove meaningless testcase in SatifyTests.fs --- NBitcoin.Miniscript.Tests/FSharp/SatisfyTests.fs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/NBitcoin.Miniscript.Tests/FSharp/SatisfyTests.fs b/NBitcoin.Miniscript.Tests/FSharp/SatisfyTests.fs index f90c9047ef..a65fded966 100644 --- a/NBitcoin.Miniscript.Tests/FSharp/SatisfyTests.fs +++ b/NBitcoin.Miniscript.Tests/FSharp/SatisfyTests.fs @@ -47,16 +47,4 @@ let tests = Expect.isOk r3 "could not satisfy" - testCase "Should satisfy script generated from templates" <| fun _ -> - let roundtrip sc (ks: Key list) = - let ms = Miniscript.fromScriptUnsafe(sc) - let dummySig = TransactionSignature.Empty - let keyFn pk = if ((ks |> List.map(fun k -> k.PubKey)) |> List.contains(pk)) then Some dummySig else None - ms.SatisfyUnsafe(?keyFn=Some keyFn) |> ignore - () - - let k1, k2 = NBitcoin.Key(), NBitcoin.Key() - let pk1, pk2 = (k1.PubKey), (k2.PubKey) - let p2pkh = PayToPubkeyHashTemplate.Instance.GenerateScriptPubKey(pk1) - roundtrip p2pkh [k1] ] From c706db8988aedca4fe3cf7c637cb94e01afa9a00 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Thu, 11 Apr 2019 19:12:29 +0900 Subject: [PATCH 33/40] Run Miniscript test in appveyor --- appveyor.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index 0593d31af0..ed2fc92cef 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -66,6 +66,8 @@ test_script: Write-Host "[$env:configuration] STARTED dotnet test" -foregroundcolor "magenta" cd $env:APPVEYOR_BUILD_FOLDER dotnet test -c Release ./NBitcoin.Tests/NBitcoin.Tests.csproj --filter "RestClient=RestClient|RPCClient=RPCClient|Protocol=Protocol|Core=Core|UnitTest=UnitTest" -p:ParallelizeTestCollections=false -f net461 + dotnet run -c Release --project ./NBitcoin.Miniscript.Tests/FSharp/NBitcoin.Miniscript.Tests.FSharp.fsproj + dotnet test -c Release ./NBitcoin.Miniscript.Tests/CSharp/NBitcoin.Miniscript.Tests.CSharp.csproj -p:ParallelizeTestCollections=false Write-Host "[$env:configuration] FINISHED dotnet test" -foregroundcolor "magenta" if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } From e443fecba90453be366cb81049a9edd6cb19e48b Mon Sep 17 00:00:00 2001 From: joemphilips Date: Thu, 11 Apr 2019 19:42:50 +0900 Subject: [PATCH 34/40] Speed up property test --- NBitcoin.Miniscript.Tests/FSharp/MiniScriptCompilerTests.fs | 4 ++-- NBitcoin.Miniscript.Tests/FSharp/MiniScriptParserTests.fs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/NBitcoin.Miniscript.Tests/FSharp/MiniScriptCompilerTests.fs b/NBitcoin.Miniscript.Tests/FSharp/MiniScriptCompilerTests.fs index 5fadf13817..216162c6a2 100644 --- a/NBitcoin.Miniscript.Tests/FSharp/MiniScriptCompilerTests.fs +++ b/NBitcoin.Miniscript.Tests/FSharp/MiniScriptCompilerTests.fs @@ -12,8 +12,8 @@ let logger = Log.create "MiniscriptCompiler" let config = { FsCheckConfig.defaultConfig with arbitrary = [ typeof ] - maxTest = 500 - endSize = 32 + maxTest = 300 + endSize = 16 receivedArgs = fun _ name no args -> logger.debugWithBP diff --git a/NBitcoin.Miniscript.Tests/FSharp/MiniScriptParserTests.fs b/NBitcoin.Miniscript.Tests/FSharp/MiniScriptParserTests.fs index 1e0f62d0e5..eca0c456f8 100644 --- a/NBitcoin.Miniscript.Tests/FSharp/MiniScriptParserTests.fs +++ b/NBitcoin.Miniscript.Tests/FSharp/MiniScriptParserTests.fs @@ -24,7 +24,7 @@ let check = let config = { FsCheckConfig.defaultConfig with arbitrary = [ typeof ] maxTest = 30 - endSize = 128 + endSize = 32 receivedArgs = fun _ name no args -> logger.debugWithBP From 6facd74ef861c979c9207eff66b743498d3e6689 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Thu, 11 Apr 2019 19:29:32 +0900 Subject: [PATCH 35/40] Run Miniscript tests in travis --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 29ed4248c4..c13ab36732 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,3 +28,7 @@ before_install: script: - dotnet build ./NBitcoin.Tests/NBitcoin.Tests.csproj /p:TargetFrameworkOverride=$TargetFrameworkOverride -c Release -f netcoreapp2.1 $EXTRA_CONSTANT - dotnet test --no-build -c Release -f netcoreapp2.1 ./NBitcoin.Tests/NBitcoin.Tests.csproj --filter "RestClient=RestClient|RPCClient=RPCClient|Protocol=Protocol|Core=Core|UnitTest=UnitTest" -p:ParallelizeTestCollections=false + - dotnet build ./NBitcoin.Miniscript.Tests/FSharp/NBitcoin.Miniscript.Tests.FSharp.fsproj /p:TargetFrameworkOverride=$TargetFrameworkOverride -c Release -f netcoreapp2.1 $EXTRA_CONSTANT + - dotnet run --no-build -c Release --project ./NBitcoin.Miniscript.Tests/FSharp/NBitcoin.Miniscript.Tests.FSharp.fsproj -f netcoreapp2.1 + - dotnet build ./NBitcoin.Miniscript.Tests/CSharp/NBitcoin.Miniscript.Tests.CSharp.csproj /p:TargetFrameworkOverride=$TargetFrameworkOverride -c Release -f netcoreapp2.1 $EXTRA_CONSTANT + - dotnet test --no-build -c Release -f netcoreapp2.1 ./NBitcoin.Miniscript.Tests/CSharp/NBitcoin.Miniscript.Tests.CSharp.csproj -p:ParallelizeTestCollections=false From 32ef31f2255d3fdfe70953a0564dde2688dd2c00 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Thu, 11 Apr 2019 23:57:17 +0900 Subject: [PATCH 36/40] Specify FSharp.Core as dep in FSharp test and bump Fsharp.Core version in Miniscript --- .../FSharp/NBitcoin.Miniscript.Tests.FSharp.fsproj | 1 + NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/NBitcoin.Miniscript.Tests/FSharp/NBitcoin.Miniscript.Tests.FSharp.fsproj b/NBitcoin.Miniscript.Tests/FSharp/NBitcoin.Miniscript.Tests.FSharp.fsproj index d99b173f8c..8f4dab9ad7 100644 --- a/NBitcoin.Miniscript.Tests/FSharp/NBitcoin.Miniscript.Tests.FSharp.fsproj +++ b/NBitcoin.Miniscript.Tests/FSharp/NBitcoin.Miniscript.Tests.FSharp.fsproj @@ -22,6 +22,7 @@ + diff --git a/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj b/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj index 3a6b28955f..e97ae157db 100644 --- a/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj +++ b/NBitcoin.Miniscript/NBitcoin.Miniscript.fsproj @@ -23,7 +23,7 @@ - + From 43b19b045b9ec6a6985fb608378c514e0c727031 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Fri, 12 Apr 2019 00:01:49 +0900 Subject: [PATCH 37/40] Specify framework version in appveyor --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index ed2fc92cef..1b565f2be7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -66,8 +66,8 @@ test_script: Write-Host "[$env:configuration] STARTED dotnet test" -foregroundcolor "magenta" cd $env:APPVEYOR_BUILD_FOLDER dotnet test -c Release ./NBitcoin.Tests/NBitcoin.Tests.csproj --filter "RestClient=RestClient|RPCClient=RPCClient|Protocol=Protocol|Core=Core|UnitTest=UnitTest" -p:ParallelizeTestCollections=false -f net461 - dotnet run -c Release --project ./NBitcoin.Miniscript.Tests/FSharp/NBitcoin.Miniscript.Tests.FSharp.fsproj - dotnet test -c Release ./NBitcoin.Miniscript.Tests/CSharp/NBitcoin.Miniscript.Tests.CSharp.csproj -p:ParallelizeTestCollections=false + dotnet run -c Release --project ./NBitcoin.Miniscript.Tests/FSharp/NBitcoin.Miniscript.Tests.FSharp.fsproj -f net461 + dotnet test -c Release ./NBitcoin.Miniscript.Tests/CSharp/NBitcoin.Miniscript.Tests.CSharp.csproj -p:ParallelizeTestCollections=false -f net461 Write-Host "[$env:configuration] FINISHED dotnet test" -foregroundcolor "magenta" if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } From fc619da3b82d3440cfb521f368d519d602284fcf Mon Sep 17 00:00:00 2001 From: joemphilips Date: Fri, 12 Apr 2019 16:47:35 +0900 Subject: [PATCH 38/40] Pass HTLC test for psbt --- .../CSharp/MiniscriptPSBTTests.cs | 29 +++++++++++++++++-- .../FSharp/Generators/Lib.fs | 2 +- NBitcoin.Miniscript/MiniscriptDecompiler.fs | 4 ++- NBitcoin.Miniscript/MiniscriptParser.fs | 1 + NBitcoin.Miniscript/PSBTExtension.fs | 11 ++++--- NBitcoin.Miniscript/Satisfy.fs | 10 +++---- 6 files changed, 43 insertions(+), 14 deletions(-) diff --git a/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs b/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs index 5a8d348548..859bbcd23b 100644 --- a/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs +++ b/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Xunit; +using NBitcoin.Crypto; using NBitcoin.BIP174; using NBitcoin.Miniscript; using static NBitcoin.Miniscript.AbstractPolicy; @@ -61,9 +62,15 @@ public void ShouldSatisfyMiniscript() } [Fact] - public void ShouldSatisfyPSBT() + public void ShouldSatisfyPSBTWithComplexScript() { - var policyStr = $"aor(and(pk({privKeys[0].PubKey}), time({10000})), multi(2, {privKeys[0].PubKey}, {privKeys[1].PubKey}))"; + // case 1: bip199 HTLC + var alice = privKeys[0]; + var bob = privKeys[1]; + var bobSecret = new uint256(0xdeadbeef); + var bobHash = new uint256(Hashes.SHA256(bobSecret.ToBytes()), false); + Console.WriteLine($"bobSecret and bobHash is {bobSecret} {bobHash}"); + var policyStr = $"aor(and(hash({bobHash}), pk({bob.PubKey})), and(pk({alice.PubKey}), time({10000})))"; var ms = Miniscript.FromStringUnsafe(policyStr); var script = ms.ToScript(); var funds = Utils.CreateDummyFunds(Network, privKeys, script); @@ -71,7 +78,23 @@ public void ShouldSatisfyPSBT() var psbt = PSBT.FromTransaction(tx) .AddTransactions(funds) .AddScript(script); - // psbt.Satisfy(); + + // Can not finalize without signatures. + Assert.Throws(() => psbt.FinalizeUnsafe(h => h == bobHash ? bobSecret : null, age: 10001u)); + // It has signature but it is not matured. + psbt.SignAll(alice); + Assert.Throws(() => psbt.FinalizeUnsafe(h => h == bobHash ? bobSecret : null, age: 9999u)); + + // it has both signature and a secret. + psbt.SignAll(bob); + psbt.FinalizeUnsafe(h => h == bobHash ? bobSecret : null); + Assert.True(psbt.CanExtractTX()); + + var txExtracted = psbt.ExtractTX(); + var builder = Network.CreateTransactionBuilder(); + builder.AddCoins(Utils.DummyFundsToCoins(funds, script, privKeys[0])).AddKeys(privKeys); + if (!builder.Verify(txExtracted, (Money)null, out var errors)) + throw new InvalidOperationException(errors.Aggregate(string.Empty, (a, b) => a + ";\n" + b)); } } } diff --git a/NBitcoin.Miniscript.Tests/FSharp/Generators/Lib.fs b/NBitcoin.Miniscript.Tests/FSharp/Generators/Lib.fs index 483b63d8aa..26f283d665 100644 --- a/NBitcoin.Miniscript.Tests/FSharp/Generators/Lib.fs +++ b/NBitcoin.Miniscript.Tests/FSharp/Generators/Lib.fs @@ -8,7 +8,7 @@ type Generators = static member Policy() : Arbitrary = // policy |> Arb.fromGen { new Arbitrary() with override this.Generator = policy - // TODO: This shrinker is far from ideal + // This shrinker does its job. But it is far from ideal. // 1. nested shrinking does not work well // 2. Must use Seq instead of List override this.Shrinker(p: AbstractPolicy) = diff --git a/NBitcoin.Miniscript/MiniscriptDecompiler.fs b/NBitcoin.Miniscript/MiniscriptDecompiler.fs index 944ceda9d3..de3ff94a49 100644 --- a/NBitcoin.Miniscript/MiniscriptDecompiler.fs +++ b/NBitcoin.Miniscript/MiniscriptDecompiler.fs @@ -133,7 +133,9 @@ let private tryGetItemFromOp (op: Op) = let size = op.PushData.Length match size with | 20 -> Ok(Token.Hash160Hash(uint160 (op.PushData, false))) - | 32 -> Ok(Token.Sha256Hash(uint256 (op.PushData, false))) + | 32 -> + let i = uint256 (op.PushData, false) + Ok(Token.Sha256Hash(i)) | 33 -> try Ok(Token.Pk(NBitcoin.PubKey(op.PushData))) diff --git a/NBitcoin.Miniscript/MiniscriptParser.fs b/NBitcoin.Miniscript/MiniscriptParser.fs index 42c12270d9..70d5b67edb 100644 --- a/NBitcoin.Miniscript/MiniscriptParser.fs +++ b/NBitcoin.Miniscript/MiniscriptParser.fs @@ -4,6 +4,7 @@ open NBitcoin open System.Text.RegularExpressions open System +/// High level representation of Miniscript type AbstractPolicy = | Key of PubKey | Multi of uint32 * PubKey [] diff --git a/NBitcoin.Miniscript/PSBTExtension.fs b/NBitcoin.Miniscript/PSBTExtension.fs index ae105ed399..bfae6df004 100644 --- a/NBitcoin.Miniscript/PSBTExtension.fs +++ b/NBitcoin.Miniscript/PSBTExtension.fs @@ -72,7 +72,7 @@ type PSBTExtension = if not (ctx.VerifyScript(ss, dummyTX, index, prevOut)) then let msg = sprintf "Script verification failed for following p2wsh;\nErrorCode: %s\nScript:%s\nPushItems: %A" (ctx.Error.ToString()) - (psbtin.RedeemScript.ToString()) + (psbtin.WitnessScript.ToString()) items Error (PSBTFinalizationException(msg)) else @@ -110,7 +110,6 @@ type PSBTExtension = // p2pkh if (PayToPubkeyHashTemplate.Instance.CheckScriptPubKey(spk)) then - let sigPair = psbtin.PartialSigs.First() match PSBTExtension.getSig psbtin.PartialSigs with | None -> Error(PSBTFinalizationException("No signature for p2pkh")) | Some sigPair -> @@ -125,7 +124,9 @@ type PSBTExtension = Ok(psbt) // p2sh else if spk.IsPayToScriptHash then - if PSBTExtension.isBareP2SH psbtin then + if isNull psbtin.RedeemScript then + Error(PSBTFinalizationException("no redeem scirpt for p2sh")) + else if PSBTExtension.isBareP2SH psbtin then match Miniscript.fromScript psbtin.RedeemScript with | Error msg -> let msg = "Failed to parse p2sh as a Miniscript: " + msg @@ -137,10 +138,12 @@ type PSBTExtension = Error(PSBTFinalizationException(msg)) | Ok items -> let pushes = items |> List.toArray |> Array.map(fun i -> i.ToPushOps()) + printfn "going to push item %A" (pushes) let ss = PayToScriptHashTemplate.Instance.GenerateScriptSig(pushes, psbtin.RedeemScript) if not (context.VerifyScript(ss, dummyTX, index, prevOut)) then - let msg = sprintf "Script verification failed for following p2sh;\nErrorCode: %s\nScript:%s\nPushItems: %A" + let msg = sprintf "Script verification failed for following p2sh;\nErrorCode: %s\nScriptWithPushItems: %s\nScript:%s\nPushItems: %A" (context.Error.ToString()) + (ss.ToString()) (psbtin.RedeemScript.ToString()) items Error (PSBTFinalizationException(msg)) diff --git a/NBitcoin.Miniscript/Satisfy.fs b/NBitcoin.Miniscript/Satisfy.fs index 2d5c213863..65cbba945b 100644 --- a/NBitcoin.Miniscript/Satisfy.fs +++ b/NBitcoin.Miniscript/Satisfy.fs @@ -186,11 +186,11 @@ module internal Satisfy = and satisfySwitchOr providers (l, r) = match (satisfyAST providers l), (satisfyAST providers r) with | Error e, Error _ -> Error e - | Ok lItems, Error _ -> Ok(lItems @ [RawPush([|byte 1|])]) + | Ok lItems, Error _ -> Ok(lItems @ [RawPush([|byte 1uy|])]) | Error e, Ok rItems -> Ok(rItems @ [RawPush([||])]) | Ok lItems, Ok rItems -> // return the one has less cost if satisfyCost(lItems) + 2 <= satisfyCost rItems + 1 then - Ok(lItems @ [RawPush([|byte 1|])]) + Ok(lItems @ [RawPush([|byte 1uy|])]) else Ok(rItems @ [RawPush([||])]) @@ -215,7 +215,7 @@ module internal Satisfy = | E.Likely f -> satisfyF providers f |> Result.map(fun items -> items @ [RawPush([||])]) | E.Unlikely f -> - satisfyF providers f |> Result.map(fun items -> items @ [RawPush([|byte 1|])]) + satisfyF providers f |> Result.map(fun items -> items @ [RawPush([|byte 1uy|])]) and satisfyW (providers: ProviderSet) w: SatisfactionResult = let keyFn, hashFn, age = providers @@ -223,7 +223,7 @@ module internal Satisfy = | W.CheckSig pk -> satisfyCheckSig keyFn pk | W.HashEqual h -> satisfyHashEqual hashFn h | W.Time t -> - satisfyCSV age t |> Result.map(fun items -> items @ [RawPush([| byte 1 |])]) + satisfyCSV age t |> Result.map(fun items -> items @ [RawPush([| byte 1uy |])]) | W.CastE e -> satisfyE providers e and satisfyT (providers) t = @@ -290,7 +290,7 @@ module internal Satisfy = and dissatisfyE (e: E): SatisfiedItem list = match e with | E.CheckSig pk -> [RawPush([||])] - | E.CheckMultiSig (m, pks) -> [RawPush[| byte 0 |]; RawPush[| byte(m + 1u)|]] + | E.CheckMultiSig (m, pks) -> [RawPush[||]; RawPush[| byte(m + 1u)|]] | E.Time t -> [RawPush([||])] | E.Threshold (_, e, ws) -> let wDissat = ws |> Array.toList |> List.rev |> List.map(dissatisfyW) |> List.collect id From cfed1ab87440130245cb90369781a440929f36a0 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Sun, 14 Apr 2019 04:15:10 +0900 Subject: [PATCH 39/40] Remove unnecessary props from Miniscript/AssemblyInfo.fs --- NBitcoin.Miniscript/AssemblyInfo.fs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/NBitcoin.Miniscript/AssemblyInfo.fs b/NBitcoin.Miniscript/AssemblyInfo.fs index 48805d010d..c3ab35bf4b 100644 --- a/NBitcoin.Miniscript/AssemblyInfo.fs +++ b/NBitcoin.Miniscript/AssemblyInfo.fs @@ -7,14 +7,10 @@ open System.Runtime.CompilerServices [] [] [] -[] [] [] [] [] -[] [] [] @@ -30,9 +26,6 @@ module internal AssemblyVersionInformation = [] let AssemblyVersion = "0.1.0" - [] - let AssemblyMetadata_ReleaseDate = "2017-03-17T00:00:00.0000000" - [] let AssemblyFileVersion = "0.1.0" @@ -41,6 +34,3 @@ module internal AssemblyVersionInformation = [] let AssemblyMetadata_ReleaseChannel = "release" - - [] - let AssemblyMetadata_GitHash = "bb8964b54bee133e9af64d316dc2cfee16df7f72" From f7f453a78555d3002c13a7e69c3886925aba31d4 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Sun, 14 Apr 2019 04:15:40 +0900 Subject: [PATCH 40/40] remove nits --- NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs | 1 - NBitcoin.Miniscript/PSBTExtension.fs | 1 - 2 files changed, 2 deletions(-) diff --git a/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs b/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs index 859bbcd23b..65957c15f2 100644 --- a/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs +++ b/NBitcoin.Miniscript.Tests/CSharp/MiniscriptPSBTTests.cs @@ -69,7 +69,6 @@ public void ShouldSatisfyPSBTWithComplexScript() var bob = privKeys[1]; var bobSecret = new uint256(0xdeadbeef); var bobHash = new uint256(Hashes.SHA256(bobSecret.ToBytes()), false); - Console.WriteLine($"bobSecret and bobHash is {bobSecret} {bobHash}"); var policyStr = $"aor(and(hash({bobHash}), pk({bob.PubKey})), and(pk({alice.PubKey}), time({10000})))"; var ms = Miniscript.FromStringUnsafe(policyStr); var script = ms.ToScript(); diff --git a/NBitcoin.Miniscript/PSBTExtension.fs b/NBitcoin.Miniscript/PSBTExtension.fs index bfae6df004..7127ca71cf 100644 --- a/NBitcoin.Miniscript/PSBTExtension.fs +++ b/NBitcoin.Miniscript/PSBTExtension.fs @@ -138,7 +138,6 @@ type PSBTExtension = Error(PSBTFinalizationException(msg)) | Ok items -> let pushes = items |> List.toArray |> Array.map(fun i -> i.ToPushOps()) - printfn "going to push item %A" (pushes) let ss = PayToScriptHashTemplate.Instance.GenerateScriptSig(pushes, psbtin.RedeemScript) if not (context.VerifyScript(ss, dummyTX, index, prevOut)) then let msg = sprintf "Script verification failed for following p2sh;\nErrorCode: %s\nScriptWithPushItems: %s\nScript:%s\nPushItems: %A"