mini block chain
This commit is contained in:
5
blockchain/miniblockchain/private.pem
Normal file
5
blockchain/miniblockchain/private.pem
Normal file
@@ -0,0 +1,5 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MGwCAQAwEAYHKoZIzj0CAQYFK4EEAAEEVTBTAgEBBBUAFlsrE0ZTspNiuuw/2+Ma
|
||||
tEHxhvKgBwYFK4EEAAGhLgMsAAQA/SXr0Ja+KrisdxU9Vw5UWEGyy0AHIrEwzQDR
|
||||
Oy6tS3j8y9EhROOt85o=
|
||||
-----END EC PRIVATE KEY-----
|
||||
5
blockchain/miniblockchain/private1.pem
Normal file
5
blockchain/miniblockchain/private1.pem
Normal file
@@ -0,0 +1,5 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MGwCAQAwEAYHKoZIzj0CAQYFK4EEAAEEVTBTAgEBBBUBQ8KtHoB8i9szZLYy/5O7
|
||||
tqCUN2egBwYFK4EEAAGhLgMsAAQFsrxkbMiCua/AdIkE0Xmj8jwANLAFJWw+SKuH
|
||||
kZRZNwJDqDOMl03bW58=
|
||||
-----END EC PRIVATE KEY-----
|
||||
5
blockchain/miniblockchain/private2.pem
Normal file
5
blockchain/miniblockchain/private2.pem
Normal file
@@ -0,0 +1,5 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MGwCAQAwEAYHKoZIzj0CAQYFK4EEAAEEVTBTAgEBBBUBISXkhMMqVH4ljZy3FLsy
|
||||
7IKyRP+gBwYFK4EEAAGhLgMsAAQDNOCGxozj0Fl8ys5bW5kPps3zanICJbdYdWbo
|
||||
rW6p1PH0zpjBF9wDqUg=
|
||||
-----END EC PRIVATE KEY-----
|
||||
5
blockchain/miniblockchain/private3.pem
Normal file
5
blockchain/miniblockchain/private3.pem
Normal file
@@ -0,0 +1,5 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MGwCAQAwEAYHKoZIzj0CAQYFK4EEAAEEVTBTAgEBBBUCDZu6TlUXdrq6nHsaiMlh
|
||||
jT55XBmgBwYFK4EEAAGhLgMsAAQGmyw1g3+0Q6fjf7tacNliQOnzf4UFZZ7SFEVI
|
||||
ZSqMAFWsWUwmS0maRss=
|
||||
-----END EC PRIVATE KEY-----
|
||||
4
blockchain/miniblockchain/public.pem
Normal file
4
blockchain/miniblockchain/public.pem
Normal file
@@ -0,0 +1,4 @@
|
||||
-----BEGIN EC PUBLIC KEY-----
|
||||
MEAwEAYHKoZIzj0CAQYFK4EEAAEDLAAEAP0l69CWviq4rHcVPVcOVFhBsstAByKx
|
||||
MM0A0TsurUt4/MvRIUTjrfOa
|
||||
-----END EC PUBLIC KEY-----
|
||||
4
blockchain/miniblockchain/public1.pem
Normal file
4
blockchain/miniblockchain/public1.pem
Normal file
@@ -0,0 +1,4 @@
|
||||
-----BEGIN EC PUBLIC KEY-----
|
||||
MEAwEAYHKoZIzj0CAQYFK4EEAAEDLAAEBbK8ZGzIgrmvwHSJBNF5o/I8ADSwBSVs
|
||||
Pkirh5GUWTcCQ6gzjJdN21uf
|
||||
-----END EC PUBLIC KEY-----
|
||||
4
blockchain/miniblockchain/public2.pem
Normal file
4
blockchain/miniblockchain/public2.pem
Normal file
@@ -0,0 +1,4 @@
|
||||
-----BEGIN EC PUBLIC KEY-----
|
||||
MEAwEAYHKoZIzj0CAQYFK4EEAAEDLAAEAzTghsaM49BZfMrOW1uZD6bN82pyAiW3
|
||||
WHVm6K1uqdTx9M6YwRfcA6lI
|
||||
-----END EC PUBLIC KEY-----
|
||||
4
blockchain/miniblockchain/public3.pem
Normal file
4
blockchain/miniblockchain/public3.pem
Normal file
@@ -0,0 +1,4 @@
|
||||
-----BEGIN EC PUBLIC KEY-----
|
||||
MEAwEAYHKoZIzj0CAQYFK4EEAAEDLAAEBpssNYN/tEOn43+7WnDZYkDp83+FBWWe
|
||||
0hRFSGUqjABVrFlMJktJmkbL
|
||||
-----END EC PUBLIC KEY-----
|
||||
67
blockchain/miniblockchain/src/BlockChainStarter.java
Normal file
67
blockchain/miniblockchain/src/BlockChainStarter.java
Normal file
@@ -0,0 +1,67 @@
|
||||
import core.Block;
|
||||
import core.Transaction;
|
||||
import core.Wallet;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import util.EC;
|
||||
import util.Utils;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Security;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class BlockChainStarter {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
EC ec = new EC();
|
||||
|
||||
ec.generate("private1.pem", "public1.pem");
|
||||
ec.generate("private2.pem", "public2.pem");
|
||||
ec.generate("private3.pem", "public3.pem");
|
||||
|
||||
Wallet wallet1 = new Wallet();
|
||||
wallet1.setFromFile("private1.pem", "public1.pem");
|
||||
Wallet wallet2 = new Wallet();
|
||||
wallet2.setFromFile("private2.pem", "public2.pem");
|
||||
Wallet wallet3 = new Wallet();
|
||||
wallet3.setFromFile("private3.pem", "public3.pem");
|
||||
|
||||
Block block1 = new Block(1, null, 0, new ArrayList());
|
||||
block1.mine();
|
||||
block1.showInformation();
|
||||
|
||||
Block block2 = new Block(2, block1.getBlockHash(), 0, new ArrayList());
|
||||
|
||||
// 지갑1에서 지갑2로 코인을 전송했다는 의미를 가진 트랜잭션을 생성
|
||||
Transaction transaction1 = new Transaction(wallet1, wallet2.getPublicKey(), 1.5, Utils.getDate());
|
||||
block2.addTransaction(transaction1);
|
||||
|
||||
Thread.sleep(1000);
|
||||
|
||||
// 지갑2에서 지갑3으로 코인을 전송했다는 의미를 가진 트랜잭션을 생성
|
||||
Transaction transaction2 = new Transaction(wallet2, wallet3.getPublicKey(), 3.7, Utils.getDate());
|
||||
block2.addTransaction(transaction2);
|
||||
|
||||
block2.mine();
|
||||
block2.showInformation();
|
||||
|
||||
Block block3 = new Block(3, block2.getBlockHash(), 0, new ArrayList());
|
||||
|
||||
Thread.sleep(1000);
|
||||
|
||||
// 지갑1에서 지갑3으로 코인을 전송했다는 의미를 가진 트랜잭션을 생성
|
||||
Transaction transaction3 = new Transaction(wallet1, wallet3.getPublicKey(), 2.3, Utils.getDate());
|
||||
block3.addTransaction(transaction3);
|
||||
|
||||
Thread.sleep(1000);
|
||||
|
||||
// 지갑2에서 지감3으로 코인을 전송했다는 의미를 가진 트랜잭션을 생성
|
||||
Transaction transaction4 = new Transaction(wallet2, wallet3.getPublicKey(), 1.4, Utils.getDate());
|
||||
block3.addTransaction(transaction4);
|
||||
|
||||
block3.mine();
|
||||
block3.showInformation();
|
||||
}
|
||||
}
|
||||
96
blockchain/miniblockchain/src/core/Block.java
Normal file
96
blockchain/miniblockchain/src/core/Block.java
Normal file
@@ -0,0 +1,96 @@
|
||||
package core;
|
||||
|
||||
import util.Utils;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.Signature;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class Block {
|
||||
private static final String ALGORITHM = "SHA1withECDSA";
|
||||
|
||||
private int blockID;
|
||||
private String previousBlockHash;
|
||||
private int nonce;
|
||||
private ArrayList<Transaction> transactionList;
|
||||
|
||||
public int getBlockID() {
|
||||
return blockID;
|
||||
}
|
||||
public void setBlockID(int blockID) {
|
||||
this.blockID = blockID;
|
||||
}
|
||||
public String getPreviousBlockHash() {
|
||||
return previousBlockHash;
|
||||
}
|
||||
public void setPreviousBlockHash(String previousBlockHash) {
|
||||
this.previousBlockHash = previousBlockHash;
|
||||
}
|
||||
public int getNonce() {
|
||||
return nonce;
|
||||
}
|
||||
public void setNonce(int nonce) {
|
||||
this.nonce = nonce;
|
||||
}
|
||||
public String getTransaction() {
|
||||
String transactionInformations = "";
|
||||
|
||||
for(int i=0;i<transactionList.size();i++) {
|
||||
transactionInformations += transactionList.get(i).getInformation();
|
||||
}
|
||||
|
||||
return transactionInformations;
|
||||
}
|
||||
|
||||
public Block(int blockID, String previousBlockHash, int nonce, ArrayList<Transaction> transactionList) {
|
||||
this.blockID = blockID;
|
||||
this.previousBlockHash = previousBlockHash;
|
||||
this.nonce = nonce;
|
||||
this.transactionList = transactionList;
|
||||
}
|
||||
|
||||
// 특정한 트랜잭션이 정상적인지 검증
|
||||
public boolean verifyTransaction(Transaction transaction) throws Exception {
|
||||
Signature signature;
|
||||
signature = Signature.getInstance(ALGORITHM);
|
||||
byte[] baText = transaction.getData().getBytes("UTF-8");
|
||||
signature.initVerify(transaction.getSender());;
|
||||
signature.update(baText);
|
||||
return signature.verify(new BigInteger(transaction.getSignature(), 16).toByteArray());
|
||||
}
|
||||
|
||||
// 정상적인 트랜잭션만 블록에 추가
|
||||
public void addTransaction(Transaction transaction) throws Exception {
|
||||
if(verifyTransaction(transaction)) {
|
||||
System.out.println("정상적인 트랜잭션을 발견했습니다.");
|
||||
transactionList.add(transaction);
|
||||
} else {
|
||||
System.out.println("트랜잭션이 바르게 인증되지 않았습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
public String getBlockHash() {
|
||||
return Utils.getHash(nonce + getTransaction() + previousBlockHash);
|
||||
}
|
||||
|
||||
public void showInformation() {
|
||||
System.out.println("---------------------------");
|
||||
System.out.println("블록 번호: " + getBlockID());
|
||||
System.out.println("이전 해시: " + getPreviousBlockHash());
|
||||
System.out.println("채굴 변수 값: " + getNonce());
|
||||
System.out.println("블록 데이터: ");
|
||||
for(int i=0;i<transactionList.size();i++) System.out.println(transactionList.get(i).getInformation());
|
||||
System.out.println("블록 해시: " + getBlockHash());
|
||||
System.out.println("---------------------------");
|
||||
}
|
||||
|
||||
public void mine() {
|
||||
while(true) {
|
||||
if(getBlockHash().substring(0, 4).equals("0000")) {
|
||||
System.out.println(blockID + "번째 블록의 채굴에 성공했습니다.");
|
||||
break;
|
||||
}
|
||||
nonce++;
|
||||
}
|
||||
}
|
||||
}
|
||||
69
blockchain/miniblockchain/src/core/Transaction.java
Normal file
69
blockchain/miniblockchain/src/core/Transaction.java
Normal file
@@ -0,0 +1,69 @@
|
||||
package core;
|
||||
|
||||
import util.Utils;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.sql.Timestamp;
|
||||
|
||||
public class Transaction {
|
||||
String signature;
|
||||
PublicKey sender;
|
||||
PublicKey receiver;
|
||||
double amount;
|
||||
Timestamp timestamp;
|
||||
|
||||
public String getSignature() {
|
||||
return signature;
|
||||
}
|
||||
public void setSignature(String signature) {
|
||||
this.signature = signature;
|
||||
}
|
||||
public PublicKey getSender() {
|
||||
return sender;
|
||||
}
|
||||
public void setSender(PublicKey sender) {
|
||||
this.sender = sender;
|
||||
}
|
||||
public PublicKey getReceiver() {
|
||||
return receiver;
|
||||
}
|
||||
public void setReceiver(PublicKey receiver) {
|
||||
this.receiver = receiver;
|
||||
}
|
||||
public double getAmount() {
|
||||
return amount;
|
||||
}
|
||||
public void setAmount(double amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
public Timestamp getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
public void setTimestamp(Timestamp timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
// 어떠한 지갑 주소로 얼마만큼의 가상화폐를 보냈는지에 대한 정보를 기준으로 초기화
|
||||
public Transaction(Wallet wallet, PublicKey receiver, double amount, String timestamp) throws Exception {
|
||||
this.sender = wallet.getPublicKey();
|
||||
this.receiver = receiver;
|
||||
this.amount = amount;
|
||||
this.timestamp = java.sql.Timestamp.valueOf(timestamp);
|
||||
this.signature = wallet.sign(getData());
|
||||
}
|
||||
|
||||
// 서명 값을 포함한 트랜잭션 정보를 반환
|
||||
public String getInformation() {
|
||||
return "<" + signature + ">\n" +
|
||||
new Utils().getHash(sender.toString()) + " -> " +
|
||||
new Utils().getHash(receiver.toString()) + " : " +
|
||||
amount + "개 (" + timestamp + ")";
|
||||
}
|
||||
|
||||
// 서명 값을 제외한 단순 트랜잭션 정보를 반환
|
||||
public String getData() {
|
||||
return new Utils().getHash(sender.toString()) + " -> " +
|
||||
new Utils().getHash(receiver.toString()) + " : " +
|
||||
amount + "개 (" + timestamp + ")";
|
||||
}
|
||||
}
|
||||
40
blockchain/miniblockchain/src/core/Wallet.java
Normal file
40
blockchain/miniblockchain/src/core/Wallet.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package core;
|
||||
|
||||
import util.EC;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
|
||||
public class Wallet {
|
||||
private static final String ALGORIGHM = "SHA1withECDSA";
|
||||
|
||||
private PrivateKey privateKey;
|
||||
private PublicKey publicKey;
|
||||
|
||||
public PrivateKey getPrivateKey() {
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
public PublicKey getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
// 특정한 파일로부터 개인키 및 공개키를 불러와 초기화
|
||||
public void setFromFile(String privateKey, String publicKey) throws Exception {
|
||||
this.privateKey = new EC().readPrivateKeyFromPemFile(privateKey);
|
||||
this.publicKey = new EC().readPublicKeyFromPemFile(publicKey);
|
||||
}
|
||||
|
||||
// 특정한 데이터를 개인키로 서명해서 얻은 결과를 문자열로 반환
|
||||
public String sign(String data) throws Exception {
|
||||
Signature signature;
|
||||
signature = Signature.getInstance(ALGORIGHM);
|
||||
signature.initSign(privateKey);
|
||||
byte[] baText = data.getBytes("UTF-8");
|
||||
signature.update(baText);
|
||||
byte[] baSignature = signature.sign();
|
||||
return (new BigInteger(1, baSignature).toString(16)).toUpperCase();
|
||||
}
|
||||
}
|
||||
100
blockchain/miniblockchain/src/util/EC.java
Normal file
100
blockchain/miniblockchain/src/util/EC.java
Normal file
@@ -0,0 +1,100 @@
|
||||
package util;
|
||||
|
||||
import org.bouncycastle.util.encoders.Base64;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.security.*;
|
||||
import java.security.spec.ECGenParameterSpec;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
|
||||
public class EC {
|
||||
|
||||
// 세부 알고리즘으로 sect163k1을 사용
|
||||
private final String ALGORITHM = "sect163k1";
|
||||
|
||||
public void generate(String privateKeyName, String publicKeyName) throws Exception{
|
||||
|
||||
// 바운시 캐슬의 타원 곡선 표준 알고리즘(ECDSA)을 사용
|
||||
KeyPairGenerator generator = KeyPairGenerator.getInstance("ECDSA", "BC");
|
||||
|
||||
// 타원 곡선의 세부 알고리즘으로 sect163k1을 사용
|
||||
ECGenParameterSpec ecsp;
|
||||
ecsp = new ECGenParameterSpec(ALGORITHM);
|
||||
generator.initialize(ecsp, new SecureRandom());
|
||||
|
||||
// 해당 알고리즘으로 랜덤의 키 한 쌍을 생성
|
||||
KeyPair keyPair = generator.generateKeyPair();
|
||||
System.out.println("타원곡선 암호키 한 쌍을 생성했습니다.");
|
||||
|
||||
// 생성한 키 한쌍에서 개인키와 공개키를 추출
|
||||
PrivateKey priv = keyPair.getPrivate();
|
||||
PublicKey pub = keyPair.getPublic();
|
||||
|
||||
// 개인키와 공개키를 특정한 파일 이름으로 저장
|
||||
writePemFile(priv, "EC PRIVATE KEY", privateKeyName);
|
||||
writePemFile(pub, "EC PUBLIC KEY", publicKeyName);
|
||||
}
|
||||
|
||||
// Pem 클래스를 이용해 생성된 암호키를 파일로 저장
|
||||
private void writePemFile(Key key, String description, String filename)
|
||||
throws FileNotFoundException, IOException{
|
||||
Pem pemFile = new Pem(key, description);
|
||||
pemFile.write(filename);
|
||||
System.out.printf("EC 암호키 %s을(를) %s 파일로 내보냈습니다.%n", description, filename);
|
||||
}
|
||||
|
||||
// 문자열 형태의 인증서에서 개인키를 추출하는 함수
|
||||
public PrivateKey readPrivateKeyFromPemFile(String privateKeyName)
|
||||
throws FileNotFoundException, IOException, NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
|
||||
String data = readString(privateKeyName);
|
||||
System.out.println("EC 개인키를 " + privateKeyName + "로부터 불러왔습니다.");
|
||||
System.out.println(data);
|
||||
|
||||
// 불필요한 설명 구문 제거
|
||||
data = data.replaceAll("-----BEGIN EC PRIVATE KEY-----", "");
|
||||
data = data.replaceAll("-----END EC PRIVATE KEY-----", "");
|
||||
|
||||
// PEM 파일은 Base64로 인코딩 되어있으므로 디코딩
|
||||
byte[] decoded = Base64.decode(data);
|
||||
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decoded);
|
||||
KeyFactory factory = KeyFactory.getInstance("ECDSA");
|
||||
return factory.generatePrivate(spec);
|
||||
}
|
||||
|
||||
// 문자열 형태의 인증서에서 공키를 추출하는 함수
|
||||
public PublicKey readPublicKeyFromPemFile(String publicKeyName)
|
||||
throws FileNotFoundException, IOException, NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
|
||||
String data = readString(publicKeyName);
|
||||
System.out.println("EC 개인키를 " + publicKeyName + "로부터 불러왔습니다.");
|
||||
System.out.println(data);
|
||||
|
||||
// 불필요한 설명 구문 제거
|
||||
data = data.replaceAll("-----BEGIN EC PUBLIC KEY-----", "");
|
||||
data = data.replaceAll("-----END EC PUBLIC KEY-----", "");
|
||||
|
||||
// PEM 파일은 Base64로 인코딩 되어있으므로 디코딩
|
||||
byte[] decoded = Base64.decode(data);
|
||||
X509EncodedKeySpec spec = new X509EncodedKeySpec(decoded);
|
||||
KeyFactory factory = KeyFactory.getInstance("ECDSA");
|
||||
return factory.generatePublic(spec);
|
||||
}
|
||||
|
||||
// 특정한 파일에 작성되어 있는 문자열을 읽어옴.
|
||||
private String readString(String filename) throws FileNotFoundException, IOException {
|
||||
StringBuilder pem = new StringBuilder();
|
||||
BufferedReader br = new BufferedReader(new FileReader(filename));
|
||||
|
||||
String line;
|
||||
while((line = br.readLine()) != null) pem.append(line).append("\n");
|
||||
|
||||
br.close();
|
||||
return pem.toString();
|
||||
}
|
||||
}
|
||||
28
blockchain/miniblockchain/src/util/Pem.java
Normal file
28
blockchain/miniblockchain/src/util/Pem.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package util;
|
||||
|
||||
import org.bouncycastle.util.io.pem.PemObject;
|
||||
import org.bouncycastle.util.io.pem.PemWriter;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.security.Key;
|
||||
|
||||
public class Pem {
|
||||
|
||||
private final PemObject pemObject;
|
||||
|
||||
public Pem(Key key, String description) {
|
||||
this.pemObject = new PemObject(description, key.getEncoded());
|
||||
}
|
||||
|
||||
public void write(String filename) throws IOException {
|
||||
PemWriter pemWriter = new PemWriter(new OutputStreamWriter(new FileOutputStream(filename)));
|
||||
|
||||
try {
|
||||
pemWriter.writeObject(this.pemObject);
|
||||
} finally {
|
||||
pemWriter.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
32
blockchain/miniblockchain/src/util/Utils.java
Normal file
32
blockchain/miniblockchain/src/util/Utils.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package util;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
public class Utils {
|
||||
|
||||
public static String getHash(String input) {
|
||||
StringBuffer result = new StringBuffer();
|
||||
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
md.update(input.getBytes());
|
||||
byte bytes[] = md.digest();
|
||||
for (byte aByte : bytes) {
|
||||
result.append(Integer.toString((aByte & 0xff) + 0x100, 16).substring(1));
|
||||
}
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public static String getDate() {
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.S");
|
||||
Date time = new Date();
|
||||
|
||||
String nowTime = format.format(time);
|
||||
return nowTime;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user