From 64f9752ee5c1dfa7fd8ddb5a1e7c9fdb6125010c Mon Sep 17 00:00:00 2001 From: Peter Harpending Date: Sun, 23 Oct 2022 20:19:21 -0600 Subject: [PATCH] wrote an encoder and it worked on the first try. wtf --- bindings/typescript/src/rlp.ts | 391 ++++++++++++++++++++++++++- bindings/typescript/test/src/test.ts | 22 +- 2 files changed, 397 insertions(+), 16 deletions(-) diff --git a/bindings/typescript/src/rlp.ts b/bindings/typescript/src/rlp.ts index e718bd9..0d24785 100644 --- a/bindings/typescript/src/rlp.ts +++ b/bindings/typescript/src/rlp.ts @@ -1,17 +1,54 @@ +/** + * 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/ + * + * @module + */ + export { decoded_data, decode_result, - decode + 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 @@ -71,7 +108,17 @@ decode(bytes: Uint8Array): decode_result { } } -function decode_list(bytes: Uint8Array): Array { + + +/** + * 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 @@ -84,8 +131,15 @@ function decode_list(bytes: Uint8Array): Array { return arr; } -// convert bytestring to number -function bytes_to_number(bytes: Uint8Array) { + + +/** + * Convert bytestring to number + * + * @internal + */ +function +bytes_to_number(bytes: Uint8Array): number { let n : number = 0; for (let b of bytes) { n <<= 8; @@ -94,6 +148,333 @@ function bytes_to_number(bytes: Uint8Array) { return n; } -function dr(x : decoded_data, y : Uint8Array) { + + +/** + * Make a `decode_result` containing the two arguments + * + * @internal + */ +function +dr(x : decoded_data, y : Uint8Array) { 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); +} diff --git a/bindings/typescript/test/src/test.ts b/bindings/typescript/test/src/test.ts index 6ccc30e..c1ea915 100644 --- a/bindings/typescript/test/src/test.ts +++ b/bindings/typescript/test/src/test.ts @@ -168,21 +168,21 @@ function rlp_tests(): void { case_n++; let {encoded, decoded} = this_case; - //let my_encoded = rlp.encode(decoded); + let my_encoded = rlp.encode(decoded); let my_decoded = rlp.decode(encoded).decoded_data; - //let encodes_correctly = (encoded === my_encoded); + let encodes_correctly = uint8arr_eq(encoded, my_encoded); let decodes_correctly = deepeq(decoded, my_decoded); - //if (!encodes_correctly) { - // rlp_pre.innerHTML += - // '===================================\n' + - // 'FAILED CASE: encode\n' + - // '===================================\n' + - // 'decoded : ' + decoded + '\n' + - // 'expected: ' + encoded + '\n' + - // 'actual : ' + my_encoded + '\n\n' ; - //} + if (!encodes_correctly) { + rlp_pre.innerHTML += + '===================================\n' + + 'FAILED CASE: encode\n' + + '===================================\n' + + 'decoded : ' + decoded + '\n' + + 'expected: ' + encoded + '\n' + + 'actual : ' + my_encoded + '\n\n' ; + } if (!decodes_correctly) { rlp_pre.innerHTML +=