MPC
MPC 지갑은 "Multi-Party Computation" 지갑의 약자로, 암호화폐 지갑의 보안성과 사용자 편의성을 개선하기 위해 설계된 기술입니다.
키를 분산하여 저장하고 분산된 참여자들이 공동으로 암호화 작업을 수행하는 방식으로 작동합니다.
이는 다음과 같은 장점을 가질 수 있습니다.
-
MPC 지갑에서는 비밀 키를 여러 조각으로 나누어 분산 저장허게 되며, 어느 하나의 키 조각만으로는 원래의 비밀 키를 복구할 수 없습니다. 또한 하나의 키 조각이 유출되거나 손실되어도 복원이 가능하다는 장점이 있습니다. 이로 하여금 지갑에서 단일 키의 유출이나 분실로 인한 전체 자산의 위험을 막을 수 있습니다.
-
중앙 서버에 의존하지 않기 때문에 해킹이나 내부자 위협에 강합니다. 또한, 분산된 네트워크 상에서 운영되므로 신뢰할 수 있는 단일 지점이 필요하지 않습니다.
-
니모닉과 같은 복잡한 키 관리 절차를 간소화할 수 있어 사용자 경험을 개선할 수 있습니다.
지갑 생성
클라이언트 측에서 MPC Key 생성 호출에 필요한 전체 흐름을 보여줍니다. 키 생성 요청에 반환되는 MPC Key는 사용자의 지갑에 대하여 트랜잭션을 가능하게 하는 키 조각입니다.
요청에 사용되는 devicePassword는 키 조각 암호화를 위해 사용됩니다. 키 생성/복구 API 호출 성공 응답으로 받은 encpvstr은 암호화된 KeyShare를 의미합니다. encryptDevicePassword는 요청에 사용된 devicePassword를 WaaS에서 암호화되어 반환하게 되며, 분산 키 조각과 함께 쌍을 이루게 됩니다. 클라이언트는 devicePassword의 원문을 알더라도 복호화하는 것은 불가능합니다.
MPC Key 생성/복구 시 반환된 encryptDevicePassword, encpv, wid는 클라이언트 측에서 안전하게 보관되어야 합니다.
Key 복구
KeyShare가 분실, 훼손된 경우 MPC Key를 복구 할 수 있습니다. 복구한 경우에 새로운 지갑이 생성되는 것은 아니며, KeyShare가 분실, 훼손된 경우에는 해당 KeyShare의 사용이 불가능하기에 복구할 필요가 있습니다.
복구 시마다 wid 값은 1씩 증가하며 지갑 조회 API 호출 시에는 최종 wid 값만 조회가 가능합니다.
또한, encpvstr와 encryptDevicePassword는 서로 한 쌍을 이루게 되며, 클라이언트는 wid, encpvstr, encryptDevicePassword를 안전하게 보관해야 합니다.
아래는 지갑 생성/복구 과정의 이해를 돕기 위한 예제 코드입니다.
- Typescript
- Python
- Go
- Java
// mpc.ts - WAAS 지갑 생성/복구 API 사용 예제
import axios from 'axios';
import qs from 'qs';
import { emailLogin } from './login'; // (2)
import { createSecureChannel, encrypt } from './secureChannel'; // (1)
/*
해당 예제는 정상동작하는 상황을 가정하고, 에러 처리를 따로하지 않음
구현시에 에러 및 예외처리 적용 필요
ts를 js로 빌드하여, dist파일을 실행하도록 package.json설정하여 작성된 예제
package.json 에 해당 스크립트 참고
//``` json
"scripts": {
"start": "tsc | node dist/index.js",
},
//```
*/
const WAAS_BASE_URL: string = 'https://dev-api.abcwaas.com';
// 필수 함수 아님. 유틸성 함수
function getBaseURL(): string {
const waas_base_url: string = process.env.WAAS_BASE_URL || WAAS_BASE_URL;
return waas_base_url;
}
type getWalletResult = {
uid: string;
wid: number;
sid: string;
pvencstr: string;
encryptDevicePassword: string;
};
async function getWallet(
email: string,
encryptedDevicePassword: string,
channelID: string,
accesssToken: string,
): Promise<getWalletResult> {
/*
사용자 고유 MPC 지갑을 생성합니다.
사용자는 1개의 고유 지갑을 소유하게 되며, 이미 생성된 지갑이 존재하는 경우, 기존 지갑을 복구합니다.
devicePassword는 생성 혹은 복구되는 지갑의 Key Share의 암호를 의미합니다.
GetWalletResult example :
>>> {
"uid": "d5b440b8-469b-4e16-8978-d78b73a09c4e",
"wid": 6,
"sid": "0xbE616d5b24903efc58149f3c7511FeC2085c176e",
"pvencstr": "0x1234567890abcdef",
"encryptDevicePassword": "sEbZRmmOrvmMI83XugEzEVwRpwkBBCeXb4jMq1f8Wao="
}
Args:
email (str): 사용자 이메일
encrypted_device_password (str): Secure Channel로 암호화된 devicePassword
channel_id (str): 보안 채널 ID.
access_token (str): 지갑 사용자의 JWT Token
Returns:
GetWalletResult: 사용자의 MPC 지갑 정보
Raises:
HTTPError: 지갑 생성 요청이 실패한 경우
*/
try {
const urlStr = `${getBaseURL()}/wapi/v2/mpc/wallets`;
const data = qs.stringify({
email: email,
devicePassword: encryptedDevicePassword,
});
const response = await axios.post(urlStr, data, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Bearer ${accesssToken}`,
'Secure-Channel': channelID,
},
});
const wallet: getWalletResult = response.data;
return wallet;
} catch (error) {
if (axios.isAxiosError(error)) {
throw new Error(
`fail to getWallet. stataus code: ${
error.status
}, data: ${JSON.stringify(error.response?.data)}`,
);
}
throw new Error(`fail to getWallet`);
}
}
type walletAccount = {
id: string;
sid: string;
ethAddress: string;
icon: string;
name: string;
signer: string;
pubkey: string;
};
type walletInfo = {
_id: string;
uid: string;
wid: number;
email: string;
accounts: walletAccount[];
favorites: string[];
autoconfirms: string[];
twoFactorEnabled: boolean;
twoFactorResetRetryCount: number;
twoFactorRetryFreezeEndTime: number;
twoFactorFreezeEndTime: number;
};
async function getWalletInfo(accessToken: string): Promise<walletInfo> {
/*
사용자 MPC 지갑을 조회합니다.
Args:
access_token (str): 지갑 사용자의 JWT Token
WalletInfo example:
>>> {
"_id": "657bdc790b67b600128a865f",
"uid": "d5b440b8-469b-4e16-8978-d78b73a09c4e",
"wid": 6,
"email": "test_0@myabcwallet.com",
"accounts": [
{
"id": "0",
"sid": "0xbE616d5b24903efc58149f3c7511FeC2085c176e",
"ethAddress": "0xbE616d5b24903efc58149f3c7511FeC2085c176e",
"icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4Hs....",
"name": "Account 1",
"signer": "mpc",
"pubkey": "0x025c5d89f60eba1b8fc5c2bd2fa28eefe48f4c950815acd7...."
}
],
"favorites": [],
"autoconfirms": [],
"twoFactorEnabled": false,
"twoFactorResetRetryCount": 0,
"twoFactorRetryFreezeEndTime": 0,
"twoFactorFreezeEndTime": 0
}
*/
try {
const urlStr = `${getBaseURL()}/wapi/v2/mpc/wallets/info`;
const response = await axios.get(urlStr, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
const walletInfoRes: walletInfo = response.data;
return walletInfoRes;
} catch (error) {
if (axios.isAxiosError(error)) {
throw new Error(
`fail to get walletinfo. status code: ${
error.response?.status
}, data: ${JSON.stringify(error.response?.data)}`,
);
}
throw new Error(`fail to get walletinfo.`);
}
}
export async function mpcScenario() {
const email: string = 'email@email.com'; // 사용자 이메일
const password: string = 'password'; // 사용자 비밀번호
const clientID: string = 'client id'; // 발급받은 Client ID
const clientSecret: string = 'client secret'; // 발급받은 Client Secret
// Secure Channel 생성
const secureChannelRes = await createSecureChannel();
// password는 Secure Channel 암호화가 필요합니다.
const encryptedPassword = encrypt(secureChannelRes, password);
// Client ID / Client Secret
const auth = Buffer.from(`${clientID}:${clientSecret}`).toString('base64'); // (3)
// 로그인
const loginResult = await emailLogin(
email,
encryptedPassword,
secureChannelRes.ChannelID,
auth,
);
// 성공시 jwt token 생성됨
console.log(`access token : ${loginResult.accessToken}`);
const devicePassword = 'password'; // (4)
const encryptedDevicePassword = encrypt(secureChannelRes, devicePassword);
const wallet = await getWallet(
email,
encryptedDevicePassword,
secureChannelRes.ChannelID,
loginResult.accessToken,
);
console.log(`wallet uid: ${wallet.uid}`);
console.log(`wallet wid: ${wallet.wid}`);
console.log(`wallet sid: ${wallet.sid}`);
const walletInfo = await getWalletInfo(loginResult.accessToken);
console.log(`wallet uid: ${walletInfo.uid}`);
console.log(`wallet wid: ${walletInfo.wid}`);
console.log(`wallet sid: ${walletInfo.accounts[0].sid}`);
}
- 🙋 Getting Started > Secure Channel
- 🙋 Getting Started > Login
- 🙋 사전에 발급받은 Client ID / Client Secret 이 필요합니다. Client ID 와 Client Secret 을 base64 로 인코딩 해야 합니다.
- 🙋 devicePassword 는 키 조각 암호화를 위해 사용됩니다. Secure Channel 암호화가 필요합니다.
"""mpc.py - WAAS 지갑 생성/복구 API 사용 예제"""
import base64
import os
from typing import TypedDict, List
import requests
import securechannel # (1)
import login # (2)
WAAS_BASE_URL = "https://dev-api.abcwaas.com"
WalletAccount = TypedDict(
"WalletAccount",
{
"id": str,
"sid": str,
"ethAddress": str,
"icon": str,
"name": str,
"signer": str,
"pubkey": str,
},
)
WalletInfo = TypedDict(
"WalletInfo",
{
"_id": str,
"uid": str,
"wid": int,
"email": str,
"accounts": List[WalletAccount],
"favorites": List,
"autoconfirms": List,
"twoFactorEnabled": bool,
"twoFactorResetRetryCount": int,
"twoFactorRetryFreezeEndTime": int,
"twoFactorFreezeEndTime": int,
},
)
GetWalletResult = TypedDict(
"GetWalletResult",
{
"uid": str,
"wid": int,
"sid": str,
"pvencstr": str,
"encryptDevicePassword": str,
},
)
def get_wallet_info(access_token: str) -> WalletInfo:
"""
사용자 MPC 지갑을 조회합니다.
Args:
access_token (str): 지갑 사용자의 JWT Token
WalletInfo example:
>>> {
"_id": "657bdc790b67b600128a865f",
"uid": "d5b440b8-469b-4e16-8978-d78b73a09c4e",
"wid": 6,
"email": "test_0@myabcwallet.com",
"accounts": [
{
"id": "0",
"sid": "0xbE616d5b24903efc58149f3c7511FeC2085c176e",
"ethAddress": "0xbE616d5b24903efc58149f3c7511FeC2085c176e",
"icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4Hs....",
"name": "Account 1",
"signer": "mpc",
"pubkey": "0x025c5d89f60eba1b8fc5c2bd2fa28eefe48f4c950815acd7...."
}
],
"favorites": [],
"autoconfirms": [],
"twoFactorEnabled": false,
"twoFactorResetRetryCount": 0,
"twoFactorRetryFreezeEndTime": 0,
"twoFactorFreezeEndTime": 0
}
"""
waas_base_url = os.getenv("WAAS_BASE_URL", WAAS_BASE_URL)
r = requests.get(
url=f"{waas_base_url}/wapi/v2/mpc/wallets/info",
headers={"Authorization": f"Bearer {access_token}"},
)
r.raise_for_status()
return r.json()
def get_wallet(
email: str, encrypted_device_password: str, channel_id: str, access_token: str
) -> GetWalletResult:
"""
사용자 고유 MPC 지갑을 생성합니다.
사용자는 1개의 고유 지갑을 소유하게 되며, 이미 생성된 지갑이 존재하는 경우, 기존 지갑을 복구합니다.
devicePassword는 생성 혹은 복구되는 지갑의 Key Share의 암호를 의미합니다.
GetWalletResult example :
>>> {
"uid": "d5b440b8-469b-4e16-8978-d78b73a09c4e",
"wid": 6,
"sid": "0xbE616d5b24903efc58149f3c7511FeC2085c176e",
"pvencstr": "0x1234567890abcdef",
"encryptDevicePassword": "sEbZRmmOrvmMI83XugEzEVwRpwkBBCeXb4jMq1f8Wao="
}
Args:
email (str): 사용자 이메일
encrypted_device_password (str): Secure Channel로 암호화된 devicePassword
channel_id (str): 보안 채널 ID. # (2)
access_token (str): 지갑 사용자의 JWT Token
Returns:
GetWalletResult: 사용자의 MPC 지갑 정보
Raises:
HTTPError: 지갑 생성 요청이 실패한 경우
"""
waas_base_url = os.getenv("WAAS_BASE_URL", WAAS_BASE_URL)
r = requests.post(
url=f"{waas_base_url}/wapi/v2/mpc/wallets",
headers={
"Secure-Channel": channel_id,
"Authorization": f"Bearer {access_token}",
},
data={"email": email, "devicePassword": encrypted_device_password},
)
return r.json()
def main():
email = "email" # 사용자 이메일
password = "password" # 사용자 비밀번호
client_id = "Client ID" # 발급받은 Client ID
client_secret = "Client Secret" # 발급받은 Client Secret
# Secure Channel 생성
secure_channel = securechannel.create_secure_channel()
# password 는 Secure Channel 암호화가 필요합니다
encrypted_password = securechannel.encrypt(secure_channel, password)
# Client ID / Client Secret
auth = base64.b64encode(f"{client_id}:{client_secret}".encode("utf-8")).decode(
"utf-8"
) # (3)
# 로그인
email_login_result = login.email_login(
email,
encrypted_password,
secure_channel["channel_id"],
auth,
)
# 성공 시 jwt token 생성됨
print(f"access_token: {email_login_result['access_token']}")
device_password = "password" # (4)
encrypted_device_password = securechannel.encrypt(secure_channel, device_password)
wallet = get_wallet(
email,
encrypted_device_password,
secure_channel["channel_id"],
email_login_result["access_token"],
)
print(f"wallet uid: {wallet['uid']}")
print(f"wallet wid: {wallet['wid']}")
print(f"wallet sid: {wallet['sid']}")
wallet_info = get_wallet_info(email_login_result["access_token"])
print(f"wallet_info uid: {wallet_info['uid']}")
print(f"wallet_info wid: {wallet_info['wid']}")
print(f"wallet_info sid: {wallet_info['accounts'][0]['sid']}")
if __name__ == "__main__":
main()
- 🙋 Getting Started > Secure Channel
- 🙋 Getting Started > Login
- 🙋 사전에 발급받은 Client ID / Client Secret 이 필요합니다. Client ID 와 Client Secret 을 base64 로 인코딩 해야 합니다.
- 🙋 devicePassword 는 키 조각 암호화를 위해 사용됩니다. Secure Channel 암호화가 필요합니다.
// mpc.go - WAAS 지갑 생성/복구 API 사용 예제
package mpc
import (
"encoding/base64"
"encoding/json"
"fmt"
"log"
"net/http"
"net/url"
"os"
"strings"
securechannel "github.com/ahnlabio/waas-example.git/golang/secureChannel" // (1)
"github.com/ahnlabio/waas-example.git/golang/login" // (2)
)
/*
해당 예제는 정상동작하는 상황을 가정하고, 에러 처리를 따로하지 않음
구현시에 에러 및 예외처리 적용 필요
*/
const WAAS_BASE_URL = "https://dev-api.abcwaas.com"
// 필수 함수 아님. 유틸성 함수
func getBaseURL() string {
waas_base_url, isExistEnv := os.LookupEnv("WAAS_BASE_URL")
if !isExistEnv {
waas_base_url = WAAS_BASE_URL
}
return waas_base_url
}
type WalletAccount struct {
ID string `json:"id"`
SID string `json:"sid"`
ETHAddress string `json:"ethAddress"`
Icon string `json:"icon"`
Name string `json:"name"`
Signer string `json:"signer"`
PublicKey string `json:"pubkey"`
}
type WalletInfo struct {
ID string `json:"_id"`
UID string `json:"uid"`
WID int `json:"wid"`
Email string `json:"email"`
Accounts []WalletAccount `json:"accounts"`
Favorites []string `json:"favorites"`
Autoconfirms []string `json:"autoconfirms"`
TwoFactorEnabled bool `json:"twoFactorEnabled"`
TwoFactorResetRetryCount int `json:"twoFactorResetRetryCount"`
TwoFactorRetryFreezEndTime int `json:"twoFactorRetryFreezeEndTime"`
TwoFactorFreezeEndTime int `json:"twoFactorFreezeEndTime"`
}
type GetWalletResult struct {
UID string `json:"uid"`
WID int `json:"wid"`
SID string `json:"sid"`
Pvencstr string `json:"pvencstr"`
EncryptDevicePassword string `json:"encryptDevicePassword"`
}
func GetWalletInfo(accessToken string) WalletInfo {
/*
사용자 MPC 지갑을 조회합니다.
Args:
access_token (str): 지갑 사용자의 JWT Token
WalletInfo example:
>>> {
"_id": "657bdc790b67b600128a865f",
"uid": "d5b440b8-469b-4e16-8978-d78b73a09c4e",
"wid": 6,
"email": "test_0@myabcwallet.com",
"accounts": [
{
"id": "0",
"sid": "0xbE616d5b24903efc58149f3c7511FeC2085c176e",
"ethAddress": "0xbE616d5b24903efc58149f3c7511FeC2085c176e",
"icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4Hs....",
"name": "Account 1",
"signer": "mpc",
"pubkey": "0x025c5d89f60eba1b8fc5c2bd2fa28eefe48f4c950815acd7...."
}
],
"favorites": [],
"autoconfirms": [],
"twoFactorEnabled": false,
"twoFactorResetRetryCount": 0,
"twoFactorRetryFreezeEndTime": 0,
"twoFactorFreezeEndTime": 0
}
*/
urlStr := fmt.Sprintf("%s/wapi/v2/mpc/wallets/info", getBaseURL())
req, err := http.NewRequest("GET", urlStr, nil)
if err != nil {
log.Fatal(err)
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Fatal(resp, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
log.Fatalf("fail to get wallet info: %v\n", resp)
}
var result WalletInfo
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
log.Fatalf("fail to decode result %v\n", err)
}
return result
}
func GetWallet(email, encryptedDevicePassowrd, channelID, accessToken string) GetWalletResult {
/*
사용자 고유 MPC 지갑을 생성합니다.
사용자는 1개의 고유 지갑을 소유하게 되며, 이미 생성된 지갑이 존재하는 경우, 기존 지갑을 복구합니다.
devicePassword는 생성 혹은 복구되는 지갑의 Key Share의 암호를 의미합니다.
GetWalletResult example :
>>> {
"uid": "d5b440b8-469b-4e16-8978-d78b73a09c4e",
"wid": 6,
"sid": "0xbE616d5b24903efc58149f3c7511FeC2085c176e",
"pvencstr": "0x1234567890abcdef",
"encryptDevicePassword": "sEbZRmmOrvmMI83XugEzEVwRpwkBBCeXb4jMq1f8Wao="
}
Args:
email (str): 사용자 이메일
encrypted_device_password (str): Secure Channel로 암호화된 devicePassword
channel_id (str): 보안 채널 ID.
access_token (str): 지갑 사용자의 JWT Token
Returns:
GetWalletResult: 사용자의 MPC 지갑 정보
Raises:
HTTPError: 지갑 생성 요청이 실패한 경우
*/
urlStr := fmt.Sprintf("%s/wapi/v2/mpc/wallets", getBaseURL())
data := url.Values{
"email": {email},
"devicePassword": {encryptedDevicePassowrd},
}
req, err := http.NewRequest("POST", urlStr, strings.NewReader(data.Encode()))
if err != nil {
log.Fatal("Failed to create request:", err)
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Secure-Channel", channelID)
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Fatal("Request failed:", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
log.Fatalf("Request failed with status: %d", resp.StatusCode)
}
var result GetWalletResult
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
log.Fatalf("fail to decode result %v\n", err)
}
return result
}
func MPCScenario() {
email := "email@email.com" // 사용자 이메일
password := "password" // 사용자 비밀번호
clientID := "Client ID" // 발급받은 Client ID
clientSecret := "Client Secret" // 발급받은 Client Secret
// Secure Channel 생성
secureChannelRes := securechannel.CreateSecureChannel()
// password 는 Secure Channel 암호화가 필요합니다.
encryptedPassword := securechannel.Encrypt(secureChannelRes, password)
// Client ID / Client Secret
auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", clientID, clientSecret))) // (3)
// 로그인
emailLoginResult := login.EmailLogin(email, encryptedPassword, secureChannelRes.ChannelID, auth)
// 성공시 jwt token 생성됨
fmt.Printf("access token: %s\n", emailLoginResult.AccessToken)
devicePassword := "password" // (4)
encryptedDevicePassword := securechannel.Encrypt(secureChannelRes, devicePassword)
wallet := GetWallet(email, encryptedDevicePassword, secureChannelRes.ChannelID, emailLoginResult.AccessToken)
fmt.Printf("wallet uid: %s\n", wallet.UID)
fmt.Printf("wallet wid: %v\n", wallet.WID)
fmt.Printf("wallet sid: %s\n", wallet.SID)
walletInfo := GetWalletInfo(emailLoginResult.AccessToken)
fmt.Printf("wallet info uid: %s\n", walletInfo.UID)
fmt.Printf("wallet info wid: %v\n", walletInfo.WID)
fmt.Printf("wallet info sid: %s\n", walletInfo.Accounts[0].SID)
}
- 🙋 Getting Started > Secure Channel
- 🙋 Getting Started > Login
- 🙋 사전에 발급받은 Client ID / Client Secret 이 필요합니다. Client ID 와 Client Secret 을 base64 로 인코딩 해야 합니다.
- 🙋 devicePassword 는 키 조각 암호화를 위해 사용됩니다. Secure Channel 암호화가 필요합니다.
/* Mpc.java - WAAS 지갑 생성/복구 API 사용 예제 */
package io.myabcwallet;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.List;
/* bcprov-jdk18on */
import org.bouncycastle.util.encoders.Base64;
import com.google.gson.Gson;
import io.myabcwallet.Login.EmailLoginResult;
public class Mpc {
public static String WAAS_BASE_URL = "https://dev-api.abcwaas.com";
class WalletAccount {
String id;
String sid;
String ethAddress;
String icon;
String name;
String signer;
String pubkey;
}
class WalletInfo {
String _id;
String uid;
int wid;
String email;
List<WalletAccount> accounts;
List<String> favorites;
List<String> autoconfirms;
boolean twoFactorEnabled;
int twoFactorResetRetryCount;
int twoFactorRetryFreezeEndTime;
int twoFactorFreezeEndTime;
}
class GetWalletResult {
String uid;
int wid;
String sid;
String pvencstr;
String encryptDevicePassword;
}
public <T> T build(Object object, Class<T> classOfT) throws Exception {
Gson gson = new Gson();
String json = gson.toJson(object);
return build(json, classOfT);
}
public <T> T build(String message, Class<T> classOfT) throws Exception {
Gson gson = new Gson();
return (T) gson.fromJson(message, classOfT);
}
public GetWalletResult getWallet(String email, String encDevicePassword, String channelId, String accessToken) throws Exception {
/*
사용자 고유 MPC 지갑을 생성합니다.
사용자는 1개의 고유 지갑을 소유하게 되며, 이미 생성된 지갑이 존재하는 경우, 기존 지갑을 복구합니다.
devicePassword는 생성 혹은 복구되는 지갑의 Key Share의 암호를 의미합니다.
GetWalletResult example :
>>> {
"uid": "d5b440b8-469b-4e16-8978-d78b73a09c4e",
"wid": 6,
"sid": "0xbE616d5b24903efc58149f3c7511FeC2085c176e",
"pvencstr": "0x1234567890abcdef",
"encryptDevicePassword": "sEbZRmmOrvmMI83XugEzEVwRpwkBBCeXb4jMq1f8Wao="
}
Args:
email (str): 사용자 이메일
encDevicePassword (str): Secure Channel로 암호화된 devicePassword
channelId (str): 보안 채널 ID. # (2)
accessToken (str): 지갑 사용자의 JWT Token
Returns:
GetWalletResult: 사용자의 MPC 지갑 정보
Raises:
HTTPError: 지갑 생성 요청이 실패한 경우
*/
URL url = new URL(WAAS_BASE_URL + "/wapi/v2/mpc/wallets");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
BufferedReader buffer = null;
try {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("email=" + email);
stringBuffer.append("&devicePassword=" + encDevicePassword);
byte[] postData = stringBuffer.toString().getBytes(StandardCharsets.UTF_8);
int postDataLength = postData.length;
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
connection.setRequestProperty("charset", "utf-8");
connection.setRequestProperty("Secure-Channel", channelId);
connection.setRequestProperty("Authorization", "Bearer " + accessToken);
connection.setRequestProperty("Content-Length", Integer.toString(postDataLength));
DataOutputStream stream = new DataOutputStream(connection.getOutputStream());
stream.write(postData);
buffer = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
StringBuilder stringBuilder = new StringBuilder();
String line = null;
while ((line = buffer.readLine()) != null) {
stringBuilder.append(line);
}
String response = stringBuilder.toString();
int responseCode = connection.getResponseCode();
if(responseCode != 200) {
throw new Exception(String.format("get wallet failed: [%d][%s]", responseCode, response));
}
return build(response, GetWalletResult.class);
}
catch(Exception e) {
e.printStackTrace();
throw e;
}
finally {
if(buffer != null) buffer.close();
}
}
public WalletInfo getWalletInfo(String acessToken) throws Exception {
/*
사용자 MPC 지갑을 조회합니다.
Args:
access_token (str): 지갑 사용자의 JWT Token
WalletInfo example:
>>> {
"_id": "657bdc790b67b600128a865f",
"uid": "d5b440b8-469b-4e16-8978-d78b73a09c4e",
"wid": 6,
"email": "test_0@myabcwallet.com",
"accounts": [
{
"id": "0",
"sid": "0xbE616d5b24903efc58149f3c7511FeC2085c176e",
"ethAddress": "0xbE616d5b24903efc58149f3c7511FeC2085c176e",
"icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4Hs....",
"name": "Account 1",
"signer": "mpc",
"pubkey": "0x025c5d89f60eba1b8fc5c2bd2fa28eefe48f4c950815acd7...."
}
],
"favorites": [],
"autoconfirms": [],
"twoFactorEnabled": false,
"twoFactorResetRetryCount": 0,
"twoFactorRetryFreezeEndTime": 0,
"twoFactorFreezeEndTime": 0
}
*/
URL url = new URL(WAAS_BASE_URL + "/wapi/v2/mpc/wallets/info");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
BufferedReader buffer = null;
try {
connection.setDoOutput(true);
connection.setRequestMethod("GET");
connection.setRequestProperty("charset", "utf-8");
connection.setRequestProperty("Authorization", "Bearer " + acessToken);
buffer = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
StringBuffer stringBuffer = new StringBuffer();
String line = null;
while ((line = buffer.readLine()) != null) {
stringBuffer.append(line);
}
String response = stringBuffer.toString();
int responseCode = connection.getResponseCode();
if(responseCode != 200) {
throw new Exception(String.format("get wallet info failed: [%d][%s]", responseCode, response));
}
return build(response, WalletInfo.class);
}
catch(Exception e) {
e.printStackTrace();
throw e;
}
finally {
if(buffer != null) buffer.close();
}
}
public static void main(String[] args) throws Exception {
String email = "test01@ahnlab.com"; // 사용자 이메일
String password = "0123456789"; // 사용자 비밀번호
String clientId = "Client ID"; // 발급받은 Client ID
String clientSecret = "Client Secret"; // 발급받은 Client Secret
// Secure Channel 생성
SecureChannel secureChannel = new SecureChannel();
secureChannel.create("plainText");
// password 는 Secure Channel 암호화가 필요합니다
String encryptedPassword = secureChannel.encrypt(password);
// Client ID / Client Secret
String auth = new String(Base64.encode((clientId + ":" + clientSecret).getBytes())); // (3)
// 로그인
Login login = new Login();
EmailLoginResult emailLoginResult = login.emailLogin(
email,
encryptedPassword,
secureChannel.getChannelId(),
auth
);
// 성공 시 jwt token 생성됨
System.out.println("access_token: " + emailLoginResult.access_token);
String devicePassword = "password"; // (4)
String encDevicePassword = secureChannel.encrypt(devicePassword);
Mpc mpc = new Mpc();
GetWalletResult getWalletResult = mpc.getWallet(
email,
encDevicePassword,
secureChannel.getChannelId(),
emailLoginResult.access_token
);
System.out.println("wallet uid: " + getWalletResult.uid);
System.out.println("wallet wid: " + getWalletResult.wid);
System.out.println("wallet sid: " + getWalletResult.sid);
WalletInfo walletInfo = mpc.getWalletInfo(emailLoginResult.access_token);
System.out.println("wallet_info uid: " + walletInfo.uid);
System.out.println("wallet_info wid: " + walletInfo.wid);
System.out.println("wallet_info sid: " + walletInfo.accounts.get(0).sid);
}
}
- 🙋 Getting Started > Secure Channel
- 🙋 Getting Started > Login
- 🙋 사전에 발급받은 Client ID / Client Secret 이 필요합니다. Client ID 와 Client Secret 을 base64 로 인코딩 해야 합니다.
- 🙋 devicePassword 는 키 조각 암호화를 위해 사용됩니다. Secure Channel 암호화가 필요합니다.