탈중앙화 지갑은 사용자가 자신의 디지털 자산을 직접 제어할 수 있도록 하는 핵심적인 블록체인 애플리케이션입니다. 이러한 지갑의 사용자 인증 및 키 관리 설계는 보안과 사용자 경험이라는 두 가지 중요한 측면을 모두 고려해야 합니다.
1. 지갑 생성 방식
새로운 지갑을 생성하는 방식은 주로 두 가지 주요 형태로 제공되며, 모두 키의 안전한 보관을 목표로 합니다. 다중 암호화를 통해 키를 보호하면서도 사용자가 기억해야 할 정보를 단순화하는 데 중점을 둡니다.
- 강력한 비밀번호 (Keystore 파일): 사용자가 설정한 강력한 비밀번호를 기반으로 개인 키를 암호화하여 Keystore 파일로 저장합니다. 이 파일은 사용자가 직접 관리해야 하며, 보통 JSON 형식입니다.
@wallet/client와 같은 라이브러리를 사용하여 이 과정을 구현할 수 있습니다. - 니모닉 구문 (Mnemonic Phrase): 자동으로 생성된 개인 키를 12개 또는 24개의 무작위 단어 시퀀스(니모닉 구문)로 변환합니다. 이 구문은 지갑을 복원할 수 있는 마스터 키 역할을 하며, 사용자가 반드시 안전하게 보관해야 합니다.
2. 지갑 연결 프로토콜
탈중앙화 지갑은 다양한 dApp(분산 애플리케이션) 및 서비스에 연결하기 위한 표준화된 프로토콜을 사용합니다. 주요 프로토콜은 다음과 같습니다.
-
WalletConnect: 모바일 지갑과 웹 dApp 간의 안전한 연결을 가능하게 하는 개방형 프로토콜입니다. QR 코드 스캔을 통한 연결 및 다양한 기기 지원이 특징입니다.
@walletconnect/ethereum-provider라이브러리를 사용하여 구현할 수 있습니다. -
Web3 Provider (브라우저 확장 프로그램): MetaMask와 같은 브라우저 확장 프로그램 지갑은 dApp에 Web3 공급자를 주입하여 브라우저 환경에서 직접 블록체인과 상호작용할 수 있도록 합니다.
import Web3 from 'web3'; async function connectWeb3Wallet(provider) { if (!provider) { console.error("Web3 Provider가 감지되지 않았습니다."); return; } try { const web3Client = new Web3(provider); await provider.request({ method: 'eth_requestAccounts' }); // 사용자에게 연결 승인 요청 const accounts = await web3Client.eth.getAccounts(); // 연결된 계정 목록 가져오기 if (accounts.length > 0) { console.log("연결된 계정:", accounts[0]); // 선택된 계정을 사용하여 거래 서명 등의 작업 수행 return accounts[0]; } else { console.log("계정을 찾을 수 없습니다."); return null; } } catch (error) { console.error("지갑 연결 중 오류 발생:", error); return null; } } - WalletLink (Coinbase Wallet): Coinbase Wallet에서 사용하는 오픈 소스 프로토콜로, dApp이 Coinbase Wallet 앱과 안전하게 통신할 수 있도록 합니다.
3. 지갑 키 관리 구현
3.1. Keystore 파일 생성
개인 키를 사용자 정의 비밀번호로 암호화하여 Keystore 파일을 생성합니다. 이 파일은 사용자가 다운로드하여 안전하게 보관해야 합니다.
import Wallet from 'ethereumjs-wallet';
async function createAndEncryptWallet(userPassword, encryptionOptions) {
const newEthWallet = Wallet.generate(); // 새 이더리움 지갑 생성
try {
const encryptedKeystore = await newEthWallet.toV3(userPassword, {
kdf: encryptionOptions.kdf || 'scrypt', // 키 유도 함수
n: encryptionOptions.n || 262144 // scrypt 매개변수
});
const fileName = newEthWallet.getV3Filename();
const keystoreBlob = new Blob([JSON.stringify(encryptedKeystore)], { type: 'application/json' });
const downloadUrl = URL.createObjectURL(keystoreBlob);
console.log("Keystore 파일 생성 완료:", fileName);
// 사용자에게 downloadUrl을 제공하여 파일 다운로드 유도
return { fileName, downloadUrl };
} catch (error) {
console.error("Keystore 생성 및 암호화 오류:", error);
throw error;
}
}
3.2. 니모닉 구문 기반 지갑
BIP-39 프로토콜을 사용하여 니모닉 구문을 생성하고, 이를 통해 HD(Hierarchical Deterministic) 지갑을 구현합니다.
import * as bip39 from 'bip39';
import { HDKey } from '@scure/bip32'; // '@scure/bip32' 또는 'hdkey' 라이브러리 사용
/**
* 12단어 니모닉 구문 생성 (128비트 엔트로피)
* @returns {string[]} 12개의 단어 배열
*/
function generateShortMnemonic() {
return bip39.generateMnemonic(128).split(' ');
}
/**
* 24단어 니모닉 구문 생성 (256비트 엔트로피)
* @returns {string[]} 24개의 단어 배열
*/
function generateLongMnemonic() {
return bip39.generateMnemonic(256).split(' ');
}
async function deriveKeyPairFromMnemonic(mnemonicPhrase, password, derivationPath, index) {
if (!bip39.validateMnemonic(mnemonicPhrase)) {
throw new Error("유효하지 않은 니모닉 구문입니다.");
}
// 1. 니모닉 구문으로부터 시드(Seed) 생성
const seedBuffer = await bip39.mnemonicToSeed(mnemonicPhrase, password);
// 2. 시드로부터 HDKey 마스터 노드 생성
const masterKey = HDKey.fromMasterSeed(seedBuffer);
// 3. 파생 경로 및 인덱스를 사용하여 특정 키 쌍 파생
const specificPath = `${derivationPath}/${index}`; // 예: "m/44'/60'/0'/0/0"
const derivedChildKey = masterKey.derive(specificPath);
if (!derivedChildKey.privateKey) {
throw new Error("개인 키를 파생할 수 없습니다.");
}
// 파생된 개인 키와 공개 키 반환
return {
privateKey: derivedChildKey.privateKey.toString('hex'),
publicKey: derivedChildKey.publicKey.toString('hex'),
address: '0x' + new Wallet(derivedChildKey.privateKey).getAddress().toString('hex') // 주소 파생 예시
};
}
3.3. WalletConnect V2 프로토콜 연동
WalletConnect V2를 사용하여 dApp과 지갑 간의 연결을 초기화하고 이벤트를 처리합니다.
import { EthereumProvider } from '@walletconnect/ethereum-provider';
async function initWalletConnectProvider(projectId, chains, rpcMap) {
const provider = await EthereumProvider.init({
projectId: projectId, // WalletConnect Cloud에서 발급받은 프로젝트 ID
chains: chains, // 연결할 체인 ID (예: [1, 5])
rpcMap: rpcMap, // 각 체인 ID에 대한 RPC URL (예: { 1: 'https://mainnet.infura.io/v3/...' })
showQrModal: true, // 연결 시 QR 모달 표시 여부
metadata: {
name: 'My dApp',
description: 'My dApp description',
url: 'https://my-dapp.com',
icons: ['https://my-dapp.com/logo.png'],
},
});
provider.on('connect', ({ chainId }) => {
console.log(`WalletConnect 연결됨. Chain ID: ${chainId}`);
// 연결 후, `provider` 객체를 사용하여 트랜잭션 서명, 메시지 서명 등 수행 가능
});
provider.on('disconnect', () => {
console.log('WalletConnect 연결 해제됨');
});
// 연결 시작
await provider.connect();
return provider;
}
4. 블록체인 트랜잭션 처리
4.1. 자산 교환 환율 조회
Changelly와 같은 암호화폐 환율 애그리게이터의 API를 사용하여 다양한 통화 쌍의 실시간 환율 정보를 조회합니다. 일반적으로 HTTP 요청 라이브러리(예: Axios)를 통해 API 엔드포인트에 요청을 보냅니다.
4.2. 트랜잭션 전송
web3.js 또는 ethers.js와 같은 라이브러리의 API를 사용하여 블록체인에 트랜잭션을 전송합니다. 여기에는 개인 키를 이용한 트랜잭션 서명 과정이 포함됩니다.
4.3. 트랜잭션 기록 조회
-
온체인 데이터 직접 조회:
web3.js라이브러리를 사용하여 현재 블록부터 이전 블록까지 거슬러 올라가며 특정 주소와 관련된 트랜잭션을 필터링하여 조회할 수 있습니다. 이는 실시간성이 높지만, 대량의 데이터를 처리하기에는 비효율적일 수 있습니다. - 블록체인 탐색기 API 활용: Etherscan과 같은 블록체인 탐색기는 전체 노드 데이터를 수집하여 중앙화된 서비스로 제공합니다. 이들은 페이징 및 필터링 기능이 강화된 API를 제공하므로, 특정 주소의 과거 트랜잭션 기록을 효율적으로 조회할 수 있습니다.
5. 블록체인 네트워크 전환
다양한 블록체인 네트워크(예: 이더리움 메인넷, 테스트넷, BNB Smart Chain 등) 간의 손쉬운 전환 기능을 제공하여 사용자가 여러 블록체인 생태계를 탐색할 수 있도록 지원합니다. 이는 Web3 공급자 API를 통해 구현됩니다.
6. 지갑 보안 고려 사항
개인 키와 니모닉 구문의 보안은 탈중앙화 지갑에서 가장 중요합니다. 보안을 유지하면서도 사용자 편의성을 높이는 것이 핵심 과제입니다.
- 키 기록 강조 및 백업: 사용자 인터페이스는 니모닉 구문이나 개인 키를 안전하게 기록하고 보관하도록 강력하게 권장해야 합니다. Keystore 파일 다운로드 기능은 암호화된 상태로 제공되어야 합니다.
- 로컬 암호화 및 캐싱: 매번 개인 키나 니모닉 구문을 입력하는 불편함을 줄이기 위해, 사용자의 강력한 비밀번호로 암호화된 키 정보를 브라우저의 IndexedDB 또는 안전한 로컬 저장소에 저장할 수 있습니다. 이후 재접속 시 사용자가 비밀번호를 입력하면 키가 복호화되어 세션에 사용됩니다. 이 비밀번호는 어떠한 경우에도 서버로 전송되어서는 안 됩니다.
- 중앙화된 인증 서비스 연동 (선택 사항): 지갑 로그인과 별개로 애플리케이션의 사용자 인증(예: 이메일/비밀번호, 소셜 로그인)에 중앙화된 서비스를 사용할 수 있습니다. 이 경우, 중앙화 서비스는 JWT(JSON Web Token)와 같은 인증 토큰을 발행하여 사용자 세션을 관리합니다. 하지만 이 서비스는 사용자의 지갑 개인 키나 복호화 비밀번호를 절대 저장하거나 접근해서는 안 됩니다. 지갑 키 관리는 항상 클라이언트 측에서 이루어져야 합니다.
- 다중 서명 (Multi-signature) 지갑: 여러 개의 키가 있어야 거래를 승인할 수 있는 다중 서명 지갑은 보안 수준을 크게 높일 수 있습니다. 특히 팀 환경이나 대규모 자산 관리에 적합합니다.
요약하자면, 모든 보안 방식은 최소한 이중 보호를 제공해야 합니다. 모바일 앱에서는 생체 인식과 기기 암호화를, 웹 지갑에서는 Keystore 파일과 비밀번호를, 브라우저 확장 프로그램에서는 기기 인증과 비밀번호를 결합하여 사용합니다.
7. 주요 사용 라이브러리
- bip39: 니모닉 구문 생성 및 유효성 검사에 사용됩니다.
- hdkey (또는 @scure/bip32): 시드로부터 HD 지갑의 파생 경로를 따라 주소 및 키를 생성하는 데 사용됩니다.
- @walletconnect/ethereum-provider: WalletConnect 프로토콜을 사용하여 dApp과 지갑 간의 연결을 설정합니다.
- ethers.js / web3.js: 블록체인과 상호작용하고, 트랜잭션을 생성 및 서명하며, 스마트 컨트랙트를 호출하는 데 사용되는 주요 라이브러리입니다.
- crypto-js: 니모닉 구문이나 개인 키를 로컬 저장소에 저장하기 전에 대칭 암호화하는 데 사용될 수 있습니다.
- idb (IndexedDB 라이브러리): 브라우저 내에서 암호화된 키 정보와 같은 데이터를 안전하게 저장하는 데 사용될 수 있습니다.