UNPKG

@exodus/bip322-js

Version:

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

128 lines (127 loc) 6.18 kB
import * as bitcoin from '@exodus/bitcoinjs'; import { ecdsaVerifyHashSync, schnorrVerify } from '@exodus/crypto/secp256k1'; import Address from './Address.js'; import BIP322 from './BIP322.js'; import { decodeScriptSignature } from './bitcoinjs/DecodeScriptSignature.js'; import * as bitcoinMessage from './bitcoinjs-message-verify.js'; import BIP137 from './helpers/BIP137.js'; class Verifier { static async verifySignature(signerAddress, message, signature) { if (!Buffer.isBuffer(signature)) { throw new TypeError('signature must be a Buffer'); } if (Address.isP2PKH(signerAddress) || BIP137.isBIP137Signature(signature)) { return this.verifyBIP137Signature(signerAddress, message, signature); } const scriptPubKey = Address.convertAdressToScriptPubkey(signerAddress); const toSpendTx = BIP322.buildToSpendTx(message, scriptPubKey); const toSignTx = BIP322.buildToSignTx(toSpendTx.getId(), scriptPubKey); toSignTx.updateInput(0, { finalScriptWitness: signature, }); const witness = toSignTx.extractTransaction().ins[0].witness; const encodedSignature = witness[0]; if (Address.isP2WPKHWitness(witness)) { const publicKey = witness[1]; const { signature } = decodeScriptSignature(encodedSignature); const hashedPubkey = bitcoin.crypto.hash160(publicKey); let hashToSign; if (Address.isP2SH(signerAddress)) { hashToSign = this.getHashForSigP2SHInP2WPKH(toSignTx, hashedPubkey); const lockingScript = Buffer.concat([Buffer.from([0x00, 0x14]), hashedPubkey]); const hashedLockingScript = bitcoin.crypto.hash160(lockingScript); const hashedLockingScriptInScriptPubKey = scriptPubKey.subarray(2, -1); if (Buffer.compare(hashedLockingScript, hashedLockingScriptInScriptPubKey) !== 0) { return false; } } else { hashToSign = this.getHashForSigP2WPKH(toSignTx); const hashedPubkeyInScriptPubkey = scriptPubKey.subarray(2); if (Buffer.compare(hashedPubkey, hashedPubkeyInScriptPubkey) !== 0) { return false; } } return ecdsaVerifyHashSync({ hash: hashToSign, publicKey, signature }); } if (Address.isP2TR(signerAddress)) { if (!Address.isSingleKeyP2TRWitness(witness)) { throw new Error('BIP-322 verification from script-spend P2TR is unsupported.'); } const publicKey = scriptPubKey.subarray(2); let hashToSign; let signature; if (encodedSignature.byteLength === 64) { hashToSign = this.getHashForSigP2TR(toSignTx, 0x00); signature = encodedSignature; } else if (encodedSignature.byteLength === 65) { hashToSign = this.getHashForSigP2TR(toSignTx, encodedSignature[64]); signature = encodedSignature.subarray(0, -1); } else { throw new Error('Invalid Schnorr signature provided.'); } return schnorrVerify({ data: hashToSign, xOnly: publicKey, signature }); } throw new Error('Only P2WPKH, P2SH-P2WPKH, and single-key-spend P2TR BIP-322 verification is supported. Unsupported address is provided.'); } static verifyBIP137Signature(signerAddress, message, signature) { if (Address.isP2PKH(signerAddress)) { return bitcoinMessage.verify(message, signerAddress, signature); } const publicKeySigned = BIP137.derivePubKey(message, signature); const legacySigningAddress = Address.convertPubKeyIntoAddress(publicKeySigned, 'p2pkh').mainnet; if (Address.isP2SH(signerAddress)) { const p2shAddressDerived = Address.convertPubKeyIntoAddress(publicKeySigned, 'p2sh-p2wpkh'); if (p2shAddressDerived.mainnet !== signerAddress && p2shAddressDerived.testnet !== signerAddress) { return false; } } else if (Address.isP2WPKH(signerAddress)) { const p2wpkhAddressDerived = Address.convertPubKeyIntoAddress(publicKeySigned, 'p2wpkh'); if (p2wpkhAddressDerived.mainnet !== signerAddress && p2wpkhAddressDerived.testnet !== signerAddress) { return false; } } else if (Address.isP2TR(signerAddress)) { const p2trAddressDerived = Address.convertPubKeyIntoAddress(publicKeySigned, 'p2tr'); if (p2trAddressDerived.mainnet !== signerAddress && p2trAddressDerived.testnet !== signerAddress) { return false; } } else { return false; } return bitcoinMessage.verify(message, legacySigningAddress, signature); } static getHashForSigP2WPKH(toSignTx) { const signingScript = bitcoin.payments.p2pkh({ hash: toSignTx.data.inputs[0].witnessUtxo.script.subarray(2), }).output; return toSignTx .extractTransaction() .hashForWitnessV0(0, signingScript, 0, bitcoin.Transaction.SIGHASH_ALL); } static getHashForSigP2SHInP2WPKH(toSignTx, hashedPubkey) { const signingScript = bitcoin.payments.p2pkh({ hash: hashedPubkey, }).output; return toSignTx .extractTransaction() .hashForWitnessV0(0, signingScript, 0, bitcoin.Transaction.SIGHASH_ALL); } static getHashForSigP2TR(toSignTx, hashType) { if (hashType !== bitcoin.Transaction.SIGHASH_DEFAULT && hashType !== bitcoin.Transaction.SIGHASH_ALL) { throw new Error('Invalid SIGHASH used in signature. Must be either SIGHASH_ALL or SIGHASH_DEFAULT.'); } return toSignTx .extractTransaction() .hashForWitnessV1(0, [toSignTx.data.inputs[0].witnessUtxo.script], [0], hashType); } } export default Verifier;