UNPKG

@chorus-one/signer-local

Version:

Local signer for Chorus One SDK that utilizes a BIP39 mnemonic for signing operations

158 lines (138 loc) 5.58 kB
import type { LocalSignerConfig } from './types' import type { Signature, SignerData, AddressDerivationFn, MnemonicToSeedFn, SeedToKeypairFn } from '@chorus-one/signer' import { KeyType } from '@chorus-one/signer' import type { Logger } from '@chorus-one/utils' import { nopLogger } from '@chorus-one/utils' import { mnemonicToSeed } from 'bip39' import { deriveKey as deriveEd25519, sign as signEd25519 } from './ed25519' import { deriveKey as deriveSecp256k1, sign as signSecp256k1 } from './secp256k1' /** * The LocalSigner in the Chorus One SDK is a specialized implementation of the Signer interface that utilizes a `BIP39` * mnemonic for signing operations. * * This signer is ideal for local environments where you need a straightforward and * secure method to generate and manage cryptographic keys from mnemonic phrases. */ export class LocalSigner { private readonly config: LocalSignerConfig private accounts: Map<string, { hdPath: string; privateKey: Uint8Array; publicKey: Uint8Array }> private addressDerivationFn: AddressDerivationFn private mnemonicToSeedFn: MnemonicToSeedFn private seedToKeypairFn?: SeedToKeypairFn private logger: Logger /** * Constructs a new LocalSigner. * * @param params - The parameters required to initialize the LocalSigner * @param params.mnemonic - A string containing your `BIP39` mnemonic phrase * @param params.keyType - An enum specifying the signing key type (e.g. SECP256K1, ED25519) * @param params.accounts - An array of account objects, each containing an HD path * @param params.addressDerivationFn - A function that derives the address from the public key * @param params.logger - (Optional) A logger to use for logging messages, i.e `console` * * @returns A new instance of LocalSigner. */ constructor (params: { mnemonic: string accounts: [{ hdPath: string }] keyType: KeyType addressDerivationFn: AddressDerivationFn mnemonicToSeedFn?: MnemonicToSeedFn seedToKeypairFn?: SeedToKeypairFn logger?: Logger }) { const { addressDerivationFn, ...config } = params this.config = config this.accounts = new Map() this.logger = params.logger ?? nopLogger this.addressDerivationFn = addressDerivationFn this.mnemonicToSeedFn = params.mnemonicToSeedFn ?? mnemonicToSeed this.seedToKeypairFn = params.seedToKeypairFn } /** * Initializes the signer, performing any necessary setup or configuration. * @returns A promise that resolves once the initialization is complete. */ async init (): Promise<void> { const seed = await this.mnemonicToSeedFn(this.config.mnemonic) await Promise.all( this.config.accounts.map(async (account) => { let key: { publicKey: Uint8Array; privateKey: Uint8Array } switch (this.config.keyType) { case undefined: case KeyType.SECP256K1: { const fn = this.seedToKeypairFn ?? deriveSecp256k1 key = await fn(seed, account.hdPath) break } case KeyType.ED25519: { const fn = this.seedToKeypairFn ?? deriveEd25519 key = await fn(seed, account.hdPath) break } default: throw new Error('unsupported key type') } const publicKey = key.publicKey const privateKey = key.privateKey const derivedAddresses = await this.addressDerivationFn(key.publicKey, account.hdPath) derivedAddresses.forEach((address) => { this.accounts.set(address.toLowerCase(), { hdPath: account.hdPath, privateKey, publicKey }) }) }) ) } /** * Signs the provided data using the private key associated with the signer's address. * * @param signerAddress - The address of the signer * @param signerData - The data to be signed, which can be a raw message or custom data * * @returns A promise that resolves to an object containing the signature and public key. */ async sign (signerAddress: string, signerData: SignerData): Promise<{ sig: Signature; pk: Uint8Array }> { this.logger.info(`signing data from address: ${signerAddress}`) if (signerData.message === undefined) { throw new Error('missing message to sign') } const content = signerData.message let sig: Signature | undefined switch (this.config.keyType) { case undefined: case KeyType.SECP256K1: sig = await signSecp256k1(content, this.getPrivateKey(signerAddress)) break case KeyType.ED25519: sig = await signEd25519(content, this.getPrivateKey(signerAddress)) break default: throw new Error('unsupported key type') } return { sig, pk: await this.getPublicKey(signerAddress) } } /** * Retrieves the public key associated with the signer's address. * * @param address - The address of the signer * * @returns A promise that resolves to a Uint8Array representing the public key. */ async getPublicKey (address: string): Promise<Uint8Array> { const account = this.accounts.get(address.toLowerCase()) if (account === undefined) { throw new Error(`no public key found for address: ${address}`) } return account.publicKey } private getPrivateKey (address: string): Uint8Array { const account = this.accounts.get(address.toLowerCase()) if (account === undefined) { throw new Error(`no private key found for address: ${address}`) } return account.privateKey } }