Skip to content

Commit

Permalink
Adding server-side signing to gala connect (GalaChain#373)
Browse files Browse the repository at this point in the history
* Adding server-side signing to gala connect

* Serverside signing

* Lint fix

* Update chain-connect/src/GalachainClient.ts

Co-authored-by: Jakub Dzikowski <[email protected]>

* Normalize Getters/setters

* Code review changes

---------

Co-authored-by: Jakub Dzikowski <[email protected]>
  • Loading branch information
IndiaJonathan and dzikowski authored Sep 24, 2024
1 parent dbcacdb commit d51ab70
Show file tree
Hide file tree
Showing 11 changed files with 237 additions and 61 deletions.
8 changes: 4 additions & 4 deletions chain-connect/src/ClientApi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ describe("API tests", () => {
},
Request: {
options: {
body: '{"domain":{"name":"Galachain"},"prefix":"\\u0019Ethereum Signed Message:\\n261","quantity":"1","signature":"sampleSignature","to":"client|63580d94c574ad78b121c267","tokenInstance":{"additionalKey":"none","category":"Unit","collection":"GALA","instance":"0","type":"none"},"types":{"TransferToken":[{"name":"quantity","type":"string"},{"name":"to","type":"string"},{"name":"tokenInstance","type":"tokenInstance"},{"name":"uniqueKey","type":"string"}],"tokenInstance":[{"name":"additionalKey","type":"string"},{"name":"category","type":"string"},{"name":"collection","type":"string"},{"name":"instance","type":"string"},{"name":"type","type":"string"}]},"uniqueKey":"26d4122e-34c8-4639-baa6-4382b398e68e"}',
body: '{"domain":{"name":"GalaChain"},"prefix":"\\u0019Ethereum Signed Message:\\n261","quantity":"1","signature":"sampleSignature","to":"client|63580d94c574ad78b121c267","tokenInstance":{"additionalKey":"none","category":"Unit","collection":"GALA","instance":"0","type":"none"},"types":{"TransferToken":[{"name":"quantity","type":"string"},{"name":"to","type":"string"},{"name":"tokenInstance","type":"tokenInstance"},{"name":"uniqueKey","type":"string"}],"tokenInstance":[{"name":"additionalKey","type":"string"},{"name":"category","type":"string"},{"name":"collection","type":"string"},{"name":"instance","type":"string"},{"name":"type","type":"string"}]},"uniqueKey":"26d4122e-34c8-4639-baa6-4382b398e68e"}',
headers: {
"Content-Type": "application/json"
},
Expand Down Expand Up @@ -117,7 +117,7 @@ describe("API tests", () => {
},
Request: {
options: {
body: '{"domain":{"name":"Galachain"},"prefix":"\\u0019Ethereum Signed Message:\\n74","publicKey":"3","signature":"sampleSignature","types":{"RegisterUser":[{"name":"publicKey","type":"string"},{"name":"user","type":"string"}]},"user":"4"}',
body: '{"domain":{"name":"GalaChain"},"prefix":"\\u0019Ethereum Signed Message:\\n74","publicKey":"3","signature":"sampleSignature","types":{"RegisterUser":[{"name":"publicKey","type":"string"},{"name":"user","type":"string"}]},"user":"4"}',
headers: {
"Content-Type": "application/json"
},
Expand Down Expand Up @@ -146,7 +146,7 @@ describe("API tests", () => {
},
Request: {
options: {
body: '{"domain":{"name":"Galachain"},"prefix":"\\u0019Ethereum Signed Message:\\n74","publicKey":"3","signature":"sampleSignature","types":{"RegisterUser":[{"name":"publicKey","type":"string"},{"name":"user","type":"string"}]},"user":"4"}',
body: '{"domain":{"name":"GalaChain"},"prefix":"\\u0019Ethereum Signed Message:\\n74","publicKey":"3","signature":"sampleSignature","types":{"RegisterUser":[{"name":"publicKey","type":"string"},{"name":"user","type":"string"}]},"user":"4"}',
headers: {
"Content-Type": "application/json"
},
Expand All @@ -166,7 +166,7 @@ describe("API tests", () => {
},
Request: {
options: {
body: '{"domain":{"name":"Galachain"},"prefix":"\\u0019Ethereum Signed Message:\\n74","publicKey":"3","signature":"sampleSignature","types":{"RegisterUser":[{"name":"publicKey","type":"string"},{"name":"user","type":"string"}]},"user":"4"}',
body: '{"domain":{"name":"GalaChain"},"prefix":"\\u0019Ethereum Signed Message:\\n74","publicKey":"3","signature":"sampleSignature","types":{"RegisterUser":[{"name":"publicKey","type":"string"},{"name":"user","type":"string"}]},"user":"4"}',
headers: {
"Content-Type": "application/json"
},
Expand Down
4 changes: 2 additions & 2 deletions chain-connect/src/GalachainClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ describe("MetamaskConnectClient", () => {
},
Request: {
options: {
body: '{"domain":{"name":"Galachain"},"prefix":"\\u0019Ethereum Signed Message:\\n261","quantity":"1","signature":"sampleSignature","to":"client|63580d94c574ad78b121c267","tokenInstance":{"additionalKey":"none","category":"Unit","collection":"GALA","instance":"0","type":"none"},"types":{"TransferToken":[{"name":"quantity","type":"string"},{"name":"to","type":"string"},{"name":"tokenInstance","type":"tokenInstance"},{"name":"uniqueKey","type":"string"}],"tokenInstance":[{"name":"additionalKey","type":"string"},{"name":"category","type":"string"},{"name":"collection","type":"string"},{"name":"instance","type":"string"},{"name":"type","type":"string"}]},"uniqueKey":"26d4122e-34c8-4639-baa6-4382b398e68e"}',
body: '{"domain":{"name":"GalaChain"},"prefix":"\\u0019Ethereum Signed Message:\\n261","quantity":"1","signature":"sampleSignature","to":"client|63580d94c574ad78b121c267","tokenInstance":{"additionalKey":"none","category":"Unit","collection":"GALA","instance":"0","type":"none"},"types":{"TransferToken":[{"name":"quantity","type":"string"},{"name":"to","type":"string"},{"name":"tokenInstance","type":"tokenInstance"},{"name":"uniqueKey","type":"string"}],"tokenInstance":[{"name":"additionalKey","type":"string"},{"name":"category","type":"string"},{"name":"collection","type":"string"},{"name":"instance","type":"string"},{"name":"type","type":"string"}]},"uniqueKey":"26d4122e-34c8-4639-baa6-4382b398e68e"}',
headers: {
"Content-Type": "application/json"
},
Expand Down Expand Up @@ -272,7 +272,7 @@ describe("TrustConnectClient", () => {
},
Request: {
options: {
body: '{"domain":{"name":"Galachain"},"prefix":"\\u0019Ethereum Signed Message:\\n261","quantity":"1","signature":"sampleSignature","to":"client|63580d94c574ad78b121c267","tokenInstance":{"additionalKey":"none","category":"Unit","collection":"GALA","instance":"0","type":"none"},"types":{"TransferToken":[{"name":"quantity","type":"string"},{"name":"to","type":"string"},{"name":"tokenInstance","type":"tokenInstance"},{"name":"uniqueKey","type":"string"}],"tokenInstance":[{"name":"additionalKey","type":"string"},{"name":"category","type":"string"},{"name":"collection","type":"string"},{"name":"instance","type":"string"},{"name":"type","type":"string"}]},"uniqueKey":"26d4122e-34c8-4639-baa6-4382b398e68e"}',
body: '{"domain":{"name":"GalaChain"},"prefix":"\\u0019Ethereum Signed Message:\\n261","quantity":"1","signature":"sampleSignature","to":"client|63580d94c574ad78b121c267","tokenInstance":{"additionalKey":"none","category":"Unit","collection":"GALA","instance":"0","type":"none"},"types":{"TransferToken":[{"name":"quantity","type":"string"},{"name":"to","type":"string"},{"name":"tokenInstance","type":"tokenInstance"},{"name":"uniqueKey","type":"string"}],"tokenInstance":[{"name":"additionalKey","type":"string"},{"name":"category","type":"string"},{"name":"collection","type":"string"},{"name":"instance","type":"string"},{"name":"type","type":"string"}]},"uniqueKey":"26d4122e-34c8-4639-baa6-4382b398e68e"}',
headers: {
"Content-Type": "application/json"
},
Expand Down
87 changes: 53 additions & 34 deletions chain-connect/src/GalachainClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,43 +12,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ChainCallDTO, ConstructorArgs, serialize, signatures } from "@gala-chain/api";
import { ChainCallDTO, serialize, signatures } from "@gala-chain/api";
import { BrowserProvider, SigningKey, computeAddress, getAddress, getBytes, hashMessage } from "ethers";

import { CustomEventEmitter, MetaMaskEvents } from "./helpers";
import { EventEmitter, Listener, MetaMaskEvents } from "./helpers";

export abstract class CustomClient extends CustomEventEmitter<MetaMaskEvents> {
abstract connect(): Promise<string>;
export abstract class CustomClient {
abstract sign(method: string, dto: any): Promise<any>;

protected address: string;
protected provider: BrowserProvider | undefined;

set setWalletAddress(val: string) {
this.address = getAddress(`0x${val.replace(/0x|eth\|/, "")}`);
}

get getGalachainAddress() {
return this.address.replace("0x", "eth|");
}

get getWalletAddress(): string {
return this.address;
}

async getPublicKey() {
const message = "Sign this to retrieve your public key";

const signature = await this.signMessage(message);

const messageHash = hashMessage(message);

const publicKey = SigningKey.recoverPublicKey(getBytes(messageHash), signature);

const recoveredAddress = computeAddress(publicKey);

return { publicKey, recoveredAddress };
}
abstract getPublicKey(): Promise<{ publicKey: string; recoveredAddress: string }>;
abstract walletAddress: string;

async submit<T, U>({
url,
Expand Down Expand Up @@ -94,7 +66,7 @@ export abstract class CustomClient extends CustomEventEmitter<MetaMaskEvents> {
try {
const data = await response.json();
if (data.error) {
return Promise.reject(data.error);
return Promise.reject(data.message ? new Error(data.message) : data.error);
}
return Promise.resolve(id ? { Hash: id, ...data } : data);
} catch (error) {
Expand All @@ -116,6 +88,53 @@ export abstract class CustomClient extends CustomEventEmitter<MetaMaskEvents> {
}
return this.calculatePersonalSignPrefix(newPayload);
}
}
export abstract class WebSigner extends CustomClient {
protected address: string;
protected provider: BrowserProvider | undefined;
abstract connect(): Promise<string>;

set walletAddress(val: string) {
this.address = getAddress(`0x${val.replace(/0x|eth\|/, "")}`);
}

get walletAddress(): string {
return this.address;
}

get galachainEthAlias() {
return this.address.replace("0x", "eth|");
}

private eventEmitter = new EventEmitter<MetaMaskEvents>();

public on(event: keyof MetaMaskEvents, listener: Listener<string | string[] | null>): this {
this.eventEmitter.on(event, listener);
return this;
}

public off(event: keyof MetaMaskEvents, listener: Listener<string | string[] | null>): this {
this.eventEmitter.off(event, listener);
return this;
}

public emit(event: keyof MetaMaskEvents, data: string[] | string | null): boolean {
return this.eventEmitter.emit(event, data);
}

async getPublicKey() {
const message = "Sign this to retrieve your public key";

const signature = await this.signMessage(message);

const messageHash = hashMessage(message);

const publicKey = SigningKey.recoverPublicKey(getBytes(messageHash), signature);

const recoveredAddress = computeAddress(publicKey);

return { publicKey, recoveredAddress };
}

public async signMessage(message: string) {
if (!this.provider) {
Expand Down
4 changes: 3 additions & 1 deletion chain-connect/src/chainApis/PublicKeyApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ export class PublicKeyApi {
public GetMyProfile(message?: string) {
return this.connection.submit<UserProfileBody, { message?: string }>({
method: "GetMyProfile",
payload: { message },
payload: {
...(message ? { message } : {})
},
sign: true,
url: this.chainCodeUrl
});
Expand Down
20 changes: 10 additions & 10 deletions chain-connect/src/customClients/MetamaskConnectClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ChainCallDTO, ConstructorArgs, signatures } from "@gala-chain/api";
import { ChainCallDTO, ConstructorArgs } from "@gala-chain/api";
import { BrowserProvider, getAddress } from "ethers";

import { CustomClient } from "../GalachainClient";
import { WebSigner } from "../GalachainClient";
import { generateEIP712Types } from "../Utils";
import { CustomEventEmitter, ExtendedEip1193Provider, MetaMaskEvents } from "../helpers";
import { ExtendedEip1193Provider } from "../helpers";

declare global {
interface Window {
ethereum?: ExtendedEip1193Provider;
}
}

export class MetamaskConnectClient extends CustomClient {
export class MetamaskConnectClient extends WebSigner {
constructor() {
super();
this.address = "";
Expand All @@ -42,11 +42,11 @@ export class MetamaskConnectClient extends CustomClient {
}
window.ethereum.on("accountsChanged", (accounts: string[]) => {
if (accounts.length > 0) {
this.setWalletAddress = getAddress(accounts[0]);
this.emit("accountChanged", this.getGalachainAddress);
this.walletAddress = getAddress(accounts[0]);
this.emit("accountChanged", this.galachainEthAlias);
this.emit("accountsChanged", accounts);
} else {
this.setWalletAddress = "";
this.walletAddress = "";
this.emit("accountChanged", null);
this.emit("accountsChanged", null);
}
Expand All @@ -61,8 +61,8 @@ export class MetamaskConnectClient extends CustomClient {

try {
const accounts = (await this.provider.send("eth_requestAccounts", [])) as string[];
this.setWalletAddress = getAddress(accounts[0]);
return this.getGalachainAddress;
this.walletAddress = getAddress(accounts[0]);
return this.galachainEthAlias;
} catch (error: unknown) {
throw new Error((error as Error).message);
}
Expand All @@ -80,7 +80,7 @@ export class MetamaskConnectClient extends CustomClient {
}

try {
const domain = { name: "Galachain" };
const domain = { name: "GalaChain" };
const types = generateEIP712Types(method, payload);

const prefix = this.calculatePersonalSignPrefix(payload);
Expand Down
58 changes: 58 additions & 0 deletions chain-connect/src/customClients/ServerSigningClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (c) Gala Games Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { SigningKey, computeAddress, ethers, hashMessage } from "ethers";

import { CustomClient } from "../GalachainClient";
import { generateEIP712Types } from "../Utils";
import { calculatePersonalSignPrefix } from "../helpers";

export class ServerSigningClient extends CustomClient {
get walletAddress(): string {
return this.wallet.address;
}
async getPublicKey(): Promise<{ publicKey: string; recoveredAddress: string }> {
const message = "I <3 GalaChain";
const signedMessage = await this.wallet.signMessage(message);
const publicKey = SigningKey.recoverPublicKey(hashMessage(message), signedMessage);
const recoveredAddress = computeAddress(publicKey);
return { publicKey, recoveredAddress };
}

private wallet: ethers.Wallet;

constructor(privateKey: string) {
super();
this.wallet = new ethers.Wallet(privateKey);
}

public async sign<U extends object>(
method: string,
payload: U
): Promise<U & { signature: string; prefix: string }> {
try {
const domain = { name: "GalaChain" };
const types = generateEIP712Types(method, payload);

const prefix = calculatePersonalSignPrefix(payload);
const prefixedPayload = { ...payload, prefix };

const signature = await this.wallet.signTypedData(domain, types, prefixedPayload);

return { ...prefixedPayload, signature, types, domain };
} catch (error: unknown) {
throw new Error((error as Error).message);
}
}
}
16 changes: 8 additions & 8 deletions chain-connect/src/customClients/TrustConnectClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import { ChainCallDTO, ConstructorArgs } from "@gala-chain/api";
import { BrowserProvider, getAddress } from "ethers";

import { CustomClient } from "../GalachainClient";
import { CustomClient, WebSigner } from "../GalachainClient";
import { generateEIP712Types } from "../Utils";
import { ExtendedEip1193Provider } from "../helpers";

Expand Down Expand Up @@ -91,7 +91,7 @@ function getTrustWalletFromWindow() {
return window["trustwallet"] ?? null;
}

export class GalachainConnectTrustClient extends CustomClient {
export class GalachainConnectTrustClient extends WebSigner {
constructor() {
super();
this.address = "";
Expand All @@ -103,11 +103,11 @@ export class GalachainConnectTrustClient extends CustomClient {
}
window.ethereum.on("accountsChanged", (accounts: string[]) => {
if (accounts.length > 0) {
this.setWalletAddress = getAddress(accounts[0]);
this.emit("accountChanged", this.getGalachainAddress);
this.walletAddress = getAddress(accounts[0]);
this.emit("accountChanged", this.galachainEthAlias);
this.emit("accountsChanged", accounts);
} else {
this.setWalletAddress = "";
this.walletAddress = "";
this.emit("accountChanged", null);
this.emit("accountsChanged", null);
}
Expand All @@ -123,8 +123,8 @@ export class GalachainConnectTrustClient extends CustomClient {

try {
const accounts = (await this.provider.send("eth_requestAccounts", [])) as string[];
this.setWalletAddress = getAddress(accounts[0]);
return this.getGalachainAddress;
this.walletAddress = getAddress(accounts[0]);
return this.galachainEthAlias;
} catch (error: any) {
if (error.code === 4001) {
console.error("User denied connection.");
Expand All @@ -145,7 +145,7 @@ export class GalachainConnectTrustClient extends CustomClient {
}

try {
const domain = { name: "Galachain" };
const domain = { name: "GalaChain" };
const types = generateEIP712Types(method, payload);

const prefix = this.calculatePersonalSignPrefix(payload);
Expand Down
1 change: 1 addition & 0 deletions chain-connect/src/customClients/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@
*/
export * from "./MetamaskConnectClient";
export * from "./TrustConnectClient";
export * from "./ServerSigningClient";
Loading

0 comments on commit d51ab70

Please sign in to comment.