Skip to content

Commit 8f8bc28

Browse files
committed
实现 WalletUtils ,修改部分代码逻辑
1 parent e661f6d commit 8f8bc28

File tree

11 files changed

+307
-84
lines changed

11 files changed

+307
-84
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.idea/
22
target/
33
*.iml
4-
blockchain.db/
4+
blockchain.db/
5+
wallet.dat

pom.xml

+5
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@
3636
<artifactId>commons-cli</artifactId>
3737
<version>1.4</version>
3838
</dependency>
39+
<dependency>
40+
<groupId>commons-io</groupId>
41+
<artifactId>commons-io</artifactId>
42+
<version>2.6</version>
43+
</dependency>
3944
<dependency>
4045
<groupId>com.google.guava</groupId>
4146
<artifactId>guava</artifactId>

src/main/java/one/wangwei/blockchain/block/Blockchain.java

+16-16
Original file line numberDiff line numberDiff line change
@@ -143,18 +143,18 @@ public BlockchainIterator getBlockchainIterator() {
143143
/**
144144
* 查找钱包地址对应的所有UTXO
145145
*
146-
* @param address 钱包地址
146+
* @param pubKeyHash 钱包公钥Hash
147147
* @return
148148
*/
149-
public TXOutput[] findUTXO(String address) throws Exception {
150-
Transaction[] unspentTxs = this.findUnspentTransactions(address);
149+
public TXOutput[] findUTXO(byte[] pubKeyHash) throws Exception {
150+
Transaction[] unspentTxs = this.findUnspentTransactions(pubKeyHash);
151151
TXOutput[] utxos = {};
152152
if (unspentTxs == null || unspentTxs.length == 0) {
153153
return utxos;
154154
}
155155
for (Transaction tx : unspentTxs) {
156156
for (TXOutput txOutput : tx.getOutputs()) {
157-
if (txOutput.canBeUnlockedWith(address)) {
157+
if (txOutput.isLockedWithKey(pubKeyHash)) {
158158
utxos = ArrayUtils.add(utxos, txOutput);
159159
}
160160
}
@@ -166,11 +166,11 @@ public TXOutput[] findUTXO(String address) throws Exception {
166166
/**
167167
* 查找钱包地址对应的所有未花费的交易
168168
*
169-
* @param address 钱包地址
169+
* @param pubKeyHash 钱包公钥Hash
170170
* @return
171171
*/
172-
private Transaction[] findUnspentTransactions(String address) throws Exception {
173-
Map<String, int[]> allSpentTXOs = this.getAllSpentTXOs(address);
172+
private Transaction[] findUnspentTransactions(byte[] pubKeyHash) throws Exception {
173+
Map<String, int[]> allSpentTXOs = this.getAllSpentTXOs(pubKeyHash);
174174
Transaction[] unspentTxs = {};
175175

176176
// 再次遍历所有区块中的交易输出
@@ -188,7 +188,7 @@ private Transaction[] findUnspentTransactions(String address) throws Exception {
188188
}
189189

190190
// 保存不存在 allSpentTXOs 中的交易
191-
if (transaction.getOutputs()[outIndex].canBeUnlockedWith(address)) {
191+
if (transaction.getOutputs()[outIndex].isLockedWithKey(pubKeyHash)) {
192192
unspentTxs = ArrayUtils.add(unspentTxs, transaction);
193193
}
194194
}
@@ -201,11 +201,11 @@ private Transaction[] findUnspentTransactions(String address) throws Exception {
201201
/**
202202
* 从交易输入中查询区块链中所有已被花费了的交易输出
203203
*
204-
* @param address 钱包地址
204+
* @param pubKeyHash 钱包公钥Hash
205205
* @return 交易ID以及对应的交易输出下标地址
206206
* @throws Exception
207207
*/
208-
private Map<String, int[]> getAllSpentTXOs(String address) throws Exception {
208+
private Map<String, int[]> getAllSpentTXOs(byte[] pubKeyHash) throws Exception {
209209
// 定义TxId ——> spentOutIndex[],存储交易ID与已被花费的交易输出数组索引值
210210
Map<String, int[]> spentTXOs = new HashMap<>();
211211
for (BlockchainIterator blockchainIterator = this.getBlockchainIterator(); blockchainIterator.hashNext(); ) {
@@ -217,7 +217,7 @@ private Map<String, int[]> getAllSpentTXOs(String address) throws Exception {
217217
continue;
218218
}
219219
for (TXInput txInput : transaction.getInputs()) {
220-
if (txInput.canUnlockOutputWith(address)) {
220+
if (txInput.usesKey(pubKeyHash)) {
221221
String inTxId = Hex.encodeHexString(txInput.getTxId());
222222
int[] spentOutIndexArray = spentTXOs.get(inTxId);
223223
if (spentOutIndexArray == null) {
@@ -237,11 +237,11 @@ private Map<String, int[]> getAllSpentTXOs(String address) throws Exception {
237237
/**
238238
* 寻找能够花费的交易
239239
*
240-
* @param address 钱包地址
241-
* @param amount 花费金额
240+
* @param pubKeyHash 钱包公钥Hash
241+
* @param amount 花费金额
242242
*/
243-
public SpendableOutputResult findSpendableOutputs(String address, int amount) throws Exception {
244-
Transaction[] unspentTXs = this.findUnspentTransactions(address);
243+
public SpendableOutputResult findSpendableOutputs(byte[] pubKeyHash, int amount) throws Exception {
244+
Transaction[] unspentTXs = this.findUnspentTransactions(pubKeyHash);
245245
int accumulated = 0;
246246
Map<String, int[]> unspentOuts = new HashMap<>();
247247
for (Transaction tx : unspentTXs) {
@@ -252,7 +252,7 @@ public SpendableOutputResult findSpendableOutputs(String address, int amount) th
252252

253253
TXOutput txOutput = tx.getOutputs()[outId];
254254

255-
if (txOutput.canBeUnlockedWith(address) && accumulated < amount) {
255+
if (txOutput.isLockedWithKey(pubKeyHash) && accumulated < amount) {
256256
accumulated += txOutput.getValue();
257257

258258
int[] outIds = unspentOuts.get(txId);

src/main/java/one/wangwei/blockchain/cli/CLI.java

+44-1
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,20 @@
22

33
import one.wangwei.blockchain.block.Block;
44
import one.wangwei.blockchain.block.Blockchain;
5+
import one.wangwei.blockchain.crypto.Base58Check;
56
import one.wangwei.blockchain.pow.ProofOfWork;
67
import one.wangwei.blockchain.transaction.TXOutput;
78
import one.wangwei.blockchain.transaction.Transaction;
89
import one.wangwei.blockchain.util.RocksDBUtils;
10+
import one.wangwei.blockchain.wallet.Wallet;
11+
import one.wangwei.blockchain.wallet.WalletUtils;
912
import org.apache.commons.cli.*;
1013
import org.apache.commons.lang3.StringUtils;
1114
import org.apache.commons.lang3.math.NumberUtils;
1215

16+
import java.util.Arrays;
17+
import java.util.Set;
18+
1319
/**
1420
* 程序命令行工具入口
1521
*
@@ -72,6 +78,12 @@ public void parse() {
7278
}
7379
this.send(sendFrom, sendTo, Integer.valueOf(sendAmount));
7480
break;
81+
case "createwallet":
82+
this.createWallet();
83+
break;
84+
case "printaddresses":
85+
this.printAddresses();
86+
break;
7587
case "printchain":
7688
this.printChain();
7789
break;
@@ -109,14 +121,45 @@ private void createBlockchain(String address) throws Exception {
109121
System.out.println("Done ! ");
110122
}
111123

124+
/**
125+
* 创建钱包
126+
*
127+
* @throws Exception
128+
*/
129+
private void createWallet() throws Exception {
130+
Wallet wallet = WalletUtils.getInstance().createWallet();
131+
System.out.println("wallet address : " + wallet.getAddress());
132+
}
133+
134+
/**
135+
* 打印钱包地址
136+
*
137+
* @throws Exception
138+
*/
139+
private void printAddresses() throws Exception {
140+
Set<String> addresses = WalletUtils.getInstance().getAddresses();
141+
if (addresses == null || addresses.isEmpty()) {
142+
System.out.println("There isn't address");
143+
return;
144+
}
145+
for (String address : addresses) {
146+
System.out.println("Wallet address: " + address);
147+
}
148+
}
149+
112150
/**
113151
* 查询钱包余额
114152
*
115153
* @param address 钱包地址
116154
*/
117155
private void getBalance(String address) throws Exception {
118156
Blockchain blockchain = Blockchain.createBlockchain(address);
119-
TXOutput[] txOutputs = blockchain.findUTXO(address);
157+
158+
// 得到公钥Hash值
159+
byte[] versionedPayload = Base58Check.base58ToBytes(address);
160+
byte[] pubKeyHash = Arrays.copyOfRange(versionedPayload, 1, versionedPayload.length);
161+
162+
TXOutput[] txOutputs = blockchain.findUTXO(pubKeyHash);
120163
int balance = 0;
121164
if (txOutputs != null && txOutputs.length > 0) {
122165
for (TXOutput txOutput : txOutputs) {

src/main/java/one/wangwei/blockchain/transaction/TXInput.java

+2-16
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,13 @@ public class TXInput {
2727
*/
2828
private int txOutputIndex;
2929
/**
30-
* 解锁脚本
30+
* 签名
3131
*/
32-
private String scriptSig;
32+
private byte[] signature;
3333
/**
3434
* 公钥
3535
*/
3636
private byte[] pubKey;
37-
/**
38-
* 签名
39-
*/
40-
private byte[] signature;
4137

4238

4339
/**
@@ -51,14 +47,4 @@ public boolean usesKey(byte[] pubKeyHash) {
5147
return Arrays.equals(lockingHash, pubKeyHash);
5248
}
5349

54-
/**
55-
* 判断解锁数据是否能够解锁交易输出
56-
*
57-
* @param unlockingData
58-
* @return
59-
*/
60-
public boolean canUnlockOutputWith(String unlockingData) {
61-
return this.getScriptSig().endsWith(unlockingData);
62-
}
63-
6450
}

src/main/java/one/wangwei/blockchain/transaction/TXOutput.java

+24-12
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import lombok.NoArgsConstructor;
66
import one.wangwei.blockchain.crypto.Base58Check;
77

8+
import java.util.Arrays;
9+
810
/**
911
* 交易输出
1012
*
@@ -20,34 +22,44 @@ public class TXOutput {
2022
* 数值
2123
*/
2224
private int value;
23-
/**
24-
* 锁定脚本
25-
*/
26-
private String scriptPubKey;
2725
/**
2826
* 公钥Hash
2927
*/
30-
private byte[] pubHashKey;
28+
private byte[] pubKeyHash;
3129

30+
/**
31+
* 创建交易输出
32+
*
33+
* @param value
34+
* @param address
35+
* @return
36+
*/
37+
public static TXOutput newTXOutput(int value, String address) {
38+
TXOutput txOutput = new TXOutput(value, null);
39+
txOutput.lock(address);
40+
return txOutput;
41+
}
3242

3343
/**
3444
* 使用钱包地址锁住交易输出
3545
*
3646
* @param address
3747
*/
3848
public void lock(String address) {
39-
Base58Check.base58ToBytes(address);
40-
41-
49+
// 反向转化为 byte 数组
50+
byte[] versionedPayload = Base58Check.base58ToBytes(address);
51+
// 去除版本号
52+
this.pubKeyHash = Arrays.copyOfRange(versionedPayload, 1, versionedPayload.length);
4253
}
4354

4455
/**
45-
* 判断解锁数据是否能够解锁交易输出
56+
* 检查交易输出是否能够指定的公钥使用
4657
*
47-
* @param unlockingData
58+
* @param pubKeyHash
4859
* @return
4960
*/
50-
public boolean canBeUnlockedWith(String unlockingData) {
51-
return this.getScriptPubKey().endsWith(unlockingData);
61+
public boolean isLockedWithKey(byte[] pubKeyHash) {
62+
return Arrays.equals(this.getPubKeyHash(), pubKeyHash);
5263
}
64+
5365
}

src/main/java/one/wangwei/blockchain/transaction/Transaction.java

+13-6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import lombok.NoArgsConstructor;
66
import one.wangwei.blockchain.block.Blockchain;
77
import one.wangwei.blockchain.util.SerializeUtils;
8+
import one.wangwei.blockchain.wallet.Wallet;
9+
import one.wangwei.blockchain.wallet.WalletUtils;
810
import org.apache.commons.codec.binary.Hex;
911
import org.apache.commons.codec.digest.DigestUtils;
1012
import org.apache.commons.lang3.ArrayUtils;
@@ -59,9 +61,9 @@ public static Transaction newCoinbaseTX(String to, String data) {
5961
data = String.format("Reward to '%s'", to);
6062
}
6163
// 创建交易输入
62-
TXInput txInput = new TXInput(new byte[]{}, -1, data);
64+
TXInput txInput = new TXInput(new byte[]{}, -1, null, data.getBytes());
6365
// 创建交易输出
64-
TXOutput txOutput = new TXOutput(SUBSIDY, to);
66+
TXOutput txOutput = TXOutput.newTXOutput(SUBSIDY, to);
6567
// 创建交易
6668
Transaction tx = new Transaction(null, new TXInput[]{txInput}, new TXOutput[]{txOutput});
6769
// 设置交易ID
@@ -91,7 +93,11 @@ public boolean isCoinbase() {
9193
* @return
9294
*/
9395
public static Transaction newUTXOTransaction(String from, String to, int amount, Blockchain blockchain) throws Exception {
94-
SpendableOutputResult result = blockchain.findSpendableOutputs(from, amount);
96+
// 获取钱包
97+
Wallet senderWallet = WalletUtils.getInstance().getWallet(from);
98+
byte[] senderPubKey = senderWallet.getPublicKey().getEncoded();
99+
100+
SpendableOutputResult result = blockchain.findSpendableOutputs(DigestUtils.sha256(senderPubKey), amount);
95101
int accumulated = result.getAccumulated();
96102
Map<String, int[]> unspentOuts = result.getUnspentOuts();
97103

@@ -100,21 +106,22 @@ public static Transaction newUTXOTransaction(String from, String to, int amount,
100106
}
101107
Iterator<Map.Entry<String, int[]>> iterator = unspentOuts.entrySet().iterator();
102108

109+
103110
TXInput[] txInputs = {};
104111
while (iterator.hasNext()) {
105112
Map.Entry<String, int[]> entry = iterator.next();
106113
String txIdStr = entry.getKey();
107114
int[] outIdxs = entry.getValue();
108115
byte[] txId = Hex.decodeHex(txIdStr);
109116
for (int outIndex : outIdxs) {
110-
txInputs = ArrayUtils.add(txInputs, new TXInput(txId, outIndex, from));
117+
txInputs = ArrayUtils.add(txInputs, new TXInput(txId, outIndex, null, senderPubKey));
111118
}
112119
}
113120

114121
TXOutput[] txOutput = {};
115-
txOutput = ArrayUtils.add(txOutput, new TXOutput(amount, to));
122+
txOutput = ArrayUtils.add(txOutput, TXOutput.newTXOutput(amount, to));
116123
if (accumulated > amount) {
117-
txOutput = ArrayUtils.add(txOutput, new TXOutput((accumulated - amount), from));
124+
txOutput = ArrayUtils.add(txOutput, TXOutput.newTXOutput((accumulated - amount), from));
118125
}
119126

120127
Transaction newTx = new Transaction(null, txInputs, txOutput);

src/main/java/one/wangwei/blockchain/wallet/Wallet.java

+6-4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.bouncycastle.util.Arrays;
1212

1313
import java.io.ByteArrayOutputStream;
14+
import java.io.Serializable;
1415
import java.security.*;
1516

1617
/**
@@ -21,13 +22,12 @@
2122
*/
2223
@Data
2324
@AllArgsConstructor
24-
public class Wallet {
25+
public class Wallet implements Serializable {
2526

2627
/**
2728
* 校验码长度
2829
*/
2930
private static final int ADDRESS_CHECKSUM_LEN = 4;
30-
3131
/**
3232
* 私钥
3333
*/
@@ -65,13 +65,15 @@ private KeyPair newKeyPair() throws Exception {
6565
// 注册 BC Provider
6666
Security.addProvider(new BouncyCastleProvider());
6767
// 创建椭圆曲线算法的密钥对生成器,算法为 ECDSA
68-
KeyPairGenerator g = KeyPairGenerator.getInstance("ECDSA", "BC");
68+
KeyPairGenerator g = KeyPairGenerator.getInstance("ECDSA", BouncyCastleProvider.PROVIDER_NAME);
6969
// 椭圆曲线(EC)域参数设定
70-
ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("B-571");
70+
// bitcoin 为什么会选择 secp256k1,详见:https://bitcointalk.org/index.php?topic=151120.0
71+
ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1");
7172
g.initialize(ecSpec, new SecureRandom());
7273
return g.generateKeyPair();
7374
}
7475

76+
7577
/**
7678
* 获取钱包地址
7779
*

0 commit comments

Comments
 (0)