Java编写属于你的第一个区块链币交易

 Block.java的代码:

package noodchain;

import java.util.ArrayList;
import java.util.Date;

public  class Block{
	 public String hash;
	 public String previousHash;
	 public String merkleRoot;
	 public ArrayList<Transaction> transactions = new ArrayList<Transaction>();//我们的数据将是一个简单的信息。
	 public long timeStamp;//从1970年1月1日读取的毫秒数
	 public int nonce;
	 //块构造函数
	 public Block(String previousHash) {
		 this.previousHash = previousHash;
		 this.timeStamp = new Date().getTime();
		 this.hash = calculateHash();//确保在设置了其他值之前做这步
	 }
	 //根据块内容计算新的哈希
	public String calculateHash() {
		// TODO Auto-generated method stub
		String calculatedhash = StringUtil.applySha256(
				previousHash + Long.toString(timeStamp)
				+Integer.toString(nonce)
				+ merkleRoot);
		return calculatedhash;
	}
	//增加nonce值,直到达到散列目标为止。
	public void mineBlock(int difficulty) {
		merkleRoot = StringUtil.getMerkleRoot(transactions);
		String target = StringUtil.getDificultyString(difficulty);//设定难度系数
		while(!hash.substring(0,difficulty).equals(target)) {
			nonce++;
			hash = calculateHash();
		}
		System.out.println("挖掘出区块:" + hash);
	}
	//将事务添加到这个块中
	public boolean addTransaction(Transaction transaction) {
		//处理事务并检查是否有效,除非块是起源块,否则忽略。
		if(transaction == null) 
			return false;		
		if((!"0".equals(previousHash))) {
			if((transaction.processTransaction() != true)) {
				System.out.println("事务失败.丢弃。");
				return false;
			}
		}
		transactions.add(transaction);
		System.out.println("事务成功添加到Block中");
		return true;
}
 }

NoodChain代码:

package noodchain;

import java.security.Security;
import java.util.ArrayList;
import com.google.gson.*;
import java.util.Base64;
import java.util.HashMap;


public class NoodChain {
	public static ArrayList<Block> blockchain = new ArrayList<Block>();
	//所有未使用的交易清单。
	public static HashMap<String,TransactionOutput> UTXOs = new HashMap<String,TransactionOutput>();
	public static int difficulty = 2;
	public static float minimumTransaction = 0.1f;
	public static Wallet walletA;
	public static Wallet walletB;
	public static Transaction genesisTransaction;
	public static void main(String[] args) {
		//设置bouncecastle作为安全供应商
		Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
		//创建新的钱包
		walletA = new Wallet();
		walletB = new Wallet();
		Wallet coinbase = new Wallet();
		//创建genesis事务,向walletA发送100个NoobCoin:
		genesisTransaction = new Transaction(coinbase.publicKey, walletA.publicKey, 100f, null);
		genesisTransaction.generateSignature(coinbase.privateKey);//手工签署起源事务
		genesisTransaction.transactionId = "0";  //手动设置事务id
		genesisTransaction.outputs.add(new TransactionOutput(genesisTransaction.reciepient, genesisTransaction.value, genesisTransaction.transactionId)); //手动添加事务输出
		UTXOs.put(genesisTransaction.outputs.get(0).id, genesisTransaction.outputs.get(0)); //在UTXOs列表中存储第一个事务是很重要的。
		
		System.out.println("创建和挖掘起源块... ");
		Block genesis = new Block("0");
		genesis.addTransaction(genesisTransaction);
        addBlock(genesis);
        
        //测试
        Block block1 = new Block(genesis.hash);
		System.out.println("\n钱包A的余额: " + walletA.getBalance());
		System.out.println("\n钱包A试图向钱包B提供资金40...");
		block1.addTransaction(walletA.sendFunds(walletB.publicKey, 40f));
		addBlock(block1);
		System.out.println("\n钱包A的余额: " + walletA.getBalance());
		System.out.println("\n钱包B的余额: " + walletB.getBalance());
		
		Block block2 = new Block(block1.hash);
		System.out.println("\n钱包A试图发送比它所拥有的更多的资金(1000)...");
		block2.addTransaction(walletA.sendFunds(walletB.publicKey, 1000f));
		addBlock(block2);
		System.out.println("\n钱包A的余额: " + walletA.getBalance());
		System.out.println("\n钱包B的余额: " + walletB.getBalance());
		
		Block block3 = new Block(block2.hash);
		System.out.println("\n钱包B正试图向钱包A提供资金20...");
		block3.addTransaction(walletB.sendFunds( walletA.publicKey, 20));
		System.out.println("\n钱包A的余额: " + walletA.getBalance());
		System.out.println("\n钱包B的余额: " + walletB.getBalance());
		
		isChainValid();
	}
	public static Boolean isChainValid() {
		// TODO Auto-generated method stub
		Block currentBlock; 
		Block previousBlock;
		String hashTarget = new String(new char[difficulty]).replace('\0', '0');
		HashMap<String,TransactionOutput> tempUTXOs = new HashMap<String,TransactionOutput>(); //a temporary working list of unspent transactions at a given block state.
		tempUTXOs.put(genesisTransaction.outputs.get(0).id, genesisTransaction.outputs.get(0));
		
		//通过区块链循环检查哈希:
		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("#当前哈希不相等");
				return false;
			}
			//比较之前的哈希和注册之前的哈希
			if(!previousBlock.hash.equals(currentBlock.previousHash) ) {
				System.out.println("#之前的哈希不相等");
				return false;
			}
			//检查哈希是否被解决
			if(!currentBlock.hash.substring( 0, difficulty).equals(hashTarget)) {
				System.out.println("#这个区块尚未开矿");
				return false;
			}
			
			//循环通过blockchains事务:
			TransactionOutput tempOutput;
			for(int t=0; t <currentBlock.transactions.size(); t++) {
				Transaction currentTransaction = currentBlock.transactions.get(t);
				
				if(!currentTransaction.verifiySignature()) {
					System.out.println("#事务签名(" + t + ") 有效");
					return false; 
				}
				if(currentTransaction.getInputsValue() != currentTransaction.getOutputsValue()) {
					System.out.println("#输入等于交易的输出(" + t + ")");
					return false; 
				}
				
				for(TransactionInput input: currentTransaction.inputs) {	
					tempOutput = tempUTXOs.get(input.transactionOutputId);
					
					if(tempOutput == null) {
						System.out.println("#引用输入到事务(" + t + ") 消失");
						return false;
					}
					
					if(input.UTXO.value != tempOutput.value) {
						System.out.println("#引用输入到事务(" + t + ") 的值有效");
						return false;
					}
					
					tempUTXOs.remove(input.transactionOutputId);
				}
				
				for(TransactionOutput output: currentTransaction.outputs) {
					tempUTXOs.put(output.id, output);
				}
				
				if( currentTransaction.outputs.get(0).reciepient != currentTransaction.reciepient) {
					System.out.println("#事务(" + t + ") 的输出接受者不应该是谁");
					return false;
				}
				if( currentTransaction.outputs.get(1).reciepient != currentTransaction.sender) {
					System.out.println("#事务(" + t + ") 输出“change”不是发送者.");
					return false;
				}
				
			}
			
		}
		System.out.println("区块链有效");
		return true;
	}
	public static void addBlock(Block block) {
		// TODO Auto-generated method stub
		block.mineBlock(difficulty);
		blockchain.add(block);
	}
}

StringUtil.java代码:

package noodchain;

import java.security.Key;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import com.google.gson.*;
public class StringUtil {
	//将sha256应用于字符串并返回结果。
	public static String applySha256(String input) {
		try {
			MessageDigest digest = MessageDigest.getInstance("SHA-256");
			//将sha-256应用到我们的输入
			byte[] hash = digest.digest(input.getBytes("UTF-8"));
			StringBuffer hexString = new StringBuffer();//这将包含哈希值作为16进制
			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) {
			// TODO: handle exception
			throw new RuntimeException(e);
		}
	}
	//应用ECDSA签名并返回结果(作为字节)。
	//理解不了这段没关系
	//只需要记住:applyECDSASig接受发送者私钥和字符串输入,对其签名并返回一个字节数组。
	//verifyECDSASig接受签名、公钥和字符串数据,如果签名有效,则返回true或false。
	//getStringFromKey从任何键返回编码的字符串。
	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;
		
	}
	//验证签名字符串
	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) {
			// TODO: handle exception
			throw new RuntimeException(e);
		}
	}
	//将对象转换为json字符串的简短助手
	public static String getJson(Object o) {
		return new GsonBuilder().setPrettyPrinting().create().toJson(o);
	}
	public static String getStringFromkey(Key key) {
		// TODO Auto-generated method stub
		return Base64.getEncoder().encodeToString(key.getEncoded());
	}
	//在事务数组中使用Tacks并返回一个merkle根。
	public static String getMerkleRoot(ArrayList<Transaction> transactions) {
		int count = transactions.size();
		
		List<String> previousTreeLayer = new ArrayList<String>();
		for(Transaction transaction : transactions) {
			previousTreeLayer.add(transaction.transactionId);
		}
		List<String> treeLayer = previousTreeLayer;
		
		while(count > 1) {
			treeLayer = new ArrayList<String>();
			for(int i=1; i < previousTreeLayer.size(); i+=2) {
				treeLayer.add(applySha256(previousTreeLayer.get(i-1) + previousTreeLayer.get(i)));
			}
			count = treeLayer.size();
			previousTreeLayer = treeLayer;
		}
		
		String merkleRoot = (treeLayer.size() == 1) ? treeLayer.get(0) : "";
		return merkleRoot;
}
	//返回比较哈希的困难字符串目标。5的难度将返回“00000”
	public static String getDificultyString(int difficulty) {
		// TODO Auto-generated method stub
		return new String(new char[difficulty]).replace('\0', '0');
	}
	
}

Transaction的代码:

package noodchain;
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;
	}
	//这将计算事务哈希(将用作其Id)
	private String calculateHash() {
		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);
	}
	//验证我们签署的数据没有被篡改。
	public boolean verifiySignature() {
		String data = StringUtil.getStringFromkey(sender) + StringUtil.getStringFromkey(reciepient) + Float.toString(value);
		return StringUtil.verifyECDSASig(sender, data, signature);
	}
	//实际上,您可能想要签署更多的信息,比如所使用的输出/输入和/或时间戳(目前我们只是签署了最低要求)
	public boolean processTransaction() {
		// TODO Auto-generated method stub
		if(verifiySignature() == false) {
			System.out.println("#事务签名无法验证");
		return false;
	}
		//收集交易输入(确保未使用):
		for(TransactionInput i : inputs) {
			i.UTXO = NoodChain.UTXOs.get(i.transactionOutputId);
		}

		//检查事务是否有效
		if(getInputsValue() < NoodChain.minimumTransaction) {
			System.out.println("事务输入太小: " + getInputsValue());
			System.out.println("请输入大于 " + NoodChain.minimumTransaction);
			return false;
		}
		
		//生成事务输出:
		float leftOver = getInputsValue() - value; //得到输入的值,然后剩下会发生更改:
		transactionId = calculateHash();
		outputs.add(new TransactionOutput( this.reciepient, value,transactionId)); //值发送给接收方
		outputs.add(new TransactionOutput( this.sender, leftOver,transactionId)); //将剩余的“更改”发送回发送方		
				
		//将输出添加到未使用的列表
		for(TransactionOutput o : outputs) {
			NoodChain.UTXOs.put(o.id , o);
		}
		
		//删除UTXO列表中的事务输入:
		for(TransactionInput i : inputs) {
			if(i.UTXO == null) continue; // 如果找不到事务,就跳过它
			NoodChain.UTXOs.remove(i.UTXO.id);
		}
		
		return true;
	}
	public float getInputsValue() {
		// TODO Auto-generated method stub
		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;
	}
	
	private String calulateHash() {
		sequence++; //增加序列以避免两个具有相同散列的相同事务
		return StringUtil.applySha256(
				StringUtil.getStringFromkey(sender) +
				StringUtil.getStringFromkey(reciepient) +
				Float.toString(value) + sequence
				);
}
}

TransactionInput.java的代码:

package noodchain;
/*这个类将用于引用尚未使用的事务输出。
 * transactionOutputId将用于查找相关的事务输出,
 * 允许矿工检查您的所有权。
 */
public class TransactionInput {
	public String transactionOutputId;//对transactionoutputs的引用-> transactionId
	public TransactionOutput UTXO;//包含未使用的事务输出
	
	public TransactionInput(String transactionOutputId) {
		this.transactionOutputId = transactionOutputId;
	}
}

TransactionOutput.java的代码:

package noodchain;

import java.security.PublicKey;

public class TransactionOutput {
	public String id;
	public PublicKey reciepient;//也被称为这些硬币的新主人。
	public float value;//他们拥有的硬币数量
	public String parentTransactionId;//此输出是在其中创建的事务的id
	//构造函数
	public TransactionOutput(PublicKey reciepient, float value, String parentTransactionId) {
		this.reciepient = reciepient;
		this.value = value;
		this.parentTransactionId = parentTransactionId;
		this.id = StringUtil.applySha256(StringUtil.getStringFromkey(reciepient)+Float.toString(value)+parentTransactionId);
}
	//检查这个币是否属于你
	public boolean isMine(PublicKey publicKey) {
		return (publicKey == reciepient);
}
}

Wallet.java的代码:

package noodchain;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import javax.management.RuntimeErrorException;

public class Wallet {
	public PrivateKey privateKey;
	public PublicKey publicKey;
	//只有这个钱包里的UTXOs。
	public HashMap<String,TransactionOutput> UTXOs = new HashMap<String,TransactionOutput>(); 
	public Wallet() {
		generateKeyPair();
		//关于这个方法,您需要了解的是它使用Java.security。
		//密钥对发生器产生一个椭圆曲线密钥对。
		//该方法生成并设置我们的公钥和私钥。
	}

	private void generateKeyPair() {
		// TODO Auto-generated method stub
		try {
			KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ECDSA","BC");
			SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
			ECGenParameterSpec ecSpec = new ECGenParameterSpec("prime192v1");
			//初始化密钥生成器并生成密钥对
			keyGen.initialize(ecSpec,random);//256个字节提供了可接受的安全级别
			KeyPair keyPair = keyGen.generateKeyPair();
			//从密钥对中设置公钥和私钥
			privateKey = keyPair.getPrivate();
			publicKey = keyPair.getPublic();
			
		} catch (Exception e) {
			// TODO: handle exception
			throw new RuntimeException(e);
		}
	}
	public float getBalance() {
		float total = 0;	
        for (Map.Entry<String, TransactionOutput> item: NoodChain.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("#没有足够的资金进行交易。事务被丢弃。");
			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);
		newTransaction.generateSignature(privateKey);
		
		for(TransactionInput input: inputs){
			UTXOs.remove(input.transactionOutputId);
		}
		
		return newTransaction;
} 
}

慢慢去看吧 代码能运行,是全的,只不过注意两个地方:

 这里需要导入一个包,

 导入之后可能还是有红色下划线,但可以忽略,好像是版本问题,此时可以运行,只要import com.google.gson.*;有就行了

这里需要导入

 这个包,导入之后 一样也许依然标红  但可以运行

运行结果:

猜你喜欢

转载自blog.csdn.net/sinat_38832964/article/details/81186646