@pharosnames/address-encoder
Version:
Encodes and decodes address formats for various cryptocurrencies with Pharos network support
525 lines (448 loc) • 13.6 kB
text/typescript
// Ported from https://www.npmjs.com/package/cbor-redux as the release 0.4.0 was missing on npm, which has critical changes.
/** @hidden */
const POW_2_24 = 5.960464477539063e-8;
/** @hidden */
const POW_2_32 = 4294967296;
/** @hidden */
const POW_2_53 = 9007199254740992;
/** @hidden */
const DECODE_CHUNK_SIZE = 8192;
/** @hidden */
function objectIs(x: any, y: any) {
if (typeof Object.is === "function") {
return Object.is(x, y);
}
// SameValue algorithm
// Steps 1-5, 7-10
if (x === y) {
// Steps 6.b-6.e: +0 != -0
return x !== 0 || 1 / x === 1 / y;
}
// Step 6.a: NaN == NaN
return x !== x && y !== y;
}
/** Convenience class for structuring a tagged value. */
export class TaggedValue<T> {
public value: T;
public tag: number;
constructor(value: T, tag: number) {
this.value = value;
this.tag = tag;
}
}
/** Convenience class for structuring a simple value. */
export class SimpleValue<T> {
public value: T;
constructor(value: T) {
this.value = value;
}
}
export function cborDecode<T = any>(data: ArrayBuffer | SharedArrayBuffer): T {
const dataView = new DataView(data);
const ta = new Uint8Array(data);
let offset = 0;
let tagValueFunction = (value: any, tag: number) => {
return new TaggedValue(value, tag);
};
let simpleValueFunction = (value: any) => {
return new SimpleValue(value);
};
function commitRead(length: number, value: any) {
offset += length;
return value;
}
function readArrayBuffer(length: number) {
return commitRead(length, new Uint8Array(data, offset, length));
}
function readFloat16() {
const tempArrayBuffer = new ArrayBuffer(4);
const tempDataView = new DataView(tempArrayBuffer);
const value = readUint16();
const sign = value & 0x8000;
let exponent = value & 0x7c00;
const fraction = value & 0x03ff;
if (exponent === 0x7c00) {
exponent = 0xff << 10;
} else if (exponent !== 0) {
exponent += (127 - 15) << 10;
} else if (fraction !== 0) {
return (sign ? -1 : 1) * fraction * POW_2_24;
}
tempDataView.setUint32(
0,
(sign << 16) | (exponent << 13) | (fraction << 13)
);
return tempDataView.getFloat32(0);
}
function readFloat32(): number {
return commitRead(4, dataView.getFloat32(offset));
}
function readFloat64(): number {
return commitRead(8, dataView.getFloat64(offset));
}
function readUint8(): number {
return commitRead(1, ta[offset]);
}
function readUint16(): number {
return commitRead(2, dataView.getUint16(offset));
}
function readUint32(): number {
return commitRead(4, dataView.getUint32(offset));
}
function readUint64(): number {
return readUint32() * POW_2_32 + readUint32();
}
function readBreak(): boolean {
if (ta[offset] !== 0xff) {
return false;
}
offset += 1;
return true;
}
function readLength(additionalInformation: number): number {
if (additionalInformation < 24) {
return additionalInformation;
} else if (additionalInformation === 24) {
return readUint8();
} else if (additionalInformation === 25) {
return readUint16();
} else if (additionalInformation === 26) {
return readUint32();
} else if (additionalInformation === 27) {
return readUint64();
} else if (additionalInformation === 31) {
return -1;
}
throw new Error("Invalid length encoding");
}
function readIndefiniteStringLength(majorType: number): number {
const initialByte = readUint8();
if (initialByte === 0xff) {
return -1;
}
const length = readLength(initialByte & 0x1f);
if (length < 0 || initialByte >> 5 !== majorType) {
throw new Error("Invalid indefinite length element");
}
return length;
}
function appendUtf16Data(utf16data: number[], length: number) {
for (let i = 0; i < length; ++i) {
let value = readUint8();
if (value & 0x80) {
if (value < 0xe0) {
value = ((value & 0x1f) << 6) | (readUint8() & 0x3f);
length -= 1;
} else if (value < 0xf0) {
value =
((value & 0x0f) << 12) |
((readUint8() & 0x3f) << 6) |
(readUint8() & 0x3f);
length -= 2;
} else {
value =
((value & 0x0f) << 18) |
((readUint8() & 0x3f) << 12) |
((readUint8() & 0x3f) << 6) |
(readUint8() & 0x3f);
length -= 3;
}
}
if (value < 0x10000) {
utf16data.push(value);
} else {
value -= 0x10000;
utf16data.push(0xd800 | (value >> 10));
utf16data.push(0xdc00 | (value & 0x3ff));
}
}
}
function decodeItem(): any {
const initialByte = readUint8();
const majorType = initialByte >> 5;
const additionalInformation = initialByte & 0x1f;
let i;
let length;
if (majorType === 7) {
switch (additionalInformation) {
case 25:
return readFloat16();
case 26:
return readFloat32();
case 27:
return readFloat64();
}
}
length = readLength(additionalInformation);
if (length < 0 && (majorType < 2 || 6 < majorType)) {
throw new Error("Invalid length");
}
switch (majorType) {
case 0:
return length;
case 1:
return -1 - length;
case 2:
if (length < 0) {
const elements = [];
let fullArrayLength = 0;
length = readIndefiniteStringLength(majorType);
while (length >= 0) {
fullArrayLength += length;
elements.push(readArrayBuffer(length));
}
const fullArray = new Uint8Array(fullArrayLength);
let fullArrayOffset = 0;
for (i = 0; i < elements.length; ++i) {
fullArray.set(elements[i], fullArrayOffset);
fullArrayOffset += elements[i].length;
}
return fullArray;
}
return readArrayBuffer(length);
case 3:
const utf16data: number[] = [];
if (length < 0) {
length = readIndefiniteStringLength(majorType);
while (length >= 0) {
appendUtf16Data(utf16data, length);
}
} else {
appendUtf16Data(utf16data, length);
}
let str = "";
for (i = 0; i < utf16data.length; i += DECODE_CHUNK_SIZE) {
str += String.fromCharCode.apply(
null,
utf16data.slice(i, i + DECODE_CHUNK_SIZE)
);
}
return str;
case 4:
let retArray;
if (length < 0) {
retArray = [];
while (!readBreak()) {
retArray.push(decodeItem());
}
} else {
retArray = new Array(length);
for (i = 0; i < length; ++i) {
retArray[i] = decodeItem();
}
}
return retArray;
case 5:
const retObject: any = {};
for (i = 0; i < length || (length < 0 && !readBreak()); ++i) {
const key = decodeItem();
retObject[key] = decodeItem();
}
return retObject;
case 6:
return tagValueFunction(decodeItem(), length);
case 7:
switch (length) {
case 20:
return false;
case 21:
return true;
case 22:
return null;
case 23:
return undefined;
default:
return simpleValueFunction(length);
}
}
}
const ret = decodeItem();
if (offset !== data.byteLength) {
throw new Error("Remaining bytes");
}
return ret;
}
export function cborEncode<T = any>(value: T): ArrayBuffer {
let data = new ArrayBuffer(256);
let dataView = new DataView(data);
let byteView = new Uint8Array(data);
let lastLength: number;
let offset = 0;
function prepareWrite(length: number): DataView {
let newByteLength = data.byteLength;
const requiredLength = offset + length;
while (newByteLength < requiredLength) {
newByteLength <<= 1;
}
if (newByteLength !== data.byteLength) {
const oldDataView = dataView;
data = new ArrayBuffer(newByteLength);
dataView = new DataView(data);
byteView = new Uint8Array(data);
const uint32count = (offset + 3) >> 2;
for (let i = 0; i < uint32count; ++i) {
dataView.setUint32(i << 2, oldDataView.getUint32(i << 2));
}
}
lastLength = length;
return dataView;
}
function commitWrite(..._args: any[]) {
offset += lastLength;
}
function writeFloat64(val: number) {
commitWrite(prepareWrite(8).setFloat64(offset, val));
}
function writeUint8(val: number) {
commitWrite(prepareWrite(1).setUint8(offset, val));
}
function writeUint8Array(val: number[] | Uint8Array) {
prepareWrite(val.length);
byteView.set(val, offset);
commitWrite();
}
function writeUint16(val: number) {
commitWrite(prepareWrite(2).setUint16(offset, val));
}
function writeUint32(val: number) {
commitWrite(prepareWrite(4).setUint32(offset, val));
}
function writeUint64(val: number) {
const low = val % POW_2_32;
const high = (val - low) / POW_2_32;
const view = prepareWrite(8);
view.setUint32(offset, high);
view.setUint32(offset + 4, low);
commitWrite();
}
function writeVarUint(val: number, mod: number) {
if (val <= 0xff) {
if (val < 24) {
writeUint8(val | mod);
} else {
writeUint8(0x18 | mod);
writeUint8(val);
}
} else if (val <= 0xffff) {
writeUint8(0x19 | mod);
writeUint16(val);
} else if (val <= 0xffffffff) {
writeUint8(0x1a | mod);
writeUint32(val);
} else {
writeUint8(0x1b | mod);
writeUint64(val);
}
}
function writeTypeAndLength(type: number, length: number) {
if (length < 24) {
writeUint8((type << 5) | length);
} else if (length < 0x100) {
writeUint8((type << 5) | 24);
writeUint8(length);
} else if (length < 0x10000) {
writeUint8((type << 5) | 25);
writeUint16(length);
} else if (length < 0x100000000) {
writeUint8((type << 5) | 26);
writeUint32(length);
} else {
writeUint8((type << 5) | 27);
writeUint64(length);
}
}
function encodeItem(val: any): void {
let i;
if (val === false) {
return writeUint8(0xf4);
} else if (val === true) {
return writeUint8(0xf5);
} else if (val === null) {
return writeUint8(0xf6);
} else if (val === undefined) {
return writeUint8(0xf7);
} else if (objectIs(val, -0)) {
return writeUint8Array([0xf9, 0x80, 0x00]);
}
switch (typeof val) {
case "number":
if (Math.floor(val) === val) {
if (0 <= val && val <= POW_2_53) {
return writeTypeAndLength(0, val);
} else if (-POW_2_53 <= val && val < 0) {
return writeTypeAndLength(1, -(val + 1));
}
}
writeUint8(0xfb);
return writeFloat64(val);
case "string":
const utf8data = [];
for (i = 0; i < val.length; i++) {
let charCode = val.charCodeAt(i);
if (charCode < 0x80) {
utf8data.push(charCode);
} else if (charCode < 0x800) {
utf8data.push(0xc0 | (charCode >> 6));
utf8data.push(0x80 | (charCode & 0x3f));
} else {
charCode = (charCode & 0x3ff) << 10;
charCode |= val.charCodeAt(++i) & 0x3ff;
charCode += 0x10000;
utf8data.push(0xf0 | (charCode >> 18));
utf8data.push(0x80 | ((charCode >> 12) & 0x3f));
utf8data.push(0x80 | ((charCode >> 6) & 0x3f));
utf8data.push(0x80 | (charCode & 0x3f));
}
}
writeTypeAndLength(3, utf8data.length);
return writeUint8Array(utf8data);
default:
let length;
let converted;
if (Array.isArray(val)) {
length = val.length;
writeTypeAndLength(4, length);
for (i = 0; i < length; i++) {
encodeItem(val[i]);
}
} else if (val instanceof Uint8Array) {
writeTypeAndLength(2, val.length);
writeUint8Array(val);
} else if (ArrayBuffer.isView(val)) {
converted = new Uint8Array(val.buffer);
writeTypeAndLength(2, converted.length);
writeUint8Array(converted);
} else if (
val instanceof ArrayBuffer ||
(typeof SharedArrayBuffer === "function" &&
val instanceof SharedArrayBuffer)
) {
converted = new Uint8Array(val);
writeTypeAndLength(2, converted.length);
writeUint8Array(converted);
} else if (val instanceof TaggedValue) {
writeVarUint(val.tag, 0b11000000);
encodeItem(val.value);
} else {
const keys = Object.keys(val);
length = keys.length;
writeTypeAndLength(5, length);
for (i = 0; i < length; i++) {
const key = keys[i];
encodeItem(key);
encodeItem(val[key]);
}
}
}
}
encodeItem(value);
if ("slice" in data) {
return data.slice(0, offset);
}
const ret = new ArrayBuffer(offset);
const retView = new DataView(ret);
for (let i = 0; i < offset; ++i) {
retView.setUint8(i, dataView.getUint8(i));
}
return ret;
}