From 30f76048c0c20c5844b8162375ecb27b91ae7047 Mon Sep 17 00:00:00 2001 From: Peter Harpending Date: Mon, 14 Aug 2023 11:05:49 -0600 Subject: [PATCH] [wip] jex can 'sign' a tx but it's not working for some reason --- jrx/src/b_lib.ts | 79 +++++++++++--- libs/vdk_aeser/jex.eterms | 2 + libs/vdk_aeser/src/vdk_aeser.ts | 181 +++++++++++++++++++++++++++++++- 3 files changed, 248 insertions(+), 14 deletions(-) diff --git a/jrx/src/b_lib.ts b/jrx/src/b_lib.ts index 5465a46..f8ab669 100644 --- a/jrx/src/b_lib.ts +++ b/jrx/src/b_lib.ts @@ -295,6 +295,10 @@ bi_msg_handler_content console.log('jr bg content message handler method:', msg.data.method); + + let public_key : Uint8Array = i_state.keypairs[0].publicKey; + let secret_key : Uint8Array = i_state.keypairs[0].secretKey; + switch (msg.data.method) { // right now just give connect info back case "connection.open": @@ -307,26 +311,25 @@ bi_msg_handler_content // right now just give address back case "address.subscribe": console.log('jr bg content message handler address.subscribe'); - // get the keypairs - // for now only getting the first one - let address_bytes : Uint8Array = i_state.keypairs[0].publicKey; // convert it to a string - let address_ak_str : string = await vdk_aeser.pubkey2ak_str(address_bytes); + let address_ak_str : string = await vdk_aeser.pubkey2ak_str(public_key); + console.log('public key:', address_ak_str); return w2a_ok(bi_address_reply(address_ak_str, [])); // right now just sign message case "message.sign": console.log('jr bg content message handler message sign'); - let secret_key : Uint8Array = i_state.keypairs[0].secretKey; - let msg_str : string = msg.data.params.message; - // use nacl detached signatures - // https://github.com/aeternity/aepp-sdk-js/blob/5df22dd297abebc0607710793a7234e6761570d4/src/utils/crypto.ts#L141-L143 - // https://github.com/aeternity/aepp-sdk-js/blob/5df22dd297abebc0607710793a7234e6761570d4/src/utils/crypto.ts#L160-L167 - let hashed_salted_msg : Uint8Array = hash_and_salt_msg(msg_str); - let signature : Uint8Array = nacl.sign.detached(hashed_salted_msg, secret_key); - let signature_str : string = vdk_binary.bytes_to_hex_str(signature); - return w2a_ok({signature: signature_str}); + let msg_str : string = msg.data.params.message; + return w2a_ok(msg_sign(msg_str, secret_key)); + // right now just sign tx + case "transaction.sign": + console.log('jr bg content message handler transaction sign'); + let tx_str : string = msg.data.params.tx; + console.log('transaction: ', tx); + let result = await tx_sign(tx_str, secret_key) + console.log('signed transaction: ', result.signedTransaction); + return w2a_ok(result); // default is NYI default: @@ -335,6 +338,56 @@ bi_msg_handler_content } +/** + * Fucking js block scope rules + * + * NOOOOOOOO YOU CAN'T DECLARE TWO DIFFERENT VARIABLES WITH THE SAME NAME IN + * TWO DIFFERENT CASES EVEN THOUGH THEY'RE MUTUALLY EXCLUSIVE + * + * wojak.jpg + * + * @internal + */ +function +msg_sign + (msg_str : string, + secret_key : Uint8Array) + : {signature : string} +{ + // use nacl detached signatures + // https://github.com/aeternity/aepp-sdk-js/blob/5df22dd297abebc0607710793a7234e6761570d4/src/utils/crypto.ts#L141-L143 + // https://github.com/aeternity/aepp-sdk-js/blob/5df22dd297abebc0607710793a7234e6761570d4/src/utils/crypto.ts#L160-L167 + let hashed_salted_msg : Uint8Array = hash_and_salt_msg(msg_str); + let signature : Uint8Array = nacl.sign.detached(hashed_salted_msg, secret_key); + let signature_str : string = vdk_binary.bytes_to_hex_str(signature); + return {signature: signature_str}; +} + + + +/** + * Fucking js block scope rules + * + * NOOOOOOOO YOU CAN'T DECLARE TWO DIFFERENT VARIABLES WITH THE SAME NAME IN + * TWO DIFFERENT CASES EVEN THOUGH THEY'RE MUTUALLY EXCLUSIVE + * + * wojak.jpg + * + * @internal + */ +async function +tx_sign + (tx_str : string, + secret_key : Uint8Array) + : Promise<{signedTransaction : string}> +{ + let tx_bytes : Uint8Array = (await vdk_aeser.unbaseNcheck(tx_str)).bytes; + let signature : Uint8Array = nacl.sign.detached(tx_bytes, secret_key); + let signed_tx_bytes : Uint8Array = vdk_aeser.signed_tx([signature], tx_bytes); + let signed_tx_str : string = await vdk_aeser.baseNcheck('tx', signed_tx_bytes); + return {signedTransaction: signed_tx_str}; +} + /** * Make the dumb address_subscribe thing * diff --git a/libs/vdk_aeser/jex.eterms b/libs/vdk_aeser/jex.eterms index 218c2c1..30b6647 100644 --- a/libs/vdk_aeser/jex.eterms +++ b/libs/vdk_aeser/jex.eterms @@ -3,4 +3,6 @@ {name, vdk_aeser}. {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_binary-0.1.0"]}. diff --git a/libs/vdk_aeser/src/vdk_aeser.ts b/libs/vdk_aeser/src/vdk_aeser.ts index f2557ae..2a43088 100644 --- a/libs/vdk_aeser/src/vdk_aeser.ts +++ b/libs/vdk_aeser/src/vdk_aeser.ts @@ -10,13 +10,18 @@ import * as vdk_base58 from './jex_include/local-vdk_base58-0.1.0/dist/vdk_base58.js'; +import * as vdk_base64 from './jex_include/local-vdk_base64-0.1.0/dist/vdk_base64.js'; import * as vdk_binary from './jex_include/local-vdk_binary-0.1.0/dist/vdk_binary.js'; +import * as vdk_rlp from './jex_include/local-vdk_rlp-0.1.0/dist/vdk_rlp.js'; export { pubkey2ak_str, add_check_bytes, - shasha4 + shasha4, + unbaseNcheck, + baseNcheck, + signed_tx } @@ -95,3 +100,177 @@ shasha4 let shashad_bytes_4 : ArrayBuffer = shashad_bytes.slice(0, 4); return new Uint8Array(shashad_bytes_4); } + + + +/** + * Result of decoding a baseNCheck encoded string + */ +type baseNchecked + = {bytes : Uint8Array, + check_passed : boolean} + + +/** + * take a baseNcheck (N = 58, 64) encoded string and get out the resulting bytes + * + * returns boolean + */ +async function +unbaseNcheck + (tx_str : string) + : Promise +{ + // tx_... ak_... etc + let prefix : string = tx_str.slice(0, 2); + let baseNstr : string = tx_str.slice(3); + + // branch for base58 or base64 based on prefix + // table: + // https://github.com/aeternity/protocol/blob/fd179822fc70241e79cbef7636625cf344a08109/node/api/api_encoding.md + switch (prefix) { + case 'ak': return await unbase58check(baseNstr); // Account pubkey + case 'ba': return await unbase64check(baseNstr); // Byte array + case 'bf': return await unbase58check(baseNstr); // Block Proof of Fraud hash + case 'bs': return await unbase58check(baseNstr); // Block State hash + case 'bx': return await unbase58check(baseNstr); // Block transaction hash + case 'cb': return await unbase64check(baseNstr); // Contract byte array + case 'ch': return await unbase58check(baseNstr); // Channel + case 'cm': return await unbase58check(baseNstr); // Commitment + case 'ct': return await unbase58check(baseNstr); // Contract pubkey + case 'kh': return await unbase58check(baseNstr); // Key block hash + case 'mh': return await unbase58check(baseNstr); // Micro block hash + case 'nm': return await unbase58check(baseNstr); // Name + case 'ok': return await unbase58check(baseNstr); // Oracle pubkey + case 'oq': return await unbase58check(baseNstr); // Oracle query id + case 'or': return await unbase64check(baseNstr); // Oracle response + case 'ov': return await unbase64check(baseNstr); // Oracle query + case 'pi': return await unbase64check(baseNstr); // Proof of Inclusion + case 'pp': return await unbase58check(baseNstr); // Peer pubkey + case 'sg': return await unbase58check(baseNstr); // Signature + case 'ss': return await unbase64check(baseNstr); // State trees + case 'cs': return await unbase64check(baseNstr); // Contract calls state tree + case 'ck': return await unbase64check(baseNstr); // Contract state key + case 'cv': return await unbase64check(baseNstr); // Contract state value + case 'st': return await unbase64check(baseNstr); // State + case 'th': return await unbase58check(baseNstr); // Transaction hash + case 'tx': return await unbase64check(baseNstr); // Transaction + default : throw new Error('unknown prefix: ' + prefix); + } +} + +async function +unbase64check + (baseNstr : string) + : Promise +{ + let bytes : Uint8Array = vdk_base64.decode(baseNstr); + let check_bytes : Uint8Array = bytes.slice(0, 4); + let data_bytes : Uint8Array = bytes.slice(4); + let correct_check_bytes : Uint8Array = await shasha4(data_bytes); + let check_passed : boolean = vdk_binary.bytes_eq(correct_check_bytes, check_bytes); + return {bytes : data_bytes, + check_passed : check_passed}; +} + + +async function +unbase58check + (baseNstr : string) + : Promise +{ + let bytes : Uint8Array = vdk_base58.decode(baseNstr); + let check_bytes : Uint8Array = bytes.slice(0, 4); + let data_bytes : Uint8Array = bytes.slice(4); + let correct_check_bytes : Uint8Array = await shasha4(data_bytes); + let check_passed : boolean = vdk_binary.bytes_eq(correct_check_bytes, check_bytes); + return {bytes : data_bytes, + check_passed : check_passed}; +} + + + +/** + * take bytes and encode using baseNcheck (N = 58, 64) according to the prefix + */ +async function +baseNcheck + (prefix : string, + data_bytes : Uint8Array) + : Promise +{ + // tx_... ak_... etc + + // branch for base58 or base64 based on prefix + // table: + // https://github.com/aeternity/protocol/blob/fd179822fc70241e79cbef7636625cf344a08109/node/api/api_encoding.md + switch (prefix) { + case 'ak': return await base58check(prefix, data_bytes); // Account pubkey + case 'ba': return await base64check(prefix, data_bytes); // Byte array + case 'bf': return await base58check(prefix, data_bytes); // Block Proof of Fraud hash + case 'bs': return await base58check(prefix, data_bytes); // Block State hash + case 'bx': return await base58check(prefix, data_bytes); // Block transaction hash + case 'cb': return await base64check(prefix, data_bytes); // Contract byte array + case 'ch': return await base58check(prefix, data_bytes); // Channel + case 'cm': return await base58check(prefix, data_bytes); // Commitment + case 'ct': return await base58check(prefix, data_bytes); // Contract pubkey + case 'kh': return await base58check(prefix, data_bytes); // Key block hash + case 'mh': return await base58check(prefix, data_bytes); // Micro block hash + case 'nm': return await base58check(prefix, data_bytes); // Name + case 'ok': return await base58check(prefix, data_bytes); // Oracle pubkey + case 'oq': return await base58check(prefix, data_bytes); // Oracle query id + case 'or': return await base64check(prefix, data_bytes); // Oracle response + case 'ov': return await base64check(prefix, data_bytes); // Oracle query + case 'pi': return await base64check(prefix, data_bytes); // Proof of Inclusion + case 'pp': return await base58check(prefix, data_bytes); // Peer pubkey + case 'sg': return await base58check(prefix, data_bytes); // Signature + case 'ss': return await base64check(prefix, data_bytes); // State trees + case 'cs': return await base64check(prefix, data_bytes); // Contract calls state tree + case 'ck': return await base64check(prefix, data_bytes); // Contract state key + case 'cv': return await base64check(prefix, data_bytes); // Contract state value + case 'st': return await base64check(prefix, data_bytes); // State + case 'th': return await base58check(prefix, data_bytes); // Transaction hash + case 'tx': return await base64check(prefix, data_bytes); // Transaction + default : throw new Error('unknown prefix: ' + prefix); + } +} + +async function +base64check + (prefix : string, + data_bytes : Uint8Array) + : Promise +{ + let check_bytes : Uint8Array = await shasha4(data_bytes); + let full_bytes : Uint8Array = vdk_binary.bytes_concat(check_bytes, data_bytes); + let full_str : string = vdk_base64.encode(full_bytes); + return prefix + '_' + full_str; +} + + +async function +base58check + (prefix : string, + data_bytes : Uint8Array) + : Promise +{ + let check_bytes : Uint8Array = await shasha4(data_bytes); + let full_bytes : Uint8Array = vdk_binary.bytes_concat(check_bytes, data_bytes); + let full_str : string = vdk_base58.encode(full_bytes); + return prefix + '_' + full_str; +} + + +/** + * RLP-encode signed tx (signatures and tx are both the BINARY representations) + * + * See https://github.com/aeternity/protocol/blob/fd179822fc70241e79cbef7636625cf344a08109/serializations.md#signed-transaction + */ +function +signed_tx + (signatures : Array, + tx : Uint8Array) + : Uint8Array +{ + return vdk_rlp.encode([signatures, tx]); +}