diff --git a/BitcoinKit.xcodeproj/project.pbxproj b/BitcoinKit.xcodeproj/project.pbxproj index 82c89f33..1336d9cf 100644 --- a/BitcoinKit.xcodeproj/project.pbxproj +++ b/BitcoinKit.xcodeproj/project.pbxproj @@ -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 */; }; @@ -357,6 +358,7 @@ 2914BE5B211BD0DF00B349CB /* OP_CHECKSEQUENCEVERIFY.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OP_CHECKSEQUENCEVERIFY.swift; sourceTree = ""; }; 2914BE5D211C062200B349CB /* OP_PUBKEYHASH.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OP_PUBKEYHASH.swift; sourceTree = ""; }; 2914BE5F211C063300B349CB /* OP_PUBKEY.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OP_PUBKEY.swift; sourceTree = ""; }; + 292185362154B50E00570618 /* MockUnlockScriptBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUnlockScriptBuilder.swift; sourceTree = ""; }; 29248EEE2104B64E00CC9051 /* ScriptChunkHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptChunkHelper.swift; sourceTree = ""; }; 29290B8C210AF59600D2BE78 /* OpCodeFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpCodeFactory.swift; sourceTree = ""; }; 29290B90210AF79300D2BE78 /* OP_DUP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OP_DUP.swift; sourceTree = ""; }; @@ -583,6 +585,7 @@ 29089F112122D07500E0C305 /* Mock */ = { isa = PBXGroup; children = ( + 292185362154B50E00570618 /* MockUnlockScriptBuilder.swift */, 29089F0B2122B9CD00E0C305 /* MockHelper.swift */, 29089F0F2122D06300E0C305 /* MockKey.swift */, ); @@ -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 */, diff --git a/Sources/BitcoinKit/Mock/MockHelper.swift b/Sources/BitcoinKit/Mock/MockHelper.swift index cb78d8be..541be2bd 100644 --- a/Sources/BitcoinKit/Mock/MockHelper.swift +++ b/Sources/BitcoinKit/Mock/MockHelper.swift @@ -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) @@ -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) @@ -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) @@ -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) @@ -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) diff --git a/Sources/BitcoinKit/Mock/MockUnlockScriptBuilder.swift b/Sources/BitcoinKit/Mock/MockUnlockScriptBuilder.swift new file mode 100644 index 00000000..0b4312c4 --- /dev/null +++ b/Sources/BitcoinKit/Mock/MockUnlockScriptBuilder.swift @@ -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 +} diff --git a/Sources/BitcoinKit/Scripts/ScriptMachine.swift b/Sources/BitcoinKit/Scripts/ScriptMachine.swift index 1ad1e80a..c8699303 100644 --- a/Sources/BitcoinKit/Scripts/ScriptMachine.swift +++ b/Sources/BitcoinKit/Scripts/ScriptMachine.swift @@ -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. diff --git a/Tests/BitcoinKitTests/MockHelperTests.swift b/Tests/BitcoinKitTests/MockHelperTests.swift index 3ba1bc07..e7c779e9 100644 --- a/Tests/BitcoinKitTests/MockHelperTests.swift +++ b/Tests/BitcoinKitTests/MockHelperTests.swift @@ -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) { @@ -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) { @@ -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) { @@ -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]) {