-
Notifications
You must be signed in to change notification settings - Fork 50
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 AccountUtils which allows for general serialization of the account classes #571
base: main
Are you sure you want to change the base?
Changes from all commits
8099034
35df474
c8778a6
cdbe647
3dc46d2
c81c2e6
cdd2c4e
8c10d16
db87a22
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ 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"; | ||
|
@@ -50,7 +50,7 @@ export interface CreateEd25519SingleKeyAccountFromPrivateKeyArgs { | |
* @category Account (On-Chain Model) | ||
*/ | ||
export interface CreateSingleKeyAccountFromPrivateKeyArgs { | ||
privateKey: Exclude<PrivateKey, Ed25519PrivateKey>; | ||
privateKey: PrivateKeyInput; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not the same right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would maybe use |
||
address?: AccountAddressInput; | ||
legacy?: false; | ||
} | ||
|
@@ -65,7 +65,7 @@ export interface CreateSingleKeyAccountFromPrivateKeyArgs { | |
* @category Account (On-Chain Model) | ||
*/ | ||
export interface CreateAccountFromPrivateKeyArgs { | ||
privateKey: PrivateKey; | ||
privateKey: PrivateKeyInput; | ||
address?: AccountAddressInput; | ||
legacy?: boolean; | ||
} | ||
|
@@ -206,10 +206,9 @@ export abstract class Account { | |
* @category Account (On-Chain Model) | ||
*/ | ||
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 { | ||
Comment on lines
-209
to
+211
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why these changes? |
||
const { privateKey, address, legacy = true } = args; | ||
if (privateKey instanceof Ed25519PrivateKey && legacy) { | ||
return new Ed25519Account({ | ||
|
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we need a static class to namespace these functions. export function accountToBytes(account: Account): Uint8Array { ... }
export function accountFromHexInput(input: HexInput): Account { ... }
export function serializeAccount(serializer: Serializer, account: Account) { ... }
export function deserializeAccount(deserializer: Deserializer): Account { ... } or alternatively, a namespace export namespace AccountUtils {
export function ...
} |
||
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; | ||
} | ||
Comment on lines
+169
to
+207
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need these? const ed25519Account = AccountUtils.fromHex(hex) as Ed25519Account; |
||
|
||
static fromHex(hex: HexInput): Account { | ||
return this.deserialize(Deserializer.fromHex(hex)); | ||
} | ||
|
||
static fromBytes(bytes: Uint8Array): Account { | ||
return this.fromHex(bytes); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about we make
this.publicKey
return the wrapped key for consistency?So from outside we don't have to call a different method.
And if we need access to
KeylessPublicKey
we can make an internal field for that e.g.private readonly keylessPublicKey: KeylessPublicKey