Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add serialization to the account classes #571

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 32 additions & 6 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 { EphemeralCertificateVariant, HexInput, SigningScheme } from "../types";
import { AnyPublicKeyVariant, EphemeralCertificateVariant, HexInput, SigningScheme } from "../types";
import { AccountAddress } from "../core/accountAddress";
import {
AnyPublicKey,
Expand All @@ -26,9 +26,11 @@ import { deriveTransactionType, generateSigningMessage } from "../transactions/t
import { AnyRawTransaction, AnyRawTransactionInstance } from "../transactions/types";
import { base64UrlDecode } from "../utils/helpers";
import { FederatedKeylessPublicKey } from "../core/crypto/federatedKeyless";
import { Account } from "./Account";
import type { Account } from "./Account";
import { AptosConfig } from "../api/aptosConfig";
import { KeylessError, KeylessErrorType } from "../errors";
import { deserializeSchemeAndAddress } from "./utils";
import type { SingleKeySigner } from "./SingleKeyAccount";

/**
* An interface which defines if an Account utilizes Keyless signing.
Expand All @@ -45,7 +47,7 @@ export function isKeylessSigner(obj: any): obj is KeylessSigner {
* Account implementation for the Keyless authentication scheme. This abstract class is used for standard Keyless Accounts
* and Federated Keyless Accounts.
*/
export abstract class AbstractKeylessAccount extends Serializable implements KeylessSigner {
export abstract class AbstractKeylessAccount extends Serializable implements KeylessSigner, SingleKeySigner {
static readonly PEPPER_LENGTH: number = 31;

/**
Expand Down Expand Up @@ -98,7 +100,7 @@ export abstract class AbstractKeylessAccount extends Serializable implements Key
/**
* Signing scheme used to sign transactions
*/
readonly signingScheme: SigningScheme;
readonly signingScheme: SigningScheme = SigningScheme.SingleKey;

/**
* The JWT token used to derive the account
Expand Down Expand Up @@ -185,7 +187,6 @@ export abstract class AbstractKeylessAccount extends Serializable implements Key
// Note, this is purposely not awaited to be non-blocking. The caller should await on the proofFetchCallback.
this.init(proof);
}
this.signingScheme = SigningScheme.SingleKey;
const pepperBytes = Hex.fromHexInput(pepper).toUint8Array();
if (pepperBytes.length !== AbstractKeylessAccount.PEPPER_LENGTH) {
throw new Error(`Pepper length in bytes should be ${AbstractKeylessAccount.PEPPER_LENGTH}`);
Expand All @@ -199,6 +200,10 @@ export abstract class AbstractKeylessAccount extends Serializable implements Key
}
}

getAnyPublicKey(): AnyPublicKey {
return new AnyPublicKey(this.publicKey);
}

/**
* This initializes the asynchronous proof fetch.
* @return Emits whether the proof succeeds or fails, but has no return.
Expand All @@ -223,7 +228,13 @@ 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 @@ -244,7 +255,22 @@ export abstract class AbstractKeylessAccount extends Serializable implements Key
proof: ZeroKnowledgeSig;
verificationKeyHash?: Uint8Array;
} {
const address = AccountAddress.deserialize(deserializer);
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 jwt = deserializer.deserializeStr();
const uidKey = deserializer.deserializeStr();
const pepper = deserializer.deserializeFixedBytes(31);
Expand Down
38 changes: 31 additions & 7 deletions src/account/Account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ import type { AccountAuthenticator } from "../transactions/authenticator/account
import { HexInput, SigningScheme, SigningSchemeInput } from "../types";
import type { AccountAddress, AccountAddressInput } from "../core/accountAddress";
import { AuthenticationKey } from "../core/authenticationKey";
import { AccountPublicKey, Ed25519PrivateKey, PrivateKey, Signature, VerifySignatureArgs } from "../core/crypto";
import { AccountPublicKey, Ed25519PrivateKey, PrivateKeyInput, Signature, VerifySignatureArgs } from "../core/crypto";
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 @@ -44,7 +48,7 @@ export interface CreateEd25519SingleKeyAccountFromPrivateKeyArgs {
* @param legacy - Always false; cannot be explicitly set to true.
*/
export interface CreateSingleKeyAccountFromPrivateKeyArgs {
privateKey: Exclude<PrivateKey, Ed25519PrivateKey>;
privateKey: PrivateKeyInput;
address?: AccountAddressInput;
legacy?: false;
}
Expand All @@ -57,7 +61,7 @@ export interface CreateSingleKeyAccountFromPrivateKeyArgs {
* @param legacy - Optional flag indicating if the account is a legacy account.
*/
export interface CreateAccountFromPrivateKeyArgs {
privateKey: PrivateKey;
privateKey: PrivateKeyInput;
address?: AccountAddressInput;
legacy?: boolean;
}
Expand Down Expand Up @@ -128,7 +132,7 @@ export interface PrivateKeyFromDerivationPathArgs {
*
* Note: Generating an account instance does not create the account on-chain.
*/
export abstract class Account {
export abstract class Account extends Serializable {
/**
* Public key associated with the account
*/
Expand Down Expand Up @@ -176,10 +180,9 @@ export abstract class Account {
* @returns An instance of either Ed25519Account or SingleKeyAccount based on the provided private key.
*/
static fromPrivateKey(args: CreateEd25519AccountFromPrivateKeyArgs): Ed25519Account;
static fromPrivateKey(args: CreateEd25519SingleKeyAccountFromPrivateKeyArgs): SingleKeyAccount;
static fromPrivateKey(args: CreateSingleKeyAccountFromPrivateKeyArgs): SingleKeyAccount;
static fromPrivateKey(args: CreateAccountFromPrivateKeyArgs): Account;
static fromPrivateKey(args: CreateAccountFromPrivateKeyArgs) {
static fromPrivateKey(args: CreateAccountFromPrivateKeyArgs): SingleKeyAccount;
static fromPrivateKey(args: CreateAccountFromPrivateKeyArgs): Ed25519Account | SingleKeyAccount {
const { privateKey, address, legacy = true } = args;
if (privateKey instanceof Ed25519PrivateKey && legacy) {
return new Ed25519Account({
Expand Down Expand Up @@ -286,4 +289,25 @@ export abstract class Account {
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}`);
}
}
}
34 changes: 33 additions & 1 deletion src/account/Ed25519Account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ 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 @@ -45,7 +48,7 @@ export interface VerifyEd25519SignatureArgs {
*
* Note: Generating an instance of this class does not create the account on-chain.
*/
export class Ed25519Account implements Account {
export class Ed25519Account extends Serializable implements Account {
/**
* Private key associated with the account
*/
Expand All @@ -68,6 +71,7 @@ export class Ed25519Account 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 @@ -156,4 +160,32 @@ export class Ed25519Account 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: 10 additions & 0 deletions src/account/EphemeralKeyPair.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,16 @@ 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
14 changes: 12 additions & 2 deletions src/account/FederatedKeylessAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { EphemeralKeyPair } from "./EphemeralKeyPair";
import { Deserializer, Serializer } from "../bcs";
import { FederatedKeylessPublicKey } from "../core/crypto/federatedKeyless";
import { AbstractKeylessAccount, ProofFetchCallback } from "./AbstractKeylessAccount";
import { Hex } from "../core";

/**
* Account implementation for the FederatedKeyless authentication scheme.
Expand Down Expand Up @@ -100,13 +99,24 @@ 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.
* @returns
*/
static fromBytes(bytes: HexInput): FederatedKeylessAccount {
return FederatedKeylessAccount.deserialize(new Deserializer(Hex.hexInputToUint8Array(bytes)));
return FederatedKeylessAccount.deserialize(Deserializer.fromHex(bytes));
}

/**
Expand Down
11 changes: 11 additions & 0 deletions src/account/KeylessAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,17 @@ 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.
Expand Down
Loading
Loading