From 127be10d031def66b5bcefead0d7b43882912869 Mon Sep 17 00:00:00 2001 From: Peter Harpending Date: Sun, 6 Nov 2022 12:20:45 -0700 Subject: [PATCH] [wip] code cleanups, making style uniform in vanillae ts --- bindings/typescript/notes | 7 +- bindings/typescript/scratch | 159 +++++++++++++++++ bindings/typescript/src/anth.ts | 308 ++++++++++++-------------------- bindings/typescript/src/b58.ts | 114 +++++++++--- bindings/typescript/src/b64.ts | 90 +++++++--- bindings/typescript/src/bin.ts | 54 +++++- bindings/typescript/src/rlp.ts | 68 +++++-- 7 files changed, 537 insertions(+), 263 deletions(-) create mode 100644 bindings/typescript/scratch diff --git a/bindings/typescript/notes b/bindings/typescript/notes index 6c8ee14..97c0e0c 100644 --- a/bindings/typescript/notes +++ b/bindings/typescript/notes @@ -1,10 +1,11 @@ done: - base64/base58 encoding/decoding +- rlp + +today: +- pull apart data tomorrow: - -- rlp -- pull apart data - basic easy serialization/deserialization - serialize/deserialize examples - (maybe) pull in awcp/sidekick diff --git a/bindings/typescript/scratch b/bindings/typescript/scratch new file mode 100644 index 0000000..9213abc --- /dev/null +++ b/bindings/typescript/scratch @@ -0,0 +1,159 @@ + +const OTAG_SIGNED_TX = 11n; +const OTAG_SPEND_TX = 12n; +const OTAG_CONTRACT_CREATE_TX = 42n; +const OTAG_CONTRACT_CALL_TX = 43n; + +type otag = 11n | 12n | 42n | 43n; + +const IDTAG_ACCOUNT = 1n; +const IDTAG_NAME = 2n; +const IDTAG_CONTRACT = 5n; + +type idtag = 1n | 2n | 5n; + + +type id = + {tag : idtag, + hash : Uint8Array}; + +type SignedTx = + {signatures : Array, + transaction : Uint8Array}; + +type SpendTx = + {sender : id, + recipient : id, + amount : bigint, + fee : bigint, + ttl : bigint, + nonce : bigint, + payload : Uint8Array}; + +type ContractCreateTx = + {owner : id, + nonce : bigint, + code : Uint8Array, + ct_version : bigint, + fee : bigint, + ttl : bigint, + deposit : bigint, + amount : bigint, + gas : bigint, + gas_price : bigint, + call_data : Uint8Array}; + +type ContractCallTx = + {caller : id, + nonce : bigint, + contract : id, + abi_version : bigint, + fee : bigint, + ttl : bigint, + amount : bigint, + gas : bigint, + gas_price : bigint, + call_data : Uint8Array}; + +type tx = SignedTx | SpendTx | ContractCreateTx | ContractCallTx; + +type decoded_tx = + {tag : otag, + version : Uint8Array, + tx : tx}; + +/** + * Decode a `tx_Base64` string + */ +function +decode_tx(tx_str : string): decoded_tx { + let base64_stuff : string = tx_str.slice(3); // tx_[...] -> [...] + let stuff : Uint8Array = b64.decode(base64_stuff); // <> + let rlp_stuff : Uint8Array = stuff.slice(0, stuff.length - 4); // <> + let decoded_datas : Array = rlp.decode(rlp_stuff).decoded_data as Array; // decoded_data : list(rlp.decoded_data() :: binary() | list(decoded_data())) + // tag, vsn + let tag_bytes : Uint8Array = decoded_datas[0] as Uint8Array; // [tag, vsn, fields] -> tag + let tag : bigint = bytes_to_bigint(tag_bytes); // <> = TagBytes + let vsn : Uint8Array = decoded_datas[1] as Uint8Array; + // tx fields + let tx_fields : Array = decoded_datas.slice(2); + let tx : tx = decode_fields(tag, tx_fields); + return {tag: tag as otag, version: vsn, tx: tx}; +} + +/** + * Decode a transaction given the raw fields + * + * @internal + */ +function +decode_fields(tag: bigint, fields: Array): tx { + switch (tag) { + case 11n: return decode_fields_SignedTx(fields); + case 12n: return decode_fields_SpendTx(fields); + case 42n: return decode_fields_ContractCreateTx(fields); + case 43n: return decode_fields_ContractCallTx(fields); + default : throw new Error("invalid object tag: " + tag); + } + console.log('fields: ', fields); + throw new Error("nyi"); +} + + +/** + * Decode a SignedTx + * + * @internal + */ +function +decode_fields_SignedTx(fields: Array): SignedTx { + throw new Error('nyi'); +} + + +/** + * Decode a SpendTx + * + * @internal + */ +function +decode_fields_SpendTx(fields: Array): SpendTx { + // [ :: id(), + // :: id(), + // :: int(), + // :: int(), + // :: int(), + // :: int(), + // :: binary()] + let sender : id = decode_id(fields[0] as Uint8Array); + let recipient : id = decode_id(fields[1] as Uint8Array); + let amount : bigint = bytes_to_bigint(fields[2] as Uint8Array); + let fee : bigint = bytes_to_bigint(fields[3] as Uint8Array); + let ttl : bigint = bytes_to_bigint(fields[4] as Uint8Array); + let nonce : bigint = bytes_to_bigint(fields[5] as Uint8Array); + let payload : Uint8Array = fields[6] as Uint8Array; + return {sender : sender, + recipient : recipient, + amount : amount, + fee : fee, + ttl : ttl, + nonce : nonce, + payload : payload}; + +} + +function +decode_fields_ContractCreateTx(fields: Array): ContractCreateTx { + throw new Error('nyi'); +} + +function +decode_fields_ContractCallTx(fields: Array): ContractCallTx { + throw new Error('nyi'); +} + +function +decode_id(id: Uint8Array): id { + let idtag : idtag = BigInt(id[0]) as idtag; + return {tag: idtag, hash: id.slice(1)}; +} diff --git a/bindings/typescript/src/anth.ts b/bindings/typescript/src/anth.ts index dd9a1a3..32ff252 100644 --- a/bindings/typescript/src/anth.ts +++ b/bindings/typescript/src/anth.ts @@ -1,14 +1,13 @@ /** - * Humanization/dehumanization of data + * Node API constructor/deconstructor * - * This is similar to serialization/deserialization, but not quite the same thing + * This is similar to serialization/deserialization, but not quite the same + * thing. It converts back and forth between different forms of + * "api-serialized" data. * - * This is for taking "api-encoded" data, and pulling it apart and seeing what - * is in it. See example below. - * - * This is not exhaustive. - * - * Reference: https://github.com/aeternity/protocol/blob/master/serializations.md + * References: + * 1. https://github.com/aeternity/protocol/blob/master/serializations.md + * 2. https://github.com/aeternity/protocol/blob/master/node/api/api_encoding.md * * ## General type rules * @@ -118,7 +117,7 @@ * * The remaining fields are the fields of a spend transaction (https://github.com/aeternity/protocol/blob/master/serializations.md#spend-transaction) * - * ``` + * ```erlang * [ :: id() % <<1,201,99,126,...> "=" "ak_2XhCkjzTwcq1coXSSzHJoMZkUzTwnjH88zmPGkkowUsFNTo9UE" * , :: id() % <<1,123,102,230,...> "=" "ak_wM8yFU8eSETXU7VSN48HMDmevGoCMiuveQZgkPuRn1nTiRqyv" * , :: int() % <<"\n">> "=" 10 @@ -131,207 +130,136 @@ * * Our task here is to be able to pull apart the "tx_..." string into its fields. * - * Need to think about this + * Converting the binaries to integers is pretty trivial. The only mildly + * annoying thing is the `id` type. + * + * `id`s have two fields: a single-byte prefix which says which type of ID it + * is. In this case, both `id`s have a prefix of `1`, which means they are both + * normal accounts (hence the `ak_` prefix on the "api-encoded" id). The other + * options are oracles (prefix `4`/`ok_`), contracts (prefix `5`/`ct_`), or + * names (prefix `2`/`nm_`) + * + * To "api-encode" the name, we first pick the appropriate prefix based on the + * first byte (in this case `1 -> "ak_"). The remaining 32 bytes are then + * double-SHA'd to get the 4-byte check suffix + * + * ```erlang + * 30> SenderBytes = lists:nth(3, Data). + * <<1,201,99,126,70,0,231,171,59,194,230,127,198,64,232,35, + * 118,68,68,244,48,85,218,93,85,120,164,154,55,...>> + * 31> <<1, SenderAddrBytes/binary>> = SenderBytes. + * <<1,201,99,126,70,0,231,171,59,194,230,127,198,64,232,35, + * 118,68,68,244,48,85,218,93,85,120,164,154,55,...>> + * 32> DoubleSha = fun(Bytes) -> <> = crypto:hash(sha256, crypto:hash(sha256, Bytes)), Foo end. + * #Fun + * 33> "ak_" ++ b58:enc(<>). + * "ak_2XhCkjzTwcq1coXSSzHJoMZkUzTwnjH88zmPGkkowUsFNTo9UE" + * 34> RecipBytes = lists:nth(4, Data). + * <<1,123,102,230,195,5,7,8,54,228,233,121,32,82,61,195,234, + * 89,203,191,216,108,88,186,7,3,234,139,205,...>> + * 35> <<1, RecipAddrBytes/binary>> = RecipBytes. + * <<1,123,102,230,195,5,7,8,54,228,233,121,32,82,61,195,234, + * 89,203,191,216,108,88,186,7,3,234,139,205,...>> + * 36> "ak_" ++ b58:enc(<>). + * "ak_wM8yFU8eSETXU7VSN48HMDmevGoCMiuveQZgkPuRn1nTiRqyv" + * ``` + * + * ```js + * > anth.deconstruct("tx_+FgMAaEByWN+RgDnqzvC5n/GQOgjdkRE9DBV2l1VeKSaN1r6GNyhAXtm5sMFBwg25Ol5IFI9w+pZy7/YbFi6BwPqi80KuKdsCoYPJvVhyAAACYdoYWluYW5hA7ZC1w==") + * {tag : 'SpendTx', + * version : 1n, + * fields : {sender : "ak_2XhCkjzTwcq1coXSSzHJoMZkUzTwnjH88zmPGkkowUsFNTo9UE", + * recipient : "ak_wM8yFU8eSETXU7VSN48HMDmevGoCMiuveQZgkPuRn1nTiRqyv", + * amount : 10n, + * fee : 16660000000000n, + * ttl : 0n, + * nonce : 9n, + * payload : Uint8Array([104, 97, 105, 110, 97, 110, 97])}} + * ``` * * @module */ export { - OTAG_SIGNED_TX, - OTAG_SPEND_TX, - OTAG_CONTRACT_CREATE_TX, - OTAG_CONTRACT_CALL_TX, - IDTAG_ACCOUNT, - IDTAG_NAME, - IDTAG_CONTRACT, - id, - SignedTx, - SpendTx, - ContractCreateTx, - ContractCallTx, - decode_tx + // types + tx_str, + deconstructed_tx, + // functions + deconstruct_tx }; import * as b64 from './b64.js' +import * as bin from './bin.js' import * as rlp from './rlp.js' -const OTAG_SIGNED_TX = 11n; -const OTAG_SPEND_TX = 12n; -const OTAG_CONTRACT_CREATE_TX = 42n; -const OTAG_CONTRACT_CALL_TX = 43n; - -type otag = 11n | 12n | 42n | 43n; - -const IDTAG_ACCOUNT = 1n; -const IDTAG_NAME = 2n; -const IDTAG_CONTRACT = 5n; - -type idtag = 1n | 2n | 5n; - - -type id = - {tag : idtag, - hash : Uint8Array}; - -type SignedTx = - {signatures : Array, - transaction : Uint8Array}; - -type SpendTx = - {sender : id, - recipient : id, - amount : bigint, - fee : bigint, - ttl : bigint, - nonce : bigint, - payload : Uint8Array}; - -type ContractCreateTx = - {owner : id, - nonce : bigint, - code : Uint8Array, - ct_version : bigint, - fee : bigint, - ttl : bigint, - deposit : bigint, - amount : bigint, - gas : bigint, - gas_price : bigint, - call_data : Uint8Array}; - -type ContractCallTx = - {caller : id, - nonce : bigint, - contract : id, - abi_version : bigint, - fee : bigint, - ttl : bigint, - amount : bigint, - gas : bigint, - gas_price : bigint, - call_data : Uint8Array}; - -type tx = SignedTx | SpendTx | ContractCreateTx | ContractCallTx; - -type decoded_tx = - {tag : otag, - version : Uint8Array, - tx : tx}; /** - * Decode a `tx_Base64` string + * Alias type for a `tx_...` string + */ +type tx_str = string; + + + */ +type deconstructed_tx + = {type : 'SignedTx', + version : bigint, + fields : fields_SignedTx} + | {type : 'SpendTx', + version : bigint, + fields : fields_SpendTx} + | {type : 'ContractCreateTx', + version : bigint, + fields : fields_ContractCreateTx} + | {type : 'ContractCallTx' + version : bigint, + fields : fields_ContractCallTx}; + +/** + * Convenient type alias + * + * @internal + */ +type rlpdata = rlp.decoded_data; + + +/** + * Deconstruct a Tx */ function -decode_tx(tx_str : string): decoded_tx { - let base64_stuff : string = tx_str.slice(3); // tx_[...] -> [...] - let stuff : Uint8Array = b64.decode(base64_stuff); // <> - let rlp_stuff : Uint8Array = stuff.slice(0, stuff.length - 4); // <> - let decoded_datas : Array = rlp.decode(rlp_stuff).decoded_data as Array; // decoded_data : list(rlp.decoded_data() :: binary() | list(decoded_data())) - // tag, vsn - let tag_bytes : Uint8Array = decoded_datas[0] as Uint8Array; // [tag, vsn, fields] -> tag - let tag : bigint = bytes_to_bigint(tag_bytes); // <> = TagBytes - let vsn : Uint8Array = decoded_datas[1] as Uint8Array; - // tx fields - let tx_fields : Array = decoded_datas.slice(2); - let tx : tx = decode_fields(tag, tx_fields); - return {tag: tag as otag, version: vsn, tx: tx}; +deconstruct_tx + (tx_str: tx_str) + : deconstructed_tx +{ + let b64_str : string = tx_str.slice(3); // tx_[...] -> [...] + let tx_rlp_encoded : Uint8Array = b64.decode(b64_str); // [...] -> bytes + let tx_data : Array = shasha_rlp_decode_list(tx_rlp_encoded); // decode data and check the double-sha thing + let tx_type : bigint = bin.bytes_to_bigint(tx_data[0]); // get a bigint + let tx_type_str : tx_type_str = tx_type_str(tx_type); + let tx_version : bigint = bin.bytes_to_bigint(tx_data[1]); + let tx_fields : fields = deconstruct_fields(tx_type_str, tx_version, tx_data.slice(2)); + return {type : tx_type_str, + version : tx_version, + fields : tx_fields}; } - /** - * Convert a byte array to a bigint + * Data that's "api-encoded" goes through the following stages: + * + * 1. data structure -> rlp decode data (arbitrary-depth [possibly 0] list of bytestrings) + * 2. rlp decode data -> bytestring + * 3. bytestring -> <> + * 4. HashedBytestring -> base64/base58 string encoding + * 5. Add string prefix + * + * This function undoes step 3 and step 2, returns back the rlp decode data * * @internal */ function -bytes_to_bigint(bytes: Uint8Array): bigint { - let n : bigint = 0n; - for (let b of bytes) { - // move first, then add - // otherwise it ends on a move - // imperative languages are for losers - n <<= 8n; - n += BigInt(b); - } - return n; -} - - - -/** - * Decode a transaction given the raw fields - * - * @internal - */ -function -decode_fields(tag: bigint, fields: Array): tx { - switch (tag) { - case 11n: return decode_fields_SignedTx(fields); - case 12n: return decode_fields_SpendTx(fields); - case 42n: return decode_fields_ContractCreateTx(fields); - case 43n: return decode_fields_ContractCallTx(fields); - default : throw new Error("invalid object tag: " + tag); - } - console.log('fields: ', fields); - throw new Error("nyi"); -} - - -/** - * Decode a SignedTx - * - * @internal - */ -function -decode_fields_SignedTx(fields: Array): SignedTx { - throw new Error('nyi'); -} - - -/** - * Decode a SpendTx - * - * @internal - */ -function -decode_fields_SpendTx(fields: Array): SpendTx { - // [ :: id(), - // :: id(), - // :: int(), - // :: int(), - // :: int(), - // :: int(), - // :: binary()] - let sender : id = decode_id(fields[0] as Uint8Array); - let recipient : id = decode_id(fields[1] as Uint8Array); - let amount : bigint = bytes_to_bigint(fields[2] as Uint8Array); - let fee : bigint = bytes_to_bigint(fields[3] as Uint8Array); - let ttl : bigint = bytes_to_bigint(fields[4] as Uint8Array); - let nonce : bigint = bytes_to_bigint(fields[5] as Uint8Array); - let payload : Uint8Array = fields[6] as Uint8Array; - return {sender : sender, - recipient : recipient, - amount : amount, - fee : fee, - ttl : ttl, - nonce : nonce, - payload : payload}; - -} - -function -decode_fields_ContractCreateTx(fields: Array): ContractCreateTx { - throw new Error('nyi'); -} - -function -decode_fields_ContractCallTx(fields: Array): ContractCallTx { - throw new Error('nyi'); -} - -function -decode_id(id: Uint8Array): id { - let idtag : idtag = BigInt(id[0]) as idtag; - return {tag: idtag, hash: id.slice(1)}; +shasha_rlp_decode + (hashed_bs : Uint8Array) + : Array +{ + } diff --git a/bindings/typescript/src/b58.ts b/bindings/typescript/src/b58.ts index 50f9e9d..1fd30b8 100644 --- a/bindings/typescript/src/b58.ts +++ b/bindings/typescript/src/b58.ts @@ -17,7 +17,10 @@ export { * Encode a Uint8Array into base58 */ function -encode(binary : Uint8Array): string { +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); @@ -34,13 +37,15 @@ encode(binary : Uint8Array): string { * @internal */ function -nlz(bytes: Uint8Array): number { +nlz + (bytes: Uint8Array) + : number +{ let n = 0; - for (let this_byte of bytes) { - if (0 === this_byte) - n++; - else - break; + for (let this_byte of bytes) + { + if (0 === this_byte) { n++; } + else { break; } } return n; } @@ -53,7 +58,10 @@ nlz(bytes: Uint8Array): number { * @internal */ function -encode_zeros(how_many : number): string { +encode_zeros + (how_many : number) + : string +{ let ones : string = ''; for (let i = 1; i <= how_many; @@ -73,7 +81,10 @@ encode_zeros(how_many : number): string { * @internal */ function -encode_rest(bytes : Uint8Array): string { +encode_rest + (bytes : Uint8Array) + : string +{ let bytes_bignum : bigint = bytes_to_bigint(bytes); let result : string = bignum_to_base58(bytes_bignum); return result; @@ -87,9 +98,13 @@ encode_rest(bytes : Uint8Array): string { * @internal */ function -bytes_to_bigint(bytes: Uint8Array): bigint { +bytes_to_bigint + (bytes: Uint8Array) + : bigint +{ let acc_bigint : bigint = 0n; - for(let this_byte of bytes) { + for(let this_byte of bytes) + { acc_bigint <<= 8n; acc_bigint += BigInt(this_byte); } @@ -104,9 +119,13 @@ bytes_to_bigint(bytes: Uint8Array): bigint { * @internal */ function -bignum_to_base58(q: bigint) { +bignum_to_base58 + (q: bigint) + : string +{ let s = ''; - while (q !== 0n) { + while (q !== 0n) + { let this_n : bigint = q % 58n; q /= 58n; @@ -126,7 +145,10 @@ bignum_to_base58(q: bigint) { * Decode a Base58 string into a Uint8Array */ function -decode(base58: string): Uint8Array { +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); @@ -143,13 +165,15 @@ decode(base58: string): Uint8Array { * @internal */ function -nlo(base58: string): number { +nlo + (base58: string) + : number +{ let n = 0; - for (let this_char of base58) { - if ('1' === this_char) - n++; - else - break; + for (let this_char of base58) + { + if ('1' === this_char) { n++; } + else { break; } } return n; } @@ -162,7 +186,10 @@ nlo(base58: string): number { * @internal */ function -decode_ones(how_many : number): Array { +decode_ones + (how_many : number) + : Array +{ let zeros : Array = []; for (let i = 1; i <= how_many; @@ -182,7 +209,10 @@ decode_ones(how_many : number): Array { * @internal */ function -decode_rest(base58: string): Array { +decode_rest + (base58: string) + : Array +{ let result_bignum : bigint = base58_to_bigint(base58); let result : Array = bigint_to_base256(result_bignum); return result; @@ -196,9 +226,13 @@ decode_rest(base58: string): Array { * @internal */ function -base58_to_bigint(base58: string): bigint { +base58_to_bigint + (base58: string) + : bigint +{ let acc_bigint : bigint = 0n; - for(let this_char of base58) { + for(let this_char of base58) + { acc_bigint *= 58n; acc_bigint += char_to_bigint(this_char); } @@ -212,10 +246,14 @@ base58_to_bigint(base58: string): bigint { * @end */ function -bigint_to_base256(q: bigint): Array { +bigint_to_base256 + (q: bigint) + : Array +{ let arr_reverse = []; - while(q !== 0n) { - let r = Number(q % 256n); + while(q !== 0n) + { + let r: number = Number(q % 256n); q /= 256n; arr_reverse.push(r); } @@ -228,8 +266,17 @@ bigint_to_base256(q: bigint): Array { // TRANSLATION TABLES //============================================================================= + +/** + * Base58 integer -> character conversion table + * + * @internal + */ function -bigint_to_char(n: bigint): string { +bigint_to_char + (n: bigint) + : string +{ switch(n) { case 0n: return '1'; case 1n: return '2'; @@ -295,8 +342,17 @@ bigint_to_char(n: bigint): string { } + +/** + * Base58 character -> integer conversion table + * + * @internal + */ function -char_to_bigint(s: string): bigint { +char_to_bigint + (s: string) + : bigint +{ switch(s) { case '1': return 0n; case '2': return 1n; diff --git a/bindings/typescript/src/b64.ts b/bindings/typescript/src/b64.ts index c6ede50..d82cd73 100644 --- a/bindings/typescript/src/b64.ts +++ b/bindings/typescript/src/b64.ts @@ -13,7 +13,10 @@ export { * Encode an array of bytes as a Uint8Array in base64 notation. */ function -encode(bytes: Uint8Array): string { +encode + (bytes: Uint8Array) + : string +{ // slice the array // length of head is a multiple of 3 // treat the tail as a special case @@ -25,11 +28,10 @@ encode(bytes: Uint8Array): string { -type slice3k = {head : Uint8Array, - tail : Uint8Array, - tail_len : number}; - - +type slice3k + = {head : Uint8Array, + tail : Uint8Array, + tail_len : number}; /** * Take a Uint8Array, take the first 3k (k >= 0) bytes, put them in head, and @@ -38,7 +40,10 @@ type slice3k = {head : Uint8Array, * @internal */ function -slice3k(bytes: Uint8Array): slice3k { +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; @@ -62,7 +67,10 @@ slice3k(bytes: Uint8Array): slice3k { * @internal */ function -encode_head(head_bytes: Uint8Array): string { +encode_head + (head_bytes: Uint8Array) + : string +{ // can assume length of bytes is a multiple of 3 // start index at 0 // increment by 3 @@ -90,7 +98,10 @@ encode_head(head_bytes: Uint8Array): string { * @internal */ function -encode3(bytes: Uint8Array) { +encode3 + (bytes: Uint8Array) + : string +{ let b0 : number = bytes[0]; let b1 : number = bytes[1]; let b2 : number = bytes[2]; @@ -139,9 +150,11 @@ encode3(bytes: Uint8Array) { * @internal */ function -encode_tail(tail_bytes : Uint8Array, - tail_len : number) - : string { +encode_tail + (tail_bytes : Uint8Array, + tail_len : number) + : string +{ switch(tail_len) { case 0: return ''; case 1: return encode1(tail_bytes); @@ -159,7 +172,10 @@ encode_tail(tail_bytes : Uint8Array, * @internal */ function -encode1(bytes: Uint8Array): string { +encode1 + (bytes: Uint8Array) + : string +{ let b0 : number = bytes[0]; // n0 = __ABCDEF // b0 = ABCDEFGH @@ -183,7 +199,10 @@ encode1(bytes: Uint8Array): string { * @internal */ function -encode2(bytes: Uint8Array): string { +encode2 + (bytes: Uint8Array) + : string +{ let b0 : number = bytes[0]; let b1 : number = bytes[1]; @@ -222,7 +241,10 @@ encode2(bytes: Uint8Array): string { * Decode a base64-encoded string */ function -decode(base64_str : string): Uint8Array { +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; @@ -280,7 +302,10 @@ decode(base64_str : string): Uint8Array { * @internal */ function -decode_head(s: string): Array { +decode_head + (s: string) + : Array +{ // go 4 characters at a time let max_i0 : number = s.length - 1; let decoded_acc : Array = []; @@ -304,7 +329,10 @@ decode_head(s: string): Array { * @internal */ function -decode_tail(s: string): Array { +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 @@ -333,7 +361,11 @@ decode_tail(s: string): Array { * * @internal */ -function decode3(s: string): Array { +function +decode3 + (s: string) + : Array +{ // pull out strings let s0 : string = s[0]; let s1 : string = s[1]; @@ -382,7 +414,11 @@ function decode3(s: string): Array { * * @internal */ -function decode2(s: string): Array { +function +decode2 + (s: string) + : Array +{ // xyz= // pull out strings let s0 : string = s[0]; @@ -424,7 +460,11 @@ function decode2(s: string): Array { * * @internal */ -function decode1(s: string): Array { +function +decode1 + (s: string) + : Array +{ // xy== // pull out strings let s0 : string = s[0]; @@ -460,7 +500,9 @@ function decode1(s: string): Array { * @internal */ function -int2char(n: number): string +int2char + (n: number) + : string { switch(n) { case 0: return 'A'; @@ -539,7 +581,9 @@ int2char(n: number): string * @internal */ function -char2int(s: string): number +char2int + (s: string) + : number { switch(s) { case 'A': return 0; @@ -609,5 +653,3 @@ char2int(s: string): number default: throw new Error("invalid base64 character: " + s); } } - - diff --git a/bindings/typescript/src/bin.ts b/bindings/typescript/src/bin.ts index 8c11276..1e8a382 100644 --- a/bindings/typescript/src/bin.ts +++ b/bindings/typescript/src/bin.ts @@ -5,6 +5,56 @@ */ export { - encode_unsigned, - decode_unsigned + bytes_to_bigint, + bigint_to_bytes }; + + +/** + * Convert a byte array to a bigint + * + * Equivalent to `binary:decode_unsigned/1` from Erlang + */ +function +bytes_to_bigint + (bytes: Uint8Array) + : bigint +{ + let n : bigint = 0n; + for (let b of bytes) { + // move first, then add + // otherwise it ends on a move + // imperative languages are for losers + n <<= 8n; + n += BigInt(b); + } + return n; +} + + + +/** + * Convert a bigint to a byte array + * + * Equivalent to `binary:encode_unsigned/1` from Erlang + * + * Requires input to be positive + */ +function +bigint_to_bytes + (q: bigint) + : Uint8Array +{ + if (q < 0n) { + throw new Error('q < 0n: ' + q); + } + + let arr_reverse = []; + while (q > 0n) { + let r = Number(q % 256n); + q /= 256n; + arr_reverse.push(r); + } + arr_reverse.reverse(); + return new Uint8Array(arr_reverse); +} diff --git a/bindings/typescript/src/rlp.ts b/bindings/typescript/src/rlp.ts index e2c8d50..4ef26ec 100644 --- a/bindings/typescript/src/rlp.ts +++ b/bindings/typescript/src/rlp.ts @@ -50,7 +50,10 @@ type decode_result * whatever wasn't consumed) */ function -decode(bytes: Uint8Array): decode_result { +decode + (bytes: Uint8Array) + : decode_result +{ // check the first byte let first_byte: number = bytes[0]; let rest : Uint8Array = bytes.slice(1); @@ -118,7 +121,10 @@ decode(bytes: Uint8Array): decode_result { * @internal */ function -decode_list(bytes: Uint8Array): Array { +decode_list + (bytes: Uint8Array) + : Array +{ let arr : Array = []; while (bytes.length > 0) { // grab an item off the bytes @@ -139,9 +145,13 @@ decode_list(bytes: Uint8Array): Array { * @internal */ function -bytes_to_number(bytes: Uint8Array): number { +bytes_to_number + (bytes: Uint8Array) + : number +{ let n : number = 0; - for (let b of bytes) { + for (let b of bytes) + { n <<= 8; n += b; } @@ -156,8 +166,13 @@ bytes_to_number(bytes: Uint8Array): number { * @internal */ function -dr(x : decoded_data, y : Uint8Array): decode_result { - return {decoded_data: x, remainder: y}; +dr + (x : decoded_data, + y : Uint8Array) + : decode_result +{ + return {decoded_data : x, + remainder : y}; } @@ -172,12 +187,17 @@ dr(x : decoded_data, y : Uint8Array): decode_result { * Encode some decoded data */ function -encode(data: decoded_data): Uint8Array { +encode + (data: decoded_data) + : Uint8Array +{ // is it an array or data - if (is_binary(data)) { + if + (is_binary(data)) { return encode_binary(data as Uint8Array); } - else if (is_list(data)) { + else if + (is_list(data)) { return encode_list(data as Array); } else { @@ -193,7 +213,10 @@ encode(data: decoded_data): Uint8Array { * @internal */ function -encode_binary(bytes: Uint8Array): Uint8Array { +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 @@ -273,7 +296,10 @@ encode_binary(bytes: Uint8Array): Uint8Array { * @internal */ function -encode_list(list: Array): Uint8Array { +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); @@ -361,7 +387,10 @@ encode_list(list: Array): Uint8Array { * @internal */ function -encode_unsigned(n: number): Uint8Array { +encode_unsigned + (n: number) + : Uint8Array +{ // can assume that n initially is greater than 55 // have to encode it in reverse order let arr : Array = []; @@ -431,7 +460,10 @@ encode_unsigned(n: number): Uint8Array { * @internal */ function -uint8arr_concat(arrs: Array): Uint8Array { +uint8arr_concat + (arrs: Array) + : Uint8Array +{ // total length let total_len = arrs.reduce(// fold function (acc_len: number, this_uint8array: Uint8Array): number { @@ -463,7 +495,10 @@ uint8arr_concat(arrs: Array): Uint8Array { * @internal */ function -is_binary(x: any): boolean { +is_binary + (x: any) + : boolean +{ return (x instanceof Uint8Array); } @@ -475,6 +510,9 @@ is_binary(x: any): boolean { * @internal */ function -is_list(x: any): boolean { +is_list + (x: any) + : boolean +{ return (x instanceof Array); }