function signOne(hash, addrStr, keys)
{
  var keyObj = keys[addrStr];
  var rawPrivKey = new Buffer(keyObj.priv, 'hex');
  var key = new KeyModule.Key();
  key.private = rawPrivKey;
  var signature = key.signSync(hash);

  return signature;
}

function signTxIn(nIn, tx, txInputs, network, keys, scripts)
{
  // locate TX input needing a signature
  var txin = tx.ins[nIn];
  var scriptSig = txin.getScript();

  // locate TX output, within txInputs
  var txoutHash = txin.getOutpointHash();
  if (!(txoutHash in txInputs))
    throw new Error("signTxIn missing input hash");
  var txFrom = txInputs[txoutHash];
  var txoutIndex = txin.getOutpointIndex();
  if (txFrom.outs.length >= txoutIndex)
    throw new Error("signTxIn missing input index");
  var txout = txFrom.outs[txoutIndex];
  var scriptPubKey = txout.getScript();

  // detect type of transaction, and extract useful elements
  var txType = scriptPubKey.classify();
  if (txType == TX_UNKNOWN)
    throw new Error("unknown TX type");
  var scriptData = scriptPubKey.capture();

  // if P2SH, lookup the script
  var subscriptRaw = undefined;
  var subscript = undefined;
  var subType = undefined;
  var subData = undefined;
  if (txType == TX_SCRIPTHASH) {
    var addr = new Address(network.P2SHVersion, scriptData[0]);
    var addrStr = addr.toString();
    if (!(addrStr in scripts))
      throw new Error("unknown script hash address");

    subscriptRaw = new Buffer(scripts[addrStr], 'hex');
    subscript = new Script(subscriptRaw);
    subType = subscript.classify();
    if (subType == TX_UNKNOWN)
      throw new Error("unknown subscript TX type");
    subData = subscript.capture();
  }

  var hash = tx.hashForSignature(scriptPubKey, i, 0);

  switch (txType) {
  case TX_PUBKEY:
    // already signed
    if (scriptSig.chunks.length > 0)
      return;

    var pubkeyhash = util.sha256ripe160(scriptData[0]);
    var addr = new Address(network.addressVersion, pubkeyhash);
    var addrStr = addr.toString();
    if (!(addrStr in keys))
      throw new Error("unknown pubkey");

    var signature = signOne(hash, addrStr, keys);
    scriptSig.writeBytes(signature);
    break;

  case TX_PUBKEYHASH:
    // already signed
    if (scriptSig.chunks.length > 0)
      return;

    var addr = new Address(network.addressVersion, scriptData[0]);
    var addrStr = addr.toString();
    if (!(addrStr in keys))
      throw new Error("unknown pubkey hash address");

    var signature = signOne(hash, addrStr, keys);
    scriptSig.writeBytes(signature);
    scriptSig.writeBytes(key.public);
    break;

  case TX_SCRIPTHASH:
    // already signed
    if (scriptSig.chunks.length > 0)
      return;

    var addr = new Address(network.addressVersion, subData[0]);
    var addrStr = addr.toString();
    if (!(addrStr in keys))
      throw new Error("unknown script(pubkey hash) address");

    var signature = signOne(hash, addrStr, keys);
    scriptSig.writeBytes(signature);
    scriptSig.writeBytes(key.public);
    break;

  case TX_MULTISIG:
    while (scriptSig.chunks.length < scriptData.length) {
      scriptSig.writeBytes(util.EMPTY_BUFFER);
    }
    for (var i = 0; i < scriptData.length; i++) {
      // skip already signed
      if (scriptSig.chunks[i].length > 0)
        continue;

      var pubkeyhash = util.sha256ripe160(scriptSig.chunks[i]);
      var addr = new Address(network.addressVersion, pubkeyhash);
      var addrStr = addr.toString();
      if (!(addrStr in keys))
        continue;

      var signature = signOne(hash, addrStr, keys);
      scriptSig.chunks[i] = signature;
    }
    break;
  }

  if (txtype == TX_SCRIPTHASH)
    scriptSig.writeBytes(subscriptRaw);
}

exports.Transaction = function Transaction(tx, txInputs, network, keys, scripts)
{
  for (var i = 0; i < tx.ins.length; i++)
    signTxIn(i, tx, txInputs, network, keys, scripts);
};