Swift Bitcoin – Full node library

Mar 21, 2025 • Feature Specification

Miniscript DSL

One of the superpowers of Swift is the ability to define Domain Specific Languages (DSL) directly over the type system. That presented a great opportunity to have Bitcoin Miniscript implemented as a Swift DSL and have the compiler check its syntax entirely.

On its face Miniscript has a very straightforward syntax: fragments use a fragment(arguments,...) notation while wrappers are written using prefixes separated from other fragments by a colon. The colon is dropped between subsequent wrappers for instance in dv:older(144) the d: wrapper applied to the v: wrapper applied to the older fragment for 144 blocks.

Aside from mapping the Miniscript format closely to something that the Swift Compiler can understand we face additional challenges. While the language is designed to be composable not all expressions are mutually compatible. There's four basic expression types: base (B), verify, (V) key (K) and wrapped (W). There's also five independent type modifiers: zero-arg (z), one-arg (o), non-zero (n), dissatisfiable (d) and unit (u) for additional guaranties.

To model this we created two sets of protocols: one for expression types and an orthogonal one for modifiers. Also we included some aggregate protocols for cases where either one type or another was required by the fragment. Finally the top level protocol MiniscriptExp represents the all-important capability of being able to emit (transpile into) actual Bitcoin SCRIPT directly from a Miniscript program.

public protocol MiniscriptExp {
    var compiled: [ScriptOp] { get }
}

/// B, K or V
public protocol ExpBKV: MiniscriptExp { }

public protocol ExpB: ExpBKV { }

public protocol ModZ: MiniscriptExp { }
…

This allows us to specify exactly which type of expression we will construct and which type a particular fragment requires as argument. Even cases where an optional trait of an argument has an effect on the output's modifiers can be modeled.

public struct Older: ExpB, ModZ {
    let n: Int

    public var compiled: [ScriptOp] {
        [.encodeMinimally(n), .checkSequenceVerify]
    }
}

public struct OrB<X: ExpB & ModD, Z: ExpW & ModD>: ExpB, ModD, ModU { … }

public struct AndOr<X: ExpB, Y: ExpBKV, Z: ExpBKV>: ExpBKV { … }

extension AndOr: ModZ where X: ModZ, Y: ModZ, Z: ModZ { }
…

For the case of wrappers we'll make use of Swift operator overload feature. Unfortunately the semi-colon is not a valid identifier so we used ~ instead. To group valid combinations of nested wrappers together we'll just generate functions for each of them. It ends up reading somewhat cryptic but all of this complexity will be hidden from the smart contract programmer.

public func ~<X: ExpB>(lhs: @escaping (_ x: X) -> U_<X>, rhs: X) -> U_<X> { lhs(rhs) }
public func U<X: ExpB>(_ x: X) -> U_<X> { U_(x) }
public struct U_<X: ExpB>: ExpB, ModD {

public func ~<X: ExpB>(lhs: @escaping (_ x: X) -> S_<L_<N_<X>>>, rhs: X) -> S_<L_<N_<X>>> { lhs(rhs) }
public func SLN<X: ExpB>(_ x: X) -> S_<L_<N_<X>>> { S_(L_(N_(x))) }

And with all that the finally result is one of an uncanny resemblance between the original Miniscript source and the DSL version of it. Take a look at this example from the specification:

// thresh(3,pk(key1),s:pk(key2),s:pk(key3),sln:older(12960))

Thresh(3, PK(key1), S~PK(key2), S~PK(key3), SLN~Older(12960))

From writing the policy like the one above directly in Swift we get full type checking at compile time and the ability to execute without the need for parsing.

The Bitcoin Miniscript DSL will be part of the initial release of Swift Bitcoin and can be tested right now.

PS: If you'd like to help enhance the current solution there's an open discussion on the Swift Forums which shares some of the challenges faced and how we worked around them in a less-than-ideal fashion.