-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTransaction.java
203 lines (170 loc) · 8.25 KB
/
Transaction.java
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
import java.security.*;
import java.util.*;
import java.util.stream.Collectors;
class Transaction {
public Peer peer; //Required for this simulation to work properly, stores the transaction initiator
public String transactionId; //Contains a hash of transaction
public PublicKey sender; //Sender's address/public key.
public PublicKey recipient; //Recipient's address/public key.
public long timeStamp; //Time of first creation
public int value; //The amount to be transferred
public byte[] signature; //Signature to prevent others from spending funds in our wallet
// Transaction inputs (The UTXOs coming to the sender and will be spent in this Tx)
public List<TransactionInput> inputs;
// UTXOs that will be generated by this Tx. The money that will be paid and possibly the change.
public List<TransactionOutput> outputs;
//There must be at least three type of constructors for different
//situations where transaction objects are created:
// 1. When a peer wants to pay someone else so requests a transaction
// 2. When the miner is preparing the coinbase transaction to itself
// 3. When a peer gets a tx request from others
// Constructor for creating a coinbase transaction (mining reward tx) (2)
public Transaction(PublicKey to, int value, Peer p) throws NoSuchAlgorithmException {
this.peer = p;
this.sender = null; // There is no sender for a mining reward transaction
this.recipient = to;
this.value = value;
this.timeStamp = System.currentTimeMillis();
this.transactionId = calculateHash(); // Setting id before finding out outputs a little misleading, will be fixed later.
inputs = null;//new ArrayList<TransactionInput>();
outputs = new ArrayList<TransactionOutput>();
this.peer.wallet.arrangeFunds(to, value, outputs, this.transactionId);
}
// Constructor to be used when peer is initially creating the transaction (1)
public Transaction(PublicKey from, PublicKey to, int value, Peer p) throws NoSuchAlgorithmException {
this.peer = p;
this.sender = from;
this.recipient = to;
this.value = value;
this.timeStamp = System.currentTimeMillis();
this.transactionId = calculateHash();
inputs = new ArrayList<TransactionInput>();
outputs = new ArrayList<TransactionOutput>();
this.peer.wallet.arrangeFunds(to, value, inputs, outputs, this.transactionId);
}
// Constructor to be used when it is an incoming transaction to be copied to our list (3)
public Transaction(PublicKey from, PublicKey to, int value, long timeStamp,
List<TransactionInput> in, List<TransactionOutput> out, String transactionId, Peer p) {
this.peer = p;
this.sender = from;
this.recipient = to;
this.value = value;
this.timeStamp = timeStamp;
this.transactionId = transactionId;
this.inputs = in;
this.outputs = out;
}
// Handles the calculation of the transaction hash
public String calculateHash() throws NoSuchAlgorithmException {
String dataToHash = "" + this.sender + this.recipient + this.value + this.timeStamp;// There is a subtle caveat here!
return StringUtil.hash(dataToHash);
}
// Prepare input for signing and call signature function
public void generateSignature(PrivateKey privateKey) throws InvalidKeyException, NoSuchAlgorithmException, SignatureException {
// Prepare data
String data = prepareData();
//Sign
signature = peer.wallet.sign(data);
//Now, the signature for this transaction is ready.
}
// Prepare the input for signature verification
public boolean verifySignature() throws InvalidKeyException, NoSuchAlgorithmException, SignatureException {
//Prepare data
String data = prepareData();
//Verify
if (this.sender == null) return true; //coinbase transaction => no signature at all
return peer.wallet.verify(sender, data, signature);
//return verifyECDSASig(Wallet.getStringFromPublicKey(sender), data, signature);
}
private String prepareData() {
String data = Wallet.getStringFromPublicKey(sender)
+ Wallet.getStringFromPublicKey(recipient)
+ value
+ timeStamp;
for (TransactionInput input : this.inputs) {
data += input.transactionOutputId;
}
for (TransactionOutput output : this.outputs) {
data += output.id;
}
return data;
}
public boolean processTransaction() throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
// Gather transaction inputs (Make sure they are unspent):
for (TransactionInput i : inputs) {
i.UTXO = peer.blockchain.UTXOs.get(i.transactionOutputId);
System.out.println("blockchain : " + peer.blockchain);
}
// Generate transaction outputs:
int creatorOver = getInputsValue() + value; // because we should add the score and value
outputs.add(new TransactionOutput(this.recipient, value, transactionId)); //send value to recipient
outputs.add(new TransactionOutput(this.sender, creatorOver, transactionId)); //send the left over 'change' back to sender
// Add outputs to Unspent list
for (TransactionOutput o : outputs) {
peer.blockchain.UTXOs.put(o.id, o);
}
// Remove transaction inputs from UTXO lists as spent:
for (TransactionInput i : inputs) {
if (i.UTXO == null) continue; //if Transaction can't be found skip it
peer.blockchain.UTXOs.remove(i.UTXO.id);
}
return true;
}
public int getInputsValue() {
int total = 0;
for (TransactionInput i : inputs) {
if (i.UTXO == null) continue; // if Transaction can't be found skip it
total += i.UTXO.value;
}
return total;
}
public int getOutputsValue() {
int total = 0;
for (TransactionOutput o : outputs) {
total += o.value;
}
return total;
}
// Converts the transaction into a delimited string.
public String toString() {
String inputString = inputs.stream()
.filter(i -> i.UTXO != null)
.map(i -> i.transactionOutputId + ":" + i.UTXO.value)
.collect(Collectors.joining(","));
String outputString = outputs.stream()
.map(o -> o.id + ":" + o.value)
.collect(Collectors.joining(","));
String senderString = sender == null ? "COINBASE" : Base64.getEncoder().encodeToString(sender.getEncoded());
String recipientString = Base64.getEncoder().encodeToString(recipient.getEncoded());
return transactionId + ";" + senderString + ";" + recipientString + ";" + value + ";" + timeStamp + ";" + inputString + ";" + outputString;
}
// Reconstructs the transaction from a delimited string.
public static Transaction fromString(String s, Peer peer) throws NumberFormatException, Exception {
String[] parts = s.split(";");
PublicKey senderKey = parts[1].equals("COINBASE") ? null : Wallet.getPublicKeyFromString(parts[1]);
PublicKey recipientKey = Wallet.getPublicKeyFromString(parts[2]);
Transaction t = new Transaction(senderKey,
recipientKey,
Integer.parseInt(parts[3]),
Long.parseLong(parts[4]),
new ArrayList<>(),
new ArrayList<>(),
parts[0],
peer);
int inIndex = 5;
int outIndex = 6;
if (parts.length > inIndex && !parts[inIndex].isEmpty()) {
for (String input : parts[inIndex].split(",")) {
String[] inputParts = input.split(":");
t.inputs.add(new TransactionInput(inputParts[0]));
}
}
if (parts.length > outIndex && !parts[outIndex].isEmpty()) {
for (String output : parts[outIndex].split(",")) {
String[] outputParts = output.split(":");
t.outputs.add(new TransactionOutput(t.recipient, Integer.parseInt(outputParts[1]), t.transactionId));
}
}
return t;
}
}