# Sequence WaaS Unreal SDK This document outlines the scope for getting a v1.0 of the Sequence Unreal SDK ready to be shared with initial partners. It also includes high-level information on how to integrate WaaS (Wallet as a Service) authentication and transaction signing. ## Scope Sequence Unreal SDK is meant to be the easiest way to integrate with the Sequence WaaS solution, providing out of the box authentication and embedded wallet capabilities for any Unreal engine based game. High level functionality required for a v1.0 launch is as follows: - Email authentication (with WaaS) - OAuth social login (with WaaS) - Google - Discord - Facebook - Apple - SDK methods for session management: - List sessions - Close session (sign out) - Session validation - SDK methods for on-chain activity: - Sign message - Send transaction - Send ERC20 / ERC721 / ERC1155 transaction - Call arbitrary methods on any contract - SDK Indexer methods for reading on-chain data: - Get token balances for address (and contract) - Get transaction history for address Only UI flows needed are for email and OAuth login / registration. All other functionality is meant to be programmatic only. [Figma designs are available here](https://www.figma.com/file/Utqtfk7bWp0OQlP52vBcjq/Unity-%2F-Unreal-SDK?type=design&node-id=370-9012&mode=design) (password: `gameon`). However the only relevant sections within this scope are "Sign in" and "Success modal". ## WaaS Authentication Flow What follows is a high-level guide on all the steps the SDK needs to tackle in order complete a successful authentication process on WaaS. ### Config values required: - `region` - `identityPoolId` - `kmsKeyId` - `projectId` - `cognitoClientId` (if using Passwordless) ### Passwordless (Cognito) sign-in 1. Collect email address from the user. 2. Call AWS `CognitoIdentityProvider.InitiateAuth` with: ``` { "AuthFlow": "CUSTOM_AUTH", "AuthParameters": { "USERNAME": $email }, "ClientId": $cognitoClientId } ``` 3. If it fails with 400 and "user not found" as part of `message` field: a. Generate a password as `aB1%` + 12 random characters. b. Call AWS `CognitoIdentityProvider.SignUp` with: ``` { "ClientId": $cognitoClientId, "Password": $password, "UserAttributes": [ { "Name": "email", "Value": $email } ], "Username": $email } ``` c. Repeat step 2, if it fails again, abort. 4. The `InitiateAuth` call returns a `Session` field, this is a `challengeSession`. In the meantime, the user receives a login code to their email inbox. 5. Collect the login code from the user. 6. Call AWS `CognitoIdentityProvider.RespondToAuthChallenge` with: ``` { "ChallengeName": "CUSTOM_CHALLENGE", "ClientId": $cognitoClientId, "Session": $challengeSession, "ChallengeResponses": { "USERNAME": $email, "ANSWER": $loginCode } } ``` 7. The `RespondToAuthChallenge` call returns `AuthenticationResult.IdToken` that is `idToken` to be used in the setup. ### Setup steps: 1. Sign-in the user with OAuth or passwordless and receive ID token. 2. Call AWS `CognitoIdentity.GetId` with `identityPoolId` and `logins: { [issuer]: idToken }`. This returns `identityId`. 3. Call AWS `CognitoIdentity.GetCredentialsForIdentity` with `identityId` and `logins: { [issuer]: idToken }`. This returns credentials (`accessKeyId`, `secretKey`, `sessionToken`) 4. Future AWS calls should use the credentials retrieved above. 5. Call AWS `KMS.GenerateDataKey` with `keyId=kmsKeyId` and `keySpec=AES_256`. This returns `plaintext` and `ciphertextBlob` (`encryptedPayloadKey`), this is a `TransportKey`. Store it locally for the duration of the session. 6. Generate a local eth wallet from random entropy. This is a `SessionWallet`. Store it locally for the duration of the session. ### Register session: 1. Prepare the intent JSON object: ``` { "version": "1.0.0", "packet": { "code": "openSession", "expires": 1600086400, "issued": 1600000000, "proof": { "idToken": $idToken }, "session": $SessionWallet.address, } } ``` 2. Prepare the payload JSON object: ``` { "projectId": $projectId, "idToken": $idToken, "sessionAddress": $SessionWallet.address, "friendlyName": "FRIENDLY SESSION WALLET", "intentJSON": string($intentJSON) } ``` 3. Use AES-256-CBC (see below) to encrypt the payload JSON string. This is `payloadCiphertext`. 4. Use the `SessionWallet` to sign the payload JSON string. The signature is the `payloadSig`. 5. Call the `WaasAuthenticator/RegisterSession` RPC with the following arguments: `encryptedPayloadKey`, `payloadCiphertext`, `payloadSig`. This returns the `session.id` and `data.wallet` - both should be stored locally as `sessionId` and `userWallet`. Temporary root endpoint for this RPC is https://d14tu8valot5m0.cloudfront.net/rpc/WaasAuthenticator ### AES-256-CBC encryption: 1. Generate `iv`: 16 random bytes 2. Create a CBC encrypter with AES-256 block cipher using parameters: `iv` generated above and `plaintext` TransportKey as encryption key. 3. Add PKCS7 padding to the payload string. 4. Encrypt padded payload string using the CBC encrypter. 5. `payloadCiphertext` is concatenated `iv` bytes and encryption result. ### Misc Notes You can view an actual implementation of the authentication flow [within the Unity SDK here](https://github.com/0xsequence/sequence-unity/blob/Feature/WaaSIntegration_auth/Assets/SequenceSDK/WaaS/WaaSLogin.cs). Full specs for WaaS [are available here](https://hackmd.io/@-_WYFKbvSmip5m7MNB4b8A/H1IMOZNMT). Docs for WaaS client [are available here](https://docs.sequence.xyz/waas/intro/). ## WaaS Transaction Signature Flow 1. [Intent payload](https://docs.sequence.xyz/waas/implementation/payloads/#sign-message-payload) is first signed and then stringified into JSON. To do this, prepare a packet, e.g. for `signMessage`: ``` { "code": "signMessage", "expires": 1600086400, "issued": 1600000000, "message": "Join game: #284892" "network": "1", "wallet": "0xBc5F07A5852fdF3DBd57A76835109220D0ADd8E8", } ``` (ensure fields are in alphabetical order and there's no whitespace outside of quotes) 2. Sign that packet string using EIP-191, full steps (your library might already do all or some of this, but remember that the packet must be hashed first): ``` packetHash = keccak256(packet) message = "\x19Ethereum Signed Message:\n" + len(packetHash) + packetHash messageHash = keccak256(message) signature = sessionWallet.sign(message_hash) ``` 3. Construct the final intent: ``` { "version": "1.0.0", "packet": $packet, "signatures": [{ "session": $sessionWallet.address, "signature": $signature }] } ``` 4. That is then encoded within this struct: ``` struct SendIntentPayload - sessionId: string - intentJson: string ``` 5. That struct is then stringified into JSON (this is now "RPC payload") 6. The RPC payload JSON string is encrypted using the Plaintext from KMS -> payloadCiphertext 7. The RPC payload JSON string is signed using the sessionWallet -> signedPayload. It is not the same signature as the one inside the signatures array of the intent payload, since that signature is only of the (canonical) packet JSON, while the signedPayload is a signature of the entire RPC payload JSON string (before encryption)