@btc-vision/transaction
Version:
OPNet transaction library allows you to create and sign transactions for the OPNet network.
217 lines (216 loc) • 8.85 kB
JavaScript
import * as ecc from '@bitcoinerlab/secp256k1';
import bip32, { BIP32Factory } from 'bip32';
import bitcoin, { address, fromOutputScript, initEccLib, networks, opcodes, payments, script, toXOnly, } from '@btc-vision/bitcoin';
import { ECPairFactory } from 'ecpair';
import { secp256k1 } from '@noble/curves/secp256k1';
import { bytesToNumberBE, concatBytes, utf8ToBytes } from '@noble/curves/abstract/utils';
import { mod } from '@noble/curves/abstract/modular';
import { sha256 } from '@noble/hashes/sha2';
initEccLib(ecc);
const BIP32factory = typeof bip32 === 'function' ? bip32 : BIP32Factory;
if (!BIP32factory) {
throw new Error('Failed to load BIP32 library');
}
secp256k1.utils.precompute(8);
const { Point, CURVE } = secp256k1;
const TAP_TAG = utf8ToBytes('TapTweak');
const TAP_TAG_HASH = sha256(TAP_TAG);
function tapTweakHash(x) {
return sha256(concatBytes(TAP_TAG_HASH, TAP_TAG_HASH, x));
}
export class EcKeyPair {
static fromWIF(wif, network = networks.bitcoin) {
return this.ECPair.fromWIF(wif, network);
}
static fromPrivateKey(privateKey, network = networks.bitcoin) {
return this.ECPair.fromPrivateKey(!Buffer.isBuffer(privateKey) ? Buffer.from(privateKey) : privateKey, { network });
}
static fromPublicKey(publicKey, network = networks.bitcoin) {
const buf = !Buffer.isBuffer(publicKey) ? Buffer.from(publicKey) : publicKey;
return this.ECPair.fromPublicKey(buf, { network });
}
static generateMultiSigAddress(pubKeys, minimumSignatureRequired, network = networks.bitcoin) {
const publicKeys = this.verifyPubKeys(pubKeys, network);
if (publicKeys.length !== pubKeys.length)
throw new Error(`Contains invalid public keys`);
const p2ms = payments.p2ms({
m: minimumSignatureRequired,
pubkeys: publicKeys,
network: network,
});
const p2wsh = payments.p2wsh({ redeem: p2ms, network: network });
const address = p2wsh.address;
if (!address) {
throw new Error('Failed to generate address');
}
return address;
}
static verifyPubKeys(pubKeys, network = networks.bitcoin) {
return pubKeys.map((pubKey) => {
const key = EcKeyPair.fromPublicKey(pubKey, network);
if (!key) {
throw new Error('Failed to regenerate key');
}
return Buffer.from(key.publicKey);
});
}
static getP2WPKHAddress(keyPair, network = networks.bitcoin) {
const res = payments.p2wpkh({ pubkey: Buffer.from(keyPair.publicKey), network: network });
if (!res.address) {
throw new Error('Failed to generate wallet');
}
return res.address;
}
static tweakedPubKeyToAddress(tweakedPubKeyHex, network) {
if (tweakedPubKeyHex.startsWith('0x')) {
tweakedPubKeyHex = tweakedPubKeyHex.slice(2);
}
let tweakedPubKeyBuffer = Buffer.from(tweakedPubKeyHex, 'hex');
if (tweakedPubKeyBuffer.length !== 32) {
tweakedPubKeyBuffer = toXOnly(tweakedPubKeyBuffer);
}
return EcKeyPair.tweakedPubKeyBufferToAddress(tweakedPubKeyBuffer, network);
}
static tweakedPubKeyBufferToAddress(tweakedPubKeyBuffer, network) {
const { address } = payments.p2tr({
pubkey: Buffer.isBuffer(tweakedPubKeyBuffer)
? tweakedPubKeyBuffer
: Buffer.from(tweakedPubKeyBuffer),
network: network,
});
if (!address) {
throw new Error('Failed to generate Taproot address');
}
return address;
}
static p2op(bytes, network = networks.bitcoin, deploymentVersion = 0) {
const witnessProgram = Buffer.concat([
Buffer.from([deploymentVersion]),
bitcoin.crypto.hash160(Buffer.from(bytes)),
]);
if (witnessProgram.length < 2 || witnessProgram.length > 40) {
throw new Error('Witness program must be 2-40 bytes.');
}
const scriptData = script.compile([opcodes.OP_16, witnessProgram]);
return fromOutputScript(scriptData, network);
}
static xOnlyTweakedPubKeyToAddress(tweakedPubKeyHex, network) {
if (tweakedPubKeyHex.startsWith('0x')) {
tweakedPubKeyHex = tweakedPubKeyHex.slice(2);
}
const tweakedPubKeyBuffer = Buffer.from(tweakedPubKeyHex, 'hex');
const { address } = payments.p2tr({
pubkey: tweakedPubKeyBuffer,
network: network,
});
if (!address) {
throw new Error('Failed to generate Taproot address');
}
return address;
}
static tweakPublicKey(pub) {
if (typeof pub === 'string' && pub.startsWith('0x'))
pub = pub.slice(2);
const P = Point.fromHex(pub);
const Peven = (P.y & 1n) === 0n ? P : P.negate();
const xBytes = Peven.toBytes(true).subarray(1);
const tBytes = tapTweakHash(xBytes);
const t = mod(bytesToNumberBE(tBytes), CURVE.n);
const Q = Peven.add(Point.BASE.multiply(t));
return Buffer.from(Q.toBytes(true));
}
static tweakBatchSharedT(pubkeys, tweakScalar) {
const T = Point.BASE.multiply(tweakScalar);
return pubkeys.map((bytes) => {
const P = Point.fromHex(bytes);
const P_even = P.y % 2n === 0n ? P : P.negate();
const Q = P_even.add(T);
return Q.toBytes(true);
});
}
static generateWallet(network = networks.bitcoin) {
const keyPair = this.ECPair.makeRandom({
network: network,
});
const wallet = this.getP2WPKHAddress(keyPair, network);
if (!wallet) {
throw new Error('Failed to generate wallet');
}
return {
address: wallet,
privateKey: keyPair.toWIF(),
publicKey: Buffer.from(keyPair.publicKey).toString('hex'),
};
}
static verifyContractAddress(contractAddress, network = networks.bitcoin) {
return !!address.toOutputScript(contractAddress, network);
}
static getLegacySegwitAddress(keyPair, network = networks.bitcoin) {
const wallet = payments.p2sh({
redeem: payments.p2wpkh({ pubkey: Buffer.from(keyPair.publicKey), network: network }),
network: network,
});
if (!wallet.address) {
throw new Error('Failed to generate wallet');
}
return wallet.address;
}
static getLegacyAddress(keyPair, network = networks.bitcoin) {
const wallet = payments.p2pkh({ pubkey: Buffer.from(keyPair.publicKey), network: network });
if (!wallet.address) {
throw new Error('Failed to generate wallet');
}
return wallet.address;
}
static getP2PKH(publicKey, network = networks.bitcoin) {
const wallet = payments.p2pkh({ pubkey: Buffer.from(publicKey), network: network });
if (!wallet.address) {
throw new Error('Failed to generate wallet');
}
return wallet.address;
}
static getP2PKAddress(keyPair, network = networks.bitcoin) {
const wallet = payments.p2pk({ pubkey: Buffer.from(keyPair.publicKey), network: network });
if (!wallet.output) {
throw new Error('Failed to generate wallet');
}
return '0x' + wallet.output.toString('hex');
}
static generateRandomKeyPair(network = networks.bitcoin) {
return this.ECPair.makeRandom({
network: network,
});
}
static fromSeed(seed, network = networks.bitcoin) {
return this.BIP32.fromSeed(seed, network);
}
static getTaprootAddress(keyPair, network = networks.bitcoin) {
const { address } = payments.p2tr({
internalPubkey: toXOnly(Buffer.from(keyPair.publicKey)),
network: network,
});
if (!address) {
throw new Error(`Failed to generate sender address for transaction`);
}
return address;
}
static getTaprootAddressFromAddress(inAddr, network = networks.bitcoin) {
const { address } = payments.p2tr({
address: inAddr,
network: network,
});
if (!address) {
throw new Error(`Failed to generate sender address for transaction`);
}
return address;
}
static fromSeedKeyPair(seed, network = networks.bitcoin) {
const fromSeed = this.BIP32.fromSeed(seed, network);
const privKey = fromSeed.privateKey;
if (!privKey)
throw new Error('Failed to generate key pair');
return this.ECPair.fromPrivateKey(Buffer.from(privKey), { network });
}
}
EcKeyPair.BIP32 = BIP32factory(ecc);
EcKeyPair.ECPair = ECPairFactory(ecc);