forked from taustin/bcl
-
Notifications
You must be signed in to change notification settings - Fork 1
/
transaction.js
131 lines (123 loc) · 4.54 KB
/
transaction.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
"use strict";
const utils = require('./utils.js');
/**
* A transaction is made up of a collection of inputs and outputs.
* The total value of the outputs must equal or exceed the inputs.
*
* One exception: coinbase transactions have no inputs; Their total
* outputs should match up with the transaction fees from the
* transactions in the block, plus an extra reward for for the block
* itself.
*
* For a transaction, the mining fee is specified as the difference
* between the total value of the inputs and the total value of the
* outputs.
*
*/
module.exports = class Transaction {
/**
* The constructor for a transaction specifies an array of inputs
* and outputs. The inputs are optional, in order to support
* coinbase transactions.
*
* An output is a pair of an amount of coins and the hash of a
* public key (also called the address), in the form:
* {amount, address}
*
* An input is a triple of a transaction ID, the index of an output
* within that transaction ID, and the public key that matches the
* hash of the public key from a previous output. It is in the form:
* {txID, outputIndex, pubKey, sig}
*
* @constructor
* @param {Object} obj - The inputs and outputs of the transaction.
* @param {Array} obj.outputs - An array of the outputs.
* @param {Array} obj.inputs - An array of the inputs.
*/
constructor({outputs, inputs=[]}) {
this.inputs = inputs;
this.outputs = outputs;
// The id is determined at creation and remains constant,
// even if outputs change. (This case should only come up
// with coinbase transactions).
this.id = utils.hash("" + JSON.stringify({inputs, outputs}));
}
/**
* Validates the input and returns the amount of tokens in the output.
* If the input is invalid, either due to an invalid signature or due
* to the wrong transaction ID, an exception is raised.
*
* @param {Object} input - The object representing an input
*/
spendOutput(input) {
let {txID, outputIndex, pubKey, sig} = input;
if (txID !== this.id) {
throw new Error(`Transaction id of input was ${txID}, but this transaction's id is ${this.id}`);
}
let output = this.outputs[outputIndex];
let {amount, address} = output;
if (utils.calcAddress(pubKey) !== address) {
throw new Error(`Public key does not match its hash for tx ${this.id}, output ${outputIndex}.`);
} else if (!utils.verifySignature(pubKey, output, sig)) {
throw new Error(`Invalid signature for ${this.id}, outpout ${outputIndex}.`);
} else {
return amount;
}
}
/**
* Validates that a transaction's inputs and outputs are valid.
* In order to validate a transaction, the map of UTXOs is needed.
*
* A transaction is valid if the sum of the UTXOs matching the inputs
* must be at least as large as the sum out the outputs. Also, the
* signatures of the inputs must be valid and match the address
* specified in the corresponding UTXOs.
*
* Note that coinbase transactions are **not** valid according to this
* method, and should not be tested with it.
*
* @param {Object} utxos - The UTXOs matching the inputs.
* @returns {boolean} True if the transaction is valid, false otherwise.
*/
isValid(utxos) {
let sumIn = 0;
for(let tx of this.inputs){
if(Object.keys(utxos).includes(tx.txID)){
let temp = utxos[tx.txID][tx.outputIndex];
if(temp && utils.calcAddress(tx.pubKey) == temp.address && utils.verifySignature(tx.pubKey, temp, tx.sig)){ //Verifying the public key hash in the previous output matches the transaction's public key, and that the signature on the UTXOis valid.
sumIn += temp.amount;
}
else{
return false;
}
}
else
{
return false;
}
}
return sumIn >= this.totalOutput();
}
/**
* This method is used to give an additional reward to the miner for including a
* transaction. All rewards are added to the first output in this transaction.
*
* This should only be called on coinbase transactions.
*
* Also note the this changes the contents, but not the id. That means that the
* hash of the transaction and the tranasction ID are no longer connected.
*
* @param {number} amount - The number of coins offered as a miner reward.
*/
addFee(amount) {
this.outputs[0].amount += amount;
}
/**
* Calculates the total value of all outputs.
*/
totalOutput() {
return this.outputs.reduce(
(acc, {amount}) => acc + amount,
0);
}
}