UNPKG

lotus-sdk

Version:

Central repository for several classes of tools for integrating with, and building for, the Lotusia ecosystem

282 lines (281 loc) 10.3 kB
import { Preconditions } from './util/preconditions.js'; import { Base58 } from './encoding/base58.js'; import { BufferWriter } from './encoding/bufferwriter.js'; import { Networks } from './networks.js'; import { Hash } from './crypto/hash.js'; import { JSUtil } from './util/js.js'; import { BufferUtil } from './util/buffer.js'; const TOKEN_NAME = 'lotus'; const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; export class XAddress { static PayToPublicKeyHash = 'pubkeyhash'; static PayToScriptHash = 'scripthash'; static PayToTaproot = 'taproot'; prefix; hashBuffer; network; type; constructor(data, network, type, prefix = TOKEN_NAME) { if (data instanceof XAddress) { return data; } Preconditions.checkArgument(data !== undefined, 'data', 'First argument is required, please include address data.', 'guide/address.html'); const networkExplicitlyProvided = network !== undefined; network ||= Networks.defaultNetwork.name; if (network && !Networks.get(network)) { throw new TypeError('Second argument must be "livenet", "testnet", or "regtest".'); } if (type && type !== XAddress.PayToPublicKeyHash && type !== XAddress.PayToScriptHash && type !== XAddress.PayToTaproot) { throw new TypeError('Third argument must be "pubkeyhash", "scripthash", or "taproot".'); } const info = this._classifyArguments(data, network, type, prefix, networkExplicitlyProvided); info.network = info.network || Networks.get(network) || Networks.defaultNetwork; info.type = info.type || type || XAddress.PayToPublicKeyHash; JSUtil.defineImmutable(this, { prefix: info.prefix, hashBuffer: info.hashBuffer, network: info.network, type: info.type, }); } _classifyArguments(data, network, type, prefix, networkExplicitlyProvided = true) { if (typeof data === 'string') { return XAddress._transformString(data, networkExplicitlyProvided ? network : undefined, type); } else if (Buffer.isBuffer(data) || data instanceof Uint8Array) { return XAddress._transformBuffer(data, network, type, prefix); } else if (typeof data === 'object' && data !== null) { return XAddress._transformObject(data); } else { throw new TypeError('First argument is an unrecognized data format.'); } } static _transformObject(data) { Preconditions.checkArgument(data.hashBuffer !== undefined, 'data', 'Must provide a `hash` or `hashBuffer` property'); Preconditions.checkArgument(data.type !== undefined, 'data', 'Must provide a `type` property'); return { hashBuffer: data.hashBuffer || Buffer.from(data.hashBuffer.toString(), 'hex'), network: Networks.get(data.network) || Networks.defaultNetwork, type: data.type, prefix: data.prefix, }; } static _classifyFromVersion(buffer) { const version = {}; const pubkeyhashNetwork = Networks.get(buffer[0], 'pubkeyhash'); const scripthashNetwork = Networks.get(buffer[0], 'scripthash'); if (pubkeyhashNetwork) { version.network = pubkeyhashNetwork; version.type = XAddress.PayToPublicKeyHash; } else if (scripthashNetwork) { version.network = scripthashNetwork; version.type = XAddress.PayToScriptHash; } return version; } static _transformString(data, network, type) { if (typeof data !== 'string') { throw new TypeError('data parameter supplied is not a string.'); } data = data.trim(); const networkObj = Networks.get(network); if (network && !networkObj) { throw new TypeError('Unknown network'); } const info = XAddress._decode(data); if (!info.network || (networkObj && networkObj.name !== info.network.name)) { throw new TypeError('Address has mismatched network type.'); } return info; } static _transformBuffer(buffer, network, type, prefix = TOKEN_NAME) { const info = {}; if (!Buffer.isBuffer(buffer) && !(buffer instanceof Uint8Array)) { throw new TypeError('XAddress supplied is not a buffer.'); } const networkObj = Networks.get(network); if (network && !networkObj) { throw new TypeError('Unknown network'); } if (type === undefined) { throw new TypeError('Unknown type.'); } info.prefix = prefix; info.hashBuffer = Buffer.from(buffer); info.network = networkObj || Networks.defaultNetwork; info.type = type; return info; } static fromString(str, network, type) { const info = XAddress._transformString(str, network, type); return new XAddress(info.hashBuffer, info.network, info.type); } static fromObject(obj) { Preconditions.checkState(JSUtil.isHexa(obj.hash), 'Unexpected hash property, "' + obj.hash + '", expected to be hex.'); const hashBuffer = Buffer.from(obj.hash, 'hex'); return new XAddress(hashBuffer, obj.network, obj.type, obj.prefix); } static getValidationError(data, network, type) { try { new XAddress(data, network, type); return null; } catch (e) { return e; } } static isValid(data, network, type) { return !XAddress.getValidationError(data, network, type); } static _decode(address) { return decode(address); } toBuffer() { const version = Buffer.from([ this.network[this.type], ]); const buf = Buffer.concat([version, this.hashBuffer]); return buf; } toObject() { return { prefix: this.prefix, hash: this.hashBuffer.toString('hex'), type: this.type, network: this.network.toString(), }; } toJSON() { return this.toObject(); } toXAddress() { const prefix = this.prefix; const networkChar = getNetworkChar(this.network); const networkByte = Buffer.from(networkChar); const typeByte = Buffer.from([getTypeByte(this.type)]); const payload = this.hashBuffer; const checksum = createChecksum(prefix, networkByte, typeByte, payload); const encodedPayload = encodePayload(typeByte, payload, checksum); return prefix + networkChar + encodedPayload; } toString() { return this.toXAddress(); } isPayToPublicKeyHash() { return this.type === XAddress.PayToPublicKeyHash; } isPayToScriptHash() { return this.type === XAddress.PayToScriptHash; } isPayToTaproot() { return this.type === XAddress.PayToTaproot; } inspect() { return '<XAddress: ' + this.toString() + ', type: ' + this.type + '>'; } } function createChecksum(prefix, networkByte, typeByte, payload) { const data = BufferUtil.concat([ Buffer.from(prefix), networkByte, typeByte, payload, ]); return Hash.sha256(data).subarray(0, 4); } function createChecksumLegacy(prefix, networkByte, typeByte, payload) { const bw = new BufferWriter(); bw.writeVarintNum(prefix.length); bw.write(Buffer.from(prefix)); bw.writeUInt8(networkByte[0]); bw.writeUInt8(typeByte[0]); bw.writeVarintNum(payload.length); bw.write(payload); const buf = bw.concat(); return Hash.sha256(buf).subarray(0, 4); } function getType(typeByte) { switch (typeByte) { case 0: return 'pubkeyhash'; case 1: return 'scripthash'; case 2: return 'taproot'; } return 'pubkeyhash'; } function getTypeByte(type) { switch (type) { case 'pubkeyhash': case 'scripthash': return 0; case 'taproot': return 2; } return 0; } function getNetworkFromChar(networkChar) { switch (networkChar) { case '_': return Networks.get('livenet'); case 'T': return Networks.get('testnet'); case 'R': return Networks.get('regtest'); default: throw new TypeError('Unknown network type: ' + networkChar); } } function getNetworkChar(network) { if (network.name === 'livenet') { return '_'; } else if (network.name === 'testnet') { return 'T'; } else if (network.name === 'regtest') { return 'R'; } else { throw new TypeError('Unknown network: ' + network.name); } } function encodePayload(typeByte, payload, checksum) { const bw = new BufferWriter(); bw.writeUInt8(typeByte[0]); bw.write(payload); bw.write(checksum); const buf = bw.concat(); return Base58.encode(buf); } function decode(address) { const match = /[A-Z]|_/.exec(address); const splitLocation = match ? match.index : 0; const prefix = address.substring(0, splitLocation); const networkChar = address.substring(splitLocation, splitLocation + 1); const networkByte = Buffer.from(networkChar); const encodedPayload = address.substring(splitLocation + 1); const decodedBytes = Base58.decode(encodedPayload); const typeByte = decodedBytes.subarray(0, 1); const payload = decodedBytes.subarray(1, decodedBytes.length - 4); const decodedChecksum = decodedBytes.subarray(decodedBytes.length - 4); const checksum = createChecksum(prefix, networkByte, typeByte, payload); const legacyChecksum = createChecksumLegacy(prefix, networkByte, typeByte, payload); Preconditions.checkArgument(checksum.toString('hex') === decodedChecksum.toString('hex') || legacyChecksum.toString('hex') === decodedChecksum.toString('hex'), 'checksum', 'Invalid checksum: ' + address); const info = {}; info.hashBuffer = payload; info.network = getNetworkFromChar(networkChar); info.type = getType(typeByte[0]); info.prefix = prefix; return info; }