PHP WebShell
Текущая директория: /opt/BitGoJS/node_modules/@aptos-labs/ts-sdk/src/account
Просмотр файла: AbstractKeylessAccount.ts
// Copyright © Aptos Foundation
// SPDX-License-Identifier: Apache-2.0
import EventEmitter from "eventemitter3";
import { jwtDecode } from "jwt-decode";
import { EphemeralCertificateVariant, HexInput, SigningScheme } from "../types";
import { AccountAddress } from "../core/accountAddress";
import {
AnyPublicKey,
AnySignature,
KeylessPublicKey,
KeylessSignature,
EphemeralCertificate,
ZeroKnowledgeSig,
ZkProof,
getKeylessJWKs,
MoveJWK,
getKeylessConfig,
} from "../core/crypto";
import { EphemeralKeyPair } from "./EphemeralKeyPair";
import { Hex } from "../core/hex";
import { AccountAuthenticatorSingleKey } from "../transactions/authenticator/account";
import { Deserializer, Serializable, Serializer } from "../bcs";
import { deriveTransactionType, generateSigningMessage } from "../transactions/transactionBuilder/signingMessage";
import { AnyRawTransaction, AnyRawTransactionInstance } from "../transactions/types";
import { base64UrlDecode } from "../utils/helpers";
import { FederatedKeylessPublicKey } from "../core/crypto/federatedKeyless";
import { Account } from "./Account";
import { AptosConfig } from "../api/aptosConfig";
import { KeylessError, KeylessErrorType } from "../errors";
/**
* An interface which defines if an Account utilizes Keyless signing.
*/
export interface KeylessSigner extends Account {
checkKeylessAccountValidity(aptosConfig: AptosConfig): Promise<void>;
}
export function isKeylessSigner(obj: any): obj is KeylessSigner {
return obj !== null && obj !== undefined && typeof obj.checkKeylessAccountValidity === "function";
}
/**
* 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 {
static readonly PEPPER_LENGTH: number = 31;
/**
* The KeylessPublicKey associated with the account
*/
readonly publicKey: KeylessPublicKey | FederatedKeylessPublicKey;
/**
* The EphemeralKeyPair used to generate sign.
*/
readonly ephemeralKeyPair: EphemeralKeyPair;
/**
* The claim on the JWT to identify a user. This is typically 'sub' or 'email'.
*/
readonly uidKey: string;
/**
* The value of the uidKey claim on the JWT. This intended to be a stable user identifier.
*/
readonly uidVal: string;
/**
* The value of the 'aud' claim on the JWT, also known as client ID. This is the identifier for the dApp's
* OIDC registration with the identity provider.
*/
readonly aud: string;
/**
* A value contains 31 bytes of entropy that preserves privacy of the account. Typically fetched from a pepper provider.
*/
readonly pepper: Uint8Array;
/**
* Account address associated with the account
*/
readonly accountAddress: AccountAddress;
/**
* The zero knowledge signature (if ready) which contains the proof used to validate the EphemeralKeyPair.
*/
proof: ZeroKnowledgeSig | undefined;
/**
* The proof of the EphemeralKeyPair or a promise that provides the proof. This is used to allow for awaiting on
* fetching the proof.
*/
readonly proofOrPromise: ZeroKnowledgeSig | Promise<ZeroKnowledgeSig>;
/**
* Signing scheme used to sign transactions
*/
readonly signingScheme: SigningScheme;
/**
* The JWT token used to derive the account
*/
readonly jwt: string;
/**
* The hash of the verification key used to verify the proof. This is optional and can be used to check verifying key
* rotations which may invalidate the proof.
*/
readonly verificationKeyHash?: Uint8Array;
/**
* An event emitter used to assist in handling asynchronous proof fetching.
*/
private readonly emitter: EventEmitter<ProofFetchEvents>;
/**
* Use the static generator `create(...)` instead.
* Creates an instance of the KeylessAccount with an optional proof.
*
* @param args - The parameters for creating a KeylessAccount.
* @param args.address - Optional account address associated with the KeylessAccount.
* @param args.publicKey - A KeylessPublicKey or FederatedKeylessPublicKey.
* @param args.ephemeralKeyPair - The ephemeral key pair used in the account creation.
* @param args.iss - A JWT issuer.
* @param args.uidKey - The claim on the JWT to identify a user. This is typically 'sub' or 'email'.
* @param args.uidVal - The unique id for this user, intended to be a stable user identifier.
* @param args.aud - The value of the 'aud' claim on the JWT, also known as client ID. This is the identifier for the dApp's
* OIDC registration with the identity provider.
* @param args.pepper - A hexadecimal input used for additional security.
* @param args.proof - A Zero Knowledge Signature or a promise that resolves to one.
* @param args.proofFetchCallback - Optional callback function for fetching proof.
* @param args.jwt - A JSON Web Token used for authentication.
* @param args.verificationKeyHash Optional 32-byte verification key hash as hex input used to check proof validity.
*/
protected constructor(args: {
address?: AccountAddress;
publicKey: KeylessPublicKey | FederatedKeylessPublicKey;
ephemeralKeyPair: EphemeralKeyPair;
iss: string;
uidKey: string;
uidVal: string;
aud: string;
pepper: HexInput;
proof: ZeroKnowledgeSig | Promise<ZeroKnowledgeSig>;
proofFetchCallback?: ProofFetchCallback;
jwt: string;
verificationKeyHash?: HexInput;
}) {
super();
const {
address,
ephemeralKeyPair,
publicKey,
uidKey,
uidVal,
aud,
pepper,
proof,
proofFetchCallback,
jwt,
verificationKeyHash,
} = args;
this.ephemeralKeyPair = ephemeralKeyPair;
this.publicKey = publicKey;
this.accountAddress = address ? AccountAddress.from(address) : this.publicKey.authKey().derivedAddress();
this.uidKey = uidKey;
this.uidVal = uidVal;
this.aud = aud;
this.jwt = jwt;
this.emitter = new EventEmitter<ProofFetchEvents>();
this.proofOrPromise = proof;
if (proof instanceof ZeroKnowledgeSig) {
this.proof = proof;
} else {
if (proofFetchCallback === undefined) {
throw new Error("Must provide callback for async proof fetch");
}
this.emitter.on("proofFetchFinish", async (status) => {
await proofFetchCallback(status);
this.emitter.removeAllListeners();
});
// 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}`);
}
this.pepper = pepperBytes;
if (verificationKeyHash !== undefined) {
if (Hex.hexInputToUint8Array(verificationKeyHash).length !== 32) {
throw new Error("verificationKeyHash must be 32 bytes");
}
this.verificationKeyHash = Hex.hexInputToUint8Array(verificationKeyHash);
}
}
/**
* This initializes the asynchronous proof fetch.
* @return Emits whether the proof succeeds or fails, but has no return.
*/
async init(promise: Promise<ZeroKnowledgeSig>) {
try {
this.proof = await promise;
this.emitter.emit("proofFetchFinish", { status: "Success" });
} catch (error) {
if (error instanceof Error) {
this.emitter.emit("proofFetchFinish", { status: "Failed", error: error.toString() });
} else {
this.emitter.emit("proofFetchFinish", { status: "Failed", error: "Unknown" });
}
}
}
/**
* Serializes the jwt data into a format suitable for transmission or storage.
* This function ensures that both the jwt data and the proof are properly serialized.
*
* @param serializer - The serializer instance used to convert the jwt data into bytes.
*/
serialize(serializer: Serializer): void {
this.accountAddress.serialize(serializer);
serializer.serializeStr(this.jwt);
serializer.serializeStr(this.uidKey);
serializer.serializeFixedBytes(this.pepper);
this.ephemeralKeyPair.serialize(serializer);
if (this.proof === undefined) {
throw new Error("Cannot serialize - proof undefined");
}
this.proof.serialize(serializer);
serializer.serializeOption(this.verificationKeyHash, 32);
}
static partialDeserialize(deserializer: Deserializer): {
address: AccountAddress;
jwt: string;
uidKey: string;
pepper: Uint8Array;
ephemeralKeyPair: EphemeralKeyPair;
proof: ZeroKnowledgeSig;
verificationKeyHash?: Uint8Array;
} {
const address = AccountAddress.deserialize(deserializer);
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 { address, jwt, uidKey, pepper, ephemeralKeyPair, proof, verificationKeyHash };
}
/**
* Checks if the proof is expired. If so the account must be re-derived with a new EphemeralKeyPair
* and JWT token.
* @return boolean
*/
isExpired(): boolean {
return this.ephemeralKeyPair.isExpired();
}
/**
* Sign a message using Keyless.
* @param message the message to sign, as binary input
* @return the AccountAuthenticator containing the signature, together with the account's public key
*/
signWithAuthenticator(message: HexInput): AccountAuthenticatorSingleKey {
const signature = new AnySignature(this.sign(message));
const publicKey = new AnyPublicKey(this.publicKey);
return new AccountAuthenticatorSingleKey(publicKey, signature);
}
/**
* Sign a transaction using Keyless.
* @param transaction the raw transaction
* @return the AccountAuthenticator containing the signature of the transaction, together with the account's public key
*/
signTransactionWithAuthenticator(transaction: AnyRawTransaction): AccountAuthenticatorSingleKey {
const signature = new AnySignature(this.signTransaction(transaction));
const publicKey = new AnyPublicKey(this.publicKey);
return new AccountAuthenticatorSingleKey(publicKey, signature);
}
/**
* Waits for asynchronous proof fetching to finish.
* @return
*/
async waitForProofFetch() {
if (this.proofOrPromise instanceof Promise) {
await this.proofOrPromise;
}
}
/**
* Validates that the Keyless Account can be used to sign transactions.
* @return
*/
async checkKeylessAccountValidity(aptosConfig: AptosConfig): Promise<void> {
if (this.isExpired()) {
throw KeylessError.fromErrorType({
type: KeylessErrorType.EPHEMERAL_KEY_PAIR_EXPIRED,
});
}
await this.waitForProofFetch();
if (this.proof === undefined) {
throw KeylessError.fromErrorType({
type: KeylessErrorType.ASYNC_PROOF_FETCH_FAILED,
});
}
const header = jwtDecode(this.jwt, { header: true });
if (header.kid === undefined) {
throw KeylessError.fromErrorType({
type: KeylessErrorType.JWT_PARSING_ERROR,
details: "checkKeylessAccountValidity failed. JWT is missing 'kid' in header. This should never happen.",
});
}
if (this.verificationKeyHash !== undefined) {
const { verificationKey } = await getKeylessConfig({ aptosConfig });
if (Hex.hexInputToString(verificationKey.hash()) !== Hex.hexInputToString(this.verificationKeyHash)) {
throw KeylessError.fromErrorType({
type: KeylessErrorType.INVALID_PROOF_VERIFICATION_KEY_NOT_FOUND,
});
}
} else {
// eslint-disable-next-line no-console
console.warn(
"[Aptos SDK] The verification key hash was not set. Proof may be invalid if the verification key has rotated.",
);
}
await AbstractKeylessAccount.fetchJWK({ aptosConfig, publicKey: this.publicKey, kid: header.kid });
}
/**
* Sign the given message using Keyless.
* @param message in HexInput format
* @returns Signature
*/
sign(message: HexInput): KeylessSignature {
const { expiryDateSecs } = this.ephemeralKeyPair;
if (this.isExpired()) {
throw KeylessError.fromErrorType({
type: KeylessErrorType.EPHEMERAL_KEY_PAIR_EXPIRED,
});
}
if (this.proof === undefined) {
throw KeylessError.fromErrorType({
type: KeylessErrorType.PROOF_NOT_FOUND,
details: "Proof not found - make sure to call `await account.checkKeylessAccountValidity()` before signing.",
});
}
const ephemeralPublicKey = this.ephemeralKeyPair.getPublicKey();
const ephemeralSignature = this.ephemeralKeyPair.sign(message);
return new KeylessSignature({
jwtHeader: base64UrlDecode(this.jwt.split(".")[0]),
ephemeralCertificate: new EphemeralCertificate(this.proof, EphemeralCertificateVariant.ZkProof),
expiryDateSecs,
ephemeralPublicKey,
ephemeralSignature,
});
}
/**
* Sign the given transaction with Keyless.
* Signs the transaction and proof to guard against proof malleability.
* @param transaction the transaction to be signed
* @returns KeylessSignature
*/
signTransaction(transaction: AnyRawTransaction): KeylessSignature {
if (this.proof === undefined) {
throw KeylessError.fromErrorType({
type: KeylessErrorType.PROOF_NOT_FOUND,
details: "Proof not found - make sure to call `await account.checkKeylessAccountValidity()` before signing.",
});
}
const raw = deriveTransactionType(transaction);
const txnAndProof = new TransactionAndProof(raw, this.proof.proof);
const signMess = txnAndProof.hash();
return this.sign(signMess);
}
/**
* Note - This function is currently incomplete and should only be used to verify ownership of the KeylessAccount
*
* Verifies a signature given the message.
*
* TODO: Groth16 proof verification
*
* @param args.message the message that was signed.
* @param args.signature the KeylessSignature to verify
* @returns boolean
*/
verifySignature(args: { message: HexInput; signature: KeylessSignature }): boolean {
const { message, signature } = args;
if (this.isExpired()) {
return false;
}
if (!this.ephemeralKeyPair.getPublicKey().verifySignature({ message, signature: signature.ephemeralSignature })) {
return false;
}
return true;
}
/**
* Fetches the JWK from the issuer's well-known JWKS endpoint.
*
* @param args.publicKey The keyless public key to query
* @param args.kid The kid of the JWK to fetch
* @returns A JWK matching the `kid` in the JWT header.
* @throws {KeylessError} If the JWK cannot be fetched
*/
static async fetchJWK(args: {
aptosConfig: AptosConfig;
publicKey: KeylessPublicKey | FederatedKeylessPublicKey;
kid: string;
}): Promise<MoveJWK> {
const { aptosConfig, publicKey, kid } = args;
const keylessPubKey = publicKey instanceof KeylessPublicKey ? publicKey : publicKey.keylessPublicKey;
const { iss } = keylessPubKey;
let allJWKs: Map<string, MoveJWK[]>;
const jwkAddr = publicKey instanceof FederatedKeylessPublicKey ? publicKey.jwkAddress : undefined;
try {
allJWKs = await getKeylessJWKs({ aptosConfig, jwkAddr });
} catch (error) {
throw KeylessError.fromErrorType({
type: KeylessErrorType.FULL_NODE_JWKS_LOOKUP_ERROR,
error,
details: `Failed to fetch ${jwkAddr ? "Federated" : "Patched"}JWKs ${jwkAddr ? `for address ${jwkAddr}` : "0x1"}`,
});
}
// Find the corresponding JWK set by `iss`
const jwksForIssuer = allJWKs.get(iss);
if (jwksForIssuer === undefined) {
throw KeylessError.fromErrorType({
type: KeylessErrorType.INVALID_JWT_ISS_NOT_RECOGNIZED,
details: `JWKs for issuer ${iss} not found.`,
});
}
// Find the corresponding JWK by `kid`
const jwk = jwksForIssuer.find((key) => key.kid === kid);
if (jwk === undefined) {
throw KeylessError.fromErrorType({
type: KeylessErrorType.INVALID_JWT_JWK_NOT_FOUND,
details: `JWK with kid '${kid}' for issuer '${iss}' not found.`,
});
}
return jwk;
}
}
/**
* A container class to hold a transaction and a proof. It implements CryptoHashable which is used to create
* the signing message for Keyless transactions. We sign over the proof to ensure non-malleability.
*/
export class TransactionAndProof extends Serializable {
/**
* The transaction to sign.
*/
transaction: AnyRawTransactionInstance;
/**
* The zero knowledge proof used in signing the transaction.
*/
proof?: ZkProof;
/**
* The domain separator prefix used when hashing.
*/
readonly domainSeparator = "APTOS::TransactionAndProof";
constructor(transaction: AnyRawTransactionInstance, proof?: ZkProof) {
super();
this.transaction = transaction;
this.proof = proof;
}
/**
* Serializes the transaction data into a format suitable for transmission or storage.
* This function ensures that both the transaction bytes and the proof are properly serialized.
*
* @param serializer - The serializer instance used to convert the transaction data into bytes.
*/
serialize(serializer: Serializer): void {
serializer.serializeFixedBytes(this.transaction.bcsToBytes());
serializer.serializeOption(this.proof);
}
/**
* Hashes the bcs serialized from of the class. This is the typescript corollary to the BCSCryptoHash macro in aptos-core.
*
* @returns Uint8Array
*/
hash(): Uint8Array {
return generateSigningMessage(this.bcsToBytes(), this.domainSeparator);
}
}
export type ProofFetchSuccess = {
status: "Success";
};
export type ProofFetchFailure = {
status: "Failed";
error: string;
};
export type ProofFetchStatus = ProofFetchSuccess | ProofFetchFailure;
export type ProofFetchCallback = (status: ProofFetchStatus) => Promise<void>;
export interface ProofFetchEvents {
proofFetchFinish: (status: ProofFetchStatus) => void;
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!