Mobile SDK
ABC WaaS Mobile SDK는 MPC 기반 지갑의 키쉐어 생성, 복구, 서명 기능을 Android와 iOS에서 사용할 수 있도록 제공합니다. SDK에는 네이티브 라이브러리(.aar, .so, .a, .xcframework)가 포함되어 있으며, 각 플랫폼의 네이티브 인터페이스를 통해 호출됩니다.
서비스 이용을 위해서는 유효한 Access Token이 필요합니다. Access Token은 고객사 백엔드에서 WaaS v3 인증 API를 통해 발급받은 후, 클라이언트 앱에 전달하여 사용합니다.
인증
SDK를 사용하기 위해서는 Access Token이 필요합니다. v3에서는 고객사 백엔드 서버에서 WaaS 인증 API를 호출하여 Access Token을 발급받고, 이를 클라이언트 앱에 전달하는 방식으로 동작합니다.
고객사 서버 → WaaS v3 인증 API → Access Token 발급
↓
클라이언트 앱 ← Access Token 전달
↓
Mobile SDK → Access Token으로 MPC/WaaS API 호출
Access Token 발급에 관한 자세한 내용은 v3.0 API 문서를 참고하세요.
설치
ABC WaaS SDK에는 MPC 라이브러리(ABCMpc)가 포함되어 있으므로, WaaS SDK만 설치하면 MPC 기능을 별도로 추가할 필요 없이 바로 사용할 수 있습니다.
최신 버전
| 플랫폼 | WaaS SDK | 포함된 MPC 버전 |
|---|---|---|
| Android | 0.1.29 | 0.1.14 |
| iOS | 0.1.29 | 0.1.13 |
Android
build.gradle.kts에 다음 의존성을 추가합니다.
settings.gradle.kts에 Maven 저장소를 추가합니다.
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven {
name = "abc-waas-android-lib"
url = uri("https://ahnlabio.github.io/abc-waas-android-lib")
}
maven {
name = "abc-mpc-android-lib"
url = uri("https://ahnlabio.github.io/abc-mpc-android-lib")
}
}
}
build.gradle.kts에 의존성을 추가합니다.
dependencies {
implementation("io.ahnlab:abcwaas:0.1.29")
}
iOS
Swift Package Manager를 통해 설치합니다. Xcode에서 File > Add Package Dependencies를 선택한 후, 다음 URL을 입력합니다.
https://github.com/ahnlabio/abc-waas-ios-lib
또는 Package.swift에 직접 추가합니다.
dependencies: [
.package(url: "https://github.com/ahnlabio/abc-waas-ios-lib", from: "0.1.29")
]
초기화
SDK를 사용하기 위해 WaasClient와 WaasHelper를 생성해야 합니다.
WaasClient는 WaaS 서버와의 통신을 담당하는 HTTP 클라이언트입니다. 지갑 정보 조회, 키 등록, 토큰 발급 등 WaaS API 호출에 사용됩니다.
WaasHelper는 MPC 키쉐어 생성, 복구, 서명 등의 작업을 수행하는 헬퍼 클래스입니다. 내부적으로 WaasClient를 통해 WaaS 서버와 통신하고, MPC 노드(node1, node2)와 직접 통신하여 MPC 연산을 수행합니다.
Android
import io.ahnlab.abcwaas.WaasClient
import io.ahnlab.abcwaas.WaasHelper
// WaasClient 생성
val waasClient = WaasClient.create(baseUrl = "https://waas.example.com")
// WaasHelper 생성 (기본)
val helper = WaasHelper(
waasClient = waasClient,
node1BaseURL = "https://mpc-node1.example.com",
node2BaseURL = "https://mpc-node2.example.com"
)
iOS
import ABCWaas
// WaasClient 생성
let waasClient = try WaasClient(baseUrl: "https://waas.example.com")
// WaasHelper 생성 (기본)
let helper = WaasHelper(
waasClient: waasClient,
node1BaseURL: "https://mpc-node1.example.com",
node2BaseURL: "https://mpc-node2.example.com"
)
키쉐어 생성
MPC 키쉐어를 생성합니다. 지원하는 curve는 secp256k1과 ed25519입니다.
생성이 완료되면 다음 4가지 값이 반환됩니다.
| 필드 | 설명 |
|---|---|
keyId | 키 식별자. 서명 요청 시 사용됩니다. |
encryptedShare | 암호화된 키쉐어. 서명의 핵심 데이터입니다. |
secretStore | 키쉐어 복호화에 필요한 시크릿 데이터입니다. |
curve | 사용된 curve (secp256k1 또는 ed25519) |
보안 권장사항: 반환된 값들은 반드시 모바일 기기의 보안 영역에 저장해야 합니다. Android에서는 Keystore 기반의 EncryptedSharedPreferences, iOS에서는 Keychain을 사용하는 것을 권장합니다.
별도의 보안 저장소 설정 없이 간편하게 사용하고 싶은 경우, SDK에서 제공하는
generateAndStoreKeyShare를 사용하면 키쉐어 생성과 동시에 플랫폼별 보안 저장소에 자동으로 저장됩니다. 자세한 내용은 키쉐어 보안 저장소 섹션을 참고하세요.
Android
val result = helper.generateKeyShare(
accessToken = accessToken,
curve = "secp256k1",
password = "user_password"
)
if (result.isSuccess) {
val response = result.getOrThrow()
val keyId = response.keyId // 키 식별자
val encryptedShare = response.encryptedShare // 암호화된 키쉐어
val secretStore = response.secretStore // 시크릿 스토어
val curve = response.curve // 사용된 curve
// 반환된 값을 앱의 보안 저장소에 저장합니다.
}
iOS
let result = await helper.generateKeyShare(
accessToken: accessToken,
curve: "secp256k1",
password: "user_password"
)
if case .success(let response) = result {
let keyId = response.keyId // 키 식별자
let encryptedShare = response.encryptedShare // 암호화된 키쉐어
let secretStore = response.secretStore // 시크릿 스토어
let curve = response.curve // 사용된 curve
// 반환된 값을 앱의 보안 저장소에 저장합니다.
}
키쉐어 복구
기기 변경이나 앱 재설치 등으로 로컬에 저장된 키쉐어를 잃어버린 경우, 서버에 등록된 키 정보를 기반으로 키쉐어를 복구할 수 있습니다. 복구 시 새로운 keyId가 발급되며, 기존 키와 동일한 공개키를 사용하는 새로운 키쉐어가 생성됩니다.
복구된 키쉐어도 생성 시와 동일하게 keyId, encryptedShare, secretStore, curve 값이 반환됩니다. 이 값들은 키쉐어 생성과 마찬가지로 모바일 기기의 보안 영역에 저장하는 것을 권장합니다. 자동 저장이 필요한 경우 키쉐어 보안 저장소 섹션의 recoverAndStoreKeyShare를 참고하세요.
Android
val result = helper.recoverKeyShare(
accessToken = accessToken,
curve = "secp256k1",
password = "user_password"
)
if (result.isSuccess) {
val response = result.getOrThrow()
val keyId = response.keyId // 새로 발급된 키 식별자
val encryptedShare = response.encryptedShare // 암호화된 키쉐어
val secretStore = response.secretStore // 시크릿 스토어
val curve = response.curve // 사용된 curve
// 반환된 값을 앱의 보안 저장소에 저장합니다.
}
iOS
let result = await helper.recoverKeyShare(
accessToken: accessToken,
curve: "secp256k1",
password: "user_password"
)
if case .success(let response) = result {
let keyId = response.keyId // 새로 발급된 키 식별자
let encryptedShare = response.encryptedShare // 암호화된 키쉐어
let secretStore = response.secretStore // 시크릿 스토어
let curve = response.curve // 사용된 curve
// 반환된 값을 앱의 보안 저장소에 저장합니다.
}
키쉐어 보안 저장소
SDK는 키쉐어를 플랫폼별 보안 저장소에 자동으로 저장하는 기능을 제공합니다. 별도의 Keystore나 Keychain 설정 없이, SDK가 제공하는 API만으로 키쉐어를 안전하게 저장하고 불러올 수 있습니다.
저장된 키쉐어는 다음과 같은 보안 정책이 적용됩니다.
- 해당 기기에서만 접근 가능합니다. 다른 기기에서는 저장된 키쉐어를 읽을 수 없습니다.
- iCloud 백업, 기기 이전, Android 백업 등을 통해 복원되지 않습니다.
- 다른 앱과 공유되지 않습니다. 동일 기기 내에서도 해당 앱에서만 접근할 수 있습니다.
| 플랫폼 | 저장 방식 | 보안 정책 |
|---|---|---|
| Android | EncryptedSharedPreferences | AES256-GCM 암호화, Android Keystore 기반 마스터키 관리 |
| iOS | Keychain | kSecAttrAccessibleWhenUnlockedThisDeviceOnly 적용, 이 기기에서만 유효 |
초기화 (KeyShareStorage 포함)
Android
import io.ahnlab.abcwaas.KeyShareStorage
val storage = KeyShareStorage(context)
val helper = WaasHelper(
waasClient = waasClient,
node1BaseURL = "https://mpc-node1.example.com",
node2BaseURL = "https://mpc-node2.example.com",
keyShareStorage = storage
)
iOS
import ABCWaas
let storage = KeyShareStorage()
let helper = WaasHelper(
waasClient: waasClient,
node1BaseURL: "https://mpc-node1.example.com",
node2BaseURL: "https://mpc-node2.example.com",
keyShareStorage: storage
)
키쉐어 생성 + 자동 저장
generateAndStoreKeyShare는 키쉐어 생성과 보안 저장소 저장을 한 번에 수행합니다. 내부적으로 generateKeyShare를 호출한 후, 성공 시 반환된 keyId, encryptedShare, secretStore, curve를 자동으로 보안 저장소에 저장합니다. 앱에서 별도로 저장 로직을 구현할 필요가 없습니다.
Android
val result = helper.generateAndStoreKeyShare(
accessToken = accessToken,
curve = "secp256k1",
password = "user_password"
)
if (result.isSuccess) {
// 키쉐어가 생성되고 보안 저장소에 자동 저장되었습니다.
// 이후 signWithStoredKeyShare 등으로 저장된 키쉐어를 바로 사용할 수 있습니다.
}
iOS
let result = await helper.generateAndStoreKeyShare(
accessToken: accessToken,
curve: "secp256k1",
password: "user_password"
)
if case .success(_) = result {
// 키쉐어가 생성되고 보안 저장소에 자동 저장되었습니다.
// 이후 signWithStoredKeyShare 등으로 저장된 키쉐어를 바로 사용할 수 있습니다.
}
키쉐어 복구 + 자동 저장
recoverAndStoreKeyShare는 키쉐어 복구와 보안 저장소 저장을 한 번에 수행합니다. 기기 변경이나 앱 재설치 후 키쉐어를 복구하면서 동시에 보안 저장소에 저장하므로, 복구 직후부터 저장된 키쉐어 기반의 서명 API를 바로 사용할 수 있습니다.
Android
val result = helper.recoverAndStoreKeyShare(
accessToken = accessToken,
curve = "secp256k1",
password = "user_password"
)
if (result.isSuccess) {
// 키쉐어가 복구되고 보안 저장소에 자동 저장되었습니다.
}
iOS
let result = await helper.recoverAndStoreKeyShare(
accessToken: accessToken,
curve: "secp256k1",
password: "user_password"
)
if case .success(_) = result {
// 키쉐어가 복구되고 보안 저장소에 자동 저장되었습니다.
}
저장된 키쉐어 관리
Android
// 조회
val stored = helper.getStoredKeyShare("secp256k1")
// 삭제
helper.deleteStoredKeyShare("secp256k1")
// 전체 삭제
helper.clearStoredKeyShares()
iOS
// 조회
let stored = helper.getStoredKeyShare(curve: "secp256k1")
// 삭제
helper.deleteStoredKeyShare(curve: "secp256k1")
// 전체 삭제
helper.clearStoredKeyShares()
서명
SDK는 curve에 따라 적합한 서명 방식을 제공합니다. 각 서명 API는 키쉐어 정보를 직접 전달하는 방식과, 보안 저장소에 저장된 키쉐어를 자동으로 불러와 서명하는 방식 두 가지를 지원합니다.
서명 결과로 반환되는 signature는 hex 인코딩된 문자열입니다. message는 서명할 데이터의 해시값(hex 문자열)입니다.
| Curve | 권장 서명 방식 | 설명 |
|---|---|---|
| secp256k1 | MTA 서명 | Multiplicative-to-Additive 프로토콜 기반의 보안 강화 서명 |
| ed25519 | 기본 서명 | 표준 EdDSA 서명 |
서명 성공 시 다음과 같은 형태의 서명값이 반환됩니다.
// signature 예시
e4ee7a1e2bdb671873ef3d3b0d320d05319fdc6e129a1b21e0e64e6573520b26
fe5dcfb06a164aaea171a55bf187a85ff2ce3fdd27e28d8fde9b152ce86e2801
각 서명 API는 두 가지 방식으로 키쉐어를 전달할 수 있습니다.
- 키쉐어 직접 전달: 키쉐어 생성 또는 복구 시 반환된
keyId,encryptedShare,secretStore값을 앱에서 관리하고, 서명 시 직접 전달합니다. 앱에서 키쉐어 저장소를 직접 구현하여 관리하는 경우에 사용합니다. - 저장된 키쉐어 사용: SDK의 보안 저장소(
KeyShareStorage)에 저장된 키쉐어를 자동으로 불러와 서명합니다.curve만 지정하면 되므로 코드가 간결해지며, 키쉐어 관리를 SDK에 위임할 수 있습니다.
기본 서명 (ed25519)
ed25519 curve를 사용하는 경우 기본 서명을 사용합니다.
키쉐어 직접 전달
키쉐어 생성 또는 복구 시 반환된 값을 직접 전달하여 서명합니다.
Android
val result = helper.sign(
accessToken = accessToken,
keyId = keyId,
encryptedShare = encryptedShare,
secretStore = secretStore,
curve = "ed25519",
message = message,
password = "user_password"
)
if (result.isSuccess) {
val signature = result.getOrThrow().signature
}
iOS
let result = await helper.sign(
accessToken: accessToken,
keyId: keyId,
encryptedShare: encryptedShare,
secretStore: secretStore,
curve: "ed25519",
message: message,
password: "user_password"
)
if case .success(let response) = result {
let signature = response.signature
}
저장된 키쉐어 사용
보안 저장소에 저장된 키쉐어를 자동으로 불러와 서명합니다. keyId, encryptedShare, secretStore를 직접 전달할 필요 없이 curve만 지정하면 됩니다.
Android
val result = helper.signWithStoredKeyShare(
accessToken = accessToken,
curve = "ed25519",
message = message,
password = "user_password"
)
iOS
let result = await helper.signWithStoredKeyShare(
accessToken: accessToken,
curve: "ed25519",
message: message,
password: "user_password"
)
MTA 서명 (secp256k1)
secp256k1 curve를 사용하는 경우 MTA(Multiplicative-to-Additive) 서명을 사용합니다. MTA 프로토콜은 기본 서명 대비 보안성이 강화된 서명 방식입니다.
키쉐어 직접 전달
Android
val result = helper.signMta(
accessToken = accessToken,
keyId = keyId,
encryptedShare = encryptedShare,
secretStore = secretStore,
message = message,
password = "user_password"
)
if (result.isSuccess) {
val signature = result.getOrThrow().signature
}
iOS
let result = await helper.signMta(
accessToken: accessToken,
keyId: keyId,
encryptedShare: encryptedShare,
secretStore: secretStore,
message: message,
password: "user_password"
)
if case .success(let response) = result {
let signature = response.signature
}
저장된 키쉐어 사용
Android
val result = helper.signMtaWithStoredKeyShare(
accessToken = accessToken,
curve = "secp256k1",
message = message,
password = "user_password"
)
iOS
let result = await helper.signMtaWithStoredKeyShare(
accessToken: accessToken,
curve: "secp256k1",
message: message,
password: "user_password"
)
지갑 정보 조회
WaasClient를 통해 지갑, 키, 사용자 정보를 조회할 수 있습니다.
지갑 정보 조회
사용자의 지갑 목록과 각 지갑의 주소 정보를 조회합니다.
Android
val result = waasClient.getV3Wallet(accessToken)
iOS
let result = await waasClient.getV3Wallet(accessToken: accessToken)
응답 예시
{
"user_id": "470021fdc945471396a9e16ab320c017",
"wallets": [
{
"key": {
"curve": "secp256k1",
"public_key": "04a1b2c3d4..."
},
"address": {
"evm": "0x1234...abcd",
"btc": "bc1q...",
"tron": "T..."
}
},
{
"key": {
"curve": "ed25519",
"public_key": "a1b2c3d4..."
},
"address": {
"solana": "5Kb8...",
"aptos": "0x..."
}
}
]
}
키 정보 조회
사용자가 등록한 키 목록을 조회합니다. curve별로 최신 키 하나씩 반환됩니다.
Android
val result = waasClient.getV3WalletKey(accessToken)
iOS
let result = await waasClient.getV3WalletKey(accessToken: accessToken)
응답 예시
[
{
"id": "d9545d5fffca6af3bc6e08c34645d3d8...",
"curve": "secp256k1",
"public_key": "04a1b2c3d4...",
"created_at": "2026-04-01T12:00:00Z"
},
{
"id": "c6a243b94aa52894e26257d843806e45...",
"curve": "ed25519",
"public_key": "a1b2c3d4...",
"created_at": "2026-04-01T12:01:00Z"
}
]
사용자 정보 조회
사용자 ID와 등록된 키 정보를 함께 조회합니다.
Android
val result = waasClient.getV3WalletUser(accessToken)
iOS
let result = await waasClient.getV3WalletUser(accessToken: accessToken)
응답 예시
{
"user_id": "470021fdc945471396a9e16ab320c017",
"key": [
{
"id": "d9545d5fffca6af3bc6e08c34645d3d8...",
"curve": "secp256k1",
"public_key": "04a1b2c3d4...",
"created_at": "2026-04-01T12:00:00Z"
}
]
}
검증
서명에 사용할 키쉐어와 비밀번호가 유효한지 사전에 검증할 수 있습니다. 서명 전에 검증을 수행하면 잘못된 비밀번호나 손상된 키쉐어로 인한 서명 실패를 미리 방지할 수 있습니다.
비밀번호 검증
키쉐어 생성 시 사용한 비밀번호가 올바른지 검증합니다. secretStore에 저장된 정보와 입력한 비밀번호를 대조하여 일치 여부를 확인합니다.
Android
val result = helper.validatePassword(
password = "user_password",
secretStore = secretStore
)
if (result.isSuccess) {
val isValid = result.getOrThrow().result // true 또는 false
}
iOS
let result = await helper.validatePassword(
password: "user_password",
secretStore: secretStore
)
if case .success(let response) = result {
let isValid = response.result // true 또는 false
}
응답 예시
{
"result": true
}
키쉐어 검증
키쉐어 데이터의 무결성을 검증합니다. encryptedShare와 secretStore가 손상되지 않았는지, 비밀번호로 정상적으로 복호화할 수 있는지 확인합니다.
Android
val result = helper.validateShare(
encryptedShare = encryptedShare,
secretStore = secretStore,
password = "user_password"
)
if (result.isSuccess) {
val isValid = result.getOrThrow().result // true 또는 false
}
iOS
let result = await helper.validateShare(
encryptedShare: encryptedShare,
secretStore: secretStore,
password: "user_password"
)
if case .success(let response) = result {
let isValid = response.result // true 또는 false
}
응답 예시
{
"result": true
}
지원 Curve
| Curve | 권장 서명 방식 |
|---|---|
| secp256k1 | MTA 서명 |
| ed25519 | 기본 서명 |
에러 처리
SDK의 에러는 HelperError 타입으로 반환됩니다.
Android
val result = helper.generateKeyShare(accessToken, curve, password)
if (result.isFailure) {
when (val error = result.exceptionOrNull()) {
is HelperError.WaasError -> {
// WaaS API 에러
println("WaaS 에러: ${error.message}")
}
is HelperError.MpcError -> {
// MPC 연산 에러
println("MPC 에러: ${error.message}")
}
is HelperError.UnknownError -> {
// 기타 에러
println("에러: ${error.message}")
}
}
}
iOS
let result = await helper.generateKeyShare(accessToken: accessToken, curve: curve, password: password)
if case .failure(let error) = result {
switch error {
case .waasError(let waasError):
// WaaS API 에러
print("WaaS 에러: \(waasError.description)")
case .mpcError(let mpcError):
// MPC 연산 에러
print("MPC 에러: \(mpcError.description)")
case .unknownError(let message):
// 기타 에러
print("에러: \(message)")
}
}
보안 참고사항
- 키쉐어 보안 저장소는 플랫폼별 보안 저장소를 활용합니다.
- Android: EncryptedSharedPreferences (AES256-GCM 암호화, Android Keystore 기반 마스터키 관리)
- iOS: Keychain (
kSecAttrAccessibleWhenUnlockedThisDeviceOnly적용)
- 저장된 키쉐어는 해당 기기의 해당 앱에서만 접근 가능합니다. 다른 앱이나 다른 기기에서는 접근할 수 없습니다.
- iCloud 백업, 기기 이전, Android 백업 등을 통해 키쉐어가 복원되지 않습니다.
- MTA 서명은 Multi-round 프로토콜을 사용하므로, 동일한 키에 대해 동시에 여러 서명 요청을 보내지 않도록 주의해야 합니다.