import { AsBytes, DIDKey, isCryptoKeyPair, isInsecureCryptoKeyPair, KeyPairRaw, Signer, Verifier, } from "../interface.ts"; import { NativeEd25519Signer, NativeEd25519Verifier } from "./native.ts"; import { NobleEd25519Signer, NobleEd25519Verifier } from "./noble.ts"; import * as bip39 from "@scure/bip39"; import { wordlist } from "@scure/bip39/wordlists/english"; import { ed25519RawToPkcs8, fromPEM, generateEd25519Pkcs8, isNativeEd25519Supported, pkcs8ToEd25519Raw, toPEM, } from "./utils.ts"; // Creation options used in `Ed25519Signer` instantiation. export interface Ed25519CreateConfig { // Indicates the preference of implementation to use. // If not specified, "webcrypto" is preferred if supported, // falling back to "noble" otherwise. // If specified, uses that implementation if supported, failing otherwise. implementation?: "webcrypto" | "noble"; } // Platform-specific implementation of an ED25519 Keypair. // // On browsers[0] that implement ed25519, the native Web Crypto // `NativeEd25519` is used. Otherwise, the `@noble/ed25519` implementation // is used. // // [0]: https://caniuse.com/mdn-api_subtlecrypto_sign_ed25519 export class Ed25519Signer implements Signer { #impl: NativeEd25519Signer | NobleEd25519Signer; constructor(impl: NativeEd25519Signer | NobleEd25519Signer) { this.#impl = impl; } get verifier(): Verifier { return this.#impl.verifier; } serialize(): KeyPairRaw { return this.#impl.serialize(); } did() { return this.#impl.did(); } sign(payload: AsBytes) { return this.#impl.sign(payload); } // Only "noble" implementations can be converted to PKCS8 // since we need the raw material. toPkcs8() { const serialized = this.serialize(); if ( "privateKey" in serialized && serialized.privateKey instanceof Uint8Array ) { return toPEM(ed25519RawToPkcs8(serialized.privateKey)); } throw new Error( 'Cannot convert identity to PKCS8 format: requires "noble" implementation.', ); } static async fromRaw( rawPrivateKey: Uint8Array, config: Ed25519CreateConfig = {}, ): Promise> { return new Ed25519Signer( await canUseNative(config) ? await NativeEd25519Signer.fromRaw(rawPrivateKey) : await NobleEd25519Signer.fromRaw(rawPrivateKey), ); } static async generate( config: Ed25519CreateConfig = {}, ): Promise> { return new Ed25519Signer( await canUseNative(config) ? await NativeEd25519Signer.generate() : await NobleEd25519Signer.generate(), ); } static async generateMnemonic( config: Ed25519CreateConfig = {}, ): Promise< [Ed25519Signer, string] > { const mnemonic = bip39.generateMnemonic(wordlist, 256); return [await Ed25519Signer.fromMnemonic(mnemonic, config), mnemonic]; } static generatePkcs8(): Uint8Array { return toPEM(generateEd25519Pkcs8()); } static async fromPkcs8( pkcs8: Uint8Array, config: Ed25519CreateConfig = {}, ): Promise> { const raw = pkcs8ToEd25519Raw(fromPEM(pkcs8)); return await Ed25519Signer.fromRaw(raw, config); } static async fromMnemonic( mnemonic: string, config: Ed25519CreateConfig = {}, ): Promise> { const bytes = bip39.mnemonicToEntropy(mnemonic, wordlist); return await Ed25519Signer.fromRaw(bytes, config); } static async deserialize( input: KeyPairRaw, ): Promise> { if (isCryptoKeyPair(input)) { return new Ed25519Signer( await NativeEd25519Signer.deserialize(input), ); } else if (isInsecureCryptoKeyPair(input)) { return new Ed25519Signer(await NobleEd25519Signer.deserialize(input)); } else { throw new Error("common-identity: Could not deserialize key."); } } } export class Ed25519Verifier implements Verifier { #impl: NativeEd25519Verifier | NobleEd25519Verifier; constructor(impl: NativeEd25519Verifier | NobleEd25519Verifier) { this.#impl = impl; } verify(auth: { payload: Uint8Array; signature: Uint8Array }) { return this.#impl.verify(auth); } did() { return this.#impl.did(); } static async fromDid( did: ID, config: Ed25519CreateConfig = {}, ): Promise> { return new Ed25519Verifier( await canUseNative(config) ? await NativeEd25519Verifier.fromDid(did) : await NobleEd25519Verifier.fromDid(did), ); } static async fromRaw( rawPublicKey: Uint8Array, config: Ed25519CreateConfig = {}, ): Promise> { return new Ed25519Verifier( await canUseNative(config) ? await NativeEd25519Verifier.fromRaw(rawPublicKey) : await NobleEd25519Verifier.fromRaw(rawPublicKey), ); } } // Returns `true` if native WebCrypto should be used, // or `false` if Noble implementation should be used. // // If WebCrypto explicitly requested and not supported, // throws an error. async function canUseNative(config: Ed25519CreateConfig = {}) { if (config.implementation === "webcrypto") { if (!(await isNativeEd25519Supported())) { throw new Error( "Required WebCrypto features are not supported on this platform.", ); } return true; } if (config.implementation === "noble") { return false; } return await isNativeEd25519Supported(); }