Skip to content

Commit 01f902e

Browse files
committed
Add tx decode to js value to wasm lib
1 parent 632f15a commit 01f902e

5 files changed

Lines changed: 156 additions & 58 deletions

File tree

Cargo.lock

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wasm-wrappers/Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ serialization = { path = "../serialization" }
1919
tx-verifier = { path = "../chainstate/tx-verifier" }
2020
utils = { path = "../utils" }
2121

22-
bip39 = { workspace = true, default-features = false, features = ["std", "zeroize"] }
22+
bip39 = { workspace = true, default-features = false, features = [
23+
"std",
24+
"zeroize",
25+
] }
2326
fixed-hash.workspace = true
2427
itertools.workspace = true
2528
serde.workspace = true
@@ -33,6 +36,7 @@ tsify = "0.5"
3336
wasm-bindgen = "0.2"
3437
# web-sys provides `console::log` and is useful during debugging.
3538
web-sys = { version = "0.3", features = ["console"] }
39+
serde-wasm-bindgen = "0.6"
3640

3741
[dev-dependencies]
3842
hex.workspace = true

wasm-wrappers/js-bindings-test/tests/test_transaction_and_witness_encoding.ts

Lines changed: 112 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,16 @@ import {
2121
estimate_transaction_size,
2222
make_default_account_privkey,
2323
make_receiving_address,
24+
decode_signed_transaction_to_js,
2425
Network,
2526
SignatureHashType,
2627
} from "../../pkg/wasm_wrappers.js";
2728

28-
import {
29-
assert_eq_arrays,
30-
get_err_msg,
31-
TEXT_ENCODER,
32-
} from "./utils.js";
29+
import { assert_eq_arrays, get_err_msg, TEXT_ENCODER } from "./utils.js";
3330

34-
import {
35-
MNEMONIC,
36-
RANDOM_HEIGHT,
37-
} from "./defs.js";
38-
import {
39-
ADDRESS
40-
} from "./test_address_generation.js";
41-
import {
42-
INPUTS,
43-
} from "./test_encode_other_inputs.js";
31+
import { MNEMONIC, RANDOM_HEIGHT } from "./defs.js";
32+
import { ADDRESS } from "./test_address_generation.js";
33+
import { INPUTS } from "./test_encode_other_inputs.js";
4434
import {
4535
OUTPUTS,
4636
OUTPUT_CREATE_STAKE_POOL,
@@ -50,7 +40,7 @@ import {
5040
export function test_transaction_and_witness_encoding() {
5141
const account_pubkey = make_default_account_privkey(
5242
MNEMONIC,
53-
Network.Testnet
43+
Network.Testnet,
5444
);
5545
const receiving_privkey = make_receiving_address(account_pubkey, 0);
5646

@@ -76,19 +66,23 @@ export function test_transaction_and_witness_encoding() {
7666
console.log("Tested invalid outputs successfully");
7767
}
7868

79-
const tx = encode_transaction(Uint8Array.from(INPUTS), Uint8Array.from(OUTPUTS), BigInt(0));
69+
const tx = encode_transaction(
70+
Uint8Array.from(INPUTS),
71+
Uint8Array.from(OUTPUTS),
72+
BigInt(0),
73+
);
8074
const expected_tx = [
8175
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,
8276
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,
8377
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,
8478
0, 4, 8, 1, 0, 145, 1, 1, 91, 58, 110, 176, 100, 207, 6, 194, 41, 193, 30,
85-
91, 4, 195, 202, 103, 207, 80, 217, 178, 0, 145, 1, 3, 0, 0, 0, 0, 0, 0,
79+
91, 4, 195, 202, 103, 207, 80, 217, 178, 0, 145, 1, 3, 0, 0, 0, 0, 0, 0, 0,
8680
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,
87-
0, 2, 113, 2, 0, 1, 91, 58, 110, 176, 100, 207, 6, 194, 41, 193, 30, 91,
88-
4, 195, 202, 103, 207, 80, 217, 178, 0, 108, 245, 234, 97, 170, 9, 247,
89-
158, 169, 100, 84, 123, 235, 183, 147, 29, 136, 118, 203, 24, 146, 56, 60,
90-
217, 2, 198, 32, 133, 255, 240, 84, 123, 1, 91, 58, 110, 176, 100, 207, 6,
91-
194, 41, 193, 30, 91, 4, 195, 202, 103, 207, 80, 217, 178, 100, 0, 0,
81+
2, 113, 2, 0, 1, 91, 58, 110, 176, 100, 207, 6, 194, 41, 193, 30, 91, 4,
82+
195, 202, 103, 207, 80, 217, 178, 0, 108, 245, 234, 97, 170, 9, 247, 158,
83+
169, 100, 84, 123, 235, 183, 147, 29, 136, 118, 203, 24, 146, 56, 60, 217,
84+
2, 198, 32, 133, 255, 240, 84, 123, 1, 91, 58, 110, 176, 100, 207, 6, 194,
85+
41, 193, 30, 91, 4, 195, 202, 103, 207, 80, 217, 178, 100, 0, 0,
9286
];
9387
assert_eq_arrays(tx, expected_tx);
9488
console.log("tx encoding ok");
@@ -98,7 +92,12 @@ export function test_transaction_and_witness_encoding() {
9892
assert_eq_arrays(witness, expected_no_signature_witness);
9993
console.log("empty witness encoding ok");
10094

101-
const opt_utxos = [1, ...OUTPUT_LOCK_THEN_TRANSFER, 1, ...OUTPUT_CREATE_STAKE_POOL];
95+
const opt_utxos = [
96+
1,
97+
...OUTPUT_LOCK_THEN_TRANSFER,
98+
1,
99+
...OUTPUT_CREATE_STAKE_POOL,
100+
];
102101

103102
try {
104103
const invalid_private_key = TEXT_ENCODER.encode("invalid private key");
@@ -111,7 +110,7 @@ export function test_transaction_and_witness_encoding() {
111110
0,
112111
{ pool_info: {}, order_info: {} },
113112
BigInt(RANDOM_HEIGHT),
114-
Network.Testnet
113+
Network.Testnet,
115114
);
116115
throw new Error("Invalid private key worked somehow!");
117116
} catch (e) {
@@ -131,7 +130,7 @@ export function test_transaction_and_witness_encoding() {
131130
0,
132131
{ pool_info: {}, order_info: {} },
133132
BigInt(RANDOM_HEIGHT),
134-
Network.Testnet
133+
Network.Testnet,
135134
);
136135
throw new Error("Invalid address worked somehow!");
137136
} catch (e) {
@@ -151,7 +150,7 @@ export function test_transaction_and_witness_encoding() {
151150
0,
152151
{ pool_info: {}, order_info: {} },
153152
BigInt(RANDOM_HEIGHT),
154-
Network.Testnet
153+
Network.Testnet,
155154
);
156155
throw new Error("Invalid transaction worked somehow!");
157156
} catch (e) {
@@ -171,7 +170,7 @@ export function test_transaction_and_witness_encoding() {
171170
0,
172171
{ pool_info: {}, order_info: {} },
173172
BigInt(RANDOM_HEIGHT),
174-
Network.Testnet
173+
Network.Testnet,
175174
);
176175
throw new Error("Invalid utxo worked somehow!");
177176
} catch (e) {
@@ -191,11 +190,15 @@ export function test_transaction_and_witness_encoding() {
191190
0,
192191
{ pool_info: {}, order_info: {} },
193192
BigInt(RANDOM_HEIGHT),
194-
Network.Testnet
193+
Network.Testnet,
195194
);
196195
throw new Error("Invalid utxo worked somehow!");
197196
} catch (e) {
198-
if (!get_err_msg(e).includes("Error creating sighash input commitments: Utxo not found")) {
197+
if (
198+
!get_err_msg(e).includes(
199+
"Error creating sighash input commitments: Utxo not found",
200+
)
201+
) {
199202
throw e;
200203
}
201204
console.log("Tested invalid utxo count in encode witness successfully");
@@ -211,7 +214,7 @@ export function test_transaction_and_witness_encoding() {
211214
invalid_input_idx,
212215
{ pool_info: {}, order_info: {} },
213216
BigInt(RANDOM_HEIGHT),
214-
Network.Testnet
217+
Network.Testnet,
215218
);
216219
throw new Error("Invalid address worked somehow!");
217220
} catch (e) {
@@ -230,18 +233,18 @@ export function test_transaction_and_witness_encoding() {
230233
0,
231234
{ pool_info: {}, order_info: {} },
232235
BigInt(RANDOM_HEIGHT),
233-
Network.Testnet
236+
Network.Testnet,
234237
);
235238

236239
// as signatures are random, hardcode one so we can test the encodings for the signed transaction
237240
const random_witness2 = [
238241
1, 1, 141, 1, 0, 2, 227, 252, 33, 195, 223, 44, 38, 35, 73, 145, 212, 180,
239-
49, 115, 4, 150, 204, 250, 205, 123, 131, 201, 114, 130, 186, 209, 98,
240-
181, 118, 233, 133, 89, 0, 99, 87, 109, 227, 15, 21, 164, 83, 151, 14,
241-
235, 106, 83, 230, 40, 64, 146, 112, 52, 103, 203, 31, 216, 54, 141, 223,
242-
27, 175, 133, 164, 172, 239, 122, 121, 17, 88, 114, 99, 6, 19, 220, 156,
243-
167, 40, 17, 211, 196, 45, 209, 111, 170, 161, 2, 254, 122, 169, 127, 235,
244-
158, 62, 127, 177, 12, 228,
242+
49, 115, 4, 150, 204, 250, 205, 123, 131, 201, 114, 130, 186, 209, 98, 181,
243+
118, 233, 133, 89, 0, 99, 87, 109, 227, 15, 21, 164, 83, 151, 14, 235, 106,
244+
83, 230, 40, 64, 146, 112, 52, 103, 203, 31, 216, 54, 141, 223, 27, 175,
245+
133, 164, 172, 239, 122, 121, 17, 88, 114, 99, 6, 19, 220, 156, 167, 40, 17,
246+
211, 196, 45, 209, 111, 170, 161, 2, 254, 122, 169, 127, 235, 158, 62, 127,
247+
177, 12, 228,
245248
];
246249

247250
try {
@@ -261,7 +264,7 @@ export function test_transaction_and_witness_encoding() {
261264
} catch (e) {
262265
if (
263266
!get_err_msg(e).includes(
264-
"The number of signatures does not match the number of inputs"
267+
"The number of signatures does not match the number of inputs",
265268
)
266269
) {
267270
throw e;
@@ -287,39 +290,91 @@ export function test_transaction_and_witness_encoding() {
287290
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,
288291
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,
289292
0, 4, 8, 1, 0, 145, 1, 1, 91, 58, 110, 176, 100, 207, 6, 194, 41, 193, 30,
290-
91, 4, 195, 202, 103, 207, 80, 217, 178, 0, 145, 1, 3, 0, 0, 0, 0, 0, 0,
293+
91, 4, 195, 202, 103, 207, 80, 217, 178, 0, 145, 1, 3, 0, 0, 0, 0, 0, 0, 0,
291294
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,
292-
0, 2, 113, 2, 0, 1, 91, 58, 110, 176, 100, 207, 6, 194, 41, 193, 30, 91,
293-
4, 195, 202, 103, 207, 80, 217, 178, 0, 108, 245, 234, 97, 170, 9, 247,
294-
158, 169, 100, 84, 123, 235, 183, 147, 29, 136, 118, 203, 24, 146, 56, 60,
295-
217, 2, 198, 32, 133, 255, 240, 84, 123, 1, 91, 58, 110, 176, 100, 207, 6,
296-
194, 41, 193, 30, 91, 4, 195, 202, 103, 207, 80, 217, 178, 100, 0, 0, 8,
297-
1, 1, 141, 1, 0, 2, 227, 252, 33, 195, 223, 44, 38, 35, 73, 145, 212, 180,
298-
49, 115, 4, 150, 204, 250, 205, 123, 131, 201, 114, 130, 186, 209, 98,
299-
181, 118, 233, 133, 89, 0, 99, 87, 109, 227, 15, 21, 164, 83, 151, 14,
295+
2, 113, 2, 0, 1, 91, 58, 110, 176, 100, 207, 6, 194, 41, 193, 30, 91, 4,
296+
195, 202, 103, 207, 80, 217, 178, 0, 108, 245, 234, 97, 170, 9, 247, 158,
297+
169, 100, 84, 123, 235, 183, 147, 29, 136, 118, 203, 24, 146, 56, 60, 217,
298+
2, 198, 32, 133, 255, 240, 84, 123, 1, 91, 58, 110, 176, 100, 207, 6, 194,
299+
41, 193, 30, 91, 4, 195, 202, 103, 207, 80, 217, 178, 100, 0, 0, 8, 1, 1,
300+
141, 1, 0, 2, 227, 252, 33, 195, 223, 44, 38, 35, 73, 145, 212, 180, 49,
301+
115, 4, 150, 204, 250, 205, 123, 131, 201, 114, 130, 186, 209, 98, 181, 118,
302+
233, 133, 89, 0, 99, 87, 109, 227, 15, 21, 164, 83, 151, 14, 235, 106, 83,
303+
230, 40, 64, 146, 112, 52, 103, 203, 31, 216, 54, 141, 223, 27, 175, 133,
304+
164, 172, 239, 122, 121, 17, 88, 114, 99, 6, 19, 220, 156, 167, 40, 17, 211,
305+
196, 45, 209, 111, 170, 161, 2, 254, 122, 169, 127, 235, 158, 62, 127, 177,
306+
12, 228, 1, 1, 141, 1, 0, 2, 227, 252, 33, 195, 223, 44, 38, 35, 73, 145,
307+
212, 180, 49, 115, 4, 150, 204, 250, 205, 123, 131, 201, 114, 130, 186, 209,
308+
98, 181, 118, 233, 133, 89, 0, 99, 87, 109, 227, 15, 21, 164, 83, 151, 14,
300309
235, 106, 83, 230, 40, 64, 146, 112, 52, 103, 203, 31, 216, 54, 141, 223,
301310
27, 175, 133, 164, 172, 239, 122, 121, 17, 88, 114, 99, 6, 19, 220, 156,
302311
167, 40, 17, 211, 196, 45, 209, 111, 170, 161, 2, 254, 122, 169, 127, 235,
303-
158, 62, 127, 177, 12, 228, 1, 1, 141, 1, 0, 2, 227, 252, 33, 195, 223,
304-
44, 38, 35, 73, 145, 212, 180, 49, 115, 4, 150, 204, 250, 205, 123, 131,
305-
201, 114, 130, 186, 209, 98, 181, 118, 233, 133, 89, 0, 99, 87, 109, 227,
306-
15, 21, 164, 83, 151, 14, 235, 106, 83, 230, 40, 64, 146, 112, 52, 103,
307-
203, 31, 216, 54, 141, 223, 27, 175, 133, 164, 172, 239, 122, 121, 17, 88,
308-
114, 99, 6, 19, 220, 156, 167, 40, 17, 211, 196, 45, 209, 111, 170, 161,
309-
2, 254, 122, 169, 127, 235, 158, 62, 127, 177, 12, 228,
312+
158, 62, 127, 177, 12, 228,
310313
];
311314
assert_eq_arrays(signed_tx, expected_signed_tx);
312315

313316
const estimated_size = estimate_transaction_size(
314317
Uint8Array.from(INPUTS),
315318
[ADDRESS, ADDRESS],
316319
Uint8Array.from(OUTPUTS),
317-
Network.Testnet
320+
Network.Testnet,
318321
);
319322
if (estimated_size != expected_signed_tx.length) {
320323
throw new Error("wrong estimated size");
321324
}
322325
console.log(
323-
`estimated size ${estimated_size} vs real ${expected_signed_tx.length}`
326+
`estimated size ${estimated_size} vs real ${expected_signed_tx.length}`,
327+
);
328+
329+
let decoded_tx = decode_signed_transaction_to_js(signed_tx);
330+
console.log("decoded tx format:");
331+
console.dir(decoded_tx, { depth: null });
332+
333+
// top-level check
334+
if (!decoded_tx.V1) {
335+
throw new Error("Missing V1 field in decoded transaction");
336+
}
337+
338+
if (decoded_tx.V1.version !== 1) {
339+
throw new Error(`Unexpected version: ${decoded_tx.V1.version}`);
340+
}
341+
342+
if (!Array.isArray(decoded_tx.V1.inputs)) {
343+
throw new Error("Inputs is not an array");
344+
}
345+
346+
if (!Array.isArray(decoded_tx.V1.outputs)) {
347+
throw new Error("Outputs is not an array");
348+
}
349+
350+
// specific input check
351+
const firstInput = decoded_tx.V1.inputs[0];
352+
if (!firstInput.Utxo) {
353+
throw new Error("First input is not Utxo");
354+
}
355+
if (firstInput.Utxo.index !== 1) {
356+
throw new Error(`Unexpected Utxo index: ${firstInput.Utxo.index}`);
357+
}
358+
359+
// structural checks
360+
if (decoded_tx.V1.inputs.length === 0) {
361+
throw new Error("No inputs found");
362+
}
363+
if (decoded_tx.V1.outputs.length === 0) {
364+
throw new Error("No outputs found");
365+
}
366+
367+
// example output check
368+
const lockTransfer = decoded_tx.V1.outputs.find(
369+
(o: any) => o.LockThenTransfer,
324370
);
371+
if (!lockTransfer) {
372+
throw new Error("Missing LockThenTransfer output");
373+
}
374+
const coin = lockTransfer.LockThenTransfer[0];
375+
if (!coin.Coin || coin.Coin.atoms !== "100") {
376+
throw new Error(
377+
`Unexpected LockThenTransfer coin: ${JSON.stringify(coin)}`,
378+
);
379+
}
325380
}

wasm-wrappers/src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ pub enum Error {
8181
#[error("Invalid signed transaction intent encoding: {0}")]
8282
InvalidSignedTransactionIntentEncoding(serialization::Error),
8383

84+
#[error("Invalid signed transaction encoding: {0}")]
85+
InvalidSignedTransactionEncoding(serialization::Error),
86+
8487
#[error("Error creating multisig spend: {0}")]
8588
MultisigSpendCreationError(DestinationSigError),
8689

wasm-wrappers/src/lib.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
3232
use std::{num::NonZeroU8, str::FromStr};
3333

34+
use wasm_bindgen::JsValue;
35+
3436
use bip39::Language;
3537
use itertools::Itertools as _;
3638
use wasm_bindgen::prelude::*;
@@ -689,6 +691,16 @@ pub fn encode_transaction(inputs: &[u8], outputs: &[u8], flags: u64) -> Result<V
689691
Ok(tx.encode())
690692
}
691693

694+
/// Decodes a signed transaction from its binary encoding into a JavaScript object.
695+
#[wasm_bindgen]
696+
pub fn decode_signed_transaction_to_js(transaction: &[u8]) -> Result<JsValue, Error> {
697+
let tx = SignedTransaction::decode_all(&mut &transaction[..])
698+
.map_err(Error::InvalidSignedTransactionEncoding)?;
699+
let value =
700+
serde_wasm_bindgen::to_value(tx.transaction()).expect("Should not fail to create js value");
701+
Ok(value)
702+
}
703+
692704
/// Encode an input witness of the variant that contains no signature.
693705
#[wasm_bindgen]
694706
pub fn encode_witness_no_signature() -> Vec<u8> {

0 commit comments

Comments
 (0)