Category Archives: Development

Smallet Connector

대부분의 이더리움 Dapp들은 메타마스크를 표준처럼 사용하고 있다. 즉 결제를 하고 그 결과를 기다리고 하는 작업들을 일반 지갑으로 할 경우 편리성이 떨어져서 실제 운영이 어렵기 때문이다. Dapp을 사용하지 못한다면 이더리움을 구해서 고작 거래소에서 사고 팔고 하는거 말고는 할 것이 별로 없다. 그러니 지갑은 필수적으로 DApp을 지원해야 한다.

그래서 이더리움 스몰렛에서 메타마스크와 호환되는 결제 기능을 만들어 보기로 하였다. 메타마스크는 web3라는 개체를 웹페이지에 삽입하는 방법을 사용한다. 웹 프로그램은 이 개체가 있으면 메타마스크가 있다고 보고 web3에 붙은 API들을 사용하는 것이다.

메타마스크 소스에서 대부분의 UI를 다 없애고 실제 결제 부분을 안드로이드 앱으로 통신하여 앱에서 실제 결제 행위가 이루어지 지도록 하는 것이 구현의 핵심이다.

만들어진 크롬 확장프로그램은 여기에서 설치하면 된다. 스몰렛 커넥트라고 이름 지었다.

설치가 되면 아이콘을 눌러서 팝업을 연다.

   

Connet to Smallet 버튼을 누르면 그림과 같이 QR 코드가 나오는데 이더리움 스몰렛 앱에서 메뉴>지갑접속을 해서 이 QR 코드를 읽는다. 접속이 되면 아래 화면과 같이 계좌번호, 디바이스 이름 등이 나타난다.

모든 준비가 되었다. 이제 Dapp을 실행하여 실전을 해볼때다. 현재 어느정도 활성화된 Dapp은 거래소, 도박, 게임 정도이다. Dapp 종류들은 https://dappradar.com/ 에서 찾아 볼 수 있다. 사용자수나 거래 대금등으로 순위를 매겨서 볼 수 있다. 아직 거래소나 도박 외에는 별로 활성화된 DApp이 없음을 알 수 있다.

https://www.blockasino.com/ 에서 바카라 비슷한 게임을 해보자.

주사위 3개를 던져서 합계를 가지고 결정되는 도박이다. 그림과 같이 원하는 판에다 이더를 걸면된다. 중앙의 3개가 같은 숫자가 나오는 부분에 걸지 않는다면 세개가 같은 숫자가 나오면 하우스가 먹는 방식이다. 그림은 SMALL에다 0.03 이더를 베팅한 경우이다. SMALL은 세개의 주사위 합이 4~10 사이로 나오면 두배를 먹게 된다. 3개가 같은 숫자가 나오는 경우를 제외하면 기대값은 100%이다.

“PLACE BET!” 버튼을 누르면 이더리움 스몰렛 앱에 결제 요청이 오면 모든 실행이 제대로 되고 있는 것이다. 결제를 해주면 그 다음은 알아서 돌아간다. 블록체인에 기록되는데 시간이 걸리므로 한참 기다려야한다. 오래 기다려도 안되면 etherscan.io에서 본인 계정을 확인해 보면 진행상황을 알 수 있다.

이더를 소비하지 않고 테스트를 해보는 방법은 메시지 사인을 보내 보는 것이다. https://smallet.co/contracts2/#/sign-message 에서 테스트로 해 볼 수 있다.

모바일 브라우저에서도 가능한데, 크롬은 모바일 버전에서 확장프로그램을 지원하지 않으므로 크롬 확장 프로그램을 지원하는 Yandex 브라우저를 설치해서 해 볼 수 있다.

도박이니 만큼 절대 많은 돈을 걸지 않도록 주의 한다. 특히 하우스가 떼가는 수수료가 적으것 같지만 반복적으로 베팅할 경우 상당히 큰 금액이라 장기적으로 플레이 하면 당연히 돈을 다 잃게 되어 있다. 컨트랙트에 의해 움직이는 도박은 반칙은 없다. 즉 하우스가 나를 속이지는 않지만 컨트랙트 자체의 룰이 장기적으로 플레이어가 돈을 다 잃는 구조로 되어 있다는 점을 잊어서는 안된다. 거의 모든 가상화폐 도박장이 동일하다. 많은 이더리움 도박장들이 1% 정도의 수수료가 싸다고 하지만 주식이나 가상화폐 거래 수수료가 불과 0.0x % 대인것을 감안하면 매우 비싼 것이다.

Firebase, Node server + Android

android studio,

firebase 홈페이지>프로젝트선택>프로젝트설정>일반에서 google-services.json을 다운로드 하여 프로젝트에 추가

project gradle에 아래 내용추가

apply plugin: 'com.google.gms.google-services'
...
implementation 'com.google.firebase:firebase-core:16.0.1'
implementation 'com.google.firebase:firebase-messaging:17.1.0'
implementation 'com.google.firebase:firebase-auth:16.0.2'

android, onStart

FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener( new OnSuccessListener<InstanceIdResult>() {
    @Override
    public void onSuccess(InstanceIdResult instanceIdResult) {
        deviceToken = instanceIdResult.getToken();
    }
});

node.js

var admin = require('firebase-admin');
var serviceAccount = require('./smallet-27f86-firebase-adminsdk-7vwqg-b48b172508.json');

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: 'https://smallet-27f86.firebaseio.com/'
});

여기서 사용되는 json file은 firebase 홈페이지>프로젝트선택>프로젝트설정>서비스계정>새 비공개 키 생성 에서 만들어야 한다. 위에서 만든 google-services.json을 쓰면 안된다.(이것만 헛갈리고 대부분 그냥 절차대로 하면 됨)

서버에서 메시지를 특정 device에 보낼때는 아래 예제와 같이 한다. message 안의 data부분은 알아서 만들면된다. 중요한건 token이고 token은 위 안드로이드 예제에서 저장된  deviceToken을 사용하면 된다.

function sendNotification(tokenId, _receipt) {
  var message = {
    data: {
      transactionHash: _receipt.transactionHash,
      gasUsed: _receipt.gasUsed + "",
      to: _receipt.to + "",
      status: _receipt.status + ""
    },
    token: tokenId
  };
  (async () => {
    console.log(message);
    admin.messaging().send(message)
      .then((response) => {
        // Response is a message ID string.
        console.log('Successfully sent message:', response);
      })
      .catch((error) => {
        console.log('Error sending message:', error);
      });
  })();
}

안드로이드쪽에서 이 메시지를 수신하려면 서비스를 만들면 되고, 포그라운드이든 백그라운드이든 onMessageReceived는 호출되므로 백그라운드에서는 노티피케이션을, 포그라운드에서는 보여지는 액티비티에서 적절히 처리 하면 되겠다.

public class FirebaseService extends FirebaseMessagingService {
    String TAG = "wallet";

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        Log.d(TAG, "From: " + remoteMessage.getFrom());

        // Check if message contains a data payload.
        if (remoteMessage.getData().size() > 0) {
            Log.d(TAG, "Message data payload: " + remoteMessage.getData());
            Bundle bundle = new Bundle();
            for (Map.Entry<String, String> entry : remoteMessage.getData().entrySet()) {
                bundle.putString(entry.getKey(), entry.getValue());
            }
            if (MainActivity.isActivityVisible()) {
                Message msg = new Message();
                msg.what = Constants.RECEIVE_TX_RECEIPT;
                msg.setData(bundle);
                MainActivity.mHandle.sendMessage(msg);
            } else {
                sendNotification(bundle);
            }
        }

    }

    private void sendNotification(Bundle data) {
        Intent intent = new Intent(this, MainActivity.class);
        intent.putExtras(data);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
                PendingIntent.FLAG_ONE_SHOT);
        String channelId = getString(R.string.default_notification_channel_id);
        Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
        String title = data.getString("status").equals("true") ? "Transaction Success" : "Transaction Fail";
        NotificationCompat.Builder notificationBuilder =
                new NotificationCompat.Builder(this, channelId).setSmallIcon(R.drawable.ic_notification)
                        .setContentTitle(title)
                        .setContentText(String.format("to %s", data.getString("to")))
                        .setAutoCancel(true)
                        .setSound(defaultSoundUri)
                        .setContentIntent(pendingIntent);

        NotificationManager notificationManager =
                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        // Since android Oreo notification channel is needed.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(channelId,
                    "Channel human readable title",
                    NotificationManager.IMPORTANCE_DEFAULT);
            notificationManager.createNotificationChannel(channel);
        }

        notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
    }
}

 

 

ropsten testnet용 faucet을 만들다-1

스몰렛 베타 버전을 내려고 보니 테스트를 위한 Ether가 필요한데, ropsten faucet이 잘 동작하지를 않는다. 아무래도 오류인듯 한것이 신청자가 한명도 없을때도 이더를 주지 않는다. 이래서야 테스트를 할 수가 없다.

이리저리 궁리하다가 그냥 테스터 이더를 주는 faucet을 직접하나 만들면 어떨까 싶다. 실제도 아니고 test넷의 채굴 난이도야 얼마나 되겠는가. 까짓거 채굴해서 직접 나눠주는 방법을 해보기로 한다.

geth에서 CPU채굴로 해보니 거의 채굴이 안된다. 역시 POW채굴은 그래픽 카드가 있어야 한다. geth CPU채굴은 간단하다. 여기 모든 예제는 ropsten testnet 기준이다. OS는 ubuntu 16.04 이다.

geth –testnet console
>miner.start(10)

start안에 들어가는 숫자는 채굴을 실행할 thread의 갯수이다. 아무튼 이건 거의 채굴 안된다.

GPU채굴을 하려면 설치를 좀 해야 한다. 먼저 OpenCL부터,

sudo apt-get install nvidia-cuda-toolkit clinfo

clinfo를 실행하여 NVIDIA어쩌구 하는 내용이 쭉 나오면 설치 성공이다.  인터넷에 보면 여러가지 OpenCL설치 방법이 있는데 NVIDA드라이버를 NVIDA사이트에서 받아서 설치하는 방법이 표준 답으로 되어 있는데 내 경우는 이렇게 하니 ubuntu desktop이 망가져 버려서 툴킷만 따로 설치하는 방법으로 하였다.

다음은 채굴기 설치. 채굴기는 cpp-ethereum을 설치하면 그 안에 ethminer가 들어 있다.

sudo apt-get install software-properties-common
sudo add-apt-repository -y ppa:ethereum/ethereum
sudo apt-get update
sudo apt-get install ethereum

설치가 되었으면 이제 실행을 한다. 메인넷은 ethminer만 있으면 그냥 하면 되는데 testnet은 geth를 사용해서 했다. 다른 방법이 있을지도 모르지만 geth에서 해야 지갑도 쓰기 편하고 여러가지로 편하다.

geth –rpc –rpccorsdomain localhost –datadir /your/geth/datadir –testnet
ethminer -G

이렇게 하면 ethminer가 geth에 rpc로 연결해서 마이닝 결과를 넘겨준다. geth에는 반드시 채굴 보상을 받을 계정을 만들어 둬야 한다. geth에 연결할 일이 있을때는,

geth attach ipc://your/geth/datadir/geth.ipc

이렇게 연결하면 geth console을 열어서 다양한 정보를 관찰할 수 있다.

하룻밤을 돌렸더니 1,000 이더가 모였다. 뭐 이정도면 스몰렛 테스터용 이더 모으는데는 별 문제가 없겠다. 이제 이더 신청 받아서 나눠주는 모듈을 만들어야 하겠다. 이건 파트 2 에서.

메인넷과 ropsten을 같이 동작하게 하려면 포트를 서로 다르게 하면 된다.

geth –rpc –rpcport 8546 –rpccorsdomain localhost –datadir /your/data/dir –port 30304

Android O service

안드로이드 앱을 테스트 하다보면 주로 낮은 버전의 기계가 문제가 많으므로 상위버전 OS 테스트는 잘 안하게 되는데 최신 버전인 안드로이드 O버전(API 26)에서 서비스관련 내용이 바뀌었다. 조금 바뀐것이 아니라 정책이 바뀌어서 예전 방식으로는 동작을 안했다.

다른 앱의 Service를 호출 할때 startService를 사용하는데 안드로이드 O이상에서는 이렇게 부르면 다른앱이 실행중이 아닌경우 서비스 호출을 실패한다. 안드로이드 O 문서에 보면 아래와 같은 내용이 나온다.

Android 8.0 이전에 포그라운드 서비스를 생성하는 일반적인 방법은 백그라운드 서비스를 생성한 후 이 서비스를 포그라운드로 승격시키는 것이었습니다. Android 8.0에서는 좀 복잡하며, 시스템은 백그라운드 앱이 백그라운드 서비스를 생성하는 것을 허용하지 않습니다. 이 때문에 Android 8.0에서는 새 서비스를 포그라운드에서 시작하는 새로운 메서드 Context.startForegroundService()를 소개합니다. 시스템이 서비스를 생성한 후, 앱은 5초 이내에 해당 서비스의 startForeground() 메서드를 호출하여 새 서비스의 알림을 사용자에게 표시해야 합니다. 앱이 이 시간 한도 내에 startForeground()를 호출하지 않으면 시스템이 서비스를 중단하고 이 앱을 ANR로 선언합니다.

복잡한 이야기인데 결론적으로 startForegroundService를 호출 하지 않으면 백그라운드 앱은 백그라운드 서비스를 생성하지 못한다는 것이다. 그러므로 백그라운드에 있더라도 서비스가 호출 되어야 한다면 startForegroundService를 사용해야 한다. 이 경우 위 설명에 있는 바와 같이 호출된 서비스는 startForground를 반드시 실행해야한다. startForground는 노티피케이션을 동반하므로 조용히 실행될 수는 없다. 이렇게 한 이유는 정확히 실행되는 서비스들을 사용자에게 보여주기 위함일 것이다.

안드로이드 P(API 28)에서는 이부분이 좀 더 강화 되어 startForegroundService를 사용하려면 별도 퍼미션까지 요구한다고 한다. 그렇지만 실제로 해보니 퍼미션 요구는 안하는데 좀 더 들여다 봐야 할 것 같다.

여담으로 안드로이드 P를 테스트 하기 위해서 에뮬레이터(Pixel XL)를 설치 했는데 에뮬레이터 상태에서도 정말 빠르다. 에뮬레이터에서 개발하는 분들에게는 이 버전을 추천한다.

 

Smallet Key Vault, Ethereum Smallet 0.3 release

이 버전은 내 손을 떠나는 첫번째 버전이다. 개발하면서는 버전코드를 한번도 안 올렸으니 버전코드 1번이다.

현재 나와 있는 가상화폐들의 종류가 매우 다양하여 모든 가상화폐를 한 지갑 앱에 담는 것은 무모하다. 특히 최근의 가상화폐들은 단순하게 화폐를 전송하고 받는 기능을 넘어 스마트컨트랙트나 기타 유사한 DApp을 사용할 수 있게 됨에 따라 지갑의 기능이 계속 부가될 수 밖에 없다. 그러므로 비트코인 종류의 단순 화폐기능을 가지고 있고 프로토콜이 유사한 것들을 합쳐서 비트코인류 지갑으로 만들고, 이더리움은 수많은 토큰들이 이미 존재하므로 이들도 같이 관리하고 DApp도 처리가 가능한 지갑을 별도로 만들었다. 모든 앱들의 아이콘에는 SILC아이콘을 포함시켜 한 식구 임을 쉽게 알 수 있도록 하였다.

향후 통합장부(ICMP)에서 이러한 지갑들의 입출금 상황을 통합관리 하게 될것이다.

기본적으로 모든 지갑은 Key Vault에 키를 저장한다. 즉 지갑앱들은 개인키를 보관하지 않으며 모든 키는 Key Vault 앱에서 관리한다.

 

Key Vault는 색상을 붉은 색으로 하였다. 키를 생성할때 월렛과 Key Vault화면을 오가게 되고, 전문을 사인할때도 키 볼트에서 하게 되므로 사용자가 확실히 어느 앱에 있는지를 확인하기 쉽게 하기 위해서 이다. 각 월렛들도 다른 색상의 Theme를 사용하여 구분이 쉽도록 하였다. 현재로서는 키 볼트는 BIP39 마스터 시드로 생성한 키만을 생성하거나 다른 곳에서 가져와서 사용할 수 있다. 개별 키의 Import를 지원할지 아직 결정하지 못했다. 개별키를 가져오게 될 경우 복잡도가 높아져서 사용자들이 관리하기 힘들어 질 것이다.

모든 코인은 12개 이상의 단어로 이루어진 Master Seed로 부터 생성된다. HD키(Hierarchical, Deterministic)를 지원하므로 이 Master Seed만 있으면 모든 지갑 주소는 그대로 복원된다. 물론 이걸 잃으면 모두 다 잃는다.

실크에코시스템 백서에 나와 있듯이 키 볼트를 지갑에서 분리한 이유는 보안때문이다. 기능이 점점 많이 지면서 코드가 복잡해지고 다양한 권한을 가질 수 밖에 없는 지갑에서 분리하여 단순한 코드를 유지하고 아무런 OS권한도 사용하지 않음으로서 원천적으로 해킹을 막는 것이다. 코드가 복잡해지면 해킹을 막기가 그만큼 어려워진다. 또한 키 볼트 부분은 완전한 오픈소스여야 하므로 더욱더 코드를 단순하고 읽기 쉽게 하여 누구든 보안에 위협이 되는 사항을 확인하고 볼 수 있도록 하므로 복잡한 지갑 코드와의 분리는 필수 적이다.

즉 앱 자체가 인터넷 접근권한이 없으므로 네크웍을 통한 해킹은 루팅된 상태가 아닌다음에는 불가능 하다고 봐야한다. 월렛과의 통신은 서비를 통해서 하는데 월렛과 키 볼트 사이에서만 호출이 가능하다. 이 또는 앱사인에 의한 방어 이므로 OS자체의 앱 사인을 해킹하지 못한다면 다른 앱이 이 통신에 끼어 들기는 어렵다.

마스터 시드와 프라이비트 키는 안드로이드 키 스토리지를 사용하여 보관되며 모든 소스를 본다해도 이를 복원 할 방법은 없다.

이제 부터는 모든 지갑들이 다 가진 기능들이다.

  

이더리움 지갑 화면이다. QR코드 읽기, 보여주기를 지원하고 30여가지 화폐 단위로 잔고를 보여준다. 배경 색상은 이더리움 색상을 기본으로 하였다.

  

메인넷 외에도 3가지 테스트넷을 제공한다. 초보자들은 돈 많이 드는 메인넷을 사용하기 전에 테스트넷을 이용하여 충분히 기능을 사용해 볼 수 있다. Ropsten을 추천한다. 테스트용 돈 받기가 쉽다. 설정에서 개발자 모드를 켜면 Ropsten Faucet 화면에 바로가서 테스트용 이더를 받을 수 있다.

화면을 보면 다른 지갑과 달리 사인과 전송을 분리하고 있다.

  

사인화면을 보면 붉은색이다. 즉 월렛이 아니라 키 볼트에서 사인을 한다. 프라이비트 키를 키 볼트만 가지고 있으니 당연하다. 사인이 되면 아래쪽에 사인된 전문이 보인다. 사인을 분리한 이유는 완전한 오프라인 사인을 가능하게 하기 위해서다. 즉 사인버튼을 누르기 전에 항공기 모드를 켜거나 하는 방법으로 완전히 네트웍을 차단하고 사인을 할 수 있다. 사인된 전문이 오면 네트웍을 켜고 전송 버튼을 누르면 된다. 계정 추가를 통해서 계정은 몇개든 추가 할 수 있으며 따로 운영이 된다.

구글플레이에 등록 했으니 곧 노출될 것이다.

스몰렛 키볼트

이더리움 스몰렛

새로 발견한 멋진 개발 툴들

개발자에게 가장 중요한 도구가 있다면 에디터이다. 코드를 쓰는 도구다. 문서작업하는 사람들에게 워드나 엑셀이 필요하다면 개발자는 에디터가 있어야 한다. 물론 노트패드로도 된다. 그렇다고 노트패드로 문서 만드는 사람은 없지 않겠는가?

간만에 개발을 본격 해보려고 하는데 에디터를 한번 바꿔 보고 싶었다. 그동안 좋은게 나왔겠지 생각했다. 인터넷을 찾아보니 너도 나도 Sublime Text라는 것을 권한다. 받아서 사용해 보았다. 처음에는 뭐 그냥 그러려니 했다. 이틀을 사용해보고 바로 유료결제를 했다. 특별히 기능제약이 있는것 같지는 않은데 결제 메시지가 뜨길래 바로 했다. $80이다. 세상에 뭐 이렇게 잘 만든 프로그램이 다 있나. 싶다. 에디터가 개발 시간을 단축해준다고 생각하지 않았는데 이건 좀 다른 차원의 프로그램이다. 지금 기능의 1% 정도를 사용하고 있는것 같다. 그런데도 놀랍다. 우주인이 만든 것이 틀림없다.

안드로이드 개발자라면 거의 다 사용하겠지만 나같이 올드한 사람들을 위해서 소개해 본다. 일찌기 안드로이드 개발을 시작한 사람은 이클립스를 썼다. 당시는 이 프로그램이 없었으니 당연하다. 개발툴이라는 것이 한번 시작하면 옮겨가기 쉽지 않다. 뭐… 귀찮아서 그렇다.

그런데 빨리 옮기시라. 그냥 무조건 옮기시라. 비주얼스튜디오 없이 윈도우앱 만들수 없듯이 안드로이드 스튜디오 없이 안드로이드 앱을 만드는 것은 바보 짓이다.

웹사이트를 만드는 CMS는 정말 종류가 많다. 그중 1등은 워드프레스다. 이번에 좀 심각하게 한번 써봤다. 역시 잘 나가는 건 이유가 있다. 만드는 것도 쉽지만 관리가 정말 쉽다. 특히 모든 저작에 대해서 리비전 관리를 자동으로 해준다. VCS가 탑재되어 있다. 엄청나게 많은 플러그인이니 뭐니 이런 장점은 이야기 하지 않겠다. 다만, 이걸로 만들다 보면 사이트가 참 무거워 진다. 뭔가 날렵한것이 필요할떄는 다른것을 찾아봐야 하겠다. bootstrap을 심각히 고민해 보고 있다.

마지막으로 chrome://inspect/#devices. 크롬에 탑재된 웹 디버거이다. 데스크탑 웹 개발하는 사람들이야 이미 많이 사용하겠지만… 이것이 안드로이앱에 들어가 있는 웹뷰도 디버깅 해준다. 이것이 없었다면 Smallet 개발을 포기했을지도 모른다. 웹뷰를 쓴다면 이것은 구세주다.

Smallet – early, early alpha release

연식이 좀 되는지라 웹 개발에는 별 경험도 지식도 없다. 15년전에 닷넷으로 웹사이트 만들어 본것이 전부다. 그동안 세상은 변해서 웹 어플리케이션도 많은 진화를 했다. 수많은 개발 프레임웍이 있어서 어떤것을 써야 할지 알기도 어렵다. 프레임웍이라는 것이 다들 자기가 최고라고 하니 말이다.

Smallet은 가상화폐 지갑이 기반인데 모바일에 최적화 되도록 만드는 것이 목표다. 그래서 Small+Wallet=Smallet이다.

블록체인 세계는 C++과 javascript두가지가 주축이다. 블록체인 자체는 C++로 개발되고 인터페이스를 담당하는 프론트는 거의 javascript이다. 거의 모든 코드가 javascript기반으로 되어 있다. 간간히 JAVA 포팅이 보이기는 하지만 포팅이다. 즉 javascript로 먼저 작성되고 다른 플렛폼으로 옮겨 간다.

javascript는 내가 싫어하는 언어이다. 가독성이 떨어지고 지나치게 이벤트 중심이고 모든것이 async다. 네트웍 환경을 중요시하는 요즘의 개발 환경을 반영한 결과이겠지만 여전히 눈에 잘 안들어온다.

이글은 지난 한달간 Smallet 프로토타입을 개발하는 동안의 삽질 기록이다.

개발에 들어가기 전에 이번 만큼 많은 생각을 했던 경우는 없었다. 내 경우는 프로그램을 만들어 가면서 고민을 한다. 이번에는 그것이 불가능하였다. 환경이 달라도 너무 다르고 정리된 것이 거의 없다. 구글신 조차 엉뚱한 답변을 하는 경우가 허다하다. 스택 오버플로우에 검색해서 들어 갔는데 질문도 답변도 좋아요가 0인 경우가 허다하다.

통상 내실력으로 뭔가 몰라서 스택오버플로우를 찾아보면 좋아요 몇백개씩 있는것들이 나오기 마련이다. 그런데 블록체인 관련 프로그래밍 질문은 답도 거의 없지만 같은 질문을 하는 사람들 뿐이다.

있는 것은 정리된 설명이 아니라 소스코드다. 이게 맞는지 안 맞는지 알 수 없는 코드들. github에 버려진 수많은 프로젝트들.

만들고자 하는 것은 간단한데 시작을 할 수가 없다. 첫번째 고리를 끼우기가 어렵다.

결국 아무것도 없다는 것을 알았다. 수많은 코드들을 받아서 실행하고 분석하고 그것만이 방법이다. 그래도 리눅스 개발환경이 너무 좋아져서 그것이 많은 위로가 되었다. 뭐든 빨리 설치되고 충돌없이 잘된다. 예전에는 뭐든 설치가 안돼서 하지를 못했다. 설치 기술에 많은 진보가 있었고 이제는 정말 잘된다. 다행이다.

블록체인은 이제 시작단계이다. 너도나도 블록체인을 만든다. 소스코드가 다 있고 동작원리자체가 복잡하지도 않고 결과물도 소스크기도 그리 크지 않다. OS로 하자면 DOS정도 된다. 지금의 윈도우나 리눅스 같은걸 새로 만들겠다는 사람은 있기 어렵지만 DOS 수준이면 너도나도 하고 싶을 만 하다.

지갑을 만들려고 하는 사람에게는 이게 문제다. 수천개의 코인을 어떻게 관리하나. 저마다 일단 이름이 다르고 네트웍이 다르다. 그들의 지갑주소를 생성하는 것 조차 엄청난 노가다이다. 다 비슷한 알고리즘으로 생성하면서 같지는 않다. 표준화는 아직 멀고도 멀다.

그래도 인터넷에는 많은 훌륭한 개발자들이 있고 정리가 시작되었다. BIP39 마스터시드 알고리즘이 코딩을 시작할 수 있는 한줄기 빛을 보여 주었다. 모든 코인을 하나의 마스터시드로 관리한다. 수천 수만개의 주소를 단 12단어로 다 생성한다. 더 좋은 것은 항상 같이 생성된다. deterministic 하다는 이야기다. 핸드폰을 분실해도 이 12 단어만 있으면 모조리 복구할수 있다. 수백개의 코인과 수천개의 주소 모두를 말이다. 훌륭하다. 여기에 BIP32를 결합하면 hierarchical 하게 만들 수 있다. 즉 거의 모든 코인의 주소를 생성할 수 있게 된다.

기초가 세워졌으니 코딩을 해야 하는데 문제는 javascript다. 안드로이드는 javascript하고 안친하다. 기계마다 다른 시스템에서 제공하는 웹뷰가 거의 유일한 javascript엔진이다. 웹뷰는 기계마다 달라서 웹을 기반으로 뭔가를 만들면 늘 불안하다. 그래도 일단은 방법이 없다. 모든 코드 기반이 javascript인데 어찌하겠가? JAVA로 열심히 포팅해봐야 하루에도 몇번씩 commit되는 프로젝트들을 따라갈 방법이 없다.

좀 어글리 하지만 웹뷰로 가고 다음은 node든 뭐든 옮겨 가면 되겠다 판단한다.

최근 안드로이드 버전들은 웹뷰도 따로 구글이 열심히 업데이트 해주니 웹뷰도 장차는 쓸만할 것이다. 물론 실제 릴리스를 해보기전에는 장담하기 어렵다. 과감히 옛날 안드로이드 버전은 다 버린다. API 23정도 부터 가는 것으로 정한다. 이 버전 아랫쪽은 javascript는 안하는 것이 좋다는 판단이다.

지갑의 핵심이자 털리면 절대 안되는 키 저장소를 분리해야 한다. 키를 생성하는 코드는 사용자의 키를 훔칠수 있는 곳이다. 훔치는 주체는 개발자 자신과 외부의 공격이다. 두가지를 다 막아야 한다.

개발자 자신으로 부터 막는 방법은 오픈소스로 하고 코드를 최대한 단순화 하여 누구나 검사 할 수 있도록 한다. 외부로부터의 공격을 막으려면 네트웍을 사용하면 안된다. 안드로이드에서 no permission 모듈을 만들어서 해결한다.

일견 당연해 보이는 이러한 구조로된 지갑은 아무리 찾아 봐도 없다. 본시 뭘 새로 만들기 보다 베끼기를 좋아하는 터라 열심히 찾았다 그런데 정말 없다….???? 이상하다. 모든 지갑들이 하나의 앱으로 되어있고 당연히 인터넷에 연결한다. 도대체 어떻게 해킹을 막는거지? 다들 참 머리가 좋다. 나는 그렇게 못한다. 그냥 다 틀어막고 본다.

키 저장소를 만들었는데 지갑모듈과 통신이 문제다. 이건 좀 경험이 있는데 안드로이드가 이것도 제대로 지원이 안된다. 더 정확히는 이런 기능을 쓰는 사람이 없어서 좋은 예제가 없다. startActivityForResult로 부터 broadcastReceiver 다 해봤다. 예전에도 해봤었는데 잘 안됐는데 역시 잘안된다. activity는 UI에 맞춰진 요소라 통신에 당연히 안좋다. breadcastReceiver는 모듈간 통신이라기 보다 시스템 와이드 방송용이다. 그래서 느리다. 예전에 안해봤던 startService를 사용했다. 오! 생각보다 잘된다.

앱간 통신에 대한 보안은 android custom permission으로 해결한다. 같은 사인으로 배포된 앱 끼리만 통신할 수 있게 할 수 있다.

키저장소는 지갑으로 부터 오는 단 두가지 요청만 전달하면 된다. public key를 전해주는 것과 전문에 사인을 해주는 것이다. public key 그냥 퍼블릭 한것이니 대충 주면된다. 문제는 사인이다. 즉 사인이 안된 전문을 받아서 private key로 사인해서 돌려주면 된다. 키 저장소와 지갑 둘다 서비스를 만들고 서비스 끼리 startService로 통신한다.

지갑은 결제를 담당하는 곳이다. 외부로 부터 결제요청을 받아서 처리해주는 것이 핵심이다. 현재의 모든 모바일 지갑들은 QR코드를 읽은 것이 유일한 편리성이다. 그게 아니면 지갑주소를 복사해서 붙이기를 한다. 참 불편하다. 주소까지는 뭐 이렇게 가져오는데 보내야 하는 금액은 직접 입력해야 한다. 정말 불편하다.

데스크탑에는 Metamask라는 지갑이 있다. 크롬이나 파이어폭스 플러그인이라 웹사이트에서 결제요청을 하면 받아서 바로 결제 버튼만 누르면 끝이다. 당연히 이렇게 되어야 한다. 모바일에서는 앱간 통신이니 표준이 있어야 하지만 뭐… 아직 DOS 시절인데 이런게 있을리가 없다. Metamask를 시뮬레이션 하는 것이 유일한 방법이다.

javascript 해킹이 필요하다. 웹사이트들이 Metamask와 어떻게 통신하는지 보면 될 것이다. 들여다 보니 통신을 하는 것이 아니라 아예 웹사이트에다 코드를 삽입해 주고 그걸 call하는 방식이다. 음… 뭔가 많이 무식하다. 많은 개선이 되어야 하겠다. 무척 위험해 보인다. 다른 플러그인을 슬쩍 끼워넣고 금액과 받는 주소를 간단히 바꿀수 있겠다. 물론 귀찮아서 안해봤지만 하나도 안 어려워 보인다. 물론 사용자가 똑똑해서 주소가 바뀐것을 눈치채면 모르겠지만 그 복잡한 주소를 어떻게 구분하겠는가?

아무튼 웹뷰에 Metamask를 지원하는 웹사이트를 뛰우고 같은 방식으로 코드를 밀어넣고 해보니 잘된다. 웹에서 요청하면 지갑 앱이 받아서 처리해주면 끝이다. 웹뷰이니 다른 공격자가 뭘 끼워 넣기도 많이 어렵다. 이게 더 보안에 효과적이다.

마지막으로 One-click payment. 웹사이트에 링크를 넣어두고 클릭하면 앱이 받아서 처리해주는 것이다. 뭐 이건 모바일에서는 표준이나 다름없다. 그냥 껌으로 되는 것이다. 다른 앱들도 하는지 모르겠는데 수십개 앱을 봤으나 이부분에 대한 설명이 있는 것을 보지 못했다.

이제 모든 지갑을 통솔하는 ICMP(Integrated Cryptocurrenty Management Platform)를 만들어야 한다. 이건 단순노가다 이므로 그냥 열심히 하면 되겠다. 노가다 꾼들이 많이 필요하겠다.