# BitcoinKit ハンズオン資料 ## 0. setup まず、以下の3つを満たすようにupdateして下さい - iOS 9.0+ - Xcode 9.0+ - Swift 4.1+ ### プロジェクトのダウンロード まずはGithubからハンズオン用のレポジトリをcloneして下さい。 ```shell $ git clone https://github.com/BitcoinHackathon/Hands-on-iOS.git ``` ### BitcoinKitのインストール #### Carthage `Swift4.1.2`の人は何もしなくてもそのまま使えます。 `Swift4.1.2`以外の人は、コマンドラインで下記を実行して下さい。 ```shell $ carthage update --platform iOS ``` # 基礎編:ビットコインの受け取り〜送金まで ## 1. Walletの作成 今回は、HDWalletではなく、1つしかアドレスを持たない一番単純なWalletを作ります。 Private keyを生成し、それを引数にとってWalletが作れます ```swift= let privateKey = PrivateKey(network: .testnet) let wallet = Wallet(privateKey: privateKey) ``` `network`では、BCH/BTC, Mainnet/Testnetの4種類が選べます。 このチュートリアルはBCHのテストネット用に作られているので、`.testnet`をセットして下さい。 #### WIF(Wallet Import Format) 生成したPrivate keyをUserDefaultsなどに保存したい場合、**WIF(Wallet Import Format)** というString型に変換すると便利です。Libraryでは以下のコードから、WIFの生成及び、WIFからWalletを復元する方法が示されています。 ```swift= let wif: String = privateKey.toWIF() let wallet = Wallet(wif: wif) ``` ### 実際にやってみよー! `ViewController.swift`内に`createWalletIfNeeded()`という関数があります。 BitcoinCashのテストネットで、Private keyを生成し、そこからWalletを作りましょう。 ```swift= func createWalletIfNeeded() { if wallet == nil { let privateKey = PrivateKey(network: .testnet) wallet = Wallet(privateKey: privateKey) wallet?.save() } } ``` 一番下の`save()`で、WIFをUserDefualtsに保存してくれています。 ## 2. Addressの表示 Walletが作成できたら、アドレスを表示しましょう。 Private key -> Public Key -> Address という順序で、Addressは生成できます。 BCHには、アドレス形式が2種類あり、BTCと同じbase58Address(LegacyAddress)と、Cash Addressが存在します。 Libraryでは、以下のような関数を用意してあります。 ```swift= let publicKey: PublicKey = privateKey.publicKey() let legacyAddress: LegacyAddress = publicKey.toLegacy() let cashAddr: Cashaddr = publicKey.toCashaddr() // Walletからアドレスを生成する let cashAddr2: Address = wallet?.address ``` ### 実際にやってみよー! `ViewController.swift`内に`updateLabels()`という関数があります。 `addressLabel`と`qrCodeImageView`にアドレスを入れて、実行してみましょう。 ```swift= func updateLabels() { addressLabel.text = wallet?.address.cashaddr qrCodeImageView.image = wallet?.address.qrImage() } ``` ## 受金 先ほど表示したアドレスへいくらかお金を送っておきましょう。 ### 実際にやってみよー! Bitcoin DeveloperというSlackのワークスペースを用意してますので登録して下さい。 Slackへ入るリンクは[こちら](https://join.slack.com/t/bitcoin-developer/shared_invite/enQtNDIxOTI1MDc4MTI5LTM3Y2RlNDE3OTg1OThkYWMzOTZkNWY1NTVjNTRlNGQ2OWExYzdmOWViZmM0YTU4ZTk5YTMyZWMxMjEyYTIxZmY) "hackathon-hq"というチャンネルがありますので、そちらにお入り下さい。 このチャンネルで、 `/faucet bchtest:qzu6l90m7jcg6jwvck24kz86l2dxpu5twqkn9e7psy` このような形で `/faucet` の後にBCHのTestnetアドレスを指定すると、Testnetのコインがいくらかもらえます(使い過ぎはご遠慮ください)。送るのに成功すると、該当するTransactionのExplorerのリンクが返ってくるので、確認してみてください。 ## 3. 残高を確認する 今回は、[Bitcoin.com API](https://rest.bitcoin.com)を利用して、ブロックチェーンにある情報を取得していきます。 End pointはこちら ``` Testnet: https://trest.bitcoin.com/v1/ Mainnet: https://rest.bitcoin.com/v1/ ``` ### 実際にやってみよー! Walletクラスの`balance()`は、キャッシュされた残高を取得して返してくれる関数です。 残高を更新するためには、Walletクラスの`reloadBalance()`という関数を使いましょう。残高を非同期で取得し、UserDefaultsにキャッシュしてくれます。残高の取得が完了した際には、先ほど書いた`updateLabels()`で更新された残高をラベルに反映するようにします。 ```swift= func reloadBalance() { wallet?.reloadBalance(completion: { [weak self] (utxos) in DispatchQueue.main.async { self?.updateLabels() } }) } func updateLabels() { qrCodeImageView.image = wallet?.address.qrImage() addressLabel.text = wallet?.address.cashaddr if let balance = wallet?.balance() { balanceLabel.text = "Balance : \(balance) satoshi" } } ``` ## 4. 送金をする では、次に送金をしてみましょう。 `bchtest:qpytf7xczxf2mxa3gd6s30rthpts0tmtgyw8ud2sy3` 宛てに1000satoshi送金してみましょう。 #### 閑話休題: 単位について Bitcoinの基本単位は`satoshi`です。1億satoshiで1coin。 | 単位 | 定義 |備考| | :---: | :---: | :---: | | satoshi | 1 satoshi |約0.0005円| | bits = cash | 100 satoshi |約0.05円| | BCH/BTC/coin | 10,000,000 satoshi |約5万円| 備考の日本円価格はBitcoinCashで計算しています。 ### 実際にやってみよー! まず、`AddressFactory`を使って、String型のアドレスをAddress型に変換します。 その後、Walletクラスに用意されている`send()`を使えば、Transactionの作成及び、非同期でTransactionの送信までを行ってくれます。 ```swift= do { let address: Address = try AddressFactory.create(addressString) try wallet?.send(to: address, amount: 1000, completion: { [weak self] (response) in print("送金完了 txid : ", response ?? "") self?.reloadBalance() }) } catch { print(error) } ``` 送金が完了していればconsoleに下記のようなtxidが出力されているはずです。 ``` 送金完了 txid : "d86b1ce619b7a35c1efdbf13f371a2330b0f42f7edb55c05e3f68a9f0f32443d" ``` このtxidのトランザクションが実際に送金されたことを[Block Trail](https://www.blocktrail.com/tBCC)というExplorerで確認してみましょう。 https://www.blocktrail.com/tBCC/tx/d86b1ce619b7a35c1efdbf13f371a2330b0f42f7edb55c05e3f68a9f0f32443d これでWalletの一通りの作業は終わりです。それでは次に、いよいよBitcoin Scriptを書いていきましょう。 # 応用編:Hello, Bitcoin Script! ※別資料アリ >応用編は、Bitcoin Scriptについての発表を聞いていることが想定されています。 資料はSpeaker Deckに上がっていますので、わからなかった時には目を通すことをオススメします。 P19 ~ P90あたりが該当箇所です。 https://speakerdeck.com/usatie/lets-write-bitcoin-script-number-tokyobitcoinhackathon `MockScripts.swift`というファイルがありますので、そちらを開いてください。 ここでは、簡単にLockScriptとUnlockScriptを書いて試すことが出来ます。 ## 5. Simple Calculation Script まずは、単純な計算をやってみましょう。 ### 実際にやってみよー! それでは、レクチャーで行った、このScriptを実際に書いてみましょう。 <img src="https://user-images.githubusercontent.com/24402451/45797857-8da79100-bce2-11e8-920f-95c8772c6938.png" width=600> Scriptは、`Script()`の後にチェーンで書き足していくことができます。 LockScriptはそのまま書けば良いのですが、UnlockScriptを作るときには、Script Builderを作ります。 このScript Builderに、UnlockScriptにどの鍵を使うかを引数として指定することができます。 ですが、最初はとりあえず鍵を使わないので、あまり気にしなくて構いません。 ```swift= struct simpleCalculation { // lock script static let lockScript = try! Script() .append(.OP_2) .append(.OP_3) .append(.OP_ADD) .append(.OP_EQUAL) // unlock script builder struct UnlockScriptBuilder: MockUnlockScriptBuilder { func build(pairs: [SigKeyPair]) -> Script { let script = try! Script() .append(.OP_5) return script } } } ``` その後、`ViewController`に戻り、`testMockScript()`のコメントアウト部分消して、Enableさせます。 `testMockScript()`では、先ほど書いたLock ScriptとUnlock Scriptを実行します。 実行すると、結果が返ってくるのが確認できます。 さらに、コンソールにスタックマシーンの状況が表示されます。 これを見ると、どの段階でエラーが出ているのかデバッグが楽になるでしょう。 ## 6. P2PKH(Pay to Public Key Hash) では、鍵と署名を使ったScriptに入っていきます。 実際のTransactionで最も多く使われているP2PKHを書いてみましょう。 <img width="600" src="https://user-images.githubusercontent.com/24402451/45799712-60121600-bce9-11e8-8fa4-44d6e087e637.png"> Scriptをちょこっと書くだけなのに、Private keyやPublic Keyを毎回準備するのは面倒です。 そこで、BitcoinKitでは、KeyのMockを準備しています。 ### 実際にやってみよー! ```swift= struct P2PKH { // lock script static let lockScript = try! Script() .append(.OP_DUP) .append(.OP_HASH160) .appendData(MockKey.keyA.pubkeyHash) .append(.OP_EQUALVERIFY) .append(.OP_CHECKSIG) // unlock script builder struct UnlockScriptBuilder: MockUnlockScriptBuilder { func build(pairs: [SigKeyPair]) -> Script { guard let sigKeyPair = pairs.first else { return Script() } let script = try! Script() .appendData(sigKeyPair.signature) .appendData(sigKeyPair.key.data) return script } } } ``` その後、`testMockScript()`に戻り、2つ目の`verifyScript()`を実行してみましょう。 先ほど説明しませんでしたが、この`verifyScript()`の引数にである`key`に、UnlockScriptでどの鍵を使うかを指定できます。 今、`verifyScript()`の引数には`keyA`が入っているので、実行すると、うまくいくと思います。 例えば、ここの`MockKey.keyB`を入れてみてください。すると、`OP_EQUALVERIFY`がうまくいかなくて、エラーが出ると思います。 ## 7. Multisig では、次にMultisigをやってみましょう。 3つ中2つの秘密鍵が揃えば、unlock出来る2 of 3のMultisigを書いてみます。 <img width="600" src="https://user-images.githubusercontent.com/24402451/45868076-dda85580-bdbf-11e8-9e63-aa02b6ebec98.png"> ### 実際にやってみよー! `unlockScriptBuilder`では、複数の鍵のペアを引数に取るようにします。 ```swift= struct Multisig2of3 { // lock script static let lockScript = try! Script() .append(.OP_2) .appendData(MockKey.keyA.pubkey.data) .appendData(MockKey.keyB.pubkey.data) .appendData(MockKey.keyC.pubkey.data) .append(.OP_3) .append(.OP_CHECKMULTISIG) // unlock script builder struct UnlockScriptBuilder: MockUnlockScriptBuilder { func build(pairs: [SigKeyPair]) -> Script { let script = try! Script() .append(.OP_0) pairs.forEach { try! script.appendData($0.signature) } return script } } } ``` Multisigの時は、`.verifyMultiKey`の`keys`の引数のところに、複数のkeyを入れるようにしましょう。 実行して、うまくいくことを確認してください。 コンソールのスタックを見ると、最後に一気にスタックに積まれているものが消えるので気持ち良いです。 ## 8. P2SH (Pay to Script Hash) お次は、P2SHです。 元のLockScript(Redeem Script)をHashし、それをLock Scriptに入れます。 Unlock Scriptに通常のScriptに加えて、Redeem Scriptを入れることが求められます。 スライドでは、Redeem Scriptを"ややこしいScript"と称していました笑 <img width="600" alt="2018-09-22 9 03 46" src="https://user-images.githubusercontent.com/24402451/45910718-84373980-be46-11e8-8c26-e5fed8f33e7c.png"> ### 実際にやってみよー! 1 of 3 のMultisigのLock Scriptを書いて、それをP2SH形式のScriptにする練習をしましょう。 Redeem Scriptに使われているMultisigは、 `Script(publicKeys: [BitcoinKit.PublicKey], signaturesRequired: UInt)`: Public keyとUnlockに必要な署名数を指定することで、MultisigのLockScriptを生成する が使われています。 先ほど、みなさんがチェーンで書いていたものが、関数で簡単に書けるように準備してあります。 ```swift= struct P2SHMultisig { // lock script static let redeemScript = Script(publicKeys: [MockKey.keyA.pubkey, MockKey.keyB.pubkey, MockKey.keyC.pubkey], signaturesRequired: 1)! static let lockScript = try! Script() .append(.OP_HASH160) .appendData(Crypto.sha256ripemd160(redeemScript.data)) .append(.OP_EQUAL) // unlock script builder 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(signature) .appendData(redeemScript.data) return script } } } ``` まずは、Lock ScriptとUnlock ScriptのRedeem Scriptが一致しているかを調べます。 それが一致していることが確認できたら、再びRedeem Scriptが実行されて、通常のMultisigの処理が続行します。 ## 9. OP_IFを使ってみよう BitcoinのOPCodeは実は100種類以上もあります。今回は、その中でも`OP_IF`について、解説していきます。 `OP_IF`が実行されると、スタックマシーンの一番上にある値がポップされて、それのbool値が判定されます。OPCodeで、if文を使い時は以下のように書きます。 ``` OP_IF trueなら実行したいこと OP_ELSE falseなら実行したいこと OP_END ``` ネストする場合も、同じように書けば大丈夫です。 スタックにbool1, bool2が積まれているとします。 ``` OP_IF bool1がtrueなら実行したいこと OP_IF bool2がtrueなら実行したいこと OP_ELSE bool2がfalseなら実行したいこと OP_END OP_ELSE bool1がfalseなら実行したいこと OP_ENDIF ``` LockScriptに`OP_IF`を書いておけば、UnlockScriptにtrue/falseを入れて、コインの使用者に任意のLockScriptを選ばす事が出来ます。 ### 実際にやってみよー! `OP_IF`を使って、unlockする側が、`keyA`か`keyB`を使うかを選ぶようなScriptを書きましょう。 ```swift struct OPIF { // lock script static let lockScript = try! Script() .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_EQUALVERIFY) .append(.OP_CHECKSIG) // unlock script builder struct UnlockScriptBuilder: MockUnlockScriptBuilder { func build(pairs: [SigKeyPair]) -> Script { guard let sigKeyPair = pairs.first else { return Script() } let script = try! Script() .appendData(sigKeyPair.signature) .appendData(sigKeyPair.key.data) .append(.OP_TRUE) return script } } } ``` UnlockScroptの最後の`OP_TRUE`に注目して下さい。 今、UnlockScriptによって、スタックの先頭に`OP_TRUE`来ますので、LockScriptと合わせた時に、`OP_IF`の最初の部分が実行されます。つまり、keyAに対してunlockするようなScriptが実行されます。 それでは、`MockHelper.verifySingleKey`を実行してみて下さい。おっと!エラーが出るはずです。 `verifySingleKey`の引数では、keyBが指定されていて、UnlockScriptはkeyBを利用しています。 つまり、LockScriptでは、keyAの部分が実行されているので、これではunlock出来ません。 そこで、UnlockScriptの`OP_TRUE`を`OP_FALSE`に変更してみて下さい。 こうすると、LockScriptの`OP_ELSE`が実行され、keyBに対してunlockするようなScriptが実行されます。 さあ、実行してみてください。今度はうまくいったと思います! このように、`OP_IF`を利用することで、Lockする側は複数のUnlock方法を指定することができ、Unlockする側も任意の方法を選べるようになります。 # アドバンス編: Transactionを自分で送ってみよう! 先ほど、Walletクラスの`send()`を使っていましたが、自分でも様々なTransactionを送れるようにしましょう。 まずは、BitcoinのTransactionが一体、どんな風に作られているのかを見ていきます。 <img src="https://user-images.githubusercontent.com/24402451/45806005-e97e1400-bcfa-11e8-8e95-5aff0745be3e.png" width=600> (出典: [The Challenges of Optimizing Unspent Output Selection](https://medium.com/@lopp/the-challenges-of-optimizing-unspent-output-selection-a3e5d05d13ef)) 上の図のTransaction2に注目して下さい。 BitcoinのTrasncationは、実は複数のInputとOutputがあるのが通常です。 誰かに0.8BTC送りたいとすると、Inputが0.8BTC以上になるように色んなアドレスからコインを集めます。 それぞれのアドレスに含まれているコインをUnlockし、Outputにセットしたアドレスにコインを送って、Lockします。 こうして、コインを受け取ったOutputは、再度コインを送るときには、次はInputとしてTrasnsactionにセットされます。 BitcoinのTransactionでは、このように複数のInputのコインをUnlockし、複数のOutputに送ってLockするという作業が繰り返されます。 コインが送られたOutputで、まだ次のInputとして使われていないものを、特別に`UTXO(Unspent Transaction Output)`と呼びます。 つまり、Bitcoinを送りたい時は、このUTXOを集めて、次のTransactionのInputとしてセットするのです。 また、通常、送りたい額とInputの合計が一致することは少ないです。 なので、Inputの合計が送りたい額を超えた分はお釣りとして、再度また自分のアドレスに送り直すために、Outputを追加します。 それでは、今説明したことがコードでどのように行われているかを見てみましょう。 こちらが`send()`の定義です。 ```swift= public func send(to toAddress: Address, amount: UInt64, completion: ((_ txid: String?) -> Void)? = nil) throws { // 1. 自分が使えるUTXOを取ってくる let utxos = utxoProvider.cached // 2. 送りたい額`amount`に対して、十分なInputをセットするために、UTXOを選択する(Inputになる) let (utxosToSpend, fee) = try utxoSelector.select(from: utxos, targetValue: amount) // 3. Inputに入れるUTXOの合計を算出する let totalAmount: UInt64 = utxosToSpend.sum() // 4. UTXOの額の合計から、送りたい額`amount`とTransaction feeを引いて、それが再度自分に返ってくるお釣りとなります let change: UInt64 = totalAmount - amount - fee // 5. 送る額とアドレスのペアを作る(Outputになる) let destinations: [(Address, UInt64)] = [(toAddress, amount), (address, change)] // 6. InputとOutputを組み合わせて、Transactionを作る let unsignedTx = try transactionBuilder.build(destinations: destinations, utxos: utxosToSpend) // 7. Inputのコインに署名をして、Unlockする let signedTx = try transactionSigner.sign(unsignedTx, with: [privateKey]) // 8. 作ったTrasnactionを他のノードに送信する(BroadCastする) let rawtx = signedTx.serialized().hex transactionBroadcaster.post(rawtx, completion: completion) } ``` 今まで皆さんが書いてきたScriptは、この中のTransactionを実際に作って署名する7, 8の過程で使われています。 この関数を、また紐解いてみましょう。 ```swift= public func build(destinations: [(address: Address, amount: UInt64)], utxos: [UnspentTransaction]) throws -> UnsignedTransaction { let outputs = try destinations.map { (address: Address, amount: UInt64) -> TransactionOutput in // LockingScript作成!!! guard let lockingScript = Script(address: address)?.data else { throw TransactionBuildError.error("Invalid address type") } return TransactionOutput(value: amount, lockingScript: lockingScript) } let unsignedInputs = utxos.map { TransactionInput(previousOutput: $0.outpoint, signatureScript: Data(), sequence: UInt32.max) } let tx = Transaction(version: 1, inputs: unsignedInputs, outputs: outputs, lockTime: 0) return UnsignedTransaction(tx: tx, utxos: utxos) } ``` ```swift= public func sign(_ unsignedTransaction: UnsignedTransaction, with keys: [PrivateKey]) throws -> Transaction { // Define Transaction var signingInputs: [TransactionInput] var signingTransaction: Transaction { let tx: Transaction = unsignedTransaction.tx return Transaction(version: tx.version, inputs: signingInputs, outputs: tx.outputs, lockTime: tx.lockTime) } // Sign signingInputs = unsignedTransaction.tx.inputs let hashType = SighashType.BCH.ALL for (i, utxo) in unsignedTransaction.utxos.enumerated() { // Select key let pubkeyHash: Data = Script.getPublicKeyHash(from: utxo.output.lockingScript) let keysOfUtxo: [PrivateKey] = keys.filter { $0.publicKey().pubkeyHash == pubkeyHash } guard let key = keysOfUtxo.first else { continue } // Sign transaction hash let sighash: Data = signingTransaction.signatureHash(for: utxo.output, inputIndex: i, hashType: SighashType.BCH.ALL) let signature: Data = try Crypto.sign(sighash, privateKey: key) let txin = signingInputs[i] let pubkey = key.publicKey() // Create Signature Script let sigWithHashType: Data = signature + UInt8(hashType) // UnlockingScript作成!!! let unlockingScript: Script = try Script() .appendData(sigWithHashType) .appendData(pubkey.data) // Update TransactionInput signingInputs[i] = TransactionInput(previousOutput: txin.previousOutput, signatureScript: unlockingScript.data, sequence: txin.sequence) } return signingTransaction } ``` 中で、Scriptが実際に使われていることが確認できます。 残念ながらWalletクラスの`send()`は、P2PKHのみしか対応していないので、MultisigやTimeLockを使いたい場合は、自分でこの部分を実装するしかありません。 ちょっと大変ですが、見ていきましょう。 ## Transactionを自分で書いてみよう `ViewController`内の`customSend()`を見てください。 ここでは、先ほど見た`send()`とほぼ同じなのですが、Transactionの作成と署名の実装は、`SendUtility`内の`customTransactionBuild`,`customTransactionSign`に委ねられています。 それでは、`SendUtility`の`customTransactionBuild`を見ていきます。 今、`lockScriptTo`とC`lockScriptChange`が定義されています。どちらもP2PKH用のLockScriptなのですが、チェーンでなくても`Script(address: change.address)`で生成することが出来ます。 ```swift= let lockScriptTo = try! Script() .append(.OP_DUP) .append(.OP_HASH160) .appendData(to.address.data) .append(.OP_EQUALVERIFY) .append(.OP_CHECKSIG) let lockScriptChange = Script(address: change.address)! let toOutput = TransactionOutput(value: to.amount, lockingScript: lockScriptTo.data) let changeOutput = TransactionOutput(value: change.amount, lockingScript: lockScriptChange.data) ``` ここをいろいろ書き換えることで、他タイプのTransactionを作成していきましょう。 ## 10. ブロックチェーンに任意のデータを刻もう `OP_RETURN`を用いると、任意のデータをブロックチェーンに刻むことができます。 ### 実際にやってみよー! 使い方は非常に単純で、OutputにのLockScript内に `OP_RETURN [刻みたいデータ]`と書くだけです。LockScript内に`OP_RETURN`が入っていると、そのOutputは二度と使うことができなくなります。刻めるデータの大きさは、BCHだと220 byte、BTCだと80 byteになります。 ```swift // OP_RETURNのOutputを作成する let opReturnScript = try! Script() .append(.OP_RETURN) .appendData("Hello Bitcoin".data(using: .utf8)!) let opReturnOutput = TransactionOutput(value: 0, lockingScript: opReturnScript.data) // txのoutputsにopReturnOutputを入れる let tx = Transaction(version: 1, inputs: unsignedInputs, outputs: [opReturnOutput, changeOutput], lockTime: 0) ``` `value`に0以外の数値を入れると、そのコインは二度と使えなくなります。 したがって、Bitcoinを`burn`したいときに、`value`に値を入れると良いでしょう。 実際にBroadCastに成功したら、レスポンスのTxIDでExplorerを検索してみて下さい。 ちゃんとOP_RETURNが反映されているかを確認できます。 ## 11. 任意の時間までコインを取り出せないようにしよう `OP_CHECKLOCKTIMEVERIFY`を用いると、任意の時間まで、そのコインを使用できなく出来ます。 <img width="600" src="https://user-images.githubusercontent.com/24402451/45812894-1be43d00-bd0c-11e8-940c-07ceeec7693f.png"> <expiry time>は、ブロック高またはUNIXタイムで指定することが出来ます。 ### 実際にやってみよー! サンプルには、`string2ExpiryTime()`という日付をUNIXタイムに変換し、リトルエンディアン形式のData型に変換する関数を用意しているので、それで任意の時間を指定して、ロックタイムを使ってみましょう。 ```swift // OP_CLTVのOutputを作成する let opCLTVScript = try! Script() .appendData(string2ExpiryTime(dateString: "2018-09-25 14:45:00")) .append(.OP_CHECKLOCKTIMEVERIFY) .append(.OP_DROP) .append(.OP_DUP) .append(.OP_HASH160) .appendData(to.address.data) .append(.OP_EQUALVERIFY) .append(.OP_CHECKSIG) let opCLTVOutput = TransactionOutput(value: 1000, lockingScript: opCLTVScript.data) ``` ### CLTVのtxを利用する CLTVがLockScriptに入っているUTXOを利用するのは、少し面倒です。 形式が今までと異なるので、APIからUTXOを取得する事が出来ません ハードコーディングですが、送金された時のTxIDを利用して、そこからUTXOを作って送金するところ見てみましょう。 今、下記のような`TransactionOutput`があるとします。 ```json "txid":"10a879077602483f7e89cae7202c95119fc9ce53db55f33c7efe401703aa7c38", "vout":[ { "value":"0.00020000", "n":0, "scriptPubKey":{ "hex":"047c039059b17576a914f9a93ce9b7ebed298597655065a96c2e0846db1788ac", "asm":"1502610300 OP_CHECKLOCKTIMEVERIFY OP_DROP OP_DUP OP_HASH160 f9a93ce9b7ebed298597655065a96c2e0846db17 OP_EQUALVERIFY OP_CHECKSIG"}, } } ] ``` ここから、`UnspentTransaction`は下記のように生成できます。 ```swift let transactionOutput = TransactionOutput(value: 20000, lockingScript: Data(hex: "047c039059b17576a914f9a93ce9b7ebed298597655065a96c2e0846db1788ac")!) let txid: Data = Data(hex: "10a879077602483f7e89cae7202c95119fc9ce53db55f33c7efe401703aa7c38")! let txHash: Data = Data(txid.reversed()) let transactionOutpoint = TransactionOutPoint(hash: txHash, index: 0) let utxo = UnspentTransaction(output: transactionOutput, outpoint: transactionOutpoint) ``` 次に、`createUnsignedTx()`に変更を加えます。 UTXOからinputを作るときの`sequence`に`0xffffffff`以外の値を設定します(0などでよいでしょう)。 また、txを作るときの`lockTime`に、先ほど指定したUNIXタイムの値を入れます。 ```swift let unsignedInputs = utxos.map { TransactionInput(previousOutput: $0.outpoint, signatureScript: Data(), sequence: 0) } let tx = Transaction(version: 1, inputs: unsignedInputs, outputs: [toOutput, changeOutput], lockTime: 1502610300) ``` 最後に、署名を行う`signTx()`で、pubkey hashが必要なのですが、デフォルトで使っている`getPublicKeyHash()`は、単純なP2PKHのLockScriptにしか対応していません。 ```swift let pubkeyHash: Data = Script.getPublicKeyHash(from: utxo.output.lockingScript) ``` とりあえず、ここでは、直接pubkey hashの値を入力します。 ```swift let pubkeyHash: Data = Data(hex: "f9a93ce9b7ebed298597655065a96c2e0846db17")! ``` かなり強引ですが、これでLockTimeからtxを送ることができました。もちろん、LockTimeで指定された時間よりも前に送ろうとすれば、エラーが返ってきます。 ```json "txid":"b947a9c5d62996cbb444387854660efcbc1d7eccb17bf5c9b8c0e406ee5462f5", "locktime":1502610300, "vin":[ { "txid":"10a879077602483f7e89cae7202c95119fc9ce53db55f33c7efe401703aa7c38", "vout":0, "sequence":0, "scriptSig":{ "hex":"4730440220234815af5486872bffca3ce54621abba4bac01f5fe7f1a2620cf619ff26e872a0220117b7d70f31aec97fe853ccc5c75f6d6cee2ff48402e653e6da52373cef41a184121023f6b6c5cff1f18abc1462c4c5c055040aea3aa0cb27a943095f972a5adfd2ab1", "asm":"30440220234815af5486872bffca3ce54621abba4bac01f5fe7f1a2620cf619ff26e872a0220117b7d70f31aec97fe853ccc5c75f6d6cee2ff48402e653e6da52373cef41a1841 023f6b6c5cff1f18abc1462c4c5c055040aea3aa0cb27a943095f972a5adfd2ab1" } } ] ``` ## 11. P2SHのMultisig 最後に、先ほどScriptで練習したP2SH形式のMultisigアドレスの生成のやり方と、そこからTransactionを送る方法を見ていきましょう。 ### 実際にやってみよー! Libraryには、以下の2つの関数が用意されています。 - `toP2SH() -> BitcoinKit.Script`: Scriptをハッシュする - `standardAddress(network: BitcoinKit.Network) -> BitcoinKit.Address?`: Scriptからアドレスを生成する `toP2SH`は先ほど、みなさんに書いてもらったP2SH形式のLock ScriptをRedeem Scriptを入れれば作ってもらえるような関数です。 ```swift=f static func createMultisigAddress() -> Address { let multisgLockScript = Script(publicKeys: [MockKey.keyA.pubkey, MockKey.keyB.pubkey, MockKey.keyC.pubkey], signaturesRequired: 2)! return multisgLockScript.toP2SH().standardAddress(network: .testnet)! } ``` ### Multisig及びP2SHからの送金のやり方 P2SH形式のMultisigに送金すると、以下のようなUTXOが生成されます。 ```json "txid":"89123eecf8e7aa0678ca01c3303447312368868dead93b85a9b842b6ac6bd97f", "vout":[ { "value":"0.00003000", "n":0, "scriptPubKey"{ "hex":"a9149c1657fb5142ca85ab2d27ea847f648ec172a01287", "asm":"OP_HASH160 9c1657fb5142ca85ab2d27ea847f648ec172a012 OP_EQUAL", "addresses":[ "pzwpv4lm29pv4pdt95n74prlvj8vzu4qzg7pgrspya" ], "type":"scripthash" } ] ``` それでは、このUTXOをUnlockするために、`customTransactionSign`に変更を加えていきます。 署名を行うときに`sighash`という変数を必要としていて、`sighash`を作るときには、通常はUTXOが必要です。しかし、P2SHの場合は、このときにRedeemScriptを必要としていて、そのデータを入れまず。 UnlockScriptは先ほど書いた通り、それぞれの署名にRedeemScriptを入れればオッケーです。 ```swift // Signing let hashType = SighashType.BCH.ALL for (i, utxo) in unsignedTx.utxos.enumerated() { // RedeemScriptの作成 let redeemScript = Script(publicKeys: [MockKey.keyA.pubkey, MockKey.keyB.pubkey, MockKey.keyC.pubkey], signaturesRequired: 2)! // outputを作り直す let output = TransactionOutput(value: utxo.output.value, lockingScript: redeemScript.data) // 作り直したoutputをsighashを作るときに入れる let sighash: Data = transactionToSign.signatureHash(for: output, inputIndex: i, hashType: SighashType.BCH.ALL) let signatureA: Data = try! Crypto.sign(sighash, privateKey: walletA.privateKey) let signatureB: Data = try! Crypto.sign(sighash, privateKey: walletB.privateKey) let txin = inputsToSign[i] let unlockingScript = try! Script() .append(.OP_0) .appendData(signatureA + UInt8(hashType)) .appendData(signatureB + UInt8(hashType)) .appendData(redeemScript.data) inputsToSign[i] = TransactionInput(previousOutput: txin.previousOutput, signatureScript: unlockingScript.data, sequence: txin.sequence) } ``` 実際に送ったtxのインプットは以下のようになります。 ``` "txid":"761597c8e424697b1dc02031c5b012e2c758fb17bd6f8b638fbeaecadfa00a5e", vin":[ { "txid":"89123eecf8e7aa0678ca01c3303447312368868dead93b85a9b842b6ac6bd97f", "vout":0, "sequence":4294967295, "n":0, "scriptSig":{ "hex":"00483045022100db04a8d0e2cfaefa9e4caf0f23ec20bf44e40b5608c0909502825f9c25f18969022069c0efad7a5a37369fb070bbd8dd607b4e5a4f9a9ff9b1e7a34b9e320cc3901f41483045022100d4c9aa8adce483335164b0b942533fb5263e350c004f9d17ee9769f2c292bab6022001c33b0de5f41af77486bf67a3480025aafb938015f8fe60648c7a6633405f99414c69522102fea95fdb5f88807f1b8c188b3807a2408e58ff1c8800e1c42dfa7c1e6b6a53da2102acff7939b19ab09eaa965f5b88a5c0dae0815d054b5652e5df6722008bb8fe2d2103ef16edc18bb606ee2397805c6dbf2dc93021246598f81429193c52a41d6c351153ae", "asm":"OP_0 3045022100db04a8d0e2cfaefa9e4caf0f23ec20bf44e40b5608c0909502825f9c25f18969022069c0efad7a5a37369fb070bbd8dd607b4e5a4f9a9ff9b1e7a34b9e320cc3901f41 3045022100d4c9aa8adce483335164b0b942533fb5263e350c004f9d17ee9769f2c292bab6022001c33b0de5f41af77486bf67a3480025aafb938015f8fe60648c7a6633405f9941 522102fea95fdb5f88807f1b8c188b3807a2408e58ff1c8800e1c42dfa7c1e6b6a53da2102acff7939b19ab09eaa965f5b88a5c0dae0815d054b5652e5df6722008bb8fe2d2103ef16edc18bb606ee2397805c6dbf2dc93021246598f81429193c52a41d6c351153ae" }, "addr":null, } ] ``` 最後の方は、駆け足でしたが、これでハンズオンを終わりにします。