From 3dc46d2fa4e2fbe0f52a2d1365d8ffffd130eb3a Mon Sep 17 00:00:00 2001
From: Oliver <heliuchuan@gmail.com>
Date: Wed, 11 Dec 2024 06:30:57 +0900
Subject: [PATCH] Move serialization of accounts to accountUtils

---
 src/account/AbstractKeylessAccount.ts   |  28 +--
 src/account/Account.ts                  |  27 +--
 src/account/AccountUtils.ts             | 216 ++++++++++++++++++++++++
 src/account/Ed25519Account.ts           |  34 +---
 src/account/EphemeralKeyPair.ts         |  10 --
 src/account/FederatedKeylessAccount.ts  |  17 +-
 src/account/KeylessAccount.ts           |  13 +-
 src/account/MultiKeyAccount.ts          |  77 +--------
 src/account/SingleKeyAccount.ts         |  54 ++----
 src/account/index.ts                    |   1 +
 src/account/utils.ts                    |   4 +-
 src/bcs/deserializer.ts                 |  25 +--
 tests/unit/accountSerialization.test.ts |  49 +++---
 tests/unit/helper.ts                    |  12 +-
 14 files changed, 274 insertions(+), 293 deletions(-)
 create mode 100644 src/account/AccountUtils.ts

diff --git a/src/account/AbstractKeylessAccount.ts b/src/account/AbstractKeylessAccount.ts
index 20db0d7f4..e8a736ec4 100644
--- a/src/account/AbstractKeylessAccount.ts
+++ b/src/account/AbstractKeylessAccount.ts
@@ -3,7 +3,7 @@
 
 import EventEmitter from "eventemitter3";
 import { jwtDecode } from "jwt-decode";
-import { AnyPublicKeyVariant, EphemeralCertificateVariant, HexInput, SigningScheme } from "../types";
+import { EphemeralCertificateVariant, HexInput, SigningScheme } from "../types";
 import { AccountAddress } from "../core/accountAddress";
 import {
   AnyPublicKey,
@@ -26,10 +26,9 @@ import { deriveTransactionType, generateSigningMessage } from "../transactions/t
 import { AnyRawTransaction, AnyRawTransactionInstance } from "../transactions/types";
 import { base64UrlDecode } from "../utils/helpers";
 import { FederatedKeylessPublicKey } from "../core/crypto/federatedKeyless";
-import type { Account } from "./Account";
+import { Account } from "./Account";
 import { AptosConfig } from "../api/aptosConfig";
 import { KeylessError, KeylessErrorType } from "../errors";
-import { deserializeSchemeAndAddress } from "./utils";
 import type { SingleKeySigner } from "./SingleKeyAccount";
 
 /**
@@ -228,13 +227,7 @@ export abstract class AbstractKeylessAccount extends Serializable implements Key
    * @param serializer - The serializer instance used to convert the jwt data into bytes.
    */
   serialize(serializer: Serializer): void {
-    serializer.serializeU32AsUleb128(this.signingScheme);
     this.accountAddress.serialize(serializer);
-    if (this.publicKey instanceof KeylessPublicKey) {
-      serializer.serializeU32AsUleb128(AnyPublicKeyVariant.Keyless);
-    } else {
-      serializer.serializeU32AsUleb128(AnyPublicKeyVariant.FederatedKeyless);
-    }
     serializer.serializeStr(this.jwt);
     serializer.serializeStr(this.uidKey);
     serializer.serializeFixedBytes(this.pepper);
@@ -255,22 +248,7 @@ export abstract class AbstractKeylessAccount extends Serializable implements Key
     proof: ZeroKnowledgeSig;
     verificationKeyHash?: Uint8Array;
   } {
-    const { address, signingScheme } = deserializeSchemeAndAddress(deserializer);
-    if (signingScheme !== SigningScheme.SingleKey) {
-      throw new Error(
-        `Deserialization of AbstractKeylessAccount failed: Signing scheme was not SingleKey, was ${signingScheme}`,
-      );
-    }
-    const anyPublicKeyIndex = deserializer.deserializeUleb128AsU32();
-    if (
-      anyPublicKeyIndex !== AnyPublicKeyVariant.Keyless &&
-      anyPublicKeyIndex !== AnyPublicKeyVariant.FederatedKeyless
-    ) {
-      throw new Error(
-        // eslint-disable-next-line max-len
-        `Deserialization of AbstractKeylessAccount failed: Public key variant was not Keyless or FederatedKeyless, was ${anyPublicKeyIndex}`,
-      );
-    }
+    const address = AccountAddress.deserialize(deserializer);
     const jwt = deserializer.deserializeStr();
     const uidKey = deserializer.deserializeStr();
     const pepper = deserializer.deserializeFixedBytes(31);
diff --git a/src/account/Account.ts b/src/account/Account.ts
index c4aad3c2c..c84dee19a 100644
--- a/src/account/Account.ts
+++ b/src/account/Account.ts
@@ -6,10 +6,6 @@ import { AccountPublicKey, Ed25519PrivateKey, PrivateKeyInput, Signature, Verify
 import { Ed25519Account } from "./Ed25519Account";
 import { SingleKeyAccount } from "./SingleKeyAccount";
 import { AnyRawTransaction } from "../transactions/types";
-import { Serializable } from "../bcs/serializer";
-import { Deserializer } from "../bcs/deserializer";
-import { deserializeSchemeAndAddress } from "./utils";
-import { deserializeNonMultiKeyAccount, MultiKeyAccount } from "./MultiKeyAccount";
 
 /**
  * Arguments for creating an `Ed25519Account` from an `Ed25519PrivateKey`.
@@ -132,7 +128,7 @@ export interface PrivateKeyFromDerivationPathArgs {
  *
  * Note: Generating an account instance does not create the account on-chain.
  */
-export abstract class Account extends Serializable {
+export abstract class Account {
   /**
    * Public key associated with the account
    */
@@ -289,25 +285,4 @@ export abstract class Account extends Serializable {
   verifySignature(args: VerifySignatureArgs): boolean {
     return this.publicKey.verifySignature(args);
   }
-
-  static fromHex(hex: HexInput): Account {
-    return Account.deserialize(Deserializer.fromHex(hex));
-  }
-
-  static deserialize(deserializer: Deserializer): Account {
-    const offset = deserializer.getOffset();
-    const { signingScheme } = deserializeSchemeAndAddress(deserializer);
-    switch (signingScheme) {
-      case SigningScheme.Ed25519:
-      case SigningScheme.SingleKey: {
-        deserializer.reset(offset);
-        return deserializeNonMultiKeyAccount(deserializer);
-      }
-      case SigningScheme.MultiKey:
-        deserializer.reset(offset);
-        return MultiKeyAccount.deserialize(deserializer);
-      default:
-        throw new Error(`Deserialization of Account failed: invalid signingScheme value ${signingScheme}`);
-    }
-  }
 }
diff --git a/src/account/AccountUtils.ts b/src/account/AccountUtils.ts
new file mode 100644
index 000000000..d07766a0b
--- /dev/null
+++ b/src/account/AccountUtils.ts
@@ -0,0 +1,216 @@
+import { Deserializer, Serializer } from "../bcs";
+import { AnyPublicKeyVariant, HexInput, SigningScheme } from "../types";
+import { MultiKeyAccount } from "./MultiKeyAccount";
+import { Account } from "./Account";
+import { Ed25519Account } from "./Ed25519Account";
+import { isSingleKeySigner, SingleKeyAccount, SingleKeySignerOrLegacyEd25519Account } from "./SingleKeyAccount";
+import { KeylessAccount } from "./KeylessAccount";
+import { FederatedKeylessAccount } from "./FederatedKeylessAccount";
+import { AbstractKeylessAccount } from "./AbstractKeylessAccount";
+import {
+  AccountAddress,
+  Ed25519PrivateKey,
+  getIssAudAndUidVal,
+  Hex,
+  MultiKey,
+  Secp256k1PrivateKey,
+  ZeroKnowledgeSig,
+} from "../core";
+import { deserializeSchemeAndAddress } from "./utils";
+import { EphemeralKeyPair } from "./EphemeralKeyPair";
+
+/**
+ * Utility functions for working with accounts.
+ */
+export class AccountUtils {
+  private static serializeKeylessAccountCommon(account: AbstractKeylessAccount, serializer: Serializer): void {
+    serializer.serializeStr(account.jwt);
+    serializer.serializeStr(account.uidKey);
+    serializer.serializeFixedBytes(account.pepper);
+    account.ephemeralKeyPair.serialize(serializer);
+    if (account.proof === undefined) {
+      throw new Error("Cannot serialize - proof undefined");
+    }
+    account.proof.serialize(serializer);
+    serializer.serializeOption(account.verificationKeyHash, 32);
+  }
+
+  private static deserializeKeylessAccountCommon(deserializer: Deserializer): {
+    jwt: string;
+    uidKey: string;
+    pepper: Uint8Array;
+    ephemeralKeyPair: EphemeralKeyPair;
+    proof: ZeroKnowledgeSig;
+    verificationKeyHash?: Uint8Array;
+  } {
+    const jwt = deserializer.deserializeStr();
+    const uidKey = deserializer.deserializeStr();
+    const pepper = deserializer.deserializeFixedBytes(31);
+    const ephemeralKeyPair = EphemeralKeyPair.deserialize(deserializer);
+    const proof = ZeroKnowledgeSig.deserialize(deserializer);
+    const verificationKeyHash = deserializer.deserializeOption("fixedBytes", 32);
+    return { jwt, uidKey, pepper, ephemeralKeyPair, proof, verificationKeyHash };
+  }
+
+  static toBytes(account: Account): Uint8Array {
+    const serializer = new Serializer();
+    serializer.serializeU32AsUleb128(account.signingScheme);
+    account.accountAddress.serialize(serializer);
+    switch (account.signingScheme) {
+      case SigningScheme.Ed25519:
+        (account as Ed25519Account).privateKey.serialize(serializer);
+        return serializer.toUint8Array();
+      case SigningScheme.SingleKey: {
+        if (!isSingleKeySigner(account)) {
+          throw new Error("Account is not a SingleKeySigner");
+        }
+        const anyPublicKey = account.getAnyPublicKey();
+        serializer.serializeU32AsUleb128(anyPublicKey.variant);
+        switch (anyPublicKey.variant) {
+          case AnyPublicKeyVariant.Keyless: {
+            const keylessAccount = account as KeylessAccount;
+            this.serializeKeylessAccountCommon(keylessAccount, serializer);
+            return serializer.toUint8Array();
+          }
+          case AnyPublicKeyVariant.FederatedKeyless: {
+            const federatedKeylessAccount = account as FederatedKeylessAccount;
+            this.serializeKeylessAccountCommon(federatedKeylessAccount, serializer);
+            federatedKeylessAccount.publicKey.jwkAddress.serialize(serializer);
+            serializer.serializeBool(federatedKeylessAccount.audless);
+            return serializer.toUint8Array();
+          }
+          case AnyPublicKeyVariant.Secp256k1:
+          case AnyPublicKeyVariant.Ed25519: {
+            const singleKeyAccount = account as SingleKeyAccount;
+            singleKeyAccount.privateKey.serialize(serializer);
+            return serializer.toUint8Array();
+          }
+          default: {
+            throw new Error(`Invalid public key variant: ${anyPublicKey.variant}`);
+          }
+        }
+      }
+      case SigningScheme.MultiKey: {
+        const multiKeyAccount = account as MultiKeyAccount;
+        multiKeyAccount.publicKey.serialize(serializer);
+        serializer.serializeU32AsUleb128(multiKeyAccount.signers.length);
+        multiKeyAccount.signers.forEach((signer) => {
+          serializer.serializeFixedBytes(this.toBytes(signer));
+        });
+        return serializer.toUint8Array();
+      }
+      default:
+        throw new Error(`Deserialization of Account failed: invalid signingScheme value ${account.signingScheme}`);
+    }
+  }
+
+  static toHexStringWithoutPrefix(account: Account): string {
+    return Hex.hexInputToStringWithoutPrefix(this.toBytes(account));
+  }
+
+  static toHexString(account: Account): string {
+    return Hex.hexInputToString(this.toBytes(account));
+  }
+
+  static deserialize(deserializer: Deserializer): Account {
+    const { address, signingScheme } = deserializeSchemeAndAddress(deserializer);
+    switch (signingScheme) {
+      case SigningScheme.Ed25519: {
+        const privateKey = Ed25519PrivateKey.deserialize(deserializer);
+        return new Ed25519Account({ privateKey, address });
+      }
+      case SigningScheme.SingleKey: {
+        const variantIndex = deserializer.deserializeUleb128AsU32();
+        switch (variantIndex) {
+          case AnyPublicKeyVariant.Ed25519: {
+            const privateKey = Ed25519PrivateKey.deserialize(deserializer);
+            return new SingleKeyAccount({ privateKey, address });
+          }
+          case AnyPublicKeyVariant.Secp256k1: {
+            const privateKey = Secp256k1PrivateKey.deserialize(deserializer);
+            return new SingleKeyAccount({ privateKey, address });
+          }
+          case AnyPublicKeyVariant.Keyless: {
+            const keylessComponents = this.deserializeKeylessAccountCommon(deserializer);
+            const jwtClaims = getIssAudAndUidVal(keylessComponents);
+            return new KeylessAccount({ ...keylessComponents, ...jwtClaims });
+          }
+          case AnyPublicKeyVariant.FederatedKeyless: {
+            const keylessComponents = this.deserializeKeylessAccountCommon(deserializer);
+            const jwkAddress = AccountAddress.deserialize(deserializer);
+            const audless = deserializer.deserializeBool();
+            const jwtClaims = getIssAudAndUidVal(keylessComponents);
+            return new FederatedKeylessAccount({ ...keylessComponents, ...jwtClaims, jwkAddress, audless });
+          }
+          default:
+            throw new Error(`Unsupported public key variant ${variantIndex}`);
+        }
+      }
+      case SigningScheme.MultiKey: {
+        const multiKey = MultiKey.deserialize(deserializer);
+        const length = deserializer.deserializeUleb128AsU32();
+        const signers = new Array<SingleKeySignerOrLegacyEd25519Account>();
+        for (let i = 0; i < length; i += 1) {
+          const signer = this.deserialize(deserializer);
+          if (!isSingleKeySigner(signer) && !(signer instanceof Ed25519Account)) {
+            throw new Error(
+              "Deserialization of MultiKeyAccount failed. Signer is not a SingleKeySigner or Ed25519Account",
+            );
+          }
+          signers.push(signer);
+        }
+        return new MultiKeyAccount({ multiKey, signers, address });
+      }
+      default:
+        throw new Error(`Deserialization of Account failed: invalid signingScheme value ${signingScheme}`);
+    }
+  }
+
+  static keylessAccountFromHex(hex: HexInput): KeylessAccount {
+    const account = this.fromHex(hex);
+    if (!(account instanceof KeylessAccount)) {
+      throw new Error("Deserialization of KeylessAccount failed");
+    }
+    return account;
+  }
+
+  static federatedKeylessAccountFromHex(hex: HexInput): FederatedKeylessAccount {
+    const account = this.fromHex(hex);
+    if (!(account instanceof FederatedKeylessAccount)) {
+      throw new Error("Deserialization of FederatedKeylessAccount failed");
+    }
+    return account;
+  }
+
+  static multiKeyAccountFromHex(hex: HexInput): MultiKeyAccount {
+    const account = this.fromHex(hex);
+    if (!(account instanceof MultiKeyAccount)) {
+      throw new Error("Deserialization of MultiKeyAccount failed");
+    }
+    return account;
+  }
+
+  static singleKeyAccountFromHex(hex: HexInput): SingleKeyAccount {
+    const account = this.fromHex(hex);
+    if (!(account instanceof SingleKeyAccount)) {
+      throw new Error("Deserialization of SingleKeyAccount failed");
+    }
+    return account;
+  }
+
+  static ed25519AccountFromHex(hex: HexInput): Ed25519Account {
+    const account = this.fromHex(hex);
+    if (!(account instanceof Ed25519Account)) {
+      throw new Error("Deserialization of Ed25519Account failed");
+    }
+    return account;
+  }
+
+  static fromHex(hex: HexInput): Account {
+    return this.deserialize(Deserializer.fromHex(hex));
+  }
+
+  static fromBytes(bytes: Uint8Array): Account {
+    return this.fromHex(bytes);
+  }
+}
diff --git a/src/account/Ed25519Account.ts b/src/account/Ed25519Account.ts
index 9bc8adc6b..6a7fc599e 100644
--- a/src/account/Ed25519Account.ts
+++ b/src/account/Ed25519Account.ts
@@ -5,9 +5,6 @@ import { Ed25519PrivateKey, Ed25519PublicKey, Ed25519Signature } from "../core/c
 import type { Account } from "./Account";
 import { AnyRawTransaction } from "../transactions/types";
 import { generateSigningMessageForTransaction } from "../transactions/transactionBuilder/signingMessage";
-import { Serializable, Serializer } from "../bcs/serializer";
-import { Deserializer } from "../bcs/deserializer";
-import { deserializeSchemeAndAddress } from "./utils";
 
 /**
  * Arguments required to create an instance of an Ed25519 signer.
@@ -48,7 +45,7 @@ export interface VerifyEd25519SignatureArgs {
  *
  * Note: Generating an instance of this class does not create the account on-chain.
  */
-export class Ed25519Account extends Serializable implements Account {
+export class Ed25519Account implements Account {
   /**
    * Private key associated with the account
    */
@@ -71,7 +68,6 @@ export class Ed25519Account extends Serializable implements Account {
    * @param args.address - The optional account address; if not provided, it will derive the address from the public key.
    */
   constructor(args: Ed25519SignerConstructorArgs) {
-    super();
     const { privateKey, address } = args;
     this.privateKey = privateKey;
     this.publicKey = privateKey.publicKey();
@@ -160,32 +156,4 @@ export class Ed25519Account extends Serializable implements Account {
   }
 
   // endregion
-
-  serialize(serializer: Serializer): void {
-    serializer.serializeU32AsUleb128(this.signingScheme);
-    this.accountAddress.serialize(serializer);
-    this.privateKey.serialize(serializer);
-  }
-
-  /**
-   * Deserialize bytes using this account's information.
-   *
-   * @param hex The hex being deserialized into an Ed25519Account.
-   * @returns
-   */
-  static fromHex(hex: HexInput): Ed25519Account {
-    return Ed25519Account.deserialize(Deserializer.fromHex(hex));
-  }
-
-  static deserialize(deserializer: Deserializer): Ed25519Account {
-    const { address, signingScheme } = deserializeSchemeAndAddress(deserializer);
-    if (signingScheme !== SigningScheme.Ed25519) {
-      throw new Error(
-        // eslint-disable-next-line max-len
-        `Deserialization of Ed25519Account failed: Signing scheme was not Ed25519, was ${signingScheme} at offset ${deserializer.getOffset()}`,
-      );
-    }
-    const privateKey = Ed25519PrivateKey.deserialize(deserializer);
-    return new Ed25519Account({ privateKey, address });
-  }
 }
diff --git a/src/account/EphemeralKeyPair.ts b/src/account/EphemeralKeyPair.ts
index c54d4590f..f01832efc 100644
--- a/src/account/EphemeralKeyPair.ts
+++ b/src/account/EphemeralKeyPair.ts
@@ -136,16 +136,6 @@ export class EphemeralKeyPair extends Serializable {
     return new EphemeralKeyPair({ privateKey, expiryDateSecs: Number(expiryDateSecs), blinder });
   }
 
-  /**
-   * Deserialize hex using this account's information.
-   *
-   * @param hex The hex being deserialized into an EphemeralKeyPair.
-   * @returns
-   */
-  static fromHex(hex: HexInput): EphemeralKeyPair {
-    return EphemeralKeyPair.deserialize(Deserializer.fromHex(hex));
-  }
-
   /**
    * Deserialize a byte array into an EphemeralKeyPair object.
    * This function allows you to reconstruct an EphemeralKeyPair from its serialized byte representation.
diff --git a/src/account/FederatedKeylessAccount.ts b/src/account/FederatedKeylessAccount.ts
index 507394ac8..a4ec5d1ca 100644
--- a/src/account/FederatedKeylessAccount.ts
+++ b/src/account/FederatedKeylessAccount.ts
@@ -27,6 +27,8 @@ export class FederatedKeylessAccount extends AbstractKeylessAccount {
    */
   readonly publicKey: FederatedKeylessPublicKey;
 
+  readonly audless: boolean;
+
   /**
    * Use the static generator `FederatedKeylessAccount.create(...)` instead.
    * Creates a KeylessAccount instance using the provided parameters.
@@ -41,7 +43,7 @@ export class FederatedKeylessAccount extends AbstractKeylessAccount {
    * @param args.uidKey - Optional key for user identification, defaults to "sub".
    * @param args.proofFetchCallback - Optional callback function for fetching proof.
    */
-  private constructor(args: {
+  constructor(args: {
     address?: AccountAddress;
     ephemeralKeyPair: EphemeralKeyPair;
     iss: string;
@@ -54,10 +56,12 @@ export class FederatedKeylessAccount extends AbstractKeylessAccount {
     proofFetchCallback?: ProofFetchCallback;
     jwt: string;
     verificationKeyHash?: HexInput;
+    audless?: boolean;
   }) {
     const publicKey = FederatedKeylessPublicKey.create(args);
     super({ publicKey, ...args });
     this.publicKey = publicKey;
+    this.audless = args.audless ?? false;
   }
 
   /**
@@ -99,17 +103,6 @@ export class FederatedKeylessAccount extends AbstractKeylessAccount {
   }
 
   /**
-   * Deserialize hex using this account's information.
-   *
-   * @param hex The hex being deserialized into an FederatedKeylessAccount.
-   * @returns
-   */
-  static fromHex(hex: HexInput): FederatedKeylessAccount {
-    return FederatedKeylessAccount.deserialize(Deserializer.fromHex(hex));
-  }
-
-  /**
-   * @deprecated Use `fromHex` instead.
    * Deserialize bytes using this account's information.
    *
    * @param bytes The bytes being interpreted.
diff --git a/src/account/KeylessAccount.ts b/src/account/KeylessAccount.ts
index 27e70c226..e76332d12 100644
--- a/src/account/KeylessAccount.ts
+++ b/src/account/KeylessAccount.ts
@@ -45,7 +45,7 @@ export class KeylessAccount extends AbstractKeylessAccount {
    * @param args.proofFetchCallback - Optional callback function for fetching proof.
    * @param args.jwt - A JSON Web Token used for authentication.
    */
-  private constructor(args: {
+  constructor(args: {
     address?: AccountAddress;
     ephemeralKeyPair: EphemeralKeyPair;
     iss: string;
@@ -99,17 +99,6 @@ export class KeylessAccount extends AbstractKeylessAccount {
   }
 
   /**
-   * Deserialize bytes using this account's information.
-   *
-   * @param hex The hex being deserialized into an MultiKeyAccount.
-   * @returns
-   */
-  static fromHex(hex: HexInput): KeylessAccount {
-    return KeylessAccount.deserialize(Deserializer.fromHex(hex));
-  }
-
-  /**
-   * @deprecated Use `fromHex` instead.
    * Deserialize bytes using this account's information.
    *
    * @param bytes The bytes being interpreted.
diff --git a/src/account/MultiKeyAccount.ts b/src/account/MultiKeyAccount.ts
index ef876de5d..ff0bfdd39 100644
--- a/src/account/MultiKeyAccount.ts
+++ b/src/account/MultiKeyAccount.ts
@@ -4,18 +4,13 @@
 import type { Account } from "./Account";
 import { MultiKey, MultiKeySignature, PublicKey } from "../core/crypto";
 import { AccountAddress, AccountAddressInput } from "../core/accountAddress";
-import { AnyPublicKeyVariant, HexInput, SigningScheme } from "../types";
+import { HexInput, SigningScheme } from "../types";
 import { AccountAuthenticatorMultiKey } from "../transactions/authenticator/account";
 import { AnyRawTransaction } from "../transactions/types";
 import { AbstractKeylessAccount, KeylessSigner } from "./AbstractKeylessAccount";
 import { AptosConfig } from "../api/aptosConfig";
-import { Serializable, Serializer } from "../bcs/serializer";
-import { Deserializer } from "../bcs/deserializer";
-import { deserializeSchemeAndAddress } from "./utils";
 import { SingleKeyAccount, SingleKeySigner, SingleKeySignerOrLegacyEd25519Account } from "./SingleKeyAccount";
 import { Ed25519Account } from "./Ed25519Account";
-import { KeylessAccount } from "./KeylessAccount";
-import { FederatedKeylessAccount } from "./FederatedKeylessAccount";
 
 /**
  * Arguments required to verify a multi-key signature against a given message.
@@ -36,7 +31,7 @@ export interface VerifyMultiKeySignatureArgs {
  *
  * Note: Generating a signer instance does not create the account on-chain.
  */
-export class MultiKeyAccount extends Serializable implements Account, KeylessSigner {
+export class MultiKeyAccount implements Account, KeylessSigner {
   /**
    * Public key associated with the account
    */
@@ -81,7 +76,6 @@ export class MultiKeyAccount extends Serializable implements Account, KeylessSig
     signers: SingleKeySignerOrLegacyEd25519Account[];
     address?: AccountAddressInput;
   }) {
-    super();
     const { multiKey, address } = args;
 
     const signers: SingleKeySigner[] = args.signers.map((signer) =>
@@ -251,71 +245,4 @@ export class MultiKeyAccount extends Serializable implements Account, KeylessSig
     }
     return true;
   }
-
-  serialize(serializer: Serializer): void {
-    serializer.serializeU32AsUleb128(this.signingScheme);
-    this.accountAddress.serialize(serializer);
-    this.publicKey.serialize(serializer);
-    serializer.serializeVector(this.signers);
-  }
-
-  /**
-   * Deserialize bytes using this account's information.
-   *
-   * @param hex The hex being deserialized into an MultiKeyAccount.
-   * @returns
-   */
-  static fromHex(hex: HexInput): MultiKeyAccount {
-    return MultiKeyAccount.deserialize(Deserializer.fromHex(hex));
-  }
-
-  static deserialize(deserializer: Deserializer): MultiKeyAccount {
-    const { address, signingScheme } = deserializeSchemeAndAddress(deserializer);
-    if (signingScheme !== SigningScheme.MultiKey) {
-      throw new Error(
-        `Deserialization of MultiKeyAccount failed: Signing scheme was not MultiKey, was ${signingScheme}`,
-      );
-    }
-    const multiKey = MultiKey.deserialize(deserializer);
-    const length = deserializer.deserializeUleb128AsU32();
-    const signers = new Array<SingleKeySignerOrLegacyEd25519Account>();
-    for (let i = 0; i < length; i += 1) {
-      signers.push(deserializeNonMultiKeyAccount(deserializer));
-    }
-    return new MultiKeyAccount({ multiKey, signers, address });
-  }
-}
-
-export function deserializeNonMultiKeyAccount(deserializer: Deserializer): SingleKeySignerOrLegacyEd25519Account {
-  const offset = deserializer.getOffset();
-  const { signingScheme } = deserializeSchemeAndAddress(deserializer);
-  switch (signingScheme) {
-    case SigningScheme.Ed25519:
-      deserializer.reset(offset);
-      return Ed25519Account.deserialize(deserializer);
-    case SigningScheme.SingleKey: {
-      const anyKeyVariant = deserializer.deserializeUleb128AsU32();
-      const anyKeyVariantOffset = deserializer.getOffset();
-      deserializer.reset(offset);
-      switch (anyKeyVariant) {
-        case AnyPublicKeyVariant.Keyless:
-          return KeylessAccount.deserialize(deserializer);
-        case AnyPublicKeyVariant.FederatedKeyless:
-          return FederatedKeylessAccount.deserialize(deserializer);
-        case AnyPublicKeyVariant.Ed25519:
-        case AnyPublicKeyVariant.Secp256k1:
-          return SingleKeyAccount.deserialize(deserializer);
-        default:
-          throw new Error(
-            // eslint-disable-next-line max-len
-            `Deserialization of Account failed: AnyPublicKey variant ${anyKeyVariant} is invalid ending at offset ${anyKeyVariantOffset}.\n
-              ${JSON.stringify(deserializer, null, 2)}`,
-          );
-      }
-    }
-    default:
-      throw new Error(
-        `Deserialization of Account failed: SigningScheme variant ${signingScheme} is invalid ending at offset ${offset}`,
-      );
-  }
 }
diff --git a/src/account/SingleKeyAccount.ts b/src/account/SingleKeyAccount.ts
index 2a543193e..cc77b0f60 100644
--- a/src/account/SingleKeyAccount.ts
+++ b/src/account/SingleKeyAccount.ts
@@ -1,13 +1,10 @@
 import { AccountAuthenticatorSingleKey } from "../transactions/authenticator/account";
-import { AnyPublicKeyVariant, type HexInput, SigningScheme, SigningSchemeInput } from "../types";
+import { type HexInput, SigningScheme, SigningSchemeInput } from "../types";
 import { AccountAddress, AccountAddressInput } from "../core/accountAddress";
 import { AnyPublicKey, AnySignature, Ed25519PrivateKey, PrivateKeyInput, Secp256k1PrivateKey } from "../core/crypto";
 import type { Account } from "./Account";
 import { generateSigningMessageForTransaction } from "../transactions/transactionBuilder/signingMessage";
 import { AnyRawTransaction } from "../transactions/types";
-import { Serializable, Serializer } from "../bcs/serializer";
-import { Deserializer } from "../bcs/deserializer";
-import { deserializeSchemeAndAddress } from "./utils";
 import { Ed25519Account } from "./Ed25519Account";
 
 /**
@@ -19,6 +16,15 @@ export interface SingleKeySigner extends Account {
   getAnyPublicKey(): AnyPublicKey;
 }
 
+export function isSingleKeySigner(obj: unknown): obj is SingleKeySigner {
+  return (
+    typeof obj === "object" &&
+    obj !== null &&
+    "getAnyPublicKey" in obj &&
+    typeof (obj as any).getAnyPublicKey === "function"
+  );
+}
+
 export type SingleKeySignerOrLegacyEd25519Account = SingleKeySigner | Ed25519Account;
 
 /**
@@ -67,7 +73,7 @@ export interface VerifySingleKeySignatureArgs {
  *
  * Note: Generating a signer instance does not create the account on-chain.
  */
-export class SingleKeyAccount extends Serializable implements Account, SingleKeySigner {
+export class SingleKeyAccount implements Account, SingleKeySigner {
   /**
    * Private key associated with the account
    */
@@ -88,7 +94,6 @@ export class SingleKeyAccount extends Serializable implements Account, SingleKey
    * @param args.address - The optional account address; if not provided, it will derive the address from the public key.
    */
   constructor(args: SingleKeySignerConstructorArgs) {
-    super();
     const { privateKey, address } = args;
     this.privateKey = privateKey;
     this.publicKey = new AnyPublicKey(privateKey.publicKey());
@@ -205,43 +210,6 @@ export class SingleKeyAccount extends Serializable implements Account, SingleKey
 
   // endregion
 
-  serialize(serializer: Serializer): void {
-    serializer.serializeU32AsUleb128(this.signingScheme);
-    this.accountAddress.serialize(serializer);
-    serializer.serializeU32AsUleb128(this.publicKey.variant);
-    this.privateKey.serialize(serializer);
-  }
-
-  /**
-   * Deserialize bytes using this account's information.
-   *
-   * @param hex The hex being deserialized into an SingleKeyAccount.
-   * @returns
-   */
-  static fromHex(hex: HexInput): SingleKeyAccount {
-    return SingleKeyAccount.deserialize(Deserializer.fromHex(hex));
-  }
-
-  static deserialize(deserializer: Deserializer): SingleKeyAccount {
-    const { address, signingScheme } = deserializeSchemeAndAddress(deserializer);
-    if (signingScheme !== SigningScheme.SingleKey) {
-      throw new Error(`Deserialization of SingleKeyAccount failed: Unsupported signing scheme ${signingScheme}`);
-    }
-    const variantIndex = deserializer.deserializeUleb128AsU32();
-    let privateKey: PrivateKeyInput;
-    switch (variantIndex) {
-      case AnyPublicKeyVariant.Ed25519:
-        privateKey = Ed25519PrivateKey.deserialize(deserializer);
-        break;
-      case AnyPublicKeyVariant.Secp256k1:
-        privateKey = Secp256k1PrivateKey.deserialize(deserializer);
-        break;
-      default:
-        throw new Error(`Unsupported public key variant ${variantIndex}`);
-    }
-    return new SingleKeyAccount({ privateKey, address });
-  }
-
   static fromEd25519Account(account: Ed25519Account): SingleKeyAccount {
     return new SingleKeyAccount({ privateKey: account.privateKey, address: account.accountAddress });
   }
diff --git a/src/account/index.ts b/src/account/index.ts
index d610bfc39..2893a580f 100644
--- a/src/account/index.ts
+++ b/src/account/index.ts
@@ -6,3 +6,4 @@ export * from "./KeylessAccount";
 export * from "./AbstractKeylessAccount";
 export * from "./FederatedKeylessAccount";
 export * from "./MultiKeyAccount";
+export * from "./AccountUtils";
diff --git a/src/account/utils.ts b/src/account/utils.ts
index d6755f1e9..9de9e3426 100644
--- a/src/account/utils.ts
+++ b/src/account/utils.ts
@@ -9,9 +9,7 @@ export function deserializeSchemeAndAddress(deserializer: Deserializer): {
   const signingScheme = deserializer.deserializeUleb128AsU32();
   // Validate that signingScheme is a valid SigningScheme value
   if (!Object.values(SigningScheme).includes(signingScheme)) {
-    throw new Error(
-      `Deserialization of Account failed: SigningScheme variant ${signingScheme} is invalid ending at offset ${deserializer.getOffset()}`,
-    );
+    throw new Error(`Deserialization of Account failed: SigningScheme variant ${signingScheme} is invalid`);
   }
   const address = AccountAddress.deserialize(deserializer);
   return { address, signingScheme };
diff --git a/src/bcs/deserializer.ts b/src/bcs/deserializer.ts
index 92359d226..33d0b8c07 100644
--- a/src/bcs/deserializer.ts
+++ b/src/bcs/deserializer.ts
@@ -6,10 +6,6 @@ import { MAX_U32_NUMBER } from "./consts";
 import { Uint8, Uint16, Uint32, Uint64, Uint128, Uint256, HexInput } from "../types";
 import { Hex } from "../core/hex";
 
-export interface DeserializableAccount<T> extends Deserializable<T> {
-  fromHex(hex: HexInput): T;
-}
-
 /**
  * This interface exists to define Deserializable<T> inputs for functions that
  * deserialize a byte buffer into a type T.
@@ -92,26 +88,7 @@ export class Deserializer {
   }
 
   /**
-   * Resets the deserializer's offset back to the beginning of the buffer.
-   * This allows reusing the same deserializer instance to read the buffer again from the start.
-   *
-   * @example
-   * ```typescript
-   * const deserializer = new Deserializer(new Uint8Array([1, 2, 3]));
-   * deserializer.deserializeU8(); // reads first byte
-   * deserializer.reset(); // resets to beginning
-   * deserializer.deserializeU8(); // reads first byte again
-   * ```
-   */
-  reset(offset?: number): void {
-    this.offset = offset ?? 0;
-  }
-
-  getOffset(): number {
-    return this.offset;
-  }
-
-  /**
+   * @deprecated use `deserializeOption` instead.
    * Deserializes a UTF-8 encoded string from a byte array. It first reads the length of the string in bytes,
    * followed by the actual byte content, and decodes it into a string.
    *
diff --git a/tests/unit/accountSerialization.test.ts b/tests/unit/accountSerialization.test.ts
index 31792a26b..dcfc4a0e1 100644
--- a/tests/unit/accountSerialization.test.ts
+++ b/tests/unit/accountSerialization.test.ts
@@ -5,8 +5,6 @@
 import {
   Account,
   SigningSchemeInput,
-  Ed25519Account,
-  SingleKeyAccount,
   MultiKeyAccount,
   KeylessAccount,
   FederatedKeylessAccount,
@@ -17,8 +15,13 @@ import {
   ZkpVariant,
   Groth16VerificationKey,
 } from "../../src";
+import { AccountUtils } from "../../src/account/AccountUtils";
 
-import { testAccountSerializationDeserialization } from "./helper";
+export function testAccountSerializationDeserialization(account: Account) {
+  const bytes = AccountUtils.toBytes(account);
+  const deserializedAccount = AccountUtils.fromBytes(bytes);
+  expect(bytes).toEqual(AccountUtils.toBytes(deserializedAccount));
+}
 
 describe("Account Serialization", () => {
   const proof = new ZeroKnowledgeSig({
@@ -74,36 +77,44 @@ describe("Account Serialization", () => {
 
   describe("serialize", () => {
     it("legacy Ed25519 Account should serlialize and deserialize properly", () => {
-      testAccountSerializationDeserialization(legacyEdAccount, Ed25519Account);
-      testAccountSerializationDeserialization(legacyEdAccount, Account);
+      testAccountSerializationDeserialization(legacyEdAccount);
+      const accountAsHex = AccountUtils.toHexString(legacyEdAccount);
+      expect(legacyEdAccount).toEqual(AccountUtils.ed25519AccountFromHex(accountAsHex));
     });
     it("SingleKey Ed25519 Account should serlialize and deserialize properly", () => {
-      testAccountSerializationDeserialization(singleSignerEdAccount, SingleKeyAccount);
-      testAccountSerializationDeserialization(singleSignerEdAccount, Account);
+      testAccountSerializationDeserialization(singleSignerEdAccount);
+      const accountAsHex = AccountUtils.toHexString(singleSignerEdAccount);
+      expect(singleSignerEdAccount).toEqual(AccountUtils.singleKeyAccountFromHex(accountAsHex));
     });
     it("SingleKey Secp256k1 Account should serlialize and deserialize properly", () => {
-      testAccountSerializationDeserialization(secp256k1Account, SingleKeyAccount);
-      testAccountSerializationDeserialization(secp256k1Account, Account);
+      testAccountSerializationDeserialization(secp256k1Account);
+      const accountAsHex = AccountUtils.toHexString(secp256k1Account);
+      expect(secp256k1Account).toEqual(AccountUtils.singleKeyAccountFromHex(accountAsHex));
     });
     it("Keyless Account should serlialize and deserialize properly", () => {
-      testAccountSerializationDeserialization(keylessAccount, KeylessAccount);
-      testAccountSerializationDeserialization(keylessAccount, Account);
+      testAccountSerializationDeserialization(keylessAccount);
+      const accountAsHex = AccountUtils.toHexString(keylessAccount);
+      expect(keylessAccount).toEqual(AccountUtils.keylessAccountFromHex(accountAsHex));
     });
     it("Keyless Account with verification key should serlialize and deserialize properly", () => {
-      testAccountSerializationDeserialization(keylessAccountWithVerificationKey, KeylessAccount);
-      testAccountSerializationDeserialization(keylessAccountWithVerificationKey, Account);
+      testAccountSerializationDeserialization(keylessAccountWithVerificationKey);
+      const accountAsHex = AccountUtils.toHexString(keylessAccountWithVerificationKey);
+      expect(keylessAccountWithVerificationKey).toEqual(AccountUtils.keylessAccountFromHex(accountAsHex));
     });
     it("FederatedKeyless Account should serlialize and deserialize properly", () => {
-      testAccountSerializationDeserialization(federatedKeylessAccount, FederatedKeylessAccount);
-      testAccountSerializationDeserialization(federatedKeylessAccount, Account);
+      testAccountSerializationDeserialization(federatedKeylessAccount);
+      const accountAsHex = AccountUtils.toHexString(federatedKeylessAccount);
+      expect(federatedKeylessAccount).toEqual(AccountUtils.federatedKeylessAccountFromHex(accountAsHex));
     });
     it("MultiKey Account should serlialize and deserialize properly", () => {
-      testAccountSerializationDeserialization(multiKeyAccount, MultiKeyAccount);
-      testAccountSerializationDeserialization(multiKeyAccount, Account);
+      testAccountSerializationDeserialization(multiKeyAccount);
+      const accountAsHex = AccountUtils.toHexString(multiKeyAccount);
+      expect(multiKeyAccount).toEqual(AccountUtils.multiKeyAccountFromHex(accountAsHex));
     });
     it("MultiKey Account with backup signer should serlialize and deserialize properly", () => {
-      testAccountSerializationDeserialization(keylessAccountWithBackupSigner, MultiKeyAccount);
-      testAccountSerializationDeserialization(keylessAccountWithBackupSigner, Account);
+      testAccountSerializationDeserialization(keylessAccountWithBackupSigner);
+      const accountAsHex = AccountUtils.toHexString(keylessAccountWithBackupSigner);
+      expect(keylessAccountWithBackupSigner).toEqual(AccountUtils.multiKeyAccountFromHex(accountAsHex));
     });
   });
 });
diff --git a/tests/unit/helper.ts b/tests/unit/helper.ts
index 94c9a01d1..2ff918c08 100644
--- a/tests/unit/helper.ts
+++ b/tests/unit/helper.ts
@@ -2,7 +2,7 @@
 // SPDX-License-Identifier: Apache-2.0
 /* eslint-disable max-len */
 
-import { ClientRequest, ClientResponse, DeserializableAccount, Deserializer, Serializable } from "../../src";
+import { ClientRequest, ClientResponse } from "../../src";
 
 export const FUND_AMOUNT = 100_000_000;
 export const TRANSFER_AMOUNT = 500_000;
@@ -115,16 +115,6 @@ export const multiKeyTestObject = {
 
 export const longTestTimeout = 120 * 1000;
 
-export function testAccountSerializationDeserialization<T extends Serializable, U extends Serializable>(
-  obj: T,
-  cls: DeserializableAccount<U>,
-) {
-  const bytes = obj.bcsToBytes();
-  const deserialized = cls.deserialize(new Deserializer(bytes));
-  expect(bytes).toEqual(deserialized.bcsToBytes());
-  expect(bytes).toEqual(cls.fromHex(bytes).bcsToBytes());
-}
-
 export async function customClient<Req, Res>(requestOptions: ClientRequest<Req>): Promise<ClientResponse<Res>> {
   const { params, method, url, headers, body } = requestOptions;