First we will see , what a bitcoin transaction is, alongside will create a bitcoin transaction using javascript & then will broadcast it to bitcoin testnet.

There are two types of Bitcoin transactions, one is coinbase transaction and other is normal transaction. we will discuss only normal transactions here.

Transaction consists of version number, inputs, outputs and locktime.
4-byte version number tells bitcoin peers and miners that it follows certain consensus rules, so peers and miners can validate & verify them according to those set of rules.

4-byte Locktime field is used to set time in future when transaction will be ready to included in blockchain.

Transaction has inputs and outputs, inputs spends unspendable outputs of previous transaction, whereas outputs sits as (UTXO) unspendable outputs until some input spends them.A transaction can have multiple inputs and outputs.

Structure of Transaction looks like.
en-tx-overview

Pic courtesy: bitcoin.org

Javascript Code used here is simplified version of coinbin github repository[1], to understand bitcoin transaction.

This code demonstrates spending of previous P2PKH (Pay to Pub Key hash) script to a new P2PKH output and uses standard bitcoin addresses

    var btrx = {};
    btrx.version = 1;
    btrx.inputs = [];
    btrx.outputs = [];
    btrx.locktime = 0;

An output has 4-byte index number, (default 0), amount in satoshi to spend and a pubkey script.

One bitcoin equals 100,000,000 satoshis

A input uses transaction identifier and output index number with signature script to spend previous output, the signature script is a collection of data parameters which satisfies pubkey script included in output which current input is spending.
Every input has a 4-byte sequence number, it is usually set to maximum, but to disable time locked transaction,at least in one input it should be set less than maximum.

en-tx-overview-spending

Pic courtesy: bitcoin.org

	btrx.addinput = function(txid, index, script, sequence) {
        var o = {};
        o.outpoint = {'hash': txid, 'index': index};
        //Signature and Public Key will be added after signing
        //push previous output pubkey script first
        o.script = Crypto.util.hexToBytes(script); 
        o.sequence = sequence || ((btrx.locktime==0) ? 4294967295 : 0);
        return this.inputs.push(o);
    } 



    btrx.addoutput = function(address, value) {
        var o = {};
        var buf = [];
        var addrDecoded = btrx.addressDecode(address);
        o.value = new BigInteger('' + Math.round((value*1) * 1e8), 10);
        buf.push(118); //OP_DUP
        buf.push(169);  //OP_HASH160
        buf.push(addrDecoded.length);
        buf = buf.concat(addrDecoded); // address in bytes
        buf.push(136 ); //OP_EQUALVERIFY
        buf.push(172); //  OP_CHECKSIG
        o.script =   buf;
        return this.outputs.push(o);
    }

    // Only standard addresses
    btrx.addressDecode = function(address) {
        var bytes = B58.decode(address);
        var front = bytes.slice(0, bytes.length-4);
        var back = bytes.slice(bytes.length-4);
        var checksum = Crypto.SHA256(Crypto.SHA256(front,{asBytes: true}),
                                     {asBytes: true}).slice(0, 4);
                               
        if (checksum+""  ==  back+"") {
            return front.slice(1);
            }
    }

A raw transaction has to be serialised, according to structure mentioned here Bitcoin Raw Transaction format.

    /* serialize a transaction */
    btrx.serialize = function() {
        var buffer = [];
        buffer = buffer.concat(bitjs.numToBytes(parseInt(this.version),4));

        buffer = buffer.concat(bitjs.numToVarInt(this.inputs.length));
        for (var i = 0; i < this.inputs.length; i++) {
            var txin = this.inputs[i];
            buffer = buffer.concat(Crypto.util.hexToBytes(txin.outpoint.hash).reverse());
            buffer = buffer.concat(bitjs.numToBytes(parseInt(txin.outpoint.index),4));
            var scriptBytes = txin.script;
            buffer = buffer.concat(bitjs.numToVarInt(scriptBytes.length));
            buffer = buffer.concat(scriptBytes);
            buffer = buffer.concat(bitjs.numToBytes(parseInt(txin.sequence),4));
        }
        buffer = buffer.concat(bitjs.numToVarInt(this.outputs.length));

        for (var i = 0; i < this.outputs.length; i++) {
            var txout = this.outputs[i];
            buffer = buffer.concat(bitjs.numToBytes(txout.value,8));
            var scriptBytes = txout.script;
            buffer = buffer.concat(bitjs.numToVarInt(scriptBytes.length));
            buffer = buffer.concat(scriptBytes);
        }

        buffer = buffer.concat(bitjs.numToBytes(parseInt(this.locktime),4));
        return Crypto.util.bytesToHex(buffer);		
    }

After serializing a transaction in raw transaction format, we need to sign it and signature script will be added to inputs.
When bitcoin node will process transactions it will validate that new signature script corresponds to previous pubkey script.

For more: How P2PKH script validation works.

Pubkey script: OP_DUP OP_HASH160 <PubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
Signature script: <sig> <pubkey>

In signature script, there are two components one is signature and second is public key of spender.

OP_CHECKSIG extracts a non-stack argument from each signature it evaluates, allowing the signer to decide which parts of the transaction to sign. Since the signature protects those parts of the transaction from modification, this lets signers selectively choose to let other people modify their transactions.

For more info : SigHash Types

    /* generate the transaction hash to sign from a transaction input */
    btrx.transactionHash = function(index, sigHashType) {

        var clone = bitjs.clone(this);
        var shType = sigHashType || 1;

        /* black out all other ins, except this one */
        for (var i = 0; i < clone.inputs.length; i++) {
            if(index!=i){
                clone.inputs[i].script = [];
            }
        }


        if((clone.inputs) && clone.inputs[index]){

            /* SIGHASH : For more info on sig hashs see https://en.bitcoin.it/wiki/OP_CHECKSIG
                and https://bitcoin.org/en/developer-guide#signature-hash-type */

            if(shType == 1){
                //SIGHASH_ALL 0x01

            } else if(shType == 2){
                //SIGHASH_NONE 0x02
                clone.outputs = [];
                for (var i = 0; i < clone.inputs.length; i++) {
                    if(index!=i){
                        clone.inputs[i].sequence = 0;
                    }
                }

            } else if(shType == 3){

                //SIGHASH_SINGLE 0x03
                clone.outputs.length = index + 1;

                for(var i = 0; i < index; i++){
                    clone.outputs[i].value = -1;
                    clone.outputs[i].script = [];
                }

                for (var i = 0; i < clone.inputs.length; i++) {
                    if(index!=i){
                        clone.inputs[i].sequence = 0;
                    }
                }

            } else if (shType >= 128){
                //SIGHASH_ANYONECANPAY 0x80
                clone.inputs = [clone.inputs[index]];

                if(shType==129){
                    // SIGHASH_ALL + SIGHASH_ANYONECANPAY

                } else if(shType==130){
                    // SIGHASH_NONE + SIGHASH_ANYONECANPAY
                    clone.outputs = [];

                } else if(shType==131){
                                            // SIGHASH_SINGLE + SIGHASH_ANYONECANPAY
                    clone.outputs.length = index + 1;
                    for(var i = 0; i < index; i++){
                        clone.outputs[i].value = -1;
                        clone.outputs[i].script = [];
                    }
                }
            }

            var buffer = Crypto.util.hexToBytes(clone.serialize());
            buffer = buffer.concat(bitjs.numToBytes(parseInt(shType), 4));
            var hash = Crypto.SHA256(buffer, {asBytes: true});
            var r = Crypto.util.bytesToHex(Crypto.SHA256(hash, {asBytes: true}));
            return r;
        } else {
            return false;
        }
    }

    /* generate a signature from a transaction hash */
    btrx.transactionSig = function(index, wif, sigHashType, txhash){

        function serializeSig(r, s) {
            var rBa = r.toByteArraySigned();
            var sBa = s.toByteArraySigned();

            var sequence = [];
            sequence.push(0x02); // INTEGER
            sequence.push(rBa.length);
            sequence = sequence.concat(rBa);

            sequence.push(0x02); // INTEGER
            sequence.push(sBa.length);
            sequence = sequence.concat(sBa);

            sequence.unshift(sequence.length);
            sequence.unshift(0x30); // SEQUENCE

            return sequence;
        }

        var shType = sigHashType || 1;
        var hash = txhash || Crypto.util.hexToBytes(this.transactionHash(index, shType));

        if(hash){
            var curve = EllipticCurve.getSECCurveByName("secp256k1");
            var key = bitjs.wif2privkey(wif);
            var priv = BigInteger.fromByteArrayUnsigned(Crypto.util.hexToBytes(key['privkey']));
            var n = curve.getN();
            var e = BigInteger.fromByteArrayUnsigned(hash);
            var badrs = 0
            do {
                var k = this.deterministicK(wif, hash, badrs);
                var G = curve.getG();
                var Q = G.multiply(k);
                var r = Q.getX().toBigInteger().mod(n);
                var s = k.modInverse(n).multiply(e.add(priv.multiply(r))).mod(n);
                badrs++
            } while (r.compareTo(BigInteger.ZERO) <= 0 || s.compareTo(BigInteger.ZERO) <= 0);

            // Force lower s values per BIP62
            var halfn = n.shiftRight(1);
            if (s.compareTo(halfn) > 0) {
                s = n.subtract(s);
            };

            var sig = serializeSig(r, s);
            sig.push(parseInt(shType, 10));

            return Crypto.util.bytesToHex(sig);
        } else {
            return false;
        }
    }

    // https://tools.ietf.org/html/rfc6979#section-3.2
    btrx.deterministicK = function(wif, hash, badrs) {
        // if r or s were invalid when this function was used in signing,
        // we do not want to actually compute r, s here for efficiency, so,
        // we can increment badrs. explained at end of RFC 6979 section 3.2

        // wif is b58check encoded wif privkey.
        // hash is byte array of transaction digest.
        // badrs is used only if the k resulted in bad r or s.

        // some necessary things out of the way for clarity.
        badrs = badrs || 0;
        var key = bitjs.wif2privkey(wif);
        var x = Crypto.util.hexToBytes(key['privkey'])
        var curve = EllipticCurve.getSECCurveByName("secp256k1");
        var N = curve.getN();

        // Step: a
        // hash is a byteArray of the message digest. so h1 == hash in our case

        // Step: b
        var v = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];

        // Step: c
        var k = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];

        // Step: d
        k = Crypto.HMAC(Crypto.SHA256, v.concat([0]).concat(x).concat(hash), k, { asBytes: true });

        // Step: e
        v = Crypto.HMAC(Crypto.SHA256, v, k, { asBytes: true });

        // Step: f
        k = Crypto.HMAC(Crypto.SHA256, v.concat([1]).concat(x).concat(hash), k, { asBytes: true });

        // Step: g
        v = Crypto.HMAC(Crypto.SHA256, v, k, { asBytes: true });

        // Step: h1
        var T = [];

        // Step: h2 (since we know tlen = qlen, just copy v to T.)
        v = Crypto.HMAC(Crypto.SHA256, v, k, { asBytes: true });
        T = v;

        // Step: h3
        var KBigInt = BigInteger.fromByteArrayUnsigned(T);

        // loop if KBigInt is not in the range of [1, N-1] or if badrs needs incrementing.
        var i = 0After broadcasting you will a new transaction 
        while (KBigInt.compareTo(N) >= 0 || KBigInt.compareTo(BigInteger.ZERO) <= 0 || i < badrs) {
            k = Crypto.HMAC(Crypto.SHA256, v.concat([0]), k, { asBytes: true });
            v = Crypto.HMAC(Crypto.SHA256, v, k, { asBytes: true });
            v = Crypto.HMAC(Crypto.SHA256, v, k, { asBytes: true });
            T = v;
            KBigInt = BigInteger.fromByteArrayUnsigned(T);
            i++
        };

        return KBigInt;
    };

    /* sign a "standard" input */
    btrx.signinput = function(index, wif, sigHashType){
        var key = bitjs.wif2pubkey(wif);
        var shType = sigHashType || 1;
        var signature = this.transactionSig(index, wif, shType);
        var buf = [];
        var sigBytes = Crypto.util.hexToBytes(signature);
        buf.push(sigBytes.length);
        buf = buf.concat(sigBytes);
        var pubKeyBytes = Crypto.util.hexToBytes(key['pubkey']);
        buf.push(pubKeyBytes.length);
        buf = buf.concat(pubKeyBytes);
        this.inputs[index].script = buf;
        return true;
    }

    /* sign inputs */
    btrx.sign = function(wif, sigHashType) {
        var shType = sigHashType || 1;
        for (var i = 0; i < this.inputs.length; i++) {
            this.signinput(i, wif, shType);
        }	
        return this.serialize();
    }
    
    return btrx;

This completes our transaction object.
There are other utility functions like address decoding,private key to wif format, public key from privkey,number to bytes etc, which are not explained here, but are included in full working code, which is embedded as plunk at the bottom of this post.

Now, we will create a new transaction from previous unspent outputs,
located at transaction hash: 3aefb0e4a14073b23ef873d1fb87f602c68092003b5ff9d4a1a17515c243d876

In this transaction, we have two UTXO(Unspent Outputs), one corresponds to address mnRAfMJpLA8vEXgNDvqr323rJJcp5sa2pf at index 0 and another to mm6kWNfAyaeynQmy966CR1sxeHLd9t891X at index 1.

We will spend UTXO at index 1 corresponding to address mm6kWNfAyaeynQmy966CR1sxeHLd9t891X and will send 0.1 BTC out of 1.6 BTC to address
mnRAfMJpLA8vEXgNDvqr323rJJcp5sa2pf, 1.499 BTC to self, and remaining 0.001 BTC will become transaction fees.

for creating this signed raw transaction, i have made a simple html page on plunker which is embedded here at the bottom, you can also try.

Here is an screeshot taken after creation of transaction.

Screenshot-from-2018-01-08-18-49-52

Decode a signed raw transaction here: https://live.blockcypher.com/btc-testnet/decodetx/
and can broadcast it here: https://live.blockcypher.com/btc-testnet/pushtx.

View broadcasted transaction: 2fc34244bdecc723996b406c0dd3fc773920780fe5ffaf57c0d19a763726d6b3

References:

  1. Bitcoin Transaction Guide @ bitcoin.org.

  1. Javascript Code used in this blog post is simplified version of coinbin github repository. ↩︎