mini block chain

This commit is contained in:
haerong22
2021-08-07 01:01:28 +09:00
parent 304ed0f1a4
commit d1fac41dc3
15 changed files with 468 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MGwCAQAwEAYHKoZIzj0CAQYFK4EEAAEEVTBTAgEBBBUAFlsrE0ZTspNiuuw/2+Ma
tEHxhvKgBwYFK4EEAAGhLgMsAAQA/SXr0Ja+KrisdxU9Vw5UWEGyy0AHIrEwzQDR
Oy6tS3j8y9EhROOt85o=
-----END EC PRIVATE KEY-----

View File

@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MGwCAQAwEAYHKoZIzj0CAQYFK4EEAAEEVTBTAgEBBBUBQ8KtHoB8i9szZLYy/5O7
tqCUN2egBwYFK4EEAAGhLgMsAAQFsrxkbMiCua/AdIkE0Xmj8jwANLAFJWw+SKuH
kZRZNwJDqDOMl03bW58=
-----END EC PRIVATE KEY-----

View File

@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MGwCAQAwEAYHKoZIzj0CAQYFK4EEAAEEVTBTAgEBBBUBISXkhMMqVH4ljZy3FLsy
7IKyRP+gBwYFK4EEAAGhLgMsAAQDNOCGxozj0Fl8ys5bW5kPps3zanICJbdYdWbo
rW6p1PH0zpjBF9wDqUg=
-----END EC PRIVATE KEY-----

View File

@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MGwCAQAwEAYHKoZIzj0CAQYFK4EEAAEEVTBTAgEBBBUCDZu6TlUXdrq6nHsaiMlh
jT55XBmgBwYFK4EEAAGhLgMsAAQGmyw1g3+0Q6fjf7tacNliQOnzf4UFZZ7SFEVI
ZSqMAFWsWUwmS0maRss=
-----END EC PRIVATE KEY-----

View File

@@ -0,0 +1,4 @@
-----BEGIN EC PUBLIC KEY-----
MEAwEAYHKoZIzj0CAQYFK4EEAAEDLAAEAP0l69CWviq4rHcVPVcOVFhBsstAByKx
MM0A0TsurUt4/MvRIUTjrfOa
-----END EC PUBLIC KEY-----

View File

@@ -0,0 +1,4 @@
-----BEGIN EC PUBLIC KEY-----
MEAwEAYHKoZIzj0CAQYFK4EEAAEDLAAEBbK8ZGzIgrmvwHSJBNF5o/I8ADSwBSVs
Pkirh5GUWTcCQ6gzjJdN21uf
-----END EC PUBLIC KEY-----

View File

@@ -0,0 +1,4 @@
-----BEGIN EC PUBLIC KEY-----
MEAwEAYHKoZIzj0CAQYFK4EEAAEDLAAEAzTghsaM49BZfMrOW1uZD6bN82pyAiW3
WHVm6K1uqdTx9M6YwRfcA6lI
-----END EC PUBLIC KEY-----

View File

@@ -0,0 +1,4 @@
-----BEGIN EC PUBLIC KEY-----
MEAwEAYHKoZIzj0CAQYFK4EEAAEDLAAEBpssNYN/tEOn43+7WnDZYkDp83+FBWWe
0hRFSGUqjABVrFlMJktJmkbL
-----END EC PUBLIC KEY-----

View 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();
}
}

View 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++;
}
}
}

View 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 + ")";
}
}

View 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();
}
}

View 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();
}
}

View 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();
}
}
}

View 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;
}
}