forked from taustin/bcl
-
Notifications
You must be signed in to change notification settings - Fork 1
/
wallet.js
135 lines (122 loc) · 3.82 KB
/
wallet.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
132
133
134
135
"use strict";
const keypair = require('keypair');
const utils = require('./utils.js')
/**
* A wallet is a collection of "coins", where a coin is defined as
* a UTXO (unspent transaction output) and its associated
* transaction ID and output index.
*
* In order to spend the coins, we also hold the public/private keys
* associated with each coin.
*
* For simplicity, we use a JBOK ("just a bag of keys") wallet.
*/
module.exports = class Wallet {
/**
* Initializes an array for coins as well as an address->keypair map.
*
* A coin is a triple of the UTXO, a transaction ID, and an output index,
* in the form:
* { output, txID, outputIndex }
*
* An address is the hash of the corresponding public key.
*/
constructor() {
// An array of the UTXOs
this.coins = [];
// An address is the hash of the public key.
// Its value is the public/private key pair.
this.addresses = {};
}
/**
* Return the total balance of all UTXOs.
*
* @returns The total number of coins in the wallet.
*/
get balance() {
return this.coins.reduce((acc, {output}) => acc + output.amount, 0);
}
/**
* Accepts and stores a UTXO and the information needed to create
* the input to spend it.
*
* @param {Object} utxo - The unspent transaction output.
* @param {String} txID - The hex string representing the ID of the transaction
* where the UTXO was created.
* @param {number} outputIndex - The index of the output in the transaction.
*/
addUTXO(utxo, txID, outputIndex) {
if (this.addresses[utxo.address] === undefined) {
throw new Error(`Wallet does not have key for ${utxo.address}`);
}
// We store the coins in a queue, so that we spend the oldest
// (and most likely finalized) first.
this.coins.unshift({
output: utxo,
txID: txID,
outputIndex: outputIndex,
});
}
/**
* Returns inputs to spend enough UTXOs to meet or exceed the specified
* amount of coins.
*
* Calling this method also **deletes** the UTXOs used. This approach
* optimistically assumes that the transaction will be accepted. Just
* in case, the keys are not deleted. From the blockchain and the
* key pair, the wallet can manually recreate the UTXO if it fails to
* be created.
*
* If the amount requested exceeds the available funds, an exception is
* thrown.
*
* @param {number} amount - The amount that is desired to spend.
*
* @returns An object containing an array of inputs that meet or exceed
* the amount required, and the amount of change left over.
*/
spendUTXOs(amount) {
if (amount > this.balance) {
throw new Error(`Insufficient funds. Requested ${amount}, but only ${this.balance} is available.`);
}
let arr = [];
let sum = 0;
while(sum < amount){
let coin = this.coins.pop();
let adr = this.addresses[coin.output.address];
arr.push({
txID: coin.txID,
outputIndex: coin.outputIndex,
pubKey: adr.public,
sig: utils.sign(adr.private, JSON.stringify(coin.output)),
});
sum += coin.output.amount;
}
return{
inputs: arr,
changeAmt: sum - amount,
};
}
/**
* Makes a new keypair and calculates its address from that.
* The address is the hash of the public key.
*
* @returns The address.
*/
makeAddress() {
let kp = keypair();
let addr = utils.calcAddress(kp.public);
this.addresses[addr] = kp;
return addr;
}
/**
* Checks to see if the wallet contains the specified public key.
* This function allows a client to check if a broadcast output
* should be added to the client's wallet.
*
* @param {String} address - The hash of the public key identifying an address.
*/
hasKey(address) {
return !!this.addresses[address];
}
}