MetaMask 지갑 뜯어보기
안녕하세요 케이스타라이브 캡틴체인입니다.
오랜만에 글을 올리네요.. 새해에도 Day34 기술블로그에 많은 정보를
올리고자 화이팅하며 시작합니다.
제가 이전 글에도 메타마스크라는 지갑을 소개해 드린적이 있었습니다.
이더리움 개발을 할 때나 범용적으로 많이 사용하는 지갑 중에 하나 인데요.
지갑을 사용하면서 개인적으로 여러가지 궁금증이 생겼습니다. 글쓰기
초보인지라 반말과 존댓말을 섞어가며 혼란스럽게 쓸 예정이니 감정 상함
주의 부탁드립니다 -_-;
첫 째, 지갑은 어떻게 구성이 되어 있을까?
둘 째, 메타마스크 지갑은 메인넷에서 사용할 만큼 과연 안전한 것인가?
위의 두 가지 질문이 저에게 있었습니다. 분석하게 된 계기이기도 합니다.
먼저, 두 가지의 물음 중 첫 번째 질문에 대한 분석을 진행해 보도록 하겠습니다.
첫 째, 지갑은 어떻게 구성이 되어 있을까?
먼저 Chrome 또는 Firefox 브라우저가 설치 되어있어야하고 플러그인 형태로 설치가 가능하다.
설치가 끝나면 브라우저 상단 우측 부분에 여우모양 아이콘이 생기게 되며 클릭하게 되면 아래와 같은 웹 앱 형태의 팝업이 나올 것이다.
우선 계정을 생성하는 과정과 Ether Faucet을 통해 테스트넷에서 이더를 받는 과정은 생략하도록 하겠다. 우리가 현재 이 부분에서 주요하게 봐야할 곳은 로그인 패스워드이다.
이 로그인 패스워드는 어떠한 역할을 하고 있을까이다.
먼저 메타마스크 아이콘에 마우스 커서를 올려놓고 오른쪽 버튼을 클릭해보세요!
확장 프로그램 관리 메뉴를 클릭 하시면 설정 탭이 열리게 되고 아래와 같은 화면이 나오게 됩니다.
여기서 우측 상단에 개발자 모드를 On 하시고 뷰 검사 메뉴에서 [백그라운드 페이지] 링크를 클릭 하시면 해당 메타마스크 웹앱의 디버깅 창이 팝업으로 나오게 된다. 이 부분이 실제 메타마스크의 백그라운드 디버깅 이라고 보면 될 것이다.
- MetaMask의 Private Key 관리는 어떻게 할까?
MetaMask의 사용자의 키는 어떻게 관리하고 있을까 에 대한 부분을 먼저 다루고자 한다. 실제로 MetaMask는 이더/토큰을 누군가의 송금을 하거나 스마트 컨트랙트의 특정 함수를 실행시키기 위해선 SendRawTransaction을 하기 전에는 해당 트랜잭션 Data를 Signing을 해야한다. Signing을 할 때 필요한 정보가 바로 Private Key인데 MetaMask는 Chrome Local Storage 에서 이 값을 관리하고 있다.
How to use the Vault Decryptor with the MetaMask Vault Data
메타마스크 공식 FAQ 중 위의 글을 확인하면 어떻게 니모닉 코드를 추출할 수 있는지 방법이 나와있다.
- 니모닉 코드 추출하기
조금 전에 연 MetaMask의 백그라운드 디버깅 창 콘솔에 아래와 같은 명령어를 넣어보자.
chrome.storage.local.get('data', result => {
var vault = result.data.KeyringController.vault
console.log(vault)
})
그럼 JSON 형식으로 된 데이터가 추출 되는 것을 확인 할 수 있다. ( 과거 MetaMask 버전은 이 정보가 Chrome Storage 가 아닌 Window Local Storage에 저장이 되어있었다. )
위의 추출된 데이터를 Vault Data라고 부르는데 이 Vault Data를 통해 내가 MetaMask 로그인 시 입력한 패스워드와 결합하여 특정 Decryptor 소스를 통해 니모닉 코드를 추출 할 수 있다.
위의 Site가 Vault를 Decryptor 할 수 있는 곳이다. 온라인 상 해독을 할 수 있는 곳으로 아래와 같이 Textarea에 추출된 Vault Data를 넣고 내가 MetaMask에 지정한 패스워드를 Input 박스에 넣어 [Decrypt] 버튼을 눌러 해독하면 아래 결과로 Array Type의 데이터가 추출 되는 것을 확인할 수 있다.
결과값 >> [{"type":"HD Key Tree","data":{"mnemonic":"{12자리의 니모닉 코드 단어}","numberOfAccounts":3,"hdPath":"m/44'/60'/0'/0"}},{"type":"Simple Key Pair","data":["133b0a6a6144a8a085d10446f3404610f55ce3e5f8c1f654651fcf6d79d0c8a7"]}]
위의 정보를 통해 HDWallet(계층 결정적 지갑)으로 생성된 계정이 총 3개라는 것을 확인할 수 있고 니모닉 코드 12 자리를 확인 할 수 있다. 근데 이 과정을 온라인 상으로 추출한게 영 찝찝하다. 내가 지정한 패스워드와 Vault Data가 기록이 남는다면 해킹을 받을 위험의 소지가 충분히 있지 않을까 해서 말이다.
- Vault Decryptor 소스 받기
참고 깃헙 : https://github.com/nujabes403/metamaskVaultDecrypt
nujabes403 님의 깃헙에 가면 MetaMask Vault를 해독할 수 있는 Javascript 소스가 있다는 것을 확인할 수 있다. 사용법은 간결하게 READEME.md 파일을 보면 알 수 있는데….
decrypt.js 파일을 전체 복사해서 Chrome Console 에 붙여 넣은 뒤 Chrome 콘솔에서 위의 Javascript 코드를 입력해 주면 끝이다.
붙여 넣은 후 console에 아래와 같이 추가적으로 코드를 넣어주면 결과가 온라인을 통해 했던 것하고 동일하게 나오는 것을 확인할 수 있다.
decrypt("{MetaMask 패스워드}", '{Vault 값}').then(console.log)
위의 같이 console 입력해 주면 아래와 같은 결과가 나온다.
여기까지가 MetaMask가 내 Local에 존재하는 PrivateKey를 어떻게 관리하고 있는지에 대한 부분이다.
- MetaMask 정보 동기화 부분
MetaMask 에서 작업을 하다보면 Ether나 트랜잭션 히스토리들의 상태가 어떻게 반영이 되고 있는지 궁금할 때가 있다. 그 부분을 살펴 보기 위한 중요한 단서는 바로 네트워크 스냅 샷이다. 디버깅 창에 [Network] 탭으로 가서 확인해보도록 하자.
- MetaMask 네트워크 확인해보기
우선 기본적인 Background 동작으로 일정시간 같은 패턴으로 네트워크에 Query를 날리는 것을 확인할 수 있다.
- https://api.infura.io/v1/jsonrpc/rinkeby/eth_blockNumber?params=%5B%5D
- https://api.infura.io/v1/jsonrpc/rinkeby/eth_getBlockByNumber?params=%5B%220x38585a%22%2Ctrue%5D
위에서 확인할 수 있는 단서는 먼저 MetaMask는 이더리움 네트워크에 접속을 하기 위해서 클라우드 서비스인 Infura API 서비스를 사용 중이다. 그 다음으로는 약 20초 간격의 반복적인 패턴으로 현재 Block 번호와 Block 번호를 통해 현재 블록에 대한 정보를 반복적으로 가져오는 것을 확인할 수 있다.
// eth_blockNumber 16진수의 결과 값 ( Rinkeby 네트워크의 최근 생성 블록 번호 )
{"jsonrpc":"2.0","id":0,"result":"0x385860"}// 위의 Block 번호로 조회한 Block Header 정보 ( 난이도, 로그정보, State 머클 루트 해시, 트랜잭션 정보, 이전 해시, 현재 해시, Receipt 머클루트 해시, 타임스탬프, 넌스, 가스리밋 등 )
{"jsonrpc":"2.0","id":0,"result":{"difficulty":"0x2","extraData":"0xd883010900846765746888676f312e31312e34856c696e75780000000000000008197fd847c1ef3a3afe3eeff5190f76e2b90fec20c2fe2be402ee1364795ba96a639357b8a6ef974fd2801b23e88a230359435a7675ac735d0a984a99e13fa301","gasLimit":"0x6acfc0","gasUsed":"0x33c19a","hash":"0x4fe5ffbcf43fe2f5230f897c1f68bcdc68f2d0e888010e3c74b924c473f9dbca","logsBloom":"0x00000000000800000000000000000000000000000000000000000000200000000000000000142000000000042200000000000000c00000400020000050000020000000800000000000000020008000003020080000000000000000000040000000000000001000002000000000800000000000000000000000000200000000000000008000000000000000000000000000000000040000000000080000000010002000000000000000000000400000000002000000000000100400000000001000000000000008000010000000000000000000000000000080000000000010000000000000000000000000000000008000000000000000001800080000000020","miner":"0x0000000000000000000000000000000000000000","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","number":"0x385860","parentHash":"0x4e4c698ce83703839040f8bbce11a408b6355846258acec988bf8fcac962790c","receiptsRoot":"0xca848159d85db5ae1a6909775a4eaa877b2ca8e005aba657321066293dd11f8c","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x741","stateRoot":"0x4986d1462381ba8c0a628daea527116919c2e3426c21b3f7fe819f57f57d26a0","timestamp":"0x5c3d4241","totalDifficulty":"0x67cc04","transactions":[{"blockHash":"0x4fe5ffbcf43fe2f5230f897c1f68bcdc68f2d0e888010e3c74b924c473f9dbca","blockNumber":"0x385860","from":"0x13a9a2ae288b5ee9de55d5355b3344fb1a601934","gas":"0xf4240","gasPrice":"0x2540be400","hash":"0x88ea2a522278b8e992d6fdae44e2618125f9ffaf55ab3432758347350bd7431e","input":"0x4352fa9f000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000930b647320f738d92f5647b2e5c4458497ce3c9500000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000126aeb350f1c35c","nonce":"0xb340","r":"0xc6cbf6a6fd58cadb260320cce432d62f50d9f2d7b3fc0114e572ea9dcc6c144d","s":"0x3251fed9e8ae7478b1d1c88e731be604916e0cdabcaa56630d017e7497fe4806","to":"0xd2b1eca822550d9358e97e72c6c1a93ae28408d0","transactionIndex":"0x0","v":"0x2b","value":"0x0"},{"blockHash":"0x4fe5ffbcf43fe2f5230f897c1f68bcdc68f2d0e888010e3c74b924c473f9dbca","blockNumber":"0x385860","from":"0x8a37b79e54d69e833d79cac3647c877ef72830e1","gas":"0x40f61","gasPrice":"0x3b9aca00","hash":"0x39e969ff652b2696e4f02d5ed51ac1ffd846617ff74ace886fa9324d2f939b33","input":"0x9956b436000000000000000000000000d372b24b82ba42450af69386f2adc78e1d300bc80000000000000000000000000000000000000000000000000002e7d13fc3303b000000000000000000000000000000000000000000000000029afa2e928e53b20000000000000000000000000000000000000000000000000000000000000000","nonce":"0x95d5f","r":"0x90ba07627141293b2d02d85a55f941a8aef8fc1d3154358c2398cec77790bc79","s":"0x3c895e5fb7cde4b6575ee472ade1b6ba5a00812ef7367028fcc7960bf6c1ccae","to":"0x54a298ee9fccbf0ad8e55bc641d3086b81a48c41","transactionIndex":"0x1","v":"0x1c","value":"0x0"},{"blockHash":"0x4fe5ffbcf43fe2f5230f897c1f68bcdc68f2d0e888010e3c74b924c473f9dbca","blockNumber":"0x385860","from":"0x4e271c99560e27f6150160dce7dcfc1eebe1b32e","gas":"0x30d400","gasPrice":"0x3b9aca00","hash":"0x912d7f2d85b4f81cd63f047cfea47f48fc20e3eae6929d1710fa94028bd65dcb","input":"0xd4807fb2","nonce":"0x7b","r":"0x1d979429f7ff42a7669b32e3d3589fa326455528ef32c72a3e0ade69e8f2253b","s":"0x63c21950ba33099bc5d66f6e83f676d1ad1d6bdbe5c16a12ec17cc332e195334","to":"0xeb0ef46b5771d523402234ff0d7596d2c62411de","transactionIndex":"0x2","v":"0x2c","value":"0x0"},{"blockHash":"0x4fe5ffbcf43fe2f5230f897c1f68bcdc68f2d0e888010e3c74b924c473f9dbca","blockNumber":"0x385860","from":"0x921f2cf348b8b45d6cd5eaf139d30303e6b9646f","gas":"0x2dc6c0","gasPrice":"0x3b9aca00","hash":"0x9a85612bfb36f905b5ba8f356349312f9f69e7a14cfd8586522d5f32118073e3","input":"0x09ac86d32bf12aa2a337946aab540c3e5e3e1e70a42d2881174214f3d01e3317358526051f7243b109c881d775a7f684f9c053c23e4ab401b476ab388bd9544084617610","nonce":"0x58d9","r":"0x1d208ad75a62c15361df487870dcb97d65a883e08908497359e23ce54e2bf1bf","s":"0x7d0fde213a054ec1df0bcf9706826dcb316e7cfa95c17cdac8b99602b6e22b07","to":"0x13a59ba5aaa130482ec11d9e4ba8bb688b1c38a4","transactionIndex":"0x3","v":"0x1c","value":"0x0"},{"blockHash":"0x4fe5ffbcf43fe2f5230f897c1f68bcdc68f2d0e888010e3c74b924c473f9dbca","blockNumber":"0x385860","from":"0xbe1085bc3e0812f3df63deced87e29b3bc2db524","gas":"0x122cf","gasPrice":"0x3b9aca00","hash":"0xbbd1d48b6127cfcb8e2e23ba5705945315a7ad6c6e965b7ff122260b4e8879ff","input":"0xbaa47694b3280c53e95705940c44529648cfe7b4bc41c10014695f9d1d20419a9b7ee536","nonce":"0x143e99","r":"0x377a794d6718d5e8d9d5611a955cb1ce677b32d891237cdd617ba12d74f36dd4","s":"0x644b03836148322f80c8857205edb6096f8866d43b8a001dc9cc538505eb750","to":"0x40af244c94e679aebf897512720a41d843954a29","transactionIndex":"0x4","v":"0x1b","value":"0x0"},{"blockHash":"0x4fe5ffbcf43fe2f5230f897c1f68bcdc68f2d0e888010e3c74b924c473f9dbca","blockNumber":"0x385860","from":"0xbe1085bc3e0812f3df63deced87e29b3bc2db524","gas":"0x1230f","gasPrice":"0x3b9aca00","hash":"0x877c40f5dfbd18c84a448fc5eb6732ad5eed14cccd928ab554511b2c14f10a2b","input":"0xbaa476946eba419f6c2e36c3b39d451391b66bfc62209f6b77f637e7bd85336694cecf2c","nonce":"0x143e9a","r":"0x7dc37d957088ca35c796f7b578a14b370024902e425d7437cf68a81af0187144","s":"0x1692ccb4968ae00743a5b63775b5463b99f849a45c27a8f75f94e4fcdf0e7083","to":"0x40af244c94e679aebf897512720a41d843954a29","transactionIndex":"0x5","v":"0x1c","value":"0x0"},{"blockHash":"0x4fe5ffbcf43fe2f5230f897c1f68bcdc68f2d0e888010e3c74b924c473f9dbca","blockNumber":"0x385860","from":"0xbe1085bc3e0812f3df63deced87e29b3bc2db524","gas":"0x122cf","gasPrice":"0x3b9aca00","hash":"0xdfa41286c8a600ce1e40c68fbcf306f1a195bedbae6d97da37a48854b4947f36","input":"0xbaa476940ebc1342d4d599ee410aa9f830ae68ab84a4b7bf1ae1d420ec1000342eddb9af","nonce":"0x143e9b","r":"0x25fc5001584c6c1ba58bffcb806d76c4885bf54706249352ef557463100fe83b","s":"0x5a18ca87e087485b36807bfb49c3025c8cb3057bb25a78eab5d5529408cb0b22","to":"0x40af244c94e679aebf897512720a41d843954a29","transactionIndex":"0x6","v":"0x1c","value":"0x0"}],"transactionsRoot":"0xdcebc6055ede89b62660f4a0005ebed458557846dd8f1ed4b5cdb7e6021ed933","uncles":[]}}
그리고 10분 간격으로 이더의 USD Ticker 정보와 MetaMask 에서 각 이더리움 네트워크의 네트워크 상태 체크 정보라던지 특정 시간 주기에 따라 Http Request 된다는 것을 확인할 수 있다
- https://api.infura.io/v1/ticker/ethusd (이더리움 USD 가격정보 )
- https://api.infura.io/v1/status/metamask (이더리움 네트워크 상태체크)
- https://api.infura.io/v2/blacklist ( MetaMask에서 관리하는 블랙리스트 )
- MetaMask 활성화 해보기
위의 이미지처럼 MetaMask 아이콘을 클릭하여 팝업을 띄우면 프로그램이 Resume 되는 순간 엄청난 Http Request들의 Query가 날라가는 것을 볼 수 있다. 이 또한 특정 시간의 간격으로 일정한 패턴대로 Query가 날라간다.
요약하자면 아래와 같은 Request 정보로 질의하는 것을 확인할 수 있다.
- https://api.infura.io/v1/jsonrpc/rinkeby/eth_getTransactionCount?params=%5B%220x8bbd95a66902a0dad03d1850926ab062d9202fb6%22%2C%220x3858a0%22%5D ( 내주소의 트랜잭션 카운트 구하기 )
-> 아마 새로운 트랜잭션 생성할 때 Nonce를 구하기 위해서 지속적으로 확인하는 값인 것 같다.
2. https://api.infura.io/v1/jsonrpc/rinkeby/eth_getBalance?params=%5B%220x8bbd95a66902a0dad03d1850926ab062d9202fb6%22%2C%220x3858a1%22%5D ( 접속 네트워크 지갑에 들어있는 이더리움 밸러스 체크 )
- 이더리움 송금 시 네트워크 확인해보기
이번엔 MetaMask에서 송금 트랜잭션을 발생 시켜서 어떠한 과정을 통해 송금이 되는지 확인해보자.
위의 Account1 계정에서 새 계정 2에게 1이더를 송금해보도록 하겠다.
Gas Transaction Fee의 경우 Fast 로 설정한 뒤 [다음]을 눌러 트랜잭션을 진행보자
최종적인 내용을 확인하고 [승인]을 눌러 바로 네트워크를 확인해보자
이후 41개의 HTTP Request 가 정신없이 실행이 되지만 실제로 간추려보면 아래와 같은 작업으로 Send Transaction 한다는 것을 확인할 수 있다.
- Nonce 구하기 ( 트랜잭션에 필요한 Nonce를 만들기 위해 eth_getTransactionCount를 수행함 )
- 네트워크가 아닌 메타마스크 프로그램 내부에서 SignedTransaction 을 진행함
- SendRawTransaction을 통해 서명된 트랜잭션 Raw 데이터를 보냄
- 트랜잭션 처리가 완료될 때까지 트랜잭션 해시 값을 eth_getTransactionByBash 로 계속 보냄
블록에 트랜잭션 해시가 포함되고 처리가 완료된 상항은 위와 같음
5. 현재 지갑의 이더 잔고를 확인함 (eth_getBalance)
6. 이더 트랜잭션 Receipt 를 호출 함 (eth_getTransactionReceipt)
위의 과정으로 MetaMask에서 발생된 트랜잭션이 수행되는 것을 확인할 수 있다.
추가적으로 MetaMask 의 트랜잭션 히스토리의 경우 Web SQL이나 별도의 Storage를 활용하는 것으로 예상된다.
위에서 우리가 조금 더 살펴보아야 할 부분은 바로 2번 과정이다. 3번 SendRawTransaction 과정을 수행하기 전에 온라인 상태가 아닌 오프라인상태에서 Transaction 데이터를 Private Key로 Signing 해주는 것이다.
- 트랜잭션 데이터 사이닝 해보기 ( via Web3 라이브러리 )
MetaMask SendRawTransaction 을 진행하기 전 Signed Transaction을 오프라인 상에서 만들어보도록 하겠다. 먼저web3 라이브러리를 활용할텐데 API 는 아래와 같다.
https://web3js.readthedocs.io/en/1.0/web3-eth.html#signtransaction
예시 ) web3.eth.signTransaction API 로 rawTx를 만들어보는 예제
web3.eth.signTransaction({
from: "0xEB014f8c8B418Db6b45774c326A0E64C78914dC0",
gasPrice: "20000000000",
gas: "21000",
to: '0x3535353535353535353535353535353535353535',
value: "1000000000000000000",
data: ""
}).then(console.log);
> {
raw: '0xf86c808504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a04f4c17305743700648bc4f6cd3038ec6f6af0df73e31757007b7f59df7bee88da07e1941b264348e80c78c4027afc65a87b0a5e43e86742b8ca0823584c6788fd0',
tx: {
nonce: '0x0',
gasPrice: '0x4a817c800',
gas: '0x5208',
to: '0x3535353535353535353535353535353535353535',
value: '0xde0b6b3a7640000',
input: '0x',
v: '0x25',
r: '0x4f4c17305743700648bc4f6cd3038ec6f6af0df73e31757007b7f59df7bee88d',
s: '0x7e1941b264348e80c78c4027afc65a87b0a5e43e86742b8ca0823584c6788fd0',
hash: '0xda3be87732110de6c1354c83770aae630ede9ac308d9f7b399ecfba23d923384'
}
}
위의 Web3 1.0 Spec 과 예제 중 Bold 체로 되어있는 부분을 주시하자. 먼저 전제는 web3 객체를 만들어줄 때 기본적으로 접속해 있는 사람의 계정은 unlock 상태여야 한다.
저자도 이해를 돕기 위해 간단하게 코드로 작성해 본 UI 화면이다.
먼저 트랜잭션 데이터를 아래와 같이 TextArea 영역에 넣어보자
트랜잭션 데이터는 단순한 송금 데이터이다.
{"from": "0x8BBd95a66902a0DaD03D1850926Ab062D9202FB6","gasPrice": "20000000000","gas": "21000","to": "0x5cB484E7b57a05F53b65e0760c22f88Fe9bf92DF","value": "1000000000000000000","data": ""}
위의 JSON 구조로 된 데이터를 넣고 버튼을 눌렀을 때 작동되는 코드는 아래와 같다.
... 중략 ...const signTransacitonData = await web3.eth.signTransaction(JSON.parse(this.transactionData))... 중략 ...
web3.eth.signTransaction 을 통해 해당 트랜잭션 데이터를 JSON parse 하여 input으로 넣어주고 rawTx를 뽑아내는 과정이다. 콘솔에 찍은 결과를 보면 아래와 같다.
위와 같이 raw 데이터가 생성되는 것을 확인 할 수 있다. 이 raw 데이터를 sendRawTransaction를 활용하여 트랜잭션을 발생시켜 보자. 우선 rawTx를 복사 한 후 또 다른 sendRawTransaction 을 위한 TextArea에 값을 붙여넣기 하겠다.
해당 코드는 아래와 같다.
const jsonData = JSON.stringify({
"jsonrpc" : "2.0",
"method" : "eth_sendRawTransaction",
"params" :[this.rawTransactionData],
"id" : "1"
})const result = $.ajax({
url :
'https://rinkeby.infura.io/v3/a0790b14a0164dd7b6c3d6c28d72eb8e',
async : false,
type : 'POST',
data : jsonData,
dataType : 'application/json'
})console.log(result.responseText)
해당 sendRawTransaction의 경우 web3 API Spec으론 존재하지만 MetaMask에서 Infura에 eth_sendRawTransaction을 JSON-RPC의 Spec으로 처리한 것과 같이 처리해 보도록 하겠다.
JSON-RPC 스펙은 위에 사이트에서 확인가능하다.
sendRawTransaction으로 Infura를 통해 Rinkeby 네트워크 트랜잭션을 요청하게 되면 아래와 같은 transactionHash 값이 정상적으로 출력되는 것을 확인 할 수있다.
{"jsonrpc":"2.0","id":"1","result":"0x7af376e0868676445c194306c402a2a3d1a442deeb752936b5b3ca153f6c0958"}
해당 transactionHash 값을 etherscan 에서 확인해보자
위의 이미지와 사이트를 통해 정상적으로 처리된 것을 확인할 수 있다.
( * 주의 : 한번 처리된 rawTx를 재사용하면 안된다. rawTx 에는 트랜잭션 발생 주체인 계정의 Nonce 값이 포함되기 때문에 같은 Nonce로는 처리가 불가능 하기 때문이다 )
정리해보자. 메타마스크를 통해 계정 A에서 계정 B로 처리되는 과정을 분석하여 동일하게 계정의 트랜잭션 카운트의 논스를 구해서 보내고자 하는 이더의 양과 누구로부터 누구에게 보낼것인지에 대한 전반적인 트랜잭션 데이터를 signedTransaction이라는 web3 spec을 통해 오프라인으로 만들어주었다.
만들어준 rawTx를 JSON-RPC Spec에 존재하는 sendRawTransaction 으로 send 하였고 바로 transactionHash 값이 출력되었다.
MetaMask는 이 트랜잭션이 블록에 포함될 때까지 트랜잭션을 주기적으로 체크해준다.
블록이 생성되는 순간 현재 사용자의 ether 잔고를 확인하고 Pending 되어있던 트랜잭션을 “승인완료됨” 이라고 처리 해준다. 이러한 과정으로 MetaMask가 송금 트랜잭션을 처리하는 구나 라고 추측해 볼 수 있을 것이다(추측이 아니라 분석을 통한 사실이긴 하다)
그렇다면 컨트랙트를 생성하거나 생성된 컨트랙트의 함수를 실행할 수도 있겠네?
대답은 “예, 그렇습니다" 라고 얘기할 수 있다.
- 간단한 컨트랙트를 sendRawTransaction 을 통해 배포해보기
먼저 Remix를 통해 간단한 HelloWorld 컨트랙트를 작성해보겠다. 코드는 단순한다. setGreeting 함수 (send 함수)와 sayHello 함수 ( call 함수 )만 존재할 뿐이다.
pragma solidity ^0.4.25;contract HelloWorld {
string private greeting;
constructor() public {
greeting = "Hello!";
}
function sayHello() public view returns(string) {
return greeting;
}
function setGreeting(string _greeting) public {
greeting = _greeting;
}
}
해당 컨트랙트를 Remix 에서 ByteCode로 전환하면 아래와 같다.
Remix에 [Compile] 탭에서 [Details] 버튼을 누르게 되면 BYTECODE 섹션에 object 값이 있다 이 부분이 실제 바이트코드로 변환된 스마트 컨트랙트의 값이다. 이걸 복사해서 signedTransaction 데이터를 아래와 같이 붙여넣어 구성해보자 ( * 주의 : 앞에 0x 를 Prefix 로 꼭 붙이자 )
{
"from": "0x8BBd95a66902a0DaD03D1850926Ab062D9202FB6",
"gasPrice": "2000000000",
"gas": "4700000",
"to": "",
"value": "",
"data": "0x608060405234801561001057600080fd5b506040805180820190915260068082527f48656c6c6f21000000000000000000000000000000000000000000000000000060209092019182526100559160009161005b565b506100f6565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061009c57805160ff19168380011785556100c9565b828001600101855582156100c9579182015b828111156100c95782518255916020019190600101906100ae565b506100d59291506100d9565b5090565b6100f391905b808211156100d557600081556001016100df565b90565b6102a7806101056000396000f30060806040526004361061004b5763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663a41368628114610050578063ef5fb05b146100ab575b600080fd5b34801561005c57600080fd5b506040805160206004803580820135601f81018490048402850184019095528484526100a99436949293602493928401919081908401838280828437509497506101359650505050505050565b005b3480156100b757600080fd5b506100c061014c565b6040805160208082528351818301528351919283929083019185019080838360005b838110156100fa5781810151838201526020016100e2565b50505050905090810190601f1680156101275780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b80516101489060009060208401906101e3565b5050565b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156101d85780601f106101ad576101008083540402835291602001916101d8565b820191906000526020600020905b8154815290600101906020018083116101bb57829003601f168201915b505050505090505b90565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061022457805160ff1916838001178555610251565b82800160010185558215610251579182015b82811115610251578251825591602001919060010190610236565b5061025d929150610261565b5090565b6101e091905b8082111561025d57600081556001016102675600a165627a7a72305820a94fc1ceda3a0111f688b54ef996916b223ec7e93f2efdc65332d30c2e66ed580029"
}
위의 부분을 signedTrasaction으로 실행하게 되면 아래와 같이 rawTx가 만들어진다.
위의 raw 값을 복사하여 sendRawTransaction을 실행하고 결과를 확인해보자.
transactionHash 값이 console에 출력되고 해당 출력값을 복사하여 etherscan에서 확인해보면…
컨트랙트가 정상적으로 Creation 된 것을 확인할 수 있을 것이다.
해당 컨트랙트가 제대로 생성 되었는지 프로그램상 sayHello를 호출하기 위해 컨트랙트 주소 (Contract Address)를 복사한 뒤 Remix 에서 [At Address]로 객체를 생성해보자.
At Address로 Rinkeby에 배포된 컨트랙트 주소를 넣어 생성하고 sayHello를 실행하면 Hello! 라고 나오는 것을 확인할 수 있다.
- 배포한 HelloWorld 컨트랙트를 MetaMask로 실행 해보기
마지막으로, MetaMask를 통해 배포된 HelloWorld 컨트랙트를 실행하여 보겠다. 우선 송금이 아닌 컨트랙트의 특정 function을 실행하기 위해선 MetaMask HexData 필드를 활성화시켜줘야한다.
설정 정보에서 Hex 데이터를 활성화 시킨다.
그리고 두 번째로 web3 라이브러리 스펙 중 encodeFunctionCall 스펙이 있는데 이걸 참조하여 실행하고자 하는 Function과 Parameter를 직렬화시켜 바이트 코드를 생성해보겠다.
https://web3js.readthedocs.io/en/1.0/web3-eth-abi.html#encodefunctioncall
코드는 간단하다. 아래와 같다.
this.encodeData = this.web3.eth.abi.encodeFunctionCall({
name: 'setGreeting',
type: 'function',
inputs: [{
type: 'string',
name: '_greeting'
}] // 실행하고자 하는 Function 이름과 Input Data Type & Name 정의
},
['Hi!!!!'] // 입력 값
);console.log(this.encodeData)
위의 코드를 실행하여 Padding 된 ByteCode를 출력하면 아래와 같다.
0xa4136862000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000064869212121210000000000000000000000000000000000000000000000000000
이제 MetaMask에 입력해 보겠다.
받는 주소에는 아까 생성한 HelloWorld 컨트랙트 주소를 넣으면 된다. Hex 데이터에는 방금 만들어준 바이트코드를 넣자
트랜잭션 처리가 완료가 되면 아래와 같이 Success 가 나오는 것을 이더스캔에서 확인할 수 있다.
최종적으로 데이터를 확인하자
sayHello를 call 하면 Hi!!!! 라고 변경된 것을 확인할 수 있을 것이다.
마무리를 하며 이번 기술 블로그 글에서는 다음과 같은 사항을 살펴 보았다.
- MetaMask 를 통한 개인 키 관리는 어떻게 하고 있는지에 대한 부분
- Chrome Storage에 저장되어있는 Vault 데이터에서 니모닉 추출해보기
- MetaMask 백그라운드 및 Resume 시 Network 를 통해 어떤 API 들이 실행되고 있는지 알아보기
- MetaMask 를 통해 송금을 한 과정을 분석해보기
- (4) 분석내용을 토대로 동일하게 구성하여 실행해보기
- (5) 의 심화과정으로 컨트랙트 배포와 실행해보기
크게 위의 6가지를 단계로 수행해 보았다.
그렇다면 마지막 질문인 “MetaMask는 안전한가에 대한 부분” 인데요… 우선 MetaMask 설치 파일이 공식적인 설치 파일로 안전하게 설치 되어 있다는 전제하에서는 “어느정도 안전하다” 라고 얘기할 수 있을 것 같습니다. 니모닉도 바로 노출되어 있는 것이 아니라 MetaMask 접속 시 생성한 Password로 한번 더 암호화 되어있고 대부분 MetaMask는 본인 PC에서 설치 될 것이기 때문입니다.
물론 컴퓨터가 바이러스나 멀웨어/스파이웨워 등에 노출이 되어있지 않다면 상대적으로 안전하게 트랜잭션을 보낼 수 있는 지갑의 역할을 할 수 있지 않을까 생각됩니다!
질문사항이나 잘못된 부분이 있다면 지적을 부탁드리며 다음 기술블로그 포스팅에서 뵙겠습니다 ^_^
끄읏~
- 저는 블록체인 개발사 (주)34일에서 블록체인 엔지니어로 일하고 있습니다.
- 880만 팔로워 전세계 1위 한류 미디어 케이스타라이브(KStarLive)와 함께 만든 한류 플랫폼에서 사용되는 케이스타코인(KStarCoin) 프로젝트를 진행 중입니다. 팬 커뮤니티 활동을 하면서 코인을 얻을 수 있으며, 한류 콘텐츠 구매, 공연 예매, 한국 관광 상품 구매, 기부 및 팬클럽 활동 등에 사용 될 계획입니다.