UNPKG

@exodus/bip322-js

Version:

A Javascript library that provides utility functions related to the BIP-322 signature scheme

127 lines (126 loc) 5.62 kB
import Address from './Address.js'; import BIP322 from './BIP322.js'; import * as bitcoin from '@exodus/bitcoinjs'; import * as bitcoinMessage from '@exodus/bitcoinjs/message'; import * as secp256k1 from '@exodus/crypto/secp256k1'; import { hash } from '@exodus/crypto/hash'; import assert from 'minimalistic-assert'; import wif from 'wif'; export const createSigner = (encodedKey, network) => { let decoded; if (typeof encodedKey === 'string') { decoded = wif.decode(encodedKey); const version = decoded.version; if (version !== network.wif) throw new Error('Invalid network version'); } else { decoded = { privateKey: encodedKey, compressed: true }; } const { privateKey, compressed } = decoded; const publicKey = secp256k1.privateKeyToPublicKey({ privateKey, compressed, format: 'buffer' }); const tweakPrivateKey = (tweak) => secp256k1.privateKeyTweakAdd({ privateKey: publicKey[0] === 3 ? secp256k1.privateKeyTweakNegate({ privateKey }) : privateKey, tweak, }); const signer = { getPublicKey: async () => publicKey, sign: async ({ data, signatureType, enc, tweak, extraEntropy = null }) => { if (signatureType === 'schnorr') { return secp256k1.schnorrSign({ data, privateKey: tweak ? tweakPrivateKey(tweak) : privateKey, extraEntropy, format: 'buffer', }); } assert(signatureType === 'ecdsa', 'Invalid signature type'); assert(enc === 'sig' || enc === 'sig,rec', 'Invalid encoding'); return secp256k1.ecdsaSignHash({ hash: data, privateKey, extraEntropy, recovery: enc !== 'sig', format: 'buffer', }); }, }; return { signer, compressed }; }; const createAsyncSigner = ({ signer, publicKey, tweak }) => ({ publicKey, getPublicKey: () => publicKey, sign: async (data) => signer.sign({ signatureType: 'ecdsa', data, enc: 'sig' }), signSchnorr: async (hash) => signer.sign({ signatureType: 'schnorr', data: hash, tweak }), }); class Signer { static async signAsync(signerOrKey, address, message, network = bitcoin.networks.bitcoin) { if (typeof signerOrKey === 'string' || Buffer.isBuffer(signerOrKey)) { const { signer, compressed } = createSigner(signerOrKey, network); return this.#signAsyncInternal(signer, address, message, network, compressed); } return this.#signAsyncInternal(signerOrKey, address, message, network, true); } static async #signAsyncInternal(signer, address, message, network = bitcoin.networks.bitcoin, compressed) { const publicKey = await signer.getPublicKey(); assert(this.checkPubKeyCorrespondToAddress(publicKey, address), `Invalid signer for address "${address}".`); if (Address.isP2PKH(address)) { const asyncSigner = { sign(hash, extraEntropy) { return signer.sign({ signatureType: 'ecdsa', data: hash, extraEntropy, enc: 'sig,rec', }); }, }; return bitcoinMessage.signAsync(message, asyncSigner, compressed); } const scriptPubKey = Address.convertAdressToScriptPubkey(address); const toSpendTx = BIP322.buildToSpendTx(message, scriptPubKey); let toSignTx; let tweak; if (Address.isP2SH(address)) { const redeemScript = bitcoin.payments.p2wpkh({ hash: await hash('hash160', publicKey, 'buffer'), network, }).output; toSignTx = BIP322.buildToSignTx(toSpendTx.getId(), redeemScript, true); } else if (Address.isP2WPKH(address)) { toSignTx = BIP322.buildToSignTx(toSpendTx.getId(), scriptPubKey); } else { const internalPublicKey = publicKey.subarray(1, 33); tweak = bitcoin.crypto.taggedHash('TapTweak', publicKey.subarray(1, 33)); toSignTx = BIP322.buildToSignTx(toSpendTx.getId(), scriptPubKey, false, internalPublicKey); } const asyncSigner = createAsyncSigner({ signer, publicKey, tweak }); await toSignTx.signAllInputsAsync(asyncSigner, [ bitcoin.Transaction.SIGHASH_ALL, bitcoin.Transaction.SIGHASH_DEFAULT, ]); return BIP322.encodeWitness(toSignTx.finalizeAllInputs()); } static checkPubKeyCorrespondToAddress(publicKey, claimedAddress) { let derivedAddresses; if (Address.isP2PKH(claimedAddress)) { derivedAddresses = Address.convertPubKeyIntoAddress(publicKey, 'p2pkh'); } else if (Address.isP2SH(claimedAddress)) { derivedAddresses = Address.convertPubKeyIntoAddress(publicKey, 'p2sh-p2wpkh'); } else if (Address.isP2WPKH(claimedAddress)) { derivedAddresses = Address.convertPubKeyIntoAddress(publicKey, 'p2wpkh'); } else if (Address.isP2TR(claimedAddress)) { derivedAddresses = Address.convertPubKeyIntoAddress(publicKey, 'p2tr'); } else { throw new Error('Unable to sign BIP-322 message for unsupported address type.'); } return (derivedAddresses.mainnet === claimedAddress || derivedAddresses.testnet === claimedAddress); } } export default Signer;