@mysten/sui
Version: 
Sui TypeScript API(Work in Progress)
182 lines (161 loc) • 5.15 kB
text/typescript
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0
import { ed25519 } from '@noble/curves/ed25519';
import {
	decodeSuiPrivateKey,
	encodeSuiPrivateKey,
	Keypair,
	PRIVATE_KEY_SIZE,
} from '../../cryptography/keypair.js';
import { isValidHardenedPath, mnemonicToSeedHex } from '../../cryptography/mnemonics.js';
import type { SignatureScheme } from '../../cryptography/signature-scheme.js';
import { derivePath } from './ed25519-hd-key.js';
import { Ed25519PublicKey } from './publickey.js';
export const DEFAULT_ED25519_DERIVATION_PATH = "m/44'/784'/0'/0'/0'";
/**
 * Ed25519 Keypair data. The publickey is the 32-byte public key and
 * the secretkey is 64-byte, where the first 32 bytes is the secret
 * key and the last 32 bytes is the public key.
 */
export interface Ed25519KeypairData {
	publicKey: Uint8Array;
	secretKey: Uint8Array;
}
/**
 * An Ed25519 Keypair used for signing transactions.
 */
export class Ed25519Keypair extends Keypair {
	private keypair: Ed25519KeypairData;
	/**
	 * Create a new Ed25519 keypair instance.
	 * Generate random keypair if no {@link Ed25519Keypair} is provided.
	 *
	 * @param keypair Ed25519 keypair
	 */
	constructor(keypair?: Ed25519KeypairData) {
		super();
		if (keypair) {
			this.keypair = {
				publicKey: keypair.publicKey,
				secretKey: keypair.secretKey.slice(0, 32),
			};
		} else {
			const privateKey = ed25519.utils.randomPrivateKey();
			this.keypair = {
				publicKey: ed25519.getPublicKey(privateKey),
				secretKey: privateKey,
			};
		}
	}
	/**
	 * Get the key scheme of the keypair ED25519
	 */
	getKeyScheme(): SignatureScheme {
		return 'ED25519';
	}
	/**
	 * Generate a new random Ed25519 keypair
	 */
	static generate(): Ed25519Keypair {
		const secretKey = ed25519.utils.randomPrivateKey();
		return new Ed25519Keypair({
			publicKey: ed25519.getPublicKey(secretKey),
			secretKey,
		});
	}
	/**
	 * Create a Ed25519 keypair from a raw secret key byte array, also known as seed.
	 * This is NOT the private scalar which is result of hashing and bit clamping of
	 * the raw secret key.
	 *
	 * @throws error if the provided secret key is invalid and validation is not skipped.
	 *
	 * @param secretKey secret key as a byte array or Bech32 secret key string
	 * @param options: skip secret key validation
	 */
	static fromSecretKey(
		secretKey: Uint8Array | string,
		options?: { skipValidation?: boolean },
	): Ed25519Keypair {
		if (typeof secretKey === 'string') {
			const decoded = decodeSuiPrivateKey(secretKey);
			if (decoded.schema !== 'ED25519') {
				throw new Error(`Expected a ED25519 keypair, got ${decoded.schema}`);
			}
			return this.fromSecretKey(decoded.secretKey, options);
		}
		const secretKeyLength = secretKey.length;
		if (secretKeyLength !== PRIVATE_KEY_SIZE) {
			throw new Error(
				`Wrong secretKey size. Expected ${PRIVATE_KEY_SIZE} bytes, got ${secretKeyLength}.`,
			);
		}
		const keypair = {
			publicKey: ed25519.getPublicKey(secretKey),
			secretKey,
		};
		if (!options || !options.skipValidation) {
			const encoder = new TextEncoder();
			const signData = encoder.encode('sui validation');
			const signature = ed25519.sign(signData, secretKey);
			if (!ed25519.verify(signature, signData, keypair.publicKey)) {
				throw new Error('provided secretKey is invalid');
			}
		}
		return new Ed25519Keypair(keypair);
	}
	/**
	 * The public key for this Ed25519 keypair
	 */
	getPublicKey(): Ed25519PublicKey {
		return new Ed25519PublicKey(this.keypair.publicKey);
	}
	/**
	 * The Bech32 secret key string for this Ed25519 keypair
	 */
	getSecretKey(): string {
		return encodeSuiPrivateKey(
			this.keypair.secretKey.slice(0, PRIVATE_KEY_SIZE),
			this.getKeyScheme(),
		);
	}
	/**
	 * Return the signature for the provided data using Ed25519.
	 */
	async sign(data: Uint8Array) {
		return ed25519.sign(data, this.keypair.secretKey);
	}
	/**
	 * Derive Ed25519 keypair from mnemonics and path. The mnemonics must be normalized
	 * and validated against the english wordlist.
	 *
	 * If path is none, it will default to m/44'/784'/0'/0'/0', otherwise the path must
	 * be compliant to SLIP-0010 in form m/44'/784'/{account_index}'/{change_index}'/{address_index}'.
	 */
	static deriveKeypair(mnemonics: string, path?: string): Ed25519Keypair {
		if (path == null) {
			path = DEFAULT_ED25519_DERIVATION_PATH;
		}
		if (!isValidHardenedPath(path)) {
			throw new Error('Invalid derivation path');
		}
		const { key } = derivePath(path, mnemonicToSeedHex(mnemonics));
		return Ed25519Keypair.fromSecretKey(key);
	}
	/**
	 * Derive Ed25519 keypair from mnemonicSeed and path.
	 *
	 * If path is none, it will default to m/44'/784'/0'/0'/0', otherwise the path must
	 * be compliant to SLIP-0010 in form m/44'/784'/{account_index}'/{change_index}'/{address_index}'.
	 */
	static deriveKeypairFromSeed(seedHex: string, path?: string): Ed25519Keypair {
		if (path == null) {
			path = DEFAULT_ED25519_DERIVATION_PATH;
		}
		if (!isValidHardenedPath(path)) {
			throw new Error('Invalid derivation path');
		}
		const { key } = derivePath(path, seedHex);
		return Ed25519Keypair.fromSecretKey(key);
	}
}