Skip to content

Commit bb89029

Browse files
authored
Merge pull request #167 from yenom/refactor-MockUnlockScriptBuilder
Refactor mock unlock script builder
2 parents 7a5a78d + ca57e06 commit bb89029

File tree

5 files changed

+161
-88
lines changed

5 files changed

+161
-88
lines changed

BitcoinKit.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@
132132
2914BE5C211BD0DF00B349CB /* OP_CHECKSEQUENCEVERIFY.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2914BE5B211BD0DF00B349CB /* OP_CHECKSEQUENCEVERIFY.swift */; };
133133
2914BE5E211C062200B349CB /* OP_PUBKEYHASH.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2914BE5D211C062200B349CB /* OP_PUBKEYHASH.swift */; };
134134
2914BE60211C063300B349CB /* OP_PUBKEY.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2914BE5F211C063300B349CB /* OP_PUBKEY.swift */; };
135+
292185372154B50E00570618 /* MockUnlockScriptBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292185362154B50E00570618 /* MockUnlockScriptBuilder.swift */; };
135136
29248EEF2104B64E00CC9051 /* ScriptChunkHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29248EEE2104B64E00CC9051 /* ScriptChunkHelper.swift */; };
136137
29290B8D210AF59600D2BE78 /* OpCodeFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29290B8C210AF59600D2BE78 /* OpCodeFactory.swift */; };
137138
29290B91210AF79300D2BE78 /* OP_DUP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29290B90210AF79300D2BE78 /* OP_DUP.swift */; };
@@ -357,6 +358,7 @@
357358
2914BE5B211BD0DF00B349CB /* OP_CHECKSEQUENCEVERIFY.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OP_CHECKSEQUENCEVERIFY.swift; sourceTree = "<group>"; };
358359
2914BE5D211C062200B349CB /* OP_PUBKEYHASH.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OP_PUBKEYHASH.swift; sourceTree = "<group>"; };
359360
2914BE5F211C063300B349CB /* OP_PUBKEY.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OP_PUBKEY.swift; sourceTree = "<group>"; };
361+
292185362154B50E00570618 /* MockUnlockScriptBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUnlockScriptBuilder.swift; sourceTree = "<group>"; };
360362
29248EEE2104B64E00CC9051 /* ScriptChunkHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptChunkHelper.swift; sourceTree = "<group>"; };
361363
29290B8C210AF59600D2BE78 /* OpCodeFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpCodeFactory.swift; sourceTree = "<group>"; };
362364
29290B90210AF79300D2BE78 /* OP_DUP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OP_DUP.swift; sourceTree = "<group>"; };
@@ -583,6 +585,7 @@
583585
29089F112122D07500E0C305 /* Mock */ = {
584586
isa = PBXGroup;
585587
children = (
588+
292185362154B50E00570618 /* MockUnlockScriptBuilder.swift */,
586589
29089F0B2122B9CD00E0C305 /* MockHelper.swift */,
587590
29089F0F2122D06300E0C305 /* MockKey.swift */,
588591
);
@@ -1190,6 +1193,7 @@
11901193
0C1DD40821181719004BA8A8 /* OP_ABS.swift in Sources */,
11911194
1482B5E8202721FF0098B612 /* HDPrivateKey.swift in Sources */,
11921195
298E17CD2150AEB300FF6C77 /* StandardTransactionBuilder.swift in Sources */,
1196+
292185372154B50E00570618 /* MockUnlockScriptBuilder.swift in Sources */,
11931197
0C09002621169B430077E9BC /* OP_MIN.swift in Sources */,
11941198
0C1DD421211820D4004BA8A8 /* OP_DIV.swift in Sources */,
11951199
0C1DE157211D79D900FE8E43 /* OP_FROMALTSTACK.swift in Sources */,

Sources/BitcoinKit/Mock/MockHelper.swift

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,8 @@
2424

2525
import Foundation
2626

27-
public typealias SigKeyPair = (sig: Data, key: MockKey)
28-
29-
public typealias SingleKeyScriptBuilder = ((SigKeyPair) -> Script)
30-
public typealias MultiKeyScriptBuilder = (([SigKeyPair]) -> Script)
31-
3227
public struct MockHelper {
28+
3329
public static func createUtxo(lockScript: Script) -> UnspentTransaction {
3430
let outputMock = TransactionOutput(value: 100_000_000, lockingScript: lockScript.data)
3531
let outpointMock = TransactionOutPoint(hash: Data(), index: 0)
@@ -79,7 +75,7 @@ public struct MockHelper {
7975
lockTime: tx.lockTime)
8076
}
8177

82-
public static func verifySingleKey(lockScript: Script, unlockScriptBuilder: SingleKeyScriptBuilder, key: MockKey, verbose: Bool = true) throws -> Bool {
78+
public static func verifySingleKey(lockScript: Script, unlockScriptBuilder: MockUnlockScriptBuilder, key: MockKey, verbose: Bool = true) throws -> Bool {
8379
// mocks
8480
let utxoMock: UnspentTransaction = MockHelper.createUtxo(lockScript: lockScript)
8581
let txMock: Transaction = MockHelper.createTransaction(utxo: utxoMock)
@@ -88,8 +84,8 @@ public struct MockHelper {
8884
let hashType = SighashType.BCH.ALL
8985
let signature: Data = key.privkey.sign(txMock, utxoToSign: utxoMock, hashType: hashType)
9086
let sigWithHashType: Data = signature + UInt8(hashType)
91-
let unlockScript: Script = unlockScriptBuilder((sigWithHashType, key))
92-
87+
let pair: SigKeyPair = SigKeyPair(sigWithHashType, key.pubkey)
88+
let unlockScript: Script = unlockScriptBuilder.build(pairs: [pair])
9389
// signed tx
9490
let signedTxMock = MockHelper.updateTransaction(txMock, unlockScriptData: unlockScript.data)
9591

@@ -101,7 +97,7 @@ public struct MockHelper {
10197
return try ScriptMachine.verify(lockScript: lockScript, unlockScript: unlockScript, context: context)
10298
}
10399

104-
public static func verifyMultiKey(lockScript: Script, unlockScriptBuilder: MultiKeyScriptBuilder, keys: [MockKey], verbose: Bool = true) throws -> Bool {
100+
public static func verifyMultiKey(lockScript: Script, unlockScriptBuilder: MockUnlockScriptBuilder, keys: [MockKey], verbose: Bool = true) throws -> Bool {
105101
// mocks
106102
let utxoMock: UnspentTransaction = MockHelper.createUtxo(lockScript: lockScript)
107103
let txMock: Transaction = MockHelper.createTransaction(utxo: utxoMock)
@@ -112,11 +108,10 @@ public struct MockHelper {
112108
for key in keys {
113109
let signature: Data = key.privkey.sign(txMock, utxoToSign: utxoMock, hashType: hashType)
114110
let sigWithHashType: Data = signature + UInt8(hashType)
115-
sigKeyPairs.append(SigKeyPair(sigWithHashType, key))
111+
sigKeyPairs.append(SigKeyPair(sigWithHashType, key.pubkey))
116112
}
117113

118-
let unlockScript: Script = unlockScriptBuilder(sigKeyPairs)
119-
114+
let unlockScript: Script = unlockScriptBuilder.build(pairs: sigKeyPairs)
120115
// signed tx
121116
let signedTxMock = MockHelper.updateTransaction(txMock, unlockScriptData: unlockScript.data)
122117

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//
2+
// MockUnlockScriptBuilder.swift
3+
//
4+
// Copyright © 2018 BitcoinKit developers
5+
//
6+
// Permission is hereby granted, free of charge, to any person obtaining a copy
7+
// of this software and associated documentation files (the "Software"), to deal
8+
// in the Software without restriction, including without limitation the rights
9+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
// copies of the Software, and to permit persons to whom the Software is
11+
// furnished to do so, subject to the following conditions:
12+
//
13+
// The above copyright notice and this permission notice shall be included in
14+
// all copies or substantial portions of the Software.
15+
//
16+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
// THE SOFTWARE.
23+
//
24+
25+
import Foundation
26+
27+
public typealias SigKeyPair = (signature: Data, key: PublicKey)
28+
29+
// This protocol is prepared for playing with Script.
30+
// If you are building production application, you should implement TransactionSigner instead of MockUnlockScriptBuilder.
31+
public protocol MockUnlockScriptBuilder {
32+
func build(pairs: [SigKeyPair]) -> Script
33+
}

Sources/BitcoinKit/Scripts/ScriptMachine.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,9 @@ public struct ScriptMachine {
9898
throw ScriptMachineError.error("Last item on the stack is false.")
9999
}
100100
} else {
101-
print("This is not p2sh")
102-
print(context.shouldVerifyP2SH(), lockScript.isPayToScriptHashScript)
101+
if context.verbose {
102+
print("context.shouldVerifyP2SH : ", context.shouldVerifyP2SH(), "isP2SH : ", lockScript.isPayToScriptHashScript)
103+
}
103104
}
104105

105106
// If nothing failed, validation passed.

Tests/BitcoinKitTests/MockHelperTests.swift

Lines changed: 114 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,35 @@ import XCTest
2727

2828
class MockHelperTests: XCTestCase {
2929
// MARK: - 1 of 3 Multi-Sig [ABC]
30-
// Standard Multi-Sig
31-
func testStandard() {
32-
func verify(with key: MockKey) throws -> Bool {
33-
// lock script
34-
let standardLockScript = Script(publicKeys: [MockKey.keyA.pubkey, MockKey.keyB.pubkey, MockKey.keyC.pubkey], signaturesRequired: 1)!
35-
36-
// unlock script builder
37-
let standardUnlockScript = { (sig: Data, key: MockKey) -> Script in
30+
// MARK: Standard Multi-Sig
31+
struct Standard1of3 {
32+
static let lockScript: Script = Script(publicKeys: [MockKey.keyA.pubkey,
33+
MockKey.keyB.pubkey,
34+
MockKey.keyC.pubkey],
35+
signaturesRequired: 1)!
36+
37+
struct UnlockScriptBuilder: MockUnlockScriptBuilder {
38+
func build(pairs: [SigKeyPair]) -> Script {
39+
guard let signature = pairs.first?.signature else {
40+
return Script()
41+
}
42+
3843
let script = try! Script()
3944
.append(.OP_0)
40-
.appendData(sig)
45+
.appendData(signature)
4146
return script
4247
}
43-
44-
return try MockHelper.verifySingleKey(lockScript: standardLockScript, unlockScriptBuilder: standardUnlockScript, key: key, verbose: false)
48+
}
49+
}
50+
51+
52+
func testStandard() {
53+
func verify(with key: MockKey) throws -> Bool {
54+
return try MockHelper.verifySingleKey(
55+
lockScript: Standard1of3.lockScript,
56+
unlockScriptBuilder: Standard1of3.UnlockScriptBuilder(),
57+
key: key,
58+
verbose: false)
4559
}
4660

4761
func succeed(with key: MockKey) {
@@ -72,22 +86,32 @@ class MockHelperTests: XCTestCase {
7286
}
7387

7488
// P2SH Multi-Sig
75-
func testP2SH() {
76-
func verify(with key: MockKey) throws -> Bool {
77-
// P2SH multisig[ABC]
78-
let redeemScript: Script = Script(publicKeys: [MockKey.keyA.pubkey, MockKey.keyB.pubkey, MockKey.keyC.pubkey], signaturesRequired: 1)!
79-
80-
let p2shLockScript: Script = redeemScript.toP2SH()
81-
82-
// p2sh multisig[ABC] unlock
83-
let p2shUnlockScriptBuilder = { (sigWithHashType: Data, key: MockKey) -> Script in
89+
struct P2SH1of3 {
90+
static let redeemScript: Script = Script(publicKeys: [MockKey.keyA.pubkey, MockKey.keyB.pubkey, MockKey.keyC.pubkey], signaturesRequired: 1)!
91+
92+
static let lockScript: Script = redeemScript.toP2SH()
93+
94+
struct UnlockScriptBuilder: MockUnlockScriptBuilder {
95+
func build(pairs: [SigKeyPair]) -> Script {
96+
guard let signature = pairs.first?.signature else {
97+
return Script()
98+
}
99+
84100
return try! Script()
85101
.append(.OP_0)
86-
.appendData(sigWithHashType)
102+
.appendData(signature)
87103
.appendData(redeemScript.data)
88104
}
105+
}
106+
}
89107

90-
return try MockHelper.verifySingleKey(lockScript: p2shLockScript, unlockScriptBuilder: p2shUnlockScriptBuilder, key: key, verbose: false)
108+
func testP2SH() {
109+
func verify(with key: MockKey) throws -> Bool {
110+
return try MockHelper.verifySingleKey(
111+
lockScript: P2SH1of3.lockScript,
112+
unlockScriptBuilder: P2SH1of3.UnlockScriptBuilder(),
113+
key: key,
114+
verbose: false)
91115
}
92116

93117
func succeed(with key: MockKey) {
@@ -118,61 +142,72 @@ class MockHelperTests: XCTestCase {
118142
}
119143

120144
// Custom Multi-Sig
121-
func testCustom() {
122-
func verify(with key: MockKey) throws -> Bool {
123-
let customLockScript = try! Script()
124-
// stack: sig pub bool2 bool1
125-
.append(.OP_IF)
126-
.append(.OP_IF)
127-
.append(.OP_DUP)
128-
.append(.OP_HASH160)
129-
.appendData(MockKey.keyA.pubkeyHash)
130-
.append(.OP_ELSE)
131-
.append(.OP_DUP)
132-
.append(.OP_HASH160)
133-
.appendData(MockKey.keyB.pubkeyHash)
134-
.append(.OP_ENDIF)
135-
.append(.OP_ELSE)
136-
.append(.OP_DUP)
137-
.append(.OP_HASH160)
138-
.appendData(MockKey.keyC.pubkeyHash)
139-
.append(.OP_ENDIF)
140-
// stack: sig pub pubkeyhash pubkeyhash
141-
.append(.OP_EQUALVERIFY)
142-
// stack: sig pub
143-
.append(.OP_CHECKSIG)
144-
145-
// custom multisig unlock
146-
let customUnlockScript = { (sigWithHashType: Data, key: MockKey) -> Script in
145+
struct Custom1of3 {
146+
static let lockScript = try! Script()
147+
// stack: sig pub bool2 bool1
148+
.append(.OP_IF)
149+
.append(.OP_IF)
150+
.append(.OP_DUP)
151+
.append(.OP_HASH160)
152+
.appendData(MockKey.keyA.pubkeyHash)
153+
.append(.OP_ELSE)
154+
.append(.OP_DUP)
155+
.append(.OP_HASH160)
156+
.appendData(MockKey.keyB.pubkeyHash)
157+
.append(.OP_ENDIF)
158+
.append(.OP_ELSE)
159+
.append(.OP_DUP)
160+
.append(.OP_HASH160)
161+
.appendData(MockKey.keyC.pubkeyHash)
162+
.append(.OP_ENDIF)
163+
// stack: sig pub pubkeyhash pubkeyhash
164+
.append(.OP_EQUALVERIFY)
165+
// stack: sig pub
166+
.append(.OP_CHECKSIG)
167+
168+
struct UnlockScriptBuilder: MockUnlockScriptBuilder {
169+
func build(pairs: [SigKeyPair]) -> Script {
170+
guard let key = pairs.first?.key, let signature = pairs.first?.signature else {
171+
return Script()
172+
}
173+
147174
switch key {
148-
case .keyA:
175+
case MockKey.keyA.privkey.publicKey():
149176
return try! Script()
150-
.appendData(sigWithHashType)
151-
.appendData(key.pubkey.data)
177+
.appendData(signature)
178+
.appendData(key.data)
152179
.append(.OP_TRUE)
153180
.append(.OP_TRUE)
154-
case .keyB:
181+
case MockKey.keyB.privkey.publicKey():
155182
return try! Script()
156-
.appendData(sigWithHashType)
157-
.appendData(key.pubkey.data)
183+
.appendData(signature)
184+
.appendData(key.data)
158185
.append(.OP_FALSE)
159186
.append(.OP_TRUE)
160-
case .keyC:
187+
case MockKey.keyC.privkey.publicKey():
161188
return try! Script()
162-
.appendData(sigWithHashType)
163-
.appendData(key.pubkey.data)
189+
.appendData(signature)
190+
.appendData(key.data)
164191
.append(.OP_FALSE)
165192
default:
166193
// unlock script for keyA
167194
return try! Script()
168-
.appendData(sigWithHashType)
169-
.appendData(key.pubkey.data)
195+
.appendData(signature)
196+
.appendData(key.data)
170197
.append(.OP_TRUE)
171198
.append(.OP_TRUE)
172199
}
173200
}
201+
}
174202

175-
return try MockHelper.verifySingleKey(lockScript: customLockScript, unlockScriptBuilder: customUnlockScript, key: key, verbose: false)
203+
}
204+
func testCustom() {
205+
func verify(with key: MockKey) throws -> Bool {
206+
return try MockHelper.verifySingleKey(
207+
lockScript: Custom1of3.lockScript,
208+
unlockScriptBuilder: Custom1of3.UnlockScriptBuilder(),
209+
key: key,
210+
verbose: false)
176211
}
177212

178213
func succeed(with key: MockKey) {
@@ -204,22 +239,27 @@ class MockHelperTests: XCTestCase {
204239

205240
// MARK: - 2 of 3 Multi-Sig [ABC]
206241
// Standard Multi-Sig
207-
func testStandard2of3() {
208-
func verify(with keys: [MockKey]) throws -> Bool {
209-
// lock script
210-
let standardLockScript = Script(publicKeys: [MockKey.keyA.pubkey, MockKey.keyB.pubkey, MockKey.keyC.pubkey], signaturesRequired: 2)!
211-
212-
// unlock script builder
213-
let standardUnlockScript = { (pairs: [SigKeyPair]) -> Script in
214-
let script = try! Script()
215-
.append(.OP_0)
216-
for pair in pairs {
217-
try! script.appendData(pair.sig)
218-
}
242+
struct Standard2of3 {
243+
static let lockScript = Script(publicKeys: [MockKey.keyA.pubkey,
244+
MockKey.keyB.pubkey,
245+
MockKey.keyC.pubkey],
246+
signaturesRequired: 2)!
247+
struct UnlockScriptBuilder: MockUnlockScriptBuilder {
248+
func build(pairs: [SigKeyPair]) -> Script {
249+
let script = try! Script().append(.OP_0)
250+
pairs.forEach { try! script.appendData($0.signature) }
219251
return script
252+
220253
}
221-
222-
return try MockHelper.verifyMultiKey(lockScript: standardLockScript, unlockScriptBuilder: standardUnlockScript, keys: keys, verbose: false)
254+
}
255+
}
256+
func testStandard2of3() {
257+
func verify(with keys: [MockKey]) throws -> Bool {
258+
return try MockHelper.verifyMultiKey(
259+
lockScript: Standard2of3.lockScript,
260+
unlockScriptBuilder: Standard2of3.UnlockScriptBuilder(),
261+
keys: keys,
262+
verbose: false)
223263
}
224264

225265
func succeed(with keys: [MockKey]) {

0 commit comments

Comments
 (0)