UNPKG

@bigmi/core

Version:

TypeScript library for Bitcoin apps.

150 lines (123 loc) 3.12 kB
import { sha256 } from '@noble/hashes/sha256' import { type Decoded, bech32, bech32m } from 'bech32' import bs58 from 'bs58' import type { Address } from '../types/address.ts' export enum UTXONetwork { Mainnet = 'mainnet', Testnet = 'testnet', Regtest = 'regtest', } export enum UTXOAddressType { p2pkh = 'p2pkh', p2sh = 'p2sh', p2wpkh = 'p2wpkh', p2wsh = 'p2wsh', p2tr = 'p2tr', } export type UTXOAddress = { bech32: boolean network: UTXONetwork address: Address type: UTXOAddressType } const addressTypes: { [key: number]: { type: UTXOAddressType; network: UTXONetwork } } = { 0: { type: UTXOAddressType.p2pkh, network: UTXONetwork.Mainnet, }, 111: { type: UTXOAddressType.p2pkh, network: UTXONetwork.Testnet, }, 5: { type: UTXOAddressType.p2sh, network: UTXONetwork.Mainnet, }, 196: { type: UTXOAddressType.p2sh, network: UTXONetwork.Testnet, }, } const parseBech32 = (address: Address): UTXOAddress => { let decoded: Decoded try { if ( address.startsWith('bc1p') || address.startsWith('tb1p') || address.startsWith('bcrt1p') ) { decoded = bech32m.decode(address) } else { decoded = bech32.decode(address) } } catch (_error) { throw new Error('Invalid address') } const mapPrefixToNetwork: { [key: string]: UTXONetwork } = { bc: UTXONetwork.Mainnet, tb: UTXONetwork.Testnet, bcrt: UTXONetwork.Regtest, } const network: UTXONetwork = mapPrefixToNetwork[decoded.prefix] if (network === undefined) { throw new Error('Invalid address') } const witnessVersion = decoded.words[0] if (witnessVersion < 0 || witnessVersion > 16) { throw new Error('Invalid address') } const data = bech32.fromWords(decoded.words.slice(1)) let type: UTXOAddressType if (data.length === 20) { type = UTXOAddressType.p2wpkh } else if (witnessVersion === 1) { type = UTXOAddressType.p2tr } else { type = UTXOAddressType.p2wsh } return { bech32: true, network, address, type, } } export const getUTXOAddress = (address: Address): UTXOAddress => { let decoded: Uint8Array const prefix = address.substring(0, 2).toLowerCase() if (prefix === 'bc' || prefix === 'tb') { return parseBech32(address) } try { decoded = bs58.decode(address) } catch (_error) { throw new Error('Invalid address') } const { length } = decoded if (length !== 25) { throw new Error('Invalid address') } const version = decoded[0] const checksum = decoded.slice(length - 4, length) const body = decoded.slice(0, length - 4) const expectedChecksum = sha256(sha256(body)).slice(0, 4) if ( checksum.some( (value: number, index: number) => value !== expectedChecksum[index] ) ) { throw new Error('Invalid address') } const validVersions = Object.keys(addressTypes).map(Number) if (!validVersions.includes(version)) { throw new Error('Invalid address') } const addressType = addressTypes[version] return { ...addressType, address, bech32: false, } }