@aptos-labs/ts-sdk
Version:
Aptos TypeScript SDK
180 lines (155 loc) • 7 kB
text/typescript
import { AptosConfig } from "../api/aptosConfig.js";
import { AccountAddress, AccountAddressInput } from "../core/accountAddress.js";
import { Ed25519PrivateKey, Signature } from "../core/crypto/index.js";
import { MultiEd25519PublicKey, MultiEd25519Signature } from "../core/crypto/multiEd25519.js";
import { AccountAuthenticatorMultiEd25519 } from "../transactions/authenticator/account.js";
import { generateSigningMessageForTransaction } from "../transactions/transactionBuilder/signingMessage.js";
import { AnyRawTransaction } from "../transactions/types.js";
import { HexInput, SigningScheme } from "../types/index.js";
import type { Account } from "./Account.js";
export interface MultiEd25519SignerConstructorArgs {
publicKey: MultiEd25519PublicKey;
signers: Ed25519PrivateKey[];
address?: AccountAddressInput;
}
export interface VerifyMultiEd25519SignatureArgs {
message: HexInput;
signature: MultiEd25519Signature;
}
/**
* Signer implementation for the Multi-Ed25519 authentication scheme.
*
* Note: This authentication scheme is a legacy authentication scheme. Prefer using MultiKeyAccounts as a
* MultiKeyAccount can support any type of signer, not just Ed25519. Generating a signer instance does not
* create the account on-chain.
*/
export class MultiEd25519Account implements Account {
readonly publicKey: MultiEd25519PublicKey;
readonly accountAddress: AccountAddress;
readonly signingScheme = SigningScheme.MultiEd25519;
/**
* The signers used to sign messages. These signers should correspond to public keys in the
* MultiEd25519Account. The number of signers should be equal to this.publicKey.threshold.
* @group Implementation
* @category Account (On-Chain Model)
*/
readonly signers: Ed25519PrivateKey[];
/**
* An array of indices where for signer[i], signerIndicies[i] is the index of the corresponding public key in
* publicKey.publicKeys. Used to derive the right public key to use for verification.
* @group Implementation
* @category Account (On-Chain Model)
*/
readonly signerIndices: number[];
readonly signaturesBitmap: Uint8Array;
// region Constructors
constructor(args: MultiEd25519SignerConstructorArgs) {
const { signers, publicKey, address } = args;
this.publicKey = publicKey;
this.accountAddress = address ? AccountAddress.from(address) : this.publicKey.authKey().derivedAddress();
if (publicKey.threshold > signers.length) {
throw new Error(
`Not enough signers provided to satisfy the required signatures. Need ${publicKey.threshold} signers, but only ${signers.length} provided`,
);
} else if (publicKey.threshold < signers.length) {
throw new Error(
`More signers provided than required. Need ${publicKey.threshold} signers, but ${signers.length} provided`,
);
}
// For each signer, find its corresponding position in the public keys array
const bitPositions: number[] = [];
for (const signer of signers) {
bitPositions.push(this.publicKey.getIndex(signer.publicKey()));
}
// Create pairs of [signer, position] and sort them by position
// This sorting is critical because:
// 1. The on-chain verification expects signatures to be in ascending order by bit position
// 2. The bitmap must match the order of signatures when verifying
const signersAndBitPosition: [Ed25519PrivateKey, number][] = signers.map((signer, index) => [
signer,
bitPositions[index],
]);
signersAndBitPosition.sort((a, b) => a[1] - b[1]);
// Extract the sorted signers and their positions into separate arrays
this.signers = signersAndBitPosition.map((value) => value[0]);
this.signerIndices = signersAndBitPosition.map((value) => value[1]);
// Create a bitmap representing which public keys from the MultiEd25519PublicKey are being used
// This bitmap is used during signature verification to identify which public keys
// should be used to verify each signature
this.signaturesBitmap = this.publicKey.createBitmap({ bits: bitPositions });
}
// endregion
// region Account
/**
* Verify the given message and signature with the public key.
*
* @param args.message raw message data in HexInput format
* @param args.signature signed message Signature
* @returns
*/
verifySignature(args: VerifyMultiEd25519SignatureArgs): boolean {
return this.publicKey.verifySignature(args);
}
/**
* Verify the given message and signature with the public key.
*
* MultiEd25519 signatures do not depend on chain state, so this function is
* equivalent to the synchronous verifySignature method.
*
* @param args - The arguments for verifying the signature.
* @param args.aptosConfig - The configuration object for connecting to the Aptos network
* @param args.message - Raw message data in HexInput format.
* @param args.signature - Signed message signature.
* @returns A boolean indicating whether the signature is valid.
* @group Implementation
* @category Account (On-Chain Model)
*/
async verifySignatureAsync(args: {
aptosConfig: AptosConfig;
message: HexInput;
signature: Signature;
options?: { throwErrorWithReason?: boolean };
}): Promise<boolean> {
return this.publicKey.verifySignatureAsync({
...args,
signature: args.signature,
});
}
/**
* Sign a message using the account's Ed25519 private key.
* @param message the signing message, as binary input
* @return the AccountAuthenticator containing the signature, together with the account's public key
*/
signWithAuthenticator(message: HexInput): AccountAuthenticatorMultiEd25519 {
return new AccountAuthenticatorMultiEd25519(this.publicKey, this.sign(message));
}
/**
* Sign a transaction using the account's Ed25519 private keys.
* @param transaction the raw transaction
* @return the AccountAuthenticator containing the signature of the transaction, together with the account's public key
*/
signTransactionWithAuthenticator(transaction: AnyRawTransaction): AccountAuthenticatorMultiEd25519 {
return new AccountAuthenticatorMultiEd25519(this.publicKey, this.signTransaction(transaction));
}
/**
* Sign the given message using the account's Ed25519 private keys.
* @param message in HexInput format
* @returns MultiEd25519Signature
*/
sign(message: HexInput): MultiEd25519Signature {
const signatures = [];
for (const signer of this.signers) {
signatures.push(signer.sign(message));
}
return new MultiEd25519Signature({ signatures, bitmap: this.signaturesBitmap });
}
/**
* Sign the given transaction using the available signing capabilities.
* @param transaction the transaction to be signed
* @returns Signature
*/
signTransaction(transaction: AnyRawTransaction): MultiEd25519Signature {
return this.sign(generateSigningMessageForTransaction(transaction));
}
// endregion
}