[wip] factoring vdk out into several packages
This commit is contained in:
@@ -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
|
||||
{
|
||||
}
|
||||
Reference in New Issue
Block a user