一、签名和校验
比特币通过对交易进行签名和校验,保证交易的安全性。
下图展示了数据签名和交易的过程。
签名过程:
1)使用哈希算法对数据文件进行加密处理,得到一个哈希值;
2)使用私钥对哈希值进行加密,生成数字签名;
3)最后把数据文件和数字签名一起发送给对方;
校验过程:
1)对方接收到数据文件和数字签名之后,首先使用相同的哈希算法对数据文件进行加密处理,得到一个哈希值;
2)使用公钥对数字签名进行解密,得到解密后的哈希;
3)将1和2得到的哈希值进行比较,如果相等代表验证通过;
二、添加交易功能
(1)定义和初始化钱包
// 钱包
public class Wallet {
// 钱包私钥
public PrivateKey privateKey;
// 钱包公钥
public PublicKey publicKey;
// 用户名字,创建钱包时候传入
private String name;
public Wallet(String name) {
this.name = name;
// 判断私钥和公钥是否存在,如果不存在,则创建它们
File priKeyFile = new File(name + ".pri");
File pubKeyFile = new File(name + ".pub");
if (!priKeyFile.exists() || priKeyFile.length() == 0
|| !pubKeyFile.exists() || pubKeyFile.length() == 0) {
RSAUtils.generateKeysJS("RSA", name + ".pri", name + ".pub");
}
}
}
(2)定义交易
// 交易
public class Transaction {
// 付款方公钥
public String senderPublicKey;
// 收款方公钥
public String receiverPublicKey;
// 金额
public String content;
// 签名
public String signaturedData;
public Transaction() {
}
public Transaction(String senderPublicKey, String receiverPublicKey, String content, String signaturedData) {
this.senderPublicKey = senderPublicKey;
this.receiverPublicKey = receiverPublicKey;
this.content = content;
this.signaturedData = signaturedData;
}
...
}
(3)在Wallet中提供创建交易的方法。
// 转账
// 参数一:接收方的公钥
// 参数二:交易数据
public Transaction sendMoney(String receiverPublicKey, String content) {
// 生成签名
String signature = RSAUtils.getSignature("SHA256withRSA", privateKey, content);
// 把publicKey对象转换成字符串
String senderPublicKey = Base64.encode(publicKey.getEncoded());
// 创建并返回交易
return new Transaction(senderPublicKey, receiverPublicKey, content, signature);
}
(4)在Transaction中定义校验交易的方法。
// 校验交易的方法
public boolean verify() {
PublicKey publicKey = RSAUtils.getPublicKeyFromString("RSA", senderPublicKey);
return RSAUtils.verifyDataJS("SHA256withRSA", publicKey, content, signaturedData);
}
(5)在客户端生成私钥。
第一步:添加发送方的私钥和公钥(数字签名和认证),以及接收方公钥(对方钱包地址)的输入框;
<label>发送方私钥oc
</label>
<textarea class="form-control" id="senderPrivateKey">-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCX/Nt0pTfSdSH6GRW/tJOeEqTo
2ExsUPakhTMhPHJOQjLPwaHHqkBnmHBGYr8fsu8wqmTb0Uy3yccJ2InqB3If7yYFwiihI5Gjtwjr
13oMUJqma8tAULEGrIIj081LRIBGCSbTohb0qqVPIQkP8rI11Jjx8UYAhon2xl1DrcVGzJnCMJ0D
H0ovq4gJboIwKxij4SJKEDiwKDqvFc4VKLI9KUJq5y3FjZMPAsKRC0e2psJCI7i+HhF4kq8usnkm
6z993emwV24JP1dpe8CeYrAgrLApZ5yEmEZv557YstNVwOhWCYFl9GkGTh1UU9eC3DTcKW+hzZtW
fhKhtNRJyfzHAgMBAAECggEAcPKhJ/MsI8OWR2Ti679HQF58nOk5Cfm1ARhXoehozc2WMxyjnFzi
VdpL/ZKek/EGnfTS1SSaTa6usptwCjIUVrUDXZ4nUXC8Z5y7DYDpG0O/WdObjSUqGVqTsApTcw7q
AKIGb5nyU0qJZN+Y+3gRhb2DF7GEoxlYZ8KMRqJZi72VMEXHarCDILeiHlqzg66zNpE8L92pxKUE
FzsbplK4XXeMdrgTZ2vdeISFyXn5Vd63UCv+6/N9ECSSpowEN1Q+3+v8Cb0+i5v22/qq5OYjFpOW
KdSOI8umDZmNuNewN/vIhDR7GovjUhpyRDcUkL92AU4CK6ZUDGpydMJT+XC4OQKBgQDPTtb8f951
fWC7YRJUrLfjACF57IEqCgJ4Dm0KLh4ZXK0uFk4ogtAf90ek9Rxu8fQ4411NOz7C71VUbhwOuLDr
SaFdz/oNXecNBUWeYDQ82Xlm9Y6TGKe5bbzTZnPIUry98X/VWOkXMmQ7MIk8KliV21+0JJS2EeCe
I0kOdLjDqwKBgQC7r67CrojOl7ZK66XFyw0IKmHoHoPTiiyht12Znj/gjoczxGx6Q7H9xjMghWkp
ujtTrrTufT2XAId4WaTJsIjGC5GhkiINjoLZSnaNDeAcF7H1yDWyJgwNhmB/ROh4ehwAIblt/TT2
DU28fYgBOFBInGb1M04Jx6ydc0H0ZO4PVQKBgQCKVk0GP/neIyVqxPMrh/pJw6uTJexndjiBjvba
hT3WpM5347CSPgDOY//uJxarPlA/qhF32SIfiQBDEBsvA7YjvIWvCXsOcmwddzgm4IHbXTAzMYfL
xvcduQn5c/OtaPDEO6SXZPZeYWUbTl81w/hLQBHUL1kBSlq+jENTHzawcwKBgFI8zGppZ3B+cVWq
o1xjeDZXu8aleEW8iYniepTRDlQqn3tDWfTIrLjxm31od4fdHhmwt364ScBcbv+A5/+n5oZAk3Mk
QN+HzjW/tupfZg9pIoT7UOvaV/WlJ6scWnBPsO0t2b4j8IzPj3xD1NAUCLmILmTKMit+3levJPsd
LmJxAoGALeQADQ/HnicAQeWAJY4OPya5nHhthBflrSC9T100xHmh01pEWM/wsuHjSAoN1pFNfLs9
vko+4B2omIcvzor4HsUJs5ZJhIP7A4vQDvd4k+3DBxMz8jvcm2x6aSpt8Mh4ZwGUo+Y/vCeCFLQv
3BUGFh6UHw69Y0vmiGApQcpUY7g=
-----END PRIVATE KEY-----</textarea>
<label>发送方公钥
</label>
<textarea class="form-control" id="senderPublicKey">MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl/zbdKU30nUh+hkVv7STnhKk6NhMbFD2
pIUzITxyTkIyz8Ghx6pAZ5hwRmK/H7LvMKpk29FMt8nHCdiJ6gdyH+8mBcIooSORo7cI69d6DFCa
pmvLQFCxBqyCI9PNS0SARgkm06IW9KqlTyEJD/KyNdSY8fFGAIaJ9sZdQ63FRsyZwjCdAx9KL6uI
CW6CMCsYo+EiShA4sCg6rxXOFSiyPSlCauctxY2TDwLCkQtHtqbCQiO4vh4ReJKvLrJ5Jus/fd3p
sFduCT9XaXvAnmKwIKywKWechJhGb+ee2LLTVcDoVgmBZfRpBk4dVFPXgtw03Clvoc2bVn4SobTU
Scn8xwIDAQAB</textarea>
<label>接收方公钥
</label>
<textarea class="form-control" id="receiverPublicKey">MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1dpjVhAjVwz/yhi6lPoasRkmbk0zKU+T
iP+2I35j3PUKXHawXRlN7/fqaiqM14WvYbgj2GzSHHT4b6WMIuH9Jbm7Vrqu0Daxszewx4RV9Ixg
haurIx3e+7zAigavBRDidjlgSgmq9H7wwYL/hnVzPGf+oH09ymFkzXdtbrr0yZjwJ+Q3fT33Co5G
ezbhNfL5ZVhOKoq+gdyTU9rhdoLXog1FVraz384rkPovVGi/TzvoGNNnie4qM1J5WzBKpJpmVwCo
t7xLFC0fctTdR9iLpD6vNrlsetxHwceHSCLQSS/w3FR258g+mWZI1Kj6cqR6PL+qBDmpv7aRMm0N
7e/3OwIDAQAB</textarea>
注意:赋值内容到textarea输入框时候,<textarea></textarea>标签之间不要有空格和换行。因为空格和换行都要导致哈希运算的结果不正确。
第二步:修改addBlock方法,在该方法中生成私钥,并发送给后台处理。
// 创建普通区块
function addBlock() {
// 发送方私钥
var senderPrivateKey = $("#senderPrivateKey").val();
// 发送方公钥
var senderPublicKey = $("#senderPublicKey").val();
// 接收方公钥
var receiverPublicKey = $("#receiverPublicKey").val();
// 获取用户输入的内容
var content = $("#content").val();
//-------------------- 生成签名信息 -----------------
// 获取私钥
var prvKey = KEYUTIL.getKey(senderPrivateKey);
// 指定算法
var sig = new KJUR.crypto.Signature({"alg": "SHA256withRSA"});
// 初始化私钥
sig.init(prvKey);
// 传入原文
sig.updateString(content)
// 生成签名
var sigValueHex = sig.sign()
console.log(sigValueHex);
//----------------------------------------------------
// 显示进度条
loading.baosight.showPageLoadingMsg(false);
// 发起请求
$.post("addBlock", {
senderPublicKey: senderPublicKey,
receiverPublicKey: receiverPublicKey,
content: content,
signaturedData: sigValueHex
}, function(data) {
// 展示操作结果
$("#result").html(data)
// 展示最新数据
showList();
// 清空输入框
$("#content").val("");
// 隐藏进度条
loading.baosight.hidePageLoadingMsg();
});
}
(6)修改BlockController的addBlock方法,把方法参数改为Transaction对象,并且添加签名校验。
@RequestMapping(value = "/addBlock", method = RequestMethod.POST)
//public String addBlock(String content) {
public String addBlock(Transaction tx) {
try {
//noteBook.addBlock(content);
//System.out.println(tx);
if (tx.verify()) {
// 把Transaction对象转换成字符串
ObjectMapper objectMapper = new ObjectMapper();
String txInfo = objectMapper.writeValueAsString(tx);
// 执行添加操作
noteBook.addBlock(txInfo);
return "添加区块成功!";
} else {
throw new RuntimeException("交易数据校验失败!");
}
} catch (Exception e) {
return "添加失败:" + e.getMessage();
}
}