Skip to content

Commit

Permalink
Merge pull request #5 from openfort-xyz/feat/refactor
Browse files Browse the repository at this point in the history
Feat/refactor
  • Loading branch information
jamalavedra authored Mar 11, 2024
2 parents 7e3dc4e + cc49cea commit 02d51ea
Show file tree
Hide file tree
Showing 20 changed files with 803 additions and 333 deletions.
Binary file added .DS_Store
Binary file not shown.
87 changes: 28 additions & 59 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,81 +40,50 @@ yarn add @openfort/openfort-js

## Usage

The package needs to be configured with your account's public key, which is
available in the [Openfort Dashboard][api-keys]. Require it with the key's
value:

```js
import Openfort from '@openfort/openfort-js';
const openfort = new Openfort('pk_test_...');
```
In order to sign messages, you have 4 options to choose from:
* Let Openfort handle the signing process, dont need to pass any signer to the Openfort instance.
* Sign yourself and pass the signature to Openfort, dont need to pass any signer to the Openfort instance.
* Use a Session Key to sign messages, you need to pass a SessionSigner to the Openfort instance.
* Use Embedded Signer to sign messages, you need to pass an Embedded Signer to the Openfort instance.

#### Session Signer
```ts
const sessionSigner = new SessionSigner()
const openfort = new Openfort('pk_test_...', sessionSigner);
```

#### Embedded Signer
For the embedded signer, if your player has an account you can pass it to the embedded signer to use it. If the account is not provided, the embedded signer will check if the localstorage has a device which is already registered, if not, it will create a new device and store it in the localstorage.
For the recovery process, you can ask the user for a password to encrypt the recovery share.

```ts
const embeddedSigner = new EmbeddedSigner('pk_test_...', 'acc_...', '********');
const openfort = new Openfort('pk_test_...', embeddedSigner);
```


### Create and store a new player session key

1. Create a session key pair for the player:

With the Openfort Unity SDK, you can sign transaction intents using one of four methods or signers:
```typescript
openfort.createSessionKey();
const sdk = new Openfort("pk_test_XXXXXXX");
```

2. Save the generated session key pair on device:
### 1. Session Signer
The Session Signer allows you to use external signing keys, without needing to provide it every time. Here's how to use it:

- **Configure the Session Key**: Call `configureSessionKey()`. This method returns an Ethereum address and a boolean indicating whether you need to register the key from the backend.
```typescript
openfort.saveSessionKey();
const sessionKey = sdk.configureSessionKey();
```
- **Register Key and Send Signature Session Request**: If `sessionKey.isRegistered` boolean is false, register the key from the backend. Refer to the documentation for [session management](https://www.openfort.xyz/docs/guides/accounts/sessions).
- **Send Signature Transaction Intent Request**: When calling sendSignatureTransactionIntentRequest, pass the transaction intent ID and the user operation hash. The session signer will handle the signing.

### 2. External Sign

3. Authorize player with the game backend service and passing the address of the session key pair:
This method allows you to externally sign transaction intents without logging in or additional configurations:

- **Call SendSignatureTransactionIntentRequest**: Simply pass the transaction intent ID and the signature.
```typescript
const address = openfort.sessionKey.address
// API call to the game backend with the address to register it
const response = await sdk.sendSignatureTransactionIntentRequest("transactionIntentId", null, "signature");
```

#### Register the session key using a non-custodial signer

If the Openfort account is owned by an external signer, the owner must use it to sign and approve the registration of the session key. The hash containing the message to be signed appears in [next_actions][next-action] from the create session request.

### 3. Embedded Signer
The Embedded Signer uses SSS to manage the private key on the client side. To learn more, visit our [security documentation](https://www.openfort.xyz/docs/security).
- **Login and Configure the Embedded Signer**: First, ensure the user is logged in, using `LoginWithEmailPassword`, `LoginWithOAuth` or if not registred `SignUpWithEmailPassword`. Then call `ConfigureEmbeddedSigner`. If a `MissingRecoveryMethod` exception is thrown, it indicates there's no share on the device and you have to call `ConfigureEmbeddedRecovery` to provide a recovery method.
```typescript
// Sign the message with the signer
await openfort.sendSignatureSessionRequest(
session_id,
signed_message
);
try {
await sdk.loginWithEmailPassword("email", "password");
sdk.configureEmbeddedSigner(chainId);
} catch (e) {
if (e instanceof MissingRecoveryMethod) {
await sdk.configureEmbeddedSignerRecovery(new PasswordRecovery("password"));
}
}
```

### Use the session key to sign a message

The hash containing the message to be signed appears in [next_actions][next-action] from the create transactionIntent request.

For now the only recovery method available is the `PasswordRecovery` method.
- **Send Signature Transaction Intent Request**: Similar to the session signer, pass the transaction intent ID and the user operation hash. The embedded signer reconstructs the key and signs the transaction.
```typescript
await openfort.signMessage(message);
await openfort.sendSignatureTransactionIntentRequest(
transactionIntent_id,
signed_message
);
const response = await sdk.sendSignatureTransactionIntentRequest("transactionIntentId", "userOp");
```


## Usage examples
- [Next.js application with non-custodial signer](https://github.com/openfort-xyz/samples/tree/main/rainbow-ssv-nextjs)
- [Next.js application with custodial signer and social login](https://github.com/openfort-xyz/samples/tree/main/ssv-social-nextjs)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openfort/openfort-js",
"version": "0.4.6",
"version": "0.5.0",
"description": "",
"author": "Openfort",
"repository": {
Expand Down
Binary file added src/.DS_Store
Binary file not shown.
58 changes: 47 additions & 11 deletions src/utils/iframe-client.ts → src/clients/iframe-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,30 @@ export class IframeClient {
if (!document) {
throw new Error("must be run in a browser");
}

const actualIframeURL = document.getElementById("openfort-iframe");
if (actualIframeURL) {
this._iframe = actualIframeURL as HTMLIFrameElement;
return;
}

this._chainId = chainId;
this._iframe = document.createElement("iframe");
const baseURL = iframeURL || "https://iframe.openfort.xyz";
this._iframe.src = baseURL + "/iframe?accessToken=" + accessToken + "&publishableKey=" + publishableKey;
this._iframe.style.display = "none";
this._iframe.id = "openfort-iframe";
document.body.appendChild(this._iframe);
}

private waitForIframeLoad(): Promise<void> {
if (!this._iframe.contentWindow) {
return new Promise((resolve) => {
this._iframe.onload = () => {
resolve();
};
});
}
public isLoaded(): boolean {
return this._iframe.contentWindow !== null;
}

return Promise.resolve();
private async waitForIframeLoad(): Promise<void> {
while (!this.isLoaded()) {
await new Promise((resolve) => setTimeout(resolve, 100));
}
}

async createAccount(password?: string): Promise<string> {
Expand Down Expand Up @@ -158,7 +164,37 @@ export class IframeClient {
});
}

dispose() {
document.body.removeChild(this._iframe);
async dispose(): Promise<void> {
await this.waitForIframeLoad();

return new Promise((resolve, reject) => {
const handleMessage = (event: MessageEvent) => {
if (event.data.action === "loggedOut") {
if (event.data.success) {
document.body.removeChild(this._iframe);
resolve();
} else {
reject(new Error(event.data.error || "Dispose failed"));
}

window.removeEventListener("message", handleMessage);
}
};

window.addEventListener("message", handleMessage);

setTimeout(() => {
if (this._iframe.contentWindow) {
this._iframe.contentWindow.postMessage(
{
action: "logout",
},
"*",
);
} else {
console.error("No iframe content window");
}
}, 1000);
});
}
}
27 changes: 6 additions & 21 deletions src/key-pair.ts → src/crypto/key-pair.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import {secp256k1} from "@noble/curves/secp256k1";
import {LocalStorage} from "./storage/local-storage";
import {StorageKeys} from "./storage/storage-keys";
import {SigningKey} from "@ethersproject/signing-key";
import {arrayify, Bytes, BytesLike, joinSignature} from "@ethersproject/bytes";
import {computeAddress} from "@ethersproject/transactions";
import {hashMessage} from "@ethersproject/hash";

export class KeyPair extends SigningKey {
private static readonly storage = new LocalStorage();

/**
* Initialize keypair based on the private key, if it is provided or generate a brand new keypair.
* @param privateKey Optional parameter to initialize private key from
Expand All @@ -25,32 +21,21 @@ export class KeyPair extends SigningKey {
return joinSignature(this.signDigest(hashMessage(arrayify(message))));
}

/**
* Save to the storage initialized as a static property of the KeyPair class
*/
public save(): void {
KeyPair.storage.save(StorageKeys.SESSION_KEY, this.privateKey);
}

/**
* Remove the keypair from the storage
*/
public remove(): void {
KeyPair.storage.remove(StorageKeys.SESSION_KEY);
}

/**
* Load private key from the storage and generate keypair based on it.
*/
public static load(): KeyPair | null {
const privateKey = KeyPair.storage.get(StorageKeys.SESSION_KEY);
public static load(privateKey: string): KeyPair | null {
return privateKey ? new KeyPair(arrayify(privateKey)) : null;
}

/**
* Return the address for the keypair
*/
public get address(): string {
public getPublicKey(): string {
return computeAddress(this.privateKey);
}

public getPrivateKey(): string {
return this.privateKey;
}
}
Loading

0 comments on commit 02d51ea

Please sign in to comment.