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