UNPKG

@roochnetwork/rooch-sdk

Version:
171 lines (170 loc) 5.54 kB
import { HDKey } from "@scure/bip32"; import { generateMnemonic } from "@scure/bip39"; import { wordlist } from "@scure/bip39/wordlists/english"; import { schnorr, secp256k1 } from "@noble/curves/secp256k1"; import { Authenticator, BitcoinSignMessage, encodeRoochSercetKey, isValidBIP86Path, Keypair, mnemonicToSeed, decodeRoochSercetKey } from "../../crypto/index.js"; import { blake2b, sha256, toHEX } from "../../utils/index.js"; import { Secp256k1PublicKey } from "./publickey.js"; const DEFAULT_SECP256K1_DERIVATION_PATH = "m/86'/0'/0'/0/1"; class Secp256k1Keypair extends Keypair { /** * Create a new keypair instance. * Generate random keypair if no {@link Secp256k1Keypair} is provided. * * @param keypair secp256k1 keypair */ constructor(keypair) { super(); if (keypair) { this.keypair = keypair; } else { const secretKey = secp256k1.utils.randomPrivateKey(); const publicKey = secp256k1.getPublicKey(secretKey, true); this.keypair = { publicKey, secretKey }; } } getBitcoinAddress() { return this.getSchnorrPublicKey().toAddress().bitcoinAddress; } getBitcoinAddressWith(network) { return this.getSchnorrPublicKey().toAddressWith(network).bitcoinAddress; } getRoochAddress() { return this.getSchnorrPublicKey().toAddress().roochAddress; } /** * Get the key scheme of the keypair Secp256k1 */ getKeyScheme() { return "Secp256k1"; } /** * Generate a new random keypair */ static generate() { return new Secp256k1Keypair(); } /** * Create a keypair from a raw secret key byte array. * * This method should only be used to recreate a keypair from a previously * generated secret key. Generating keypairs from a random seed should be done * with the {@link Keypair.fromSeed} method. * * @throws error if the provided secret key is invalid and validation is not skipped. * * @param secretKey secret key byte array * @param skipValidation skip secret key validation */ static fromSecretKey(secretKey, skipValidation) { const decodeSecretKey = typeof secretKey === "string" ? (() => { const decoded = decodeRoochSercetKey(secretKey); if (decoded.schema !== "Secp256k1") { throw new Error("provided secretKey is invalid"); } return decoded.secretKey; })() : secretKey; const publicKey = secp256k1.getPublicKey(decodeSecretKey, true); if (!skipValidation) { const encoder = new TextEncoder(); const signData = encoder.encode("rooch validation"); const msgHash = toHEX(blake2b(signData, { dkLen: 32 })); const signature = secp256k1.sign(msgHash, decodeSecretKey); if (!secp256k1.verify(signature, msgHash, publicKey, { lowS: true })) { throw new Error("Provided secretKey is invalid"); } } return new Secp256k1Keypair({ publicKey, secretKey: decodeSecretKey }); } /** * Generate a keypair from a 32 byte seed. * * @param seed seed byte array */ static fromSeed(seed) { let publicKey = secp256k1.getPublicKey(seed, true); return new Secp256k1Keypair({ publicKey, secretKey: seed }); } /** * The public key for this keypair */ getPublicKey() { return new Secp256k1PublicKey(this.keypair.publicKey); } getSchnorrPublicKey() { return new Secp256k1PublicKey(schnorr.getPublicKey(this.keypair.secretKey)); } /** * The Bech32 secret key string for this Secp256k1 keypair */ getSecretKey() { return encodeRoochSercetKey(this.keypair.secretKey, this.getKeyScheme()); } /** * Return the ecdsa signature for the provided data. */ async sign(input) { const msgHash = sha256(input); const sig = secp256k1.sign(msgHash, this.keypair.secretKey, { lowS: true }); return sig.toCompactRawBytes(); } /** * Return the schnorr signature for the provided data. */ async sign_schnorr(input, auxRand) { const sig = schnorr.sign(input, this.keypair.secretKey, auxRand); return sig; } async signTransaction(input) { return await Authenticator.bitcoin( new BitcoinSignMessage(input.hashData(), input.getInfo() ?? "sdk"), this ); } /** * Derive Secp256k1 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/86'/0'/0'/0/1, otherwise the path must * be compliant to BIP-32 in form m/86'/0'/{account_index}'/{change_index}/{address_index}. */ static deriveKeypair(mnemonics, path) { if (path == null) { path = DEFAULT_SECP256K1_DERIVATION_PATH; } if (!isValidBIP86Path(path)) { throw new Error("Invalid derivation path"); } const key = HDKey.fromMasterSeed(mnemonicToSeed(mnemonics)).derive(path); if (key.publicKey == null || key.privateKey == null) { throw new Error("Invalid key"); } return Secp256k1Keypair.fromSecretKey(key.privateKey); } /** * Generate a new mnemonic and derive a keypair from it. * * @param path Optional derivation path. If not provided, will use DEFAULT_SECP256K1_DERIVATION_PATH * @returns An object containing the mnemonic and the derived keypair */ static generateWithMnemonic(path) { const mnemonic = generateMnemonic(wordlist); const keypair = this.deriveKeypair(mnemonic, path); return { mnemonic, keypair }; } } export { DEFAULT_SECP256K1_DERIVATION_PATH, Secp256k1Keypair }; //# sourceMappingURL=keypair.js.map