UNPKG

@exodus/bip322-js

Version:

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

119 lines (118 loc) 5.41 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 assert from 'minimalistic-assert'; const createAsyncSigner = ({ signer, publicKey, tweak }) => { return { 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 sign(privateKeyOrWIF, address, message, network = bitcoin.networks.bitcoin) { const ECPair = bitcoin.ECPair; let signer = Buffer.isBuffer(privateKeyOrWIF) ? ECPair.fromPrivateKey(privateKeyOrWIF) : ECPair.fromWIF(privateKeyOrWIF, network); if (!this.checkPubKeyCorrespondToAddress(signer.publicKey, address)) { throw new Error(`Invalid private key provided for signing message for ${address}.`); } if (Address.isP2PKH(address)) { return bitcoinMessage.signSync(message, signer.privateKey, signer.compressed); } const scriptPubKey = Address.convertAdressToScriptPubkey(address); const toSpendTx = BIP322.buildToSpendTx(message, scriptPubKey); let toSignTx; if (Address.isP2SH(address)) { const redeemScript = bitcoin.payments.p2wpkh({ hash: bitcoin.crypto.hash160(signer.publicKey), network, }).output; toSignTx = BIP322.buildToSignTx(toSpendTx.getId(), redeemScript, true); } else if (Address.isP2WPKH(address)) { toSignTx = BIP322.buildToSignTx(toSpendTx.getId(), scriptPubKey); } else { const internalPublicKey = signer.publicKey.subarray(1, 33); signer = signer.tweak(bitcoin.crypto.taggedHash('TapTweak', signer.publicKey.subarray(1, 33))); toSignTx = BIP322.buildToSignTx(toSpendTx.getId(), scriptPubKey, false, internalPublicKey); } const toSignTxSigned = toSignTx .signAllInputs(signer, [bitcoin.Transaction.SIGHASH_ALL, bitcoin.Transaction.SIGHASH_DEFAULT]) .finalizeAllInputs(); return BIP322.encodeWitness(toSignTxSigned); } static async signAsync(signer, address, message, network = bitcoin.networks.bitcoin) { if (typeof signer === 'string' || Buffer.isBuffer(signer)) { return this.sign(signer, address, message, network); } 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, true); } 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: bitcoin.crypto.hash160(publicKey), 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;