Usando CoreBitcoin en Swift para crear transacciones sin procesar

Soy nuevo en la codificación, y solo sé rápido, obj-C todavía es extraño para mí. Tengo una billetera que funciona, pero por ahora confío en la API BlockCypher para crear una transacción que NO quiero hacer. ¿Puede alguien ayudarme a decirme qué estoy haciendo mal en el siguiente fragmento de código? Estoy creando una transacción sin procesar, sin embargo, recibo una respuesta extraña cuando la decodifico donde las matrices de direcciones están vacías o son nulas. Algo está muy mal, si alguien tiene alguna experiencia se lo agradecería mucho ya que esto me está volviendo loco.

import UIKit

clase BuildTransactionViewController: UIViewController, BTCTransactionBuilderDataSource {

var addressToSpendFrom = "n1QQYAHbw3q6UjWN6Q4d9oqa6u5iUDnPHT"
var privateKeyToSign = "cNeZkP1QPQ37C4rLvoQ8xZ5eujcjsYHZMj8CLfPPohYPvfKhzHWu"
var receiverAddress = "n1v9HH9Abs36fYf8KbwnFUfzR4prLBXhtW"
var inputData = [NSDictionary]()
var scriptArray = [String]()
var transaction = BTCTransaction()

override func viewDidLoad() {
    super.viewDidLoad()

    getUTXOforAddress(address: addressToSpendFrom)
}

func getUTXOforAddress(address: String) {

    var url:NSURL!
    url = NSURL(string: "https://api.blockcypher.com/v1/btc/test3/addrs/\(address)?unspentOnly=true")

    let task = URLSession.shared.dataTask(with: url! as URL) { (data, response, error) -> Void in

        do {

            if error != nil {

                print(error as Any)
                DispatchQueue.main.async {
                    displayAlert(viewController: self, title: "Error", message: "Please check your interent connection.")
                }

            } else {

                if let urlContent = data {

                    do {

                        let jsonUTXOResult = try JSONSerialization.jsonObject(with: urlContent, options: JSONSerialization.ReadingOptions.mutableLeaves) as! NSDictionary

                        print("json = \(jsonUTXOResult)")

                        if let utxoCheck = jsonUTXOResult["txrefs"] as? NSArray {

                            self.inputData = utxoCheck as! [NSDictionary]
                            print("utxoCheck = \(utxoCheck)")

                            for item in self.inputData {

                               let transactionHash = (item)["tx_hash"] as! String
                                let value = (item)["value"] as! Int

                                var url:NSURL!
                                url = NSURL(string: "https://api.blockcypher.com/v1/btc/test3/txs/\(transactionHash)")

                                let task = URLSession.shared.dataTask(with: url! as URL) { (data, response, error) -> Void in

                                    do {

                                        if error != nil {

                                            print(error as Any)
                                            DispatchQueue.main.async {
                                                displayAlert(viewController: self, title: "Error", message: "Please check your interent connection.")
                                            }

                                        } else {

                                            if let urlContent = data {

                                                do {

                                                    let txHashResult = try JSONSerialization.jsonObject(with: urlContent, options: JSONSerialization.ReadingOptions.mutableLeaves) as! NSDictionary

                                                    print("txHashResult = \(txHashResult)")

                                                    if let outputsCheck = txHashResult["outputs"] as? NSArray {

                                                        print("outputs = \(outputsCheck)")

                                                        for output in outputsCheck {

                                                            if let valueCheck = (output as! NSDictionary)["value"] as? Int {

                                                                if valueCheck == value {

                                                                    let script = (output as! NSDictionary)["script"] as! String
                                                                    self.scriptArray.append(script)
                                                                    print("script = \(script)")
                                                                }

                                                            }

                                                        }

                                                        print("inputData = \(self.inputData)")
                                                        print("scriptArray = \(self.scriptArray)")
                                                        self.callBTCTransaction()

                                                    }

                                                } catch {

                                                    print("JSon processing failed")
                                                    DispatchQueue.main.async {
                                                        displayAlert(viewController: self, title: "Error", message: "Please try again.")
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }

                                task.resume()
                            }
                       }

                    } catch {

                        print("JSon processing failed")
                        DispatchQueue.main.async {
                            displayAlert(viewController: self, title: "Error", message: "Please try again.")
                        }
                    }
                }
            }
        }
    }

    task.resume()

}

func callBTCTransaction() {

    let address = BTCAddress(string: self.receiverAddress)
    let newTransaction = BTCTransactionBuilder()
    newTransaction.dataSource = self
    newTransaction.shouldSign = true
    newTransaction.changeAddress = BTCAddress(string: self.addressToSpendFrom)
    newTransaction.outputs = [BTCTransactionOutput(value: BTCAmount(1000), address: address)]
    newTransaction.feeRate = BTCAmount(2000)
    var result:BTCTransactionBuilderResult? = nil
    do {
        result = try newTransaction.buildTransaction()
        print("transactionRaw = \(String(describing: result?.transaction.hex))")
    } catch {
        print("error = \(error as Any)")
    }
}

func transactionBuilder(_ txbuilder: BTCTransactionBuilder!, keyForUnspentOutput txout: BTCTransactionOutput!) -> BTCKey! {
    print("transactionBuilder")

    let key = BTCKey.init(wif: self.privateKeyToSign)
    key?.isPublicKeyCompressed = true

    return key
}



func unspentOutputs(for txbuilder: BTCTransactionBuilder!) -> NSEnumerator! {

    let outputs = NSMutableArray()

    for (index, item) in inputData.enumerated() {

        let txout = BTCTransactionOutput()
        txout.value = BTCAmount((item).value(forKey: "value") as! Int64)
        txout.script = BTCScript.init(hex: self.scriptArray[index])
        txout.index = UInt32((item).value(forKey: "tx_output_n") as! Int)
        txout.confirmations = UInt((item).value(forKey: "confirmations") as! Int)
        let transactionHash = (item)["tx_hash"] as! String
        txout.transactionHash = transactionHash.data(using: .utf8)
        outputs.add(txout)

    }

    print("outputs = \(outputs)")

    return outputs.objectEnumerator()
}

}

Ofreceré una recompensa de $ 50 en Bitcoin a cualquiera que me ayude a resolver esto para que devuelva una transacción hexadecimal adecuada.

Respuestas (1)

No puedo comentar debido a la mala reputación. Echa un vistazo a este enlace: https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoinTestsOSX/BTCTransactionTests.swift

Básicamente, crean un tx como ese:

    let tx = BTCTransaction()

    var spentCoins = BTCAmount(0)

    // Add all outputs as inputs
    for txout in txouts {
        let txin = BTCTransactionInput()
        txin.previousHash = txout.transactionHash
        txin.previousIndex = txout.index
        tx.addInput(txin)

        print("txhash: http://blockchain.info/rawtx/\(BTCHexFromData(txout.transactionHash))")
        print("txhash: http://blockchain.info/rawtx/\(BTCHexFromData(BTCReversedData(txout.transactionHash))) (reversed)")

        spentCoins += txout.value
    }

    print(String(format: "Total satoshis to spend:       %lld", spentCoins))
    print(String(format: "Total satoshis to destination: %lld", amount))
    print(String(format: "Total satoshis to fee:         %lld", fee))
    print(String(format: "Total satoshis to change:      %lld", spentCoins - (amount + fee)))

    // Add required outputs - payment and change
    let paymentOutput = BTCTransactionOutput(value: amount, address: destinationAddress)
    let changeOutput = BTCTransactionOutput(value: (spentCoins - (amount + fee)), address: changeAddress)

    // Idea: deterministically-randomly choose which output goes first to improve privacy.
    tx.addOutput(paymentOutput)
    tx.addOutput(changeOutput)

    for i in 0 ..< txouts.count {
        // Normally, we have to find proper keys to sign this txin, but in this
        // example we already know that we use a single private key.

        let txout = txouts[i] // output from a previous tx which is referenced by this txin.
        let txin = tx.inputs[i] as! BTCTransactionInput

        let sigScript = BTCScript()

        let d1 = tx.data

        let hashType = BTCSignatureHashType.SIGHASH_ALL


        let getHash: NSData?
        do {
            getHash = try tx.signatureHashForScript(txout.script, inputIndex: UInt32(i), hashType: hashType)
        } catch {
            errorOut = error
            getHash = nil
        }

        let d2 = tx.data

        XCTAssertEqual(d1, d2, "Transaction must not change within signatureHashForScript!")

        // 134675e153a5df1b8e0e0f0c45db0822f8f681a2eb83a0f3492ea8f220d4d3e4
        guard let hash = getHash else { return (nil, errorOut) }
        print(String(format: "Hash for input %d: \(BTCHexFromData(hash))", i))
        let signatureForScript = key.signatureForHash(hash, hashType: hashType)
        sigScript.appendData(signatureForScript)
        sigScript.appendData(key.publicKey)

        let sig = signatureForScript.subdataWithRange(NSRange(location: 0, length: signatureForScript.length - 1))  // trim hashtype byte to check the signature.
        XCTAssertTrue(key.isValidSignature(sig, hash: hash), "Signature must be valid")

        txin.signatureScript = sigScript
    }

    // Validate the signatures before returning for extra measure.

    do {
        let sm = BTCScriptMachine(transaction: tx, inputIndex: 0)

        do {
            try sm.verifyWithOutputScript((txouts.first as BTCTransactionOutput!).script.copy() as! BTCScript)
        } catch {
            print("Error: \(error)")
            XCTFail("should verify first output")
        }


    }

En mi caso, recupero tx que se creó en el backend de JS y solo lo estoy firmando. Publicaré mi ejemplo de código más tarde.