비트코인, 이더리움, 리플 지갑주소 검증 (nodejs)

707·2022년 11월 17일
2
post-thumbnail

❗️ 우선 이 글은 간단하게 블록체인 지갑 주소를 검증하고 싶어서 작성한 함수이고 나와 비슷한 사람들이 찾아보길 바라는 마음에 후루룩 쓴 것이기 때문에 자세한 암호화 방법이나 이론에 관해서는 다루지 않습니다.
그렇기에 이것이 정확한 방법이 아닐수도, 제가 가져와 쓴 코드나 라이브러리에 버그가 있을 수도 있는 일이니 100%의 신뢰는 하지 않길 바랍니다.


지갑 주소를 왜 확인할까?

✏️ 암호화폐를 사용하여 누군가에게 송금을 하려고 할 때
우리는 상대의 지갑주소를 받고 그 주소로 돈을 보내는 트랜잭션을 일으킨다.

하지만 그 지갑주소라 하면
0xf57cFb90Dbbc8B51f83f6207EcBa06fbe3f7d528
대충 요따구로 생긴 문자열이다. 더구나 이것은 이더리움의 address이고 (40글자의 hex) 비트코인, 리플 등등 각각의 체인마다 자신만의 주소 정의 방법이 다 따로 있다.

랜덤해 보이는 저 문자열도 사실은 나름의 암호화 방법을 거친 선택받은 문자열이기 때문에 저기서 한 글자만 바꾼
0xf57cFb90Dbbc8B51f83f6207EcBa06fbe3f7d529
이건 또 올바르지 않은 주소가 되어 트랜잭션이 실패하거나 최악의 경우 내 돈이 엉뚱한 곳으로 영영 사라지게 되버릴 가능성도 있다.

때문에 트랜잭션을 일으키기 전 이 주소가 유효한 지 확인해주는 과정이 필요하다.

BTC, ETH, XRP 세 체인을 사용한 프로젝트를 진행중이기에 이 세 체인을 위주로 검증을 하는 함수를 만들었다.



1. 일단 간단하게 정규식으로 확인하기

validateAddressSimple.ts


export enum ChainName {
  btc = 'BTC',
  eth = 'ETH',
  xrp = 'XRP'
}

export const validateAddressSimple: (chainName: ChainName, address: string) => boolean = (chainName, address) => {
  switch (chainName) {
    case ChainName.btc:
      if (/^(bc1|[13])[a-zA-HJ-NP-Z0-9]{25,39}$/i.test(address)) {
        return true;
      }
      return false;
    case ChainName.eth:
      if (/^(0x)?[0-9a-fA-F]{40}$/.test(address)) {
        return true;
      }
      return false;
    case ChainName.xrp:
      if (/^r[rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz]{27,35}$/.test(address)) {
        return true;
      }
      return false
    default:
      console.log('address validation error : not supporting blockchain');
      return false;
  }
}

위의 함수로 address를 검증해보면 아래와 같은 결과가 나온다.

// expect true
console.log('btc address check => ', validateAddressSimple(ChainName.btc, '1KxbYrdBbHaSSVtJozW36sP7DdAtGLL68y'));
console.log('eth address check => ', validateAddressSimple(ChainName.eth, '0x855697C5e19020E223A48c87391201ACD6057220'));
console.log('xrp address check => ', validateAddressSimple(ChainName.xrp, 'rETx8GBiH6fxhTcfHM9fGeyShqxozyD3xe'));

// expect false (but returns true)
console.log('btc address check => ', validateAddressSimple(ChainName.btc, '1KxbYrdBbHaSSVtJozW36sP7DdAtGLL68z'));
console.log('eth address check => ', validateAddressSimple(ChainName.eth, '0x855697C5e19020E223A48c87391201ACD6057221'));
console.log('xrp address check => ', validateAddressSimple(ChainName.xrp, 'rETx8GBiH6fxhTcfHM9fGeyShqxozyD3xf'));

true가 나와야 하는 주소값들은 모두 true로 잘 나온다.
하지만 주소값의 마지막 글자만 바꾼 가짜주소들 역시 true가 나온다.

단순히 정규식으로 포함될 수 있는 문자열 여부만 확인하였기에 정확한 검증이 이루어지지 않았다.

이런 검증은 프론트단에서 간단하고 빠르게 확인을 하는데 이용될 수는 있겠지만
실제로 해당 address가 해당 체인에서 사용하는 암호화 방식을 이용해 생성된 private key - public key 쌍을 가진 제대로된 address임을 보증하지는 않기에 확실한 검증이 필요한 곳에서는 사용하면 안된다.



2. 라이브러리 좋은거 많으니까 그걸로 확인하자!

아래는 암호화를 거친 제대로 된 주소값이 맞는지를 확인하는 함수이다.
암호화에 관해서는 아직 지식이 모자라기 때문에 자세한 원리는 서술하지 못한다😅
하지만 이미 좋은 라이브러리들이 다 나와있기 때문에 직접 어떤 암호화나 해독 과정을 코드로 짜지 않아도 잘 가져다가 쓸 수 있다.

사용한 라이브러리는 다음과 같다 (nodeJS - npm 사용)

  • BTC : bitcoin-address-validation
  • ETH : ethers
  • XRP : 얘는 따로 라이브러리가 없다. 일단 hash.js base-x 이거 두 개 사용

❗️ 리플의 경우는 라이브러리가 따로 없는 대신 깃헙 에 어떤 분이 올려주신 코드를 가져왔다. (감사합니다👍)

import hashjs from 'hash.js';
import baseCodec from 'base-x';
const codec = baseCodec("rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz"); // for ripple

function _checkXrp (address:string) {
  if (/^r[rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz]{27,35}$/.test(address) === false)
    return false;

  const bytes = codec.decode(address);
  const computed = sha256(sha256(bytes.slice(0, -4))).slice(0, 4);
  const checksum = bytes.slice(-4);
  return seqEqual(computed, checksum);

  function sha256(bytes: any) {
    return hashjs.sha256().update(bytes).digest();
  }

  function seqEqual(arr1: number[], arr2: Uint8Array) {
    if (arr1.length !== arr2.length) {
      return false;
    }
    for (let len = arr1.length, i = 0; i < len; i++) {
      if (arr1[i] !== arr2[i]) {
        return false;
      }
    }
    return true;
  }
};

이걸로 위의 검증함수를 다시 바꿨다.

validateAddress.ts

// npm i bitcoin-address-validation ethers hash.js base-x

import { validate } from 'bitcoin-address-validation';
import { isAddress } from 'ethers/lib/utils';
import hashjs from 'hash.js';
import baseCodec from 'base-x';
const codec = baseCodec("rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz"); // for ripple

export enum ChainName {
  btc = 'BTC',
  eth = 'ETH',
  xrp = 'XRP'
}

function _checkXrp (address:string) {
	... // 위 함수 참조
};

export const validateAddress: (chainName: ChainName, address: string) => boolean = (chainName, address) => {
  switch (chainName) {
    case ChainName.btc:
      return validate(address);
    case ChainName.eth:
      return isAddress(address);
    case ChainName.xrp:
      return _checkXrp(address);
    default:
      console.log('address validation error : not supporting blockchain');
      return false;
  }
}

원하던 대로 잘못된 주소값을 입력하면 false를 리턴해준다!

// expect true
console.log('btc address check => ', validateAddress(ChainName.btc, '1KxbYrdBbHaSSVtJozW36sP7DdAtGLL68y'));
console.log('eth address check => ', validateAddress(ChainName.eth, '0x855697C5e19020E223A48c87391201ACD6057220'));
console.log('xrp address check => ', validateAddress(ChainName.xrp, 'rETx8GBiH6fxhTcfHM9fGeyShqxozyD3xe'));

// expect false
console.log('btc address check => ', validateAddress(ChainName.btc, '1KxbYrdBbHaSSVtJozW36sP7DdAtGLL68z'));
console.log('eth address check => ', validateAddress(ChainName.eth, '0x855697C5e19020E223A48c87391201ACD6057221'));
console.log('xrp address check => ', validateAddress(ChainName.xrp, 'rETx8GBiH6fxhTcfHM9fGeyShqxozyD3xf'));

0개의 댓글