UNPKG

@hdwallet/core

Version:

A complete Hierarchical Deterministic (HD) Wallet generator for 200+ cryptocurrencies, built with TypeScript.

122 lines 6.01 kB
// SPDX-License-Identifier: MIT import { Monero } from '../cryptocurrencies'; import { decodeMonero, encodeMonero } from '../libs/base58'; import { keccak256 } from '../crypto'; import { SLIP10Ed25519MoneroPublicKey, validateAndGetPublicKey } from '../eccs'; import { bytesToString, concatBytes, ensureTypeMatch, getBytes, integerToBytes, equalBytes } from '../utils'; import { Network } from '../cryptocurrencies/cryptocurrency'; import { AddressError, BaseError } from '../exceptions'; import { Address } from './address'; export class MoneroAddress extends Address { static checksumLength = Monero.PARAMS.CHECKSUM_LENGTH; static paymentIDLength = Monero.PARAMS.PAYMENT_ID_LENGTH; static network = Monero.DEFAULT_NETWORK; static addressType = Monero.DEFAULT_ADDRESS_TYPE; static networks = { mainnet: { addressTypes: { 'standard': Monero.NETWORKS.MAINNET.STANDARD, 'integrated': Monero.NETWORKS.MAINNET.INTEGRATED, 'sub-address': Monero.NETWORKS.MAINNET.SUB_ADDRESS } }, stagenet: { addressTypes: { 'standard': Monero.NETWORKS.STAGENET.STANDARD, 'integrated': Monero.NETWORKS.STAGENET.INTEGRATED, 'sub-address': Monero.NETWORKS.STAGENET.SUB_ADDRESS } }, testnet: { addressTypes: { 'standard': Monero.NETWORKS.TESTNET.STANDARD, 'integrated': Monero.NETWORKS.TESTNET.INTEGRATED, 'sub-address': Monero.NETWORKS.TESTNET.SUB_ADDRESS } } }; static getName() { return 'Monero'; } static computeChecksum(data) { return keccak256(data).subarray(0, this.checksumLength); } static encode(publicKeys, options = { network: this.network, addressType: this.addressType }) { const { spendPublicKey, viewPublicKey } = publicKeys; const addressType = options.addressType ?? this.addressType; const paymentID = options.paymentID ? getBytes(options.paymentID) : undefined; const spend = validateAndGetPublicKey(spendPublicKey, SLIP10Ed25519MoneroPublicKey); const view = validateAndGetPublicKey(viewPublicKey, SLIP10Ed25519MoneroPublicKey); if (paymentID && paymentID.length !== this.paymentIDLength) { throw new BaseError('Invalid payment ID length', { expected: this.paymentIDLength, got: paymentID.length }); } const network = options.network ?? this.network; const resolvedNetwork = ensureTypeMatch(network, Network, { otherTypes: ['string'] }); const networkName = resolvedNetwork.isValid ? resolvedNetwork.value.NAME : network; const version = integerToBytes(this.networks[networkName].addressTypes[addressType]); const payload = concatBytes(version, spend.getRawCompressed(), view.getRawCompressed(), getBytes(paymentID ?? new Uint8Array(0))); const checksum = this.computeChecksum(getBytes(payload)); return encodeMonero(getBytes(concatBytes(payload, checksum))); } static decode(address, options = { network: this.network, addressType: this.addressType }) { const addressType = options.addressType ?? this.addressType; const paymentID = getBytes(options.paymentID ?? new Uint8Array(0)); const decoded = decodeMonero(address); const checksum = decoded.subarray(-this.checksumLength); const payloadWithPrefix = decoded.subarray(0, -this.checksumLength); const computedChecksum = this.computeChecksum(payloadWithPrefix); if (!equalBytes(checksum, computedChecksum)) { throw new AddressError('Invalid checksum', { expected: bytesToString(checksum), got: bytesToString(computedChecksum) }); } const network = options.network ?? this.network; const resolvedNetwork = ensureTypeMatch(network, Network, { otherTypes: ['string'] }); const networkName = resolvedNetwork.isValid ? resolvedNetwork.value.NAME : network; const version = integerToBytes(this.networks[networkName].addressTypes[addressType]); const versionGot = payloadWithPrefix.subarray(0, version.length); if (!equalBytes(versionGot, version)) { throw new AddressError('Invalid version', { expected: version, got: versionGot }); } const payload = payloadWithPrefix.subarray(version.length); const pubkeyLen = SLIP10Ed25519MoneroPublicKey.getCompressedLength(); let spend; let view; if (payload.length === 2 * pubkeyLen) { spend = payload.subarray(0, pubkeyLen); view = payload.subarray(pubkeyLen); } else if (payload.length === 2 * pubkeyLen + this.paymentIDLength) { if (!paymentID || paymentID.length !== this.paymentIDLength) { throw new BaseError('Missing or invalid payment ID'); } const paymentIDGot = payload.subarray(-this.paymentIDLength); if (!equalBytes(paymentID, paymentIDGot)) { throw new BaseError('Payment ID mismatch', { expected: bytesToString(paymentIDGot), got: bytesToString(paymentID) }); } spend = payload.subarray(0, pubkeyLen); view = payload.subarray(pubkeyLen, pubkeyLen * 2); } else { throw new AddressError('Invalid payload length', { expected: 2 * pubkeyLen, got: payload.length }); } if (!SLIP10Ed25519MoneroPublicKey.isValidBytes(spend)) { throw new BaseError('Invalid spend public key'); } if (!SLIP10Ed25519MoneroPublicKey.isValidBytes(view)) { throw new BaseError('Invalid view public key'); } return [bytesToString(spend), bytesToString(view)]; } } //# sourceMappingURL=monero.js.map