From 85965702144b71c5fc781ec3a0493a5997c6b7be Mon Sep 17 00:00:00 2001 From: Jin <128556004+0xjinn@users.noreply.github.com> Date: Thu, 28 Sep 2023 01:11:37 -0700 Subject: [PATCH] [TSSDKv2][3/n] Account and AuthenticationKey Classes (#10210) * Add helper.ts and fixes based on comments * add asymmetric_crypto file which includes all crypto base classes as abstract. And have all concrete crypto classes to extend from based abstract classes * Update crypto classes name to include Ed25519 prefix * fixes based on comments * Add account and auth key classes * update Account class to accept abstract PublicKey and PrivateKey * update methods' comment --- .../typescript/sdk_v2/src/core/account.ts | 168 ++++++++++++++++++ .../sdk_v2/src/crypto/asymmetric_crypto.ts | 18 ++ .../sdk_v2/src/crypto/authentication_key.ts | 104 +++++++++++ .../typescript/sdk_v2/src/crypto/ed25519.ts | 2 +- .../sdk_v2/src/crypto/multi_ed25519.ts | 13 ++ .../typescript/sdk_v2/src/utils/hd-key.ts | 76 ++++++++ .../sdk_v2/tests/unit/account.test.ts | 71 ++++++++ .../tests/unit/authentication_key.test.ts | 49 +++++ .../typescript/sdk_v2/tests/unit/helper.ts | 3 + 9 files changed, 503 insertions(+), 1 deletion(-) create mode 100644 ecosystem/typescript/sdk_v2/src/core/account.ts create mode 100644 ecosystem/typescript/sdk_v2/src/crypto/authentication_key.ts create mode 100644 ecosystem/typescript/sdk_v2/src/utils/hd-key.ts create mode 100644 ecosystem/typescript/sdk_v2/tests/unit/account.test.ts create mode 100644 ecosystem/typescript/sdk_v2/tests/unit/authentication_key.test.ts diff --git a/ecosystem/typescript/sdk_v2/src/core/account.ts b/ecosystem/typescript/sdk_v2/src/core/account.ts new file mode 100644 index 0000000000000..c0dce7938563c --- /dev/null +++ b/ecosystem/typescript/sdk_v2/src/core/account.ts @@ -0,0 +1,168 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +import nacl from "tweetnacl"; +import * as bip39 from "@scure/bip39"; +import { AccountAddress } from "./account_address"; +import { Hex } from "./hex"; +import { bytesToHex } from "@noble/hashes/utils"; +import { HexInput } from "../types"; +import { PrivateKey, PublicKey, Signature } from "../crypto/asymmetric_crypto"; +import { derivePath } from "../utils/hd-key"; +import { AuthenticationKey } from "../crypto/authentication_key"; +import { Ed25519PrivateKey, Ed25519PublicKey, Ed25519Signature } from "../crypto/ed25519"; + +/** + * Class for creating and managing account on Aptos network + * + * Use this class to create accounts, sign transactions, and more. + * Note: Creating an account instance does not create the account onchain. + */ +export class Account { + /** + * A private key and public key, associated with the given account + */ + readonly publicKey: PublicKey; + readonly privateKey: PrivateKey; + + /** + * Account address associated with the account + */ + readonly accountAddress: AccountAddress; + + /** + * constructor for Account + * + * TODO: This constructor uses the nacl library directly, which only works with ed25519 keys. + * Need to update this to use the new crypto library if new schemes are added. + * + * @param args.privateKey PrivateKey - private key of the account + * @param args.address AccountAddress - address of the account + * + * This method is private because it should only be called by the factory static methods. + * @returns Account + */ + private constructor(args: { privateKey: PrivateKey; address: AccountAddress }) { + const { privateKey, address } = args; + + // Derive the public key from the private key + const keyPair = nacl.sign.keyPair.fromSeed(privateKey.toUint8Array().slice(0, 32)); + this.publicKey = new Ed25519PublicKey({ hexInput: keyPair.publicKey }); + + this.privateKey = privateKey; + this.accountAddress = address; + } + + /** + * Derives an account with random private key and address + * + * @returns Account + */ + static generate(): Account { + const keyPair = nacl.sign.keyPair(); + const privateKey = new Ed25519PrivateKey({ value: keyPair.secretKey.slice(0, 32) }); + const address = new AccountAddress({ data: Account.authKey({ publicKey: keyPair.publicKey }).toUint8Array() }); + return new Account({ privateKey, address }); + } + + /** + * Derives an account with provided private key + * + * @param args.privateKey Hex - private key of the account + * @returns Account + */ + static fromPrivateKey(args: { privateKey: HexInput }): Account { + const privatekeyHex = Hex.fromHexInput({ hexInput: args.privateKey }); + const keyPair = nacl.sign.keyPair.fromSeed(privatekeyHex.toUint8Array().slice(0, 32)); + const privateKey = new Ed25519PrivateKey({ value: keyPair.secretKey.slice(0, 32) }); + const address = new AccountAddress({ data: Account.authKey({ publicKey: keyPair.publicKey }).toUint8Array() }); + return new Account({ privateKey, address }); + } + + /** + * Derives an account with provided private key and address + * This is intended to be used for account that has it's key rotated + * + * @param args.privateKey Hex - private key of the account + * @param args.address AccountAddress - address of the account + * @returns Account + */ + static fromPrivateKeyAndAddress(args: { privateKey: HexInput; address: AccountAddress }): Account { + const privatekeyHex = Hex.fromHexInput({ hexInput: args.privateKey }); + const signingKey = nacl.sign.keyPair.fromSeed(privatekeyHex.toUint8Array().slice(0, 32)); + const privateKey = new Ed25519PrivateKey({ value: signingKey.secretKey.slice(0, 32) }); + return new Account({ privateKey, address: args.address }); + } + + /** + * Derives an account with bip44 path and mnemonics, + * + * @param path. (e.g. m/44'/637'/0'/0'/0') + * Detailed description: {@link https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki} + * @param mnemonics. + * @returns AptosAccount + */ + static fromDerivationPath(args: { path: string; mnemonic: string }): Account { + const { path, mnemonic } = args; + if (!Account.isValidPath({ path })) { + throw new Error("Invalid derivation path"); + } + + const normalizeMnemonics = mnemonic + .trim() + .split(/\s+/) + .map((part) => part.toLowerCase()) + .join(" "); + + const { key } = derivePath(path, bytesToHex(bip39.mnemonicToSeedSync(normalizeMnemonics))); + + const signingKey = nacl.sign.keyPair.fromSeed(key.slice(0, 32)); + const privateKey = new Ed25519PrivateKey({ value: signingKey.secretKey.slice(0, 32) }); + const address = new AccountAddress({ data: Account.authKey({ publicKey: signingKey.publicKey }).toUint8Array() }); + + return new Account({ privateKey, address }); + } + + /** + * Check's if the derive path is valid + */ + static isValidPath(args: { path: string }): boolean { + return /^m\/44'\/637'\/[0-9]+'\/[0-9]+'\/[0-9]+'+$/.test(args.path); + } + + /** + * This key enables account owners to rotate their private key(s) + * associated with the account without changing the address that hosts their account. + * See here for more info: {@link https://aptos.dev/concepts/accounts#single-signer-authentication} + * @returns Authentication key for the associated account + */ + static authKey(args: { publicKey: HexInput }): Hex { + const publicKey = new Ed25519PublicKey({ hexInput: args.publicKey }); + const authKey = AuthenticationKey.fromPublicKey({ publicKey }); + return authKey.data; + } + + /** + * Sign the given message with the private key. + * + * @param args.data in HexInput format + * @returns Signature + */ + sign(args: { data: HexInput }): Signature { + const signature = this.privateKey.sign({ message: args.data }); + return signature; + } + + /** + * Verify the given message and signature with the public key. + * + * @param args.message raw message data in HexInput format + * @param args.signature signed message Signature + * @returns + */ + verifySignature(args: { message: HexInput; signature: Signature }): boolean { + const { message, signature } = args; + const rawMessage = Hex.fromHexInput({ hexInput: message }).toUint8Array(); + return this.publicKey.verifySignature({ data: rawMessage, signature }); + } +} diff --git a/ecosystem/typescript/sdk_v2/src/crypto/asymmetric_crypto.ts b/ecosystem/typescript/sdk_v2/src/crypto/asymmetric_crypto.ts index 11e3ab99aab15..8073aa3d34bf8 100644 --- a/ecosystem/typescript/sdk_v2/src/crypto/asymmetric_crypto.ts +++ b/ecosystem/typescript/sdk_v2/src/crypto/asymmetric_crypto.ts @@ -8,6 +8,12 @@ export abstract class PublicKey implements Serializable, Deserializable { + // Convert the signature to bytes or Uint8Array. + abstract toUint8Array(): Uint8Array; + + // Convert the signature to a hex string with the 0x prefix. + abstract toString(): string; + // TODO: This should be a static method. abstract deserialize(deserializer: Deserializer): Signature; abstract serialize(serializer: Serializer): void; diff --git a/ecosystem/typescript/sdk_v2/src/crypto/authentication_key.ts b/ecosystem/typescript/sdk_v2/src/crypto/authentication_key.ts new file mode 100644 index 0000000000000..7527dfa8114d0 --- /dev/null +++ b/ecosystem/typescript/sdk_v2/src/crypto/authentication_key.ts @@ -0,0 +1,104 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +import { sha3_256 as sha3Hash } from "@noble/hashes/sha3"; +import { AccountAddress, Hex } from "../core"; +import { HexInput } from "../types"; +import { MultiEd25519PublicKey } from "./multi_ed25519"; +import { PublicKey } from "./asymmetric_crypto"; + +/** + * Each account stores an authentication key. Authentication key enables account owners to rotate + * their private key(s) associated with the account without changing the address that hosts their account. + * @see {@link https://aptos.dev/concepts/accounts | Account Basics} + * + * Note: AuthenticationKey only supports Ed25519 and MultiEd25519 public keys for now. + * + * Account addresses can be derived from AuthenticationKey + */ +export class AuthenticationKey { + // Length of AuthenticationKey in bytes(Uint8Array) + static readonly LENGTH: number = 32; + + // Scheme identifier for MultiEd25519 signatures used to derive authentication keys for MultiEd25519 public keys + static readonly MULTI_ED25519_SCHEME: number = 1; + + // Scheme identifier for Ed25519 signatures used to derive authentication key for MultiEd25519 public key + static readonly ED25519_SCHEME: number = 0; + + // Scheme identifier used when hashing an account's address together with a seed to derive the address (not the + // authentication key) of a resource account. + static readonly DERIVE_RESOURCE_ACCOUNT_SCHEME: number = 255; + + // Actual data of AuthenticationKey, in Hex format + public readonly data: Hex; + + constructor(args: { data: HexInput }) { + const { data } = args; + const hex = Hex.fromHexInput({ hexInput: data }); + if (hex.toUint8Array().length !== AuthenticationKey.LENGTH) { + throw new Error(`Authentication Key length should be ${AuthenticationKey.LENGTH}`); + } + this.data = hex; + } + + toString(): string { + return this.data.toString(); + } + + toUint8Array(): Uint8Array { + return this.data.toUint8Array(); + } + + /** + * Converts a K-of-N MultiEd25519PublicKey to AuthenticationKey with: + * `auth_key = sha3-256(p_1 | … | p_n | K | 0x01)`. `K` represents the K-of-N required for + * authenticating the transaction. `0x01` is the 1-byte scheme for multisig. + * + * @param multiPublicKey A K-of-N MultiPublicKey + * @returns AuthenticationKey + */ + static fromMultiPublicKey(args: { multiPublicKey: MultiEd25519PublicKey }): AuthenticationKey { + const { multiPublicKey } = args; + const multiPubKeyBytes = multiPublicKey.toUint8Array(); + + const bytes = new Uint8Array(multiPubKeyBytes.length + 1); + bytes.set(multiPubKeyBytes); + bytes.set([AuthenticationKey.MULTI_ED25519_SCHEME], multiPubKeyBytes.length); + + const hash = sha3Hash.create(); + hash.update(bytes); + + return new AuthenticationKey({ data: hash.digest() }); + } + + /** + * Converts a PublicKey(s) to AuthenticationKey + * + * @param publicKey + * @returns AuthenticationKey + */ + static fromPublicKey(args: { publicKey: PublicKey }): AuthenticationKey { + const { publicKey } = args; + const pubKeyBytes = publicKey.toUint8Array(); + + const bytes = new Uint8Array(pubKeyBytes.length + 1); + bytes.set(pubKeyBytes); + bytes.set([AuthenticationKey.ED25519_SCHEME], pubKeyBytes.length); + + const hash = sha3Hash.create(); + hash.update(bytes); + + return new AuthenticationKey({ data: hash.digest() }); + } + + /** + * Derives an account address from AuthenticationKey. Since current AccountAddress is 32 bytes, + * AuthenticationKey bytes are directly translated to AccountAddress. + * + * @returns AccountAddress + */ + derivedAddress(): AccountAddress { + return new AccountAddress({ data: this.data.toUint8Array() }); + } +} diff --git a/ecosystem/typescript/sdk_v2/src/crypto/ed25519.ts b/ecosystem/typescript/sdk_v2/src/crypto/ed25519.ts index 188cc38c3b2a5..b71f9984c2d13 100644 --- a/ecosystem/typescript/sdk_v2/src/crypto/ed25519.ts +++ b/ecosystem/typescript/sdk_v2/src/crypto/ed25519.ts @@ -149,7 +149,7 @@ export class Ed25519PrivateKey extends PrivateKey { /** * Generate a new random private key. * - * @returns + * @returns Ed25519PrivateKey */ static generate(): Ed25519PrivateKey { const keyPair = nacl.sign.keyPair(); diff --git a/ecosystem/typescript/sdk_v2/src/crypto/multi_ed25519.ts b/ecosystem/typescript/sdk_v2/src/crypto/multi_ed25519.ts index 3acac64d50cb1..0cbf1cd985cef 100644 --- a/ecosystem/typescript/sdk_v2/src/crypto/multi_ed25519.ts +++ b/ecosystem/typescript/sdk_v2/src/crypto/multi_ed25519.ts @@ -6,6 +6,7 @@ import { Serializer } from "../bcs/serializer"; import { Ed25519PublicKey, Ed25519Signature } from "./ed25519"; import { PublicKey, Signature } from "./asymmetric_crypto"; import { HexInput } from "../types"; +import { Hex } from "../core/hex"; export class MultiEd25519PublicKey extends PublicKey { // Maximum number of public keys supported @@ -71,6 +72,10 @@ export class MultiEd25519PublicKey extends PublicKey { return bytes; } + toString(): string { + return Hex.fromHexInput({ hexInput: this.toUint8Array() }).toString(); + } + verifySignature(args: { data: HexInput; signature: MultiEd25519Signature }): boolean { throw new Error("TODO - Method not implemented."); } @@ -135,6 +140,10 @@ export class MultiEd25519Signature extends Signature { ); } + if (signatures.length > MultiEd25519Signature.MAX_SIGNATURES_SUPPORTED) { + throw new Error(`The number of signatures cannot be greater than ${MultiEd25519Signature.MAX_SIGNATURES_SUPPORTED}`); + } + this.signatures = signatures; this.bitmap = bitmap; } @@ -153,6 +162,10 @@ export class MultiEd25519Signature extends Signature { return bytes; } + toString(): string { + return Hex.fromHexInput({ hexInput: this.toUint8Array() }).toString(); + } + /** * Helper method to create a bitmap out of the specified bit positions * @param bits The bitmap positions that should be set. A position starts at index 0. diff --git a/ecosystem/typescript/sdk_v2/src/utils/hd-key.ts b/ecosystem/typescript/sdk_v2/src/utils/hd-key.ts new file mode 100644 index 0000000000000..dba2e0c79ce40 --- /dev/null +++ b/ecosystem/typescript/sdk_v2/src/utils/hd-key.ts @@ -0,0 +1,76 @@ +import nacl from "tweetnacl"; +import { hmac } from "@noble/hashes/hmac"; +import { sha512 } from "@noble/hashes/sha512"; +import { hexToBytes } from "@noble/hashes/utils"; + +export type Keys = { + key: Uint8Array; + chainCode: Uint8Array; +}; + +const pathRegex = /^m(\/[0-9]+')+$/; + +const replaceDerive = (val: string): string => val.replace("'", ""); + +const HMAC_KEY = "ed25519 seed"; +const HARDENED_OFFSET = 0x80000000; + +export const getMasterKeyFromSeed = (seed: string): Keys => { + const h = hmac.create(sha512, HMAC_KEY); + const I = h.update(hexToBytes(seed)).digest(); + const IL = I.slice(0, 32); + const IR = I.slice(32); + return { + key: IL, + chainCode: IR, + }; +}; + +export const CKDPriv = ({ key, chainCode }: Keys, index: number): Keys => { + const buffer = new ArrayBuffer(4); + new DataView(buffer).setUint32(0, index); + const indexBytes = new Uint8Array(buffer); + const zero = new Uint8Array([0]); + const data = new Uint8Array([...zero, ...key, ...indexBytes]); + + const I = hmac.create(sha512, chainCode).update(data).digest(); + const IL = I.slice(0, 32); + const IR = I.slice(32); + return { + key: IL, + chainCode: IR, + }; +}; + +export const getPublicKey = (privateKey: Uint8Array, withZeroByte = true): Uint8Array => { + const keyPair = nacl.sign.keyPair.fromSeed(privateKey); + const signPk = keyPair.secretKey.subarray(32); + const zero = new Uint8Array([0]); + return withZeroByte ? new Uint8Array([...zero, ...signPk]) : signPk; +}; + +export const isValidPath = (path: string): boolean => { + if (!pathRegex.test(path)) { + return false; + } + return !path + .split("/") + .slice(1) + .map(replaceDerive) + .some(Number.isNaN as any); +}; + +export const derivePath = (path: string, seed: string, offset = HARDENED_OFFSET): Keys => { + if (!isValidPath(path)) { + throw new Error("Invalid derivation path"); + } + + const { key, chainCode } = getMasterKeyFromSeed(seed); + const segments = path + .split("/") + .slice(1) + .map(replaceDerive) + .map((el) => parseInt(el, 10)); + + return segments.reduce((parentKeys, segment) => CKDPriv(parentKeys, segment + offset), { key, chainCode }); +}; \ No newline at end of file diff --git a/ecosystem/typescript/sdk_v2/tests/unit/account.test.ts b/ecosystem/typescript/sdk_v2/tests/unit/account.test.ts new file mode 100644 index 0000000000000..8c1444264fc07 --- /dev/null +++ b/ecosystem/typescript/sdk_v2/tests/unit/account.test.ts @@ -0,0 +1,71 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +import { Account } from "../../src/core/account"; +import { AccountAddress } from "../../src/core/account_address"; +import { Hex } from "../../src/core/hex"; +import { Ed25519Signature } from "../../src/crypto/ed25519"; +import { ed25519 } from "./helper"; + +describe("Account", () => { + it("should create an instance of Account correctly without error", () => { + const account = Account.generate(); + expect(account).toBeInstanceOf(Account); + }); + + it("should create a new account from a provided private key", () => { + const { privateKey, publicKey, address } = ed25519; + const newAccount = Account.fromPrivateKey({ privateKey }); + expect(newAccount).toBeInstanceOf(Account); + expect(newAccount.privateKey.toString()).toEqual(privateKey); + expect(newAccount.publicKey.toString()).toEqual(publicKey); + expect(newAccount.accountAddress.toString()).toEqual(address); + }); + + it("should create a new account from a provided private key and address", () => { + const { privateKey, publicKey, address } = ed25519; + const newAccount = Account.fromPrivateKeyAndAddress({ + privateKey, + address: AccountAddress.fromString({ input: address }), + }); + expect(newAccount).toBeInstanceOf(Account); + expect(newAccount.privateKey.toString()).toEqual(privateKey); + expect(newAccount.publicKey.toString()).toEqual(publicKey); + expect(newAccount.accountAddress.toString()).toEqual(address); + }); + + it("should create a new account from a bip44 path and mnemonics", () => { + const { mnemonic } = ed25519; + const address = "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"; + const path = "m/44'/637'/0'/0'/0'"; + const newAccount = Account.fromDerivationPath({ path, mnemonic }); + expect(newAccount.accountAddress.toString()).toEqual(address); + }); + + it("should check if a derivation path is valid", () => { + const validPath = "m/44'/637'/0'/0'/0'"; // Valid path + const invalidPath = "invalid/path"; // Invalid path + expect(Account.isValidPath({ path: validPath })).toBe(true); + expect(Account.isValidPath({ path: invalidPath })).toBe(false); + }); + + it("should return the authentication key for a public key", () => { + const { publicKey, address } = ed25519; + const authKey = Account.authKey({ publicKey }); + expect(authKey).toBeInstanceOf(Hex); + expect(authKey.toString()).toEqual(address); + }); + + it("should sign data, return a Hex signature, and verify", () => { + const { privateKey, address, message, signedMessage } = ed25519; + const account = Account.fromPrivateKeyAndAddress({ + privateKey, + address: AccountAddress.fromString({ input: address }), + }); + expect(account.sign({ data: message }).toString()).toEqual(signedMessage); + + // Verify the signature + const signature = new Ed25519Signature({ data: signedMessage }); + expect(account.verifySignature({ message, signature })).toBe(true); + }); +}); diff --git a/ecosystem/typescript/sdk_v2/tests/unit/authentication_key.test.ts b/ecosystem/typescript/sdk_v2/tests/unit/authentication_key.test.ts new file mode 100644 index 0000000000000..176c62f385859 --- /dev/null +++ b/ecosystem/typescript/sdk_v2/tests/unit/authentication_key.test.ts @@ -0,0 +1,49 @@ +import { AuthenticationKey } from "../../src/crypto/authentication_key"; +import { Ed25519PublicKey } from "../../src/crypto/ed25519"; +import { MultiEd25519PublicKey } from "../../src/crypto/multi_ed25519"; +import { ed25519, multiEd25519PkTestObject } from "./helper"; + +describe("AuthenticationKey", () => { + it("should create an instance with save the hexinput correctly", () => { + const authKey = new AuthenticationKey({ data: ed25519.authKey }); + expect(authKey).toBeInstanceOf(AuthenticationKey); + expect(authKey.data.toString()).toEqual(ed25519.authKey); + }); + + it("should throw an error with invalid hex input length", () => { + const invalidHexInput = "0123456789abcdef"; // Invalid length + expect(() => new AuthenticationKey({ data: invalidHexInput })).toThrowError( + "Authentication Key length should be 32", + ); + }); + + it("should create AuthenticationKey from Ed25519PublicKey", () => { + const publicKey = new Ed25519PublicKey({ hexInput: ed25519.publicKey }); + const authKey = AuthenticationKey.fromPublicKey({ publicKey }); + expect(authKey).toBeInstanceOf(AuthenticationKey); + expect(authKey.data.toString()).toEqual(ed25519.authKey); + }); + + it("should create AuthenticationKey from MultiPublicKey", () => { + // create the MultiPublicKey + let edPksArray = []; + for (let i = 0; i < multiEd25519PkTestObject.public_keys.length; i++) { + edPksArray.push(new Ed25519PublicKey({ hexInput: multiEd25519PkTestObject.public_keys[i] })); + } + + const pubKeyMultiSig = new MultiEd25519PublicKey({ + publicKeys: edPksArray, + threshold: multiEd25519PkTestObject.threshold, + }); + + const authKey = AuthenticationKey.fromMultiPublicKey({ multiPublicKey: pubKeyMultiSig }); + expect(authKey).toBeInstanceOf(AuthenticationKey); + expect(authKey.data.toString()).toEqual("0xa81cfac3df59920593ff417b45fc347ead3d88f8e25112c0488d34d7c9eb20af"); + }); + + it("should derive an AccountAddress from AuthenticationKey with same string", () => { + const authKey = new AuthenticationKey({ data: ed25519.authKey }); + const accountAddress = authKey.derivedAddress(); + expect(accountAddress.toString()).toEqual(ed25519.authKey); + }); +}); diff --git a/ecosystem/typescript/sdk_v2/tests/unit/helper.ts b/ecosystem/typescript/sdk_v2/tests/unit/helper.ts index 8a0da5eacd11b..9605e2af9c98d 100644 --- a/ecosystem/typescript/sdk_v2/tests/unit/helper.ts +++ b/ecosystem/typescript/sdk_v2/tests/unit/helper.ts @@ -4,6 +4,9 @@ export const ed25519 = { privateKey: "0xc5338cd251c22daa8c9c9cc94f498cc8a5c7e1d2e75287a5dda91096fe64efa5", publicKey: "0xde19e5d1880cac87d57484ce9ed2e84cf0f9599f12e7cc3a52e4e7657a763f2c", + authKey: "0x978c213990c4833df71548df7ce49d54c759d6b6d932de22b24d56060b7af2aa", + address: "0x978c213990c4833df71548df7ce49d54c759d6b6d932de22b24d56060b7af2aa", + mnemonic: "shoot island position soft burden budget tooth cruel issue economy destroy above", message: "0x7777", // eslint-disable-next-line max-len signedMessage: