Mỗi giao dịch sẽ mang một lượng dữ liệu nhất định: Khóa công khai (địa chỉ) của người gửi tiền.
Khóa công khai (địa chỉ) của người nhận tiền. Giá trị / số tiền cần chuyển.
Đầu vào, là các tham chiếu đến các giao dịch trước đó chứng minh người gửi có tiền để gửi.
Kết quả, cho thấy số lượng địa chỉ liên quan nhận được trong giao dịch. (Những đầu ra này được tham chiếu như đầu vào trong các giao dịch mới)
Một chữ ký mã hoá, chứng minh chủ sở hữu địa chỉ là người gửi giao dịch này và dữ liệu không bị thay đổi. (ví dụ: ngăn cản một bên thứ ba thay đổi số tiền đã gửi)
Thuật toán giao dịch trong ví Blockchain bằng đoạn code như sau: package NoobChain;
import java.security.*; import java.util.ArrayList; public class Transaction { public String transactionId;
public PublicKey sender; public PublicKey reciepient; public float value;
public byte[] signature;
public ArrayList<TransactionInput> inputs = new ArrayList<TransactionInput>();
public ArrayList<TransactionOutput> outputs = new ArrayList<TransactionOutput>();
private static int sequence = 0
public Transaction(PublicKey from, PublicKey to, float value, ArrayList<TransactionInput> inputs) { this.sender = from; this.reciepient = to; this.value = value; this.inputs = inputs; }
private String calulateHash() {
sequence++;
return StringUtil.applySha256(StringUtil.getStringFromKey(sender) + StringUtil.getStringFromKey(reciepient)
+ Float.toString(value) + sequence); }
public void generateSignature(PrivateKey privateKey) { String data = StringUtil.getStringFromKey(sender) + StringUtil.getStringFromKey(reciepient)
+ Float.toString(value);
signature = StringUtil.applyECDSASig(privateKey, data); }
String data = StringUtil.getStringFromKey(sender) + StringUtil.getStringFromKey(reciepient)
+ Float.toString(value);
return StringUtil.verifyECDSASig(sender, data, signature); }
public boolean processTransaction() { if (verifiySignature() == false) {
System.out.println("#Transaction Signature failed to verify"); return false;
}
for (TransactionInput i : inputs) {
i.UTXO = NoobChain.UTXOs.get(i.transactionOutputId); }
if (getInputsValue() < NoobChain.minimumTransaction) { System.out.println("#Transaction Inputs to small: " + getInputsValue());
return false; }
float leftOver = getInputsValue() - value; transactionId = calulateHash();
outputs.add(new TransactionOutput(this.reciepient, value, transactionId)); outputs.add(new TransactionOutput(this.sender, leftOver, transactionId)); for (TransactionOutput o : outputs) {
NoobChain.UTXOs.put(o.id, o); }
for (TransactionInput i : inputs) { if (i.UTXO == null)
continue;
NoobChain.UTXOs.remove(i.UTXO.id); }
return true; }
public float getInputsValue() { float total = 0;
for (TransactionInput i : inputs) { if (i.UTXO == null) continue; total += i.UTXO.value; } return total; }
public float getOutputsValue() { float total = 0;
for (TransactionOutput o : outputs) { total += o.value; } return total; } } 2.4.2. Xác thực chữ ký
Chữ ký thực hiện hai nhiệm vụ rất quan trọng trên Blockchain: Thứ nhất, nó chỉ cho phép chủ sở hữu chi tiêu tiền của họ, thứ hai, nó ngăn chặn người khác can thiệp vào giao dịch trước khi một Block mới được khai thác.
Ví dụ: Tú muốn gửi 2 coin đến Minh, do đó ví của họ tạo ra giao dịch và gửi cho người khai thác mỏ để thêm dữ liệu vào trong block tiếp theo. Một thợ mỏ cố gắng để thay đổi người nhận của 2 đồng tiền là Hoa. Tuy nhiên, may mắn thay, Tú đã ký kết dữ liệu giao dịch với khóa riêng của mình, cho phép bất cứ ai kiểm tra xem dữ liệu giao dịch đã được thay đổi bằng khóa công khai của Tú hay không.
Thuật toán ký và kiểm tra chữ ký vào lớp chữ ký StringUtil như sau: // Applies ECDSA Signature and returns the result ( as bytes ).
public static byte[] applyECDSASig(PrivateKey privateKey, String input) {
Signature dsa;
byte[] output = new byte[0]; try {
dsa = Signature.getInstance("ECDSA", "BC"); dsa.initSign(privateKey);
byte[] strByte = input.getBytes(); dsa.update(strByte);
byte[] realSig = dsa.sign(); output = realSig;
} catch (Exception e) {
throw new RuntimeException(e); }
return output; }
// Verifies a String signature
public static boolean verifyECDSASig(PublicKey publicKey, String data, byte[] signature) {
try {
Signature ecdsaVerify = Signature.getInstance("ECDSA", "BC"); ecdsaVerify.initVerify(publicKey);
ecdsaVerify.update(data.getBytes()); return ecdsaVerify.verify(signature); } catch (Exception e) {
throw new RuntimeException(e); }
2.4.3. Sở hữu tiền điện tử
Để bạn sở hữu một Bitcoin, bạn phải nhận được một Bitcoin. Sổ cái không thực sự thêm một Bitcoin cho bạn và trừ đi một Bitcoin từ người gửi, người gửi phải tham chiếu rằng trước đây họ đã nhận được một Bitcoin, sau đó một giao dịch được tạo ra cho thấy một Bitcoin được gửi đến địa chỉ của bạn. ( Đầu vào của giao dịch tham chiếu đến kết quả các giao dịch trước đó).
Số dư ví của bạn là tổng của tất cả các kết quả giao dịch chưa được chi tiêu được gửi cho bạn.
Hình 2.7. Ví dụ tập UTXO
Thuật toán chuyển tiền được thực hiện qua đoạn code sau: Lớp này sẽ được sử dụng để tham chiếu đến TransactionOutputs chưa được chi tiêu. TransactionOutputId sẽ được sử dụng để tìm các TransactionOutput có liên quan, cho phép các thợ mỏ kiểm tra quyền sở hữu của bạn.
Package NoobChain;
public String transactionOutputId; public TransactionOutput UTXO;
public TransactionInput(String transactionOutputId) { this.transactionOutputId = transactionOutputId; }
}
Tạo TransactionOutput class: package NoobChain;
import java.security.PublicKey; public class TransactionOutput {
public String id;
public PublicKey reciepient; public float value;
public String parentTransactionId; //Constructor
public TransactionOutput(PublicKey reciepient, float value, String parentTransactionId) { this.reciepient = reciepient; this.value = value; this.parentTransactionId = parentTransactionId; this.id=StringUtil.applySha256(StringUtil.getStringFromKey(reciepient)+Floa t.toString(value)+parentTransactionId); }
public boolean isMine(PublicKey publicKey) { return (publicKey == reciepient);
} }
Kết quả giao dịch sẽ hiển thị số tiền cuối cùng được gửi cho mỗi bên từ giao dịch. Nó được tham chiếu là đầu vào trong các giao dịch mới, hoạt động
như bằng chứng cho thấy bạn có tiền để gửi.
2.4.4. Xử lý giao dịch
Các khối trong chuỗi có thể nhận được nhiều giao dịch và Blockchain có thể rất dài, có thể mất thời gian để xử lý một giao dịch mới vì chúng ta phải tìm và kiểm tra đầu vào của nó. Để giải quyết vấn đề này, chúng ta sẽ giữ thêm một tập hợp tất cả các giao dịch chưa được chi tiêu được sử dụng làm đầu vào.
Chúng ta thực hiện một số kiểm tra để đảm bảo rằng giao dịch hợp lệ, sau đó thu thập các đầu vào và tạo đầu ra.
Quan trọng hơn, cuối cùng, chúng ta loại bỏ Đầu vào từ tập hợp UTXO (unspent transaction outputs), nghĩa là đầu ra giao dịch chỉ có thể được sử dụng duy nhất một lần làm đầu vào… Do đó, giá trị đầy đủ của đầu vào phải được sử dụng, vì vậy người gửi gửi 'thay đổi' lại cho chính họ.
Hình 2.8. Thực hiện giao dịch
Những mũi tên màu đỏ thể hiện đầu ra, mũi tên màu xanh lá thể hiện đầu vào tham chiếu đến các giao dịch trước đó.
Thuật toán cập nhật ví giao dịch tiền điện tử thông qua đoạn code sau để: Tập hợp số dư
Và tạo giao dịch package NoobChain; import java.security.*; import java.security.spec.ECGenParameterSpec; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; public class Wallet {
public PrivateKey privateKey; public PublicKey publicKey;
public HashMap<String,TransactionOutput> UTXOs = new HashMap<String,TransactionOutput>();
public Wallet() {
generateKeyPair(); }
public void generateKeyPair() { try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ECDSA", "BC"); SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
ECGenParameterSpec ecSpec = new ECGenParameterSpec("prime192v1"); keyGen.initialize(ecSpec, random);
KeyPair keyPair = keyGen.generateKeyPair(); privateKey = keyPair.getPrivate();
publicKey = keyPair.getPublic(); } catch (Exception e) {
throw new RuntimeException(e); }
}
float total = 0;
for (Map.Entry<String, TransactionOutput> item : VutaChain.UTXOs.entrySet()) {
TransactionOutput UTXO = item.getValue(); if (UTXO.isMine(publicKey)) { UTXOs.put(UTXO.id, UTXO); total += UTXO.value; } } return total; }
public Transaction sendFunds(PublicKey _recipient, float value) { if (getBalance() < value) {
System.out.println("#Not Enough funds to send transaction. Transaction Discarded.");
return null; }
ArrayList<TransactionInput> inputs = new ArrayList<TransactionInput>(); float total = 0;
for (Map.Entry<String, TransactionOutput> item : UTXOs.entrySet()) { TransactionOutput UTXO = item.getValue(); total += UTXO.value;
inputs.add(new TransactionInput(UTXO.id)); if (total > value)
break; }
Transaction newTransaction = new Transaction(publicKey, _recipient, value, inputs);
for (TransactionInput input : inputs) { UTXOs.remove(input.transactionOutputId); } return newTransaction; } }
Chương 3
THỰC NGHIỆM MÔ PHỎNG VÍ DÙNG CÔNG NGHỆ BLOCKCHAIN
3.1. PHÁT BIỂU BÀI TOÁN
Bài toán đặt ra làm thế nào để tạo ra một ví điện tử sử dụng công nghệ Blockchain để giao dịch. Quá trình bao gồm cách tạo một block; mỗi Block bao gồm thông tin như dữ liệu, hàm băm, xử lý giao dịch và được lưu trữ vào Blockchain như thế nào; cách tạo ra ví, cập nhật và quản lý ví sẽ được thực hiện trong luận văn này.
Tôi chọn Java, Eclipse và Netbean để phát triển ứng dụng.
Để giao tiếp với blockchain ứng dụng này sử dụng thư viện GSON là thư viên JAVA được tạo bởi Google, thư viện Bounceycastle là một API mật mã cho JAVA được cung cấp bởi một tổ chức từ thiện phi lợi nhuận tại Úc. Môi trường phát triển được tổng hợp lại trong bảng sau:
Bảng 3.1. Môi trường triển khai
Môi trường Tên môi trường
Ngôn ngữ lập trình JAVA, Eclipse và Netbean
Hệ điều hành Windows
Dịch vụ API GSON và Bounceycastle
Tên Project NoobChain
3.2. TRIỂN KHAI THỰC NGHIỆM
Với phạm vi luận văn, ứng dụng triển khai sẽ mô phỏng quá trình tạo Block, thêm vào chuỗi Blockchain, tạo ra ví và xử lý giao dịch giữa các ví. Tất cả các quá trình này sẽ xây dựng nên API tiện cho việc triển khai ví trong ứng dụng sau này.
Giao diện chương trình như sau:
Hình 3.1. Giao diện chương trình 3.2.1. Tạo ra Blockchain
Một Blockchain chỉ là một chuỗi các khối. Mỗi khối trong Blockchain sẽ có chữ ký số riêng của nó, chứa chữ ký số của khối trước và có một số dữ liệu (dữ liệu này có thể là các giao dịch chẳng hạn).
Hình 3.2. Chuỗi Blockchain
Hash = Digital Signature.
Mỗi khối không chỉ chứa hash của khối phía trước của nó, mà hash của khối đó còn được tính toán từ hash của khối phía trước. Nếu dữ liệu của khối
dữ liệu trước đó được thay đổi thì hash của nó cũng sẽ thay đổi (vì nó được tính toán một phần theo dữ liệu) lần lượt ảnh hưởng đến tất cả các hash của các khối sau đó. Tính toán và so sánh các hash cho phép chúng ta xem nếu một blockchain là không hợp lệ.
3.2.1.1. Chữ ký số
Có rất nhiều thuật toán mã hóa để lựa chọn, tuy nhiên tôi sử dụng thuật toán mã hóa SHA256 phù hợp cho ví dụ này. Chúng ta có thể import java.security.MessageDigest; để có được quyền truy cập vào thuật toán SHA256 và tạo ra chữ ký số trong class StringUtil như sau:
import java.security.MessageDigest; public class StringUtil {
// Applies Sha256 to a string and returns the result. public static String applySha256(String input) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256"); // Applies sha256 to our input,
byte[] hash = digest.digest(input.getBytes("UTF-8")); StringBuffer hexString = new StringBuffer();
for (int i = 0; i < hash.length; i++) {
String hex = Integer.toHexString(0xff & hash[i]); if (hex.length() == 1) hexString.append('0'); hexString.append(hex); } return hexString.toString(); } catch (Exception e) {
throw new RuntimeException(e); }
} }
Chúng ta phải tính toán hash từ tất cả các phần của khối mà chúng ta không muốn bị giả mạo. Vì vậy, hãy sử dụng đến previousHash, data và timeStamp. public String calculateHash() {
String calculatedhash = StringUtil.applySha256(previousHash + Long.toString(timeStamp) + data);
return calculatedhash; }
Và thêm phương thức tính hash đó vào constructor của class Block1 Public Block1(String data, String previousHash) {
this.data = data;
this.previousHash = previousHash; this.timeStamp = new Date().getTime(); this.hash = calculateHash();
Tất cả những gì bạn cần biết là đưa vào một string và áp dụng thuật toán SHA256 vào nó, và trả về chữ ký đã tạo là một string:
3.2.1.2. Xác thực công việc
Chúng ta sẽ yêu cầu người khai thác phải làm việc bằng cách thử nghiệm nhiều giá trị khác nhau trong một Block cho đến khi hash của Block đó bắt đầu với một hoặc nhiều số 0, bằng cách thêm một biến int gọi là nonce, biến này sẽ được sử dụng trong phương thức calculateHash() dùng để tính toán hash của Block. Trên thực tế, mỗi người khai thác sẽ bắt đầu lặp lại từ một điểm ngẫu nhiên. Một số người khai thác thậm chí có thể thử số ngẫu nhiên cho nonce.
Phương thức mineBlock() nhận vào một tham số difficulty (Độ khó), đó là số lượng số 0 bắt đầu hash mà người khai thác phải giải quyết. Độ khó thấp
có thể rất dễ dàng để giải quyết gần như là tức thời trên phần lớn các máy tính phổ biến. Tuy nhiên dành cho test chúng ta sẽ đặt tạm là 5. Độ khó càng lớn thì càng mất nhiều thời gian để giải quyết.
Thuật toán xác thực công việc thông qua phương thức mineBlock() trong Class Block như sau:
package NoobChain; import java.util.Date; public class Block {
public String hash;
public String previousHash; private String data;
private long timeStamp; private int nonce;
public Block(String data, String previousHash) { this.data = data;
this.previousHash = previousHash; this.timeStamp = new Date().getTime(); this.hash = calculateHash();
}
public String calculateHash() {
String calculatedhash = StringUtil.applySha256(previousHash + Long.toString(timeStamp) + Integer.toString(nonce) + data);
return calculatedhash; }
public void mineBlock(int difficulty) {
String target = new String(new char[difficulty]).replace('\0', '0'); //Create a string with difficulty * "0"
while(!hash.substring( 0, difficulty).equals(target)) { nonce ++;
hash = calculateHash(); }
System.out.println("Block Mined!!! : " + hash); }
}
3.2.1.3. Tạo block
Thuật toán tạo một Block trong project NoobChain có code như sau: package NoobChain;
import java.util.Date; public class Block1 { public String hash;
public String previousHash;
public String data; // our data will be a simple message.
public long timeStamp; // as number of milliseconds since 1/1/1970. public int nonce;
private String log = "";
public Block1(String data, String previousHash) { this.data = data;
this.previousHash = previousHash; this.timeStamp = new Date().getTime();
this.hash = calculateHash(); // Making sure we do this after we set the other values.
}
public String calculateHash() {
String calculatedhash = StringUtil.applySha256(previousHash + Long.toString(timeStamp) + Integer.toString(nonce) + data);
return calculatedhash; }
String target = new String(new char[difficulty]).replace('\0', '0'); while(!hash.substring( 0, difficulty).equals(target)) {
nonce ++;
hash = calculateHash();
}
this.log += "Block Mined!!! : " + hash; }
public String getLog () { String rs = this.log; this.log = "\n"; return rs; }
}
Kết quả Block1 chứa một String hash sẽ giữ chữ ký số của Block hiện tại. Biến previousHash để giữ hash của block trước đó và String data để giữ dữ liệu được thể hiện như hình 3.3.
3.2.1.4. Kiểm tra tính toàn vẹn của Blockchain
Hàm isChainValid() dùng trong Class NoobChain, nó sẽ lặp qua tất cả các Block trong chuỗi và so sánh tất cả các hash. Hàm này sẽ cần thiết để kiểm tra tính toàn vẹn của Blockchain.
Public static Boolean isChainValid() { Block currentBlock; Block previousBlock;
for (int i = 1; i < blockchain.size(); i++) { currentBlock = blockchain.get(i); previousBlock = blockchain.get(i - 1); if !currentBlock.hash.equals(currentBlock.calculateHash())) {
System.out.println("Current Hashes not equal"); return false;
}
if (!previousBlock.hash.equals(currentBlock.previousHash)) {
System.out.println("Previous Hashes not equal"); return false; } } return true; } 3.2.1.5. Tạo Blockchain
Hãy tạo một class NoobChain chứa main method như sau và thực hiện nút lệnh Tạo Blockchain để tạo ra một chuỗi Blockchain gồm 3 Block.
Public void TaoBlockchain() {
blockchain1.add(new Block1("Chào bạn tôi là block thứ 1", "0")); blockchain1.get(0).mineBlock(difficulty);
blockchain1.get(blockchain1.size() - 1).hash)); blockchain1.get(1).mineBlock(difficulty);
blockchain1.add(new Block1("Chào bạn tôi là block thứ 3", blockchain1.get(blockchain1.size() - 1).hash));
blockchain1.get(2).mineBlock(difficulty); String blockchainJson = new
GsonBuilder().setPrettyPrinting().create().toJson(blockchain1); txtLog.append("\nThe block chain: ");
txtLog.append(blockchainJson);
txtLog.append("\n- Chain count: " + blockchain1.size());
txtLog.setCaretPosition(txtLog.getDocument().getLength()); }
Kết quả như sau:
Hình 3.4. Kết quả tạo ra chuỗi Blockchain
Để thêm một Block tiếp theo vào chuỗi Blockchain đã có thực hiện nút lệnh Thêm Block với code sau:
Private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) { if (blockchain1.size()>0){
cursor1 = blockchain1.size()+1;
blockchain1.add(new Block1("Chào bạn tôi là block thứ "+ cursor1 , blockchain1.get(blockchain1.size() - 1).hash)); blockchain1.get(blockchain1.size() - 1).mineBlock(difficulty); Block1 b = blockchain1.get(blockchain1.size() - 1); txtBlocknum1.setText(0 + ""); txtHash1.setText(b.hash); txtPrevhash1.setText(b.previousHash); txtData1.setText(b.data);
Date date = new Date(b.timeStamp);
txtTimestamp1.setText(date.toGMTString()); txtNonce1.setText(b.nonce + "");
String page = (cursor1) + "/" + this.blockchain1.size(); txtBlocknum1.setText(cursor1 + "");
txtBlock1.setText(page); String blockchainJson = new
GsonBuilder().setPrettyPrinting().create().toJson(blockchain1); txtLog.append("\nThe block chain: ");
txtLog.append(blockchainJson);
txtLog.append("\n- Chain count: " + blockchain1.size()); txtLog.setCaretPosition(txtLog.getDocument().getLength()); }
}
Hình 3.5. Kết quả thêm một Block vào chuỗi Blockchain 3.2.2. Giao dịch trong Ví Blockchain
Ta tiến hành giao dịch chuyển tiền giữa hai Ví A và Ví B dựa vào quá trình tạo một giao dịch ban đầu từ Block Genesis chuyển cho Ví A 100 coin, sau đó giao dịch giữa Ví A và Ví B được thực hiện để thêm các Block tiếp theo thuật toán giao dịch được cài đặt bởi code sau:
Public static void GiaodichVi() {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); walletA = new Wallet();
walletB = new Wallet();
Wallet coinbase = new Wallet();
genesisTransaction = new Transaction(coinbase.publicKey, walletA.publicKey, 100f, null);
genesisTransaction.transactionId = "0";genesisTransaction.outputs.add(new TransactionOutput(genesisTransaction.reciepient, genesisTransaction.value, genesisTransaction.transactionId));
UTXOs.put(genesisTransaction.outputs.get(0).id, genesisTransaction.outputs.get(0));
NoobChain.log += "\n***** GIAO DỊCH GIỮA 2 VÍ A VA B *****"; Block genesis = new Block("0");
genesis.addTransaction(genesisTransaction); addBlock(genesis);
Block block1 = new Block(genesis.hash);
NoobChain.log += "\nWalletA's balance is: " + walletA.getBalance(); NoobChain.log += "\nWalletA is Attempting to send funds (40) to WalletB..."; block1.addTransaction(walletA.sendFunds(walletB.publicKey, 40f)); NoobChain.log += "\nWalletA is Attempting to send funds (20) to WalletB..."; block1.addTransaction(walletA.sendFunds(walletB.publicKey, 20f)); addBlock(block1);
NoobChain.log += block1.getLog();
NoobChain.log += "\nWalletA's balance is: " + walletA.getBalance(); NoobChain.log += "\nWalletB's balance is: " + walletB.getBalance(); Block block2 = new Block(block1.hash);
NoobChain.log += "\nWalletA Attempting to send more funds (1000) than it has..."; block2.addTransaction(walletA.sendFunds(walletB.publicKey, 1000f));
NoobChain.log += "\nWalletA Attempting to send more funds (10) to WalletB..."; block2.addTransaction(walletA.sendFunds(walletB.publicKey, 10f));
addBlock(block2);
NoobChain.log += block2.getLog();
NoobChain.log += "\nWalletA's balance is: " + walletA.getBalance(); NoobChain.log += "\nWalletB's balance is: " + walletB.getBalance();
NoobChain.log += "\nWalletB is Attempting to send funds (20) to WalletA..."; block3.addTransaction(walletB.sendFunds( walletA.publicKey, 20));
addBlock(block3);
NoobChain.log += block3.getLog();
NoobChain.log += "\nWalletA's balance is: " + walletA.getBalance(); NoobChain.log += "\nWalletB's balance is: " + walletB.getBalance(); System.out.println("\n\nLOG"+ NoobChain.log);
}
Kết quả giao dịch sẽ được hiển thị như sau:
KẾT LUẬN
Công nghệ Blockchain đang chuyển đổi thành một nguồn tài nguyên đáng tin cậy và có giá trị trong thế giới kỹ thuật số và kinh doanh. Nó có các ứng dụng trong hầu hết các ngành công nghiệp. Thậm chí các tập đoàn lớn nhất cũng đang theo đuổi việc sử dụng công nghệ này để nâng cấp các quy