NAV Navbar
Logo

Documentation Index

swift java javascript

Introduction

Welcome to the Trusted Key Wallet SDK Documentation

This resource provides all the documentation you'll need to get started developing a Trusted Key Wallet app using the Trusted Key Wallet SDK.

API Credentials

Developers need to have API credentials to access the Trusted Key Platform. The Client ID and Client Secret credentials can be obtained by following the below steps:

  1. Download the Trusted Key App to your mobile device.
  2. Go to the Trusted Key Developer Portal homepage and click on "Sign up with Trusted Key App" in the upper navigation bar. You will need to enter your the email address you have previously registered with the Trusted Key app and authorize the login in the App.
  3. Once you have logged into the Trusted Key Developer Portal click on "Register New App" where you will be asked to enter your App name and callback URL. The Portal will then provision you with a Client ID and Client Secret to use in your API calls to the Trusted Key Platform.
  4. If you forget your App's API credentials you can always log back into the Trusted Key Developer portal and click on "My Apps".

Wallet SDK Documentation

Introduction

Welcome to the Trusted Key Wallet SDK! You can use the Wallet SDK to implement a Wallet Application, offering digital identity features as part of your custom app.

In chronological order, a Wallet App is responsible for

For mobile apps, we have SDKs for Swift (iOS) and Java (Android). You can view code examples in the dark area to the right, and you can switch the programming language of the examples with the tabs in the top right.

Securing the wallet

Setup PIN/biometric lock

class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

        // App-credentials from developer.trustedkey.com are required for certain blockchain transactions and APIs
        AppCredential.initialize("api_client_here", "api_secret_here")

        // Initialize the Passcode Lock
        PasscodeLockManager.initialize(logoImage: UIImage(named: "passcodelock_logo"))

        if PasscodeLockManager.hasPasscode() {
            // Request the passcode at application start
            PasscodeLockManager.showEnterPasscodeRoot(successCallback: applicationUnlocked)
        }
    }

    func applicationWillEnterForeground(_ application: UIApplication) {

        if (PasscodeLockManager.hasPasscode()) {
            // Request the passcode at application focus
            PasscodeLockManager.showEnterPasscodeRoot(successCallback: applicationUnlocked)
        }
    }

    // ...
}
// To enable the global PIN lock, derive from GlobalState
public final class MyApplication extends PassCodeLock.GlobalState {
    @Override
    public void onCreate() {
        super.onCreate();

        // App-credentials from developer.trustedkey.com are required for certain blockchain transactions and APIs
        AppCredential.initialize("api_client_here", "api_secret_here");

        PasscodeLockManager.initialize(R.mipmap.ic_launcher);
    }
}
// The wallet API is not supported in javascript.

The Wallet App should protect any information in the wallet by securing access to the app. To this end, the Wallet SDK provides a passcode/PIN lock which requires the user to enter a 6-digit PIN, or optionally use an OS-supported biometric (fingerprint or facial recognition), before granting access to the wallet.

Let the user set a PIN

class OnboardingSetPinCodeViewController : UIViewController {

    @IBAction func continueButton_Click(_ sender: AnyObject) {
        // Force the user to set a passcode
        PasscodeLockManager.showSetPasscode(parentVC: self, successCallback: {

            // A PIN has been set; continue with on-boarding
        })
    }

    // ...
}
class OnboardingSetPinCodeActivity extends AppCompatActivity {

    private static final int REQUEST_CODE_ENABLE = 11234;

    public void clickNext(View v) {
        // Force the user to set a passcode
        PasscodeLockManager.showSetPasscode(SetPinCodeActivity.this, REQUEST_CODE_ENABLE);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {

        // Check which request we're responding to
        if (requestCode == REQUEST_CODE_ENABLE) {

            // A PIN has been set; continue with on-boarding
        }
    }

    // ...
}
// The wallet API is not supported in javascript.

During on-boarding, the user is asked to the set a PIN for the wallet. This PIN is unrelated to the phone's lock screen, since the user might share the phone with others, but can still protect their identity wallet.

Change passcode/PIN

@IBAction func changePinCodeButton_Click(_ sender: Any) {

    assert(PasscodeLockManager.hasPasscode());
    PasscodeLockManager.showChangePasscode(parentVC: self, successCallback: { _ in })
}
Preference change_pin = findPreference("change_pin");
change_pin.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
    @Override
    public boolean onPreferenceClick(Preference preference) {
        PasscodeLockManager.showChangePasscode(getActivity());
        return true;
    }
});
// The wallet API is not supported in javascript.

Give the user an option to change the PIN.

Credential management

Generating a new set of credentials

if !Credential.Default.initialize() {
    // Failed to generate the default credential; already exist, or no Secure Enclave available
}
if (!Credential.Default.initialize()) {
    // Failed to generate the default credential; already exist, or no TrustZone available
}
// The wallet API is not supported in javascript.

Before we can request Trusted Key Identity Claims, we need to generate a secure set of credentials.

Check for valid credential

if Credential.Default.isValid() {

}
if (Credential.Default.isValid()) {

}
// The wallet API is not supported in javascript.

It's good to check for the validity of the default set of credentials at application start. The default credentials might get invalidated when the security of the device has been compromised, for example when the phone's lock screen has been disabled.

Deleting the user credentials

Credential.Default.delete()

Credential.Default.delete();
// The wallet API is not supported in javascript.

Deleting the credentials removes it from the device's Secure Enclave or TrustZone. Deletion is not the same as revocation: any associated data is still deemed valid by the relying parties, although the user will no longer be able to provide proof of ownership without having the valid credentials.

Identity Claim Management

Request Email claim (using Trusted Key)

@IBAction func sendEmailButtonClicked(_ sender: Any) {

    // Grab the email address from the UITextField
    if let email = emailTextField.text {

        if !isValidEmail(email) {
            // reject values that are not valid email addresses
            return
        }

        // Before requesting email verification, we need credentials to associate the email with
        _ = Credential.Default.initialize()

        // Create a unique request ID for this email verification request
        let uuid = UUID().uuidString

        TrustedKeyIssuerService.shared.requestEmailClaims(requestID: uuid, emailAddress: email, onSuccess: {

                // Save the request ID and email address for the verifyEmailClaim call below
                self.requestID = uuid

                // A verification email has been sent; enable the verification code text field
                codeTextField.isEnabled = true
                emailTextField.isEnabled = false
            })
        }, onError: { errorMessage in

            // Request for email verification failed; consider deleting the credentials we created
        })
    }
}

@IBAction func verifyCodeClicked(_ sender: Any) {

    // Grab the verification code from the UITextField
    if let code = codeTextField.text {

        if !isValidCode(code) {
            // reject values that are not valid 6-digit verification codes
            return
        }

        // The email had been verified in sendEmailButtonClicked
        let email = emailTextField.text

        TrustedKeyWalletService.shared.verifyEmailClaim(requestID: self.requestID!, authcode: code, emailAddress: email!, onSuccess: {

            // Before registering with the backend, make sure the user protects the identity with a PIN
            PasscodeLockManager.showSetPasscode(parentVC: self, successCallback: {

                // Now it's safe to register this identity with your backend
                self.registerWithBackend(Credential.Default)
            })
        }, onError: { errorMessage in

            // Verification of the code failed
        })
    }
}
public void sendEmailButtonClicked(View view) {

    String email = emailEditText.getText().toString();

    if (!isValidEmail(email)) {
        // reject values that are not valid email addresses
        return;
    }

    // Before requesting email verification, we need credentials to associate the email with
    Credential.Default.initialize()

    // Create a unique request ID for this email verification request
    String uuidString = UUID.randomUUID().toString();

    TrustedKeyIssuerService.shared.requestEmailClaims(
            uuidString,
            email,
            new CompletionHandler<Void>() {
                @Override
                public void onCompleted(Void result) {
                    // Save the request ID and email address for the verifyEmailClaim call below
                    this.requestID = uuid;

                    // A verification email has been sent; enable the verification code text field
                    codeTextField.setEnabled(true);
                    emailTextField.setEnabled(false);
                }

                @Override
                public void onError(String message) {
                }
            }
    );
}

public void verifyCodeClicked(View view) {

    String code = verify_code.getText().toString().trim();

    if (!isValidCode(code)) {
        // reject values that are not valid 6-digit verification codes
        return;
    }

    // The email had been verified in sendEmailButtonClicked
    String email = emailEditText.getText().toString();

    // Verify the code from email using TrustedKeyWalletService
    TrustedKeyWalletService.shared.verifyEmailClaim(this.requestID, code, email, new CompletionHandler<Void>() {
        @Override
        public void onCompleted(Void result) {

            // Before registering with the backend, make sure the user protects the identity with a PIN
            // This will result in a call to onActivityResult with the same value of REQUEST_CODE_ENABLE
            PasscodeLockManager.showSetPasscode(this, REQUEST_CODE_ENABLE);
        }

        @Override
        public void onError(String message) {
            // Verification of the code failed
        }
    });
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {

    // Check which request we're responding to
    if (requestCode == REQUEST_CODE_ENABLE && resultCode == RESULT_OK) {

        // Now it's safe to register this identity with your backend
        this.registerWithBackend(Credential.Default);
    }
}
// The wallet API is not supported in javascript.

Requesting an email claim is done by first contacting the Trusted Key Issuer Service. Each request needs a unique requestID, which is easily generated using a new UUID. Before contacting the issuer, make sure the user has a valid set of credentials, which is created by the Credential.Default.initialize() method.

The Trusted Key Issuer Service will then send a verification email to the provided email address. The email contains a unique 6-digit verification code, which the user has to enter to complete the verification. Unlike the initial TrustedKeyIssuerService.requestEmailClaims call, the verification of the code is done by the Trusted Key Wallet Service. This allows the verified email address to be used for future OAuth or OpenID-Connect requests, which are handled by the Wallet Service.

To verify the email, invoked TrustedKeyWalletService.verifyEmailClaim with the same requestID and the email address and verification code that was entered by the user. When verification succeeds, the email address has been registered with the Wallet Service and the user can now safely be registered with your own backend service.

Requesting claims is an asynchronous process. The actual claims are obtained from the issuer service by a call to ClaimIssuerService.getClaims.

Request generic claims (using Trusted Key)

let requestID = UUID().uuidString
let expiry = Date()
let attributes : [String:Any] = [
    OIDs.CommonName: "Your Name"
]

let image : [String:Any] = [
    "name": "diploma.jpg",
    "data": "....base64 encoded JPEG..."
]

TrustedKeyIssuerService.shared.requestImageClaims(
    requestID: requestID,
    expiry: expiry,
    attr: attributes,
    images: [image],
    onSuccess: { },
    onError: { errorMessage in })
String requestID = UUID.randomUUID().toString();
Date expiry = new Date();
JSONObject attributes = new JSONObject();
attributes.put(OIDs.CommonName, "Your Name");

JSONObject image = new JSONObject()
    .put("name", "diploma.jpg")
    .put("data", "....base64 encoded JPEG...");

JSONArray images = new JSONArray();
images.put(image)

TrustedKeyIssuerService.shared.requestImageClaims(
    requestID,
    expiry,
    attributes,
    images,
    new CompletionHandler() {
        public void onSuccess(Void json) { }
        public void onError(String errorMessage) { }
    });
const ClaimIssuerService = require("trustedkey-js/services/claimissuerservice")

// Get the key/secret are from https://developer.trustedkey.com/
const claimIssuerService = new ClaimIssuerService("https://issuer.trustedkey.com", "key", "secret")

let attr = {
    expiry: new Date().toString(),
    attributes: {
        "1.6.43.65.34": "value"
    },
    images: [{
        name: "diploma.jpg",
        data: "....base64 encoded JPEG..."
    }]
}

// You can either use async/await or Promises .then/.catch
const result = await claimIssuerService.requestImageClaims(attr)

Requesting generic claims is done by contacting the Trusted Key Claim Issuer Service. Before you can issue claims, your app has to be whitelisted by Trusted Key. During the whitelisting procedure we will provision an issuer tenant for your app, including an HSM-backed key for signing all the claims issued to your app.

Requesting claims is an asynchronous process. The actual claims are obtained from the issuer service by a call to TrustedKeyIssuerService.getClaims.

Request ID Document claims (using Trusted Key + AuthenticID)

let requestID = UUID().uuidString
let transactionID = "from Catfishair REST API"

TrustedKeyIssuerService.shared.requestAuthenticIDClaims(
    requestID: requestID,
    catfishAirTransactionID: transactionID,
    catfishAirVersion: 3,
    onSuccess: { _ in },
    onError: { errorMessage in })
String requestID = UUID.randomUUID().toString();
String transactionID = "from Catfishair REST API";

TrustedKeyIssuerService.shared.requestAuthenticIDClaims(
    requestID,
    transactionID,
    3,  // Catfishair API version
    new HttpUtils.HttpHandler() {
        public void onSuccess(JSONObject json) { }
        public void onError(String errorMessage) { }
    });
// This wallet API is not supported in javascript.

Requesting claims is done by contacting a Claim Issuer Service. Every issuer may have different requirements for the claims that it issues and each issuer may provide its own API for requesting those claims.

Here we request claims using the Trusted Key Issuer service, which uses the AuthenticID mobile SDK to verify the user's identity document such as Driver's License or Passport. Refer to the AuthenticID SDK documentation on how to obtain the transactionID.

Requesting claims is an asynchronous process. The actual claims are obtained from the issuer service by a call to TrustedKeyIssuerService.getClaims.

Check claim request status

TrustedKeyIssuerService.shared.getClaims(
    requestID: requestID,
    onSuccess: { claims in },
    onError: { errorMessage in })
TrustedKeyIssuerService.shared.getClaims(requestID, new CompletionHandler<Claim[]>() {
        @Override
        public void onCompleted(Claim[] pemArray) { }

        @Override
        public void onError(String message) { }
    });
// The wallet API is not supported in javascript.

After requesting claims, the same requestID should be used to fetch the actual claims once they have been issued by the issuer service.

While the requestClaims-related API relies on the specifics of the issuer, the getClaims API has no such requirement and is identical for all issuer APIs.

Claim push notification

class AppDelegate: UIResponder, UIApplicationDelegate {

    // ...

    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {

        TrustedKeyWalletService.shared.registerDevice(deviceToken: deviceToken, onSuccess: { _ in }, onError: { _ in })
    }


    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo:[AnyHashable: Any], fetchCompletionHandler:@escaping (UIBackgroundFetchResult) -> Void) {

        // We can receive two kinds of notifications: signature request and Claim status

        let requestid = userInfo["requestid"] as? String
        if requestid != nil {

            // The claims for request 'requestid' are available; you can use `getClaims` now.
        }
        else {

            // Handle the signature request (see below)
        }
    }

}
public class DeviceTokenService extends FirebaseInstanceIdService {

    @Override
    public void onTokenRefresh() {

        // Grab the unique device token (might get called before user creds are created!)
        String refreshedToken = FirebaseInstanceId.getInstance().getToken();

        TrustedKeyWalletService.shared.registerDevice(refreshedToken, null);
    }

}


public final class NotificationService extends FirebaseMessagingService {

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {

        String requestid = remoteMessage.getData().get("requestid");
        if (requestid != null) {

            // The claims for request 'requestid' are available; you can use `getClaims` now.
        }
        else {

            // Handle the signature request (see below)
        }
    }

}
// This wallet API is not supported in javascript.

Instead of polling, a Wallet App can register for push notifications with the wallet service. In order to receive push notifications, your Apple Push Notification certificate and/or your Firbase Cloud Messaging key must have been registered with your app in the Trusted Key Developer Portal.

First party login

The Wallet SDK can be used to securely login to your App's backend.

Request a signature request from the backend

func loginRequest(completion: @escaping (WalletUtils.SignatureRequest?, String?) -> Void) {

    // The 'login' endpoint is exposed by your App's backend to generate a login challenge
    HttpUtils.httpPost("https://your.app.backend/login", json: [:], onSuccess: { json in

        // For registration, force inclusion of the user's public key in the reply
        json.setValue(OIDs.PublicKey, forKey: "objectIds")

        // Parse the JSON structure into a `SignatureRequest` structure
        if let sr = WalletUtils.parseLoginNotification(userInfo: json) {

            completion(sr, nil)
        }
        else {

            completion(nil, LoginService.invalidLoginRequest)
        }
    }, onError: { errorMessage, _ in

        completion(nil, errorMessage)
    })
}
// TO-DO
const Express = require('express')
const Utils = require('trustedkey-js/utils')
const OID = require('trustedkey-js/oid')

const router = module.exports = Express.Router()

/* login request from app. */
router.post('/login', function(req, res, next) {

    const signatureRequest = {
        nonce: Utils.generateNonce(),
        callbackType: "POST",
        callbackUrl: "https://" + req.get('host') + '/confirmLogin',
        objectIds: OID.publicKey,
    }

    // ...store the request nonce in storage...

    res.json(signatureRequest)
})

A login to the App's backend involves first generating a challenge on the backend, and then sending that challenge to the App where the user can confirm or deny the login. Then, the App cryptographically signs the challenge with the user's credentials and sends back the signed challenge to the backend, where it's finally verified.

Send a signature request to a user

// This wallet API is not supported in swift.
// This wallet API is not supported in java.
const WalletService = require('trustedkey-js/services/walletservice')

const walletService = new WalletService("https://wallet.trustedkey.com/",
    "api_client_here",
    "api_secret_here")

const nonce = Utils.generateNonce()
walletService.request("user_address_or_login", nonce, "https://your.app.domain/callback_endpoint_here")
    .then(result => {
        console.log(result)
    })
    .catch(err => {
        console.error(err.stack)
        process.exitCode = 2
    })

Instead of polling an endpoint for signature requests, the Trusted Key Wallet API provides the request endpoint to push signature requests to individual users, identified by the blockchain address or registered login (using the verified email address or the registerLogin API.)

Respond to the login request

// Sign the request and reply to the backend.
// Setting `result` to `true` will sign the request and invoke the callback URL.
WalletUtils.invokeLoginCallback(loginInfo: signatureRequest!, result: true, onSuccess: { json in

    // At this point you'll want to store any session token from the backend for further API auth
    let token = json["token"]
    UserDefaults.standard.set(token!, forKey: "token")

    completion(nil)

}, onError: { errorMessage in

    completion(errorMessage)
})
// TO-DO
const JsonWebToken = require('jsonwebtoken')

router.post('/confirmLogin', function(req, res, next) {

    // Grab the id_token from the query string
    const id_token = req.query.id_token

    if (id_token === undefined) {
        throw new Error("Unauthorized")
    }

    // Decode the claims first without verification
    let claims = JsonWebToken.decode(id_token)

    // ...verify the id_token...

    // Example of creating a session token and returning it to the client
    const token = Utils.generateNonce()

    // ...store the session token in storage...

    res.json({token: "bearer "+token})
})

The Wallet SDK generates a signed id_token and invokes the callback URL from the signature request with the id_token. The id_token can then be verified on the backend with any available JWT APIs. In the JavaScript example we are using Auth0's jsonwebtoken NPM module.

After verification, the backend should generate a regular bearer token for authentication of any private backend API. In the example we are simply generating a unique nonce and use that as a bearer token on subsequent API calls.

Wallet Service

The wallet service manages pending signature requests from relying parties.

Get a pending signature request

TrustedKeyWalletService.shared.getPendingSignatureRequest(onSuccess: { signatureRequest in
  // Handle the request
}, onError: { errorMessage in
  // Handle the error
})
TrustedKeyWalletService.shared.getPendingSignatureRequest(
        new CompletionHandler<WalletUtils.SignatureRequest>() {
            public void onSuccess(WalletUtils.SignatureRequest request) { }
            public void onError(String errorMessage) { }
        }
);
// The wallet API is not supported in javascript.

HTTP Response

{
  "nonce": "unique_nonce_for_this_request",
  "callbackUrl": "https://relying_party_callback.url/?may_have=parameters",
  "objectIds": ["optional","requested.information.oid"],
  "documentUrl": "https://optional.document.url/for-signing.pdf"
}

There are three mechanisms for the Wallet App to receive signature requests from relying parties:

This endpoint retrieves the last pending signature request by polling the wallet service. The HTTP Authorization header determines for which user to fetch the request.

We currently have four kinds of signature requests:

The requests are distinguished by the presence or absence of fields in the SignatureRequest object:

Note that document signing might simultaneously involve a request for claims.

HTTP Request

GET https://wallet.trustedkey.com/getPendingRequest

URL Parameters

None. The HTTP Authorization header determines for which user to fetch the request.

Remove a specific signature request

TrustedKeyWalletService.shared.removeSignatureRequest(nonce: signatureRequest!.nonce, onSuccess: { _ in }, onError: { _ in })
TrustedKeyWalletService.shared.removeSignatureRequest(signatureRequest.nonce,
        new CompletionHandler<Void>() {
            public void onSuccess(Void unused) { }
            public void onError(String errorMessage) { }
        });
// The wallet API is not supported in javascript.

HTTP Response

{
  "result": true
}

This endpoint removes a specific signature request.

HTTP Request

GET https://wallet.trustedkey.com/removeSignatureRequest/?nonce=...

URL Parameters

Parameter Description
nonce The nonce of the request, as returned by getPendingSignatureRequest

Replying to a login request

// Create the callback URL for a accepted login
let url = WalletUtils.buildLoginCallback(loginInfo: signatureRequest!, result: true)

// Open the URL with the iOS URL handler
UIApplication.shared.openURL(url)
// Create the callback URL for a accepted login
String uri = WalletUtils.buildLoginCallback(loginInfo, true);

// Open the URL with the Android URL handler
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
// The wallet API is not supported in javascript.

In this part we'll use the functionality offered by WalletUtils to assemble the callback URL for the relying party. The callback URL differs depending on the kind of signature that was requested.

Push notifications

class AppDelegate: UIResponder, UIApplicationDelegate {

    // ...

    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo:[AnyHashable: Any], fetchCompletionHandler:@escaping (UIBackgroundFetchResult) -> Void) {

        // We can receive two kinds of notifications: auth and claim

        let requestid = userInfo["requestid"] as? String
        if requestid == nil {

            let request = WalletUtils.parseLoginNotification(userInfo: userInfo as NSDictionary)

            if (request != nil && isApplicationUnlocked) {

                // Pass the request to a view controller
            } else {

                // Application is locked; save for later
                signatureRequest = request
            }
        }
        else {
            // Handle claim notifications (see above)
        }
    }

}
public final class NotificationService extends FirebaseMessagingService {

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {

        String requestid = remoteMessage.getData().get("requestid");

        if (requestid != null) {

            // Handle the claim request (see above)
        }
        else {

            WalletUtils.SignatureRequest signatureRequest = WalletUtils.parseLoginNotification(remoteMessage.getData());
            if (signatureRequest != null) {

                // You can now pass to signatureRequest to an activty by serializing it into a bundle
            }
        }
    }

}
// The wallet API is not supported in javascript.

In addition to polling the wallet service for pending requests, the app can register for push notifications and receive signature requests when the app is not running.

Push notification require signing up for either Apple Push Notifications (for iOS) or Firebase Cloud Messaging (for Android).

class AppDelegate: UIResponder, UIApplicationDelegate {

    // ...

    // This gets called when our app is invoked from a Universal Link or some other type of activity.
    func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
        NSLog("AppDelegate - continueUserActivity")

        if (userActivity.activityType != NSUserActivityTypeBrowsingWeb) {
            return false
        }

        if (userActivity.webpageURL == nil) {
            return false
        }

        signatureRequest = WalletUtils.parseLoginUniversalLink(url: webpageURL!)
        return true
    }

}
public class LoginActivity extends AppCompatPinActivity {

    // ...

    @Override
    protected void onResume() {
        super.onResume();

        Uri universalLink = getIntent().getData();
        if (universalLink != null) {

            // Parse
            loginInfo = WalletUtils.parseLoginUniversalLink(universalLink);
        }
        else {

            // Perhaps we got invoked with a bundle?
            loginInfo = WalletUtils.parseLoginBundle(getIntent().getExtras());
        }
    }
}
// The wallet API is not supported in javascript.

Finally, an app can register a Universal Link. Clicking this link will open the wallet application, instead of opening the link in the browser. The Wallet Application can then parse the URL parameters and show the signature request details.

Replying to a user information request

// Decide which claims we need to satisfy the request
let claims = ClaimUtils.loadKeychainClaims().filter( { claim in
    // Return true or false, depending whether we want this claim or not
    return true
})

// Convert the required claims to Base64
let certs = ClaimUtils.getClaimsDer(claims: claims)
let base64certs = ClaimUtils.derToPem(certs.0)
let base64chain = ClaimUtils.derToPem(certs.1)

// Create the callback URL for a claim request
let url = WalletUtils.buildClaimCallback(loginInfo: signatureRequest!, claims: base64Certs, issuers: base64Chain)

// Open the URL with the iOS URL handler
UIApplication.shared.openURL(url)
// Decide which claims we need to satisfy the request
Claim[] claims = ClaimUtils.matchClaims(oids);

ClaimResult certs = ClaimUtils.getClaimsDer(claims);
String base64certs = ClaimUtils.derToPem(certs[0]);
String base64chain = ClaimUtils.derToPem(certs[1]);

// Create the callback URL for a claim request
String uri = WalletUtils.buildClaimCallback(loginInfo, base64certs, base64chain);

// Open the URL with the Android URL handler
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
// The wallet API is not supported in javascript.

The utilities in ClaimUtils allow you to iterate through the installed claims on the device. Use the helpers in WalletUtils to assemble the callback URL, which includes the nonce, a result code, claims (if applicable), any intermediary claims from the issuers (optional), and the final signature.

The assembled URL can be opened with the default browser, or with HttpUtils.httpGet if a JSON reply is expected.

Responding to OAuth/OpenID-Connect requests

Verifying a user's email address, as per the steps above, automatically register that email address as a login name for the user. Any relying party can use the OAuth endpoints to send login requests to the user's wallet using that verified email address.

Registering a user's login name

@IBAction func registerClicked(_ sender: Any) {

    let phoneNumber = "..."

    TrustedKeyWalletService.shared.registerLogin(login: phoneNumber, onSuccess: {
        // Succeeded
    }, onError: { errorMessage in
        // Failed
    })
}
public void registerClicked(View view) {

    String phoneNumber = "...";

    TrustedKeyWalletService.shared.registerLogin(phoneNumber, new CompletionHandler<Void>() {
        @Override
        public void onCompleted(Void result) {
            // Succeeded
        }

        @Override
        public void onError(String message) {
            // Failed
        }
    });
}
// This wallet API is not supported in javascript.

The registerLogin endpoint can be used by a Wallet App to register any identifier for the user to be used by the OAuth/OpenID-Connect endpoints. This could be, for example, a verified phone number of unique nickname.

Backup and recovery

Credential backup and recovery

@IBAction func createRecoveryKey_clicked(_ sender: Any) {

    let wordlist = RecoveryKeyUtils.getRandomWordList()

    let recoveryKey = RecoveryKeyUtils.deriveKeyFromWordList(wordList: wordlist, passphrase: nil)

    TrustedKeyRegistryService.shared.registerRecoveryKey(recoveryKey: recoveryKey!, onSuccess: { txid in

        TrustedKeyRegistryService.waitForTransaction(txID: txid, seconds: 30, completion: { txstatus in

            if txstatus == .SUCCEEDED {

                // Save the word list for quick testing of the recovery feature
                UserDefaults.standard.set(wordlist, forKey: "WORDLIST")

                // Show the word list to the user
            }
            else {
                // The transaction timed out or the blockchain could not be contacted
            }
        })

    }, onError: { errorMessage in

        // Recovery failed
    })
}


@IBAction func recover_clicked(_ sender: Any) {

    let wordlist = "..."

    // Derive the key from the saved wordlist (avoid the hassle of entering for purpose of demo)
    let recoveryKey = RecoveryKeyUtils.deriveKeyFromWordList(wordList: wordlist, passphrase: nil)

    // Before invoking the smart contract, we need to create credentials to recover to
    _ = Credential.Default.initialize()

    // Invoke the recovery method on the smart contract; this will return a blockchain transaction ID
    TrustedKeyRegistryService.shared.recoverCredential(recoveryKey: recoveryKey!, onSuccess: { txid in

        // Wait for the transaction to get validated
        TrustedKeyRegistryService.waitForTransaction(txID: txid, seconds: 30) { txstatus in

            if txstatus == .SUCCEEDED {

                // The old identity has been recovered to the newly created one; proceed with email/login
            }
            else {

                // The transaction timed out or the blockchain could not be contacted
            }
        }

    }, onError: { errorMessage in

        // Recovery failed; consider deleting the credentials we created
    })
}
// TO-DO
// This wallet API is not supported in javascript.

The user credentials are stored in the phone's secure hardware element and cannot be migrated to other phones. To backup and restore the credentials, a software-backed recovery key can be associated with the credentials and be used at a future time to associate a new set of credentials with the user's identity. This will register the recovery on the blockchain and will mark the old set of credentials as being replaced by the new credentials.

It's customary to have the user create the recovery key from a list of words which the user can write down and then be prompted for at a later time in order to regenerate the same recovery key. In addition to the word list, an optional passphrase can be provided as well, for example a memorized PIN code, to protect against a leaked word list.

Claim backup and recovery

After downloading the claims from the issuer service, a user's claims are only stored on the device and are not kept anywhere in the cloud. The credential recovery procedure mentiond above only takes care of replacing one set of credentials with a new set of credentials on a new phone. Claims will have to be separately backed up, and restored after recovery of the credentials.

The WalletService API helper provides the postBlob and getBlob APIs to store and retrieve arbitrary data on the Trusted Key servers. This data is encrypted and decrypted on the phone, so that our servers never know the contents of what is being stored. By default, the recovery key used by the credential recovery procedure is also used as the encryption key by the blob APIs.

The helpers in the ClaimUtils class can be used to retrieve all claims from the secure device storage and serialize them to a blob for easy backup.

Wallet API Authentication

To authorize, use this code:

import TrustedKeyWalletFramework

class AuthDemo {
  func HttpApi() {
    AppCredential.initialize("api_client_here", "api_secret_here")
    HttpUtils.httpGet(urlPath: "https://wallet.trustedkey.com/api_endpoint_here", onSuccess: { json in }, onError: { error in })
  }
}
import com.trustedkey.trustedkeyframework.Utils;
import com.trustedkey.trustedkeyframework.HttpUtils;

class AuthDemo {
  void HttpApi() {
    AppCredential.initialize("api_client_here", "api_secret_here");
    HttpUtils.httpGet("https://wallet.trustedkey.com/api_endpoint_here", new HttpUtils.HttpHandler() {
      @Override public void onSuccess(JSONObject json) {}
      @Override public void onError(String errorMessage) {}
    });
  }

  void ManualJwt() {
    long exp = 5*60 + System.currentTimeMillis() / 1000L;
    String jose = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
    String claims = "{\"iss\":\"api_client_here\",\"aud\":\"https://wallet.trustedkey.com/api_endpoint_here\",\"exp\":"+exp+"}";
    String jws = Utils.createHmacJws(jose, claims, "api_secret_here");
    String authorizationHeader = "Bearer " + jws;
  }
}
// npm i trustedkey-js
const Http = require('trustedkey-js/services/http');

// Using high-level HTTP API
const http = new Http("https://wallet.trustedkey.com/", "api_client_here", "api_secret_here");
http.get("api_endpoint_here", {param: value})
  .then(data => { })
  .catch(err => { });

// Low-level manual JWT construction
const Utils = require('trustedkey-js/utils');
let jose = {alg:"HS256",typ:"JWT"};
let claims = {iss:"api_client_here",aud:"https://wallet.trustedkey.com/api_endpoint_here",exp:new Date()};
let jws = Utils.createHmacJWS(jose, claims, "api_secret_here");

let httpAuthHeader = {Authorization: "Bearer " + jws};
const Request = require('request');
Request({url:claims.aud, headers: httpAuthHeader}, (err,data) => { });

Make sure to replace api_client_here and api_secret_here with your API credentials.

Trusted Key API uses API credentials to allow access to the API endpoints. You can register your developer account at developer portal.

The API key must be included in all API requests to the server in a header that looks like the following:

Authorization: Bearer hs256_jwt_here

Nested JWT

Pseudo code

let inner_jwt = [
    {"alg":"ES256","typ":"JWT"},
    {"sub":"ec_public_key","aud":"https://wallet.trustedkey.com/api_endpoint_here","exp":time_in_unix_epoch},
    ecdsa_sha256_signature_here
].map(base64url).join('.');

let outer_jwt = [
    {"alg":"HS256","typ":"JWT","cty":"JWT","iss":"api_client_here"},
    inner_jwt,
    hmac_sha256_signature_here,
].map(base64url).join('.');

In addition to the API credentials, some APIs require additional authentication with specific user credentials, in order to authorize the action on behalf of the end user. These APIs use a nested JWT instead, where the payload of the HMAC-SHA256 is itself a Base64-URL encoded JWT with the same payload, signed by the end user using the hardware ES256 key. The HttpUtils class does this for you.

Errors

The Trusted Key platform uses standard HTTP response codes for indicating whether a request succeeded or failed. In general, response codes in the 200-level range indicate success, response codes in the 400-level range indicate a failure caused by information provided, and response codes in the 500-level range indicate a failure caused by a problem on the server.

HTTP Status Codes

Code Meaning
200 OK
400 Bad request - Check the parameters to the API
401 Unauthorized - No HTTP Authorization header was present
403 Forbidden - The app or user was not authorized for this API
404 Not found
408 Request Timeout - The request timed out; please retry
410 Gone - The request is no longer valid
500 Internal server error
503 Service Unavailable - The blockchain node could not be reached