*Add dist
This commit is contained in:
parent
517c99fecb
commit
5e6cdba3e7
|
@ -0,0 +1,3 @@
|
|||
declare const BLAKE2B_HASH_SIZE = 32;
|
||||
declare function blake2b(input: Uint8Array): Uint8Array;
|
||||
export { BLAKE2B_HASH_SIZE, blake2b };
|
|
@ -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 };
|
|
@ -0,0 +1,3 @@
|
|||
import { Err } from "./types.js";
|
||||
declare function checkObjProps(obj: any, checks: [string, string][]): Err;
|
||||
export { checkObjProps };
|
|
@ -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 };
|
|
@ -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, };
|
|
@ -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, };
|
|
@ -0,0 +1,2 @@
|
|||
declare function addContextToErr(err: any, context: string): string;
|
||||
export { addContextToErr };
|
|
@ -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 };
|
|
@ -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 };
|
|
@ -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 };
|
|
@ -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";
|
|
@ -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";
|
|
@ -0,0 +1,2 @@
|
|||
declare function objAsString(obj: any): string;
|
||||
export { objAsString };
|
|
@ -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 };
|
|
@ -0,0 +1,3 @@
|
|||
import { Err } from "./types.js";
|
||||
declare function parseJSON(json: string): [any, Err];
|
||||
export { parseJSON };
|
|
@ -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 };
|
|
@ -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 };
|
|
@ -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 };
|
|
@ -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 };
|
|
@ -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 };
|
|
@ -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 };
|
|
@ -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 };
|
|
@ -0,0 +1,3 @@
|
|||
import { Err } from "./types.js";
|
||||
declare function jsonStringify(obj: any): [string, Err];
|
||||
export { jsonStringify };
|
|
@ -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 };
|
|
@ -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, };
|
|
@ -0,0 +1 @@
|
|||
export {};
|
|
@ -0,0 +1,3 @@
|
|||
import { Err } from "./types.js";
|
||||
declare function validateObjPropTypes(obj: any, checks: [string, string][]): Err;
|
||||
export { validateObjPropTypes };
|
|
@ -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 };
|
Reference in New Issue