Source: Network.js

const Ripemd160 = require('ripemd160');

/**
 * Represents a network.
 */
class Network {
	/**
	 * Creates a new network with the specified name and identifier byte.
	 * @param {string} name Network name.
	 * @param {number} identifier Network identifier byte.
	 * @param {function} addressHasher Gets the primary hasher to use in the public key to address conversion.
	 * @param {function} createAddress Creates an encoded address from an address without checksum and checksum bytes.
	 */
	constructor(name, identifier, addressHasher, createAddress) {
		this.name = name;
		this.identifier = identifier;
		this.addressHasher = addressHasher;
		this.createAddress = createAddress;
	}

	/**
	 * Converts a public key to an address.
	 * @param {PublicKey} publicKey Public key to convert.
	 * @returns {Address} Address corresponding to the public key input.
	 */
	publicKeyToAddress(publicKey) {
		const partOneHashBuilder = this.addressHasher();
		partOneHashBuilder.update(publicKey.bytes);
		const partOneHash = new Uint8Array(partOneHashBuilder.arrayBuffer());

		const partTwoHash = new Ripemd160().update(Buffer.from(partOneHash)).digest();

		const version = new Uint8Array([this.identifier, ...partTwoHash]);

		const partThreeHashBuilder = this.addressHasher();
		partThreeHashBuilder.update(version);
		const checksum = new Uint8Array(partThreeHashBuilder.arrayBuffer()).subarray(0, 4);

		return this.createAddress(version, checksum);
	}

	/**
	 * Checks if an address is valid and belongs to this network.
	 * @param {Address} address Address to check
	 * @returns {boolean} True if address is valid and belongs to this network.
	 */
	isValidAddress(address) {
		if (address.bytes[0] !== this.identifier)
			return false;

		const hashBuilder = this.addressHasher();
		hashBuilder.update(address.bytes.subarray(0, 1 + 20));

		const checkSumFromAddress = address.bytes.subarray(1 + 20);
		const calculatedChecksum = new Uint8Array(hashBuilder.arrayBuffer()).subarray(0, checkSumFromAddress.length);

		for (let i = 0; i < checkSumFromAddress.length; ++i) {
			if (checkSumFromAddress[i] !== calculatedChecksum[i])
				return false;
		}

		return true;
	}

	/**
	 * Returns string representation of this object.
	 * @returns {string} String representation of this object
	 */
	toString() {
		return this.name;
	}
}

/**
 * Provides utility functions for finding a network.
 */
const NetworkLocator = {
	/**
	 * Finds a network with a specified name within a list of networks.
	 * @param {array<Network>} networks List of networks to search.
	 * @param {array<string>|string} singleOrMultipleNames Names for which to search.
	 * @returns {Network} First network with a name in the supplied list.
	 */
	findByName: (networks, singleOrMultipleNames) => {
		const names = Array.isArray(singleOrMultipleNames) ? singleOrMultipleNames : [singleOrMultipleNames];
		const matchingNetwork = networks.find(network => names.some(name => name === network.name));
		if (undefined === matchingNetwork)
			throw RangeError(`no network found with name '${names.join(', ')}'`);

		return matchingNetwork;
	},

	/**
	 * Finds a network with a specified identifier within a list of networks.
	 * @param {array<Network>} networks List of networks to search.
	 * @param {array<number>|number} singleOrMultipleIdentifiers Identifiers for which to search.
	 * @returns {Network} First network with an identifier in the supplied list.
	 */
	findByIdentifier: (networks, singleOrMultipleIdentifiers) => {
		const identifiers = Array.isArray(singleOrMultipleIdentifiers) ? singleOrMultipleIdentifiers : [singleOrMultipleIdentifiers];
		const matchingNetwork = networks.find(network => identifiers.some(identifier => identifier === network.identifier));
		if (undefined === matchingNetwork)
			throw RangeError(`no network found with identifier '${identifiers.join(', ')}'`);

		return matchingNetwork;
	}
};

module.exports = { Network, NetworkLocator };