Skip to content

Commit

Permalink
Merge pull request #167 from yenom/refactor-MockUnlockScriptBuilder
Browse files Browse the repository at this point in the history
Refactor mock unlock script builder
  • Loading branch information
usatie authored Sep 21, 2018
2 parents 7a5a78d + ca57e06 commit bb89029
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 88 deletions.
4 changes: 4 additions & 0 deletions BitcoinKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
2914BE5C211BD0DF00B349CB /* OP_CHECKSEQUENCEVERIFY.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2914BE5B211BD0DF00B349CB /* OP_CHECKSEQUENCEVERIFY.swift */; };
2914BE5E211C062200B349CB /* OP_PUBKEYHASH.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2914BE5D211C062200B349CB /* OP_PUBKEYHASH.swift */; };
2914BE60211C063300B349CB /* OP_PUBKEY.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2914BE5F211C063300B349CB /* OP_PUBKEY.swift */; };
292185372154B50E00570618 /* MockUnlockScriptBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292185362154B50E00570618 /* MockUnlockScriptBuilder.swift */; };
29248EEF2104B64E00CC9051 /* ScriptChunkHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29248EEE2104B64E00CC9051 /* ScriptChunkHelper.swift */; };
29290B8D210AF59600D2BE78 /* OpCodeFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29290B8C210AF59600D2BE78 /* OpCodeFactory.swift */; };
29290B91210AF79300D2BE78 /* OP_DUP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29290B90210AF79300D2BE78 /* OP_DUP.swift */; };
Expand Down Expand Up @@ -357,6 +358,7 @@
2914BE5B211BD0DF00B349CB /* OP_CHECKSEQUENCEVERIFY.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OP_CHECKSEQUENCEVERIFY.swift; sourceTree = "<group>"; };
2914BE5D211C062200B349CB /* OP_PUBKEYHASH.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OP_PUBKEYHASH.swift; sourceTree = "<group>"; };
2914BE5F211C063300B349CB /* OP_PUBKEY.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OP_PUBKEY.swift; sourceTree = "<group>"; };
292185362154B50E00570618 /* MockUnlockScriptBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUnlockScriptBuilder.swift; sourceTree = "<group>"; };
29248EEE2104B64E00CC9051 /* ScriptChunkHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptChunkHelper.swift; sourceTree = "<group>"; };
29290B8C210AF59600D2BE78 /* OpCodeFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpCodeFactory.swift; sourceTree = "<group>"; };
29290B90210AF79300D2BE78 /* OP_DUP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OP_DUP.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -583,6 +585,7 @@
29089F112122D07500E0C305 /* Mock */ = {
isa = PBXGroup;
children = (
292185362154B50E00570618 /* MockUnlockScriptBuilder.swift */,
29089F0B2122B9CD00E0C305 /* MockHelper.swift */,
29089F0F2122D06300E0C305 /* MockKey.swift */,
);
Expand Down Expand Up @@ -1190,6 +1193,7 @@
0C1DD40821181719004BA8A8 /* OP_ABS.swift in Sources */,
1482B5E8202721FF0098B612 /* HDPrivateKey.swift in Sources */,
298E17CD2150AEB300FF6C77 /* StandardTransactionBuilder.swift in Sources */,
292185372154B50E00570618 /* MockUnlockScriptBuilder.swift in Sources */,
0C09002621169B430077E9BC /* OP_MIN.swift in Sources */,
0C1DD421211820D4004BA8A8 /* OP_DIV.swift in Sources */,
0C1DE157211D79D900FE8E43 /* OP_FROMALTSTACK.swift in Sources */,
Expand Down
19 changes: 7 additions & 12 deletions Sources/BitcoinKit/Mock/MockHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,8 @@

import Foundation

public typealias SigKeyPair = (sig: Data, key: MockKey)

public typealias SingleKeyScriptBuilder = ((SigKeyPair) -> Script)
public typealias MultiKeyScriptBuilder = (([SigKeyPair]) -> Script)

public struct MockHelper {

public static func createUtxo(lockScript: Script) -> UnspentTransaction {
let outputMock = TransactionOutput(value: 100_000_000, lockingScript: lockScript.data)
let outpointMock = TransactionOutPoint(hash: Data(), index: 0)
Expand Down Expand Up @@ -79,7 +75,7 @@ public struct MockHelper {
lockTime: tx.lockTime)
}

public static func verifySingleKey(lockScript: Script, unlockScriptBuilder: SingleKeyScriptBuilder, key: MockKey, verbose: Bool = true) throws -> Bool {
public static func verifySingleKey(lockScript: Script, unlockScriptBuilder: MockUnlockScriptBuilder, key: MockKey, verbose: Bool = true) throws -> Bool {
// mocks
let utxoMock: UnspentTransaction = MockHelper.createUtxo(lockScript: lockScript)
let txMock: Transaction = MockHelper.createTransaction(utxo: utxoMock)
Expand All @@ -88,8 +84,8 @@ public struct MockHelper {
let hashType = SighashType.BCH.ALL
let signature: Data = key.privkey.sign(txMock, utxoToSign: utxoMock, hashType: hashType)
let sigWithHashType: Data = signature + UInt8(hashType)
let unlockScript: Script = unlockScriptBuilder((sigWithHashType, key))

let pair: SigKeyPair = SigKeyPair(sigWithHashType, key.pubkey)
let unlockScript: Script = unlockScriptBuilder.build(pairs: [pair])
// signed tx
let signedTxMock = MockHelper.updateTransaction(txMock, unlockScriptData: unlockScript.data)

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

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

let unlockScript: Script = unlockScriptBuilder(sigKeyPairs)

let unlockScript: Script = unlockScriptBuilder.build(pairs: sigKeyPairs)
// signed tx
let signedTxMock = MockHelper.updateTransaction(txMock, unlockScriptData: unlockScript.data)

Expand Down
33 changes: 33 additions & 0 deletions Sources/BitcoinKit/Mock/MockUnlockScriptBuilder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// MockUnlockScriptBuilder.swift
//
// Copyright © 2018 BitcoinKit developers
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//

import Foundation

public typealias SigKeyPair = (signature: Data, key: PublicKey)

// This protocol is prepared for playing with Script.
// If you are building production application, you should implement TransactionSigner instead of MockUnlockScriptBuilder.
public protocol MockUnlockScriptBuilder {
func build(pairs: [SigKeyPair]) -> Script
}
5 changes: 3 additions & 2 deletions Sources/BitcoinKit/Scripts/ScriptMachine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,9 @@ public struct ScriptMachine {
throw ScriptMachineError.error("Last item on the stack is false.")
}
} else {
print("This is not p2sh")
print(context.shouldVerifyP2SH(), lockScript.isPayToScriptHashScript)
if context.verbose {
print("context.shouldVerifyP2SH : ", context.shouldVerifyP2SH(), "isP2SH : ", lockScript.isPayToScriptHashScript)
}
}

// If nothing failed, validation passed.
Expand Down
188 changes: 114 additions & 74 deletions Tests/BitcoinKitTests/MockHelperTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,35 @@ import XCTest

class MockHelperTests: XCTestCase {
// MARK: - 1 of 3 Multi-Sig [ABC]
// Standard Multi-Sig
func testStandard() {
func verify(with key: MockKey) throws -> Bool {
// lock script
let standardLockScript = Script(publicKeys: [MockKey.keyA.pubkey, MockKey.keyB.pubkey, MockKey.keyC.pubkey], signaturesRequired: 1)!

// unlock script builder
let standardUnlockScript = { (sig: Data, key: MockKey) -> Script in
// MARK: Standard Multi-Sig
struct Standard1of3 {
static let lockScript: Script = Script(publicKeys: [MockKey.keyA.pubkey,
MockKey.keyB.pubkey,
MockKey.keyC.pubkey],
signaturesRequired: 1)!

struct UnlockScriptBuilder: MockUnlockScriptBuilder {
func build(pairs: [SigKeyPair]) -> Script {
guard let signature = pairs.first?.signature else {
return Script()
}

let script = try! Script()
.append(.OP_0)
.appendData(sig)
.appendData(signature)
return script
}

return try MockHelper.verifySingleKey(lockScript: standardLockScript, unlockScriptBuilder: standardUnlockScript, key: key, verbose: false)
}
}


func testStandard() {
func verify(with key: MockKey) throws -> Bool {
return try MockHelper.verifySingleKey(
lockScript: Standard1of3.lockScript,
unlockScriptBuilder: Standard1of3.UnlockScriptBuilder(),
key: key,
verbose: false)
}

func succeed(with key: MockKey) {
Expand Down Expand Up @@ -72,22 +86,32 @@ class MockHelperTests: XCTestCase {
}

// P2SH Multi-Sig
func testP2SH() {
func verify(with key: MockKey) throws -> Bool {
// P2SH multisig[ABC]
let redeemScript: Script = Script(publicKeys: [MockKey.keyA.pubkey, MockKey.keyB.pubkey, MockKey.keyC.pubkey], signaturesRequired: 1)!

let p2shLockScript: Script = redeemScript.toP2SH()

// p2sh multisig[ABC] unlock
let p2shUnlockScriptBuilder = { (sigWithHashType: Data, key: MockKey) -> Script in
struct P2SH1of3 {
static let redeemScript: Script = Script(publicKeys: [MockKey.keyA.pubkey, MockKey.keyB.pubkey, MockKey.keyC.pubkey], signaturesRequired: 1)!

static let lockScript: Script = redeemScript.toP2SH()

struct UnlockScriptBuilder: MockUnlockScriptBuilder {
func build(pairs: [SigKeyPair]) -> Script {
guard let signature = pairs.first?.signature else {
return Script()
}

return try! Script()
.append(.OP_0)
.appendData(sigWithHashType)
.appendData(signature)
.appendData(redeemScript.data)
}
}
}

return try MockHelper.verifySingleKey(lockScript: p2shLockScript, unlockScriptBuilder: p2shUnlockScriptBuilder, key: key, verbose: false)
func testP2SH() {
func verify(with key: MockKey) throws -> Bool {
return try MockHelper.verifySingleKey(
lockScript: P2SH1of3.lockScript,
unlockScriptBuilder: P2SH1of3.UnlockScriptBuilder(),
key: key,
verbose: false)
}

func succeed(with key: MockKey) {
Expand Down Expand Up @@ -118,61 +142,72 @@ class MockHelperTests: XCTestCase {
}

// Custom Multi-Sig
func testCustom() {
func verify(with key: MockKey) throws -> Bool {
let customLockScript = try! Script()
// stack: sig pub bool2 bool1
.append(.OP_IF)
.append(.OP_IF)
.append(.OP_DUP)
.append(.OP_HASH160)
.appendData(MockKey.keyA.pubkeyHash)
.append(.OP_ELSE)
.append(.OP_DUP)
.append(.OP_HASH160)
.appendData(MockKey.keyB.pubkeyHash)
.append(.OP_ENDIF)
.append(.OP_ELSE)
.append(.OP_DUP)
.append(.OP_HASH160)
.appendData(MockKey.keyC.pubkeyHash)
.append(.OP_ENDIF)
// stack: sig pub pubkeyhash pubkeyhash
.append(.OP_EQUALVERIFY)
// stack: sig pub
.append(.OP_CHECKSIG)

// custom multisig unlock
let customUnlockScript = { (sigWithHashType: Data, key: MockKey) -> Script in
struct Custom1of3 {
static let lockScript = try! Script()
// stack: sig pub bool2 bool1
.append(.OP_IF)
.append(.OP_IF)
.append(.OP_DUP)
.append(.OP_HASH160)
.appendData(MockKey.keyA.pubkeyHash)
.append(.OP_ELSE)
.append(.OP_DUP)
.append(.OP_HASH160)
.appendData(MockKey.keyB.pubkeyHash)
.append(.OP_ENDIF)
.append(.OP_ELSE)
.append(.OP_DUP)
.append(.OP_HASH160)
.appendData(MockKey.keyC.pubkeyHash)
.append(.OP_ENDIF)
// stack: sig pub pubkeyhash pubkeyhash
.append(.OP_EQUALVERIFY)
// stack: sig pub
.append(.OP_CHECKSIG)

struct UnlockScriptBuilder: MockUnlockScriptBuilder {
func build(pairs: [SigKeyPair]) -> Script {
guard let key = pairs.first?.key, let signature = pairs.first?.signature else {
return Script()
}

switch key {
case .keyA:
case MockKey.keyA.privkey.publicKey():
return try! Script()
.appendData(sigWithHashType)
.appendData(key.pubkey.data)
.appendData(signature)
.appendData(key.data)
.append(.OP_TRUE)
.append(.OP_TRUE)
case .keyB:
case MockKey.keyB.privkey.publicKey():
return try! Script()
.appendData(sigWithHashType)
.appendData(key.pubkey.data)
.appendData(signature)
.appendData(key.data)
.append(.OP_FALSE)
.append(.OP_TRUE)
case .keyC:
case MockKey.keyC.privkey.publicKey():
return try! Script()
.appendData(sigWithHashType)
.appendData(key.pubkey.data)
.appendData(signature)
.appendData(key.data)
.append(.OP_FALSE)
default:
// unlock script for keyA
return try! Script()
.appendData(sigWithHashType)
.appendData(key.pubkey.data)
.appendData(signature)
.appendData(key.data)
.append(.OP_TRUE)
.append(.OP_TRUE)
}
}
}

return try MockHelper.verifySingleKey(lockScript: customLockScript, unlockScriptBuilder: customUnlockScript, key: key, verbose: false)
}
func testCustom() {
func verify(with key: MockKey) throws -> Bool {
return try MockHelper.verifySingleKey(
lockScript: Custom1of3.lockScript,
unlockScriptBuilder: Custom1of3.UnlockScriptBuilder(),
key: key,
verbose: false)
}

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

// MARK: - 2 of 3 Multi-Sig [ABC]
// Standard Multi-Sig
func testStandard2of3() {
func verify(with keys: [MockKey]) throws -> Bool {
// lock script
let standardLockScript = Script(publicKeys: [MockKey.keyA.pubkey, MockKey.keyB.pubkey, MockKey.keyC.pubkey], signaturesRequired: 2)!

// unlock script builder
let standardUnlockScript = { (pairs: [SigKeyPair]) -> Script in
let script = try! Script()
.append(.OP_0)
for pair in pairs {
try! script.appendData(pair.sig)
}
struct Standard2of3 {
static let lockScript = Script(publicKeys: [MockKey.keyA.pubkey,
MockKey.keyB.pubkey,
MockKey.keyC.pubkey],
signaturesRequired: 2)!
struct UnlockScriptBuilder: MockUnlockScriptBuilder {
func build(pairs: [SigKeyPair]) -> Script {
let script = try! Script().append(.OP_0)
pairs.forEach { try! script.appendData($0.signature) }
return script

}

return try MockHelper.verifyMultiKey(lockScript: standardLockScript, unlockScriptBuilder: standardUnlockScript, keys: keys, verbose: false)
}
}
func testStandard2of3() {
func verify(with keys: [MockKey]) throws -> Bool {
return try MockHelper.verifyMultiKey(
lockScript: Standard2of3.lockScript,
unlockScriptBuilder: Standard2of3.UnlockScriptBuilder(),
keys: keys,
verbose: false)
}

func succeed(with keys: [MockKey]) {
Expand Down

0 comments on commit bb89029

Please sign in to comment.