From e1bec6b996d335a0ebb0c637469a4e55d06b2d67 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Wed, 7 Jan 2026 16:48:13 +0900 Subject: [PATCH] Add examples to snippets --- snippets/b58.ts | 418 ++++++++++++++++++++++++++++++ snippets/b64.ts | 655 ++++++++++++++++++++++++++++++++++++++++++++++++ snippets/rlp.ts | 521 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 1594 insertions(+) create mode 100644 snippets/b58.ts create mode 100644 snippets/b64.ts create mode 100644 snippets/rlp.ts diff --git a/snippets/b58.ts b/snippets/b58.ts new file mode 100644 index 0000000..1fd30b8 --- /dev/null +++ b/snippets/b58.ts @@ -0,0 +1,418 @@ +/** + * Base58 encoding/decoding + */ + +export { + encode, + decode +} + + + +//============================================================================= +// ENCODING +//============================================================================= + +/** + * Encode a Uint8Array into base58 + */ +function +encode + (binary : Uint8Array) + : string +{ + let num_leading_zeros : number = nlz(binary); + let rest : Uint8Array = binary.slice(num_leading_zeros); + let ones : string = encode_zeros(num_leading_zeros); + let rest_b58 : string = encode_rest(rest); + let result : string = ones + rest_b58; + return result; +} + + + +/** + * count the number of leading zeros in a uint8array + * + * @internal + */ +function +nlz + (bytes: Uint8Array) + : number +{ + let n = 0; + for (let this_byte of bytes) + { + if (0 === this_byte) { n++; } + else { break; } + } + return n; +} + + + +/** + * Generate a bunch of '1's for however many leading zeros there are + * + * @internal + */ +function +encode_zeros + (how_many : number) + : string +{ + let ones : string = ''; + for (let i = 1; + i <= how_many; + i++) + { + ones += '1'; + } + + return ones; +} + + + +/** + * Encode a Uint8Array that has no leading zeros + * + * @internal + */ +function +encode_rest + (bytes : Uint8Array) + : string +{ + let bytes_bignum : bigint = bytes_to_bigint(bytes); + let result : string = bignum_to_base58(bytes_bignum); + return result; +} + + + +/** + * Convert a bytestring to a bignum + * + * @internal + */ +function +bytes_to_bigint + (bytes: Uint8Array) + : bigint +{ + let acc_bigint : bigint = 0n; + for(let this_byte of bytes) + { + acc_bigint <<= 8n; + acc_bigint += BigInt(this_byte); + } + return acc_bigint; +} + + + +/** + * Convert a BigInt to Base58 + * + * @internal + */ +function +bignum_to_base58 + (q: bigint) + : string +{ + let s = ''; + while (q !== 0n) + { + let this_n : bigint = q % 58n; + q /= 58n; + + let this_b58_char : string = bigint_to_char(this_n); + s = this_b58_char + s; + } + return s; +} + + + +//============================================================================= +// DECODING +//============================================================================= + +/** + * Decode a Base58 string into a Uint8Array + */ +function +decode + (base58: string) + : Uint8Array +{ + let num_leading_ones : number = nlo(base58); + let rest : string = base58.slice(num_leading_ones); + let zeros : Array = decode_ones(num_leading_ones); + let rest_arr : Array = decode_rest(rest); + let pre_result : Array = zeros.concat(rest_arr); + return new Uint8Array(pre_result); +} + + + +/** + * count the number of leading 1 characters in a uint8array + * + * @internal + */ +function +nlo + (base58: string) + : number +{ + let n = 0; + for (let this_char of base58) + { + if ('1' === this_char) { n++; } + else { break; } + } + return n; +} + + + +/** + * Generate a bunch of '0's for however many leading ones there are + * + * @internal + */ +function +decode_ones + (how_many : number) + : Array +{ + let zeros : Array = []; + for (let i = 1; + i <= how_many; + i++) + { + zeros.push(0); + } + + return zeros; +} + + + +/** + * Decode a string that has no leading 1s + * + * @internal + */ +function +decode_rest + (base58: string) + : Array +{ + let result_bignum : bigint = base58_to_bigint(base58); + let result : Array = bigint_to_base256(result_bignum); + return result; +} + + + +/** + * Convert a base58 string to a bignum + * + * @internal + */ +function +base58_to_bigint + (base58: string) + : bigint +{ + let acc_bigint : bigint = 0n; + for(let this_char of base58) + { + acc_bigint *= 58n; + acc_bigint += char_to_bigint(this_char); + } + return acc_bigint; +} + + +/** + * convert a bignum into a byte array + * + * @end + */ +function +bigint_to_base256 + (q: bigint) + : Array +{ + let arr_reverse = []; + while(q !== 0n) + { + let r: number = Number(q % 256n); + q /= 256n; + arr_reverse.push(r); + } + arr_reverse.reverse(); + return arr_reverse; +} + + +//============================================================================= +// TRANSLATION TABLES +//============================================================================= + + +/** + * Base58 integer -> character conversion table + * + * @internal + */ +function +bigint_to_char + (n: bigint) + : string +{ + switch(n) { + case 0n: return '1'; + case 1n: return '2'; + case 2n: return '3'; + case 3n: return '4'; + case 4n: return '5'; + case 5n: return '6'; + case 6n: return '7'; + case 7n: return '8'; + case 8n: return '9'; + case 9n: return 'A'; + case 10n: return 'B'; + case 11n: return 'C'; + case 12n: return 'D'; + case 13n: return 'E'; + case 14n: return 'F'; + case 15n: return 'G'; + case 16n: return 'H'; + case 17n: return 'J'; + case 18n: return 'K'; + case 19n: return 'L'; + case 20n: return 'M'; + case 21n: return 'N'; + case 22n: return 'P'; + case 23n: return 'Q'; + case 24n: return 'R'; + case 25n: return 'S'; + case 26n: return 'T'; + case 27n: return 'U'; + case 28n: return 'V'; + case 29n: return 'W'; + case 30n: return 'X'; + case 31n: return 'Y'; + case 32n: return 'Z'; + case 33n: return 'a'; + case 34n: return 'b'; + case 35n: return 'c'; + case 36n: return 'd'; + case 37n: return 'e'; + case 38n: return 'f'; + case 39n: return 'g'; + case 40n: return 'h'; + case 41n: return 'i'; + case 42n: return 'j'; + case 43n: return 'k'; + case 44n: return 'm'; + case 45n: return 'n'; + case 46n: return 'o'; + case 47n: return 'p'; + case 48n: return 'q'; + case 49n: return 'r'; + case 50n: return 's'; + case 51n: return 't'; + case 52n: return 'u'; + case 53n: return 'v'; + case 54n: return 'w'; + case 55n: return 'x'; + case 56n: return 'y'; + case 57n: return 'z'; + default: + throw new Error('invalid base58 bigint: ' + n); + } +} + + + +/** + * Base58 character -> integer conversion table + * + * @internal + */ +function +char_to_bigint + (s: string) + : bigint +{ + switch(s) { + case '1': return 0n; + case '2': return 1n; + case '3': return 2n; + case '4': return 3n; + case '5': return 4n; + case '6': return 5n; + case '7': return 6n; + case '8': return 7n; + case '9': return 8n; + case 'A': return 9n; + case 'B': return 10n; + case 'C': return 11n; + case 'D': return 12n; + case 'E': return 13n; + case 'F': return 14n; + case 'G': return 15n; + case 'H': return 16n; + case 'J': return 17n; + case 'K': return 18n; + case 'L': return 19n; + case 'M': return 20n; + case 'N': return 21n; + case 'P': return 22n; + case 'Q': return 23n; + case 'R': return 24n; + case 'S': return 25n; + case 'T': return 26n; + case 'U': return 27n; + case 'V': return 28n; + case 'W': return 29n; + case 'X': return 30n; + case 'Y': return 31n; + case 'Z': return 32n; + case 'a': return 33n; + case 'b': return 34n; + case 'c': return 35n; + case 'd': return 36n; + case 'e': return 37n; + case 'f': return 38n; + case 'g': return 39n; + case 'h': return 40n; + case 'i': return 41n; + case 'j': return 42n; + case 'k': return 43n; + case 'm': return 44n; + case 'n': return 45n; + case 'o': return 46n; + case 'p': return 47n; + case 'q': return 48n; + case 'r': return 49n; + case 's': return 50n; + case 't': return 51n; + case 'u': return 52n; + case 'v': return 53n; + case 'w': return 54n; + case 'x': return 55n; + case 'y': return 56n; + case 'z': return 57n; + default: + throw new Error('invalid base58 char: ' + s); + } +} diff --git a/snippets/b64.ts b/snippets/b64.ts new file mode 100644 index 0000000..d82cd73 --- /dev/null +++ b/snippets/b64.ts @@ -0,0 +1,655 @@ +/** + * Base64 Utility Functions in TypeScript + */ + +export { + encode, + decode +} + + + +/** + * Encode an array of bytes as a Uint8Array in base64 notation. + */ +function +encode + (bytes: Uint8Array) + : string +{ + // slice the array + // length of head is a multiple of 3 + // treat the tail as a special case + let {head, tail, tail_len} = slice3k(bytes); + let head_str : string = encode_head(head); + let tail_str : string = encode_tail(tail, tail_len); + return head_str + tail_str; +} + + + +type slice3k + = {head : Uint8Array, + tail : Uint8Array, + tail_len : number}; + +/** + * Take a Uint8Array, take the first 3k (k >= 0) bytes, put them in head, and + * the remaining 0,1, or 2 bytes, put them in tail + * + * @internal + */ +function +slice3k + (bytes: Uint8Array) + : slice3k +{ + let len : number = bytes.length; + // too lazy to look up how to do integer division in js so this will do + let tail_len : number = len % 3; + let head_len : number = len - tail_len; + // for slice: + // first argument is the 0-index of the start + // second - first is the length of the slice + let head : Uint8Array = bytes.slice(0, head_len); + // empty second argument means go to the end + let tail : Uint8Array = bytes.slice(head_len); + return {head : head, + tail : tail, + tail_len : tail_len}; +} + + + +/** + * Encode a Uint8Array whose length is known to be a multiple of 3 + * + * @internal + */ +function +encode_head + (head_bytes: Uint8Array) + : string +{ + // can assume length of bytes is a multiple of 3 + // start index at 0 + // increment by 3 + let head_bytes_len : number = head_bytes.length; + let max_idx0 : number = head_bytes_len - 1; + + let head_str_acc : string = ''; + for(let this_3slice_start_idx0 = 0; + this_3slice_start_idx0 <= max_idx0; + this_3slice_start_idx0 += 3) + { + let this_3slice_bytes : Uint8Array = head_bytes.slice(this_3slice_start_idx0, this_3slice_start_idx0 + 3); + let this_3slice_str : string = encode3(this_3slice_bytes); + head_str_acc += this_3slice_str; + } + + return head_str_acc; +} + + + +/** + * Encode a 3 bytes into base64 notation + * + * @internal + */ +function +encode3 + (bytes: Uint8Array) + : string +{ + let b0 : number = bytes[0]; + let b1 : number = bytes[1]; + let b2 : number = bytes[2]; + + // ABCDEFGH 12345678 abcdefgh + // b0 b1 b2 + // ABCDEF GH1234 5678ab cdefgh + // n0 n1 n2 n3 + let n0 : number = b0 >> 2; + // b0 = ABCDEFGH + // 4 = _____1__ + // b0 % 4 = ______GH + // (b0 % 4) << 4 = __GH____ + // b1 = 12345678 + // b1 >> 4 = ____1234 + // n1 = __GH1234 + let n1 : number = ((b0 % 4) << 4) + (b1 >> 4); + // b1 = 12345678 + // 16 = ___1____ + // b1 % 16 = ____5678 + // (b1 % 16) << 2 = __5678__ + // b2 = abcdefgh + // b2 >> 6 = ______ab + // n2 = __5678ab + let n2 : number = ((b1 % 16) << 2) + (b2 >> 6); + // b2 = abcdefgh + // 64 = _1______ + // n3 = __cdefgh + let n3 : number = b2 % 64; + + // convert to chars + let s0 : string = int2char(n0); + let s1 : string = int2char(n1); + let s2 : string = int2char(n2); + let s3 : string = int2char(n3); + + // retrvn + return s0 + s1 + s2 + s3; +} + + + +/** + * Encode the final 0, 1, or 2 bytes + * + * @internal + */ +function +encode_tail + (tail_bytes : Uint8Array, + tail_len : number) + : string +{ + switch(tail_len) { + case 0: return ''; + case 1: return encode1(tail_bytes); + case 2: return encode2(tail_bytes); + default: + throw new Error('encode_tail with tail_len = ' + tail_len); + } +} + + + +/** + * Encode a single byte + * + * @internal + */ +function +encode1 + (bytes: Uint8Array) + : string +{ + let b0 : number = bytes[0]; + // n0 = __ABCDEF + // b0 = ABCDEFGH + // b0 >> 2 = __ABCDEF + let n0 : number = b0 >> 2; + // n1 = __GH____ + // b0 = ABCDEFGH + // 4 = _____1__ + // b0 % 4 = ______GH + // (b0 % 4) << 4 = __GH____ + let n1 : number = (b0 % 4) << 4; + + return int2char(n0) + int2char(n1) + '=='; +} + + + +/** + * Encode two bytes + * + * @internal + */ +function +encode2 + (bytes: Uint8Array) + : string +{ + let b0 : number = bytes[0]; + let b1 : number = bytes[1]; + + // ABCDEFGH 12345678 + // b0 b1 + // ABCDEF GH1234 5678__ + // n0 n1 n2 + let n0 : number = b0 >> 2; + // b0 = ABCDEFGH + // 4 = _____1__ + // b0 % 4 = ______GH + // (b0 % 4) << 4 = __GH____ + // b1 = 12345678 + // b1 >> 4 = ____1234 + // n1 = __GH1234 + let n1 : number = ((b0 % 4) << 4) + (b1 >> 4); + // b1 = 12345678 + // 16 = ___1____ + // b1 % 16 = ____5678 + // (b1 % 16) << 2 = __5678__ + // n2 = __5678__ + let n2 : number = (b1 % 16) << 2; + + // convert to chars + let s0 : string = int2char(n0); + let s1 : string = int2char(n1); + let s2 : string = int2char(n2); + + // retrvn + return s0 + s1 + s2 + '='; +} + + + +/** + * Decode a base64-encoded string + */ +function +decode + (base64_str : string) + : Uint8Array +{ + // length of the string is guaranteed to be a multiple of 4 + // if the string is empty, return the empty array + let len = base64_str.length; + // this branching contains the implicit assertion that the length is a + // multiple of 4. If this is not true, the bottom branch is triggered. + // general case goes first because speeeeeed + if ( (4 < len) + && (0 === (len % 4))) + { + // split the head and tail + let tail_start_idx0 : number = len - 4; + let head_s : string = base64_str.slice(0, tail_start_idx0); + let tail_s : string = base64_str.slice(tail_start_idx0); + // Using arrays because Uint8Arrays don't have a concat operation + let head_arr : Array = decode_head(head_s); + let tail_arr : Array = decode_tail(tail_s); + // silly to put these in variables but this is exactly the type of + // situation where JS type insanity shows up + // + // see: i forgot + // > [1,2,3] + [4,5,6] + // '1,2,34,5,6' + // + // Originally, I used + like some sort of moron who codes in a sane + // language + // + // seriously what is this language + // + // this is some clown behavior + let total_arr : Array = head_arr.concat(tail_arr); + return new Uint8Array(total_arr); + } + // special case if the length is exactly 4 + else if (4 === len) + { + // it's just a tail + return new Uint8Array(decode_tail(base64_str)); + } + // empty string + else if (0 === len) + { + return new Uint8Array([]); + } + else + { + throw new Error('base64 decode: invalid string length: ' + len); + } +} + + + +/** + * Decode a string known to not have any padding + * + * @internal + */ +function +decode_head + (s: string) + : Array +{ + // go 4 characters at a time + let max_i0 : number = s.length - 1; + let decoded_acc : Array = []; + for(let i0 = 0; + i0 <= max_i0; + i0 += 4) + { + let this_slice_s : string = s.slice(i0, i0 + 4); + let this_slice_arr : Array = decode3(this_slice_s); + // update accumulator + decoded_acc = decoded_acc.concat(this_slice_arr); + } + return decoded_acc; +} + + + +/** + * Decode 4 characters that correspond to either 3 bytes, 2, bytes, or 1 byte + * + * @internal + */ +function +decode_tail + (s: string) + : Array +{ + // all that matters right now is the last 2 chars + // s0, s1, s2, s3 + // 0 based indexing is so annoying + let s2 = s[2]; + let s3 = s[3]; + + // braaaaaaaaaaaaaaaaaench + // two equals signs means 1 byte + if (('=' === s3) && ('=' === s2)) { + return decode1(s); + } + // one equals sign means 2 bytes + else if (('=' === s3)) { + return decode2(s); + } + // 0 equals signs means 3 bytes + else { + return decode3(s); + } +} + + + +/** + * Decode a 4-character long base64 string corresponding to 3 bytes + * + * @internal + */ +function +decode3 + (s: string) + : Array +{ + // pull out strings + let s0 : string = s[0]; + let s1 : string = s[1]; + let s2 : string = s[2]; + let s3 : string = s[3]; + + // convert to numbers + let n0 : number = char2int(s0); + let n1 : number = char2int(s1); + let n2 : number = char2int(s2); + let n3 : number = char2int(s3); + + // abcdef gh1234 5678ab cdefgh + // n0 n1 n2 n3 + // abcdefgh 12345678 abcdefgh + // b0 b1 b2 + + // n0 = __abcdef + // n1 = __gh1234 + // n0 << 2 = abcdef__ + // n1 >> 4 = ______gh + // b0 = abcdefgh + let b0 : number = (n0 << 2) + (n1 >> 4); + // n1 = __gh1234 + // 16 = ___1____ + // n1 % 16 = ____1234 + // (n1 % 16) << 4 = 1234____ + // n2 = __5678ab + // n2 >> 2 = ____5678 + // b1 = 12345678 + let b1 : number = ((n1 % 16) << 4) + (n2 >> 2); + // n2 = __5678ab + // 4 = _____1__ + // n2 % 4 = ______ab + // (n2 % 4) << 6 = ab______ + // n3 = __cdefgh + let b2 : number = ((n2 % 4) << 6) + n3; + + return [b0, b1, b2]; +} + + + +/** + * Decode a 4-character long base64 string corresponding to 2 bytes + * + * @internal + */ +function +decode2 + (s: string) + : Array +{ + // xyz= + // pull out strings + let s0 : string = s[0]; + let s1 : string = s[1]; + let s2 : string = s[2]; + + // convert to numbers + let n0 : number = char2int(s0); + let n1 : number = char2int(s1); + let n2 : number = char2int(s2); + + // abcdef gh1234 5678__ + // n0 n1 n2 + // abcdefgh 12345678 + // b0 b1 + + // n0 = __abcdef + // n1 = __gh1234 + // n0 << 2 = abcdef__ + // n1 >> 4 = ______gh + // b0 = abcdefgh + let b0 : number = (n0 << 2) + (n1 >> 4); + // n1 = __gh1234 + // 16 = ___1____ + // n1 % 16 = ____1234 + // (n1 % 16) << 4 = 1234____ + // n2 = __5678__ + // n2 >> 2 = ____5678 + // b1 = 12345678 + let b1 : number = ((n1 % 16) << 4) + (n2 >> 2); + + return [b0, b1]; +} + + + +/** + * Decode a 4-character long base64 string corresponding to 2 bytes + * + * @internal + */ +function +decode1 + (s: string) + : Array +{ + // xy== + // pull out strings + let s0 : string = s[0]; + let s1 : string = s[1]; + + // convert to numbers + let n0 : number = char2int(s0); + let n1 : number = char2int(s1); + + // abcdef gh____ + // n0 n1 + // abcdefgh + // b0 + + // n0 = __abcdef + // n1 = __gh____ + // n0 << 2 = abcdef__ + // n1 >> 4 = ______gh + // b0 = abcdefgh + let b0 : number = (n0 << 2) + (n1 >> 4); + + return [b0]; +} + + + + +// FIXME: these tables would *probably* be faster if they were made into objects + +/** + * Conversion table for base64 encode + * + * @internal + */ +function +int2char + (n: number) + : string +{ + switch(n) { + case 0: return 'A'; + case 1: return 'B'; + case 2: return 'C'; + case 3: return 'D'; + case 4: return 'E'; + case 5: return 'F'; + case 6: return 'G'; + case 7: return 'H'; + case 8: return 'I'; + case 9: return 'J'; + case 10: return 'K'; + case 11: return 'L'; + case 12: return 'M'; + case 13: return 'N'; + case 14: return 'O'; + case 15: return 'P'; + case 16: return 'Q'; + case 17: return 'R'; + case 18: return 'S'; + case 19: return 'T'; + case 20: return 'U'; + case 21: return 'V'; + case 22: return 'W'; + case 23: return 'X'; + case 24: return 'Y'; + case 25: return 'Z'; + case 26: return 'a'; + case 27: return 'b'; + case 28: return 'c'; + case 29: return 'd'; + case 30: return 'e'; + case 31: return 'f'; + case 32: return 'g'; + case 33: return 'h'; + case 34: return 'i'; + case 35: return 'j'; + case 36: return 'k'; + case 37: return 'l'; + case 38: return 'm'; + case 39: return 'n'; + case 40: return 'o'; + case 41: return 'p'; + case 42: return 'q'; + case 43: return 'r'; + case 44: return 's'; + case 45: return 't'; + case 46: return 'u'; + case 47: return 'v'; + case 48: return 'w'; + case 49: return 'x'; + case 50: return 'y'; + case 51: return 'z'; + case 52: return '0'; + case 53: return '1'; + case 54: return '2'; + case 55: return '3'; + case 56: return '4'; + case 57: return '5'; + case 58: return '6'; + case 59: return '7'; + case 60: return '8'; + case 61: return '9'; + case 62: return '+'; + case 63: return '/'; + default: throw new Error("invalid base64 encode byte: " + n); + } +} + + + +/** + * Conversion table for base64 decode + * + * @internal + */ +function +char2int + (s: string) + : number +{ + switch(s) { + case 'A': return 0; + case 'B': return 1; + case 'C': return 2; + case 'D': return 3; + case 'E': return 4; + case 'F': return 5; + case 'G': return 6; + case 'H': return 7; + case 'I': return 8; + case 'J': return 9; + case 'K': return 10; + case 'L': return 11; + case 'M': return 12; + case 'N': return 13; + case 'O': return 14; + case 'P': return 15; + case 'Q': return 16; + case 'R': return 17; + case 'S': return 18; + case 'T': return 19; + case 'U': return 20; + case 'V': return 21; + case 'W': return 22; + case 'X': return 23; + case 'Y': return 24; + case 'Z': return 25; + case 'a': return 26; + case 'b': return 27; + case 'c': return 28; + case 'd': return 29; + case 'e': return 30; + case 'f': return 31; + case 'g': return 32; + case 'h': return 33; + case 'i': return 34; + case 'j': return 35; + case 'k': return 36; + case 'l': return 37; + case 'm': return 38; + case 'n': return 39; + case 'o': return 40; + case 'p': return 41; + case 'q': return 42; + case 'r': return 43; + case 's': return 44; + case 't': return 45; + case 'u': return 46; + case 'v': return 47; + case 'w': return 48; + case 'x': return 49; + case 'y': return 50; + case 'z': return 51; + case '0': return 52; + case '1': return 53; + case '2': return 54; + case '3': return 55; + case '4': return 56; + case '5': return 57; + case '6': return 58; + case '7': return 59; + case '8': return 60; + case '9': return 61; + case '+': return 62; + case '/': return 63; + default: throw new Error("invalid base64 character: " + s); + } +} diff --git a/snippets/rlp.ts b/snippets/rlp.ts new file mode 100644 index 0000000..8c48b75 --- /dev/null +++ b/snippets/rlp.ts @@ -0,0 +1,521 @@ +/** + * Ethereum Recursive-Length Prefix Encoding implementation + * + * Two reference implementations: + * + * 1. Ethereum Python implementation + * 2. My Erlang implementation (agrees with Ethereum Python in randomized + * tests) + * + * This work can be found in ../test/testgen/ + * + * FIXME: need to have Safe assertions for "i am decoding a list", "i am + * decoding a binary", "I am decoding something with no remainder", etc + * + * @module + */ + +export { + decoded_data, + decode_result, + decode, + encode +} + + +//============================================================================= +//============================================================================= +// DECODING +//============================================================================= +//============================================================================= + + +/** + * Data that RLP can encode. Also the result of decoding. + */ +type decoded_data + = Uint8Array + | Array; + + + +/** + * Decoding can have a remainder + */ +type decode_result + = {decoded_data : decoded_data, + remainder : Uint8Array}; + + + +/** + * Decode an RLP-encoded bytestring into a `decode_result` (decoded data + + * whatever wasn't consumed) + */ +function +decode + (bytes: Uint8Array) + : decode_result +{ + // check the first byte + let first_byte: number = bytes[0]; + let rest : Uint8Array = bytes.slice(1); + // if the first byte is between 0 and 127, that is the data + if + (first_byte <= 127) { + return dr(new Uint8Array([first_byte]), rest); + } + // if the first byte is between 128 and 183 = 128 + 55, it is a bytestring + // and the length is Byte - 128 + else if + (first_byte <= 183) { + let payload_byte_length : number = first_byte - 128; + let payload : Uint8Array = rest.slice(0, payload_byte_length); + let rest2 : Uint8Array = rest.slice(payload_byte_length); + return dr(payload, rest2); + } + // if the first byte is between 184 = 183 + 1 and 191 = 183 + 8, it is a + // bytestring. the byte length of bytestring is FirstByte - 183. Then pull + // out the actual data + else if + (first_byte <= 191) { + let byte_length_of_byte_length : number = first_byte - 183; + let bytes_of_byte_length : Uint8Array = rest.slice(0, byte_length_of_byte_length); + let byte_length : number = bytes_to_number(bytes_of_byte_length); + let bytes : Uint8Array = rest.slice(byte_length_of_byte_length, + byte_length + byte_length_of_byte_length); + let rest2 : Uint8Array = rest.slice(byte_length + byte_length_of_byte_length); + return dr(bytes, rest2); + } + // If the first byte is between 192 and 247 = 192 + 55, it is a list. The + // byte length of the list-payload is FirstByte - 192. Then the list + // payload, which needs to be decoded on its own. + else if + (first_byte <= 247) { + let byte_length_of_list : number = first_byte - 192; + let list_payload : Uint8Array = rest.slice(0, byte_length_of_list); + let list : Array = decode_list(list_payload); + let rest2 : Uint8Array = rest.slice(byte_length_of_list); + return dr(list, rest2); + } + // If the first byte is between 248 = 247 + 1 and 255 = 247 + 8, it is a + // list. The byte length of the byte length of the list-payload is + // FirstByte - 247. Then the byte length of the list. Then the list + // payload, which needs to be decoded on its own. + else { + let byte_length_of_byte_length : number = first_byte - 247; + let bytes_of_byte_length : Uint8Array = rest.slice(0, byte_length_of_byte_length); + let byte_length : number = bytes_to_number(bytes_of_byte_length); + let list_bytes : Uint8Array = rest.slice(byte_length_of_byte_length, + byte_length + byte_length_of_byte_length); + let list : Array = decode_list(list_bytes); + let rest2 : Uint8Array = rest.slice(byte_length + byte_length_of_byte_length); + return dr(list, rest2); + } +} + + + +/** + * Decode a list payload (non-prefixed) into an `Array`. + * + * Repeatedly decodes an element off the list until remainder is empty. + * + * @internal + */ +function +decode_list + (bytes: Uint8Array) + : Array +{ + let arr : Array = []; + while (bytes.length > 0) { + // grab an item off the bytes + let {decoded_data, remainder} = decode(bytes); + // push it + arr.push(decoded_data); + // update bytes + bytes = remainder; + } + return arr; +} + + + +/** + * Convert bytestring to number + * + * @internal + */ +function +bytes_to_number + (bytes: Uint8Array) + : number +{ + let n : number = 0; + for (let b of bytes) + { + n <<= 8; + n += b; + } + return n; +} + + + +/** + * Make a `decode_result` containing the two arguments + * + * @internal + */ +function +dr + (x : decoded_data, + y : Uint8Array) + : decode_result +{ + return {decoded_data : x, + remainder : y}; +} + + + +//============================================================================= +//============================================================================= +// ENCODING +//============================================================================= +//============================================================================= + +/** + * Encode some decoded data + */ +function +encode + (data: decoded_data) + : Uint8Array +{ + // is it an array or data + if + (is_binary(data)) { + return encode_binary(data as Uint8Array); + } + else if + (is_list(data)) { + return encode_list(data as Array); + } + else { + throw new Error('encode told to encode something that is not an array or a binary: ' + data); + } +} + + + +/** + * Encode a binary into RLP + * + * @internal + */ +function +encode_binary + (bytes: Uint8Array) + : Uint8Array +{ + let len: number = bytes.length; + // single byte case when the byte is between 0..127 + // result is the bytestring containing the byte itself + if + ((len === 1) && + (bytes[0] <= 127)){ + return bytes; + } + // if the bytestring is 0..55 bytes long, the first byte in the result is + // 128 + Length, the rest of the result bytestring is the input string + else if + (len <= 55) { + // construct the result + // <<128 + Len, Bytes/binary>> + let result : Uint8Array = new Uint8Array(len + 1); + // first byte is 128 + length + result[0] = 128 + len; + // copy input bytes into result + for (let input_idx0 = 0; + input_idx0 < len; + input_idx0++) + { + let result_idx0 : number = input_idx0 + 1; + result[result_idx0] = bytes[input_idx0]; + } + return result; + } + // if the bytestring is more than 55 bytes long, the first byte is 183 + + // ByteLengthOfByteLength, followed by the byte length, followed by the + // bytes + else { + let len_bytes : Uint8Array = encode_unsigned(len); + let len_bytes_length : number = len_bytes.length; + // total array is + // <<183 + len_bytes_length, len_bytes, Bytes>> + // 1 byte len_bytes_length bytes len bytes + let result : Uint8Array = new Uint8Array(1 + len_bytes_length + len); + // <<183 + len_bytes_length, len_bytes, Bytes>> + // 1 byte len_bytes_length bytes len bytes + // + // ^ YOU ARE HERE + result[0] = 183 + len_bytes_length; + // copy len_bytes into result + for (let len_bytes_idx0 = 0; + len_bytes_idx0 < len_bytes_length; + len_bytes_idx0++) + { + // <<183 + len_bytes_length, len_bytes, Bytes>> + // 1 byte len_bytes_length bytes len bytes + // + // ^ YOU ARE HERE + let result_idx0: number = len_bytes_idx0 + 1; + result[result_idx0] = len_bytes[len_bytes_idx0]; + } + // copy original byte array into result + for (let input_idx0 = 0; + input_idx0 < len; + input_idx0++) + { + // <<183 + len_bytes_length, len_bytes, Bytes>> + // 1 byte len_bytes_length bytes len bytes + // + // ^ YOU ARE HERE + let result_idx0: number = input_idx0 + (1 + len_bytes_length); + result[result_idx0] = bytes[input_idx0]; + } + // finally, return the result + return result; + } +} + + + +/** + * Encode a list + * + * @internal + */ +function +encode_list + (list: Array) + : Uint8Array +{ + // first encode every element in the list, then branch + let payloads : Array = list.map(encode); + let payload : Uint8Array = uint8arr_concat(payloads); + let payload_size : number = payload.length; + // if the payload size is in 0..55, then the first byte is 192 + payload + // size, followed by the payload + if + (payload_size <= 55) { + // result: <<(192 + Payload_Size), Payload/binary >>; + // 1 byte payload_size bytes + let result : Uint8Array = new Uint8Array(1 + payload_size); + // result: <<(192 + Payload_Size), Payload/binary >>; + // 1 byte payload_size bytes + // + // ^ YOU ARE HERE + result[0] = 192 + payload_size; + // copy the rest of the payload into result + for (let payload_idx0 = 0; + payload_idx0 < payload_size; + payload_idx0++) + { + // result: <<(192 + Payload_Size), Payload/binary >>; + // 1 byte payload_size bytes + // + // ^ YOU ARE HERE + let result_idx0: number = payload_idx0 + 1; + result[result_idx0] = payload[payload_idx0]; + } + return result; + } + // if the payload size is greater than 55, the first byte is 247 + + // size_of_payload_size, followed by the payload size, followed by the + // payload + else { + // compute the payload size size + let payload_size_bytes : Uint8Array = encode_unsigned(payload_size); + let payload_size_size : number = payload_size_bytes.length; + // result = <<(247 + payload_size_size), payload_size_bytes/binary, payload/binary >> + // 1 byte payload_size_size bytes payload_size bytes + let result: Uint8Array = new Uint8Array(1 + payload_size_size + payload_size); + // result = <<(247 + payload_size_size), payload_size_bytes/binary, payload/binary >> + // 1 byte payload_size_size bytes payload_size bytes + // + // ^ YOU ARE HERE + // first byte is 247 + payload_size_size + result[0] = 247 + payload_size_size; + // copy the payload_size_bytes into result + for (let psb_idx0 = 0; + psb_idx0 < payload_size_size; + psb_idx0++) + { + // result = <<(247 + payload_size_size), payload_size_bytes/binary, payload/binary >> + // 1 byte payload_size_size bytes payload_size bytes + // + // ^ YOU ARE HERE + // offset is 1 byte for this + let result_idx0 : number = psb_idx0 + 1; + result[result_idx0] = payload_size_bytes[psb_idx0]; + } + // copy the payload into result + for (let pb_idx0 = 0; + pb_idx0 < payload_size; + pb_idx0++) + { + // result = <<(247 + payload_size_size), payload_size_bytes/binary, payload/binary >> + // 1 byte payload_size_size bytes payload_size bytes + // + // ^ YOU ARE HERE + // offset is 1 + payload_size_size bytes for this + let result_idx0 : number = pb_idx0 + (1 + payload_size_size); + result[result_idx0] = payload[pb_idx0]; + } + // finally, return result + return result; + } +} + + + +/** + * Encode a number as a bytestring + * + * Not for general use, this assumes the input number is greater than 55 + * + * @internal + */ +function +encode_unsigned + (n: number) + : Uint8Array +{ + // can assume that n initially is greater than 55 + // have to encode it in reverse order + let arr : Array = []; + // repeated division by 256 with remainder + // + // example: suppose bign was 1234 and we were dividing by 10 + // + // iteration 0: + // bign = 1234 + // arr = [] + // --- + // bign / 10 = 123 + // bign % 10 = 4 + // --- + // bign = 123 + // arr = [4] + // + // iteration 1: + // bign = 123 + // arr = [4] + // --- + // bign / 10 = 12 + // bign % 10 = 3 + // --- + // bign = 12 + // arr = [4, 3] + // + // iteration 2: + // bign = 12 + // arr = [4, 3] + // --- + // bign / 10 = 1 + // bign % 10 = 2 + // --- + // bign = 1 + // arr = [4, 3, 2] + // + // iteration 3: + // bign = 1 + // arr = [4, 3, 2] + // --- + // bign / 10 = 0 + // bign % 10 = 1 + // --- + // bign = 0 + // arr = [4, 3, 2, 1] + // + // iteration 4: + // bign = 0 + // arr = [4, 3, 2, 1] + // --- + // DONE + while (n > 0) { + arr.push(n % 256); + n >>= 8; + } + // reverse and make into Uint8Array + arr.reverse(); + return new Uint8Array(arr); +} + + + +/** + * concatenate an array of `Uint8Array`s into a single Uint8Array + * + * @internal + */ +function +uint8arr_concat + (arrs: Array) + : Uint8Array +{ + // total length + let total_len = arrs.reduce(// fold + function (acc_len: number, this_uint8array: Uint8Array): number { + return acc_len + this_uint8array.length; + }, + // initial accumulator + 0); + // start up result + let result : Uint8Array = new Uint8Array(total_len); + let result_idx0 : number = 0; + // concatenate + for (let this_uint8arr of arrs) { + // bytewise copy of this uint8array + for (let this_uint8arr_idx0 = 0; + this_uint8arr_idx0 < this_uint8arr.length; + this_uint8arr_idx0++) + { + // copy byte and increment result idx0 + result[result_idx0] = this_uint8arr[this_uint8arr_idx0]; + result_idx0++; + } + } + return result; +} + +/** + * returns true if input is `instanceof Uint8Array` + * + * @internal + */ +function +is_binary + (x: any) + : boolean +{ + return (x instanceof Uint8Array); +} + + + +/** + * returns true if input is `instanceof Array` + * + * @internal + */ +function +is_list + (x: any) + : boolean +{ + return (x instanceof Array); +}