Compare commits

..

3 Commits

Author SHA1 Message Date
Derrick Hammer 5e6cdba3e7
*Add dist 2023-01-14 02:26:26 -05:00
Derrick Hammer 517c99fecb
*add typescript dev dep 2023-01-14 02:24:49 -05:00
Derrick Hammer b2d14a5f10
*Move test files to test folder 2023-01-14 02:24:36 -05:00
35 changed files with 1749 additions and 1 deletions

3
dist/blake2b.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
declare const BLAKE2B_HASH_SIZE = 32;
declare function blake2b(input: Uint8Array): Uint8Array;
export { BLAKE2B_HASH_SIZE, blake2b };

185
dist/blake2b.js vendored Normal file
View File

@ -0,0 +1,185 @@
// Blake2B, adapted from the reference implementation in RFC7693
// Ported to Javascript by DC - https://github.com/dcposch
// Then ported to typescript by https://github.com/DavidVorick
// 64-bit unsigned addition
// Sets v[a,a+1] += v[b,b+1]
// v should be a Uint32Array
function ADD64AA(v, a, b) {
const o0 = v[a] + v[b];
let o1 = v[a + 1] + v[b + 1];
if (o0 >= 0x100000000) {
o1++;
}
v[a] = o0;
v[a + 1] = o1;
}
// 64-bit unsigned addition
// Sets v[a,a+1] += b
// b0 is the low 32 bits of b, b1 represents the high 32 bits
function ADD64AC(v, a, b0, b1) {
let o0 = v[a] + b0;
if (b0 < 0) {
o0 += 0x100000000;
}
let o1 = v[a + 1] + b1;
if (o0 >= 0x100000000) {
o1++;
}
v[a] = o0;
v[a + 1] = o1;
}
// Little-endian byte access
function B2B_GET32(arr, i) {
return arr[i] ^ (arr[i + 1] << 8) ^ (arr[i + 2] << 16) ^ (arr[i + 3] << 24);
}
// G Mixing function
// The ROTRs are inlined for speed
function B2B_G(a, b, c, d, ix, iy, m, v) {
const x0 = m[ix];
const x1 = m[ix + 1];
const y0 = m[iy];
const y1 = m[iy + 1];
ADD64AA(v, a, b); // v[a,a+1] += v[b,b+1] ... in JS we must store a uint64 as two uint32s
ADD64AC(v, a, x0, x1); // v[a, a+1] += x ... x0 is the low 32 bits of x, x1 is the high 32 bits
// v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated to the right by 32 bits
let xor0 = v[d] ^ v[a];
let xor1 = v[d + 1] ^ v[a + 1];
v[d] = xor1;
v[d + 1] = xor0;
ADD64AA(v, c, d);
// v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 24 bits
xor0 = v[b] ^ v[c];
xor1 = v[b + 1] ^ v[c + 1];
v[b] = (xor0 >>> 24) ^ (xor1 << 8);
v[b + 1] = (xor1 >>> 24) ^ (xor0 << 8);
ADD64AA(v, a, b);
ADD64AC(v, a, y0, y1);
// v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated right by 16 bits
xor0 = v[d] ^ v[a];
xor1 = v[d + 1] ^ v[a + 1];
v[d] = (xor0 >>> 16) ^ (xor1 << 16);
v[d + 1] = (xor1 >>> 16) ^ (xor0 << 16);
ADD64AA(v, c, d);
// v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 63 bits
xor0 = v[b] ^ v[c];
xor1 = v[b + 1] ^ v[c + 1];
v[b] = (xor1 >>> 31) ^ (xor0 << 1);
v[b + 1] = (xor0 >>> 31) ^ (xor1 << 1);
}
// Initialization Vector
const BLAKE2B_IV32 = new Uint32Array([
0xf3bcc908, 0x6a09e667, 0x84caa73b, 0xbb67ae85, 0xfe94f82b, 0x3c6ef372,
0x5f1d36f1, 0xa54ff53a, 0xade682d1, 0x510e527f, 0x2b3e6c1f, 0x9b05688c,
0xfb41bd6b, 0x1f83d9ab, 0x137e2179, 0x5be0cd19,
]);
const SIGMA8 = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 14, 10, 4, 8, 9, 15, 13,
6, 1, 12, 0, 2, 11, 7, 5, 3, 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1,
9, 4, 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8, 9, 0, 5, 7, 2, 4,
10, 15, 14, 1, 11, 12, 6, 8, 3, 13, 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5,
15, 14, 1, 9, 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11, 13, 11, 7,
14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10, 6, 15, 14, 9, 11, 3, 0, 8, 12, 2,
13, 7, 1, 4, 10, 5, 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0, 0,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 14, 10, 4, 8, 9, 15, 13, 6,
1, 12, 0, 2, 11, 7, 5, 3,
];
// These are offsets into a uint64 buffer.
// Multiply them all by 2 to make them offsets into a uint32 buffer,
// because this is Javascript and we don't have uint64s
const SIGMA82 = new Uint8Array(SIGMA8.map(function (x) {
return x * 2;
}));
// Compression function. 'last' flag indicates last block.
// Note we're representing 16 uint64s as 32 uint32s
function blake2bCompress(ctx, last) {
const v = new Uint32Array(32);
const m = new Uint32Array(32);
let i = 0;
// init work variables
for (i = 0; i < 16; i++) {
v[i] = ctx.h[i];
v[i + 16] = BLAKE2B_IV32[i];
}
// low 64 bits of offset
v[24] = v[24] ^ ctx.t;
v[25] = v[25] ^ (ctx.t / 0x100000000);
// high 64 bits not supported, offset may not be higher than 2**53-1
// last block flag set ?
if (last) {
v[28] = ~v[28];
v[29] = ~v[29];
}
// get little-endian words
for (i = 0; i < 32; i++) {
m[i] = B2B_GET32(ctx.b, 4 * i);
}
// twelve rounds of mixing
for (i = 0; i < 12; i++) {
B2B_G(0, 8, 16, 24, SIGMA82[i * 16 + 0], SIGMA82[i * 16 + 1], m, v);
B2B_G(2, 10, 18, 26, SIGMA82[i * 16 + 2], SIGMA82[i * 16 + 3], m, v);
B2B_G(4, 12, 20, 28, SIGMA82[i * 16 + 4], SIGMA82[i * 16 + 5], m, v);
B2B_G(6, 14, 22, 30, SIGMA82[i * 16 + 6], SIGMA82[i * 16 + 7], m, v);
B2B_G(0, 10, 20, 30, SIGMA82[i * 16 + 8], SIGMA82[i * 16 + 9], m, v);
B2B_G(2, 12, 22, 24, SIGMA82[i * 16 + 10], SIGMA82[i * 16 + 11], m, v);
B2B_G(4, 14, 16, 26, SIGMA82[i * 16 + 12], SIGMA82[i * 16 + 13], m, v);
B2B_G(6, 8, 18, 28, SIGMA82[i * 16 + 14], SIGMA82[i * 16 + 15], m, v);
}
for (i = 0; i < 16; i++) {
ctx.h[i] = ctx.h[i] ^ v[i] ^ v[i + 16];
}
}
// Creates a BLAKE2b hashing context
// Requires an output length between 1 and 64 bytes
function blake2bInit() {
// state, 'param block'
const ctx = {
b: new Uint8Array(128),
h: new Uint32Array(16),
t: 0,
c: 0,
outlen: 32, // output length in bytes
};
// initialize hash state
for (let i = 0; i < 16; i++) {
ctx.h[i] = BLAKE2B_IV32[i];
}
ctx.h[0] ^= 0x01010000 ^ 32;
return ctx;
}
// Updates a BLAKE2b streaming hash
// Requires hash context and Uint8Array (byte array)
function blake2bUpdate(ctx, input) {
for (let i = 0; i < input.length; i++) {
if (ctx.c === 128) {
// buffer full ?
ctx.t += ctx.c; // add counters
blake2bCompress(ctx, false); // compress (not last)
ctx.c = 0; // counter to zero
}
ctx.b[ctx.c++] = input[i];
}
}
// Completes a BLAKE2b streaming hash
// Returns a Uint8Array containing the message digest
function blake2bFinal(ctx) {
ctx.t += ctx.c; // mark last block offset
while (ctx.c < 128) {
// fill up with zeros
ctx.b[ctx.c++] = 0;
}
blake2bCompress(ctx, true); // final block flag = 1
// little endian convert and store
const out = new Uint8Array(ctx.outlen);
for (let i = 0; i < ctx.outlen; i++) {
out[i] = ctx.h[i >> 2] >> (8 * (i & 3));
}
return out;
}
const BLAKE2B_HASH_SIZE = 32;
// Computes the blake2b hash of the input. Returns 32 bytes.
function blake2b(input) {
const ctx = blake2bInit();
blake2bUpdate(ctx, input);
return blake2bFinal(ctx);
}
export { BLAKE2B_HASH_SIZE, blake2b };

3
dist/checkObjProps.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
import { Err } from "./types.js";
declare function checkObjProps(obj: any, checks: [string, string][]): Err;
export { checkObjProps };

36
dist/checkObjProps.js vendored Normal file
View File

@ -0,0 +1,36 @@
// checkObjProps take an untrusted object and a list of typechecks to perform
// and will check that the object adheres to the typechecks. If a type is
// missing or has the wrong type, an error will be returned. This is intended
// to be used to check untrusted objects after they get decoded from JSON. This
// is particularly useful when receiving objects from untrusted entities over
// the network or over postMessage.
//
// Below is an example object, followed by the call that you would make to
// checkObj to verify the object.
//
// const expectedObj = {
// aNum: 35,
// aStr: "hi",
// aBig: 10n,
// };
//
// const err = checkObjProps(expectedObj, [
// ["aNum", "number"],
// ["aStr", "string"],
// ["aBig", "bigint"],
// ]);
//
// Over time, we intend to extend this function to support more types than just
// the default types supported by javascript. For example, we intend to add
// special cases for arrays, and for cryptographic objects.
function checkObjProps(obj, checks) {
for (let i = 0; i < checks.length; i++) {
const check = checks[i];
const type = typeof obj[check[0]];
if (type !== check[1]) {
return "check failed, expecting " + check[1] + " got " + type;
}
}
return null;
}
export { checkObjProps };

10
dist/encoding.d.ts vendored Normal file
View File

@ -0,0 +1,10 @@
import { Err } from "./types.js";
declare function b64ToBuf(b64: string): [Uint8Array, Err];
declare function bufToHex(buf: Uint8Array): string;
declare function bufToB64(buf: Uint8Array): string;
declare function bufToStr(buf: ArrayBuffer): [string, Err];
declare function decodeU64(u8: Uint8Array): [bigint, Err];
declare function encodePrefixedBytes(bytes: Uint8Array): [Uint8Array, Err];
declare function encodeU64(num: bigint): [Uint8Array, Err];
declare function hexToBuf(hex: string): [Uint8Array, Err];
export { b64ToBuf, bufToHex, bufToB64, bufToStr, decodeU64, encodePrefixedBytes, encodeU64, hexToBuf, };

122
dist/encoding.js vendored Normal file
View File

@ -0,0 +1,122 @@
import { addContextToErr } from "./err.js";
const MAX_UINT_64 = 18446744073709551615n;
// b64ToBuf will take an untrusted base64 string and convert it into a
// Uin8Array, returning an error if the input is not valid base64.
const b64regex = /^[0-9a-zA-Z-_/+=]*$/;
function b64ToBuf(b64) {
// Check that the final string is valid base64.
if (!b64regex.test(b64)) {
return [new Uint8Array(0), "provided string is not valid base64"];
}
// Swap any '-' characters for '+', and swap any '_' characters for '/'
// for use in the atob function.
b64 = b64.replaceAll("-", "+").replaceAll("_", "/");
// Perform the conversion.
const binStr = atob(b64);
const len = binStr.length;
const buf = new Uint8Array(len);
for (let i = 0; i < len; i++) {
buf[i] = binStr.charCodeAt(i);
}
return [buf, null];
}
// bufToHex takes a Uint8Array as input and returns the hex encoding of those
// bytes as a string.
function bufToHex(buf) {
return [...buf].map((x) => x.toString(16).padStart(2, "0")).join("");
}
// bufToB64 will convert a Uint8Array to a base64 string with URL encoding and
// no padding characters.
function bufToB64(buf) {
const b64Str = btoa(String.fromCharCode(...buf));
return b64Str.replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
}
// bufToStr takes an ArrayBuffer as input and returns a text string. bufToStr
// will check for invalid characters.
function bufToStr(buf) {
try {
const text = new TextDecoder("utf-8", { fatal: true }).decode(buf);
return [text, null];
}
catch (err) {
return [
"",
addContextToErr(err.toString(), "unable to decode ArrayBuffer to string"),
];
}
}
// decodeU64 is the opposite of encodeU64, it takes a uint64 encoded as 8 bytes
// and decodes them into a BigInt.
function decodeU64(u8) {
// Check the input.
if (u8.length !== 8) {
return [0n, "input should be 8 bytes"];
}
// Process the input.
let num = 0n;
for (let i = u8.length - 1; i >= 0; i--) {
num *= 256n;
num += BigInt(u8[i]);
}
return [num, null];
}
// encodePrefixedBytes takes a Uint8Array as input and returns a Uint8Array
// that has the length prefixed as an 8 byte prefix.
function encodePrefixedBytes(bytes) {
const [encodedLen, err] = encodeU64(BigInt(bytes.length));
if (err !== null) {
return [
new Uint8Array(0),
addContextToErr(err, "unable to encode array length"),
];
}
const prefixedArray = new Uint8Array(8 + bytes.length);
prefixedArray.set(encodedLen, 0);
prefixedArray.set(bytes, 8);
return [prefixedArray, null];
}
// encodeU64 will encode a bigint in the range of a uint64 to an 8 byte
// Uint8Array.
function encodeU64(num) {
// Check the bounds on the bigint.
if (num < 0) {
return [new Uint8Array(0), "expected a positive integer"];
}
if (num > MAX_UINT_64) {
return [new Uint8Array(0), "expected a number no larger than a uint64"];
}
// Encode the bigint into a Uint8Array.
const encoded = new Uint8Array(8);
for (let i = 0; i < encoded.length; i++) {
const byte = Number(num & 0xffn);
encoded[i] = byte;
num = num >> 8n;
}
return [encoded, null];
}
// hexToBuf takes an untrusted string as input, verifies that the string is
// valid hex, and then converts the string to a Uint8Array.
const allHex = /^[0-9a-f]+$/i;
function hexToBuf(hex) {
// The rest of the code doesn't handle zero length input well, so we handle
// that separately. It's not an error, we just return an empty array.
if (hex.length === 0) {
return [new Uint8Array(0), null];
}
// Check that the length makes sense.
if (hex.length % 2 !== 0) {
return [new Uint8Array(0), "input has incorrect length"];
}
// Check that all of the characters are legal.
if (!allHex.test(hex)) {
return [new Uint8Array(0), "input has invalid character"];
}
// Create the buffer and fill it.
const matches = hex.match(/.{2}/g);
if (matches === null) {
return [new Uint8Array(0), "input is incomplete"];
}
const u8 = new Uint8Array(matches.map((byte) => parseInt(byte, 16)));
return [u8, null];
}
export { b64ToBuf, bufToHex, bufToB64, bufToStr, decodeU64, encodePrefixedBytes, encodeU64, hexToBuf, };

2
dist/err.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
declare function addContextToErr(err: any, context: string): string;
export { addContextToErr };

14
dist/err.js vendored Normal file
View File

@ -0,0 +1,14 @@
import { objAsString } from "./objAsString.js";
// addContextToErr is a helper function that standardizes the formatting of
// adding context to an error.
//
// NOTE: To protect against accidental situations where an Error type or some
// other type is provided instead of a string, we wrap both of the inputs with
// objAsString before returning them. This prevents runtime failures.
function addContextToErr(err, context) {
if (err === null || err === undefined) {
err = "[no error provided]";
}
return objAsString(context) + ": " + objAsString(err);
}
export { addContextToErr };

13
dist/errTracker.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
import { Err } from "./types.js";
interface HistoricErr {
err: Err;
date: Date;
}
interface ErrTracker {
recentErrs: HistoricErr[];
oldErrs: HistoricErr[];
addErr: (err: Err) => void;
viewErrs: () => HistoricErr[];
}
declare function newErrTracker(): ErrTracker;
export { ErrTracker, HistoricErr, newErrTracker };

73
dist/errTracker.js vendored Normal file
View File

@ -0,0 +1,73 @@
// errTracker.ts defines an 'ErrTracker' type which keeps track of historical
// errors. When the number of errors gets too large, it randomly starts pruning
// errors. It always keeps 250 of the most recent errors, and then keeps up to
// 500 historic errors, where the first few errors after runtime are always
// kept, and the ones in the middle are increasingly likely to be omitted from
// the history.
// MAX_ERRORS defines the maximum number of errors that will be held in the
// HistoricErr object.
const MAX_ERRORS = 1000;
// newErrTracker returns an ErrTracker object that is ready to have errors
// added to it.
function newErrTracker() {
const et = {
recentErrs: [],
oldErrs: [],
addErr: function (err) {
addHistoricErr(et, err);
},
viewErrs: function () {
return viewErrs(et);
},
};
return et;
}
// addHistoricErr is a function that will add an error to a set of historic
// errors. It uses randomness to prune errors once the error object is too
// large.
function addHistoricErr(et, err) {
// Add this error to the set of most recent errors.
et.recentErrs.push({
err,
date: new Date(),
});
// Determine whether some of the most recent errors need to be moved into
// logTermErrs. If the length of the mostRecentErrs is not at least half of
// the MAX_ERRORS, we don't need to do anything.
if (et.recentErrs.length < MAX_ERRORS / 2) {
return;
}
// Iterate through the recentErrs. For the first half of the recentErrs, we
// will use randomness to either toss them or move them to oldErrs. The
// second half of the recentErrs will be kept as the new recentErrs array.
const newRecentErrs = [];
for (let i = 0; i < et.recentErrs.length; i++) {
// If we are in the second half of the array, add the element to
// newRecentErrs.
if (i > et.recentErrs.length / 2) {
newRecentErrs.push(et.recentErrs[i]);
continue;
}
// We are in the first half of the array, use a random number to add the
// error oldErrs probabilistically.
const rand = Math.random();
const target = et.oldErrs.length / (MAX_ERRORS / 2);
if (rand > target || et.oldErrs.length < 25) {
et.oldErrs.push(et.recentErrs[i]);
}
}
et.recentErrs = newRecentErrs;
}
// viewErrs returns the list of errors that have been retained by the
// HistoricErr object.
function viewErrs(et) {
const finalErrs = [];
for (let i = 0; i < et.oldErrs.length; i++) {
finalErrs.push(et.oldErrs[i]);
}
for (let i = 0; i < et.recentErrs.length; i++) {
finalErrs.push(et.recentErrs[i]);
}
return finalErrs;
}
export { newErrTracker };

13
dist/index.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
export { BLAKE2B_HASH_SIZE, blake2b } from "./blake2b.js";
export { checkObjProps } from "./checkObjProps.js";
export { b64ToBuf, bufToB64, bufToHex, bufToStr, decodeU64, encodePrefixedBytes, encodeU64, hexToBuf, } from "./encoding.js";
export { addContextToErr } from "./err.js";
export { ErrTracker, HistoricErr, newErrTracker } from "./errTracker.js";
export { objAsString } from "./objAsString.js";
export { parseJSON } from "./parse.js";
export { SHA512_HASH_SIZE, sha512 } from "./sha512.js";
export { SKYLINK_U8_V1_V2_LENGTH, parseSkylinkBitfield, skylinkV1Bitfield, } from "./skylinkBitfield.js";
export { validateSkyfileMetadata, validateSkyfilePath, validateSkylink, } from "./skylinkValidate.js";
export { jsonStringify } from "./stringifyJSON.js";
export { DataFn, Err, ErrFn, ErrTuple, KernelAuthStatus, RequestOverrideResponse, SkynetPortal, } from "./types.js";
export { validateObjPropTypes } from "./validateObjPropTypes.js";

12
dist/index.js vendored Normal file
View File

@ -0,0 +1,12 @@
export { BLAKE2B_HASH_SIZE, blake2b } from "./blake2b.js";
export { checkObjProps } from "./checkObjProps.js";
export { b64ToBuf, bufToB64, bufToHex, bufToStr, decodeU64, encodePrefixedBytes, encodeU64, hexToBuf, } from "./encoding.js";
export { addContextToErr } from "./err.js";
export { newErrTracker } from "./errTracker.js";
export { objAsString } from "./objAsString.js";
export { parseJSON } from "./parse.js";
export { SHA512_HASH_SIZE, sha512 } from "./sha512.js";
export { SKYLINK_U8_V1_V2_LENGTH, parseSkylinkBitfield, skylinkV1Bitfield, } from "./skylinkBitfield.js";
export { validateSkyfileMetadata, validateSkyfilePath, validateSkylink, } from "./skylinkValidate.js";
export { jsonStringify } from "./stringifyJSON.js";
export { validateObjPropTypes } from "./validateObjPropTypes.js";

2
dist/objAsString.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
declare function objAsString(obj: any): string;
export { objAsString };

60
dist/objAsString.js vendored Normal file
View File

@ -0,0 +1,60 @@
// objAsString will try to return the provided object as a string. If the
// object is already a string, it will be returned without modification. If the
// object is an 'Error', the message of the error will be returned. If the object
// has a toString method, the toString method will be called and the result
// will be returned. If the object is null or undefined, a special string will
// be returned indicating that the undefined/null object cannot be converted to
// a string. In all other cases, JSON.stringify is used. If JSON.stringify
// throws an exception, a message "[could not provide object as string]" will
// be returned.
//
// NOTE: objAsString is intended to produce human readable output. It is lossy,
// and it is not intended to be used for serialization.
function objAsString(obj) {
// Check for undefined input.
if (obj === undefined) {
return "[cannot convert undefined to string]";
}
if (obj === null) {
return "[cannot convert null to string]";
}
// Parse the error into a string.
if (typeof obj === "string") {
return obj;
}
// Check if the object is an error, and return the message of the error if
// so.
if (obj instanceof Error) {
return obj.message;
}
// Check if the object has a 'toString' method defined on it. To ensure
// that we don't crash or throw, check that the toString is a function, and
// also that the return value of toString is a string.
if (Object.prototype.hasOwnProperty.call(obj, "toString")) {
if (typeof obj.toString === "function") {
const str = obj.toString();
if (typeof str === "string") {
return str;
}
}
}
// If the object does not have a custom toString, attempt to perform a
// JSON.stringify. We use a lot of bigints in libskynet, and calling
// JSON.stringify on an object with a bigint will cause a throw, so we add
// some custom handling to allow bigint objects to still be encoded.
try {
return JSON.stringify(obj, (_, v) => {
if (typeof v === "bigint") {
return v.toString();
}
return v;
});
}
catch (err) {
if (err !== undefined && typeof err.message === "string") {
return `[stringify failed]: ${err.message}`;
}
return "[stringify failed]";
}
}
export { objAsString };

3
dist/parse.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
import { Err } from "./types.js";
declare function parseJSON(json: string): [any, Err];
export { parseJSON };

342
dist/parse.js vendored Normal file
View File

@ -0,0 +1,342 @@
// @ts-nocheck
import { objAsString } from "./objAsString.js";
// json_parse extracted from the json-bigint npm library
// regexpxs extracted from
// (c) BSD-3-Clause
// https://github.com/fastify/secure-json-parse/graphs/contributors and https://github.com/hapijs/bourne/graphs/contributors
const suspectProtoRx = /(?:_|\\u005[Ff])(?:_|\\u005[Ff])(?:p|\\u0070)(?:r|\\u0072)(?:o|\\u006[Ff])(?:t|\\u0074)(?:o|\\u006[Ff])(?:_|\\u005[Ff])(?:_|\\u005[Ff])/;
const suspectConstructorRx = /(?:c|\\u0063)(?:o|\\u006[Ff])(?:n|\\u006[Ee])(?:s|\\u0073)(?:t|\\u0074)(?:r|\\u0072)(?:u|\\u0075)(?:c|\\u0063)(?:t|\\u0074)(?:o|\\u006[Ff])(?:r|\\u0072)/;
let json_parse = function (options) {
"use strict";
// This is a function that can parse a JSON text, producing a JavaScript
// data structure. It is a simple, recursive descent parser. It does not use
// eval or regular expressions, so it can be used as a model for implementing
// a JSON parser in other languages.
// We are defining the function inside of another function to avoid creating
// global variables.
// Default options one can override by passing options to the parse()
let _options = {
strict: false,
storeAsString: false,
alwaysParseAsBig: false,
protoAction: "error",
constructorAction: "error",
};
// If there are options, then use them to override the default _options
if (options !== undefined && options !== null) {
if (options.strict === true) {
_options.strict = true;
}
if (options.storeAsString === true) {
_options.storeAsString = true;
}
_options.alwaysParseAsBig =
options.alwaysParseAsBig === true ? options.alwaysParseAsBig : false;
if (typeof options.constructorAction !== "undefined") {
if (options.constructorAction === "error" ||
options.constructorAction === "ignore" ||
options.constructorAction === "preserve") {
_options.constructorAction = options.constructorAction;
}
else {
throw new Error(`Incorrect value for constructorAction option, must be "error", "ignore" or undefined but passed ${options.constructorAction}`);
}
}
if (typeof options.protoAction !== "undefined") {
if (options.protoAction === "error" ||
options.protoAction === "ignore" ||
options.protoAction === "preserve") {
_options.protoAction = options.protoAction;
}
else {
throw new Error(`Incorrect value for protoAction option, must be "error", "ignore" or undefined but passed ${options.protoAction}`);
}
}
}
let at, // The index of the current character
ch, // The current character
escapee = {
'"': '"',
"\\": "\\",
"/": "/",
b: "\b",
f: "\f",
n: "\n",
r: "\r",
t: "\t",
}, text, error = function (m) {
// Call error when something is wrong.
throw {
name: "SyntaxError",
message: m,
at: at,
text: text,
};
}, next = function (c) {
// If a c parameter is provided, verify that it matches the current character.
if (c && c !== ch) {
error("Expected '" + c + "' instead of '" + ch + "'");
}
// Get the next character. When there are no more characters,
// return the empty string.
ch = text.charAt(at);
at += 1;
return ch;
}, number = function () {
// Parse a number value.
let number, string = "";
if (ch === "-") {
string = "-";
next("-");
}
while (ch >= "0" && ch <= "9") {
string += ch;
next();
}
if (ch === ".") {
string += ".";
while (next() && ch >= "0" && ch <= "9") {
string += ch;
}
}
if (ch === "e" || ch === "E") {
string += ch;
next();
if (ch === "-" || ch === "+") {
string += ch;
next();
}
while (ch >= "0" && ch <= "9") {
string += ch;
next();
}
}
number = +string;
if (!isFinite(number)) {
error("Bad number");
}
else {
if (Number.isSafeInteger(number))
return !_options.alwaysParseAsBig ? number : BigInt(number);
// Number with fractional part should be treated as number(double) including big integers in scientific notation, i.e 1.79e+308
else
return _options.storeAsString
? string
: /[.eE]/.test(string)
? number
: BigInt(string);
}
}, string = function () {
// Parse a string value.
let hex, i, string = "", uffff;
// When parsing for string values, we must look for " and \ characters.
if (ch === '"') {
let startAt = at;
while (next()) {
if (ch === '"') {
if (at - 1 > startAt)
string += text.substring(startAt, at - 1);
next();
return string;
}
if (ch === "\\") {
if (at - 1 > startAt)
string += text.substring(startAt, at - 1);
next();
if (ch === "u") {
uffff = 0;
for (i = 0; i < 4; i += 1) {
hex = parseInt(next(), 16);
if (!isFinite(hex)) {
break;
}
uffff = uffff * 16 + hex;
}
string += String.fromCharCode(uffff);
}
else if (typeof escapee[ch] === "string") {
string += escapee[ch];
}
else {
break;
}
startAt = at;
}
}
}
error("Bad string");
}, white = function () {
// Skip whitespace.
while (ch && ch <= " ") {
next();
}
}, word = function () {
// true, false, or null.
switch (ch) {
case "t":
next("t");
next("r");
next("u");
next("e");
return true;
case "f":
next("f");
next("a");
next("l");
next("s");
next("e");
return false;
case "n":
next("n");
next("u");
next("l");
next("l");
return null;
}
error("Unexpected '" + ch + "'");
}, value, // Place holder for the value function.
array = function () {
// Parse an array value.
let array = [];
if (ch === "[") {
next("[");
white();
if (ch === "]") {
next("]");
return array; // empty array
}
while (ch) {
array.push(value());
white();
if (ch === "]") {
next("]");
return array;
}
next(",");
white();
}
}
error("Bad array");
}, object = function () {
// Parse an object value.
let key, object = Object.create(null);
if (ch === "{") {
next("{");
white();
if (ch === "}") {
next("}");
return object; // empty object
}
while (ch) {
key = string();
white();
next(":");
if (_options.strict === true &&
Object.hasOwnProperty.call(object, key)) {
error('Duplicate key "' + key + '"');
}
if (suspectProtoRx.test(key) === true) {
if (_options.protoAction === "error") {
error("Object contains forbidden prototype property");
}
else if (_options.protoAction === "ignore") {
value();
}
else {
object[key] = value();
}
}
else if (suspectConstructorRx.test(key) === true) {
if (_options.constructorAction === "error") {
error("Object contains forbidden constructor property");
}
else if (_options.constructorAction === "ignore") {
value();
}
else {
object[key] = value();
}
}
else {
object[key] = value();
}
white();
if (ch === "}") {
next("}");
return object;
}
next(",");
white();
}
}
error("Bad object");
};
value = function () {
// Parse a JSON value. It could be an object, an array, a string, a number,
// or a word.
white();
switch (ch) {
case "{":
return object();
case "[":
return array();
case '"':
return string();
case "-":
return number();
default:
return ch >= "0" && ch <= "9" ? number() : word();
}
};
// Return the json_parse function. It will have access to all of the above
// functions and variables.
return function (source, reviver) {
let result;
text = source + "";
at = 0;
ch = " ";
result = value();
white();
if (ch) {
error("Syntax error");
}
// If there is a reviver function, we recursively walk the new structure,
// passing each name/value pair to the reviver function for possible
// transformation, starting with a temporary root object that holds the result
// in an empty key. If there is not a reviver function, we simply return the
// result.
return typeof reviver === "function"
? (function walk(holder, key) {
let v, value = holder[key];
if (value && typeof value === "object") {
Object.keys(value).forEach(function (k) {
v = walk(value, k);
if (v !== undefined) {
value[k] = v;
}
else {
delete value[k];
}
});
}
return reviver.call(holder, key, value);
})({ "": result }, "")
: result;
};
};
// parseJSON is a wrapper for JSONbig.parse that returns an error rather than
// throwing an error. JSONbig is an alternative to JSON.parse that decodes
// every number as a bigint. This is required when working with the skyd API
// because the skyd API uses 64 bit precision for all of its numbers, and
// therefore cannot be parsed losslessly by javascript. The skyd API is
// cryptographic, therefore full precision is required.
function parseJSON(json) {
try {
let obj = json_parse({ alwaysParseAsBig: true })(json);
return [obj, null];
}
catch (err) {
return [{}, objAsString(err)];
}
}
export { parseJSON };

4
dist/sha512.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
declare const SHA512_HASH_SIZE = 64;
declare const sha512internal: (out: Uint8Array, m: Uint8Array, n: number) => number;
declare function sha512(m: Uint8Array): Uint8Array;
export { SHA512_HASH_SIZE, sha512, sha512internal };

416
dist/sha512.js vendored Normal file
View File

@ -0,0 +1,416 @@
const SHA512_HASH_SIZE = 64;
const K = [
0x428a2f98, 0xd728ae22, 0x71374491, 0x23ef65cd, 0xb5c0fbcf, 0xec4d3b2f,
0xe9b5dba5, 0x8189dbbc, 0x3956c25b, 0xf348b538, 0x59f111f1, 0xb605d019,
0x923f82a4, 0xaf194f9b, 0xab1c5ed5, 0xda6d8118, 0xd807aa98, 0xa3030242,
0x12835b01, 0x45706fbe, 0x243185be, 0x4ee4b28c, 0x550c7dc3, 0xd5ffb4e2,
0x72be5d74, 0xf27b896f, 0x80deb1fe, 0x3b1696b1, 0x9bdc06a7, 0x25c71235,
0xc19bf174, 0xcf692694, 0xe49b69c1, 0x9ef14ad2, 0xefbe4786, 0x384f25e3,
0x0fc19dc6, 0x8b8cd5b5, 0x240ca1cc, 0x77ac9c65, 0x2de92c6f, 0x592b0275,
0x4a7484aa, 0x6ea6e483, 0x5cb0a9dc, 0xbd41fbd4, 0x76f988da, 0x831153b5,
0x983e5152, 0xee66dfab, 0xa831c66d, 0x2db43210, 0xb00327c8, 0x98fb213f,
0xbf597fc7, 0xbeef0ee4, 0xc6e00bf3, 0x3da88fc2, 0xd5a79147, 0x930aa725,
0x06ca6351, 0xe003826f, 0x14292967, 0x0a0e6e70, 0x27b70a85, 0x46d22ffc,
0x2e1b2138, 0x5c26c926, 0x4d2c6dfc, 0x5ac42aed, 0x53380d13, 0x9d95b3df,
0x650a7354, 0x8baf63de, 0x766a0abb, 0x3c77b2a8, 0x81c2c92e, 0x47edaee6,
0x92722c85, 0x1482353b, 0xa2bfe8a1, 0x4cf10364, 0xa81a664b, 0xbc423001,
0xc24b8b70, 0xd0f89791, 0xc76c51a3, 0x0654be30, 0xd192e819, 0xd6ef5218,
0xd6990624, 0x5565a910, 0xf40e3585, 0x5771202a, 0x106aa070, 0x32bbd1b8,
0x19a4c116, 0xb8d2d0c8, 0x1e376c08, 0x5141ab53, 0x2748774c, 0xdf8eeb99,
0x34b0bcb5, 0xe19b48a8, 0x391c0cb3, 0xc5c95a63, 0x4ed8aa4a, 0xe3418acb,
0x5b9cca4f, 0x7763e373, 0x682e6ff3, 0xd6b2b8a3, 0x748f82ee, 0x5defb2fc,
0x78a5636f, 0x43172f60, 0x84c87814, 0xa1f0ab72, 0x8cc70208, 0x1a6439ec,
0x90befffa, 0x23631e28, 0xa4506ceb, 0xde82bde9, 0xbef9a3f7, 0xb2c67915,
0xc67178f2, 0xe372532b, 0xca273ece, 0xea26619c, 0xd186b8c7, 0x21c0c207,
0xeada7dd6, 0xcde0eb1e, 0xf57d4f7f, 0xee6ed178, 0x06f067aa, 0x72176fba,
0x0a637dc5, 0xa2c898a6, 0x113f9804, 0xbef90dae, 0x1b710b35, 0x131c471b,
0x28db77f5, 0x23047d84, 0x32caab7b, 0x40c72493, 0x3c9ebe0a, 0x15c9bebc,
0x431d67c4, 0x9c100d4c, 0x4cc5d4be, 0xcb3e42b6, 0x597f299c, 0xfc657e2a,
0x5fcb6fab, 0x3ad6faec, 0x6c44198c, 0x4a475817,
];
function ts64(x, i, h, l) {
x[i] = (h >> 24) & 0xff;
x[i + 1] = (h >> 16) & 0xff;
x[i + 2] = (h >> 8) & 0xff;
x[i + 3] = h & 0xff;
x[i + 4] = (l >> 24) & 0xff;
x[i + 5] = (l >> 16) & 0xff;
x[i + 6] = (l >> 8) & 0xff;
x[i + 7] = l & 0xff;
}
function crypto_hashblocks_hl(hh, hl, m, n) {
const wh = new Int32Array(16), wl = new Int32Array(16);
let bh0, bh1, bh2, bh3, bh4, bh5, bh6, bh7, bl0, bl1, bl2, bl3, bl4, bl5, bl6, bl7, th, tl, i, j, h, l, a, b, c, d;
let ah0 = hh[0], ah1 = hh[1], ah2 = hh[2], ah3 = hh[3], ah4 = hh[4], ah5 = hh[5], ah6 = hh[6], ah7 = hh[7], al0 = hl[0], al1 = hl[1], al2 = hl[2], al3 = hl[3], al4 = hl[4], al5 = hl[5], al6 = hl[6], al7 = hl[7];
let pos = 0;
while (n >= 128) {
for (i = 0; i < 16; i++) {
j = 8 * i + pos;
wh[i] = (m[j + 0] << 24) | (m[j + 1] << 16) | (m[j + 2] << 8) | m[j + 3];
wl[i] = (m[j + 4] << 24) | (m[j + 5] << 16) | (m[j + 6] << 8) | m[j + 7];
}
for (i = 0; i < 80; i++) {
bh0 = ah0;
bh1 = ah1;
bh2 = ah2;
bh3 = ah3;
bh4 = ah4;
bh5 = ah5;
bh6 = ah6;
bh7 = ah7;
bl0 = al0;
bl1 = al1;
bl2 = al2;
bl3 = al3;
bl4 = al4;
bl5 = al5;
bl6 = al6;
bl7 = al7;
// add
h = ah7;
l = al7;
a = l & 0xffff;
b = l >>> 16;
c = h & 0xffff;
d = h >>> 16;
// Sigma1
h =
((ah4 >>> 14) | (al4 << (32 - 14))) ^
((ah4 >>> 18) | (al4 << (32 - 18))) ^
((al4 >>> (41 - 32)) | (ah4 << (32 - (41 - 32))));
l =
((al4 >>> 14) | (ah4 << (32 - 14))) ^
((al4 >>> 18) | (ah4 << (32 - 18))) ^
((ah4 >>> (41 - 32)) | (al4 << (32 - (41 - 32))));
a += l & 0xffff;
b += l >>> 16;
c += h & 0xffff;
d += h >>> 16;
// Ch
h = (ah4 & ah5) ^ (~ah4 & ah6);
l = (al4 & al5) ^ (~al4 & al6);
a += l & 0xffff;
b += l >>> 16;
c += h & 0xffff;
d += h >>> 16;
// K
h = K[i * 2];
l = K[i * 2 + 1];
a += l & 0xffff;
b += l >>> 16;
c += h & 0xffff;
d += h >>> 16;
// w
h = wh[i % 16];
l = wl[i % 16];
a += l & 0xffff;
b += l >>> 16;
c += h & 0xffff;
d += h >>> 16;
b += a >>> 16;
c += b >>> 16;
d += c >>> 16;
th = (c & 0xffff) | (d << 16);
tl = (a & 0xffff) | (b << 16);
// add
h = th;
l = tl;
a = l & 0xffff;
b = l >>> 16;
c = h & 0xffff;
d = h >>> 16;
// Sigma0
h =
((ah0 >>> 28) | (al0 << (32 - 28))) ^
((al0 >>> (34 - 32)) | (ah0 << (32 - (34 - 32)))) ^
((al0 >>> (39 - 32)) | (ah0 << (32 - (39 - 32))));
l =
((al0 >>> 28) | (ah0 << (32 - 28))) ^
((ah0 >>> (34 - 32)) | (al0 << (32 - (34 - 32)))) ^
((ah0 >>> (39 - 32)) | (al0 << (32 - (39 - 32))));
a += l & 0xffff;
b += l >>> 16;
c += h & 0xffff;
d += h >>> 16;
// Maj
h = (ah0 & ah1) ^ (ah0 & ah2) ^ (ah1 & ah2);
l = (al0 & al1) ^ (al0 & al2) ^ (al1 & al2);
a += l & 0xffff;
b += l >>> 16;
c += h & 0xffff;
d += h >>> 16;
b += a >>> 16;
c += b >>> 16;
d += c >>> 16;
bh7 = (c & 0xffff) | (d << 16);
bl7 = (a & 0xffff) | (b << 16);
// add
h = bh3;
l = bl3;
a = l & 0xffff;
b = l >>> 16;
c = h & 0xffff;
d = h >>> 16;
h = th;
l = tl;
a += l & 0xffff;
b += l >>> 16;
c += h & 0xffff;
d += h >>> 16;
b += a >>> 16;
c += b >>> 16;
d += c >>> 16;
bh3 = (c & 0xffff) | (d << 16);
bl3 = (a & 0xffff) | (b << 16);
ah1 = bh0;
ah2 = bh1;
ah3 = bh2;
ah4 = bh3;
ah5 = bh4;
ah6 = bh5;
ah7 = bh6;
ah0 = bh7;
al1 = bl0;
al2 = bl1;
al3 = bl2;
al4 = bl3;
al5 = bl4;
al6 = bl5;
al7 = bl6;
al0 = bl7;
if (i % 16 === 15) {
for (j = 0; j < 16; j++) {
// add
h = wh[j];
l = wl[j];
a = l & 0xffff;
b = l >>> 16;
c = h & 0xffff;
d = h >>> 16;
h = wh[(j + 9) % 16];
l = wl[(j + 9) % 16];
a += l & 0xffff;
b += l >>> 16;
c += h & 0xffff;
d += h >>> 16;
// sigma0
th = wh[(j + 1) % 16];
tl = wl[(j + 1) % 16];
h =
((th >>> 1) | (tl << (32 - 1))) ^
((th >>> 8) | (tl << (32 - 8))) ^
(th >>> 7);
l =
((tl >>> 1) | (th << (32 - 1))) ^
((tl >>> 8) | (th << (32 - 8))) ^
((tl >>> 7) | (th << (32 - 7)));
a += l & 0xffff;
b += l >>> 16;
c += h & 0xffff;
d += h >>> 16;
// sigma1
th = wh[(j + 14) % 16];
tl = wl[(j + 14) % 16];
h =
((th >>> 19) | (tl << (32 - 19))) ^
((tl >>> (61 - 32)) | (th << (32 - (61 - 32)))) ^
(th >>> 6);
l =
((tl >>> 19) | (th << (32 - 19))) ^
((th >>> (61 - 32)) | (tl << (32 - (61 - 32)))) ^
((tl >>> 6) | (th << (32 - 6)));
a += l & 0xffff;
b += l >>> 16;
c += h & 0xffff;
d += h >>> 16;
b += a >>> 16;
c += b >>> 16;
d += c >>> 16;
wh[j] = (c & 0xffff) | (d << 16);
wl[j] = (a & 0xffff) | (b << 16);
}
}
}
// add
h = ah0;
l = al0;
a = l & 0xffff;
b = l >>> 16;
c = h & 0xffff;
d = h >>> 16;
h = hh[0];
l = hl[0];
a += l & 0xffff;
b += l >>> 16;
c += h & 0xffff;
d += h >>> 16;
b += a >>> 16;
c += b >>> 16;
d += c >>> 16;
hh[0] = ah0 = (c & 0xffff) | (d << 16);
hl[0] = al0 = (a & 0xffff) | (b << 16);
h = ah1;
l = al1;
a = l & 0xffff;
b = l >>> 16;
c = h & 0xffff;
d = h >>> 16;
h = hh[1];
l = hl[1];
a += l & 0xffff;
b += l >>> 16;
c += h & 0xffff;
d += h >>> 16;
b += a >>> 16;
c += b >>> 16;
d += c >>> 16;
hh[1] = ah1 = (c & 0xffff) | (d << 16);
hl[1] = al1 = (a & 0xffff) | (b << 16);
h = ah2;
l = al2;
a = l & 0xffff;
b = l >>> 16;
c = h & 0xffff;
d = h >>> 16;
h = hh[2];
l = hl[2];
a += l & 0xffff;
b += l >>> 16;
c += h & 0xffff;
d += h >>> 16;
b += a >>> 16;
c += b >>> 16;
d += c >>> 16;
hh[2] = ah2 = (c & 0xffff) | (d << 16);
hl[2] = al2 = (a & 0xffff) | (b << 16);
h = ah3;
l = al3;
a = l & 0xffff;
b = l >>> 16;
c = h & 0xffff;
d = h >>> 16;
h = hh[3];
l = hl[3];
a += l & 0xffff;
b += l >>> 16;
c += h & 0xffff;
d += h >>> 16;
b += a >>> 16;
c += b >>> 16;
d += c >>> 16;
hh[3] = ah3 = (c & 0xffff) | (d << 16);
hl[3] = al3 = (a & 0xffff) | (b << 16);
h = ah4;
l = al4;
a = l & 0xffff;
b = l >>> 16;
c = h & 0xffff;
d = h >>> 16;
h = hh[4];
l = hl[4];
a += l & 0xffff;
b += l >>> 16;
c += h & 0xffff;
d += h >>> 16;
b += a >>> 16;
c += b >>> 16;
d += c >>> 16;
hh[4] = ah4 = (c & 0xffff) | (d << 16);
hl[4] = al4 = (a & 0xffff) | (b << 16);
h = ah5;
l = al5;
a = l & 0xffff;
b = l >>> 16;
c = h & 0xffff;
d = h >>> 16;
h = hh[5];
l = hl[5];
a += l & 0xffff;
b += l >>> 16;
c += h & 0xffff;
d += h >>> 16;
b += a >>> 16;
c += b >>> 16;
d += c >>> 16;
hh[5] = ah5 = (c & 0xffff) | (d << 16);
hl[5] = al5 = (a & 0xffff) | (b << 16);
h = ah6;
l = al6;
a = l & 0xffff;
b = l >>> 16;
c = h & 0xffff;
d = h >>> 16;
h = hh[6];
l = hl[6];
a += l & 0xffff;
b += l >>> 16;
c += h & 0xffff;
d += h >>> 16;
b += a >>> 16;
c += b >>> 16;
d += c >>> 16;
hh[6] = ah6 = (c & 0xffff) | (d << 16);
hl[6] = al6 = (a & 0xffff) | (b << 16);
h = ah7;
l = al7;
a = l & 0xffff;
b = l >>> 16;
c = h & 0xffff;
d = h >>> 16;
h = hh[7];
l = hl[7];
a += l & 0xffff;
b += l >>> 16;
c += h & 0xffff;
d += h >>> 16;
b += a >>> 16;
c += b >>> 16;
d += c >>> 16;
hh[7] = ah7 = (c & 0xffff) | (d << 16);
hl[7] = al7 = (a & 0xffff) | (b << 16);
pos += 128;
n -= 128;
}
return n;
}
const sha512internal = function (out, m, n) {
const hh = new Int32Array(8), hl = new Int32Array(8), x = new Uint8Array(256), b = n;
let i;
hh[0] = 0x6a09e667;
hh[1] = 0xbb67ae85;
hh[2] = 0x3c6ef372;
hh[3] = 0xa54ff53a;
hh[4] = 0x510e527f;
hh[5] = 0x9b05688c;
hh[6] = 0x1f83d9ab;
hh[7] = 0x5be0cd19;
hl[0] = 0xf3bcc908;
hl[1] = 0x84caa73b;
hl[2] = 0xfe94f82b;
hl[3] = 0x5f1d36f1;
hl[4] = 0xade682d1;
hl[5] = 0x2b3e6c1f;
hl[6] = 0xfb41bd6b;
hl[7] = 0x137e2179;
crypto_hashblocks_hl(hh, hl, m, n);
n %= 128;
for (i = 0; i < n; i++)
x[i] = m[b - n + i];
x[n] = 128;
n = 256 - 128 * (n < 112 ? 1 : 0);
x[n - 9] = 0;
ts64(x, n - 8, (b / 0x20000000) | 0, b << 3);
crypto_hashblocks_hl(hh, hl, x, n);
for (i = 0; i < 8; i++)
ts64(out, 8 * i, hh[i], hl[i]);
return 0;
};
// sha512 is the standard sha512 cryptographic hash function. This is the
// default choice for Skynet operations, though many of the Sia protocol
// standards use blake2b instead, so you will see both.
function sha512(m) {
const out = new Uint8Array(SHA512_HASH_SIZE);
sha512internal(out, m, m.length);
return out;
}
export { SHA512_HASH_SIZE, sha512, sha512internal };

4
dist/skylinkBitfield.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
declare const SKYLINK_U8_V1_V2_LENGTH = 34;
declare function parseSkylinkBitfield(skylinkU8: Uint8Array): [bigint, bigint, bigint, string | null];
declare function skylinkV1Bitfield(dataSizeBI: bigint): [Uint8Array, string | null];
export { SKYLINK_U8_V1_V2_LENGTH, parseSkylinkBitfield, skylinkV1Bitfield };

164
dist/skylinkBitfield.js vendored Normal file
View File

@ -0,0 +1,164 @@
// skylinkBitfield.ts defines a bunch of operations for working with skylink
// bitfields. This code is ported from the Sia codebase. You can see that code
// and all of the comments at:
// https://gitlab.com/SkynetLabs/skyd/-/blob/master/skymodules/skylink.go
// SKYLINK_U8_V1_V2_LENGTH defines the length of a skylink that is V1 or V2
// when it is encoded as a Uint8Array.
const SKYLINK_U8_V1_V2_LENGTH = 34;
// SECTOR_SIZE is the size of a sector
const SECTOR_SIZE = 1 << 22;
// parseSkylinkBitfield parses a skylink bitfield and returns the corresponding
// version, offset, and fetchSize.
function parseSkylinkBitfield(skylinkU8) {
// Validate the input.
if (skylinkU8.length !== SKYLINK_U8_V1_V2_LENGTH) {
return [0n, 0n, 0n, "provided skylink has incorrect length"];
}
// Extract the bitfield.
let bitfield = new DataView(skylinkU8.buffer).getUint16(0, true);
// Extract the version. We add '1' so that an empty version field maps to
// version 1, and a version field set to '1' maps to version 2.
const version = (bitfield & 3) + 1;
// Only versions 1 and 2 are recognized.
if (version !== 1 && version !== 2) {
return [0n, 0n, 0n, "provided skylink has unrecognized version"];
}
// If the skylink is set to version 2, we only recognize the link if the rest
// of the bits in the bitfield are empty. This is the definition of a v2
// skylink, other skylink versions (such as version 1) use more of the
// bitfield.
if (version === 2) {
if ((bitfield & 3) !== bitfield) {
return [0n, 0n, 0n, "provided skylink has unrecognized version"];
}
return [BigInt(version), 0n, 0n, null];
}
// Verify that the mode is valid, then fetch the mode.
bitfield = bitfield >> 2;
if ((bitfield & 255) === 255) {
return [0n, 0n, 0n, "provided skylink has an unrecognized version"];
}
let mode = 0;
for (let i = 0; i < 8; i++) {
if ((bitfield & 1) === 0) {
bitfield = bitfield >> 1;
break;
}
bitfield = bitfield >> 1;
mode++;
}
// If the mode is greater than 7, this is not a valid v1 skylink.
if (mode > 7) {
return [0n, 0n, 0n, "provided skylink has an invalid v1 bitfield"];
}
// Determine the offset and fetchSize increment.
const offsetIncrement = 4096 << mode;
let fetchSizeIncrement = 4096;
let fetchSizeStart = 0;
if (mode > 0) {
fetchSizeIncrement = fetchSizeIncrement << (mode - 1);
fetchSizeStart = (1 << 15) << (mode - 1);
}
// The next three bits decide the fetchSize.
const fetchSizeBits = (bitfield & 7) + 1; // +1 because semantic range is [1,8].
const fetchSize = fetchSizeBits * fetchSizeIncrement + fetchSizeStart;
bitfield = bitfield >> 3;
// The remaining bits determine the offset.
const offset = bitfield * offsetIncrement;
if (offset + fetchSize > SECTOR_SIZE) {
return [0n, 0n, 0n, "provided skylink has an invalid v1 bitfield"];
}
// Return what we learned.
return [BigInt(version), BigInt(offset), BigInt(fetchSize), null];
}
// skylinkV1Bitfield sets the bitfield of a V1 skylink. It assumes the version
// is 1 and the offset is 0. It will determine the appropriate fetchSize from
// the provided dataSize.
function skylinkV1Bitfield(dataSizeBI) {
// Check that the dataSize is not too large.
if (dataSizeBI > SECTOR_SIZE) {
return [new Uint8Array(0), "dataSize must be less than the sector size"];
}
const dataSize = Number(dataSizeBI);
// Determine the mode for the file. The mode is determined by the
// dataSize.
let mode = 0;
for (let i = 1 << 15; i < dataSize; i *= 2) {
mode += 1;
}
// Determine the download number.
let downloadNumber = 0;
if (mode === 0) {
if (dataSize !== 0) {
downloadNumber = Math.floor((dataSize - 1) / (1 << 12));
}
}
else {
const step = 1 << (11 + mode);
const target = dataSize - (1 << (14 + mode));
if (target !== 0) {
downloadNumber = Math.floor((target - 1) / step);
}
}
// Create the Uint8Array and fill it out. The main reason I switch over
// the 7 modes like this is because I wasn't sure how to make a uint16
// in javascript. If we could treat the uint8array as a uint16 and then
// later convert it over, we could use basic bitshifiting and really
// simplify the code here.
const bitfield = new Uint8Array(2);
if (mode === 7) {
// 0 0 0 X X X 0 1|1 1 1 1 1 1 0 0
bitfield[0] = downloadNumber;
bitfield[0] *= 4;
bitfield[0] += 1;
bitfield[1] = 4 + 8 + 16 + 32 + 64 + 128;
}
if (mode === 6) {
// 0 0 0 0 X X X 0|1 1 1 1 1 1 0 0
bitfield[0] = downloadNumber;
bitfield[0] *= 2;
bitfield[1] = 4 + 8 + 16 + 32 + 64 + 128;
}
if (mode === 5) {
// 0 0 0 0 0 X X X|0 1 1 1 1 1 0 0
bitfield[0] = downloadNumber;
bitfield[1] = 4 + 8 + 16 + 32 + 64;
}
if (mode === 4) {
// 0 0 0 0 0 0 X X|X 0 1 1 1 1 0 0
bitfield[0] = downloadNumber;
bitfield[0] /= 2;
bitfield[1] = (downloadNumber & 1) * 128;
bitfield[1] += 4 + 8 + 16 + 32;
}
if (mode === 3) {
// 0 0 0 0 0 0 0 X|X X 0 1 1 1 0 0
bitfield[0] = downloadNumber;
bitfield[0] /= 4;
bitfield[1] = (downloadNumber & 3) * 64;
bitfield[1] += 4 + 8 + 16;
}
if (mode === 2) {
// 0 0 0 0 0 0 0 0|X X X 0 1 1 0 0
bitfield[0] = 0;
bitfield[1] = downloadNumber * 32;
bitfield[1] += 4 + 8;
}
if (mode === 1) {
// 0 0 0 0 0 0 0 0|0 X X X 0 1 0 0
bitfield[0] = 0;
bitfield[1] = downloadNumber * 16;
bitfield[1] += 4;
}
if (mode === 0) {
// 0 0 0 0 0 0 0 0|0 0 X X X 0 0 0
bitfield[0] = 0;
bitfield[1] = downloadNumber * 8;
}
// Swap the byte order.
const zero = bitfield[0];
bitfield[0] = bitfield[1];
bitfield[1] = zero;
return [bitfield, null];
}
export { SKYLINK_U8_V1_V2_LENGTH, parseSkylinkBitfield, skylinkV1Bitfield };

5
dist/skylinkValidate.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
import { Err } from "./types.js";
declare function validateSkyfilePath(path: string): string | null;
declare function validateSkyfileMetadata(metadata: any): string | null;
declare function validateSkylink(skylink: string | Uint8Array): Err;
export { validateSkyfileMetadata, validateSkyfilePath, validateSkylink };

115
dist/skylinkValidate.js vendored Normal file
View File

@ -0,0 +1,115 @@
import { addContextToErr } from "./err.js";
import { b64ToBuf } from "./encoding.js";
import { SKYLINK_U8_V1_V2_LENGTH, parseSkylinkBitfield, } from "./skylinkBitfield.js";
// validateSkyfilePath checks whether the provided path is a valid path for a
// file in a skylink.
function validateSkyfilePath(path) {
if (path === "") {
return "path cannot be blank";
}
if (path === "..") {
return "path cannot be ..";
}
if (path === ".") {
return "path cannot be .";
}
if (path.startsWith("/")) {
return "metdata.Filename cannot start with /";
}
if (path.startsWith("../")) {
return "metdata.Filename cannot start with ../";
}
if (path.startsWith("./")) {
return "metdata.Filename cannot start with ./";
}
const pathElems = path.split("/");
for (let i = 0; i < pathElems.length; i++) {
if (pathElems[i] === ".") {
return "path cannot have a . element";
}
if (pathElems[i] === "..") {
return "path cannot have a .. element";
}
if (pathElems[i] === "") {
return "path cannot have an empty element, cannot contain //";
}
}
return null;
}
// validateSkyfileMetadata checks whether the provided metadata is valid
// metadata for a skyfile.
function validateSkyfileMetadata(metadata) {
// Check that the filename is valid.
if (!("Filename" in metadata)) {
return "metadata.Filename does not exist";
}
if (typeof metadata.Filename !== "string") {
return "metadata.Filename is not a string";
}
const errVSP = validateSkyfilePath(metadata.Filename);
if (errVSP !== null) {
return addContextToErr(errVSP, "metadata.Filename does not have a valid path");
}
// Check that there are no subfiles.
if ("Subfiles" in metadata) {
// TODO: Fill this out using code from
// skymodules.ValidateSkyfileMetadata to support subfiles.
return "cannot upload files that have subfiles";
}
// Check that the default path rules are being respected.
if ("DisableDefaultPath" in metadata && "DefaultPath" in metadata) {
return "cannot set both a DefaultPath and also DisableDefaultPath";
}
if ("DefaultPath" in metadata) {
// TODO: Fill this out with code from
// skymodules.validateDefaultPath to support subfiles and
// default paths.
return "cannot set a default path if there are no subfiles";
}
if ("TryFiles" in metadata) {
if (!metadata.TryFiles.IsArray()) {
return "metadata.TryFiles must be an array";
}
if (metadata.TryFiles.length === 0) {
return "metadata.TryFiles should not be empty";
}
if ("DefaultPath" in metadata) {
return "metadata.TryFiles cannot be used alongside DefaultPath";
}
if ("DisableDefaultPath" in metadata) {
return "metadata.TryFiles cannot be used alongside DisableDefaultPath";
}
// TODO: finish the TryFiles checking using skymodules.ValidateTryFiles
return "TryFiles is not supported at this time";
}
if ("ErrorPages" in metadata) {
// TODO: finish using skymodules.ValidateErrorPages
return "ErrorPages is not supported at this time";
}
return null;
}
// validateSkylink returns null if the provided Uint8Array is a valid skylink.
function validateSkylink(skylink) {
// If the input is a string, convert it to a Uint8Array.
let skylinkU8;
if (typeof skylink === "string") {
const [buf, err] = b64ToBuf(skylink);
if (err !== null) {
return addContextToErr(err, "unable to convert skylink from string");
}
skylinkU8 = buf;
}
else {
skylinkU8 = skylink;
}
// skylink is now a Uint8
if (skylinkU8.length !== SKYLINK_U8_V1_V2_LENGTH) {
return `skylinkU8 has an invalid length: ${skylinkU8.length}`;
}
const [, , , errPSB] = parseSkylinkBitfield(skylinkU8);
if (errPSB !== null) {
return addContextToErr(errPSB, "skylink did not decode");
}
return null;
}
export { validateSkyfileMetadata, validateSkyfilePath, validateSkylink };

3
dist/stringifyJSON.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
import { Err } from "./types.js";
declare function jsonStringify(obj: any): [string, Err];
export { jsonStringify };

22
dist/stringifyJSON.js vendored Normal file
View File

@ -0,0 +1,22 @@
import { addContextToErr } from "./err.js";
import { objAsString } from "./objAsString.js";
// jsonStringify is a replacement for JSON.stringify that returns an error
// rather than throwing.
function jsonStringify(obj) {
try {
const str = JSON.stringify(obj, (_, v) => {
if (typeof v === "bigint") {
return Number(v);
}
return v;
});
return [str, null];
}
catch (err) {
return [
"",
addContextToErr(objAsString(err), "unable to stringify object"),
];
}
}
export { jsonStringify };

19
dist/types.d.ts vendored Normal file
View File

@ -0,0 +1,19 @@
type DataFn = (data?: any) => void;
type Err = string | null;
type ErrFn = (errMsg: string) => void;
type ErrTuple = [data: any, err: Err];
interface KernelAuthStatus {
loginComplete: boolean;
kernelLoaded: "not yet" | "success" | string;
logoutComplete: boolean;
}
interface SkynetPortal {
url: string;
name: string;
}
interface RequestOverrideResponse {
override: boolean;
headers?: any;
body?: Uint8Array;
}
export { DataFn, ErrFn, Err, ErrTuple, KernelAuthStatus, RequestOverrideResponse, SkynetPortal, };

1
dist/types.js vendored Normal file
View File

@ -0,0 +1 @@
export {};

3
dist/validateObjPropTypes.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
import { Err } from "./types.js";
declare function validateObjPropTypes(obj: any, checks: [string, string][]): Err;
export { validateObjPropTypes };

98
dist/validateObjPropTypes.js vendored Normal file
View File

@ -0,0 +1,98 @@
import { addContextToErr } from "./err.js";
// validateObjPropTypes takes an object as input, along with a list of checks
// that should performed on the properties of the object. If all of the
// properties are present in the object and adhere to the suggested types,
// `null` is returned. Otherwise a string is returned indicating the first
// property that failed a check.
//
// This function is intended to be used on objects that were decoded from JSON
// after being received by an untrusted source.
//
// validateObjProperties supports all of the basic types, as well as arrays for
// types boolean, number, bigint, and string. In the future, support for more
// types may be added as well.
//
// Below is an example object, followed by the call that you would make to
// checkObj to verify the object.
//
// const expectedObj = {
// aNum: 35,
// aStr: "hi",
// aBig: 10n,
// aArr: [1, 2, 3],
// };
//
// const err = validateObjPropTypes(expectedObj, [
// ["aNum", "number"],
// ["aStr", "string"],
// ["aBig", "bigint"],
// ["aArr", "numberArray"],
// ["aUint8Array", "Uint8Array"],
// ]);
function validateObjPropTypes(obj, checks) {
for (let i = 0; i < checks.length; i++) {
const [property, expectedType] = checks[i];
// Loop through the array cases.
const arrayCases = [
["booleanArray", "boolean"],
["numberArray", "number"],
["bigintArray", "bigint"],
["stringArray", "string"],
];
let checkPassed = false;
for (let j = 0; j < arrayCases.length; j++) {
// If this is not an array case, ignore it.
const [arrCaseType, arrType] = arrayCases[j];
if (expectedType !== arrCaseType) {
continue;
}
// Check every element in the array.
const err = validateArrayTypes(obj[property], arrType);
if (err !== null) {
return addContextToErr(err, `check failed for array property '${property}'`);
}
// We found the expected type for this check, we can stop checking the
// rest.
checkPassed = true;
break;
}
// If the type was an array type, we don't need to perform the next check.
if (checkPassed === true) {
continue;
}
// Uint8Array check.
if (expectedType === "Uint8Array") {
if (obj[property] instanceof Uint8Array) {
continue;
}
else {
return `check failed for property '${property};, expecting Uint8Array`;
}
}
// Generic typeof check.
const type = typeof obj[property];
if (type !== expectedType) {
return `check failed for property '${property}', expecting ${expectedType} got ${type}`;
}
}
return null;
}
// validateArrayTypes takes an array as input and validates that every element
// in the array matches the provided type.
//
// This is a helper function for validateObjPropTypes, the property is provided
// as an input to produce a more coherent error message.
function validateArrayTypes(arr, expectedType) {
// Check that the provided input is actually an array.
if (!Array.isArray(arr)) {
return `not an array`;
}
for (let i = 0; i < arr.length; i++) {
const type = typeof arr[i];
if (type !== expectedType) {
return `element ${i} is expected to be ${expectedType}, got ${type}`;
}
}
return null;
}
export { validateObjPropTypes };

View File

@ -17,6 +17,7 @@
"/dist"
],
"devDependencies": {
"prettier": "^2.8.3"
"prettier": "^2.8.3",
"typescript": "^4.9.4"
}
}