diff --git a/libs/vdk_aeser/.gitignore b/libs/vdk_aeser/.gitignore new file mode 100644 index 0000000..3967e48 --- /dev/null +++ b/libs/vdk_aeser/.gitignore @@ -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/ diff --git a/libs/vdk_aeser/jex.eterms b/libs/vdk_aeser/jex.eterms new file mode 100644 index 0000000..218c2c1 --- /dev/null +++ b/libs/vdk_aeser/jex.eterms @@ -0,0 +1,6 @@ +{type, library}. +{realm, local}. +{name, vdk_aeser}. +{version, "0.1.0"}. +{deps, ["local-vdk_base58-0.1.0", + "local-vdk_binary-0.1.0"]}. diff --git a/libs/vdk_aeser/scratch/anth.ts b/libs/vdk_aeser/scratch/anth.ts new file mode 100644 index 0000000..6117067 --- /dev/null +++ b/libs/vdk_aeser/scratch/anth.ts @@ -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> <> = 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> <> = 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 + * [ :: id() % <<1,201,99,126,...> "=" "ak_2XhCkjzTwcq1coXSSzHJoMZkUzTwnjH88zmPGkkowUsFNTo9UE" + * , :: id() % <<1,123,102,230,...> "=" "ak_wM8yFU8eSETXU7VSN48HMDmevGoCMiuveQZgkPuRn1nTiRqyv" + * , :: int() % <<"\n">> "=" 10 + * , :: int() % <<15,38,245,97,200,0>> "=" 16_660_000_000_000 + * , :: int() % <<0>> "=" 0 + * , :: int() % <<"\t">> "=" 9 + * , :: 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) -> <> = crypto:hash(sha256, crypto:hash(sha256, Bytes)), Foo end. + * #Fun + * 33> "ak_" ++ b58:enc(<>). + * "ak_2XhCkjzTwcq1coXSSzHJoMZkUzTwnjH88zmPGkkowUsFNTo9UE" + * 34> RecipBytes = lists:nth(4, Data). + * <<1,123,102,230,195,5,7,8,54,228,233,121,32,82,61,195,234, + * 89,203,191,216,108,88,186,7,3,234,139,205,...>> + * 35> <<1, RecipAddrBytes/binary>> = RecipBytes. + * <<1,123,102,230,195,5,7,8,54,228,233,121,32,82,61,195,234, + * 89,203,191,216,108,88,186,7,3,234,139,205,...>> + * 36> "ak_" ++ b58:enc(<>). + * "ak_wM8yFU8eSETXU7VSN48HMDmevGoCMiuveQZgkPuRn1nTiRqyv" + * ``` + * + * ```js + * > anth.deconstruct("tx_+FgMAaEByWN+RgDnqzvC5n/GQOgjdkRE9DBV2l1VeKSaN1r6GNyhAXtm5sMFBwg25Ol5IFI9w+pZy7/YbFi6BwPqi80KuKdsCoYPJvVhyAAACYdoYWluYW5hA7ZC1w==") + * {tag : 'SpendTx', + * version : 1n, + * fields : {sender : "ak_2XhCkjzTwcq1coXSSzHJoMZkUzTwnjH88zmPGkkowUsFNTo9UE", + * recipient : "ak_wM8yFU8eSETXU7VSN48HMDmevGoCMiuveQZgkPuRn1nTiRqyv", + * amount : 10n, + * fee : 16660000000000n, + * ttl : 0n, + * nonce : 9n, + * payload : Uint8Array([104, 97, 105, 110, 97, 110, 97])}} + * ``` + * + * @module + */ + +export { + // 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, + 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 = 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 -> <> + * 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 +{ + let len = hashed_bs.length; + let bytes = hashed_bs.slice(0, len - 4); + let {decoded_data} = rlp.decode(bytes); + return (decoded_data as Array); +} + + + +/** + * 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) + : 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) + : 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 +*/ diff --git a/libs/vdk_aeser/scratch/bin.ts b/libs/vdk_aeser/scratch/bin.ts new file mode 100644 index 0000000..47ad66a --- /dev/null +++ b/libs/vdk_aeser/scratch/bin.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}; +} + diff --git a/libs/vdk_aeser/scratch/scratch.txt b/libs/vdk_aeser/scratch/scratch.txt new file mode 100644 index 0000000..9213abc --- /dev/null +++ b/libs/vdk_aeser/scratch/scratch.txt @@ -0,0 +1,159 @@ + +const OTAG_SIGNED_TX = 11n; +const OTAG_SPEND_TX = 12n; +const OTAG_CONTRACT_CREATE_TX = 42n; +const OTAG_CONTRACT_CALL_TX = 43n; + +type otag = 11n | 12n | 42n | 43n; + +const IDTAG_ACCOUNT = 1n; +const IDTAG_NAME = 2n; +const IDTAG_CONTRACT = 5n; + +type idtag = 1n | 2n | 5n; + + +type id = + {tag : idtag, + hash : Uint8Array}; + +type SignedTx = + {signatures : Array, + transaction : Uint8Array}; + +type SpendTx = + {sender : id, + recipient : id, + amount : bigint, + fee : bigint, + ttl : bigint, + nonce : bigint, + payload : Uint8Array}; + +type ContractCreateTx = + {owner : id, + nonce : bigint, + code : Uint8Array, + ct_version : bigint, + fee : bigint, + ttl : bigint, + deposit : bigint, + amount : bigint, + gas : bigint, + gas_price : bigint, + call_data : Uint8Array}; + +type ContractCallTx = + {caller : id, + nonce : bigint, + contract : id, + abi_version : bigint, + fee : bigint, + ttl : bigint, + amount : bigint, + gas : bigint, + gas_price : bigint, + call_data : Uint8Array}; + +type tx = SignedTx | SpendTx | ContractCreateTx | ContractCallTx; + +type decoded_tx = + {tag : otag, + version : Uint8Array, + tx : tx}; + +/** + * Decode a `tx_Base64` string + */ +function +decode_tx(tx_str : string): decoded_tx { + let base64_stuff : string = tx_str.slice(3); // tx_[...] -> [...] + let stuff : Uint8Array = b64.decode(base64_stuff); // <> + let rlp_stuff : Uint8Array = stuff.slice(0, stuff.length - 4); // <> + let decoded_datas : Array = rlp.decode(rlp_stuff).decoded_data as Array; // decoded_data : list(rlp.decoded_data() :: binary() | list(decoded_data())) + // tag, vsn + let tag_bytes : Uint8Array = decoded_datas[0] as Uint8Array; // [tag, vsn, fields] -> tag + let tag : bigint = bytes_to_bigint(tag_bytes); // <> = TagBytes + let vsn : Uint8Array = decoded_datas[1] as Uint8Array; + // tx fields + let tx_fields : Array = decoded_datas.slice(2); + let tx : tx = decode_fields(tag, tx_fields); + return {tag: tag as otag, version: vsn, tx: tx}; +} + +/** + * Decode a transaction given the raw fields + * + * @internal + */ +function +decode_fields(tag: bigint, fields: Array): tx { + switch (tag) { + case 11n: return decode_fields_SignedTx(fields); + case 12n: return decode_fields_SpendTx(fields); + case 42n: return decode_fields_ContractCreateTx(fields); + case 43n: return decode_fields_ContractCallTx(fields); + default : throw new Error("invalid object tag: " + tag); + } + console.log('fields: ', fields); + throw new Error("nyi"); +} + + +/** + * Decode a SignedTx + * + * @internal + */ +function +decode_fields_SignedTx(fields: Array): SignedTx { + throw new Error('nyi'); +} + + +/** + * Decode a SpendTx + * + * @internal + */ +function +decode_fields_SpendTx(fields: Array): SpendTx { + // [ :: id(), + // :: id(), + // :: int(), + // :: int(), + // :: int(), + // :: int(), + // :: binary()] + let sender : id = decode_id(fields[0] as Uint8Array); + let recipient : id = decode_id(fields[1] as Uint8Array); + let amount : bigint = bytes_to_bigint(fields[2] as Uint8Array); + let fee : bigint = bytes_to_bigint(fields[3] as Uint8Array); + let ttl : bigint = bytes_to_bigint(fields[4] as Uint8Array); + let nonce : bigint = bytes_to_bigint(fields[5] as Uint8Array); + let payload : Uint8Array = fields[6] as Uint8Array; + return {sender : sender, + recipient : recipient, + amount : amount, + fee : fee, + ttl : ttl, + nonce : nonce, + payload : payload}; + +} + +function +decode_fields_ContractCreateTx(fields: Array): ContractCreateTx { + throw new Error('nyi'); +} + +function +decode_fields_ContractCallTx(fields: Array): ContractCallTx { + throw new Error('nyi'); +} + +function +decode_id(id: Uint8Array): id { + let idtag : idtag = BigInt(id[0]) as idtag; + return {tag: idtag, hash: id.slice(1)}; +} diff --git a/libs/vdk_aeser/scratch/sp.ts b/libs/vdk_aeser/scratch/sp.ts new file mode 100644 index 0000000..8448945 --- /dev/null +++ b/libs/vdk_aeser/scratch/sp.ts @@ -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 +{ +} diff --git a/libs/vdk_aeser/src/vdk_aeser.ts b/libs/vdk_aeser/src/vdk_aeser.ts new file mode 100644 index 0000000..f2557ae --- /dev/null +++ b/libs/vdk_aeser/src/vdk_aeser.ts @@ -0,0 +1,97 @@ +/** + * Miscellaneous serialization/deserialization functions + * + * Refs: + * 1. https://github.com/aeternity/protocol/blob/master/node/api/api_encoding.md + * 2. https://github.com/aeternity/protocol/blob/master/serializations.md + * + * @module + */ + + +import * as vdk_base58 from './jex_include/local-vdk_base58-0.1.0/dist/vdk_base58.js'; +import * as vdk_binary from './jex_include/local-vdk_binary-0.1.0/dist/vdk_binary.js'; + + +export { + pubkey2ak_str, + add_check_bytes, + shasha4 +} + + + +/** + * Take a public key as 32 bytes and encode it in AE API ak_... notation + * + * Refs: + * 1. https://github.com/aeternity/protocol/blob/master/node/api/api_encoding.md + */ +async function +pubkey2ak_str + (pubkey_bytes : Uint8Array) + : Promise +{ + let checked_pubkey_bytes : Uint8Array = await add_check_bytes(pubkey_bytes); + let b58_str : string = vdk_base58.encode(checked_pubkey_bytes); + return ("ak_" + b58_str); +} + + + +/** + * Take the double-sha of a byte array and return the first 4 bytes + * + * TypeScript equivalent of + * + * ```erlang + * add_check_bytes(Bin) when is_binary(Bin) -> + * <> = sha256(sha256(Bin)), + * <>. + * ``` + * + * Refs: + * 1. https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest + * 2. https://github.com/aeternity/protocol/blob/master/node/api/api_encoding.md + * 3. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer/slice + * + * is async because everything is retarded. +*/ +async function +add_check_bytes + (pubkey_bytes : Uint8Array) + : Promise +{ + let check_bytes : Uint8Array = await shasha4(pubkey_bytes); + return vdk_binary.bytes_concat(pubkey_bytes, check_bytes); +} + + +/** + * Take the double-sha of a byte array and return the first 4 bytes + * + * TypeScript equivalent of + * + * ```erlang + * shasha4(Bin) when is_binary(Bin) -> + * <> = sha256(sha256(Bin)), + * <>. + * ``` + * + * Refs: + * 1. https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest + * 2. https://github.com/aeternity/protocol/blob/master/node/api/api_encoding.md + * 3. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer/slice + * + * is async because everything is retarded. +*/ +async function +shasha4 + (pubkey_bytes : Uint8Array) + : Promise +{ + let shad_bytes : ArrayBuffer = await crypto.subtle.digest("SHA-256", pubkey_bytes); + let shashad_bytes : ArrayBuffer = await crypto.subtle.digest("SHA-256", shad_bytes); + let shashad_bytes_4 : ArrayBuffer = shashad_bytes.slice(0, 4); + return new Uint8Array(shashad_bytes_4); +} diff --git a/libs/vdk_aeser/tsconfig.json b/libs/vdk_aeser/tsconfig.json new file mode 100644 index 0000000..f240cdc --- /dev/null +++ b/libs/vdk_aeser/tsconfig.json @@ -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}