vdk_binary seems to be done

This commit is contained in:
2023-05-25 15:13:16 -06:00
parent ee30eaaf7f
commit a548572de9
14 changed files with 44 additions and 2109 deletions
+10
View File
@@ -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/
+5
View File
@@ -0,0 +1,5 @@
{type, library}.
{realm, local}.
{name, vdk_binary}.
{version, "0.1.0"}.
{deps, []}.
+24
View File
@@ -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
+403
View File
@@ -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
*/
+112
View File
@@ -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};
}
+159
View File
@@ -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)};
}
+23
View File
@@ -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
{
}
+417
View File
@@ -0,0 +1,417 @@
/**
* Miscellaneous binary utility functions
*
* @module
*/
export {
// basic bytes functions
bytes_eq,
bytes_concat,
strong_rand_bytes,
bytes_to_bigint,
bigint_to_bytes,
// basic bits functions
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
*/
function
bytes_concat
(arr1 : Uint8Array,
arr2 : Uint8Array)
: Uint8Array
{
let len1 : number = arr1.length;
let len2 : number = arr2.length;
let arr1_idx0_offset : number = 0;
let arr2_idx0_offset : number = len1;
let result_len : number = len1 + len2;
let result : Uint8Array = new Uint8Array(result_len);
// copy first array into result
for (let arr1_idx0 = 0;
arr1_idx0 < len1;
arr1_idx0++)
{
// no offset here
let result_idx0 : number = arr1_idx0 + arr1_idx0_offset;
result[result_idx0] = arr1[arr1_idx0];
}
// copy second array into result
for (let arr2_idx0 = 0;
arr2_idx0 < len2;
arr2_idx0++)
{
// offset by the length of the first array
let result_idx0 : number = arr2_idx0 + arr2_idx0_offset;
result[result_idx0] = arr2[arr2_idx0];
}
return result;
}
/**
* Cryptographically random bytes
*/
function
strong_rand_bytes
(how_many : number)
: Uint8Array
{
let arr = new Uint8Array(how_many);
(new Crypto()).getRandomValues(arr);
return arr;
}
/**
* Convert a byte array to a bigint
*
* Equivalent to `binary:decode_unsigned/1` from Erlang
*/
function
bytes_to_bigint
(bytes: Uint8Array)
: bigint
{
let n : bigint = 0n;
for (let b of bytes) {
// move first, then add
// otherwise it ends on a move
// imperative languages are for losers
n <<= 8n;
n += BigInt(b);
}
return n;
}
/**
* Convert a bigint to a byte array
*
* Equivalent to `binary:encode_unsigned/1` from Erlang
*
* Requires input to be positive
*/
function
bigint_to_bytes
(q: bigint)
: Uint8Array
{
if (q < 0n) {
throw new Error('q < 0n: ' + q);
}
let arr_reverse = [];
while (q > 0n) {
let r = Number(q % 256n);
q /= 256n;
arr_reverse.push(r);
}
arr_reverse.reverse();
return new Uint8Array(arr_reverse);
}
/**
* Oh no, bitstrings in a language that only has bytestrings
*
* By convention these are `Uint8Array`s with byte length `ceil(bit_length /
* 8)`, and all trailing bits are zero.
*/
type bits =
{bit_length : number,
bytes : Uint8Array};
/**
* Get an uninitialized bitstring
*
* @internal
*/
function
bits_null
(bit_length : number)
: bits
{
let byte_length : number = Math.ceil(bit_length / 8);
let result : Uint8Array = new Uint8Array(byte_length);
return {bit_length : bit_length,
bytes : result};
}
/**
* Get a bitstring of a given length where every value is 0.
*/
function
bits_zeros
(bit_length : number)
: bits
{
let byte_length : number = Math.ceil(bit_length / 8);
let result : Uint8Array = new Uint8Array(byte_length);
for (let i0 = 0;
i0 < byte_length;
i0++)
{
result[i0] = 0;
}
return {bit_length : bit_length,
bytes : result};
}
/**
* Get a bitstring of a given length where every value is 1.
*/
function
bits_ones
(bit_length : number)
: bits
{
let byte_length : number = Math.ceil(bit_length / 8);
let result : Uint8Array = new Uint8Array(byte_length);
// fill everything except the last byte with 255s
for (let i0 = 0;
i0 < (byte_length - 1);
i0++)
{
result[i0] = 255;
}
// alright so the last byte
// ok so the number of leading 0s is
// 8 - (bit_length % 8)
let num_trailing_zero_bits : number = 8 - (bit_length % 8);
// the trailing byte is 255 << that
// e.g. 3 trailing 0s
// 1111_1111 -> 1111_1000
let last_byte : number = 255 << num_trailing_zero_bits;
let last_byte_idx0 : number = byte_length - 1;
result[last_byte_idx0] = last_byte;
return {bit_length : bit_length,
bytes : result};
}
/**
* Get the bit at a given 0-index
*/
function
bits_i0th
(bit_idx0 : number,
bits : bits)
: number
{
// first task is figuring out what byte we're at
// for instance if we want bit 27
// 3*8 = 24 =< 27 < 4*8
// so it's Math.floor(bit_idx0 / 8)
let byte_idx0 : number = Math.floor(bit_idx0 / 8);
// let's fetch the byte and work with that
let the_byte : number = bits.bytes[byte_idx0];
// ok so let's go with 27 again
// 27 = 3 mod 8
// so we bitshift right by (8 - 3)
// and then take the remainder dividing by 2
// --B-_---- -> ----_---B -> 0000_000B
let bsr : number = 8 - (bit_idx0 % 8);
return (the_byte >> bsr) % 2;
}
/**
* 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_null(result_bit_length);
let result_bytes : Uint8Array = result_bits.bytes;
// go along each byte in result, and compute the byte boundary
for (let i = 0;
i < result_bytes.length;
i++)
{
// ABCD_EFGH _
// 0123_4567 8
// this is the bit index of the leftmost bit in this byte
let start_bit_bi0 : number = i * 8;
let next_start_bit_bi0 : number = start_bit_bi0 + 8;
// does the bit at the beginning of this byte correspond to the first array?
// strict comparison:
// suppose i = 0,
// suppose bit_length1 is 0
// then this says no, go to second array
// suppose bl1 = 1,
// this says start at first array
let start_bit_is_of_first_array : boolean = start_bit_bi0 < bits1.bit_length;
// weak comparison:
// suppose bit_length1 = 8
// ABCD_EFGH _
// 0123_4567 8
// ^
// start_bit_bi0 ^ next_start_bit_bi0
let stop_bit_is_of_first_array : boolean = next_start_bit_bi0 <= bits1.bit_length;
// is this a bytes1 byte
let is_bytes1_byte : boolean = start_bit_is_of_first_array && stop_bit_is_of_first_array;
let is_boundary_byte : boolean = start_bit_is_of_first_array && !stop_bit_is_of_first_array;
// need to work out the ping_pong bs up here because js is dumb and I
// can't put lets between elseifs
// alright, now we're in the case of only copying from the second array
// we have two cases:
// ping-pong:
// ABCD_EFGH 1234_5678
// - ---- ---
// copying these bits
// ping:
// ABCD_EFGH <end>
// - ---- 000
//
// how to distinguish between these two??
//
// we're in the ping case when the start_bit_bi0 corresponds to the
// final byte of the second array
//
// ok
//
// we need to compute the bit address in the second array that
// corresponds to the bit address at the beginning of this byte in the
// result array
let bits2_addr_bi0 : number = start_bit_bi0 - bits1.bit_length;
// I think the variable is
// bits_left_to_copy = bits2.bit_length - bit_addr2_bi0
// no + 1 because the current bit is uncopied,
// so if bit_addr2_bi0 = 7 and bits2.bit_length is 8, it means we
// have the last bit to copy
// cases:
// bits_left_to_copy <= 0 ->
// this would mean we have more bits to copy, but are out of
// source bits. should be impossible if bits_null is correct
// bits_left_to_copy <= 8 ->
// this would mean we are going to fill this last byte in the
// result array with the correct bits from array2, but how we
// do this will depend on how those are arranged in bytes2
// (whether we grab one or two bytes)
//
// this is the tricky case
//
// I think what matters here is the byte address in the second array
// if we're on the last byte
// 8 < bits_left_to_copy ->
// this means we can safely grab two bytes from bytes2, and do
// our bitshifting to make it correct
//
// FIXME
let bits_left_to_copy : number = bits2.bit_length - bits2_addr_bi0;
// no the variable that matters is which byte we're on in the result
let this_bytes2_addr_i0 : number = Math.floor(bits2_addr_bi0 / 8);
let next_bytes2_addr_i0 : number = this_bytes2_addr_i0 + 1;
let bitshift_amt : number = bits1.bit_length % 8;
let ping : boolean = next_bytes2_addr_i0 === bytes2.length;
// simple case: this is a byte from the first array
// just copy it
if (is_bytes1_byte)
result_bytes[i] = bytes1[i];
// this is a boundary byte
// further cases:
// second bit length is 0 ->
// ABCD_EF-- <empty>
// ^ starting here
// just copy first byte and move along
else if (is_boundary_byte && (bits2.bit_length === 0))
result_bytes[i] = bytes1[i];
// boundary byte, and there is at least one byte in the second array
else if (is_boundary_byte)
{
// copy over the first byte
result_bytes[i] = bytes1[i];
// take the first byte from the second array
let first_byte_of_second_array : number = bytes2[0];
// bytes1:
// ABCD_EF00
// 6
// bytes2:
// 1234_5678
// 0000_0012
// and bitshift it right by that amount
let bitshift_amt : number = bits1.bit_length % 8;
result_bytes[i] ^= first_byte_of_second_array >> bitshift_amt;
}
// last byte of second array
else if (ping)
result_bytes[i] = (bytes2[this_bytes2_addr_i0] << bitshift_amt);
// ping-pong: not last byte of second array
else
result_bytes[i] = (bytes2[this_bytes2_addr_i0] << bitshift_amt) ^ (bytes2[next_bytes2_addr_i0] << bitshift_amt);
}
return {bit_length : result_bit_length,
bytes : result_bytes};
}
+16
View File
@@ -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}