@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
JavaScript
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;