Home Reference Source

packages/oo7-substrate/src/ss58.js

const bs58 = require('bs58')
const { blake2b } = require('blakejs')
const { toLE, leToNumber } = require('./utils')
const { AccountIndex, AccountId } = require('./types')

let defaultType = 42
const KNOWN_TYPES = [0, 1, 42, 43, 68, 69]

function setNetworkDefault(type) {
	defaultType = type
}

function ss58Encode(a, type = defaultType, checksumLength = null, length = null, accountId) {
	let payload
	if (KNOWN_TYPES.indexOf(type) === -1) {
		throw new Error('Unknown ss58 address type', type)
	}
	if (typeof a === 'number' || a instanceof AccountIndex) {
		let minLength = (a < (1 << 8) ? 1 : a < (1 << 16) ? 2 : a < (1 << 32) ? 4 : 8)
		length = length ? length : minLength
		if ([1, 2, 4, 8].indexOf(length) === -1) {
			throw new Error('Invalid length')
		}
		length = Math.max(minLength, length)
		if (checksumLength && typeof checksumLength !== 'number') {
			throw new Error('Invalid checksum length')
		}
		switch (length) {
			case 1: { checksumLength = 1; break; }
			case 2: { checksumLength = ([1, 2].indexOf(checksumLength) + 1) || 1; break; }
			case 4: { checksumLength = ([1, 2, 3, 4].indexOf(checksumLength) + 1) || 1; break; }
			case 8: { checksumLength = ([1, 2, 3, 4, 5, 6, 7, 8].indexOf(checksumLength) + 1) || 1; break; }
		}
		payload = toLE(a, length)
	} else if ((a instanceof AccountId || a instanceof Uint8Array) && a.length === 32) {
		checksumLength = 2
		payload = a
		accountId = a
	} else {
		throw new Error('Unknown item to encode as ss58. Passing back.', a)
	}
	let hash = blake2b((type & 1) ? accountId : new Uint8Array([type, ...payload]))
	let complete = new Uint8Array([type, ...payload, ...hash.slice(0, checksumLength)])
	return bs58.encode(Buffer.from(complete))
}

/// `lookupIndex` must be synchronous. If you can do that, then throw, catch outside the
/// invocation and then retry once you have the result to hand.
function ss58Decode(ss58, lookupIndex) {
	let a
	try {
		a = bs58.decode(ss58)
	}
	catch (e) {
		return null
	}

	let type = a[0]
	if (KNOWN_TYPES.indexOf(type) === -1) {
		return null
	}

	if (a.length < 3) {
		return null
		//throw new Error('Invalid length of payload for address', a.length)
	}
	let length = a.length <= 3
		? 1
		: a.length <= 5
		? 2
		: a.length <= 9
		? 4
		: a.length <= 17
		? 8
		: 32
	let checksumLength = a.length - 1 - length

	let payload = a.slice(1, 1 + length)
	let checksum = a.slice(1 + a.length)

	let accountId
	if (length === 32) {
		accountId = payload
	}

	let result = length < 32
		? new AccountIndex(leToNumber(payload))
		: new AccountId(payload)

	if (a[0] % 1 && !accountId && !lookupIndex) {
		return null
	}
	let hash = blake2b(a[0] % 1 ? (accountId || lookupIndex(result)) : a.slice(0, 1 + length))

	for (var i = 0; i < checksumLength; ++i) {
		if (hash[i] !== a[1 + length + i]) {
			// invalid checksum
			return null
		}
	}

	return result
}

module.exports = { ss58Decode, ss58Encode, setNetworkDefault }