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