Bitcoin Forum
November 06, 2024, 02:01:03 PM *
News: Latest Bitcoin Core release: 28.0 [Torrent]
 
   Home   Help Search Login Register More  
Pages: [1]
  Print  
Author Topic: BOUNTY 0.1BTC signing question with multiple inputs  (Read 965 times)
doof (OP)
Hero Member
*****
Offline Offline

Activity: 765
Merit: 503


View Profile WWW
April 12, 2014, 10:28:11 AM
Last edit: April 25, 2014, 04:08:38 AM by doof
 #1

lll pay 0.1BTC if someone can explain & show some good code that explains the following (python, c++, c#)

Quick question about signing transactions.  I've written a method to sign transactions using bytes.  Mostly just to understand the scrypt part and inputs and outputs better.

All works well with one input and 2 outputs (change)  Eg, this transaction worked http://blockexplorer.com/tx/852cb5b462de0fc0d67d14618945e854822e649098242577a363bd1d2caa9c7a#o0

What im struggling with is multiple inputs.  The below artcile lists an input as this.  Which makes sense.   If input is a collection, the below is just repeated?  Then the whole structure is signed?


previous output hash
(reversed)   48 4d 40 d4 5b 9e a0 d6 52 fc a8 25 8a b7 ca a4 25 41 eb 52 97 58 57 f9 6f b5 0c d7 32 c8 b4 81
previous output index   00 00 00 00
script length   
scriptSig   script containing signature
sequence   ff ff ff ff

I have been following this tut, and the python example off it.
http://www.righto.com/2014/02/bitcoins-hard-way-using-raw-bitcoin.html


c# code:  NB, DTO classes not in sample

public class Transaction
    {
        public Byte[] Version
        {
            get
            {
                return new Byte[4] { 0x01, 0x00, 0x00, 0x00 };
            }
        }

        public Byte[] LockTime
        {
            get
            {
                return new Byte[4] { 0x00, 0x00, 0x00, 0x00 };
            }
        }

        public Byte[] HashType
        {
            get
            {
                return new Byte[4] { 0x01, 0x00, 0x00, 0x00 };
            }
        }

        public Byte[] Change
        {
            get;
            private set;
        }

        public Byte[] Value
        {
            get;
            private set;
        }

        public Byte NumTxIn
        {
            get
            {
                if (_outputsToSpend != null)
                {
                    return Convert.ToByte(_outputsToSpend.Count); //new Byte[1] { 0x01 };
                }
                else
                {
                    return Convert.ToByte(0);
                }
            }
        }

        public Byte NumTxOut
        {
            get
            {
                return Convert.ToByte(2);
            }
        }

        public Byte[] OutPuts
        {
            get
            {
                return null;
            }
        }

        public Byte[] Sequence
        {
            get
            {
                return new Byte[4] { 0xFF, 0xFF, 0xFF, 0xFF }; //FF FF FF FF
            }
        }

        private IList<DC.Common.Models.UnspentOutput> _outputsToSpend;

        public Transaction(IList<DC.Common.Models.UnspentOutput> outputsToSpend, DC.Common.Models.Address sourceAddress, String destinationAddress, String destinationHash160, Decimal amount, Decimal fee = 0.0001M)
        {
            //amount = amount * DC.Common.Math.SATOSHI;
            //fee = fee * DC.Common.Math.SATOSHI;

            //Amount to send + miners fee
            Decimal change = (amount + fee) - outputsToSpend.Sum(o => o.Value) / DC.Common.Math.SATOSHI;

            //CHANGE
            this.Change = DC.Common.Math.GetValueAsBytes(change);
            this.Value = DC.Common.Math.GetValueAsBytes(amount);

            _outputsToSpend = outputsToSpend;
        }

        public byte[] CreateAndSignTransaction(DC.Common.Models.Address sourceAddress, String destinationAddress, String destinationHash160, Decimal amount, Decimal fee = 0.0001M)
        {
            if (!DC.Common.BitcoinHelper.IsValidAddress(destinationAddress))
                throw new ArgumentException("Destination address is not valid");

            //Int32 OUTPUT_INDEX = 1;
            Int32 SIGHASH_ALL = 1;
            
            //1
            Byte[] version = this.Version;
            Debug.Assert(version[0] == 1);
            Debug.Assert(version.Length == 4);

            //2
            //Byte num_txin = Convert.ToByte(outputsToSpend.Count); //new Byte[1] { 0x01 };
            Byte[] tx_fields = version.Concat(this.NumTxIn);
            Debug.Assert(tx_fields.Length == 5);

            foreach (DC.Common.Models.UnspentOutput outputToSpend in _outputsToSpend)
            {
                //THIS IS HASH OF THE TRANSACTION THAT CONTAINS THE UNSPENT OUTPUT
                //NB, ALREADY REVERSED!
                Byte[] incomingTxHash = DC.Common.StringHelper.HexStringToByte(outputToSpend.TxHash);
                //Array.Reverse(incomingTxHash);

                //3
                Byte[] prevout_hash = DC.Common.StringHelper.HexStringToByte(outputToSpend.TxHash);
                //Array.Reverse(prevout_hash);
                tx_fields = tx_fields.Concat(prevout_hash);

                //4 (index 0)
                //TODO:  CONFIRM THIS.  4BYTE ARRAY, WIHT PREVIOUS INDEX EG 00 00 00 01
                Byte[] output_index = BitConverter.GetBytes(outputToSpend.TxOutputN);
                Array.Reverse(output_index);
                //Byte[] output_index = new Byte[4] { 0x00, 0x00, 0x00, 0x00 };
                //Array.Copy(outputIndexTemp, 1, output_index, 4 - outputIndexTemp.Length, outputIndexTemp.Length);
                tx_fields = tx_fields.Concat(output_index);

                //SWAP.  NB WE HAVE THE SCRYPT, JUST CONVERT TO BYTES
                //##next comes the part of the transaction input. here we place the script of the *output* that we want to redeem
                //String sourceOutputPublicKey = DC.Common.Script.ParseScript(outputsToSpend[0].Script);
                Byte[] tempScript = DC.Common.StringHelper.HexStringToByte(outputToSpend.Script);

                Byte scriptSighHashLength = Convert.ToByte(tempScript.Length);
                tx_fields = tx_fields.Concat(scriptSighHashLength);

                Byte[] scriptSigHash = DC.Common.StringHelper.HexStringToByte(outputToSpend.Script);
                tx_fields = tx_fields.Concat(scriptSigHash);

                //7
                tx_fields = tx_fields.Concat(this.Sequence);
            }

            //8
            //Byte[] num_txout = new Byte[1] { 0x02 }; //1
            tx_fields = tx_fields.Concat(this.NumTxOut);

            //9
            //Byte[] value = DC.Common.Math.GetValueAsBytes(amount);
            tx_fields = tx_fields.Concat(this.Value);

            //10
            //http://bitcoin.stackexchange.com/questions/3374/how-to-redeem-a-basic-tx
            Byte[] scriptPubKey = DC.Common.Script.CreateScriptPubKey(destinationHash160);

            Byte scriptLen = Convert.ToByte(scriptPubKey.Length);
            tx_fields = tx_fields.Concat(scriptLen);
            tx_fields = tx_fields.Concat(scriptPubKey);

            //Amount to send + miners fee
            //Decimal change = (amount + fee) - outputsToSpend.Sum(o => o.Value) / DC.Common.Math.SATOSHI;

            //CHANGE
            tx_fields = tx_fields.Concat(this.Change);

            //http://bitcoin.stackexchange.com/questions/3374/how-to-redeem-a-basic-tx
            Byte[] changeScriptPubKey = DC.Common.Script.CreateScriptPubKey(sourceAddress.PublicKeyHash160); //SELF

            Byte changeScriptLen = Convert.ToByte(changeScriptPubKey.Length);
            tx_fields = tx_fields.Concat(changeScriptLen);
            tx_fields = tx_fields.Concat(changeScriptPubKey);
            //END CHANGE

            //12
            tx_fields = tx_fields.Concat(this.LockTime);

            //13
            tx_fields = tx_fields.Concat(this.HashType);

            //DOUBLE HASH
            Byte[] hash_scriptless = Crypto.DoubleSHA256(tx_fields);

            //SIGN DATA
            Byte[] sig_data = Crypto.Sign(hash_scriptless, sourceAddress.d, DC.Common.Crypto.GetDomainParams());
            sig_data = sig_data.Concat(Convert.ToByte(SIGHASH_ALL));

            //final_tx.write_int32(tx_fields['version'])
            //final_tx.write_compact_size(tx_fields['num_txin'])
            //final_tx.write(tx_fields['prevout_hash'])
            //final_tx.write_uint32(tx_fields['output_index'])
            Byte[] final_tx = this.Version;  //new Byte[4] { 0x01, 0x00, 0x00, 0x00 }; //version
            Debug.Assert(final_tx[0] == 1);

            final_tx = final_tx.Concat(NumTxIn); //count

            foreach (DC.Common.Models.UnspentOutput outputToSpend in _outputsToSpend)
            {
                //final_tx = final_tx.Concat(prevout_hash);
                Byte[] previousTxHash = StringHelper.HexStringToByte(outputToSpend.TxHash);
                final_tx = final_tx.Concat(previousTxHash);

                Byte[] previousOutputIndex = new Byte[4];
                previousOutputIndex = BitConverter.GetBytes(outputToSpend.TxOutputN); //TODO
                Array.Reverse(previousOutputIndex);
                final_tx = final_tx.Concat(previousOutputIndex);
            }

            Byte[] pub_key = StringHelper.HexStringToByte(sourceAddress.PublicKeyAsHex);

            //scriptSig = chr(len(sig_data)) + sig_data + chr(len(pubkey_data)) + pubkey_data
            Byte[] scriptSig = new Byte[1]; //Length
            scriptSig[0] = Convert.ToByte(sig_data.Length); //Length

            scriptSig = scriptSig.Concat(sig_data); //data
            scriptSig = scriptSig.Concat(Convert.ToByte(pub_key.Length)); //Length pub key
            scriptSig = scriptSig.Concat(pub_key); //pub key

            final_tx = final_tx.Concat(Convert.ToByte(scriptSig.Length)); //Length of script sig
            final_tx = final_tx.Concat(scriptSig);

            final_tx = final_tx.Concat(this.Sequence);
            final_tx = final_tx.Concat(this.NumTxOut);
            final_tx = final_tx.Concat(this.Value);
            final_tx = final_tx.Concat(Convert.ToByte(scriptPubKey.Length));
            final_tx = final_tx.Concat(scriptPubKey);
            final_tx = final_tx.Concat(this.Change);
            final_tx = final_tx.Concat(Convert.ToByte(changeScriptPubKey.Length));
            final_tx = final_tx.Concat(changeScriptPubKey);
            final_tx = final_tx.Concat(this.LockTime);

            return final_tx;
        }
    }
doof (OP)
Hero Member
*****
Offline Offline

Activity: 765
Merit: 503


View Profile WWW
April 25, 2014, 07:54:05 AM
 #2

Bounty added.
Nicolas Dorier
Hero Member
*****
Offline Offline

Activity: 714
Merit: 662


View Profile
April 30, 2014, 12:14:32 AM
Last edit: April 30, 2014, 12:55:52 AM by Nicolas Dorier
 #3

Take a look at my code

https://github.com/NicolasDorier/NBitcoin/blob/master/NBitcoin/Script.cs#L358

Response : It depends on the type of signature.

A signature in a script is in two part the DER + One SigHash type :

"All" sign the hash of all the transaction (No output, no input can be added)
"Anyone can pay" same thing, but without the other inputs, (other inputs can be added or removed)
"None" will not sign output (other output can be added or removed)
"Single" you only care about your input, and the output of the same index.

You are using C#, so feel free to take my code, and run the unit tests. (Or make your own)

I ported the C++ unit test suite.
https://github.com/NicolasDorier/NBitcoin/blob/master/NBitcoin.Tests/transaction_tests.cs

Just take my code, put a break point in the SignatureHash method, run tests, and compare the difference with your code.

In your case though, it appears you only want to support the "All" type. So you can freely skip if case statements in my code.

Bitcoin address 15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe
doof (OP)
Hero Member
*****
Offline Offline

Activity: 765
Merit: 503


View Profile WWW
April 30, 2014, 02:06:09 AM
 #4

Thanks for posting.  I have received an answer via PM.  Im just verifying that first.
Jori
Newbie
*
Offline Offline

Activity: 25
Merit: 0


View Profile
May 01, 2014, 09:18:16 AM
 #5

Thanks for posting.  I have received an answer via PM.  Im just verifying that first.

Nice trick. No need to pay any bounty there...
doof (OP)
Hero Member
*****
Offline Offline

Activity: 765
Merit: 503


View Profile WWW
May 02, 2014, 10:29:46 AM
 #6

Thanks for posting.  I have received an answer via PM.  Im just verifying that first.

Nice trick. No need to pay any bounty there...
Shut up you muppet.  Ill let you contact the apxu who helped me directly, and wasn't too interested in the bounty.

doof (OP)
Hero Member
*****
Offline Offline

Activity: 765
Merit: 503


View Profile WWW
May 02, 2014, 10:35:21 AM
 #7

Paid to apxu f2efa958f00d6d88cdcf5110b2bfba42585d09415da5f1fac6ad5a6eca9457a7-000

Jori
Newbie
*
Offline Offline

Activity: 25
Merit: 0


View Profile
May 02, 2014, 12:16:58 PM
 #8

Thanks for posting.  I have received an answer via PM.  Im just verifying that first.

Nice trick. No need to pay any bounty there...
Shut up you muppet.  Ill let you contact the apxu who helped me directly, and wasn't too interested in the bounty.

http://s30.postimg.org/ylo9navy9/fuckoff.png

Just saying that (IMHO) if you offer a bounty in public, you should pay in public. Anyone could forge this images, it says nothing. No need to get grumpy, unless indeed you have something to hide.
apxu
Member
**
Offline Offline

Activity: 229
Merit: 13


View Profile
May 04, 2014, 09:10:42 PM
 #9

Got it. Thanks.
doof (OP)
Hero Member
*****
Offline Offline

Activity: 765
Merit: 503


View Profile WWW
May 05, 2014, 01:02:02 PM
 #10

No worries apxu.  Thanks for the help.
Pages: [1]
  Print  
 
Jump to:  

Powered by MySQL Powered by PHP Powered by SMF 1.1.19 | SMF © 2006-2009, Simple Machines Valid XHTML 1.0! Valid CSS!