UNPKG

@btc-vision/transaction

Version:

OPNet transaction library allows you to create and sign transactions for the OPNet network.

220 lines (219 loc) 8.89 kB
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'); } const Point = secp256k1.Point; const CURVE_N = Point.Fn.ORDER; 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); (() => { Point.BASE.precompute(8); })();