Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to create a transaction which contains two or more inputs #137

Closed
SunLn opened this issue Aug 23, 2018 · 5 comments
Closed

How to create a transaction which contains two or more inputs #137

SunLn opened this issue Aug 23, 2018 · 5 comments
Assignees
Labels
question Further information is requested

Comments

@SunLn
Copy link

SunLn commented Aug 23, 2018

Thanks a lot for this repo and your hard work.

I have learned a lot from it.

I have passed the unit test in file unitTest .

The testSignTransaction1 shows the step how to create a transaction contains a input and two outputs.

But I don't know how to create a transaction which contains two or more inputs.

  • Details:

Transaction toAddress is mgU6dEQBFpwdSD46Qq2MoMw1tCBi3hEmvy, toValue is 95000000 .
And I have two inputs which hash-value-script is below:

input 1 :
hash: 2eb9d00e2448aea9601df8f4326ee6d753e80c3d261e78795da7b20bb5d451f3
value: 68000000
script: 76a9147c457cce7d24a467ff39d157d3e3c1371bf2b6c888ac

input 2:
hash: 02e7c0a97dee2f31d4fe181c735fb32f8166664f7630c713d003f584cfafb490
value: 32500000
script: 76a9147c457cce7d24a467ff39d157d3e3c1371bf2b6c888ac

Detail Link is input 1 or input 2

The privateKey(wif) in (btc testnet) is cVX5NApUtWqrJneZK2PYsjqcbJvSZVU4Wh7CEF9CQP9Qwa2pCANV and from address is mrr3KvFX3Ekzw5Zjys7VJ1PVf3v9WDcuFq

I have tried two method.

  • Methods One:
    func testSignTransaction3() {
        // Transaction in testnet3
        // https://api.blockcypher.com/v1/btc/test3/txs/0189910c263c4d416d5c5c2cf70744f9f6bcd5feaf0b149b02e5d88afbe78992

        // build input1
        let prevTxID = "2eb9d00e2448aea9601df8f4326ee6d753e80c3d261e78795da7b20bb5d451f3"
        let hash = Data(Data(hex: prevTxID).reversed())
        let index: UInt32 = 1
        let outpoint = BTCTransaction.OutPoint(transactionHash: hash, index: index)
        let subScript = Data(hex: "76a9147c457cce7d24a467ff39d157d3e3c1371bf2b6c888ac")
        let inputForSign = BTCTransaction.Input(outPoint: outpoint, signatureScript: subScript, sequence: UInt32.max)

        // build input2
        let prevTxID2 = "02e7c0a97dee2f31d4fe181c735fb32f8166664f7630c713d003f584cfafb490"
        let hash2 = Data(Data(hex: prevTxID2).reversed())
        let index2: UInt32 = 0
        let outpoint2 = BTCTransaction.OutPoint(transactionHash: hash2, index: index2)
        let subScript2 = Data(hex: "76a9147c457cce7d24a467ff39d157d3e3c1371bf2b6c888ac")
        let inputForSign2 = BTCTransaction.Input(outPoint: outpoint2, signatureScript: subScript2, sequence: UInt32.max)

        // build output
        let balance: Int64 =  68000000
        let balance2: Int64 = 32500000
        let amount: Int64  =  95000000
        let fee: Int64     =   2000000

        let toAddress = "mgU6dEQBFpwdSD46Qq2MoMw1tCBi3hEmvy" // https://testnet.coinfaucet.eu/en/
        let toPubKeyHash = Base58.decode(toAddress).dropFirst().dropLast(4)

        let privateKey = try! PrivateKey(wif: "cVX5NApUtWqrJneZK2PYsjqcbJvSZVU4Wh7CEF9CQP9Qwa2pCANV")
        let fromPublicKey = privateKey.publicKey()

        XCTAssertEqual(fromPublicKey.raw.hex, "03ecb24876e81293acca623d39dbc74ad23d2483e14ba2cbb86ac6d75eb5689d29")

        let fromPubKeyHash = Crypto.sha256ripemd160(fromPublicKey.raw)

        let lockingScript1 = Script.buildPublicKeyHashOut(pubKeyHash: toPubKeyHash)
        let lockingScript2 = Script.buildPublicKeyHashOut(pubKeyHash: fromPubKeyHash)

        XCTAssertEqual(lockingScript1.hex, "76a9140a6dd4fc6b19713621871f01fd9cfc37feb5026788ac")
        XCTAssertEqual(lockingScript2.hex, "76a9147c457cce7d24a467ff39d157d3e3c1371bf2b6c888ac")

        let sending = BTCTransaction.Output(value: amount, lockingScript: lockingScript1)
        let payback = BTCTransaction.Output(value: balance + balance2 - amount - fee, lockingScript: lockingScript2)

        // build signed BTCTransaction input1
        let _tx = BTCTransaction(version: 1, inputs: [inputForSign, inputForSign2], outputs: [sending, payback], lockTime: 0)
        let hashType: SighashType = SighashType.BTC.SINGLE
        let _txHash = Crypto.doubleSHA256(_tx.bitcoinData + UInt32(hashType).littleEndian)

        XCTAssertEqual(_txHash.hex, "28ab5307bb98d78a10514faa6fbbba7bc7881d5eb3f885e110c713ed40f9024a")

        guard let signature: Data = try? Crypto.sign(_txHash, privateKey: privateKey) else {
            XCTFail("failed to sign")
            return
        }

        XCTAssertEqual(signature.hex, "3045022100ef6aa7c8548a62b20a0edda83c90bc7841ede71d6e745eb8fb969f8b5047f00a022024288fa32844be4fe8cdfafb487e95158202b736aa81402cb20c9b172667a3d3")
        // scriptSig: <sig> <pubKey>
        var unlockingScript: Data = Data([UInt8(signature.count + 1)]) + signature + UInt8(hashType)
        unlockingScript += UInt8(fromPublicKey.raw.count)
        unlockingScript += fromPublicKey.raw

        let input = BTCTransaction.Input(outPoint: outpoint, signatureScript: unlockingScript, sequence: UInt32.max)
        let input2 = BTCTransaction.Input(outPoint: outpoint2, signatureScript: unlockingScript, sequence: UInt32.max)

        // build BTCTransaction
        let transaction = BTCTransaction(version: 1, inputs: [input, input2], outputs: [sending, payback], lockTime: 0)

        let expect = Data(hex: "0100000002f351d4b50bb2a75d79781e263d0ce853d7e66e32f4f81d60a9ae48240ed0b92e010000006b483045022100ef6aa7c8548a62b20a0edda83c90bc7841ede71d6e745eb8fb969f8b5047f00a022024288fa32844be4fe8cdfafb487e95158202b736aa81402cb20c9b172667a3d3032103ecb24876e81293acca623d39dbc74ad23d2483e14ba2cbb86ac6d75eb5689d29ffffffff90b4afcf84f503d013c730764f6666812fb35f731c18fed4312fee7da9c0e702000000006b483045022100ef6aa7c8548a62b20a0edda83c90bc7841ede71d6e745eb8fb969f8b5047f00a022024288fa32844be4fe8cdfafb487e95158202b736aa81402cb20c9b172667a3d3032103ecb24876e81293acca623d39dbc74ad23d2483e14ba2cbb86ac6d75eb5689d29ffffffff02c095a905000000001976a9140a6dd4fc6b19713621871f01fd9cfc37feb5026788ace0673500000000001976a9147c457cce7d24a467ff39d157d3e3c1371bf2b6c888ac00000000")
        XCTAssertEqual(transaction.bitcoinData.hex, expect.hex)
        XCTAssertEqual(transaction.txID, "430bafc24b6724028af4455ebfc320b12bec24ca850376910a8302e00246af96")
    }
  • Methods Two:
    func testSignTransaction4() {
        // Transaction in testnet3
        // https://api.blockcypher.com/v1/btc/test3/txs/0189910c263c4d416d5c5c2cf70744f9f6bcd5feaf0b149b02e5d88afbe78992

        // build input1
        let prevTxID = "2eb9d00e2448aea9601df8f4326ee6d753e80c3d261e78795da7b20bb5d451f3"
        let hash = Data(Data(hex: prevTxID).reversed())
        let index: UInt32 = 1
        let outpoint = BTCTransaction.OutPoint(transactionHash: hash, index: index)
        let subScript = Data(hex: "76a9147c457cce7d24a467ff39d157d3e3c1371bf2b6c888ac")
        let inputForSign = BTCTransaction.Input(outPoint: outpoint, signatureScript: subScript, sequence: UInt32.max)

        // build input2
        let prevTxID2 = "02e7c0a97dee2f31d4fe181c735fb32f8166664f7630c713d003f584cfafb490"
        let hash2 = Data(Data(hex: prevTxID2).reversed())
        let outpoint2 = BTCTransaction.OutPoint(transactionHash: hash2, index: 0)
        let subScript2 = Data(hex: "76a9147c457cce7d24a467ff39d157d3e3c1371bf2b6c888ac")
        let inputForSign2 = BTCTransaction.Input(outPoint: outpoint2, signatureScript: subScript2, sequence: UInt32.max)

        // build output
        let balance: Int64 =  68000000
        let balance2: Int64 = 32500000
        let amount: Int64  =  95000000
        let fee: Int64     =   2000000

        let toAddress = "mgU6dEQBFpwdSD46Qq2MoMw1tCBi3hEmvy" // https://testnet.coinfaucet.eu/en/
        let toPubKeyHash = Base58.decode(toAddress).dropFirst().dropLast(4)

        let privateKey = try! PrivateKey(wif: "cVX5NApUtWqrJneZK2PYsjqcbJvSZVU4Wh7CEF9CQP9Qwa2pCANV")
        let fromPublicKey = privateKey.publicKey()

        XCTAssertEqual(fromPublicKey.raw.hex, "03ecb24876e81293acca623d39dbc74ad23d2483e14ba2cbb86ac6d75eb5689d29")

        let fromPubKeyHash = Crypto.sha256ripemd160(fromPublicKey.raw)

        let lockingScript1 = Script.buildPublicKeyHashOut(pubKeyHash: toPubKeyHash)
        let lockingScript2 = Script.buildPublicKeyHashOut(pubKeyHash: fromPubKeyHash)

        XCTAssertEqual(lockingScript1.hex, "76a9140a6dd4fc6b19713621871f01fd9cfc37feb5026788ac")
        XCTAssertEqual(lockingScript2.hex, "76a9147c457cce7d24a467ff39d157d3e3c1371bf2b6c888ac")


        let sending = BTCTransaction.Output(value: amount, lockingScript: lockingScript1)
        let payback = BTCTransaction.Output(value: balance + balance2 - amount - fee, lockingScript: lockingScript2)


        // build signed BTCTransaction input1
        let _tx = BTCTransaction(version: 1, inputs: [inputForSign], outputs: [sending, payback], lockTime: 0)
        let hashType: SighashType = SighashType.BTC.SINGLE
        let _txHash = Crypto.doubleSHA256(_tx.bitcoinData + UInt32(hashType).littleEndian)

        XCTAssertEqual(_txHash.hex, "23bce5a777f3c90d73c5f0af2b03c9faccd08b37c3c6a982165814a714ff9265")

        guard let signature: Data = try? Crypto.sign(_txHash, privateKey: privateKey) else {
            XCTFail("failed to sign")
            return
        }
        XCTAssertEqual(signature.hex, "3044022037533c68e544db72b0ad27e0514626408e9c4e447a61566a0ad2343ee02c3d4102206574f4bef4c27b64b3ba570bc9d95b9bd8b827f28d2151f314cc5310196f06d2")
        // scriptSig: <sig> <pubKey>
        var unlockingScript: Data = Data([UInt8(signature.count + 1)]) + signature + UInt8(hashType)
        unlockingScript += UInt8(fromPublicKey.raw.count)
        unlockingScript += fromPublicKey.raw
        let input = BTCTransaction.Input(outPoint: outpoint, signatureScript: unlockingScript, sequence: UInt32.max)

        // build signed BTCTransaction input2
        let _tx2 = BTCTransaction(version: 1, inputs: [inputForSign2], outputs: [sending, payback], lockTime: 0)
        let _txHash2 = Crypto.doubleSHA256(_tx2.bitcoinData + UInt32(hashType).littleEndian)

        XCTAssertEqual(_txHash2.hex, "444c452e767c626f6fc1c1b940b27cf246f71a2f37ed4e39b5339e8c8f1ca5fc")

        guard let signature2: Data = try? Crypto.sign(_txHash2, privateKey: privateKey) else {
            XCTFail("failed to sign")
            return
        }
        XCTAssertEqual(signature.hex, "3044022037533c68e544db72b0ad27e0514626408e9c4e447a61566a0ad2343ee02c3d4102206574f4bef4c27b64b3ba570bc9d95b9bd8b827f28d2151f314cc5310196f06d2")
        // scriptSig: <sig> <pubKey>
        var unlockingScript2: Data = Data([UInt8(signature2.count + 1)]) + signature2 + UInt8(hashType)
        unlockingScript2 += UInt8(fromPublicKey.raw.count)
        unlockingScript2 += fromPublicKey.raw
        let input2 = BTCTransaction.Input(outPoint: outpoint2, signatureScript: unlockingScript2, sequence: UInt32.max)


        // build BTCTransaction
        let transaction = BTCTransaction(version: 1, inputs: [input, input2], outputs: [sending, payback], lockTime: 0)

        let expect = Data(hex: "0100000002f351d4b50bb2a75d79781e263d0ce853d7e66e32f4f81d60a9ae48240ed0b92e010000006a473044022037533c68e544db72b0ad27e0514626408e9c4e447a61566a0ad2343ee02c3d4102206574f4bef4c27b64b3ba570bc9d95b9bd8b827f28d2151f314cc5310196f06d2032103ecb24876e81293acca623d39dbc74ad23d2483e14ba2cbb86ac6d75eb5689d29ffffffff90b4afcf84f503d013c730764f6666812fb35f731c18fed4312fee7da9c0e702000000006b483045022100a5a602528de5f741ab58fa62ae0d51a1813f36642172d986ef3342ad36cf6fc1022050beb570a28e5708844b98880c9d984919fa7de3efdef10d0c7fd97c787025a8032103ecb24876e81293acca623d39dbc74ad23d2483e14ba2cbb86ac6d75eb5689d29ffffffff02c095a905000000001976a9140a6dd4fc6b19713621871f01fd9cfc37feb5026788ace0673500000000001976a9147c457cce7d24a467ff39d157d3e3c1371bf2b6c888ac00000000")
        XCTAssertEqual(transaction.bitcoinData.hex, expect.hex)
        XCTAssertEqual(transaction.txID, "ca56271ffb2ada14f3624125cd3ba5bc14ab00b7d7cd334ba6f70edac3ff904b")
    }

testSignTransaction3 and testSignTransaction4 both passed unit test.

I paste transaction.bitcoinData.hex value to this page https://live.blockcypher.com/btc/pushtx/ Transaction Hex* and select Bitcoin Testnet, then click Broadcast Transaction .

And both verified failed.
Both show Error validating transaction: Error running script for input 0 referencing 2eb9d00e2448aea9601df8f4326ee6d753e80c3d261e78795da7b20bb5d451f3 at 1: Script was NOT verified successfully..

I don't know why. It cost me a lot time to try.
Can you help me?
After solved this problem, I will make a PR if you pleasure.
Thanks a lot.

@usatie usatie added the question Further information is requested label Aug 23, 2018
@usatie usatie self-assigned this Aug 23, 2018
@usatie
Copy link
Member

usatie commented Aug 23, 2018

Thanks, this example would be your help!
sendToSomeAddress(), createUnsignedTx(), signTx() are all you should implement.
Signing a transaction with multiple inputs is a bit tricky process.

  1. Unsigned transaction. (All inputs are "unsigned". "unsigned" means unlock script is filled with lock script.)
  2. Partially signed transaction. (Sign inputs one by one, and each time update the input with new signature. )
  3. Signed transaction (All inputs are filled with unlock script = signature.)

Pseudo code is like this.

// Create unsigned Tx
unsignedTx = Transaction(unsignedInput1, unsignedInput2)

// Sign input1
hash1 = unsignedTx.signatureHash
sig1 = privkey1.sign(hash1)
signedInput1 = unsignedInput1.update(with: sig1)

// Sign input2
partiallySignedTx = Transaction(signedInput1, unsignedInput2)
hash2 = partiallySignedTx.signatureHash
sig2 = privkey2.sign(hash2)
signedInput2 = unsignedInput2.update(with: sig2)

// Create signed Tx
signedTx = Transaction(signedInput1, signedInput2)

https://github.com/yenom/BitcoinKit/blob/master/Examples/Wallet/Wallet/SendViewController.swift#L121

@usatie
Copy link
Member

usatie commented Aug 23, 2018

@GitCash send 1000 bits to @SunLn .
Thanks for asking a question here!

@GitCash
Copy link

GitCash commented Aug 23, 2018

Hey SunLn, user usatie tipped you 1000 bits in Bitcoin Cash ( ~ $0.521 ).

Click here to claim it!

You can also add the "thumbs down" reaction to usatie's comment above to prevent future tips.

@SunLn
Copy link
Author

SunLn commented Aug 23, 2018

Thanks. You really help me a lot.

@usatie
Copy link
Member

usatie commented Aug 23, 2018

@SunLn
No worry! Let's enjoy Bitcoin with Swift!

@usatie usatie closed this as completed Aug 23, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants