Merge branch 'pharpend/develop' of github.com:aeternity/Vanillae into pharpend/develop
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
TODONE
|
||||||
|
- vdk_base58
|
||||||
|
- vdk_faert
|
||||||
|
- vdk_rlp
|
||||||
|
- vdk_tests
|
||||||
|
- vdk_tests_cases
|
||||||
|
|
||||||
|
TODO
|
||||||
|
- sidekick package
|
||||||
|
- jex run
|
||||||
|
- jex docs
|
||||||
|
- website
|
||||||
|
- entropy counting doc
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{type, site}.
|
|
||||||
{realm, local}.
|
|
||||||
{name, jrw}.
|
|
||||||
{version, "0.1.0"}.
|
|
||||||
{deps, ["local-awcp-0.2.0"]}.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{type, library}.
|
|
||||||
{realm, local}.
|
|
||||||
{name, vanillae_test_cases}.
|
|
||||||
{version, "0.1.0"}.
|
|
||||||
{deps, ["local-vanillae-0.1.0"]}.
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{type, library}.
|
|
||||||
{realm, local}.
|
|
||||||
{name, vanillae_test}.
|
|
||||||
{version, "0.1.0"}.
|
|
||||||
{deps, ["local-vanillae-0.1.0",
|
|
||||||
"local-vanillae_test_cases-0.1.0"]}.
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{type, library}.
|
||||||
|
{realm, local}.
|
||||||
|
{name, vdk_base58}.
|
||||||
|
{version, "0.1.0"}.
|
||||||
|
{deps, []}.
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
dist
|
||||||
|
jex_mindist
|
||||||
|
jex_include
|
||||||
|
*.beam
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
test/testgen/b58_cases_2.eterms
|
||||||
|
test/testgen/b58_cases_3.eterms
|
||||||
|
__pycache__
|
||||||
|
docs/
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{type, library}.
|
||||||
|
{realm, local}.
|
||||||
|
{name, vdk_base64}.
|
||||||
|
{version, "0.1.0"}.
|
||||||
|
{deps, []}.
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
done:
|
||||||
|
- base64/base58 encoding/decoding
|
||||||
|
- rlp
|
||||||
|
|
||||||
|
today:
|
||||||
|
- pull apart data
|
||||||
|
|
||||||
|
tomorrow:
|
||||||
|
- basic easy serialization/deserialization
|
||||||
|
- serialize/deserialize examples
|
||||||
|
- (maybe) pull in awcp/sidekick
|
||||||
|
- sidekick: sign/propagate and sign/noprop
|
||||||
|
|
||||||
|
later:
|
||||||
|
|
||||||
|
- general node querying with parasite (think about autogeneration)
|
||||||
|
- simple aetto/ae/etc unit converter
|
||||||
|
- ghetto sophia ide
|
||||||
|
- general "send money to someone" page
|
||||||
|
|
||||||
|
GUI:
|
||||||
|
|
||||||
|
- simple testnet explorer
|
||||||
|
- contract ide
|
||||||
@@ -0,0 +1,403 @@
|
|||||||
|
/**
|
||||||
|
* Node API constructor/deconstructor
|
||||||
|
*
|
||||||
|
* This is similar to serialization/deserialization, but not quite the same
|
||||||
|
* thing. It converts back and forth between different forms of
|
||||||
|
* "api-serialized" data.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* ERLANG TYPE | JS TYPE
|
||||||
|
* -------------------------------
|
||||||
|
* integer | bigint
|
||||||
|
* list | Array
|
||||||
|
* binary | Uint8Array
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* # Example
|
||||||
|
*
|
||||||
|
* We start with the string `tx_+FgMAaEByWN+RgDnqzvC5n/GQOgjdkRE9DBV2l1VeKSaN1r6GNyhAXtm5sMFBwg25Ol5IFI9w+pZy7/YbFi6BwPqi80KuKdsCoYPJvVhyAAACYdoYWluYW5hA7ZC1w==`.
|
||||||
|
*
|
||||||
|
* We can tell by the `tx_` prefix that this represents transaction data of
|
||||||
|
* some sort. But the rest of the data is totally opaque. The task of this
|
||||||
|
* module is to "humanize" that `tx_...` string and show what data is contained
|
||||||
|
* in the rest of it.
|
||||||
|
*
|
||||||
|
* The remainder of the string is a base64-encoded bytestring
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* 3> io:format("~tw~n", [base64:decode(<<"+FgMAaEByWN+RgDnqzvC5n/GQOgjdkRE9DBV2l1VeKSaN1r6GNyhAXtm5sMFBwg25Ol5IFI9w+pZy7/YbFi6BwPqi80KuKdsCoYPJvVhyAAACYdoYWluYW5hA7ZC1w==">>)]).
|
||||||
|
* <<248,88,12,1,161,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,90,250,24,220,161,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,10,184,167,108,10,134,15,38,245,97,200,0,0,9,135,104,97,105,110,97,110,97,3,182,66,215>>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* That bytestring contains data encoded using Ethereum's RLP codec. Luckily, I
|
||||||
|
* wrote an RLP decoder. RLP has two types of data: binaries, and
|
||||||
|
* arbitrary-depth (possibly empty) lists of binaries.
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* -type decoded_data() :: binary() | [decoded_data()].
|
||||||
|
*
|
||||||
|
* -spec decode(RLP) -> {Data, Rest}
|
||||||
|
* when RLP :: binary(),
|
||||||
|
* Data :: decoded_data(),
|
||||||
|
* Rest :: binary().
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* 2> rlp:decode(base64:decode(<<"+FgMAaEByWN+RgDnqzvC5n/GQOgjdkRE9DBV2l1VeKSaN1r6GNyhAXtm5sMFBwg25Ol5IFI9w+pZy7/YbFi6BwPqi80KuKdsCoYPJvVhyAAACYdoYWluYW5hA7ZC1w==">>)).
|
||||||
|
* {[<<"\f">>,
|
||||||
|
* <<1>>,
|
||||||
|
* <<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,...>>,
|
||||||
|
* <<1,123,102,230,195,5,7,8,54,228,233,121,32,82,61,195,234,
|
||||||
|
* 89,203,191,216,108,88,186,...>>,
|
||||||
|
* <<"\n">>,
|
||||||
|
* <<15,38,245,97,200,0>>,
|
||||||
|
* <<0>>,
|
||||||
|
* <<"\t">>,<<"hainana">>],
|
||||||
|
* <<3,182,66,215>>}
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* As expected, we get back the return tuple `{Data, Rest}`. `Rest` is the double-sha256 of the beginning
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* 3> X = base64:decode(<<"+FgMAaEByWN+RgDnqzvC5n/GQOgjdkRE9DBV2l1VeKSaN1r6GNyhAXtm5sMFBwg25Ol5IFI9w+pZy7/YbFi6BwPqi80KuKdsCoYPJvVhyAAACYdoYWluYW5hA7ZC1w==">>).
|
||||||
|
* <<248,88,12,1,161,1,201,99,126,70,0,231,171,59,194,230,
|
||||||
|
* 127,198,64,232,35,118,68,68,244,48,85,218,93,...>>
|
||||||
|
* 4> SizeX = byte_size(X).
|
||||||
|
* 94
|
||||||
|
* 6> <<RLPEncodedData:(SizeX - 4)/binary, Hash/binary>> = X.
|
||||||
|
* <<248,88,12,1,161,1,201,99,126,70,0,231,171,59,194,230,
|
||||||
|
* 127,198,64,232,35,118,68,68,244,48,85,218,93,...>>
|
||||||
|
* 10> <<Check:4/binary, _/binary>> = crypto:hash(sha256, crypto:hash(sha256, RLPEncodedData)).
|
||||||
|
* <<3,182,66,215,195,99,112,99,25,7,84,31,151,188,149,81,
|
||||||
|
* 189,184,82,207,164,68,128,43,11,174,236,59,77,...>>
|
||||||
|
* 11> Hash.
|
||||||
|
* <<3,182,66,215>>
|
||||||
|
* 12> Check.
|
||||||
|
* <<3,182,66,215>>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* What we're really interested in is `Data`
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* 14> {Data, _} = rlp:decode(X).
|
||||||
|
* {[<<"\f">>,
|
||||||
|
* <<1>>,
|
||||||
|
* <<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,...>>,
|
||||||
|
* <<1,123,102,230,195,5,7,8,54,228,233,121,32,82,61,195,234,
|
||||||
|
* 89,203,191,216,108,88,186,...>>,
|
||||||
|
* <<"\n">>,
|
||||||
|
* <<15,38,245,97,200,0>>,
|
||||||
|
* <<0>>,
|
||||||
|
* <<"\t">>,<<"hainana">>],
|
||||||
|
* <<3,182,66,215>>}
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* `Data` is a list. The first field `<<"\f">>` is meant to be an integer which
|
||||||
|
* tells us what type of data this is.
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* 16> $\f.
|
||||||
|
* 12
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* If we look at our table
|
||||||
|
* (https://github.com/aeternity/protocol/blob/master/serializations.md#table-of-object-tags),
|
||||||
|
* we see that a value of `12` is a spend transaction.
|
||||||
|
*
|
||||||
|
* The second field `<<1>>` tells us the "version" of the field orderings,
|
||||||
|
* which we can ignore for now.
|
||||||
|
*
|
||||||
|
* The remaining fields are the fields of a spend transaction (https://github.com/aeternity/protocol/blob/master/serializations.md#spend-transaction)
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* [ <sender> :: id() % <<1,201,99,126,...> "=" "ak_2XhCkjzTwcq1coXSSzHJoMZkUzTwnjH88zmPGkkowUsFNTo9UE"
|
||||||
|
* , <recipient> :: id() % <<1,123,102,230,...> "=" "ak_wM8yFU8eSETXU7VSN48HMDmevGoCMiuveQZgkPuRn1nTiRqyv"
|
||||||
|
* , <amount> :: int() % <<"\n">> "=" 10
|
||||||
|
* , <fee> :: int() % <<15,38,245,97,200,0>> "=" 16_660_000_000_000
|
||||||
|
* , <ttl> :: int() % <<0>> "=" 0
|
||||||
|
* , <nonce> :: int() % <<"\t">> "=" 9
|
||||||
|
* , <payload> :: binary() % <<"hainana">> "=" "hainana"
|
||||||
|
* ]
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Our task here is to be able to pull apart the "tx_..." string into its fields.
|
||||||
|
*
|
||||||
|
* 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) -> <<Foo:4/binary, _/binary>> = crypto:hash(sha256, crypto:hash(sha256, Bytes)), Foo end.
|
||||||
|
* #Fun<erl_eval.44.97283095>
|
||||||
|
* 33> "ak_" ++ b58:enc(<<SenderAddrBytes/binary, (DoubleSha(SenderAddrBytes))/binary>>).
|
||||||
|
* "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(<<RecipAddrBytes/binary, (DoubleSha(RecipAddrBytes))/binary>>).
|
||||||
|
* "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 {
|
||||||
|
// 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'
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias type for a `tx_...` string
|
||||||
|
*/
|
||||||
|
type tx_str = string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias type for a `sg_...` string
|
||||||
|
*/
|
||||||
|
type sg_str = string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* types of decoded tx we currently support
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
type tx_type_str
|
||||||
|
= 'SignedTx'
|
||||||
|
| 'SpendTx'
|
||||||
|
| 'ContractCreateTx'
|
||||||
|
| 'ContractCallTx';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Results of deconstruct_tx
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fields types
|
||||||
|
*/
|
||||||
|
type fields
|
||||||
|
= fields_SignedTx
|
||||||
|
| fields_SpendTx
|
||||||
|
| fields_ContractCreateTx
|
||||||
|
| fields_ContractCallTx;
|
||||||
|
|
||||||
|
type fields_SignedTx
|
||||||
|
= {signatures : Array<sg_str>,
|
||||||
|
transaction : tx_str};
|
||||||
|
|
||||||
|
type fields_SpendTx
|
||||||
|
= {sender : string,
|
||||||
|
recipient : string,
|
||||||
|
amount : bigint,
|
||||||
|
fee : bigint,
|
||||||
|
ttl : bigint,
|
||||||
|
nonce : bigint,
|
||||||
|
payload : Uint8Array};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deconstruct a Tx
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
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<rlpdata> = 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 tts : 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(tts, tx_version, tx_data.slice(2));
|
||||||
|
return {type : tts,
|
||||||
|
version : tx_version,
|
||||||
|
fields : tx_fields};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 -> <<Bytestring/binary, Hash:4/binary>>
|
||||||
|
* 4. HashedBytestring -> base64/base58 string encoding
|
||||||
|
* 5. Add string prefix
|
||||||
|
*
|
||||||
|
* This function undoes step 3 and step 2, returns back the rlp decode data
|
||||||
|
*
|
||||||
|
* FIXME: Does not check double-sha (yet); need to figure out way to handle hash failures
|
||||||
|
* FIXME: No good way to handle failure cases
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
shasha_rlp_decode_list
|
||||||
|
(hashed_bs : Uint8Array)
|
||||||
|
: Array<rlpdata>
|
||||||
|
{
|
||||||
|
let len = hashed_bs.length;
|
||||||
|
let bytes = hashed_bs.slice(0, len - 4);
|
||||||
|
let {decoded_data} = rlp.decode(bytes);
|
||||||
|
return (decoded_data as Array<rlpdata>);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an object tag that's a type of transaction to the type string
|
||||||
|
*
|
||||||
|
* See: https://github.com/aeternity/protocol/blob/master/serializations.md#table-of-object-tags
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
tx_type_str
|
||||||
|
(tx_type_int : bigint)
|
||||||
|
: tx_type_str
|
||||||
|
{
|
||||||
|
switch (tx_type_int)
|
||||||
|
{
|
||||||
|
case 11n: return 'SignedTx';
|
||||||
|
case 12n: return 'SpendTx';
|
||||||
|
case 42n: return 'ContractCreateTx';
|
||||||
|
case 43n: return 'ContractCallTx';
|
||||||
|
default: throw new Error('invalid transaction type: ' + tx_type_int);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an array of data decoded from RLP, convert it to the fields, as
|
||||||
|
* appropriate as given by the tx type string and the version
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
deconstruct_fields
|
||||||
|
(tx_type_str : tx_type_str,
|
||||||
|
tx_version : bigint,
|
||||||
|
tx_rawfields : Array<rlpdata>)
|
||||||
|
: fields
|
||||||
|
{
|
||||||
|
switch (tx_type_str)
|
||||||
|
{
|
||||||
|
// case 'SignedTx' : return deconstruct_fields_SignedTx(tx_rawfields);
|
||||||
|
case 'SpendTx' : return deconstruct_fields_SpendTx(tx_rawfields);
|
||||||
|
// case 'ContractCreateTx' : return deconstruct_fields_ContractCreateTx(tx_rawfields);
|
||||||
|
// case 'ContractCallTx' : return deconstruct_fields_ContractCallTx(tx_rawfields);
|
||||||
|
default : throw new Error('invalid tx type str: ' + tx_type_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: do all this in Erlang
|
||||||
|
function
|
||||||
|
deconstruct_fields_SpendTx
|
||||||
|
(fields: Array<rlpdata>)
|
||||||
|
: fields_SpendTx
|
||||||
|
{
|
||||||
|
let sender_bytes = fields[0];
|
||||||
|
let recip_bytes = fields[1];
|
||||||
|
let amount_bytes = fields[2];
|
||||||
|
let fee_bytes = fields[3];
|
||||||
|
let ttl_bytes = fields[4];
|
||||||
|
let nonce_bytes = fields[5];
|
||||||
|
let payload_bytes = fields[6];
|
||||||
|
return {sender : encode_id(sender_bytes),
|
||||||
|
recipient : encode_id(sender_bytes),
|
||||||
|
amount : bin.bytes_to_bigint(amount_bytes),
|
||||||
|
fee : bin.bytes_to_bigint(fee_bytes),
|
||||||
|
ttl : bin.bytes_to_bigint(ttl_bytes),
|
||||||
|
nonce : bin.bytes_to_bigint(nonce_bytes),
|
||||||
|
payload : bin.bytes_to_bigint(payload_bytes)};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a binary account/name/etc binary id into the appropriate type of string
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
encode_id
|
||||||
|
(id: Uint8Array)
|
||||||
|
: string
|
||||||
|
{
|
||||||
|
throw new Error('nyi');
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
FIXME:
|
||||||
|
1. work out all this in Erlang to clear conceptual goo
|
||||||
|
2. think about how i want type safety etc to work
|
||||||
|
3. think about a language to assert that the data has the correct shape to it
|
||||||
|
4. get some examples working in Erlang
|
||||||
|
5. convert erlang code back to ts
|
||||||
|
*/
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
/**
|
||||||
|
* Concatenate two bitstrings
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
bits_concat
|
||||||
|
(bits1 : bits,
|
||||||
|
bits2 : bits)
|
||||||
|
: bits
|
||||||
|
{
|
||||||
|
let result_bit_length : number = bits1.bit_length + bits2.bit_length;
|
||||||
|
let bytes1 : Uint8Array = bits1.bytes;
|
||||||
|
let bytes2 : Uint8Array = bits2.bytes;
|
||||||
|
// using zeros here because of our xor trick in a minute
|
||||||
|
let result_bits : bits = bits_zeros(result_bit_length);
|
||||||
|
let result_bytes : Uint8Array = result_bits.bytes;
|
||||||
|
|
||||||
|
// alright so
|
||||||
|
// we can start by copying the first bytes into result bytes
|
||||||
|
for (let bytes1_idx0 = 0;
|
||||||
|
bytes1_idx0 < bytes1.length;
|
||||||
|
bytes1_idx0++)
|
||||||
|
{
|
||||||
|
result_bytes[bytes1_idx0] = bytes1[bytes1_idx0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// next
|
||||||
|
// we need to calculate the left-shift offset
|
||||||
|
// this will be 8 - (bytes1.bit_length % 8)
|
||||||
|
let num_trailing_zeros_in_first_array : number = 8 - (bits1.bit_length % 8);
|
||||||
|
// so
|
||||||
|
// bytes1: ABCD_EF00
|
||||||
|
// bytes2: GH12_3000
|
||||||
|
// result: ABCD_EFGH 1230_0000
|
||||||
|
// ah ok, so we need to for each byte in the second array
|
||||||
|
// take the first however many bits, xor it with the existing byte
|
||||||
|
// then take the last however many bits and place them into the next byte
|
||||||
|
// this is super confusing but
|
||||||
|
// ABCD_EF00
|
||||||
|
// GH12_3456
|
||||||
|
// operation:
|
||||||
|
// ABCD_EF00
|
||||||
|
// xor 0000_00GH
|
||||||
|
// = ABCD_EFGH 1234_5600
|
||||||
|
//
|
||||||
|
// then on the next iteration
|
||||||
|
// 1234_5600
|
||||||
|
// abcd_efgh
|
||||||
|
// ->
|
||||||
|
// 1234_56ab cdef_gh00
|
||||||
|
//
|
||||||
|
// ah so there's a pattern
|
||||||
|
// however many trailing 0s there are in the first array
|
||||||
|
// say there's 2
|
||||||
|
// we take the first 2 bits of the upcoming byte
|
||||||
|
// xor that against the current byte
|
||||||
|
// take the last 6 bits of the upcoming byte
|
||||||
|
// set the next byte to that
|
||||||
|
//
|
||||||
|
// have to think about edge behavior
|
||||||
|
// this is ripe for off-by-1 errors
|
||||||
|
// but i think the general idea is right
|
||||||
|
//
|
||||||
|
// so we start the iteration
|
||||||
|
// on the last byte of the first array
|
||||||
|
let last_byte_of_first_array_idx0 : number = bytes1.length - 1;
|
||||||
|
// and we end
|
||||||
|
// on the second-to-last-byte of the result array
|
||||||
|
let second_to_last_byte_of_result_array_idx0 : number = result_bytes.length - 2;
|
||||||
|
// the reason we do that is because we're doing this is because we are
|
||||||
|
// going along, xoring against the current byte and then setting the next
|
||||||
|
// byte
|
||||||
|
//
|
||||||
|
// ok so
|
||||||
|
for (let this_result_byte_idx0 = last_byte_of_first_array_idx0;
|
||||||
|
this_result_byte_idx0 <= second_to_last_byte_of_result_array_idx0;
|
||||||
|
this_result_byte_idx0++)
|
||||||
|
{
|
||||||
|
let this_result_byte : number = result_bytes[this_result_byte_idx0];
|
||||||
|
|
||||||
|
// ok here we need to fish out the relevant byte of the second array
|
||||||
|
// gaaah
|
||||||
|
// so this will be 0 at the start of the loop
|
||||||
|
let relevant_byte_of_second_array_idx0 : number = this_result_byte_idx0 - last_byte_of_first_array_idx0;
|
||||||
|
let relevant_byte_of_second_array : number = bytes2[relevant_byte_of_second_array_idx0];
|
||||||
|
|
||||||
|
// ok so let's fish out the leading digits
|
||||||
|
// the number of leading digits is the number of trailing 0s in the first array
|
||||||
|
let num_leading_digits : number = num_trailing_zeros_in_first_array;
|
||||||
|
let num_trailing_digits : number = 8 - num_leading_digits;
|
||||||
|
|
||||||
|
// suppose there are 2 leading digits and 6 trailing digits
|
||||||
|
// ABCD_EFGH
|
||||||
|
// leading digits are
|
||||||
|
// ABCD_EFGH >> 6 = 0000_00AB
|
||||||
|
// trailing digits are
|
||||||
|
// (ABCD_EFGH << 2) % 255 = CDEF_GH00
|
||||||
|
let leading_digits : number = relevant_byte_of_second_array >> num_trailing_digits;
|
||||||
|
let trailing_digits : number = (relevant_byte_of_second_array << num_leading_digits) % 255;
|
||||||
|
|
||||||
|
// xor the current byte against the leading digits
|
||||||
|
let new_this_result_byte : number = this_result_byte ^ leading_digits;
|
||||||
|
result_bytes[this_result_byte_idx0] = new_this_result_byte;
|
||||||
|
|
||||||
|
// set the next byte to the trailing digits
|
||||||
|
result_bytes[this_result_byte_idx0 + 1] = trailing_digits;
|
||||||
|
}
|
||||||
|
|
||||||
|
// i think we're done
|
||||||
|
return {bit_length : result_bit_length,
|
||||||
|
bytes : result_bytes};
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<Uint8Array>,
|
||||||
|
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); // <<Bin/binary, DoubleSha:4>>
|
||||||
|
let rlp_stuff : Uint8Array = stuff.slice(0, stuff.length - 4); // <<Bin/binary>>
|
||||||
|
let decoded_datas : Array<rlp.decoded_data> = rlp.decode(rlp_stuff).decoded_data as Array<rlp.decoded_data>; // 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); // <<Tag:(byte_size(TagBytes))>> = TagBytes
|
||||||
|
let vsn : Uint8Array = decoded_datas[1] as Uint8Array;
|
||||||
|
// tx fields
|
||||||
|
let tx_fields : Array<rlp.decoded_data> = 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<rlp.decoded_data>): 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<rlp.decoded_data>): SignedTx {
|
||||||
|
throw new Error('nyi');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a SpendTx
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
decode_fields_SpendTx(fields: Array<rlp.decoded_data>): SpendTx {
|
||||||
|
// [<sender> :: id(),
|
||||||
|
// <recipient> :: id(),
|
||||||
|
// <amount> :: int(),
|
||||||
|
// <fee> :: int(),
|
||||||
|
// <ttl> :: int(),
|
||||||
|
// <nonce> :: int(),
|
||||||
|
// <payload> :: 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<rlp.decoded_data>): ContractCreateTx {
|
||||||
|
throw new Error('nyi');
|
||||||
|
}
|
||||||
|
|
||||||
|
function
|
||||||
|
decode_fields_ContractCallTx(fields: Array<rlp.decoded_data>): 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)};
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Vanillae Seed Phrase Library
|
||||||
|
*
|
||||||
|
* Refs:
|
||||||
|
* 1. BIP 39: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
|
||||||
|
*
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a given number of seed bits.
|
||||||
|
*
|
||||||
|
* `how_many` must be a multiple of 33.
|
||||||
|
*
|
||||||
|
* Ref: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#generating-the-mnemonic
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
seed_and_check_bits
|
||||||
|
(how_many : number)
|
||||||
|
: Uint8Array
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
dist
|
||||||
|
jex_mindist
|
||||||
|
jex_include
|
||||||
|
*.beam
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
test/testgen/b58_cases_2.eterms
|
||||||
|
test/testgen/b58_cases_3.eterms
|
||||||
|
__pycache__
|
||||||
|
docs/
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{type, library}.
|
||||||
|
{realm, local}.
|
||||||
|
{name, vdk_binary}.
|
||||||
|
{version, "0.1.0"}.
|
||||||
|
{deps, []}.
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
done:
|
||||||
|
- base64/base58 encoding/decoding
|
||||||
|
- rlp
|
||||||
|
|
||||||
|
today:
|
||||||
|
- pull apart data
|
||||||
|
|
||||||
|
tomorrow:
|
||||||
|
- basic easy serialization/deserialization
|
||||||
|
- serialize/deserialize examples
|
||||||
|
- (maybe) pull in awcp/sidekick
|
||||||
|
- sidekick: sign/propagate and sign/noprop
|
||||||
|
|
||||||
|
later:
|
||||||
|
|
||||||
|
- general node querying with parasite (think about autogeneration)
|
||||||
|
- simple aetto/ae/etc unit converter
|
||||||
|
- ghetto sophia ide
|
||||||
|
- general "send money to someone" page
|
||||||
|
|
||||||
|
GUI:
|
||||||
|
|
||||||
|
- simple testnet explorer
|
||||||
|
- contract ide
|
||||||
@@ -0,0 +1,403 @@
|
|||||||
|
/**
|
||||||
|
* Node API constructor/deconstructor
|
||||||
|
*
|
||||||
|
* This is similar to serialization/deserialization, but not quite the same
|
||||||
|
* thing. It converts back and forth between different forms of
|
||||||
|
* "api-serialized" data.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* ERLANG TYPE | JS TYPE
|
||||||
|
* -------------------------------
|
||||||
|
* integer | bigint
|
||||||
|
* list | Array
|
||||||
|
* binary | Uint8Array
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* # Example
|
||||||
|
*
|
||||||
|
* We start with the string `tx_+FgMAaEByWN+RgDnqzvC5n/GQOgjdkRE9DBV2l1VeKSaN1r6GNyhAXtm5sMFBwg25Ol5IFI9w+pZy7/YbFi6BwPqi80KuKdsCoYPJvVhyAAACYdoYWluYW5hA7ZC1w==`.
|
||||||
|
*
|
||||||
|
* We can tell by the `tx_` prefix that this represents transaction data of
|
||||||
|
* some sort. But the rest of the data is totally opaque. The task of this
|
||||||
|
* module is to "humanize" that `tx_...` string and show what data is contained
|
||||||
|
* in the rest of it.
|
||||||
|
*
|
||||||
|
* The remainder of the string is a base64-encoded bytestring
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* 3> io:format("~tw~n", [base64:decode(<<"+FgMAaEByWN+RgDnqzvC5n/GQOgjdkRE9DBV2l1VeKSaN1r6GNyhAXtm5sMFBwg25Ol5IFI9w+pZy7/YbFi6BwPqi80KuKdsCoYPJvVhyAAACYdoYWluYW5hA7ZC1w==">>)]).
|
||||||
|
* <<248,88,12,1,161,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,90,250,24,220,161,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,10,184,167,108,10,134,15,38,245,97,200,0,0,9,135,104,97,105,110,97,110,97,3,182,66,215>>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* That bytestring contains data encoded using Ethereum's RLP codec. Luckily, I
|
||||||
|
* wrote an RLP decoder. RLP has two types of data: binaries, and
|
||||||
|
* arbitrary-depth (possibly empty) lists of binaries.
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* -type decoded_data() :: binary() | [decoded_data()].
|
||||||
|
*
|
||||||
|
* -spec decode(RLP) -> {Data, Rest}
|
||||||
|
* when RLP :: binary(),
|
||||||
|
* Data :: decoded_data(),
|
||||||
|
* Rest :: binary().
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* 2> rlp:decode(base64:decode(<<"+FgMAaEByWN+RgDnqzvC5n/GQOgjdkRE9DBV2l1VeKSaN1r6GNyhAXtm5sMFBwg25Ol5IFI9w+pZy7/YbFi6BwPqi80KuKdsCoYPJvVhyAAACYdoYWluYW5hA7ZC1w==">>)).
|
||||||
|
* {[<<"\f">>,
|
||||||
|
* <<1>>,
|
||||||
|
* <<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,...>>,
|
||||||
|
* <<1,123,102,230,195,5,7,8,54,228,233,121,32,82,61,195,234,
|
||||||
|
* 89,203,191,216,108,88,186,...>>,
|
||||||
|
* <<"\n">>,
|
||||||
|
* <<15,38,245,97,200,0>>,
|
||||||
|
* <<0>>,
|
||||||
|
* <<"\t">>,<<"hainana">>],
|
||||||
|
* <<3,182,66,215>>}
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* As expected, we get back the return tuple `{Data, Rest}`. `Rest` is the double-sha256 of the beginning
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* 3> X = base64:decode(<<"+FgMAaEByWN+RgDnqzvC5n/GQOgjdkRE9DBV2l1VeKSaN1r6GNyhAXtm5sMFBwg25Ol5IFI9w+pZy7/YbFi6BwPqi80KuKdsCoYPJvVhyAAACYdoYWluYW5hA7ZC1w==">>).
|
||||||
|
* <<248,88,12,1,161,1,201,99,126,70,0,231,171,59,194,230,
|
||||||
|
* 127,198,64,232,35,118,68,68,244,48,85,218,93,...>>
|
||||||
|
* 4> SizeX = byte_size(X).
|
||||||
|
* 94
|
||||||
|
* 6> <<RLPEncodedData:(SizeX - 4)/binary, Hash/binary>> = X.
|
||||||
|
* <<248,88,12,1,161,1,201,99,126,70,0,231,171,59,194,230,
|
||||||
|
* 127,198,64,232,35,118,68,68,244,48,85,218,93,...>>
|
||||||
|
* 10> <<Check:4/binary, _/binary>> = crypto:hash(sha256, crypto:hash(sha256, RLPEncodedData)).
|
||||||
|
* <<3,182,66,215,195,99,112,99,25,7,84,31,151,188,149,81,
|
||||||
|
* 189,184,82,207,164,68,128,43,11,174,236,59,77,...>>
|
||||||
|
* 11> Hash.
|
||||||
|
* <<3,182,66,215>>
|
||||||
|
* 12> Check.
|
||||||
|
* <<3,182,66,215>>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* What we're really interested in is `Data`
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* 14> {Data, _} = rlp:decode(X).
|
||||||
|
* {[<<"\f">>,
|
||||||
|
* <<1>>,
|
||||||
|
* <<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,...>>,
|
||||||
|
* <<1,123,102,230,195,5,7,8,54,228,233,121,32,82,61,195,234,
|
||||||
|
* 89,203,191,216,108,88,186,...>>,
|
||||||
|
* <<"\n">>,
|
||||||
|
* <<15,38,245,97,200,0>>,
|
||||||
|
* <<0>>,
|
||||||
|
* <<"\t">>,<<"hainana">>],
|
||||||
|
* <<3,182,66,215>>}
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* `Data` is a list. The first field `<<"\f">>` is meant to be an integer which
|
||||||
|
* tells us what type of data this is.
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* 16> $\f.
|
||||||
|
* 12
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* If we look at our table
|
||||||
|
* (https://github.com/aeternity/protocol/blob/master/serializations.md#table-of-object-tags),
|
||||||
|
* we see that a value of `12` is a spend transaction.
|
||||||
|
*
|
||||||
|
* The second field `<<1>>` tells us the "version" of the field orderings,
|
||||||
|
* which we can ignore for now.
|
||||||
|
*
|
||||||
|
* The remaining fields are the fields of a spend transaction (https://github.com/aeternity/protocol/blob/master/serializations.md#spend-transaction)
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* [ <sender> :: id() % <<1,201,99,126,...> "=" "ak_2XhCkjzTwcq1coXSSzHJoMZkUzTwnjH88zmPGkkowUsFNTo9UE"
|
||||||
|
* , <recipient> :: id() % <<1,123,102,230,...> "=" "ak_wM8yFU8eSETXU7VSN48HMDmevGoCMiuveQZgkPuRn1nTiRqyv"
|
||||||
|
* , <amount> :: int() % <<"\n">> "=" 10
|
||||||
|
* , <fee> :: int() % <<15,38,245,97,200,0>> "=" 16_660_000_000_000
|
||||||
|
* , <ttl> :: int() % <<0>> "=" 0
|
||||||
|
* , <nonce> :: int() % <<"\t">> "=" 9
|
||||||
|
* , <payload> :: binary() % <<"hainana">> "=" "hainana"
|
||||||
|
* ]
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Our task here is to be able to pull apart the "tx_..." string into its fields.
|
||||||
|
*
|
||||||
|
* 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) -> <<Foo:4/binary, _/binary>> = crypto:hash(sha256, crypto:hash(sha256, Bytes)), Foo end.
|
||||||
|
* #Fun<erl_eval.44.97283095>
|
||||||
|
* 33> "ak_" ++ b58:enc(<<SenderAddrBytes/binary, (DoubleSha(SenderAddrBytes))/binary>>).
|
||||||
|
* "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(<<RecipAddrBytes/binary, (DoubleSha(RecipAddrBytes))/binary>>).
|
||||||
|
* "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 {
|
||||||
|
// 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'
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias type for a `tx_...` string
|
||||||
|
*/
|
||||||
|
type tx_str = string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias type for a `sg_...` string
|
||||||
|
*/
|
||||||
|
type sg_str = string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* types of decoded tx we currently support
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
type tx_type_str
|
||||||
|
= 'SignedTx'
|
||||||
|
| 'SpendTx'
|
||||||
|
| 'ContractCreateTx'
|
||||||
|
| 'ContractCallTx';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Results of deconstruct_tx
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fields types
|
||||||
|
*/
|
||||||
|
type fields
|
||||||
|
= fields_SignedTx
|
||||||
|
| fields_SpendTx
|
||||||
|
| fields_ContractCreateTx
|
||||||
|
| fields_ContractCallTx;
|
||||||
|
|
||||||
|
type fields_SignedTx
|
||||||
|
= {signatures : Array<sg_str>,
|
||||||
|
transaction : tx_str};
|
||||||
|
|
||||||
|
type fields_SpendTx
|
||||||
|
= {sender : string,
|
||||||
|
recipient : string,
|
||||||
|
amount : bigint,
|
||||||
|
fee : bigint,
|
||||||
|
ttl : bigint,
|
||||||
|
nonce : bigint,
|
||||||
|
payload : Uint8Array};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deconstruct a Tx
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
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<rlpdata> = 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 tts : 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(tts, tx_version, tx_data.slice(2));
|
||||||
|
return {type : tts,
|
||||||
|
version : tx_version,
|
||||||
|
fields : tx_fields};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 -> <<Bytestring/binary, Hash:4/binary>>
|
||||||
|
* 4. HashedBytestring -> base64/base58 string encoding
|
||||||
|
* 5. Add string prefix
|
||||||
|
*
|
||||||
|
* This function undoes step 3 and step 2, returns back the rlp decode data
|
||||||
|
*
|
||||||
|
* FIXME: Does not check double-sha (yet); need to figure out way to handle hash failures
|
||||||
|
* FIXME: No good way to handle failure cases
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
shasha_rlp_decode_list
|
||||||
|
(hashed_bs : Uint8Array)
|
||||||
|
: Array<rlpdata>
|
||||||
|
{
|
||||||
|
let len = hashed_bs.length;
|
||||||
|
let bytes = hashed_bs.slice(0, len - 4);
|
||||||
|
let {decoded_data} = rlp.decode(bytes);
|
||||||
|
return (decoded_data as Array<rlpdata>);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an object tag that's a type of transaction to the type string
|
||||||
|
*
|
||||||
|
* See: https://github.com/aeternity/protocol/blob/master/serializations.md#table-of-object-tags
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
tx_type_str
|
||||||
|
(tx_type_int : bigint)
|
||||||
|
: tx_type_str
|
||||||
|
{
|
||||||
|
switch (tx_type_int)
|
||||||
|
{
|
||||||
|
case 11n: return 'SignedTx';
|
||||||
|
case 12n: return 'SpendTx';
|
||||||
|
case 42n: return 'ContractCreateTx';
|
||||||
|
case 43n: return 'ContractCallTx';
|
||||||
|
default: throw new Error('invalid transaction type: ' + tx_type_int);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an array of data decoded from RLP, convert it to the fields, as
|
||||||
|
* appropriate as given by the tx type string and the version
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
deconstruct_fields
|
||||||
|
(tx_type_str : tx_type_str,
|
||||||
|
tx_version : bigint,
|
||||||
|
tx_rawfields : Array<rlpdata>)
|
||||||
|
: fields
|
||||||
|
{
|
||||||
|
switch (tx_type_str)
|
||||||
|
{
|
||||||
|
// case 'SignedTx' : return deconstruct_fields_SignedTx(tx_rawfields);
|
||||||
|
case 'SpendTx' : return deconstruct_fields_SpendTx(tx_rawfields);
|
||||||
|
// case 'ContractCreateTx' : return deconstruct_fields_ContractCreateTx(tx_rawfields);
|
||||||
|
// case 'ContractCallTx' : return deconstruct_fields_ContractCallTx(tx_rawfields);
|
||||||
|
default : throw new Error('invalid tx type str: ' + tx_type_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: do all this in Erlang
|
||||||
|
function
|
||||||
|
deconstruct_fields_SpendTx
|
||||||
|
(fields: Array<rlpdata>)
|
||||||
|
: fields_SpendTx
|
||||||
|
{
|
||||||
|
let sender_bytes = fields[0];
|
||||||
|
let recip_bytes = fields[1];
|
||||||
|
let amount_bytes = fields[2];
|
||||||
|
let fee_bytes = fields[3];
|
||||||
|
let ttl_bytes = fields[4];
|
||||||
|
let nonce_bytes = fields[5];
|
||||||
|
let payload_bytes = fields[6];
|
||||||
|
return {sender : encode_id(sender_bytes),
|
||||||
|
recipient : encode_id(sender_bytes),
|
||||||
|
amount : bin.bytes_to_bigint(amount_bytes),
|
||||||
|
fee : bin.bytes_to_bigint(fee_bytes),
|
||||||
|
ttl : bin.bytes_to_bigint(ttl_bytes),
|
||||||
|
nonce : bin.bytes_to_bigint(nonce_bytes),
|
||||||
|
payload : bin.bytes_to_bigint(payload_bytes)};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a binary account/name/etc binary id into the appropriate type of string
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
encode_id
|
||||||
|
(id: Uint8Array)
|
||||||
|
: string
|
||||||
|
{
|
||||||
|
throw new Error('nyi');
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
FIXME:
|
||||||
|
1. work out all this in Erlang to clear conceptual goo
|
||||||
|
2. think about how i want type safety etc to work
|
||||||
|
3. think about a language to assert that the data has the correct shape to it
|
||||||
|
4. get some examples working in Erlang
|
||||||
|
5. convert erlang code back to ts
|
||||||
|
*/
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
/**
|
||||||
|
* Concatenate two bitstrings
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
bits_concat
|
||||||
|
(bits1 : bits,
|
||||||
|
bits2 : bits)
|
||||||
|
: bits
|
||||||
|
{
|
||||||
|
let result_bit_length : number = bits1.bit_length + bits2.bit_length;
|
||||||
|
let bytes1 : Uint8Array = bits1.bytes;
|
||||||
|
let bytes2 : Uint8Array = bits2.bytes;
|
||||||
|
// using zeros here because of our xor trick in a minute
|
||||||
|
let result_bits : bits = bits_zeros(result_bit_length);
|
||||||
|
let result_bytes : Uint8Array = result_bits.bytes;
|
||||||
|
|
||||||
|
// alright so
|
||||||
|
// we can start by copying the first bytes into result bytes
|
||||||
|
for (let bytes1_idx0 = 0;
|
||||||
|
bytes1_idx0 < bytes1.length;
|
||||||
|
bytes1_idx0++)
|
||||||
|
{
|
||||||
|
result_bytes[bytes1_idx0] = bytes1[bytes1_idx0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// next
|
||||||
|
// we need to calculate the left-shift offset
|
||||||
|
// this will be 8 - (bytes1.bit_length % 8)
|
||||||
|
let num_trailing_zeros_in_first_array : number = 8 - (bits1.bit_length % 8);
|
||||||
|
// so
|
||||||
|
// bytes1: ABCD_EF00
|
||||||
|
// bytes2: GH12_3000
|
||||||
|
// result: ABCD_EFGH 1230_0000
|
||||||
|
// ah ok, so we need to for each byte in the second array
|
||||||
|
// take the first however many bits, xor it with the existing byte
|
||||||
|
// then take the last however many bits and place them into the next byte
|
||||||
|
// this is super confusing but
|
||||||
|
// ABCD_EF00
|
||||||
|
// GH12_3456
|
||||||
|
// operation:
|
||||||
|
// ABCD_EF00
|
||||||
|
// xor 0000_00GH
|
||||||
|
// = ABCD_EFGH 1234_5600
|
||||||
|
//
|
||||||
|
// then on the next iteration
|
||||||
|
// 1234_5600
|
||||||
|
// abcd_efgh
|
||||||
|
// ->
|
||||||
|
// 1234_56ab cdef_gh00
|
||||||
|
//
|
||||||
|
// ah so there's a pattern
|
||||||
|
// however many trailing 0s there are in the first array
|
||||||
|
// say there's 2
|
||||||
|
// we take the first 2 bits of the upcoming byte
|
||||||
|
// xor that against the current byte
|
||||||
|
// take the last 6 bits of the upcoming byte
|
||||||
|
// set the next byte to that
|
||||||
|
//
|
||||||
|
// have to think about edge behavior
|
||||||
|
// this is ripe for off-by-1 errors
|
||||||
|
// but i think the general idea is right
|
||||||
|
//
|
||||||
|
// so we start the iteration
|
||||||
|
// on the last byte of the first array
|
||||||
|
let last_byte_of_first_array_idx0 : number = bytes1.length - 1;
|
||||||
|
// and we end
|
||||||
|
// on the second-to-last-byte of the result array
|
||||||
|
let second_to_last_byte_of_result_array_idx0 : number = result_bytes.length - 2;
|
||||||
|
// the reason we do that is because we're doing this is because we are
|
||||||
|
// going along, xoring against the current byte and then setting the next
|
||||||
|
// byte
|
||||||
|
//
|
||||||
|
// ok so
|
||||||
|
for (let this_result_byte_idx0 = last_byte_of_first_array_idx0;
|
||||||
|
this_result_byte_idx0 <= second_to_last_byte_of_result_array_idx0;
|
||||||
|
this_result_byte_idx0++)
|
||||||
|
{
|
||||||
|
let this_result_byte : number = result_bytes[this_result_byte_idx0];
|
||||||
|
|
||||||
|
// ok here we need to fish out the relevant byte of the second array
|
||||||
|
// gaaah
|
||||||
|
// so this will be 0 at the start of the loop
|
||||||
|
let relevant_byte_of_second_array_idx0 : number = this_result_byte_idx0 - last_byte_of_first_array_idx0;
|
||||||
|
let relevant_byte_of_second_array : number = bytes2[relevant_byte_of_second_array_idx0];
|
||||||
|
|
||||||
|
// ok so let's fish out the leading digits
|
||||||
|
// the number of leading digits is the number of trailing 0s in the first array
|
||||||
|
let num_leading_digits : number = num_trailing_zeros_in_first_array;
|
||||||
|
let num_trailing_digits : number = 8 - num_leading_digits;
|
||||||
|
|
||||||
|
// suppose there are 2 leading digits and 6 trailing digits
|
||||||
|
// ABCD_EFGH
|
||||||
|
// leading digits are
|
||||||
|
// ABCD_EFGH >> 6 = 0000_00AB
|
||||||
|
// trailing digits are
|
||||||
|
// (ABCD_EFGH << 2) % 255 = CDEF_GH00
|
||||||
|
let leading_digits : number = relevant_byte_of_second_array >> num_trailing_digits;
|
||||||
|
let trailing_digits : number = (relevant_byte_of_second_array << num_leading_digits) % 255;
|
||||||
|
|
||||||
|
// xor the current byte against the leading digits
|
||||||
|
let new_this_result_byte : number = this_result_byte ^ leading_digits;
|
||||||
|
result_bytes[this_result_byte_idx0] = new_this_result_byte;
|
||||||
|
|
||||||
|
// set the next byte to the trailing digits
|
||||||
|
result_bytes[this_result_byte_idx0 + 1] = trailing_digits;
|
||||||
|
}
|
||||||
|
|
||||||
|
// i think we're done
|
||||||
|
return {bit_length : result_bit_length,
|
||||||
|
bytes : result_bytes};
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<Uint8Array>,
|
||||||
|
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); // <<Bin/binary, DoubleSha:4>>
|
||||||
|
let rlp_stuff : Uint8Array = stuff.slice(0, stuff.length - 4); // <<Bin/binary>>
|
||||||
|
let decoded_datas : Array<rlp.decoded_data> = rlp.decode(rlp_stuff).decoded_data as Array<rlp.decoded_data>; // 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); // <<Tag:(byte_size(TagBytes))>> = TagBytes
|
||||||
|
let vsn : Uint8Array = decoded_datas[1] as Uint8Array;
|
||||||
|
// tx fields
|
||||||
|
let tx_fields : Array<rlp.decoded_data> = 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<rlp.decoded_data>): 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<rlp.decoded_data>): SignedTx {
|
||||||
|
throw new Error('nyi');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a SpendTx
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
decode_fields_SpendTx(fields: Array<rlp.decoded_data>): SpendTx {
|
||||||
|
// [<sender> :: id(),
|
||||||
|
// <recipient> :: id(),
|
||||||
|
// <amount> :: int(),
|
||||||
|
// <fee> :: int(),
|
||||||
|
// <ttl> :: int(),
|
||||||
|
// <nonce> :: int(),
|
||||||
|
// <payload> :: 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<rlp.decoded_data>): ContractCreateTx {
|
||||||
|
throw new Error('nyi');
|
||||||
|
}
|
||||||
|
|
||||||
|
function
|
||||||
|
decode_fields_ContractCallTx(fields: Array<rlp.decoded_data>): 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)};
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Vanillae Seed Phrase Library
|
||||||
|
*
|
||||||
|
* Refs:
|
||||||
|
* 1. BIP 39: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
|
||||||
|
*
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a given number of seed bits.
|
||||||
|
*
|
||||||
|
* `how_many` must be a multiple of 33.
|
||||||
|
*
|
||||||
|
* Ref: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#generating-the-mnemonic
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
seed_and_check_bits
|
||||||
|
(how_many : number)
|
||||||
|
: Uint8Array
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -5,19 +5,59 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
// basic bytes functions
|
||||||
|
bytes_eq,
|
||||||
|
bytes_concat,
|
||||||
|
strong_rand_bytes,
|
||||||
bytes_to_bigint,
|
bytes_to_bigint,
|
||||||
bigint_to_bytes,
|
bigint_to_bytes,
|
||||||
concat,
|
// basic bits functions
|
||||||
strong_rand_bytes
|
bits_null,
|
||||||
|
bits_zeros,
|
||||||
|
bits_ones,
|
||||||
|
bits_i0th,
|
||||||
|
bits_concat
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type {
|
||||||
|
bits
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bytewise equality of two `Uint8Array`s
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
bytes_eq
|
||||||
|
(arr1 : Uint8Array,
|
||||||
|
arr2 : Uint8Array)
|
||||||
|
: boolean
|
||||||
|
{
|
||||||
|
if (arr1.length !== arr2.length)
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
let len : number = arr1.length;
|
||||||
|
for (let i = 0;
|
||||||
|
i < len;
|
||||||
|
i++)
|
||||||
|
{
|
||||||
|
if (arr1[i] !== arr2[i])
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Concatenate two arrays
|
* Concatenate two arrays
|
||||||
*/
|
*/
|
||||||
function
|
function
|
||||||
concat
|
bytes_concat
|
||||||
(arr1 : Uint8Array,
|
(arr1 : Uint8Array,
|
||||||
arr2 : Uint8Array)
|
arr2 : Uint8Array)
|
||||||
: Uint8Array
|
: Uint8Array
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
dist
|
||||||
|
jex_mindist
|
||||||
|
jex_include
|
||||||
|
*.beam
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
test/testgen/b58_cases_2.eterms
|
||||||
|
test/testgen/b58_cases_3.eterms
|
||||||
|
__pycache__
|
||||||
|
docs/
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{type, library}.
|
||||||
|
{realm, local}.
|
||||||
|
{name, vdk_faert}.
|
||||||
|
{version, "0.1.0"}.
|
||||||
|
{deps, ["local-vdk_safe-0.1.0"]}.
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
done:
|
||||||
|
- base64/base58 encoding/decoding
|
||||||
|
- rlp
|
||||||
|
|
||||||
|
today:
|
||||||
|
- pull apart data
|
||||||
|
|
||||||
|
tomorrow:
|
||||||
|
- basic easy serialization/deserialization
|
||||||
|
- serialize/deserialize examples
|
||||||
|
- (maybe) pull in awcp/sidekick
|
||||||
|
- sidekick: sign/propagate and sign/noprop
|
||||||
|
|
||||||
|
later:
|
||||||
|
|
||||||
|
- general node querying with parasite (think about autogeneration)
|
||||||
|
- simple aetto/ae/etc unit converter
|
||||||
|
- ghetto sophia ide
|
||||||
|
- general "send money to someone" page
|
||||||
|
|
||||||
|
GUI:
|
||||||
|
|
||||||
|
- simple testnet explorer
|
||||||
|
- contract ide
|
||||||
@@ -0,0 +1,403 @@
|
|||||||
|
/**
|
||||||
|
* Node API constructor/deconstructor
|
||||||
|
*
|
||||||
|
* This is similar to serialization/deserialization, but not quite the same
|
||||||
|
* thing. It converts back and forth between different forms of
|
||||||
|
* "api-serialized" data.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* ERLANG TYPE | JS TYPE
|
||||||
|
* -------------------------------
|
||||||
|
* integer | bigint
|
||||||
|
* list | Array
|
||||||
|
* binary | Uint8Array
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* # Example
|
||||||
|
*
|
||||||
|
* We start with the string `tx_+FgMAaEByWN+RgDnqzvC5n/GQOgjdkRE9DBV2l1VeKSaN1r6GNyhAXtm5sMFBwg25Ol5IFI9w+pZy7/YbFi6BwPqi80KuKdsCoYPJvVhyAAACYdoYWluYW5hA7ZC1w==`.
|
||||||
|
*
|
||||||
|
* We can tell by the `tx_` prefix that this represents transaction data of
|
||||||
|
* some sort. But the rest of the data is totally opaque. The task of this
|
||||||
|
* module is to "humanize" that `tx_...` string and show what data is contained
|
||||||
|
* in the rest of it.
|
||||||
|
*
|
||||||
|
* The remainder of the string is a base64-encoded bytestring
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* 3> io:format("~tw~n", [base64:decode(<<"+FgMAaEByWN+RgDnqzvC5n/GQOgjdkRE9DBV2l1VeKSaN1r6GNyhAXtm5sMFBwg25Ol5IFI9w+pZy7/YbFi6BwPqi80KuKdsCoYPJvVhyAAACYdoYWluYW5hA7ZC1w==">>)]).
|
||||||
|
* <<248,88,12,1,161,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,90,250,24,220,161,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,10,184,167,108,10,134,15,38,245,97,200,0,0,9,135,104,97,105,110,97,110,97,3,182,66,215>>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* That bytestring contains data encoded using Ethereum's RLP codec. Luckily, I
|
||||||
|
* wrote an RLP decoder. RLP has two types of data: binaries, and
|
||||||
|
* arbitrary-depth (possibly empty) lists of binaries.
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* -type decoded_data() :: binary() | [decoded_data()].
|
||||||
|
*
|
||||||
|
* -spec decode(RLP) -> {Data, Rest}
|
||||||
|
* when RLP :: binary(),
|
||||||
|
* Data :: decoded_data(),
|
||||||
|
* Rest :: binary().
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* 2> rlp:decode(base64:decode(<<"+FgMAaEByWN+RgDnqzvC5n/GQOgjdkRE9DBV2l1VeKSaN1r6GNyhAXtm5sMFBwg25Ol5IFI9w+pZy7/YbFi6BwPqi80KuKdsCoYPJvVhyAAACYdoYWluYW5hA7ZC1w==">>)).
|
||||||
|
* {[<<"\f">>,
|
||||||
|
* <<1>>,
|
||||||
|
* <<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,...>>,
|
||||||
|
* <<1,123,102,230,195,5,7,8,54,228,233,121,32,82,61,195,234,
|
||||||
|
* 89,203,191,216,108,88,186,...>>,
|
||||||
|
* <<"\n">>,
|
||||||
|
* <<15,38,245,97,200,0>>,
|
||||||
|
* <<0>>,
|
||||||
|
* <<"\t">>,<<"hainana">>],
|
||||||
|
* <<3,182,66,215>>}
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* As expected, we get back the return tuple `{Data, Rest}`. `Rest` is the double-sha256 of the beginning
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* 3> X = base64:decode(<<"+FgMAaEByWN+RgDnqzvC5n/GQOgjdkRE9DBV2l1VeKSaN1r6GNyhAXtm5sMFBwg25Ol5IFI9w+pZy7/YbFi6BwPqi80KuKdsCoYPJvVhyAAACYdoYWluYW5hA7ZC1w==">>).
|
||||||
|
* <<248,88,12,1,161,1,201,99,126,70,0,231,171,59,194,230,
|
||||||
|
* 127,198,64,232,35,118,68,68,244,48,85,218,93,...>>
|
||||||
|
* 4> SizeX = byte_size(X).
|
||||||
|
* 94
|
||||||
|
* 6> <<RLPEncodedData:(SizeX - 4)/binary, Hash/binary>> = X.
|
||||||
|
* <<248,88,12,1,161,1,201,99,126,70,0,231,171,59,194,230,
|
||||||
|
* 127,198,64,232,35,118,68,68,244,48,85,218,93,...>>
|
||||||
|
* 10> <<Check:4/binary, _/binary>> = crypto:hash(sha256, crypto:hash(sha256, RLPEncodedData)).
|
||||||
|
* <<3,182,66,215,195,99,112,99,25,7,84,31,151,188,149,81,
|
||||||
|
* 189,184,82,207,164,68,128,43,11,174,236,59,77,...>>
|
||||||
|
* 11> Hash.
|
||||||
|
* <<3,182,66,215>>
|
||||||
|
* 12> Check.
|
||||||
|
* <<3,182,66,215>>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* What we're really interested in is `Data`
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* 14> {Data, _} = rlp:decode(X).
|
||||||
|
* {[<<"\f">>,
|
||||||
|
* <<1>>,
|
||||||
|
* <<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,...>>,
|
||||||
|
* <<1,123,102,230,195,5,7,8,54,228,233,121,32,82,61,195,234,
|
||||||
|
* 89,203,191,216,108,88,186,...>>,
|
||||||
|
* <<"\n">>,
|
||||||
|
* <<15,38,245,97,200,0>>,
|
||||||
|
* <<0>>,
|
||||||
|
* <<"\t">>,<<"hainana">>],
|
||||||
|
* <<3,182,66,215>>}
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* `Data` is a list. The first field `<<"\f">>` is meant to be an integer which
|
||||||
|
* tells us what type of data this is.
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* 16> $\f.
|
||||||
|
* 12
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* If we look at our table
|
||||||
|
* (https://github.com/aeternity/protocol/blob/master/serializations.md#table-of-object-tags),
|
||||||
|
* we see that a value of `12` is a spend transaction.
|
||||||
|
*
|
||||||
|
* The second field `<<1>>` tells us the "version" of the field orderings,
|
||||||
|
* which we can ignore for now.
|
||||||
|
*
|
||||||
|
* The remaining fields are the fields of a spend transaction (https://github.com/aeternity/protocol/blob/master/serializations.md#spend-transaction)
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* [ <sender> :: id() % <<1,201,99,126,...> "=" "ak_2XhCkjzTwcq1coXSSzHJoMZkUzTwnjH88zmPGkkowUsFNTo9UE"
|
||||||
|
* , <recipient> :: id() % <<1,123,102,230,...> "=" "ak_wM8yFU8eSETXU7VSN48HMDmevGoCMiuveQZgkPuRn1nTiRqyv"
|
||||||
|
* , <amount> :: int() % <<"\n">> "=" 10
|
||||||
|
* , <fee> :: int() % <<15,38,245,97,200,0>> "=" 16_660_000_000_000
|
||||||
|
* , <ttl> :: int() % <<0>> "=" 0
|
||||||
|
* , <nonce> :: int() % <<"\t">> "=" 9
|
||||||
|
* , <payload> :: binary() % <<"hainana">> "=" "hainana"
|
||||||
|
* ]
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Our task here is to be able to pull apart the "tx_..." string into its fields.
|
||||||
|
*
|
||||||
|
* 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) -> <<Foo:4/binary, _/binary>> = crypto:hash(sha256, crypto:hash(sha256, Bytes)), Foo end.
|
||||||
|
* #Fun<erl_eval.44.97283095>
|
||||||
|
* 33> "ak_" ++ b58:enc(<<SenderAddrBytes/binary, (DoubleSha(SenderAddrBytes))/binary>>).
|
||||||
|
* "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(<<RecipAddrBytes/binary, (DoubleSha(RecipAddrBytes))/binary>>).
|
||||||
|
* "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 {
|
||||||
|
// 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'
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias type for a `tx_...` string
|
||||||
|
*/
|
||||||
|
type tx_str = string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias type for a `sg_...` string
|
||||||
|
*/
|
||||||
|
type sg_str = string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* types of decoded tx we currently support
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
type tx_type_str
|
||||||
|
= 'SignedTx'
|
||||||
|
| 'SpendTx'
|
||||||
|
| 'ContractCreateTx'
|
||||||
|
| 'ContractCallTx';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Results of deconstruct_tx
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fields types
|
||||||
|
*/
|
||||||
|
type fields
|
||||||
|
= fields_SignedTx
|
||||||
|
| fields_SpendTx
|
||||||
|
| fields_ContractCreateTx
|
||||||
|
| fields_ContractCallTx;
|
||||||
|
|
||||||
|
type fields_SignedTx
|
||||||
|
= {signatures : Array<sg_str>,
|
||||||
|
transaction : tx_str};
|
||||||
|
|
||||||
|
type fields_SpendTx
|
||||||
|
= {sender : string,
|
||||||
|
recipient : string,
|
||||||
|
amount : bigint,
|
||||||
|
fee : bigint,
|
||||||
|
ttl : bigint,
|
||||||
|
nonce : bigint,
|
||||||
|
payload : Uint8Array};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deconstruct a Tx
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
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<rlpdata> = 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 tts : 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(tts, tx_version, tx_data.slice(2));
|
||||||
|
return {type : tts,
|
||||||
|
version : tx_version,
|
||||||
|
fields : tx_fields};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 -> <<Bytestring/binary, Hash:4/binary>>
|
||||||
|
* 4. HashedBytestring -> base64/base58 string encoding
|
||||||
|
* 5. Add string prefix
|
||||||
|
*
|
||||||
|
* This function undoes step 3 and step 2, returns back the rlp decode data
|
||||||
|
*
|
||||||
|
* FIXME: Does not check double-sha (yet); need to figure out way to handle hash failures
|
||||||
|
* FIXME: No good way to handle failure cases
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
shasha_rlp_decode_list
|
||||||
|
(hashed_bs : Uint8Array)
|
||||||
|
: Array<rlpdata>
|
||||||
|
{
|
||||||
|
let len = hashed_bs.length;
|
||||||
|
let bytes = hashed_bs.slice(0, len - 4);
|
||||||
|
let {decoded_data} = rlp.decode(bytes);
|
||||||
|
return (decoded_data as Array<rlpdata>);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an object tag that's a type of transaction to the type string
|
||||||
|
*
|
||||||
|
* See: https://github.com/aeternity/protocol/blob/master/serializations.md#table-of-object-tags
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
tx_type_str
|
||||||
|
(tx_type_int : bigint)
|
||||||
|
: tx_type_str
|
||||||
|
{
|
||||||
|
switch (tx_type_int)
|
||||||
|
{
|
||||||
|
case 11n: return 'SignedTx';
|
||||||
|
case 12n: return 'SpendTx';
|
||||||
|
case 42n: return 'ContractCreateTx';
|
||||||
|
case 43n: return 'ContractCallTx';
|
||||||
|
default: throw new Error('invalid transaction type: ' + tx_type_int);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an array of data decoded from RLP, convert it to the fields, as
|
||||||
|
* appropriate as given by the tx type string and the version
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
deconstruct_fields
|
||||||
|
(tx_type_str : tx_type_str,
|
||||||
|
tx_version : bigint,
|
||||||
|
tx_rawfields : Array<rlpdata>)
|
||||||
|
: fields
|
||||||
|
{
|
||||||
|
switch (tx_type_str)
|
||||||
|
{
|
||||||
|
// case 'SignedTx' : return deconstruct_fields_SignedTx(tx_rawfields);
|
||||||
|
case 'SpendTx' : return deconstruct_fields_SpendTx(tx_rawfields);
|
||||||
|
// case 'ContractCreateTx' : return deconstruct_fields_ContractCreateTx(tx_rawfields);
|
||||||
|
// case 'ContractCallTx' : return deconstruct_fields_ContractCallTx(tx_rawfields);
|
||||||
|
default : throw new Error('invalid tx type str: ' + tx_type_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: do all this in Erlang
|
||||||
|
function
|
||||||
|
deconstruct_fields_SpendTx
|
||||||
|
(fields: Array<rlpdata>)
|
||||||
|
: fields_SpendTx
|
||||||
|
{
|
||||||
|
let sender_bytes = fields[0];
|
||||||
|
let recip_bytes = fields[1];
|
||||||
|
let amount_bytes = fields[2];
|
||||||
|
let fee_bytes = fields[3];
|
||||||
|
let ttl_bytes = fields[4];
|
||||||
|
let nonce_bytes = fields[5];
|
||||||
|
let payload_bytes = fields[6];
|
||||||
|
return {sender : encode_id(sender_bytes),
|
||||||
|
recipient : encode_id(sender_bytes),
|
||||||
|
amount : bin.bytes_to_bigint(amount_bytes),
|
||||||
|
fee : bin.bytes_to_bigint(fee_bytes),
|
||||||
|
ttl : bin.bytes_to_bigint(ttl_bytes),
|
||||||
|
nonce : bin.bytes_to_bigint(nonce_bytes),
|
||||||
|
payload : bin.bytes_to_bigint(payload_bytes)};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a binary account/name/etc binary id into the appropriate type of string
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
encode_id
|
||||||
|
(id: Uint8Array)
|
||||||
|
: string
|
||||||
|
{
|
||||||
|
throw new Error('nyi');
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
FIXME:
|
||||||
|
1. work out all this in Erlang to clear conceptual goo
|
||||||
|
2. think about how i want type safety etc to work
|
||||||
|
3. think about a language to assert that the data has the correct shape to it
|
||||||
|
4. get some examples working in Erlang
|
||||||
|
5. convert erlang code back to ts
|
||||||
|
*/
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
/**
|
||||||
|
* Concatenate two bitstrings
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
bits_concat
|
||||||
|
(bits1 : bits,
|
||||||
|
bits2 : bits)
|
||||||
|
: bits
|
||||||
|
{
|
||||||
|
let result_bit_length : number = bits1.bit_length + bits2.bit_length;
|
||||||
|
let bytes1 : Uint8Array = bits1.bytes;
|
||||||
|
let bytes2 : Uint8Array = bits2.bytes;
|
||||||
|
// using zeros here because of our xor trick in a minute
|
||||||
|
let result_bits : bits = bits_zeros(result_bit_length);
|
||||||
|
let result_bytes : Uint8Array = result_bits.bytes;
|
||||||
|
|
||||||
|
// alright so
|
||||||
|
// we can start by copying the first bytes into result bytes
|
||||||
|
for (let bytes1_idx0 = 0;
|
||||||
|
bytes1_idx0 < bytes1.length;
|
||||||
|
bytes1_idx0++)
|
||||||
|
{
|
||||||
|
result_bytes[bytes1_idx0] = bytes1[bytes1_idx0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// next
|
||||||
|
// we need to calculate the left-shift offset
|
||||||
|
// this will be 8 - (bytes1.bit_length % 8)
|
||||||
|
let num_trailing_zeros_in_first_array : number = 8 - (bits1.bit_length % 8);
|
||||||
|
// so
|
||||||
|
// bytes1: ABCD_EF00
|
||||||
|
// bytes2: GH12_3000
|
||||||
|
// result: ABCD_EFGH 1230_0000
|
||||||
|
// ah ok, so we need to for each byte in the second array
|
||||||
|
// take the first however many bits, xor it with the existing byte
|
||||||
|
// then take the last however many bits and place them into the next byte
|
||||||
|
// this is super confusing but
|
||||||
|
// ABCD_EF00
|
||||||
|
// GH12_3456
|
||||||
|
// operation:
|
||||||
|
// ABCD_EF00
|
||||||
|
// xor 0000_00GH
|
||||||
|
// = ABCD_EFGH 1234_5600
|
||||||
|
//
|
||||||
|
// then on the next iteration
|
||||||
|
// 1234_5600
|
||||||
|
// abcd_efgh
|
||||||
|
// ->
|
||||||
|
// 1234_56ab cdef_gh00
|
||||||
|
//
|
||||||
|
// ah so there's a pattern
|
||||||
|
// however many trailing 0s there are in the first array
|
||||||
|
// say there's 2
|
||||||
|
// we take the first 2 bits of the upcoming byte
|
||||||
|
// xor that against the current byte
|
||||||
|
// take the last 6 bits of the upcoming byte
|
||||||
|
// set the next byte to that
|
||||||
|
//
|
||||||
|
// have to think about edge behavior
|
||||||
|
// this is ripe for off-by-1 errors
|
||||||
|
// but i think the general idea is right
|
||||||
|
//
|
||||||
|
// so we start the iteration
|
||||||
|
// on the last byte of the first array
|
||||||
|
let last_byte_of_first_array_idx0 : number = bytes1.length - 1;
|
||||||
|
// and we end
|
||||||
|
// on the second-to-last-byte of the result array
|
||||||
|
let second_to_last_byte_of_result_array_idx0 : number = result_bytes.length - 2;
|
||||||
|
// the reason we do that is because we're doing this is because we are
|
||||||
|
// going along, xoring against the current byte and then setting the next
|
||||||
|
// byte
|
||||||
|
//
|
||||||
|
// ok so
|
||||||
|
for (let this_result_byte_idx0 = last_byte_of_first_array_idx0;
|
||||||
|
this_result_byte_idx0 <= second_to_last_byte_of_result_array_idx0;
|
||||||
|
this_result_byte_idx0++)
|
||||||
|
{
|
||||||
|
let this_result_byte : number = result_bytes[this_result_byte_idx0];
|
||||||
|
|
||||||
|
// ok here we need to fish out the relevant byte of the second array
|
||||||
|
// gaaah
|
||||||
|
// so this will be 0 at the start of the loop
|
||||||
|
let relevant_byte_of_second_array_idx0 : number = this_result_byte_idx0 - last_byte_of_first_array_idx0;
|
||||||
|
let relevant_byte_of_second_array : number = bytes2[relevant_byte_of_second_array_idx0];
|
||||||
|
|
||||||
|
// ok so let's fish out the leading digits
|
||||||
|
// the number of leading digits is the number of trailing 0s in the first array
|
||||||
|
let num_leading_digits : number = num_trailing_zeros_in_first_array;
|
||||||
|
let num_trailing_digits : number = 8 - num_leading_digits;
|
||||||
|
|
||||||
|
// suppose there are 2 leading digits and 6 trailing digits
|
||||||
|
// ABCD_EFGH
|
||||||
|
// leading digits are
|
||||||
|
// ABCD_EFGH >> 6 = 0000_00AB
|
||||||
|
// trailing digits are
|
||||||
|
// (ABCD_EFGH << 2) % 255 = CDEF_GH00
|
||||||
|
let leading_digits : number = relevant_byte_of_second_array >> num_trailing_digits;
|
||||||
|
let trailing_digits : number = (relevant_byte_of_second_array << num_leading_digits) % 255;
|
||||||
|
|
||||||
|
// xor the current byte against the leading digits
|
||||||
|
let new_this_result_byte : number = this_result_byte ^ leading_digits;
|
||||||
|
result_bytes[this_result_byte_idx0] = new_this_result_byte;
|
||||||
|
|
||||||
|
// set the next byte to the trailing digits
|
||||||
|
result_bytes[this_result_byte_idx0 + 1] = trailing_digits;
|
||||||
|
}
|
||||||
|
|
||||||
|
// i think we're done
|
||||||
|
return {bit_length : result_bit_length,
|
||||||
|
bytes : result_bytes};
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<Uint8Array>,
|
||||||
|
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); // <<Bin/binary, DoubleSha:4>>
|
||||||
|
let rlp_stuff : Uint8Array = stuff.slice(0, stuff.length - 4); // <<Bin/binary>>
|
||||||
|
let decoded_datas : Array<rlp.decoded_data> = rlp.decode(rlp_stuff).decoded_data as Array<rlp.decoded_data>; // 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); // <<Tag:(byte_size(TagBytes))>> = TagBytes
|
||||||
|
let vsn : Uint8Array = decoded_datas[1] as Uint8Array;
|
||||||
|
// tx fields
|
||||||
|
let tx_fields : Array<rlp.decoded_data> = 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<rlp.decoded_data>): 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<rlp.decoded_data>): SignedTx {
|
||||||
|
throw new Error('nyi');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a SpendTx
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
decode_fields_SpendTx(fields: Array<rlp.decoded_data>): SpendTx {
|
||||||
|
// [<sender> :: id(),
|
||||||
|
// <recipient> :: id(),
|
||||||
|
// <amount> :: int(),
|
||||||
|
// <fee> :: int(),
|
||||||
|
// <ttl> :: int(),
|
||||||
|
// <nonce> :: int(),
|
||||||
|
// <payload> :: 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<rlp.decoded_data>): ContractCreateTx {
|
||||||
|
throw new Error('nyi');
|
||||||
|
}
|
||||||
|
|
||||||
|
function
|
||||||
|
decode_fields_ContractCallTx(fields: Array<rlp.decoded_data>): 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)};
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Vanillae Seed Phrase Library
|
||||||
|
*
|
||||||
|
* Refs:
|
||||||
|
* 1. BIP 39: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
|
||||||
|
*
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a given number of seed bits.
|
||||||
|
*
|
||||||
|
* `how_many` must be a multiple of 33.
|
||||||
|
*
|
||||||
|
* Ref: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#generating-the-mnemonic
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
seed_and_check_bits
|
||||||
|
(how_many : number)
|
||||||
|
: Uint8Array
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ export {
|
|||||||
words
|
words
|
||||||
}
|
}
|
||||||
|
|
||||||
import * as safe from './safe.js';
|
import * as safe from './jex_include/local-vdk_safe-0.1.0/dist/vdk_safe.js';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
dist
|
||||||
|
jex_mindist
|
||||||
|
jex_include
|
||||||
|
*.beam
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
test/testgen/b58_cases_2.eterms
|
||||||
|
test/testgen/b58_cases_3.eterms
|
||||||
|
__pycache__
|
||||||
|
docs/
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{type, library}.
|
{type, library}.
|
||||||
{realm, local}.
|
{realm, local}.
|
||||||
{name, vanillae}.
|
{name, vdk_rlp}.
|
||||||
{version, "0.1.0"}.
|
{version, "0.1.0"}.
|
||||||
{deps, []}.
|
{deps, []}.
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
done:
|
||||||
|
- base64/base58 encoding/decoding
|
||||||
|
- rlp
|
||||||
|
|
||||||
|
today:
|
||||||
|
- pull apart data
|
||||||
|
|
||||||
|
tomorrow:
|
||||||
|
- basic easy serialization/deserialization
|
||||||
|
- serialize/deserialize examples
|
||||||
|
- (maybe) pull in awcp/sidekick
|
||||||
|
- sidekick: sign/propagate and sign/noprop
|
||||||
|
|
||||||
|
later:
|
||||||
|
|
||||||
|
- general node querying with parasite (think about autogeneration)
|
||||||
|
- simple aetto/ae/etc unit converter
|
||||||
|
- ghetto sophia ide
|
||||||
|
- general "send money to someone" page
|
||||||
|
|
||||||
|
GUI:
|
||||||
|
|
||||||
|
- simple testnet explorer
|
||||||
|
- contract ide
|
||||||
@@ -0,0 +1,403 @@
|
|||||||
|
/**
|
||||||
|
* Node API constructor/deconstructor
|
||||||
|
*
|
||||||
|
* This is similar to serialization/deserialization, but not quite the same
|
||||||
|
* thing. It converts back and forth between different forms of
|
||||||
|
* "api-serialized" data.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* ERLANG TYPE | JS TYPE
|
||||||
|
* -------------------------------
|
||||||
|
* integer | bigint
|
||||||
|
* list | Array
|
||||||
|
* binary | Uint8Array
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* # Example
|
||||||
|
*
|
||||||
|
* We start with the string `tx_+FgMAaEByWN+RgDnqzvC5n/GQOgjdkRE9DBV2l1VeKSaN1r6GNyhAXtm5sMFBwg25Ol5IFI9w+pZy7/YbFi6BwPqi80KuKdsCoYPJvVhyAAACYdoYWluYW5hA7ZC1w==`.
|
||||||
|
*
|
||||||
|
* We can tell by the `tx_` prefix that this represents transaction data of
|
||||||
|
* some sort. But the rest of the data is totally opaque. The task of this
|
||||||
|
* module is to "humanize" that `tx_...` string and show what data is contained
|
||||||
|
* in the rest of it.
|
||||||
|
*
|
||||||
|
* The remainder of the string is a base64-encoded bytestring
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* 3> io:format("~tw~n", [base64:decode(<<"+FgMAaEByWN+RgDnqzvC5n/GQOgjdkRE9DBV2l1VeKSaN1r6GNyhAXtm5sMFBwg25Ol5IFI9w+pZy7/YbFi6BwPqi80KuKdsCoYPJvVhyAAACYdoYWluYW5hA7ZC1w==">>)]).
|
||||||
|
* <<248,88,12,1,161,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,90,250,24,220,161,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,10,184,167,108,10,134,15,38,245,97,200,0,0,9,135,104,97,105,110,97,110,97,3,182,66,215>>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* That bytestring contains data encoded using Ethereum's RLP codec. Luckily, I
|
||||||
|
* wrote an RLP decoder. RLP has two types of data: binaries, and
|
||||||
|
* arbitrary-depth (possibly empty) lists of binaries.
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* -type decoded_data() :: binary() | [decoded_data()].
|
||||||
|
*
|
||||||
|
* -spec decode(RLP) -> {Data, Rest}
|
||||||
|
* when RLP :: binary(),
|
||||||
|
* Data :: decoded_data(),
|
||||||
|
* Rest :: binary().
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* 2> rlp:decode(base64:decode(<<"+FgMAaEByWN+RgDnqzvC5n/GQOgjdkRE9DBV2l1VeKSaN1r6GNyhAXtm5sMFBwg25Ol5IFI9w+pZy7/YbFi6BwPqi80KuKdsCoYPJvVhyAAACYdoYWluYW5hA7ZC1w==">>)).
|
||||||
|
* {[<<"\f">>,
|
||||||
|
* <<1>>,
|
||||||
|
* <<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,...>>,
|
||||||
|
* <<1,123,102,230,195,5,7,8,54,228,233,121,32,82,61,195,234,
|
||||||
|
* 89,203,191,216,108,88,186,...>>,
|
||||||
|
* <<"\n">>,
|
||||||
|
* <<15,38,245,97,200,0>>,
|
||||||
|
* <<0>>,
|
||||||
|
* <<"\t">>,<<"hainana">>],
|
||||||
|
* <<3,182,66,215>>}
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* As expected, we get back the return tuple `{Data, Rest}`. `Rest` is the double-sha256 of the beginning
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* 3> X = base64:decode(<<"+FgMAaEByWN+RgDnqzvC5n/GQOgjdkRE9DBV2l1VeKSaN1r6GNyhAXtm5sMFBwg25Ol5IFI9w+pZy7/YbFi6BwPqi80KuKdsCoYPJvVhyAAACYdoYWluYW5hA7ZC1w==">>).
|
||||||
|
* <<248,88,12,1,161,1,201,99,126,70,0,231,171,59,194,230,
|
||||||
|
* 127,198,64,232,35,118,68,68,244,48,85,218,93,...>>
|
||||||
|
* 4> SizeX = byte_size(X).
|
||||||
|
* 94
|
||||||
|
* 6> <<RLPEncodedData:(SizeX - 4)/binary, Hash/binary>> = X.
|
||||||
|
* <<248,88,12,1,161,1,201,99,126,70,0,231,171,59,194,230,
|
||||||
|
* 127,198,64,232,35,118,68,68,244,48,85,218,93,...>>
|
||||||
|
* 10> <<Check:4/binary, _/binary>> = crypto:hash(sha256, crypto:hash(sha256, RLPEncodedData)).
|
||||||
|
* <<3,182,66,215,195,99,112,99,25,7,84,31,151,188,149,81,
|
||||||
|
* 189,184,82,207,164,68,128,43,11,174,236,59,77,...>>
|
||||||
|
* 11> Hash.
|
||||||
|
* <<3,182,66,215>>
|
||||||
|
* 12> Check.
|
||||||
|
* <<3,182,66,215>>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* What we're really interested in is `Data`
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* 14> {Data, _} = rlp:decode(X).
|
||||||
|
* {[<<"\f">>,
|
||||||
|
* <<1>>,
|
||||||
|
* <<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,...>>,
|
||||||
|
* <<1,123,102,230,195,5,7,8,54,228,233,121,32,82,61,195,234,
|
||||||
|
* 89,203,191,216,108,88,186,...>>,
|
||||||
|
* <<"\n">>,
|
||||||
|
* <<15,38,245,97,200,0>>,
|
||||||
|
* <<0>>,
|
||||||
|
* <<"\t">>,<<"hainana">>],
|
||||||
|
* <<3,182,66,215>>}
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* `Data` is a list. The first field `<<"\f">>` is meant to be an integer which
|
||||||
|
* tells us what type of data this is.
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* 16> $\f.
|
||||||
|
* 12
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* If we look at our table
|
||||||
|
* (https://github.com/aeternity/protocol/blob/master/serializations.md#table-of-object-tags),
|
||||||
|
* we see that a value of `12` is a spend transaction.
|
||||||
|
*
|
||||||
|
* The second field `<<1>>` tells us the "version" of the field orderings,
|
||||||
|
* which we can ignore for now.
|
||||||
|
*
|
||||||
|
* The remaining fields are the fields of a spend transaction (https://github.com/aeternity/protocol/blob/master/serializations.md#spend-transaction)
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* [ <sender> :: id() % <<1,201,99,126,...> "=" "ak_2XhCkjzTwcq1coXSSzHJoMZkUzTwnjH88zmPGkkowUsFNTo9UE"
|
||||||
|
* , <recipient> :: id() % <<1,123,102,230,...> "=" "ak_wM8yFU8eSETXU7VSN48HMDmevGoCMiuveQZgkPuRn1nTiRqyv"
|
||||||
|
* , <amount> :: int() % <<"\n">> "=" 10
|
||||||
|
* , <fee> :: int() % <<15,38,245,97,200,0>> "=" 16_660_000_000_000
|
||||||
|
* , <ttl> :: int() % <<0>> "=" 0
|
||||||
|
* , <nonce> :: int() % <<"\t">> "=" 9
|
||||||
|
* , <payload> :: binary() % <<"hainana">> "=" "hainana"
|
||||||
|
* ]
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Our task here is to be able to pull apart the "tx_..." string into its fields.
|
||||||
|
*
|
||||||
|
* 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) -> <<Foo:4/binary, _/binary>> = crypto:hash(sha256, crypto:hash(sha256, Bytes)), Foo end.
|
||||||
|
* #Fun<erl_eval.44.97283095>
|
||||||
|
* 33> "ak_" ++ b58:enc(<<SenderAddrBytes/binary, (DoubleSha(SenderAddrBytes))/binary>>).
|
||||||
|
* "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(<<RecipAddrBytes/binary, (DoubleSha(RecipAddrBytes))/binary>>).
|
||||||
|
* "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 {
|
||||||
|
// 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'
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias type for a `tx_...` string
|
||||||
|
*/
|
||||||
|
type tx_str = string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias type for a `sg_...` string
|
||||||
|
*/
|
||||||
|
type sg_str = string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* types of decoded tx we currently support
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
type tx_type_str
|
||||||
|
= 'SignedTx'
|
||||||
|
| 'SpendTx'
|
||||||
|
| 'ContractCreateTx'
|
||||||
|
| 'ContractCallTx';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Results of deconstruct_tx
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fields types
|
||||||
|
*/
|
||||||
|
type fields
|
||||||
|
= fields_SignedTx
|
||||||
|
| fields_SpendTx
|
||||||
|
| fields_ContractCreateTx
|
||||||
|
| fields_ContractCallTx;
|
||||||
|
|
||||||
|
type fields_SignedTx
|
||||||
|
= {signatures : Array<sg_str>,
|
||||||
|
transaction : tx_str};
|
||||||
|
|
||||||
|
type fields_SpendTx
|
||||||
|
= {sender : string,
|
||||||
|
recipient : string,
|
||||||
|
amount : bigint,
|
||||||
|
fee : bigint,
|
||||||
|
ttl : bigint,
|
||||||
|
nonce : bigint,
|
||||||
|
payload : Uint8Array};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deconstruct a Tx
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
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<rlpdata> = 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 tts : 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(tts, tx_version, tx_data.slice(2));
|
||||||
|
return {type : tts,
|
||||||
|
version : tx_version,
|
||||||
|
fields : tx_fields};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 -> <<Bytestring/binary, Hash:4/binary>>
|
||||||
|
* 4. HashedBytestring -> base64/base58 string encoding
|
||||||
|
* 5. Add string prefix
|
||||||
|
*
|
||||||
|
* This function undoes step 3 and step 2, returns back the rlp decode data
|
||||||
|
*
|
||||||
|
* FIXME: Does not check double-sha (yet); need to figure out way to handle hash failures
|
||||||
|
* FIXME: No good way to handle failure cases
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
shasha_rlp_decode_list
|
||||||
|
(hashed_bs : Uint8Array)
|
||||||
|
: Array<rlpdata>
|
||||||
|
{
|
||||||
|
let len = hashed_bs.length;
|
||||||
|
let bytes = hashed_bs.slice(0, len - 4);
|
||||||
|
let {decoded_data} = rlp.decode(bytes);
|
||||||
|
return (decoded_data as Array<rlpdata>);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an object tag that's a type of transaction to the type string
|
||||||
|
*
|
||||||
|
* See: https://github.com/aeternity/protocol/blob/master/serializations.md#table-of-object-tags
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
tx_type_str
|
||||||
|
(tx_type_int : bigint)
|
||||||
|
: tx_type_str
|
||||||
|
{
|
||||||
|
switch (tx_type_int)
|
||||||
|
{
|
||||||
|
case 11n: return 'SignedTx';
|
||||||
|
case 12n: return 'SpendTx';
|
||||||
|
case 42n: return 'ContractCreateTx';
|
||||||
|
case 43n: return 'ContractCallTx';
|
||||||
|
default: throw new Error('invalid transaction type: ' + tx_type_int);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an array of data decoded from RLP, convert it to the fields, as
|
||||||
|
* appropriate as given by the tx type string and the version
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
deconstruct_fields
|
||||||
|
(tx_type_str : tx_type_str,
|
||||||
|
tx_version : bigint,
|
||||||
|
tx_rawfields : Array<rlpdata>)
|
||||||
|
: fields
|
||||||
|
{
|
||||||
|
switch (tx_type_str)
|
||||||
|
{
|
||||||
|
// case 'SignedTx' : return deconstruct_fields_SignedTx(tx_rawfields);
|
||||||
|
case 'SpendTx' : return deconstruct_fields_SpendTx(tx_rawfields);
|
||||||
|
// case 'ContractCreateTx' : return deconstruct_fields_ContractCreateTx(tx_rawfields);
|
||||||
|
// case 'ContractCallTx' : return deconstruct_fields_ContractCallTx(tx_rawfields);
|
||||||
|
default : throw new Error('invalid tx type str: ' + tx_type_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: do all this in Erlang
|
||||||
|
function
|
||||||
|
deconstruct_fields_SpendTx
|
||||||
|
(fields: Array<rlpdata>)
|
||||||
|
: fields_SpendTx
|
||||||
|
{
|
||||||
|
let sender_bytes = fields[0];
|
||||||
|
let recip_bytes = fields[1];
|
||||||
|
let amount_bytes = fields[2];
|
||||||
|
let fee_bytes = fields[3];
|
||||||
|
let ttl_bytes = fields[4];
|
||||||
|
let nonce_bytes = fields[5];
|
||||||
|
let payload_bytes = fields[6];
|
||||||
|
return {sender : encode_id(sender_bytes),
|
||||||
|
recipient : encode_id(sender_bytes),
|
||||||
|
amount : bin.bytes_to_bigint(amount_bytes),
|
||||||
|
fee : bin.bytes_to_bigint(fee_bytes),
|
||||||
|
ttl : bin.bytes_to_bigint(ttl_bytes),
|
||||||
|
nonce : bin.bytes_to_bigint(nonce_bytes),
|
||||||
|
payload : bin.bytes_to_bigint(payload_bytes)};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a binary account/name/etc binary id into the appropriate type of string
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
encode_id
|
||||||
|
(id: Uint8Array)
|
||||||
|
: string
|
||||||
|
{
|
||||||
|
throw new Error('nyi');
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
FIXME:
|
||||||
|
1. work out all this in Erlang to clear conceptual goo
|
||||||
|
2. think about how i want type safety etc to work
|
||||||
|
3. think about a language to assert that the data has the correct shape to it
|
||||||
|
4. get some examples working in Erlang
|
||||||
|
5. convert erlang code back to ts
|
||||||
|
*/
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
/**
|
||||||
|
* Concatenate two bitstrings
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
bits_concat
|
||||||
|
(bits1 : bits,
|
||||||
|
bits2 : bits)
|
||||||
|
: bits
|
||||||
|
{
|
||||||
|
let result_bit_length : number = bits1.bit_length + bits2.bit_length;
|
||||||
|
let bytes1 : Uint8Array = bits1.bytes;
|
||||||
|
let bytes2 : Uint8Array = bits2.bytes;
|
||||||
|
// using zeros here because of our xor trick in a minute
|
||||||
|
let result_bits : bits = bits_zeros(result_bit_length);
|
||||||
|
let result_bytes : Uint8Array = result_bits.bytes;
|
||||||
|
|
||||||
|
// alright so
|
||||||
|
// we can start by copying the first bytes into result bytes
|
||||||
|
for (let bytes1_idx0 = 0;
|
||||||
|
bytes1_idx0 < bytes1.length;
|
||||||
|
bytes1_idx0++)
|
||||||
|
{
|
||||||
|
result_bytes[bytes1_idx0] = bytes1[bytes1_idx0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// next
|
||||||
|
// we need to calculate the left-shift offset
|
||||||
|
// this will be 8 - (bytes1.bit_length % 8)
|
||||||
|
let num_trailing_zeros_in_first_array : number = 8 - (bits1.bit_length % 8);
|
||||||
|
// so
|
||||||
|
// bytes1: ABCD_EF00
|
||||||
|
// bytes2: GH12_3000
|
||||||
|
// result: ABCD_EFGH 1230_0000
|
||||||
|
// ah ok, so we need to for each byte in the second array
|
||||||
|
// take the first however many bits, xor it with the existing byte
|
||||||
|
// then take the last however many bits and place them into the next byte
|
||||||
|
// this is super confusing but
|
||||||
|
// ABCD_EF00
|
||||||
|
// GH12_3456
|
||||||
|
// operation:
|
||||||
|
// ABCD_EF00
|
||||||
|
// xor 0000_00GH
|
||||||
|
// = ABCD_EFGH 1234_5600
|
||||||
|
//
|
||||||
|
// then on the next iteration
|
||||||
|
// 1234_5600
|
||||||
|
// abcd_efgh
|
||||||
|
// ->
|
||||||
|
// 1234_56ab cdef_gh00
|
||||||
|
//
|
||||||
|
// ah so there's a pattern
|
||||||
|
// however many trailing 0s there are in the first array
|
||||||
|
// say there's 2
|
||||||
|
// we take the first 2 bits of the upcoming byte
|
||||||
|
// xor that against the current byte
|
||||||
|
// take the last 6 bits of the upcoming byte
|
||||||
|
// set the next byte to that
|
||||||
|
//
|
||||||
|
// have to think about edge behavior
|
||||||
|
// this is ripe for off-by-1 errors
|
||||||
|
// but i think the general idea is right
|
||||||
|
//
|
||||||
|
// so we start the iteration
|
||||||
|
// on the last byte of the first array
|
||||||
|
let last_byte_of_first_array_idx0 : number = bytes1.length - 1;
|
||||||
|
// and we end
|
||||||
|
// on the second-to-last-byte of the result array
|
||||||
|
let second_to_last_byte_of_result_array_idx0 : number = result_bytes.length - 2;
|
||||||
|
// the reason we do that is because we're doing this is because we are
|
||||||
|
// going along, xoring against the current byte and then setting the next
|
||||||
|
// byte
|
||||||
|
//
|
||||||
|
// ok so
|
||||||
|
for (let this_result_byte_idx0 = last_byte_of_first_array_idx0;
|
||||||
|
this_result_byte_idx0 <= second_to_last_byte_of_result_array_idx0;
|
||||||
|
this_result_byte_idx0++)
|
||||||
|
{
|
||||||
|
let this_result_byte : number = result_bytes[this_result_byte_idx0];
|
||||||
|
|
||||||
|
// ok here we need to fish out the relevant byte of the second array
|
||||||
|
// gaaah
|
||||||
|
// so this will be 0 at the start of the loop
|
||||||
|
let relevant_byte_of_second_array_idx0 : number = this_result_byte_idx0 - last_byte_of_first_array_idx0;
|
||||||
|
let relevant_byte_of_second_array : number = bytes2[relevant_byte_of_second_array_idx0];
|
||||||
|
|
||||||
|
// ok so let's fish out the leading digits
|
||||||
|
// the number of leading digits is the number of trailing 0s in the first array
|
||||||
|
let num_leading_digits : number = num_trailing_zeros_in_first_array;
|
||||||
|
let num_trailing_digits : number = 8 - num_leading_digits;
|
||||||
|
|
||||||
|
// suppose there are 2 leading digits and 6 trailing digits
|
||||||
|
// ABCD_EFGH
|
||||||
|
// leading digits are
|
||||||
|
// ABCD_EFGH >> 6 = 0000_00AB
|
||||||
|
// trailing digits are
|
||||||
|
// (ABCD_EFGH << 2) % 255 = CDEF_GH00
|
||||||
|
let leading_digits : number = relevant_byte_of_second_array >> num_trailing_digits;
|
||||||
|
let trailing_digits : number = (relevant_byte_of_second_array << num_leading_digits) % 255;
|
||||||
|
|
||||||
|
// xor the current byte against the leading digits
|
||||||
|
let new_this_result_byte : number = this_result_byte ^ leading_digits;
|
||||||
|
result_bytes[this_result_byte_idx0] = new_this_result_byte;
|
||||||
|
|
||||||
|
// set the next byte to the trailing digits
|
||||||
|
result_bytes[this_result_byte_idx0 + 1] = trailing_digits;
|
||||||
|
}
|
||||||
|
|
||||||
|
// i think we're done
|
||||||
|
return {bit_length : result_bit_length,
|
||||||
|
bytes : result_bytes};
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<Uint8Array>,
|
||||||
|
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); // <<Bin/binary, DoubleSha:4>>
|
||||||
|
let rlp_stuff : Uint8Array = stuff.slice(0, stuff.length - 4); // <<Bin/binary>>
|
||||||
|
let decoded_datas : Array<rlp.decoded_data> = rlp.decode(rlp_stuff).decoded_data as Array<rlp.decoded_data>; // 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); // <<Tag:(byte_size(TagBytes))>> = TagBytes
|
||||||
|
let vsn : Uint8Array = decoded_datas[1] as Uint8Array;
|
||||||
|
// tx fields
|
||||||
|
let tx_fields : Array<rlp.decoded_data> = 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<rlp.decoded_data>): 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<rlp.decoded_data>): SignedTx {
|
||||||
|
throw new Error('nyi');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a SpendTx
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
decode_fields_SpendTx(fields: Array<rlp.decoded_data>): SpendTx {
|
||||||
|
// [<sender> :: id(),
|
||||||
|
// <recipient> :: id(),
|
||||||
|
// <amount> :: int(),
|
||||||
|
// <fee> :: int(),
|
||||||
|
// <ttl> :: int(),
|
||||||
|
// <nonce> :: int(),
|
||||||
|
// <payload> :: 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<rlp.decoded_data>): ContractCreateTx {
|
||||||
|
throw new Error('nyi');
|
||||||
|
}
|
||||||
|
|
||||||
|
function
|
||||||
|
decode_fields_ContractCallTx(fields: Array<rlp.decoded_data>): 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)};
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Vanillae Seed Phrase Library
|
||||||
|
*
|
||||||
|
* Refs:
|
||||||
|
* 1. BIP 39: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
|
||||||
|
*
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a given number of seed bits.
|
||||||
|
*
|
||||||
|
* `how_many` must be a multiple of 33.
|
||||||
|
*
|
||||||
|
* Ref: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#generating-the-mnemonic
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
seed_and_check_bits
|
||||||
|
(how_many : number)
|
||||||
|
: Uint8Array
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{"compilerOptions" : {"target" : "es2022",
|
||||||
|
"strict" : true,
|
||||||
|
"esModuleInterop" : true,
|
||||||
|
"skipLibCheck" : true,
|
||||||
|
"forceConsistentCasingInFileNames" : true,
|
||||||
|
"noImplicitAny" : true,
|
||||||
|
"strictNullChecks" : true,
|
||||||
|
"strictPropertyInitialization" : true,
|
||||||
|
"sourceMap" : true,
|
||||||
|
"outDir" : "dist",
|
||||||
|
"declaration" : true},
|
||||||
|
"$schema" : "https://json.schemastore.org/tsconfig",
|
||||||
|
"display" : "Recommended",
|
||||||
|
"include" : ["src/**/*"],
|
||||||
|
"exclude" : ["src/jex_include"],
|
||||||
|
"composite" : true}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
dist
|
||||||
|
jex_mindist
|
||||||
|
jex_include
|
||||||
|
*.beam
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
test/testgen/b58_cases_2.eterms
|
||||||
|
test/testgen/b58_cases_3.eterms
|
||||||
|
__pycache__
|
||||||
|
docs/
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{type, library}.
|
||||||
|
{realm, local}.
|
||||||
|
{name, vdk_safe}.
|
||||||
|
{version, "0.1.0"}.
|
||||||
|
{deps, []}.
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
done:
|
||||||
|
- base64/base58 encoding/decoding
|
||||||
|
- rlp
|
||||||
|
|
||||||
|
today:
|
||||||
|
- pull apart data
|
||||||
|
|
||||||
|
tomorrow:
|
||||||
|
- basic easy serialization/deserialization
|
||||||
|
- serialize/deserialize examples
|
||||||
|
- (maybe) pull in awcp/sidekick
|
||||||
|
- sidekick: sign/propagate and sign/noprop
|
||||||
|
|
||||||
|
later:
|
||||||
|
|
||||||
|
- general node querying with parasite (think about autogeneration)
|
||||||
|
- simple aetto/ae/etc unit converter
|
||||||
|
- ghetto sophia ide
|
||||||
|
- general "send money to someone" page
|
||||||
|
|
||||||
|
GUI:
|
||||||
|
|
||||||
|
- simple testnet explorer
|
||||||
|
- contract ide
|
||||||
@@ -0,0 +1,403 @@
|
|||||||
|
/**
|
||||||
|
* Node API constructor/deconstructor
|
||||||
|
*
|
||||||
|
* This is similar to serialization/deserialization, but not quite the same
|
||||||
|
* thing. It converts back and forth between different forms of
|
||||||
|
* "api-serialized" data.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* ERLANG TYPE | JS TYPE
|
||||||
|
* -------------------------------
|
||||||
|
* integer | bigint
|
||||||
|
* list | Array
|
||||||
|
* binary | Uint8Array
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* # Example
|
||||||
|
*
|
||||||
|
* We start with the string `tx_+FgMAaEByWN+RgDnqzvC5n/GQOgjdkRE9DBV2l1VeKSaN1r6GNyhAXtm5sMFBwg25Ol5IFI9w+pZy7/YbFi6BwPqi80KuKdsCoYPJvVhyAAACYdoYWluYW5hA7ZC1w==`.
|
||||||
|
*
|
||||||
|
* We can tell by the `tx_` prefix that this represents transaction data of
|
||||||
|
* some sort. But the rest of the data is totally opaque. The task of this
|
||||||
|
* module is to "humanize" that `tx_...` string and show what data is contained
|
||||||
|
* in the rest of it.
|
||||||
|
*
|
||||||
|
* The remainder of the string is a base64-encoded bytestring
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* 3> io:format("~tw~n", [base64:decode(<<"+FgMAaEByWN+RgDnqzvC5n/GQOgjdkRE9DBV2l1VeKSaN1r6GNyhAXtm5sMFBwg25Ol5IFI9w+pZy7/YbFi6BwPqi80KuKdsCoYPJvVhyAAACYdoYWluYW5hA7ZC1w==">>)]).
|
||||||
|
* <<248,88,12,1,161,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,90,250,24,220,161,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,10,184,167,108,10,134,15,38,245,97,200,0,0,9,135,104,97,105,110,97,110,97,3,182,66,215>>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* That bytestring contains data encoded using Ethereum's RLP codec. Luckily, I
|
||||||
|
* wrote an RLP decoder. RLP has two types of data: binaries, and
|
||||||
|
* arbitrary-depth (possibly empty) lists of binaries.
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* -type decoded_data() :: binary() | [decoded_data()].
|
||||||
|
*
|
||||||
|
* -spec decode(RLP) -> {Data, Rest}
|
||||||
|
* when RLP :: binary(),
|
||||||
|
* Data :: decoded_data(),
|
||||||
|
* Rest :: binary().
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* 2> rlp:decode(base64:decode(<<"+FgMAaEByWN+RgDnqzvC5n/GQOgjdkRE9DBV2l1VeKSaN1r6GNyhAXtm5sMFBwg25Ol5IFI9w+pZy7/YbFi6BwPqi80KuKdsCoYPJvVhyAAACYdoYWluYW5hA7ZC1w==">>)).
|
||||||
|
* {[<<"\f">>,
|
||||||
|
* <<1>>,
|
||||||
|
* <<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,...>>,
|
||||||
|
* <<1,123,102,230,195,5,7,8,54,228,233,121,32,82,61,195,234,
|
||||||
|
* 89,203,191,216,108,88,186,...>>,
|
||||||
|
* <<"\n">>,
|
||||||
|
* <<15,38,245,97,200,0>>,
|
||||||
|
* <<0>>,
|
||||||
|
* <<"\t">>,<<"hainana">>],
|
||||||
|
* <<3,182,66,215>>}
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* As expected, we get back the return tuple `{Data, Rest}`. `Rest` is the double-sha256 of the beginning
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* 3> X = base64:decode(<<"+FgMAaEByWN+RgDnqzvC5n/GQOgjdkRE9DBV2l1VeKSaN1r6GNyhAXtm5sMFBwg25Ol5IFI9w+pZy7/YbFi6BwPqi80KuKdsCoYPJvVhyAAACYdoYWluYW5hA7ZC1w==">>).
|
||||||
|
* <<248,88,12,1,161,1,201,99,126,70,0,231,171,59,194,230,
|
||||||
|
* 127,198,64,232,35,118,68,68,244,48,85,218,93,...>>
|
||||||
|
* 4> SizeX = byte_size(X).
|
||||||
|
* 94
|
||||||
|
* 6> <<RLPEncodedData:(SizeX - 4)/binary, Hash/binary>> = X.
|
||||||
|
* <<248,88,12,1,161,1,201,99,126,70,0,231,171,59,194,230,
|
||||||
|
* 127,198,64,232,35,118,68,68,244,48,85,218,93,...>>
|
||||||
|
* 10> <<Check:4/binary, _/binary>> = crypto:hash(sha256, crypto:hash(sha256, RLPEncodedData)).
|
||||||
|
* <<3,182,66,215,195,99,112,99,25,7,84,31,151,188,149,81,
|
||||||
|
* 189,184,82,207,164,68,128,43,11,174,236,59,77,...>>
|
||||||
|
* 11> Hash.
|
||||||
|
* <<3,182,66,215>>
|
||||||
|
* 12> Check.
|
||||||
|
* <<3,182,66,215>>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* What we're really interested in is `Data`
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* 14> {Data, _} = rlp:decode(X).
|
||||||
|
* {[<<"\f">>,
|
||||||
|
* <<1>>,
|
||||||
|
* <<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,...>>,
|
||||||
|
* <<1,123,102,230,195,5,7,8,54,228,233,121,32,82,61,195,234,
|
||||||
|
* 89,203,191,216,108,88,186,...>>,
|
||||||
|
* <<"\n">>,
|
||||||
|
* <<15,38,245,97,200,0>>,
|
||||||
|
* <<0>>,
|
||||||
|
* <<"\t">>,<<"hainana">>],
|
||||||
|
* <<3,182,66,215>>}
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* `Data` is a list. The first field `<<"\f">>` is meant to be an integer which
|
||||||
|
* tells us what type of data this is.
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* 16> $\f.
|
||||||
|
* 12
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* If we look at our table
|
||||||
|
* (https://github.com/aeternity/protocol/blob/master/serializations.md#table-of-object-tags),
|
||||||
|
* we see that a value of `12` is a spend transaction.
|
||||||
|
*
|
||||||
|
* The second field `<<1>>` tells us the "version" of the field orderings,
|
||||||
|
* which we can ignore for now.
|
||||||
|
*
|
||||||
|
* The remaining fields are the fields of a spend transaction (https://github.com/aeternity/protocol/blob/master/serializations.md#spend-transaction)
|
||||||
|
*
|
||||||
|
* ```erlang
|
||||||
|
* [ <sender> :: id() % <<1,201,99,126,...> "=" "ak_2XhCkjzTwcq1coXSSzHJoMZkUzTwnjH88zmPGkkowUsFNTo9UE"
|
||||||
|
* , <recipient> :: id() % <<1,123,102,230,...> "=" "ak_wM8yFU8eSETXU7VSN48HMDmevGoCMiuveQZgkPuRn1nTiRqyv"
|
||||||
|
* , <amount> :: int() % <<"\n">> "=" 10
|
||||||
|
* , <fee> :: int() % <<15,38,245,97,200,0>> "=" 16_660_000_000_000
|
||||||
|
* , <ttl> :: int() % <<0>> "=" 0
|
||||||
|
* , <nonce> :: int() % <<"\t">> "=" 9
|
||||||
|
* , <payload> :: binary() % <<"hainana">> "=" "hainana"
|
||||||
|
* ]
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Our task here is to be able to pull apart the "tx_..." string into its fields.
|
||||||
|
*
|
||||||
|
* 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) -> <<Foo:4/binary, _/binary>> = crypto:hash(sha256, crypto:hash(sha256, Bytes)), Foo end.
|
||||||
|
* #Fun<erl_eval.44.97283095>
|
||||||
|
* 33> "ak_" ++ b58:enc(<<SenderAddrBytes/binary, (DoubleSha(SenderAddrBytes))/binary>>).
|
||||||
|
* "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(<<RecipAddrBytes/binary, (DoubleSha(RecipAddrBytes))/binary>>).
|
||||||
|
* "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 {
|
||||||
|
// 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'
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias type for a `tx_...` string
|
||||||
|
*/
|
||||||
|
type tx_str = string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias type for a `sg_...` string
|
||||||
|
*/
|
||||||
|
type sg_str = string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* types of decoded tx we currently support
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
type tx_type_str
|
||||||
|
= 'SignedTx'
|
||||||
|
| 'SpendTx'
|
||||||
|
| 'ContractCreateTx'
|
||||||
|
| 'ContractCallTx';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Results of deconstruct_tx
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fields types
|
||||||
|
*/
|
||||||
|
type fields
|
||||||
|
= fields_SignedTx
|
||||||
|
| fields_SpendTx
|
||||||
|
| fields_ContractCreateTx
|
||||||
|
| fields_ContractCallTx;
|
||||||
|
|
||||||
|
type fields_SignedTx
|
||||||
|
= {signatures : Array<sg_str>,
|
||||||
|
transaction : tx_str};
|
||||||
|
|
||||||
|
type fields_SpendTx
|
||||||
|
= {sender : string,
|
||||||
|
recipient : string,
|
||||||
|
amount : bigint,
|
||||||
|
fee : bigint,
|
||||||
|
ttl : bigint,
|
||||||
|
nonce : bigint,
|
||||||
|
payload : Uint8Array};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deconstruct a Tx
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
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<rlpdata> = 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 tts : 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(tts, tx_version, tx_data.slice(2));
|
||||||
|
return {type : tts,
|
||||||
|
version : tx_version,
|
||||||
|
fields : tx_fields};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 -> <<Bytestring/binary, Hash:4/binary>>
|
||||||
|
* 4. HashedBytestring -> base64/base58 string encoding
|
||||||
|
* 5. Add string prefix
|
||||||
|
*
|
||||||
|
* This function undoes step 3 and step 2, returns back the rlp decode data
|
||||||
|
*
|
||||||
|
* FIXME: Does not check double-sha (yet); need to figure out way to handle hash failures
|
||||||
|
* FIXME: No good way to handle failure cases
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
shasha_rlp_decode_list
|
||||||
|
(hashed_bs : Uint8Array)
|
||||||
|
: Array<rlpdata>
|
||||||
|
{
|
||||||
|
let len = hashed_bs.length;
|
||||||
|
let bytes = hashed_bs.slice(0, len - 4);
|
||||||
|
let {decoded_data} = rlp.decode(bytes);
|
||||||
|
return (decoded_data as Array<rlpdata>);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an object tag that's a type of transaction to the type string
|
||||||
|
*
|
||||||
|
* See: https://github.com/aeternity/protocol/blob/master/serializations.md#table-of-object-tags
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
tx_type_str
|
||||||
|
(tx_type_int : bigint)
|
||||||
|
: tx_type_str
|
||||||
|
{
|
||||||
|
switch (tx_type_int)
|
||||||
|
{
|
||||||
|
case 11n: return 'SignedTx';
|
||||||
|
case 12n: return 'SpendTx';
|
||||||
|
case 42n: return 'ContractCreateTx';
|
||||||
|
case 43n: return 'ContractCallTx';
|
||||||
|
default: throw new Error('invalid transaction type: ' + tx_type_int);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an array of data decoded from RLP, convert it to the fields, as
|
||||||
|
* appropriate as given by the tx type string and the version
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
deconstruct_fields
|
||||||
|
(tx_type_str : tx_type_str,
|
||||||
|
tx_version : bigint,
|
||||||
|
tx_rawfields : Array<rlpdata>)
|
||||||
|
: fields
|
||||||
|
{
|
||||||
|
switch (tx_type_str)
|
||||||
|
{
|
||||||
|
// case 'SignedTx' : return deconstruct_fields_SignedTx(tx_rawfields);
|
||||||
|
case 'SpendTx' : return deconstruct_fields_SpendTx(tx_rawfields);
|
||||||
|
// case 'ContractCreateTx' : return deconstruct_fields_ContractCreateTx(tx_rawfields);
|
||||||
|
// case 'ContractCallTx' : return deconstruct_fields_ContractCallTx(tx_rawfields);
|
||||||
|
default : throw new Error('invalid tx type str: ' + tx_type_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: do all this in Erlang
|
||||||
|
function
|
||||||
|
deconstruct_fields_SpendTx
|
||||||
|
(fields: Array<rlpdata>)
|
||||||
|
: fields_SpendTx
|
||||||
|
{
|
||||||
|
let sender_bytes = fields[0];
|
||||||
|
let recip_bytes = fields[1];
|
||||||
|
let amount_bytes = fields[2];
|
||||||
|
let fee_bytes = fields[3];
|
||||||
|
let ttl_bytes = fields[4];
|
||||||
|
let nonce_bytes = fields[5];
|
||||||
|
let payload_bytes = fields[6];
|
||||||
|
return {sender : encode_id(sender_bytes),
|
||||||
|
recipient : encode_id(sender_bytes),
|
||||||
|
amount : bin.bytes_to_bigint(amount_bytes),
|
||||||
|
fee : bin.bytes_to_bigint(fee_bytes),
|
||||||
|
ttl : bin.bytes_to_bigint(ttl_bytes),
|
||||||
|
nonce : bin.bytes_to_bigint(nonce_bytes),
|
||||||
|
payload : bin.bytes_to_bigint(payload_bytes)};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a binary account/name/etc binary id into the appropriate type of string
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
encode_id
|
||||||
|
(id: Uint8Array)
|
||||||
|
: string
|
||||||
|
{
|
||||||
|
throw new Error('nyi');
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
FIXME:
|
||||||
|
1. work out all this in Erlang to clear conceptual goo
|
||||||
|
2. think about how i want type safety etc to work
|
||||||
|
3. think about a language to assert that the data has the correct shape to it
|
||||||
|
4. get some examples working in Erlang
|
||||||
|
5. convert erlang code back to ts
|
||||||
|
*/
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
/**
|
||||||
|
* Concatenate two bitstrings
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
bits_concat
|
||||||
|
(bits1 : bits,
|
||||||
|
bits2 : bits)
|
||||||
|
: bits
|
||||||
|
{
|
||||||
|
let result_bit_length : number = bits1.bit_length + bits2.bit_length;
|
||||||
|
let bytes1 : Uint8Array = bits1.bytes;
|
||||||
|
let bytes2 : Uint8Array = bits2.bytes;
|
||||||
|
// using zeros here because of our xor trick in a minute
|
||||||
|
let result_bits : bits = bits_zeros(result_bit_length);
|
||||||
|
let result_bytes : Uint8Array = result_bits.bytes;
|
||||||
|
|
||||||
|
// alright so
|
||||||
|
// we can start by copying the first bytes into result bytes
|
||||||
|
for (let bytes1_idx0 = 0;
|
||||||
|
bytes1_idx0 < bytes1.length;
|
||||||
|
bytes1_idx0++)
|
||||||
|
{
|
||||||
|
result_bytes[bytes1_idx0] = bytes1[bytes1_idx0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// next
|
||||||
|
// we need to calculate the left-shift offset
|
||||||
|
// this will be 8 - (bytes1.bit_length % 8)
|
||||||
|
let num_trailing_zeros_in_first_array : number = 8 - (bits1.bit_length % 8);
|
||||||
|
// so
|
||||||
|
// bytes1: ABCD_EF00
|
||||||
|
// bytes2: GH12_3000
|
||||||
|
// result: ABCD_EFGH 1230_0000
|
||||||
|
// ah ok, so we need to for each byte in the second array
|
||||||
|
// take the first however many bits, xor it with the existing byte
|
||||||
|
// then take the last however many bits and place them into the next byte
|
||||||
|
// this is super confusing but
|
||||||
|
// ABCD_EF00
|
||||||
|
// GH12_3456
|
||||||
|
// operation:
|
||||||
|
// ABCD_EF00
|
||||||
|
// xor 0000_00GH
|
||||||
|
// = ABCD_EFGH 1234_5600
|
||||||
|
//
|
||||||
|
// then on the next iteration
|
||||||
|
// 1234_5600
|
||||||
|
// abcd_efgh
|
||||||
|
// ->
|
||||||
|
// 1234_56ab cdef_gh00
|
||||||
|
//
|
||||||
|
// ah so there's a pattern
|
||||||
|
// however many trailing 0s there are in the first array
|
||||||
|
// say there's 2
|
||||||
|
// we take the first 2 bits of the upcoming byte
|
||||||
|
// xor that against the current byte
|
||||||
|
// take the last 6 bits of the upcoming byte
|
||||||
|
// set the next byte to that
|
||||||
|
//
|
||||||
|
// have to think about edge behavior
|
||||||
|
// this is ripe for off-by-1 errors
|
||||||
|
// but i think the general idea is right
|
||||||
|
//
|
||||||
|
// so we start the iteration
|
||||||
|
// on the last byte of the first array
|
||||||
|
let last_byte_of_first_array_idx0 : number = bytes1.length - 1;
|
||||||
|
// and we end
|
||||||
|
// on the second-to-last-byte of the result array
|
||||||
|
let second_to_last_byte_of_result_array_idx0 : number = result_bytes.length - 2;
|
||||||
|
// the reason we do that is because we're doing this is because we are
|
||||||
|
// going along, xoring against the current byte and then setting the next
|
||||||
|
// byte
|
||||||
|
//
|
||||||
|
// ok so
|
||||||
|
for (let this_result_byte_idx0 = last_byte_of_first_array_idx0;
|
||||||
|
this_result_byte_idx0 <= second_to_last_byte_of_result_array_idx0;
|
||||||
|
this_result_byte_idx0++)
|
||||||
|
{
|
||||||
|
let this_result_byte : number = result_bytes[this_result_byte_idx0];
|
||||||
|
|
||||||
|
// ok here we need to fish out the relevant byte of the second array
|
||||||
|
// gaaah
|
||||||
|
// so this will be 0 at the start of the loop
|
||||||
|
let relevant_byte_of_second_array_idx0 : number = this_result_byte_idx0 - last_byte_of_first_array_idx0;
|
||||||
|
let relevant_byte_of_second_array : number = bytes2[relevant_byte_of_second_array_idx0];
|
||||||
|
|
||||||
|
// ok so let's fish out the leading digits
|
||||||
|
// the number of leading digits is the number of trailing 0s in the first array
|
||||||
|
let num_leading_digits : number = num_trailing_zeros_in_first_array;
|
||||||
|
let num_trailing_digits : number = 8 - num_leading_digits;
|
||||||
|
|
||||||
|
// suppose there are 2 leading digits and 6 trailing digits
|
||||||
|
// ABCD_EFGH
|
||||||
|
// leading digits are
|
||||||
|
// ABCD_EFGH >> 6 = 0000_00AB
|
||||||
|
// trailing digits are
|
||||||
|
// (ABCD_EFGH << 2) % 255 = CDEF_GH00
|
||||||
|
let leading_digits : number = relevant_byte_of_second_array >> num_trailing_digits;
|
||||||
|
let trailing_digits : number = (relevant_byte_of_second_array << num_leading_digits) % 255;
|
||||||
|
|
||||||
|
// xor the current byte against the leading digits
|
||||||
|
let new_this_result_byte : number = this_result_byte ^ leading_digits;
|
||||||
|
result_bytes[this_result_byte_idx0] = new_this_result_byte;
|
||||||
|
|
||||||
|
// set the next byte to the trailing digits
|
||||||
|
result_bytes[this_result_byte_idx0 + 1] = trailing_digits;
|
||||||
|
}
|
||||||
|
|
||||||
|
// i think we're done
|
||||||
|
return {bit_length : result_bit_length,
|
||||||
|
bytes : result_bytes};
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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<Uint8Array>,
|
||||||
|
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); // <<Bin/binary, DoubleSha:4>>
|
||||||
|
let rlp_stuff : Uint8Array = stuff.slice(0, stuff.length - 4); // <<Bin/binary>>
|
||||||
|
let decoded_datas : Array<rlp.decoded_data> = rlp.decode(rlp_stuff).decoded_data as Array<rlp.decoded_data>; // 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); // <<Tag:(byte_size(TagBytes))>> = TagBytes
|
||||||
|
let vsn : Uint8Array = decoded_datas[1] as Uint8Array;
|
||||||
|
// tx fields
|
||||||
|
let tx_fields : Array<rlp.decoded_data> = 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<rlp.decoded_data>): 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<rlp.decoded_data>): SignedTx {
|
||||||
|
throw new Error('nyi');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a SpendTx
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
decode_fields_SpendTx(fields: Array<rlp.decoded_data>): SpendTx {
|
||||||
|
// [<sender> :: id(),
|
||||||
|
// <recipient> :: id(),
|
||||||
|
// <amount> :: int(),
|
||||||
|
// <fee> :: int(),
|
||||||
|
// <ttl> :: int(),
|
||||||
|
// <nonce> :: int(),
|
||||||
|
// <payload> :: 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<rlp.decoded_data>): ContractCreateTx {
|
||||||
|
throw new Error('nyi');
|
||||||
|
}
|
||||||
|
|
||||||
|
function
|
||||||
|
decode_fields_ContractCallTx(fields: Array<rlp.decoded_data>): 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)};
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Vanillae Seed Phrase Library
|
||||||
|
*
|
||||||
|
* Refs:
|
||||||
|
* 1. BIP 39: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
|
||||||
|
*
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a given number of seed bits.
|
||||||
|
*
|
||||||
|
* `how_many` must be a multiple of 33.
|
||||||
|
*
|
||||||
|
* Ref: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#generating-the-mnemonic
|
||||||
|
*/
|
||||||
|
function
|
||||||
|
seed_and_check_bits
|
||||||
|
(how_many : number)
|
||||||
|
: Uint8Array
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -3,6 +3,9 @@
|
|||||||
*
|
*
|
||||||
* The idea here is that there are situations where it is known
|
* The idea here is that there are situations where it is known
|
||||||
*
|
*
|
||||||
|
* 1. a given function call is likely to fail
|
||||||
|
* 2. the likely errors can be enumerated
|
||||||
|
*
|
||||||
* ```typescript
|
* ```typescript
|
||||||
* type Safe<ok_t, err_t>
|
* type Safe<ok_t, err_t>
|
||||||
* = Ok<ok_t>
|
* = Ok<ok_t>
|
||||||
@@ -17,9 +20,6 @@
|
|||||||
* error : err_t};
|
* error : err_t};
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* 1. a given function call is likely to fail
|
|
||||||
* 2. the likely errors can be enumerated
|
|
||||||
*
|
|
||||||
* These are called "positive errors". An example would be a page script
|
* These are called "positive errors". An example would be a page script
|
||||||
* asking a browser wallet extension to sign a transaction. The following
|
* asking a browser wallet extension to sign a transaction. The following
|
||||||
* errors, among others, are likely:
|
* errors, among others, are likely:
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{"compilerOptions" : {"target" : "es2022",
|
||||||
|
"strict" : true,
|
||||||
|
"esModuleInterop" : true,
|
||||||
|
"skipLibCheck" : true,
|
||||||
|
"forceConsistentCasingInFileNames" : true,
|
||||||
|
"noImplicitAny" : true,
|
||||||
|
"strictNullChecks" : true,
|
||||||
|
"strictPropertyInitialization" : true,
|
||||||
|
"sourceMap" : true,
|
||||||
|
"outDir" : "dist",
|
||||||
|
"declaration" : true},
|
||||||
|
"$schema" : "https://json.schemastore.org/tsconfig",
|
||||||
|
"display" : "Recommended",
|
||||||
|
"include" : ["src/**/*"],
|
||||||
|
"exclude" : ["src/jex_include"],
|
||||||
|
"composite" : true}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Base58 encoding/decoding
|
||||||
|
*/
|
||||||
|
export { encode, decode };
|
||||||
|
/**
|
||||||
|
* Encode a Uint8Array into base58
|
||||||
|
*/
|
||||||
|
declare function encode(binary: Uint8Array): string;
|
||||||
|
/**
|
||||||
|
* Decode a Base58 string into a Uint8Array
|
||||||
|
*/
|
||||||
|
declare function decode(base58: string): Uint8Array;
|
||||||
@@ -0,0 +1,308 @@
|
|||||||
|
/**
|
||||||
|
* Base58 encoding/decoding
|
||||||
|
*/
|
||||||
|
export { encode, decode };
|
||||||
|
//=============================================================================
|
||||||
|
// ENCODING
|
||||||
|
//=============================================================================
|
||||||
|
/**
|
||||||
|
* Encode a Uint8Array into base58
|
||||||
|
*/
|
||||||
|
function encode(binary) {
|
||||||
|
let num_leading_zeros = nlz(binary);
|
||||||
|
let rest = binary.slice(num_leading_zeros);
|
||||||
|
let ones = encode_zeros(num_leading_zeros);
|
||||||
|
let rest_b58 = encode_rest(rest);
|
||||||
|
let result = ones + rest_b58;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* count the number of leading zeros in a uint8array
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function nlz(bytes) {
|
||||||
|
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) {
|
||||||
|
let ones = '';
|
||||||
|
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) {
|
||||||
|
let bytes_bignum = bytes_to_bigint(bytes);
|
||||||
|
let result = bignum_to_base58(bytes_bignum);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Convert a bytestring to a bignum
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function bytes_to_bigint(bytes) {
|
||||||
|
let acc_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) {
|
||||||
|
let s = '';
|
||||||
|
while (q !== 0n) {
|
||||||
|
let this_n = q % 58n;
|
||||||
|
q /= 58n;
|
||||||
|
let this_b58_char = bigint_to_char(this_n);
|
||||||
|
s = this_b58_char + s;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
//=============================================================================
|
||||||
|
// DECODING
|
||||||
|
//=============================================================================
|
||||||
|
/**
|
||||||
|
* Decode a Base58 string into a Uint8Array
|
||||||
|
*/
|
||||||
|
function decode(base58) {
|
||||||
|
let num_leading_ones = nlo(base58);
|
||||||
|
let rest = base58.slice(num_leading_ones);
|
||||||
|
let zeros = decode_ones(num_leading_ones);
|
||||||
|
let rest_arr = decode_rest(rest);
|
||||||
|
let pre_result = zeros.concat(rest_arr);
|
||||||
|
return new Uint8Array(pre_result);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* count the number of leading 1 characters in a uint8array
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function nlo(base58) {
|
||||||
|
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) {
|
||||||
|
let zeros = [];
|
||||||
|
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) {
|
||||||
|
let result_bignum = base58_to_bigint(base58);
|
||||||
|
let result = bigint_to_base256(result_bignum);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Convert a base58 string to a bignum
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function base58_to_bigint(base58) {
|
||||||
|
let acc_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) {
|
||||||
|
let arr_reverse = [];
|
||||||
|
while (q !== 0n) {
|
||||||
|
let r = 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) {
|
||||||
|
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) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=vdk_base58.js.map
|
||||||
File diff suppressed because one or more lines are too long
@@ -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<number> = decode_ones(num_leading_ones);
|
||||||
|
let rest_arr : Array<number> = decode_rest(rest);
|
||||||
|
let pre_result : Array<number> = 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<number>
|
||||||
|
{
|
||||||
|
let zeros : Array<number> = [];
|
||||||
|
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<number>
|
||||||
|
{
|
||||||
|
let result_bignum : bigint = base58_to_bigint(base58);
|
||||||
|
let result : Array<number> = 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<number>
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Base64 Utility Functions in TypeScript
|
||||||
|
*/
|
||||||
|
export { encode, decode };
|
||||||
|
/**
|
||||||
|
* Encode an array of bytes as a Uint8Array in base64 notation.
|
||||||
|
*/
|
||||||
|
declare function encode(bytes: Uint8Array): string;
|
||||||
|
/**
|
||||||
|
* Decode a base64-encoded string
|
||||||
|
*/
|
||||||
|
declare function decode(base64_str: string): Uint8Array;
|
||||||
@@ -0,0 +1,507 @@
|
|||||||
|
/**
|
||||||
|
* Base64 Utility Functions in TypeScript
|
||||||
|
*/
|
||||||
|
export { encode, decode };
|
||||||
|
/**
|
||||||
|
* Encode an array of bytes as a Uint8Array in base64 notation.
|
||||||
|
*/
|
||||||
|
function encode(bytes) {
|
||||||
|
// 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 = encode_head(head);
|
||||||
|
let tail_str = encode_tail(tail, tail_len);
|
||||||
|
return head_str + tail_str;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
let len = bytes.length;
|
||||||
|
// too lazy to look up how to do integer division in js so this will do
|
||||||
|
let tail_len = len % 3;
|
||||||
|
let head_len = len - tail_len;
|
||||||
|
// for slice:
|
||||||
|
// first argument is the 0-index of the start
|
||||||
|
// second - first is the length of the slice
|
||||||
|
let head = bytes.slice(0, head_len);
|
||||||
|
// empty second argument means go to the end
|
||||||
|
let tail = 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) {
|
||||||
|
// can assume length of bytes is a multiple of 3
|
||||||
|
// start index at 0
|
||||||
|
// increment by 3
|
||||||
|
let head_bytes_len = head_bytes.length;
|
||||||
|
let max_idx0 = head_bytes_len - 1;
|
||||||
|
let head_str_acc = '';
|
||||||
|
for (let this_3slice_start_idx0 = 0; this_3slice_start_idx0 <= max_idx0; this_3slice_start_idx0 += 3) {
|
||||||
|
let this_3slice_bytes = head_bytes.slice(this_3slice_start_idx0, this_3slice_start_idx0 + 3);
|
||||||
|
let this_3slice_str = 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) {
|
||||||
|
let b0 = bytes[0];
|
||||||
|
let b1 = bytes[1];
|
||||||
|
let b2 = bytes[2];
|
||||||
|
// ABCDEFGH 12345678 abcdefgh
|
||||||
|
// b0 b1 b2
|
||||||
|
// ABCDEF GH1234 5678ab cdefgh
|
||||||
|
// n0 n1 n2 n3
|
||||||
|
let n0 = b0 >> 2;
|
||||||
|
// b0 = ABCDEFGH
|
||||||
|
// 4 = _____1__
|
||||||
|
// b0 % 4 = ______GH
|
||||||
|
// (b0 % 4) << 4 = __GH____
|
||||||
|
// b1 = 12345678
|
||||||
|
// b1 >> 4 = ____1234
|
||||||
|
// n1 = __GH1234
|
||||||
|
let n1 = ((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 = ((b1 % 16) << 2) + (b2 >> 6);
|
||||||
|
// b2 = abcdefgh
|
||||||
|
// 64 = _1______
|
||||||
|
// n3 = __cdefgh
|
||||||
|
let n3 = b2 % 64;
|
||||||
|
// convert to chars
|
||||||
|
let s0 = int2char(n0);
|
||||||
|
let s1 = int2char(n1);
|
||||||
|
let s2 = int2char(n2);
|
||||||
|
let s3 = int2char(n3);
|
||||||
|
// retrvn
|
||||||
|
return s0 + s1 + s2 + s3;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Encode the final 0, 1, or 2 bytes
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function encode_tail(tail_bytes, tail_len) {
|
||||||
|
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) {
|
||||||
|
let b0 = bytes[0];
|
||||||
|
// n0 = __ABCDEF
|
||||||
|
// b0 = ABCDEFGH
|
||||||
|
// b0 >> 2 = __ABCDEF
|
||||||
|
let n0 = b0 >> 2;
|
||||||
|
// n1 = __GH____
|
||||||
|
// b0 = ABCDEFGH
|
||||||
|
// 4 = _____1__
|
||||||
|
// b0 % 4 = ______GH
|
||||||
|
// (b0 % 4) << 4 = __GH____
|
||||||
|
let n1 = (b0 % 4) << 4;
|
||||||
|
return int2char(n0) + int2char(n1) + '==';
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Encode two bytes
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function encode2(bytes) {
|
||||||
|
let b0 = bytes[0];
|
||||||
|
let b1 = bytes[1];
|
||||||
|
// ABCDEFGH 12345678
|
||||||
|
// b0 b1
|
||||||
|
// ABCDEF GH1234 5678__
|
||||||
|
// n0 n1 n2
|
||||||
|
let n0 = b0 >> 2;
|
||||||
|
// b0 = ABCDEFGH
|
||||||
|
// 4 = _____1__
|
||||||
|
// b0 % 4 = ______GH
|
||||||
|
// (b0 % 4) << 4 = __GH____
|
||||||
|
// b1 = 12345678
|
||||||
|
// b1 >> 4 = ____1234
|
||||||
|
// n1 = __GH1234
|
||||||
|
let n1 = ((b0 % 4) << 4) + (b1 >> 4);
|
||||||
|
// b1 = 12345678
|
||||||
|
// 16 = ___1____
|
||||||
|
// b1 % 16 = ____5678
|
||||||
|
// (b1 % 16) << 2 = __5678__
|
||||||
|
// n2 = __5678__
|
||||||
|
let n2 = (b1 % 16) << 2;
|
||||||
|
// convert to chars
|
||||||
|
let s0 = int2char(n0);
|
||||||
|
let s1 = int2char(n1);
|
||||||
|
let s2 = int2char(n2);
|
||||||
|
// retrvn
|
||||||
|
return s0 + s1 + s2 + '=';
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Decode a base64-encoded string
|
||||||
|
*/
|
||||||
|
function decode(base64_str) {
|
||||||
|
// 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 = len - 4;
|
||||||
|
let head_s = base64_str.slice(0, tail_start_idx0);
|
||||||
|
let tail_s = base64_str.slice(tail_start_idx0);
|
||||||
|
// Using arrays because Uint8Arrays don't have a concat operation
|
||||||
|
let head_arr = decode_head(head_s);
|
||||||
|
let tail_arr = 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 = 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) {
|
||||||
|
// go 4 characters at a time
|
||||||
|
let max_i0 = s.length - 1;
|
||||||
|
let decoded_acc = [];
|
||||||
|
for (let i0 = 0; i0 <= max_i0; i0 += 4) {
|
||||||
|
let this_slice_s = s.slice(i0, i0 + 4);
|
||||||
|
let this_slice_arr = 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) {
|
||||||
|
// 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) {
|
||||||
|
// pull out strings
|
||||||
|
let s0 = s[0];
|
||||||
|
let s1 = s[1];
|
||||||
|
let s2 = s[2];
|
||||||
|
let s3 = s[3];
|
||||||
|
// convert to numbers
|
||||||
|
let n0 = char2int(s0);
|
||||||
|
let n1 = char2int(s1);
|
||||||
|
let n2 = char2int(s2);
|
||||||
|
let n3 = 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 = (n0 << 2) + (n1 >> 4);
|
||||||
|
// n1 = __gh1234
|
||||||
|
// 16 = ___1____
|
||||||
|
// n1 % 16 = ____1234
|
||||||
|
// (n1 % 16) << 4 = 1234____
|
||||||
|
// n2 = __5678ab
|
||||||
|
// n2 >> 2 = ____5678
|
||||||
|
// b1 = 12345678
|
||||||
|
let b1 = ((n1 % 16) << 4) + (n2 >> 2);
|
||||||
|
// n2 = __5678ab
|
||||||
|
// 4 = _____1__
|
||||||
|
// n2 % 4 = ______ab
|
||||||
|
// (n2 % 4) << 6 = ab______
|
||||||
|
// n3 = __cdefgh
|
||||||
|
let b2 = ((n2 % 4) << 6) + n3;
|
||||||
|
return [b0, b1, b2];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Decode a 4-character long base64 string corresponding to 2 bytes
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function decode2(s) {
|
||||||
|
// xyz=
|
||||||
|
// pull out strings
|
||||||
|
let s0 = s[0];
|
||||||
|
let s1 = s[1];
|
||||||
|
let s2 = s[2];
|
||||||
|
// convert to numbers
|
||||||
|
let n0 = char2int(s0);
|
||||||
|
let n1 = char2int(s1);
|
||||||
|
let n2 = 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 = (n0 << 2) + (n1 >> 4);
|
||||||
|
// n1 = __gh1234
|
||||||
|
// 16 = ___1____
|
||||||
|
// n1 % 16 = ____1234
|
||||||
|
// (n1 % 16) << 4 = 1234____
|
||||||
|
// n2 = __5678__
|
||||||
|
// n2 >> 2 = ____5678
|
||||||
|
// b1 = 12345678
|
||||||
|
let b1 = ((n1 % 16) << 4) + (n2 >> 2);
|
||||||
|
return [b0, b1];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Decode a 4-character long base64 string corresponding to 2 bytes
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function decode1(s) {
|
||||||
|
// xy==
|
||||||
|
// pull out strings
|
||||||
|
let s0 = s[0];
|
||||||
|
let s1 = s[1];
|
||||||
|
// convert to numbers
|
||||||
|
let n0 = char2int(s0);
|
||||||
|
let n1 = char2int(s1);
|
||||||
|
// abcdef gh____
|
||||||
|
// n0 n1
|
||||||
|
// abcdefgh
|
||||||
|
// b0
|
||||||
|
// n0 = __abcdef
|
||||||
|
// n1 = __gh____
|
||||||
|
// n0 << 2 = abcdef__
|
||||||
|
// n1 >> 4 = ______gh
|
||||||
|
// b0 = abcdefgh
|
||||||
|
let b0 = (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) {
|
||||||
|
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) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=vdk_base64.js.map
|
||||||
File diff suppressed because one or more lines are too long
@@ -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<number> = decode_head(head_s);
|
||||||
|
let tail_arr : Array<number> = 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<number> = 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<number>
|
||||||
|
{
|
||||||
|
// go 4 characters at a time
|
||||||
|
let max_i0 : number = s.length - 1;
|
||||||
|
let decoded_acc : Array<number> = [];
|
||||||
|
for(let i0 = 0;
|
||||||
|
i0 <= max_i0;
|
||||||
|
i0 += 4)
|
||||||
|
{
|
||||||
|
let this_slice_s : string = s.slice(i0, i0 + 4);
|
||||||
|
let this_slice_arr : Array<number> = 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<number>
|
||||||
|
{
|
||||||
|
// 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<number>
|
||||||
|
{
|
||||||
|
// 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<number>
|
||||||
|
{
|
||||||
|
// 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<number>
|
||||||
|
{
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* 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 };
|
||||||
|
/**
|
||||||
|
* Data that RLP can encode. Also the result of decoding.
|
||||||
|
*/
|
||||||
|
declare type decoded_data = Uint8Array | Array<decoded_data>;
|
||||||
|
/**
|
||||||
|
* Decoding can have a remainder
|
||||||
|
*/
|
||||||
|
declare type decode_result = {
|
||||||
|
decoded_data: decoded_data;
|
||||||
|
remainder: Uint8Array;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Decode an RLP-encoded bytestring into a `decode_result` (decoded data +
|
||||||
|
* whatever wasn't consumed)
|
||||||
|
*/
|
||||||
|
declare function decode(bytes: Uint8Array): decode_result;
|
||||||
|
/**
|
||||||
|
* Encode some decoded data
|
||||||
|
*/
|
||||||
|
declare function encode(data: decoded_data): Uint8Array;
|
||||||
@@ -0,0 +1,382 @@
|
|||||||
|
/**
|
||||||
|
* 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 { decode, encode };
|
||||||
|
/**
|
||||||
|
* Decode an RLP-encoded bytestring into a `decode_result` (decoded data +
|
||||||
|
* whatever wasn't consumed)
|
||||||
|
*/
|
||||||
|
function decode(bytes) {
|
||||||
|
// check the first byte
|
||||||
|
let first_byte = bytes[0];
|
||||||
|
let rest = 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 = first_byte - 128;
|
||||||
|
let payload = rest.slice(0, payload_byte_length);
|
||||||
|
let rest2 = 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 = first_byte - 183;
|
||||||
|
let bytes_of_byte_length = rest.slice(0, byte_length_of_byte_length);
|
||||||
|
let byte_length = bytes_to_number(bytes_of_byte_length);
|
||||||
|
let bytes = rest.slice(byte_length_of_byte_length, byte_length + byte_length_of_byte_length);
|
||||||
|
let rest2 = 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 = first_byte - 192;
|
||||||
|
let list_payload = rest.slice(0, byte_length_of_list);
|
||||||
|
let list = decode_list(list_payload);
|
||||||
|
let rest2 = 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 = first_byte - 247;
|
||||||
|
let bytes_of_byte_length = rest.slice(0, byte_length_of_byte_length);
|
||||||
|
let byte_length = bytes_to_number(bytes_of_byte_length);
|
||||||
|
let list_bytes = rest.slice(byte_length_of_byte_length, byte_length + byte_length_of_byte_length);
|
||||||
|
let list = decode_list(list_bytes);
|
||||||
|
let rest2 = rest.slice(byte_length + byte_length_of_byte_length);
|
||||||
|
return dr(list, rest2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
let arr = [];
|
||||||
|
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) {
|
||||||
|
let n = 0;
|
||||||
|
for (let b of bytes) {
|
||||||
|
n <<= 8;
|
||||||
|
n += b;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Make a `decode_result` containing the two arguments
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function dr(x, y) {
|
||||||
|
return { decoded_data: x,
|
||||||
|
remainder: y };
|
||||||
|
}
|
||||||
|
//=============================================================================
|
||||||
|
//=============================================================================
|
||||||
|
// ENCODING
|
||||||
|
//=============================================================================
|
||||||
|
//=============================================================================
|
||||||
|
/**
|
||||||
|
* Encode some decoded data
|
||||||
|
*/
|
||||||
|
function encode(data) {
|
||||||
|
// is it an array or data
|
||||||
|
if (is_binary(data)) {
|
||||||
|
return encode_binary(data);
|
||||||
|
}
|
||||||
|
else if (is_list(data)) {
|
||||||
|
return encode_list(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) {
|
||||||
|
let len = 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 = 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 = 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 = encode_unsigned(len);
|
||||||
|
let len_bytes_length = len_bytes.length;
|
||||||
|
// total array is
|
||||||
|
// <<183 + len_bytes_length, len_bytes, Bytes>>
|
||||||
|
// 1 byte len_bytes_length bytes len bytes
|
||||||
|
let result = 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 = 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 = 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) {
|
||||||
|
// first encode every element in the list, then branch
|
||||||
|
let payloads = list.map(encode);
|
||||||
|
let payload = uint8arr_concat(payloads);
|
||||||
|
let payload_size = 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 = 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 = 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 = encode_unsigned(payload_size);
|
||||||
|
let payload_size_size = 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 = 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 = 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 = 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) {
|
||||||
|
// can assume that n initially is greater than 55
|
||||||
|
// have to encode it in reverse order
|
||||||
|
let arr = [];
|
||||||
|
// 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) {
|
||||||
|
// total length
|
||||||
|
let total_len = arrs.reduce(// fold
|
||||||
|
function (acc_len, this_uint8array) {
|
||||||
|
return acc_len + this_uint8array.length;
|
||||||
|
},
|
||||||
|
// initial accumulator
|
||||||
|
0);
|
||||||
|
// start up result
|
||||||
|
let result = new Uint8Array(total_len);
|
||||||
|
let result_idx0 = 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) {
|
||||||
|
return (x instanceof Uint8Array);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* returns true if input is `instanceof Array`
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function is_list(x) {
|
||||||
|
return (x instanceof Array);
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=vdk_rlp.js.map
|
||||||
File diff suppressed because one or more lines are too long
@@ -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<decoded_data>;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<decoded_data> = 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<decoded_data> = 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<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> = [];
|
||||||
|
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<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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
export declare const rlp: {
|
||||||
|
decoded: import("./jex_include/local-vdk_rlp-0.1.0/dist/vdk_rlp.js").decoded_data;
|
||||||
|
encoded: Uint8Array;
|
||||||
|
}[];
|
||||||
|
export declare const base58: {
|
||||||
|
encoded: string;
|
||||||
|
decoded: Uint8Array;
|
||||||
|
}[];
|
||||||
|
export declare const base64: {
|
||||||
|
decoded: Uint8Array;
|
||||||
|
encoded: string;
|
||||||
|
}[];
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
+37
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* 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 };
|
||||||
|
/**
|
||||||
|
* Data that RLP can encode. Also the result of decoding.
|
||||||
|
*/
|
||||||
|
declare type decoded_data = Uint8Array | Array<decoded_data>;
|
||||||
|
/**
|
||||||
|
* Decoding can have a remainder
|
||||||
|
*/
|
||||||
|
declare type decode_result = {
|
||||||
|
decoded_data: decoded_data;
|
||||||
|
remainder: Uint8Array;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Decode an RLP-encoded bytestring into a `decode_result` (decoded data +
|
||||||
|
* whatever wasn't consumed)
|
||||||
|
*/
|
||||||
|
declare function decode(bytes: Uint8Array): decode_result;
|
||||||
|
/**
|
||||||
|
* Encode some decoded data
|
||||||
|
*/
|
||||||
|
declare function encode(data: decoded_data): Uint8Array;
|
||||||
+382
@@ -0,0 +1,382 @@
|
|||||||
|
/**
|
||||||
|
* 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 { decode, encode };
|
||||||
|
/**
|
||||||
|
* Decode an RLP-encoded bytestring into a `decode_result` (decoded data +
|
||||||
|
* whatever wasn't consumed)
|
||||||
|
*/
|
||||||
|
function decode(bytes) {
|
||||||
|
// check the first byte
|
||||||
|
let first_byte = bytes[0];
|
||||||
|
let rest = 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 = first_byte - 128;
|
||||||
|
let payload = rest.slice(0, payload_byte_length);
|
||||||
|
let rest2 = 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 = first_byte - 183;
|
||||||
|
let bytes_of_byte_length = rest.slice(0, byte_length_of_byte_length);
|
||||||
|
let byte_length = bytes_to_number(bytes_of_byte_length);
|
||||||
|
let bytes = rest.slice(byte_length_of_byte_length, byte_length + byte_length_of_byte_length);
|
||||||
|
let rest2 = 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 = first_byte - 192;
|
||||||
|
let list_payload = rest.slice(0, byte_length_of_list);
|
||||||
|
let list = decode_list(list_payload);
|
||||||
|
let rest2 = 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 = first_byte - 247;
|
||||||
|
let bytes_of_byte_length = rest.slice(0, byte_length_of_byte_length);
|
||||||
|
let byte_length = bytes_to_number(bytes_of_byte_length);
|
||||||
|
let list_bytes = rest.slice(byte_length_of_byte_length, byte_length + byte_length_of_byte_length);
|
||||||
|
let list = decode_list(list_bytes);
|
||||||
|
let rest2 = rest.slice(byte_length + byte_length_of_byte_length);
|
||||||
|
return dr(list, rest2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
let arr = [];
|
||||||
|
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) {
|
||||||
|
let n = 0;
|
||||||
|
for (let b of bytes) {
|
||||||
|
n <<= 8;
|
||||||
|
n += b;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Make a `decode_result` containing the two arguments
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function dr(x, y) {
|
||||||
|
return { decoded_data: x,
|
||||||
|
remainder: y };
|
||||||
|
}
|
||||||
|
//=============================================================================
|
||||||
|
//=============================================================================
|
||||||
|
// ENCODING
|
||||||
|
//=============================================================================
|
||||||
|
//=============================================================================
|
||||||
|
/**
|
||||||
|
* Encode some decoded data
|
||||||
|
*/
|
||||||
|
function encode(data) {
|
||||||
|
// is it an array or data
|
||||||
|
if (is_binary(data)) {
|
||||||
|
return encode_binary(data);
|
||||||
|
}
|
||||||
|
else if (is_list(data)) {
|
||||||
|
return encode_list(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) {
|
||||||
|
let len = 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 = 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 = 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 = encode_unsigned(len);
|
||||||
|
let len_bytes_length = len_bytes.length;
|
||||||
|
// total array is
|
||||||
|
// <<183 + len_bytes_length, len_bytes, Bytes>>
|
||||||
|
// 1 byte len_bytes_length bytes len bytes
|
||||||
|
let result = 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 = 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 = 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) {
|
||||||
|
// first encode every element in the list, then branch
|
||||||
|
let payloads = list.map(encode);
|
||||||
|
let payload = uint8arr_concat(payloads);
|
||||||
|
let payload_size = 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 = 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 = 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 = encode_unsigned(payload_size);
|
||||||
|
let payload_size_size = 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 = 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 = 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 = 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) {
|
||||||
|
// can assume that n initially is greater than 55
|
||||||
|
// have to encode it in reverse order
|
||||||
|
let arr = [];
|
||||||
|
// 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) {
|
||||||
|
// total length
|
||||||
|
let total_len = arrs.reduce(// fold
|
||||||
|
function (acc_len, this_uint8array) {
|
||||||
|
return acc_len + this_uint8array.length;
|
||||||
|
},
|
||||||
|
// initial accumulator
|
||||||
|
0);
|
||||||
|
// start up result
|
||||||
|
let result = new Uint8Array(total_len);
|
||||||
|
let result_idx0 = 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) {
|
||||||
|
return (x instanceof Uint8Array);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* returns true if input is `instanceof Array`
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function is_list(x) {
|
||||||
|
return (x instanceof Array);
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=vdk_rlp.js.map
|
||||||
+1
File diff suppressed because one or more lines are too long
+521
@@ -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<decoded_data>;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<decoded_data> = 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<decoded_data> = 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<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> = [];
|
||||||
|
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<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);
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
import * as rlp from './jex_include/local-vdk_rlp-0.1.0/dist/vdk_rlp.js';
|
||||||
|
declare type rlpcases = {
|
||||||
|
decoded: rlp.decoded_data;
|
||||||
|
encoded: Uint8Array;
|
||||||
|
};
|
||||||
|
export declare const cases: Array<rlpcases>;
|
||||||
|
export {};
|
||||||
+12568
File diff suppressed because it is too large
Load Diff
+1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import * as rlp from './jex_include/local-vanillae-0.1.0/dist/rlp.js';
|
import * as rlp from './jex_include/local-vdk_rlp-0.1.0/dist/vdk_rlp.js';
|
||||||
|
|
||||||
type rlpcases = {decoded: rlp.decoded_data, encoded: Uint8Array};
|
type rlpcases = {decoded: rlp.decoded_data, encoded: Uint8Array};
|
||||||
|
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
export {};
|
||||||
Vendored
+179
@@ -0,0 +1,179 @@
|
|||||||
|
// test cases
|
||||||
|
import * as cases from './jex_include/local-vdk_tests_cases-0.1.0/dist/cases.js';
|
||||||
|
// vanillae libs
|
||||||
|
import * as b64 from './jex_include/local-vdk_base64-0.1.0/dist/vdk_base64.js';
|
||||||
|
import * as b58 from './jex_include/local-vdk_base58-0.1.0/dist/vdk_base58.js';
|
||||||
|
import * as rlp from './jex_include/local-vdk_rlp-0.1.0/dist/vdk_rlp.js';
|
||||||
|
// TODO: move this to rlp library
|
||||||
|
function deepeq(dd1, dd2) {
|
||||||
|
// if both are Uint8Arrays
|
||||||
|
if ((dd1 instanceof Uint8Array) && (dd2 instanceof Uint8Array)) {
|
||||||
|
return uint8arr_eq(dd1, dd2);
|
||||||
|
}
|
||||||
|
// if both are Arrays
|
||||||
|
else if ((dd1 instanceof Array) && (dd2 instanceof Array)) {
|
||||||
|
return arr_eq(dd1, dd2);
|
||||||
|
}
|
||||||
|
// otherwise
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function arr_eq(a, b) {
|
||||||
|
// make sure they have the same length
|
||||||
|
if (a.length !== b.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// they have the same length now
|
||||||
|
else {
|
||||||
|
let len = a.length;
|
||||||
|
// loop over the items
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
let a_elt = a[i];
|
||||||
|
let b_elt = b[i];
|
||||||
|
let eq = deepeq(a_elt, b_elt);
|
||||||
|
// if they are not equal, break and return false
|
||||||
|
if (!eq) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// at the end, if we havent' proven ourselves wrong yet,
|
||||||
|
// the arrays are equal
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function uint8arr_eq(a, b) {
|
||||||
|
// first test if the length
|
||||||
|
if (a.length !== b.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// alright so they have the same length
|
||||||
|
for (let i = 0; i < a.length; i++) {
|
||||||
|
// return false if a[i] != b[i]
|
||||||
|
if (a[i] !== b[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
function b64_tests() {
|
||||||
|
// @ts-ignore ts can't prove to itself that the element exists
|
||||||
|
let b64_pre = document.getElementById('b64-assershins');
|
||||||
|
// @ts-ignore ts can't prove to itself that the element exists
|
||||||
|
let b64_casen = document.getElementById('b64-current-case');
|
||||||
|
let case_n = 1;
|
||||||
|
for (let this_case of cases.base64) {
|
||||||
|
// i love this type error
|
||||||
|
// can't set the inner html to a number
|
||||||
|
// but a string plus a number is totally cool
|
||||||
|
b64_casen.innerHTML = '' + case_n;
|
||||||
|
case_n++;
|
||||||
|
let { encoded, decoded } = this_case;
|
||||||
|
let my_encoded = b64.encode(decoded);
|
||||||
|
let my_decoded = b64.decode(encoded);
|
||||||
|
let encodes_correctly = (encoded === my_encoded);
|
||||||
|
let decodes_correctly = uint8arr_eq(decoded, my_decoded);
|
||||||
|
if (!encodes_correctly) {
|
||||||
|
b64_pre.innerHTML +=
|
||||||
|
'===================================\n' +
|
||||||
|
'FAILED CASE: encode\n' +
|
||||||
|
'===================================\n' +
|
||||||
|
'decoded : ' + decoded + '\n' +
|
||||||
|
'expected: ' + encoded + '\n' +
|
||||||
|
'actual : ' + my_encoded + '\n\n';
|
||||||
|
}
|
||||||
|
if (!decodes_correctly) {
|
||||||
|
b64_pre.innerHTML +=
|
||||||
|
'===================================\n' +
|
||||||
|
'FAILED CASE: decode\n' +
|
||||||
|
'===================================\n' +
|
||||||
|
'encoded : ' + encoded + '\n' +
|
||||||
|
'expected: ' + decoded + '\n' +
|
||||||
|
'actual : ' + my_decoded + '\n\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function b58_tests() {
|
||||||
|
// @ts-ignore ts can't prove to itself that the element exists
|
||||||
|
let b58_pre = document.getElementById('b58-assershins');
|
||||||
|
// @ts-ignore ts can't prove to itself that the element exists
|
||||||
|
let b58_casen = document.getElementById('b58-current-case');
|
||||||
|
let case_n = 1;
|
||||||
|
for (let this_case of cases.base58) {
|
||||||
|
// i love this type error
|
||||||
|
// can't set the inner html to a number
|
||||||
|
// but a string plus a number is totally cool
|
||||||
|
b58_casen.innerHTML = '' + case_n;
|
||||||
|
case_n++;
|
||||||
|
let { encoded, decoded } = this_case;
|
||||||
|
let my_encoded = b58.encode(decoded);
|
||||||
|
let my_decoded = b58.decode(encoded);
|
||||||
|
let encodes_correctly = (encoded === my_encoded);
|
||||||
|
let decodes_correctly = uint8arr_eq(decoded, my_decoded);
|
||||||
|
if (!encodes_correctly) {
|
||||||
|
b58_pre.innerHTML +=
|
||||||
|
'===================================\n' +
|
||||||
|
'FAILED CASE: encode\n' +
|
||||||
|
'===================================\n' +
|
||||||
|
'decoded : ' + decoded + '\n' +
|
||||||
|
'expected: ' + encoded + '\n' +
|
||||||
|
'actual : ' + my_encoded + '\n\n';
|
||||||
|
}
|
||||||
|
if (!decodes_correctly) {
|
||||||
|
b58_pre.innerHTML +=
|
||||||
|
'===================================\n' +
|
||||||
|
'FAILED CASE: decode\n' +
|
||||||
|
'===================================\n' +
|
||||||
|
'encoded : ' + encoded + '\n' +
|
||||||
|
'expected: ' + decoded + '\n' +
|
||||||
|
'actual : ' + my_decoded + '\n\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function rlp_tests() {
|
||||||
|
// @ts-ignore ts can't prove to itself that the element exists
|
||||||
|
let rlp_pre = document.getElementById('rlp-assershins');
|
||||||
|
// @ts-ignore ts can't prove to itself that the element exists
|
||||||
|
let rlp_casen = document.getElementById('rlp-current-case');
|
||||||
|
let case_n = 1;
|
||||||
|
for (let this_case of cases.rlp) {
|
||||||
|
// i love this type error
|
||||||
|
// can't set the inner html to a number
|
||||||
|
// but a string plus a number is totally cool
|
||||||
|
rlp_casen.innerHTML = '' + case_n;
|
||||||
|
case_n++;
|
||||||
|
let { encoded, decoded } = this_case;
|
||||||
|
let my_encoded = rlp.encode(decoded);
|
||||||
|
let my_decoded = rlp.decode(encoded).decoded_data;
|
||||||
|
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 (!decodes_correctly) {
|
||||||
|
rlp_pre.innerHTML +=
|
||||||
|
'===================================\n' +
|
||||||
|
'FAILED CASE: decode\n' +
|
||||||
|
'===================================\n' +
|
||||||
|
'encoded : ' + encoded + '\n' +
|
||||||
|
'expected: ' + decoded + '\n' +
|
||||||
|
'actual : ' + my_decoded + '\n\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function main() {
|
||||||
|
document.getElementById('b64-total-cases').innerHTML = '' + cases.base64.length;
|
||||||
|
document.getElementById('b64-go').onclick = b64_tests;
|
||||||
|
document.getElementById('b58-total-cases').innerHTML = '' + cases.base58.length;
|
||||||
|
document.getElementById('b58-go').onclick = b58_tests;
|
||||||
|
document.getElementById('rlp-total-cases').innerHTML = '' + cases.rlp.length;
|
||||||
|
document.getElementById('rlp-go').onclick = rlp_tests;
|
||||||
|
}
|
||||||
|
main();
|
||||||
|
//# sourceMappingURL=test.js.map
|
||||||
Vendored
+1
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 459 B After Width: | Height: | Size: 459 B |
@@ -0,0 +1,8 @@
|
|||||||
|
{type, library}.
|
||||||
|
{realm, local}.
|
||||||
|
{name, vdk_tests}.
|
||||||
|
{version, "0.1.0"}.
|
||||||
|
{deps, ["local-vdk_base58-0.1.0",
|
||||||
|
"local-vdk_base64-0.1.0",
|
||||||
|
"local-vdk_rlp-0.1.0",
|
||||||
|
"local-vdk_tests_cases-0.1.0"]}.
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Base58 encoding/decoding
|
||||||
|
*/
|
||||||
|
export { encode, decode };
|
||||||
|
/**
|
||||||
|
* Encode a Uint8Array into base58
|
||||||
|
*/
|
||||||
|
declare function encode(binary: Uint8Array): string;
|
||||||
|
/**
|
||||||
|
* Decode a Base58 string into a Uint8Array
|
||||||
|
*/
|
||||||
|
declare function decode(base58: string): Uint8Array;
|
||||||
@@ -0,0 +1,308 @@
|
|||||||
|
/**
|
||||||
|
* Base58 encoding/decoding
|
||||||
|
*/
|
||||||
|
export { encode, decode };
|
||||||
|
//=============================================================================
|
||||||
|
// ENCODING
|
||||||
|
//=============================================================================
|
||||||
|
/**
|
||||||
|
* Encode a Uint8Array into base58
|
||||||
|
*/
|
||||||
|
function encode(binary) {
|
||||||
|
let num_leading_zeros = nlz(binary);
|
||||||
|
let rest = binary.slice(num_leading_zeros);
|
||||||
|
let ones = encode_zeros(num_leading_zeros);
|
||||||
|
let rest_b58 = encode_rest(rest);
|
||||||
|
let result = ones + rest_b58;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* count the number of leading zeros in a uint8array
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function nlz(bytes) {
|
||||||
|
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) {
|
||||||
|
let ones = '';
|
||||||
|
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) {
|
||||||
|
let bytes_bignum = bytes_to_bigint(bytes);
|
||||||
|
let result = bignum_to_base58(bytes_bignum);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Convert a bytestring to a bignum
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function bytes_to_bigint(bytes) {
|
||||||
|
let acc_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) {
|
||||||
|
let s = '';
|
||||||
|
while (q !== 0n) {
|
||||||
|
let this_n = q % 58n;
|
||||||
|
q /= 58n;
|
||||||
|
let this_b58_char = bigint_to_char(this_n);
|
||||||
|
s = this_b58_char + s;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
//=============================================================================
|
||||||
|
// DECODING
|
||||||
|
//=============================================================================
|
||||||
|
/**
|
||||||
|
* Decode a Base58 string into a Uint8Array
|
||||||
|
*/
|
||||||
|
function decode(base58) {
|
||||||
|
let num_leading_ones = nlo(base58);
|
||||||
|
let rest = base58.slice(num_leading_ones);
|
||||||
|
let zeros = decode_ones(num_leading_ones);
|
||||||
|
let rest_arr = decode_rest(rest);
|
||||||
|
let pre_result = zeros.concat(rest_arr);
|
||||||
|
return new Uint8Array(pre_result);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* count the number of leading 1 characters in a uint8array
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function nlo(base58) {
|
||||||
|
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) {
|
||||||
|
let zeros = [];
|
||||||
|
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) {
|
||||||
|
let result_bignum = base58_to_bigint(base58);
|
||||||
|
let result = bigint_to_base256(result_bignum);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Convert a base58 string to a bignum
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function base58_to_bigint(base58) {
|
||||||
|
let acc_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) {
|
||||||
|
let arr_reverse = [];
|
||||||
|
while (q !== 0n) {
|
||||||
|
let r = 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) {
|
||||||
|
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) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=vdk_base58.js.map
|
||||||
File diff suppressed because one or more lines are too long
@@ -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<number> = decode_ones(num_leading_ones);
|
||||||
|
let rest_arr : Array<number> = decode_rest(rest);
|
||||||
|
let pre_result : Array<number> = 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<number>
|
||||||
|
{
|
||||||
|
let zeros : Array<number> = [];
|
||||||
|
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<number>
|
||||||
|
{
|
||||||
|
let result_bignum : bigint = base58_to_bigint(base58);
|
||||||
|
let result : Array<number> = 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<number>
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Base64 Utility Functions in TypeScript
|
||||||
|
*/
|
||||||
|
export { encode, decode };
|
||||||
|
/**
|
||||||
|
* Encode an array of bytes as a Uint8Array in base64 notation.
|
||||||
|
*/
|
||||||
|
declare function encode(bytes: Uint8Array): string;
|
||||||
|
/**
|
||||||
|
* Decode a base64-encoded string
|
||||||
|
*/
|
||||||
|
declare function decode(base64_str: string): Uint8Array;
|
||||||
@@ -0,0 +1,507 @@
|
|||||||
|
/**
|
||||||
|
* Base64 Utility Functions in TypeScript
|
||||||
|
*/
|
||||||
|
export { encode, decode };
|
||||||
|
/**
|
||||||
|
* Encode an array of bytes as a Uint8Array in base64 notation.
|
||||||
|
*/
|
||||||
|
function encode(bytes) {
|
||||||
|
// 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 = encode_head(head);
|
||||||
|
let tail_str = encode_tail(tail, tail_len);
|
||||||
|
return head_str + tail_str;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
let len = bytes.length;
|
||||||
|
// too lazy to look up how to do integer division in js so this will do
|
||||||
|
let tail_len = len % 3;
|
||||||
|
let head_len = len - tail_len;
|
||||||
|
// for slice:
|
||||||
|
// first argument is the 0-index of the start
|
||||||
|
// second - first is the length of the slice
|
||||||
|
let head = bytes.slice(0, head_len);
|
||||||
|
// empty second argument means go to the end
|
||||||
|
let tail = 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) {
|
||||||
|
// can assume length of bytes is a multiple of 3
|
||||||
|
// start index at 0
|
||||||
|
// increment by 3
|
||||||
|
let head_bytes_len = head_bytes.length;
|
||||||
|
let max_idx0 = head_bytes_len - 1;
|
||||||
|
let head_str_acc = '';
|
||||||
|
for (let this_3slice_start_idx0 = 0; this_3slice_start_idx0 <= max_idx0; this_3slice_start_idx0 += 3) {
|
||||||
|
let this_3slice_bytes = head_bytes.slice(this_3slice_start_idx0, this_3slice_start_idx0 + 3);
|
||||||
|
let this_3slice_str = 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) {
|
||||||
|
let b0 = bytes[0];
|
||||||
|
let b1 = bytes[1];
|
||||||
|
let b2 = bytes[2];
|
||||||
|
// ABCDEFGH 12345678 abcdefgh
|
||||||
|
// b0 b1 b2
|
||||||
|
// ABCDEF GH1234 5678ab cdefgh
|
||||||
|
// n0 n1 n2 n3
|
||||||
|
let n0 = b0 >> 2;
|
||||||
|
// b0 = ABCDEFGH
|
||||||
|
// 4 = _____1__
|
||||||
|
// b0 % 4 = ______GH
|
||||||
|
// (b0 % 4) << 4 = __GH____
|
||||||
|
// b1 = 12345678
|
||||||
|
// b1 >> 4 = ____1234
|
||||||
|
// n1 = __GH1234
|
||||||
|
let n1 = ((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 = ((b1 % 16) << 2) + (b2 >> 6);
|
||||||
|
// b2 = abcdefgh
|
||||||
|
// 64 = _1______
|
||||||
|
// n3 = __cdefgh
|
||||||
|
let n3 = b2 % 64;
|
||||||
|
// convert to chars
|
||||||
|
let s0 = int2char(n0);
|
||||||
|
let s1 = int2char(n1);
|
||||||
|
let s2 = int2char(n2);
|
||||||
|
let s3 = int2char(n3);
|
||||||
|
// retrvn
|
||||||
|
return s0 + s1 + s2 + s3;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Encode the final 0, 1, or 2 bytes
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function encode_tail(tail_bytes, tail_len) {
|
||||||
|
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) {
|
||||||
|
let b0 = bytes[0];
|
||||||
|
// n0 = __ABCDEF
|
||||||
|
// b0 = ABCDEFGH
|
||||||
|
// b0 >> 2 = __ABCDEF
|
||||||
|
let n0 = b0 >> 2;
|
||||||
|
// n1 = __GH____
|
||||||
|
// b0 = ABCDEFGH
|
||||||
|
// 4 = _____1__
|
||||||
|
// b0 % 4 = ______GH
|
||||||
|
// (b0 % 4) << 4 = __GH____
|
||||||
|
let n1 = (b0 % 4) << 4;
|
||||||
|
return int2char(n0) + int2char(n1) + '==';
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Encode two bytes
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function encode2(bytes) {
|
||||||
|
let b0 = bytes[0];
|
||||||
|
let b1 = bytes[1];
|
||||||
|
// ABCDEFGH 12345678
|
||||||
|
// b0 b1
|
||||||
|
// ABCDEF GH1234 5678__
|
||||||
|
// n0 n1 n2
|
||||||
|
let n0 = b0 >> 2;
|
||||||
|
// b0 = ABCDEFGH
|
||||||
|
// 4 = _____1__
|
||||||
|
// b0 % 4 = ______GH
|
||||||
|
// (b0 % 4) << 4 = __GH____
|
||||||
|
// b1 = 12345678
|
||||||
|
// b1 >> 4 = ____1234
|
||||||
|
// n1 = __GH1234
|
||||||
|
let n1 = ((b0 % 4) << 4) + (b1 >> 4);
|
||||||
|
// b1 = 12345678
|
||||||
|
// 16 = ___1____
|
||||||
|
// b1 % 16 = ____5678
|
||||||
|
// (b1 % 16) << 2 = __5678__
|
||||||
|
// n2 = __5678__
|
||||||
|
let n2 = (b1 % 16) << 2;
|
||||||
|
// convert to chars
|
||||||
|
let s0 = int2char(n0);
|
||||||
|
let s1 = int2char(n1);
|
||||||
|
let s2 = int2char(n2);
|
||||||
|
// retrvn
|
||||||
|
return s0 + s1 + s2 + '=';
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Decode a base64-encoded string
|
||||||
|
*/
|
||||||
|
function decode(base64_str) {
|
||||||
|
// 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 = len - 4;
|
||||||
|
let head_s = base64_str.slice(0, tail_start_idx0);
|
||||||
|
let tail_s = base64_str.slice(tail_start_idx0);
|
||||||
|
// Using arrays because Uint8Arrays don't have a concat operation
|
||||||
|
let head_arr = decode_head(head_s);
|
||||||
|
let tail_arr = 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 = 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) {
|
||||||
|
// go 4 characters at a time
|
||||||
|
let max_i0 = s.length - 1;
|
||||||
|
let decoded_acc = [];
|
||||||
|
for (let i0 = 0; i0 <= max_i0; i0 += 4) {
|
||||||
|
let this_slice_s = s.slice(i0, i0 + 4);
|
||||||
|
let this_slice_arr = 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) {
|
||||||
|
// 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) {
|
||||||
|
// pull out strings
|
||||||
|
let s0 = s[0];
|
||||||
|
let s1 = s[1];
|
||||||
|
let s2 = s[2];
|
||||||
|
let s3 = s[3];
|
||||||
|
// convert to numbers
|
||||||
|
let n0 = char2int(s0);
|
||||||
|
let n1 = char2int(s1);
|
||||||
|
let n2 = char2int(s2);
|
||||||
|
let n3 = 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 = (n0 << 2) + (n1 >> 4);
|
||||||
|
// n1 = __gh1234
|
||||||
|
// 16 = ___1____
|
||||||
|
// n1 % 16 = ____1234
|
||||||
|
// (n1 % 16) << 4 = 1234____
|
||||||
|
// n2 = __5678ab
|
||||||
|
// n2 >> 2 = ____5678
|
||||||
|
// b1 = 12345678
|
||||||
|
let b1 = ((n1 % 16) << 4) + (n2 >> 2);
|
||||||
|
// n2 = __5678ab
|
||||||
|
// 4 = _____1__
|
||||||
|
// n2 % 4 = ______ab
|
||||||
|
// (n2 % 4) << 6 = ab______
|
||||||
|
// n3 = __cdefgh
|
||||||
|
let b2 = ((n2 % 4) << 6) + n3;
|
||||||
|
return [b0, b1, b2];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Decode a 4-character long base64 string corresponding to 2 bytes
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function decode2(s) {
|
||||||
|
// xyz=
|
||||||
|
// pull out strings
|
||||||
|
let s0 = s[0];
|
||||||
|
let s1 = s[1];
|
||||||
|
let s2 = s[2];
|
||||||
|
// convert to numbers
|
||||||
|
let n0 = char2int(s0);
|
||||||
|
let n1 = char2int(s1);
|
||||||
|
let n2 = 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 = (n0 << 2) + (n1 >> 4);
|
||||||
|
// n1 = __gh1234
|
||||||
|
// 16 = ___1____
|
||||||
|
// n1 % 16 = ____1234
|
||||||
|
// (n1 % 16) << 4 = 1234____
|
||||||
|
// n2 = __5678__
|
||||||
|
// n2 >> 2 = ____5678
|
||||||
|
// b1 = 12345678
|
||||||
|
let b1 = ((n1 % 16) << 4) + (n2 >> 2);
|
||||||
|
return [b0, b1];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Decode a 4-character long base64 string corresponding to 2 bytes
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function decode1(s) {
|
||||||
|
// xy==
|
||||||
|
// pull out strings
|
||||||
|
let s0 = s[0];
|
||||||
|
let s1 = s[1];
|
||||||
|
// convert to numbers
|
||||||
|
let n0 = char2int(s0);
|
||||||
|
let n1 = char2int(s1);
|
||||||
|
// abcdef gh____
|
||||||
|
// n0 n1
|
||||||
|
// abcdefgh
|
||||||
|
// b0
|
||||||
|
// n0 = __abcdef
|
||||||
|
// n1 = __gh____
|
||||||
|
// n0 << 2 = abcdef__
|
||||||
|
// n1 >> 4 = ______gh
|
||||||
|
// b0 = abcdefgh
|
||||||
|
let b0 = (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) {
|
||||||
|
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) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=vdk_base64.js.map
|
||||||
File diff suppressed because one or more lines are too long
@@ -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<number> = decode_head(head_s);
|
||||||
|
let tail_arr : Array<number> = 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<number> = 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<number>
|
||||||
|
{
|
||||||
|
// go 4 characters at a time
|
||||||
|
let max_i0 : number = s.length - 1;
|
||||||
|
let decoded_acc : Array<number> = [];
|
||||||
|
for(let i0 = 0;
|
||||||
|
i0 <= max_i0;
|
||||||
|
i0 += 4)
|
||||||
|
{
|
||||||
|
let this_slice_s : string = s.slice(i0, i0 + 4);
|
||||||
|
let this_slice_arr : Array<number> = 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<number>
|
||||||
|
{
|
||||||
|
// 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<number>
|
||||||
|
{
|
||||||
|
// 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<number>
|
||||||
|
{
|
||||||
|
// 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<number>
|
||||||
|
{
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* 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 };
|
||||||
|
/**
|
||||||
|
* Data that RLP can encode. Also the result of decoding.
|
||||||
|
*/
|
||||||
|
declare type decoded_data = Uint8Array | Array<decoded_data>;
|
||||||
|
/**
|
||||||
|
* Decoding can have a remainder
|
||||||
|
*/
|
||||||
|
declare type decode_result = {
|
||||||
|
decoded_data: decoded_data;
|
||||||
|
remainder: Uint8Array;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Decode an RLP-encoded bytestring into a `decode_result` (decoded data +
|
||||||
|
* whatever wasn't consumed)
|
||||||
|
*/
|
||||||
|
declare function decode(bytes: Uint8Array): decode_result;
|
||||||
|
/**
|
||||||
|
* Encode some decoded data
|
||||||
|
*/
|
||||||
|
declare function encode(data: decoded_data): Uint8Array;
|
||||||
@@ -0,0 +1,382 @@
|
|||||||
|
/**
|
||||||
|
* 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 { decode, encode };
|
||||||
|
/**
|
||||||
|
* Decode an RLP-encoded bytestring into a `decode_result` (decoded data +
|
||||||
|
* whatever wasn't consumed)
|
||||||
|
*/
|
||||||
|
function decode(bytes) {
|
||||||
|
// check the first byte
|
||||||
|
let first_byte = bytes[0];
|
||||||
|
let rest = 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 = first_byte - 128;
|
||||||
|
let payload = rest.slice(0, payload_byte_length);
|
||||||
|
let rest2 = 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 = first_byte - 183;
|
||||||
|
let bytes_of_byte_length = rest.slice(0, byte_length_of_byte_length);
|
||||||
|
let byte_length = bytes_to_number(bytes_of_byte_length);
|
||||||
|
let bytes = rest.slice(byte_length_of_byte_length, byte_length + byte_length_of_byte_length);
|
||||||
|
let rest2 = 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 = first_byte - 192;
|
||||||
|
let list_payload = rest.slice(0, byte_length_of_list);
|
||||||
|
let list = decode_list(list_payload);
|
||||||
|
let rest2 = 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 = first_byte - 247;
|
||||||
|
let bytes_of_byte_length = rest.slice(0, byte_length_of_byte_length);
|
||||||
|
let byte_length = bytes_to_number(bytes_of_byte_length);
|
||||||
|
let list_bytes = rest.slice(byte_length_of_byte_length, byte_length + byte_length_of_byte_length);
|
||||||
|
let list = decode_list(list_bytes);
|
||||||
|
let rest2 = rest.slice(byte_length + byte_length_of_byte_length);
|
||||||
|
return dr(list, rest2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
let arr = [];
|
||||||
|
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) {
|
||||||
|
let n = 0;
|
||||||
|
for (let b of bytes) {
|
||||||
|
n <<= 8;
|
||||||
|
n += b;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Make a `decode_result` containing the two arguments
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function dr(x, y) {
|
||||||
|
return { decoded_data: x,
|
||||||
|
remainder: y };
|
||||||
|
}
|
||||||
|
//=============================================================================
|
||||||
|
//=============================================================================
|
||||||
|
// ENCODING
|
||||||
|
//=============================================================================
|
||||||
|
//=============================================================================
|
||||||
|
/**
|
||||||
|
* Encode some decoded data
|
||||||
|
*/
|
||||||
|
function encode(data) {
|
||||||
|
// is it an array or data
|
||||||
|
if (is_binary(data)) {
|
||||||
|
return encode_binary(data);
|
||||||
|
}
|
||||||
|
else if (is_list(data)) {
|
||||||
|
return encode_list(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) {
|
||||||
|
let len = 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 = 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 = 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 = encode_unsigned(len);
|
||||||
|
let len_bytes_length = len_bytes.length;
|
||||||
|
// total array is
|
||||||
|
// <<183 + len_bytes_length, len_bytes, Bytes>>
|
||||||
|
// 1 byte len_bytes_length bytes len bytes
|
||||||
|
let result = 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 = 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 = 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) {
|
||||||
|
// first encode every element in the list, then branch
|
||||||
|
let payloads = list.map(encode);
|
||||||
|
let payload = uint8arr_concat(payloads);
|
||||||
|
let payload_size = 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 = 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 = 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 = encode_unsigned(payload_size);
|
||||||
|
let payload_size_size = 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 = 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 = 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 = 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) {
|
||||||
|
// can assume that n initially is greater than 55
|
||||||
|
// have to encode it in reverse order
|
||||||
|
let arr = [];
|
||||||
|
// 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) {
|
||||||
|
// total length
|
||||||
|
let total_len = arrs.reduce(// fold
|
||||||
|
function (acc_len, this_uint8array) {
|
||||||
|
return acc_len + this_uint8array.length;
|
||||||
|
},
|
||||||
|
// initial accumulator
|
||||||
|
0);
|
||||||
|
// start up result
|
||||||
|
let result = new Uint8Array(total_len);
|
||||||
|
let result_idx0 = 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) {
|
||||||
|
return (x instanceof Uint8Array);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* returns true if input is `instanceof Array`
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function is_list(x) {
|
||||||
|
return (x instanceof Array);
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=vdk_rlp.js.map
|
||||||
File diff suppressed because one or more lines are too long
@@ -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<decoded_data>;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<decoded_data> = 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<decoded_data> = 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<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> = [];
|
||||||
|
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<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);
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user