Mobile SDK
ABC WaaS Mobile SDK provides MPC-based wallet key share generation, recovery, and signing features for both Android and iOS. The SDK includes native libraries (.aar, .so, .a, .xcframework) and is called through each platform’s native interface.
To use the service, a valid Access Token is required. The Access Token is issued from the customer backend through the WaaS v3 authentication API and then delivered to the client app.
Authentication
An Access Token is required to use the SDK. In v3, the customer backend server calls the WaaS auth API to issue an Access Token and passes it to the client app.
Customer backend server → WaaS v3 Auth API → Issue Access Token
↓
Client app ← Access Token delivery
↓
Mobile SDK → Call MPC/WaaS APIs with Access Token
For details about Access Token issuance, refer to the v3.0 API docs.
Installation
ABC WaaS SDK already includes the MPC library (ABCMpc), so you only need to install the WaaS SDK without adding MPC separately.
Latest versions
| Platform | WaaS SDK | Included MPC version |
|---|---|---|
| Android | 0.1.29 | 0.1.14 |
| iOS | 0.1.29 | 0.1.13 |
Android
Add the following dependency in build.gradle.kts.
Add Maven repositories in settings.gradle.kts.
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")
}
}
}
Add dependency in build.gradle.kts.
dependencies {
implementation("io.ahnlab:abcwaas:0.1.29")
}
iOS
Install via Swift Package Manager. In Xcode, select File > Add Package Dependencies, then enter the following URL.
https://github.com/ahnlabio/abc-waas-ios-lib
Or add it directly in Package.swift.
dependencies: [
.package(url: "https://github.com/ahnlabio/abc-waas-ios-lib", from: "0.1.29")
]
Initialization
To use the SDK, you need to create WaasClient and WaasHelper.
WaasClient is an HTTP client responsible for communication with the WaaS server. It is used for WaaS API calls such as wallet lookup, key registration, and token issuance.
WaasHelper is a helper class that performs MPC key share generation, recovery, and signing. Internally, it communicates with the WaaS server via WaasClient and performs MPC computation by directly communicating with MPC nodes (node1, node2).
Android
import io.ahnlab.abcwaas.WaasClient
import io.ahnlab.abcwaas.WaasHelper
// Create WaasClient
val waasClient = WaasClient.create(baseUrl = "https://waas.example.com")
// Create WaasHelper (basic)
val helper = WaasHelper(
waasClient = waasClient,
node1BaseURL = "https://mpc-node1.example.com",
node2BaseURL = "https://mpc-node2.example.com"
)
iOS
import ABCWaas
// Create WaasClient
let waasClient = try WaasClient(baseUrl: "https://waas.example.com")
// Create WaasHelper (basic)
let helper = WaasHelper(
waasClient: waasClient,
node1BaseURL: "https://mpc-node1.example.com",
node2BaseURL: "https://mpc-node2.example.com"
)
Key share generation
Generates an MPC key share. Supported curves are secp256k1 and ed25519.
When generation completes, the following four values are returned.
| Field | Description |
|---|---|
keyId | Key identifier used when requesting signatures. |
encryptedShare | Encrypted key share; core data for signing. |
secretStore | Secret data required to decrypt the key share. |
curve | Used curve (secp256k1 or ed25519) |
Security recommendation: Returned values must be stored in secure storage on the mobile device. On Android, Keystore-based EncryptedSharedPreferences is recommended; on iOS, Keychain is recommended.
If you want a simpler flow without implementing your own secure storage setup, use
generateAndStoreKeySharefrom the SDK. It automatically stores the generated key share in platform secure storage. For details, see Key share secure storage.
Android
val result = helper.generateKeyShare(
accessToken = accessToken,
curve = "secp256k1",
password = "user_password"
)
if (result.isSuccess) {
val response = result.getOrThrow()
val keyId = response.keyId // key identifier
val encryptedShare = response.encryptedShare // encrypted key share
val secretStore = response.secretStore // secret store
val curve = response.curve // used curve
// Save returned values in the app's secure storage.
}
iOS
let result = await helper.generateKeyShare(
accessToken: accessToken,
curve: "secp256k1",
password: "user_password"
)
if case .success(let response) = result {
let keyId = response.keyId // key identifier
let encryptedShare = response.encryptedShare // encrypted key share
let secretStore = response.secretStore // secret store
let curve = response.curve // used curve
// Save returned values in the app's secure storage.
}
Key share recovery
If a locally stored key share is lost due to device change, app reinstall, etc., you can recover it based on key information registered on the server. During recovery, a new keyId is issued, and a new key share is generated with the same public key as the existing key.
Recovered key shares return the same values as generation: keyId, encryptedShare, secretStore, curve. As with key generation, these values should be stored in secure mobile storage. If automatic storage is needed, refer to recoverAndStoreKeyShare in Key share secure storage.
Android
val result = helper.recoverKeyShare(
accessToken = accessToken,
curve = "secp256k1",
password = "user_password"
)
if (result.isSuccess) {
val response = result.getOrThrow()
val keyId = response.keyId // newly issued key identifier
val encryptedShare = response.encryptedShare // encrypted key share
val secretStore = response.secretStore // secret store
val curve = response.curve // used curve
// Save returned values in the app's secure storage.
}
iOS
let result = await helper.recoverKeyShare(
accessToken: accessToken,
curve: "secp256k1",
password: "user_password"
)
if case .success(let response) = result {
let keyId = response.keyId // newly issued key identifier
let encryptedShare = response.encryptedShare // encrypted key share
let secretStore = response.secretStore // secret store
let curve = response.curve // used curve
// Save returned values in the app's secure storage.
}
Key share secure storage
The SDK provides automatic key share storage using platform secure storage. You can safely store and load key shares using SDK APIs only, without separately configuring Keystore or Keychain.
Stored key shares follow these security policies.
- Accessible only on the same device. Key shares cannot be read on other devices.
- Not restorable via iCloud backup, device migration, or Android backup.
- Not shared with other apps. Even on the same device, only the same app can access it.
| Platform | Storage method | Security policy |
|---|---|---|
| Android | EncryptedSharedPreferences | AES256-GCM encryption, Android Keystore-based master key management |
| iOS | Keychain | kSecAttrAccessibleWhenUnlockedThisDeviceOnly, valid only on this device |
Initialization (including 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
)
Generate key share + auto store
generateAndStoreKeyShare performs key share generation and secure storage in one step. Internally, it calls generateKeyShare, then automatically stores keyId, encryptedShare, secretStore, and curve in secure storage when successful. You do not need to implement separate storage logic in the app.
Android
val result = helper.generateAndStoreKeyShare(
accessToken = accessToken,
curve = "secp256k1",
password = "user_password"
)
if (result.isSuccess) {
// Key share has been generated and automatically stored in secure storage.
// You can immediately use stored key shares with signWithStoredKeyShare and related APIs.
}
iOS
let result = await helper.generateAndStoreKeyShare(
accessToken: accessToken,
curve: "secp256k1",
password: "user_password"
)
if case .success(_) = result {
// Key share has been generated and automatically stored in secure storage.
// You can immediately use stored key shares with signWithStoredKeyShare and related APIs.
}
Recover key share + auto store
recoverAndStoreKeyShare performs key share recovery and secure storage in one step. Since it stores the recovered key share immediately, you can use signing APIs based on stored key shares right after recovery.
Android
val result = helper.recoverAndStoreKeyShare(
accessToken = accessToken,
curve = "secp256k1",
password = "user_password"
)
if (result.isSuccess) {
// Key share has been recovered and automatically stored in secure storage.
}
iOS
let result = await helper.recoverAndStoreKeyShare(
accessToken: accessToken,
curve: "secp256k1",
password: "user_password"
)
if case .success(_) = result {
// Key share has been recovered and automatically stored in secure storage.
}
Stored key share management
Android
// Read
val stored = helper.getStoredKeyShare("secp256k1")
// Delete
helper.deleteStoredKeyShare("secp256k1")
// Delete all
helper.clearStoredKeyShares()
iOS
// Read
let stored = helper.getStoredKeyShare(curve: "secp256k1")
// Delete
helper.deleteStoredKeyShare(curve: "secp256k1")
// Delete all
helper.clearStoredKeyShares()
Signing
The SDK provides suitable signing methods by curve. Each signing API supports two modes: passing key share values directly, or loading stored key shares automatically from secure storage.
The returned signature is a hex-encoded string. message is the hash value (hex string) of the data to sign.
| Curve | Recommended signing method | Description |
|---|---|---|
| secp256k1 | MTA signing | Security-enhanced signing based on Multiplicative-to-Additive protocol |
| ed25519 | Standard signing | Standard EdDSA signing |
On successful signing, the signature value is returned in the following format.
// signature example
e4ee7a1e2bdb671873ef3d3b0d320d05319fdc6e129a1b21e0e64e6573520b26
fe5dcfb06a164aaea171a55bf187a85ff2ce3fdd27e28d8fde9b152ce86e2801
Each signing API supports two ways to provide key share data.
- Pass key share directly: Manage
keyId,encryptedShare, andsecretStorevalues returned from key share generation/recovery in your app, then pass them directly when signing. Use this mode when you implement and manage your own key share storage. - Use stored key share: Automatically load key share from SDK secure storage (
KeyShareStorage) and sign. You only need to specifycurve, which simplifies code and delegates key share management to the SDK.
Standard signing (ed25519)
Use standard signing for ed25519 curve.
Pass key share directly
Sign by directly passing values returned from key share generation or recovery.
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
}
Use stored key share
Automatically loads key share from secure storage and signs. You only need to specify curve without directly passing keyId, encryptedShare, and secretStore.
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 signing (secp256k1)
Use MTA (Multiplicative-to-Additive) signing for secp256k1 curve. MTA provides stronger security compared to standard signing.
Pass key share directly
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
}
Use stored key share
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"
)
Wallet info query
Using WaasClient, you can query wallet, key, and user information.
Get wallet information
Query the user’s wallet list and address information for each wallet.
Android
val result = waasClient.getV3Wallet(accessToken)
iOS
let result = await waasClient.getV3Wallet(accessToken: accessToken)
Response example
{
"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..."
}
}
]
}
Get key information
Query the list of keys registered by the user. One latest key is returned per curve.
Android
val result = waasClient.getV3WalletKey(accessToken)
iOS
let result = await waasClient.getV3WalletKey(accessToken: accessToken)
Response example
[
{
"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"
}
]
Get user information
Query user ID and registered key information together.
Android
val result = waasClient.getV3WalletUser(accessToken)
iOS
let result = await waasClient.getV3WalletUser(accessToken: accessToken)
Response example
{
"user_id": "470021fdc945471396a9e16ab320c017",
"key": [
{
"id": "d9545d5fffca6af3bc6e08c34645d3d8...",
"curve": "secp256k1",
"public_key": "04a1b2c3d4...",
"created_at": "2026-04-01T12:00:00Z"
}
]
}
Validation
You can validate key share and password in advance before signing. Running validation before signing helps prevent signing failures due to incorrect password or corrupted key share data.
Password validation
Validates whether the password used during key share generation is correct. It compares information stored in secretStore with the provided password.
Android
val result = helper.validatePassword(
password = "user_password",
secretStore = secretStore
)
if (result.isSuccess) {
val isValid = result.getOrThrow().result // true or false
}
iOS
let result = await helper.validatePassword(
password: "user_password",
secretStore: secretStore
)
if case .success(let response) = result {
let isValid = response.result // true or false
}
Response example
{
"result": true
}
Key share validation
Validates integrity of key share data. It checks whether encryptedShare and secretStore are intact and decryptable with the given password.
Android
val result = helper.validateShare(
encryptedShare = encryptedShare,
secretStore = secretStore,
password = "user_password"
)
if (result.isSuccess) {
val isValid = result.getOrThrow().result // true or 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 or false
}
Response example
{
"result": true
}
Supported curves
| Curve | Recommended signing method |
|---|---|
| secp256k1 | MTA signing |
| ed25519 | Standard signing |
Error handling
SDK errors are returned as HelperError.
Android
val result = helper.generateKeyShare(accessToken, curve, password)
if (result.isFailure) {
when (val error = result.exceptionOrNull()) {
is HelperError.WaasError -> {
// WaaS API error
println("WaaS error: ${error.message}")
}
is HelperError.MpcError -> {
// MPC computation error
println("MPC error: ${error.message}")
}
is HelperError.UnknownError -> {
// Other error
println("Error: ${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 error
print("WaaS error: \(waasError.description)")
case .mpcError(let mpcError):
// MPC computation error
print("MPC error: \(mpcError.description)")
case .unknownError(let message):
// Other error
print("Error: \(message)")
}
}
Security notes
- Key share secure storage uses platform secure storage.
- Android: EncryptedSharedPreferences (AES256-GCM encryption, Android Keystore-based master key management)
- iOS: Keychain (
kSecAttrAccessibleWhenUnlockedThisDeviceOnly)
- Stored key shares are accessible only by the same app on the same device. Other apps or devices cannot access them.
- Key shares are not restored via iCloud backup, device migration, or Android backup.
- MTA signing uses a multi-round protocol, so avoid sending multiple simultaneous signing requests for the same key.