Bitcoinマイニング計算をjavaで。

とても久々の投稿です。

最近Bitcoinを初めとした仮想通過関係の技術調査行っています。

ちなみに2018年1月はBitcoin価格が200万円から100万円に大暴落したりと、とっても面白い一カ月でした。
まだまだこれからどうなる事やら。という感じです。

さて、Bitcoinに関する技術情報ですが、もちろんJavaでの実装も世の中にはあるのですが、ピンポイントでマイニングを行っている箇所を探すのはちょっと厳しそうです。

wikiにはご丁寧にサンプルコードも載っているのですが、残念な事にJava版はありません。
https://en.bitcoin.it/wiki/Block_hashing_algorithm

という事で、上記のサンプルコードをもとに、Java版を作ってみました。
パラメータも全部同じものを使っています。

余計なlibを導入しても仕方無いのでSDKだけで動作可能ないわゆるピュアJavaです。

まだまだ無駄な箇所は多いでしょうし、マイニングを本気で行いたいのであればそもそも今更自作する理由は特に無いと思います。
あくまで学習目的です。

リトルエンディアン化はしょうがないとしても、Javaだとunsigned intをナチュラルに使えないので、この辺はちょっと微妙な事をしないといけないですね。

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;

public class Main {

	public static void main(String[] args) throws Exception {
		
		// バージョン番号
		int paramVersion = 1;	
		
		// 前ブロックハッシュ
		String paramPrevBlockHash = "00000000000008a3a41b85b8b29ad444def299fee21793cd8b9e567eab02cd81";
		
		// マークルルート
		String paramRootHash = "2b12fcf1b09288fcaff797d71e950e71ae42b91e8bdb2304758dfcffc2b620e3";

		// タイムスタンプ
		String paramTime = "2011/05/22 02:26:31";
		
		// 難易度
		int paramBits = 440711666;
		
		// ノンス(これを探すのがマイニング)
		// javaではunsigned intを宣言出来ないので文字列で。
		String checkNonce = "2504433986";
		
		// パラメータ値の変換
		String version = littleEndian(paramVersion);
		String prevBlockHash = swapOrder(paramPrevBlockHash);
		String rootHash = swapOrder(paramRootHash);
		String time = littleEndian((int) (new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(paramTime).getTime() / 1000));
		String bits = littleEndian(paramBits);
		String nonce = littleEndian(Integer.parseUnsignedInt(checkNonce));

		// 連結してバイト配列に。
		String headerHex = version + prevBlockHash + rootHash + time + bits + nonce;
		byte[] headerBin = toBin(headerHex);
	
		// ハッシュ計算。
		byte[] pass = sha256(sha256(headerBin));
		
		// 16進文字列に変換した後、並びを逆転。
		String result = swapOrder(toHexString(pass));
		
		// 00000000000000001e8d6829a8a21adc5d38d0a473b144b6765798e61f98bd1d
		System.out.println(result);
		
	}

	// 16進文字列からバイナリへ。
	public static byte[] toBin(String hex) {
	    byte[] ret = new byte[hex.length() / 2];
	    int len = ret.length;
	    for (int i = 0; i < len; i++) {
	        ret[i] = (byte) Integer.parseInt(hex.substring(i * 2, (i + 1) * 2), 16);
	    }
	    return ret;
	}	
	
	// リトルエンディアン変換。
	private static String littleEndian(int val) {

		ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
		buf.putInt(val);
		buf.flip();
		buf.order(ByteOrder.LITTLE_ENDIAN);
		int b = buf.getInt();
		ByteBuffer buf2 = ByteBuffer.allocate(Integer.BYTES);
		buf2.putInt(b);

		return toHexString(buf2.array());
	}
	
	// 16進文字列に。
	private static String toHexString(byte[] bytes) {

       	 	StringBuilder sb = new StringBuilder();
       	 	for (byte b : bytes) {
	            sb.append(String.format("%02x", b));
        	}
	
	        return sb.toString();
	}
	
	// 16進文字列を逆順に。
	private static String swapOrder(String str) {
		char[] chars = new StringBuilder(str).reverse().toString().toCharArray();
		
		int len = chars.length;
		
		StringBuilder result = new StringBuilder(str.length());
		
		for (int i = 0; i < len; i += 2) {
			result.append(chars[i + 1]).append(chars[i]);
		}
		
		return result.toString();
	}

	// sha256ハッシュ計算。
	public static byte[] sha256(byte[] val) throws NoSuchAlgorithmException {
		MessageDigest md = MessageDigest.getInstance("SHA-256");
		md.update(val);
		return md.digest();
	}	
		
}

尚、最近キュレーションまがいなサイトが少しだけ加工したコピペコードを投稿したりしているみたいですので、せめて参照先としてリンクくらい張っておいて欲しいです。

Comments are closed.