Script Interpreter

1: 
module Script

This module implements an interpreter for the Bitcoin Script language. The link points to a good documentation and I will just mention the main charateristics of this language. For more details, refer to the link.

Bitcoin script is a stack based primary arithmetic language. There are operators to push data to the stack and to perform operations on the elements of the stack. The stack is made of arbitrary long binary strings. When used for math operations, these byte strings are considered little endian big integers.

The language has IF/THEN/ELSE but no loops. It is therefore non Turing complete and program execution time is bounded. Besides the classic operators, it has cryptographic functions such as hashing operators and signature verification.

When a transaction produces Outputs, they mention a value and a script. The later is similar to a program split in two. To spend the output, one must provide the other half of the program so that the full program executes successfully. Another way to see it is to consider the output as a challenge and the spending transaction as the answer to the challenge.

Most of the transactions use the Pay-To-PubHash script. The script has a 20 byte long hash value. The spending transaction must provide a pub key that hashes to the given value and a signature that matches both the pub key and the transaction content.

However, many other scripts exist even though only a few are considered standard. Standard scripts have been created to fulfill a particular use case and are recognized by the core client. The later will not relay transactions that contain non standard scripts. This node does not care and will relay anything that validates successfully.

This part of Bitcoin has a few special cases that are due to bugs or bug fixes. I will mention their impact as I walk through the code.

Bitcoin Elliptic Curve secp256k1

1: 
let halfN = 57896044618658097711785492504343953926418782139537452191302581570759080747168I // Half the order of N

Commonly used op codes.

Other op codes are implemented but are seldom used. I left their numeric value in the code.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
let OP_DUP = 118uy // duplicate the last element on the stack
let OP_1 = 81uy // push 1
let OP_16 = 96uy // push 16
let OP_HASH160 = 169uy // Replace the top of the stack by its HASH-160 value (= SHA then RIPEMD)
let OP_DATA_20 = 20uy // Push the next 20 bytes on the stack
let OP_DATA_32 = 32uy // Push the next 32 bytes on the stack
let OP_EQUAL = 135uy // Compare the top two elements
let OP_EQUALVERIFY = 136uy // Do the same as above but fail the script if they are not equal
[<Literal>] 
let OP_CHECKSIG = 172uy // Single signature check
[<Literal>] 
let OP_CHECKSIGVERIFY = 173uy // As above and fail the script if the check doesn't succeed
[<Literal>] 
let OP_CHECKMULTISIG = 174uy // Multi signature check
[<Literal>] 
let OP_CHECKMULTISIGVERIFY = 175uy // As above and fail the script if the check doesn't succeed
let OP_DATA_65 = 65uy // Push the next 65 bytes on the stack
let OP_DATA_33 = 33uy // Push the next 33 bytes on the stack

Limits

1: 
2: 
3: 
4: 
5: 
6: 
let stackMaxDepth = 1000
let maxPushLength = 520
let maxMultiSig = 20
let maxOpCodeCount = 201
let maxSigOpCount = 201
let maxScriptSize = 10000

P2SH

Pay to Script Hash is specified in BIP-16 and has its own address space. P2SH is quite odd. The script itself is half the story. It is first verified normally like any other script but then if the evaluator recognizes a special script pattern and he will do something completely different. It is as if the program said print hello but then starts calculating pi.

Anyway, the idea is that the signing script gives the signatures but also the pub script that it is signing. Normally, you would have two scripts that once stitched together form a program. P2SH has a script inside a script. That script is provided as data pushed into the stack but then that data is then poped and evaluated. Once again BIP-16 is strange and I just scratched the surface. For more information refer to its specification.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
// This test is the first of a few checks
let isP2SH (script: byte[]) = script.Length = 23 && script.[0] = OP_HASH160 && script.[1] = OP_DATA_20 && script.[22] = OP_EQUAL

// These are used in BIP-37 when the update mode is BLOOM_UPDATE_P2PUBKEY_ONLY. Since it's a probabilistic filter, 
// I can report false positives and therefore I don't have to be accurate
let isPay2PubKey (script: byte[]) = script.Length > 0 && script.[script.Length-1] = OP_CHECKSIG
let isPay2MultiSig (script: byte[]) = script.Length > 0 && script.[script.Length-1] = OP_CHECKMULTISIG

Signature verification

Signature verification is one of the most expensive steps in verification. Bitcoin core uses the OPENSSL library but this application uses another implementation specially written for Bitcoin. It is significantly faster. However, it may have incompatibilities.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
let ECDSACheck (txHash: byte[], pub: byte[], signature: byte[]) = 
    if signature.Length > 0 && pub.Length > 0 then // 0 length signature or pub keys crash the library
        let result = Signatures.Verify(txHash, signature, pub)
        result = Signatures.VerifyResult.Verified
    else false
    (* // The Bouncy Castle way
    let signer = new ECDsaSigner()
    let pubKey = new ECPublicKeyParameters(secp256k1Curve.Curve.DecodePoint pub, ecDomain)
    signer.Init(false, pubKey)
    let parser = new Asn1StreamParser(signature)
    let sequence = parser.ReadObject().ToAsn1Object() :?> DerSequence
    let r = sequence.[0] :?> DerInteger
    let s = sequence.[1] :?> DerInteger

    signer.VerifySignature(txHash, r.Value, s.Value)
    *)

Stack

The stack is implemented as a .NET List, i.e. a variable size array. Its elements are native byte arrays. I added extension methods so that I can use the array as a stack with push & pop.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
type List<'a> with
    member x.Push(v: 'a) = 
        x.Add(v)
    member x.Pop() = 
        let v = x.Peek()
        x.RemoveAt(x.Count-1)
        v
    member x.Peek() = x.Item(x.Count-1)

type ByteList() =
    inherit List<byte[]>()
    member x.Push(v: byte[]) = 
        // logger.DebugF "Pushing %s" (Hex.ToHexString v)
        base.Add(v)

Compute the signature hash

The signature is applied on a given document. Instead of signing the entire document, one first calculates a hash of the document and signs the hash. Bitcoin has a specific way to create the hash and it depends on a few parameters.

  • the signature type. Most of the time, it's SIGHASH_ALL and the complete transaction is hashed. After that, changing a single byte of the transaction invalidates the signature. But it can be SIGHASH_NONE for which all the outputs are blanked meaning that the signature will remain valid even if the outputs are changed. It can also be SIGHASH_SINGLE. In the last case, all the outputs except the one with the same index as the input signature are blanked. It means that all the outputs can be changed except one.
  • anyone can pay: Another type of signature where the inputs are blanked giving a signature that remains valid even if more inputs are added later.

The signing process is described here.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
49: 
50: 
51: 
52: 
53: 
54: 
55: 
56: 
57: 
58: 
59: 
60: 
let computeTxHash (tx: Tx) (index: int) (subScript: byte[]) (sigType: int) = 
    let mutable returnBuggyHash = false

    let anyoneCanPay = (sigType &&& 0x80) <> 0
    let sigHashType = 
        match sigType &&& 0x1F with
        | 2 -> 2uy
        | 3 -> 3uy
        | _ -> 1uy
    use oms = new MemoryStream()
    use writer = new BinaryWriter(oms)
    writer.Write(tx.Version)

    if anyoneCanPay then // reduce to a single input
        writer.WriteVarInt(1)
        let txIn2 = new TxIn(tx.TxIns.[index].PrevOutPoint, subScript, tx.TxIns.[index].Sequence)
        writer.Write(txIn2.ToByteArray())
    else // otherwise keep the other inputs but blank their script 
        writer.WriteVarInt(tx.TxIns.Length)
        tx.TxIns |> Array.iteri (fun i txIn ->
            let script = if i = index then subScript else Array.empty
            let txIn2 = 
                if sigHashType <> 1uy && i <> index then 
                    new TxIn(txIn.PrevOutPoint, script, 0)
                else
                    new TxIn(txIn.PrevOutPoint, script, txIn.Sequence)
            writer.Write(txIn2.ToByteArray())
        )
    
    match sigHashType with
    | 1uy ->
        writer.WriteVarInt(tx.TxOuts.Length)
        for txOut in tx.TxOuts do
            writer.Write(txOut.ToByteArray())
    | 2uy ->
        writer.WriteVarInt 0
    | 3uy -> // Normally in SIGHASH_SINGLE, the number of outputs must exceed the input index but if it's not the case
    // the reference client returns a hash of 1
        if index >= tx.TxOuts.Length then
            returnBuggyHash <- true
        else
            writer.WriteVarInt (index+1)
            for i in 0..index do
                if i < index then
                    let txOut = new TxOut(-1L, Array.empty)
                    writer.Write(txOut.ToByteArray())
                else
                    writer.Write(tx.TxOuts.[i].ToByteArray())
    | _ -> ignore() // cannot happen

    writer.Write(tx.LockTime)
    writer.Write(sigType)

    if returnBuggyHash then
        let hash = Array.zeroCreate<byte> 32
        hash.[0] <- 1uy
        hash
    else
        let data = oms.ToArray()
        dsha data

Stack element conversion

These functions are used when integers are put into the stack. They must be converted to byte[]. It's using variable length byte strings in 1-complement encoding. Therefore there is positive 0 and negative 0.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
let bigintToBytes (i: bigint) =
    let pos = abs i
    let bi = pos.ToByteArray()
    let posTrimIdx = revFind bi (fun b -> b <> 0uy)
    let iTrimIdx = 
        if (posTrimIdx >= 0 && (bi.[posTrimIdx] &&& 0x80uy) <> 0uy)
            then posTrimIdx + 1
            else posTrimIdx
    let bytes = bi.[0..iTrimIdx]
    if i < 0I then
        bytes.[iTrimIdx] <- bytes.[iTrimIdx] ||| 0x80uy
    bytes

let intToBytes (i: int) = bigintToBytes (bigint i)
let int64ToBytes (i: int64) = bigintToBytes (bigint i)
        
let bytesToInt (bytes: byte[]) =
    let b = Array.zeroCreate<byte> bytes.Length
    Array.Copy(bytes, b, bytes.Length)
    let neg = 
        if b.Length > 0 && b.[b.Length-1] &&& 0x80uy <> 0uy then
            b.[b.Length-1] <- b.[b.Length-1] &&& 0x7Fuy
            true
        else false

    let bi = bigint b
    if neg then -bi else bi

Interpreter

The interpreter takes a transaction hashing function at construction. When the function is applied, the index of the input matters and will not give the same hash.

1: 
2: 
3: 
4: 
5: 
6: 
type ScriptRuntime(getTxHash: byte[] -> int -> byte[]) =
    let evalStack = new ByteList()
    let altStack = new ByteList()
    let ifStack = new List<bool>()
    let mutable skipping = false
    let mutable codeSep = 0

Basic stack helpers

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
    let checkDepth (stack: List<'a>) minDepth = 
        if stack.Count < minDepth then raise (ValidationException "not enough stack depth")
    let checkMaxDepth () =
        if evalStack.Count + altStack.Count > stackMaxDepth then raise (ValidationException "stack too deep")
    let popAsBool() = 
        checkDepth evalStack 1
        bytesToInt(evalStack.Pop()) <> 0I
    let verify() =
        if not (popAsBool()) 
        then raise (ValidationException "verification failed")
    let fail() = 
        evalStack.Push(intToBytes(0))
        raise (ValidationException "verification failed")
    let checkOverflow (bytes: byte[]) =
        if bytes.Length > 4 then fail()
        bytes
    let checkIfStackEmpty() = 
        if ifStack.Count > 0 then fail()

    let roll m =
        let n = m+1
        checkDepth evalStack n
        let i = evalStack.Count-n
        let top = evalStack.Item i
        evalStack.RemoveAt i
        evalStack.Push top

    let dup n depth =
        checkDepth evalStack (n+depth)
        for i in 1..n do
            evalStack.Push(evalStack.Item(evalStack.Count-n-depth))
        checkMaxDepth()

Operators on stack elements

They pull a certain number of elements from the stack, apply a function and then push the result back to the stack. The number of arguments and the nature of the operation varies with the helper.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
    let unaryOp (f: int64 -> int64) =
        checkDepth evalStack 1
        let arg = evalStack.Pop() |> checkOverflow |> bytesToInt |> int64
        f(arg) |> int64ToBytes |> evalStack.Push

    let binaryOp (f: int64 -> int64 -> int64) = 
        checkDepth evalStack 2
        let arg2 = evalStack.Pop() |> checkOverflow |> bytesToInt |> int64
        let arg1 = evalStack.Pop() |> checkOverflow |> bytesToInt |> int64
        f arg1 arg2 |> int64ToBytes |> evalStack.Push

    let logicalOp (f: bool -> bool -> bool) = 
        checkDepth evalStack 2
        let arg2 = evalStack.Pop() |> checkOverflow |> bytesToInt |> int |> fun x -> x <> 0
        let arg1 = evalStack.Pop() |> checkOverflow |> bytesToInt |> int |> fun x -> x <> 0
        f arg1 arg2 |> (fun x -> if x then 1 else 0) |> intToBytes |> evalStack.Push

    let binaryBoolOp (f: int -> int -> bool) = 
        checkDepth evalStack 2
        let arg2 = evalStack.Pop() |> checkOverflow |> bytesToInt |> int
        let arg1 = evalStack.Pop() |> checkOverflow |> bytesToInt |> int
        f arg1 arg2 |> (fun x -> if x then 1 else 0) |> intToBytes |> evalStack.Push

    let tertiaryOp (f: int -> int -> int -> int) = 
        checkDepth evalStack 3
        let arg3 = evalStack.Pop() |> checkOverflow |> bytesToInt |> int
        let arg2 = evalStack.Pop() |> checkOverflow |> bytesToInt |> int
        let arg1 = evalStack.Pop() |> checkOverflow |> bytesToInt |> int
        f arg1 arg2 arg3 |> intToBytes |> evalStack.Push

    let hashOp (f: byte[] -> byte[]) =
        checkDepth evalStack 1
        let data = evalStack.Pop()
        data |> f |> evalStack.Push

Remove data that matches a predicate

This function parses the script quickly and identifies the data parts. The predicate is evaluated on the data part and if it returns true, the data is removed from the script. Surprisingly, there are a few places where the standard dictates this behavior.

It also removes any instance of OP_CODESEPARATOR

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
    let removeData (script: byte[]) (pred: byte[] -> bool) = 
        let dataList = new List<byte[]>()
        use ms = new MemoryStream(script)
        use reader = new BinaryReader(ms)
        (
            try
                use oms = new MemoryStream()
                use writer = new BinaryWriter(oms)

                let rec removeDataInner (): byte[] =
                    if ms.Position < ms.Length then
                        let b = reader.ReadByte()
                        let c = int b
                        if c >= 1 && c <= 78 then
                            let startPos = ms.Position-1L
                            let len = 
                                match c with
                                | 76 -> int (reader.ReadByte())
                                | 77 -> int (reader.ReadInt16())
                                | 78 -> reader.ReadInt32()
                                | x -> x
                            let canonical = 
                                if len < 75 then len
                                elif len < 0xFF then 76
                                elif len < 0xFFFF then 77
                                else 78

                            let data = reader.ReadBytes(len)
                            if canonical <> c || not (pred(data)) then
                                let lenRead = int(ms.Position - startPos)
                                ms.Seek(startPos, SeekOrigin.Begin) |> ignore
                                writer.Write(reader.ReadBytes(lenRead))
                            dataList.Add(data)
                        elif b <> 171uy then
                            writer.Write(b)
                        removeDataInner()
                    else
                        oms.ToArray()

                let script = removeDataInner()
                ms.Dispose()
                (true, script, dataList.ToArray())
            with 
            | e -> 
                logger.DebugF "Invalid script %A" e
                let currentPos = int ms.Position-1
                (false, script.[0..currentPos], dataList.ToArray())
        )

Remove the signature bytes from a script

Because a signature cannot be signed, the algorithm always removes any occurence of the signature before passing the script to the hashing function

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
    let removeSignatures (script: byte[]) (signatures: HashSet<byte[]>) = 
        let subScript = 
            if codeSep <> 0
                then script.[codeSep..]
                else script
        let (success, script, _) = removeData subScript (fun data -> signatures.Contains data)
        if not success then fail() 
        script

    let getData (script: byte[]) = 
        let (_, _, data) = removeData script (fun _ -> true)
        data

Calculate the number of OP_CHECKSIG

This is to protect against scripts that have a large number of OP_CHECKSIG. They are expensive to execute and without a limit can be used to make the node overwork. It's an anti-DoS measure but is quite painful to check in practice as every script must be checked even if the tx output is never spent.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
    let scriptCheckSigCount (script: byte[]) =
        let (_, ops, _) = removeData script (fun _ -> true)
        ops.Prepend(0xFFuy).Window(2) |> Seq.map (fun x -> // Prepend a dummy byte to deal with boundary case
            match x.ToArray() with
            | [|op1; op2|] ->
                match op2 with
                | OP_CHECKSIG | OP_CHECKSIGVERIFY -> 1 // single signature check counts as 1
                | OP_CHECKMULTISIG | OP_CHECKMULTISIGVERIFY -> // if preceeded by OP_X, multi-sig counts as x, 
                    if op1 >= OP_1 && op1 <= OP_16 // otherwise counts as 20
                    then int (op1-OP_1)
                    else 20
                | _ -> 0
            | _ -> 0
            ) |> Seq.sum

Single signature verification

The signature hash type is the last byte of the signature and is always removed from it. Then any occurence of the signature itself is removed from the pub script before the tx hash is calculated.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
    let checksigInner pubScript pub (signature: byte[]) = 
        let sigType = if signature.Length > 0 then signature.[signature.Length-1] else 0uy
        let txHash = getTxHash pubScript (int sigType)
        ECDSACheck(txHash, pub, signature)

    let checksig script pub signature = 
        let pubScript = removeSignatures script (new HashSet<byte[]>([signature], hashCompare))
        checksigInner pubScript pub signature

Multi signature verification

In a multi sig, every pub key is checked against the signatures and then discarded. Eventually, all the signatures must match a pub key. Because of this order of evaluation, signatures have to be provided in the same order as the pub keys.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
    let checkmultisig script (pubs: byte[][]) (sigs: byte[] list) = 
        let pubScript = removeSignatures script (new HashSet<byte[]>(sigs, hashCompare))

        let checkOneSig (pubs: byte[] list) (signature: byte[]) = 
            pubs |> List.tryFindIndex (fun pub -> checksigInner pubScript pub signature)
            |> Option.map (fun index -> pubs |> Seq.skip (index+1) |> Seq.toList)
                
        sigs |> Option.foldM checkOneSig (pubs |> Array.toList) |> Option.isSome

Evaluate a script

It's the main eval loop using tail recursion. The eval function reads one byte from the program and decide if it's data or instruction. If it's data, one or more bytes are then read. If it's instruction, it's always a single byte. It's an important trick. When the code must be skipped because it is on the wrong side of a If/then/else, the function can quickly jump to the next code.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
    let eval (pushOnly: bool) (script: byte[]) =
        codeSep <- 0
        if script.Length > maxScriptSize then fail()
        let ms = new MemoryStream(script)
        let reader = new BinaryReader(ms)

        let rec innerEval (opCount: int) =
            let mutable multiSigOps = 0
            if opCount > maxOpCodeCount then fail()
            if ms.Position < ms.Length then
                let c = int (reader.ReadByte())
                // logger.DebugF "Stack size %d op = %x opc = %d" evalStack.Count c opCount
                if c = 0 then
                    if not skipping then
                        evalStack.Push(Array.empty)
                elif c = 79 then
                    if not skipping then
                        evalStack.Push([| 0x81uy |])

Push data

There are different size of push data, 1, 2, or 4 bytes

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
                elif c >= 1 && c <= 78 then
                    let len = 
                        match c with
                        | 76 -> int (reader.ReadByte())
                        | 77 -> int (reader.ReadInt16())
                        | 78 -> reader.ReadInt32()
                        | x -> x
                    let data = reader.ReadBytes(len)
                    if data.Length < len then raise (ValidationException "Not enough data to read")
                    if len > maxPushLength then raise (ValidationException "PushData exceeds 520 bytes")
                    if not skipping then
                        evalStack.Push(data)

OP_1 etc

1: 
2: 
3: 
4: 
                elif c >= 81 && c <= 96 then
                    if not skipping then
                        evalStack.Push(intToBytes (c-80))
                elif pushOnly then fail()

IF/THEN/ELSE

Some special care must be given to if/then/else. They can nest so the logic must accomodate that. The parser keeps a state variable skipping which tells it whether instructions and push data must be skipped or not. On a IF, the current value of skipping is pushed on a 'if' stack and if the parser is not currently skipping, the top of the stack is read. In other words, the if statement is evaluated if it was not part of a dead-if branch, but the push itself is always done in order for the parser to keep track of the if/endif nesting. Same thing happens on an else. skipping is flipped only if the enclosing block isn't skipped too. The parser knows that by peeking at the top of the if-stack.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
                elif c = 99 || c = 100 then
                    ifStack.Push(skipping)
                    if not skipping then
                        skipping <- not (popAsBool())
                        if c = 100 then skipping <- not skipping
                elif c = 103 then
                    checkDepth ifStack 1
                    if not (ifStack.Peek()) then skipping <- not skipping
                elif c = 104 then
                    checkDepth ifStack 1
                    skipping <- ifStack.Pop()
                elif c >= 176 && c <= 185 then ignore()
                elif 
                    match c with
                    | 101 | 102 
                    | 126 | 127 | 128 | 129 | 131 | 132 | 133 | 134
                    | 141 | 142 | 149 | 150 | 151 | 152 | 153 -> true
                    | _ -> false
                    then fail()
                elif not skipping then

Stack operators

These do various permutations of the stack element and other simple stack manipulation. The parser also maintains a alt-stack but it is cleared between the parts of the script evaluation.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
49: 
50: 
51: 
52: 
53: 
54: 
55: 
56: 
57: 
58: 
59: 
60: 
61: 
62: 
63: 
64: 
65: 
66: 
67: 
68: 
69: 
70: 
71: 
72: 
73: 
74: 
                    match c with
                    | 97 -> ignore()
                    | 105 -> verify()
                    | 106 | 80 | 98 | 137 | 138 -> fail()
                    | 107 -> 
                        checkDepth evalStack 1
                        let top = evalStack.Pop()
                        altStack.Push(top)
                    | 108 ->
                        checkDepth altStack 1
                        let top = altStack.Pop()
                        evalStack.Push(top)
                    | 109 ->
                        checkDepth evalStack 2
                        evalStack.Pop() |> ignore
                        evalStack.Pop() |> ignore
                    | 110 -> dup 2 0
                    | 111 -> dup 3 0
                    | 112 -> dup 2 2
                    | 113 -> 
                        roll 5
                        roll 5
                    | 114 ->
                        roll 3
                        roll 3
                    | 115 ->
                        checkDepth evalStack 1
                        let t = evalStack.Peek()
                        evalStack.Push t
                        if popAsBool() then evalStack.Push t
                    | 116 -> evalStack.Push(intToBytes (evalStack.Count))
                    | 117 ->
                        checkDepth evalStack 1
                        evalStack.Pop() |> ignore
                    | 118 -> 
                        checkDepth evalStack 1
                        evalStack.Push(evalStack.Peek())
                    | 119 -> 
                        checkDepth evalStack 2
                        evalStack.RemoveAt(evalStack.Count-2)
                    | 120 ->
                        checkDepth evalStack 2
                        evalStack.Push(evalStack.Item(evalStack.Count-2))
                    | 121 ->
                        let n = int(bytesToInt(evalStack.Pop()))
                        if n < 0 then fail()
                        checkDepth evalStack (n+1)
                        evalStack.Push(evalStack.Item(evalStack.Count-1-n))
                    | 122 ->
                        let n = int(bytesToInt(evalStack.Pop()))
                        if n < 0 then fail()
                        roll n
                    | 123 -> roll 2
                    | 124 ->
                        checkDepth evalStack 2
                        let a = evalStack.Pop()
                        let b = evalStack.Pop()
                        evalStack.Push a
                        evalStack.Push b
                    | 125 ->
                        checkDepth evalStack 2
                        let top = evalStack.Peek()
                        evalStack.Insert(evalStack.Count-2, top)
                    | 130 -> 
                        checkDepth evalStack 1
                        let top = evalStack.Peek()
                        evalStack.Push(intToBytes top.Length)
                    | 135 | 136 ->
                        checkDepth evalStack 2
                        let a = evalStack.Pop()
                        let b = evalStack.Pop()
                        let eq = if hashCompare.Equals(a, b) then 1 else 0
                        evalStack.Push(intToBytes eq)
                        if c = 136 then verify()

Arithmetic operators

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
                    | 139 ->
                        unaryOp(fun x -> x+1L)
                    | 140 ->
                        unaryOp(fun x -> x-1L)
                    | 143 ->
                        unaryOp(fun x -> -x)
                    | 144 ->
                        unaryOp(fun x -> if x > 0L then x else -x)
                    | 145 ->
                        unaryOp(fun x -> if x <> 0L then 0L else 1L)
                    | 146 ->
                        unaryOp(fun x -> if x <> 0L then 1L else 0L)
                    | 147 ->
                        binaryOp(fun x y -> x+y)
                    | 148 ->
                        binaryOp(fun x y -> x-y)

Logical operators

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
                    | 154 ->
                        logicalOp(fun x y -> x&&y)
                    | 155 ->
                        logicalOp(fun x y -> x||y)
                    | 156 ->
                        logicalOp(fun x y -> x=y)
                    | 157 ->
                        logicalOp(fun x y -> x=y)
                        verify()
                    | 158 ->
                        binaryBoolOp(fun x y -> x<>y)
                    | 159 ->
                        binaryBoolOp(fun x y -> x<y)
                    | 160 ->
                        binaryBoolOp(fun x y -> x>y)
                    | 161 ->
                        binaryBoolOp(fun x y -> x<=y)
                    | 162 ->
                        binaryBoolOp(fun x y -> x>=y)
                    | 163 ->
                        binaryOp(fun x y -> if x<y then x else y)
                    | 164 ->
                        binaryOp(fun x y -> if x>y then x else y)
                    | 165 ->
                        tertiaryOp(fun x a b -> if x>=a && x<b then 1 else 0)

Hashing operators

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
                    | 166 ->
                        hashOp(ripe160)
                    | 167 ->
                        hashOp(sha1)
                    | 168 ->
                        hashOp(sha256)
                    | 169 ->
                        hashOp(hash160)
                    | 170 ->
                        hashOp(dsha)
                    | 171 ->
                        codeSep <- int(reader.BaseStream.Position)

Signature verification

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
                    | 172 | 173 ->
                        checkDepth evalStack 2
                        let pub = evalStack.Pop()
                        let signature = evalStack.Pop()
                        let check = checksig script pub signature
                        evalStack.Push(intToBytes(if check then 1 else 0))
                        if c = 173 then verify()
                    | 174 | 175 ->
                        checkDepth evalStack 1
                        let nPubs = int(bytesToInt(evalStack.Pop()))
                        multiSigOps <- nPubs
                        if nPubs > maxMultiSig then fail()
                        checkDepth evalStack nPubs
                        let pubs = seq { for _ in 1..nPubs do yield evalStack.Pop() } |> Seq.toArray
                        let nSigs = int(bytesToInt(evalStack.Pop()))
                        if nSigs > nPubs then fail()
                        checkDepth evalStack nSigs
                        let sigs = seq { for _ in 1..nSigs do yield evalStack.Pop() } |> Seq.toList
                        checkDepth evalStack 1
                        let dummy = evalStack.Pop()
                        if dummy.Length > 0 then fail()
                        let check = checkmultisig script pubs sigs
                        evalStack.Push(intToBytes(if check then 1 else 0))
                        if c = 175 then verify()
                    | _ -> fail()

                innerEval ((if c < 97 then opCount else (opCount+1))+multiSigOps)

        innerEval 0
        checkIfStackEmpty()

P2SH special case

After the P2SH script is evaluated normally, the stack is cleared. The signature script is then reevaluated and the last stack element poped from it. It becomes the new script to evaluate successfully.

TODO: Enforce strict BIP16 rules: No other opcode other than push data and redeem script limit

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
    let evalP2SH (script: byte[]) =
        evalStack.Clear()
        eval true script
        checkDepth evalStack 1
        let redeemScript = evalStack.Pop()
        eval false redeemScript
        popAsBool()

    let redeemScript (script: byte[]) =
        let (_, _, data) = removeData script (fun _ -> true)
        data |> Array.last

    member x.Verify(inScript: byte[], outScript: byte[]) = 
        try
            eval false inScript
            altStack.Clear() // bitcoin core does that
            ifStack.Clear() // 
            eval false outScript
            let res = popAsBool()
            res && (not (isP2SH outScript) || evalP2SH inScript)
        with
        | :? ValidationException -> false

    member x.CheckSigCount (script: byte[]) = scriptCheckSigCount script
    member x.RedeemScript (script: byte[]) = redeemScript script
    static member IsP2SH (script: byte[]) = isP2SH script

    member x.GetData (script: byte[]) = getData script
module Script
namespace System
namespace System.Collections
namespace System.Collections.Generic
namespace System.Linq
namespace System.IO
module Option

from Microsoft.FSharp.Core
val halfN : Numerics.BigInteger

Full name: Script.halfN
val OP_DUP : byte

Full name: Script.OP_DUP
val OP_1 : byte

Full name: Script.OP_1
val OP_16 : byte

Full name: Script.OP_16
val OP_HASH160 : byte

Full name: Script.OP_HASH160
val OP_DATA_20 : byte

Full name: Script.OP_DATA_20
val OP_DATA_32 : byte

Full name: Script.OP_DATA_32
val OP_EQUAL : byte

Full name: Script.OP_EQUAL
val OP_EQUALVERIFY : byte

Full name: Script.OP_EQUALVERIFY
Multiple items
type LiteralAttribute =
  inherit Attribute
  new : unit -> LiteralAttribute

Full name: Microsoft.FSharp.Core.LiteralAttribute

--------------------
new : unit -> LiteralAttribute
val OP_CHECKSIG : byte

Full name: Script.OP_CHECKSIG
val OP_CHECKSIGVERIFY : byte

Full name: Script.OP_CHECKSIGVERIFY
val OP_CHECKMULTISIG : byte

Full name: Script.OP_CHECKMULTISIG
val OP_CHECKMULTISIGVERIFY : byte

Full name: Script.OP_CHECKMULTISIGVERIFY
val OP_DATA_65 : byte

Full name: Script.OP_DATA_65
val OP_DATA_33 : byte

Full name: Script.OP_DATA_33
val stackMaxDepth : int

Full name: Script.stackMaxDepth
val maxPushLength : int

Full name: Script.maxPushLength
val maxMultiSig : int

Full name: Script.maxMultiSig
val maxOpCodeCount : int

Full name: Script.maxOpCodeCount
val maxSigOpCount : int

Full name: Script.maxSigOpCount
val maxScriptSize : int

Full name: Script.maxScriptSize
val scriptToHash : script:byte [] -> 'a

Full name: Script.scriptToHash
val script : byte []
Multiple items
val byte : value:'T -> byte (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.byte

--------------------
type byte = Byte

Full name: Microsoft.FSharp.Core.byte
val p2pkh : obj
property Array.Length: int
val p2sh : obj
val isP2SH : script:byte [] -> bool

Full name: Script.isP2SH
val isPay2PubKey : script:byte [] -> bool

Full name: Script.isPay2PubKey
val isPay2MultiSig : script:byte [] -> bool

Full name: Script.isPay2MultiSig
val ECDSACheck : txHash:byte [] * pub:byte [] * signature:byte [] -> bool

Full name: Script.ECDSACheck
val txHash : byte []
val pub : byte []
val signature : byte []
val result : obj
Multiple items
type List<'T> =
  new : unit -> List<'T> + 2 overloads
  member Add : item:'T -> unit
  member AddRange : collection:IEnumerable<'T> -> unit
  member AsReadOnly : unit -> ReadOnlyCollection<'T>
  member BinarySearch : item:'T -> int + 2 overloads
  member Capacity : int with get, set
  member Clear : unit -> unit
  member Contains : item:'T -> bool
  member ConvertAll<'TOutput> : converter:Converter<'T, 'TOutput> -> List<'TOutput>
  member CopyTo : array:'T[] -> unit + 2 overloads
  ...
  nested type Enumerator

Full name: System.Collections.Generic.List<_>

--------------------
List() : unit
List(capacity: int) : unit
List(collection: IEnumerable<'T>) : unit
val x : List<'T>
member List.Push : v:'T -> unit

Full name: Script.Push
val v : 'T
List.Add(item: 'T) : unit
member List.Pop : unit -> 'T

Full name: Script.Pop
member List.Peek : unit -> 'T
List.RemoveAt(index: int) : unit
Multiple items
property List.Count: int

--------------------
(extension) IEnumerable.Count<'TSource>() : int
(extension) IEnumerable.Count<'TSource>(predicate: Func<'TSource,bool>) : int
member List.Peek : unit -> 'T

Full name: Script.Peek
property List.Item: int -> 'T
Multiple items
type ByteList =
  inherit List<byte []>
  new : unit -> ByteList
  member Push : v:byte [] -> unit

Full name: Script.ByteList

--------------------
new : unit -> ByteList
val x : ByteList
member ByteList.Push : v:byte [] -> unit

Full name: Script.ByteList.Push
val v : byte []
val computeTxHash : tx:'a -> index:int -> subScript:byte [] -> sigType:int -> byte []

Full name: Script.computeTxHash
val tx : 'a
val index : int
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
val subScript : byte []
val sigType : int
val mutable returnBuggyHash : bool
val anyoneCanPay : bool
val sigHashType : byte
val oms : MemoryStream
Multiple items
type MemoryStream =
  inherit Stream
  new : unit -> MemoryStream + 6 overloads
  member CanRead : bool
  member CanSeek : bool
  member CanWrite : bool
  member Capacity : int with get, set
  member Flush : unit -> unit
  member GetBuffer : unit -> byte[]
  member Length : int64
  member Position : int64 with get, set
  member Read : buffer:byte[] * offset:int * count:int -> int
  ...

Full name: System.IO.MemoryStream

--------------------
MemoryStream() : unit
MemoryStream(capacity: int) : unit
MemoryStream(buffer: byte []) : unit
MemoryStream(buffer: byte [], writable: bool) : unit
MemoryStream(buffer: byte [], index: int, count: int) : unit
MemoryStream(buffer: byte [], index: int, count: int, writable: bool) : unit
MemoryStream(buffer: byte [], index: int, count: int, writable: bool, publiclyVisible: bool) : unit
val writer : BinaryWriter
Multiple items
type BinaryWriter =
  new : output:Stream -> BinaryWriter + 1 overload
  member BaseStream : Stream
  member Close : unit -> unit
  member Dispose : unit -> unit
  member Flush : unit -> unit
  member Seek : offset:int * origin:SeekOrigin -> int64
  member Write : value:bool -> unit + 17 overloads
  static val Null : BinaryWriter

Full name: System.IO.BinaryWriter

--------------------
BinaryWriter(output: Stream) : unit
BinaryWriter(output: Stream, encoding: Text.Encoding) : unit
BinaryWriter.Write(value: string) : unit
   (+0 other overloads)
BinaryWriter.Write(value: float32) : unit
   (+0 other overloads)
BinaryWriter.Write(value: uint64) : unit
   (+0 other overloads)
BinaryWriter.Write(value: int64) : unit
   (+0 other overloads)
BinaryWriter.Write(value: uint32) : unit
   (+0 other overloads)
BinaryWriter.Write(value: int) : unit
   (+0 other overloads)
BinaryWriter.Write(value: uint16) : unit
   (+0 other overloads)
BinaryWriter.Write(value: int16) : unit
   (+0 other overloads)
BinaryWriter.Write(value: decimal) : unit
   (+0 other overloads)
BinaryWriter.Write(value: float) : unit
   (+0 other overloads)
Multiple items
type Version =
  new : unit -> Version + 4 overloads
  member Build : int
  member Clone : unit -> obj
  member CompareTo : version:obj -> int + 1 overload
  member Equals : obj:obj -> bool + 1 overload
  member GetHashCode : unit -> int
  member Major : int
  member MajorRevision : int16
  member Minor : int
  member MinorRevision : int16
  ...

Full name: System.Version

--------------------
Version() : unit
Version(version: string) : unit
Version(major: int, minor: int) : unit
Version(major: int, minor: int, build: int) : unit
Version(major: int, minor: int, build: int, revision: int) : unit
val txIn2 : obj
type Array =
  member Clone : unit -> obj
  member CopyTo : array:Array * index:int -> unit + 1 overload
  member GetEnumerator : unit -> IEnumerator
  member GetLength : dimension:int -> int
  member GetLongLength : dimension:int -> int64
  member GetLowerBound : dimension:int -> int
  member GetUpperBound : dimension:int -> int
  member GetValue : params indices:int[] -> obj + 7 overloads
  member Initialize : unit -> unit
  member IsFixedSize : bool
  ...

Full name: System.Array
val iteri : action:(int -> 'T -> unit) -> array:'T [] -> unit

Full name: Microsoft.FSharp.Collections.Array.iteri
val i : int
val txIn : obj
val empty<'T> : 'T []

Full name: Microsoft.FSharp.Collections.Array.empty
val txOut : obj
val i : int32
val ignore : value:'T -> unit

Full name: Microsoft.FSharp.Core.Operators.ignore
val hash : byte []
val zeroCreate : count:int -> 'T []

Full name: Microsoft.FSharp.Collections.Array.zeroCreate
val data : byte []
MemoryStream.ToArray() : byte []
val bigintToBytes : i:bigint -> byte []

Full name: Script.bigintToBytes
val i : bigint
type bigint = Numerics.BigInteger

Full name: Microsoft.FSharp.Core.bigint
val pos : bigint
val abs : value:'T -> 'T (requires member Abs)

Full name: Microsoft.FSharp.Core.Operators.abs
val bi : byte []
Numerics.BigInteger.ToByteArray() : byte []
val posTrimIdx : int
val iTrimIdx : int
val bytes : byte []
val intToBytes : i:int -> byte []

Full name: Script.intToBytes
val int64ToBytes : i:int64 -> byte []

Full name: Script.int64ToBytes
val i : int64
Multiple items
val int64 : value:'T -> int64 (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int64

--------------------
type int64 = Int64

Full name: Microsoft.FSharp.Core.int64

--------------------
type int64<'Measure> = int64

Full name: Microsoft.FSharp.Core.int64<_>
val bytesToInt : bytes:byte [] -> Numerics.BigInteger

Full name: Script.bytesToInt
val b : byte []
Array.Copy(sourceArray: Array, destinationArray: Array, length: int64) : unit
Array.Copy(sourceArray: Array, destinationArray: Array, length: int) : unit
Array.Copy(sourceArray: Array, sourceIndex: int64, destinationArray: Array, destinationIndex: int64, length: int64) : unit
Array.Copy(sourceArray: Array, sourceIndex: int, destinationArray: Array, destinationIndex: int, length: int) : unit
val neg : bool
val bi : Numerics.BigInteger
Multiple items
type ScriptRuntime =
  new : getTxHash:(byte [] -> int -> byte []) -> ScriptRuntime
  member CheckSigCount : script:byte [] -> int
  member GetData : script:byte [] -> byte [] []
  member RedeemScript : script:byte [] -> byte []
  member Verify : inScript:byte [] * outScript:byte [] -> bool
  static member IsP2SH : script:byte [] -> bool

Full name: Script.ScriptRuntime

--------------------
new : getTxHash:(byte [] -> int -> byte []) -> ScriptRuntime
val getTxHash : (byte [] -> int -> byte [])
val evalStack : ByteList
val altStack : ByteList
val ifStack : List<bool>
type bool = Boolean

Full name: Microsoft.FSharp.Core.bool
val mutable skipping : bool
val mutable codeSep : int
val checkDepth : (List<'a> -> int -> unit)
val stack : List<'a>
val minDepth : int
val raise : exn:Exception -> 'T

Full name: Microsoft.FSharp.Core.Operators.raise
val checkMaxDepth : (unit -> unit)
val popAsBool : (unit -> bool)
member List.Pop : unit -> 'T
val verify : (unit -> unit)
val not : value:bool -> bool

Full name: Microsoft.FSharp.Core.Operators.not
val fail : (unit -> 'a)
member List.Push : v:'T -> unit
member ByteList.Push : v:byte [] -> unit
val checkOverflow : (byte [] -> byte [])
val checkIfStackEmpty : (unit -> unit)
val roll : (int -> unit)
val m : int
val n : int
val top : byte []
property List.Item: int -> byte []
val dup : (int -> int -> unit)
val depth : int
val unaryOp : ((int64 -> int64) -> unit)
val f : (int64 -> int64)
val arg : int64
val binaryOp : ((int64 -> int64 -> int64) -> unit)
val f : (int64 -> int64 -> int64)
val arg2 : int64
val arg1 : int64
val logicalOp : ((bool -> bool -> bool) -> unit)
val f : (bool -> bool -> bool)
val arg2 : bool
val x : int
val arg1 : bool
val x : bool
val binaryBoolOp : ((int -> int -> bool) -> unit)
val f : (int -> int -> bool)
val arg2 : int
val arg1 : int
val tertiaryOp : ((int -> int -> int -> int) -> unit)
val f : (int -> int -> int -> int)
val arg3 : int
val hashOp : ((byte [] -> byte []) -> unit)
val f : (byte [] -> byte [])
val removeData : (byte [] -> (byte [] -> bool) -> bool * byte [] * byte [] [])
val pred : (byte [] -> bool)
val dataList : List<byte []>
val ms : MemoryStream
val reader : BinaryReader
Multiple items
type BinaryReader =
  new : input:Stream -> BinaryReader + 1 overload
  member BaseStream : Stream
  member Close : unit -> unit
  member Dispose : unit -> unit
  member PeekChar : unit -> int
  member Read : unit -> int + 2 overloads
  member ReadBoolean : unit -> bool
  member ReadByte : unit -> byte
  member ReadBytes : count:int -> byte[]
  member ReadChar : unit -> char
  ...

Full name: System.IO.BinaryReader

--------------------
BinaryReader(input: Stream) : unit
BinaryReader(input: Stream, encoding: Text.Encoding) : unit
val removeDataInner : (unit -> byte [])
property MemoryStream.Position: int64
property MemoryStream.Length: int64
val b : byte
BinaryReader.ReadByte() : byte
val c : int
val startPos : int64
val len : int
BinaryReader.ReadInt16() : int16
BinaryReader.ReadInt32() : int
val canonical : int
BinaryReader.ReadBytes(count: int) : byte []
val lenRead : int
MemoryStream.Seek(offset: int64, loc: SeekOrigin) : int64
type SeekOrigin =
  | Begin = 0
  | Current = 1
  | End = 2

Full name: System.IO.SeekOrigin
field SeekOrigin.Begin = 0
List.Add(item: byte []) : unit
Stream.Dispose() : unit
(extension) IEnumerable.ToArray<'TSource>() : 'TSource []
List.ToArray() : byte [] []
val e : exn
val currentPos : int
val removeSignatures : (byte [] -> HashSet<byte []> -> byte [])
val signatures : HashSet<byte []>
Multiple items
type HashSet<'T> =
  new : unit -> HashSet<'T> + 3 overloads
  member Add : item:'T -> bool
  member Clear : unit -> unit
  member Comparer : IEqualityComparer<'T>
  member Contains : item:'T -> bool
  member CopyTo : array:'T[] -> unit + 2 overloads
  member Count : int
  member ExceptWith : other:IEnumerable<'T> -> unit
  member GetEnumerator : unit -> Enumerator<'T>
  member GetObjectData : info:SerializationInfo * context:StreamingContext -> unit
  ...
  nested type Enumerator

Full name: System.Collections.Generic.HashSet<_>

--------------------
HashSet() : unit
HashSet(comparer: IEqualityComparer<'T>) : unit
HashSet(collection: IEnumerable<'T>) : unit
HashSet(collection: IEnumerable<'T>, comparer: IEqualityComparer<'T>) : unit
val success : bool
(extension) IEnumerable.Contains<'TSource>(value: 'TSource) : bool
HashSet.Contains(item: byte []) : bool
(extension) IEnumerable.Contains<'TSource>(value: 'TSource, comparer: IEqualityComparer<'TSource>) : bool
val getData : (byte [] -> byte [] [])
val data : byte [] []
val scriptCheckSigCount : (byte [] -> int)
val ops : byte []
module Seq

from Microsoft.FSharp.Collections
val map : mapping:('T -> 'U) -> source:seq<'T> -> seq<'U>

Full name: Microsoft.FSharp.Collections.Seq.map
val x : obj
val op1 : byte
val op2 : byte
val sum : source:seq<'T> -> 'T (requires member ( + ) and member get_Zero)

Full name: Microsoft.FSharp.Collections.Seq.sum
val checksigInner : (byte [] -> byte [] -> byte [] -> bool)
val pubScript : byte []
val sigType : byte
val checksig : (byte [] -> byte [] -> byte [] -> bool)
val checkmultisig : (byte [] -> byte [] [] -> byte [] list -> bool)
val pubs : byte [] []
val sigs : byte [] list
type 'T list = List<'T>

Full name: Microsoft.FSharp.Collections.list<_>
val checkOneSig : (byte [] list -> byte [] -> byte [] list option)
val pubs : byte [] list
val tryFindIndex : predicate:('T -> bool) -> list:'T list -> int option

Full name: Microsoft.FSharp.Collections.List.tryFindIndex
val map : mapping:('T -> 'U) -> option:'T option -> 'U option

Full name: Microsoft.FSharp.Core.Option.map
val skip : count:int -> source:seq<'T> -> seq<'T>

Full name: Microsoft.FSharp.Collections.Seq.skip
val toList : source:seq<'T> -> 'T list

Full name: Microsoft.FSharp.Collections.Seq.toList
val toList : array:'T [] -> 'T list

Full name: Microsoft.FSharp.Collections.Array.toList
val isSome : option:'T option -> bool

Full name: Microsoft.FSharp.Core.Option.isSome
val eval : (bool -> byte [] -> unit)
val pushOnly : bool
val innerEval : (int -> unit)
val opCount : int
val mutable multiSigOps : int
member List.Push : v:'T -> unit
val t : byte []
val a : byte []
List.Insert(index: int, item: byte []) : unit
val eq : int
val x : int64
val y : int64
val y : bool
val y : int
val a : int
val b : int
property BinaryReader.BaseStream: Stream
property Stream.Position: int64
val check : bool
val nPubs : int
Multiple items
val seq : sequence:seq<'T> -> seq<'T>

Full name: Microsoft.FSharp.Core.Operators.seq

--------------------
type seq<'T> = IEnumerable<'T>

Full name: Microsoft.FSharp.Collections.seq<_>
val toArray : source:seq<'T> -> 'T []

Full name: Microsoft.FSharp.Collections.Seq.toArray
val nSigs : int
val dummy : byte []
val evalP2SH : (byte [] -> bool)
List.Clear() : unit
val redeemScript : byte []
val redeemScript : (byte [] -> byte [])
val last : array:'T [] -> 'T

Full name: Microsoft.FSharp.Collections.Array.last
val x : ScriptRuntime
member ScriptRuntime.Verify : inScript:byte [] * outScript:byte [] -> bool

Full name: Script.ScriptRuntime.Verify
val inScript : byte []
val outScript : byte []
val res : bool
member ScriptRuntime.CheckSigCount : script:byte [] -> int

Full name: Script.ScriptRuntime.CheckSigCount
member ScriptRuntime.RedeemScript : script:byte [] -> byte []

Full name: Script.ScriptRuntime.RedeemScript
static member ScriptRuntime.IsP2SH : script:byte [] -> bool

Full name: Script.ScriptRuntime.IsP2SH
member ScriptRuntime.GetData : script:byte [] -> byte [] []

Full name: Script.ScriptRuntime.GetData