HWI (Bitcoin Hardware Wallet Interface)
HWI(Bitcoin Hardware Wallet Interface)는 Bitcoin Core 팀에서 만든 하드웨어 월렛 인터페이스 오픈 소스 프로그램으로 소스 코드는 https://github.com/bitcoin-core/HWI 에 있다. HWI는 Python 3로 작성되어 있으며, HWI 설치 후 hwilib 폴더에 파이썬 라이브러리들이 있고, HWI 폴더에 hwi.py 라는 실행 파일이 있다.
HWI 설치
HWI 깃허브 레파지토리에 각 OS별 설치 방법이 자세히 나와 있는데, Ubuntu에 설치하는 방법은 다음과 같다.
(1) HWI 설치 $ sudo apt install libusb-1.0-0-dev libudev-dev python3-dev $ git clone https://github.com/bitcoin-core/HWI.git $ cd HWI $ pip3 install . (2) udev rules 추가 $ cd hwilib (HWI/hwilib folder) $ sudo cp udev/*.rules /etc/udev/rules.d/ && \ sudo udevadm trigger && \ sudo udevadm control --reload-rules && \ sudo groupadd plugdev && \ sudo usermod -aG plugdev `whoami`
HWI 사용
HWI 설치가 완료된 후, Ledger, Trezor 등과 같은 하드웨어 월렛을 연결하고, PIN 번호를 넣어 Unlock 한 후, 아래와 같은 HWI 명령은 하드웨어 월렛을 Detect 한다. enumerate 명령의 출력에서 중요한 것은 fingerprint 로서 이는 그 하드웨어의 ID 역활을 한다.
$ ./hwi.py enumerate [{"type": "ledger", "model": "ledger_nano_x", "label": null, "path": "1-8:1.0", "fingerprint": "d8fb486b", "needs_pin_sent": false, "needs_passphrase_sent": false}]
하드웨어가 Detect 되었으면, H/W Wallet의 public key들(즉, 비트코인 주소들)을 가져올 수 있는데, 아래 명령은 Bitcoin Testnet을 사용하는 것으로 이를 위해 우선 하드웨어 월렛에 Bitcoin Testnet App을 설치해야 하고 또한 Bitcoin Testnet App을 실행해 주어야 한다.
다음으로 HWI 프로그램에서 하드웨어 fingerprint를 -f (혹은 --fingerprint) 옵션으로 지정하고, getkeypool 명령을 사용하여 주소 키 범위 (0 ~ 10)를 지정하면 아래와 같이 Extended Public Key를 포함하는 Descriptor를 출력하게 된다. 이 Descriptor를 사용하면 Child Key들을 생성하고 Watch only 주소들을 자동 생성할 수 있다.
$ ./hwi.py --chain test -f d8fb486b getkeypool 0 10 [{"desc": "wpkh([d8fb486b/84h/1h/0h]tpubDDVAHV7k1K3tUQbjf4D1bfuNv7wKQVMiXWAwWCpUqgrBY5Cvpki5jf6rjPd1VkKupWhcVJxFMU67YfnQ1oJaaT9WFgswyQxziJR7jEDjvxX/0/*)#9y4rpzay", "range": [0, 10], "timestamp": "now", "internal": false, "keypool": true, "active": true, "watchonly": true}, {"desc": "wpkh([d8fb486b/84h/1h/0h]tpubDDVAHV7k1K3tUQbjf4D1bfuNv7wKQVMiXWAwWCpUqgrBY5Cvpki5jf6rjPd1VkKupWhcVJxFMU67YfnQ1oJaaT9WFgswyQxziJR7jEDjvxX/1/*)#5sszuhdu", "range": [0, 10], "timestamp": "now", "internal": true, "keypool": true, "active": true, "watchonly": true}]
HWI 프로그램으로부터 Descriptor를 얻은 후에는, bitcoin-cli에서 importmulti 명령을 사용하여 주소들을 생성하고 import 할 수 있다. 아래는 새로운 월렛을 만들고 그 안에 공개키 주소들을 import하는 예이다. 이렇게 Import된 주소로 코인을 보내면, 이는 개인키를 가진 하드웨어 월렛으로 코인을 보내는 것과 같게 된다.
$ bitcoin-cli --named createwallet wallet_name="ledger1" disable_private_keys=true $ bitcoin-cli -rpcwallet=ledger1 importmulti '[{"desc": "wpkh([d8fb486b/84h/1h/0h]tpubDDVAHV7k1K3tUQbjf4D1bfuNv7wKQVMiXWAwWCpUqgrBY5Cvpki5jf6rjPd1VkKupWhcVJxFMU67YfnQ1oJaaT9WFgswyQxziJR7jEDjvxX/0/*)#9y4rpzay", "range": [0, 10], "timestamp": "now", "internal": false, "keypool": true, "active": true, "watchonly": true}, {"desc": "wpkh([d8fb486b/84h/1h/0h]tpubDDVAHV7k1K3tUQbjf4D1bfuNv7wKQVMiXWAwWCpUqgrBY5Cvpki5jf6rjPd1VkKupWhcVJxFMU67YfnQ1oJaaT9WFgswyQxziJR7jEDjvxX/1/*)#5sszuhdu", "range": [0, 10], "timestamp": "now", "internal": true, "keypool": true, "active": true, "watchonly": true}]' $ bitcoin-cli -rpcwallet=ledger1 getaddressesbylabel "" { "tb1q53682xqc93yylv8syydr9qw8crxfclj700twru": { "purpose": "receive" }, "tb1qyxpde6h75qcg59tct6fz4m6a4rf6zlyty87tc2": { "purpose": "receive" }, "tb1q06p88r7p9u8k49576va5zjn3qqhwm3csvswcy4": { "purpose": "receive" }, ...생략... }
PSBT과 하드웨어 월렛 사용
PSBT를 활용하여 서명을 요하는 트랜잭션을 생성하고 이를 하드웨어 월렛에서 서명한 후, 완료된 트랜잭션을 전송할 수 있다. PSBT 트랜잭션 생성은 UTXO를 지정하는 createpsbt 명령을 사용할 수도 있고, UTXO를 자동으로 선택하도록 하는 walletcreatefundedpsbt 명령을 사용할 수도 있다. 하드웨어에서의 서명은 HWI의 signtx 명령을 사용하여 PSBT를 서명하게 되고, 그 결과를 finalize 하고(finalizepsbt) Raw Transaction을 전송하면 된다.
// (1) psbt 생성 $ psbt=$(bitcoin-cli -rpcwallet=ledger1 walletcreatefundedpsbt '[]' '{"tb1q8z7fkxccgagc3y77gdp73cuygqh0uajujw5w5d":0.0009}' | jq -r '.psbt') // (2) psbt 체크 //$ bitcoin-cli decodepsbt $psbt //$ bitcoin-cli analyzepsbt $psbt // (3) 하드웨어 월렛에서 서명 $ ./hwi.py --chain test -f d8fb486b signtx $psbt {"psbt": "cHNidP8BAHECAAAAABEGBB...", "signed": true} // (4) 완성된 TX 보내기 $ signedpsbt="cHNidP8BAHECAAAAABEGBB..." $ hex=$(bitcoin-cli finalizepsbt $signedpsbt | jq -r '.hex') $ bitcoin-cli sendrawtransaction $hex
스텝(3) 하드웨어 월렛 서명에서 코인이 Testnet 코인이므로 --chain test 옵션을 지정해 주어야 하며, 이 명령이 실행되면 하드웨어 월렛에서 Approve 버튼을 눌러 주어야 한다. 정상적으로 처리되면, 출력 결과에서 "signed"가 true가 리턴된다.