diff --git a/Cargo.lock b/Cargo.lock index 93ea45fc87..5c8df4f47f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7305,6 +7305,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_bytes" version = "0.11.17" @@ -9591,6 +9602,7 @@ dependencies = [ "randomness", "rstest", "serde", + "serde-wasm-bindgen", "serialization", "test-utils", "thiserror 1.0.69", diff --git a/common/src/address/dehexify.rs b/common/src/address/dehexify.rs index c15726a32f..a0885ae08c 100644 --- a/common/src/address/dehexify.rs +++ b/common/src/address/dehexify.rs @@ -13,9 +13,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crypto::vrf::VRFPublicKey; use serialization::json_encoded::JsonEncoded; -use crate::chain::{tokens::TokenId, ChainConfig, DelegationId, Destination, PoolId}; +use crate::chain::{tokens::TokenId, ChainConfig, DelegationId, Destination, OrderId, PoolId}; use super::hexified::HexifiedAddress; @@ -25,6 +26,8 @@ pub fn dehexify_all_addresses(conf: &ChainConfig, input: &str) -> String { let result = HexifiedAddress::::replace_with_address(conf, &result).to_string(); let result = HexifiedAddress::::replace_with_address(conf, &result).to_string(); let result = HexifiedAddress::::replace_with_address(conf, &result).to_string(); + let result = HexifiedAddress::::replace_with_address(conf, &result).to_string(); + let result = HexifiedAddress::::replace_with_address(conf, &result).to_string(); result } @@ -39,3 +42,131 @@ pub fn to_dehexified_json( } // TODO: add tests that create blocks, and ensure the replacement in json works properly. +#[cfg(test)] +mod tests { + use rstest::rstest; + use strum::{EnumCount, EnumDiscriminants, EnumIter, IntoEnumIterator}; + + use crypto::{ + key::{KeyKind, PrivateKey}, + vrf::{VRFKeyKind, VRFPrivateKey, VRFPublicKey}, + }; + use test_utils::random::{gen_random_alnum_string, make_seedable_rng, Seed}; + + use crate::{ + address::{hexified::HexifiedAddress, pubkeyhash::PublicKeyHash, Address}, + chain::{ + config::create_regtest, tokens::TokenId, DelegationId, Destination, DestinationTag, + OrderId, PoolId, + }, + primitives::H256, + }; + + #[derive(PartialEq, Eq, PartialOrd, Ord, EnumCount, EnumDiscriminants)] + #[strum_discriminants(name(HexifiableTag), derive(EnumIter))] + enum Hexifiable { + Destination(Destination), + PoolId(PoolId), + DelegationId(DelegationId), + TokenId(TokenId), + OrderId(OrderId), + VRFPublicKey(VRFPublicKey), + } + + #[rstest] + #[trace] + #[case(Seed::from_entropy())] + fn many_random_instances(#[case] seed: Seed) { + use crate::address::dehexify::dehexify_all_addresses; + use randomness::seq::IteratorRandom; + + let mut rng = make_seedable_rng(seed); + let chain_config = create_regtest(); + + let strings = (0..100) + .map(|_| gen_random_alnum_string(&mut rng, 0, 50)) + .collect::>(); + + let keys = (0..strings.len()) + .map(|_| match HexifiableTag::iter().choose(&mut rng).unwrap() { + HexifiableTag::Destination => { + let dest = match DestinationTag::iter().choose(&mut rng).unwrap() { + DestinationTag::AnyoneCanSpend => Destination::AnyoneCanSpend, + DestinationTag::PublicKey => { + let (_private_key, public_key) = + PrivateKey::new_from_rng(&mut rng, KeyKind::Secp256k1Schnorr); + Destination::PublicKey(public_key) + } + DestinationTag::PublicKeyHash => { + let (_private_key, public_key) = + PrivateKey::new_from_rng(&mut rng, KeyKind::Secp256k1Schnorr); + Destination::PublicKeyHash(PublicKeyHash::from(&public_key)) + } + DestinationTag::ScriptHash => Destination::ScriptHash( + crate::primitives::Id::new(H256::random_using(&mut rng)), + ), + DestinationTag::ClassicMultisig => { + let (_private_key, public_key) = + PrivateKey::new_from_rng(&mut rng, KeyKind::Secp256k1Schnorr); + Destination::ClassicMultisig(PublicKeyHash::from(&public_key)) + } + }; + Hexifiable::Destination(dest) + } + HexifiableTag::PoolId => Hexifiable::PoolId(PoolId::random_using(&mut rng)), + HexifiableTag::DelegationId => { + Hexifiable::DelegationId(DelegationId::random_using(&mut rng)) + } + HexifiableTag::TokenId => Hexifiable::TokenId(TokenId::random_using(&mut rng)), + HexifiableTag::OrderId => Hexifiable::OrderId(OrderId::random_using(&mut rng)), + HexifiableTag::VRFPublicKey => Hexifiable::VRFPublicKey( + VRFPrivateKey::new_from_rng(&mut rng, VRFKeyKind::Schnorrkel).1, + ), + }) + .collect::>(); + + let final_str = strings + .iter() + .zip(keys.iter()) + .map(|(s, k)| { + let hexified = match k { + Hexifiable::Destination(d) => HexifiedAddress::new(d).to_string(), + Hexifiable::PoolId(d) => HexifiedAddress::new(d).to_string(), + Hexifiable::DelegationId(d) => HexifiedAddress::new(d).to_string(), + Hexifiable::TokenId(d) => HexifiedAddress::new(d).to_string(), + Hexifiable::OrderId(d) => HexifiedAddress::new(d).to_string(), + Hexifiable::VRFPublicKey(d) => HexifiedAddress::new(d).to_string(), + }; + s.clone() + &hexified + }) + .collect::>() + .join(""); + + let to_test = dehexify_all_addresses(&chain_config, &final_str); + + let expected = strings + .iter() + .zip(keys.into_iter()) + .map(|(s, k)| { + let address_str = match k { + Hexifiable::Destination(d) => { + Address::new(&chain_config, d).unwrap().to_string() + } + Hexifiable::PoolId(d) => Address::new(&chain_config, d).unwrap().to_string(), + Hexifiable::DelegationId(d) => { + Address::new(&chain_config, d).unwrap().to_string() + } + Hexifiable::TokenId(d) => Address::new(&chain_config, d).unwrap().to_string(), + Hexifiable::OrderId(d) => Address::new(&chain_config, d).unwrap().to_string(), + Hexifiable::VRFPublicKey(d) => { + Address::new(&chain_config, d).unwrap().to_string() + } + }; + s.clone() + &address_str + }) + .collect::>() + .join(""); + + assert_eq!(to_test, expected); + } +} diff --git a/common/src/chain/transaction/signature/inputsig/mod.rs b/common/src/chain/transaction/signature/inputsig/mod.rs index 59acc75a56..40fd1edfa5 100644 --- a/common/src/chain/transaction/signature/inputsig/mod.rs +++ b/common/src/chain/transaction/signature/inputsig/mod.rs @@ -28,7 +28,18 @@ use standard_signature::StandardInputSignature; use super::{DestinationSigError, Signable}; -#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, Ord, PartialOrd, EnumDiscriminants)] +#[derive( + Debug, + Encode, + Decode, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + EnumDiscriminants, + serde::Serialize, +)] #[strum_discriminants(name(InputWitnessTag))] pub enum InputWitness { #[codec(index = 0)] diff --git a/common/src/chain/transaction/signature/inputsig/standard_signature.rs b/common/src/chain/transaction/signature/inputsig/standard_signature.rs index 6bb9a69207..8b3539cb96 100644 --- a/common/src/chain/transaction/signature/inputsig/standard_signature.rs +++ b/common/src/chain/transaction/signature/inputsig/standard_signature.rs @@ -47,7 +47,7 @@ use super::{ }, }; -#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, serde::Serialize)] pub struct StandardInputSignature { sighash_type: SigHashType, raw_signature: Vec, diff --git a/common/src/chain/transaction/signed_transaction.rs b/common/src/chain/transaction/signed_transaction.rs index b3bd59f7da..d8920edea4 100644 --- a/common/src/chain/transaction/signed_transaction.rs +++ b/common/src/chain/transaction/signed_transaction.rs @@ -24,7 +24,7 @@ use crate::{ use serialization::{Decode, Encode}; use utils::ensure; -#[derive(Debug, Clone, PartialEq, Eq, Encode)] +#[derive(Debug, Clone, PartialEq, Eq, Encode, serde::Serialize)] pub struct SignedTransaction { transaction: Transaction, signatures: Vec, diff --git a/wasm-wrappers/Cargo.toml b/wasm-wrappers/Cargo.toml index 13a66e524a..41da62ec4a 100644 --- a/wasm-wrappers/Cargo.toml +++ b/wasm-wrappers/Cargo.toml @@ -19,7 +19,10 @@ serialization = { path = "../serialization" } tx-verifier = { path = "../chainstate/tx-verifier" } utils = { path = "../utils" } -bip39 = { workspace = true, default-features = false, features = ["std", "zeroize"] } +bip39 = { workspace = true, default-features = false, features = [ + "std", + "zeroize", +] } fixed-hash.workspace = true itertools.workspace = true serde.workspace = true @@ -33,6 +36,7 @@ tsify = "0.5" wasm-bindgen = "0.2" # web-sys provides `console::log` and is useful during debugging. web-sys = { version = "0.3", features = ["console"] } +serde-wasm-bindgen = "0.6" [dev-dependencies] hex.workspace = true diff --git a/wasm-wrappers/WASM-API.md b/wasm-wrappers/WASM-API.md index 227b16ff3e..e9908aad45 100644 --- a/wasm-wrappers/WASM-API.md +++ b/wasm-wrappers/WASM-API.md @@ -211,6 +211,10 @@ ScriptHash and ClassicMultisig destinations are not supported. Given inputs as bytes, outputs as bytes, and flags settings, this function returns the transaction that contains them all, as bytes. +### Function: `decode_signed_transaction_to_js` + +Decodes a signed transaction from its binary encoding into a JavaScript object. + ### Function: `encode_witness_no_signature` Encode an input witness of the variant that contains no signature. diff --git a/wasm-wrappers/js-bindings-test/tests/test_transaction_and_witness_encoding.ts b/wasm-wrappers/js-bindings-test/tests/test_transaction_and_witness_encoding.ts index 0c01773936..66ccf53ad0 100644 --- a/wasm-wrappers/js-bindings-test/tests/test_transaction_and_witness_encoding.ts +++ b/wasm-wrappers/js-bindings-test/tests/test_transaction_and_witness_encoding.ts @@ -21,26 +21,21 @@ import { estimate_transaction_size, make_default_account_privkey, make_receiving_address, + decode_signed_transaction_to_js, Network, SignatureHashType, } from "../../pkg/wasm_wrappers.js"; import { assert_eq_arrays, + assert_eq_vals, get_err_msg, TEXT_ENCODER, } from "./utils.js"; -import { - MNEMONIC, - RANDOM_HEIGHT, -} from "./defs.js"; -import { - ADDRESS -} from "./test_address_generation.js"; -import { - INPUTS, -} from "./test_encode_other_inputs.js"; +import { MNEMONIC, RANDOM_HEIGHT } from "./defs.js"; +import { ADDRESS } from "./test_address_generation.js"; +import { INPUTS } from "./test_encode_other_inputs.js"; import { OUTPUTS, OUTPUT_CREATE_STAKE_POOL, @@ -50,7 +45,7 @@ import { export function test_transaction_and_witness_encoding() { const account_pubkey = make_default_account_privkey( MNEMONIC, - Network.Testnet + Network.Testnet, ); const receiving_privkey = make_receiving_address(account_pubkey, 0); @@ -76,19 +71,23 @@ export function test_transaction_and_witness_encoding() { console.log("Tested invalid outputs successfully"); } - const tx = encode_transaction(Uint8Array.from(INPUTS), Uint8Array.from(OUTPUTS), BigInt(0)); + const tx = encode_transaction( + Uint8Array.from(INPUTS), + Uint8Array.from(OUTPUTS), + BigInt(0), + ); const expected_tx = [ 1, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 8, 1, 0, 145, 1, 1, 91, 58, 110, 176, 100, 207, 6, 194, 41, 193, 30, - 91, 4, 195, 202, 103, 207, 80, 217, 178, 0, 145, 1, 3, 0, 0, 0, 0, 0, 0, + 91, 4, 195, 202, 103, 207, 80, 217, 178, 0, 145, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 2, 113, 2, 0, 1, 91, 58, 110, 176, 100, 207, 6, 194, 41, 193, 30, 91, - 4, 195, 202, 103, 207, 80, 217, 178, 0, 108, 245, 234, 97, 170, 9, 247, - 158, 169, 100, 84, 123, 235, 183, 147, 29, 136, 118, 203, 24, 146, 56, 60, - 217, 2, 198, 32, 133, 255, 240, 84, 123, 1, 91, 58, 110, 176, 100, 207, 6, - 194, 41, 193, 30, 91, 4, 195, 202, 103, 207, 80, 217, 178, 100, 0, 0, + 2, 113, 2, 0, 1, 91, 58, 110, 176, 100, 207, 6, 194, 41, 193, 30, 91, 4, + 195, 202, 103, 207, 80, 217, 178, 0, 108, 245, 234, 97, 170, 9, 247, 158, + 169, 100, 84, 123, 235, 183, 147, 29, 136, 118, 203, 24, 146, 56, 60, 217, + 2, 198, 32, 133, 255, 240, 84, 123, 1, 91, 58, 110, 176, 100, 207, 6, 194, + 41, 193, 30, 91, 4, 195, 202, 103, 207, 80, 217, 178, 100, 0, 0, ]; assert_eq_arrays(tx, expected_tx); console.log("tx encoding ok"); @@ -98,7 +97,12 @@ export function test_transaction_and_witness_encoding() { assert_eq_arrays(witness, expected_no_signature_witness); console.log("empty witness encoding ok"); - const opt_utxos = [1, ...OUTPUT_LOCK_THEN_TRANSFER, 1, ...OUTPUT_CREATE_STAKE_POOL]; + const opt_utxos = [ + 1, + ...OUTPUT_LOCK_THEN_TRANSFER, + 1, + ...OUTPUT_CREATE_STAKE_POOL, + ]; try { const invalid_private_key = TEXT_ENCODER.encode("invalid private key"); @@ -111,7 +115,7 @@ export function test_transaction_and_witness_encoding() { 0, { pool_info: {}, order_info: {} }, BigInt(RANDOM_HEIGHT), - Network.Testnet + Network.Testnet, ); throw new Error("Invalid private key worked somehow!"); } catch (e) { @@ -131,7 +135,7 @@ export function test_transaction_and_witness_encoding() { 0, { pool_info: {}, order_info: {} }, BigInt(RANDOM_HEIGHT), - Network.Testnet + Network.Testnet, ); throw new Error("Invalid address worked somehow!"); } catch (e) { @@ -151,7 +155,7 @@ export function test_transaction_and_witness_encoding() { 0, { pool_info: {}, order_info: {} }, BigInt(RANDOM_HEIGHT), - Network.Testnet + Network.Testnet, ); throw new Error("Invalid transaction worked somehow!"); } catch (e) { @@ -171,7 +175,7 @@ export function test_transaction_and_witness_encoding() { 0, { pool_info: {}, order_info: {} }, BigInt(RANDOM_HEIGHT), - Network.Testnet + Network.Testnet, ); throw new Error("Invalid utxo worked somehow!"); } catch (e) { @@ -191,11 +195,15 @@ export function test_transaction_and_witness_encoding() { 0, { pool_info: {}, order_info: {} }, BigInt(RANDOM_HEIGHT), - Network.Testnet + Network.Testnet, ); throw new Error("Invalid utxo worked somehow!"); } catch (e) { - if (!get_err_msg(e).includes("Error creating sighash input commitments: Utxo not found")) { + if ( + !get_err_msg(e).includes( + "Error creating sighash input commitments: Utxo not found", + ) + ) { throw e; } console.log("Tested invalid utxo count in encode witness successfully"); @@ -211,7 +219,7 @@ export function test_transaction_and_witness_encoding() { invalid_input_idx, { pool_info: {}, order_info: {} }, BigInt(RANDOM_HEIGHT), - Network.Testnet + Network.Testnet, ); throw new Error("Invalid address worked somehow!"); } catch (e) { @@ -230,18 +238,18 @@ export function test_transaction_and_witness_encoding() { 0, { pool_info: {}, order_info: {} }, BigInt(RANDOM_HEIGHT), - Network.Testnet + Network.Testnet, ); // as signatures are random, hardcode one so we can test the encodings for the signed transaction const random_witness2 = [ 1, 1, 141, 1, 0, 2, 227, 252, 33, 195, 223, 44, 38, 35, 73, 145, 212, 180, - 49, 115, 4, 150, 204, 250, 205, 123, 131, 201, 114, 130, 186, 209, 98, - 181, 118, 233, 133, 89, 0, 99, 87, 109, 227, 15, 21, 164, 83, 151, 14, - 235, 106, 83, 230, 40, 64, 146, 112, 52, 103, 203, 31, 216, 54, 141, 223, - 27, 175, 133, 164, 172, 239, 122, 121, 17, 88, 114, 99, 6, 19, 220, 156, - 167, 40, 17, 211, 196, 45, 209, 111, 170, 161, 2, 254, 122, 169, 127, 235, - 158, 62, 127, 177, 12, 228, + 49, 115, 4, 150, 204, 250, 205, 123, 131, 201, 114, 130, 186, 209, 98, 181, + 118, 233, 133, 89, 0, 99, 87, 109, 227, 15, 21, 164, 83, 151, 14, 235, 106, + 83, 230, 40, 64, 146, 112, 52, 103, 203, 31, 216, 54, 141, 223, 27, 175, + 133, 164, 172, 239, 122, 121, 17, 88, 114, 99, 6, 19, 220, 156, 167, 40, 17, + 211, 196, 45, 209, 111, 170, 161, 2, 254, 122, 169, 127, 235, 158, 62, 127, + 177, 12, 228, ]; try { @@ -261,7 +269,7 @@ export function test_transaction_and_witness_encoding() { } catch (e) { if ( !get_err_msg(e).includes( - "The number of signatures does not match the number of inputs" + "The number of signatures does not match the number of inputs", ) ) { throw e; @@ -287,26 +295,26 @@ export function test_transaction_and_witness_encoding() { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 8, 1, 0, 145, 1, 1, 91, 58, 110, 176, 100, 207, 6, 194, 41, 193, 30, - 91, 4, 195, 202, 103, 207, 80, 217, 178, 0, 145, 1, 3, 0, 0, 0, 0, 0, 0, + 91, 4, 195, 202, 103, 207, 80, 217, 178, 0, 145, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 2, 113, 2, 0, 1, 91, 58, 110, 176, 100, 207, 6, 194, 41, 193, 30, 91, - 4, 195, 202, 103, 207, 80, 217, 178, 0, 108, 245, 234, 97, 170, 9, 247, - 158, 169, 100, 84, 123, 235, 183, 147, 29, 136, 118, 203, 24, 146, 56, 60, - 217, 2, 198, 32, 133, 255, 240, 84, 123, 1, 91, 58, 110, 176, 100, 207, 6, - 194, 41, 193, 30, 91, 4, 195, 202, 103, 207, 80, 217, 178, 100, 0, 0, 8, - 1, 1, 141, 1, 0, 2, 227, 252, 33, 195, 223, 44, 38, 35, 73, 145, 212, 180, - 49, 115, 4, 150, 204, 250, 205, 123, 131, 201, 114, 130, 186, 209, 98, - 181, 118, 233, 133, 89, 0, 99, 87, 109, 227, 15, 21, 164, 83, 151, 14, + 2, 113, 2, 0, 1, 91, 58, 110, 176, 100, 207, 6, 194, 41, 193, 30, 91, 4, + 195, 202, 103, 207, 80, 217, 178, 0, 108, 245, 234, 97, 170, 9, 247, 158, + 169, 100, 84, 123, 235, 183, 147, 29, 136, 118, 203, 24, 146, 56, 60, 217, + 2, 198, 32, 133, 255, 240, 84, 123, 1, 91, 58, 110, 176, 100, 207, 6, 194, + 41, 193, 30, 91, 4, 195, 202, 103, 207, 80, 217, 178, 100, 0, 0, 8, 1, 1, + 141, 1, 0, 2, 227, 252, 33, 195, 223, 44, 38, 35, 73, 145, 212, 180, 49, + 115, 4, 150, 204, 250, 205, 123, 131, 201, 114, 130, 186, 209, 98, 181, 118, + 233, 133, 89, 0, 99, 87, 109, 227, 15, 21, 164, 83, 151, 14, 235, 106, 83, + 230, 40, 64, 146, 112, 52, 103, 203, 31, 216, 54, 141, 223, 27, 175, 133, + 164, 172, 239, 122, 121, 17, 88, 114, 99, 6, 19, 220, 156, 167, 40, 17, 211, + 196, 45, 209, 111, 170, 161, 2, 254, 122, 169, 127, 235, 158, 62, 127, 177, + 12, 228, 1, 1, 141, 1, 0, 2, 227, 252, 33, 195, 223, 44, 38, 35, 73, 145, + 212, 180, 49, 115, 4, 150, 204, 250, 205, 123, 131, 201, 114, 130, 186, 209, + 98, 181, 118, 233, 133, 89, 0, 99, 87, 109, 227, 15, 21, 164, 83, 151, 14, 235, 106, 83, 230, 40, 64, 146, 112, 52, 103, 203, 31, 216, 54, 141, 223, 27, 175, 133, 164, 172, 239, 122, 121, 17, 88, 114, 99, 6, 19, 220, 156, 167, 40, 17, 211, 196, 45, 209, 111, 170, 161, 2, 254, 122, 169, 127, 235, - 158, 62, 127, 177, 12, 228, 1, 1, 141, 1, 0, 2, 227, 252, 33, 195, 223, - 44, 38, 35, 73, 145, 212, 180, 49, 115, 4, 150, 204, 250, 205, 123, 131, - 201, 114, 130, 186, 209, 98, 181, 118, 233, 133, 89, 0, 99, 87, 109, 227, - 15, 21, 164, 83, 151, 14, 235, 106, 83, 230, 40, 64, 146, 112, 52, 103, - 203, 31, 216, 54, 141, 223, 27, 175, 133, 164, 172, 239, 122, 121, 17, 88, - 114, 99, 6, 19, 220, 156, 167, 40, 17, 211, 196, 45, 209, 111, 170, 161, - 2, 254, 122, 169, 127, 235, 158, 62, 127, 177, 12, 228, + 158, 62, 127, 177, 12, 228, ]; assert_eq_arrays(signed_tx, expected_signed_tx); @@ -314,12 +322,104 @@ export function test_transaction_and_witness_encoding() { Uint8Array.from(INPUTS), [ADDRESS, ADDRESS], Uint8Array.from(OUTPUTS), - Network.Testnet + Network.Testnet, ); if (estimated_size != expected_signed_tx.length) { throw new Error("wrong estimated size"); } console.log( - `estimated size ${estimated_size} vs real ${expected_signed_tx.length}` + `estimated size ${estimated_size} vs real ${expected_signed_tx.length}`, + ); + + let decoded_tx = decode_signed_transaction_to_js(signed_tx, Network.Testnet); + + const expected_decoded_tx = { + transaction: { + V1: { + version: 1, + flags: 0, + inputs: [ + { + Utxo: { + id: { + Transaction: + "0000000000000000000000000000000000000000000000000000000000000000", + }, + index: 1, + }, + }, + { + Account: { + nonce: 1, + account: { + DelegationBalance: [ + "tdelg1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqnu8zn4", + { atoms: "1" }, + ], + }, + }, + }, + ], + outputs: [ + { + LockThenTransfer: [ + { Coin: { atoms: "100" } }, + "tmt1q9dn5m4svn8sds3fcy09kpxrefnu75xekgr5wa3n", + { type: "UntilHeight", content: 100 }, + ], + }, + { + CreateStakePool: [ + "tpool1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqza035u", + { + pledge: { atoms: "40000" }, + staker: "tmt1q9dn5m4svn8sds3fcy09kpxrefnu75xekgr5wa3n", + vrf_public_key: + "006cf5ea61aa09f79ea964547bebb7931d8876cb1892383cd902c62085fff0547b", + decommission_key: + "tmt1q9dn5m4svn8sds3fcy09kpxrefnu75xekgr5wa3n", + margin_ratio_per_thousand: "10%", + cost_per_block: { atoms: "0" }, + }, + ], + }, + ], + }, + }, + signatures: [ + { + Standard: { + sighash_type: 1, + raw_signature: [ + 0, 2, 227, 252, 33, 195, 223, 44, 38, 35, 73, 145, 212, 180, 49, + 115, 4, 150, 204, 250, 205, 123, 131, 201, 114, 130, 186, 209, 98, + 181, 118, 233, 133, 89, 0, 99, 87, 109, 227, 15, 21, 164, 83, 151, + 14, 235, 106, 83, 230, 40, 64, 146, 112, 52, 103, 203, 31, 216, 54, + 141, 223, 27, 175, 133, 164, 172, 239, 122, 121, 17, 88, 114, 99, 6, + 19, 220, 156, 167, 40, 17, 211, 196, 45, 209, 111, 170, 161, 2, 254, + 122, 169, 127, 235, 158, 62, 127, 177, 12, 228, + ], + }, + }, + { + Standard: { + sighash_type: 1, + raw_signature: [ + 0, 2, 227, 252, 33, 195, 223, 44, 38, 35, 73, 145, 212, 180, 49, + 115, 4, 150, 204, 250, 205, 123, 131, 201, 114, 130, 186, 209, 98, + 181, 118, 233, 133, 89, 0, 99, 87, 109, 227, 15, 21, 164, 83, 151, + 14, 235, 106, 83, 230, 40, 64, 146, 112, 52, 103, 203, 31, 216, 54, + 141, 223, 27, 175, 133, 164, 172, 239, 122, 121, 17, 88, 114, 99, 6, + 19, 220, 156, 167, 40, 17, 211, 196, 45, 209, 111, 170, 161, 2, 254, + 122, 169, 127, 235, 158, 62, 127, 177, 12, 228, + ], + }, + }, + ], + }; + + assert_eq_vals( + JSON.stringify(decoded_tx), + JSON.stringify(expected_decoded_tx), ); } diff --git a/wasm-wrappers/src/error.rs b/wasm-wrappers/src/error.rs index 36adb71ac1..989b122703 100644 --- a/wasm-wrappers/src/error.rs +++ b/wasm-wrappers/src/error.rs @@ -81,6 +81,9 @@ pub enum Error { #[error("Invalid signed transaction intent encoding: {0}")] InvalidSignedTransactionIntentEncoding(serialization::Error), + #[error("JSON parsing error: {0:?}")] + JsonParseError(JsValue), + #[error("Error creating multisig spend: {0}")] MultisigSpendCreationError(DestinationSigError), diff --git a/wasm-wrappers/src/lib.rs b/wasm-wrappers/src/lib.rs index 43be7c55c1..4b15f5dc6d 100644 --- a/wasm-wrappers/src/lib.rs +++ b/wasm-wrappers/src/lib.rs @@ -33,10 +33,10 @@ use std::{num::NonZeroU8, str::FromStr}; use bip39::Language; use itertools::Itertools as _; -use wasm_bindgen::prelude::*; +use wasm_bindgen::{prelude::*, JsValue}; use common::{ - address::{pubkeyhash::PublicKeyHash, Address}, + address::{dehexify::dehexify_all_addresses, pubkeyhash::PublicKeyHash, Address}, chain::{ block::timestamp::BlockTimestamp, classic_multisig::ClassicMultisigChallenge, @@ -74,7 +74,7 @@ use crypto::key::{ hdkd::{child_number::ChildNumber, derivable::Derivable, u31::U31}, KeyKind, PrivateKey, PublicKey, Signature, }; -use serialization::{Decode, DecodeAll, Encode}; +use serialization::{json_encoded::JsonEncoded, Decode, DecodeAll, Encode}; use crate::{ error::Error, @@ -689,6 +689,22 @@ pub fn encode_transaction(inputs: &[u8], outputs: &[u8], flags: u64) -> Result Result { + let chain_config = Builder::new(network.into()).build(); + let tx = SignedTransaction::decode_all(&mut &transaction[..]) + .map_err(Error::InvalidTransactionEncoding)?; + + let str = JsonEncoded::new(&tx).to_string(); + let str = dehexify_all_addresses(&chain_config, &str); + + js_sys::JSON::parse(&str).map_err(Error::JsonParseError) +} + /// Encode an input witness of the variant that contains no signature. #[wasm_bindgen] pub fn encode_witness_no_signature() -> Vec {