저번 내용에 bitcoinj로 지갑을 만드는 방법을 살펴보았습니다.
이전에 만든 지갑은 몇 가지 문제점을 가지고 있다는 것을 눈치채신 분들도 있을 것입니다.
첫 번째는 지갑 프로그램을 실행할 때 마다 새로운 주소, 잔액, 시드, 니모닉, 생성일자가 나타난다는 것입니다.
이것은 이전에 내가 만든 주소를 기억했다가 사용하는 것이 아니라 매번 새로이 만들어지기 때문에 프로그램이 시작하면 동작을 멈추면 안됩니다. 이러한 프로그램은 사용하기 아무래도 어렵습니다.
두 번째는 지갑을 만들 수는 있지만 거래를 할 수 없습니다.
물론 완전히 거래가 안되는 것은 아니지만 거래를 하려면 주소를 알아야 합니다. 그러나 이전에 맏든 주소로 거래를 한다면 거래된 비트코인은 영원히 잃어버릴 수도 있습니다.
다만 니모닉을 정확하게 백업해놓았다면 가능합니다. 니모닉은 비트코인 지갑의 비밀번호, 즉 개인키를 쉽게 암기할 수 있도록 만들어진 암호 기억용 연상 단어입니다. 지갑에 따라 12글자를 제공하기도하고 24글자를 제공하기도 합니다.
bitcoinj는 기본적으로 12글자를 제공하며, 전에 프로그램에서 보았을 것입니다.
idea tissue rent fluid close useless bread female horror already city brain
위 12글자를 잘 적어서 백업해 놓으면 암호를 잃어버려도 니모닉으로 암호를 복구할 수 있습니다.
반드시 꼭 기억해야 합니다. 이 방법은 비트코인 표준 제안 BIP 39에 정의되어 있습니다.
https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
자 이제 위 2가지 문제를 쉽게 해결하기 위한 HD 지갑을 만들어 봅니다.
#1. 새로운 자바 class를 만듭니다.
#2. 테스트넷에 접속하는 NetworkParameters 메소드를 만듭니다.
public NetworkParameters getTestNetParam() {
return MainNetParams.fromID(MainNetParams.ID_TESTNET);
}
#3. 지갑을 좀더 쉽게 사용할 수 있게 도와주는 WalletApptKit 을 초기화하는 메소드를 만듭니다.
public WalletAppKit initialWallet(NetworkParameters params) {
String filePrefix = "wallet-service-testnet";
return new WalletAppKit(params, new File("."), filePrefix);
}
여기서 filePrefix는 WalletAppKit에서 저장할 wallet 파일의 이름이 됩니다. 파일의 확장자는 wallet이 됩니다.
new WalletAppKit(params, new File("."), filePrefix);
에서 1번째 인수는 테스트넷, 2번째 인수는 wallet 파일이 저장될 위치입니다.
위치가 . (dot) 인 것은 자바 프로젝트의 시작 위치를 말합니다. 별도의 위치에 저장을 원하시면 위치를 적어주면 됩니다.
3번째 인수는 filePrefix로 파일의 이름입니다.
#4. WalletAppKit를 실행하기 위한 synchBlockchain 메소드를 작성합니다.
public void synchBlockchain(WalletAppKit kit) {
System.out.println("Synchronizing the blockchain ...");
kit.startAsync();
kit.awaitRunning();
System.out.println("Synchronized the blockchain ...");
}
synchBlockchain() 메소드는 WalletAppKit를 startAsync()로 주변 비트코인 노드와 Peer 싱크를 주고받기를 시작하게 합니다. awaitRunning()은 Peer 싱크가 끝날 때 까지 WalletAppKit를 대기 상태로 만들어 줍니다.
싱크가 완료되면 "Synchronized the blockchain ..." 화면에 출력해 줍니다. 테스트넷의 경우 Peer 싱크는 처음에는 약 30초~10분까지 걸릴 수 있습니다. 시간이 너무 오래 걸리면 프로그램을 다시 시작하면 됩니다.
#5. 지갑 주소 생성 및 관련된 정보 만들어 봅니다.
#5.1. 지갑 정보를 담을 수 있는 자바 클래스를 만듭니다.
class MyWallet {
String address = "";
String balance = "";
String seed = "";
String creationTime = "";
String mnemonics = "";
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Address : \t" + address);
sb.append("\r\n");
sb.append("Balance : \t" + balance);
sb.append("\r\n");
sb.append("Seed : \t" + seed);
sb.append("\r\n");
sb.append("Creationtime : \t" + creationTime);
sb.append("\r\n");
sb.append("Mnemonics : \t" + mnemonics);
sb.append("\r\n");
return sb.toString();
}
}
MyWallet 클래스를 만듭니다. MyWallet 클래스는 비트코인 주소, 잔액, 시드, 생성 시간, 니모닉을 저장합니다.
좀더 쉽게 MyWallet 의 변수에 접근하기 위한 GET/SET 메소드를 만듭니다.
메뉴 Source > Generator Getters/Setters 를 선택합니다.
Getters/Setter를 생성할 변수를 선택합니다.
#5.2. HD 주소를 생성하는 메소드를 만듭니다.
public List< MyWallet> getWalletAddress(WalletAppKit kit) {
List< MyWallet> walletList = new ArrayList< MyWallet>();
List< Address> list = kit.wallet().getWatchedAddresses();
if (list.size() < 5) {
kit.wallet().addWatchedAddress(kit.wallet().freshReceiveAddress());
System.out.println("New address created");
}
MyWallet w = null;
for (Address addr : kit.wallet().getWatchedAddresses()) {
w = new MyWallet();
w.setAddress(addr.toBase58());
w.setBalance(kit.wallet().getBalance().toFriendlyString());
DeterministicSeed seed = kit.wallet().getActiveKeyChain().getSeed();
w.setSeed(seed.toHexString());
if ( (seed.getCreationTimeSeconds()-1525190911)<1) {
seed.setCreationTimeSeconds(System.currentTimeMillis());
}
w.setCreationTime(Utils.dateTimeFormat(seed.getCreationTimeSeconds()));
w.setMnemonics(Utils.join(seed.getMnemonicCode()));
walletList.add(w);
}
return walletList;
}
먼저 지갑을 저장할 리스트 객체를 생성합니다.
List< MyWallet> walletList = new ArrayList< MyWallet>();
현재 wallet에 포함된 Address 리스트를 가져옵니다.
List< Address> list = kit.wallet().getWatchedAddresses();
HD 지갑은 5개까지 만들 수 있습니다. 만약 더 많은 지갑을 생성하고자 하시면 list.size()<5 에서 5보다 큰 숫자를 입력하면 됩니다.
if (list.size() < 5) {
kit.wallet().addWatchedAddress(kit.wallet().freshReceiveAddress());
System.out.println("New address created");
}
처음 실행을 하면 list.size()는 0이며 if 문장에 true가 되어 다음 아래 내용이 실행됩니다.
kit.wallet().addWatchedAddress(kit.wallet().freshReceiveAddress());
여기서 kit.wallet().freshReceiveAddress() 는 새로운 Address를 받습니다. 이를 wallet에 다시 addWatchedAddress()로 추가합니다.
이제 추가된 wallet을 MyWallet 객체에 저장합니다.
MyWallet w = null;
for (Address addr : kit.wallet().getWatchedAddresses()) {
w = new MyWallet();
w.setAddress(addr.toBase58());
w.setBalance(kit.wallet().getBalance().toFriendlyString());
DeterministicSeed seed = kit.wallet().getActiveKeyChain().getSeed();
w.setSeed(seed.toHexString());
if ( (seed.getCreationTimeSeconds()-1525190911)<1) {
seed.setCreationTimeSeconds(System.currentTimeMillis());
}
w.setCreationTime(Utils.dateTimeFormat(seed.getCreationTimeSeconds()));
w.setMnemonics(Utils.join(seed.getMnemonicCode()));
walletList.add(w);
}
for() 문장은 wallet이 가지고 있는 Address 리스트를 가져옵니다. 해당 정보를 MyWallet w 객체에 저장합니다.
w.setAddress(addr.toBase58());
w.setBalance(kit.wallet().getBalance().toFriendlyString());
w.setSeed(seed.toHexString());
w.setCreationTime(Utils.dateTimeFormat(seed.getCreationTimeSeconds()));
w.setMnemonics(Utils.join(seed.getMnemonicCode()));
여기서 CreationTime이 1970년1월1일보다 큰 값인지를 확인하여 값이 맞지 않는다면 현재 시간을 CreationTime으로 입력합니다. (참고 유닉스 시간, https://ko.wikipedia.org/wiki/%EC%9C%A0%EB%8B%89%EC%8A%A4_%EC%8B%9C%EA%B0%84)
if ( (seed.getCreationTimeSeconds()-1525190911)<1) {
seed.setCreationTimeSeconds(System.currentTimeMillis());
}
MyWallet w 객체에 값 설정이 완료되면 walletList.add() 하여 저장합니다.
walletList.add(w);
#6. main 메소드에서 화면으로 출력합니다.
public static void main(String[] args) {
try {
MyWallet2 myWallet = new MyWallet2();
WalletAppKit kit = myWallet.initialWallet(myWallet.getTestNetParam());
myWallet.synchBlockchain(kit);
List< MyWallet> walletList = myWallet.getWalletAddress(kit);
for (MyWallet w : walletList) {
System.out.println(w.toString());
}
Thread.sleep(1000);
kit.stopAsync();
kit.awaitTerminated();
}
catch (Exception ex) {
ex.printStackTrace();
}
}
#7. 실행결과 보기
Eclipse에서 완성된 프로그램 소스를 저장한 후에, 실행을 합니다.(Ctrl + F11)
총 5번을 실행시켰습니다. HD 지갑이라 여러 개의 주소가 보이고 모두 같은 Seed와 니모닉 Mnemonics이 보입니다.
HD 지갑을 완성했습니다.
*다음에는 HD지갑에서 실제 비트코인을 주고 받는 것을 구현해보겠습니다.
12글자라고 하기보단 12단어라고 해야 맞지 않을까요?
수정했습니다. 감사합니다.^^
You received 1.42 % upvote as a reward From round 2 on 2018.05.10! Congrats!