|
| 1 | +let post11 = Post("/post/2025-03-27-psbt", "Partially Signed Bitcoin Transactions", "2025-03-27T12:00:00Z", .specification) { """ |
| 2 | +
|
| 3 | +One popular request for Swift Bitcoin is to support the Partially Signed Bitcoin Transactions (PSBT) format defined in BIP174 et al. Making PSBT flows available early on is great not only for users but as a trial for the entire framework. |
| 4 | +
|
| 5 | +The PSBT version 0 and 2 specifications cover pretty much all standard interactions with Bitcoin transactions from when they are first created and funded, until they are broadcasted to the network. |
| 6 | +""" } |
| 7 | + |
| 8 | +let post10 = Post("/post/2025-03-24-node-config-swift", "Node Configuration in Swift", "2025-03-24T12:00:00Z", .implementation) { """ |
| 9 | +
|
| 10 | +A cool feature of the Swift Package Manager (SPM) is how its package manifest format is itself part of the Swift Language. This is easier before compilation when the project's source code and the Swift compiler are at hand but can that approach be extended for compiled tools like `bcnode`? |
| 11 | +
|
| 12 | +We accepted the challenge as it would be fitting for our product to have its configuration optionally specified in Swift. This of course will only be possible if the Swift Runtime is installed on the user's system which is why we'll also allow JSON5 versions of the configuration file. |
| 13 | +
|
| 14 | +Actually why not make the JSON format precisely match the serialization of the Swift Bitcoin configuration format in Swift? This way we can decode a JSON configuration and get the internal representation which we can work with directly in our source code. Conversely, if we take a `config.swift` file and serialize the object defined inside, we will end up with the JSON version of the same parameters. |
| 15 | +
|
| 16 | +So how can we achieve this versatility? Swift can act as both a compiled or an interpreted language which definitely can come in handy. One thing it cannot do though is evaluate Swift code from within a compiled program so we need to be creative. Our solution involves reading the configuration file containing a variable declaration. We will prepend that declaration with the necessary type definition. |
| 17 | +""" } |
| 18 | + |
| 19 | +let post09 = Post("/post/2025-03-21-miniscript-dsl", "Miniscript DSL", "2025-03-21T12:00:00Z", .specification) { """ |
| 20 | +
|
| 21 | +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](https://bitcoin.sipa.be/miniscript/) implemented as a [Swift DSL](https://developer.apple.com/videos/play/wwdc2021/10253) and have the compiler check its syntax entirely. |
| 22 | +
|
| 23 | +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. |
| 24 | +
|
| 25 | +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. |
| 26 | +
|
| 27 | +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. |
| 28 | +
|
| 29 | +```swift |
| 30 | +public protocol MiniscriptExp { |
| 31 | + var compiled: [ScriptOp] { get } |
| 32 | +} |
| 33 | +
|
| 34 | +/// B, K or V |
| 35 | +public protocol ExpBKV: MiniscriptExp { } |
| 36 | +
|
| 37 | +public protocol ExpB: ExpBKV { } |
| 38 | +
|
| 39 | +public protocol ModZ: MiniscriptExp { } |
| 40 | +… |
| 41 | +``` |
| 42 | +
|
| 43 | +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. |
| 44 | +
|
| 45 | +```swift |
| 46 | +public struct Older: ExpB, ModZ { |
| 47 | + let n: Int |
| 48 | +
|
| 49 | + public var compiled: [ScriptOp] { |
| 50 | + [.encodeMinimally(n), .checkSequenceVerify] |
| 51 | + } |
| 52 | +} |
| 53 | +
|
| 54 | +public struct OrB<X: ExpB & ModD, Z: ExpW & ModD>: ExpB, ModD, ModU { … } |
| 55 | +
|
| 56 | +public struct AndOr<X: ExpB, Y: ExpBKV, Z: ExpBKV>: ExpBKV { … } |
| 57 | +
|
| 58 | +extension AndOr: ModZ where X: ModZ, Y: ModZ, Z: ModZ { } |
| 59 | +… |
| 60 | +``` |
| 61 | +
|
| 62 | +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. |
| 63 | +
|
| 64 | +```swift |
| 65 | +public func ~<X: ExpB>(lhs: @escaping (_ x: X) -> U_<X>, rhs: X) -> U_<X> { lhs(rhs) } |
| 66 | +public func U<X: ExpB>(_ x: X) -> U_<X> { U_(x) } |
| 67 | +public struct U_<X: ExpB>: ExpB, ModD { |
| 68 | +
|
| 69 | +public func ~<X: ExpB>(lhs: @escaping (_ x: X) -> S_<L_<N_<X>>>, rhs: X) -> S_<L_<N_<X>>> { lhs(rhs) } |
| 70 | +public func SLN<X: ExpB>(_ x: X) -> S_<L_<N_<X>>> { S_(L_(N_(x))) } |
| 71 | +``` |
| 72 | +
|
| 73 | +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](https://github.com/swift-bitcoin/swift-bitcoin/blob/develop/test/bitcoin-miniscript/Miniscript.swift) from the specification: |
| 74 | +
|
| 75 | +```swift |
| 76 | +// thresh(3,pk(key1),s:pk(key2),s:pk(key3),sln:older(12960)) |
| 77 | +
|
| 78 | +Thresh(3, PK(key1), S~PK(key2), S~PK(key3), SLN~Older(12960)) |
| 79 | +``` |
| 80 | +
|
| 81 | +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. |
| 82 | +
|
| 83 | +The [Bitcoin Miniscript](https://swiftbitcoin.org/docs/miniscript/documentation/bitcoinminiscript/) DSL will be part of the initial release of Swift Bitcoin and can be tested right now. |
| 84 | +
|
| 85 | +PS: If you'd like to help enhance the current solution there's an open discussion on the [Swift Forums](https://forums.swift.org/t/miniscript-dsl-implementation-how-to-simplify-complex-solution/78684) which shares some of the challenges faced and how we worked around them in a less-than-ideal fashion. |
| 86 | +""" } |
| 87 | + |
1 | 88 | let post08 = Post("/post/2025-01-28-improved-docs-generation", "Improved Docs Generation", "2025-01-28T12:00:00Z", .housekeeping) { """ |
2 | 89 |
|
3 | 90 | The [/docs](https://swift-bitcoin.github.io/docs) subfolder on this site is now generated independently from the [main repo](https://github.com/swift-bitcoin/swift-bitcoin) which means more frequent updates and accuracy in regards to the actual source code. |
|
0 commit comments