wrote an encoder and it worked on the first try. wtf
This commit is contained in:
@@ -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 {
|
export {
|
||||||
decoded_data,
|
decoded_data,
|
||||||
decode_result,
|
decode_result,
|
||||||
decode
|
decode,
|
||||||
|
encode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
//=============================================================================
|
||||||
|
// DECODING
|
||||||
|
//=============================================================================
|
||||||
|
//=============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data that RLP can encode. Also the result of decoding.
|
||||||
|
*/
|
||||||
type decoded_data
|
type decoded_data
|
||||||
= Uint8Array
|
= Uint8Array
|
||||||
| Array<decoded_data>;
|
| Array<decoded_data>;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decoding can have a remainder
|
||||||
|
*/
|
||||||
type decode_result
|
type decode_result
|
||||||
= {decoded_data : decoded_data,
|
= {decoded_data : decoded_data,
|
||||||
remainder : Uint8Array};
|
remainder : Uint8Array};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode an RLP-encoded bytestring into a `decode_result` (decoded data +
|
||||||
|
* whatever wasn't consumed)
|
||||||
|
*/
|
||||||
function
|
function
|
||||||
decode(bytes: Uint8Array): decode_result {
|
decode(bytes: Uint8Array): decode_result {
|
||||||
// check the first byte
|
// check the first byte
|
||||||
@@ -71,7 +108,17 @@ decode(bytes: Uint8Array): decode_result {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function decode_list(bytes: Uint8Array): Array<decoded_data> {
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a list payload (non-prefixed) into an `Array<decoded_data>`.
|
||||||
|
*
|
||||||
|
* Repeatedly decodes an element off the list until remainder is empty.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
decode_list(bytes: Uint8Array): Array<decoded_data> {
|
||||||
let arr : Array<decoded_data> = [];
|
let arr : Array<decoded_data> = [];
|
||||||
while (bytes.length > 0) {
|
while (bytes.length > 0) {
|
||||||
// grab an item off the bytes
|
// grab an item off the bytes
|
||||||
@@ -84,8 +131,15 @@ function decode_list(bytes: Uint8Array): Array<decoded_data> {
|
|||||||
return arr;
|
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;
|
let n : number = 0;
|
||||||
for (let b of bytes) {
|
for (let b of bytes) {
|
||||||
n <<= 8;
|
n <<= 8;
|
||||||
@@ -94,6 +148,333 @@ function bytes_to_number(bytes: Uint8Array) {
|
|||||||
return n;
|
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};
|
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<decoded_data>);
|
||||||
|
}
|
||||||
|
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<decoded_data>): Uint8Array {
|
||||||
|
// first encode every element in the list, then branch
|
||||||
|
let payloads : Array<Uint8Array> = 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<number> = [];
|
||||||
|
// 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>): 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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -168,21 +168,21 @@ function rlp_tests(): void {
|
|||||||
case_n++;
|
case_n++;
|
||||||
|
|
||||||
let {encoded, decoded} = this_case;
|
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 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);
|
let decodes_correctly = deepeq(decoded, my_decoded);
|
||||||
|
|
||||||
//if (!encodes_correctly) {
|
if (!encodes_correctly) {
|
||||||
// rlp_pre.innerHTML +=
|
rlp_pre.innerHTML +=
|
||||||
// '===================================\n' +
|
'===================================\n' +
|
||||||
// 'FAILED CASE: encode\n' +
|
'FAILED CASE: encode\n' +
|
||||||
// '===================================\n' +
|
'===================================\n' +
|
||||||
// 'decoded : ' + decoded + '\n' +
|
'decoded : ' + decoded + '\n' +
|
||||||
// 'expected: ' + encoded + '\n' +
|
'expected: ' + encoded + '\n' +
|
||||||
// 'actual : ' + my_encoded + '\n\n' ;
|
'actual : ' + my_encoded + '\n\n' ;
|
||||||
//}
|
}
|
||||||
|
|
||||||
if (!decodes_correctly) {
|
if (!decodes_correctly) {
|
||||||
rlp_pre.innerHTML +=
|
rlp_pre.innerHTML +=
|
||||||
|
|||||||
Reference in New Issue
Block a user