Skip to content

Commit

Permalink
Move serialization of accounts to accountUtils
Browse files Browse the repository at this point in the history
  • Loading branch information
heliuchuan committed Dec 10, 2024
1 parent cdbe647 commit 3dc46d2
Show file tree
Hide file tree
Showing 14 changed files with 274 additions and 293 deletions.
28 changes: 3 additions & 25 deletions src/account/AbstractKeylessAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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";

/**
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down
27 changes: 1 addition & 26 deletions src/account/Account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -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}`);
}
}
}
216 changes: 216 additions & 0 deletions src/account/AccountUtils.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
34 changes: 1 addition & 33 deletions src/account/Ed25519Account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
*/
Expand All @@ -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();
Expand Down Expand Up @@ -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 });
}
}
10 changes: 0 additions & 10 deletions src/account/EphemeralKeyPair.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading

0 comments on commit 3dc46d2

Please sign in to comment.