From 0511b65899d2c882bb95f6cf4725210b6bcf8f7a Mon Sep 17 00:00:00 2001 From: danield9tqh Date: Fri, 8 Nov 2024 12:58:24 -0500 Subject: [PATCH 01/68] Exporting mutisig from SDK --- ironfish/src/wallet/index.ts | 1 + ironfish/src/wallet/multisig.ts | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ironfish/src/wallet/index.ts b/ironfish/src/wallet/index.ts index e9a61f7f56..63a341cf2e 100644 --- a/ironfish/src/wallet/index.ts +++ b/ironfish/src/wallet/index.ts @@ -9,3 +9,4 @@ export { Base64JsonEncoder } from './exporter/encoders/base64json' export { JsonEncoder } from './exporter/encoders/json' export * from './validator' export * from './walletdb/walletdb' +export * from './multisig' diff --git a/ironfish/src/wallet/multisig.ts b/ironfish/src/wallet/multisig.ts index 9d4d14c39b..78e9e86003 100644 --- a/ironfish/src/wallet/multisig.ts +++ b/ironfish/src/wallet/multisig.ts @@ -4,6 +4,4 @@ import { multisig } from '@ironfish/rust-nodejs' -const Multisig = multisig - -export default Multisig +export const Multisig = multisig From ca1f5c84c6bea5d26991481f8c597a00306448b6 Mon Sep 17 00:00:00 2001 From: danield9tqh Date: Fri, 8 Nov 2024 13:08:58 -0500 Subject: [PATCH 02/68] remove old export --- ironfish/src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/ironfish/src/index.ts b/ironfish/src/index.ts index 0dd1a6a20d..a8da92f77e 100644 --- a/ironfish/src/index.ts +++ b/ironfish/src/index.ts @@ -27,6 +27,5 @@ export * from './network' export * from './package' export * from './platform' export * from './primitives' -export * from './wallet/multisig' export { getFeeRate } from './memPool' export * as devUtils from './devUtils' From 965e008fe474de3a9a69d77ae5f9029826b268a2 Mon Sep 17 00:00:00 2001 From: andrea Date: Mon, 11 Nov 2024 15:16:55 -0800 Subject: [PATCH 03/68] Add a barebone WASM crate --- Cargo.lock | 36 +++++++++++++++++++++----------- Cargo.toml | 1 + ironfish-rust-wasm/Cargo.toml | 19 +++++++++++++++++ ironfish-rust-wasm/src/errors.rs | 26 +++++++++++++++++++++++ ironfish-rust-wasm/src/lib.rs | 12 +++++++++++ supply-chain/audits.toml | 30 ++++++++++++++++++++++++++ supply-chain/config.toml | 4 ---- supply-chain/imports.lock | 24 +++++++++++++++++++++ 8 files changed, 136 insertions(+), 16 deletions(-) create mode 100644 ironfish-rust-wasm/Cargo.toml create mode 100644 ironfish-rust-wasm/src/errors.rs create mode 100644 ironfish-rust-wasm/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index e80aef1b43..43d672289d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1150,8 +1150,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -1628,6 +1630,15 @@ dependencies = [ "signal-hook", ] +[[package]] +name = "ironfish-wasm" +version = "0.1.0" +dependencies = [ + "getrandom", + "ironfish", + "wasm-bindgen", +] + [[package]] name = "ironfish_zkp" version = "0.2.0" @@ -3053,26 +3064,27 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.77", "wasm-bindgen-shared", ] @@ -3090,9 +3102,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3100,22 +3112,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.77", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "web-sys" diff --git a/Cargo.toml b/Cargo.toml index 955ccc7d88..68ba04e0c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "benchmarks", "ironfish-rust", "ironfish-rust-nodejs", + "ironfish-rust-wasm", "ironfish-zkp", ] diff --git a/ironfish-rust-wasm/Cargo.toml b/ironfish-rust-wasm/Cargo.toml new file mode 100644 index 0000000000..cd6a9ac545 --- /dev/null +++ b/ironfish-rust-wasm/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ironfish-wasm" +version = "0.1.0" +license = "MPL-2.0" + +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true + +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +getrandom = { version = "0.2.8", features = ["js"] } # need to explicitly enable the `js` feature in order to run in a browser +ironfish = { version = "0.3.0", path = "../ironfish-rust" } +wasm-bindgen = "0.2.95" diff --git a/ironfish-rust-wasm/src/errors.rs b/ironfish-rust-wasm/src/errors.rs new file mode 100644 index 0000000000..62142de8f1 --- /dev/null +++ b/ironfish-rust-wasm/src/errors.rs @@ -0,0 +1,26 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +#[derive(Debug)] +pub struct IronfishError(ironfish::errors::IronfishError); + +impl From for IronfishError +where + ironfish::errors::IronfishError: From, +{ + fn from(e: T) -> Self { + Self(ironfish::errors::IronfishError::from(e)) + } +} + +impl AsRef for IronfishError { + fn as_ref(&self) -> &ironfish::errors::IronfishError { + &self.0 + } +} + +impl AsRef for IronfishError { + fn as_ref(&self) -> &ironfish::errors::IronfishErrorKind { + &self.0.kind + } +} diff --git a/ironfish-rust-wasm/src/lib.rs b/ironfish-rust-wasm/src/lib.rs new file mode 100644 index 0000000000..2ef579a640 --- /dev/null +++ b/ironfish-rust-wasm/src/lib.rs @@ -0,0 +1,12 @@ +#![warn(clippy::dbg_macro)] +#![warn(clippy::print_stderr)] +#![warn(clippy::print_stdout)] +#![warn(unreachable_pub)] +#![warn(unused_crate_dependencies)] +#![warn(unused_macro_rules)] +#![warn(unused_qualifications)] + +// The getrandom dependency exists only to ensure that the `js` feature is enabled +use getrandom as _; + +pub mod errors; diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index b6ca798d9f..cbe25b4615 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -108,6 +108,36 @@ who = "Andrea " criteria = "safe-to-deploy" version = "1.0.0" +[[audits.wasm-bindgen]] +who = "andrea " +criteria = "safe-to-deploy" +delta = "0.2.84 -> 0.2.95" +notes = "New features and removed code; some new unsafe blocks and they all look benign" + +[[audits.wasm-bindgen-backend]] +who = "andrea " +criteria = "safe-to-deploy" +delta = "0.2.84 -> 0.2.95" +notes = "New features and bug fixes; there's are new/changed unsafe blocks and they all look benign" + +[[audits.wasm-bindgen-macro]] +who = "andrea " +criteria = "safe-to-deploy" +delta = "0.2.84 -> 0.2.95" +notes = "Minor changes and a lot of removed code" + +[[audits.wasm-bindgen-macro-support]] +who = "andrea " +criteria = "safe-to-deploy" +delta = "0.2.92 -> 0.2.95" +notes = "New features and bug fixes; no new unsafe code or system calls introduced" + +[[audits.wasm-bindgen-shared]] +who = "andrea " +criteria = "safe-to-deploy" +delta = "0.2.92 -> 0.2.95" +notes = "Only minor changes; no new unsafe code or system calls introduced" + [[trusted.reddsa]] criteria = "safe-to-deploy" user-id = 6289 # Jack Grigg (str4d) diff --git a/supply-chain/config.toml b/supply-chain/config.toml index fce27b73e8..a88e7ac87a 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -776,10 +776,6 @@ criteria = "safe-to-deploy" version = "0.2.84" criteria = "safe-to-deploy" -[[exemptions.wasm-bindgen-macro-support]] -version = "0.2.84" -criteria = "safe-to-deploy" - [[exemptions.web-sys]] version = "0.3.61" criteria = "safe-to-deploy" diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index 06a5fee8c1..ee33b788d6 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -1297,9 +1297,33 @@ delta = "0.4.1 -> 0.5.0" notes = "I checked correctness of to_blocks which uses unsafe code in a safe function." aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" +[[audits.zcash.audits.wasm-bindgen-macro-support]] +who = "Daira-Emma Hopwood " +criteria = "safe-to-deploy" +version = "0.2.92" +aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" + [[audits.zcash.audits.wasm-bindgen-shared]] who = "Jack Grigg " criteria = "safe-to-deploy" delta = "0.2.83 -> 0.2.84" notes = "Bumps the schema version to add `linked_modules`." aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" + +[[audits.zcash.audits.wasm-bindgen-shared]] +who = "Jack Grigg " +criteria = "safe-to-deploy" +delta = "0.2.84 -> 0.2.87" +aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" + +[[audits.zcash.audits.wasm-bindgen-shared]] +who = "Jack Grigg " +criteria = "safe-to-deploy" +delta = "0.2.87 -> 0.2.89" +aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" + +[[audits.zcash.audits.wasm-bindgen-shared]] +who = "Daira-Emma Hopwood " +criteria = "safe-to-deploy" +delta = "0.2.89 -> 0.2.92" +aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" From 3d3e14ee81fec673907bf5d6bcac0409f0964198 Mon Sep 17 00:00:00 2001 From: danield9tqh Date: Tue, 12 Nov 2024 13:14:55 -0500 Subject: [PATCH 04/68] Fix fixtures for wallet scanner --- .../walletScanner.test.ts.fixture | 817 +++++++++--------- .../src/wallet/scanner/walletScanner.test.ts | 4 +- 2 files changed, 418 insertions(+), 403 deletions(-) diff --git a/ironfish/src/wallet/scanner/__fixtures__/walletScanner.test.ts.fixture b/ironfish/src/wallet/scanner/__fixtures__/walletScanner.test.ts.fixture index e4e86e3d9a..996e1d26f0 100644 --- a/ironfish/src/wallet/scanner/__fixtures__/walletScanner.test.ts.fixture +++ b/ironfish/src/wallet/scanner/__fixtures__/walletScanner.test.ts.fixture @@ -2,23 +2,24 @@ "WalletScanner adds transactions to the wallet db with decrypted notes": [ { "value": { + "encrypted": false, "version": 4, - "id": "d1cf4a73-26ba-463e-84dd-db70931caaef", + "id": "d43dd52e-76af-41bb-8ce1-57c0761d197f", "name": "a", - "spendingKey": "dec96500a1803c548afc31244b1c3c2af1dd2e656a6f5cce4eae23b40b8f245a", - "viewKey": "c2b12faf677f79b93f770adb4ffc7c2e3cba20a277645a676a58724782ffe861d1d88fcd45ed80e5e16a1320ff875250eb0f614a34d368db9b7f2b59efa5f93b", - "incomingViewKey": "d2d98c9c0cfb2051ea4dea5948bdfa35a18db6b244706a48fec8fd9c0eab4e05", - "outgoingViewKey": "ebc96689717ae1fb810465e5b827b03c849b4e17c9caa336190159db3c212b05", - "publicAddress": "cf2deda03dfb466f832c25aee99f6aae5e65d87ccf89120c2168eb9a15e39000", + "spendingKey": "78bcd9b40f905ad3824c73d61f2dc8853daf7b638e0da937db652ebfe82a9e67", + "viewKey": "3d7e33591ba4bfe2f361090c10e694e90bfbc44119b23347efdbb342ab913c666155abf8bcdcf65981ac28169a4f6fc0900f636fc7ed8fb4555ca8929378625f", + "incomingViewKey": "53cc95af981836053963cfb72588a26ec3e1d80ba5c53db1aa56e73aeb1afe01", + "outgoingViewKey": "8c381fbae9cad131c1d1582ba74f955d4b1e7011d453ef0eee01d5eb9c0211d5", + "publicAddress": "5e4df39b131301dc5d212ece23f25abb58525ca39bb09751a51818518239312f", "createdAt": { + "sequence": 1, "hash": { "type": "Buffer", - "data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" - }, - "sequence": 1 + "data": "base64:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + } }, "scanningEnabled": true, - "proofAuthorizingKey": "d8e8073254aae15fe24704d590fa37ca0c396026ef5bdb2c6012bf044d5d5408" + "proofAuthorizingKey": "2465e715c919d019eb7425ba89db1799e07d4b16044fe31a2ac530216638aa08" }, "head": { "hash": { @@ -34,15 +35,15 @@ "previousBlockHash": "4791D7AE9F97DF100EF1558E84772D6A09B43762388283F75C6F20A32A88AA86", "noteCommitment": { "type": "Buffer", - "data": "base64:itEBRkuh+NpQRbD6hs9/4O9Vv7mfl2/pQ8ntJJLn7Qw=" + "data": "base64:ZmlG68PcwvIGDHBW2kD7J68g1g6bh7gMy5pygwKWnCk=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:il3l3FuDvwOhXk7L4snmAUPdAMnJZYQ6UotnfP178Fc=" + "data": "base64:2XSOnHPYivAGtau6cKEB1Hx9LjQE3Dk1BKgaBXC6NSg=" }, "target": "9282972777491357380673661573939192202192629606981189395159182914949423", "randomness": "0", - "timestamp": 1719441703087, + "timestamp": 1731435258181, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 4, "work": "0" @@ -50,25 +51,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAWnEuHSSveUq4ZoQoRPTTrnZ/6EFMa5OBfv9Fzk2gsDaRTm15GIdSlbIVvScNfI5gCG/SelzULkElM04+cnrE1j594IpBErw7xWf2mxkNOsKZ48P0tYyyTEPlXp98g/C9EAY/4pPIEC+eOuCzboW6SvPJMzecOgYBuDhpMhaG//oRDuNHvYug7JoFbJH6wObIWhJM6oixok3tsFKlT8Qs1CJd9ZBrtfKwZ6TpEC/0gbGY5Dmy1nmgVG03ge/WzGTRPuYrPHVbxR2qD9PXwrJhKrs9x6aGbwG9uAPKounEJ5wxuEhw5MTscaUknt38YpdhcKLgcAniLt3Jw/QWeKeiCvkOTLSBNf/dSDw79YFEvFKfzi6dRV7v+myr+M297MIRzmcUX11BzPP/2IHji37MC3OWJf7Xrj7qu2OS1qU9+MnPOdKRIb+YCrrTu22xR0ya3dpryD0/5IuHYDFTQAMYqBXDGBK2ZHhsz8TV29T9Li2ggPKSqIN5M3zD3wG5Sw4JLtl2/g4ulpCpskMwGGd+ft2Q0bMTC5rko60WNf34aSJHCSl415N6yJK4vJFQK2e+qGsbrcLTb8UGgIaWIOssGAyH0z89DMthYiP8O80uoq6nvoS8rpLhiUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwYRGel8eDPjscFom3Ss+KyCvF2mZ6ITTaAE3CaNd/pQsH7kbKfzN3/4PetOD614d/IyZar3jqwioeDgp2o97VCQ==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAIvQuDBv50+iphcBbw8gLwvVqfSI6B2Jye2t5HnZ6hNCkBKgE/2PXJxTsPGH5M/fjJYFiCHFOd1/GAeMTLw4O8OkMZ23Ei+efsx8Ft0KaJXW5XxQX/QY/lqJ2BtG6hDQuHStOh0/7xlC4d36b84ek0PNiRNshjhm0h4S3xZuj2rgIjip5GBYo9XGDjEVOdh8LWhK0DjhJ72E2Mod+H+j+0OuT50tSSN8EYxHIfQhZHLKCEkmGF6INZm23sKMHFfjXDs8bnNv4lJ05LW0DPSR06xu4m2VZ9w0+IeyMyfsL/s7qUIGKDeXA2V5b+gDJxElXXIeJpmd9yztH8oKN6CJjjiux51D5gwiQ2gixCdxAo5Px+grn60HsGNWikuzr8mNFX+u3f5PF28byDZ+cGBctwgBRkMDLP2IUafoD6p6WgcyjRxHDRGVEvvaVAL641DGP7DPpYE1TUXnZc520jcdTkjkfex+fqBwgnnQRLrhmeiwwrlZeL5Mc5B12A6l34dbhh/0b7H/VI/4Euf21e+TiuM42/yz+j2z36B5okVg64lKzbcYP4t5FjjjyBsfjxbcTtHnG9pDfH5B4g/Hxy/eP1E2TQmdpHEZHDYjJaOKKmLednhtrAaKZWUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwPDgbbF4FkLZ7KqGEdIHXnMHPSCqVQKwpHHfSnQsbatrS53VAG6+NfIxoni01slRZwQZkpC8de0p9W+UzNIM8CQ==" } ] }, { "header": { "sequence": 3, - "previousBlockHash": "BD771382B6F51DBAD16BBF20828E397304360B8F4C41EA4AAE4F899DCA091348", + "previousBlockHash": "E2BFBA31C5B756EF69B6E3E1188675F35C7B51B48182029E380AA11ECF55B6D5", "noteCommitment": { "type": "Buffer", - "data": "base64:GI9hJ67TDZtvgeaDQWMz5v8BU3x4KLOebmx6LX3IDBk=" + "data": "base64:zlapttzO0t0Ap9yYsNQqQ1lGTdbT2jFsmBvL+q3xMjA=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:apcufvG/5MYo2BuJxXgf/0XKvpyWF1PUUVfMbOT7kB4=" + "data": "base64:7H3VCTcIQ4fokO4MNjRl3P0AyRwlZ+RclRI566xi/M4=" }, "target": "9255858786337818395603165512831024101510453493377417362192396248796027", "randomness": "0", - "timestamp": 1719441703553, + "timestamp": 1731435258479, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 5, "work": "0" @@ -76,25 +77,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAev/XVrZBMN/+tj4cnb5nDkkOKHeMS1Oo/udw+KFj8MSjevA2febVCbQWbtdVF622fZilPU6egNmyuBKSRHrqFCjB5dDFINgwyBP5NBqmsYeIrfnb/2K4YkHN6aWl7D+PH/p1n2DsVyyX6LyQtinl7inmIXtUu5G5zHKTJ/3aljoMSaXrtUuvyeZhXiT2vNEZ/aju1zsFZU5WFryRBrPoxdE23WjhNUfz4xHwxPdLBfivYp1O2jdNUlG6INpxNWxxy+s2pH3ct7Eby6eBHrEzDR7npfVjQnpWdMFbFnTQ29aGAxg4fsGM+H6QdCvvUg1QkUwnPfNC2UwnCg8QlhX3y0b/PdkiF/0hDetDv2bMlBWU9fwnfzftnUlX76pA6UFbJp6IdKhRM8Yu+Pbs5JkdO1lXDusx4Czv1kzhWqS9psGjHMboT/pCtLhZaeMmBI1LTaHSgpfq/PhcdQ7I2wzLmnztZQz1eRbf2uKmycLqiWUt0zvqt8ODu74BWzMVD21Yj3BM9CthY32ikWAUzUnI+AAIf1+eeUD/rxcI583dl5cmGhN/fYP2OlwF1rZBk7soFrOT5bvma+w3+RoqhjzJP6lT5UHwrY9vRSd3BipYWvytSdUVcI+X2Ulyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwK+0oMAXwcI6ruO8mM0tz6ivDuImpFkTRswicw6hYfgtKM1uycguZow8m4QYbnrU1xyRBWrqAbeHelR9cJfedBQ==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAI2EUnLJ7JWD+RNIJ4LzZ4kQr6zNbC8oIBc9bAi2sSC6QJ8kkV2sXLQ0b4OT7QS5gYos8o6xQwz2BofNVZmGwMgEHY/pvtvCHAtG3HQVUt8CMwq+M6VYdBXDEwP/jYCLnov9eaDxQnNCP2L8Adj/eHgjS165H8bZoT7vYl0P1oKcUX0kvbklEoX7mDrC3EdUSg21XvyxtEaavEIhQ6akpXgq0FR1XyxPdGKqtBgyZqf+xXDJT3JSzzKYfTOWppK7+7pBDQPt0og9bcZBUFTsVNQpg1B1Zn2EDDokNyhLh3Gb+HvITjaj0ZH8TDiubKEqyGU7bKbF9WzYcGRzZa62WI4y5VFwxjGqq4QSiORisv4GzoJFW/RPIuH0gmf1oK20Z6QZG83/fd1pVrd7gMB7npfSjNZxr5/MneU1IulvPZXN1WsTYCZHP4+0QCtgpNoI/Ha01E1H/nJt5xyZigZ/TAQ+BZyUdkfBFIghHbeEjO9jXAP19fyJY7E3ee7mwNLyRY18i4Bq57X4Gx2zqU6C0YnOjbRqwS5xH+wHmhkIa7Q1VJcC2U4hYpkxWhjazzUjCwk36TsojfUrnqTBWjLBr6Y452fRZBWK58pItuKYq2mrNttHO23HBGUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw6MEJKRHRApmHKfwORk2CYOQNoDxLrxeTdCXVhExM846VrRuf+nq2xbYtqxupm+yffONAhy3ayvyPrLtmoOBrCw==" } ] }, { "header": { "sequence": 4, - "previousBlockHash": "C3C3D2CA169A13FEDEECC66178A2A186F405B60CC5B7E66C795464614A752CAF", + "previousBlockHash": "45CE4F39E6B30B35C92AFABC4E3A103DD0661994E532473249EA60C96A098081", "noteCommitment": { "type": "Buffer", - "data": "base64:zWgjVHbnPw8JQmC9NL5vMtrb9ReTUjqCBvHUS9DBFEU=" + "data": "base64:Q/jM03KrzjlgDZlK4T/kwvXhUbWWJ1lW+7vTxjt3wkk=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:X8E0ZOL8NQZcDOubQBJRvGi7wg4cdEQllw7D1Yivv0I=" + "data": "base64:UcwK0KX+JrfCgthWiWmvIDQz9tMG4D64wpcRoyWEaGs=" }, "target": "9228823284279306817296266184515742822248210830185427859262273659833347", "randomness": "0", - "timestamp": 1719441704027, + "timestamp": 1731435258771, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 6, "work": "0" @@ -102,7 +103,7 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAd/4AdJJdsAtp5NFcJ31d5YC26X8zbPWS3V3AFRedjoyG6S0abi+QVAEL9+R7+dgh3rIVUjfLS93XTdRNZI8HS4fHoCcMs3qy/1DGARr6enW0nSZk/AwN8Hdjs68ykYfzTuxQDMJrlhWn6h/XctyymTjlqIHGA6kuRP7X2lB04F0EPOei4YZGCsry42zXXQUmwxJIPEdZk1DCYmoaVxKKv+0ww1L7Hj7Xs5o69Qt95h2gogpZKKaepMbteFB1Rn3Kfz0xSpkx79A61Q6BrtEDfRbEF/Dp9EDzMyrNPfM4nLiW/MnMLReIiOU8JcnkxcGrQPu4r1mfW3ptu1mVA7CIQxHimKAmHmdt2Y9khCpJQKpyKQ46JqIwD/bxCqcfJ7odhYPzBMrNc8AerIs08aN9odE9A+KTskRo0jchZJuEaaf+2NvgRRA9w2qYQ709d5jhf+gIlWxFghhonu4kKvF8qJcZeyQY4wvzhufeYL46bx7w8L/0FdZDLfXY/POIgm6/3//GSAmx5k16Rgsi3BrkT7wv+BaChtxRuKV3sji86HxlpQOW88Dlid+g5dRj2wsJyfH8QjkbcoIrq4VIMqfX7RqtlRFTsVoBCQx4jOhN0R62umnKztgN2Ulyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwWkIKQufSaYEC5DGaLxzHP5Kx7SDL66mZgY9ZyJnhQ1mhH+V1bMV/aoBrG9mVSkmG/sy8vlXN4WU6jk7Eb7qgDA==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAFZ1lW+jiapgSx6c8rHffSqRI8y44wFWjfj3DWDc7oq+DYz7JjVIMg4n0AGY79+vi1q8SAdpB8cV8lC80QVGWCiPxa22sMN8suV1YWS8i49SRo5ggGmHD7uwMweyU16qjltS/OdzoRFnBIEvkbcMfORJeWfUO89bRmDmrzDae35AAunuY/e9+/2BCneHwsDj8Mopoh30CAP2UJ1P4j4VUa70YI/K4xfYRIOhfDUAK69WD6d473nVnV+0K1NTqxKW2t29EstpSty0JSFp42v0PaPR88Qktx3CospsSMMZBwXX9BPE1U5QWwNScgyEuepT8F+jbCrrFMBK2L6k3+bOXVUj/UiNTNOQkwpIGcImcBsBk7TkzvDpKkGlVaJtvyQdV7bzB2Q9BiCqAU60gLAp13TgwfdmCt4x3WPMJmduMdN1/ArV2TCS/aZZp4hbG2kNpupfKE30TiNIYqPUF0jv5veF9xwa6HOEEnZn/dODwkDGw++YVy1QnyjvACfbrTjYVGe6txo51lYwaDF8oplREcPFBAirT8dUVi3LKTxtuctDBQ84nLoVwuSft041ZS48dKO4VIihx0D3BpBaRfB772DsviFF1o9nyj4LQ0HR6KQXXGmm0mbRax0lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwvTfYva4HpLYXYGJSXCHTMnE9GuytjMKmVlrKAtHQeeOY90y2prIdI3aS4TKQg1Bk9c/OdRcBEk3gr3bAH0TJAQ==" } ] } @@ -110,23 +111,24 @@ "WalletScanner updates the account head hash": [ { "value": { + "encrypted": false, "version": 4, - "id": "4845feb1-b76b-4675-a4a0-0386104197b3", + "id": "c55bc1a8-2d91-4ecd-86a6-55a839341e24", "name": "a", - "spendingKey": "8864a332b9ffe6ac4f9ebcb5756086285e3799f30a432cdd7ffb9d1a6d39b000", - "viewKey": "09d115a480a1d2b1b8a149a68542a7ed251d573af7c807b17e98bb529892d8b1e74605cacb2e2473a9f83ec96d8acd3e435254dd63b6b50bf4cb1f67febc8980", - "incomingViewKey": "958e5c34a309ccd6faad896122a1ea549803d423e78c002a3fcba6cc7db95704", - "outgoingViewKey": "cb7ad68454680fc415010dbb0cea2e18dac420cc34566fda8047bfb4e5d74ba9", - "publicAddress": "012d22c53dd3aa7cc986bd8755f8a7af024c3154bc170c8a588d4dbd55bcc416", + "spendingKey": "d8aff543e45f79ea411003c748190e4c5d3c46487a64dfca0cfaa86711164064", + "viewKey": "2f5de8ccf926eeb71ead6fd08f325f19125f726f38459f3966c07596c8c5e2f0b180c9f35e07a765ebbf762b7e451c4e0303b2fa385c864c8e6cae844a2cd541", + "incomingViewKey": "dcb04d23d7a12135ab28b4b109f9ff8d29304928ac836e433793c08161335301", + "outgoingViewKey": "a7701cdc5e6f52625ead9450c0659a9f9d1d51079a58b6d9cc94280ac3f22165", + "publicAddress": "f16646d871752badbfd27b1f3cd358f4be7ad8d1911071df6ade7bbdfd7f5cde", "createdAt": { + "sequence": 1, "hash": { "type": "Buffer", - "data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" - }, - "sequence": 1 + "data": "base64:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + } }, "scanningEnabled": true, - "proofAuthorizingKey": "faa1fc9d7137e3cb50ecd6e02dadfd30af82a1ec5c2ea9a8e482fc20ed60ae08" + "proofAuthorizingKey": "bc4e01cf59af8eb8f74aa055cd6831db6e02605a420bb531d424b0a8e9731206" }, "head": { "hash": { @@ -142,15 +144,15 @@ "previousBlockHash": "4791D7AE9F97DF100EF1558E84772D6A09B43762388283F75C6F20A32A88AA86", "noteCommitment": { "type": "Buffer", - "data": "base64:0yP53NgcgapmcTlawnhFEADpOt3D8bPEZTqA2OopGSE=" + "data": "base64:0slEEDIJFIkYKhKW5In0j40nxkjGBd8PhDY+wfzdvD4=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:4xqaNuh/b3OqHXC5adH+PM9V9G7195kNile9LCOumDc=" + "data": "base64:zij0l1zjYtFreJJytfWaDSTpMJtITZJGfbveYMxSk6g=" }, "target": "9282972777491357380673661573939192202192629606981189395159182914949423", "randomness": "0", - "timestamp": 1719441704807, + "timestamp": 1731435259184, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 4, "work": "0" @@ -158,25 +160,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAADkyAZx9VeNdAbJwLTnTKAe8+xo/bb7LVT/oTP/wHjQyGaAdsW7NUXP+Wy+l6oRrvR38y+gqpM4ArMNb5bqAHtUyI66srXud4dvDQL8wEkD65l5HzkTr6ez8bD9EKpvQXluSPu31cn6JsmYVPxzfdi8JAXsAohW2jehBOx2bqMKsHpcp9mDatVtBs7hwtJ9nD1mCOfk+bLJqs+kxscHxtJj+4SsQ37KoV2Wk6O7XL+5CV97iBFhfUtrh/M6GJjSvvdjyaoM/FBPe6/8DmFV+d0iToMW6H3UiJda6CoJ8kKJpYi7Y0lz4v/M0tFI3EiMPrDADlwOGpJYZ0S20TFDemROedpEs7IOkjfHhw+CL4DurUfT31gW8fBXklb6VdbaspTAu/jwoUUXCmQ29l55PHrJz05RJJuEOe61hdbPabisWisiUIk6eeR0ijQCWzUTOFMK0ml+fqdPQlS/Dd1/MlUOpp0ApM5zyrSbHw1EHihWmQu5k/NjScIemsKPYItfqHCCiIlwvE/sCYEB9J4UF385h0Cue1iHIW6hBrSUMREKuTcVB2crFrAaZc6Drx4/foy8b74IjnYFolJnj/5BWj0b+5ty2NG67fUroo3+P5E/sySN2oekhOoElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw8K31x/ZrfevKhpGEccgYy+TZ5zIc2kodOYlG+rY/DEZdgfxlxpUL0dbUine/x0KBZDnqPBOqxadikP+veqDCAQ==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA8jQt1ZMHIb5/+7gXT9lRR63IjLrWlsUKfH+mkKVTqK+u0zjoubhSNC39a2KCknp5I7wzDGdDCwcBorGUSt6hhCi6ta3wvi9pHLpxBy449syHVFyYPQzvx3D7hiXVAG+WdJlA7cVUSgW9kUEGXnIUL8SmCiHMCtkl7S3DR8NjSdsT6uUpV2ITQhH+DoZuPhBS05z/C2OAI5UZhWbSmjM9FToTv24c54v5yjs7kx9ltEiw3ZhyB5rl4sPkeGCMOifDDd6dngKQfC5k8icad/d2oxfuFqNzDuEhlJPTo7bs9BVnAlfox6hCJ7VcYDWcHhc9uY7epucZAGSVstPxI5jvMKOyVYJDQwNV52h6uNz/PQksnZVNrrQB+TSj4A3aX8IHsEu22CtWbg7yXcOUnsjCG4s00n7hXan/DzeBg4i6EwKBW0BiqzwnN2o9D87oRKsRSFpYC5M/WgJ/89owVtTgVaiV1aKGBMpwv+uFVJRwbM93TeZ/OAgWJ96B9fCHHkcbAFSbEEAFcUq3IpiPIQM8kcOjC5wMbun1K2/5HrqoS7aUcg/X96sYHYBZgWjRtkfFGlxmMh6yijucaRvOMafhaUI1CHRh6aBpu1r2W4JGZ0acuBF2hupTNUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwM52WsRqt9pWbDGn4DIG6fiXvd36jEm85E5hZdyqVx838aqntu0lcISXlFTzr4IpH5ZWXoWHr5ZIQp/v0GOS0Ag==" } ] }, { "header": { "sequence": 3, - "previousBlockHash": "84E024B89F913F167200B32F0C043FEAC806E0FE96B2B2CC0D49FF9D6ED413F6", + "previousBlockHash": "55F398A61EDEE48FA1A0F45DC993F9DB6F150B1BFF2BA074742752FCCCD08A50", "noteCommitment": { "type": "Buffer", - "data": "base64:eGT2PrD9wxP0pCoqeqOdHuCEOmoCa8Pp+c6NLtxdmzo=" + "data": "base64:Z8xZAj/FvDH+jNSL3o25FsfUVbn1ONQD/drINy3fTzY=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:RDvoWjRIxsSVM5ctsU417Zwa6H3H9HicqiHES5dGZI8=" + "data": "base64:514seQiTzEwibppFY0bnXTFyvbNgt7OOa5j6Kl43DIw=" }, "target": "9255858786337818395603165512831024101510453493377417362192396248796027", "randomness": "0", - "timestamp": 1719441705285, + "timestamp": 1731435259479, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 5, "work": "0" @@ -184,25 +186,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAALOJRFX+2wNDH/UBKelg3Vvh5g5AfkdivpDMA7lMhty+UBHCJXFrlkQRKV/kNlakvAklq6iYHZYplbLlvvAbLvjG23E675POU+CeZR92crOmvaEMUV+SDo+U8fuJzyg0zkwaTi/oUBWs6A7q+5tYOib/mgKHekf3/3MjixeMCGpwOUHeBjJIiNmtGXBQHlSJhLWb3VJ+UvdRNcQAEGDpB2iWfqkgv3HRSehYlhxtvNBGGM4m/y4z05O55DbIBua+fZKWvn7ONZwJix0xyGt2ZWYT4t974cGFmvK2qgwMGf66A1b8D4g0Wlsox/7k2ZeYwLegvXVDoY3aff2pPvKVfuvCGCqSEcgX5mmGpLHSL5SDPrD/Rgv0HYJiPe4sShiEkYjjiynfHdyqz0wII1m2OQt1OZC8Q//smh/Wi9uuEmo9Ukd9Qd2+X4sEB2Pa5H/QCVlXh1N1muRXpH8feELQKYw0UyL0MK8XpeulcFHa9QLUoLS0tVuiJ02of2dqPpYQsoS+EV/Csj1YGqxO+JfjondVhNhwoLyl+4dinOpb+7SIc5dgHaCVFll6MQpH+mkc0figt7Bn8MOXPCq75BwhGvcJsVHNQeDDr3Rhg1lm/yDOcg6h0gHXLoElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwZ+aYeJt37LklpZxHcMExSuceAui6llbkPXNt5Nva+PAka8Q6LPw98mcjbodVcxjgmri3iFAKfa3cWehZoNzkAA==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAhBki1Z6yAXTyp5AXTSjHjdymG7Ca94+C60XihJE1e5m2jdwUV5Hsm6PLOFms8ckXk52xvr/s9CayqaU1/GIJPW9hLhnnxq5y0lrsfXHgdJm1qjWvlobHim4jVWwBrWBMzniBTxGO0qZy9jTO38dYzd4iTwqItkxjvSsyZhCrHEoMg47v3q1sVuClqqIB2kYIK0FLgt4Y0MbR3sPXNAkbDFWWrrXDkeuTG6ASp391D1W3WUnm4odgnne2ETUVfcTTR/0BNb7FL94bIvbdpxyiidLTgjBKfGR5ocmkhhfkBf6Xgc6vxuzPcxTUuObetGqnAq1PPE4bnvoELxMlqgeLzSZBdHvnzYhbwB4FkGZSFkk/a6ywTz3GIobIPzNJV2lCGFDpLqThkZKs4oXcsjg0JEIiOnzNw2p2olBhjQ+VOd6G/tw2Z/l3TKGv7mDXqCIszjMvhkrWvPPFgHdlxslwPa6iu6iuAR4LHzK2IQxHi+J2+Ii7snPCpNnF205/FMZaZ1pUnjC9jl4cufR2HBV5uK2fmqzHmcJl8TjzpWWSYhma2Su6JTYJpCW5uKSIfNxOo30wOTc4O/PVSVrWDpVnFIspL9IpDnzN6RRtqNm2sqC1TbKl0xQZUklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw5euh38DesTleFXIsoIt3bd1qhTBv8QDM8seYLqRwTzOxk5XqDL6mV85Er1Yp3XTqd/zN9pRLy3qHPv/H0wOxCA==" } ] }, { "header": { "sequence": 4, - "previousBlockHash": "92C047025D6E0D505BB553F35E15A608FAAC31A096EA5B1B80310CD04FC1AAB6", + "previousBlockHash": "5C333E2C4C880985F251B489DC06E92A8C7D20A5711089BC3BCCEA0DC2132001", "noteCommitment": { "type": "Buffer", - "data": "base64:4vrfGlMPz/rVKYTJk9cR5DiFvvltdENwkp3iBQWASyo=" + "data": "base64:dm89nRbOUdjA/KeKAGcD6/IdRfu5jPxRTPUdED/Z2U0=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:yuz27wPlERZ9zvnUtE9m+9aJGfzQaueZdRXrAjFpd2w=" + "data": "base64:IGNVvtbkAv/0ClvHOrwzaAiYuUfn5GFhe3T5MNSIj7k=" }, "target": "9228823284279306817296266184515742822248210830185427859262273659833347", "randomness": "0", - "timestamp": 1719441705753, + "timestamp": 1731435259776, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 6, "work": "0" @@ -210,7 +212,7 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAVV72G2AbMfeY4iqe9DUR3dNwVBULTM8anEaHxKuOmWKHz0JYmYQ+7Dvg3eyhLvqNzM3WWPCLVmr7EaCoEU1Hb5r97OJzHx46HW+JUGKB1uOWg0Y8IXU8YFZfQTkO4HRMqS9Ga/rTUYOjUKWYNaz98f9fK/cdd7Zt2A+cgxXj35AA0ij4aP3c+CSOCtNGMlXRpwckTO4kWXij4eelqzO957265yJlk5w3IkNwsakjNmioVHFoSnADKvLNmdP9T4OOGkP3FBzGIwsLAXstXm0CdZE6QcJA2r1nfrysym6fi0l84vRCgCKr3UrAsdgjsyB8L7RNY28qM6rGdBe68JWFWR7qGh/aJ82u2e6+OXb7QAgq1O1FtzGvP7PpT0r0+p9tQP30ZC1oRTAxVokTBKL4Zi+sRkIFmC6nhuDoKrhaBGLqlK7TsMdcQ8eSpGrn53HZmBdXPpt/xnrrNwC3YoHPAR4f1RQ1J56aYvJi/75omAs0ePBdaafeXK+MU2rX12Yj4KOARd1RT49zdcm5XUoS+Y3XLXoYEYDfsKhK6Z5RFZfkpo51su7LYAu1N3pJhxpDdpsZ0T1wtK1qQEPr+TonfTKBhFs4iakIftqgR7KraXRTMPCTevaGb0lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwZzTk55BdcsixSDHZtS9Ucqx38nIr4P+IUUNk5b5y2rLmh3yM1GCuspaNQb5nKQGa8FoTjLa4/w70QmDO3LjRCA==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA6M9Ll46MTy/Uc+XZiZYokLDlywRxmAmeMU+azmt9+FeQQ9+oTdt2BG8tiwkth+k8rYG9HO/eeFGoK5pgPQMrAyWnUCA81IbR09vYUrFOh06LXDsERJDf7E51CPRwgYLZ57pBOJ3ePfEuo153pGWX8Aden3Cf6zQcyburOAC/BjwTGZVWxY2BOxdxzktaNMGXajlwdMli4XjDxudDnDTz2GXZmh0jlwQteo6O6aOqFVGCrAsER9Q9r4VjLX0NnISsxikEAY3gYkDxsXakNSyuZJUj7JCheuzMuHr44vM3k+gbUSspK+aZrsrd3WkW9nNMab36yzda9+h/fLEbCcoEsqYhgCPU5ST5Y1alDDL3OarkZvYJiigHGi+pbLHUxYoQrQ76QcPNhFDK8sQe5+rSzG2DVR8uGo96CWqmvdj5QKzFpW6U8JKkqwKVu01ZHjjuupWqbMhQsCkNKQtX3AoP6OWJY/Yi86LfrRkSRqeciydSAQGYANTm0s7xCUtf1FKZ71UDpdbT4FXoRhzu0DQYvtyDhE763EbXW+Kt5R5J0earEF9LICoEsR1ovpUEIIjF3lqHYCX0o3OhI3M/UQ9waEZ8jwu/chSbPKQ20ZDunm70T3vAIOYcCklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw4/EDZkwXhYgTLUDPi70Wv8U8EMGNhq0dIflZZ0gWcBVJ0ZUKUTmrFlSH8EMSvjHbWn81Y4xcYeWB6tjJmdZWBw==" } ] } @@ -218,23 +220,24 @@ "WalletScanner ignores accounts that have scanning disabled": [ { "value": { + "encrypted": false, "version": 4, - "id": "065b1469-8875-4ee1-bbe7-6d36561d8de1", + "id": "bcaca9c7-d8d4-4459-a237-8e6e1db1e8d0", "name": "a", - "spendingKey": "fd0a9e9f4d537a70c486eb2da068df45f231b6aeab564cdcdddc4064fd4eb9dc", - "viewKey": "25fb041e50363538fa6f9657e7c8224ff63a1954c55f61c130d1b1157dabc0adad00669610f7247e238f22f0dc359d4844932fbc86d99057d4e82f66534de65f", - "incomingViewKey": "b53fe8b6b6d13b89bf030ef7764855ca4bf8d9f24e1033e942c2038ca1d9d807", - "outgoingViewKey": "210eed54a39f09e7c97ed4114d69d1ee265d0c724da681451dd461ce353d895c", - "publicAddress": "9fced945add6d7beeca3bfc0bc951f3cb758463a3845487b96f2187c086b40c4", + "spendingKey": "cc2c962f8f1466beb16e890f3035f7f32603b410825673228b1e99d358595ee0", + "viewKey": "d5f22fa099259d0e8196327390e74547e9d2e2312e163d2281f15c5ad7c6fb89f15729b99fde7785b8bca60aa7de2c81942b25958ff3f602ff7d19c7b1b8d93c", + "incomingViewKey": "9f55e1e840e885a7a324450177b1f918db8f47d58e2ae42cf3cae63e81f94400", + "outgoingViewKey": "562481abccead0d59949c3ad7851bb0ccacb45673f56ddc22b0846e37654f70f", + "publicAddress": "8c68dfd416b5cf9ce091498be0c778e0e2ceccf7013a3c6a3e1ec23536bd5126", "createdAt": { + "sequence": 1, "hash": { "type": "Buffer", - "data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" - }, - "sequence": 1 + "data": "base64:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + } }, "scanningEnabled": true, - "proofAuthorizingKey": "5ef2b62c3676bdf5da4d6915f0fbca870a1852c605521caff0399e0b9b1e4005" + "proofAuthorizingKey": "d44bb9956ced1f9e4b18a624bdae507852e93785fc532c82b0d2c3aec9748d05" }, "head": { "hash": { @@ -246,23 +249,24 @@ }, { "value": { + "encrypted": false, "version": 4, - "id": "c986f231-7f91-452f-a91a-2cf792fb8b5f", + "id": "4569421c-996b-4f24-89e9-dc70942db882", "name": "b", - "spendingKey": "506cc73cf1bef79d4d4d367b47e84c3a13d238cc61dcc558579ccfe453a504b9", - "viewKey": "c1a3951064fba9f691d3273c47c5be0d45fdc4143798a43b918f613e87dd0a0224001643acfa030d51ff991eb925563045444e8183869f1282663ce5adceb384", - "incomingViewKey": "0ce9869ce32bb9b8363ca02519cb37b7121a987005fd3ebc7ad4bb803fc49c02", - "outgoingViewKey": "ae150ab59040f0ac65c451cd68122b37e75aba445673d1dd5fe0d4f77b4f4386", - "publicAddress": "fdc8750b5b06467ca88a2351b01fe1c04c22374cebc4910e167532428d0f4dc6", + "spendingKey": "c851d136baa923ee87361c17dca091cbbc4e923efb78d6ff1cb1510e274c1610", + "viewKey": "895e38af9a57d91ff16ec1d5827dba2d035d2a63f235a99a683ac07093634c439d804b4362fc18daa27988d4dcb696627223125668f0af4aab15a64566de6497", + "incomingViewKey": "c93e784dcc5be5594fbd29090e04750547a36265fd06223220a646a3df545f03", + "outgoingViewKey": "3d64f255cc49a897b3cd2f5a04bdc4628421fab921e6c8dbf3a89135dc192a0c", + "publicAddress": "ccc5727b50371a491c8d1d21351b833d11c2642e53a243aabf9a4898ec205686", "createdAt": { + "sequence": 1, "hash": { "type": "Buffer", - "data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" - }, - "sequence": 1 + "data": "base64:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + } }, "scanningEnabled": true, - "proofAuthorizingKey": "9dbe4ceefa60801569a907266c225bb491b1b9f7fe77c6338faab50395051300" + "proofAuthorizingKey": "78bdb30ada8925bf5d652ccc58ef29d227720dcef1bbd80ad02d25401b7b8e03" }, "head": { "hash": { @@ -274,23 +278,24 @@ }, { "value": { + "encrypted": false, "version": 4, - "id": "3247103c-f0f6-44be-aab9-3caebdeeed97", + "id": "818ce563-5b52-4485-825c-e8daa97de454", "name": "c", - "spendingKey": "0a20db55d442b982b7ec8e472d4de751253656e7a8c9d8a7c4a2bca178fd4a0d", - "viewKey": "e12f8440a0d53301d668fc283f7a8a8ecde21d3c0c61fa219416bc17e48d8535ffc49c82da7f38b40bbdf08623075f097227e9c7ce136b95bc3465501c5a395e", - "incomingViewKey": "9d17222d43f5d3810d5c8c315ec56981d62bd449949e97ff15084f683f442d00", - "outgoingViewKey": "61604829e709df68364562002bce307167489162682dd783612dedbf467b2d7f", - "publicAddress": "7657b2be336b7f421204d3f77d6ef6987987c0ff26d2eb126d16df76377b6102", + "spendingKey": "d80a4028f8cd36af5a0af903e9c56fe7f9ae968ad8cbc84d36eb4f369f597903", + "viewKey": "17ba9302d0c254b217dffe5d6d78e9e26b3be8ea75f50e557f23196ed9ec6f0cf77bc4265c7c1e38e091ba713cc99664653c6b30e6b4c5fcd6f085515365dd60", + "incomingViewKey": "1a5f2f06afd0b822bc808c7afb1e2aa9d68e217b95bf5fb3adaec2afe3007205", + "outgoingViewKey": "f5c415e7b5d4657a0c6a8ae703bf3c2defca07b7b6edfa0324e6885743bf2d91", + "publicAddress": "f67db3f16c1da91110eb31390303e71ff1460101730cb4e62fd2e8b453a17e17", "createdAt": { + "sequence": 1, "hash": { "type": "Buffer", - "data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" - }, - "sequence": 1 + "data": "base64:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + } }, "scanningEnabled": true, - "proofAuthorizingKey": "065b39b91094875391065c11b4537a87c56354a4ac5d328cc030a870dde6060a" + "proofAuthorizingKey": "310c5418abaeaa0e1c94858781f9651d739d2d523001556187f6a65b03c6e008" }, "head": { "hash": { @@ -302,23 +307,24 @@ }, { "value": { + "encrypted": false, "version": 4, - "id": "14622193-0462-478e-a64b-1052fe31b17e", + "id": "ac8d0550-1742-49c1-abc6-9dc94000c9eb", "name": "d", - "spendingKey": "9a5484f82aeb1c23964d85390b1c4e003e4c640652d1617638454ac0cd5ea1a2", - "viewKey": "c6ee78b3c8ab7d29a5946163254036ef01ee62d6579bcbd7a4877a5910f6bec6ad767a68541e702565c185d83b79b5a34bb546dd0f2051f2833bb865e8a1fd9f", - "incomingViewKey": "9204a43c2e699d4d1c1ee89a37e8125c11de657653462a1130e76e6e34983800", - "outgoingViewKey": "ab6bf9dd3118cfd00564519429a1d4a81819bcd5a7b7f7cc82317c72ba610da4", - "publicAddress": "58d0e4790b30940363a48dc576826307f71ccbb8258d79fc47f20337269ebf50", + "spendingKey": "8bd79ee0ee4d3de7c723183f5e95d5d2055042690688a6b67a41ecb8864131bd", + "viewKey": "5774bda7de5cc441ab269b33172878ebb643fab1decd979722af33355c21ae17ae45b5b5b0ab390ee7b3877aecd1c75150f33a561a0081a894fb1ea10f843be8", + "incomingViewKey": "3c2c91f9f6ad24fab8aa46a8ea6a0bb13154986130247fd0159d90ed75ecbe00", + "outgoingViewKey": "172247730e29cd60a2805662d1097629f412d74a4b2b6cec25dc655b4177ebb0", + "publicAddress": "f64536a6557e75f8865c590a1b4dc1cc434109eba763d56c8dce8ebbb26b4832", "createdAt": { + "sequence": 1, "hash": { "type": "Buffer", - "data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" - }, - "sequence": 1 + "data": "base64:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + } }, "scanningEnabled": true, - "proofAuthorizingKey": "7909244afc59a8a93f67ed1e6dfb5a424d981aea2aaeaca2c97e25cae6403a0e" + "proofAuthorizingKey": "1733c5c9a428bbc3abf1063dbcd7537530de0dc3386476ca319a7b52d2dae20b" }, "head": { "hash": { @@ -334,15 +340,15 @@ "previousBlockHash": "4791D7AE9F97DF100EF1558E84772D6A09B43762388283F75C6F20A32A88AA86", "noteCommitment": { "type": "Buffer", - "data": "base64:6jaJJl61oR/k59FxFDFByNwVYS+CwucrEoluoYlFXGQ=" + "data": "base64:V/boRa616tEGdKescH+jfMmcxmCcPxmVYZ4Y6dKcCi0=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:6sKQ4yRZDjKOT5nvXwWg5LZniN+7sfyD0AikFfmgqxY=" + "data": "base64:KAlSWdWnEneg5VCWl1HgxcsNCBOE0bCUrnvYL7E8pi0=" }, "target": "9282972777491357380673661573939192202192629606981189395159182914949423", "randomness": "0", - "timestamp": 1719441706482, + "timestamp": 1731435260182, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 4, "work": "0" @@ -350,25 +356,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAqMbKPDNR2Yts4XeNiR3HUxI3t09nM/0J9tUHZ5hCFA6xy1hGlqQJqCnwU8ZWqhdxRQmFio+Z8zkts/vig1T1QXcot3t4tGfzlNt+yUxOORGgrCjxHDMI2dWLRlE+SJWX/OvjoA4gjmcXwXXxRPaPVXGJ20itMr7qNQtZb07GSnEMyE5OjS4CetFJyvbyBw6/eEta4ygjzFeqKcU2Iv5QoOoSE405rF/ZdUsajTjF0GKCG0jtR9wAX4dUKnjTDa0ZLh56cWQ2DZQdJ/oRuRRC1FRpi+btePyLfTnN8XjqwMULyQJybCMhGuCM9OTyhZDgohAAJbHMhe9RGQYRxPdwsx5fPwV+nLoW6KeQhgAcsGTo3bp3+Q8PtVVbVs2T4GhsjbUk5oco0ZNK5+BW8ljdEgPfXBmOb1PPv9NOeHFzRj/aK0ViaBhYC5Iuy4oGyHMXscxwVdhPdKpd5+7rGHeAXIkF+Sp7glkvhBj5yhJk1xqDZYZsLmRYbor8/n+SVMNqxO/5Px1QIOriMA5yP04KdhBvgeNEqWi6ivYRfUnwvOKTOKA693DkoRJ/A36fSk+GKDlm4X+94yUpvyGYr9XSGyW7N9AFi16wBqBWGQqMP/q78l75fojYUElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw1z+InxdLdAscu0OPEqQwCDIahWk+CCvOgRcfuc+JwuFM8ctzXOkXW/Znf/cDYKz0ONx/fDUNYnLeX/pI0P8QAQ==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAfVoJZoZrl+WqVniMnFEA90UFL0ybPbbYVpARtFXffleOVKnZMJOGCynDof9HR4O70sJKzit2AlI3WzNKmAGyKLIWMO8Yvtw3A2I23lge0N+nTMYW620aQOUfwbcDbgX0tIyQx06qF61o7wR8Rw2LDI+uwyrkgvoqL6VW6ArW0osNOA8Z0Mz3Pf453Mizl8ifaFuYNNFbv6v3WqQLUqGcoBPBVQZTpHDt4m6zopspciKHG/+n70RDVhHzQfMO3ucVjKjBKVGUE29vcvC8GRadDH2WMspvh0ZTNCN7KlPbjPGK8dEpL6sMzjsuJUcf1UN3qOWdQcbBcVgPJc7vQ/+CS2hLX+mo5KhpfDP2I9ZVnDfxAZoPhFe+wJTpxWlsjsBC8mHFWPyiE4FDNTV5HzLYluxQf6qp7sLfR/7ShFL8dr+o55s217QTdc9rp5SbvNccty8nTU5XUaajDPoJXB1YSdP7J2vGiuuTzMCeevBq82bbSp7RZWq93/r4nDIvtqcs0T5VcuB/mQGRB6lwgLWELqKbuLyPbkC0Yvx0z4fhgQuXfFCNhH7nyHhx2H/mLi62FmJyLHT4QYWHeYA1CuYmq3qjA3GJbMblpgqk0aJ7GAEqUSK80jeSZElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwPimkIFHop7VZLnaTf0YPRHheRxWMD1lA8RwupysM242nU7xqvPGssX/C75mJjYvKwJEbHI3/t/BR5eh3Asv4CQ==" } ] }, { "header": { "sequence": 3, - "previousBlockHash": "01FBE0D832AF2D36749432271E8E02FFEB76F377410E73C66C59BAABE4274D37", + "previousBlockHash": "2B008934D5DF4FEC4404B11DF2D0A72A6350D443EC5D9741F34C59D8C17F52B6", "noteCommitment": { "type": "Buffer", - "data": "base64:wyhr9qpeBSVCzDqyyxvbDVG1+8LQ5Q6pbIVtuHrOJFI=" + "data": "base64:dJc6ibZSN6+k+PvmAZglUc7mQJWAuwJaOWAvmkq0wko=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:/RoQcHHeRE1iEnYSvmRc1tA90LB6kQahXFECIx2uy7c=" + "data": "base64:IGpBZfoysC9nr0LeMEMfI9Kzvn/IfPFB38zwK48YRZ0=" }, "target": "9255858786337818395603165512831024101510453493377417362192396248796027", "randomness": "0", - "timestamp": 1719441706961, + "timestamp": 1731435260477, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 5, "work": "0" @@ -376,25 +382,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAKsDfnw1z8D5WxMUkzL1jM2oa/32D+JvMnwvl7GiYA26XfVg8AC+TYl8TC1SYiGJaY74jDGrVp9szDWKTraM0CIifnJI1JLqewZipjMm/C2yJzyZiAH0cqMmCkO2uDkl3eO/jiHnO+QksXcQE2ZWlsu9/OhQlD0m2snk65d7mmNYHgOXpsASBTc9HqTyFsNdjIfmRnro9pc+5GGw95EuTiNHzoEzDQs5sbzOfVeEFo6WrL92hiXQ6Pz+nZpOtUmT3KorezFva0Nm4pa1GOipFMAc2zOWI1LNSxQuAd4r1RUN86/o8kZxugBsBUF8edT4uQyWvkj5Nm3TFOPlb+Ek/m53nDCDh4mBqZdEZfFYl0uxmQ1BViA6SfNzW2WPf9FwEikZYpTUEE9x0yRnOcF4w5L/vXyKRIUrs9mU60poJW4ZZ3kGOIofurPE9O/p9yRDfoqkoNpJKBI+Yb2EGW+KWk5JG7KNyufZ1rSWckFhFtWHd/DoFHayM/Dc5iH/z7GnZyVU8R4Cz9IAR/v8MEjSkc+M3+By9INB5pOK36jNzBg7RBvbQX2CilHw/ohT7c8Frmf6IZ2zNyMMCyAi4oXj7zsypMEvv9v+NSwehxPn8zeMB3MoSXnfzwElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwlAXuvSVA6UI2UOLCrtB8B+NXJCZuh8egx1aFSrSNtkAKHrIgXSBpZ99n2tRE1z9rphTlbxWbdATbiJpW8VrFCA==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA7TKfh94vWDnXGux6yRYyPlU+dk0d959miwbPdz/KEESND50lMXg/dvh0aWtAMyUxo8o7XeTl9Itl5Stpc3hApUXmoe7DQt0YTxfFlvb4EnarV8xoidAdmE70OdhYTk2zWcN8sp1U29EU9VPqvO92DniaoSjK60MSds4lKBX7dQwVRfGU1Ro1P61tIYgpojwo+si2350bioZ6BwcwEV3ZTUMWtB7L2hdW5SqFSdwpV1GoGsOg/nXibbxcQukE9poFBienoGTlxmA32O243Fan9Wf1NgYcnVEOLsOeDblqjX8Y1QJ9jlW5zEDBlWgVs/W9LNDV695oGz4LGnLr0c1Yli0oT3GnvceRQVoy+bw1G5amXILbxi3g8DVvb/nnX4AJbU5ii5agAs2WTekIXv+n5AOVwHNcFBJBDxQIf3OdvF4CL87DZ8Ws+tMxfGAXAAjYfz4EjvWkEkePfBVdGM/7VsjN1Uj6orTA1033kRy38T5dkC24xIYI/x3ZRXn1aTJ8QpTbo6ia5MQNwvbQHHV60yQRbO2dqQnY7qeKjivekEx+FtoKMy790lsNGhB6v0wL3IjLGQTqszKT91uy4QtJDS7MzJpBGbOTs/6z3znmDkHRkjPXqdAq9klyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwPMLyy4Pc6OLlwVD3p7+EISjjYg6/SNcCQUmcAhv+kJ6CVEo/GYWPZ16C3pVpKl5lHX6CNcUhU+IVEL/Lud/SAA==" } ] }, { "header": { "sequence": 4, - "previousBlockHash": "4C25AD18B5FD890D7151EF9F12CBBC5DC49B7CF79BE2A5437FE15BA660F7D11A", + "previousBlockHash": "16E2073C893996076F38A502CD2F4672ABB63CACDE4075CEA235DEB6A15BEE28", "noteCommitment": { "type": "Buffer", - "data": "base64:7Q/OPCaCRx+rkjjgRo3mirLyPr/NRnDjFKz5uIwdukg=" + "data": "base64:tC0QNEoI8YyWc2ViBuhX1beAQSwmmhpq2Z+Y97G4F1E=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:QtmrusnKO2T3Ay4C2b02NKppz2dMwSnDtM+LdnwEq9c=" + "data": "base64:chCcFW2ILUIQoqURdX4QoEPkR6vRy5EmIMeEUkX5c0I=" }, "target": "9228823284279306817296266184515742822248210830185427859262273659833347", "randomness": "0", - "timestamp": 1719441707431, + "timestamp": 1731435260766, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 6, "work": "0" @@ -402,25 +408,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAyukXH/ZnanqTAxDwpA8M112/riPdGFT5eMgYNPGrnVWG3KTVTvGkgdVi4ax6eUulFJDnd1xoq07fl2NPEk+URLBeAotFQxEt26PpVh6b/jy0k5tfE0rJIOhncgVJMwf29DFnAjMEIAvIOLPT9qVbLNQEMY6pFyiDQKt8OyKgVRgPjgBZeZaWzzA0Yir78n3iPQ4sDzYdWY26CN6pe3Rf6kHh+HuVD5Seb+FsIggpE1mBhYABy9oK6GXq4qI65NDsLwsGoVH5Qz2VUMHlNZbZL8OC7ViGtasy6EbCOneuI+7kCGyB5T0J+kxEzfRmJvdZL1yag1RSx9FxWFRPmnXggMZPpSAGYKAi9gWItsWcv/pUgMK09I0KED7q1WxckTQ4AbcOnyV5mF6AzxDnon/05TU5bLVgcEVjQtcI2V91yefkRAsjKgsv7gKZTeX/cWhv2wIYzbUSsv31ZY2n6rbnb3FxJitBuFD5Fi2KRlsiLBQ5qhOiuGG+Vs0UPmdIWDvKUIOzJzcjb0I5ZUs3xcKE87z5KpBsvqIfM3h6c+BszNHDtHoUzJ73j6B8eLVgPTJ+dpdEJkaZZtRRI13wdZoJZft3Lg1LvTqmVRgbjig0Nww3z6DFAh8VHklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwOtDS2Qw2/8ZrRoPibD3/w6c4TUZuXVe7uVlcVxBtaGPLE6xfuW7jcWf654NM/oUAshVSibAfIoMA5XuFJxa4AQ==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAd6fuxo9b7XhuA+jdpvhRSOfKUUpXDrl5S6BXksYZRl62+HFIbpJt6HWo3QhSnKTJAHIkW2hZ2pdlTq7fQRRystWK/BprXi/nHj3XVzoHhjardH0V/O1vWHq1zAbwHJLrjfKQe9xTDxDUcraiUvgScLPVjMpW7hHcfDnVX9gNBe8CekkmUpiMv00ff2JC/undimuI9JgOs6V9Vigzy3fA6Xb57yerfHQaU7qn+wTjhD6Qbma5OcXGMP1MG2ZzLYCXX02cCqBpPD0MmSh1qsTOH5YkUzK+3xh8DLTdGipcOhkb82Zq2/OarjyfBfMJM8fTDn7BCEZMvTmfLiXqb1+86fjgn6l8Fq9dtavFxmUam1SyBbcINfD9CeJ8+U/lbCdtE6H+pU8/uTZ9v5DZaWQV1Urc2idEPNC/xgb477MYjizqFKeST3wclaYsskFxX4u/IpyfIhmg6NU0JA3NBWq31fG6JFRGSXTQKkqMx9b1KJOcQHXPpblXyk8ZRw8be8xZ1X6oHxLVJMBFm5V14tozT0BpbFpDacagoRoh/TzZOhUH8lQ0M1N1TpYtudBNWyu3k9wJF1qpt1Hl0I8Hee+Wgxqvn2J+DWYO8sbxanHBbRLNPe4KXRRfLElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw2y5VcKvnKgleqEAmTqWDOL8VS9DdanCA0hZNUzeHFiOjnrY3uWWlMoMgIT0F3EE4SZ34t8va1TOPV4IRe4F9Aw==" } ] }, { "header": { "sequence": 5, - "previousBlockHash": "6C0FFCCB541112535C9D3F7FACA59206E97D7F8C03819F76DF5D3BDB16C37EB7", + "previousBlockHash": "D9CCFE6F5ACAECC1D2AA55856BD8C755E595030A53937948ABA7A619C31851A4", "noteCommitment": { "type": "Buffer", - "data": "base64:l1cZSZAZP+Xa3HOzuU/VQ2EEGfXgciHvljUkalQmZHA=" + "data": "base64:urWblztE3Xd1XRBzka9XHAuZ+7NlSCkNWlItE/qdLhc=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:Xn5b79Q4VamUTW4LCp0dFs5Amcs4q7wQ6GCp+KzijaY=" + "data": "base64:YKffZq7a4yoagu0c/7jRtWJYSYLfls55Fng4t9kaydU=" }, "target": "9201866281654531936596795386791503876274441021197252859723586932895305", "randomness": "0", - "timestamp": 1719441707902, + "timestamp": 1731435261058, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 7, "work": "0" @@ -428,25 +434,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA+HDDRgCSqtfU+unIAQmlR7lr+Vl15xGMpiJE97r6lA+xA+ePuQHVUR1+GV4Ipdc5hMz88i71DeQj7sF4jpZmhovomQuSnV/4+up8smfdzU+C5RhT6YEFTvp5z5bWxTIl+9wNboZpmNIGLUxLFbiFe/Unzh0V03ffsI3vIAikUQsLEsN2eHzXixufZEM/6U2Cr2LLIgwb/zzILagG432iByw5PVtwXD+9E6ye4q+8OXCvrZwCe1Sm/rrpTYcIEFrlH9ujolXdynOpfl2W1m79V85xv7myyedadpdgBf98hYIBfS13BeDgRxmUhIvxayp+HmQ6tnl8mAYvC23hWU4/Ep+Ox301S6rAhsv7bCIXZYGuVwxpWV/DZovCh8NCe5FmqBfa6BxaA2p+qnCAEqkvO/pB9NjaE2DJB2GfVt+6OGlPEYc5+UOpWxMFQw8DFB1DyJxpvHNxbVQ+VBic7cjQl/QeBKE5fxidJdXnGYfGSuuHZghBFkIZCJfrhMbcnNJuaGFvnNaJxfd0L8P+jND5R+SjEgjvJPh4IrqPKbMXG5v3ubYidIC48Md01wBQKOdYmdlLtkq7vcI9rcUdWUr2GbFvksNza9SmE1yGimVvZ79j6ksLp+3WP0lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwcsoJNdbrQJXDZlew3/ExS2Fo5DGeXnf99Mqrl6vRFFYbkJUuoHbR7Q835Da8c/lJIh0JTNV7kRdqCBsCSdqhAQ==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAd242pzTOEVuUqM4eHs63R6Se9WNvdM2fqZmQTJbTyuiDUbNeQlfAF6u+L2ZDER7UC8M85gaxeVgGfGZblE8wVSjGa5SfLaMGp67f1S+IUkqgEPm65bDuRBXCx7yyx3/yKb+iNv8bjotkqwj/Qak7oxXAxPxB8YllSDjqTXPOOVcSld09IDakJ7OuHq/6IK4ssE+MghdgkcApH/eVeoW4K0fv2nrcL9OmFc76/68Jj5aPOS9g2GuGfdwpBHr4LWyuAxBeV41TJ81p+hTFrY0myzJNA4HNEUymAfEHj3CAJZLFM/2lg7aUqRpod3qfzwJ51UF7RkOF4EhmGRKsnkazZEKPxKXtQx9uQbvC1vj54Ey4I5hKb65/WjQ8ZOPHNno1aLQ44Of6Ctu+rlqaL0oIqQ8L5YLVQxIj6UFoZT8enFHetG8PS2OkZw5WvPBXk8H7U7t5jXpVcIXZ41nCjyEbGIB7f+rjaGkXpQRrf2x7t2I1Q9JIJOQXDvtE9K8x4J/esBxkV1qJLrm0C1jjpRpT1FH1hPfSuaU73qBhljNF3nH0HeHo73fmkRGPHApRC3r0nomrs38ydjU6ekVwk2zlr5sd47qXYTzgjT6EP4Lr0uorxfMEfwkiP0lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwlOHSTWCUUKI0uUqfzhlfHqAqwzMkl6IXm3pLtKDHCQL5/1OFLehpWmbt9FfAgsJboDUzu7XNeJSXHw4kq/tCAQ==" } ] }, { "header": { "sequence": 6, - "previousBlockHash": "D18C0BD715B3742DE3EE603FA5464013713CD3EC47516601CEB2F1C92636A1E5", + "previousBlockHash": "C6483E637A7C3B56ACE0D2ABA2074F616FB9C1F8052C7F432553F6A9603EC1B3", "noteCommitment": { "type": "Buffer", - "data": "base64:z8pNfaYp9hR0Vdfjvcxw1dNpnVLFozRRKdUNvIZdhFc=" + "data": "base64:e1UQAsBbv/EjXBUTVUz2xqXiMBgHrG5LBhC7mbdgoR8=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:ACdINcnUrHT649NvK9h/Ueb9HjNPli5Iedp3Nv9LkAw=" + "data": "base64:7uZSUdQhZQjVMTRGv5K7V2LTeAIijUEqmsOlmGpjjlg=" }, "target": "9174987784651351638043000274530578397566067964335270621952759689537226", "randomness": "0", - "timestamp": 1719441708372, + "timestamp": 1731435261348, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 8, "work": "0" @@ -454,25 +460,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAPqmLs3kEtsp6RDX27GEwYjJ/swB+dCIticFhUkR5PFKRpqDK0qtlfAz05kPvCEMeXTiuG4Ej/a+Bq4gWGIFc2oKT2nErneqA9qEfiESkRzyVEi/cjWAoGaWC0sHhhYQoJDKNCEwQ7CwqLnkjtFm5kdd5J2/5/wOt+KtPD7B/NvEOMAM0Z7vsc9SX8wcI0sJiHCMVeemCTG1+KEOJSrhI+L9gtdbWbGl8MyL6gpBkLpmM5YXEPgGlOliSUFUbwq7aGQch2ITMSK81YsTTf+7dn3up8FIROe7OVQIXcd3gaFO886h0aL/+z9Ef5/GGqkipIDJgzVZOHhRivXi7ZCSBEsqyk7QL9D723uYGFWDfht5tBDsGse2QVssYTOwctr0ThSDur6eFui9IV0qbm06ksd3DvwxGryg4UEwJEL+xBqp/WYhMAlW2wwSSqXHqqo0DjOQirJyvVE0b6xFWiDQk6TzgE6+EcAAgltmqMGcf/NSiuXWBk5EEAxTljgdivN2d7Z4R/G1PtOMUFrM1l3CdcfDmFPqQuLZ3vSFxqY9/PguHIP0qW9POjrk93IgKsq6GN9oHuq+/zynvjvp4VXQHEkZzPZn7c32mbzMiMBhbklAt3XD2DIIzhElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwcwRDzsXh5cxHX35HkJ1kQdotmhaWO6ytlzJpO2q51d5Wopic26jC3z+8ZHtDu6ot5FKg+d6JvTL6gtXFuyhWBg==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAti/RhPnw4E+do7jzaE390152AjN1K+hnXuy2wpS14auD6u9mUwic9tekNwLM8FKoYDRsHjBREewy038h1Fbb5FSpe4Q2agLCemESqbzjwaeZ+5vUm4yqtVQRYuxK1n7ljAYBYKL1yZ5NA9PO6Dt1B0o6OGtIwAv2VpRYPP6m19UYtzxKgARSybmRuWcfJ1cslAuCo045eSrGpGJcJ2Fxi7TocKn6IzR6iHRc4RfCg1OOjkRu6BW3ORBb0IGoMequbDHaaU+16StWhygNQlaF0frbCsq/rGYUqBazhbyI7jE9ulv0fYURQ0zbWiZwsZZZVKBVLyBnHhYnsr5i+/3eNEcixbSG5zMe/wB+CdtkYTRTYxpUhtZETpfu+qsBXeQmgkERcqKAYK2r4HpZST52Vj6yMZgn2+iWZWsnO8xMhA6ML8rigghUuXNgKRic1KVoyaO+mZdzlfV2NAuauI5WTxFlJyZRV3QffbsmOp3tmrJQincF4IpJp6MxVwxwUDe43SPJedKGKg57jAgr410rJz+Pjk8rVIEVYMwubMu0CirTUUo+NwKUFoxfyhqwX9tj6lelOiWj32oscEeGe8GJelbgCC+xbDbtVBAi/suPdvuHAiKNhrLYpklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwC3YxehRLmMQbkQdK0nj/+dD6BfLaG0kN/QtELhfUMUHkvewqtPMGHnYFaIcPgoBWZC64nSsQiEUjgXuLIRqQAA==" } ] }, { "header": { "sequence": 7, - "previousBlockHash": "0BB4B6B6E56E8C9A863EA4266D2D75D79E23E28779483EE7337EE4F757B5F049", + "previousBlockHash": "386D4E39CF3677624C61332786FB14310D35D104D4D74B5F7BC75FBEA0A60490", "noteCommitment": { "type": "Buffer", - "data": "base64:k3BDdZVJODTWmPWsUvVNBH54zyAJBTGcYegReQeHhyM=" + "data": "base64:/MoUxnmqrVPjDD5s2TR8yzClwWlCPPSUJtvenC4EPQA=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:cWMk8WHQB6zLDM7ilPcWI09ki2ensf/wyXSMFjhf2x4=" + "data": "base64:3E7U+VkqK91aWB5EXmHJDGLTg0VcrE59QyXWnXF7ztM=" }, "target": "9148187795366513087508709149025146424715856256637674150531751753357577", "randomness": "0", - "timestamp": 1719441708837, + "timestamp": 1731435261635, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 9, "work": "0" @@ -480,25 +486,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAbnLEHaOgEnj+BuzPXF/0zlnW7ZxZytlIqi2EsXbAkBCqmHLASf7uaDoyOH5QcbjMY3qwjgHuoTaKdCvKSKFG4Ak0A5M4NnC+PNDyc/mj2ku2IVrA1E8BmQNs5wgFWlOW3sy1OcnkE69b4ln1n9He/uen3q/EsmhkJNZKe/OftXkJB2j+N+3OBGWSUaUEbBh2gaH2JmXIKHdBEUFngu/JEPXNOF2iyQaaCIEFNkoe2uyxrwsULYDIYnFVR1ZMWrQ7MG3Po04muutSIXMtaky7S+AdCk2efHNJqzxFQRJaA668/pnQVHqkr7z9PLdRt32mqRS9baFtsfBJ71UZgOWgI8Jb81Lb5k0BNfWLrg/PvOLrAs9VhKqQxgffmAL3bY8T06DrAcIbM2L+ac9k10XKsRjejBq46jcbB6Jnzk22AI4/2+pqZSzltRAddtOFkZUmQvL9EMSEsy5vG/dqomndgeFpFNA3F47LXtmFn2vXaraiZbovuFFRGCBFx4GwQCXo5IPXEkRDU8REmx+pC4vuym3p7wZzBII8TkN+fqiCwSSt3xaemYD6GdKuO+LuzPInqFjhNK0/OLOKk6V3SIVVhpswyrk0UW9hQ2RbjsR9h+yXibUB4woaKElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwjUHz9KZjRvDfUpZtLjkDOjuDQ5op+Z0l9unsXY1Uc7O4Nv2qgAa2IDLfi52UMd8O80h1yzGqc5b07P+sKRtTCg==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAZ2CoOMyKNGpuxiLYetAaerdzH5ZAesFqCgIwpIlewGmt91qX8w+wShXx63e/CpdYbWjKU1SzSsLjRDrG0M0x3MyAtRnJrW/hocvNeA/X2cCFFSLYtSLiAl3fTETorHBOyTk79Sl72lGYBLSfI+pFAfbrmchWH487daPqdXxQ3pQVqzeKVVX8Da0f58BWo7tGaaukeNUE1ujKFBCStPFyY7c4gnlO0dQSsgTWo/PRqyW4ETqKvu1VO3PqrFFjNNFQw6/xrOLp88W0ieSQNer+k8tNMgKTmuy7WDAW9SdipprQhwk3BRFZGTnYOM9jxIflP9dufXwt8SMFxIS6IYTOUdt4fIe1NS8pHocg+a4w0ASMbK46rysEKRv3NPnXg5BvGaOK5oG6L2O+sHXWc//UhToIbzJruFkCnksI/ifnjEll3M8FYBsVRA4OkthzP4u+hq+4RGnI2QeL34T9SjeohTbww/S7nIzJ7WOqT/wLmiQulxGhBhqnfHeTvHgpYZVtrrJl6bQ6+PPdmg6grPUWYiGD8/bkpGUWYByOXSBL/N/RCz/oEvZOSmkmWhQIW1RKuzTnHaTn3mxBu1srJT2Zq0wJUCtWxmiecAtjexxVe1M1FfTxtf8lUklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwgr9ECxqniXXzgU1p7eAb0W3r5dTN7d5BoK09TRVc4MR5Jr3Rcy+2A+3KZd1tvDmdNhqNSo63QKSmqSqNbQziAw==" } ] }, { "header": { "sequence": 8, - "previousBlockHash": "DD2E10F6A5EEE09B16680D21855369F2271D9FA73BDBC300FBBB891049F3D7A5", + "previousBlockHash": "966611617A8EF9D28E468F5C94E5F16FEF8EA99D4F8245581FCC16C979E41BFE", "noteCommitment": { "type": "Buffer", - "data": "base64:ml8D5iykqwVr05+UxsQ5DRamCVvVfGN3SswiLeCHW1o=" + "data": "base64:jGKw0ttxcK1f5QHlZ2km+6xDsXRSj/Et2H7shnvk3G8=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:2qqjRiEepseDBj20XRTF6iHPuR5wfZgMQy9TzmVtcqI=" + "data": "base64:CO5KMDPatu6aSUwvEgPbs89lBIqtVJ6WBk6axSq7slE=" }, "target": "9121466311864876128923245652724724632104869735746188813030060672759072", "randomness": "0", - "timestamp": 1719441709303, + "timestamp": 1731435261928, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 10, "work": "0" @@ -506,25 +512,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA21ilbjArKP+C4LpwOnmDLX7uf0OckijaP3ilua55UYS5eFH8tRd05pWBm0VwtWA7TsTogKdGp3v9tVXUWCj5H0Vss+53PruNDYmqcnsulY6hbk+2JT3SKLD2dZIPfwvPe6nAHcrhYQFGhVd3nHlayfjUvxRGYG+vpVIKBK5K9koNNCH/63L7QoVc7k90yH76rk2eTnAINO9qMTG1cxOePap/c2CTEOKUJCTrhzoixBC0U2NkODHHwJZaf8Z9kgGf/Tdwc/V6Uw+OillwSQ8P7g1BJLZQz+IQOyntED2VpEBoq71z1ntz9xhq+L/59b1OV0ZQiuPbBfJMLRfvlbQjmmOeYIv1XJibBHLUV+odU/bGuaZiYed5UQ/EeC4BZGJWgw/GJ/g6M2t5wujBVJdWZHdeBVkLi3PHr9RoRw+6OGANyuIGZAs9YEkrnUt7weViRmO14GLnbgJzlKnuTTRxEJd6scs6+LQJwjxKqTH2Z/kcnZ++uy0h8ZmfhHE/AqGaDc3roqWsI5KnEVa3l6hrdvfakZrIlqBlHVG3A5bJQSpLLgISA5TEKfvOXLCZgSeV+dFjGlK+/tONsneaph1AzzGEZWfcNP1NYsYmlo/8F68Du4tMTzIRzUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwzrU/k7Joho9/q9tte08Zb2lMIVC+Z+N9zu9NVNUgOw6Rrix1XS5hhpZMJg8Bpgstix4y5KecXbg8kBQZUI9kAA==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAjjhG39XJz6yPj+KovuB2C9Rf1qIhnlPaAjxgZ+zM6oS3JfctMbmqhZrRKVzlXfvM7TEhPXtwuVo6ZwqyD0Ac26F5xF3a/qsP3GdgxQmRb9y5sUGAhp8LoUouhITHwlZTVmzWEaxu4Oh4cOdjLtwBdlzT7jftYL9qd6D1G7gpveoPuNfx/uDUW3ExtUlQ/f2fD4AbSyEDjLEgFVLBPnkP5pwvKlWPTKr1UXkkRiMovLO0YbflhvnH2YN5KWqxdjxXhaZ9wQKSZxVo87Lw6MkJPZJku8oLt0j/apK1EaXSZLSMNeKuWhGMrAYJYxJecM7nD66Ja5R4KLTpTiNhi35ykq4xxGGOAG8SyRbU9FsXVBsESN0NIK+YKmJd6Q02skkg6DFfDl8/Ji3p9/EP69N08HlyLM+dzh9svXj164TLINcpBSMfWf4KOEb/vfo6XJJB4jqaiKgwVgCDQvY/CLuo8SU9RArgIXpYlCsXteOCyPEayTT7SIvWE8vPgF2Uu3ddra6hZyQFpt1PGyqbZD5t22CBjZ4sBd7IsoqLvOIHrC9C1QIBzm5VlSJl13wZS1Lwz0K0Nm/Y2iiCcst4gAcjRgoCJbd589vBBPE3MGthC52qCsLq/LA8aklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwPBx4/XMC8280LFfwRc2H4Vz1YGE38jLo1gpPqtMhgmDuNBl1zydsGvOgPoMVD9VlKWci9npItunrwuBz/1VwAw==" } ] }, { "header": { "sequence": 9, - "previousBlockHash": "68A8E7E164F55EAE6C472227972BC9F36715D3334BA410E856D0B716FE454354", + "previousBlockHash": "FF53EAAE34CA1756C81DB16109626E7CB5F15130152F57FAFD30A3B67A7CA445", "noteCommitment": { "type": "Buffer", - "data": "base64:A0EePpa2R8oKPAW/9QsU4IsShV/gXumNWosUa+SwjE0=" + "data": "base64:3dRia8oTiQ/Qmmy2r10UlcyiLobRGBMAqcWpcQWCfRs=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:lqyBehni47RhZPoC+o+W5yLgAxSsKeOSX1GGUEJD/Xo=" + "data": "base64:axoh2iVQbDxDvfxG2C7WHvxrZbac5BcAoWFocnL1pSk=" }, "target": "9094823328238119324660168503613036415495463326164889575918026009508991", "randomness": "0", - "timestamp": 1719441709765, + "timestamp": 1731435262217, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 11, "work": "0" @@ -532,7 +538,7 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAuWwvugJs406ahfgO6OdzFvIgr3h+590GcwHZfAL0wq2PgfQiAFCyU5/HKtALDLNgeAAs5M+M4QOIS1DMbbCTWvMOh1XKjRslqiADR2HzOCqjiSyK94jYw9HvhcIZF5njDk2chAyxaMoqwuHKDsEAGHcyTLTeVmfQBebnAe6NtsYKS5V+Q8O2w+FR8d/noKKHP3zht7ZARE7WcA0VU+0++mcPIW5BnNrfI3jl1uQCyzyLNAvXSe0oWmxPAzy2lo59hy2mJ//I4iTu5kfqhWZj1AVgQhRNGDe55g5Ypsr4o3ivyOZoZExuQd+azZiVtwVrbYzrWVj39Wo/W4ug1SxrQhKY3yCxXfT2C7q9rZbx/VqVwX9Z/ua6nCbvpQYw8NM3I1ty8CO7CoWyMpB8TH8zcZB78vqapSGfnsIXcv09fgXbpuV1Rqpp5qkYOi8wsYECiGS4TWYDr+xOPxEorBddzCGDRbvu2dwBdsX/RV4SjfizP97+uEP3OYelDL0Vo7j2ffS+EzRg/JdSuAGzbeYr3OgC+uBp7mil31v6KyqIXi3mJZPSyOhmqcPRCQ+/h0lDcU6vaFVV+fR5Ojlw0xIrKZBr01ILydtb4sx3gMxg6Dpb5ueEnMfoTklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwaoEq8vrFIXGk/vwRx65L89NP65JXnguMH14mcNj9aZjXLVFIcD8kvDnaGLO2yzsbFpvztR/MhA+RVc8Ux09bCg==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAoMqHq/SxUgQaX1avi+IbOmtNTYBKCB1/8JJUwJHvZs2Jr5hyD+0/bqnAQ1qndXFSBKEdckQenEwDuEv1UHadgN60HGkCqj6MAkwDEOJqCM6tamPUNfo9y/egi4KyyP/zIX5gKoK/n5EhOVOwN1Pf4OwCqnXf75nMda1SuRuFY8YZAOJMd2ZIz0Msa5rUQ3dbn1NIRZ5eXnGiNt95/3iSvOtKA8ZqwDwfAuOs3YITlHWP04zE/bqkpWq24B4HNh+G6gan4Eg66HoZSi1iRDdaewF7yTzMfaeJhdK+/o+w2eCsHB+37MxNC5p5gConKzTCrBRFeE97kO0ouiDexeA9TeaoUUr+22pkL2U+uIifaC3vGp3f8hEWavHerJ6cPRgQZuY4z6XJCV5oUtmnBW56yqd0IK16IkFT1SAMepipf3PaWkmFJM9tIt6ojd7xdZKPYUqNJDN8X5dm7AbPgPST+ooUS/4CazVFc7/PyVOwf6xpimQ7d1uxgxMHwf3ACYzP7HH+6xmmRanrFPmQmKX0ltsU+e++/RyE9ZBHLi+CU58+Kt6+1VmCZn5F522BKdh+NmaekS8izEFhqqAivDmiyjHehlJ8mdQtpXrRbG/fBZZSnWLLExDflUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAfAdTndZauPICneJ8JTvIywzsgrmu8UUORWyWXS3RZcwxoNhhh/6sGovD4XPmlHrSGhk5z00AjZzXlBeH+VjBg==" } ] } @@ -540,23 +546,24 @@ "WalletScanner skips decryption for accounts with createdAt later than the block header": [ { "value": { + "encrypted": false, "version": 4, - "id": "d9a15ffb-857b-4b35-9cdd-f6aebd6fafb4", + "id": "f4c48f39-8132-4658-9a29-becc84171ef7", "name": "a", - "spendingKey": "22b1818679e09c97ffd67ae0860ccaa55b2647bdc4e9c7124f7d1bda0f56afcf", - "viewKey": "2c0c02c7affbd92a2e0ebe4e92c26a40e2a75648edd6a3120e7847f3d8b022dc72611332c4649d178fd1146cfda95188fbd84c78476d617163b071a2115b00cf", - "incomingViewKey": "8725edfa3900dc75bd03d9b3d5cfa0660343b51184180e58325f78ea726a9305", - "outgoingViewKey": "b451d2f3e6f74f91db3ee30066d9da1bfe910ea92b96a8bcd894d4f08b6b82d6", - "publicAddress": "3caf65b0e6927c83b65dd4a4665ad94374bed54388eb75b7891d24ec52cef4d3", + "spendingKey": "5793ddee7137b3643e0d618b36a0db71e6f62410131b88092d7bb8ec072b6aca", + "viewKey": "d3b2a80c8777573850ec775d8eaf6e32fdb31341ba594fa4028868632efa105f5c4f84d77d23d83f2705fc8075a2182eac53ccc3c6983ecf32c43d909328ca33", + "incomingViewKey": "a34c3e69bd867aa9ffa90b9ceeeefa69236f11ea8fc306f308397aae3e283506", + "outgoingViewKey": "df79e781b0fa06dd9094dab66f014459c0d2700a430dcbcda3893c927c8ce71a", + "publicAddress": "fa2f1a05859ae0b12ff2cd8cb98425e50d8e1c635172597a22c91482b9a03f51", "createdAt": { + "sequence": 1, "hash": { "type": "Buffer", - "data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" - }, - "sequence": 1 + "data": "base64:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + } }, "scanningEnabled": true, - "proofAuthorizingKey": "d6fa34fa12847f56e656c4053b6ab91ee02ccf0ac38390cbb0fa82361ca86d0a" + "proofAuthorizingKey": "f5c47aef28da2ca7dbcee9df24b6866d33be434a95128a10e664aa43fa2bcc05" }, "head": { "hash": { @@ -572,15 +579,15 @@ "previousBlockHash": "4791D7AE9F97DF100EF1558E84772D6A09B43762388283F75C6F20A32A88AA86", "noteCommitment": { "type": "Buffer", - "data": "base64:t/nGRBfEM41bTDxqlaX709f32cBhvjnkDBuhenxUZFE=" + "data": "base64:SpZ5dPMipLyviCEP5j+OfaA617NA/LlVXWlTGMDsxRo=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:U/40oB03yqTvyY7aSV79yLUPad/shYB6WTv9jd5Kxak=" + "data": "base64:cvMaxsLSXuk755ZutbZ9BTdadM2rzZFSBVaKwG914Dk=" }, "target": "9282972777491357380673661573939192202192629606981189395159182914949423", "randomness": "0", - "timestamp": 1719441710613, + "timestamp": 1731435262706, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 4, "work": "0" @@ -588,25 +595,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA/esP+kmF8bkJ1NxWAyk2dmoqjlCOs4l2wFfu1/iCzO+p5NndvXVSnThaEUKtr2Rq8aq4Kq1mShyptH74ToK0O0SIyUN631Fcf6XOjVuyCb65EvVqj/8RvBA87I+m+2aLSyhl0ktuZMl3pQzPATdp8cML2HsUWiZCiTS/CUco79wTflqXlBsFEZSsjT1kilmVxwquvQt9qESHLb29Y9XWJnHSbrlwWNfiefTSvHrqVaKEaeys3SrVzyNSkvJOvHNPnpZdvTBLumnJfcViYjA0gahA1CPKODcRzPv820Zskp8hsauw3byb+xVfCZQGA5melUg9x348Eatp/5WmM3ZTRZ+61GOXhfiJVSeS+JluL7zkzgNkxG8qKxc/pmtGRsBrloy5KKtN1jY7BB9r7qqqvD0WAdXknZkqpsBFtJ5qeup68B7TAfbTBHdUh5vAbdtCbxoANOLQNrdKM7fGEc9f37KkJk9KucJdfAY6BYA3bK41jVZgBLzaA0tEAXx9N9MAVUW8Pftt+xowV3XrWzjO2G6UHvS8Y6lpNR9rc687O5ogLrKyvqE6pYXdWFesdm1VvE7E/ALgQxYPJMZRyVQaU9RXkNrUixVyo7/1dKNjlBgCg3GC9OyqaElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwrRTjhXSQIwdA/IwpRPE92hipHCD86Bd8uVV7UY700aWK5JcRmYYUTCBHlr3EaAP0tNqY6uH4T3xUxi2hMUzwBw==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAuB6a2Q+Kap5/H+l0M8wZrMtI0GKLBp/YCJtG7Q0CN5eS0F9xaSX0u3UqLRAo2D2Axkb7pvVSgKHaFGp9S4I0YaQ5ePy+dHAr4M7hVyeO9+is1OmGk8ALuF/+6R4ndKS98ymBH/rYS0wy+uJ47XjEddpeSzrE5Uou9tBJBcLpqT8DwWkCuB0SRTTKlHloQzlq0QvdtRlJIH69ytg13Mlc/+msxLQ0HrJTnJ98acbDuNWlpmi+7A4Skdu4U2tDJ5O2aaw/2HnlzGP6wLfhCVBIeFwcnfNTw/norUXlEiv0mAQgoMtIQ2J74yJMnO4BTNOcKH+lMpMKvuNt4iDkLJDUrtYA8PckhQqqEfQpK2IWQitt/sZfXGFPzK3G2/osCQBgBnhBFCc/QVYyk9TDWuqtK3FbQSJz8f7jnHfi8UoBVhXkMX/FTISxopLTnaK1bjnlUAyE3ODmVy4OHreUnyjlH+j1mOq+5ueKIjnFv6YTOsvl/IYnEQbdmECDN0eb5Y8FEAnPzbEpXM/SR3s330B5bhVz6fIrsnypWIFkIhiK8kktNkdnA6Hbs2x+mJWK1gQiMfHW6QiZoYsjbuyF0BZfLdT1+JIEBvgMmclxrbKwvu5SubQ96y14XElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwP7EzDJs+iL4Ua9ONhagbpDPpQe+K+3r+s+bx5IvguYTOfotZlabwfKNsDnSW49jmZCpaTF6fGCBdnb1gfL/8DQ==" } ] }, { "header": { "sequence": 3, - "previousBlockHash": "B69F5673E6D012B151A6E7F1EF4A49BF8982BDFFF46F5E15116E4D52163D641A", + "previousBlockHash": "AE77FB9BA67654613077B6F7F627639C623D9AF31E33962D1BA1500D9A23EFA2", "noteCommitment": { "type": "Buffer", - "data": "base64:O5Ti0VZzOH2K5836j7oYVkvXnRyaO3ftHWYowlVy/1I=" + "data": "base64:9e02foIVgVuaZlR7u3jFMIe47mfFxiw8G75yhHOdU2Y=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:DIlzKFLoKriJ60ZF5Z9nW+RMbEBDl9nsXK2m+xubzxk=" + "data": "base64:2tug36AzfZW0LzCgePZN/023igHTyguHWFrWj0gvR8A=" }, "target": "9255858786337818395603165512831024101510453493377417362192396248796027", "randomness": "0", - "timestamp": 1719441711079, + "timestamp": 1731435262992, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 5, "work": "0" @@ -614,25 +621,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAtNXI4O2D3UHH3OTDo/uka34tATF91q0lEAriziW4/jqpVdJKRQaod2TfnAR2SsYWJsiMNTv4xXatHuCP9xSDvKuKDLe53JDUBatP4wsRLAW3kBcHTNrc91Jh4Ixjc3VATU65ok079ONqvsYhQEOKEDW5FRosPEhnxo4edvpdxFgO+ScfLYdofprgR9Tca4lWQmrHCdt8qibqLTeK4MwEfTPAbn3Lz1nP0xU1PCoIIIOT0jBeAe6nhuh+GTSr9xxxezvMWC/XiS3SKHvOKKrQVUvZT1TcwWA1cBJdMBQQir8itqTArADqCF/U401oxKu/9SfNqM0stq2BW5IrReKKXtkIpsv3ZLCy22Ytuw14nzWxPUeSN4Z1VfRYVRjmpepDho5JxB/UCFL7e+P/oSZkygTfZ+0uKlPIyzRc47SGP0kgTFszyFeURXpbzzfkY+nukmjhk6t+TCbKjksYaSM1cR7ZQ7PL0Yx0bRrVoYVYTZlKGD+J6u07CzyUvWlbMhh1+EtH5nmi8zUtdUUVqNrpuY52jCucc8BqOwdUp62GL8UM8eCJxiUvJEnZSMDGndFP1kbLxCdhybgMwcu9An20LGOL20ahOeyD2/7uqrUKDW9tKEcVOr5VwUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwqaZ3rvg2P6S4roBKdX7YfKVaLgQxWauz1mqT6E7e2pQmDQhrLq2KOmLtGAL2UDIXyzMuaTnXwwUMgkd+xonyAQ==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAiEJfWG00gF6525FpJ7W9eOnWhHSK4KLvZVKnk4rxH2OMvmYPKp5tCYNBtAQ1nSTzVZOKqueiS4eHDIG+pZ9UdpQPWA81kiPkckBnqSfIFjCIZ2PNKTeAMYFvjffrD64FnQ0Y6cCIsJ3MlvHj3v9wMZyQ9NfTVFFrVzoSx4Yk1IsQo1/As195tfWFrWQY0nMvCeMu++AQEYSRti4JJgeo27WLtFt406g+xVwyS39BuFiYToTonuhv3ZheVqBf3CqFGThIFm7I7c9LrFeVGM7Nj9CNWUmp3W9rKVSI2POqlZljTxwLLzKDk+KN3LMiox5+c/pS4y5UiIo79fJ2xen1oXhxV75Yqmjv8vODovihBmuxJatQp6G4QLZPfNFIrAkMSh9cELtPtilt6wjIw3gIypogqawNtQSNU4a812KPltGkpqrimByiQZN6Xw/oFy261WrhYoZrMUxK6U578KpaLaxJ9dcI6Zw0cXlYdTB9lkpGAvqGneeyPALtcRO6oBw+rLBcw5pNAaZDKSkNXUQ1gW5sfHs3/TvN2qJDsJWoW5e+ohiOnBZQPFAnLax7AQB1lFlKi20vdvczMC9k3Ht8wNEZBqHLq39ITPtjdjJ+s8awOChKPaNiuElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwbc4Mi9RJdvn7RlrTZoijlxlbwysiELcdMWgWnLDW4FZpcuHABLlm01yNI5hDEHGle+FHDCZDqlXt57mGo4r2Bg==" } ] }, { "header": { "sequence": 4, - "previousBlockHash": "F8C3A577B51F46225FAE20DD8B4A69882C775183A262032F8B7739EF067C4BB1", + "previousBlockHash": "7F988B41607B632C6E985FBF84BE8189D4D45ECB35F0D56B678C83EE81E4CC24", "noteCommitment": { "type": "Buffer", - "data": "base64:eRIDTZE+Z87/uElJHpqUwrNlHDuCezdU3SN+XaAzfTU=" + "data": "base64:eQ/H6OiQEe7RIoO7Z6c1GIpoVybnWx1p/3qsY1EPOwk=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:5H/Mr/pypUmRoTmGXiaWRVkeYmFJveBYv/8DvQxh+KU=" + "data": "base64:+6VPMcmqsPcDe3h3hfwvF5iO568RVaSlAZATbLZcCe0=" }, "target": "9228823284279306817296266184515742822248210830185427859262273659833347", "randomness": "0", - "timestamp": 1719441711542, + "timestamp": 1731435263332, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 6, "work": "0" @@ -640,34 +647,35 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAK2c0dfSgQKwQDHZWhi98pAEQKTri9HeXcVAST303romkp/rz0+oDYCPVrCPg3HciREuiW3d6upXvQSI2uCRLUx/l3vD7t2UtkxrRU/9cI1iw6QhKMi82kbQzwVVV8Br2Cpn2n+u7Vi7238UuH66I1nDnNtMBodKhsu1zoMrbzbsGIUwgHRwBUbGAT+e9RUGM0kMBr6VXCUgD/XCYpYJN6A/BIflVu0DRocgqqFyZbp6S/j+joJqsxRl5cYt1PQ1E3X0htjCBE4XtD2m8RKkpknRFVs09l/5WxoKRl2CuG2xfR1SroDKEk38Y8m1SQCPkpIkiVrjEgrdPSwMFz0mH8rYJ2iKoFSG1soeAT2U6svW81enpg2OVuya9iqmHKAEuXWlnruzI2W7ZXOc8/5SlIDUkbFIoiSongTOGsSi/LhL2xPVgLJkc97cHNJpSsv/qID+FBKN0skuHduZ7pMghl0ZA/tGx0dfjuwn7PVZwdhZVW2tP85o9WZl0LdP5PsBJtJTmR+J+A4Q/XXC9JOwodtqMZefQTXjPOyuhs+7a1Byw2oIYGwSe983ASM02a0OxH0IbKT0NpLtWmUvjMDrHPROaANJTybw0I+RQ3FXeF0sFTuyDxGoxB0lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwoiMgzBfo7rCtYWvJwbmHLpmTA9hSjXqLmOblA/ojpWnmL98ZGsDxzfQGJmhhyK86xANnS9T5CHfUwFsGoUFKAw==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAcEplQObE4psF8dc+FQZCy4q/OzfLKr1TvwDVN1YcHoO46z7wTAr0R8E8dEBYUaaG+KNvJ3g77+f7feEElyFu/vdp6HS6AgR/3/VtgtiZd3WucEbsEnF5gWSQqVrIclBFab0gS4kGKoDM8FhtTDuB7wE9ljPalILS4djm5NbE16sYW8A9sAk1PlHHOcH8GraaXuXtOvmQBXRe2IgY8TXJy/Qmd3DfFV0XUDJaByIL9r+q5nvGCFZ0FzCEGvdNnUgF3PYK+qgom5WOxrZzML43hdrBj5mpJ+09zxWirSvQ/Ge64AQRwzt2U/apZYVefR7QwjR205/6JVd06f0A2LwN8ARaxM0XfqMh+m6WG3ucw6a0MHuyCr5O2uHmxrzqanlyjh+wrdRJCJ6Yz10Nbd6zS1P53lGSdV6+h2BVGLHgs7/up7N78I8gThrRYkgxmRDiNl7G7QGEd58iMCpgJGd1jP9ru/Unt8UfX67MkcfW3/zb33zPJWT3YC/FIMoFR/06uqGMU3erLde3eSdOe/HAKZXDrVspcmrOVadLqjgluw/aOWyqYyEoGhgz7jFoV2NskIoWgu9CGfdTHgBfpA2tQnRLkPzbN7lnQuGBDzHb9zPySQ3AJwANbUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw+xPtmDh5ee89qMOZ8uXF+N+4dH3bd1Q6tgnt4PjGOtfBHMG6y9f1f9bTi4PgRdYN1IcXH8esEjDR4vzqV+e+Aw==" } ] }, { "value": { + "encrypted": false, "version": 4, - "id": "c1f6dd22-e561-4f82-8e6a-9c971f15b29f", + "id": "18d6c694-7659-4c18-a7ff-3c23e621ba19", "name": "b", - "spendingKey": "bf4b4f8c66d092df8a26a7011821f031f8ad5afe8b1fdc4acda68f555dc16752", - "viewKey": "589dee75144a22afbd2a397ee9607f5ddc375ff63b3b9fdf49c0ce73b6789f65ffbac13dfb77c2b254f6f1e87f426359609280253dce6867d59dd99d1c8faf0e", - "incomingViewKey": "469135ad8fa89d4673d6d1338e19e2e6fc8a6c15d63084957315b72229451b06", - "outgoingViewKey": "270b99007c639c03e1eef6cf30afcba910f9105461590e6500f7f9339cac28ba", - "publicAddress": "7f64a69fdbcb9360ef620bdb024b781c162c7319bb5a107a087dd13abdc09466", + "spendingKey": "458e00b840cb7f33a541fa5c537204f31ff6725cc2f8240a0e4d7ca266650fc8", + "viewKey": "2c5a55e16ab50fa5a4dccace5cdfb0c613f01c5c3570e24a3899446c18489200898b2a2f3b2a61a42d0451e1c446428304df545bd0050fd3bacf29267b7f8841", + "incomingViewKey": "14dcd1dc58ee5c8968e5252a3a7fc67894414960968da61aa32042627c088405", + "outgoingViewKey": "1faa11442e35ced72471602f946cde6826046bf83aee3f728addc4abee4f5a75", + "publicAddress": "35832c259d152a62bf6da0f297b213142f6096058d0227a17876e6008e37f0c2", "createdAt": { + "sequence": 4, "hash": { "type": "Buffer", - "data": "base64:/ipYiaG2PnsWFJVFjVpEDDjM5lxOzAzZ4d8hkQR/kU4=" - }, - "sequence": 4 + "data": "base64:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + } }, "scanningEnabled": true, - "proofAuthorizingKey": "f23a343fffb68241b5fc6e287a79c2e65647d1179a3429f14caeb0062d2f2802" + "proofAuthorizingKey": "e25de24adb95a8ce1d28c460007ab8688dcb5d27f72d18bb1c38c767ccf9b900" }, "head": { "hash": { "type": "Buffer", - "data": "base64:/ipYiaG2PnsWFJVFjVpEDDjM5lxOzAzZ4d8hkQR/kU4=" + "data": "base64:S79cO8iPbqnG5RDecA8kKJk3/ysS4CLjQjRMokioHmk=" }, "sequence": 4 } @@ -675,18 +683,18 @@ { "header": { "sequence": 5, - "previousBlockHash": "FE2A5889A1B63E7B161495458D5A440C38CCE65C4ECC0CD9E1DF2191047F914E", + "previousBlockHash": "4BBF5C3BC88F6EA9C6E510DE700F24289937FF2B12E022E342344CA248A81E69", "noteCommitment": { "type": "Buffer", - "data": "base64:p3+mAe7ZXnQg0WEvnZ+ueOASbdoQy9rq1uT1ZCCWDyU=" + "data": "base64:MrpMDuudlNdcxgzOk2u0iA5rBV8EbW8Y+GYLPWVXuDI=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:jIxPONrZpSh6C1zCW4eGEYHV73i1iEwB0NDY0q+JH8Q=" + "data": "base64:m2TSTPuKpCeXScGxjIEi4DMCcJqw9+tOr5ep6WM81bo=" }, "target": "9201866281654531936596795386791503876274441021197252859723586932895305", "randomness": "0", - "timestamp": 1719441712010, + "timestamp": 1731435263636, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 7, "work": "0" @@ -694,25 +702,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA0PREpfnx1UrnX+G4RxRIE3vdgJodv+Gx6c8Wj5Hl3vCB/F0eRwdvjmvzJXUoAb0vH5hiOyE38jRlTvXTy9Djy/yAu6aZsSD8uMDmDYz0JPW13VFFV+uXEhI+BAr0yWlYLT309wTlGuZYTRERzlaAfJivK9J0HWmlRYI//tLbKXURurGgQg0ydm/y9dr+yy9SbSr7UfF4vjzRzH34PwK6kldpt2mjcXvkimOuQkkpmJ+xzWUHhP+vBtd4xZeFn9jII0kmSjOgHkMa7W6krvRwoE08HS2YdzycTbtNAQL0UgXrgjzr1gMcdPQJMsQ0tpNdOoOq4ES/RVmxBFGoU2v/nz9kEwuUGZitKQRwgE0cKUzx+cJ+vinOSQHCjX7EW+BvWy96VqbH2nEdFbQv3fIEcxEWTFl2JWZxL6YnQI31el6sZ/p0hhmLWnBTRI27xe2v/qKCr3FYWYf93idp5eUWvX5f7xVGt02YTDnOguReTs/NccxhA6ngIfo2BNeHr1xqtqYuqByks2Er5cnEjZoHYtI6+bDd/ubH2BFZtYei3YPbAFV5ek0utWHr9lg6xAdZrNMOzrlH0rZtQgN/4w8a34Pau20xBcFcSkXZKEzXx3N6AsKH7qFZiUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIdhdPRFQi2+z4y4df/EZISDxdU6TiHEdm47T18UKlGsrTwPVFjtibl3l4GX6er/2jc2MmRVhY86YxBYnKLOLAA==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAALkjyUKyHPaPpEjNbpBLOaXLBjUsbozHPb58sUbLameynyykz59505c+qtsOMEcwYt3ymprdF+uRwN2YK4TXmyXPoWQhvVhyrgqI/IDUQc56VypVJ6Zqp+JTKZ83VQ6D0pTsC99Fb0/RlZA8Da8qbHMWNc3I+wlrAGkZNBCVVL/MFzy0ctxloUzJJ3Sq89ljkSXpJkyFN16qpZnjoX3dfTXcFY4yx9bQVQwXBH/bZRqKNMzrBMtzcnxkN+QA4VjAqdkEvEb7T7zNw0m2qj354I1Fx6xsBqcjAEB8pmDwoJp3clRll8peerFAocs9IcRRl9Z8OPm420Nyez+KQCf9Bvr8rrKGK+xklo8JYkPOcaBDQ7yEa6N1XM0Q2HKWKxuNrQ4G0YDJowXojEPJTfjiDhcKNbWdOofqqGHyuDBNLuEMeZ/2rKj/6hzTzws/Qt+VzN/is555uNygRGt6VJvgpMtaM2PHmOTh6TO7u7bd+bF8IVhWKBzKBuMed2UfDgUeunQ39FiOQ8pgs52KHVsN+6fmHQyZoTOIarztRvulR5dTb6op14VacRkAlay6VyLe+AqPa1N0QtLEsbZRFwuKahbnUvecmryiG6rSg6gWkRStKrg46K2mZV0lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwoiDZ35gjED5IjthAXuf7WsjKfrL2SjXsRiQYtQT6eyGhOH0mabfhpBhQKCrG7oCGP9Kgf5h5exODEV4vkCu6Bg==" } ] }, { "header": { "sequence": 6, - "previousBlockHash": "689314CE4EE1C3BBE6EFBA25F64A8596EB38F70F25BFDF6F1D71448D82409B9C", + "previousBlockHash": "A0EBE2ABCDBB65B0494DD7F2D351A48D961D03C9F0F733068DA271D9DE9EA52F", "noteCommitment": { "type": "Buffer", - "data": "base64:EUSZDapvMTqNwZ/wIVqbZ27l520PL5mcDpFjqO4J0Dc=" + "data": "base64:qvYju8b61UW2pfKKiSr6bivhJveG4oy8PsSqKKZfJz0=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:wYTT4nOnC08v1j4QD2y7+y7Ltj/sLzRMAo0DG+H7fMU=" + "data": "base64:RaPZujniCBhlVr4f62ZjbYbZHHwVVFUhosv7PaQ+ooQ=" }, "target": "9174987784651351638043000274530578397566067964335270621952759689537226", "randomness": "0", - "timestamp": 1719441712468, + "timestamp": 1731435263926, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 8, "work": "0" @@ -720,25 +728,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAwB9MZEdyIee95Jkofyz0wz8rvOnpqd4WcrhQhDMGyuWurYlBXBftPylZZTDIpw3DWvRNKJ1kiaJ+fFs0pjlJCImf/meYPcbg+F4iQa/rtJCIYwaRgPppCPTQ4cDYvU63n8wANuR/TUXtp0xqO+RD7ugTe/+nO18p5zeQD2K6rzYJyefXm3/IA8oy8RlVhZhuF/CsgdhO4jlLwKn7xJI6uVrhsRmrYgZ+cZWAffK170ao0EtQaUbImLdhMpApQuwcYalLPvSuhZsmmXVVxw7kLELWZjY4QZgL6RoxR3A+MV7qpc9CluvOH4VKsT4kO1js9mAKyVKMFsaGDnxRRu8+LGacuXJT8CKTsT5huUtjTpOHy3YSDiIfW05QcVRWgCwFLQGrqOFpa/N3Tm4PUBAH5eOlA8r/JGQa+7wuB2yfGxVv9XwegM5aVWVvpua9u5mghvP22wmWHmMeHoIiP2afwtXtzn2VZyWqYcRidwjwGcMyqtGlEO3GefgFdjJ7FhtJMEeppUmk2EnjX6efHuSOMbzrWNCamS/qq0zXeQq5MHZkL4pEwMdYpe0/uGlZuZF4Dc/ES5cNM3M3SRrGQhFNLJ7aWgJzKthI2mobBBG0/n5y6vOY6lIe0klyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwd+jPU9f18mMXt3vsQOcw7J+kn3dhQCpscxHOFR/KeC81K5IG2VuVSbddtR8u282W3qB4+Zd34WAD5renxxe1AQ==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAHsIb51ouncRrw982CtAi0l1cp4d8w5WblJBEMT3tX020lHXdEwInTqeJLP/iY9VnMrJ3NzZsAZPeHiN6PoVUd8xY+7eDUBuqK4XfiT3kzBOxNPsnQQQRxFk2HAQ1OtAyHAMMX5WRevtcTM1Ah1bcVP/g40FJGJetXZiUEFSrwN4It0P+2lRjgPHXIXebviOhOvuGut9FpRONi1S6AvKGFe7DKWFzhnk0PufKO0dxAEaN+3xPaHB9dj/HUIGqBd06b4gYD08R+tvT5nDaVuUMAY/HfHVNyv6VWzcqU7afSAiAnB60c5UdUaNOWEOKYjiCKUa9GrrPQsSOQtxwflB2Gg+flxaRjS6npXmLMjHFyG8m2YfbES0cOAroW4+PFZYh6Qz+WTKQ912bIsUvfzltqCT+CNd4PV+hrIl9eF1BlgRZQDcP8ox1JIa3/C8X6brt0K2sIKqwu7xsaHQbrRkKX3UBA/GBTGkQgoe/qXjFYYiAZMXjiZoXTJmGqoaIYX04J32pW5/O0GO2fKSrCAf6dxneNVpyCbpXsrRH1pcD7YYxjxRFy8JD8fXiA/S+4iAg5s2cUVKfhCR1VuiwHLLErObzyo0b35JErMQ6AK4qZhzE2RbAXvOZl0lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwGcVpXIeVaoKSBAnYfFOEDzdAeQxIfQYvxDL3w3FpQ49zU7jBT+/Oc/ZAa9quByFHA9jEX24OmeOCGQfXnrUUBA==" } ] }, { "header": { "sequence": 7, - "previousBlockHash": "18ECF56F6B123864351D989605EC8FAB4C24BFD4393BB6FCDDDE54E42C8E47A5", + "previousBlockHash": "2C6114297C554F15BAF885E9087874C7F9BC879FBD7D8B9B5F703EE94D475238", "noteCommitment": { "type": "Buffer", - "data": "base64:VOICD+MfsxjYm5nKiJ5i26H8B5ij7LYE67TfmvIGnCM=" + "data": "base64:silXF2Ht3o7zATAsTylQEDUsBjRQMFCQ4swVhrxcXho=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:hcYO831nU2O+SraZYl9+P1G2gPzqzcRE4xMWIBJq5gY=" + "data": "base64:PgQeE0Gztj5duk4kY0ktUny1ZLHNT7lOhWS+kdtRWWA=" }, "target": "9148187795366513087508709149025146424715856256637674150531751753357577", "randomness": "0", - "timestamp": 1719441712938, + "timestamp": 1731435264223, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 9, "work": "0" @@ -746,7 +754,7 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAzXqmfHoGn3bU85f/Ho32mrrGpf+a1i8zalQyF5dqFQCNJBER8uHVNIJB06fECw20Os742eM/qSXRTTx6Fb8ndjPuu7D2sB/SROe0yod2db6QtpMqpJ+hroCaWb0GW7bS7Za30ZOLkJ5guJESPduOn4l7qa0H5EHxnB0GqyG/lJkXRwFGQPSd6sFqXC/86GVvtVIaZ1b/8w9iD7svVmtHsGMe49XFeXGwzik4a57E1Ja3BIMtYhOQgwWxi1kgmHJdZ+RVUJnMpdG/OkxvuOsSfXYkV9U29+IOKL5RpzYdGzELq6npjoo4lnRIZIYuE8xs9u4go7wSJ2XRB0C1XchAEQfGb4BA4oOjLskIRhnzqjLGTORjhVDnwozg13QV3Y1z6moVGKH15E26ZWuicVI6iKBHWsUoy1Wdyfkl8SBAC7ABhWy3gQ8YsO2aW5rL67GqlSIzjfhuTFBkHbJjGsv0Q7lNGkAIUmoNNZ5HvuZ6B/bF14Yrh57kVIaJ3QofdLu39g3r1CwHy6Nciw1fOf/3fKggvJDAvQPMPnLVIMmnDce9vq3ur24qX+JIwAQYTTuWpwbDf9vOKBfvMqDxNXGkeuamS55yVeoVmpjiQAl6jK2HrW8nkbsaKElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw0eqROTMQ4tkbiKk4O0z+Hp1nYZIoviQc9R4HXTa/JNiUsJbdb45S5i/tUQWr1fot/1SHcn1z7LVtS5dNj3xEBw==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAASBkrd8+yS6UO4NMaNioQhxmtiHcRUnDckAfARo23j2WN0Z6LEJT6ObApxixooQiKLm26ADi2BGZfTXLCoQKEB3L8jBGpdMOSh8Va5Kzt4aWpkS8hS53MUQj+6h05gnPUWs4Te+dewTdBbY0o8nM+LzNXV5Vd1Ezk/U5vlMTZsHYHILd4rtQ1fYJq06t8RvAdXe/jb+8QJdBSq/Aa2Ag0bsQpWw1OPYL/GHZnnk2xxnaSWH6KqkKqwA7QyF7yEoFdbrbFbiKyz3SurpBM0bK8z8zptZ65XJMM80D8RrDBwGXsYCZFu2eG0DoATCpQ64Gar55Bi3WYYf5Ip5mN7kFvImqpKR94/fkwW6/V7T38KNQpjiAshyQo4XYjFJQRawJvTrY7WQPuN2QNYahKR33UhbrfyRqxnI8pXUIipVLHsARbs+sjasJ8Y8EcKPo1XSN9RlTTMT60qyY82Od/vVOzGDVRp6uzHr868MpNmeYxhkHp+w1wen0bjIDKykZkOe+V5m8u12dEqG69K+LKa9+ltj9P4G/W0dwSsM/x5vPyqBiAa+2MnnSEff98Kr0SwwBHS3GaGJ73nNaik7WrrbrzXDT4kONVNoquVW92zU5Q8DsXmbGnSzUd2klyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw/6ddKg+qWRigLQO58Hj3TyYIUzLUDDxqm8p0aPGgxIwnPmBiCmun12uIEyc4/5xEHpf6oRRjFgpDP7jxejHmDA==" } ] } @@ -754,23 +762,24 @@ "WalletScanner skips blocks preceeding the lowest createdAt": [ { "value": { + "encrypted": false, "version": 4, - "id": "0f3912c7-d0dd-494e-8b8d-f0fb45510f22", + "id": "d46be24b-cfc0-480b-9de7-9e1ca41fe457", "name": "a", - "spendingKey": "1616f3fddfd2ccccf7fc1ba6d43ca07ebaaa7da1cfed5b4df8a05aafeb5ea0c6", - "viewKey": "c2d1aa0928bd76503b0b02affb8ee9b3efc1238aa34f595da93d24acf0b1b03274ea6a1474eaed8f1b451eabe3223a82a013a07373ce23e060b13b489d6bbd61", - "incomingViewKey": "d92c1aaca2cfc6d8d2191b38cd371ff05113e8a881cb8fc7a5f780276d83ff03", - "outgoingViewKey": "75bf194d2ed96fc414dd7731be658d53eb695481a043955bdce568a5034e6826", - "publicAddress": "669c07cc167c86b1b6cbb66dd3ae7a6a4487764d134defa5ff05c8aaeb15f1ce", + "spendingKey": "781c7deb8e2936fbf26b2c72706ad9ac20bfe68eff2807bf4f3e497792cefb56", + "viewKey": "dd1d976bfd16194b3b85bf80c4a1d812a907987bccef07299a7ae04fbe45e070b0b61a9c1ece2e168b7f685a3444f0862aec5290435330e9eb42157901ec32b6", + "incomingViewKey": "15f0ad8bdcca4b5cb440a754f3956517364f15d7ecba6b5b0e2dff2d6adcb606", + "outgoingViewKey": "68e6b26f6706fcf4562a9db9cd56716bef430a68ee3f353bb4366773e20cf21a", + "publicAddress": "826cfdd0acbd2f2031635dd5ce32de4d5118e07ae5f9c14b4fc79d6addcd28a7", "createdAt": { + "sequence": 1, "hash": { "type": "Buffer", - "data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" - }, - "sequence": 1 + "data": "base64:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + } }, "scanningEnabled": true, - "proofAuthorizingKey": "1a08001b02f599ef90837fe444719105d73e8fdcc0f0c986a5e27a14b8524505" + "proofAuthorizingKey": "45b33a52a8c01ed45a63754b1428e99979121fe2b075fc335403479d3745b302" }, "head": { "hash": { @@ -786,15 +795,15 @@ "previousBlockHash": "4791D7AE9F97DF100EF1558E84772D6A09B43762388283F75C6F20A32A88AA86", "noteCommitment": { "type": "Buffer", - "data": "base64:n+sWWQKt9PbwH7itYIFkNooevniZOIfYBzKE0+xGoSI=" + "data": "base64:lDxi2MvCE9+VTPSDUok4x0Q1t0vDBEXU+GYTn8gMdxQ=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:xTnWIr3ftgauGO15C55qB/rZQSdUmk9t0gFa/WD0xds=" + "data": "base64:gpFv6CyAon0VDWgwPUDcy7bgWxQ1juozunZ8CcBR0I4=" }, "target": "9282972777491357380673661573939192202192629606981189395159182914949423", "randomness": "0", - "timestamp": 1719441713706, + "timestamp": 1731435264710, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 4, "work": "0" @@ -802,25 +811,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAaSjZbAIdjtuTTaVpUn0OR4V8vg5kicC0o1hrMufop6Kho3YexaBPGIarOwPBk2F1PRukFnGDRV0wNtoSYkGSo85WND5oQCDoLzUlvBd1mzaXs8lSAOU//4uait4ov45uDToS2tldx5nEymYIOss6fimjQ/cdlqsJ78sOOTZ3brIJwIxU4lGLNbyiFpH4u9GKxD783SV+YZHVajGXdYNngvYqlxKJOvF5io+6McKWZoyka4LlUFtIK5aanCVYzAJShZdBvajRZXeVHkHAa+tqyafcdDnNx5Q6indRxTuQJnl4zla5xh2fNuc2tp5IHpeYr3bApMGEGOZ1G11S+W7w7vWJ1ZkUwP3I8ME/UA2sIgkmK5tIuwPM3QT4yhvdY0840nVFs5XE63/a672GXHa1OQMirduB/ajEpfwnL6LV1lj/YLt6ooe2x/AIp8wsvuzdrRvy2M+fUlCnq1tJuXRfD3fl4yx6iZIG3LwWE8A/zr6C/RUWO6hwTAON5+dSzSSi/hBOcDA6XqOnQ/e5I8lwFhklECpZqdfR9bOuo/zdM13wnEmayBU/yuQfQkGr/ynFCAQtcSzW9z1ujtnC5y0hUGKcH+xpV09IgKEpCH/nSbCqvB6g6wwNUElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwjk62f8n+8CMYl8g8kq6FzMdsckL2QLK7rUji+URQMm3FoOMmFjiB/s+r2h4wJ+45M7LgYOKsrhudonvGuTujDA==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAApApRkGckCJVivufsprQVTOFAXMzvU7+P94kOkDD7BuuDgIouyrTyEr5wWNw1cmTqtMxf7ykyOcUvvX5JeA4Wc3h3996Xyz+XFjOt1SiRc/+yDV2SzKJ0J+ZXMNwA1+nrYldhnlznhrg/bMsYQutZ4sheFZUBTYo/WFy51tA57x8O+RTt7WvebypwCuLQtTt6/92LrRDSFz8tWPDA9rGFo2pmhIkvw/cvC4OFMnj85D2gN3xvqHjUIK5olRkRU6m5/WeR+QbYBQnRZb4txbizlzqT5A9qGDbMIkylH2OjHx4jCJYsU2DICi/ogZsJKoU/puRA00NJ6ZVeYWdQgmwPoPoLhevu32tiCnhB5cqgBcAkq809iMgx8fllZUN7TVoaMGpPesBApWuszA19PsS7cGpBt+rQplU7GCk9Kv8Sn9smF8NqxEpIvW3kCp+ZkZ75Wpj2chFEkCvJFQFoHZCOSyEknIggG0S2nFYOAVZFLTFObvKsXT9sO8QLenm4unYzby8Xu2TvOvRz2TIc+7/VQL3rY/LkZWn+nu98qrLpQRZ9J+Xb9oAneoSYTq7c+YzEIsv61ffZZgx5Q16u5TqxdeqpFlXXsUhpYVWFQnfLj/4xHrsBs/EpPElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwPXLNENcaiTj+sOBAmfJftJLR4VM5Yum1qIDEBDmIyvODt0W1ZViE8f9M8H/4ECmrARNnDNh/yQmkrU0+tBfJCQ==" } ] }, { "header": { "sequence": 3, - "previousBlockHash": "0E8FEBC9A909790C9187E134B4A60AF90DB372D207886A2232FFDF775E43227F", + "previousBlockHash": "B3B7BE4B76924D3E318B099C82B86602C1AA4416C06763CF754200985D5F4E71", "noteCommitment": { "type": "Buffer", - "data": "base64:s3Z9V1u6TfkPU6X485a3RY/cnkStRIntVC6kx9pmr2I=" + "data": "base64:Qf7FoEsfeRqpg7jgMlujrqvyNpHBkDh+MRUpg/gB3WE=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:npxn/zG7ccyTfS/RVk6NqFxXNTAiBmDp9uh+4KyoZZs=" + "data": "base64:Nga5H03w3wZ5YwWuThZfKWfBS4WFgTtMvHkOmJDK+P8=" }, "target": "9255858786337818395603165512831024101510453493377417362192396248796027", "randomness": "0", - "timestamp": 1719441714178, + "timestamp": 1731435265013, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 5, "work": "0" @@ -828,25 +837,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAMKtT3oq1JfB+hNdK3rhdddiav6Gjul0r6/uhW2xqWG+oLDDwd1mtek0+3VH3pmV4IeauHL2+A6B+iZd0kEYzcZTkv0NyR2i/nFwR4GwgUu2ywL1s2r2BdLgoM16l12Ihys7IZMDZbfgZCGWBXzWPfvZXPyXw7Q8BrFwukeE29bUOKBlfTSYgaQtbmNpDP9obaj5by+Ld4Wo9fuJlBbawRK/13rI696XGxV1XvTdj3vWVYQQ2OmN+8qRHSpo2xiCg1fW3N73VpM2UorqhwVSAT0kigxxXpoRtXrl+FvmHwjLECmIZQXZ78D41fdn7kO6iyAkyjKwh705kmVCLbDqXH40JMwzCdxqbHnPsXLFELLGcVKCR9sY/JH+/kX5444sVl5d4CMkfOSlVwWXanBFRBPlNgQ9lZSaiQmXr8Gcm4RrziQQU4Iqj7piTxULqrxiTZZQBXrQAIh+W0In4QaGmhnT4efW7Zj6ri8Pliccvh8aicXuwULB1KUUmpYVvZ0286q8chbb4+7npLGwkrdxf8J1ZVill8+YTEKjkujMOAjY62F/9ahe2tw52+Hb8ZG+oAe0SULRoHtHBNN3efdygB1qetUNJLf1/I8ccWzGz3GmBeHrBHMKHIUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwUiK0RaSNlbAhO4HW4JAEMFCOektB/wIZcp1LCmsiEgpGLT/xfgZ2J3Jl/8WkcQ1JPg2QlDWsXVTVffCysfekCg==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAUFPkk8sXCXSe+XhY6Oivs3nw4916j6vx4N2Drgprm7CLYNDaYPrJ44A8IGt7g1ToyE2/mzTqC2VRdfqSWitaZ6daBdz7+7edpH+YisAt4aSCVxxDga5tlYeOs+rIUW2RQaLuQLtMTbXrsmiTR+bhjZehOTzsbWnGHjc0jJhriwgLCcB1oqduY7krt1zd8J87ft9DOumfhqar4ErnFUBnj73Psb/dVeHygXv39+DmEIal8Zjozk4wsmA4OYyG1lMQIqEci9HSeEO2vXN6C2O4/26HRuG9bGl+8rNxpAR61ZyNJJ1jgD4UImojkfAleni1/a8z1yNTDkECzRjXqVc6GU99POhM9Idjen6Nf58JrnwH1A+RS+19piVx8kFJmkIRaf14fCWn1YMYS9+9qZrld6zr74Lv/SH60HD1LJAXW8M/TfxmVFYLkPzW1ZGBRW/aqf92x662G8LZfRgO4i97Ljeuytbs5NCnWZN2cEYmSTbbKjPB+Q++yKshVk2vT1PyPJGNRVCPDYa0QCQCBYANQ9GyR54VcYsJHYGBgK3XWtB1n0sT78QonAI1AsrT/kojy2ICzSCX0IWafEOzlTIc3OT4kx8RPF2gp30cXC1otrKKQ8oaqpRDnUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwjTiKrqvGJXLF8dI7INxu0LrYxjznPscksa9HWloM3gkW0EzTI2kOZIbn73rxpLy6BDY19hxmauxU9FvcIsdaCw==" } ] }, { "header": { "sequence": 4, - "previousBlockHash": "050ADA5533F338F1BA0C69A22373CC1C1720034401038699C5FC087FA41F8458", + "previousBlockHash": "6C2A5506D36E226342D095A86634D85F29885702718F8640C1BD9DD1B317A04A", "noteCommitment": { "type": "Buffer", - "data": "base64:jFaBsVq1mcjZ2booxT9RGj8YlxLixkSETDInn5a/dHI=" + "data": "base64:RO3GjyUE3m0BM9SOwe3f7rYR05W9HGZY3p1qOj0v4C0=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:IcQgSI+5kTae2zkZc+OKhqL/U9tdCz9ljq2w0p1Cw18=" + "data": "base64:zKjNd/VkTw/mJDFEk/n0MLFqwRkGljO2aO0RLUoAZiY=" }, "target": "9228823284279306817296266184515742822248210830185427859262273659833347", "randomness": "0", - "timestamp": 1719441714662, + "timestamp": 1731435265305, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 6, "work": "0" @@ -854,34 +863,35 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAwpgPAEyajkka4eeMl1Tk7vzRxWDNfzQ80jLD+sl94z6BTuM0npeeAz1butHPEloBEStOgU9GLY+Yz7RZL6Tw1uapHzy0v9zgAWJ1fraC2zqZglZN1owevbu4xrj+mPawJnxHifmDmANPtNaZmeFqJP8gMzJ4Aq/eNPK8tbr00scXoqunHXbYvUdOX58xFRGa26FkoUguSVYf9Vv3WI00ycBpeNB7xrSmG7L0aqgHswy1DcCrgQN/6f376sfgWvGOn030p0T4U8BsjDkts5LnOnAmDpyKhDSUzEuGW0d4oC0Ds2mP69qH5VZeUSBv7nTg188EL/IP76k+MuviK+exc+WIboGykNdBnFzXfC+4bp7FetmJGcwYHpxjEZbCJ6wohCysB7WcUEdB1keMTtlLi1nLRZRhiydnL4B29nWvCQ9/erdiNdV813a9MDxHJ/b7BGLLY6WA54A94eIiUuZACDFWX5oBCooplTRo99JCf5Zxz7Kz8LSWN6EqqNd8BiVuvyk0pe/v9VbDR4cPjJT2hToWAcD5eBdczRbe5wEjTBc5ZAccKncWAUCh4nRMVKORg27mdx2AXJq7ugkuodJ5bYr7ntBDgnIGsUitIcIB76yKP93XaU5D5Ulyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwh+p9VR4u5B/UFJ6w8Mocg0H/QdLwjnL8ZafcCM/Mg/NcOn83fK5gKEKkMDEjClA1eclNc+T6v52xmbcIZlHLAw==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAiYquZgxFdpPPjcOMhZN+0AnvyM401nQP6UoFH+bIjdW0kUzYK7GDcnxoW5Xxft5HNCGjfexRFGr/4DdnWd0zo269YrFlJ8FYUwDloQ5y8QWnLexE4IRMCaouzbOtVnJ5fqpC5pMzrisKijE53nGOOo2mXS4Zt2Yn6YErxtK7LpUIRw9KcbSzQRJ2ek/yWNLtjOZCFJHXZnnRk3k1vpy8rnjpsA1ckSFdYJhcKbnpwQ2ZBrkCVWv0+iyD6Bej+/cqk3Lx5s+FasXQJBG06od9gqiMt25wqQHUaM1PzBu04EY1K4aMJBwz+N6B//D9avxfDalszd5gGl4d/DRI6Omw3ON3RTFqTUwyzmVRz3LZMbHUdx4GWeyI2rUyIW+VKB5OH+Hoky2alsiQH5cGNw8fNZ+iGlzwwmOKlwRXCU0oJqLTppVwHDyeSeRoDQwIp6nOUhPO89pyvFrzglkZ5qJEtj3XEYUdnr8fLLIRjQ8iDnwiWHSeVPaPe559pfIejNEit9tunJN8+MquvRdzw1fap0xQl0Jjm60Eh0Xn2s+WGFDl6eSI1LBnHFAxxbGhJ27op0bQb2Bic8z04x5IIaiW7/I33VULVqsxl0zYnMxy315itnuz/6dycElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw5wuL7SMMuFTah2jNHhkgKG5SdNAuxIWD0kfMScR3Uh+kUBQKlBJPH+5kPX/HRYygsjhnaRyafgqpHjvxmSk4BA==" } ] }, { "value": { + "encrypted": false, "version": 4, - "id": "2ab7a678-7f34-4cb1-8a0a-bec779f935d4", + "id": "e4d3b767-57a1-44ca-876e-2b4a9272c3ee", "name": "b", - "spendingKey": "7ae31eef18f3a40596fce5062202676e5dfbd3d015a674ff76857fd97b9ad623", - "viewKey": "75e1defc921bb4d6a2487cb79748674ee91a2977f93779871280a6b37e9072a581595f2e115fd38dd1d994600fb8fb412771ae1672a62056462b4072d95a756c", - "incomingViewKey": "effdc9b669b507c5c5c6ff4a024adb3a067c0c7a983cfee72d3863289cae0105", - "outgoingViewKey": "d2a463ef226bd1a539ae966e4486b4d73b545ea427cdd57a3c36332fb745ef84", - "publicAddress": "6cdb2bfcb2befcf5dba27e11dce86bc37e8c0f8d028c816d462bf580014ce92c", + "spendingKey": "3a14d81d59287cde9a3fd3b7223df0374082ed588524297fd6d8f117e5ebe3bf", + "viewKey": "8c6ec709e2c32fae06f603735edc8a4f6ebc65f8eb575868addd9dbd6adc172f66e4f20a2d5ebf972cb8bb77406d1e511f5ed0931ad3069488aecf98c25fb48c", + "incomingViewKey": "0fe7d250e677e24747a5bd09aec356899f28848b9587c7b2a97d56d4c8ac2a05", + "outgoingViewKey": "5863f09a67a2ded168a09c0bf59b82c4835f544a77f9de6e89a2e2208bc63eb4", + "publicAddress": "c1639dfa8866b5bde630ab6d40e076cd0a7491fcb83f5b71cff6557e55dfc4ee", "createdAt": { + "sequence": 4, "hash": { "type": "Buffer", - "data": "base64:AjZ0RgBBNBs4qIs7/5Po/PuL/08a3K8UkG5w6p6VNmA=" - }, - "sequence": 4 + "data": "base64:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + } }, "scanningEnabled": true, - "proofAuthorizingKey": "b3ec5c5ecd614b5ca21e5b1d19e2967d3e4d9b46a2f7a596b3973a5df932d709" + "proofAuthorizingKey": "cdafa173f8363c10599de71c3ab37bca963643cf9ad4864265dbad93708bfe01" }, "head": { "hash": { "type": "Buffer", - "data": "base64:AjZ0RgBBNBs4qIs7/5Po/PuL/08a3K8UkG5w6p6VNmA=" + "data": "base64:S710QaloK1lVaK7tGy7SkuKU+rrcRocsRUqcZ1ujZG0=" }, "sequence": 4 } @@ -889,18 +899,18 @@ { "header": { "sequence": 5, - "previousBlockHash": "023674460041341B38A88B3BFF93E8FCFB8BFF4F1ADCAF14906E70EA9E953660", + "previousBlockHash": "4BBD7441A9682B595568AEED1B2ED292E294FABADC46872C454A9C675BA3646D", "noteCommitment": { "type": "Buffer", - "data": "base64:rhCy5v+aagBLTFzuRQLjb3qxjOObCYAxsLPBlf/++T4=" + "data": "base64:aJ832uFjn7zYSzUnxn2PY2UM8A3WXMRL78Tsho6Cg0g=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:P1UY1e3sVBtSfeNG0CIizxZi2crCe8Lo+30reKwJCxA=" + "data": "base64:y5tZKCBAfW2q756I92mOpYn6hqlzYFgw1tsVHKOjnB8=" }, "target": "9201866281654531936596795386791503876274441021197252859723586932895305", "randomness": "0", - "timestamp": 1719441715146, + "timestamp": 1731435265600, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 7, "work": "0" @@ -908,25 +918,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAs54E9HWNTTiXFcBvkNzmyp562MuJF9Dsose2yAlsx5S531ki6xZ7opTMKPUADdiIgrw30h5xiDdZQK5r5XaJjqK+HUYia5Rtd/HTCTbxwvSyV1zl6S1iMLP1kJQHmhg42Y372K2XQsJy5wk8fDj0gCLMqZ8DvM8FvipBRMLzwg4ACkc0iKce6VRCzPr021BFY8hbrqDCzcyODl9KXkNe7Nb8f7KaN+RPF778eFiUNkCNti4ing6qS4v/Z4QLiwdrm5h1JWh+RCCPOws9pf5BRNgLYKpv1ZxREHNbKVWKCpyFMGDa4Zqg3MaKz4mb2tKtoLVyQcHL7Me7ja1gJW+k11zTvdmAAzUWJIU2Dybu9n11HajhFh9MxW+magWkp30tOitFdzA4WlT2h0HRdWNph6iGRjgZrtfdRIZudjnosTB4pAe0Vujx+U9VDAB1VliMtFRYAqO2E7RTzSs+1IqxNNEvfxTV6zqro6MfOl5YbZOcWSxBMW/o3kS+B0xE7R2cSamOG0bskTY2fMFZZl4uwE6sCs6PkwNjI7Ta8jAFT9YWoaOBJAX7a+PYZjxuXPSN6nXMJX4BXzo5AwS1+jhHLkqY/C5sQwiUkmaIxO5RwKLDpE309V1dl0lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwOKaw1frd5RHcG7TER8VH5D8xxpsm7HMr0AwyENLbFpmpxiwIF0Fg1/G49EfilNsX/SlAvDgB2rlaRb9maiJmBg==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAg+Gzogjd6v0rQ1pANutaT/bGadP3sHaOgcLlhW5l00yxzNzeyOYWMsm0FpFJglnrRv4+K47kc5pC8H2+cn5e9plq/X26wqMV7XCGQXOt3JyHGn8W6Vo7tO5llVYl8d1rBMai7se8l93Unk71segYrdh6OWv8eO6YYCDZf4vLxwcXYiKGiNZFXkxUhnCzEYQrEb0ko2rvrH+1vlLFCNyyp1landRsyaRZEZH4j+uIsdiry5AruNtNngFaaPtwiFCLNbOFDZW8+RTNYdkh/I8omYG5jqKVk+l2kYm2teBY0AmLXGpbrKARS1lfXF9ZsUm025DYndf64jbpfmWe4LhPyPncwi2Lw65ZCn89TdbKci2DD1NKHBryCYaUhqqz0J5JSFiPMVqrX5NtjhCI8I1YVQPrQIZClIRMCYnF7iyLVDJHa/7P8zMGtEXRx5OcAWv1SCCBjrTH7on/8Shhhe0UfwQhqH9whYLRx3isadZrgLrDxek6FNB9DS+HCILKEf9pZL+a44c37stkEmhF9YZXYByS3Kj4/Sw6PcEAnXd7xKIKV6TDzIeO+Qoz6XI8OH/kC44y/wMlQILuG+VvZ8R7bDlAGjhR/AYrTTIKalf38hMOHWqPWORSpElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwnYEqcewXWCrkB/Bsz5VIxk2i0VM6prgFDfGQ3CKBYIbnik2fF5j54NZ4PV61Y7m+N93bM18CV0negBfmkCm6Ag==" } ] }, { "header": { "sequence": 6, - "previousBlockHash": "C9669A682E4EE5610FB8750FA51020662AF29F6811ECE62785088AD27700B8EE", + "previousBlockHash": "236411A5ABC9DDE73A15D612E8B9DB8AD94D962E9E3A20D44E7262DED8C885F2", "noteCommitment": { "type": "Buffer", - "data": "base64:n75LmFooqdVeYYip4h/WFM6VFxCZ9OES4nfZUv7a52s=" + "data": "base64:v7Oy83oJM6nGGHqygseqMIgfY4d2mVrOw222ZEgTV08=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:93lLj1s7YLC+TooYjqFjGmn6zgoVpIF/bmdX8RgTfIc=" + "data": "base64:dmGS20oZNx7ysWXXwoE/GP2Pygxu4gAkpWgyxb4wczA=" }, "target": "9174987784651351638043000274530578397566067964335270621952759689537226", "randomness": "0", - "timestamp": 1719441715604, + "timestamp": 1731435265885, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 8, "work": "0" @@ -934,25 +944,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAj4CiLhs2IHU1PkFZKltmxULim5udHt8s2bh+Uv/UU4uou3DVVEiH4lQdvRw9yA5zqnAOWmOl6oBsRVgrKn8fLcwmOiZsXl1bgrlUkqGPOEChrHJVTlt/qAF6xa9PuxvIhgoLBMFxEtORRDkC+5RckP8M3dvIBF3+3cxTSSdqUBUKg/CUNxXaMgVi3S0xU9y1nTOYEu4sFtVzNYNjAY5MmoICcWngxxnQzSkwqTVurbC4CxW/42UbMFCR/LlgzELYFJpLhxgz6jR7Fu2QrAxu81860jWVOs4OeeWiCCU3p2++jyB0Hcweu/3IP7/eP+aUm0ecVRxaiFXo9cpNGoCoSsbV+/zKv/MLnUd9QKIuNDBb8/n9tAEeOZUt/e8VfpIMBo8+eSTUbXmybS0T8slHq8ORVO1TmFDcN7wsT0ED35vWaUzIk9Otn1vpnAJibvmbGlFUp7BpLz3J6KH54ImmLqqb95aJohKJmqeZFuTkLy6N5DRhtN5Q88cQhMz3R5+Li2PZy8NpYLlk4mJypfyGYhiky0LqEPUfI91Bm1Zx3NACD1VBlItLYBoI7LjFrinEHs1xxSm1AVVJxd4Ckktjsl9ILly9yH6rZplmiqBFbvGKpLgkxIMU8Ulyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwO4LoCRuzeFt+cisoqjfFzzGZ3yDmDtGicWz35r3oF6UCZbUR1RIHLxN8Ja9on7VzfEm7Fn0nwdTRYW1HfqRRCQ==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAYKSNr30bh4G1wvOHf7u++YaEYRm1K08jXkJtuNiZD7WNC5xJLHj+U23zEfE0cj4ZRBjtXEMFhbHa7Qhcp4PLodzfmtyiu6oWVd4tZBMI4RO5+1R+fyKpqdB4GPSWXl4OR6SIrKYuynD6kWs5p7Yi08G1i7fX2uFl9v6RbV7yy3IEIZ1jgnObRTc54kEGUfOkDnfZTe6rXx3q6jYxlIU7dOtmLvtzS390eZZzy5TPk/CnVzwH7WPH/hXiOFS9SuTYhppa5bVam2HTF0X3lZDX6YMmdTzIRy3yVM6bXJoSCM7oYORc+wfdQTAzMm3RP/GsTYYdiISl0aAVdhD5MroFxApDLkJvnHzn49WW6WXPcc57Zd4ZaybsJte22Bno0JIRWtFKAqVaqLEV+OChyttgBx6grcOJ4n87Q3+sAObygbWFzdXhZOYiWrgtMVe8zvY7vAI6mCJBa2VUJKnPcX5XCeX1MtaFS+cGDzlKU3W9f7MHKAy5upkAT8u4vrarllzF73xU+yl3neV2rGOfr5dwp0C6j91+/sftrYrVvxzCFcZ4DJcLCk52lGw4UYF6H4joz9ut5Go/ZgXYX+H3kZu0zuGxeDdxy7uIv3dyiHFXEIxMK7r/JbWt20lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwhnv1XbCkVMqKr+3HyROA5LQFYciGwdrPll9UwChVAuks/YMjvgVjKDhL1sbmUj4REkZvbBjYQdR/PFMOsa2hAQ==" } ] }, { "header": { "sequence": 7, - "previousBlockHash": "A562DF2A70F3C895A03FB74102E9659F9900F8B54E9DAEA04CD087ED24E44755", + "previousBlockHash": "217A79677517176DA246EE971BC245264712837B6AC28C02E4A24F7BFED48E32", "noteCommitment": { "type": "Buffer", - "data": "base64:Z77o6+kbFLouSQoNIerXjL23qy5nbYjBRfHsVMiDvz0=" + "data": "base64:2FJD9ADDgS+CiSW6xFuKu2zB1yFdREkRFmmcxCrE4S0=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:hfRtvGkUUTRcbaf1+ETs7yEs8cp0C2feg0RtAFwtqD0=" + "data": "base64:rdA26VQsh42WJTDLgGQKjiudGo6qA1lHvhKVpTzrics=" }, "target": "9148187795366513087508709149025146424715856256637674150531751753357577", "randomness": "0", - "timestamp": 1719441716107, + "timestamp": 1731435266228, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 9, "work": "0" @@ -960,7 +970,7 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA4FcrJ0yOAGHyKR/q+WLSBtoBXhWAVIJn9kUe7zTPpeyBUB2CsG2Ikrd80KVFy3V50rocDc6Vh+Sv0KNNquREpsOeYmarnlUTf5HGL/Vvt/O4G1vj49j9N36GgGwCptsB8KqEYYS8W9mxsPVZGU5hn3IpCLi+7U4rkpcJFPDtgsAXoWBkHKatWfiYm73gYHtIIG4mtLJPGz1WxLOiDO5JMvhl7LeqJxoomCPwn2ZkCPWw6uxZ1WEIiXu8IOHupCzKehp3wJ8NjegQch73s6UVmxLiyBuH5cutxC7XsHxmJtbVfdDUrL4sF1x1o3O6CSTgs4JQE9h4IK8IbJyofpRZYm2f/HyYDdFrwEGgy7Ey42kP91mAOAykEiyf/YaUrxg7gJawArtUqeX7BJEWxITpvv86uClQNHTlyv66TgAi5jYrZltV6ETHXoLgvUK+KNC+ifR7iXkOoUEjtSVurff0OmAf2nixpYA+4MVXbBKS4oxgfn5U6rBgFxLAm3dqLbUMPHI5b17NOEMfhGBfaUfVEzQjNvs0g1N/2Xamlsv+V9RTkqPqUVQaUlnuIourCuMrwHpAE5nHkfKNbKbMNZSeNKjcPT6lpPI9m9C8tQXSMLMVAh7d/yAD+0lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwCT2TGuANbPq8HuWQmlT2a8TnepbB3DCQAZ6zLH+x1CaFcRCSt6NxlfLDzrrMKMBGHgYp1SmO2fhIBnf/sQNtBQ==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA80juImr8DkuqjaegiYr8zFhUmQ5kPBWIVgIQVLojra6rUAB1MzByeqJIJDdC8ygh8Bu0hXyYWCghJUNS3ULqGDUP4XK/QySk4n9WuE1pggOvOIlMfffi31Ox8Ncry6veig7vJNY82nF8GQ7F8X3ugby58kwcVasxLUfhwizI+QQI3ztzDVWeP3VNKZwFlqWbxYO0BeY8zm8yuOlrxgV8PrKted6EIYzO7J7EI6R/57iTOBO0YVgEyEISnKMR2qEisStgzUR2PT1TCnW+I+Tyo86CCCfqcCgBrFP6ERYjKo1isBYLesDioJ67WjoxfQA4mGoQ2iroezXCFVhB6IjAJCKB0mLZEBW6DoCzSYjtfGVwVSXvp6Ptgacyn4uBqJUgzYnQcbyglQAEmpOuWzvr8ymfo5a4jgguvEFG7ZjC/yVMW5wDw7OfGHZweuTuTMuSenUXf82/Lto+2ek7xjYYgDsFdvkliiXlQjUPEgrZHADZrtyEK71xGA1QaWkqalii25G0dZmGVniK2GvPdu+6n4niOM2CduMERzB7gEG3VaW8MQMqRdeXqvqwFCCDrKM9D+Qfb0a7Gz/pEsApk5VnT4KJmb2spaVhCP+VKEjGSU60rZ8ls0pQQElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwkMjBZr6Gkew69DpKu2kc+8CyinubIPIX2xmPM1vvGWXnqBF24wmNBi1iDOTEg8ZUPfVxlYSn64QQGGMIMtGQDQ==" } ] } @@ -968,23 +978,24 @@ "WalletScanner restarts scanning when accounts are imported": [ { "value": { + "encrypted": false, "version": 4, - "id": "fe28d0e8-833d-4a92-9e3d-ed6208fe7667", + "id": "398159b0-5dd0-4593-a177-d5af532f77ab", "name": "a", - "spendingKey": "c9d732595166f6b8a4f285f4df0ee76adb106b0c39615895422a633a08035d24", - "viewKey": "9518d298441f85255980a09aa07d8280d159e77b892b39c88d8cb8d5854f58219775f94ffb45bfb9046868cf82683acb24995d7f1f3d2216323c9403ffc80c04", - "incomingViewKey": "ea7dc790dafb32a077078d0590313d0f1ceea22cbe9c0145fb7a6481c1693603", - "outgoingViewKey": "c702677769d9853277836e270ea232f1e81b4f804074eeec27a1da371e534379", - "publicAddress": "bbd37319139d42a222151a92f482b48e4381f48ae1cc1b83f92f7b278c57d3d8", + "spendingKey": "537151ca6016ee7ffc1615ca898746290f9f3f7f014035f0ac52c237240d0c10", + "viewKey": "bd84866fd1ad26c81206bd1eaa33ca7dc8f689b9db469c30aa88ff38f8c704a25dc09aa4094b4615cfaefbf2ac8bc08d357d1c20adf26b994269df63c4496a5b", + "incomingViewKey": "765631bada299c90f1a041b40b94cdc83de47133efb46510d7c2f9e92435ee01", + "outgoingViewKey": "19ea7374965b596c484ca9e4ea20d3159f87a0cbf325343b6557831e1eab9efa", + "publicAddress": "107bd2ddcbb1bae9bf0aa09cb8b2bcca261afbb5e5645d672dac571bd69b8d8b", "createdAt": { + "sequence": 1, "hash": { "type": "Buffer", - "data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" - }, - "sequence": 1 + "data": "base64:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + } }, "scanningEnabled": true, - "proofAuthorizingKey": "513fd18713e16aa52ff01fe6f9b9a651b28088e229c8e1e050e0507978839306" + "proofAuthorizingKey": "89cfb21d09255de6a5a0dfce604e5eaed9099defecb5b219c09349fd6200b702" }, "head": { "hash": { @@ -1000,15 +1011,15 @@ "previousBlockHash": "4791D7AE9F97DF100EF1558E84772D6A09B43762388283F75C6F20A32A88AA86", "noteCommitment": { "type": "Buffer", - "data": "base64:zesnAHOaaPkl0/TO9DoQ0ZLtdUyoy+Gm2q3p/pSWmnA=" + "data": "base64:uqhuFHMiXdEt400s4hd7MRFNBZbNyb9eYg7jmPSFrTA=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:kOw3BW+L/nRPNpJDVjsqXkF5IH10JZSCpi1bFUfxsHk=" + "data": "base64:dW9pzjojz6y44FzLLbsJgD3wZuuvTT/Zp3EyfTpkW4s=" }, "target": "9282972777491357380673661573939192202192629606981189395159182914949423", "randomness": "0", - "timestamp": 1719441717120, + "timestamp": 1731435266774, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 4, "work": "0" @@ -1016,25 +1027,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAMihdYOG31F9KdpljKhP1NL0cUntNX7RhcDH+BUxV0+WxJwyG9GtxEcw+kEvnHUl2q6wntoA3jgQFnlNmeClscGdiFtM0VxUvqI4uwlVvJz6zO3OPengore7jvSZujc5CKRgS6NH5BLodY+6wxL1+ZyXx63H/n3y9rXSAWrbfXmgIbYzwhdzulg7ePDE6xAl+Zwtcy4lAIas9i4bSM6rnkqks5e71WWqRHbIwXvZeRRWqAmZlomEp67dM1u9bAXcgSQOQQU9KCC2Rw+tLuGiniATTfmo6pSoMV2ICfyxBU84/VEnUuKuvIyMMXpN16Y2T7hZ9xyspOgBprgwQ4jpiazeYo5vk2pSB1MSm/VADqLTUI1Sr0wjbcqDRdAlc74RmQ1QFbtVesTtY2pINnj5UXJjTU+8SIC68r6JkHCYAPVNWihG2KUYzE5IGRuOLe60oi/ZzLtB+kaR4eVWWWXB1Y9cPFO4i1J0EJdYQb5JYeYWmQEUhuQ2xhiSTb8XhilF+rQBk+brdNRau0xUhDJz0ScfQreNt+C5pVnwST4n2chz3IWt797PkmanYGNmKR/9F4+B738nfzbdRCKfUS95Gm+lVO+UB0LWosQCbRiNiEcF1a61D/ChqyElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwKIcU6uhqwIsl+OCYnKU+YJ7ziYvkYXpF/R4xr3hKJCHzZ2MaSsenna91kR0alVNOeQ7ujnmUXbZjMqZz5poAAA==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAkZgjqp79kVguHtOnp1wcIcCDUtGi8g63nhZu7jsjM6auhVvtBMdhJZfxVzT2it/oynM3UCwpZx3CzSEmTCDmh8LNKPacPNcOL6ZNJs1LueG0iX4wkCx3rZeU6ZeQjnf4UeQDo9YqE6qALKoHGakHS+J7JHi/XkOfVt6bNTl9VL8Mi6FX+X8BEgAqsQkWKa/GwHyUBJJPmz2QjQzo2XauFVVDj8ih2DnUKieG77XmH9qJ+X9d0sC5DIYpr6lD8//fx1URsncm4QoMSyxQRqrRJphkxbGrZbzJQ9GkL80CYDU888AMh8xCm6+3kOQdB8KXDl4SdRt+SHs5YUulNwsgSXRfAVUKwfdyWkk2JsylFKYxXVM5AtrfRtxEwP0SmuFEXlODMYaaHJvNKAN7fb+I1H3RneBSG13yLWtsuG2DujXTvUAoP8d6Hj6NdLspiz7P2uFQDgBKD/q7NizRZBAvah9XmHy6LfCM4EMnlw7jUWGl0lKiosaZ0P5vibJOGN8WXH/2CwYCESnwHg1D/p4xwHetlrkzawTtR/fXeS2ylI/pfwMe4Ugrf38XnM80UkDbHWe1hiiJovdtwH7qZojtFYUKYgm8bmKlCx8F0uPy5tVpAoI7tZViJ0lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwadDQeKN3ygPQarygOcjaQy0+B2prCq2BNI8ccGMuZQfl0l7FFs9SGh6bZiw1/1H2M2cA5Z2FIiRAIHH1/k3aDA==" } ] }, { "header": { "sequence": 3, - "previousBlockHash": "C8CA8E6A876D5878FAD70AF8B1FEC153FBB8A7EE0BEB8FD1F734C2B938ABF1A3", + "previousBlockHash": "E333DB5D0DB9B3EA65D54678E073EC17361DFD9D19BFEEF0A646C44B26FBA939", "noteCommitment": { "type": "Buffer", - "data": "base64:1Rn0AErDH/BbtTxnDaBwFo60NbkEGgyXjs1d1aWy8i0=" + "data": "base64:ymJNM+FxSyeRVaVQwjjVXuUfmpMZuNwvCVnwY95r7m8=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:uWKhSe/jmCtM1SuBEktGCP+AE/nY34LoRQN1t/FgiVo=" + "data": "base64:krvZPEOQVmluCUFBwU+7f+7I0oSuH7IFZmOAA87ovaw=" }, "target": "9255858786337818395603165512831024101510453493377417362192396248796027", "randomness": "0", - "timestamp": 1719441717605, + "timestamp": 1731435267074, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 5, "work": "0" @@ -1042,25 +1053,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAUkStbxnJv14xGa4ZJcqzts5BPy8iqSUhz4hx/IbvP5+vA059uEDC9nIl/AusFM1TsAcqGkE15a6pLTGKgdMRzeUhXOpuA/Qp5hOXymRz0RupSSU3RNFtlpZ88VdnjkTe9PxAanWEnja4cESktVTOtdcShFtSNU8YRbVwHH5uI0sEEDSwpUKirB5rM0nlfFZNPyKuklJ05YCKpWAUmNPUq0DYmVRfOBfcy7omRFjzCIyJiVFs1CmK2FRsFAFtdCD4ikJwTumlgloqThMRFamNjn70hh9HgoCDpT+BhWUVB0AFZothSQpRoSjJXyD18s88M2xYpFRZjYfT4g0pHxYwRq4vT8UYnEPoU2yBDXPkLj9b92fALPMl1U5aQCDxd8tf3aVIQWAMlQMWuQi13EY9NVh9YV3NaBKaemoUmfOBNQCZsQcNuNCAdgwR/xCAPuEr4HaE/oR6wQZpBhDLIEOcx0UcAaDKU1InbGq98r//iWsklMgFaAWsIxQXF0FSWTN1WkcvgHDCxhjJS2mcxjXwRJ5a0d1moYW6n29MQIjXehiZpPJePrE7xnK6jxqfI7HGPcTesBLgUAxxeztAyEXOdG/R7VenvRp52s0MFn7/PiAje7GlC0RGVklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwsPgtteMzJa4nKsHJfHj/MWEdvBeejIxX4C+ppoUraAN0PwnujqDeIKppcl+de6Ht1FdYzwh6RJK1kkXfxZ1EAA==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAFuzsqey+wOJfQQXQifJps3l9hX0vo9RXgwpOzmOSlIyB+TWpEYpNPc6Mefj18F1MfXd9MJkc96JpUIMpSgXLgFnmQfmAtRrc+lTIfSVECRi1VBpvtLcaAcZsrCz57WQCdcM0oCphamBIRlz6rnigHQW6nfvxi+mDn3oqd96ZLEMGQ10yiFggce51mdA/hpgz0KLKqWB5kfq4xL3geMkAtoPduAfKFJ2RH5+h8cncET61Ik8pTfBcZoQGunjRiJbvNCG88LNbYie0QmYz4LFa10KkIEJIgwm4ul30bos1PNLRXNcSPZw3C4DjCiEyo50KdTZ/a4m73haHLaAEcyAyyiIueeKKuOKpF2pv06ba8h7DR1TgI4izEIDhKc+Fj98KuDMR7gvNmy3q+bl2c6egnlXffkhgIylah7ixiwkXKgLxvPJHow1FTVauJ3cl0F9WQUoThOt1OgZT4s9a1QuWcAtiSquLff0DAA92aODNBAuu3d3PKzZfS/5HTAo+pMsTy+CBe30lu+xDmjgAX5ey6+UKPOGVI6Cpm9GsKuqzH7AmaaLgInV1sH1PjQggTJzr6d31jnfxhCexTa73lsVBedV1stX25co6sZz3WkBpteDa3iHrjvw6AUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwx7Gt8dGZVTXn1JX1o/q8vzCnBZSTqXYvoTWVO8JD8rulmjrqv159/SeStCaRlq7T+tAaWtZvoMV2u3rdsfydAA==" } ] }, { "header": { "sequence": 4, - "previousBlockHash": "17D18CBE275C4EA79045FBB9A0152C81C5BDAA792D9412BE14E00E12B5550FCF", + "previousBlockHash": "D34F2F1A4127760840B5C04F664D17C87289A1CD0333B91846883F68ED7504E6", "noteCommitment": { "type": "Buffer", - "data": "base64:0BqcEKChkWcfXhBfKdW6MhM6MQRy0FoFf1erPAVQ3FU=" + "data": "base64:BpXFeTT8bJYt83jzW8lYqZLVUNc9EyW5OctHZj4VpTY=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:Y19EIjCOwps4v9oLCvj50uAqdtqFSeKa8Xj63zIpyIk=" + "data": "base64:VBu5/fSNT+7b4/QxtyW0mEbtiqx1EQW8ZGlJE98A+O8=" }, "target": "9228823284279306817296266184515742822248210830185427859262273659833347", "randomness": "0", - "timestamp": 1719441718073, + "timestamp": 1731435267367, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 6, "work": "0" @@ -1068,25 +1079,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAm1c920uLO3/jJmQCI47v9aMbzLLCyH8K+QA2e51OIWa489yxqWBZlY1ftJv4e6rq+kjzI1Ku3pYAHj4pzE6H/Xunvvp2CoGYJA1OSsOYFlKzGpNIf7Roy26zLwp+jevTRc9ZTy3N8+1KvpqWui4Qbuns0KLBsj64bKGaS2qtTUMIJ/VuCkpmftHkTzAYVrGmYArEASuOjnOvLlwThgW7O/gGItUJV07lkjQrm71w0lKtOnOJsnN5xEfwoVFs8OahIp4NATJFEyvUaybmrF9FRJColZ/xHS7q0Y6aI8wQKbLEuuFrcXEtwriK2IjUMbTgMFXJak0HS2t4CQu3vke1t2CI5edDd1lq1jMgal5/K/l4Yu2LSeijdUc2rV+Kd9ZXjod5YnlejZIEP+3vkix1A3JHruU38pvZSct1guQZWymBdi3eqHvh+/gCGgQdlNZhbA00teKkiDDVPAS72y+pwnJP5XasbMdol1YKLkjK1xDtl6IJc2cDBrmoqiruC0kNIhyp4qFRLC9pvJtk9I0n7FWYBYwYBfEU958bpO3avxxZ8Qqju4bXmjy5r8prYpPAdmWixHqpwo3+i7kVPh6IqEmlMNo47fkqLc8N9Df7uqtDQ5qFYze6VUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw8WS+j73JJrv0TpdwwzTbigj27kjmsDVuLwjhWJxG0ut0MPLWf4Ihcme7ZLdzipDDuh1eRmeBXx3h/RuPmvH0DQ==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAPEM3Y5cZmFd3lrg8EnuiKQPH42wLjZ/uyDxMuFhQ7oulgTQrspgVeqcvaX1onIMdeamNbt0y6sFTlrdlyH8Dsff0pkjfuJjubcFO+7BulKOAbpLv5jKNnGke/8TpEtQJ4SuWQ/cmE6RMZ4CMIy34Kruh0EuUtpuPBHuSaJYGVcUPrwsZw+hTOMSsPbCi9KHhlHnIksOuGmm0cua0/Imw3AvDl3wjyKhQtH2r+Op7xFOt3EChohQOJdukX74WIQxlkQau0wuQCBniCJc02qhU7R2Ev/l2WSV7qTPpgtqQWEj/QaTsdFKQGQYU9yPuJDw/EIpmZlGfRcysLHtNY7CFTGMFubnnnbSB9nURqvAFV8kfXWSAcekTSlIcZ5YHNsdNRfoW2B4H9O66s335wvHcqCHUZN+qpBjr1YiI81UPvUuKkY9FCtyASFe8NWFFSk6su2/GwP9f5rG66hRLG/UE9IfB7/u44C1g/w5Tl+2fQR6ymYtNIpSoeosWQFq7+lYjV8EMaf6HJ9bRT1JzKxWGEmfg7FlCtsU/Ceixg3YsjyNXT2T93bmytnZ6GvHYymlS1XXJFN+mzyBI97aCmQyFc1F1ojqHJQ4NIMuszi9hoYouFXg3tir7Wklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwE0WO06qNZ8InSeoGO7s0xb/nxvx9pRMnH2MKQoeRvgJgn8y2LtFlcUgeteexfwuKp8iohy2WMPoAbHD2/fepCA==" } ] }, { "header": { "sequence": 5, - "previousBlockHash": "5B39752EBABBDA851444F03E035BC0B6E04612C13C50B82A8CCB145361AAC51B", + "previousBlockHash": "B249A801E514A073D88044F11D3650F21156D587C25B53052774B6D5674D6388", "noteCommitment": { "type": "Buffer", - "data": "base64:fR9+wSJRKfKxT/12apfk0tNAgy+zDinHornWgrFSFQ4=" + "data": "base64:4dEEt6Z3eO5DwbOMtPLXahtTctXqLuvyMGybDJXqnAc=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:3DhppDVoL/8nVjdNcxebZOU+iEQrpY7LDZs4Z9HeN04=" + "data": "base64:R3YnzmBxyG/DmzF4w3znwxeUPBE+Tk+bUfqDliRmuXA=" }, "target": "9201866281654531936596795386791503876274441021197252859723586932895305", "randomness": "0", - "timestamp": 1719441718552, + "timestamp": 1731435267678, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 7, "work": "0" @@ -1094,25 +1105,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA84sgWcXoPfqmoR7nHjM155OIkQqj8zxt0TOatCy+KAmYlhquVcZjNKiSWHH9Sqrg2RAcL6Fv764HTdxRq9eTG9ofRyrR5B0jCEAgKruELDK4fbgw/w7WihKUtmVeoi/7kA5trExOzUONhHqC6DlihZOeyyXaSZsxTWOlMGP7t2QK8WplmnZ4mHfrIhDFyn0QJfgKM9MUD+sDESpBLVE1lhXYf6EnrACz5oDAgQ+EX0er+4pugnFW9vyZWwIR0a+8qF9pYz+Hkk2zWvZNfkYjMMrIa4hDgDTOlEdlMiRmry3U3gsKGk8fMoSmKW5Hqdip8RkzSpQLpjHdtxZLOj9scrPnNQ7oly43dPx94FWQ4fL7MwHAZcI5dz7Jf9xnF6Ru0J9D2Eeh2z099QMVQugUd9ps7oUz+LOMcLMu1cdcC2U15hywzkJnyDb8M1q+DIXTdOqvV78MxJwf3FZb0uu8FxMCHYkCLD+n5mES4+lXzARVKuPGJ+EKnfITmeoKqylaCuQ2Bx/Xtg6QscKwSCDhgAn8aEQJQU6/GuwBvJ7vl9cukJ7JAqg/IoLkbRyLfq29s/0BTT+4yRDVY7JEoZfNsBQWL0aHh3XQB6BOHNA3XrkLJY9Ow4EaUElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwHKzxAbQx5rHwq9DRAumevsJceAwH6j0INM2nyXE0l5k9vMWzIiA+QRxWb/vEbg0zUFCL4tmtZmKEpueHOUPrDQ==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA3ih7uBUhKbbAhC6Cwtfs+bjPXc2vRIG6oCFRJCDSIjuBx54iFWHYypatDSBFOdU1SSpExUNX9OFyBWeL0mPmLqhkLbI7600pf5O2RkJyFWmrOKeuTdbbF8snwHMNe91h/+bt3DYCV1kgYIzRrFdgZH6D+B9qDMttlrtD8r0lFWgNNNZ23HCQsTLueTBPguZnLuDiC6ptKFwq4UBUSiiP+BFyG8I+MHI8jWqxGEQPNkWgMJjTBIq/NcV5rFmafR2QTa9aE67bashPfKb4EQUE1JWmiMxZLD0x3/sC1BCx2RjYlGABxeEVNXhoiTh9JF5yHdQ9YQJrFEBRfTZr7pHMbdKSqFXzG5ur5p4tQEt1/lOwTmpC0dcGQrobIRKr2NBDWNbu27PILyGS8NhMJAial7THJfzuv0PbwiKMgIRliNt9j9C1dhPtsSauyHVnU56QrZSE1ealsOd+hggVWX41d5r+Xh4jBAk82LKPmecEqWOMcT0R6g1IYYG4AX9kJkFZc1HKl+bsyNWm7ztJtNHn5PBhvsWL+bQQ71KdQX5A38JgQazsxn4RB0B3YTTny5KjhT2VfWyaKlbMlCzmwAR0K08JwsPuwed+SyoWIceGZw6jxirSickJWUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwsZn+icaES4J2r/QgiZ6/Tm1LP5mb5NcxeRKLFz0y9Kd580RP9qw+NYUpI87+qkIZhQpdzFvgem4ob8g+EMxNDg==" } ] }, { "header": { "sequence": 6, - "previousBlockHash": "2E9D77A3CD1594D60F226EE0ABC1C3D71DE2E5F37EC4F768B438CDD865378B52", + "previousBlockHash": "774A233C0655B254B72F76C9FD0AF1FADE900546E16FD0AAF545AAB34ACE0CD2", "noteCommitment": { "type": "Buffer", - "data": "base64:AZ9SRuU38eL+Y3fG5n+4ojeU30otj/iQZL6ToiPEHi8=" + "data": "base64:Yo3eE9ER6SfUdbYsB6AHhvUW0kSESvzffuV7mnuI0SY=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:3+eSpl6evErbkDzWw2g1cwEsVwpHL073Kl6lwvRjMJA=" + "data": "base64:MiBQyJTFBKE+2pDLtkGWV6syBJ8TSVhaG7Q3jEK/Pas=" }, "target": "9174987784651351638043000274530578397566067964335270621952759689537226", "randomness": "0", - "timestamp": 1719441719027, + "timestamp": 1731435267971, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 8, "work": "0" @@ -1120,34 +1131,35 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAXuppMbOuX1P9Nb+QEj3LUtC20xc1z4Y7hGdhOXteKuaECo5jbIGFOGQd12ubGWqlkFf4kFHszm9u4I3ICVKIvpPqND5YmkXij5qqeNEgwgKC55tSykuv9eHIM3fHaQMgzQVhlnrd6ifhYCit3V6JFa0FrxXcviSZ19uuiVufFO0Q+Rsp7nBYSc3nVIDTq0PQmxaNrwrWOkLGwFBmvknqC2LzLKIs75Zbqo98HymeGPKQ5mIAwPzhoqf4/oqNS2hwozrOrFayLiM9YJrxnsCII44iKi1RSfYWa7kiYxqnpDREyP/uwaX35Q9bnu5gkzac3d7EUHgJjhZPCTux0uRrRbY3eT7cJny9NzSvMK4mx2r5L/2ViuzKme9cdogZrScaMhHrM18mpNu642M+DM2wM+hmB1X3gk5zOhabQoyYg9MKgWTIydyq1BWvqooeqEQUcNt2UDR0oYdmE8ta4bn1MUTuB5B+bBHckDe8jDRdlxHuWlPccw77A8mjFuDHoT2+u/KKZd4vpGnxqIKs3vv+kDvN9zp2dRQhJIFbCssld1jIhFVS1cQEt7W1+TbkPsGlcjg8X/b0PSz+KCuNEHoQ2sYyxXOkdTOwJqIlO3quegpu3Ds0+UcM8Elyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwh9ZL5A3gdeb0gUiDRfzGnIXygpFPj/BBlRsTuIa+9DLHVZ9C7ckg76VIIDsdewZHWypZOnKirTJyVTeDoclnCg==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA5PTaBQQ/UzgONBAHU/tBL9KTr8rGG+6F4AZaLbLJsrirnu9e2fQzEO2MOLoSAp3aEj97S5yUx2KB/PPDrxrNkU/X0CfZTrAURw+xoc1Cy7ezY9w8lst6M3HUFdjFtRYRx1KIiTB3uL3AsVE/p/qYtP6ZxtLQHHMj9AnhYGS2DqADQdcQmRxSPmUulQx2z1iwCiUNowT4FeM9kb5xd72HVABHsxtcq88+C97eHG0An1KoY+sYxT6RaNpQUc2FIyxJKmb/OWLxdQXg52kJrSnv2oPT3qujSYkatgNUf1AIxPemFo+U9GOffPa0vYKNWuD/AhQClbh0P0mT6tT2uBNDXUoy7jj/lZdRpA3RqkZVTeAlfC6dS8CMfjQKZyjgi2wUs4OD8vWjc/H5Xq8Bj3RoA1lc6/a7Z+vzEuoiHPL6pmd8HNo/MUBvnqM+rnDeTuD5IwXoWPbZuwdpTvhWj7EHUygyGk2EK7lBNYEmusx1Yhe5z9dOAb+7A++UClFjNLO0Aw+QBAwyU2ZWx8geDqbXARECuuoq07FSCCk1MoxZVvGnr4V3IaaAve1h7HFpqICVaagbdrxoKmwuvWuNxGu+5UDrjm/cGe9wlt9VjUcSd8+OvDg6jFeLnUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw1xbqSbGiGVLBPXuQTEUegdYk3kop+W90mhig2izhecjPTVlQyJ7+yysLKZbzpdmVcIbXGqhZkcb3DSz8brWQAw==" } ] }, { "value": { + "encrypted": false, "version": 4, - "id": "e456bae9-7fa6-4bc8-a07a-df3134b43499", + "id": "cca2a3d6-5d87-4e60-b9d4-030ef880f53a", "name": "b", - "spendingKey": "3255f21950748d71ebbf2c076fc83b9ce7d0a36bf20f65434f59dff5710640f5", - "viewKey": "c42287b519e46eafbbb030babe8f51ce959098806661a3ed1835f8bcbd726ab78414c529999900bae72e13d829c6ec915982445df1fcaa7efd7c393fa88d0357", - "incomingViewKey": "5ba98e55e02563842dbe144f53295a69befdc21a9295e606f66a068dd0063a04", - "outgoingViewKey": "3e8256d8194cad6296eeb0efc3b4069b3eb6bae6e433923a65d677d077596224", - "publicAddress": "77b8690e1d2306024d5c2d715a61e959d357799a81ebfd109f12257515eb9d73", + "spendingKey": "7eef3676adfe7ba1f44a939bd696087820b4b5dfa2bca7143357ffe9bc9fe731", + "viewKey": "28297af28e59eb740823135e91a1fe3c188181f7410b0797352cea565579a54e246c65f30d488fe61c0f8301449c093882cfd8ebc6bdd4b618afb2b31c5820b1", + "incomingViewKey": "0c067448db1cf1052e92592f111644377850539796786ab4b810910107fec106", + "outgoingViewKey": "73eeafaefdabffc137a96b0a5c5264f486d9589cfecf7d47bb7b4324fcadc402", + "publicAddress": "e997927d8802d0eeba4501b844a46d4ebfa91301f4b47e73b8ee49ada39fade2", "createdAt": { + "sequence": 6, "hash": { "type": "Buffer", - "data": "base64:+pRJwuyUK5S49lKeCQlMuoe6W9+noGtRD7+S/PseErY=" - }, - "sequence": 6 + "data": "base64:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + } }, "scanningEnabled": true, - "proofAuthorizingKey": "0362ef3d9cf083545988a4229b6021f77b9874795e6142b1f01c21cc1a61e90b" + "proofAuthorizingKey": "fae4d103f2fa051fff78018466f1eee1803d3ee30393153ec22f7430aa9d1e00" }, "head": { "hash": { "type": "Buffer", - "data": "base64:+pRJwuyUK5S49lKeCQlMuoe6W9+noGtRD7+S/PseErY=" + "data": "base64:5IG/TNtrHb/2N9K94QWjE5K6LbgZG/fzP59xlM1ZtSU=" }, "sequence": 6 } @@ -1155,18 +1167,18 @@ { "header": { "sequence": 7, - "previousBlockHash": "FA9449C2EC942B94B8F6529E09094CBA87BA5BDFA7A06B510FBF92FCFB1E12B6", + "previousBlockHash": "E481BF4CDB6B1DBFF637D2BDE105A31392BA2DB8191BF7F33F9F7194CD59B525", "noteCommitment": { "type": "Buffer", - "data": "base64:wJvBtKKtiM3i7C+pOQw3tOjSwKpAJ0B1fLQ29S08N1w=" + "data": "base64:SPGTDGavqM2oeQnw/dGBR4enSN6paml4vTUYh/6u51E=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:wpt/AHXDmcSysIT52HZwG3R5Fb6qXd+A38RDFLVT904=" + "data": "base64:AT5ISI/mc6MQvxCdfTjCT6LAhTIxxwj+NzbHjnhS/V0=" }, "target": "9148187795366513087508709149025146424715856256637674150531751753357577", "randomness": "0", - "timestamp": 1719441719496, + "timestamp": 1731435268262, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 9, "work": "0" @@ -1174,25 +1186,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA1aD62FhUOYX08jpTsSGet3Xw+/WPICIfF97I0ENKeWGHIqANRp/qcVvJ3HfYCWWvdNXrDdM9GU+oISWfuGXsqDwZz5ZkeGXyJV+9jX8nn1+rJ3wqzVmpdFXPArMCvIkA8b2wWIzG2Gtp/h7oBTkF2c3XBV0TFp0JXmdSfsl6L2MMqCOy5A6vIAPTIyvlpqSRN05wM1d5iz8yJfvvT2mpSD4syggBvH3PizIlJARWw8ezd0h/PabLS6mgNSdy0JGGNe8eI+ViDJNbpaCJBU3BO9FIF7zLTrbrpUslXdIOj0qnJGd+mjV5NzufnpsCcJ1TyitG3RzvoQnucWEriCHq2Btz5vuX1KuZjDowe1LIMRqtMyYO0tH8+Iy0Hg6YXgtIspIDSjkGAloto55Z+CVgVuQJEjiMU8DGERB4bf3h4kuC/da+JzPCPfcTh9W2Qphm+1C43A1lkIJlTScjAuFvcaRfVuragRIpWssDL6xkgoyT1VjHTMs7+ushaLGAqz4pq1DntKQvASh77UZVQH2N4keJhYmuIXdwkf7Fxe/Eou8uqs32Gmf7Qmg6PDIDNKptEeh70cwOkKrUPFU6ephodisM2mBnQLTo3YSAqByeP8gDfVXTPUTcnUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwe/hyyBzA4hnSsCXpBnk09GP6mRkhhBpOtANmUldRXJ7iotke/xgANc19FA+lTyojHHduyzv72chwzzplWjgJBg==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAS+px4+wY8q/yd9xB61D6aVNvmpcEpdOEPcNra5P/DMakxT6EIEy0cGDD4rJdNu9hFCRqZG7UQ50bRoCT2gGVOTqXgUc/tBKlbAv7iBXUNxK0VLLRt6DunlKEZ2hnvzfTPhoVzAwqNnBTjQ5NFxeFYfKp2e+UJvOCB40MSq0WwBIKUAFAyjTb/NwI4Mk2dLTJfgUk6jeawe3vC33uT9Yjq73lWkvI0ECJEqyU/bKqYQelpj3S/3RHKnjuSqFsBbYlrvYv8907omcWsmfGK5njrE4qsWwJAyZdjT6pHQrKg9nie9MVwcLHIk/lpvmAAc6qGFFSfALsz8ZydIaAsP40XDlMmZ0ugNJ/inyc9yqMK0uounNz08HNvEPemiQ8y4JQDL1PsiFRVDYxAoPylfmwIt/tNCCRIU2cLl4IA/KlwkE+O2MQpxMq+DbgXfAne50aeHg6uRrFz1olsap29u9iqflWIhwowVggkfClvUjffNoMf0Geck608GOpEAsJ0wErrKmx874MS8BGDi1UAASfDVSl0HVAPDMP9ez5+b20PZO5mO3Sr0fApkxCO1yfcpUFqo3ftF8TW2JEvHg/Q5OhxzlRJcFFLq06DaXA84pE8EyvCmt6CDfVf0lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwZ5XI/SN8D1rpxL8D4Mi+ms0LUAyllXBpchdWf1+wrmbWCNi2RCGu/tpnqO88WwVGnWppFPZ4QvMnZyP7K3ilBA==" } ] }, { "header": { "sequence": 8, - "previousBlockHash": "310A4EA8214CC2537225A980D0DEC08BC9A8A9831D63CE2B928B06903F8A7B59", + "previousBlockHash": "610761623C73212DC30AE4F7520761D73DCB89D073BA9CF9CB8F023A1566E8FE", "noteCommitment": { "type": "Buffer", - "data": "base64:o1UlnyKvu2N77NW8IkjtH0dH8lr0nZnqhA+cGwkurxw=" + "data": "base64:7a3Ewqj0+cRsclrAgPvxtQghg4bcB0oeU1nbmWqAC1A=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:qaQSGOM2f+O+IQnwWwTephTeKSA1/CCrrQmdtuil5SY=" + "data": "base64:F2xS1DhwJ+JMHIctKHDk+OwyehKsgwwbG5ogFcLEBZY=" }, "target": "9121466311864876128923245652724724632104869735746188813030060672759072", "randomness": "0", - "timestamp": 1719441719966, + "timestamp": 1731435268550, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 10, "work": "0" @@ -1200,25 +1212,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAoOwj0D3ax9FG+clbD7etJj5uTK5MiID03p2Na1JC07SDCYd/EVLeRkW4EqnedD77jWZNt7VvtGPi4vnTP8vcy0G7aPA5RL0A91qIZdrqKhuSyu5wVdx84XSAB8hcm2cLLfu9650q2aQZNxXlx3uheRwxOENo3oGW40ZBvvREWrkGjyIdUL7qUYC2QkN6u/bR7f8IMg8rKz8EpAUF2clTrbLI/FTR+ADe7B2V1nD4gKeYcv2nrjjxzdW/nkJxJGfD9S76UsCjSiQB2Oy7w2BB+mQVEHozdf3kC0SSVQ9xDYgI8/OV7oYj3nf/WEqWZzjQhk0aDSPcZJmHc15pesL1NJ0e/9l78g7MiE40rsHc1bCtzTLUCmUn60NO/tlxiu0eIWpBme266FomnDGC1JEArdyw7LCBmLKbKbBvYm7MW44xGPtqOUQ5kK37ny6ZVNt1SIEq9KZAhmqKtWGzP1a5mu210axjLiP4ley3/qDrWI/sHYJfWYQiar2yH55agQ77agxqhGXDF86Tpqu0SJ93srH32DGEsaex+ocTS9wmRcmEoIvGaiamKqEiBRpEH3T9C5b2UxKYpwbqutHJRhgXM7uZ4QUiUXdVlMn/EQGwp5sISQAUR4rgwUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwvCow5y3rRpVL2N1EBcSDL9A/Iqg9/ChqFM2OGwgCAXJ/GHA93wZdyhahefLwTobpHAUjIcvx8bjtlUh0X/0/Bw==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAQ77Rt4gKwN1MR2YbhbeSW7V7d2CcfU7lc7BceBfaAGOhreaP+haddgu+ZanhEKUPmds41cmWvqcpiwAwva8Df7TVf8yK9whhU/zH0uR/J/KRGl5WKN9k4SAfNVYsts8CAmJ6LKi21uQYek8oAc4veu5moqVCg6TURgUz1UmO+wwKUqrkuVxsu6FllzGfvlXLkplSrRqku7kpmpHaKbOwvIUTeXmE7D1MbP7/5I0wN3m0f/NhhMv/wlSreW7ELlnGT7JIcrS1VB1/Xekswj453aW2YxPwxpNgHZEFaqSUiqlSAEhgeAEsPI1wr4Q+Qptfnli56Fjr9ERw3TiR18q7YHMTtFxqAhHMUddfeS8g6iZfje5gHAwnTvYI1j+yu0FwlvXXl2BOiqS079lDYJIvBZkzTpkBSaTP1g4cD8N19xrQULSpPJ5v5FCQAu+DAiApUNNwR2EIgkhhWGp17XxOeJxYy7MpF5vrVXIEmMiSZd9DLA3adNF0A3QT0ezOGCjCoOD4HuLaGSCtb1XKtJvfju6uSscVk2Cui221jCxdZXjTh1VAXU1Ga8snanLvNLJdL3HoP4tLtzRCR2VS/+2KH7MrLEKMpA6bOvYf7GkmFXfak1uMSqNNUUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwVSIh4U12tM/WD+hc4V16xSH6iubBwpuZP6PtipvLhSQWdJwf84dQumqnxIwANlciitvERYIJ5NV4bPHXsauLCw==" } ] }, { "header": { "sequence": 9, - "previousBlockHash": "D3F62D4AA18D107C962B87B0334AE09C65879473148623CFB3AED58BC7E207F5", + "previousBlockHash": "02307FD0FDF3914C4C8C905EF1F73746F4BBF4F4B8AC8A1DB4305A7EE1202440", "noteCommitment": { "type": "Buffer", - "data": "base64:cYtqOqKYIXJxWqNDoAe97uJzPq9y246RZlCat3WrZh8=" + "data": "base64:RjkZpEDFAnTnzIDNNmCC7y9XsWsDkyOd8MKXVl3n9S0=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:BF2xeNj2jEpOOggwo1ZnL0GqUYJM3G2hV8oDN0/+Znk=" + "data": "base64:D/hbnND/2/q95wYSgxQ7yoB2MMzslr86+Ufu/dW+BkM=" }, "target": "9094823328238119324660168503613036415495463326164889575918026009508991", "randomness": "0", - "timestamp": 1719441720458, + "timestamp": 1731435268840, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 11, "work": "0" @@ -1226,25 +1238,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAtIiNhGQHI592BD+2jwj1Hux4xU/1MQRUZgJuYVztJIODD8v556Mk/6/4S9ukVaexxVrIfvAYTROVMcb/drdmU/wul3zeHT3ixNN0lqzEABiEcpTRZRhVNjDmlSmE1F6RB8Xmdjz/afkGR5UB9nnzHxxguJUyM+2PF80dmuiAdmgTEyYhesAfl4o7nkKPABmE90st1gOs7+zIGyxc4/fQ2H0w9iq/GWicrdiCOcFJ+V2qzkEQRbqPPUnZnBHjINDeVUCAnzhGxseYJwbdDMytR+s58eI44qC9to/8FhreTWmsB+VQWHblCbOsMHHGQ99V2dBPilX0rqswdPB28KE5Z5gGXGltQ+PJeferK86c/s7EvJY+IF1CrPXTzrHsfaktE8D8+Ol0twKjQUE+Jk9MDggfQBJ1MMq50O6OIAt4dtXVGtc0HaFZ6TQBSnbmpzHzMeIslDuL1XWw/QmRzvzTb0WL2Bl6bpu1yx4MG0DKfKFEs529HOfZqYPaeG6VNkwFwPOKIkjo3czg9fn5SddOhr9kgtSfK9jw8j8o8NUZcKyyh151a8t9UjNBQVwSQacHFvc0mMdSOJrrvaDPFTIW+8kGBxCCNbFcfaqyvO+10ktxMTq3HOFp7Elyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwao5gAd6MisvctVeYZ03+D5IQh3WLQo0a/iDliETlqxzeHMVgoT6BuA2WpoF3kSXFs3hT6Qva6OhyawvDl3DlCw==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAd5MyWDAnQENVMXUw3EatFk/3wubqXB7SRvm7p65vUtuELOpKdc1yl5+v9p73zZ4sdlg2nVl5UCf1iV/c4Wuu7UyJ+NGFjk4PLb8KhmQ9PtulwT49btKhKHxUx2F1fDIJ3bP1tubDvP5EsVwTDsGp2cPVZ1x8Sg3j0LOxESZFIF8QaXvvJwIc0keL0o9Vk3PiKjrLc+o2lAX95+C53wuniQvwXVT0Fh+9jfzvz5tDnhqS9NwHtEgg1/WBnJ5LkCFpWrtwfaLHjXHcFBxAsskaqDWh3e9UnrC8ZsZTU7z7swyjosGCYwNxvQeGzU9g7AX3KiJKzdmhTm1zCiK6XuTM8PO5m/w1FcrfpFaqIAy7pDUWrwKZcvmjUXGn7B6c5bwP0RyraMrxCwAm5O8eHsAOVm6ysI9X9z+Z9trnRiXtNBQKV97dsRAF5OjrJ9EdmgOR9Q3SmzGNkU3/kzKoSYDmtbUdP3yGt00QEreIh3ykDRYvZ3psRh4ma4U4c4YkCZnpUHd+yQy4+8cBsf9UPKXrWoi/GEoLBvYNt/4WvynOa00/m/yxGCE5bLgfBm6oestDHdPu6yWN7bFXjlreAaSuuFKPYpiQobs3KQGt0Kj2kelPUhqOnmU8rElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwrBihoB+t6MZ9MbubuPOkHRXUKgB4iyicO1IAJKhPAV31mYzWEn+z3xHKTl4U7yOWPo5+pnCHj2fajuKJbi3ABQ==" } ] }, { "header": { "sequence": 10, - "previousBlockHash": "53EB267C5DF1F5B5072C3159B66F8D1B50CD168B375B59E3FD6E6A7BC565D490", + "previousBlockHash": "83A1DF422EAB288486B7B9F076970E41B724C7DE63BFF5DFA0AA96B0E9346816", "noteCommitment": { "type": "Buffer", - "data": "base64:ev3+y7hDnHIyE6if3ebI3ghqYLww3T5bXm8n7eA9uDw=" + "data": "base64:JI23uD/LbKMNTVjjI14zat5WqkA/Z10Y6jM2JNnRHFY=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:t4ybCJL2S4+VF6crnGawfekJPE87oU1+DODUqlvdRPQ=" + "data": "base64:z8AZruuPKawz2/T7lKUo2f05ZYQSJ1PlAL9B9eJxfJ8=" }, "target": "9068258834662928698220540790897658244352076778286486653826470223999191", "randomness": "0", - "timestamp": 1719441720930, + "timestamp": 1731435269127, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 12, "work": "0" @@ -1252,25 +1264,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAE6GygXOf4j5BWdT54nOCh1LUxINfXl078ml5S+oGhU2wx7vinzi0qjBA5rNzwjLbTqA+76y6A9p0kiC015v8f9GmodXeBtIYgbvgoHVJH2W3s4FYdZvB1EOSuJ/tAmcLQPFR+0+yTwVPntIqRdcP0aeeWYDYYJQU5BeXQCdS15gOsREn6rTQBE/s4SN71KqX6/kfBXB6dDOpxDiyEQa5BSkEn6gBIsXUKiYgNd+IO42twxIGxhtYyhe6RpK768kEij7hzkdxLX5iCVNPieBoduZWDlj8S+L5ti/04TRdvFCP235VW7NhrNvptyi0sl5IQnQPi1po0gkIBg/HqBspWzFnzs0YPCrUYeny/jbSetdvBgW0adGw2yneLxD5STRvtcilpInmVp/r4pdn4k+dRckUPcyPgMiLx4xoGdr+88IvghXNortlt+4VspSNgWdLjI0cevUfkXTfaJABcXZnlZOTwiDyk1+H7IJOHJumfaYGYULKGG2TAVbdUi+nedQMEMJS6Z4SSsiwdsiapeGPQ3WbIgUe7DWPxXEw22mnkAYRqaXt4nzvc2IaEXLTcJLYqSjMHRCE0Z6qewZ1jbzwxxTdQ9DHcOXyPezWm+Cfe2iPYDs0bjEaBElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwSIDJaB3bHEHS7BP8+JxkwqnH3yDLXK1NCBN6pT1RP0MRIlqm82jKO0lN5VOuSv8ikxtE5Fj5fS82EBnvrPDpBg==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAIm72zymrGiFt9IevXch3I4X2CAPY/KRZVX1PWlJXGU+qkHljK0mx8fK+BkThlcFvNyLJKxyUQpOQqMZuIdnBOdDGgfaHdksc7lNwcUHlL9uYKxisljQaEZEmrZgYRkP1OJV6X4dQamjnINhLP8yRpyfS6A2BhLFkPpTuS5vqe1AEMa3J8Lt4/6S53B8y5hwsPQVK0nlAbZSZDJ5vd6kTSYPMsODTxGEoEC2t2Pz84AGmL0UjiYwsqC5OMxKe6Laz4071MHxX7aW5ViitxeE9VZfBRY5et6lvTaPcuZExT3WKjyer86h7em/bz6u6AQFuEXNku9alzaezEyaYCxNa5UnKPayVPcbGThUAGWVWkK9b3EVqXPdHHcKxZXluy3gjnGn/TT5kXSTmP544hWSqIo6OMbblXQDM9jiOJXPPmkEPC70a/jJTr5EbLB3PHcQrKeXTDspvo3wQLYTNwIkKd46whANYYPsdlWOeBJcRY0R4i0DBnpdEg3K51nJ9Rt2Eo0jIu7WCXpToYo4xMlVElTFyP6YUjDgwpvOrj1M1Srp5MIaBuPaMx9S7VCc+JfhAYSnqEzw8N1b/Z9cpr9CGKkvBkpVuR3dspQd5eu76a7uOAATXb1iZCUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwguzb9cSi3gatCCFk032ap2WpCt4x8rB34TRkwuvvhUQqRBR+3LWrHn+dCc3FVnXW8zlfCSgOmZvRpXlHFn6uAQ==" } ] }, { "header": { "sequence": 11, - "previousBlockHash": "4A82EA2E6B86275150FEC2C6AC1FDB1A2262462DEDFCE722ED151E2A5D974A76", + "previousBlockHash": "22205EF619A4918EBEF505282EACC36B483884D4C15B84B7D475EB1457C847D6", "noteCommitment": { "type": "Buffer", - "data": "base64:6FzMIVYGtIqhuJo1jE5SdOhYSqPvEoYdiFtHCNei3D4=" + "data": "base64:6Tl85r1j/2DjUsZ6LwlGHAyUwedG1qWwpOP/jcgolxI=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:wmh124Szjs67KYcdnrL53mz6/+x3QXeiEqTSs8MxNYI=" + "data": "base64:GRJEAybgJhAmUhMj7UzIXBj6ua5/DAWkpIpCITBOglk=" }, "target": "9041772817458669358631436925553476123971485443441062513642264290171806", "randomness": "0", - "timestamp": 1719441721391, + "timestamp": 1731435269415, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 13, "work": "0" @@ -1278,7 +1290,7 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAGo2d4Xh7XjUHD+Z5PP/YK1fMNt2Rm54fo4Ur6DRViteRWqVhbLy+3PbLLA2r3GuBThxhgcK7rv4plClp0NJndKoYWCq6fMzOo/Jp+W/YGVmk1q9591gptLOXoO1dpBYtJGHDwvNEkiEBxB47pYYRfINPfql94z0W1M2Sro3R410P9+pOwQ9JYdWmWSnCl3txn1eEmYPNVPFcGbsd8ZzhOhkIDV7qSTCAxdoWSAKYiNawFC/sRCxGYWHArMbTJ9aqCnNAy+OKd7xokz+4XWNEVuncZFUSuEJjrtiLsbq8qUS1PFCeak6KBWeMo1ncjTNLQ2RCQXnqlQOud4BjRIsN7nMyOQXr/R5LUf7OQ+wfWmiVsQbHBfAsss8KIxN3lUZny2voX9xVxStOadO6ETC9KbMVOdmVKpVwlmSd0c9bEGVaRBGaXNLTeLHxLdpqgwput5rgqgwMiFQnprT3snD5e7tBJ/yrWpo8lPuSjjvJJOace6d+Q/1q8vkcVFDDzlLTIDnb5h1qygCxfZyZW7RmM3l5HcLn6vXBUG4JaKC2GW1LYAHdCAMfhIIdEsQgRCl3jz0s6g8r9Ao+CriifmqaWuy2hWwmTHL2Ajgb6CTT4spj74tEHUf34Elyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwu3yIJiuBIErbZCJyytdbtIVAotG6Ri8FaPKaopuUp+8PlMMYf5C1CGqJ7jYnM1XEGf2Zap6VhPbpSsA9sa+LBg==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA+1QaxTGBBtKEWVDkqoIp1uDi33z942eg89HlzH8VGJaFxGRIav9m0HcpQ0ZwwxV2MS5m5BaO50eUAQkhHomEGSnx2674ipjDma9xdT7MpamjD9l/v7NnFn65mWgKjFtW8N00+w/lTt3a7zem3FjtKnl82LlaYMKyh5k0PV0//asDYvs1cmAE7FB47pP0dqC1Ki84cF7jXib8cWReC/xWtQHqQEaxBpoBmWHds91OE+KRw3vXMCB9jM5r2da7K+TXU/VgdywZ9GDmgp+1rn8TMDi9xmSsZn0tzv+dufEUStucCXZ1XKyE8dUZcsxT12Paxq18Ws5xpTXVdd2QHHWoyympOnEDV/7EaLAfbwrtch+qOAKI37OHsjmUiY7+LdFweLlyspm9FAjDK3y4xTg4HAbI8DTpHnndHZSO5mWD+WtlzTDRSYh/CYQY5TqzgFKcJM75lXOeoJvPf3gEv9ot2bzagU8ASgRNUVQ55fHkJBf34wQKtrTPb+ZjuRCviW7IwBj6AHdf56A6d9fLfwaO1a/fqZxBcKkEdtSoMhqmeKFc8+vEZeClSnLhbbbD0zi/5OnlJTKoiZXp+3+abD0JSu7WX1l24JO7gTGGxH0jYX6wKpOYbchNJklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwlwf55bxJ5ziN0iU3HOMEoAxv6dTK98hBWLaajln4hRClGAex9gYc/LQQ8Z5JPXJDqCF9TKiePgTRvt27Djp5Ag==" } ] } @@ -1286,23 +1298,24 @@ "WalletScanner restarts scanning when accounts are deleted": [ { "value": { + "encrypted": false, "version": 4, - "id": "5608f677-7560-4083-b0e1-c0a3bc57925d", + "id": "25d7b23e-0cca-4595-b6f0-72553eb1ee75", "name": "a", - "spendingKey": "dcac9d1a7fd7dbfadd9bd43ab3685aea9c5b3b74a4d7c9ea1cf40070ca7fb998", - "viewKey": "606e9b1087636b5c49fae5c3895de7de284e279c1f483cee1cb8aa61998697a8159675e7626ba5b6e72571753245a163b67912c632c1865c0ad5140f9188740b", - "incomingViewKey": "a7c09bfaf0cf05f6612408e7b7b8b67b10a35bbe66df052af0fc0cedad26dd06", - "outgoingViewKey": "792733ae07f1adc6fe9937578ef5c4c62684a11a3d5c49ee74554996fddecc9c", - "publicAddress": "12ff1c08a13d088f5a32dc32856c4802fdcd456d249bf598c1c862bdfc512107", + "spendingKey": "17704c7e8000f8920c6e2c0f0ae63159db5c3884d945b4e1e2640c7eaeaa699d", + "viewKey": "954c8aa17cfc4a7aa374a041a148004e28218a37f43ea16cbefec175499bdb64101c5ef9a82db1bae723acd9e5b1a69d50f978aded28b154a3846ddb294c6d42", + "incomingViewKey": "9d06cfebf99c6eb2c1681c14043b326aaf0c81fa47d7ec72a292faa53ec36806", + "outgoingViewKey": "9693b5afa9472a1664003b44cdea6f1f5b6ed078e031f7601553542a17721292", + "publicAddress": "7e9d72197ceb051c3fef01139dbf00097de0159d3e73ce61e6add153bbd63a87", "createdAt": { + "sequence": 1, "hash": { "type": "Buffer", - "data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" - }, - "sequence": 1 + "data": "base64:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + } }, "scanningEnabled": true, - "proofAuthorizingKey": "8ed1d285981ae911bcd04fbcca375f5e4ca9cfa556e98a6d1645ecef721b6308" + "proofAuthorizingKey": "9f502af07bfca425f97075b0cbc540bdc435a2707535d596ae8130ae800a6902" }, "head": { "hash": { @@ -1318,15 +1331,15 @@ "previousBlockHash": "4791D7AE9F97DF100EF1558E84772D6A09B43762388283F75C6F20A32A88AA86", "noteCommitment": { "type": "Buffer", - "data": "base64:CWScPwTfahtcIMboVijiRAd5hMgWKrjBrcqXOcyYUTM=" + "data": "base64:YTlK6cHusVEav9ZyrX78/y0tOkX40KW7HIHvJe8xCm4=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:kuoQg7iqkHOZbvaPo4S47KRDH5yAp/FntwfS9Qip60E=" + "data": "base64:ayXRYc9aGSwqe4iZko5h0tnwrrAuLlEDnvkUtgoF054=" }, "target": "9282972777491357380673661573939192202192629606981189395159182914949423", "randomness": "0", - "timestamp": 1719441722383, + "timestamp": 1731435269941, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 4, "work": "0" @@ -1334,25 +1347,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAZnIqq8GI+k1soBWMVkwTI+IF1w8RptHRWvDsIVXv1LOSG15YUnoylKYevcaKzyEV2wxE4fxLJah/mZyejjt1h1D8JYrJI4pWZ7PZP24uHWOGJjtZfI8xo2ci1FdHJbaDwXPRaQp/L+Vh4dkvu6pAaia4A4TNwe6p5kjt4PZOuVMK0XF/ZYaHC8AajbbqWlQ2W9TNhwDa9UTD+jLExW1DF///HKanqNVsEkE48orp3sSQ0xn8j2AoNsvjA0NHY7h750eUv/JrypqZzxGgGlK/Uas6ySweqZogvmcevTgZTEjYtrVK1l7ChwkK2fKWpo1Pn1Lg0NtNt2fqeaqcZJCy1V51zZgZ4GcG+SMH030h/PKPf+88FMHk4W6UDVEGBxRqiyDo2LzrWI5OtACMX7GWjVmXREdwYtcVWW0akUdUHMWGHLiu2LB6Gx6VznR+75ksPpBxGWB0c+/B4ctP+aeAhtvCPGZR6w1ILh1QnxT72rmn1bfTBNsJ8nnu+zml5Xrfzg6/4XsoZryqto6BR2lAEjilGdHzidQvuPnG44WPHEgHbszzRucDs0Usk96uV3WBhT+fJ5njuBfv2HD04IMJpHdzFEANjzggw3wi1jSHqUkltjsR5xX9xElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwFSb9Otwas5kPt7gNIWpLZ+HcswCkTmqOjOZmWm7SjQRSV3u0wU5t2gIgnZwz2J3ghl6TLwziif1z0XNzvIfiAA==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA2XhWxzhZJZbjN+CxEI3Fz1YKMZWn1MXjt0WhtL9ITquDWq6i5vxkxrDbd5DxfKh1jjVyQg0hP7rgja7rdhQlBfl/Ox8L2BVRqXRNxTjsas2zRTAkVG74+iLJTKOQXACE6gIIhxzE+Tw1rMRPRdPD2iKY8PZvEl7PPjCsSvGeRNkIvdYCe1YmuRceM1S/AuqieMxtRR1VpJ3yhik/U4M0kVC5tXBkoSsXp4odYdnDHEuHF7sDGiYBG0Hd2xCCNBfxix2SO1OFNZsy09kERyGSD7EJDTyUow5ZDrI7xHv5svR5aRDWJfdwwCFBYLNw+q8mFZsJD9gXomBBnDjaddWKSNTrPuzLy5NuvoMnASBY82vRZUbpuHx0+3qHeoiCXmcP34eZX5OvRaE78RP27lkpm25Y+APksX2WYo1JG0e8zOb+meRN+wtml68wxWMnFKEj3yiKaaVrlU+2v+vIJA0vEJejMxTsPii43FgcDyylqxuYBJ8iw6mxlJhiLb0Laod6W0EbDNB6lZ4+gspg0wyCkoKih8vDWW954GofpnIDJsbHvLsADHrc1d+xtTUv6dm5Ce96JucXDyaVk/tdunUwjOEzjgh+04bynWjd9S9N2l99sNEq8Zi1Y0lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwyppaxR2HVNXDXuyQm80+mpQDeYzaBCgaU0osWJEMqcAeR0WXvih0xFjWhdBuq8WF7HIRSZ6zOEqYKRu+wQZGBw==" } ] }, { "header": { "sequence": 3, - "previousBlockHash": "466643FB4B18A594EF5DBB72ED5B4BF5ABE28D2E1EC1D21751AEE7D51FBFD15F", + "previousBlockHash": "10E22A68A5BA955A1EC48D44AB374A9372B90E1CF6E888200DA9951F98BC4065", "noteCommitment": { "type": "Buffer", - "data": "base64:zyTGIUK6jeU1Ir1DyS5BTmP3ldDdnxxqTI4dZNkVa0g=" + "data": "base64:iMgGaYFAipS0pglsV4Ng1QpKMwgkzQ4tNWXUKZltfjA=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:LFCORxJMq7CzP89Fk9vHv5iBkgXlcetC7lcy2zUFi2Q=" + "data": "base64:YBA1zvddR8JfKMh2kiwksiCPKZFfi2Y/bLqTd3lSZE4=" }, "target": "9255858786337818395603165512831024101510453493377417362192396248796027", "randomness": "0", - "timestamp": 1719441722848, + "timestamp": 1731435270231, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 5, "work": "0" @@ -1360,25 +1373,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAB6XmNKp1IY1aEURzAqClfKG/fy/G4zdecGyD2x+B7Kelb9cDyxG6fMF21oVZ87u44EfCCKQDjrVvO6ORC2aSOCGxt9JHO+DJrmL4HGJqAwumHFVtVGgO9p9Er/p303UAB7Az46NXKB1+8sE43C2bNNhgvw4gYxgjU3d1PqZqs08TaQEJLLpX5DU2xkAAkUfhDGkl3w4GsgiCE6wcjNLHIedkqnmNPnhrLUeqJCSSut+xraTLuKPsFj9F9eBNWlAic4XcT4XjG/iK4zvY3DMfTKDi3M6+zpqNDqWLU4fMY1afTr42eqDb5zdaS898Mf67pML/5agQ6gTkhCDCSTcWOHrpyEJVduXbbarmEqoOAVjStgpz7bT2x+jFwcc9+WxCCBoxydOWK1oAABatXfPp/L49tEqp7laroroRjhsDjCql3ZkIxgw8itbXFrX61Bc2/KODWhhbwT/GPwc8p9kzPlk0XCTC3FroB9ZX6A6b/gl1VExsArUFNk2TRqfKqZjuB+3oXCKZiKqQ+k5QJrYwYsuHCMZfeuwzLaduPs8XjuM5rkgLAEPtVUjPNWd66KXymp6xzcF2jbBOGED52luOjRwMe58wGoKYcEGVw9fCmFiBU53pS6nvfElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwBbOg6xkDd2TUpT7SGMFhk7mVfpTs9qNVhPvgkmJBG1O+Lx2kh9m/ChxDDn8/KgzIMyNkRFr6DsusJF3EXSkAAw==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAhWDaEkD+Eejd6E3p55YFlI7nb0Fy/oGTrErYX7MhkESgqLpwrTERePgB9jU7/9rm3H8TnwgfC9s2PcKqckIF1Eoey07Jsv8ZWgnRKiOm0W2FXUKW6Ub98ghfWPtWLlYQgbyomjpSodBZqP+2aDavmGliB8yvG5so0YD1c5byEvEMN1dh6SZEEE0+pX+77B7QMIX7sfuNkRQxgTQi3aLn+iAQ6u/8uFhagNl2o/MOMoqrwL/Uo+VQkhdvmuFj+Dn9GH1ZAg2ljeJxX28mq6xlxlaEIOmz+OKmQxtuA8BpWvhzjOBCkoZIi5iYY1Jk8kCHeaS8A/vAalWQeuzt+rvMHgxrLbUXnYFOcpB8iimpmMjmLal/XDkIx6kuTHjqCBtvICaW7SghEKwmE9DNBv/Ppu5ZZGW/5zZ2iTKGlGtf5uzOgoA20Ev5ieQq9vUb225O4ZVz2989UBGnm6izzyJ/9lCgxQou1WgJXli4POwj8ENMM/MN3EMx0siRnZWK1QrK5hZOqp46FHY3dPbHuUYxJ4sClkkvhhv9Yv38nSx8WwIbSH7O1fEBXpkQmmnL3ohVQqiq1MI5nng1db+3+PLoXXFXNPN7EfRbUhbMrOE3B98XtsS1xrl7fElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwkKe7/ZG9ZhFRGOaorATv/BWozmmkZ0T4z1SJIHNBHu03l2Kl/zp4yAUhHZtGjKqZhUM07hVae+A5l4K3T83MBQ==" } ] }, { "header": { "sequence": 4, - "previousBlockHash": "B58450CD35422E5444F91873E650DC3F2CF8E786579285BDF92C55F6D6E08173", + "previousBlockHash": "1361A78723DA260B3EE1E888AA58B2E4C314A083CAEB21CAB9503929B207353C", "noteCommitment": { "type": "Buffer", - "data": "base64:6qloozS5eLcmWGvV+SkTYcF5t0xSQVv9zk4Q/GcP7ks=" + "data": "base64:uQaiEDyLPRPmd4B2ZBrQ5s3NhsDtrGGsdcjAxf2r4jA=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:bOgn8i0XizwLH4BR6KZI68hq/3tF2XIZkFzXXutixwU=" + "data": "base64:iUujf4t9/BaAo9otlDb+DUXZVGrmoCb9Y0xnWDP87IM=" }, "target": "9228823284279306817296266184515742822248210830185427859262273659833347", "randomness": "0", - "timestamp": 1719441723332, + "timestamp": 1731435270532, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 6, "work": "0" @@ -1386,25 +1399,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAPlTDIPH+5KJOavtB06clP3o926BhqmULU6lOcwv4+xKArGOM7GDKRSnUNrFxzyC2TlIcEHckCBJ/1o4CKyJyEjPA3Z3E7A56Yw+ho4TPdUuQftQpaJCIawzeno96qTDhSpqfVVK7SHiYLScjTLyManq8b+KFBeO1mK8M7spJHPMSR4sy4ZNoWO16DcKWR8S0RAaRE+HWarUzn8npKJ5SWDRy9G1oDPrmzJyPNZd902uR65bvMBzSPsuDQ0psttmvbDRuGizinxELPAD48x/uD0yWPRNVsz3KsZIoPteRyYKa22erA8L69d0FYPpVuNgl5Yh5hQ2F6cUz9DJyOuUNWN3hR2FC0I+NuD8CMQC9MSJ0wyOu9tCSEWFNaWHNPlkOu/+hjcCJRBN5EJLIpFuHMUZVcJW4wKJqPt94LRIepAFHzSPVOy0qsgufLXIc9QOqk8csuJMEv+iQU4u5PaFHBihVvUwS2fIWE3xT5LsNFC494509RrNQkH/Wk2yj+UotZn475ZcNpDyhsUoDSZazc16O3ojcGZ3WVAHvN3/qi0QqV4JILiMcxnocyimS5JgoB1yaeMK2bGWkND6Bw+H+z8eBWnEWEO4dXEZWx5ymRVJOX06UFa6TNklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwV1zcPBDJn9ojn0ISFZ/oT/1kYxVbwU+D5ZC+aP+tj4NaT6ZN4n/5PW1OG5OUpfNgnr2KU0VvBOvVtYd2tPMaBg==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAz4j2otbDg9yGtl2gF+NYTnY12IRKhsmUTf0wb2mJWimV3Vy+lxVMqa5BGXbuoX/4WioAdgkVN7beXZOZRTwMHwApyPdp1HaWEhiBzLFg87qoBWZlcU46ighYvHoS43in0j4qaWXRqx12kpfHknvaqQye1OiPpSm1JIQq9x+EVOIOu5Lk7jRYfuZ40VY1Acv686KMkyPu455t0kQkkzw4A6Y7xgrzthA1ezecU+3ZG4uXH8jvxFlL7uHqQ0YbQpc26IuA5FzX+l5cXFpcnEsrD7tAjJUuLy2emZ3dkvoP3AVLtvvSmVIIWwXNFwXrQaTWPzUGX0UF7D3JCurlDM8WKGwgaujPZWOVeq/w4twzaFWXUvucTr8b3SSPiRwF//cEji0cdr7a2+RVLLZhvc1kJswkuWycAHtxia8bP3qSWSiSZcxip5uUxWyF0b2z0TxxWTkdfK2NTJmWJjVVOPwC50Hqd5YlSRHBoIpKPdTfVe05+stJgBTTO5g9Oxdd5apkUNiUaERicI/M+h7sWk+gh7Y1OWdjhhqhRTBHeBLeggHj3ZQ7bLXfdOASEqFuAIGHgFf65aKF2MAtcHNF2Nz0RiB/kZbbAhobVrsM4cVxNwe/4AMkJHKT0Ulyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwKwdSbg8EbgRM3cua7tAtDuU/swt+9zHznz2n5zfnLBVXgfeeRBQrhQmuWq3ddI27M0ps02Q3qOjGtEXcoVkKCQ==" } ] }, { "header": { "sequence": 5, - "previousBlockHash": "B482FC6360EDDBD188F0FC05287C2FA687583C062192C3737CF836ED179EB8C1", + "previousBlockHash": "BEED3CA9E61B9D27CC7EC7D96D39CBC3B1113A402A974AD3C0EF1B3531C531E4", "noteCommitment": { "type": "Buffer", - "data": "base64:Wv+VfZYw0Y+eVslZb19l4M2X2CdLiWm05kjIpZ0qyic=" + "data": "base64:ANJ59LVIK6Fo+F/CQA0hHr75L1TZ7t2Lt22czoFq52c=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:FRBKSxosajxBo4ELpBQCL2iF+f1Jc8GXYGhzqRAIgww=" + "data": "base64:NwPrElBpf1Yo8wvPlspbLnPK1J5uEaZq34IloTululI=" }, "target": "9201866281654531936596795386791503876274441021197252859723586932895305", "randomness": "0", - "timestamp": 1719441723791, + "timestamp": 1731435270822, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 7, "work": "0" @@ -1412,25 +1425,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAgcY9hi1n/ejp07obbwNB+8IEmPjRas1IH4LyS0Ru9FilZV+UUZoxOkonNJ/MqV0BUqWCcNPsLN4MW0WUpCTYtQDolVa1p/ZrC4BAXhPl/s2Op0RJw6WbaErKgq9V8s9S9HJAQdBANSEEZzMoiFoszDt6UMdw/yhVePJqegbcGqMTKbxGq7Csw2Q1d4SD/SOvcVHr7db1dIthic13H80+JYTqlWv2POgM+o6vsJp+9HeLSKYGAfCdFZivDyCaKmjWR/uerw3Fd5Fa6Z9B4hgaTUsg0BPKs32rNBXQBs2+42dIo9IoY9vpbvpFfJ4mtMHyEIcQLbhmLaBl+bTWiRC0BSUjtsH/wzRtwyRQjkwf+GAOe5BdlLmUhXFOABp5idZg2hnM3aglQfeSJkvJVQVuBGCffH4XR8fiAYpIRamQwtMFNPHvFc/reY1Qew8WmlJ2zNNo+2/TGz/fQlqyRR4TQ/QBby/LZ73ZGRBj02/6tpmHNafiO/yig4K5QPod7/w1VnSQef+c10U97dHxa5FhR+cZplJfVCHnA0hq32l1RoY9G6bFISazQsZdzCq89OFBl0vZXTKryFPGpcsp2OhJrmY8+lMaj2sPahGnxF0OyK8ge7ptPrU9eklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwSU3pHewJqqCx6vPFXUijDU4+dVQbkUIvFcjjo24Sftxyg3QBH0k38MPHtLE3CtuzDZSMzZbYPT7/lupzKz09CA==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAQLbz0z4IeeqLWHmBkD6F8onDVS60Nn6BKVmr3QMC4uOoRYZNaSz5pbJijlqVgIAbDnZz/qSE6CNdcMC9t4KnWSsDa/RwsrN8W34tidQFgr+Mwdr9dhLVCkdv+yzXe+ZaBMGqynZzCfELR29N8qWYw3+tgR34b0kXr+kUpfaq+6sC+wqbnP+7syQ/mrCpSTuHfcYCV3uVJjIOVKN2+2siSKid10Fsq3cuijsv/fyyw4qYAqv2Epns7bxH9k7GyiD7wZPBH5H4balwLAsU9osCEfStwdxpP1XyGDzzWKlwUsb/Tug5cJ4udadVb/0IXSrUqQIJXSCucbyJSuNqaboZad1FjNUnl0OXoA8SEInYutO8w+/VvGCOcMYLPtzaGY0B2ckF2IvAOML+4e2Xb+ptZlLFtnQNDzv49CsSBGDAN4+Ns2oWSssOQKofyV0yG5ABZRY314DiIRo2eQgqBmvW6vI40T/G39oEL6SMan0ltNob0KA06oe8+8j/O7amqK0OkYTcEPiICrAHqBCCDI/737qeM/vp3rkgKZGtAyQnTWK4DAqpU1psJEr79erG7kDgvced25jHSkhSBaqFJ/MRbFBHYETOviZMVA0un1WQd7SDLC92szyZ1Ulyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwKng2owpoOXqOsa0l+mNN5bhNscrgnY/6C86W82Hajcp8yvKMeudM+qYP2qCLwFMyQ1ChcqyOFxYB8giZm4XxBA==" } ] }, { "header": { "sequence": 6, - "previousBlockHash": "92BDAC53C9E46C08D28E7B5F42D873AD7740D9DA8E6671C658E4EDBAC4D65F27", + "previousBlockHash": "F424AE6859C038880AB4746F654576DA9C01001EB4BD2855E8046835B5C121F4", "noteCommitment": { "type": "Buffer", - "data": "base64:ymX9ZhiQmvKURrRAWADntPSogpuulpZubDclLilywyQ=" + "data": "base64:hdduIuebPmI7vuLLMvZzohQi3innIz62Yq+IUoylEkg=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:lQXSmilggkyT+uHihSzAP3A49aib6QfmFoEH0O6s2aI=" + "data": "base64:pgrau3A05SEeJRPSVTbRryMoiVDfOQig1vGQYOGgVDI=" }, "target": "9174987784651351638043000274530578397566067964335270621952759689537226", "randomness": "0", - "timestamp": 1719441724261, + "timestamp": 1731435271113, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 8, "work": "0" @@ -1438,34 +1451,35 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAboCjeRJkAvi2tcNO+D4UJNicmWF0rozhHdMyFknr4pKyXkDINjF013wrOvy95oZcNCjU/FXlRvLHcnfNnh+fe1NL2Y1dUYG9rElI67VRe1SYlnmmG0Rvrs95ObuZTnoVTp7v1qj5r18834JV+4WXBIxWee4n1LCnEoy2Z2t/zOQJPAGV7nrzpMw/xs+zr8bs4GEl+a9VMDcP+foCEVwagTr8iuxdlSnSo+daLAbXZqK0n48re7hkaxq2hNK/zGsPJ1MS+VvQIvO/+nbR6IK8Ev3mr+WY6mX531UIFJXweKfhQzfGIKjtfRgX0jWvYAP1WSa/WH/No+cGD0JNnW9EQxJEQujJyYWq5fe5zPEaYlaf00bdr0uENHPPf/Nm+L8NaUF9S2QS9Emkv+mQkE5QP3mw1klOMYIe5pg51mesXPJ+uCxQKCi4v7b2PsMRGoXJMu+vpIaELQpw3TTt00VlTS7/8u57sQFsIiXSdZU2uWbcYGFLMsaOCVL8FAi8xbizqkCmncEO4+c1BDt4IoJdcCEGd8AweW09Tc05Wyn/93KqMY+YcArebYFKa8Ox62V1T42Fh5IkuPjmXhK7GAzU77evl03TA/VRAuA2BtmgPrFLnXfcCXl9/klyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwawhG1frCkchqhA+Dk+hMmuc7QhhB/Kj/P9PriQwAsghJhUWdWXRm7y1rLsOaEXdy53klgVh598Jlk12bBCExBA==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAt+MzUDzKFOVEHWSaM/pAF5nNX2NjvihmHs7pHq/JgGiVtTL7cmltu/6rnCWFdFPZ6FIm+8Ee3SG9Hq2lRl5npGEJOHLFrC9xy0Bxv4KFcGuttmzCENsHcg2kloV8rIYCBb6WE1fJSh8ee+vIBbEMwGB8l+/Y9zuHAnv328pHz+IYGFG6b4P766QVtrBoYVyNygahGivDSX4Phk+v7v6Wk2zv0gjN3uAQEtUT+59kmoyk8XuPGHLmCXtag//FKunv2d2QPQPMDW1qPVnc1b2nKHIMvqlmRlDzXqy4ARsstNkvI1/VinQ14CvTtfRRC+AnWgGTBej9mk87EhejKPidiZj9AN5fyvqBU7mfZ92SCNaxE2X1F9vWZlQt6h1wyEATfWoJPrBKVVWLniqcofRDMtMJ6YBIC1BS1z2Q8GlBceY/NzD2ECeH3sKlSlAGXld926Ymb2r7Tn7ByVgcHAOQ/oXxqqFbPU3eq4izRwZBJ/LkKut/g5NcrGP6XUm3VoO4tCb8Fsf7rbKwgffC4ciKEPnCkxKczulphnb69O+l97QRWI8+oof/vDT4ajEAaz8kHW0ui6nY1Q2tmNykq//dKJZbo0zUJwyl8SR+QcyEIgZb8ZGXjDAzRUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwiTVH60GrFhF9HXO3Z6qr2ActAZRm8dwFHQ1LlbY7OuweCCjehtH7Py3uMJJs6NmvypVVnnh6qimR2R3W89nQAw==" } ] }, { "value": { + "encrypted": false, "version": 4, - "id": "d3fb02e9-e057-4488-8ee5-776e98a95832", + "id": "0860752d-899d-45e9-b273-6eb8f3d29c63", "name": "b", - "spendingKey": "6b56d56e3307f99c16185bb7ae745a2c5d5e2750296a9c006dd6b8f7bd668d09", - "viewKey": "9125d6ca8bfdee1648ddfeb582e3342dc190b8b01bdf9adc6da7e8729692384385b00b181f670011c76c16cb5e67bfcf5bd9441410da8df7fc9cad095d9edaf2", - "incomingViewKey": "51a727e16a0be7e2b97b7dd528b4231d27346ffc10e01ff95d21fbcf2d042605", - "outgoingViewKey": "f9f3036afc899e86e59114786107175f84c55a23c9c1510e694b3a36e83a6959", - "publicAddress": "bd5031bf9cb4087f3069771013b389ea777cd9239fddb51a336b81273f044658", + "spendingKey": "bb5805856de4a19a077f5cea2a6c3294a6bc982f13cefa00529bfaee2b7d7ab5", + "viewKey": "93dd19f8a8954e1c9ba7dd0f66b26740c1e0ee9ec9fe3f75883d59f44066dff2985cc1e69016dead0e5323761f9f11c2f8d8d10cd353a4f42152a87a8212354c", + "incomingViewKey": "2dcffd35c6c6330f5ada935677c854abc2f9e818735223190eac08aff2dac805", + "outgoingViewKey": "e8d27665a132d2c5ea4bab718420c140dc3a648694d6e05ec7283ad1f585db19", + "publicAddress": "6ff4be8ed8f0c02280fc033a8127257c78ef7acf8190f7b00a2a36461fc2496e", "createdAt": { + "sequence": 6, "hash": { "type": "Buffer", - "data": "base64:DB8f5WPbF66VwBmmV7BJ3g/M1epyx0+0vepdatNnVwY=" - }, - "sequence": 6 + "data": "base64:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + } }, "scanningEnabled": true, - "proofAuthorizingKey": "17293732da2ea50bedb20d45d87054bd8a5612b989f82b6c34e09c857c585304" + "proofAuthorizingKey": "0b613196049a865fac9268f7bfab3eb2265da2564e89166096886e52486a8c04" }, "head": { "hash": { "type": "Buffer", - "data": "base64:DB8f5WPbF66VwBmmV7BJ3g/M1epyx0+0vepdatNnVwY=" + "data": "base64:ydBynIMsW+SmoVwxQcO/24+KkWgyyBqp8xPYVsq7qkg=" }, "sequence": 6 } @@ -1473,18 +1487,18 @@ { "header": { "sequence": 7, - "previousBlockHash": "0C1F1FE563DB17AE95C019A657B049DE0FCCD5EA72C74FB4BDEA5D6AD3675706", + "previousBlockHash": "C9D0729C832C5BE4A6A15C3141C3BFDB8F8A916832C81AA9F313D856CABBAA48", "noteCommitment": { "type": "Buffer", - "data": "base64:N3ATvm8lufM1rk6wyRWgp8+BM/aVcpHsvGz2gJ7kfxA=" + "data": "base64:iQxz0h8l5p8z3UBSFADFGTR+740quEY0pIoMkELjzAc=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:76y+Po/GE64VvnutdpKl6U6B8GqPi3eBb6tZFmRGkYA=" + "data": "base64:BuKVuSX3cWuCsOoB/kihwUB1M1UfEn+ZYBwYu0ovBFA=" }, "target": "9148187795366513087508709149025146424715856256637674150531751753357577", "randomness": "0", - "timestamp": 1719441724731, + "timestamp": 1731435271404, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 9, "work": "0" @@ -1492,25 +1506,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAlSavJP4InzBX36z/iM/PlcD/t9MYWTVQWgL6qgLHaBKEX0ZdvSdDwZOEkAimcbkmeJkyhcbBxv38bS4wlhhlPINKRWShGyrkkx313CEImoGGpPSbklrqORWwwL5j39WWrOfgMm78NkkkAsoiYBgy8jdW8+uD/5FW0e7XoYzvyGUX3M7OeFVfaaJY6mRTi8oSBezVZyAevBJl5a3vsN7QyZRXdtNT4fQd+Dt/zDtBe2+m7fS78GTI6xKkvSrHreMscdK3/gUOvDibX64BhQYAmNS+nvY7/ywapHngJuRa1UjsaOm5/6vKV93Njx4mH60NP5Ld3kignMVb9o6MEeAfwLLSMkzxwVNASydKSwTfL1M/aIz0XeOoODyEAcPw2D1UeOBYf0Lr7i7wVvniV4XMXRMHcTGdo6tdRy7LvV6ziogmHFPTd8Hb7ovGINqots1msah+kCPs8JNuhdEuinFdsi2ExUAO5SX235lENWya8//aRNRti4IuA/P4OMVMpQ2TPhwCEa1zP0ds+vgSUmzHghazLLPRj2bdrQPvmKkEK6WG4UgcPUDsCNlsQmzSHZn4RVUQM3Zdj3BxklKiqFqWvh9q75/41g+r9qzkEwEUjEvK2Iv6uG0DBUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw8GB2r5+jcduJ3AHZAccMM/gKGcxPuMQmcovNB9efttPsR6he/57tprIY0uRySZ5aQ0mzHhYzi26R9ki8uiiuAA==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAUkbYB0HsoXMRMkP8E/Zwv3sYwAcLUNcujXtvgVZv1gCPoIIHtw7Fd3f7LkaRxBWaJ2qoBwK98YYM+mCeedVZPjuz7+XJoqMKyxaTvx+8+hGoL9kdJVL++myL3iDp5KVOs9T8iclEW/kjedNRGRlavXFg4bvCrEuNR47xvnt9UkYOVZ4rmLPXL2c5lyp0eNR2Zn7aAzdaFfqwVlNTi+GL0vU6ZlNzFgs7yYLITMSIGHa06cfdQK+k1YC6gWARMe7MQtvHK7BRCwKftFFkYXANhgDlaT5bXhltXoYFnZKwY2J0kKVoaz9e98exBG80r8HNUQq7rKjR/wu2k7QfDQE0JxqTtFljQTCOJaGGH/jzgZmNZn/eFtrh8TB8L3VnO7cBNcSTGFNN8waBoAHtsoqOZBJ7NDnvFxAIKgxeconiqVivAVcab/u06mhSMqspfCrHkZ9L1fkFaM2icaFhcncgl6QTg27UwBp8MBS+y3cbKW/vAuLy3WG1dyvLDkSWADA46sZQ/UvLaB1tsK/4uoUqZN0SJHaDTVZROU+20Nf8vQ1/1CxddKQ6u8/imqSh9ENaFfMUGDacNhwELY/CN2c6eBllW/pWk1sRLQ/qIsAaC6b+fN4WSUKDfUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwLxNxQpRrjBPgqWw/AvS5YhYl9Eg+gc8oXZB/9QSzVpMIgNDx+kgz7ZUdVevHnqe8aEnwrlovoVmhQ6X2nxdiCA==" } ] }, { "header": { "sequence": 8, - "previousBlockHash": "9BD5C0D1F722DE36579E2FBE9961B5E1A4D2C3DA6B77C5195450FFFE7CA6E7B6", + "previousBlockHash": "0A52F9A3E63DFFADC6D1DA8893FA97B5876FA9FC5471099DBEB5C6873B5500D5", "noteCommitment": { "type": "Buffer", - "data": "base64:p5YfW0brP61LtOloOp9jgWexJy8oNt/KJ3g6Fw8MJ2g=" + "data": "base64:6jEOMK+mg61FzunIeMzvDSXbJxIOwT/o/io9i4TdLVU=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:HCPGx500t1evPWdtEc/LncU5TAedjob8/sYhK3RbIvY=" + "data": "base64:kg3QG0UgHgvwMoHmtY6A6nk8w836gpZA/RNPzjrcWdM=" }, "target": "9121466311864876128923245652724724632104869735746188813030060672759072", "randomness": "0", - "timestamp": 1719441725197, + "timestamp": 1731435271698, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 10, "work": "0" @@ -1518,25 +1532,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAArP4v5qoPhEx1Ig1ccVMB+sStk1ZxSkn+QTY3zSYickSRv7SO0KlXsci//yVMyH0Yqu6oLmOYFRAhhwOQPu4H4HhurAIGux+ZnlXAM1d7CaCkNO60wcOGxxo6nSTqblg4Yg5TTY4m+y9HBuvjQRvdBmR1TJ214Hq84w1mnjL+k7IPmmujJLPsZtTarlJt3UYebNMSLtqPqabUy5emWwcKNUD7P92st8vDZgxutrXiqM6lhkQTL7r3jeCngaekzkbH89ZwKVFoBIa7AR+ocVUmvBF0Wi8NtFt2SO42qzWufiQx+hcezxKLTtYFdcOJ3DasRUp9cEPv31cPw1sC61D3hOVpwLvP1P9KU7u5q6JL9uIW3vfgPBi6Us1HBOyGAR1bjlFolSltR306ys7hXrJU7py+Lmpyeiy6tEycfUuHP+r7ApnHKSqW8k1sqSIxzbX9vY8AjYNHdWp6bwpSNBn59AaxlBejMY18cTI9jCL1OJLbGi46Z4PvYzMnKPVixHyCWVkoAAnmkKQsGXGZBDrY5EYRXfERxkun4mrzo9NlFr5sOiG4TWfsz2B/FQOJRVLB3Mx1HIHXJ4LP6jlP4+VoYPcvRMneXvY19m2jhTmz8FggcHSdzPG5QElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwkqi/ZSTnMp8qNRLXx55SbglxuBMIxFoylHDjfnDxQTMepPKUCIPZrZAUUdoVp4apCz43/7yZ6348ShDR+j21Aw==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAso6gEbZPkf+r2dSIXY8RT0Nf1Vn2ywXhDe6bL9JI+laTx5AAxxfh01ng5G/A26SCi/9CcTyCtRjrpjimynD7EwprXV9oUoDESqXVpssJey+5bXfKqiLy35Q7d5hnv+YwpImVqNRTwxEDtFB4c/J8FuMalrL8txdtLRjmLpm49KQS9CxrFvIb7zcGksZRPlvxmUFEdQMfbnRiN7SwUSJzashAZBuqRVH9Low8U1zbdyeKf+WKAXK5+1AB6zMKigfbR8rcMb7GjwvOhavNaoKFP9S6/PgnaCDWAEzH/zQHhg1KM5KaQ1cG5KmFFCPuWJ1YC8t/5Z2YSDPR7iS+WlgnR8vaa//OFs0Z0MJjo6/YHpWOsZvH8LSvdST/t4808NEIcZuri2JPAC5sbwIdT0NQMyZfTPP0enDIVNGqbTP2l0dT0aracibBy/DzfI33LizFe011OJJuOuE7yXI/R3vpEqEIiROeJD3YS2HM4Mc/JDmlHo3q5GHQFMgOnTkjv1A4H9rJ+LxsRpKKgBHeDdIU/13DT+ZBgMypHIoaI4tkjWSzlKd5U+XUMhIswQyGnNVy5t0aVkskDgJml6DkcYtK1boA2FWFcn59Ei4w4RQ5JUBcWazyQ9MNDElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw3jfIn+k4WVT9rRCLjdxwWQziKF7Laoc5IMYyu5Qkk86rvtVL8zzvpOHcY3CcXf3SGR06dxCq1RiU793LICumDA==" } ] }, { "header": { "sequence": 9, - "previousBlockHash": "6B4D69F0949383DB8FDF24297DAC2425532007FDFD7A25ADD90676435BA1D86E", + "previousBlockHash": "057A4746AEF401525BFE5FC57F8E7F61DFFE59A18A5C920898496C3B71988641", "noteCommitment": { "type": "Buffer", - "data": "base64:vDbEB8W1fxR5mChrwiqsiO9K4fbk1By4Z9jpMz3WugE=" + "data": "base64:BHD+ZNN/BTmdltW322ZiMi8cR4mVXIqPkg7Lw34IBzI=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:/Q9bBFoh2VEVU3hCwwyEeO2ApkAxEJ4MfvsxVDq9Ftk=" + "data": "base64:n4zImowsiKS0DYnJhG6hnZhtA2c0H5ZDtLPB954U3r0=" }, "target": "9094823328238119324660168503613036415495463326164889575918026009508991", "randomness": "0", - "timestamp": 1719441725665, + "timestamp": 1731435271988, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 11, "work": "0" @@ -1544,25 +1558,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAfz5rQhRv2LBNplPaQ+NoyVfp06EWxT06seFXZOF++1S1iWKU8HyorFHN9Sv8Ht6MRTlL/BJH5xlPxk2Q7uJGUHvMWNYrYpfn0VlgODodVqC5+M2qGyrvKBdbPl0m9QLjpMwMerjGFQMsPgSTTUClB3nAbYF9lVwnFE+9EPXhQWwRBY3rlpJbZK1sNgr8suuXm4KJ3phM/ADfxEbwYkO8S8stBvGAYRQCxBTt9lpetnS2opy5lRj1mcDisOqbgXNPZWhqHludnnr9Bmt91c9KmbJwJlSyTVqtOJ4AEhoH9SJLH7rs8cKQCkJvmbGsjW4VGxraiH+/X2EjT+IEnE6xCBcTc+nqiwo4VePsgc+ingnsBw64nTGaAQpGnwdIqb5pAxIOm/rrdpefaTl/60Qmy40/KnNEeM6wLWu/gMwLsioSCwu+FCBqxtcANgpDfPjct0xjEHkhnJmMPgXKdq1+6ck8HWYY+EYWJ2/iE0+Hl6j/tnQITJQ7N59LICPv3ZHoyMBzh/IGwD5uQOb8JbELwe4Q8Sje83FoDaxNVoj0s/c84xBqO067bsBh1QkoxftJKh7HP6XcWRCL62VgsA9UGDFOhcZ+O0HVyvPTTmDC4kcxl6XUUpk48Elyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwDrM4b0FM0s6fmfR6ipUs7INRo7EsioJr6Xyl75ONFu0WvVglWLtZS9YgjLe9iKsXF2AGORwu/LTou2A2L0nZDQ==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAhjR/dxG9YklJg6VJflCNicL72N7IJYqLxXSxrwV8x96CmW9vT7IAptD49LfnHYkoz1m+Ka42nsLx1Y92GNfcT23gtZg3t0K6FS66OFWkKLK4dynQIv4nW8keQE1Tmt/lvdIobldr4/qR20zEC8z9WY4hNryLGaobeePc0khuws4ZXEo7DMkmib3Y/FHwSVmX4DC0eP/k+TyT1MxVfJWwi45KeYL59hpBcxuvlFRlRP2vv+nP5FFqVaHs0gAYaZwIg/sMa4q261J8/SFLAsWBHYqF2+Bx3cSU7H9G1xK+OgxED0arcUhs5DXBM4a5p5OXfvVW4FmClCHwBJFDnTBb47WzMZYha0v9e7MixODEm13UGyE9ypNBcLimVYbl9aMVK+Q0Xm1oW66dTnyBiszWx3oMjZbFKjbXIYhKiL+iqrcLAJGRUs/QLnhIB52jZavPI80LWeom7xCdn4bIFGVt05SQ4w08eXzBV9LcGItT4a5SAeJD94WwfXJ+opYTMR/8o1C8/lPKoa+IWERSaX8Jzf9ke8X6e/OYIFH4G1MzzoBz0JGja6IT2utpG1SKeOnTcSL+1OAue8EMVv97ZwY+q7V6p6uUTziqD9xgONkBhEf/NAVBfQSXp0lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwB+1WSW72A0QyYP83W35hBJXBK3qBxIucAaS2JmePBjrc//RvZ5feeu1daoKIMzv0a9f7qERE3hgnqGBovx7pCA==" } ] }, { "header": { "sequence": 10, - "previousBlockHash": "62A65BD939DAF14EB812793DAD1D68A266B0BCFB28C1AA9F5515AD0587B278A5", + "previousBlockHash": "4B907A05F5EA05390988FD447060EB7CDF18633FAADCF6AB51B6EB051AE51A37", "noteCommitment": { "type": "Buffer", - "data": "base64:30GdhXpk+n+fdrFVsXASqdE2WONKm/R/nrN4Vz3mpkk=" + "data": "base64:q/nOLhHRifzbLcJ5eogQaEuKLwCkX/zKBxhpX16QNlc=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:Yj+qdmmsGqfavNNYPMBJFxm8SSQAOZSRxHm9FtlfEBw=" + "data": "base64:5eArsRMPxzTODVOnd63SFRcBuMchtiU7AFdkDXyJ9+Q=" }, "target": "9068258834662928698220540790897658244352076778286486653826470223999191", "randomness": "0", - "timestamp": 1719441726164, + "timestamp": 1731435272278, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 12, "work": "0" @@ -1570,25 +1584,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAALPlrZRN+QtSZwyPJLy8lyUnHIvNRT7dPZp8KCxZxIzajyEKRYomauhJNnI9RqQCe4QuosEBFd3QufrhusPFx9FK7AsKGs8Mc1uOe5k2vg6eOLVTjwAnhn7k2Lc39xmpSvE8ofKFOPGai+PFIBuNx+aJnJUVY6hhP0v/0ENrex1QBxThKWyBV+4+vTqSWCXTO4SBAhmnXtox6wK1ZOmaq4HJqimB+fP31e2VBY5sFEHKCq5BuoWQVgCY/1PsU71LLJkRmCUwYgAWLfTwVfF0EwOlSVs50TG5W/rhE7gjzUA7dOhN+4jiUTlpF+VbBS6p1tzWAgfSJtyBjTgA9Bbc2hR5aW3WitwAzJvBvVB4v43A5CE4OFEBSFK/LFyieCfRguWcu4eTjSH70QoSC2oPArO0pFicRipSTyDCRcJgLidBPWVjwVnzpVGZAeZ0tmsdCf+m27orjKo7KWiWut9gn0YFOZwsDKyLWRKTrNHcgCWsysFYjM06BUC5Amn/1BvQCHZWUgi3IFFJNiELTf9XnhTS9F1XnJjjZ2HZWClSQnhT2W9x7o29v1yiv/gfZg3l3ntNtF9IRMe2Zbs6kDSk9A6EyiGHFaWVuRRZTxy545FGLR+J/d03dHUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwyQwNLQepGN1OnDu1Muhv2MqHQ4xFe/YRgZQTNXmGtUOrbJwJL9QPT/Jvd18qPZtuE/Q998PMgc/Ku/uUnAc1AQ==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAnjFoSpn5CM+5TuIOBpfXILcI9OyDjrzHoLv6CtvXPhCjzgPq8eWhYIPPSRM90VHZk/ngw0jLsw4RequbHM+wk9MLagbPxsUZNGthK9zzEhywEaWRmp0lgVATbDmwQm8jepp+LX5mzHZr3osYbwIz4g+ww4onqYGkgrpiVkwHjXwOqx9xOIAyLL3MvT6Brv08PKRElH/BV31QakvfM2x0u+38jyS0v1lRCSAAdkQnszCIVJ2tTgzu818lE2V2e7E2rHIWoE1Hjp9UlVt4kQKu5E0NOxhkJSBkOu+QCMLDTxy96N0fpxB2MN1qLgsqmo2fC05U/Hfsynk/5Dhmh3FBFtE6RR1/FiJ1blFxft9HBlDoyiHEh0NOyLT8JBgL4YlX/PsOJWASL4Llx1FcBdGtoZ1Jz6pAxjj64ysFkZSJfYs9owNCgWV3xBcSKuouH3IqToRqdo2tSRKK5cKUQMuRIyAsIo++0vq2KMwXYC1qOg2YMIv5Sn20+VGXPhCIi3apQbQJfqchhwN6JXdAk6G35NAJZ8Cpu1UezUmjwvXWJHO9VgVXEIPr8LpFskEeoBBpbxSBmzz3IRC+BEgNj/Bk0BWEoakX/h9K6PR1C8bus2lprgFrXvDYyUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwEVtazi8f8TYUf3niese40XLMCfo1rHNf88u2zEdTMypACqVDOUbTHnQ2r1DhHq/m2t3PtfaQBXqIeGJg92WbAg==" } ] }, { "header": { "sequence": 11, - "previousBlockHash": "0697AD0245CC3C719CF76286F5C3240C079739B00010E8F0F56B33EB7F115422", + "previousBlockHash": "712EB5EA3B27913203976226927E0606109FEF2F25109F69373BEE6F1B72E406", "noteCommitment": { "type": "Buffer", - "data": "base64:pTTTACel4n/TI5E8pC4OfkpmN/EGiE6uCOF7va/nVFE=" + "data": "base64:p8VwTv3RweBkQLdMiG/127HJbuBoY8oLijeNrYXjmSg=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:SN/R5FVlbTQjQ4zdbX50e3lYih0ZJS34ZANClBCDSFk=" + "data": "base64:cycBeVgkF+TwAbnCntv4XC6JzY9MY9UqMDsbGSeue0E=" }, "target": "9041772817458669358631436925553476123971485443441062513642264290171806", "randomness": "0", - "timestamp": 1719441726654, + "timestamp": 1731435272564, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 13, "work": "0" @@ -1596,7 +1610,7 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAcSX496FUPqOo4TjxtcTJqOJRxy4epG6ODKzmXaPDiT6tqmamSHBb7AUPM4Bt3cE1I05zqNs+vNQGGskW/gkVVp0JB/Mbfg0S/Q9fHx0m/fSA2LwK1kPnu7qAl+QSToDygk0h8PFn5wCh/TCN83X+VYEtSS9JStlgHHmcp3RTOCYQ5OEsn6ZqAiHiBMbvN7wQ1e8ZCoMU1C7vB/bhahqh9XhfJxuM9duvpUzWf1ABl5SK9ymQw3Lsa7pOw6BFwtl0ZzK+L136imutpyTZ4IMDT2RP/UYFAqQAN3r1DyZ9z+HeyOKC2ZWiJ8jjuqvCcANG62WXTQfhqHsZ+6DhBTb9n4KpxqBsxx4afJz0382XxCRRAlu2A26s0NWtUnFH+05HF1Ho2rjojh97JR3V59l84nCWDJMrO9aTTFeNbG01fSpegIUqraPG+afinlZKufkKOM2W/qOq+i47c8eLlWNrdpSDZ+w24qoCyQdW5WHYJOMfz2eKd2YvphEklqK7pYRmp3uXRQicum971Rs8qIo4S+Hze7ijRk/pO1zqmfwkzw+0XJBKDH2QejItxWlUnq55WwFQjU8seMN84dSizfYdn5FBc+yltI4xOj2CLAd1iU1KUcBOB5kpIUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwx3/YX2hVCm/eHIvDALpWilSatlN5BqfDouDTE+VaHSDD4m9HNKVwZpecj7PF/AKo3fBowJcylj+YwAxxvg7ZCQ==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAvhyvyNr1/KJZ3aXk2C8ptdC3JAZxOqiiGnD3Qt/b1eWLchwwCKUhbkEVSWcUmUQliO2fRIxMx7+DMaDGA8I0yL4HgIhLaVEGDwdyJvw94BaZ6NYU+JNJrM98hNLYIb4MMn5x+GranojqpWBLZNu40HiS1AdnhvtGlWG1rbNjCFQH6LPagC8VLMI8W9915BgoU6u26sSoUhtR/bmF1U+Mj/areKxjrFS00LE9ZndsFfmnrXUiZ2KhjtPUuGyZK4Vw2xIt8sdFw5604TbO2onH0ZfyGjg0MDhVhLUpNR2tt8h/dR7j9jA6MojeU6eih7IFfJg+1IKlZwNb/Ra6nUysMIn5G+DgrCKEiB8SOYThvYm8Cs9uGEsf4+aos8cOO3QMyOSLoUkOxu54BovnulBe7Qrs9S9iHEQ7vOguXPEqHZf2m7QmXNPIx/TiCE9XKds0ZlX8BoQtBINJFSUXPVgUGcMzXBRLh7CvHn9A/WAGSG4i++8MoF8mOz9P10UPMOTy2D4WzmydM+621BeAj3bPgy1VdS4+2xd2XPBmJ7E2M2a4VCFHyC2n5r5/iCVJsm0rZEntgJddqdQsrmpzY00P21xSyzATLmBKmaBlOeQpKbC3vVCru9PXxElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwiZzGxWE5n20pkiwtxcTESuHv6O8qah5r1/2J7+ysMa+9noRjhMPCuMo2yk9juurmz6DBHXLEGmK5xz3tuh1eAA==" } ] } @@ -1604,23 +1618,24 @@ "WalletScanner restarts scanning when accounts are reset": [ { "value": { + "encrypted": false, "version": 4, - "id": "1d470276-d2ea-41cf-8904-079670baca37", + "id": "7c023c37-b485-4936-bff8-7c8f589f3e27", "name": "a", - "spendingKey": "31f0b594e6a695a50328c7874f9d23f72a4fb297d49f3b1512db5969a7d0ce25", - "viewKey": "004f4b700a6548829f2595590d5ac8c1be1e8dc306bca774a1a7a1d7c42c379864fcb81fda30fae499a587a80d3cd7c5207c3bf2c90c463898a1de0903c51712", - "incomingViewKey": "2854702acce3ba91d33a3e2853a718d9626f5b37e4100e36c657f8813e2bce04", - "outgoingViewKey": "0d44394d2615186e9480110ae402b81e69128ecf1e3c3c462b2cdbb323519328", - "publicAddress": "63193f684d12f5f2189cb682334aca7c19564f0400b50258b43eebd7875bdf34", + "spendingKey": "f0394a1228d693aa6fc18b43493f8d76583ae522b0d1adbea59f9ede94187187", + "viewKey": "fee164f25d9d03e78f615cca10a197d0fc287b8077961c5c26ddd1f9b0074dcb1a3c8df8b8976d6b16c63dff0b6532d17345e1dd4fe9de991f4f7b4c91d6d2d4", + "incomingViewKey": "87ed34a2b9bd8e104e66fc341feaf266651acbe77624a24489adccbc102f2b00", + "outgoingViewKey": "a28106da8f0f86ae673cd98dd878f50f762904a819d48b09d6c0e418a3baaae7", + "publicAddress": "7e39abc4d848dc5e0b8eb05ac48d42ce8017c0af56d0298e7015fd95b54266c7", "createdAt": { + "sequence": 1, "hash": { "type": "Buffer", - "data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" - }, - "sequence": 1 + "data": "base64:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + } }, "scanningEnabled": true, - "proofAuthorizingKey": "a01d10fb10b31e4ae723892a440753e3b8c40ff23b6f635de2f8fd915109e606" + "proofAuthorizingKey": "8756cb2e6efff4b8275eef89e14f1e9c3b3c3be65e8249603043f7daced33a09" }, "head": { "hash": { @@ -1636,15 +1651,15 @@ "previousBlockHash": "4791D7AE9F97DF100EF1558E84772D6A09B43762388283F75C6F20A32A88AA86", "noteCommitment": { "type": "Buffer", - "data": "base64:o/aMntQ2IGPF18tp3O2D3KzGHRuLXZTqgQ+SVBflPTg=" + "data": "base64:06a+phFZynPZEJ13DFNpuT55t/JaWKWX2avptEmPA2Y=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:WLUEt6WpH5tQWoWuTDEgAngpK3UuO78z/jOIiB0JSO4=" + "data": "base64:rsymx9FjZ6rkM2rZeZnQVUh75s7faLWb/Hye8XmI38g=" }, "target": "9282972777491357380673661573939192202192629606981189395159182914949423", "randomness": "0", - "timestamp": 1719441727667, + "timestamp": 1731435273090, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 4, "work": "0" @@ -1652,25 +1667,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAZ9WrJ5qT6rk3666j+0MGkC51IY7ge8UuYWnJ2Aai2p2SjokDjnRaEclv6MHxahvqHnpjQBffym1WDIwkxuFHmhcJWMIz5WQwuPI9/heDZt+OPD91L2/Mz37pDoG6M7gD9ShaNRC3SAb4U4WZg+v/HFsKjCXTXIaJC1mCGxJTfJ0EZGS+Etsuinks0pOu30tdSY7vq2dMpuREpmRVHh3M+p+lCPhtBLULlp/rwWuBEr6llY4KcIkqICURe49pRwumxprhtzkYloXquamstGvA8FjRh7aJ86rqK3JDln0PuE2dXpaGVa8KPPtgQv3o0dwnZU0k+AfcnYLIRznvWKGD0ec53Hv47P7qc5jBuErBvHiKf8z2qpGg5jzsfK1FjGhrqYSmnml4EIMac8otmeMPPtynwMUN8ZVlEZ/K/juUB0RZGPSR4fhroLUFIqi8fmOQvYBt9to8ycSwpmCPlu/MnOPpF3u3BdaYux35n4QFU93JwLNj+xOkNzDgbHn9uAwgsj9O7Ky9E/cZQLmBX8T3pNAJOiEDh+zxFw5ItvpAL9GqlYzV3R4rN4fvJoQ7NFyHavocIH/2e2iE1kEnPMpAEEoKC2w+SpCR5rp7jj0rL6ruOxMZF57PbUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwDe91Aey5ZIoY6QgZpSDQb7EcUpirBsMb9fUyns9pqYbLlNxpUPODCLYm/w9qfTRY0hbrfnuwJ5Ksr4hBEEfhAw==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA3VACS8LxbJmcTws1qL6Gsz8wNWTlyE1TwjaQ4V+V8tGFypwqoS6cQaxup2Vnpv02OePrAoqFzEz+58o1Js0yQNZZ2dIBcwYOdXLfjkaTGfmL9gvsxZcOrEn2HmbcHss6pC1vFnOeOFWGVznX1VghociLAx2NV4mX7sJz0mi7gOcY+xnFJXXKkOqavIlbgo5Gs2N+9KB+mcI5aFI91ion7M2QoHixXz6l4uZn4OHbUqu3eBr6pl28ndqOpipzuHW4YoGQpJx0uOZjtlZMbtnQbed7PpQ81avmFO5QLD8aUntX/b4I+oLqww4+4wvEWdytyT80TFc1ffKY2QNPW6KHufpBya8l0y+kqUuK2j6fNS4WFqTZReSGb4OCcZ4+jTojb+lduVU7fjqz0aGtf8Cw1XRkH5cRWqQDPdRiN+RwHk9V6buBfK/JoaVGGnM/gSgZpAIenu0czItmGHMaOMBXejHnz9g1LPiOFIdbAaYVYGFOUh6LQ16VOC032qb0i5QXZsZAZRY2rq01VnUtXUwoJQnRMhaDQuLBUg32dQTYZCUN/52u9fk6AJVnvsWF13udws+EqepZjcAVuiAqzYQa9RoCnOIt5U+W+dyZXD0bRAvUK01si3k870lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwi4y7e1uJzs3FYpJChYzkfGd4/sBamOZkqV3x3r4BUU9H+rJwDmM3PEBQlO5iLYNREufnGIgOqTXMQrQ77D4ACQ==" } ] }, { "header": { "sequence": 3, - "previousBlockHash": "9516FF4C8650796025AF4FC6A13FC033766F1E6F32FB203E3562DAAFB9DF0794", + "previousBlockHash": "97462BE55D7C38D8B7DE28E99AECBD8DDD361EA85D3061675E33D77B32CE80CC", "noteCommitment": { "type": "Buffer", - "data": "base64:n7WhruSOYlHdzmXQ8623/m6JA4f4UNHJvw49tRgF6Es=" + "data": "base64:Rkq1AMz6x51hWr1Y0Nn0oF/gcB+mnDeT7Qmt6c+mIlA=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:TcR8dCjz1KafvPTpoXyaOdrfX452N0ky+VIy7+oRcpo=" + "data": "base64:ht21NC//dLFxc8b5njGJ5a89nrzucKWg5G668jcvu1U=" }, "target": "9255858786337818395603165512831024101510453493377417362192396248796027", "randomness": "0", - "timestamp": 1719441728127, + "timestamp": 1731435273380, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 5, "work": "0" @@ -1678,25 +1693,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAzawMVj35WwB5iqVoxBvfa/MH8coQ59PAIidM7hSNd7u4XqrDEKQHo7DGK/UfWpRKZIFACWyWMa7NuFTY+byae5k6NPkOrXzvynGy+FRrc+Wxgiko1nc8iwYjWCv4eVATbSgW7nNzwaXFb+aKVYI6gOpAbWuIkWqo09TvSF+cG8YFRYkSSdjyxf7QACJI4Ltis/qChWSKEApBflq0SYk8M8ubbJKi6FbDl7Syq8bcnnW5aspJeOTzInfk1+xuIwRW0KJMn+ZayDuTywF/xqPOYJmu5CZrmgQPQ6mOP/pSR5ixzuggufDGyyws3gDW12saJQnM88T+BUVFBCF9nTCkFeFBPjm6DevmOtthtd42pytHNEbt9WgdLjrg4jssyJMSdV0Qp7VV+2TkIoV3kbYqEhUXrU6sZfeSfyWFcBVt4Nicr1z03qGFfZEjOzb7IKJf4PBgtk0chiUkf1NwcvaRHR7B+bIm+0tftxR+/1yaFGfJJ1crJYfr4k+ENHMFH+xHDL7Cknr1xyve9VQMMIRVmXPk3yy/lrVCilrrJGz5QCgJutiNNp5f7K4hq+7s4reQn6PlXBtjDG1+5HywBVHYpsJVSTxf05FFRRE4ESgNS76nqYS2VLz74klyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw0/P2UgYHAJ8OVGWRUsY5K4szn30ehU7Phu5nG0Q98/Ijg3o+WTPBbUGhg2OaH5GAti+FCi+MENAB1IYQfQzhAw==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA5PdhupsUrl1dIzNaUjo6jLGVmf+oEfdmBJiw90xdw8uvp5zhIkgd5nNoMNfOlig/NLEdwFp1N9+3l6OiSwlfXjBA9T6rqoVjrnMGvKrcCIGIOD0QdyAXAHfCKCGpWHfZv0PR0/VIqaqpd/h+QHbC5reK15bkm/FeFejtAwQHevIIkoJlgmxdXvPPP1LFl8f5stJ6qFYk1jYyBCRJIr0cJHo8FBpuLfyDa69Q1bUucUSmfcJ/ODi1KC2eEVLMcVgATY9Lb8cLHbuUnGdKZtzOR2IKvbnCK4Wh+kR3YP5EM3q2dmJxZnk6ztJiAeICT+ymYTz86qqrpWpxPyYeSr5266RubDKNP72mcDbXJS9V0hb+gHszPJwZ9xoHXsyAkH0/bT9DF1mpzJ+aXJ0g2NBV8wcCVuaBbg3OLpkRuo+bHaaT7HglFCyuBZrOU5VlplA/ldXwC7Sq6Eo3R6M3ZsW0i37ZCW0F0+FgESKXO3nNsDtFhy6WgqeritZ3gxiS7TG2pJJQ5ASXQ8jH4omFJK0ONhdJWibdmOBjWnGiuopnBZq0bw5o8Lh5QadR1WHFYkrDbqwzr22X3tRd9E5M9rmF2TUet2X4JxcDqmlObiMcOUgQVzYeSFF45Elyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwp1JMuSykBedB34vDiGTHPBcFz1fvqHDaA8JDd9WHzodB9J7R3M+/rugP9Cme99qJNDoWFH176ZBf7yyJosMHAA==" } ] }, { "header": { "sequence": 4, - "previousBlockHash": "1CC0F6BE54A623A0B0AC13E0A41A14ADE49A0E214B3E26F2FCCC109993F8F76F", + "previousBlockHash": "12B9BBE3D7D7ED68ADCFF7C75997168A950F54602C280D6170A5F57BE11A3CFC", "noteCommitment": { "type": "Buffer", - "data": "base64:s/TbP//NX4N1uVEg4cAjgzAsCsMBivJawUN03ssIyhY=" + "data": "base64:lPdcFSjPgY/nVXyC42x3804zw8vFimfg7COsHuYWpwg=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:AoNUIkCzQdk6lISiTioaAD6h4GcqiGLFCjPlZzPLoVU=" + "data": "base64:GUdmQpgvuO6ki7cD9Nd8UPIStNMnnrDDwYWGzbrfwZw=" }, "target": "9228823284279306817296266184515742822248210830185427859262273659833347", "randomness": "0", - "timestamp": 1719441728574, + "timestamp": 1731435273677, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 6, "work": "0" @@ -1704,25 +1719,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA/f8lGM1diIN/TGfxQa5m4Yh5gbEYdtBrAMeFmrp52MiCrS+R9HkZvTbzqyt7zewkoA6TMjJmiuWrgWyGNfu4csVpIrTJ/CUY/FC7LHHs7qSK76JEUW0x4UrbP7fJqvUKcxHSv2POYPfFsniAd/j3/AEr947rXk/vEbfK9PonGZIBPca/Pw9AQVOioqeiPJOHabEUHXvXtFdmgCf4GZHArNuRxIPmHYNPPevamn2x/Ey4nn+iB0ouM+/meFKa+RZfrWhY6YKkX01hSR35SQLVKkXdYulQBx0fVggplXh1OKQhtTuMq7ht2jr2eY3KaiLM7JgNt97CFNJbOcS18T6Ji4/lw6Dwax+JaI1gbtO79IVl/6JdEARBZqwS+0FPYvk6forfgLp0OyJx6zeSx/jmYTMss1h7tI0Z1l7ncyzVCOx6Sg+N5E0HtAHUesnW8YxYo9kDnM7el4ogd1jIAEBOV6yuIvZ6DAgfLdHQzFJ6ph11R/JyTwSxalxRfqtJPcesd7NL8pbL+FO8/2wgv0U2JhuMqx1TAZi/AaaYtFvVoQnsFdfVi6cT0EqIqsprKDiPcCbZWnKD7/opbo3+DmHtgoUW8ipOWQPNwegIYbNtUlmqH/YI3+aTq0lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw5VIIZj/q1ET+T7hlOn3tMl4l/VLHOYvcUlXqDsFU8ktlzQr1NX7qIXNV/3fZQu2u4aktXiz1/ZG/g8ZAv5jiAA==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA0JZy8vhc0s+FObHgOZn+vIIIzK8RqqV1njaYB2GxsLGW3KtPgA7TQqgvsxBMPHbHzF0LvbT/dLIb6cilpe40BB7dzfChSNRgRQj7n4kyXFuyVPhfChY6jSopakhSfnyJ17+cGLbawD0Nhr9gLPQ8MjFAu2g3AX9cG8P8/49FfhEWO2TDcE3+JuZW7rGmBQg8yNB6xfonSyw188P+I3yyCGwNEldbM+0vldMrE/+/eCWFO4RlQdrKDuLJO+3ivIIEmh+dVKWROk6jSrbycs5g6D5KKwxM26Ub+DdJOxoHu9mzv3zjtfqnW2PbyYPkUDGc31PiJn3kIaONQd0I3gnHmfPMgLybDGJk8wM2vmTeB59PvdiW8xI3Eb//Kx2Rno9t0kssSp7wsKBtXLtp1YRsmbf7qRjGz1r98TDUbRDwokC2p4gmRXe0daoQUrC19dB/EIofbwuLVoVrDvICXwhNS/zU+Ha0GfRYuLknPRjci0ynjt3nucQRHySEAmGZFFhN4P8xDob32rzNiR+2g7A4p3RQxx3m91xgPkdd6ImKq4ASPm/hXPVveT4HadPPis8gE477U0TMTjrQX/ttrrSRuV2k4+lrC++6o/stteJUIaDNyOfHXbMTSElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwDp5BDa40ghLI/W5DzoBrGZOIVrbFjXnvc+zLflkxdjEc/eK6sm8na1aQ+jRVzo+xyPhlAtwTXqSDqCykrAhNBw==" } ] }, { "header": { "sequence": 5, - "previousBlockHash": "76CA8ADA5607F4B0D5B17ED4F9D47E09174EE39FBB7A3094298B2CA23D2A9745", + "previousBlockHash": "A70ED19DF969E671AAEA84E8C8DFDBC1A26DCF5687AFC0C56803E6AF869121D9", "noteCommitment": { "type": "Buffer", - "data": "base64:+LliQic2PDDrTqKNNxZagq3h8/AvRKz5r4w2z+kEsWo=" + "data": "base64:Ie04KX8sMSA6cKuEsp9DkPnddcPXK69l3/OcFn5inmc=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:P7S39hVJs4dVJgBEECmP54qPs3Bv+07+9aV/1eqTDoA=" + "data": "base64:rmXWBtScILnODOUCeCnhXP3iCB+1doT3uXK4+quealU=" }, "target": "9201866281654531936596795386791503876274441021197252859723586932895305", "randomness": "0", - "timestamp": 1719441729028, + "timestamp": 1731435273967, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 7, "work": "0" @@ -1730,25 +1745,25 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAxeeweRxo6iD3UcX4YXg1XwMknBV1aVkLwNfgye3ZDcOJVsC2JE/9012EEZ2ts8MQs7/PkGwHhvNuUOxQg6+sDv+NjzvGPYPiiTn4FNBhxbCsDx2f8M6KT0arJtIslzdQPehs3OVemEILnv+YT/tfGcyfWLpRg8Z5FHirnEN6qocGgUTBt95RADnq7z7Vnixy4MbiArkO8I5fjNcfwDouZTRJwFiQO8QnHiAHg9vUJGeRPGNC/4BILyT5gHnoFoTgxl/w9RZf9ReTQ+NvBIOgueSi7Q3Ppr98HQwTxVDoW9q1yBRa2hey/DF7WSs8EEmdRvZiqeDp0pjDwnGPXpUgXQAxsu4BaasNvB8mexjSWKwudPEHmkk9tQOBIZZzgmo4LvGtt7IbeTL9Omy9CYigpHm0DPtENirBtzaQbtBny2UUcvQH0X7vslfFrMg0tfW7BR6TrEOk5P7fMhfsX942WzqWv4h9hbrTMeN4j40N/8VeOObAES7rCL88qANBNUybNDVsWVQLxRhgoS7jSKjSKqcfPrIVzAj6pjJBQclh7veuYP9UaGD5fj8jVdSKsZAJ18SYOd2y2JSGHNBkXyIq/Bbm4k2A+mWDMVXlEMaI7G6LbuGEbEfhnUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw8v24RV1PeDi9MINLvOGvzD7M0IqLrTLyK9tHcaUUWSaQjUMMRVnUeFbzIM+0P2tGnclKf8NXpbsZjdtSi65jAQ==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAv0inPcWqiJwZMyKz/yhvUSf22mNZrfesEueiIrm77Gyx5O0N6yb9/xKUA47ltvWAqyktLG7zxCK481QGJgTX12sqdkZIh4F6vYSN2V5qSO+0/UPQGpwjbi5ftNhYvaz5wg+P7VlWBt+jgwsQWAhzo6xugrwKM09Ez/WAoGltO6QU4wdvsKFcJGtg4F08RUAeJPiBp9cXYKBnxhhazKoKh1ZXD3TnxQycFdivIA2lGzCjt+UoCDQbziJmoaJum5UkdZosf9iVb3AsoL/EKwPihuolUrLQP8g/lV4xV6BIflTz5fu+tRY5ASHxt748GdWO4NwLtuWuutKzEOYr7IcykPrjW+4qj54NGmGXckMhgouT/2fc3NJefreAzFLrdiJN6Lt/qdz18hgg0ELHnGZr/Mg1lm5VDqizIieGa4exlCiAwYQ9awfRB/iCQXx4sb40ZvCHk3qoa9Y8nMwFTa1Zs7t4eumjp99WUBv7QRbDzb/1TsxR1alhAYnd0L2Qf+78x4yKZn9grMuLov52jt/kEdLyJYnVQkGtZ84dA4QiGkx1R9bup8yC4kMQzL4bGSq1irc2KUztDDntjpVfo7FhnFQpK8pcIWhhwVvtLJe7tcb9k5YTBhDXq0lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwwFXML6DdWa7PErJ5+EbtUE/1uIWicnX7bUuWA0CemcmQDqI+9jDFaS3VapLaSoGe7sGZrz4eOurJa3tKvWv2CA==" } ] }, { "header": { "sequence": 6, - "previousBlockHash": "FE9F2C2EF1A280EE9F453B561E9E0EDB9DF95CA68A8CFB61206B06C63245CD0B", + "previousBlockHash": "D4CEDC8B2CED5BB24B55E9E17FE45194F269B87A77E23DE95BCE91364F7EAD8B", "noteCommitment": { "type": "Buffer", - "data": "base64:+06hDLNIGHR6hgoU3FNLh2fO5Er6p5RYs5AsN0wSvQU=" + "data": "base64:YzuQ659MeNg/ZPj0nYLxOMwLWyLDK7ja5Uc4qvYr8F8=" }, "transactionCommitment": { "type": "Buffer", - "data": "base64:mciVbP+cWYTL43++zveilipF5aqb5Epyy8towzxjFkg=" + "data": "base64:exzCXJHAAf6o9kQA9vpxJFOXf0/V/OzNBHK/xtUiwKQ=" }, "target": "9174987784651351638043000274530578397566067964335270621952759689537226", "randomness": "0", - "timestamp": 1719441729496, + "timestamp": 1731435274253, "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", "noteSize": 8, "work": "0" @@ -1756,7 +1771,7 @@ "transactions": [ { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAICBDDjaZTzDXobtlSEQHQNuc7wbFkF5LDqtvgnEoSj64ZMHwTi2MSIM+mOeeUHalIr1lQn15FhOc+vR21CxnMATPpQTutKO5mSp1xXUDEUmYn8CHjUuEUT53aIzftuXqE3xEa3NHxmA+uf30HEgC5j/B8mnPBesNw+JeYisBd3ISJ/9boxXBUK4J/MmFv/H3KZA+JBGDteH+/AIE5WyQyjL0YAVTK0frAr20W80+sZmEHpeL87oSpUMoSp2dmTLc387TQVXgqZ+uEiGDsNeycoWGMI0oCIV/lQmeo8xj4aXxRteViJDfoU5wt/v/I95DALPR+wMPRdQiKwr9ReuCEztGVVOK535c8QimYYaKQpVW1iVZ1+aa7W76kO0cJM0s1lG8o1uT4ktFMS/XK+6pGkmO3eiAPduR+PTq6ARH75MKAjDarGTHPD8WVzttDkFWMvnB2rHtFSrL8eO3Z1jKmt7OGLHvxPvoVlYA5nKNr2j1emesyGmmVS9p4Pn1IZUnSVuemDaoF/jpbSwWyfLSVvPd97ARQkitNQS/n0rlO+7AeIlruw7gOutSduazCDIoQYJGcNGYtjToyhtRSIdTeFUhzHMK9ZMT9apQ9mmDJqS2QK9S2+Lhbklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwHCCkwG0JEW+vfgElBFXqaqC+qmQw6+8fLbv4HdV7h5c4McA0TJEe+ChIrc8h6exdmKOGOsQvUjGvvDR41zuoDA==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAwrZreAIViDdV8WTLS3ZAx0xC1a+Clmf43O2us4EEMg6EMQde8eCjLQpelDhGz+oteFmCMbE6Gp/OPNYkfa+McSEgIFHouc2pMXJRiC3Stbe0MnwSjvj0OeR5bLcKD3fnzTQW/ogyMbjwEE8Sekmj5INaDTpvrwA+1CvVi+OQa6oAgj0chFhm6jL9+iYDtx/Hn67qlBEx3CXcwnb8QO4pOJsYHuM/zvLQJspjZtzf5D6pcQbW3XMzvGBMRPoSdjlUJWHo/weids4B2ieHcm1W216Su5EwAwypzHQCOfA2msE0iM35NRHT8pKfKkOnKdnYvWs5sa9zryjsXv7jw6xlyoFtF36qD1Wa5yln3fJ449pMUeNp/wo7J1QBggjXB+s9MSL5gjeVLwb0dclAup+wG3sbYEkH5t43b2jTFWHHosp2BDsFpupz/t4Z8KOA5nvyVbzRNl25qW+Scf+9ULsIRs50OcGl41GnyEVtx5RNVQiWNIOUgWtlkz1ZJeOpsaU5vVwhIl4bwoON+AgeQKfX+CyL2QQC6YQJZ2J4YaodUZ10qNe59hxoQCQK1LGqxXrsMj9vMwmuf946gxwxVu6Bzbxnz4J+Vt0urwoCaK05tw88V0nspHVgJklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwI+ecU8vVH2vBL9kztowM49yuO+Ip2+gNwOqsMzKkl07oacKwUtvT7vp6SnqGzJ1TPjuEvRq8EHLEQrtaEH/4BQ==" } ] } diff --git a/ironfish/src/wallet/scanner/walletScanner.test.ts b/ironfish/src/wallet/scanner/walletScanner.test.ts index 9a039160fb..ca2566fb40 100644 --- a/ironfish/src/wallet/scanner/walletScanner.test.ts +++ b/ironfish/src/wallet/scanner/walletScanner.test.ts @@ -217,12 +217,12 @@ describe('WalletScanner', () => { it('skips decryption for accounts with createdAt later than the block header', async () => { const accountA = await useAccountFixture(nodeTest.wallet, 'a') - expect(accountA.createdAt?.hash).toEqualHash(nodeTest.chain.genesis.hash) + expect(accountA.createdAt?.sequence).toEqual(nodeTest.chain.genesis.sequence) const firstBlocks = await createTestNotes(nodeTest.chain, nodeTest.wallet, [[accountA, 3]]) const accountB = await useAccountFixture(nodeTest.wallet, 'b') - expect(accountB.createdAt?.hash).toEqualHash(firstBlocks[2].header.hash) + expect(accountB.createdAt?.sequence).toEqual(firstBlocks[2].header.sequence) const lastBlocks = await createTestNotes(nodeTest.chain, nodeTest.wallet, [[accountB, 3]]) From 76865c56a598b7930810ca2817e99d6bb8194fe2 Mon Sep 17 00:00:00 2001 From: jowparks Date: Tue, 12 Nov 2024 11:40:35 -0800 Subject: [PATCH 05/68] fix transaction list output (#5635) --- ironfish-cli/src/commands/wallet/transactions/index.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ironfish-cli/src/commands/wallet/transactions/index.ts b/ironfish-cli/src/commands/wallet/transactions/index.ts index 9b4576c32e..b2a7670251 100644 --- a/ironfish-cli/src/commands/wallet/transactions/index.ts +++ b/ironfish-cli/src/commands/wallet/transactions/index.ts @@ -102,7 +102,9 @@ export class TransactionsCommand extends IronfishCommand { : ('Bridge (incoming)' as TransactionType) } - transactionRows = this.getTransactionRowsByNote(assetLookup, transaction, format) + transactionRows = transactionRows.concat( + this.getTransactionRowsByNote(assetLookup, transaction, format), + ) } else { const assetLookup = await getAssetsByIDs( client, @@ -110,7 +112,9 @@ export class TransactionsCommand extends IronfishCommand { account, flags.confirmations, ) - transactionRows = this.getTransactionRows(assetLookup, transaction, format) + transactionRows = transactionRows.concat( + this.getTransactionRows(assetLookup, transaction, format), + ) } hasTransactions = true } From ab377a414b54adb37bba2f967d06d133c1b77b9c Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Tue, 12 Nov 2024 15:03:34 -0800 Subject: [PATCH 06/68] removes 'clear*' methods from the walletDb (#5639) the walletDb has serveral leftover implementations of methods that clear datastores these methods are unused not only are these methods unused, but they _should not_ be used: calling 'clear' on a datastore has the potential to block the wallet and node process while deleting from the database. data are deleted from these stores incrementally in the background using 'cleanUpDeletedAccounts' for this reason --- ironfish/src/wallet/walletdb/walletdb.ts | 44 +----------------------- 1 file changed, 1 insertion(+), 43 deletions(-) diff --git a/ironfish/src/wallet/walletdb/walletdb.ts b/ironfish/src/wallet/walletdb/walletdb.ts index 2f00cac016..f97272805f 100644 --- a/ironfish/src/wallet/walletdb/walletdb.ts +++ b/ironfish/src/wallet/walletdb/walletdb.ts @@ -418,7 +418,7 @@ export class WalletDB { async removeAccount(account: Account, tx?: IDatabaseTransaction): Promise { await this.db.withTransaction(tx, async (tx) => { await this.accounts.del(account.id, tx) - await this.clearBalance(account, tx) + await this.balances.clear(tx, account.prefixRange) await this.accountIdsToCleanup.put(account.id, null, tx) }) } @@ -542,19 +542,6 @@ export class WalletDB { await this.transactions.del([account.prefix, transactionHash], tx) } - async clearTransactions(account: Account, tx?: IDatabaseTransaction): Promise { - await this.transactions.clear(tx, account.prefixRange) - await this.timestampToTransactionHash.clear(tx, account.prefixRange) - } - - async clearSequenceToNoteHash(account: Account, tx?: IDatabaseTransaction): Promise { - await this.sequenceToNoteHash.clear(tx, account.prefixRange) - } - - async clearNonChainNoteHashes(account: Account, tx?: IDatabaseTransaction): Promise { - await this.nonChainNoteHashes.clear(tx, account.prefixRange) - } - async *getTransactionHashesBySequence( account: Account, tx?: IDatabaseTransaction, @@ -658,20 +645,6 @@ export class WalletDB { }) } - /* - * clears sequenceToNoteHash entries for all accounts for a given sequence - */ - async clearSequenceNoteHashes(sequence: number, tx?: IDatabaseTransaction): Promise { - const encoding = this.sequenceToNoteHash.keyEncoding - - const keyRange = StorageUtils.getPrefixesKeyRange( - encoding.serialize([Buffer.alloc(4, 0), [sequence, Buffer.alloc(0)]]), - encoding.serialize([Buffer.alloc(4, 255), [sequence, Buffer.alloc(0)]]), - ) - - await this.sequenceToNoteHash.clear(tx, keyRange) - } - async addUnspentNoteHash( account: Account, noteHash: Buffer, @@ -1027,10 +1000,6 @@ export class WalletDB { await this.decryptedNotes.del([account.prefix, noteHash], tx) } - async clearDecryptedNotes(account: Account, tx?: IDatabaseTransaction): Promise { - await this.decryptedNotes.clear(tx, account.prefixRange) - } - async *loadDecryptedNotes( account: Account, range?: DatabaseKeyRange, @@ -1088,10 +1057,6 @@ export class WalletDB { await this.balances.put([account.prefix, assetId], balance, tx) } - async clearBalance(account: Account, tx?: IDatabaseTransaction): Promise { - await this.balances.clear(tx, account.prefixRange) - } - async *loadExpiredTransactionHashes( account: Account, headSequence: number, @@ -1221,13 +1186,6 @@ export class WalletDB { await this.pendingTransactionHashes.del([account.prefix, [expiration, transactionHash]], tx) } - async clearPendingTransactionHashes( - account: Account, - tx?: IDatabaseTransaction, - ): Promise { - await this.pendingTransactionHashes.clear(tx, account.prefixRange) - } - async forceCleanupDeletedAccounts(signal?: AbortSignal): Promise { return this.cleanupDeletedAccounts(Number.POSITIVE_INFINITY, signal) } From 911cb6910d2a5328af84733d353e8061f12ec171 Mon Sep 17 00:00:00 2001 From: Jason Spafford Date: Tue, 12 Nov 2024 15:30:12 -0800 Subject: [PATCH 07/68] Move table flags into OUTPUT group (#5638) --- ironfish-cli/src/ui/table.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ironfish-cli/src/ui/table.ts b/ironfish-cli/src/ui/table.ts index 2112e70b97..d17345ee84 100644 --- a/ironfish-cli/src/ui/table.ts +++ b/ironfish-cli/src/ui/table.ts @@ -36,18 +36,22 @@ export interface TableOptions { export const TableFlags = { csv: Flags.boolean({ description: 'output is csv format [alias: --output=csv]', + helpGroup: 'OUTPUT', }), extended: Flags.boolean({ description: 'show extra columns', + helpGroup: 'OUTPUT', }), 'no-header': Flags.boolean({ description: 'hide table header from output', exclusive: ['csv'], + helpGroup: 'OUTPUT', }), output: Flags.string({ description: 'output in a more machine friendly format', exclusive: ['csv'], options: ['csv', 'json'], + helpGroup: 'OUTPUT', }), sort: Flags.string({ description: "property to sort by (prepend '-' for descending)", From af1527cdcef6fae690f12697e755a4dc45ffd73a Mon Sep 17 00:00:00 2001 From: Jason Spafford Date: Tue, 12 Nov 2024 16:05:54 -0800 Subject: [PATCH 08/68] Support viewing transaction data for all accounts (#5640) * Support viewing transaction data for all accounts This allows you to use --no-account to now see all the transactions data for all accounts. * Fix missing notes flag --- .../src/commands/wallet/transactions/index.ts | 70 +++++++++++++++---- 1 file changed, 57 insertions(+), 13 deletions(-) diff --git a/ironfish-cli/src/commands/wallet/transactions/index.ts b/ironfish-cli/src/commands/wallet/transactions/index.ts index b2a7670251..900ef21be1 100644 --- a/ironfish-cli/src/commands/wallet/transactions/index.ts +++ b/ironfish-cli/src/commands/wallet/transactions/index.ts @@ -8,6 +8,8 @@ import { GetAccountTransactionsResponse, PartialRecursive, RpcAsset, + RpcClient, + RpcWalletTransaction, TransactionType, } from '@ironfish/sdk' import { Flags } from '@oclif/core' @@ -19,6 +21,7 @@ import { extractChainportDataFromTransaction } from '../../../utils/chainport' import { Format, TableCols } from '../../../utils/table' const { sort: _, ...tableFlags } = ui.TableFlags + export class TransactionsCommand extends IronfishCommand { static description = `list the account's transactions` @@ -27,7 +30,13 @@ export class TransactionsCommand extends IronfishCommand { ...tableFlags, account: Flags.string({ char: 'a', - description: 'Name of the account to get transactions for', + multiple: true, + description: 'show transactions from this account', + }), + 'no-account': Flags.boolean({ + description: 'show transactions for all accounts', + exclusive: ['account'], + aliases: ['no-a'], }), transaction: Flags.string({ char: 't', @@ -63,26 +72,34 @@ export class TransactionsCommand extends IronfishCommand { const client = await this.connectRpc() await ui.checkWalletUnlocked(client) - const account = await useAccount(client, flags.account) + let accounts = flags.account + if (flags['no-account']) { + const response = await client.wallet.getAccounts() + accounts = response.content.accounts + } else if (!accounts) { + const account = await useAccount(client, undefined) + accounts = [account] + } const networkId = (await client.chain.getNetworkInfo()).content.networkId - const response = client.wallet.getAccountTransactionsStream({ - account, - hash: flags.transaction, - sequence: flags.sequence, - limit: flags.limit, - offset: flags.offset, - confirmations: flags.confirmations, - notes: flags.notes, - }) + const transactions = this.getTransactions( + client, + accounts, + flags.transaction, + flags.sequence, + flags.limit, + flags.offset, + flags.confirmations, + flags.notes, + ) const columns = this.getColumns(flags.extended, flags.notes, format) let hasTransactions = false - let transactionRows: PartialRecursive[] = [] - for await (const transaction of response.contentStream()) { + + for await (const { account, transaction } of transactions) { if (transactionRows.length >= flags.limit) { break } @@ -129,6 +146,33 @@ export class TransactionsCommand extends IronfishCommand { } } + async *getTransactions( + client: RpcClient, + accounts: string[], + hash?: string, + sequence?: number, + limit?: number, + offset?: number, + confirmations?: number, + notes?: boolean, + ): AsyncGenerator<{ account: string; transaction: RpcWalletTransaction }, void> { + for (const account of accounts) { + const response = client.wallet.getAccountTransactionsStream({ + account, + hash: hash, + sequence: sequence, + limit: limit, + offset: offset, + confirmations: confirmations, + notes: notes, + }) + + for await (const transaction of response.contentStream()) { + yield { account, transaction } + } + } + } + getTransactionRows( assetLookup: { [key: string]: RpcAsset }, transaction: GetAccountTransactionsResponse, From dc683ca4c49098c1860c34cce89843cf2f0fdd26 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Tue, 12 Nov 2024 16:20:40 -0800 Subject: [PATCH 09/68] removes unused methods from walletDb (#5641) * removes unused methods from walletDb cleans up the walletDb by removing unused methods removes one unused wallet method that was the only usage of a walletDb method * puts 'loadHeads' back it's used in the 'debug' CLI command --- ironfish/src/wallet/account/account.ts | 10 ----- ironfish/src/wallet/walletdb/walletdb.ts | 48 ------------------------ 2 files changed, 58 deletions(-) diff --git a/ironfish/src/wallet/account/account.ts b/ironfish/src/wallet/account/account.ts index fc21d97f2d..51f45a4aed 100644 --- a/ironfish/src/wallet/account/account.ts +++ b/ironfish/src/wallet/account/account.ts @@ -845,16 +845,6 @@ export class Account { } } - async *getTransactionsOrderedBySequence( - tx?: IDatabaseTransaction, - ): AsyncGenerator> { - for await (const { hash } of this.walletDb.getTransactionHashesBySequence(this, tx)) { - const transaction = await this.getTransaction(hash, tx) - Assert.isNotUndefined(transaction) - yield transaction - } - } - getPendingTransactions( headSequence: number, tx?: IDatabaseTransaction, diff --git a/ironfish/src/wallet/walletdb/walletdb.ts b/ironfish/src/wallet/walletdb/walletdb.ts index f97272805f..3543c84942 100644 --- a/ironfish/src/wallet/walletdb/walletdb.ts +++ b/ironfish/src/wallet/walletdb/walletdb.ts @@ -542,19 +542,6 @@ export class WalletDB { await this.transactions.del([account.prefix, transactionHash], tx) } - async *getTransactionHashesBySequence( - account: Account, - tx?: IDatabaseTransaction, - ): AsyncGenerator<{ sequence: number; hash: Buffer }> { - for await (const [, [sequence, hash]] of this.sequenceToTransactionHash.getAllKeysIter( - tx, - account.prefixRange, - { ordered: true }, - )) { - yield { sequence, hash } - } - } - async *loadTransactions( account: Account, range?: DatabaseKeyRange, @@ -831,24 +818,6 @@ export class WalletDB { } } - async *loadNullifierToNoteHash( - account: Account, - tx?: IDatabaseTransaction, - ): AsyncGenerator<{ - nullifier: Buffer - noteHash: Buffer - }> { - for await (const [[_, nullifier], noteHash] of this.nullifierToNoteHash.getAllIter( - tx, - account.prefixRange, - )) { - yield { - nullifier, - noteHash, - } - } - } - async deleteNullifier( account: Account, nullifier: Buffer, @@ -1142,19 +1111,6 @@ export class WalletDB { } } - async saveSequenceToTransactionHash( - account: Account, - sequence: number, - transactionHash: Buffer, - tx?: IDatabaseTransaction, - ): Promise { - await this.sequenceToTransactionHash.put( - [account.prefix, [sequence, transactionHash]], - null, - tx, - ) - } - async deleteSequenceToTransactionHash( account: Account, sequence: number, @@ -1406,10 +1362,6 @@ export class WalletDB { return this.multisigIdentities.get(identity, tx) } - async hasMultisigIdentity(identity: Buffer, tx?: IDatabaseTransaction): Promise { - return (await this.getMultisigIdentity(identity, tx)) !== undefined - } - async deleteMultisigIdentity(identity: Buffer, tx?: IDatabaseTransaction): Promise { await this.multisigIdentities.del(identity, tx) } From 65321663d2ea91277bacdea8c4504b51d18f2af5 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Tue, 12 Nov 2024 16:39:39 -0800 Subject: [PATCH 10/68] inlines logic of setNoteHashSequence (#5643) remove setNoteHashSequence from the walletDb and moves the logic inline into saveDecryptedNote; the only method that called setNoteHashSequence --- ironfish/src/wallet/walletdb/walletdb.ts | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/ironfish/src/wallet/walletdb/walletdb.ts b/ironfish/src/wallet/walletdb/walletdb.ts index 3543c84942..31d10911ac 100644 --- a/ironfish/src/wallet/walletdb/walletdb.ts +++ b/ironfish/src/wallet/walletdb/walletdb.ts @@ -589,22 +589,6 @@ export class WalletDB { ) } - async setNoteHashSequence( - account: Account, - noteHash: Buffer, - sequence: number | null, - tx?: IDatabaseTransaction, - ): Promise { - await this.db.withTransaction(tx, async (tx) => { - if (sequence) { - await this.sequenceToNoteHash.put([account.prefix, [sequence, noteHash]], null, tx) - await this.nonChainNoteHashes.del([account.prefix, noteHash], tx) - } else { - await this.nonChainNoteHashes.put([account.prefix, noteHash], null, tx) - } - }) - } - async disconnectNoteHashSequence( account: Account, noteHash: Buffer, @@ -840,7 +824,12 @@ export class WalletDB { await this.saveNullifierNoteHash(account, note.nullifier, noteHash, tx) } - await this.setNoteHashSequence(account, noteHash, note.sequence, tx) + if (note.sequence) { + await this.sequenceToNoteHash.put([account.prefix, [note.sequence, noteHash]], null, tx) + await this.nonChainNoteHashes.del([account.prefix, noteHash], tx) + } else { + await this.nonChainNoteHashes.put([account.prefix, noteHash], null, tx) + } await this.decryptedNotes.put([account.prefix, noteHash], note, tx) }) From a2430679fa82cbf2cb21e27fe1a915d6bb152b9b Mon Sep 17 00:00:00 2001 From: Jason Spafford Date: Tue, 12 Nov 2024 16:49:13 -0800 Subject: [PATCH 11/68] Include account names when displaying notes (#5642) --- .../src/commands/wallet/transactions/index.ts | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/ironfish-cli/src/commands/wallet/transactions/index.ts b/ironfish-cli/src/commands/wallet/transactions/index.ts index 900ef21be1..5fa9dc8d81 100644 --- a/ironfish-cli/src/commands/wallet/transactions/index.ts +++ b/ironfish-cli/src/commands/wallet/transactions/index.ts @@ -72,15 +72,25 @@ export class TransactionsCommand extends IronfishCommand { const client = await this.connectRpc() await ui.checkWalletUnlocked(client) + const allAccounts = (await client.wallet.getAccounts()).content.accounts + let accounts = flags.account if (flags['no-account']) { - const response = await client.wallet.getAccounts() - accounts = response.content.accounts + accounts = allAccounts } else if (!accounts) { const account = await useAccount(client, undefined) accounts = [account] } + const accountsByAddress = new Map( + await Promise.all( + allAccounts.map>(async (account) => { + const response = await client.wallet.getAccountPublicKey({ account }) + return [response.content.publicKey, response.content.account] + }), + ), + ) + const networkId = (await client.chain.getNetworkInfo()).content.networkId const transactions = this.getTransactions( @@ -120,7 +130,7 @@ export class TransactionsCommand extends IronfishCommand { } transactionRows = transactionRows.concat( - this.getTransactionRowsByNote(assetLookup, transaction, format), + this.getTransactionRowsByNote(assetLookup, accountsByAddress, transaction, format), ) } else { const assetLookup = await getAssetsByIDs( @@ -235,6 +245,7 @@ export class TransactionsCommand extends IronfishCommand { getTransactionRowsByNote( assetLookup: { [key: string]: RpcAsset }, + accountLookup: Map, transaction: GetAccountTransactionsResponse, format: Format, ): PartialRecursive[] { @@ -258,6 +269,8 @@ export class TransactionsCommand extends IronfishCommand { const sender = note.sender const recipient = note.owner const memo = note.memo + const senderName = accountLookup.get(note.sender) + const recipientName = accountLookup.get(note.owner) const group = this.getRowGroup(index, noteCount, transactionRows.length) @@ -273,7 +286,9 @@ export class TransactionsCommand extends IronfishCommand { amount, feePaid, sender, + senderName, recipient, + recipientName, memo, }) } else { @@ -285,7 +300,9 @@ export class TransactionsCommand extends IronfishCommand { assetSymbol, amount, sender, + senderName, recipient, + recipientName, memo, }) } @@ -372,10 +389,18 @@ export class TransactionsCommand extends IronfishCommand { if (notes) { columns = { ...columns, + senderName: { + header: 'Sender', + get: (row) => row.senderName ?? '', + }, sender: { header: 'Sender Address', get: (row) => row.sender ?? '', }, + recipientName: { + header: 'Recipient', + get: (row) => row.recipientName ?? '', + }, recipient: { header: 'Recipient Address', get: (row) => row.recipient ?? '', @@ -435,6 +460,8 @@ type TransactionRow = { expiration: number submittedSequence: number sender: string + senderName?: string recipient: string + recipientName?: string memo?: string } From 301aa74cda6b8fa52f30492bfd1887b491f2753b Mon Sep 17 00:00:00 2001 From: andrea Date: Mon, 11 Nov 2024 15:32:10 -0800 Subject: [PATCH 12/68] WASM: add bindings for the primitive types Added bindings for: - `Scalar` - `Fr` - `ExtendedPoint` - `SubgroupPoint` - `Nullifier` - `redjubjub::PrivateKey` - `redjubjub::PublicKey` - `redjubjub::Signature` These are all structs that are required by transactions. --- Cargo.lock | 5 + ironfish-rust-wasm/Cargo.toml | 5 + ironfish-rust-wasm/src/lib.rs | 1 + ironfish-rust-wasm/src/primitives.rs | 259 +++++++++++++++++++++++++++ 4 files changed, 270 insertions(+) create mode 100644 ironfish-rust-wasm/src/primitives.rs diff --git a/Cargo.lock b/Cargo.lock index 43d672289d..16c0e2a856 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1634,8 +1634,13 @@ dependencies = [ name = "ironfish-wasm" version = "0.1.0" dependencies = [ + "blstrs", "getrandom", + "group 0.12.1", "ironfish", + "ironfish-jubjub", + "ironfish_zkp", + "rand", "wasm-bindgen", ] diff --git a/ironfish-rust-wasm/Cargo.toml b/ironfish-rust-wasm/Cargo.toml index cd6a9ac545..edbf84a64a 100644 --- a/ironfish-rust-wasm/Cargo.toml +++ b/ironfish-rust-wasm/Cargo.toml @@ -14,6 +14,11 @@ publish = false crate-type = ["cdylib"] [dependencies] +blstrs = "0.6.0" getrandom = { version = "0.2.8", features = ["js"] } # need to explicitly enable the `js` feature in order to run in a browser +group = "0.12.0" ironfish = { version = "0.3.0", path = "../ironfish-rust" } +ironfish-jubjub = "0.1.0" +ironfish_zkp = { version = "0.2.0", path = "../ironfish-zkp" } +rand = "0.8.5" wasm-bindgen = "0.2.95" diff --git a/ironfish-rust-wasm/src/lib.rs b/ironfish-rust-wasm/src/lib.rs index 2ef579a640..3815e3640c 100644 --- a/ironfish-rust-wasm/src/lib.rs +++ b/ironfish-rust-wasm/src/lib.rs @@ -10,3 +10,4 @@ use getrandom as _; pub mod errors; +pub mod primitives; diff --git a/ironfish-rust-wasm/src/primitives.rs b/ironfish-rust-wasm/src/primitives.rs new file mode 100644 index 0000000000..2e2b512281 --- /dev/null +++ b/ironfish-rust-wasm/src/primitives.rs @@ -0,0 +1,259 @@ +use crate::errors::IronfishError; +use group::GroupEncoding; +use ironfish::errors::IronfishErrorKind; +use ironfish_zkp::redjubjub; +use rand::thread_rng; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct Scalar(blstrs::Scalar); + +#[wasm_bindgen] +impl Scalar { + #[wasm_bindgen(js_name = toBytesBe)] + pub fn to_bytes_be(&self) -> Vec { + self.0.to_bytes_be().to_vec() + } + + #[wasm_bindgen(js_name = toBytesLe)] + pub fn to_bytes_le(&self) -> Vec { + self.0.to_bytes_le().to_vec() + } +} + +impl From for Scalar { + fn from(s: blstrs::Scalar) -> Self { + Self(s) + } +} + +impl AsRef for Scalar { + fn as_ref(&self) -> &blstrs::Scalar { + &self.0 + } +} + +#[wasm_bindgen] +#[derive(Default, Copy, Clone, PartialEq, Eq, Debug)] +pub struct Fr(ironfish_jubjub::Fr); + +#[wasm_bindgen] +impl Fr { + #[wasm_bindgen(js_name = toBytes)] + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes().to_vec() + } +} + +impl From for Fr { + fn from(s: ironfish_jubjub::Fr) -> Self { + Self(s) + } +} + +impl AsRef for Fr { + fn as_ref(&self) -> &ironfish_jubjub::Fr { + &self.0 + } +} + +#[wasm_bindgen] +#[derive(Default, Copy, Clone, PartialEq, Eq, Debug)] +pub struct ExtendedPoint(ironfish_jubjub::ExtendedPoint); + +#[wasm_bindgen] +impl ExtendedPoint { + #[wasm_bindgen(js_name = toBytes)] + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes().to_vec() + } +} + +impl From for ExtendedPoint { + fn from(s: ironfish_jubjub::ExtendedPoint) -> Self { + Self(s) + } +} + +impl AsRef for ExtendedPoint { + fn as_ref(&self) -> &ironfish_jubjub::ExtendedPoint { + &self.0 + } +} + +#[wasm_bindgen] +#[derive(Default, Copy, Clone, PartialEq, Eq, Debug)] +pub struct SubgroupPoint(ironfish_jubjub::SubgroupPoint); + +#[wasm_bindgen] +impl SubgroupPoint { + #[wasm_bindgen(js_name = toBytes)] + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes().to_vec() + } +} + +impl From for SubgroupPoint { + fn from(s: ironfish_jubjub::SubgroupPoint) -> Self { + Self(s) + } +} + +impl AsRef for SubgroupPoint { + fn as_ref(&self) -> &ironfish_jubjub::SubgroupPoint { + &self.0 + } +} + +#[wasm_bindgen] +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Nullifier(ironfish_zkp::Nullifier); + +#[wasm_bindgen] +impl Nullifier { + #[wasm_bindgen(js_name = toBytes)] + pub fn to_bytes(&self) -> Vec { + self.0.to_vec() + } +} + +impl From for Nullifier { + fn from(s: ironfish_zkp::Nullifier) -> Self { + Self(s) + } +} + +impl AsRef for Nullifier { + fn as_ref(&self) -> &ironfish_zkp::Nullifier { + &self.0 + } +} + +#[wasm_bindgen] +pub struct PrivateKey(redjubjub::PrivateKey); + +#[wasm_bindgen] +impl PrivateKey { + #[wasm_bindgen(constructor)] + pub fn deserialize(bytes: &[u8]) -> Result { + let s = redjubjub::PrivateKey::read(bytes)?; + Ok(Self::from(s)) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Vec { + let mut buf = Vec::new(); + self.0.write(&mut buf).expect("serialization failed"); + buf + } + + #[wasm_bindgen] + pub fn randomize(&self, alpha: &Fr) -> Self { + self.0.randomize(*alpha.as_ref()).into() + } + + #[wasm_bindgen] + pub fn sign(&self, msg: &[u8], p_g: &SubgroupPoint) -> Signature { + self.0.sign(msg, &mut thread_rng(), *p_g.as_ref()).into() + } + + #[wasm_bindgen(js_name = toPublicKey)] + pub fn to_public_key(&self, p_g: &SubgroupPoint) -> PublicKey { + redjubjub::PublicKey::from_private(self.as_ref(), *p_g.as_ref()).into() + } +} + +impl From for PrivateKey { + fn from(p: redjubjub::PrivateKey) -> Self { + Self(p) + } +} + +impl AsRef for PrivateKey { + fn as_ref(&self) -> &redjubjub::PrivateKey { + &self.0 + } +} + +#[wasm_bindgen] +#[derive(Clone, Debug)] +pub struct PublicKey(redjubjub::PublicKey); + +#[wasm_bindgen] +impl PublicKey { + #[wasm_bindgen(constructor)] + pub fn deserialize(bytes: &[u8]) -> Result { + let s = redjubjub::PublicKey::read(bytes)?; + Ok(Self::from(s)) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Vec { + let mut buf = Vec::new(); + self.0.write(&mut buf).expect("serialization failed"); + buf + } + + #[wasm_bindgen] + pub fn randomize(&self, alpha: &Fr, p_g: &SubgroupPoint) -> Self { + self.0.randomize(*alpha.as_ref(), *p_g.as_ref()).into() + } + + #[wasm_bindgen] + pub fn verify( + &self, + msg: &[u8], + sig: &Signature, + p_g: &SubgroupPoint, + ) -> Result<(), IronfishError> { + self.0 + .verify(msg, sig.as_ref(), *p_g.as_ref()) + .then_some(()) + .ok_or_else(|| IronfishErrorKind::InvalidSignature.into()) + } +} + +impl From for PublicKey { + fn from(p: redjubjub::PublicKey) -> Self { + Self(p) + } +} + +impl AsRef for PublicKey { + fn as_ref(&self) -> &redjubjub::PublicKey { + &self.0 + } +} + +#[wasm_bindgen] +#[derive(Clone, Debug)] +pub struct Signature(redjubjub::Signature); + +#[wasm_bindgen] +impl Signature { + #[wasm_bindgen(constructor)] + pub fn deserialize(bytes: &[u8]) -> Result { + let s = redjubjub::Signature::read(bytes)?; + Ok(Self::from(s)) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Vec { + let mut buf = Vec::new(); + self.0.write(&mut buf).expect("serialization failed"); + buf + } +} + +impl From for Signature { + fn from(s: redjubjub::Signature) -> Self { + Self(s) + } +} + +impl AsRef for Signature { + fn as_ref(&self) -> &redjubjub::Signature { + &self.0 + } +} From 2ab16accddefaff5a0fbeeab0b7acabadbcb9de1 Mon Sep 17 00:00:00 2001 From: Jason Spafford Date: Wed, 13 Nov 2024 10:04:39 -0800 Subject: [PATCH 13/68] Add a new output 'transfers' and make it default (#5644) This adds a new output transfers which uses the same column as notes but excludes change notes. --- .../src/commands/wallet/transactions/index.ts | 51 +++++++++++++++---- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/ironfish-cli/src/commands/wallet/transactions/index.ts b/ironfish-cli/src/commands/wallet/transactions/index.ts index 5fa9dc8d81..f808efaffa 100644 --- a/ironfish-cli/src/commands/wallet/transactions/index.ts +++ b/ironfish-cli/src/commands/wallet/transactions/index.ts @@ -54,9 +54,14 @@ export class TransactionsCommand extends IronfishCommand { description: 'Number of block confirmations needed to confirm a transaction', }), notes: Flags.boolean({ - default: false, description: 'Include data from transaction output notes', }), + format: Flags.string({ + description: 'output in a more machine friendly format', + exclusive: ['notes'], + options: ['notes', 'transactions', 'transfers'], + helpGroup: 'OUTPUT', + }), } async start(): Promise { @@ -69,6 +74,15 @@ export class TransactionsCommand extends IronfishCommand { ? Format.json : Format.cli + const output = + flags.notes || flags.format === 'notes' + ? 'notes' + : flags.format === 'transactions' + ? 'transactions' + : flags.format === 'transfers' + ? 'transfers' + : 'transfers' + const client = await this.connectRpc() await ui.checkWalletUnlocked(client) @@ -101,11 +115,9 @@ export class TransactionsCommand extends IronfishCommand { flags.limit, flags.offset, flags.confirmations, - flags.notes, + output === 'notes' || output === 'transfers', ) - const columns = this.getColumns(flags.extended, flags.notes, format) - let hasTransactions = false let transactionRows: PartialRecursive[] = [] @@ -113,8 +125,10 @@ export class TransactionsCommand extends IronfishCommand { if (transactionRows.length >= flags.limit) { break } - if (flags.notes) { + + if (output === 'notes' || output === 'transfers') { Assert.isNotUndefined(transaction.notes) + const assetLookup = await getAssetsByIDs( client, transaction.notes.map((n) => n.assetId) || [], @@ -130,7 +144,13 @@ export class TransactionsCommand extends IronfishCommand { } transactionRows = transactionRows.concat( - this.getTransactionRowsByNote(assetLookup, accountsByAddress, transaction, format), + this.getTransactionRowsByNote( + assetLookup, + accountsByAddress, + transaction, + format, + output, + ), ) } else { const assetLookup = await getAssetsByIDs( @@ -146,6 +166,8 @@ export class TransactionsCommand extends IronfishCommand { hasTransactions = true } + const columns = this.getColumns(flags.extended, output, format) + ui.table(transactionRows, columns, { printLine: this.log.bind(this), ...flags, @@ -248,6 +270,7 @@ export class TransactionsCommand extends IronfishCommand { accountLookup: Map, transaction: GetAccountTransactionsResponse, format: Format, + output: 'notes' | 'transactions' | 'transfers', ): PartialRecursive[] { Assert.isNotUndefined(transaction.notes) const transactionRows = [] @@ -272,7 +295,15 @@ export class TransactionsCommand extends IronfishCommand { const senderName = accountLookup.get(note.sender) const recipientName = accountLookup.get(note.owner) - const group = this.getRowGroup(index, noteCount, transactionRows.length) + let group = this.getRowGroup(index, noteCount, transactionRows.length) + + if (output === 'transfers') { + if (note.sender === note.owner && !transaction.mints.length) { + continue + } else { + group = '' + } + } // include full transaction details in first row or non-cli-formatted output if (transactionRows.length === 0 || format !== Format.cli) { @@ -313,7 +344,7 @@ export class TransactionsCommand extends IronfishCommand { getColumns( extended: boolean, - notes: boolean, + output: 'notes' | 'transactions' | 'transfers', format: Format, ): ui.TableColumns> { let columns: ui.TableColumns> = { @@ -327,7 +358,7 @@ export class TransactionsCommand extends IronfishCommand { }, type: { header: 'Type', - minWidth: notes ? 18 : 8, + minWidth: output === 'notes' || output === 'transfers' ? 18 : 8, get: (row) => row.type ?? '', }, hash: { @@ -386,7 +417,7 @@ export class TransactionsCommand extends IronfishCommand { }, } - if (notes) { + if (output === 'notes' || output === 'transfers') { columns = { ...columns, senderName: { From b63f951e6aea966c111d8775f3368c6998b219f1 Mon Sep 17 00:00:00 2001 From: andrea Date: Tue, 12 Nov 2024 17:31:45 -0800 Subject: [PATCH 14/68] WASM: add bindings for `PublicAddress` --- Cargo.lock | 1 + ironfish-rust-wasm/Cargo.toml | 3 + ironfish-rust-wasm/src/keys/mod.rs | 5 ++ ironfish-rust-wasm/src/keys/public_address.rs | 71 +++++++++++++++++++ ironfish-rust-wasm/src/lib.rs | 5 ++ ironfish-rust/src/keys/public_address.rs | 6 +- 6 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 ironfish-rust-wasm/src/keys/mod.rs create mode 100644 ironfish-rust-wasm/src/keys/public_address.rs diff --git a/Cargo.lock b/Cargo.lock index 16c0e2a856..5306272fa8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1637,6 +1637,7 @@ dependencies = [ "blstrs", "getrandom", "group 0.12.1", + "hex-literal", "ironfish", "ironfish-jubjub", "ironfish_zkp", diff --git a/ironfish-rust-wasm/Cargo.toml b/ironfish-rust-wasm/Cargo.toml index edbf84a64a..f1ed075051 100644 --- a/ironfish-rust-wasm/Cargo.toml +++ b/ironfish-rust-wasm/Cargo.toml @@ -22,3 +22,6 @@ ironfish-jubjub = "0.1.0" ironfish_zkp = { version = "0.2.0", path = "../ironfish-zkp" } rand = "0.8.5" wasm-bindgen = "0.2.95" + +[dev-dependencies] +hex-literal = "0.4.1" diff --git a/ironfish-rust-wasm/src/keys/mod.rs b/ironfish-rust-wasm/src/keys/mod.rs new file mode 100644 index 0000000000..7fda23142a --- /dev/null +++ b/ironfish-rust-wasm/src/keys/mod.rs @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +pub mod public_address; diff --git a/ironfish-rust-wasm/src/keys/public_address.rs b/ironfish-rust-wasm/src/keys/public_address.rs new file mode 100644 index 0000000000..adb15924ac --- /dev/null +++ b/ironfish-rust-wasm/src/keys/public_address.rs @@ -0,0 +1,71 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::errors::IronfishError; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct PublicAddress(ironfish::PublicAddress); + +#[wasm_bindgen] +impl PublicAddress { + #[wasm_bindgen(constructor)] + pub fn deserialize(bytes: &[u8]) -> Result { + Ok(Self(ironfish::PublicAddress::read(bytes)?)) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Vec { + self.0.public_address().to_vec() + } + + #[wasm_bindgen(getter)] + pub fn bytes(&self) -> Vec { + self.0.public_address().to_vec() + } + + #[wasm_bindgen(getter)] + pub fn hex(&self) -> String { + self.0.hex_public_address() + } +} + +impl From for PublicAddress { + fn from(d: ironfish::PublicAddress) -> Self { + Self(d) + } +} + +impl AsRef for PublicAddress { + fn as_ref(&self) -> &ironfish::PublicAddress { + &self.0 + } +} + +#[cfg(test)] +mod tests { + use crate::keys::public_address::PublicAddress; + use hex_literal::hex; + + #[test] + fn valid_address() { + let bytes = hex!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0"); + let addr = PublicAddress::deserialize(&bytes[..]) + .expect("valid address deserialization should have succeeded"); + assert_eq!(addr.serialize(), bytes); + assert_eq!(addr.bytes(), bytes); + assert_eq!( + addr.hex(), + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0" + ); + } + + #[test] + fn invalid_address() { + let bytes = hex!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1"); + PublicAddress::deserialize(&bytes[..]) + .expect_err("invalid address deserialization should have failed"); + } +} diff --git a/ironfish-rust-wasm/src/lib.rs b/ironfish-rust-wasm/src/lib.rs index 3815e3640c..28dc6c6999 100644 --- a/ironfish-rust-wasm/src/lib.rs +++ b/ironfish-rust-wasm/src/lib.rs @@ -1,3 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + #![warn(clippy::dbg_macro)] #![warn(clippy::print_stderr)] #![warn(clippy::print_stdout)] @@ -10,4 +14,5 @@ use getrandom as _; pub mod errors; +pub mod keys; pub mod primitives; diff --git a/ironfish-rust/src/keys/public_address.rs b/ironfish-rust/src/keys/public_address.rs index b2b9285af2..e38f574365 100644 --- a/ironfish-rust/src/keys/public_address.rs +++ b/ironfish-rust/src/keys/public_address.rs @@ -38,13 +38,13 @@ impl PublicAddress { } /// Load a public address from a Read implementation (e.g: socket, file) - pub fn read(reader: &mut R) -> Result { + pub fn read(mut reader: R) -> Result { let mut address_bytes = [0; PUBLIC_ADDRESS_SIZE]; reader.read_exact(&mut address_bytes)?; Self::new(&address_bytes) } - pub fn read_unchecked(reader: &mut R) -> Result { + pub fn read_unchecked(mut reader: R) -> Result { let mut address_bytes = [0; PUBLIC_ADDRESS_SIZE]; reader.read_exact(&mut address_bytes)?; Self::new_unchecked(&address_bytes) @@ -100,6 +100,8 @@ impl PartialEq for PublicAddress { } } +impl Eq for PublicAddress {} + #[cfg(test)] mod test { use crate::{ From 5cc13d76ab663e7e0c7d10aa3d651909a4d5a001 Mon Sep 17 00:00:00 2001 From: andrea Date: Tue, 12 Nov 2024 17:36:57 -0800 Subject: [PATCH 15/68] WASM: enable in-browser tests through `wasm-bindgen-test` The unit tests currently are marked with both `#[test]` and `#[wasm_bindgen_test]`. This allows tests to run with both `cargo test` and `wasm-pack test`. In theory, we could drop `#[test]`, but given that `cargo test` is faster than `wasm-pack test`, this makes developing easier, so I'm keeping it for now. --- Cargo.lock | 61 +++++++++++++++++-- ironfish-rust-wasm/Cargo.toml | 1 + ironfish-rust-wasm/src/keys/public_address.rs | 3 + ironfish-rust-wasm/src/lib.rs | 5 ++ supply-chain/audits.toml | 30 +++++++++ supply-chain/imports.lock | 36 +++++++++++ 6 files changed, 132 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5306272fa8..a8fdff21ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -492,6 +492,16 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + [[package]] name = "const-crc32-nostd" version = "1.3.1" @@ -1643,6 +1653,7 @@ dependencies = [ "ironfish_zkp", "rand", "wasm-bindgen", + "wasm-bindgen-test", ] [[package]] @@ -1688,9 +1699,9 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -1821,6 +1832,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minicov" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" +dependencies = [ + "cc", + "walkdir", +] + [[package]] name = "mio" version = "0.8.11" @@ -2493,6 +2514,12 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.1.0" @@ -3096,9 +3123,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.34" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if", "js-sys", @@ -3135,6 +3162,32 @@ version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +[[package]] +name = "wasm-bindgen-test" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d381749acb0943d357dcbd8f0b100640679883fcdeeef04def49daf8d33a5426" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "minicov", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c97b2ef2c8d627381e51c071c2ab328eac606d3f69dd82bcbca20a9e389d95f0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "web-sys" version = "0.3.61" diff --git a/ironfish-rust-wasm/Cargo.toml b/ironfish-rust-wasm/Cargo.toml index f1ed075051..32d317331c 100644 --- a/ironfish-rust-wasm/Cargo.toml +++ b/ironfish-rust-wasm/Cargo.toml @@ -25,3 +25,4 @@ wasm-bindgen = "0.2.95" [dev-dependencies] hex-literal = "0.4.1" +wasm-bindgen-test = "0.3.45" diff --git a/ironfish-rust-wasm/src/keys/public_address.rs b/ironfish-rust-wasm/src/keys/public_address.rs index adb15924ac..3f2e91dba0 100644 --- a/ironfish-rust-wasm/src/keys/public_address.rs +++ b/ironfish-rust-wasm/src/keys/public_address.rs @@ -48,8 +48,10 @@ impl AsRef for PublicAddress { mod tests { use crate::keys::public_address::PublicAddress; use hex_literal::hex; + use wasm_bindgen_test::wasm_bindgen_test; #[test] + #[wasm_bindgen_test] fn valid_address() { let bytes = hex!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0"); let addr = PublicAddress::deserialize(&bytes[..]) @@ -63,6 +65,7 @@ mod tests { } #[test] + #[wasm_bindgen_test] fn invalid_address() { let bytes = hex!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1"); PublicAddress::deserialize(&bytes[..]) diff --git a/ironfish-rust-wasm/src/lib.rs b/ironfish-rust-wasm/src/lib.rs index 28dc6c6999..81ab379e5e 100644 --- a/ironfish-rust-wasm/src/lib.rs +++ b/ironfish-rust-wasm/src/lib.rs @@ -16,3 +16,8 @@ use getrandom as _; pub mod errors; pub mod keys; pub mod primitives; + +#[cfg(test)] +mod tests { + wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); +} diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index cbe25b4615..99df836d8c 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -12,6 +12,11 @@ criteria = "safe-to-deploy" delta = "0.10.1 -> 0.11.0" notes = "Main change is how langauges are handled (previous version: through an enum, new version: through a marker type)" +[[audits.console_error_panic_hook]] +who = "andrea " +criteria = "safe-to-run" +version = "0.1.7" + [[audits.crypto_box]] who = "Andrea " criteria = "safe-to-deploy" @@ -78,6 +83,16 @@ criteria = "safe-to-deploy" version = "0.1.0" notes = "Crate owned by the Iron Fish organization" +[[audits.js-sys]] +who = "andrea " +criteria = "safe-to-deploy" +delta = "0.3.69 -> 0.3.72" + +[[audits.minicov]] +who = "andrea " +criteria = "safe-to-run" +version = "0.3.7" + [[audits.mio]] who = "Andrea " criteria = "safe-to-deploy" @@ -120,6 +135,11 @@ criteria = "safe-to-deploy" delta = "0.2.84 -> 0.2.95" notes = "New features and bug fixes; there's are new/changed unsafe blocks and they all look benign" +[[audits.wasm-bindgen-futures]] +who = "andrea " +criteria = "safe-to-deploy" +delta = "0.4.34 -> 0.4.45" + [[audits.wasm-bindgen-macro]] who = "andrea " criteria = "safe-to-deploy" @@ -138,6 +158,16 @@ criteria = "safe-to-deploy" delta = "0.2.92 -> 0.2.95" notes = "Only minor changes; no new unsafe code or system calls introduced" +[[audits.wasm-bindgen-test]] +who = "andrea " +criteria = "safe-to-run" +version = "0.3.45" + +[[audits.wasm-bindgen-test-macro]] +who = "andrea " +criteria = "safe-to-run" +version = "0.3.45" + [[trusted.reddsa]] criteria = "safe-to-deploy" user-id = 6289 # Jack Grigg (str4d) diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index ee33b788d6..9bb1cb54b6 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -612,6 +612,12 @@ The delta just 1) inlines/expands `impl ToTokens` that used to be handled via """ aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" +[[audits.google.audits.scoped-tls]] +who = "George Burgess IV " +criteria = "safe-to-run" +version = "1.0.0" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" + [[audits.google.audits.unicode-xid]] who = "George Burgess IV " criteria = "safe-to-deploy" @@ -1029,6 +1035,12 @@ version = "1.1.0" notes = "Straightforward crate with no unsafe code, does what it says on the tin." aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +[[audits.mozilla.audits.scoped-tls]] +who = "Mike Hommey " +criteria = "safe-to-run" +delta = "1.0.0 -> 1.0.1" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + [[audits.mozilla.audits.sha2]] who = "Mike Hommey " criteria = "safe-to-deploy" @@ -1228,6 +1240,30 @@ version = "0.1.3" notes = "Reviewed in full." aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" +[[audits.zcash.audits.js-sys]] +who = "Jack Grigg " +criteria = "safe-to-deploy" +delta = "0.3.61 -> 0.3.64" +aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" + +[[audits.zcash.audits.js-sys]] +who = "Jack Grigg " +criteria = "safe-to-deploy" +delta = "0.3.64 -> 0.3.66" +notes = """ +- Fixes the `BigInt64Array` variants of the existing `Atomics.wait` method. +- Adds `Atomics.waitAsync`, the `DataView` constructor variant that takes + `SharedArrayBuffer`, and `WebAssembly.Exception`; I checked these against their + MDN documentation. +""" +aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" + +[[audits.zcash.audits.js-sys]] +who = "Daira-Emma Hopwood " +criteria = "safe-to-deploy" +delta = "0.3.66 -> 0.3.69" +aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" + [[audits.zcash.audits.jubjub]] who = "Sean Bowe " criteria = "safe-to-deploy" From c41047e195c94019f79f397ce8c4072786a6b0cc Mon Sep 17 00:00:00 2001 From: andrea Date: Tue, 12 Nov 2024 17:56:19 -0800 Subject: [PATCH 16/68] Run tests on the WASM package as part of the GitHub workflow --- .github/workflows/rust_ci.yml | 35 ++++++++++++++++++++++++++-- ironfish-rust-wasm/src/errors.rs | 4 ++++ ironfish-rust-wasm/src/primitives.rs | 4 ++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust_ci.yml b/.github/workflows/rust_ci.yml index 8e3c12006c..a3d4afe44d 100644 --- a/.github/workflows/rust_ci.yml +++ b/.github/workflows/rust_ci.yml @@ -6,6 +6,7 @@ on: - "ironfish-phase2/**" - "ironfish-rust/**" - "ironfish-rust-nodejs/**" + - "ironfish-rust-wasm/**" - "ironfish-zkp/**" - "rust-toolchain" - ".github/workflows/rust*" @@ -20,6 +21,7 @@ on: - "ironfish-phase2/**" - "ironfish-rust/**" - "ironfish-rust-nodejs/**" + - "ironfish-rust-wasm/**" - "ironfish-zkp/**" - "rust-toolchain" - ".github/workflows/rust*" @@ -48,11 +50,14 @@ jobs: - name: Check for license headers for ironfish-rust-nodejs run: ./ci/lintHeaders.sh ./ironfish-rust-nodejs/src *.rs - - name: "`cargo fmt` check on ironfish-rust" + - name: Check for license headers for ironfish-rust-wasm + run: ./ci/lintHeaders.sh ./ironfish-rust-wasm/src *.rs + + - name: cargo fmt run: | cargo fmt --all -- --check - - name: "Clippy check on ironfish-rust" + - name: cargo clippy run: | cargo clippy --all-targets --all-features -- -D warnings @@ -220,3 +225,29 @@ jobs: with: token: ${{secrets.CODECOV_TOKEN}} flags: ironfish-zkp + + ironfish_wasm: + name: Test ironfish-rust-wasm + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + with: + shared-key: wasm + + - name: Install wasm-pack + # use the installation method reccommended on + # https://rustwasm.github.io/docs/wasm-bindgen/wasm-bindgen-test/continuous-integration.html#github-actions + run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + + - name: Run tests in Firefox + run: | + cd ironfish-rust-wasm + wasm-pack test --headless --firefox + + - name: Run tests in Chrome + run: | + cd ironfish-rust-wasm + wasm-pack test --headless --chrome diff --git a/ironfish-rust-wasm/src/errors.rs b/ironfish-rust-wasm/src/errors.rs index 62142de8f1..10e0ee9bff 100644 --- a/ironfish-rust-wasm/src/errors.rs +++ b/ironfish-rust-wasm/src/errors.rs @@ -1,3 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + use wasm_bindgen::prelude::*; #[wasm_bindgen] diff --git a/ironfish-rust-wasm/src/primitives.rs b/ironfish-rust-wasm/src/primitives.rs index 2e2b512281..99517956ac 100644 --- a/ironfish-rust-wasm/src/primitives.rs +++ b/ironfish-rust-wasm/src/primitives.rs @@ -1,3 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + use crate::errors::IronfishError; use group::GroupEncoding; use ironfish::errors::IronfishErrorKind; From 5e4369b625862d86fb025be5dad8405d81bb31f3 Mon Sep 17 00:00:00 2001 From: Jason Spafford Date: Wed, 13 Nov 2024 15:06:22 -0800 Subject: [PATCH 17/68] Rename Format -> TableOutput and output -> Format (#5650) This clarifies the names and keeps them consistent with the table naming. --- .../src/commands/wallet/transactions/index.ts | 42 +++++++++---------- ironfish-cli/src/ui/table.ts | 2 +- ironfish-cli/src/utils/table.ts | 6 +-- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/ironfish-cli/src/commands/wallet/transactions/index.ts b/ironfish-cli/src/commands/wallet/transactions/index.ts index f808efaffa..2bc52b5aec 100644 --- a/ironfish-cli/src/commands/wallet/transactions/index.ts +++ b/ironfish-cli/src/commands/wallet/transactions/index.ts @@ -18,7 +18,7 @@ import { RemoteFlags } from '../../../flags' import * as ui from '../../../ui' import { getAssetsByIDs, useAccount } from '../../../utils' import { extractChainportDataFromTransaction } from '../../../utils/chainport' -import { Format, TableCols } from '../../../utils/table' +import { TableCols, TableOutput } from '../../../utils/table' const { sort: _, ...tableFlags } = ui.TableFlags @@ -57,7 +57,7 @@ export class TransactionsCommand extends IronfishCommand { description: 'Include data from transaction output notes', }), format: Flags.string({ - description: 'output in a more machine friendly format', + description: 'show the data in a specified view', exclusive: ['notes'], options: ['notes', 'transactions', 'transfers'], helpGroup: 'OUTPUT', @@ -67,14 +67,14 @@ export class TransactionsCommand extends IronfishCommand { async start(): Promise { const { flags } = await this.parse(TransactionsCommand) - const format: Format = + const output: TableOutput = flags.csv || flags.output === 'csv' - ? Format.csv + ? TableOutput.csv : flags.output === 'json' - ? Format.json - : Format.cli + ? TableOutput.json + : TableOutput.cli - const output = + const format = flags.notes || flags.format === 'notes' ? 'notes' : flags.format === 'transactions' @@ -115,7 +115,7 @@ export class TransactionsCommand extends IronfishCommand { flags.limit, flags.offset, flags.confirmations, - output === 'notes' || output === 'transfers', + format === 'notes' || format === 'transfers', ) let hasTransactions = false @@ -126,7 +126,7 @@ export class TransactionsCommand extends IronfishCommand { break } - if (output === 'notes' || output === 'transfers') { + if (format === 'notes' || format === 'transfers') { Assert.isNotUndefined(transaction.notes) const assetLookup = await getAssetsByIDs( @@ -148,8 +148,8 @@ export class TransactionsCommand extends IronfishCommand { assetLookup, accountsByAddress, transaction, - format, output, + format, ), ) } else { @@ -160,13 +160,13 @@ export class TransactionsCommand extends IronfishCommand { flags.confirmations, ) transactionRows = transactionRows.concat( - this.getTransactionRows(assetLookup, transaction, format), + this.getTransactionRows(assetLookup, transaction, output), ) } hasTransactions = true } - const columns = this.getColumns(flags.extended, output, format) + const columns = this.getColumns(flags.extended, format, output) ui.table(transactionRows, columns, { printLine: this.log.bind(this), @@ -208,7 +208,7 @@ export class TransactionsCommand extends IronfishCommand { getTransactionRows( assetLookup: { [key: string]: RpcAsset }, transaction: GetAccountTransactionsResponse, - format: Format, + output: TableOutput, ): PartialRecursive[] { const nativeAssetId = Asset.nativeId().toString('hex') @@ -233,7 +233,7 @@ export class TransactionsCommand extends IronfishCommand { // exclude the native asset in cli output if no amount was sent/received // and it was not the only asset exchanged - if (format === Format.cli && amount === 0n && assetCount > 1) { + if (output === TableOutput.cli && amount === 0n && assetCount > 1) { assetCount -= 1 continue } @@ -251,7 +251,7 @@ export class TransactionsCommand extends IronfishCommand { } // include full transaction details in first row or non-cli-formatted output - if (transactionRows.length === 0 || format !== Format.cli) { + if (transactionRows.length === 0 || output !== TableOutput.cli) { transactionRows.push({ ...transaction, ...transactionRow, @@ -269,8 +269,8 @@ export class TransactionsCommand extends IronfishCommand { assetLookup: { [key: string]: RpcAsset }, accountLookup: Map, transaction: GetAccountTransactionsResponse, - format: Format, - output: 'notes' | 'transactions' | 'transfers', + output: TableOutput, + format: 'notes' | 'transactions' | 'transfers', ): PartialRecursive[] { Assert.isNotUndefined(transaction.notes) const transactionRows = [] @@ -297,7 +297,7 @@ export class TransactionsCommand extends IronfishCommand { let group = this.getRowGroup(index, noteCount, transactionRows.length) - if (output === 'transfers') { + if (format === 'transfers') { if (note.sender === note.owner && !transaction.mints.length) { continue } else { @@ -306,7 +306,7 @@ export class TransactionsCommand extends IronfishCommand { } // include full transaction details in first row or non-cli-formatted output - if (transactionRows.length === 0 || format !== Format.cli) { + if (transactionRows.length === 0 || output !== TableOutput.cli) { transactionRows.push({ ...transaction, group, @@ -345,7 +345,7 @@ export class TransactionsCommand extends IronfishCommand { getColumns( extended: boolean, output: 'notes' | 'transactions' | 'transfers', - format: Format, + format: TableOutput, ): ui.TableColumns> { let columns: ui.TableColumns> = { timestamp: TableCols.timestamp({ @@ -443,7 +443,7 @@ export class TransactionsCommand extends IronfishCommand { } } - if (format === Format.cli) { + if (format === TableOutput.cli) { columns = { group: { header: '', diff --git a/ironfish-cli/src/ui/table.ts b/ironfish-cli/src/ui/table.ts index d17345ee84..fd24116a9b 100644 --- a/ironfish-cli/src/ui/table.ts +++ b/ironfish-cli/src/ui/table.ts @@ -48,7 +48,7 @@ export const TableFlags = { helpGroup: 'OUTPUT', }), output: Flags.string({ - description: 'output in a more machine friendly format', + description: 'output in different file types', exclusive: ['csv'], options: ['csv', 'json'], helpGroup: 'OUTPUT', diff --git a/ironfish-cli/src/utils/table.ts b/ironfish-cli/src/utils/table.ts index 60220eb957..550b890beb 100644 --- a/ironfish-cli/src/utils/table.ts +++ b/ironfish-cli/src/utils/table.ts @@ -61,9 +61,9 @@ const timestamp = >(options?: { const asset = >(options?: { extended?: boolean - format?: Format + format?: TableOutput }): Partial>> => { - if (options?.extended || options?.format !== Format.cli) { + if (options?.extended || options?.format !== TableOutput.cli) { return { assetId: { header: 'Asset ID', @@ -127,7 +127,7 @@ function truncateCol(value: string, maxWidth: number | null): string { return value.slice(0, maxWidth - 1) + '…' } -export enum Format { +export enum TableOutput { cli = 'cli', csv = 'csv', json = 'json', From a5afca58750e0b528f33479574f8ae2a065768b1 Mon Sep 17 00:00:00 2001 From: Jason Spafford Date: Wed, 13 Nov 2024 15:45:59 -0800 Subject: [PATCH 18/68] Add filter start and end support (#5651) * Add filter start and end support This lets you filter dates based on a date input such as 04/20/2023 * Change examples to ISO format * Add command examples --- .../src/commands/wallet/transactions/index.ts | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/ironfish-cli/src/commands/wallet/transactions/index.ts b/ironfish-cli/src/commands/wallet/transactions/index.ts index 2bc52b5aec..98e21039f2 100644 --- a/ironfish-cli/src/commands/wallet/transactions/index.ts +++ b/ironfish-cli/src/commands/wallet/transactions/index.ts @@ -25,6 +25,19 @@ const { sort: _, ...tableFlags } = ui.TableFlags export class TransactionsCommand extends IronfishCommand { static description = `list the account's transactions` + static examples = [ + { + description: 'List all transactions in the current wallet:', + command: '$ <%= config.bin %> <%= command.id %>', + }, + { + description: + 'Export transactions in all wallets for the month of october in an accounting friendly format:', + command: + '$ <%= config.bin %> <%= command.id %> --no-account --filter.start 2024-10-01 --filter.end 2024-11-01 --output csv --format transfers', + }, + ] + static flags = { ...RemoteFlags, ...tableFlags, @@ -62,6 +75,14 @@ export class TransactionsCommand extends IronfishCommand { options: ['notes', 'transactions', 'transfers'], helpGroup: 'OUTPUT', }), + 'filter.start': Flags.string({ + description: 'include transactions after this date (inclusive). Example: 2023-04-01', + parse: (input) => Promise.resolve(new Date(input).toISOString()), + }), + 'filter.end': Flags.string({ + description: 'include transactions before this date (exclusive). Example: 2023-05-01', + parse: (input) => Promise.resolve(new Date(input).toISOString()), + }), } async start(): Promise { @@ -121,11 +142,22 @@ export class TransactionsCommand extends IronfishCommand { let hasTransactions = false let transactionRows: PartialRecursive[] = [] + const filterStart = flags['filter.start'] && new Date(flags['filter.start']).valueOf() + const filterEnd = flags['filter.end'] && new Date(flags['filter.end']).valueOf() + for await (const { account, transaction } of transactions) { if (transactionRows.length >= flags.limit) { break } + if (filterStart && transaction.timestamp < filterStart.valueOf()) { + continue + } + + if (filterEnd && transaction.timestamp >= filterEnd.valueOf()) { + continue + } + if (format === 'notes' || format === 'transfers') { Assert.isNotUndefined(transaction.notes) From e942146e9f0ff1e23297f23a1dd7335bada1da09 Mon Sep 17 00:00:00 2001 From: Jason Spafford Date: Wed, 13 Nov 2024 15:54:32 -0800 Subject: [PATCH 19/68] Add new date flag and use it in transactions cmd (#5654) --- .../src/commands/wallet/transactions/index.ts | 15 +++++---------- ironfish-cli/src/flags.ts | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/ironfish-cli/src/commands/wallet/transactions/index.ts b/ironfish-cli/src/commands/wallet/transactions/index.ts index 98e21039f2..31a34615c3 100644 --- a/ironfish-cli/src/commands/wallet/transactions/index.ts +++ b/ironfish-cli/src/commands/wallet/transactions/index.ts @@ -14,7 +14,7 @@ import { } from '@ironfish/sdk' import { Flags } from '@oclif/core' import { IronfishCommand } from '../../../command' -import { RemoteFlags } from '../../../flags' +import { DateFlag, RemoteFlags } from '../../../flags' import * as ui from '../../../ui' import { getAssetsByIDs, useAccount } from '../../../utils' import { extractChainportDataFromTransaction } from '../../../utils/chainport' @@ -75,13 +75,11 @@ export class TransactionsCommand extends IronfishCommand { options: ['notes', 'transactions', 'transfers'], helpGroup: 'OUTPUT', }), - 'filter.start': Flags.string({ + 'filter.start': DateFlag({ description: 'include transactions after this date (inclusive). Example: 2023-04-01', - parse: (input) => Promise.resolve(new Date(input).toISOString()), }), - 'filter.end': Flags.string({ + 'filter.end': DateFlag({ description: 'include transactions before this date (exclusive). Example: 2023-05-01', - parse: (input) => Promise.resolve(new Date(input).toISOString()), }), } @@ -142,19 +140,16 @@ export class TransactionsCommand extends IronfishCommand { let hasTransactions = false let transactionRows: PartialRecursive[] = [] - const filterStart = flags['filter.start'] && new Date(flags['filter.start']).valueOf() - const filterEnd = flags['filter.end'] && new Date(flags['filter.end']).valueOf() - for await (const { account, transaction } of transactions) { if (transactionRows.length >= flags.limit) { break } - if (filterStart && transaction.timestamp < filterStart.valueOf()) { + if (flags['filter.start'] && transaction.timestamp < flags['filter.start'].valueOf()) { continue } - if (filterEnd && transaction.timestamp >= filterEnd.valueOf()) { + if (flags['filter.end'] && transaction.timestamp >= flags['filter.end'].valueOf()) { continue } diff --git a/ironfish-cli/src/flags.ts b/ironfish-cli/src/flags.ts index c1b0eaeab2..6f6c4b8c5e 100644 --- a/ironfish-cli/src/flags.ts +++ b/ironfish-cli/src/flags.ts @@ -212,3 +212,17 @@ export const EnumLanguageKeyFlag = Flags.custom({ + parse: async (input, _ctx, opts) => { + const parsed = new Date(input) + + if (Number.isNaN(parsed.valueOf())) { + throw new Error( + `The value provided for ${opts.name} is an invalid format. It must be a valid date.`, + ) + } + + return Promise.resolve(parsed) + }, +}) From c18f4d47ca35ae42801c28026a71bf3484c5b43d Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Wed, 13 Nov 2024 16:15:21 -0800 Subject: [PATCH 20/68] adds 'format' option to account import (#5653) * adds 'format' option to account import adds a field to the wallet/importAccount RPC that allows clients to specify the format of the account (Base64Json, JSON, Mnemonic, or SpendingKey) if format is specified in the request then the decoding is only attempted for the specified format results in more specific error messages instead of an error message containing the errors for each encoder * updates test not to expect within except test would still pass if an error were not thrown --- .../rpc/routes/wallet/importAccount.test.ts | 38 +++++++++++++++++++ .../src/rpc/routes/wallet/importAccount.ts | 9 ++++- ironfish/src/wallet/exporter/account.ts | 30 +++++++++------ ironfish/src/wallet/exporter/encoder.ts | 2 + 4 files changed, 65 insertions(+), 14 deletions(-) diff --git a/ironfish/src/rpc/routes/wallet/importAccount.test.ts b/ironfish/src/rpc/routes/wallet/importAccount.test.ts index 549fbf4817..b7be8c5be5 100644 --- a/ironfish/src/rpc/routes/wallet/importAccount.test.ts +++ b/ironfish/src/rpc/routes/wallet/importAccount.test.ts @@ -642,4 +642,42 @@ describe('Route wallet/importAccount', () => { Assert.isNotUndefined(existingIdentity) expect(existingIdentity.name).toEqual('existingIdentity') }) + + describe('account format', () => { + it('should decode an account import with the requested format', async () => { + const name = 'mnemonic-format' + const mnemonic = spendingKeyToWords(generateKey().spendingKey, LanguageCode.English) + + const response = await routeTest.client.wallet.importAccount({ + account: mnemonic, + name: name, + rescan: false, + format: AccountFormat.Mnemonic, + }) + + expect(response.status).toBe(200) + expect(response.content).toMatchObject({ + name: name, + }) + }) + + it('should fail to decode an account import with the incorrect format', async () => { + const name = 'mnemonic-format' + const mnemonic = spendingKeyToWords(generateKey().spendingKey, LanguageCode.English) + + await expect( + routeTest.client.wallet.importAccount({ + account: mnemonic, + name, + rescan: false, + format: AccountFormat.JSON, + }), + ).rejects.toMatchObject({ + status: 400, + message: + expect.not.stringContaining('decoder errors:') && + expect.stringContaining('Invalid JSON'), + }) + }) + }) }) diff --git a/ironfish/src/rpc/routes/wallet/importAccount.ts b/ironfish/src/rpc/routes/wallet/importAccount.ts index 165ed8b410..0b3d310f6d 100644 --- a/ironfish/src/rpc/routes/wallet/importAccount.ts +++ b/ironfish/src/rpc/routes/wallet/importAccount.ts @@ -4,7 +4,7 @@ import * as yup from 'yup' import { DecodeInvalidName } from '../../../wallet' import { DuplicateAccountNameError, DuplicateIdentityNameError } from '../../../wallet/errors' -import { decodeAccountImport } from '../../../wallet/exporter/account' +import { AccountFormat, decodeAccountImport } from '../../../wallet/exporter/account' import { decryptEncodedAccount } from '../../../wallet/exporter/encryption' import { RPC_ERROR_CODES, RpcValidationError } from '../../adapters' import { ApiNamespace } from '../namespaces' @@ -18,6 +18,7 @@ export type ImportAccountRequest = { name?: string rescan?: boolean createdAt?: number + format?: AccountFormat } export type ImportResponse = { @@ -31,6 +32,7 @@ export const ImportAccountRequestSchema: yup.ObjectSchema name: yup.string().optional(), account: yup.string().defined(), createdAt: yup.number().optional(), + format: yup.mixed().oneOf(Object.values(AccountFormat)).optional(), }) .defined() @@ -50,7 +52,10 @@ routes.register( request.data.account = await decryptEncodedAccount(request.data.account, context.wallet) - const decoded = decodeAccountImport(request.data.account, { name: request.data.name }) + const decoded = decodeAccountImport(request.data.account, { + name: request.data.name, + format: request.data.format, + }) const account = await context.wallet.importAccount(decoded, { createdAt: request.data.createdAt, }) diff --git a/ironfish/src/wallet/exporter/account.ts b/ironfish/src/wallet/exporter/account.ts index d0669f42d0..122b7de7c1 100644 --- a/ironfish/src/wallet/exporter/account.ts +++ b/ironfish/src/wallet/exporter/account.ts @@ -27,23 +27,22 @@ export enum AccountFormat { SpendingKey = 'SpendingKey', } +const accountFormatToEncoder = new Map([ + [AccountFormat.Base64Json, Base64JsonEncoder], + [AccountFormat.JSON, JsonEncoder], + [AccountFormat.Mnemonic, MnemonicEncoder], + [AccountFormat.SpendingKey, SpendingKeyEncoder], +]) + export function encodeAccountImport( value: AccountImport, format: AccountFormat, options: AccountEncodingOptions = {}, ): string { - switch (format) { - case AccountFormat.JSON: - return new JsonEncoder().encode(value) - case AccountFormat.Base64Json: - return new Base64JsonEncoder().encode(value) - case AccountFormat.SpendingKey: - return new SpendingKeyEncoder().encode(value) - case AccountFormat.Mnemonic: - return new MnemonicEncoder().encode(value, options) - default: - return Assert.isUnreachable(format) - } + const encoder = accountFormatToEncoder.get(format) + Assert.isNotUndefined(encoder, `Invalid account encoding format: ${format}`) + + return new encoder().encode(value, options) } export function decodeAccountImport( @@ -52,6 +51,13 @@ export function decodeAccountImport( ): AccountImport { const errors: DecodeFailed[] = [] + if (options.format) { + const encoder = accountFormatToEncoder.get(options.format) + Assert.isNotUndefined(encoder, `Invalid account decoding format: ${options.format}`) + + return new encoder().decode(value, options) + } + for (const encoder of ENCODER_VERSIONS) { try { const decoded = new encoder().decode(value, options) diff --git a/ironfish/src/wallet/exporter/encoder.ts b/ironfish/src/wallet/exporter/encoder.ts index b42d54c7fa..0158c9fd02 100644 --- a/ironfish/src/wallet/exporter/encoder.ts +++ b/ironfish/src/wallet/exporter/encoder.ts @@ -2,6 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { LanguageKey } from '../../utils' +import { AccountFormat } from './account' import { AccountImport } from './accountImport' export class DecodeInvalid extends Error {} @@ -25,6 +26,7 @@ export type AccountEncodingOptions = { export type AccountDecodingOptions = { name?: string + format?: AccountFormat } export type AccountEncoder = { From 6c2f87aa115be59e387c7f51b95579c0a16660c5 Mon Sep 17 00:00:00 2001 From: danield9tqh Date: Thu, 14 Nov 2024 16:40:57 -0500 Subject: [PATCH 21/68] Don't scan empty accounts that haven't reached account createdAt --- ironfish/src/wallet/scanner/walletScanner.ts | 71 ++++++++++++-------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/ironfish/src/wallet/scanner/walletScanner.ts b/ironfish/src/wallet/scanner/walletScanner.ts index feb56da343..2d4c7bc313 100644 --- a/ironfish/src/wallet/scanner/walletScanner.ts +++ b/ironfish/src/wallet/scanner/walletScanner.ts @@ -9,8 +9,8 @@ import type { HeadValue } from '../walletdb/headValue' import { Config } from '../../fileStores' import { Logger } from '../../logger' import { Mutex } from '../../mutex' -import { BlockHeader, Transaction } from '../../primitives' -import { BufferUtils, HashUtils } from '../../utils' +import { BlockHeader, GENESIS_BLOCK_SEQUENCE, Transaction } from '../../primitives' +import { HashUtils } from '../../utils' import { WorkerPool } from '../../workerPool' import { ChainProcessorWithTransactions } from './chainProcessorWithTransactions' import { BackgroundNoteDecryptor } from './noteDecryptor' @@ -32,8 +32,13 @@ export class WalletScanner { /** * A snapshot of the accounts that have `scanningEnabled` set to true. Used * to tell what accounts should be scanned, and from what block. + * If `scanFrom` is 'always-scan' then the account's head is the same as the + * WalletScanner's head, and blocks should always be added */ - private scanningAccounts = new Array<{ account: Account; scanFrom: HeadValue | null }>() + private scanningAccounts = new Array<{ + account: Account + scanFrom: { head: HeadValue | null } | 'always-scan' + }>() constructor(options: { logger: Logger @@ -177,34 +182,32 @@ export class WalletScanner { ) } - const connectOnlyAccounts = new Array() - const decryptAndConnectAccounts = new Array() - for (const candidate of this.scanningAccounts) { - if ( - !candidate.scanFrom || - BufferUtils.equalsNullable(candidate.scanFrom.hash, blockHeader.previousBlockHash) - ) { - candidate.scanFrom = null - - if ( - candidate.account.createdAt === null || - blockHeader.sequence >= candidate.account.createdAt.sequence - ) { - decryptAndConnectAccounts.push(candidate.account) - } else { - connectOnlyAccounts.push(candidate.account) + const { account, scanFrom } = candidate + + if (scanFrom === 'always-scan') { + continue + } + + if (scanFrom.head === null) { + if (account.createdAt === null && blockHeader.sequence === GENESIS_BLOCK_SEQUENCE) { + candidate.scanFrom = 'always-scan' + } + + if (account.createdAt && blockHeader.sequence >= account.createdAt.sequence) { + candidate.scanFrom = 'always-scan' } } - } - for (const account of connectOnlyAccounts) { - if (abort?.signal.aborted) { - return + if (scanFrom.head?.hash.equals(blockHeader.previousBlockHash)) { + candidate.scanFrom = 'always-scan' } - await this.wallet.connectBlockForAccount(account, blockHeader, [], false) } + const decryptAndConnectAccounts = this.scanningAccounts + .filter(({ scanFrom }) => scanFrom === 'always-scan') + .map(({ account }) => account) + if (abort?.signal.aborted) { return } @@ -230,7 +233,7 @@ export class WalletScanner { this.logger.debug(`AccountHead DEL: ${header.sequence} => ${Number(header.sequence) - 1}`) const accounts = (await this.getScanningAccountsWithHead()).filter(({ head }) => - BufferUtils.equalsNullable(head?.hash, header.hash), + head?.hash.equals(header.hash), ) for (const { account } of accounts) { @@ -241,8 +244,12 @@ export class WalletScanner { } for (const account of this.scanningAccounts) { - if (account.scanFrom && BufferUtils.equalsNullable(account.scanFrom.hash, header.hash)) { - account.scanFrom = null + if (account.scanFrom === 'always-scan') { + continue + } + + if (account.scanFrom?.head?.hash.equals(header.hash)) { + account.scanFrom = 'always-scan' } } } @@ -308,13 +315,18 @@ export class WalletScanner { */ private async refreshScanningAccounts(): Promise { this.scanningAccounts = (await this.getScanningAccountsWithHead()).map( - ({ account, head }) => ({ account, scanFrom: head }), + ({ account, head }) => ({ account, scanFrom: { head } }), ) } private getEarliestHead(): HeadValue | null { let earliestHead = null - for (const { scanFrom: head } of this.scanningAccounts) { + for (const { scanFrom } of this.scanningAccounts) { + if (scanFrom === 'always-scan') { + continue + } + + const { head } = scanFrom if (!head) { return null } @@ -322,6 +334,7 @@ export class WalletScanner { earliestHead = head } } + return earliestHead } } From 13a7675829fa36fdc2f0d5dfdf7735b8af655bbd Mon Sep 17 00:00:00 2001 From: andrea Date: Thu, 14 Nov 2024 14:22:07 -0800 Subject: [PATCH 22/68] WASM: expose the `Asset` and `AssetIdentifier` types --- ironfish-rust-wasm/src/assets.rs | 233 +++++++++++++++++++++++++++++ ironfish-rust-wasm/src/keys/mod.rs | 4 +- ironfish-rust-wasm/src/lib.rs | 1 + ironfish-rust/src/assets/asset.rs | 2 +- 4 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 ironfish-rust-wasm/src/assets.rs diff --git a/ironfish-rust-wasm/src/assets.rs b/ironfish-rust-wasm/src/assets.rs new file mode 100644 index 0000000000..b0606caeeb --- /dev/null +++ b/ironfish-rust-wasm/src/assets.rs @@ -0,0 +1,233 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::{ + errors::IronfishError, + keys::PublicAddress, + primitives::{ExtendedPoint, SubgroupPoint}, +}; +use ironfish::errors::IronfishErrorKind; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Asset(ironfish::assets::asset::Asset); + +#[wasm_bindgen] +impl Asset { + #[wasm_bindgen(constructor)] + pub fn deserialize(bytes: &[u8]) -> Result { + Ok(Self(ironfish::assets::asset::Asset::read(bytes)?)) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Vec { + let mut buf = Vec::new(); + self.0.write(&mut buf).expect("failed to serialize asset"); + buf + } + + #[wasm_bindgen(js_name = fromParts)] + pub fn from_parts( + creator: PublicAddress, + name: &str, + metadata: &str, + ) -> Result { + Ok(Self(ironfish::assets::asset::Asset::new( + creator.as_ref().to_owned(), + name, + metadata, + )?)) + } + + #[wasm_bindgen(js_name = fromPartsWithNonce)] + pub fn from_parts_with_nonce( + creator: PublicAddress, + name: &[u8], + metadata: &[u8], + nonce: u8, + ) -> Result { + let name = name + .try_into() + .map_err(|_| IronfishErrorKind::InvalidData)?; + let metadata = metadata + .try_into() + .map_err(|_| IronfishErrorKind::InvalidData)?; + Ok(Self(ironfish::assets::asset::Asset::new_with_nonce( + creator.as_ref().to_owned(), + name, + metadata, + nonce, + )?)) + } + + #[wasm_bindgen(getter)] + pub fn metadata(&self) -> Vec { + self.0.metadata().to_vec() + } + + #[wasm_bindgen(getter)] + pub fn name(&self) -> Vec { + self.0.name().to_vec() + } + + #[wasm_bindgen(getter)] + pub fn nonce(&self) -> u8 { + self.0.nonce() + } + + #[wasm_bindgen(getter)] + pub fn creator(&self) -> PublicAddress { + PublicAddress::deserialize(self.0.creator().as_slice()) + .expect("failed to deserialize public address") + } + + #[wasm_bindgen(getter)] + pub fn id(&self) -> AssetIdentifier { + self.0.id().to_owned().into() + } + + #[wasm_bindgen(getter, js_name = assetGenerator)] + pub fn asset_generator(&self) -> ExtendedPoint { + self.0.asset_generator().into() + } + + #[wasm_bindgen(getter, js_name = valueCommitmentGenerator)] + pub fn value_commitment_generator(&self) -> SubgroupPoint { + self.0.value_commitment_generator().into() + } +} + +impl From for Asset { + fn from(d: ironfish::assets::asset::Asset) -> Self { + Self(d) + } +} + +impl AsRef for Asset { + fn as_ref(&self) -> &ironfish::assets::asset::Asset { + &self.0 + } +} + +#[wasm_bindgen] +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct AssetIdentifier(ironfish::assets::asset_identifier::AssetIdentifier); + +#[wasm_bindgen] +impl AssetIdentifier { + #[wasm_bindgen(constructor)] + pub fn deserialize(bytes: &[u8]) -> Result { + Ok(Self( + ironfish::assets::asset_identifier::AssetIdentifier::read(bytes)?, + )) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Vec { + self.0.as_bytes().to_vec() + } + + #[wasm_bindgen(getter, js_name = assetGenerator)] + pub fn asset_generator(&self) -> ExtendedPoint { + self.0.asset_generator().into() + } + + #[wasm_bindgen(getter, js_name = valueCommitmentGenerator)] + pub fn value_commitment_generator(&self) -> SubgroupPoint { + self.0.value_commitment_generator().into() + } +} + +impl From for AssetIdentifier { + fn from(d: ironfish::assets::asset_identifier::AssetIdentifier) -> Self { + Self(d) + } +} + +impl AsRef for AssetIdentifier { + fn as_ref(&self) -> &ironfish::assets::asset_identifier::AssetIdentifier { + &self.0 + } +} + +#[cfg(test)] +mod tests { + mod asset { + use crate::{assets::Asset, keys::PublicAddress}; + use hex_literal::hex; + use wasm_bindgen_test::wasm_bindgen_test; + + fn test_address() -> PublicAddress { + PublicAddress::deserialize( + hex!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0").as_slice(), + ) + .unwrap() + } + + fn test_asset() -> Asset { + let asset = Asset::from_parts(test_address(), "name", "meta").unwrap(); + + assert_eq!(asset.creator(), test_address()); + assert_eq!( + asset.name(), + b"name\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" + ); + assert_eq!( + asset.metadata(), + b"meta\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\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\0\0\0\0\0\0\0\0\0\0\0\ + \0\0\0\0\0\0\0\0\0\0\0\0\0" + ); + assert_eq!( + asset.id().serialize(), + hex!("2b845f8f97b90d2279bf502eb3ebdf71bf47460b083ca926421b0c7ee68ec816") + ); + + asset + } + + #[test] + #[wasm_bindgen_test] + fn serialize_deserialize_roundtrip() { + let asset = test_asset(); + + let serialization = asset.serialize(); + let deserialized = Asset::deserialize(&serialization[..]).unwrap(); + + assert_eq!(asset, deserialized); + assert_eq!(serialization, deserialized.serialize()); + } + + #[test] + #[wasm_bindgen_test] + fn from_parts_with_nonce() { + let asset = Asset::from_parts_with_nonce( + test_address(), + b"name\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", + b"meta\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\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\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, + ) + .unwrap(); + assert_eq!(asset, test_asset()); + } + } + + mod asset_identifier { + use crate::assets::AssetIdentifier; + use hex_literal::hex; + use wasm_bindgen_test::wasm_bindgen_test; + + #[test] + #[wasm_bindgen_test] + fn serialize_deserialize_roundtrip() { + let serialization = + hex!("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb1"); + let id = AssetIdentifier::deserialize(&serialization[..]).unwrap(); + assert_eq!(id.serialize(), serialization); + } + } +} diff --git a/ironfish-rust-wasm/src/keys/mod.rs b/ironfish-rust-wasm/src/keys/mod.rs index 7fda23142a..54b4364b7b 100644 --- a/ironfish-rust-wasm/src/keys/mod.rs +++ b/ironfish-rust-wasm/src/keys/mod.rs @@ -2,4 +2,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -pub mod public_address; +mod public_address; + +pub use public_address::PublicAddress; diff --git a/ironfish-rust-wasm/src/lib.rs b/ironfish-rust-wasm/src/lib.rs index 81ab379e5e..5c6ad85a3d 100644 --- a/ironfish-rust-wasm/src/lib.rs +++ b/ironfish-rust-wasm/src/lib.rs @@ -13,6 +13,7 @@ // The getrandom dependency exists only to ensure that the `js` feature is enabled use getrandom as _; +pub mod assets; pub mod errors; pub mod keys; pub mod primitives; diff --git a/ironfish-rust/src/assets/asset.rs b/ironfish-rust/src/assets/asset.rs index 81405df2fa..6a3ddf33bd 100644 --- a/ironfish-rust/src/assets/asset.rs +++ b/ironfish-rust/src/assets/asset.rs @@ -21,7 +21,7 @@ pub const ID_LENGTH: usize = ASSET_ID_LENGTH; /// Describes all the fields necessary for creating and transacting with an /// asset on the Iron Fish network -#[derive(Clone, Copy, PartialEq, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct Asset { /// Name of the asset pub(crate) name: [u8; NAME_LENGTH], From f310cce3cf9cb4694ac59e04071d26cd436c3ba5 Mon Sep 17 00:00:00 2001 From: andrea Date: Thu, 14 Nov 2024 14:28:27 -0800 Subject: [PATCH 23/68] WASM: expose the `MerkleNote` and `MerkleNoteHash` types --- ironfish-rust-wasm/src/lib.rs | 1 + ironfish-rust-wasm/src/merkle_note.rs | 118 ++++++++++++++++++++++++++ ironfish-rust/src/merkle_note.rs | 2 + 3 files changed, 121 insertions(+) create mode 100644 ironfish-rust-wasm/src/merkle_note.rs diff --git a/ironfish-rust-wasm/src/lib.rs b/ironfish-rust-wasm/src/lib.rs index 5c6ad85a3d..d92f15465a 100644 --- a/ironfish-rust-wasm/src/lib.rs +++ b/ironfish-rust-wasm/src/lib.rs @@ -16,6 +16,7 @@ use getrandom as _; pub mod assets; pub mod errors; pub mod keys; +pub mod merkle_note; pub mod primitives; #[cfg(test)] diff --git a/ironfish-rust-wasm/src/merkle_note.rs b/ironfish-rust-wasm/src/merkle_note.rs new file mode 100644 index 0000000000..a02d1ec444 --- /dev/null +++ b/ironfish-rust-wasm/src/merkle_note.rs @@ -0,0 +1,118 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::{errors::IronfishError, primitives::Scalar}; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct MerkleNote(ironfish::MerkleNote); + +#[wasm_bindgen] +impl MerkleNote { + #[wasm_bindgen(constructor)] + pub fn deserialize(bytes: &[u8]) -> Result { + Ok(Self(ironfish::MerkleNote::read(bytes)?)) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Vec { + let mut buf = Vec::new(); + self.0 + .write(&mut buf) + .expect("failed to serialize merkle note"); + buf + } + + #[wasm_bindgen(getter, js_name = merkleHash)] + pub fn merkle_hash(&self) -> MerkleNoteHash { + self.0.merkle_hash().into() + } +} + +impl From for MerkleNote { + fn from(d: ironfish::MerkleNote) -> Self { + Self(d) + } +} + +impl AsRef for MerkleNote { + fn as_ref(&self) -> &ironfish::MerkleNote { + &self.0 + } +} + +#[wasm_bindgen] +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct MerkleNoteHash(ironfish::MerkleNoteHash); + +#[wasm_bindgen] +impl MerkleNoteHash { + #[wasm_bindgen(constructor)] + pub fn deserialize(bytes: &[u8]) -> Result { + Ok(Self(ironfish::MerkleNoteHash::read(bytes)?)) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Vec { + let mut buf = Vec::new(); + self.0 + .write(&mut buf) + .expect("failed to serialize merkle note hash"); + buf + } + + #[wasm_bindgen(js_name = fromValue)] + pub fn from_value(value: Scalar) -> Self { + ironfish::MerkleNoteHash(value.as_ref().to_owned()).into() + } + + #[wasm_bindgen(getter)] + pub fn value(&self) -> Scalar { + self.0 .0.into() + } + + #[wasm_bindgen(js_name = combineHash)] + pub fn combine_hash(depth: usize, left: &Self, right: &Self) -> Self { + let hash = ironfish::MerkleNoteHash::combine_hash(depth, &left.0 .0, &right.0 .0); + ironfish::MerkleNoteHash(hash).into() + } +} + +impl From for MerkleNoteHash { + fn from(d: ironfish::MerkleNoteHash) -> Self { + Self(d) + } +} + +impl AsRef for MerkleNoteHash { + fn as_ref(&self) -> &ironfish::MerkleNoteHash { + &self.0 + } +} + +#[cfg(test)] +mod tests { + use crate::merkle_note::MerkleNoteHash; + use hex_literal::hex; + use wasm_bindgen_test::wasm_bindgen_test; + + #[test] + #[wasm_bindgen_test] + fn combine_hash() { + let a = MerkleNoteHash::deserialize( + hex!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa00").as_slice(), + ) + .unwrap(); + let b = MerkleNoteHash::deserialize( + hex!("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb00").as_slice(), + ) + .unwrap(); + let c = MerkleNoteHash::combine_hash(10, &a, &b); + assert_eq!( + c.serialize(), + hex!("65fa868a24f39bead19143c23b7c37c6966bec5cf5e60269cb7964d407fe3d47") + ); + } +} diff --git a/ironfish-rust/src/merkle_note.rs b/ironfish-rust/src/merkle_note.rs index a9aec78617..b038508e84 100644 --- a/ironfish-rust/src/merkle_note.rs +++ b/ironfish-rust/src/merkle_note.rs @@ -73,6 +73,8 @@ impl PartialEq for MerkleNote { } } +impl Eq for MerkleNote {} + impl MerkleNote { pub fn new( outgoing_view_key: &OutgoingViewKey, From 71cae382afed6f864f6612997a0016db4014d1f2 Mon Sep 17 00:00:00 2001 From: andrea Date: Thu, 14 Nov 2024 14:58:27 -0800 Subject: [PATCH 24/68] WASM: expose the transaction description types `SpendDescription`, `OutputDescription`, `MintDescription`, and `BurnDescription` --- ironfish-rust-wasm/src/lib.rs | 1 + ironfish-rust-wasm/src/transaction/burns.rs | 51 +++++++++++ ironfish-rust-wasm/src/transaction/mints.rs | 91 +++++++++++++++++++ ironfish-rust-wasm/src/transaction/mod.rs | 13 +++ ironfish-rust-wasm/src/transaction/outputs.rs | 62 +++++++++++++ ironfish-rust-wasm/src/transaction/spends.rs | 86 ++++++++++++++++++ ironfish-rust/src/transaction/burns.rs | 2 +- ironfish-rust/src/transaction/outputs.rs | 2 +- 8 files changed, 306 insertions(+), 2 deletions(-) create mode 100644 ironfish-rust-wasm/src/transaction/burns.rs create mode 100644 ironfish-rust-wasm/src/transaction/mints.rs create mode 100644 ironfish-rust-wasm/src/transaction/mod.rs create mode 100644 ironfish-rust-wasm/src/transaction/outputs.rs create mode 100644 ironfish-rust-wasm/src/transaction/spends.rs diff --git a/ironfish-rust-wasm/src/lib.rs b/ironfish-rust-wasm/src/lib.rs index d92f15465a..3048349c14 100644 --- a/ironfish-rust-wasm/src/lib.rs +++ b/ironfish-rust-wasm/src/lib.rs @@ -18,6 +18,7 @@ pub mod errors; pub mod keys; pub mod merkle_note; pub mod primitives; +pub mod transaction; #[cfg(test)] mod tests { diff --git a/ironfish-rust-wasm/src/transaction/burns.rs b/ironfish-rust-wasm/src/transaction/burns.rs new file mode 100644 index 0000000000..7224e4b9db --- /dev/null +++ b/ironfish-rust-wasm/src/transaction/burns.rs @@ -0,0 +1,51 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::{assets::AssetIdentifier, errors::IronfishError}; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct BurnDescription(ironfish::transaction::burns::BurnDescription); + +#[wasm_bindgen] +impl BurnDescription { + #[wasm_bindgen(constructor)] + pub fn deserialize(bytes: &[u8]) -> Result { + Ok(Self(ironfish::transaction::burns::BurnDescription::read( + bytes, + )?)) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Vec { + let mut buf = Vec::new(); + self.0 + .write(&mut buf) + .expect("failed to serialize mint description"); + buf + } + + #[wasm_bindgen(getter, js_name = assetId)] + pub fn asset_id(&self) -> AssetIdentifier { + self.0.asset_id.into() + } + + #[wasm_bindgen(getter)] + pub fn value(&self) -> u64 { + self.0.value + } +} + +impl From for BurnDescription { + fn from(d: ironfish::transaction::burns::BurnDescription) -> Self { + Self(d) + } +} + +impl AsRef for BurnDescription { + fn as_ref(&self) -> &ironfish::transaction::burns::BurnDescription { + &self.0 + } +} diff --git a/ironfish-rust-wasm/src/transaction/mints.rs b/ironfish-rust-wasm/src/transaction/mints.rs new file mode 100644 index 0000000000..d9975c3555 --- /dev/null +++ b/ironfish-rust-wasm/src/transaction/mints.rs @@ -0,0 +1,91 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::{ + assets::Asset, + errors::IronfishError, + keys::PublicAddress, + primitives::{PublicKey, Scalar}, +}; +use ironfish::{errors::IronfishErrorKind, transaction::TransactionVersion}; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +#[derive(Clone, Debug)] +pub struct MintDescription(ironfish::transaction::mints::MintDescription); + +#[wasm_bindgen] +impl MintDescription { + #[wasm_bindgen(constructor)] + pub fn deserialize(bytes: &[u8]) -> Result { + Ok(Self(ironfish::transaction::mints::MintDescription::read( + bytes, + TransactionVersion::V1, + )?)) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Vec { + let mut buf = Vec::new(); + self.0 + .write(&mut buf, TransactionVersion::V1) + .expect("failed to serialize mint description"); + buf + } + + #[wasm_bindgen(getter)] + pub fn assets(&self) -> Asset { + self.0.asset.into() + } + + #[wasm_bindgen(getter)] + pub fn value(&self) -> u64 { + self.0.value + } + + #[wasm_bindgen(getter)] + pub fn owner(&self) -> PublicAddress { + self.0.owner.into() + } + + #[wasm_bindgen(js_name = verifySignature)] + pub fn verify_signature( + &self, + signature: &[u8], + randomized_public_key: &PublicKey, + ) -> Result<(), IronfishError> { + let signature = signature + .try_into() + .map_err(|_| IronfishErrorKind::InvalidSignature)?; + self.0 + .verify_signature(signature, randomized_public_key.as_ref()) + .map_err(|e| e.into()) + } + + #[wasm_bindgen(js_name = partialVerify)] + pub fn partial_verify(&self) -> Result<(), IronfishError> { + self.0.partial_verify().map_err(|e| e.into()) + } + + #[wasm_bindgen(js_name = publicInputs)] + pub fn public_inputs(&self, randomized_public_key: &PublicKey) -> Vec { + self.0 + .public_inputs(randomized_public_key.as_ref()) + .into_iter() + .map(Scalar::from) + .collect() + } +} + +impl From for MintDescription { + fn from(d: ironfish::transaction::mints::MintDescription) -> Self { + Self(d) + } +} + +impl AsRef for MintDescription { + fn as_ref(&self) -> &ironfish::transaction::mints::MintDescription { + &self.0 + } +} diff --git a/ironfish-rust-wasm/src/transaction/mod.rs b/ironfish-rust-wasm/src/transaction/mod.rs new file mode 100644 index 0000000000..3d0fd2a72d --- /dev/null +++ b/ironfish-rust-wasm/src/transaction/mod.rs @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +mod burns; +mod mints; +mod outputs; +mod spends; + +pub use burns::BurnDescription; +pub use mints::MintDescription; +pub use outputs::OutputDescription; +pub use spends::SpendDescription; diff --git a/ironfish-rust-wasm/src/transaction/outputs.rs b/ironfish-rust-wasm/src/transaction/outputs.rs new file mode 100644 index 0000000000..5f63b72ad1 --- /dev/null +++ b/ironfish-rust-wasm/src/transaction/outputs.rs @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::{ + errors::IronfishError, + merkle_note::MerkleNote, + primitives::{PublicKey, Scalar}, +}; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +#[derive(Clone, PartialEq, Debug)] +pub struct OutputDescription(ironfish::OutputDescription); + +#[wasm_bindgen] +impl OutputDescription { + #[wasm_bindgen(constructor)] + pub fn deserialize(bytes: &[u8]) -> Result { + Ok(Self(ironfish::OutputDescription::read(bytes)?)) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Vec { + let mut buf = Vec::new(); + self.0 + .write(&mut buf) + .expect("failed to serialize output description"); + buf + } + + #[wasm_bindgen(js_name = partialVerify)] + pub fn partial_verify(&self) -> Result<(), IronfishError> { + self.0.partial_verify().map_err(|e| e.into()) + } + + #[wasm_bindgen(js_name = publicInputs)] + pub fn public_inputs(&self, randomized_public_key: &PublicKey) -> Vec { + self.0 + .public_inputs(randomized_public_key.as_ref()) + .into_iter() + .map(Scalar::from) + .collect() + } + + #[wasm_bindgen(getter, js_name = merkleNote)] + pub fn merkle_note(&self) -> MerkleNote { + self.0.merkle_note().into() + } +} + +impl From for OutputDescription { + fn from(d: ironfish::OutputDescription) -> Self { + Self(d) + } +} + +impl AsRef for OutputDescription { + fn as_ref(&self) -> &ironfish::OutputDescription { + &self.0 + } +} diff --git a/ironfish-rust-wasm/src/transaction/spends.rs b/ironfish-rust-wasm/src/transaction/spends.rs new file mode 100644 index 0000000000..6ce969b2fc --- /dev/null +++ b/ironfish-rust-wasm/src/transaction/spends.rs @@ -0,0 +1,86 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::{ + errors::IronfishError, + primitives::{Nullifier, PublicKey, Scalar}, +}; +use ironfish::errors::IronfishErrorKind; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +#[derive(Clone, Debug)] +pub struct SpendDescription(ironfish::SpendDescription); + +#[wasm_bindgen] +impl SpendDescription { + #[wasm_bindgen(constructor)] + pub fn deserialize(bytes: &[u8]) -> Result { + Ok(Self(ironfish::SpendDescription::read(bytes)?)) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Vec { + let mut buf = Vec::new(); + self.0 + .write(&mut buf) + .expect("failed to serialize spend description"); + buf + } + + #[wasm_bindgen(getter)] + pub fn nullifier(&self) -> Nullifier { + self.0.nullifier().into() + } + + #[wasm_bindgen(getter, js_name = rootHash)] + pub fn root_hash(&self) -> Scalar { + self.0.root_hash().into() + } + + #[wasm_bindgen(getter, js_name = treeSize)] + pub fn tree_size(&self) -> u32 { + self.0.tree_size() + } + + #[wasm_bindgen(js_name = verifySignature)] + pub fn verify_signature( + &self, + signature: &[u8], + randomized_public_key: &PublicKey, + ) -> Result<(), IronfishError> { + let signature = signature + .try_into() + .map_err(|_| IronfishErrorKind::InvalidSignature)?; + self.0 + .verify_signature(signature, randomized_public_key.as_ref()) + .map_err(|e| e.into()) + } + + #[wasm_bindgen(js_name = partialVerify)] + pub fn partial_verify(&self) -> Result<(), IronfishError> { + self.0.partial_verify().map_err(|e| e.into()) + } + + #[wasm_bindgen(js_name = publicInputs)] + pub fn public_inputs(&self, randomized_public_key: &PublicKey) -> Vec { + self.0 + .public_inputs(randomized_public_key.as_ref()) + .into_iter() + .map(Scalar::from) + .collect() + } +} + +impl From for SpendDescription { + fn from(d: ironfish::SpendDescription) -> Self { + Self(d) + } +} + +impl AsRef for SpendDescription { + fn as_ref(&self) -> &ironfish::SpendDescription { + &self.0 + } +} diff --git a/ironfish-rust/src/transaction/burns.rs b/ironfish-rust/src/transaction/burns.rs index 5595347761..74757b4f19 100644 --- a/ironfish-rust/src/transaction/burns.rs +++ b/ironfish-rust/src/transaction/burns.rs @@ -32,7 +32,7 @@ impl BurnBuilder { /// This description represents an action to decrease the supply of an existing /// asset on Iron Fish -#[derive(Clone, Debug)] +#[derive(Clone, PartialEq, Eq, Debug)] pub struct BurnDescription { /// Identifier for the Asset which is being burned pub asset_id: AssetIdentifier, diff --git a/ironfish-rust/src/transaction/outputs.rs b/ironfish-rust/src/transaction/outputs.rs index 008c8a9cb3..26402f6789 100644 --- a/ironfish-rust/src/transaction/outputs.rs +++ b/ironfish-rust/src/transaction/outputs.rs @@ -131,7 +131,7 @@ impl OutputBuilder { /// /// This is the variation of an Output that gets serialized to bytes and can /// be loaded from bytes. -#[derive(Clone, Debug)] +#[derive(Clone, PartialEq, Debug)] pub struct OutputDescription { /// Proof that the output circuit was valid and successful pub(crate) proof: groth16::Proof, From dd1c45780bc6e81f5e240048328f24ff362efa27 Mon Sep 17 00:00:00 2001 From: andrea Date: Thu, 14 Nov 2024 15:05:59 -0800 Subject: [PATCH 25/68] WASM: expose the `Transaction` type --- Cargo.lock | 24 +++- ironfish-rust-wasm/Cargo.toml | 1 + ironfish-rust-wasm/src/lib.rs | 4 +- ironfish-rust-wasm/src/transaction/mod.rs | 166 ++++++++++++++++++++++ supply-chain/audits.toml | 5 + supply-chain/imports.lock | 43 ++++-- 6 files changed, 223 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a8fdff21ee..ee028eb588 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1652,6 +1652,7 @@ dependencies = [ "ironfish-jubjub", "ironfish_zkp", "rand", + "rayon", "wasm-bindgen", "wasm-bindgen-test", ] @@ -2311,24 +2312,24 @@ dependencies = [ [[package]] name = "rayon" -version = "1.6.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", + "wasm_sync", ] [[package]] name = "rayon-core" -version = "1.10.1" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ - "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "num_cpus", + "wasm_sync", ] [[package]] @@ -3188,6 +3189,17 @@ dependencies = [ "syn 2.0.77", ] +[[package]] +name = "wasm_sync" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff360cade7fec41ff0e9d2cda57fe58258c5f16def0e21302394659e6bbb0ea" +dependencies = [ + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.61" diff --git a/ironfish-rust-wasm/Cargo.toml b/ironfish-rust-wasm/Cargo.toml index 32d317331c..e6e51b7de5 100644 --- a/ironfish-rust-wasm/Cargo.toml +++ b/ironfish-rust-wasm/Cargo.toml @@ -21,6 +21,7 @@ ironfish = { version = "0.3.0", path = "../ironfish-rust" } ironfish-jubjub = "0.1.0" ironfish_zkp = { version = "0.2.0", path = "../ironfish-zkp" } rand = "0.8.5" +rayon = { version = "1.8.1", features = ["web_spin_lock"] } # need to explicitly enable the `web_spin_lock` in order to run in a browser wasm-bindgen = "0.2.95" [dev-dependencies] diff --git a/ironfish-rust-wasm/src/lib.rs b/ironfish-rust-wasm/src/lib.rs index 3048349c14..0e4865b20c 100644 --- a/ironfish-rust-wasm/src/lib.rs +++ b/ironfish-rust-wasm/src/lib.rs @@ -10,8 +10,10 @@ #![warn(unused_macro_rules)] #![warn(unused_qualifications)] -// The getrandom dependency exists only to ensure that the `js` feature is enabled +// These dependencies exist only to ensure that some browser-specific features are enabled, and are +// not actually used in our code use getrandom as _; +use rayon as _; pub mod assets; pub mod errors; diff --git a/ironfish-rust-wasm/src/transaction/mod.rs b/ironfish-rust-wasm/src/transaction/mod.rs index 3d0fd2a72d..18d2d0b65a 100644 --- a/ironfish-rust-wasm/src/transaction/mod.rs +++ b/ironfish-rust-wasm/src/transaction/mod.rs @@ -7,7 +7,173 @@ mod mints; mod outputs; mod spends; +use crate::{errors::IronfishError, primitives::PublicKey}; +use wasm_bindgen::prelude::*; + pub use burns::BurnDescription; pub use mints::MintDescription; pub use outputs::OutputDescription; pub use spends::SpendDescription; + +#[wasm_bindgen] +#[derive(Clone, Debug)] +pub struct Transaction(ironfish::Transaction); + +#[wasm_bindgen] +impl Transaction { + #[wasm_bindgen(constructor)] + pub fn deserialize(bytes: &[u8]) -> Result { + Ok(Self(ironfish::Transaction::read(bytes)?)) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Vec { + let mut buf = Vec::new(); + self.0 + .write(&mut buf) + .expect("failed to serialize transaction"); + buf + } + + #[wasm_bindgen(getter)] + pub fn fee(&self) -> i64 { + self.0.fee() + } + + #[wasm_bindgen(getter)] + pub fn expiration(&self) -> u32 { + self.0.expiration() + } + + #[wasm_bindgen(getter, js_name = randomizedPublicKey)] + pub fn randomized_public_key(&self) -> PublicKey { + self.0.randomized_public_key().clone().into() + } + + #[wasm_bindgen(getter)] + pub fn spends(&self) -> Vec { + self.0 + .spends() + .iter() + .cloned() + .map(SpendDescription::from) + .collect() + } + + #[wasm_bindgen(getter)] + pub fn outputs(&self) -> Vec { + self.0 + .outputs() + .iter() + .cloned() + .map(OutputDescription::from) + .collect() + } + + #[wasm_bindgen(getter)] + pub fn mints(&self) -> Vec { + self.0 + .mints() + .iter() + .cloned() + .map(MintDescription::from) + .collect() + } + + #[wasm_bindgen(getter)] + pub fn burns(&self) -> Vec { + self.0 + .burns() + .iter() + .cloned() + .map(BurnDescription::from) + .collect() + } + + #[wasm_bindgen(js_name = transactionSignatureHash)] + pub fn transaction_signature_hash(&self) -> Result, IronfishError> { + self.0 + .transaction_signature_hash() + .map(|hash| hash.to_vec()) + .map_err(|err| err.into()) + } +} + +impl From for Transaction { + fn from(t: ironfish::Transaction) -> Self { + Self(t) + } +} + +impl AsRef for Transaction { + fn as_ref(&self) -> &ironfish::Transaction { + &self.0 + } +} + +#[cfg(test)] +mod tests { + use super::Transaction; + use hex_literal::hex; + use wasm_bindgen_test::wasm_bindgen_test; + + // Transaction copied from one of the fixtures in the `ironfish` NodeJS package + const TEST_TRANSACTION_BYTES: [u8; 661] = hex!( + "010000000000000000010000000000000000000000000000000000000000000000006cca88ffffffff00000000\ + 5e0c3088ca0767097b456c190416cc9ec82a296d5500876ce394218cde263c3e987762affaec55596ab06f7d7f4\ + 6dd949762f7705fc4978e64842242c59ff99e4dab95eaa46384f3e2e2705732db4d458bb3146e28620273558cc6\ + e31d2c4f5127d0e787468e5a56ca0d0a30b0434e22b2438e9d026f63be9dac46500671cb67197dd654f3e8fe68a\ + e3abca0fcc50009a89751a2f179c7470888f8a107492606cd30103a72870af2f87adf8210a2cb3d8d73f1150d99\ + e0dfbbb9daaba03e7daf24e26dd468b572b3dded502311ab83c17b87eb3db1a1bb8f7a3c5af0d40035d11b15a3c\ + e6f235138b2ef5f9853a01d61b9a9e549290618fbd697330380b9f0712e1d926b454b7a4cb7ddad47220bbaae68\ + 34ab67e0b42d6dd13b70d5ffb49c7067da8db3832b9f444990950bc25d7741a7ccb236b6a2eb346cfe8e02a34e6\ + b2f2993889cd256f9eb4cd2eebdc2bfdb9805e60730c92581fa4fea090f7baafcb8bf18a233ab150764bb76285b\ + 22b0f16831b8a3f47b4d41e96ab00a30e86994b4fb7b5a49d3ef8d37cce7035e741d1eacf649356f61169b06490\ + d702e34033d35f446864085f51315048de2e827746928492ef8cdec5c4faadf5bc82877462291118b643f44da99\ + e82335717cf1da9f149cc556100c4bd76c49726f6e2046697368206e6f746520656e6372797074696f6e206d696\ + e6572206b6579303030303030303030303030303030303030303030303030303030303030303030303030303030\ + 303030303030be04297828e5177a3ac901e89a7224a7e8f760a4377fc46b46384f3ef90a0c38e95fa386d2306f9\ + 8aeddeb6532ef022fb13e3b695d6df812587cd5eda684e502" + ); + + #[test] + #[wasm_bindgen_test] + fn deserialize() { + let tx = Transaction::deserialize(TEST_TRANSACTION_BYTES.as_slice()) + .expect("reading transaction should have succeeded"); + + assert_eq!(tx.fee(), -2_000_000_000); + assert_eq!(tx.expiration(), 0); + assert_eq!( + tx.randomized_public_key().serialize(), + hex!("5e0c3088ca0767097b456c190416cc9ec82a296d5500876ce394218cde263c3e") + ); + + assert_eq!(tx.spends().len(), 0); + assert_eq!(tx.outputs().len(), 1); + assert_eq!(tx.mints().len(), 0); + assert_eq!(tx.burns().len(), 0); + + let [output] = &tx.outputs()[..] else { + panic!("expected exactly one output") + }; + output + .partial_verify() + .expect("output verification should have succeeded"); + assert_eq!( + output.merkle_note().merkle_hash().serialize(), + hex!("2e1d926b454b7a4cb7ddad47220bbaae6834ab67e0b42d6dd13b70d5ffb49c70") + ); + + assert_eq!( + tx.transaction_signature_hash().unwrap(), + hex!("2ab1daec6bbb764e4247d3d82f1aa6da9eb71b98ac9e0dfc61e1d8aec487c9d2") + ); + } + + #[test] + #[wasm_bindgen_test] + fn deserialize_failure() { + Transaction::deserialize(b"abc").expect_err("reading transaction should have failed"); + } +} diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index 99df836d8c..e6a5c2b03c 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -168,6 +168,11 @@ who = "andrea " criteria = "safe-to-run" version = "0.3.45" +[[audits.wasm_sync]] +who = "andrea " +criteria = "safe-to-deploy" +version = "0.1.2" + [[trusted.reddsa]] criteria = "safe-to-deploy" user-id = 6289 # Jack Grigg (str4d) diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index 9bb1cb54b6..7cee3625ab 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -718,6 +718,36 @@ who = "David Cook " criteria = "safe-to-deploy" version = "0.6.3" +[[audits.isrg.audits.rayon]] +who = "Brandon Pitman " +criteria = "safe-to-deploy" +delta = "1.6.1 -> 1.7.0" + +[[audits.isrg.audits.rayon]] +who = "David Cook " +criteria = "safe-to-deploy" +delta = "1.7.0 -> 1.8.0" + +[[audits.isrg.audits.rayon]] +who = "Ameer Ghani " +criteria = "safe-to-deploy" +delta = "1.8.0 -> 1.8.1" + +[[audits.isrg.audits.rayon]] +who = "Brandon Pitman " +criteria = "safe-to-deploy" +delta = "1.8.1 -> 1.9.0" + +[[audits.isrg.audits.rayon]] +who = "Brandon Pitman " +criteria = "safe-to-deploy" +delta = "1.9.0 -> 1.10.0" + +[[audits.isrg.audits.rayon-core]] +who = "Ameer Ghani " +criteria = "safe-to-deploy" +version = "1.12.1" + [[audits.isrg.audits.sha2]] who = "David Cook " criteria = "safe-to-deploy" @@ -1009,19 +1039,6 @@ criteria = "safe-to-deploy" delta = "1.5.3 -> 1.6.1" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" -[[audits.mozilla.audits.rayon-core]] -who = "Josh Stone " -criteria = "safe-to-deploy" -version = "1.9.3" -notes = "All code written or reviewed by Josh Stone or Niko Matsakis." -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - -[[audits.mozilla.audits.rayon-core]] -who = "Mike Hommey " -criteria = "safe-to-deploy" -delta = "1.9.3 -> 1.10.1" -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - [[audits.mozilla.audits.redox_syscall]] who = "Jan-Erik Rediger " criteria = "safe-to-deploy" From af0da1f9bed92b0c57c874f3b140bc7b8727d4fc Mon Sep 17 00:00:00 2001 From: Rahul Patni Date: Mon, 18 Nov 2024 14:07:07 -0800 Subject: [PATCH 26/68] adds ledger flag to burn command (#5660) Allows the user to use a ledger device to sign the burn transaction. --- ironfish-cli/src/commands/wallet/burn.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ironfish-cli/src/commands/wallet/burn.ts b/ironfish-cli/src/commands/wallet/burn.ts index 7ebc9d8f7b..d6288e60f7 100644 --- a/ironfish-cli/src/commands/wallet/burn.ts +++ b/ironfish-cli/src/commands/wallet/burn.ts @@ -87,6 +87,10 @@ This will destroy tokens and decrease supply for a given asset.` default: false, description: 'Wait for the transaction to be confirmed', }), + ledger: Flags.boolean({ + default: false, + description: 'Burn a transaction using a Ledger device', + }), } async start(): Promise { @@ -221,6 +225,18 @@ This will destroy tokens and decrease supply for a given asset.` await this.confirm(assetData, amount, raw.fee, account, flags.confirm) + if (flags.ledger) { + await ui.sendTransactionWithLedger( + client, + raw, + account, + flags.watch, + flags.confirm, + this.logger, + ) + this.exit(0) + } + ux.action.start('Sending the transaction') const response = await client.wallet.postTransaction({ From b801bf622815cff8f57adf14723b1217c2b10fd7 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Mon, 18 Nov 2024 16:36:36 -0800 Subject: [PATCH 27/68] handles expert mode required error in ledger ui (#5661) as of v1.1.0 of the Ironfish Ledger app expert mode must be required to review a transaction that sends custom assets defines the error code for the new export mode error stops polling the Ledger app when an expert mode error is thrown so that the user can navigate to the expert mode screen and enable expert mode before continuing --- ironfish-cli/src/ledger/ledger.ts | 4 ++++ ironfish-cli/src/ui/ledger.ts | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/ironfish-cli/src/ledger/ledger.ts b/ironfish-cli/src/ledger/ledger.ts index ef52631a62..4743c84b05 100644 --- a/ironfish-cli/src/ledger/ledger.ts +++ b/ironfish-cli/src/ledger/ledger.ts @@ -24,6 +24,7 @@ export const IronfishLedgerStatusCodes = { UNKNOWN_TRANSPORT_ERROR: 0xffff, INVALID_TX_HASH: 0xb025, PANIC: 0xe000, + EXPERT_MODE_REQUIRED: 0x6984, } export class Ledger { @@ -71,6 +72,8 @@ export class Ledger { throw new LedgerPanicError() } else if (error.returnCode === IronfishLedgerStatusCodes.GP_AUTH_FAILED) { throw new LedgerGPAuthFailed() + } else if (error.returnCode === IronfishLedgerStatusCodes.EXPERT_MODE_REQUIRED) { + throw new LedgerExpertModeError() } else if ( [ IronfishLedgerStatusCodes.COMMAND_NOT_ALLOWED, @@ -183,3 +186,4 @@ export class LedgerAppNotOpen extends LedgerError {} export class LedgerActionRejected extends LedgerError {} export class LedgerInvalidTxHash extends LedgerError {} export class LedgerPanicError extends LedgerError {} +export class LedgerExpertModeError extends LedgerError {} diff --git a/ironfish-cli/src/ui/ledger.ts b/ironfish-cli/src/ui/ledger.ts index de6f79365d..b7e5fc1796 100644 --- a/ironfish-cli/src/ui/ledger.ts +++ b/ironfish-cli/src/ui/ledger.ts @@ -19,6 +19,7 @@ import { LedgerClaNotSupportedError, LedgerConnectError, LedgerDeviceLockedError, + LedgerExpertModeError, LedgerGPAuthFailed, LedgerPanicError, LedgerPortIsBusyError, @@ -81,6 +82,24 @@ export async function ledger({ ux.exit(0) } + if (!wasRunning) { + ux.action.start(message) + } + } else if (e instanceof LedgerExpertModeError) { + // Polling the device may prevent the user from navigating to the + // expert mode screen in the app and enabling expert mode. + ux.action.stop('Expert mode required to send custom assets') + + const confirmed = await ui.confirmList( + 'Enable expert mode and press enter to retry:', + 'Retry', + ) + + if (!confirmed) { + ux.stdout('Operation aborted.') + ux.exit(0) + } + if (!wasRunning) { ux.action.start(message) } From f16a7c340c643cc17329a3b6877351f98e3a86ed Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Tue, 19 Nov 2024 10:48:36 -0800 Subject: [PATCH 28/68] passes wallet passphrase from CLI to migrator (#5631) * passes wallet passphrase from CLI to migrator adds an error type, EncryptedWalletMigrationError, and catches errors of that type in the three commands that run migrations: 'migrations:start', 'migrations:revert', and 'start' prompts user to enter wallet passphrase and passes passphrase through to migrator to support passing the passphrase down to the migrator in the 'start' command we pass the passphrase through 'NodeUtils.waitForOpen' and the the node's 'openDB' method supports implementing database migrations that handle encrypted wallet logic. for example, throwing the EncryptedWalletMigrationError on an encrypted account and using the wallet passphrase passed to 'migrate' to decrypt and re-encrypt the wallet * removes passphrase prompt from start command displays error message to user instead removes logic plumbing passphrase through openDB to migrator --- .../src/commands/migrations/revert.ts | 37 ++++++++++++++++++- ironfish-cli/src/commands/migrations/start.ts | 31 +++++++++++++++- ironfish-cli/src/commands/start.ts | 22 ++++++++++- ironfish/src/migrations/errors.ts | 7 ++++ ironfish/src/migrations/index.ts | 1 + ironfish/src/migrations/migration.ts | 2 + ironfish/src/migrations/migrator.ts | 21 +++++++++-- ironfish/src/node.ts | 5 ++- ironfish/src/wallet/index.ts | 1 + 9 files changed, 118 insertions(+), 9 deletions(-) create mode 100644 ironfish/src/migrations/errors.ts diff --git a/ironfish-cli/src/commands/migrations/revert.ts b/ironfish-cli/src/commands/migrations/revert.ts index 39bf7b3780..49b2e2ba42 100644 --- a/ironfish-cli/src/commands/migrations/revert.ts +++ b/ironfish-cli/src/commands/migrations/revert.ts @@ -1,17 +1,50 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import { AccountDecryptionFailedError, EncryptedWalletMigrationError } from '@ironfish/sdk' +import { Flags } from '@oclif/core' import { IronfishCommand } from '../../command' +import { inputPrompt } from '../../ui' export class RevertCommand extends IronfishCommand { static description = `revert the last run migration` + static flags = { + passphrase: Flags.string({ + description: 'Passphrase to unlock the wallet database with', + }), + } + static hidden = true async start(): Promise { - await this.parse(RevertCommand) + const { flags } = await this.parse(RevertCommand) const node = await this.sdk.node() - await node.migrator.revert() + + let walletPassphrase = flags.passphrase + // eslint-disable-next-line no-constant-condition + while (true) { + try { + await node.migrator.revert({ walletPassphrase }) + break + } catch (e) { + if ( + e instanceof EncryptedWalletMigrationError || + e instanceof AccountDecryptionFailedError + ) { + this.logger.info(e.message) + walletPassphrase = await inputPrompt( + 'Enter your passphrase to unlock the wallet', + true, + { + password: true, + }, + ) + } else { + throw e + } + } + } } } diff --git a/ironfish-cli/src/commands/migrations/start.ts b/ironfish-cli/src/commands/migrations/start.ts index 125d7715e3..12d5db5455 100644 --- a/ironfish-cli/src/commands/migrations/start.ts +++ b/ironfish-cli/src/commands/migrations/start.ts @@ -1,8 +1,10 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import { AccountDecryptionFailedError, EncryptedWalletMigrationError } from '@ironfish/sdk' import { Flags } from '@oclif/core' import { IronfishCommand } from '../../command' +import { inputPrompt } from '../../ui' export class StartCommand extends IronfishCommand { static description = `run migrations` @@ -17,12 +19,39 @@ export class StartCommand extends IronfishCommand { char: 'q', default: false, }), + passphrase: Flags.string({ + description: 'Passphrase to unlock the wallet database with', + }), } async start(): Promise { const { flags } = await this.parse(StartCommand) const node = await this.sdk.node() - await node.migrator.migrate({ quiet: flags.quiet, dryRun: flags.dry }) + + let walletPassphrase = flags.passphrase + // eslint-disable-next-line no-constant-condition + while (true) { + try { + await node.migrator.migrate({ quiet: flags.quiet, dryRun: flags.dry, walletPassphrase }) + break + } catch (e) { + if ( + e instanceof EncryptedWalletMigrationError || + e instanceof AccountDecryptionFailedError + ) { + this.logger.info(e.message) + walletPassphrase = await inputPrompt( + 'Enter your passphrase to unlock the wallet', + true, + { + password: true, + }, + ) + } else { + throw e + } + } + } } } diff --git a/ironfish-cli/src/commands/start.ts b/ironfish-cli/src/commands/start.ts index 847951f645..16f9d20f26 100644 --- a/ironfish-cli/src/commands/start.ts +++ b/ironfish-cli/src/commands/start.ts @@ -1,7 +1,13 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { Assert, FullNode, NodeUtils, PromiseUtils } from '@ironfish/sdk' +import { + Assert, + EncryptedWalletMigrationError, + FullNode, + NodeUtils, + PromiseUtils, +} from '@ironfish/sdk' import { Flags } from '@oclif/core' import inspector from 'node:inspector' import { v4 as uuid } from 'uuid' @@ -223,7 +229,19 @@ export default class Start extends IronfishCommand { } this.log(` `) - await NodeUtils.waitForOpen(node, () => this.closing) + try { + await NodeUtils.waitForOpen(node, () => this.closing) + } catch (e) { + if (e instanceof EncryptedWalletMigrationError) { + this.logger.error(e.message) + this.logger.error( + 'Run `ironfish migrations:start` to enter wallet passphrase and migrate wallet databases', + ) + this.exit(1) + } else { + throw e + } + } if (this.closing) { return startDoneResolve() diff --git a/ironfish/src/migrations/errors.ts b/ironfish/src/migrations/errors.ts new file mode 100644 index 0000000000..baed8856e7 --- /dev/null +++ b/ironfish/src/migrations/errors.ts @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +export class EncryptedWalletMigrationError extends Error { + name = this.constructor.name +} diff --git a/ironfish/src/migrations/index.ts b/ironfish/src/migrations/index.ts index b696f4d3bd..00106cdebc 100644 --- a/ironfish/src/migrations/index.ts +++ b/ironfish/src/migrations/index.ts @@ -2,4 +2,5 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +export * from './errors' export { Migrator } from './migrator' diff --git a/ironfish/src/migrations/migration.ts b/ironfish/src/migrations/migration.ts index 7c9bcd17c1..52bade07b2 100644 --- a/ironfish/src/migrations/migration.ts +++ b/ironfish/src/migrations/migration.ts @@ -45,6 +45,7 @@ export abstract class Migration { tx: IDatabaseTransaction | undefined, logger: Logger, dryRun: boolean, + walletPassphrase: string | undefined, ): Promise abstract backward( @@ -53,5 +54,6 @@ export abstract class Migration { tx: IDatabaseTransaction | undefined, logger: Logger, dryRun: boolean, + walletPassphrase: string | undefined, ): Promise } diff --git a/ironfish/src/migrations/migrator.ts b/ironfish/src/migrations/migrator.ts index b8448fcf25..1f0a6fd7ed 100644 --- a/ironfish/src/migrations/migrator.ts +++ b/ironfish/src/migrations/migrator.ts @@ -69,7 +69,7 @@ export class Migrator { } } - async revert(options?: { dryRun?: boolean }): Promise { + async revert(options?: { dryRun?: boolean; walletPassphrase?: string }): Promise { const dryRun = options?.dryRun ?? false const migrations = this.migrations.slice().reverse() @@ -88,7 +88,14 @@ export class Migrator { await db.open() tx = db.transaction() - await migration.backward(this.context, db, tx, childLogger, dryRun) + await migration.backward( + this.context, + db, + tx, + childLogger, + dryRun, + options?.walletPassphrase, + ) await db.putVersion(migration.id - 1, tx) if (dryRun) { @@ -109,6 +116,7 @@ export class Migrator { quiet?: boolean quietNoop?: boolean dryRun?: boolean + walletPassphrase?: string }): Promise { const dryRun = options?.dryRun ?? false const logger = this.logger.create({}) @@ -154,7 +162,14 @@ export class Migrator { tx = db.transaction() } - await migration.forward(this.context, db, tx, childLogger, dryRun) + await migration.forward( + this.context, + db, + tx, + childLogger, + dryRun, + options?.walletPassphrase, + ) await db.putVersion(migration.id, tx) if (dryRun) { diff --git a/ironfish/src/node.ts b/ironfish/src/node.ts index 34e411e9a9..319ab9654c 100644 --- a/ironfish/src/node.ts +++ b/ironfish/src/node.ts @@ -359,7 +359,10 @@ export class FullNode { const initial = await this.migrator.isInitial() if (migrate || initial) { - await this.migrator.migrate({ quiet: !migrate, quietNoop: true }) + await this.migrator.migrate({ + quiet: !migrate, + quietNoop: true, + }) } try { diff --git a/ironfish/src/wallet/index.ts b/ironfish/src/wallet/index.ts index 63a341cf2e..7334e4fce0 100644 --- a/ironfish/src/wallet/index.ts +++ b/ironfish/src/wallet/index.ts @@ -10,3 +10,4 @@ export { JsonEncoder } from './exporter/encoders/json' export * from './validator' export * from './walletdb/walletdb' export * from './multisig' +export * from './errors' From 23d70912680926881631b10cd38496051c1318ed Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Tue, 19 Nov 2024 13:17:59 -0800 Subject: [PATCH 29/68] handles expert mode required errors from ledger dkg app (#5662) upgrades '@zondax/ironfish-ledger-js' dependency, '@zondax/ledger-js' dependency transforms ledger errors with return code matching 'ErrExpertModeMustBeEnabled' from ledger-ironfish-js into LedgerExpertModeError --- ironfish-cli/package.json | 4 +-- ironfish-cli/src/ledger/ledger.ts | 6 ++++- yarn.lock | 44 +++++++++---------------------- 3 files changed, 19 insertions(+), 35 deletions(-) diff --git a/ironfish-cli/package.json b/ironfish-cli/package.json index 5c2198ac39..208f0b7587 100644 --- a/ironfish-cli/package.json +++ b/ironfish-cli/package.json @@ -70,8 +70,8 @@ "@oclif/plugin-help": "6.2.5", "@oclif/plugin-not-found": "3.2.10", "@oclif/plugin-warn-if-update-available": "3.1.8", - "@zondax/ledger-ironfish": "1.0.0", - "@zondax/ledger-js": "1.0.1", + "@zondax/ledger-ironfish": "1.1.0", + "@zondax/ledger-js": "1.2.0", "axios": "1.7.7", "bech32": "2.0.0", "blessed": "0.1.81", diff --git a/ironfish-cli/src/ledger/ledger.ts b/ironfish-cli/src/ledger/ledger.ts index 4743c84b05..e537385b63 100644 --- a/ironfish-cli/src/ledger/ledger.ts +++ b/ironfish-cli/src/ledger/ledger.ts @@ -25,6 +25,7 @@ export const IronfishLedgerStatusCodes = { INVALID_TX_HASH: 0xb025, PANIC: 0xe000, EXPERT_MODE_REQUIRED: 0x6984, + DKG_EXPERT_MODE_REQUIRED: 0xb027, } export class Ledger { @@ -72,7 +73,10 @@ export class Ledger { throw new LedgerPanicError() } else if (error.returnCode === IronfishLedgerStatusCodes.GP_AUTH_FAILED) { throw new LedgerGPAuthFailed() - } else if (error.returnCode === IronfishLedgerStatusCodes.EXPERT_MODE_REQUIRED) { + } else if ( + error.returnCode === IronfishLedgerStatusCodes.EXPERT_MODE_REQUIRED || + error.returnCode === IronfishLedgerStatusCodes.DKG_EXPERT_MODE_REQUIRED + ) { throw new LedgerExpertModeError() } else if ( [ diff --git a/yarn.lock b/yarn.lock index db06ca37c3..4a43409614 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1506,16 +1506,6 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" -"@ledgerhq/devices@^8.4.2": - version "8.4.3" - resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-8.4.3.tgz#4c296df4dd4af6f1085d728609b6931a640baf86" - integrity sha512-+ih+M27E6cm6DHrmw3GbS3mEaznCyFc0e62VdQux40XK2psgYhL2yBPftM4KCrBYm1UbHqXzqLN+Jb7rNIzsHg== - dependencies: - "@ledgerhq/errors" "^6.19.0" - "@ledgerhq/logs" "^6.12.0" - rxjs "^7.8.1" - semver "^7.3.5" - "@ledgerhq/devices@^8.4.4": version "8.4.4" resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-8.4.4.tgz#0d195c1650fe57da2fad7f0d9074a0190947cd6f" @@ -1526,7 +1516,7 @@ rxjs "^7.8.1" semver "^7.3.5" -"@ledgerhq/errors@6.19.1", "@ledgerhq/errors@^6.18.0", "@ledgerhq/errors@^6.19.0", "@ledgerhq/errors@^6.19.1": +"@ledgerhq/errors@6.19.1", "@ledgerhq/errors@^6.19.1": version "6.19.1" resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-6.19.1.tgz#d9ac45ad4ff839e468b8f63766e665537aaede58" integrity sha512-75yK7Nnit/Gp7gdrJAz0ipp31CCgncRp+evWt6QawQEtQKYEDfGo10QywgrrBBixeRxwnMy1DP6g2oCWRf1bjw== @@ -1556,17 +1546,7 @@ node-hid "2.1.2" usb "2.9.0" -"@ledgerhq/hw-transport@6.31.2": - version "6.31.2" - resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-6.31.2.tgz#79c95f7928a64a0e3b5bc4ea7b5be04b9f738322" - integrity sha512-B27UIzMzm2IXPGYnEB95R7eHxpXBkTBHh6MUJJQZVknt8LilEz1tfpTYUdzAKDGQ+Z5MZyYb01Eh3Zqm3kn3uw== - dependencies: - "@ledgerhq/devices" "^8.4.2" - "@ledgerhq/errors" "^6.18.0" - "@ledgerhq/logs" "^6.12.0" - events "^3.3.0" - -"@ledgerhq/hw-transport@^6.31.4": +"@ledgerhq/hw-transport@6.31.4", "@ledgerhq/hw-transport@^6.31.4": version "6.31.4" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-6.31.4.tgz#9b23a6de4a4caaa5c24b149c2dea8adde46f0eb1" integrity sha512-6c1ir/cXWJm5dCWdq55NPgCJ3UuKuuxRvf//Xs36Bq9BwkV2YaRQhZITAkads83l07NAdR16hkTWqqpwFMaI6A== @@ -3908,19 +3888,19 @@ dependencies: argparse "^2.0.1" -"@zondax/ledger-ironfish@1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@zondax/ledger-ironfish/-/ledger-ironfish-1.0.0.tgz#f1831b44e75d74a372d7bcd4bbb4c5df76731211" - integrity sha512-eWbvTP2pwqiRylWuA8YybcX7V7Iw2zcvYIhQ7cYjyZCI6ruDVO7bEdi/d2Q8Wfkjq9vOadHeoVZYAaVk/ulBMw== +"@zondax/ledger-ironfish@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@zondax/ledger-ironfish/-/ledger-ironfish-1.1.0.tgz#b28c2644ea8ead90c149730a6496776cdf0db971" + integrity sha512-Ba+W7slKkwIFz0kNa0JyyH9eiAAYXOVk1HvV6gEA/a6Pll0qbhPgDxBYbAfvmCdLFlBHydX5UTcZuezYCeeIBw== dependencies: - "@zondax/ledger-js" "^1.0.1" + "@zondax/ledger-js" "^1.2.0" -"@zondax/ledger-js@1.0.1", "@zondax/ledger-js@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@zondax/ledger-js/-/ledger-js-1.0.1.tgz#a1c51943c5b7d1370cea588b193197234485d196" - integrity sha512-9h+aIXyEK+Rdic5Ppsmq+tptDFwPTacG1H6tpZHFdhtBFHYFOLLkKTTmq5rMTv84aAPS1v0tnsF1e2Il6M05Cg== +"@zondax/ledger-js@1.2.0", "@zondax/ledger-js@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@zondax/ledger-js/-/ledger-js-1.2.0.tgz#13fb6b4ac1b395f39d05d1efb23dfe97d6011463" + integrity sha512-HGvDU0s4zg0scu8ebGenFHFB6g5SLDqmrctncZUY9ie5pGibOU8C0VdjZSHbttSOCyh+IloLi/a4jyMPOEQGuQ== dependencies: - "@ledgerhq/hw-transport" "6.31.2" + "@ledgerhq/hw-transport" "6.31.4" JSONStream@^1.0.4: version "1.3.5" From 22898b6fdd576bba6b89931d31a60cb7bbb41a85 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Tue, 19 Nov 2024 14:23:09 -0800 Subject: [PATCH 30/68] updates sign command to use ui.ledger for ledger signing (#5663) the 'ledger' function from the ui module handles a variety of errors from the ledger app. using the ui function to wrap the app interactions improves user experience over calling the ledger sign method directly --- .../src/commands/wallet/transactions/sign.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/ironfish-cli/src/commands/wallet/transactions/sign.ts b/ironfish-cli/src/commands/wallet/transactions/sign.ts index ca5c7ebc40..94c80d0833 100644 --- a/ironfish-cli/src/commands/wallet/transactions/sign.ts +++ b/ironfish-cli/src/commands/wallet/transactions/sign.ts @@ -110,17 +110,15 @@ export class TransactionsSignCommand extends IronfishCommand { private async signWithLedger(client: RpcClient, unsignedTransaction: string) { const ledger = new LedgerSingleSigner() - try { - await ledger.connect() - } catch (e) { - if (e instanceof Error) { - this.error(e.message) - } else { - throw e - } - } - const signature = (await ledger.sign(unsignedTransaction)).toString('hex') + const signature = ( + await ui.ledger({ + ledger, + message: 'Sign Transaction', + approval: true, + action: () => ledger.sign(unsignedTransaction), + }) + ).toString('hex') this.log(`\nSignature: ${signature}`) From 782096be53c2e6cb323275b3629bb8373ac82b93 Mon Sep 17 00:00:00 2001 From: danield9tqh Date: Wed, 20 Nov 2024 13:52:45 -0500 Subject: [PATCH 31/68] Further scanner refactor --- ironfish/src/wallet/scanner/walletScanner.ts | 79 ++++++++++++-------- ironfish/src/wallet/wallet.ts | 2 +- 2 files changed, 47 insertions(+), 34 deletions(-) diff --git a/ironfish/src/wallet/scanner/walletScanner.ts b/ironfish/src/wallet/scanner/walletScanner.ts index 2d4c7bc313..0f859d6493 100644 --- a/ironfish/src/wallet/scanner/walletScanner.ts +++ b/ironfish/src/wallet/scanner/walletScanner.ts @@ -17,6 +17,8 @@ import { BackgroundNoteDecryptor } from './noteDecryptor' import { RemoteChainProcessor } from './remoteChainProcessor' import { ScanState } from './scanState' +type ScanFrom = { sequence: number; hash?: Buffer } + export class WalletScanner { readonly logger: Logger readonly wallet: Wallet @@ -32,12 +34,10 @@ export class WalletScanner { /** * A snapshot of the accounts that have `scanningEnabled` set to true. Used * to tell what accounts should be scanned, and from what block. - * If `scanFrom` is 'always-scan' then the account's head is the same as the - * WalletScanner's head, and blocks should always be added */ private scanningAccounts = new Array<{ account: Account - scanFrom: { head: HeadValue | null } | 'always-scan' + scanFrom: ScanFrom | 'cursor' }>() constructor(options: { @@ -81,9 +81,11 @@ export class WalletScanner { try { await this.refreshScanningAccounts() - - const start = this.getEarliestHead() + const start = await this.getEarliestHead() const end = await this.wallet.getChainHead() + if (start === 'none') { + return new ScanState(end, end) + } const decryptor = new BackgroundNoteDecryptor(this.workerPool, this.config, { decryptForSpender: false, @@ -133,7 +135,10 @@ export class WalletScanner { // chain processor. await decryptor.flush() await this.refreshScanningAccounts() - const head = this.getEarliestHead() + const head = await this.getEarliestHead() + if (head === 'none') { + break + } chainProcessor.hash = head?.hash ?? null } @@ -183,29 +188,23 @@ export class WalletScanner { } for (const candidate of this.scanningAccounts) { - const { account, scanFrom } = candidate + const { scanFrom } = candidate - if (scanFrom === 'always-scan') { + if (scanFrom === 'cursor') { continue } - if (scanFrom.head === null) { - if (account.createdAt === null && blockHeader.sequence === GENESIS_BLOCK_SEQUENCE) { - candidate.scanFrom = 'always-scan' - } - - if (account.createdAt && blockHeader.sequence >= account.createdAt.sequence) { - candidate.scanFrom = 'always-scan' - } + if (!scanFrom.hash && blockHeader.sequence >= scanFrom.sequence) { + candidate.scanFrom = 'cursor' } - if (scanFrom.head?.hash.equals(blockHeader.previousBlockHash)) { - candidate.scanFrom = 'always-scan' + if (scanFrom.hash?.equals(blockHeader.previousBlockHash)) { + candidate.scanFrom = 'cursor' } } const decryptAndConnectAccounts = this.scanningAccounts - .filter(({ scanFrom }) => scanFrom === 'always-scan') + .filter(({ scanFrom }) => scanFrom === 'cursor') .map(({ account }) => account) if (abort?.signal.aborted) { @@ -233,7 +232,7 @@ export class WalletScanner { this.logger.debug(`AccountHead DEL: ${header.sequence} => ${Number(header.sequence) - 1}`) const accounts = (await this.getScanningAccountsWithHead()).filter(({ head }) => - head?.hash.equals(header.hash), + head?.hash?.equals(header.hash), ) for (const { account } of accounts) { @@ -244,12 +243,12 @@ export class WalletScanner { } for (const account of this.scanningAccounts) { - if (account.scanFrom === 'always-scan') { + if (account.scanFrom === 'cursor') { continue } - if (account.scanFrom?.head?.hash.equals(header.hash)) { - account.scanFrom = 'always-scan' + if (account.scanFrom.hash?.equals(header.hash)) { + account.scanFrom = 'cursor' } } } @@ -315,26 +314,40 @@ export class WalletScanner { */ private async refreshScanningAccounts(): Promise { this.scanningAccounts = (await this.getScanningAccountsWithHead()).map( - ({ account, head }) => ({ account, scanFrom: { head } }), + ({ account, head }) => { + const scanFrom = head || { + sequence: (account.createdAt?.sequence ?? GENESIS_BLOCK_SEQUENCE) - 1, + } + return { account, scanFrom } + }, ) } - private getEarliestHead(): HeadValue | null { - let earliestHead = null + private async getEarliestHead(): Promise { + const withoutCursor: ScanFrom[] = [] for (const { scanFrom } of this.scanningAccounts) { - if (scanFrom === 'always-scan') { - continue + if (scanFrom !== 'cursor') { + withoutCursor.push(scanFrom) } + } - const { head } = scanFrom - if (!head) { + const sorted = withoutCursor.sort((a, b) => a.sequence - b.sequence) + + for (const scanFrom of sorted) { + if (scanFrom.sequence < GENESIS_BLOCK_SEQUENCE) { return null } - if (!earliestHead || earliestHead.sequence > head.sequence) { - earliestHead = head + + if (!scanFrom.hash) { + const atSequence = await this.wallet.accountHeadAtSequence(scanFrom.sequence) + if (atSequence) { + return atSequence + } + } else { + return { hash: scanFrom.hash, sequence: scanFrom.sequence } } } - return earliestHead + return 'none' } } diff --git a/ironfish/src/wallet/wallet.ts b/ironfish/src/wallet/wallet.ts index 001f5590f5..81037d5dd1 100644 --- a/ironfish/src/wallet/wallet.ts +++ b/ironfish/src/wallet/wallet.ts @@ -1453,7 +1453,7 @@ export class Wallet { * Try to get the block hash from the chain with createdAt sequence * Otherwise, return null */ - private async accountHeadAtSequence(sequence: number): Promise { + async accountHeadAtSequence(sequence: number): Promise { try { const previousBlock = await this.chainGetBlock({ sequence }) return previousBlock From 5c05d211c9b45159cf32f534be734e83be82dc6a Mon Sep 17 00:00:00 2001 From: danield9tqh Date: Wed, 20 Nov 2024 13:56:58 -0500 Subject: [PATCH 32/68] Remove unneeded type --- ironfish/src/wallet/scanner/walletScanner.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/ironfish/src/wallet/scanner/walletScanner.ts b/ironfish/src/wallet/scanner/walletScanner.ts index 0f859d6493..b1a0dff827 100644 --- a/ironfish/src/wallet/scanner/walletScanner.ts +++ b/ironfish/src/wallet/scanner/walletScanner.ts @@ -17,8 +17,6 @@ import { BackgroundNoteDecryptor } from './noteDecryptor' import { RemoteChainProcessor } from './remoteChainProcessor' import { ScanState } from './scanState' -type ScanFrom = { sequence: number; hash?: Buffer } - export class WalletScanner { readonly logger: Logger readonly wallet: Wallet @@ -37,7 +35,7 @@ export class WalletScanner { */ private scanningAccounts = new Array<{ account: Account - scanFrom: ScanFrom | 'cursor' + scanFrom: { sequence: number; hash?: Buffer } | 'cursor' }>() constructor(options: { @@ -324,14 +322,14 @@ export class WalletScanner { } private async getEarliestHead(): Promise { - const withoutCursor: ScanFrom[] = [] + const scanFroms: { sequence: number; hash?: Buffer }[] = [] for (const { scanFrom } of this.scanningAccounts) { if (scanFrom !== 'cursor') { - withoutCursor.push(scanFrom) + scanFroms.push(scanFrom) } } - const sorted = withoutCursor.sort((a, b) => a.sequence - b.sequence) + const sorted = scanFroms.sort((a, b) => a.sequence - b.sequence) for (const scanFrom of sorted) { if (scanFrom.sequence < GENESIS_BLOCK_SEQUENCE) { From f0096bda8f70fe5253bfd4ec5d22e88c6eb0aba1 Mon Sep 17 00:00:00 2001 From: danield9tqh Date: Wed, 20 Nov 2024 14:46:18 -0500 Subject: [PATCH 33/68] Add tests --- .../walletScanner.test.ts.fixture | 216 ++++++++++++++++++ .../src/wallet/scanner/walletScanner.test.ts | 48 +++- 2 files changed, 263 insertions(+), 1 deletion(-) diff --git a/ironfish/src/wallet/scanner/__fixtures__/walletScanner.test.ts.fixture b/ironfish/src/wallet/scanner/__fixtures__/walletScanner.test.ts.fixture index e4e86e3d9a..a4df20ebbf 100644 --- a/ironfish/src/wallet/scanner/__fixtures__/walletScanner.test.ts.fixture +++ b/ironfish/src/wallet/scanner/__fixtures__/walletScanner.test.ts.fixture @@ -1760,5 +1760,221 @@ } ] } + ], + "WalletScanner skips blocks preceeding the lowest createdAt when createdAt is reset": [ + { + "value": { + "encrypted": false, + "version": 4, + "id": "a5b66eaf-14d1-4f0d-9b53-59f2a512f132", + "name": "a", + "spendingKey": "35e3da30c0448e17512fb8cb580a2858739be2d43e32851768abb7ba282a078d", + "viewKey": "db42fba7b3e00208bc3a15822407592db28c4056e17dd4d92a873b1a5c7d2966c3ded83d3673d83e43f04893c39b9ee9150b0324a4e2af9eba52c30e9adfb332", + "incomingViewKey": "71ad66079d743f49241de39b3aac0511c1709530118fe790c57cabccf40dab04", + "outgoingViewKey": "05e92cd1fc348fbb5eb9d4f0ab6bf836af9b381a29f492995b2f2d5932ab2ad6", + "publicAddress": "105f9f49764820a479d3e76af3c3f690823316a981bf48b5adaaa9ba8292508c", + "createdAt": { + "sequence": 1, + "hash": { + "type": "Buffer", + "data": "base64:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + } + }, + "scanningEnabled": true, + "proofAuthorizingKey": "7f3a5b464e77c2ee39093c3a2356267df4400af1868c38ba023e733da29e8d05" + }, + "head": { + "hash": { + "type": "Buffer", + "data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" + }, + "sequence": 1 + } + }, + { + "header": { + "sequence": 2, + "previousBlockHash": "4791D7AE9F97DF100EF1558E84772D6A09B43762388283F75C6F20A32A88AA86", + "noteCommitment": { + "type": "Buffer", + "data": "base64:dNh7TqZqiNKZPp0WuCIYAQf5JAj8URm0IK7+t1KH7xc=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:QgkD5S8II8saTUDc+IynfFZbvjsD/zLOE+cx99JhnqM=" + }, + "target": "9282972777491357380673661573939192202192629606981189395159182914949423", + "randomness": "0", + "timestamp": 1732131902034, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 4, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAKg/o2giaSiQ2PO0mX1nW5amh01AmUKUNtWREvsZM+A2XSToBtF+AvgksVXC/eWZkgK1YDV1tp5onp3a0bWw+CqO2/mU7mjL1rosXXX1WPYupSbLZbX4eCmLVK1S0cAPTJwkIj66aynvFDlNC35Xh7VQnnYnau90U5aPm3QsytdsM73E5UGH5N4vrN0QmQC9Jh6UkYrZhx4w231wx2sNNz52JJ/D+7n5HzRgmoC6jX7ikIReRUy99H5W64kH/4tQb8OifM5wlurxjmG31CtzA8n1skPDmZiOtR9Wd1Dws1smk8akRQqdcGwkHffRTphbP+II23fuVKlft7YnewDLrbTFc9IE/GJ1Su0GyemduXCEeytuV4RRviBHCRtrcJiAFAJ7etzRJhYlUht3Jz4NWIUHp3Z45c5K13ftH3HzhH9WkwD20TR7d/csvGYequW7Gt0s2A/YdC9ibG73eCtPSSSKPp3fOHJSPHphQtrtBx1xIVjpqwwTvNU9qH9rdoRx+nG80hb2z2UR1rk9rRjFJFEjdYg+xInQnWVABj6eSSp3nW/EP3xRRi4VRLlnnec3XJEA0SwJSzSWzRCHPsljf5fy2C2eIsDVnHEK7c+Em6k2MSDs3HoQX+Ulyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwutKhqLx4Zl0oN91YbPNKf2JD0yGBY+Ws5soWOCswYteFW0gMr0caIJZ93EfcLqFwRxGpTBtjpDkU5Y6Nb5b3CQ==" + } + ] + }, + { + "header": { + "sequence": 3, + "previousBlockHash": "C59CDF4CBA791B56E42E554527DA9F054952BE2A8275CFBC3634BAAFD6D29D3E", + "noteCommitment": { + "type": "Buffer", + "data": "base64:uaKCWhSsHu87BqkC5fcSPHxMZs8wcgaw2nqLmKCQnnE=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:8UpDLoTwgqL2xxlSh3fCvIfAusLGq0dQJm3c7AFJfpU=" + }, + "target": "9255858786337818395603165512831024101510453493377417362192396248796027", + "randomness": "0", + "timestamp": 1732131902329, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 5, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAKmerNjtgzspqji9tJL5YmXmj7yLXe8mpBC5h/ee346uHkrUGJpNjq2uB0m/ut3LnOMXFTPrNGe6iTAQeJPzBw+Gnsy1zWAvMZxA+7YVQQFmy+AScrKirZhX5PxLXzeWXw6ynJjdNVGhJbEE7NWUlbbLc9DKkGsXt/pUVXyUMdk0DuP9LGpqBFttPSly/wJ7QOx/wQrXCxTOJ/h2qlUXL/Flopc31jjrgzraikVgfogGVR8sf8cpyqblsYEcax6O3RgtTjZLcfXdDZEjhueDRghV8RQ/YIBNvLyI6GeMKcGOtsFt1LXIyqhtynYOCptuN0/ZR7FCNfN6a2GLQx2QiFQA7NRjGC8WpNRw522ZTEiQH66cG0SB//qpa5KC0Xv5PKUKJ/GFJy5jW2BsLXooo8zRWlrAvt5vi68Gv2yMrh+FZO5CW3pUh7dlFj0HjSYeLqd846nfml69felU1pEn8VVTKh0bvEvzvXoCfCssWw5LqeNOtKsPgOA2PEJFvo2TGtInZejcj0C1mCBqm2E6n0t3KkvQKL0zSGFvBUiM+xYpwz2acuytfa+SPQAxEPuuUyblLLkRh59yVd+fqDHDF3fHJF6vzC67up/ZFO99QJK237eDwUvmW5klyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwaEB5IdP7aYVH/Aa3IvtyFXGPDoLDyIofgTFvOeZMEJlPxm+YG+egVHl1/l5ueh/ddOMdY39cVQkYfUeG89SUCQ==" + } + ] + }, + { + "header": { + "sequence": 4, + "previousBlockHash": "46CB6B77509AB51006EBD3CEE33D346D67018930DC7CE7BA014A5EE675E18960", + "noteCommitment": { + "type": "Buffer", + "data": "base64:7mF/TU11cntiLJOYOBjkBBtN2pN++hGCQpe/zd40WHM=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:770udlQiNaDysdS/hVhwI2gVbmkKPjTZSMw8JH3DpjM=" + }, + "target": "9228823284279306817296266184515742822248210830185427859262273659833347", + "randomness": "0", + "timestamp": 1732131902623, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 6, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAGilpMBcXk4H8Fb3x0FCsGnTjF2mH7TqDEQhU6IIFCgCrYVEx6N22J6M1h4ZN1hYyNSltHE0vUd1yYAG//SyFiIh8nPqBAHP9WwOQZSlO6g6CufaVG6BwmFFoC3rUNTZxX8/b6uAzOLHoLG9lpeAs15cGQWRm3Lta7bFF2aTYSioJYqibMCTR4EFnu60s/+smKBEhcSUtIiTHw+piF2ihEj5BqTOUuqM2Ah47hEgY+k+jmMWDH/qGEclGkg9DuGPWSQeKUZYHD+GPhVVB59ds2UuoRaHeP9wP1MMpGcNKUsPXWVe9omY1PIZyIdkyPUcsgAqp2mTn4O2DvnpVDYZWt+gDQ7XqpGBgg2/EpArRfJ5ldmXgbpVDIcwXFSWXxssLFQfyBAIXjoTPIl95RzHs5vKulvO0QG/BLRTMjq0/nrxCpLO0lCzYGr983Q7fEcJkd0DLxgY8F3uBCTwpDUHDd1JtT4JF8Pke4cNC4WYtBWXe5RauMXyeNpP21+nqwlL1XAcosxnXSvM8us8uyXWA29CuKXetYAV0J+FsXQxWXPeQt8YLcFLa+ILC5TN0tgWCondWUkq/ed/Vvq2WzKp9WWP/Cq/214C+L1HE5Kd7yGo6LahczHGBoUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw0VDqTzUQeHbjVW/j76KLCmC+cn0iXhSTbK0cEgKePZiJXnU6lQTlyiRiuMGBkSh4M3LWwf8ioUJ3733tLU+zCw==" + } + ] + }, + { + "value": { + "encrypted": false, + "version": 4, + "id": "4f950cf3-ed7a-4cbf-a888-50670c60b68b", + "name": "b", + "spendingKey": "b11906d32adb1e3374cba59ea18f06a752ce58c182715106b26896b0a86926c4", + "viewKey": "c4ad3b61102177b22104a291f83605e791d07a66dffbe9d9036887f3b87046ade10eb8a59acd88798e03a681280718c4dd1b8924fbac13e4ca3cda12ed7ef439", + "incomingViewKey": "2ca81c41d1d4a177bf79ec5bb998dd28eb7a14ccff19cecc22b40f538c75fe00", + "outgoingViewKey": "7797e17e6d45b4ae62fab696a622bb25589b818834b73d410e4a76e7891c8533", + "publicAddress": "240aed07706de6bf560216e57964b5c1fb7d242bca710a8eba0b9ae94956b501", + "createdAt": { + "sequence": 4, + "hash": { + "type": "Buffer", + "data": "base64:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + } + }, + "scanningEnabled": true, + "proofAuthorizingKey": "f05d6253c9b282df2dfa42f1fabb095813000748b89b57b085853e9239601804" + }, + "head": { + "hash": { + "type": "Buffer", + "data": "base64:ljnfpbcbwRqAmtYv0IAAk7hSgPstk3qT0SOuLJd3Ypc=" + }, + "sequence": 4 + } + }, + { + "header": { + "sequence": 5, + "previousBlockHash": "9639DFA5B71BC11A809AD62FD0800093B85280FB2D937A93D123AE2C97776297", + "noteCommitment": { + "type": "Buffer", + "data": "base64:0HeKERUoa9oUbDlymaBxBgw6tGu02qBHpdPqhvJk+mM=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:lcc9gOD75ffSpgYbA77PpDcTPoZpirG2GQIbMeFOfVA=" + }, + "target": "9201866281654531936596795386791503876274441021197252859723586932895305", + "randomness": "0", + "timestamp": 1732131902906, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 7, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAv9ut6WNfLwDe6Vz2DLdVJ2PAHVwJNkD2Y1gLfLBmOD6RFfUWthU+IM+eFl5ar7rcQ9POQCz5HEHB7naSkLrx5RMFNaj2v9ay4BjzLd+cR66XjTE93gDwlSnqHm9W3eDRjnC/PzLhz0YkmwmrE1Jn+W98PsM8tHlgZuaaCCY6zewM91Jea8mUOhfG9SNRJki5rm+a4Zn5n2szMjjDLk3aw6bnZAF+A7c77ZXan6JRJNev89hx2r2Fily1K7O5WFmrRJc66YF5UdT1egDGgJbOpXiC6vkDROQ3Ur1ITUfHOjCLsbGNG/orCC2QVUofKdVLvklJXbQNYoH4GxpoDiGM1IZUwn3U5ORMdoOb6AoWiyqUYpymmp3PKD+4Gvf8VfAzPiEDkWv1nk6dk+gz/f2/1GnmSe1Qp5exwId+Ts90H1Cqko0Or8AXArqZeR8DwDhSoId4T5iBR0UabDI46t7+TI0GGhQLJcQkaS69VTC2NYZ4C5f0XwjOFYtmvnTJn/VNLPh0o6nwA1J648VB/M5IlPJFV4q8S0c4dXT4nlKPdGH8u90dXCBl7Jz/He0803y7B+uqoRfiR2I1ZlK3wTsSn98mZ7LFyDbWBXVhlbO8l6O239d4VbpmeElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwn8Y/DYCVuwtWT9XCwYx4XEmGHhFfqdvmjgSigzhQH8Jn1gJL+DHA9oxoNA1S9SzgG/uDuTr26yntb8gs2fOcDA==" + } + ] + }, + { + "header": { + "sequence": 6, + "previousBlockHash": "4894C44ABD6FA5EAF21503F67C9FA976774C32CB126559C9BC2D870C5CEA839D", + "noteCommitment": { + "type": "Buffer", + "data": "base64:vg0kkSSfy2TYSM0/Hn0fw2rJ6WqXPwbPLeAhgWAjlwE=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:VuI9RYA0/mWA6Hd2PKP8YjXO5kmXRttr49EHpplpezg=" + }, + "target": "9174987784651351638043000274530578397566067964335270621952759689537226", + "randomness": "0", + "timestamp": 1732131903189, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 8, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAPSk1gYSe2kmXW3ULBo6iRJM04ynu32WyLcdtAeV6dtOG2KgjDyL9EJ6xlAv+QKCFyCOCKEluUwzwF0u2RI9C91+1T6pgkmrtrJYMXWvsJrSC0kg/DpeMePpWFjeNx63QlWOj4xlzfyRaTdynDCKHy3qyRUF9LCW7MpTdqjGxclkQiZXzAT+twMuga3APQZJHzRoZWcgPX1ASsJMaafr1WAG9d+pe07nG9WcX6/1hjbuENSTUkK5G2dv6KkWLMF5sCGmi9b7BI+3wMMrO6myrUTGOnFzvXUCZmsm8feZxLd3HEG5ISOGqc7hyK7Qm2l2n+Qvqty80gCT0qtgNpEwO8NXdyJezOyBvOpErvpmTH9LnQ8xJy/ihvyrRLFMx9ZAAqCQAU2U9aJWh6PDDgUMppL4zmOCk/CqQD6EQTwXE8WIHCyACdob4qc49qPNsjAx3jsNLZF22Gj2q1J5RPqdOgjl/tf6RrblzjcbTxZZEDM+GAZLwWjXF3fVslu+RWILvl9Z86S3KI/EVCTGTtyssP38caFMd8sdcRc/TaknKlhGCsjZGODTc6Obtz7lbSKmcfsIyiRDzF1H5zk5ybHqStCpl9JsZ9OTuMzvyp4/PggK7Dvpk/mahgklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwKUNBAdBUlrOuaFEgvYcT5eAFwBHtrmkE8U66RO1/DzD+Ca77mlJftustpvFBfG6BkheJ8lTCcnAXGCu07EOfBg==" + } + ] + }, + { + "header": { + "sequence": 7, + "previousBlockHash": "E1144BAA62A876BCE5B2A57EA2188A9C7DF7D63869BB5F5B9804880AFCABF9BB", + "noteCommitment": { + "type": "Buffer", + "data": "base64:lKBLev4ynMrr1MYpdZQXPE6PTFenGhtibJFZPZkqrj4=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:iB1e4bxBas4dSuQNgE7DKNHnCg9GzvfQbXS35PC0KHw=" + }, + "target": "9148187795366513087508709149025146424715856256637674150531751753357577", + "randomness": "0", + "timestamp": 1732131903479, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 9, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA15vHqi+mztLkn7Zna1IIVc5FGyZt8/rICe6TyVqvHluI9tassCajt3iBwAsdsHko//bW49alk2dMROBBCBJT3MVsLzEzhgJQUHd3zN0q7gyvMs2gsmKpyo+17M6p+RRAEVuhh+TfXl+klAUV1jnm4lv7FTvLTXVGnOFM5g1iyQcJqsja8Iksk8P3/YIhCeM8hpi+utc8oCZKJMCX+4+IoR/cI/YAZ+/F/jJvFEliA42Y+gHvf+Va05a3ZQBzMuURNLPPbyXEVwXxhZKXex3RzpR5zDzlwAyRDMHAGzgcIDOBBQywRsXoXyW8Ixlqki326+Kc/IUTT31akgX6t+GXgmppTrelqN3D686pRgy/MzT/UemrHpTJT6bDdSEekwZuk5pI8TpKwPmhmKuMHW3uGdrVYMF2Z7jpxy+oNxFzYlF3ufOxDQt2p3mVVW+uZOsKAYk5Np4PnqG5ZcrQaAQVoADcivLpgCkQKa2z8SHFyLo0ftiv0F7kXKQtchke8EXSlqr0++3ILZBqKD8w91te98p2GWlERm8FKRTMwwaZodTHm99oKjkmpsSWRMHiECFq7ZE9gxD71WcS2/FmzUigPA+78tcyMWh6OGG5+q5EKQMZBazUNTYTx0lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwK1XDOo0LBx/Rael9t2bA9vvS5JW5nLWOrPxd85EQVLh1eUxvm7fWw7c+LaQNw+3ewKeQvHVXDzu1q/3qdertCQ==" + } + ] + } ] } \ No newline at end of file diff --git a/ironfish/src/wallet/scanner/walletScanner.test.ts b/ironfish/src/wallet/scanner/walletScanner.test.ts index 9a039160fb..d9745d7417 100644 --- a/ironfish/src/wallet/scanner/walletScanner.test.ts +++ b/ironfish/src/wallet/scanner/walletScanner.test.ts @@ -7,7 +7,7 @@ import { Blockchain } from '../../blockchain' import { Block, BlockHeader } from '../../primitives' import { createNodeTest, useAccountFixture, useMinerBlockFixture } from '../../testUtilities' import { AsyncUtils } from '../../utils' -import { Account, Wallet } from '../../wallet' +import { Account, toAccountImport, Wallet } from '../../wallet' import { BackgroundNoteDecryptor } from './noteDecryptor' describe('WalletScanner', () => { @@ -304,6 +304,52 @@ describe('WalletScanner', () => { } }) + it('skips blocks preceeding the lowest createdAt when createdAt is reset', async () => { + const decryptNotesFromBlock = jest.spyOn( + BackgroundNoteDecryptor.prototype, + 'decryptNotesFromBlock', + ) + const connectBlockForAccount = jest.spyOn(nodeTest.wallet, 'connectBlockForAccount') + + const accountA = await useAccountFixture(nodeTest.wallet, 'a') + const firstBlocks = await createTestNotes(nodeTest.chain, nodeTest.wallet, [[accountA, 3]]) + await nodeTest.wallet.removeAccount(accountA) + + const accountB = await useAccountFixture(nodeTest.wallet, 'b') + const lastBlocks = await createTestNotes(nodeTest.chain, nodeTest.wallet, [[accountB, 3]]) + + await nodeTest.wallet.reset({ resetCreatedAt: true }) + + await nodeTest.wallet.getAccountByName(accountB.name)?.updateCreatedAt({ + hash: Buffer.alloc(32, 0), + sequence: accountB.createdAt?.sequence || 0, + }) + + await nodeTest.wallet.scan() + + const blocks = [firstBlocks[2], ...lastBlocks] + + expect(decryptNotesFromBlock).toHaveBeenCalledTimes(blocks.length) + expect(connectBlockForAccount).toHaveBeenCalledTimes(blocks.length) + + for (const [i, block] of blocks.entries()) { + expect(decryptNotesFromBlock).toHaveBeenNthCalledWith( + i + 1, + block.header, + block.transactions, + [expect.objectContaining({ incomingViewKey: accountB.incomingViewKey })], + expect.anything(), + ) + expect(connectBlockForAccount).toHaveBeenNthCalledWith( + i + 1, + expect.objectContaining({ incomingViewKey: accountB.incomingViewKey }), + block.header, + expect.anything(), + true, + ) + } + }) + describe('restarts scanning', () => { // Set up the BackgroundNoteDecryptor so that we can pause and resume the // scan after each block that gets processed. From ffcddaef1595480e5d7e46119d74756d3277ed13 Mon Sep 17 00:00:00 2001 From: danield9tqh Date: Wed, 20 Nov 2024 14:48:09 -0500 Subject: [PATCH 34/68] Shorten test --- ironfish/src/wallet/scanner/walletScanner.test.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/ironfish/src/wallet/scanner/walletScanner.test.ts b/ironfish/src/wallet/scanner/walletScanner.test.ts index d9745d7417..5a0d15fade 100644 --- a/ironfish/src/wallet/scanner/walletScanner.test.ts +++ b/ironfish/src/wallet/scanner/walletScanner.test.ts @@ -305,10 +305,6 @@ describe('WalletScanner', () => { }) it('skips blocks preceeding the lowest createdAt when createdAt is reset', async () => { - const decryptNotesFromBlock = jest.spyOn( - BackgroundNoteDecryptor.prototype, - 'decryptNotesFromBlock', - ) const connectBlockForAccount = jest.spyOn(nodeTest.wallet, 'connectBlockForAccount') const accountA = await useAccountFixture(nodeTest.wallet, 'a') @@ -329,17 +325,9 @@ describe('WalletScanner', () => { const blocks = [firstBlocks[2], ...lastBlocks] - expect(decryptNotesFromBlock).toHaveBeenCalledTimes(blocks.length) expect(connectBlockForAccount).toHaveBeenCalledTimes(blocks.length) for (const [i, block] of blocks.entries()) { - expect(decryptNotesFromBlock).toHaveBeenNthCalledWith( - i + 1, - block.header, - block.transactions, - [expect.objectContaining({ incomingViewKey: accountB.incomingViewKey })], - expect.anything(), - ) expect(connectBlockForAccount).toHaveBeenNthCalledWith( i + 1, expect.objectContaining({ incomingViewKey: accountB.incomingViewKey }), From b577ad975515f0372f1b3b7038d995475791a24e Mon Sep 17 00:00:00 2001 From: danield9tqh Date: Wed, 20 Nov 2024 14:49:31 -0500 Subject: [PATCH 35/68] remove unneeded import --- ironfish/src/wallet/scanner/walletScanner.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ironfish/src/wallet/scanner/walletScanner.test.ts b/ironfish/src/wallet/scanner/walletScanner.test.ts index 5a0d15fade..3bdd4888c1 100644 --- a/ironfish/src/wallet/scanner/walletScanner.test.ts +++ b/ironfish/src/wallet/scanner/walletScanner.test.ts @@ -7,7 +7,7 @@ import { Blockchain } from '../../blockchain' import { Block, BlockHeader } from '../../primitives' import { createNodeTest, useAccountFixture, useMinerBlockFixture } from '../../testUtilities' import { AsyncUtils } from '../../utils' -import { Account, toAccountImport, Wallet } from '../../wallet' +import { Account, Wallet } from '../../wallet' import { BackgroundNoteDecryptor } from './noteDecryptor' describe('WalletScanner', () => { From ec6e0672f22e7c5087440c02fac483382c123aca Mon Sep 17 00:00:00 2001 From: danield9tqh Date: Wed, 20 Nov 2024 15:02:34 -0500 Subject: [PATCH 36/68] Don't query more blocks if there is no block at the smallest sequence --- ironfish/src/wallet/scanner/walletScanner.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ironfish/src/wallet/scanner/walletScanner.ts b/ironfish/src/wallet/scanner/walletScanner.ts index b1a0dff827..3f23c760e1 100644 --- a/ironfish/src/wallet/scanner/walletScanner.ts +++ b/ironfish/src/wallet/scanner/walletScanner.ts @@ -338,9 +338,7 @@ export class WalletScanner { if (!scanFrom.hash) { const atSequence = await this.wallet.accountHeadAtSequence(scanFrom.sequence) - if (atSequence) { - return atSequence - } + return atSequence ?? 'none' } else { return { hash: scanFrom.hash, sequence: scanFrom.sequence } } From 57ded0ee8f679c9f20933f168e4ef8825ef7c322 Mon Sep 17 00:00:00 2001 From: danield9tqh Date: Wed, 20 Nov 2024 15:32:58 -0500 Subject: [PATCH 37/68] refactor earliest head --- ironfish/src/wallet/scanner/walletScanner.ts | 34 +++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/ironfish/src/wallet/scanner/walletScanner.ts b/ironfish/src/wallet/scanner/walletScanner.ts index 3f23c760e1..600a7778f9 100644 --- a/ironfish/src/wallet/scanner/walletScanner.ts +++ b/ironfish/src/wallet/scanner/walletScanner.ts @@ -322,28 +322,30 @@ export class WalletScanner { } private async getEarliestHead(): Promise { - const scanFroms: { sequence: number; hash?: Buffer }[] = [] - for (const { scanFrom } of this.scanningAccounts) { - if (scanFrom !== 'cursor') { - scanFroms.push(scanFrom) + let earliestHead: { sequence: number; hash?: Buffer } | null = null + for (const { scanFrom: head } of this.scanningAccounts) { + if (head === 'cursor') { + continue + } + + if (!earliestHead || head.sequence < earliestHead.sequence) { + earliestHead = head } } - const sorted = scanFroms.sort((a, b) => a.sequence - b.sequence) + if (!earliestHead) { + return 'none' + } - for (const scanFrom of sorted) { - if (scanFrom.sequence < GENESIS_BLOCK_SEQUENCE) { - return null - } + if (earliestHead.sequence < GENESIS_BLOCK_SEQUENCE) { + return null + } - if (!scanFrom.hash) { - const atSequence = await this.wallet.accountHeadAtSequence(scanFrom.sequence) - return atSequence ?? 'none' - } else { - return { hash: scanFrom.hash, sequence: scanFrom.sequence } - } + if (!earliestHead.hash) { + const atSequence = await this.wallet.accountHeadAtSequence(earliestHead.sequence) + return atSequence ?? 'none' } - return 'none' + return { hash: earliestHead.hash, sequence: earliestHead.sequence } } } From b6fe460c7a3fb3b830f25b9a73028538ed387460 Mon Sep 17 00:00:00 2001 From: danield9tqh Date: Wed, 20 Nov 2024 15:33:57 -0500 Subject: [PATCH 38/68] refactor 2 --- ironfish/src/wallet/scanner/walletScanner.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ironfish/src/wallet/scanner/walletScanner.ts b/ironfish/src/wallet/scanner/walletScanner.ts index 600a7778f9..7cc56ce8a4 100644 --- a/ironfish/src/wallet/scanner/walletScanner.ts +++ b/ironfish/src/wallet/scanner/walletScanner.ts @@ -323,13 +323,13 @@ export class WalletScanner { private async getEarliestHead(): Promise { let earliestHead: { sequence: number; hash?: Buffer } | null = null - for (const { scanFrom: head } of this.scanningAccounts) { - if (head === 'cursor') { + for (const { scanFrom } of this.scanningAccounts) { + if (scanFrom === 'cursor') { continue } - if (!earliestHead || head.sequence < earliestHead.sequence) { - earliestHead = head + if (!earliestHead || scanFrom.sequence < earliestHead.sequence) { + earliestHead = scanFrom } } From 7a936ee9d6689d30502194599416cf1c4891f6cb Mon Sep 17 00:00:00 2001 From: andrea Date: Mon, 4 Nov 2024 15:04:40 -0800 Subject: [PATCH 39/68] Expose the ironfish crate features in the wasm crate --- ironfish-rust-wasm/Cargo.toml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ironfish-rust-wasm/Cargo.toml b/ironfish-rust-wasm/Cargo.toml index e6e51b7de5..fbc0276266 100644 --- a/ironfish-rust-wasm/Cargo.toml +++ b/ironfish-rust-wasm/Cargo.toml @@ -13,11 +13,18 @@ publish = false [lib] crate-type = ["cdylib"] +[features] +default = ["transaction-proofs"] + +download-params = ["ironfish/download-params"] +note-encryption-stats = ["ironfish/note-encryption-stats"] +transaction-proofs = ["ironfish/transaction-proofs"] + [dependencies] blstrs = "0.6.0" getrandom = { version = "0.2.8", features = ["js"] } # need to explicitly enable the `js` feature in order to run in a browser group = "0.12.0" -ironfish = { version = "0.3.0", path = "../ironfish-rust" } +ironfish = { version = "0.3.0", path = "../ironfish-rust", default-features = false } ironfish-jubjub = "0.1.0" ironfish_zkp = { version = "0.2.0", path = "../ironfish-zkp" } rand = "0.8.5" From 0dded2d11fc823214b72e9826f2ef65b822188ae Mon Sep 17 00:00:00 2001 From: andrea Date: Wed, 20 Nov 2024 15:04:26 -0800 Subject: [PATCH 40/68] WASM: use a macro to simplify the definition of binding types This reduces the amount of repeated code. Also, it adds some conversions that were previously not implemented. --- ironfish-rust-wasm/src/assets.rs | 37 +---- ironfish-rust-wasm/src/keys/public_address.rs | 21 +-- ironfish-rust-wasm/src/lib.rs | 60 ++++++++ ironfish-rust-wasm/src/merkle_note.rs | 38 +---- ironfish-rust-wasm/src/primitives.rs | 138 ++++-------------- ironfish-rust-wasm/src/transaction/burns.rs | 21 +-- ironfish-rust-wasm/src/transaction/mints.rs | 20 +-- ironfish-rust-wasm/src/transaction/mod.rs | 21 +-- ironfish-rust-wasm/src/transaction/outputs.rs | 20 +-- ironfish-rust-wasm/src/transaction/spends.rs | 20 +-- 10 files changed, 131 insertions(+), 265 deletions(-) diff --git a/ironfish-rust-wasm/src/assets.rs b/ironfish-rust-wasm/src/assets.rs index b0606caeeb..a863c44c9a 100644 --- a/ironfish-rust-wasm/src/assets.rs +++ b/ironfish-rust-wasm/src/assets.rs @@ -6,13 +6,15 @@ use crate::{ errors::IronfishError, keys::PublicAddress, primitives::{ExtendedPoint, SubgroupPoint}, + wasm_bindgen_wrapper, }; use ironfish::errors::IronfishErrorKind; use wasm_bindgen::prelude::*; -#[wasm_bindgen] -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct Asset(ironfish::assets::asset::Asset); +wasm_bindgen_wrapper! { + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct Asset(ironfish::assets::asset::Asset); +} #[wasm_bindgen] impl Asset { @@ -99,22 +101,11 @@ impl Asset { } } -impl From for Asset { - fn from(d: ironfish::assets::asset::Asset) -> Self { - Self(d) - } +wasm_bindgen_wrapper! { + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct AssetIdentifier(ironfish::assets::asset_identifier::AssetIdentifier); } -impl AsRef for Asset { - fn as_ref(&self) -> &ironfish::assets::asset::Asset { - &self.0 - } -} - -#[wasm_bindgen] -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct AssetIdentifier(ironfish::assets::asset_identifier::AssetIdentifier); - #[wasm_bindgen] impl AssetIdentifier { #[wasm_bindgen(constructor)] @@ -140,18 +131,6 @@ impl AssetIdentifier { } } -impl From for AssetIdentifier { - fn from(d: ironfish::assets::asset_identifier::AssetIdentifier) -> Self { - Self(d) - } -} - -impl AsRef for AssetIdentifier { - fn as_ref(&self) -> &ironfish::assets::asset_identifier::AssetIdentifier { - &self.0 - } -} - #[cfg(test)] mod tests { mod asset { diff --git a/ironfish-rust-wasm/src/keys/public_address.rs b/ironfish-rust-wasm/src/keys/public_address.rs index 3f2e91dba0..8ceda76464 100644 --- a/ironfish-rust-wasm/src/keys/public_address.rs +++ b/ironfish-rust-wasm/src/keys/public_address.rs @@ -2,12 +2,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use crate::errors::IronfishError; +use crate::{errors::IronfishError, wasm_bindgen_wrapper}; use wasm_bindgen::prelude::*; -#[wasm_bindgen] -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct PublicAddress(ironfish::PublicAddress); +wasm_bindgen_wrapper! { + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct PublicAddress(ironfish::PublicAddress); +} #[wasm_bindgen] impl PublicAddress { @@ -32,18 +33,6 @@ impl PublicAddress { } } -impl From for PublicAddress { - fn from(d: ironfish::PublicAddress) -> Self { - Self(d) - } -} - -impl AsRef for PublicAddress { - fn as_ref(&self) -> &ironfish::PublicAddress { - &self.0 - } -} - #[cfg(test)] mod tests { use crate::keys::public_address::PublicAddress; diff --git a/ironfish-rust-wasm/src/lib.rs b/ironfish-rust-wasm/src/lib.rs index 0e4865b20c..ae1e56da2c 100644 --- a/ironfish-rust-wasm/src/lib.rs +++ b/ironfish-rust-wasm/src/lib.rs @@ -22,6 +22,66 @@ pub mod merkle_note; pub mod primitives; pub mod transaction; +/// Creates a [`wasm_bindgen`] wrapper for an existing type. +/// +/// This macro can be invoked as follows: +/// +/// ``` +/// wasm_bindgen_wrapper! { +/// #[derive(Clone, Debug)] +/// pub struct FooBinding(Foo); +/// } +/// ``` +/// +/// and expands to the following: +/// +/// ``` +/// #[wasm_bindgen] +/// #[derive(Clone, Debug)] +/// pub struct FooBinding(Foo); +/// +/// impl From for FooBinding { ... } +/// impl From for Foo { ... } +/// impl AsRef for FooBinding { ... } +/// impl Borrow for FooBinding { ... } +/// ``` +macro_rules! wasm_bindgen_wrapper { + ($( + $( #[ $meta:meta ] )* + $vis:vis struct $name:ident ( $inner:ty ) ; + )*) => {$( + $(#[$meta])* + #[::wasm_bindgen::prelude::wasm_bindgen] + $vis struct $name($inner); + + impl ::std::convert::From<$inner> for $name { + fn from(x: $inner) -> Self { + Self(x) + } + } + + impl ::std::convert::From<$name> for $inner { + fn from(x: $name) -> Self { + x.0 + } + } + + impl ::std::convert::AsRef<$inner> for $name { + fn as_ref(&self) -> &$inner { + &self.0 + } + } + + impl ::std::borrow::Borrow<$inner> for $name { + fn borrow(&self) -> &$inner { + &self.0 + } + } + )*} +} + +use wasm_bindgen_wrapper; + #[cfg(test)] mod tests { wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); diff --git a/ironfish-rust-wasm/src/merkle_note.rs b/ironfish-rust-wasm/src/merkle_note.rs index a02d1ec444..b19e5e30e5 100644 --- a/ironfish-rust-wasm/src/merkle_note.rs +++ b/ironfish-rust-wasm/src/merkle_note.rs @@ -2,12 +2,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use crate::{errors::IronfishError, primitives::Scalar}; +use crate::{errors::IronfishError, primitives::Scalar, wasm_bindgen_wrapper}; use wasm_bindgen::prelude::*; -#[wasm_bindgen] -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct MerkleNote(ironfish::MerkleNote); +wasm_bindgen_wrapper! { + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct MerkleNote(ironfish::MerkleNote); +} #[wasm_bindgen] impl MerkleNote { @@ -31,22 +32,11 @@ impl MerkleNote { } } -impl From for MerkleNote { - fn from(d: ironfish::MerkleNote) -> Self { - Self(d) - } +wasm_bindgen_wrapper! { + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct MerkleNoteHash(ironfish::MerkleNoteHash); } -impl AsRef for MerkleNote { - fn as_ref(&self) -> &ironfish::MerkleNote { - &self.0 - } -} - -#[wasm_bindgen] -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct MerkleNoteHash(ironfish::MerkleNoteHash); - #[wasm_bindgen] impl MerkleNoteHash { #[wasm_bindgen(constructor)] @@ -80,18 +70,6 @@ impl MerkleNoteHash { } } -impl From for MerkleNoteHash { - fn from(d: ironfish::MerkleNoteHash) -> Self { - Self(d) - } -} - -impl AsRef for MerkleNoteHash { - fn as_ref(&self) -> &ironfish::MerkleNoteHash { - &self.0 - } -} - #[cfg(test)] mod tests { use crate::merkle_note::MerkleNoteHash; diff --git a/ironfish-rust-wasm/src/primitives.rs b/ironfish-rust-wasm/src/primitives.rs index 99517956ac..8eb38fc640 100644 --- a/ironfish-rust-wasm/src/primitives.rs +++ b/ironfish-rust-wasm/src/primitives.rs @@ -2,16 +2,17 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use crate::errors::IronfishError; +use crate::{errors::IronfishError, wasm_bindgen_wrapper}; use group::GroupEncoding; use ironfish::errors::IronfishErrorKind; use ironfish_zkp::redjubjub; use rand::thread_rng; use wasm_bindgen::prelude::*; -#[wasm_bindgen] -#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub struct Scalar(blstrs::Scalar); +wasm_bindgen_wrapper! { + #[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] + pub struct Scalar(blstrs::Scalar); +} #[wasm_bindgen] impl Scalar { @@ -26,22 +27,11 @@ impl Scalar { } } -impl From for Scalar { - fn from(s: blstrs::Scalar) -> Self { - Self(s) - } -} - -impl AsRef for Scalar { - fn as_ref(&self) -> &blstrs::Scalar { - &self.0 - } +wasm_bindgen_wrapper! { + #[derive(Default, Copy, Clone, PartialEq, Eq, Debug)] + pub struct Fr(ironfish_jubjub::Fr); } -#[wasm_bindgen] -#[derive(Default, Copy, Clone, PartialEq, Eq, Debug)] -pub struct Fr(ironfish_jubjub::Fr); - #[wasm_bindgen] impl Fr { #[wasm_bindgen(js_name = toBytes)] @@ -50,22 +40,11 @@ impl Fr { } } -impl From for Fr { - fn from(s: ironfish_jubjub::Fr) -> Self { - Self(s) - } -} - -impl AsRef for Fr { - fn as_ref(&self) -> &ironfish_jubjub::Fr { - &self.0 - } +wasm_bindgen_wrapper! { + #[derive(Default, Copy, Clone, PartialEq, Eq, Debug)] + pub struct ExtendedPoint(ironfish_jubjub::ExtendedPoint); } -#[wasm_bindgen] -#[derive(Default, Copy, Clone, PartialEq, Eq, Debug)] -pub struct ExtendedPoint(ironfish_jubjub::ExtendedPoint); - #[wasm_bindgen] impl ExtendedPoint { #[wasm_bindgen(js_name = toBytes)] @@ -74,22 +53,11 @@ impl ExtendedPoint { } } -impl From for ExtendedPoint { - fn from(s: ironfish_jubjub::ExtendedPoint) -> Self { - Self(s) - } -} - -impl AsRef for ExtendedPoint { - fn as_ref(&self) -> &ironfish_jubjub::ExtendedPoint { - &self.0 - } +wasm_bindgen_wrapper! { + #[derive(Default, Copy, Clone, PartialEq, Eq, Debug)] + pub struct SubgroupPoint(ironfish_jubjub::SubgroupPoint); } -#[wasm_bindgen] -#[derive(Default, Copy, Clone, PartialEq, Eq, Debug)] -pub struct SubgroupPoint(ironfish_jubjub::SubgroupPoint); - #[wasm_bindgen] impl SubgroupPoint { #[wasm_bindgen(js_name = toBytes)] @@ -98,22 +66,11 @@ impl SubgroupPoint { } } -impl From for SubgroupPoint { - fn from(s: ironfish_jubjub::SubgroupPoint) -> Self { - Self(s) - } -} - -impl AsRef for SubgroupPoint { - fn as_ref(&self) -> &ironfish_jubjub::SubgroupPoint { - &self.0 - } +wasm_bindgen_wrapper! { + #[derive(Copy, Clone, PartialEq, Eq, Debug)] + pub struct Nullifier(ironfish_zkp::Nullifier); } -#[wasm_bindgen] -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct Nullifier(ironfish_zkp::Nullifier); - #[wasm_bindgen] impl Nullifier { #[wasm_bindgen(js_name = toBytes)] @@ -122,21 +79,10 @@ impl Nullifier { } } -impl From for Nullifier { - fn from(s: ironfish_zkp::Nullifier) -> Self { - Self(s) - } +wasm_bindgen_wrapper! { + pub struct PrivateKey(redjubjub::PrivateKey); } -impl AsRef for Nullifier { - fn as_ref(&self) -> &ironfish_zkp::Nullifier { - &self.0 - } -} - -#[wasm_bindgen] -pub struct PrivateKey(redjubjub::PrivateKey); - #[wasm_bindgen] impl PrivateKey { #[wasm_bindgen(constructor)] @@ -168,22 +114,11 @@ impl PrivateKey { } } -impl From for PrivateKey { - fn from(p: redjubjub::PrivateKey) -> Self { - Self(p) - } -} - -impl AsRef for PrivateKey { - fn as_ref(&self) -> &redjubjub::PrivateKey { - &self.0 - } +wasm_bindgen_wrapper! { + #[derive(Clone, Debug)] + pub struct PublicKey(redjubjub::PublicKey); } -#[wasm_bindgen] -#[derive(Clone, Debug)] -pub struct PublicKey(redjubjub::PublicKey); - #[wasm_bindgen] impl PublicKey { #[wasm_bindgen(constructor)] @@ -218,22 +153,11 @@ impl PublicKey { } } -impl From for PublicKey { - fn from(p: redjubjub::PublicKey) -> Self { - Self(p) - } -} - -impl AsRef for PublicKey { - fn as_ref(&self) -> &redjubjub::PublicKey { - &self.0 - } +wasm_bindgen_wrapper! { + #[derive(Clone, Debug)] + pub struct Signature(redjubjub::Signature); } -#[wasm_bindgen] -#[derive(Clone, Debug)] -pub struct Signature(redjubjub::Signature); - #[wasm_bindgen] impl Signature { #[wasm_bindgen(constructor)] @@ -249,15 +173,3 @@ impl Signature { buf } } - -impl From for Signature { - fn from(s: redjubjub::Signature) -> Self { - Self(s) - } -} - -impl AsRef for Signature { - fn as_ref(&self) -> &redjubjub::Signature { - &self.0 - } -} diff --git a/ironfish-rust-wasm/src/transaction/burns.rs b/ironfish-rust-wasm/src/transaction/burns.rs index 7224e4b9db..f5b3d603fc 100644 --- a/ironfish-rust-wasm/src/transaction/burns.rs +++ b/ironfish-rust-wasm/src/transaction/burns.rs @@ -2,12 +2,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use crate::{assets::AssetIdentifier, errors::IronfishError}; +use crate::{assets::AssetIdentifier, errors::IronfishError, wasm_bindgen_wrapper}; use wasm_bindgen::prelude::*; -#[wasm_bindgen] -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct BurnDescription(ironfish::transaction::burns::BurnDescription); +wasm_bindgen_wrapper! { + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct BurnDescription(ironfish::transaction::burns::BurnDescription); +} #[wasm_bindgen] impl BurnDescription { @@ -37,15 +38,3 @@ impl BurnDescription { self.0.value } } - -impl From for BurnDescription { - fn from(d: ironfish::transaction::burns::BurnDescription) -> Self { - Self(d) - } -} - -impl AsRef for BurnDescription { - fn as_ref(&self) -> &ironfish::transaction::burns::BurnDescription { - &self.0 - } -} diff --git a/ironfish-rust-wasm/src/transaction/mints.rs b/ironfish-rust-wasm/src/transaction/mints.rs index d9975c3555..ba03caca8d 100644 --- a/ironfish-rust-wasm/src/transaction/mints.rs +++ b/ironfish-rust-wasm/src/transaction/mints.rs @@ -7,13 +7,15 @@ use crate::{ errors::IronfishError, keys::PublicAddress, primitives::{PublicKey, Scalar}, + wasm_bindgen_wrapper, }; use ironfish::{errors::IronfishErrorKind, transaction::TransactionVersion}; use wasm_bindgen::prelude::*; -#[wasm_bindgen] -#[derive(Clone, Debug)] -pub struct MintDescription(ironfish::transaction::mints::MintDescription); +wasm_bindgen_wrapper! { + #[derive(Clone, Debug)] + pub struct MintDescription(ironfish::transaction::mints::MintDescription); +} #[wasm_bindgen] impl MintDescription { @@ -77,15 +79,3 @@ impl MintDescription { .collect() } } - -impl From for MintDescription { - fn from(d: ironfish::transaction::mints::MintDescription) -> Self { - Self(d) - } -} - -impl AsRef for MintDescription { - fn as_ref(&self) -> &ironfish::transaction::mints::MintDescription { - &self.0 - } -} diff --git a/ironfish-rust-wasm/src/transaction/mod.rs b/ironfish-rust-wasm/src/transaction/mod.rs index 18d2d0b65a..4e534bc18e 100644 --- a/ironfish-rust-wasm/src/transaction/mod.rs +++ b/ironfish-rust-wasm/src/transaction/mod.rs @@ -7,7 +7,7 @@ mod mints; mod outputs; mod spends; -use crate::{errors::IronfishError, primitives::PublicKey}; +use crate::{errors::IronfishError, primitives::PublicKey, wasm_bindgen_wrapper}; use wasm_bindgen::prelude::*; pub use burns::BurnDescription; @@ -15,9 +15,10 @@ pub use mints::MintDescription; pub use outputs::OutputDescription; pub use spends::SpendDescription; -#[wasm_bindgen] -#[derive(Clone, Debug)] -pub struct Transaction(ironfish::Transaction); +wasm_bindgen_wrapper! { + #[derive(Clone, Debug)] + pub struct Transaction(ironfish::Transaction); +} #[wasm_bindgen] impl Transaction { @@ -99,18 +100,6 @@ impl Transaction { } } -impl From for Transaction { - fn from(t: ironfish::Transaction) -> Self { - Self(t) - } -} - -impl AsRef for Transaction { - fn as_ref(&self) -> &ironfish::Transaction { - &self.0 - } -} - #[cfg(test)] mod tests { use super::Transaction; diff --git a/ironfish-rust-wasm/src/transaction/outputs.rs b/ironfish-rust-wasm/src/transaction/outputs.rs index 5f63b72ad1..64ed7b4c17 100644 --- a/ironfish-rust-wasm/src/transaction/outputs.rs +++ b/ironfish-rust-wasm/src/transaction/outputs.rs @@ -6,12 +6,14 @@ use crate::{ errors::IronfishError, merkle_note::MerkleNote, primitives::{PublicKey, Scalar}, + wasm_bindgen_wrapper, }; use wasm_bindgen::prelude::*; -#[wasm_bindgen] -#[derive(Clone, PartialEq, Debug)] -pub struct OutputDescription(ironfish::OutputDescription); +wasm_bindgen_wrapper! { + #[derive(Clone, PartialEq, Debug)] + pub struct OutputDescription(ironfish::OutputDescription); +} #[wasm_bindgen] impl OutputDescription { @@ -48,15 +50,3 @@ impl OutputDescription { self.0.merkle_note().into() } } - -impl From for OutputDescription { - fn from(d: ironfish::OutputDescription) -> Self { - Self(d) - } -} - -impl AsRef for OutputDescription { - fn as_ref(&self) -> &ironfish::OutputDescription { - &self.0 - } -} diff --git a/ironfish-rust-wasm/src/transaction/spends.rs b/ironfish-rust-wasm/src/transaction/spends.rs index 6ce969b2fc..92eadaa2c8 100644 --- a/ironfish-rust-wasm/src/transaction/spends.rs +++ b/ironfish-rust-wasm/src/transaction/spends.rs @@ -5,13 +5,15 @@ use crate::{ errors::IronfishError, primitives::{Nullifier, PublicKey, Scalar}, + wasm_bindgen_wrapper, }; use ironfish::errors::IronfishErrorKind; use wasm_bindgen::prelude::*; -#[wasm_bindgen] -#[derive(Clone, Debug)] -pub struct SpendDescription(ironfish::SpendDescription); +wasm_bindgen_wrapper! { + #[derive(Clone, Debug)] + pub struct SpendDescription(ironfish::SpendDescription); +} #[wasm_bindgen] impl SpendDescription { @@ -72,15 +74,3 @@ impl SpendDescription { .collect() } } - -impl From for SpendDescription { - fn from(d: ironfish::SpendDescription) -> Self { - Self(d) - } -} - -impl AsRef for SpendDescription { - fn as_ref(&self) -> &ironfish::SpendDescription { - &self.0 - } -} From cb31f8d39bd5cd4d6aaf7413e4a813dfb695cd98 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Fri, 22 Nov 2024 12:24:20 -0800 Subject: [PATCH 41/68] moves encryption/decryption logic into MasterKey (#5636) adds encrypt and decrypt methods to the MasterKey class to encapsulate the logic for encrypting and decrypting arbitrary blobs updates wallet encryption to use MasterKey for account encryption and decryption changes MasterKey interface to isolate key derivation to encrypt and decrypt methods, so that clients interact with the XChaCha20Poly1305 key only through the MasterKey interface - changes unlock to only set the masterKey property on MasterKey instead of returning the key - removes unused 'deriveKey' and 'deriveNewKey' methods allows decrypting account data in migrations independent of the Account and EncryptedAccount models --- ironfish/src/wallet/account/account.test.ts | 8 ++--- ironfish/src/wallet/account/account.ts | 9 +++-- .../wallet/account/encryptedAccount.test.ts | 10 +++--- .../src/wallet/account/encryptedAccount.ts | 8 ++--- ironfish/src/wallet/masterKey.test.ts | 28 +++++++++------ ironfish/src/wallet/masterKey.ts | 36 ++++++++++++++----- ironfish/src/wallet/wallet.test.ts | 18 +++++----- ironfish/src/wallet/wallet.ts | 4 +-- .../src/wallet/walletdb/accountValue.test.ts | 10 +++--- ironfish/src/wallet/walletdb/walletdb.test.ts | 6 ++-- ironfish/src/wallet/walletdb/walletdb.ts | 4 +-- 11 files changed, 82 insertions(+), 59 deletions(-) diff --git a/ironfish/src/wallet/account/account.test.ts b/ironfish/src/wallet/account/account.test.ts index 47cb5b61e0..73521eed3a 100644 --- a/ironfish/src/wallet/account/account.test.ts +++ b/ironfish/src/wallet/account/account.test.ts @@ -232,8 +232,8 @@ describe('Accounts', () => { const masterKey = node.wallet['masterKey'] Assert.isNotNull(masterKey) - const key = await masterKey.unlock(passphrase) - const decryptedAccount = encryptedAccount.decrypt(key) + await masterKey.unlock(passphrase) + const decryptedAccount = encryptedAccount.decrypt(masterKey) expect(decryptedAccount.name).toEqual(newName) }) @@ -2676,10 +2676,10 @@ describe('Accounts', () => { const passphrase = 'foo' const masterKey = MasterKey.generate(passphrase) - const key = await masterKey.unlock(passphrase) + await masterKey.unlock(passphrase) const encryptedAccount = account.encrypt(masterKey) - const decryptedAccount = encryptedAccount.decrypt(key) + const decryptedAccount = encryptedAccount.decrypt(masterKey) expect(account.serialize()).toMatchObject(decryptedAccount.serialize()) }) diff --git a/ironfish/src/wallet/account/account.ts b/ironfish/src/wallet/account/account.ts index 51f45a4aed..1450468c79 100644 --- a/ironfish/src/wallet/account/account.ts +++ b/ironfish/src/wallet/account/account.ts @@ -1334,15 +1334,14 @@ export class Account { encrypt(masterKey: MasterKey): EncryptedAccount { const encoder = new AccountValueEncoding() const serialized = encoder.serialize(this.serialize()) - const derivedKey = masterKey.deriveNewKey() - const data = derivedKey.encrypt(serialized) + const { ciphertext, salt, nonce } = masterKey.encrypt(serialized) return new EncryptedAccount({ accountValue: { encrypted: true, - data, - salt: derivedKey.salt(), - nonce: derivedKey.nonce(), + data: ciphertext, + salt, + nonce, }, walletDb: this.walletDb, }) diff --git a/ironfish/src/wallet/account/encryptedAccount.test.ts b/ironfish/src/wallet/account/encryptedAccount.test.ts index 6dac099c3b..10218ffc9c 100644 --- a/ironfish/src/wallet/account/encryptedAccount.test.ts +++ b/ironfish/src/wallet/account/encryptedAccount.test.ts @@ -15,10 +15,10 @@ describe('EncryptedAccount', () => { const account = await useAccountFixture(node.wallet) const masterKey = MasterKey.generate(passphrase) - const key = await masterKey.unlock(passphrase) + await masterKey.unlock(passphrase) const encryptedAccount = account.encrypt(masterKey) - const decryptedAccount = encryptedAccount.decrypt(key) + const decryptedAccount = encryptedAccount.decrypt(masterKey) expect(account.serialize()).toMatchObject(decryptedAccount.serialize()) }) @@ -31,11 +31,13 @@ describe('EncryptedAccount', () => { const masterKey = MasterKey.generate(passphrase) const invalidMasterKey = MasterKey.generate(invalidPassphrase) - const invalidKey = await invalidMasterKey.unlock(passphrase) + await invalidMasterKey.unlock(passphrase) await masterKey.unlock(passphrase) const encryptedAccount = account.encrypt(masterKey) - expect(() => encryptedAccount.decrypt(invalidKey)).toThrow(AccountDecryptionFailedError) + expect(() => encryptedAccount.decrypt(invalidMasterKey)).toThrow( + AccountDecryptionFailedError, + ) }) }) diff --git a/ironfish/src/wallet/account/encryptedAccount.ts b/ironfish/src/wallet/account/encryptedAccount.ts index 44f321d4d7..cc2456a7c7 100644 --- a/ironfish/src/wallet/account/encryptedAccount.ts +++ b/ironfish/src/wallet/account/encryptedAccount.ts @@ -1,8 +1,8 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { xchacha20poly1305 } from '@ironfish/rust-nodejs' import { AccountDecryptionFailedError } from '../errors' +import { MasterKey } from '../masterKey' import { AccountValueEncoding, EncryptedAccountValue } from '../walletdb/accountValue' import { WalletDB } from '../walletdb/walletdb' import { Account } from './account' @@ -26,11 +26,9 @@ export class EncryptedAccount { this.walletDb = walletDb } - decrypt(masterKey: xchacha20poly1305.XChaCha20Poly1305Key): Account { + decrypt(masterKey: MasterKey): Account { try { - const key = masterKey.deriveKey(this.salt, this.nonce) - const decryptedAccountValue = key.decrypt(this.data) - key.destroy() + const decryptedAccountValue = masterKey.decrypt(this.data, this.salt, this.nonce) const encoder = new AccountValueEncoding() const accountValue = encoder.deserializeDecrypted(decryptedAccountValue) diff --git a/ironfish/src/wallet/masterKey.test.ts b/ironfish/src/wallet/masterKey.test.ts index 7af4ec390b..32c8d39356 100644 --- a/ironfish/src/wallet/masterKey.test.ts +++ b/ironfish/src/wallet/masterKey.test.ts @@ -1,6 +1,7 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import { Assert } from '../assert' import { MasterKey } from './masterKey' describe('MasterKey', () => { @@ -9,29 +10,34 @@ describe('MasterKey', () => { const masterKey = MasterKey.generate(passphrase) const duplicate = new MasterKey({ nonce: masterKey.nonce, salt: masterKey.salt }) - const key = await masterKey.unlock(passphrase) - const reconstructed = await duplicate.unlock(passphrase) - expect(key.key().equals(reconstructed.key())).toBe(true) + await masterKey.unlock(passphrase) + await duplicate.unlock(passphrase) + Assert.isNotNull(masterKey['masterKey']) + Assert.isNotNull(duplicate['masterKey']) + expect(masterKey['masterKey'].key().equals(duplicate['masterKey'].key())).toBe(true) }) - it('can regenerate the child key from parts', async () => { + it('can save and remove the xchacha20poly1305 in memory', async () => { const passphrase = 'foobar' const masterKey = MasterKey.generate(passphrase) + await masterKey.unlock(passphrase) + expect(masterKey['masterKey']).not.toBeNull() - const childKey = masterKey.deriveNewKey() - const duplicate = masterKey.deriveKey(childKey.salt(), childKey.nonce()) - expect(childKey.key().equals(duplicate.key())).toBe(true) + await masterKey.lock() + expect(masterKey['masterKey']).toBeNull() }) - it('can save and remove the xchacha20poly1305 in memory', async () => { + it('can decrypt encrypted ciphertext', async () => { const passphrase = 'foobar' const masterKey = MasterKey.generate(passphrase) await masterKey.unlock(passphrase) - expect(masterKey['masterKey']).not.toBeNull() - await masterKey.lock() - expect(masterKey['masterKey']).toBeNull() + const plaintext = Buffer.from('ironfish') + + const { ciphertext, salt, nonce } = masterKey.encrypt(plaintext) + + expect(masterKey.decrypt(ciphertext, salt, nonce)).toEqual(plaintext) }) }) diff --git a/ironfish/src/wallet/masterKey.ts b/ironfish/src/wallet/masterKey.ts index c75eb1c3d5..722054bae7 100644 --- a/ironfish/src/wallet/masterKey.ts +++ b/ironfish/src/wallet/masterKey.ts @@ -52,7 +52,7 @@ export class MasterKey { } } - async unlock(passphrase: string): Promise { + async unlock(passphrase: string): Promise { const unlock = await this.mutex.lock() try { @@ -63,7 +63,7 @@ export class MasterKey { ) this.locked = false - return this.masterKey + return } catch (e) { if (this.masterKey) { this.masterKey.destroy() @@ -77,21 +77,39 @@ export class MasterKey { } } - deriveNewKey(): xchacha20poly1305.XChaCha20Poly1305Key { + async destroy(): Promise { + await this.lock() + } + + encrypt(plaintext: Buffer): { + ciphertext: Buffer + salt: Buffer + nonce: Buffer + } { Assert.isFalse(this.locked) Assert.isNotNull(this.masterKey) - return this.masterKey.deriveNewKey() + const derivedKey = this.masterKey.deriveNewKey() + + const ciphertext = derivedKey.encrypt(plaintext) + + return { + ciphertext, + salt: derivedKey.salt(), + nonce: derivedKey.nonce(), + } } - deriveKey(salt: Buffer, nonce: Buffer): xchacha20poly1305.XChaCha20Poly1305Key { + decrypt(ciphertext: Buffer, salt: Buffer, nonce: Buffer): Buffer { Assert.isFalse(this.locked) Assert.isNotNull(this.masterKey) - return this.masterKey.deriveKey(salt, nonce) - } + const derivedKey = this.masterKey.deriveKey(salt, nonce) - async destroy(): Promise { - await this.lock() + const plaintext = derivedKey.decrypt(ciphertext) + + derivedKey.destroy() + + return plaintext } } diff --git a/ironfish/src/wallet/wallet.test.ts b/ironfish/src/wallet/wallet.test.ts index f6b1213e59..3398ce23d0 100644 --- a/ironfish/src/wallet/wallet.test.ts +++ b/ironfish/src/wallet/wallet.test.ts @@ -1066,8 +1066,8 @@ describe('Wallet', () => { const masterKey = node.wallet['masterKey'] Assert.isNotNull(masterKey) - const key = await masterKey.unlock(passphrase) - const decryptedAccount = encryptedAccount.decrypt(key) + await masterKey.unlock(passphrase) + const decryptedAccount = encryptedAccount.decrypt(masterKey) await node.wallet.lock() expect(decryptedAccount.spendingKey).toEqual(account.spendingKey) @@ -2470,8 +2470,8 @@ describe('Wallet', () => { const masterKey = node.wallet['masterKey'] Assert.isNotNull(masterKey) - const key = await masterKey.unlock(passphrase) - const decryptedAccount = encryptedAccount.decrypt(key) + await masterKey.unlock(passphrase) + const decryptedAccount = encryptedAccount.decrypt(masterKey) await node.wallet.lock() expect(decryptedAccount.name).toEqual(account.name) @@ -2694,16 +2694,16 @@ describe('Wallet', () => { const masterKey = node.wallet['masterKey'] Assert.isNotNull(masterKey) - const key = await masterKey.unlock(passphrase) + await masterKey.unlock(passphrase) const encryptedAccountA = node.wallet.encryptedAccountById.get(accountA.id) Assert.isNotUndefined(encryptedAccountA) - const decryptedAccountA = encryptedAccountA.decrypt(key) + const decryptedAccountA = encryptedAccountA.decrypt(masterKey) expect(accountA.serialize()).toMatchObject(decryptedAccountA.serialize()) const encryptedAccountB = node.wallet.encryptedAccountById.get(accountB.id) Assert.isNotUndefined(encryptedAccountB) - const decryptedAccountB = encryptedAccountB.decrypt(key) + const decryptedAccountB = encryptedAccountB.decrypt(masterKey) expect(accountB.serialize()).toMatchObject(decryptedAccountB.serialize()) }) }) @@ -2840,12 +2840,12 @@ describe('Wallet', () => { const masterKey = node.wallet['masterKey'] Assert.isNotNull(masterKey) - const key = await masterKey.unlock(passphrase) + await masterKey.unlock(passphrase) for (const [id, account] of node.wallet.accountById.entries()) { const encryptedAccount = node.wallet.encryptedAccountById.get(id) Assert.isNotUndefined(encryptedAccount) - const decryptedAccount = encryptedAccount.decrypt(key) + const decryptedAccount = encryptedAccount.decrypt(masterKey) expect(account.serialize()).toMatchObject(decryptedAccount.serialize()) } diff --git a/ironfish/src/wallet/wallet.ts b/ironfish/src/wallet/wallet.ts index 81037d5dd1..2b9130f3b5 100644 --- a/ironfish/src/wallet/wallet.ts +++ b/ironfish/src/wallet/wallet.ts @@ -2030,10 +2030,10 @@ export class Wallet { } Assert.isNotNull(this.masterKey) - const key = await this.masterKey.unlock(passphrase) + await this.masterKey.unlock(passphrase) for (const [id, account] of this.encryptedAccountById.entries()) { - this.accountById.set(id, account.decrypt(key)) + this.accountById.set(id, account.decrypt(this.masterKey)) } this.startUnlockTimeout(timeout) diff --git a/ironfish/src/wallet/walletdb/accountValue.test.ts b/ironfish/src/wallet/walletdb/accountValue.test.ts index 252f065dbd..ee716a96b5 100644 --- a/ironfish/src/wallet/walletdb/accountValue.test.ts +++ b/ironfish/src/wallet/walletdb/accountValue.test.ts @@ -1,7 +1,7 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { generateKey, xchacha20poly1305 } from '@ironfish/rust-nodejs' +import { generateKey } from '@ironfish/rust-nodejs' import { MasterKey } from '../masterKey' import { AccountValueEncoding, @@ -91,16 +91,16 @@ describe('AccountValueEncoding', () => { const passphrase = 'foobarbaz' const masterKey = MasterKey.generate(passphrase) - const xchacha20poly1305Key = await masterKey.unlock(passphrase) + await masterKey.unlock(passphrase) const data = encoder.serialize(value) - const encryptedData = xchacha20poly1305Key.encrypt(data) + const { ciphertext: encryptedData, salt, nonce } = masterKey.encrypt(data) const encryptedValue: EncryptedAccountValue = { encrypted: true, data: encryptedData, - salt: Buffer.alloc(xchacha20poly1305.XSALT_LENGTH), - nonce: Buffer.alloc(xchacha20poly1305.XNONCE_LENGTH), + salt, + nonce, } const buffer = encoder.serialize(encryptedValue) diff --git a/ironfish/src/wallet/walletdb/walletdb.test.ts b/ironfish/src/wallet/walletdb/walletdb.test.ts index e1d12e56d2..d24515d7e9 100644 --- a/ironfish/src/wallet/walletdb/walletdb.test.ts +++ b/ironfish/src/wallet/walletdb/walletdb.test.ts @@ -485,16 +485,16 @@ describe('WalletDB', () => { const masterKeyValue = await walletDb.loadMasterKey() Assert.isNotNull(masterKeyValue) const masterKey = new MasterKey(masterKeyValue) - const key = await masterKey.unlock(passphrase) + await masterKey.unlock(passphrase) const encryptedAccountA = encryptedAccountById.get(accountA.id) Assert.isNotUndefined(encryptedAccountA) - const decryptedAccountA = encryptedAccountA.decrypt(key) + const decryptedAccountA = encryptedAccountA.decrypt(masterKey) expect(accountA.serialize()).toMatchObject(decryptedAccountA.serialize()) const encryptedAccountB = encryptedAccountById.get(accountB.id) Assert.isNotUndefined(encryptedAccountB) - const decryptedAccountB = encryptedAccountB.decrypt(key) + const decryptedAccountB = encryptedAccountB.decrypt(masterKey) expect(accountB.serialize()).toMatchObject(decryptedAccountB.serialize()) }) }) diff --git a/ironfish/src/wallet/walletdb/walletdb.ts b/ironfish/src/wallet/walletdb/walletdb.ts index 31d10911ac..13c8e2e9b6 100644 --- a/ironfish/src/wallet/walletdb/walletdb.ts +++ b/ironfish/src/wallet/walletdb/walletdb.ts @@ -1186,7 +1186,7 @@ export class WalletDB { Assert.isNotNull(masterKeyValue) const masterKey = new MasterKey(masterKeyValue) - const key = await masterKey.unlock(passphrase) + await masterKey.unlock(passphrase) for await (const [id, accountValue] of this.accounts.getAllIter(tx)) { if (!accountValue.encrypted) { @@ -1197,7 +1197,7 @@ export class WalletDB { accountValue, walletDb: this, }) - const account = encryptedAccount.decrypt(key) + const account = encryptedAccount.decrypt(masterKey) await this.accounts.put(id, account.serialize(), tx) } From baf410706219fcbc5ce466e0f9af185dbb3e097c Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Mon, 25 Nov 2024 10:40:37 -0800 Subject: [PATCH 42/68] upgrades multisig signing commands to use ui.ledger (#5670) the ui.ledger function includes error handling and retry logic that will be helpful when signing multisig transactions using the dkg ledger app --- .../wallet/multisig/commitment/create.ts | 16 ++++++---------- .../commands/wallet/multisig/signature/create.ts | 16 ++++++---------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/ironfish-cli/src/commands/wallet/multisig/commitment/create.ts b/ironfish-cli/src/commands/wallet/multisig/commitment/create.ts index f2e9103da2..f35a05cf03 100644 --- a/ironfish-cli/src/commands/wallet/multisig/commitment/create.ts +++ b/ironfish-cli/src/commands/wallet/multisig/commitment/create.ts @@ -125,20 +125,16 @@ export class CreateSigningCommitmentCommand extends IronfishCommand { signers: string[], ): Promise { const ledger = new LedgerMultiSigner() - try { - await ledger.connect() - } catch (e) { - if (e instanceof Error) { - this.error(e.message) - } else { - throw e - } - } const identityResponse = await client.wallet.multisig.getIdentity({ name: participantName }) const identity = identityResponse.content.identity - const rawCommitments = await ledger.dkgGetCommitments(unsignedTransaction) + const rawCommitments = await ui.ledger({ + ledger, + message: 'Get Commitments', + approval: true, + action: () => ledger.dkgGetCommitments(unsignedTransaction), + }) const signingCommitment = multisig.SigningCommitment.fromRaw( identity, diff --git a/ironfish-cli/src/commands/wallet/multisig/signature/create.ts b/ironfish-cli/src/commands/wallet/multisig/signature/create.ts index 7bc41efa17..9eb930ad49 100644 --- a/ironfish-cli/src/commands/wallet/multisig/signature/create.ts +++ b/ironfish-cli/src/commands/wallet/multisig/signature/create.ts @@ -121,20 +121,16 @@ export class CreateSignatureShareCommand extends IronfishCommand { frostSigningPackage: string, ): Promise { const ledger = new LedgerMultiSigner() - try { - await ledger.connect() - } catch (e) { - if (e instanceof Error) { - this.error(e.message) - } else { - throw e - } - } const identityResponse = await client.wallet.multisig.getIdentity({ name: participantName }) const identity = identityResponse.content.identity - const frostSignatureShare = await ledger.dkgSign(unsignedTransaction, frostSigningPackage) + const frostSignatureShare = await ui.ledger({ + ledger, + message: 'Sign Transaction', + approval: true, + action: () => ledger.dkgSign(unsignedTransaction, frostSigningPackage), + }) const signatureShare = multisig.SignatureShare.fromFrost( frostSignatureShare, From 117d9ab4be4496a41f984fb04ab601fa6d249dae Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Mon, 25 Nov 2024 10:41:13 -0800 Subject: [PATCH 43/68] converts dkg commands to use ui.ledger (#5669) the ui.ledger function includes error handling and retry logic that could be useful in each of the standalone dkg commands ('participant:create', 'dkg:round1', 'dkg:round2', and 'dkg:round3') --- .../commands/wallet/multisig/dkg/round1.ts | 17 ++---- .../commands/wallet/multisig/dkg/round2.ts | 21 ++----- .../commands/wallet/multisig/dkg/round3.ts | 56 +++++++++++-------- .../wallet/multisig/participant/create.ts | 16 ++---- 4 files changed, 50 insertions(+), 60 deletions(-) diff --git a/ironfish-cli/src/commands/wallet/multisig/dkg/round1.ts b/ironfish-cli/src/commands/wallet/multisig/dkg/round1.ts index 488f786e1c..3edae07d70 100644 --- a/ironfish-cli/src/commands/wallet/multisig/dkg/round1.ts +++ b/ironfish-cli/src/commands/wallet/multisig/dkg/round1.ts @@ -101,15 +101,6 @@ export class DkgRound1Command extends IronfishCommand { minSigners: number, ): Promise { const ledger = new LedgerMultiSigner() - try { - await ledger.connect() - } catch (e) { - if (e instanceof Error) { - this.error(e.message) - } else { - throw e - } - } const identityResponse = await client.wallet.multisig.getIdentity({ name: participantName }) const identity = identityResponse.content.identity @@ -118,8 +109,12 @@ export class DkgRound1Command extends IronfishCommand { identities.push(identity) } - // TODO(hughy): determine how to handle multiple identities using index - const { publicPackage, secretPackage } = await ledger.dkgRound1(0, identities, minSigners) + const { publicPackage, secretPackage } = await ui.ledger({ + ledger, + message: 'Round1 on Ledger', + approval: true, + action: () => ledger.dkgRound1(0, identities, minSigners), + }) this.log('\nRound 1 Encrypted Secret Package:\n') this.log(secretPackage.toString('hex')) diff --git a/ironfish-cli/src/commands/wallet/multisig/dkg/round2.ts b/ironfish-cli/src/commands/wallet/multisig/dkg/round2.ts index a09afdb7aa..d51b9582dc 100644 --- a/ironfish-cli/src/commands/wallet/multisig/dkg/round2.ts +++ b/ironfish-cli/src/commands/wallet/multisig/dkg/round2.ts @@ -98,22 +98,13 @@ export class DkgRound2Command extends IronfishCommand { round1SecretPackage: string, ): Promise { const ledger = new LedgerMultiSigner() - try { - await ledger.connect() - } catch (e) { - if (e instanceof Error) { - this.error(e.message) - } else { - throw e - } - } - // TODO(hughy): determine how to handle multiple identities using index - const { publicPackage, secretPackage } = await ledger.dkgRound2( - 0, - round1PublicPackages, - round1SecretPackage, - ) + const { publicPackage, secretPackage } = await ui.ledger({ + ledger, + message: 'Round2 on Ledger', + approval: true, + action: () => ledger.dkgRound2(0, round1PublicPackages, round1SecretPackage), + }) this.log('\nRound 2 Encrypted Secret Package:\n') this.log(secretPackage.toString('hex')) diff --git a/ironfish-cli/src/commands/wallet/multisig/dkg/round3.ts b/ironfish-cli/src/commands/wallet/multisig/dkg/round3.ts index 02541ef461..15794f71c6 100644 --- a/ironfish-cli/src/commands/wallet/multisig/dkg/round3.ts +++ b/ironfish-cli/src/commands/wallet/multisig/dkg/round3.ts @@ -163,15 +163,6 @@ export class DkgRound3Command extends IronfishCommand { accountCreatedAt?: number, ): Promise { const ledger = new LedgerMultiSigner() - try { - await ledger.connect() - } catch (e) { - if (e instanceof Error) { - this.error(e.message) - } else { - throw e - } - } const identityResponse = await client.wallet.multisig.getIdentity({ name: participantName }) const identity = identityResponse.content.identity @@ -192,9 +183,9 @@ export class DkgRound3Command extends IronfishCommand { .sort((a, b) => a.senderIdentity.localeCompare(b.senderIdentity)) // Extract raw parts from round1 and round2 public packages - const participants = [] - const round1FrostPackages = [] - const gskBytes = [] + const participants: string[] = [] + const round1FrostPackages: string[] = [] + const gskBytes: string[] = [] for (const pkg of round1PublicPackages) { // Exclude participant's own identity and round1 public package if (pkg.identity !== identity) { @@ -208,19 +199,33 @@ export class DkgRound3Command extends IronfishCommand { const round2FrostPackages = round2PublicPackages.map((pkg) => pkg.frostPackage) // Perform round3 with Ledger - await ledger.dkgRound3( - 0, - participants, - round1FrostPackages, - round2FrostPackages, - round2SecretPackage, - gskBytes, - ) + await ui.ledger({ + ledger, + message: 'Round3 on Ledger', + approval: true, + action: () => + ledger.dkgRound3( + 0, + participants, + round1FrostPackages, + round2FrostPackages, + round2SecretPackage, + gskBytes, + ), + }) // Retrieve all multisig account keys and publicKeyPackage - const dkgKeys = await ledger.dkgRetrieveKeys() + const dkgKeys = await ui.ledger({ + ledger, + message: 'Getting Ledger DKG keys', + action: () => ledger.dkgRetrieveKeys(), + }) - const publicKeyPackage = await ledger.dkgGetPublicPackage() + const publicKeyPackage = await ui.ledger({ + ledger, + message: 'Getting Ledger Public Package', + action: () => ledger.dkgGetPublicPackage(), + }) const accountImport = { ...dkgKeys, @@ -250,7 +255,12 @@ export class DkgRound3Command extends IronfishCommand { this.log('Creating an encrypted backup of multisig keys from your Ledger device...') this.log() - const encryptedKeys = await ledger.dkgBackupKeys() + const encryptedKeys = await ui.ledger({ + ledger, + message: 'Backup DKG Keys', + approval: true, + action: () => ledger.dkgBackupKeys(), + }) this.log() this.log('Encrypted Ledger Multisig Backup:') diff --git a/ironfish-cli/src/commands/wallet/multisig/participant/create.ts b/ironfish-cli/src/commands/wallet/multisig/participant/create.ts index d9e7df0f66..06c1e43055 100644 --- a/ironfish-cli/src/commands/wallet/multisig/participant/create.ts +++ b/ironfish-cli/src/commands/wallet/multisig/participant/create.ts @@ -72,17 +72,11 @@ export class MultisigIdentityCreate extends IronfishCommand { async getIdentityFromLedger(): Promise { const ledger = new LedgerMultiSigner() - try { - await ledger.connect() - } catch (e) { - if (e instanceof Error) { - this.error(e.message) - } else { - throw e - } - } - // TODO(hughy): support multiple identities using index - return ledger.dkgGetIdentity(0) + return ui.ledger({ + ledger, + message: 'Getting Ledger Identity', + action: () => ledger.dkgGetIdentity(0), + }) } } From 750cdf15c5eaf8d2ee0703e2426de83907d1aa3e Mon Sep 17 00:00:00 2001 From: Rahul Patni Date: Mon, 25 Nov 2024 12:40:51 -0800 Subject: [PATCH 44/68] Too many warnings for connection reattempt (#5649) We can move this to a debug log, as it is not a critical error. --- ironfish/src/network/peers/peer.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ironfish/src/network/peers/peer.ts b/ironfish/src/network/peers/peer.ts index 5889f58516..79035f3805 100644 --- a/ironfish/src/network/peers/peer.ts +++ b/ironfish/src/network/peers/peer.ts @@ -294,7 +294,13 @@ export class Peer { */ setWebSocketConnection(connection: WebSocketConnection): void { if (this.state.type !== 'DISCONNECTED' && this.state.connections.webSocket) { - this.logger.warn('Already have a WebSocket connection, ignoring the new one') + this.logger.debug( + `Peer ${this.displayName} already has a websocket connection to ${ + this.address + }, ignoring new one at ${ + connection.address ? connection.address.host : 'Unknown host' + }`, + ) return } From 6bdd3ecef8350f60892dda5da5a577650a4fe6f7 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:30:57 -0800 Subject: [PATCH 45/68] do not retry ExitErrors in ui.retryStep (#5668) if the command is exiting due to user action (e.g., choosing not to continue) or an un-retryable problem then we should not prompt to retry --- ironfish-cli/src/ui/retry.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ironfish-cli/src/ui/retry.ts b/ironfish-cli/src/ui/retry.ts index addc2038eb..ae948603a1 100644 --- a/ironfish-cli/src/ui/retry.ts +++ b/ironfish-cli/src/ui/retry.ts @@ -3,6 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { ErrorUtils, Logger } from '@ironfish/sdk' import { ux } from '@oclif/core' +import { ExitError } from '@oclif/core/errors' import { confirmList } from './prompt' export async function retryStep( @@ -18,6 +19,10 @@ export async function retryStep( const result = await stepFunction() return result } catch (error) { + if (error instanceof ExitError) { + throw error + } + logger.log(`An Error Occurred: ${ErrorUtils.renderError(error)}`) if (askToRetry) { From 0691c63a6ce3dd4cb3751e9867e5616de281bb92 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Tue, 26 Nov 2024 09:28:13 -0800 Subject: [PATCH 46/68] handles Ledger 'invalid dkg status' errors (#5667) if a user attempts to sign a transaction, but their ledger app doesn't have any multisig keys persisted, then the signing operation will fail with an 'invalid dkg status' error. this can happen if the user reinstalls the app or if the user starts creating a new account on the device using bkg, but doesn't finish the only ways to recover from this error are to restore an encrypted backup to the device, or to create a new multisig account using the device. since the error shows up during signing, we can infer that the user expects to have an account on the device and should be instructed to restore the backup --- ironfish-cli/src/ledger/ledger.ts | 4 ++++ ironfish-cli/src/ui/ledger.ts | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/ironfish-cli/src/ledger/ledger.ts b/ironfish-cli/src/ledger/ledger.ts index e537385b63..d1d11c3ee5 100644 --- a/ironfish-cli/src/ledger/ledger.ts +++ b/ironfish-cli/src/ledger/ledger.ts @@ -26,6 +26,7 @@ export const IronfishLedgerStatusCodes = { PANIC: 0xe000, EXPERT_MODE_REQUIRED: 0x6984, DKG_EXPERT_MODE_REQUIRED: 0xb027, + INVALID_DKG_STATUS: 0xb022, } export class Ledger { @@ -73,6 +74,8 @@ export class Ledger { throw new LedgerPanicError() } else if (error.returnCode === IronfishLedgerStatusCodes.GP_AUTH_FAILED) { throw new LedgerGPAuthFailed() + } else if (error.returnCode === IronfishLedgerStatusCodes.INVALID_DKG_STATUS) { + throw new LedgerInvalidDkgStatusError() } else if ( error.returnCode === IronfishLedgerStatusCodes.EXPERT_MODE_REQUIRED || error.returnCode === IronfishLedgerStatusCodes.DKG_EXPERT_MODE_REQUIRED @@ -191,3 +194,4 @@ export class LedgerActionRejected extends LedgerError {} export class LedgerInvalidTxHash extends LedgerError {} export class LedgerPanicError extends LedgerError {} export class LedgerExpertModeError extends LedgerError {} +export class LedgerInvalidDkgStatusError extends LedgerError {} diff --git a/ironfish-cli/src/ui/ledger.ts b/ironfish-cli/src/ui/ledger.ts index b7e5fc1796..ccbcb7ded2 100644 --- a/ironfish-cli/src/ui/ledger.ts +++ b/ironfish-cli/src/ui/ledger.ts @@ -21,6 +21,7 @@ import { LedgerDeviceLockedError, LedgerExpertModeError, LedgerGPAuthFailed, + LedgerInvalidDkgStatusError, LedgerPanicError, LedgerPortIsBusyError, LedgerSingleSigner, @@ -103,6 +104,12 @@ export async function ledger({ if (!wasRunning) { ux.action.start(message) } + } else if (e instanceof LedgerInvalidDkgStatusError) { + ux.action.stop('Ironfish DKG Ledger App does not have any multisig keys!') + ux.stdout( + 'Use `wallet:multisig:ledger:restore` to restore an encrypted backup to your Ledger', + ) + ux.exit(1) } else if (e instanceof LedgerActionRejected) { ux.action.status = 'User Rejected Ledger Request!' ux.stdout('User Rejected Ledger Request!') From f168064808cf63394d94adaaf742259dfbd76830 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Tue, 26 Nov 2024 11:00:14 -0800 Subject: [PATCH 47/68] adds template for migrations on encrypted wallets (#5655) * supports migrations on encrypted wallets adds a template migration , 033-encrypted-wallet-template, to demonstrate how to write wallet migrations on wallet databases that may be encrypted now that account data may be encrypted we need to be careful to decrypt the account before running the migration and to re-encrypt the migrated data before writing it back to the database adds EncryptedWalletMigrationError thrown when a migration tries to access an encrypted account without the wallet passphrase to allow client code (e.g., 'migrations:start' command) to handle decryption flow optionally passes wallet passphrase through migrator to an individual migration to decrypt/encrypt account data in migrations updates 'start' command to catch EncryptedWalletMigrationError and prompt for wallet passphrase. passes wallet passphrase through node openDB flow to suppoprt running migrator with wallet passphrase NOTE: this example assumes that the schema for the masterKey store does not change and assumes that the schema for encrypted account data does not change. if the masterKey schema changes then that change must be addressed in a separate migration. if the encrypted account schema changes, then that change should be addressed separately from any change to the decrypted account schema in a separate migration. * removes superfluous comments * updates template migration for changes to MasterKey removes util from migration data * converts encrypted-wallet-template to template renames 033-encrypted-wallet-template to 000-encrypted-wallet-template so that the migration is not runnable resets walletDb version to 32 --- .../data/000-encrypted-wallet-template.ts | 165 +++++++++++++ .../new/accountValue.ts | 229 ++++++++++++++++++ .../new/headValue.ts | 41 ++++ .../new/index.ts | 20 ++ .../new/multisigKeys.ts | 92 +++++++ .../old/accountValue.ts | 229 ++++++++++++++++++ .../old/headValue.ts | 41 ++++ .../old/index.ts | 31 +++ .../old/masterKeyValue.ts | 32 +++ .../old/multisigKeys.ts | 92 +++++++ .../000-encrypted-wallet-template/stores.ts | 16 ++ 11 files changed, 988 insertions(+) create mode 100644 ironfish/src/migrations/data/000-encrypted-wallet-template.ts create mode 100644 ironfish/src/migrations/data/000-encrypted-wallet-template/new/accountValue.ts create mode 100644 ironfish/src/migrations/data/000-encrypted-wallet-template/new/headValue.ts create mode 100644 ironfish/src/migrations/data/000-encrypted-wallet-template/new/index.ts create mode 100644 ironfish/src/migrations/data/000-encrypted-wallet-template/new/multisigKeys.ts create mode 100644 ironfish/src/migrations/data/000-encrypted-wallet-template/old/accountValue.ts create mode 100644 ironfish/src/migrations/data/000-encrypted-wallet-template/old/headValue.ts create mode 100644 ironfish/src/migrations/data/000-encrypted-wallet-template/old/index.ts create mode 100644 ironfish/src/migrations/data/000-encrypted-wallet-template/old/masterKeyValue.ts create mode 100644 ironfish/src/migrations/data/000-encrypted-wallet-template/old/multisigKeys.ts create mode 100644 ironfish/src/migrations/data/000-encrypted-wallet-template/stores.ts diff --git a/ironfish/src/migrations/data/000-encrypted-wallet-template.ts b/ironfish/src/migrations/data/000-encrypted-wallet-template.ts new file mode 100644 index 0000000000..233b604698 --- /dev/null +++ b/ironfish/src/migrations/data/000-encrypted-wallet-template.ts @@ -0,0 +1,165 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import { Assert } from '../../assert' +import { Logger } from '../../logger' +import { IDatabase, IDatabaseTransaction } from '../../storage' +import { createDB } from '../../storage/utils' +import { MasterKey } from '../../wallet/masterKey' +import { EncryptedWalletMigrationError } from '../errors' +import { Database, Migration, MigrationContext } from '../migration' +import { + AccountValueEncoding as NewAccountValueEncoding, + DecryptedAccountValue as NewDecryptedAccountValue, +} from './000-encrypted-wallet-template/new/accountValue' +import { + AccountValueEncoding as OldAccountValueEncoding, + DecryptedAccountValue as OldDecryptedAccountValue, + EncryptedAccountValue as OldEncryptedAccountValue, +} from './000-encrypted-wallet-template/old/accountValue' +import { GetStores } from './000-encrypted-wallet-template/stores' + +export class Migration000 extends Migration { + path = __filename + database = Database.WALLET + + prepare(context: MigrationContext): IDatabase { + return createDB({ location: context.config.walletDatabasePath }) + } + + async forward( + context: MigrationContext, + db: IDatabase, + tx: IDatabaseTransaction | undefined, + logger: Logger, + dryRun: boolean, + walletPassphrase: string | undefined, + ): Promise { + const stores = GetStores(db) + const oldEncoding = new OldAccountValueEncoding() + const newEncoding = new NewAccountValueEncoding() + + for await (const account of stores.old.accounts.getAllValuesIter(tx)) { + let decryptedAccount + + // Check if the account is encrypted, and throw an error to allow client + // code to prompt for passphrase. + if (account.encrypted) { + if (!walletPassphrase) { + throw new EncryptedWalletMigrationError('Cannot run migration on encrypted wallet') + } + + const masterKeyValue = await stores.old.masterKey.get('key') + Assert.isNotUndefined(masterKeyValue) + + const masterKey = new MasterKey(masterKeyValue) + await masterKey.unlock(walletPassphrase) + + // Decrypt encrypted account data + const decrypted = masterKey.decrypt(account.data, account.salt, account.nonce) + decryptedAccount = oldEncoding.deserializeDecrypted(decrypted) + + // Apply migration to decrypted account data + logger.info(` Migrating account ${decryptedAccount.name}`) + const migrated = this.accountForward(decryptedAccount) + + // Re-encrypt the migrated data and write it to the store. + // Assumes that schema for encrypted accounts has NOT changed. + const migratedSerialized = newEncoding.serialize(migrated) + const { ciphertext: data, salt, nonce } = masterKey.encrypt(migratedSerialized) + + const encryptedAccount: OldEncryptedAccountValue = { + encrypted: true, + salt, + nonce, + data, + } + + await stores.new.accounts.put(decryptedAccount.id, encryptedAccount, tx) + } else { + decryptedAccount = account + + logger.info(` Migrating account ${decryptedAccount.name}`) + const migrated = this.accountForward(decryptedAccount) + + await stores.new.accounts.put(decryptedAccount.id, migrated, tx) + } + } + } + + // Implement logic to migrate (decrypted) account data to the new schema + accountForward(oldValue: OldDecryptedAccountValue): NewDecryptedAccountValue { + const newValue = oldValue + return newValue + } + + /** + * Writing a backwards migration is optional but suggested + */ + async backward( + context: MigrationContext, + db: IDatabase, + tx: IDatabaseTransaction | undefined, + logger: Logger, + dryRun: boolean, + walletPassphrase: string | undefined, + ): Promise { + const stores = GetStores(db) + const oldEncoding = new OldAccountValueEncoding() + const newEncoding = new NewAccountValueEncoding() + + for await (const account of stores.new.accounts.getAllValuesIter(tx)) { + let decryptedAccount + + // Check if the account is encrypted, and throw an error to allow client + // code to prompt for passphrase. + if (account.encrypted) { + if (!walletPassphrase) { + throw new EncryptedWalletMigrationError('Cannot run migration on encrypted wallet') + } + + // Load master key from database + const masterKeyValue = await stores.old.masterKey.get('key') + Assert.isNotUndefined(masterKeyValue) + + const masterKey = new MasterKey(masterKeyValue) + await masterKey.unlock(walletPassphrase) + + // Decrypt encrypted account data + const decrypted = masterKey.decrypt(account.data, account.salt, account.nonce) + decryptedAccount = newEncoding.deserializeDecrypted(decrypted) + + // Apply migration to decrypted account data + logger.info(` Migrating account ${decryptedAccount.name}`) + const migrated = this.accountBackward(decryptedAccount) + + // Re-encrypt the migrated data and write it to the store. + // Assumes that schema for encrypted accounts has NOT changed. + const migratedSerialized = oldEncoding.serialize(migrated) + const { ciphertext: data, salt, nonce } = masterKey.encrypt(migratedSerialized) + + const encryptedAccount: OldEncryptedAccountValue = { + encrypted: true, + salt, + nonce, + data, + } + + await stores.old.accounts.put(decryptedAccount.id, encryptedAccount, tx) + } else { + decryptedAccount = account + + logger.info(` Migrating account ${decryptedAccount.name}`) + const migrated = this.accountBackward(decryptedAccount) + + await stores.old.accounts.put(decryptedAccount.id, migrated, tx) + } + } + } + + // Implement logic to rever (decrypted) account data to the old schema + accountBackward(newValue: NewDecryptedAccountValue): OldDecryptedAccountValue { + const oldValue = newValue + return oldValue + } +} diff --git a/ironfish/src/migrations/data/000-encrypted-wallet-template/new/accountValue.ts b/ironfish/src/migrations/data/000-encrypted-wallet-template/new/accountValue.ts new file mode 100644 index 0000000000..65b1854e73 --- /dev/null +++ b/ironfish/src/migrations/data/000-encrypted-wallet-template/new/accountValue.ts @@ -0,0 +1,229 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import { KEY_LENGTH, PUBLIC_ADDRESS_LENGTH, xchacha20poly1305 } from '@ironfish/rust-nodejs' +import bufio from 'bufio' +import { IDatabaseEncoding } from '../../../../storage' +import { MultisigKeys } from '../../../../wallet/interfaces/multisigKeys' +import { HeadValue, NullableHeadValueEncoding } from './headValue' +import { MultisigKeysEncoding } from './multisigKeys' + +export const VIEW_KEY_LENGTH = 64 +const VERSION_LENGTH = 2 + +export type EncryptedAccountValue = { + encrypted: true + salt: Buffer + nonce: Buffer + data: Buffer +} + +export type DecryptedAccountValue = { + encrypted: false + version: number + id: string + name: string + spendingKey: string | null + viewKey: string + incomingViewKey: string + outgoingViewKey: string + publicAddress: string + createdAt: HeadValue | null + scanningEnabled: boolean + multisigKeys?: MultisigKeys + proofAuthorizingKey: string | null +} + +export type AccountValue = EncryptedAccountValue | DecryptedAccountValue +export class AccountValueEncoding implements IDatabaseEncoding { + serialize(value: AccountValue): Buffer { + if (value.encrypted) { + return this.serializeEncrypted(value) + } else { + return this.serializeDecrypted(value) + } + } + + serializeEncrypted(value: EncryptedAccountValue): Buffer { + const bw = bufio.write(this.getSize(value)) + + let flags = 0 + flags |= Number(!!value.encrypted) << 5 + bw.writeU8(flags) + bw.writeBytes(value.salt) + bw.writeBytes(value.nonce) + bw.writeVarBytes(value.data) + + return bw.render() + } + + serializeDecrypted(value: DecryptedAccountValue): Buffer { + const bw = bufio.write(this.getSize(value)) + let flags = 0 + flags |= Number(!!value.spendingKey) << 0 + flags |= Number(!!value.createdAt) << 1 + flags |= Number(!!value.multisigKeys) << 2 + flags |= Number(!!value.proofAuthorizingKey) << 3 + flags |= Number(!!value.scanningEnabled) << 4 + flags |= Number(!!value.encrypted) << 5 + + bw.writeU8(flags) + bw.writeU16(value.version) + bw.writeVarString(value.id, 'utf8') + bw.writeVarString(value.name, 'utf8') + + if (value.spendingKey) { + bw.writeBytes(Buffer.from(value.spendingKey, 'hex')) + } + + bw.writeBytes(Buffer.from(value.viewKey, 'hex')) + bw.writeBytes(Buffer.from(value.incomingViewKey, 'hex')) + bw.writeBytes(Buffer.from(value.outgoingViewKey, 'hex')) + bw.writeBytes(Buffer.from(value.publicAddress, 'hex')) + + if (value.createdAt) { + const encoding = new NullableHeadValueEncoding() + bw.writeBytes(encoding.serialize(value.createdAt)) + } + + if (value.multisigKeys) { + const encoding = new MultisigKeysEncoding() + bw.writeU64(encoding.getSize(value.multisigKeys)) + bw.writeBytes(encoding.serialize(value.multisigKeys)) + } + + if (value.proofAuthorizingKey) { + bw.writeBytes(Buffer.from(value.proofAuthorizingKey, 'hex')) + } + + return bw.render() + } + + deserialize(buffer: Buffer): AccountValue { + const reader = bufio.read(buffer, true) + const flags = reader.readU8() + const encrypted = Boolean(flags & (1 << 5)) + + if (encrypted) { + return this.deserializeEncrypted(buffer) + } else { + return this.deserializeDecrypted(buffer) + } + } + + deserializeEncrypted(buffer: Buffer): EncryptedAccountValue { + const reader = bufio.read(buffer, true) + + // Skip flags + reader.readU8() + + const salt = reader.readBytes(xchacha20poly1305.XSALT_LENGTH) + const nonce = reader.readBytes(xchacha20poly1305.XNONCE_LENGTH) + const data = reader.readVarBytes() + return { + encrypted: true, + nonce, + salt, + data, + } + } + + deserializeDecrypted(buffer: Buffer): DecryptedAccountValue { + const reader = bufio.read(buffer, true) + const flags = reader.readU8() + const version = reader.readU16() + const hasSpendingKey = flags & (1 << 0) + const hasCreatedAt = flags & (1 << 1) + const hasMultisigKeys = flags & (1 << 2) + const hasProofAuthorizingKey = flags & (1 << 3) + const scanningEnabled = Boolean(flags & (1 << 4)) + const id = reader.readVarString('utf8') + const name = reader.readVarString('utf8') + const spendingKey = hasSpendingKey ? reader.readBytes(KEY_LENGTH).toString('hex') : null + const viewKey = reader.readBytes(VIEW_KEY_LENGTH).toString('hex') + const incomingViewKey = reader.readBytes(KEY_LENGTH).toString('hex') + const outgoingViewKey = reader.readBytes(KEY_LENGTH).toString('hex') + const publicAddress = reader.readBytes(PUBLIC_ADDRESS_LENGTH).toString('hex') + + let createdAt = null + if (hasCreatedAt) { + const encoding = new NullableHeadValueEncoding() + createdAt = encoding.deserialize(reader.readBytes(encoding.nonNullSize)) + } + + let multisigKeys = undefined + if (hasMultisigKeys) { + const multisigKeysLength = reader.readU64() + const encoding = new MultisigKeysEncoding() + multisigKeys = encoding.deserialize(reader.readBytes(multisigKeysLength)) + } + + const proofAuthorizingKey = hasProofAuthorizingKey + ? reader.readBytes(KEY_LENGTH).toString('hex') + : null + + return { + encrypted: false, + version, + id, + name, + viewKey, + incomingViewKey, + outgoingViewKey, + spendingKey, + publicAddress, + createdAt, + scanningEnabled, + multisigKeys, + proofAuthorizingKey, + } + } + + getSize(value: AccountValue): number { + if (value.encrypted) { + return this.getSizeEncrypted(value) + } else { + return this.getSizeDecrypted(value) + } + } + + getSizeEncrypted(value: EncryptedAccountValue): number { + let size = 0 + size += 1 // flags + size += xchacha20poly1305.XSALT_LENGTH + size += xchacha20poly1305.XNONCE_LENGTH + size += bufio.sizeVarBytes(value.data) + return size + } + + getSizeDecrypted(value: DecryptedAccountValue): number { + let size = 0 + size += 1 // flags + size += VERSION_LENGTH + size += bufio.sizeVarString(value.id, 'utf8') + size += bufio.sizeVarString(value.name, 'utf8') + if (value.spendingKey) { + size += KEY_LENGTH + } + size += VIEW_KEY_LENGTH + size += KEY_LENGTH + size += KEY_LENGTH + size += PUBLIC_ADDRESS_LENGTH + + if (value.createdAt) { + const encoding = new NullableHeadValueEncoding() + size += encoding.nonNullSize + } + + if (value.multisigKeys) { + const encoding = new MultisigKeysEncoding() + size += 8 // size of multi sig keys + size += encoding.getSize(value.multisigKeys) + } + if (value.proofAuthorizingKey) { + size += KEY_LENGTH + } + + return size + } +} diff --git a/ironfish/src/migrations/data/000-encrypted-wallet-template/new/headValue.ts b/ironfish/src/migrations/data/000-encrypted-wallet-template/new/headValue.ts new file mode 100644 index 0000000000..9da18b093c --- /dev/null +++ b/ironfish/src/migrations/data/000-encrypted-wallet-template/new/headValue.ts @@ -0,0 +1,41 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import bufio from 'bufio' +import { IDatabaseEncoding } from '../../../../storage' + +export type HeadValue = { + hash: Buffer + sequence: number +} + +export class NullableHeadValueEncoding implements IDatabaseEncoding { + readonly nonNullSize = 32 + 4 // 256-bit block hash + 32-bit integer + + serialize(value: HeadValue | null): Buffer { + const bw = bufio.write(this.getSize(value)) + + if (value) { + bw.writeHash(value.hash) + bw.writeU32(value.sequence) + } + + return bw.render() + } + + deserialize(buffer: Buffer): HeadValue | null { + const reader = bufio.read(buffer, true) + + if (reader.left()) { + const hash = reader.readHash() + const sequence = reader.readU32() + return { hash, sequence } + } + + return null + } + + getSize(value: HeadValue | null): number { + return value ? this.nonNullSize : 0 + } +} diff --git a/ironfish/src/migrations/data/000-encrypted-wallet-template/new/index.ts b/ironfish/src/migrations/data/000-encrypted-wallet-template/new/index.ts new file mode 100644 index 0000000000..ca24820f30 --- /dev/null +++ b/ironfish/src/migrations/data/000-encrypted-wallet-template/new/index.ts @@ -0,0 +1,20 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import { IDatabase, IDatabaseStore, StringEncoding } from '../../../../storage' +import { AccountValue, AccountValueEncoding } from './accountValue' + +export function GetNewStores(db: IDatabase): { + accounts: IDatabaseStore<{ key: string; value: AccountValue }> +} { + const accounts: IDatabaseStore<{ key: string; value: AccountValue }> = db.addStore( + { + name: 'a', + keyEncoding: new StringEncoding(), + valueEncoding: new AccountValueEncoding(), + }, + false, + ) + + return { accounts } +} diff --git a/ironfish/src/migrations/data/000-encrypted-wallet-template/new/multisigKeys.ts b/ironfish/src/migrations/data/000-encrypted-wallet-template/new/multisigKeys.ts new file mode 100644 index 0000000000..9aa3ace613 --- /dev/null +++ b/ironfish/src/migrations/data/000-encrypted-wallet-template/new/multisigKeys.ts @@ -0,0 +1,92 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import bufio from 'bufio' +import { Assert } from '../../../../assert' +import { IDatabaseEncoding } from '../../../../storage' +import { + MultisigHardwareSigner, + MultisigKeys, + MultisigSigner, +} from '../../../../wallet/interfaces/multisigKeys' + +export class MultisigKeysEncoding implements IDatabaseEncoding { + serialize(value: MultisigKeys): Buffer { + const bw = bufio.write(this.getSize(value)) + + let flags = 0 + flags |= Number(!!isSignerMultisig(value)) << 0 + flags |= Number(!!isHardwareSignerMultisig(value)) << 1 + bw.writeU8(flags) + + bw.writeVarBytes(Buffer.from(value.publicKeyPackage, 'hex')) + if (isSignerMultisig(value)) { + bw.writeVarBytes(Buffer.from(value.secret, 'hex')) + bw.writeVarBytes(Buffer.from(value.keyPackage, 'hex')) + } else if (isHardwareSignerMultisig(value)) { + bw.writeVarBytes(Buffer.from(value.identity, 'hex')) + } + + return bw.render() + } + + deserialize(buffer: Buffer): MultisigKeys { + const reader = bufio.read(buffer, true) + + const flags = reader.readU8() + const isSigner = flags & (1 << 0) + const isHardwareSigner = flags & (1 << 1) + + const publicKeyPackage = reader.readVarBytes().toString('hex') + if (isSigner) { + const secret = reader.readVarBytes().toString('hex') + const keyPackage = reader.readVarBytes().toString('hex') + return { + publicKeyPackage, + secret, + keyPackage, + } + } else if (isHardwareSigner) { + const identity = reader.readVarBytes().toString('hex') + return { + publicKeyPackage, + identity, + } + } + + return { + publicKeyPackage, + } + } + + getSize(value: MultisigKeys): number { + let size = 0 + size += 1 // flags + + size += bufio.sizeVarString(value.publicKeyPackage, 'hex') + if (isSignerMultisig(value)) { + size += bufio.sizeVarString(value.secret, 'hex') + size += bufio.sizeVarString(value.keyPackage, 'hex') + } else if (isHardwareSignerMultisig(value)) { + size += bufio.sizeVarString(value.identity, 'hex') + } + + return size + } +} + +export function isSignerMultisig(multisigKeys: MultisigKeys): multisigKeys is MultisigSigner { + return 'keyPackage' in multisigKeys && 'secret' in multisigKeys +} + +export function isHardwareSignerMultisig( + multisigKeys: MultisigKeys, +): multisigKeys is MultisigHardwareSigner { + return 'identity' in multisigKeys +} + +export function AssertIsSignerMultisig( + multisigKeys: MultisigKeys, +): asserts multisigKeys is MultisigSigner { + Assert.isTrue(isSignerMultisig(multisigKeys)) +} diff --git a/ironfish/src/migrations/data/000-encrypted-wallet-template/old/accountValue.ts b/ironfish/src/migrations/data/000-encrypted-wallet-template/old/accountValue.ts new file mode 100644 index 0000000000..65b1854e73 --- /dev/null +++ b/ironfish/src/migrations/data/000-encrypted-wallet-template/old/accountValue.ts @@ -0,0 +1,229 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import { KEY_LENGTH, PUBLIC_ADDRESS_LENGTH, xchacha20poly1305 } from '@ironfish/rust-nodejs' +import bufio from 'bufio' +import { IDatabaseEncoding } from '../../../../storage' +import { MultisigKeys } from '../../../../wallet/interfaces/multisigKeys' +import { HeadValue, NullableHeadValueEncoding } from './headValue' +import { MultisigKeysEncoding } from './multisigKeys' + +export const VIEW_KEY_LENGTH = 64 +const VERSION_LENGTH = 2 + +export type EncryptedAccountValue = { + encrypted: true + salt: Buffer + nonce: Buffer + data: Buffer +} + +export type DecryptedAccountValue = { + encrypted: false + version: number + id: string + name: string + spendingKey: string | null + viewKey: string + incomingViewKey: string + outgoingViewKey: string + publicAddress: string + createdAt: HeadValue | null + scanningEnabled: boolean + multisigKeys?: MultisigKeys + proofAuthorizingKey: string | null +} + +export type AccountValue = EncryptedAccountValue | DecryptedAccountValue +export class AccountValueEncoding implements IDatabaseEncoding { + serialize(value: AccountValue): Buffer { + if (value.encrypted) { + return this.serializeEncrypted(value) + } else { + return this.serializeDecrypted(value) + } + } + + serializeEncrypted(value: EncryptedAccountValue): Buffer { + const bw = bufio.write(this.getSize(value)) + + let flags = 0 + flags |= Number(!!value.encrypted) << 5 + bw.writeU8(flags) + bw.writeBytes(value.salt) + bw.writeBytes(value.nonce) + bw.writeVarBytes(value.data) + + return bw.render() + } + + serializeDecrypted(value: DecryptedAccountValue): Buffer { + const bw = bufio.write(this.getSize(value)) + let flags = 0 + flags |= Number(!!value.spendingKey) << 0 + flags |= Number(!!value.createdAt) << 1 + flags |= Number(!!value.multisigKeys) << 2 + flags |= Number(!!value.proofAuthorizingKey) << 3 + flags |= Number(!!value.scanningEnabled) << 4 + flags |= Number(!!value.encrypted) << 5 + + bw.writeU8(flags) + bw.writeU16(value.version) + bw.writeVarString(value.id, 'utf8') + bw.writeVarString(value.name, 'utf8') + + if (value.spendingKey) { + bw.writeBytes(Buffer.from(value.spendingKey, 'hex')) + } + + bw.writeBytes(Buffer.from(value.viewKey, 'hex')) + bw.writeBytes(Buffer.from(value.incomingViewKey, 'hex')) + bw.writeBytes(Buffer.from(value.outgoingViewKey, 'hex')) + bw.writeBytes(Buffer.from(value.publicAddress, 'hex')) + + if (value.createdAt) { + const encoding = new NullableHeadValueEncoding() + bw.writeBytes(encoding.serialize(value.createdAt)) + } + + if (value.multisigKeys) { + const encoding = new MultisigKeysEncoding() + bw.writeU64(encoding.getSize(value.multisigKeys)) + bw.writeBytes(encoding.serialize(value.multisigKeys)) + } + + if (value.proofAuthorizingKey) { + bw.writeBytes(Buffer.from(value.proofAuthorizingKey, 'hex')) + } + + return bw.render() + } + + deserialize(buffer: Buffer): AccountValue { + const reader = bufio.read(buffer, true) + const flags = reader.readU8() + const encrypted = Boolean(flags & (1 << 5)) + + if (encrypted) { + return this.deserializeEncrypted(buffer) + } else { + return this.deserializeDecrypted(buffer) + } + } + + deserializeEncrypted(buffer: Buffer): EncryptedAccountValue { + const reader = bufio.read(buffer, true) + + // Skip flags + reader.readU8() + + const salt = reader.readBytes(xchacha20poly1305.XSALT_LENGTH) + const nonce = reader.readBytes(xchacha20poly1305.XNONCE_LENGTH) + const data = reader.readVarBytes() + return { + encrypted: true, + nonce, + salt, + data, + } + } + + deserializeDecrypted(buffer: Buffer): DecryptedAccountValue { + const reader = bufio.read(buffer, true) + const flags = reader.readU8() + const version = reader.readU16() + const hasSpendingKey = flags & (1 << 0) + const hasCreatedAt = flags & (1 << 1) + const hasMultisigKeys = flags & (1 << 2) + const hasProofAuthorizingKey = flags & (1 << 3) + const scanningEnabled = Boolean(flags & (1 << 4)) + const id = reader.readVarString('utf8') + const name = reader.readVarString('utf8') + const spendingKey = hasSpendingKey ? reader.readBytes(KEY_LENGTH).toString('hex') : null + const viewKey = reader.readBytes(VIEW_KEY_LENGTH).toString('hex') + const incomingViewKey = reader.readBytes(KEY_LENGTH).toString('hex') + const outgoingViewKey = reader.readBytes(KEY_LENGTH).toString('hex') + const publicAddress = reader.readBytes(PUBLIC_ADDRESS_LENGTH).toString('hex') + + let createdAt = null + if (hasCreatedAt) { + const encoding = new NullableHeadValueEncoding() + createdAt = encoding.deserialize(reader.readBytes(encoding.nonNullSize)) + } + + let multisigKeys = undefined + if (hasMultisigKeys) { + const multisigKeysLength = reader.readU64() + const encoding = new MultisigKeysEncoding() + multisigKeys = encoding.deserialize(reader.readBytes(multisigKeysLength)) + } + + const proofAuthorizingKey = hasProofAuthorizingKey + ? reader.readBytes(KEY_LENGTH).toString('hex') + : null + + return { + encrypted: false, + version, + id, + name, + viewKey, + incomingViewKey, + outgoingViewKey, + spendingKey, + publicAddress, + createdAt, + scanningEnabled, + multisigKeys, + proofAuthorizingKey, + } + } + + getSize(value: AccountValue): number { + if (value.encrypted) { + return this.getSizeEncrypted(value) + } else { + return this.getSizeDecrypted(value) + } + } + + getSizeEncrypted(value: EncryptedAccountValue): number { + let size = 0 + size += 1 // flags + size += xchacha20poly1305.XSALT_LENGTH + size += xchacha20poly1305.XNONCE_LENGTH + size += bufio.sizeVarBytes(value.data) + return size + } + + getSizeDecrypted(value: DecryptedAccountValue): number { + let size = 0 + size += 1 // flags + size += VERSION_LENGTH + size += bufio.sizeVarString(value.id, 'utf8') + size += bufio.sizeVarString(value.name, 'utf8') + if (value.spendingKey) { + size += KEY_LENGTH + } + size += VIEW_KEY_LENGTH + size += KEY_LENGTH + size += KEY_LENGTH + size += PUBLIC_ADDRESS_LENGTH + + if (value.createdAt) { + const encoding = new NullableHeadValueEncoding() + size += encoding.nonNullSize + } + + if (value.multisigKeys) { + const encoding = new MultisigKeysEncoding() + size += 8 // size of multi sig keys + size += encoding.getSize(value.multisigKeys) + } + if (value.proofAuthorizingKey) { + size += KEY_LENGTH + } + + return size + } +} diff --git a/ironfish/src/migrations/data/000-encrypted-wallet-template/old/headValue.ts b/ironfish/src/migrations/data/000-encrypted-wallet-template/old/headValue.ts new file mode 100644 index 0000000000..9da18b093c --- /dev/null +++ b/ironfish/src/migrations/data/000-encrypted-wallet-template/old/headValue.ts @@ -0,0 +1,41 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import bufio from 'bufio' +import { IDatabaseEncoding } from '../../../../storage' + +export type HeadValue = { + hash: Buffer + sequence: number +} + +export class NullableHeadValueEncoding implements IDatabaseEncoding { + readonly nonNullSize = 32 + 4 // 256-bit block hash + 32-bit integer + + serialize(value: HeadValue | null): Buffer { + const bw = bufio.write(this.getSize(value)) + + if (value) { + bw.writeHash(value.hash) + bw.writeU32(value.sequence) + } + + return bw.render() + } + + deserialize(buffer: Buffer): HeadValue | null { + const reader = bufio.read(buffer, true) + + if (reader.left()) { + const hash = reader.readHash() + const sequence = reader.readU32() + return { hash, sequence } + } + + return null + } + + getSize(value: HeadValue | null): number { + return value ? this.nonNullSize : 0 + } +} diff --git a/ironfish/src/migrations/data/000-encrypted-wallet-template/old/index.ts b/ironfish/src/migrations/data/000-encrypted-wallet-template/old/index.ts new file mode 100644 index 0000000000..faf4491604 --- /dev/null +++ b/ironfish/src/migrations/data/000-encrypted-wallet-template/old/index.ts @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import { IDatabase, IDatabaseStore, StringEncoding } from '../../../../storage' +import { AccountValue, AccountValueEncoding } from './accountValue' +import { MasterKeyValue, MasterKeyValueEncoding } from './masterKeyValue' + +export function GetOldStores(db: IDatabase): { + accounts: IDatabaseStore<{ key: string; value: AccountValue }> + masterKey: IDatabaseStore<{ key: string; value: MasterKeyValue }> +} { + const accounts: IDatabaseStore<{ key: string; value: AccountValue }> = db.addStore( + { + name: 'a', + keyEncoding: new StringEncoding(), + valueEncoding: new AccountValueEncoding(), + }, + false, + ) + + const masterKey: IDatabaseStore<{ key: string; value: MasterKeyValue }> = db.addStore( + { + name: 'mk', + keyEncoding: new StringEncoding<'key'>(), + valueEncoding: new MasterKeyValueEncoding(), + }, + false, + ) + + return { accounts, masterKey } +} diff --git a/ironfish/src/migrations/data/000-encrypted-wallet-template/old/masterKeyValue.ts b/ironfish/src/migrations/data/000-encrypted-wallet-template/old/masterKeyValue.ts new file mode 100644 index 0000000000..7c3b133d95 --- /dev/null +++ b/ironfish/src/migrations/data/000-encrypted-wallet-template/old/masterKeyValue.ts @@ -0,0 +1,32 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import { xchacha20poly1305 } from '@ironfish/rust-nodejs' +import bufio from 'bufio' +import { IDatabaseEncoding } from '../../../../storage' + +export type MasterKeyValue = { + nonce: Buffer + salt: Buffer +} + +export class MasterKeyValueEncoding implements IDatabaseEncoding { + serialize(value: MasterKeyValue): Buffer { + const bw = bufio.write(this.getSize()) + bw.writeBytes(value.nonce) + bw.writeBytes(value.salt) + return bw.render() + } + + deserialize(buffer: Buffer): MasterKeyValue { + const reader = bufio.read(buffer, true) + + const nonce = reader.readBytes(xchacha20poly1305.XNONCE_LENGTH) + const salt = reader.readBytes(xchacha20poly1305.XSALT_LENGTH) + return { nonce, salt } + } + + getSize(): number { + return xchacha20poly1305.XNONCE_LENGTH + xchacha20poly1305.XSALT_LENGTH + } +} diff --git a/ironfish/src/migrations/data/000-encrypted-wallet-template/old/multisigKeys.ts b/ironfish/src/migrations/data/000-encrypted-wallet-template/old/multisigKeys.ts new file mode 100644 index 0000000000..9aa3ace613 --- /dev/null +++ b/ironfish/src/migrations/data/000-encrypted-wallet-template/old/multisigKeys.ts @@ -0,0 +1,92 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import bufio from 'bufio' +import { Assert } from '../../../../assert' +import { IDatabaseEncoding } from '../../../../storage' +import { + MultisigHardwareSigner, + MultisigKeys, + MultisigSigner, +} from '../../../../wallet/interfaces/multisigKeys' + +export class MultisigKeysEncoding implements IDatabaseEncoding { + serialize(value: MultisigKeys): Buffer { + const bw = bufio.write(this.getSize(value)) + + let flags = 0 + flags |= Number(!!isSignerMultisig(value)) << 0 + flags |= Number(!!isHardwareSignerMultisig(value)) << 1 + bw.writeU8(flags) + + bw.writeVarBytes(Buffer.from(value.publicKeyPackage, 'hex')) + if (isSignerMultisig(value)) { + bw.writeVarBytes(Buffer.from(value.secret, 'hex')) + bw.writeVarBytes(Buffer.from(value.keyPackage, 'hex')) + } else if (isHardwareSignerMultisig(value)) { + bw.writeVarBytes(Buffer.from(value.identity, 'hex')) + } + + return bw.render() + } + + deserialize(buffer: Buffer): MultisigKeys { + const reader = bufio.read(buffer, true) + + const flags = reader.readU8() + const isSigner = flags & (1 << 0) + const isHardwareSigner = flags & (1 << 1) + + const publicKeyPackage = reader.readVarBytes().toString('hex') + if (isSigner) { + const secret = reader.readVarBytes().toString('hex') + const keyPackage = reader.readVarBytes().toString('hex') + return { + publicKeyPackage, + secret, + keyPackage, + } + } else if (isHardwareSigner) { + const identity = reader.readVarBytes().toString('hex') + return { + publicKeyPackage, + identity, + } + } + + return { + publicKeyPackage, + } + } + + getSize(value: MultisigKeys): number { + let size = 0 + size += 1 // flags + + size += bufio.sizeVarString(value.publicKeyPackage, 'hex') + if (isSignerMultisig(value)) { + size += bufio.sizeVarString(value.secret, 'hex') + size += bufio.sizeVarString(value.keyPackage, 'hex') + } else if (isHardwareSignerMultisig(value)) { + size += bufio.sizeVarString(value.identity, 'hex') + } + + return size + } +} + +export function isSignerMultisig(multisigKeys: MultisigKeys): multisigKeys is MultisigSigner { + return 'keyPackage' in multisigKeys && 'secret' in multisigKeys +} + +export function isHardwareSignerMultisig( + multisigKeys: MultisigKeys, +): multisigKeys is MultisigHardwareSigner { + return 'identity' in multisigKeys +} + +export function AssertIsSignerMultisig( + multisigKeys: MultisigKeys, +): asserts multisigKeys is MultisigSigner { + Assert.isTrue(isSignerMultisig(multisigKeys)) +} diff --git a/ironfish/src/migrations/data/000-encrypted-wallet-template/stores.ts b/ironfish/src/migrations/data/000-encrypted-wallet-template/stores.ts new file mode 100644 index 0000000000..b046b7c66f --- /dev/null +++ b/ironfish/src/migrations/data/000-encrypted-wallet-template/stores.ts @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import { IDatabase } from '../../../storage' +import { GetNewStores } from './new' +import { GetOldStores } from './old' + +export function GetStores(db: IDatabase): { + old: ReturnType + new: ReturnType +} { + const oldStores = GetOldStores(db) + const newStores = GetNewStores(db) + + return { old: oldStores, new: newStores } +} From 18dd96464b5cf5e78493fb3882ebd33bef3e3e60 Mon Sep 17 00:00:00 2001 From: Derek Guenther Date: Tue, 26 Nov 2024 14:14:48 -0500 Subject: [PATCH 48/68] Pass through decryptForSpender flag to note decryption (#5671) --- ironfish/src/wallet/wallet.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/ironfish/src/wallet/wallet.ts b/ironfish/src/wallet/wallet.ts index 2b9130f3b5..84f1529fea 100644 --- a/ironfish/src/wallet/wallet.ts +++ b/ironfish/src/wallet/wallet.ts @@ -41,6 +41,7 @@ import { } from '../utils' import { WorkerPool } from '../workerPool' import { DecryptedNote, DecryptNotesItem } from '../workerPool/tasks/decryptNotes' +import { DecryptNotesOptions } from '../workerPool/tasks/decryptNotes' import { Account, ACCOUNT_SCHEMA_VERSION } from './account/account' import { EncryptedAccount } from './account/encryptedAccount' import { AssetBalances } from './assetBalances' @@ -418,13 +419,21 @@ export class Wallet { } if (accounts.length * decryptNotesPayloads.length >= workloadSize) { - notePromises.push(this.decryptNotesFromTransaction(accounts, decryptNotesPayloads)) + notePromises.push( + this.decryptNotesFromTransaction(accounts, decryptNotesPayloads, { + decryptForSpender, + }), + ) decryptNotesPayloads = [] } } if (decryptNotesPayloads.length) { - notePromises.push(this.decryptNotesFromTransaction(accounts, decryptNotesPayloads)) + notePromises.push( + this.decryptNotesFromTransaction(accounts, decryptNotesPayloads, { + decryptForSpender, + }), + ) } const mergedResults: Map> = new Map() @@ -448,6 +457,7 @@ export class Wallet { private decryptNotesFromTransaction( accounts: ReadonlyArray, encryptedNotes: Array, + options: DecryptNotesOptions, ): Promise>> { const accountKeys = accounts.map((account) => ({ accountId: account.id, @@ -456,9 +466,7 @@ export class Wallet { viewKey: Buffer.from(account.viewKey, 'hex'), })) - return this.workerPool.decryptNotes(accountKeys, encryptedNotes, { - decryptForSpender: false, - }) + return this.workerPool.decryptNotes(accountKeys, encryptedNotes, options) } async connectBlockForAccount( From d2545ee7f6d1e24a9ba694db26cec28cf7b1c37e Mon Sep 17 00:00:00 2001 From: andrea Date: Tue, 26 Nov 2024 12:23:43 -0800 Subject: [PATCH 49/68] Rust: remove unnecessary uses of dynamic dispatch --- ironfish-rust/src/merkle_note.rs | 6 ++++-- ironfish-rust/src/transaction/proposed.rs | 4 ++-- ironfish-rust/src/transaction/spends.rs | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/ironfish-rust/src/merkle_note.rs b/ironfish-rust/src/merkle_note.rs index b038508e84..562b55f184 100644 --- a/ironfish-rust/src/merkle_note.rs +++ b/ironfish-rust/src/merkle_note.rs @@ -285,7 +285,9 @@ impl MerkleNote { } #[cfg(feature = "transaction-proofs")] -pub(crate) fn sapling_auth_path(witness: &dyn WitnessTrait) -> Vec> { +pub(crate) fn sapling_auth_path( + witness: &W, +) -> Vec> { let mut auth_path = vec![]; for element in &witness.get_auth_path() { let sapling_element = match element { @@ -305,7 +307,7 @@ pub(crate) fn sapling_auth_path(witness: &dyn WitnessTrait) -> Vec u64 { +pub(crate) fn position(witness: &W) -> u64 { let mut pos = 0; for (i, element) in witness.get_auth_path().iter().enumerate() { if let WitnessNode::Right(_) = element { diff --git a/ironfish-rust/src/transaction/proposed.rs b/ironfish-rust/src/transaction/proposed.rs index 49a7b3cc48..5470c1ea98 100644 --- a/ironfish-rust/src/transaction/proposed.rs +++ b/ironfish-rust/src/transaction/proposed.rs @@ -102,10 +102,10 @@ impl ProposedTransaction { } /// Spend the note owned by spender_key at the given witness location. - pub fn add_spend( + pub fn add_spend( &mut self, note: Note, - witness: &dyn WitnessTrait, + witness: &W, ) -> Result<(), IronfishError> { self.value_balances .add(note.asset_id(), note.value().try_into()?)?; diff --git a/ironfish-rust/src/transaction/spends.rs b/ironfish-rust/src/transaction/spends.rs index e0741e1b19..5122429c3e 100644 --- a/ironfish-rust/src/transaction/spends.rs +++ b/ironfish-rust/src/transaction/spends.rs @@ -68,7 +68,7 @@ impl SpendBuilder { /// This is the only time this API thinks about the merkle tree. The witness /// contains the root-hash at the time the witness was created and the path /// to verify the location of that note in the tree. - pub(crate) fn new(note: Note, witness: &dyn WitnessTrait) -> Self { + pub(crate) fn new(note: Note, witness: &W) -> Self { let value_commitment = ValueCommitment::new(note.value, note.asset_generator()); SpendBuilder { From 2fd301db0bf9b4629c29855e24d29fea5324f886 Mon Sep 17 00:00:00 2001 From: Derek Guenther Date: Tue, 26 Nov 2024 20:08:46 -0500 Subject: [PATCH 50/68] Switch from ubuntu-latest to ubuntu-22.04 (#5675) --- .../workflows/build-ironfish-rust-nodejs.yml | 18 +++++++++--------- .github/workflows/check-pr-branch.yml | 2 +- .github/workflows/ci-regenerate-fixtures.yml | 2 +- .github/workflows/ci.yml | 6 +++--- .github/workflows/deploy-node-docker-image.yml | 2 +- .github/workflows/deploy-npm-ironfish-cli.yml | 2 +- .../deploy-npm-ironfish-rust-nodejs.yml | 2 +- .github/workflows/deploy-npm-ironfish.yml | 2 +- .github/workflows/push-version-to-api.yml | 2 +- .github/workflows/rust_ci.yml | 16 ++++++++-------- .github/workflows/rust_ci_cache.yml | 2 +- 11 files changed, 28 insertions(+), 28 deletions(-) diff --git a/.github/workflows/build-ironfish-rust-nodejs.yml b/.github/workflows/build-ironfish-rust-nodejs.yml index a9708ea729..28b46b6206 100644 --- a/.github/workflows/build-ironfish-rust-nodejs.yml +++ b/.github/workflows/build-ironfish-rust-nodejs.yml @@ -14,7 +14,7 @@ jobs: fail-fast: false matrix: settings: - - host: ubuntu-latest + - host: ubuntu-22.04 target: x86_64-apple-darwin - host: windows-latest @@ -24,16 +24,16 @@ jobs: - host: macos-latest target: x86_64-unknown-linux-gnu - - host: ubuntu-latest + - host: ubuntu-22.04 target: x86_64-unknown-linux-musl - - host: ubuntu-latest + - host: ubuntu-22.04 target: aarch64-apple-darwin - - host: ubuntu-latest + - host: ubuntu-22.04 target: aarch64-unknown-linux-gnu - - host: ubuntu-latest + - host: ubuntu-22.04 target: aarch64-unknown-linux-musl name: Build ${{ matrix.settings.target }} @@ -94,19 +94,19 @@ jobs: - host: windows-latest target: x86_64-pc-windows-msvc - - host: ubuntu-latest + - host: ubuntu-22.04 target: x86_64-unknown-linux-gnu docker: node:18-slim - - host: ubuntu-latest + - host: ubuntu-22.04 target: x86_64-unknown-linux-musl docker: node:18-alpine - - host: ubuntu-latest + - host: ubuntu-22.04 target: aarch64-unknown-linux-gnu docker: ghcr.io/napi-rs/napi-rs/nodejs:aarch64-16 - - host: ubuntu-latest + - host: ubuntu-22.04 target: aarch64-unknown-linux-musl docker: arm64v8/node:18-alpine platform: linux/arm64/v8 diff --git a/.github/workflows/check-pr-branch.yml b/.github/workflows/check-pr-branch.yml index 5ca2196c3d..d8769dbfff 100644 --- a/.github/workflows/check-pr-branch.yml +++ b/.github/workflows/check-pr-branch.yml @@ -8,7 +8,7 @@ on: jobs: check: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - if: ${{ contains(github.event.pull_request.base.ref, 'master') && !contains(github.event.pull_request.title, 'master') }} run: | diff --git a/.github/workflows/ci-regenerate-fixtures.yml b/.github/workflows/ci-regenerate-fixtures.yml index dc766b127f..bafd232b5b 100644 --- a/.github/workflows/ci-regenerate-fixtures.yml +++ b/.github/workflows/ci-regenerate-fixtures.yml @@ -7,7 +7,7 @@ on: jobs: test: name: Test - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Check out Git repository diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd24ed63fc..940458b6eb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: lint: name: Lint - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Check out Git repository @@ -51,7 +51,7 @@ jobs: test: name: Test - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 strategy: matrix: shard: [1/3, 2/3, 3/3] @@ -96,7 +96,7 @@ jobs: testslow: name: Slow Tests - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 strategy: matrix: shard: [1/2, 2/2] diff --git a/.github/workflows/deploy-node-docker-image.yml b/.github/workflows/deploy-node-docker-image.yml index aa82644504..82ba5a79c6 100644 --- a/.github/workflows/deploy-node-docker-image.yml +++ b/.github/workflows/deploy-node-docker-image.yml @@ -37,7 +37,7 @@ permissions: jobs: Deploy: name: Deploy - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Check out Git repository diff --git a/.github/workflows/deploy-npm-ironfish-cli.yml b/.github/workflows/deploy-npm-ironfish-cli.yml index 9de31cb06c..f663af98aa 100644 --- a/.github/workflows/deploy-npm-ironfish-cli.yml +++ b/.github/workflows/deploy-npm-ironfish-cli.yml @@ -8,7 +8,7 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Check out Git repository uses: actions/checkout@v4 diff --git a/.github/workflows/deploy-npm-ironfish-rust-nodejs.yml b/.github/workflows/deploy-npm-ironfish-rust-nodejs.yml index 881c78d97d..f3fd64021b 100644 --- a/.github/workflows/deploy-npm-ironfish-rust-nodejs.yml +++ b/.github/workflows/deploy-npm-ironfish-rust-nodejs.yml @@ -13,7 +13,7 @@ jobs: publish: name: Publish - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: - build-and-test defaults: diff --git a/.github/workflows/deploy-npm-ironfish.yml b/.github/workflows/deploy-npm-ironfish.yml index d3bfa98fd4..4651cdfb7a 100644 --- a/.github/workflows/deploy-npm-ironfish.yml +++ b/.github/workflows/deploy-npm-ironfish.yml @@ -5,7 +5,7 @@ on: jobs: deploy: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Check out Git repository uses: actions/checkout@v4 diff --git a/.github/workflows/push-version-to-api.yml b/.github/workflows/push-version-to-api.yml index 1c01249c35..957f7b1951 100644 --- a/.github/workflows/push-version-to-api.yml +++ b/.github/workflows/push-version-to-api.yml @@ -14,7 +14,7 @@ on: jobs: Push: name: Push Version to API - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Check out Git repository diff --git a/.github/workflows/rust_ci.yml b/.github/workflows/rust_ci.yml index a3d4afe44d..a04a612bf8 100644 --- a/.github/workflows/rust_ci.yml +++ b/.github/workflows/rust_ci.yml @@ -33,7 +33,7 @@ name: Rust CI jobs: rust_lint: name: Lint Rust - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 @@ -63,7 +63,7 @@ jobs: cargo_check: name: Check Rust - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 @@ -83,7 +83,7 @@ jobs: cargo_vet: name: Vet Dependencies - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 @@ -103,7 +103,7 @@ jobs: ironfish_rust: name: Test ironfish-rust - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 strategy: matrix: shard: [1/2, 2/2] @@ -142,7 +142,7 @@ jobs: ironfish_rust_no_default_features: name: Test ironfish-rust (no default features) - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 strategy: matrix: shard: [1/2, 2/2] @@ -168,7 +168,7 @@ jobs: ironfish_rust_all_features: name: Test ironfish-rust (all features) - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 strategy: matrix: shard: [1/2, 2/2] @@ -194,7 +194,7 @@ jobs: ironfish_zkp: name: Test ironfish-zkp - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 @@ -228,7 +228,7 @@ jobs: ironfish_wasm: name: Test ironfish-rust-wasm - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/rust_ci_cache.yml b/.github/workflows/rust_ci_cache.yml index 792183f62a..16d0edccb1 100644 --- a/.github/workflows/rust_ci_cache.yml +++ b/.github/workflows/rust_ci_cache.yml @@ -19,7 +19,7 @@ name: Cache Rust build jobs: build-rust-cache: name: Build and cache rust code - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 From b9bb24476d0709c00bf6bc037952cfd943564662 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Tue, 26 Nov 2024 17:22:21 -0800 Subject: [PATCH 51/68] optionally retries in cli commands if user rejects Ledger request (#5672) * exits cli commands if user rejects Ledger request if a user rejects an action on their Ledger, the command will now exit instead of retrying and prompting the user again to approve the action * optionally retry if user rejects request providers users a way to retry if they rejected by accident because starting over in that case is a very bad experience refactors retry prompt into reusable function --- ironfish-cli/src/ui/ledger.ts | 48 ++++++++++++++--------------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/ironfish-cli/src/ui/ledger.ts b/ironfish-cli/src/ui/ledger.ts index ccbcb7ded2..1fa4ea769b 100644 --- a/ironfish-cli/src/ui/ledger.ts +++ b/ironfish-cli/src/ui/ledger.ts @@ -72,47 +72,24 @@ export async function ledger({ // is trying to enter their pin. When we run into this error, we // cannot send any commands to the Ledger in the app's CLA. ux.action.stop('Ledger Locked') - - const confirmed = await ui.confirmList( + await confirmRetryAction( 'Ledger Locked. Unlock and press enter to retry:', - 'Retry', + wasRunning, ) - - if (!confirmed) { - ux.stdout('Operation aborted.') - ux.exit(0) - } - - if (!wasRunning) { - ux.action.start(message) - } } else if (e instanceof LedgerExpertModeError) { // Polling the device may prevent the user from navigating to the // expert mode screen in the app and enabling expert mode. ux.action.stop('Expert mode required to send custom assets') - - const confirmed = await ui.confirmList( - 'Enable expert mode and press enter to retry:', - 'Retry', - ) - - if (!confirmed) { - ux.stdout('Operation aborted.') - ux.exit(0) - } - - if (!wasRunning) { - ux.action.start(message) - } + await confirmRetryAction('Enable expert mode and press enter to retry:', wasRunning) + } else if (e instanceof LedgerActionRejected) { + ux.action.stop('User Rejected Ledger Request!') + await confirmRetryAction('Request rejected. Retry?', wasRunning) } else if (e instanceof LedgerInvalidDkgStatusError) { ux.action.stop('Ironfish DKG Ledger App does not have any multisig keys!') ux.stdout( 'Use `wallet:multisig:ledger:restore` to restore an encrypted backup to your Ledger', ) ux.exit(1) - } else if (e instanceof LedgerActionRejected) { - ux.action.status = 'User Rejected Ledger Request!' - ux.stdout('User Rejected Ledger Request!') } else if (e instanceof LedgerConnectError) { ux.action.status = 'Connect and unlock your Ledger' } else if (e instanceof LedgerAppNotOpen) { @@ -145,6 +122,19 @@ export async function ledger({ } } +async function confirmRetryAction(message: string, actionRunning: boolean): Promise { + const confirmed = await ui.confirmList(message, 'Retry') + + if (!confirmed) { + ux.stdout('Operation aborted.') + ux.exit(0) + } + + if (!actionRunning) { + ux.action.start(message) + } +} + export async function sendTransactionWithLedger( client: RpcClient, raw: RawTransaction, From 9dacf2e0f97eaa82cfa08e037afd9fc2343bf794 Mon Sep 17 00:00:00 2001 From: andrea Date: Wed, 27 Nov 2024 14:18:25 -0800 Subject: [PATCH 52/68] Remove unnecessary qualifications from Rust code These are qualifications that rust 1.76 (specified in `rust-toolchain.toml` does not detect, but the latest version of Rust detects and warns about. --- ironfish-rust/src/mining/thread.rs | 5 +---- ironfish-rust/src/transaction/mints.rs | 2 +- ironfish-rust/src/transaction/spends.rs | 2 +- ironfish-zkp/src/circuits/util.rs | 2 +- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/ironfish-rust/src/mining/thread.rs b/ironfish-rust/src/mining/thread.rs index 45e2a783d4..4d76887957 100644 --- a/ironfish-rust/src/mining/thread.rs +++ b/ironfish-rust/src/mining/thread.rs @@ -48,10 +48,7 @@ impl Thread { .name(id.to_string()) .spawn(move || { let mut fish_hash_context = if fish_hash_options.enabled { - Some(fish_hash::Context::new( - fish_hash_options.full_context, - None, - )) + Some(Context::new(fish_hash_options.full_context, None)) } else { None }; diff --git a/ironfish-rust/src/transaction/mints.rs b/ironfish-rust/src/transaction/mints.rs index 7fd1503622..3d43f4d903 100644 --- a/ironfish-rust/src/transaction/mints.rs +++ b/ironfish-rust/src/transaction/mints.rs @@ -336,7 +336,7 @@ impl MintDescription { transfer_ownership_to = None; } - let authorizing_signature = redjubjub::Signature::read(&mut reader)?; + let authorizing_signature = Signature::read(&mut reader)?; Ok(MintDescription { proof, diff --git a/ironfish-rust/src/transaction/spends.rs b/ironfish-rust/src/transaction/spends.rs index 5122429c3e..3e8fa62b7f 100644 --- a/ironfish-rust/src/transaction/spends.rs +++ b/ironfish-rust/src/transaction/spends.rs @@ -268,7 +268,7 @@ impl SpendDescription { let tree_size = reader.read_u32::()?; let mut nullifier = Nullifier([0; 32]); reader.read_exact(&mut nullifier.0)?; - let authorizing_signature = redjubjub::Signature::read(&mut reader)?; + let authorizing_signature = Signature::read(&mut reader)?; Ok(SpendDescription { proof, diff --git a/ironfish-zkp/src/circuits/util.rs b/ironfish-zkp/src/circuits/util.rs index 488ad6a33f..b9dfcc7f32 100644 --- a/ironfish-zkp/src/circuits/util.rs +++ b/ironfish-zkp/src/circuits/util.rs @@ -135,7 +135,7 @@ pub(crate) fn assert_valid_asset_generator> // Compare the generator bits to the computed generator bits, proving that // this is the asset id that derived the generator for i in 0..256 { - boolean::Boolean::enforce_equal( + Boolean::enforce_equal( cs.namespace(|| format!("asset generator bit {} equality", i)), &asset_generator_bits[i], &asset_generator_repr[i], From eed9226971265706ed2a8d1147451281b2554284 Mon Sep 17 00:00:00 2001 From: andrea Date: Mon, 25 Nov 2024 16:20:57 -0800 Subject: [PATCH 53/68] WASM: add bindings for the Sapling keys --- ironfish-rust-wasm/src/keys/mod.rs | 6 + .../src/keys/proof_generation_key.rs | 36 ++++++ ironfish-rust-wasm/src/keys/sapling_key.rs | 105 ++++++++++++++++ ironfish-rust-wasm/src/keys/view_keys.rs | 119 ++++++++++++++++++ ironfish-rust-wasm/src/primitives.rs | 12 ++ ironfish-rust/src/keys/mod.rs | 13 +- ironfish-rust/src/keys/view_keys.rs | 66 ++++++++-- .../src/primitives/proof_generation_key.rs | 15 +++ 8 files changed, 358 insertions(+), 14 deletions(-) create mode 100644 ironfish-rust-wasm/src/keys/proof_generation_key.rs create mode 100644 ironfish-rust-wasm/src/keys/sapling_key.rs create mode 100644 ironfish-rust-wasm/src/keys/view_keys.rs diff --git a/ironfish-rust-wasm/src/keys/mod.rs b/ironfish-rust-wasm/src/keys/mod.rs index 54b4364b7b..5d67846bdb 100644 --- a/ironfish-rust-wasm/src/keys/mod.rs +++ b/ironfish-rust-wasm/src/keys/mod.rs @@ -2,6 +2,12 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +mod proof_generation_key; mod public_address; +mod sapling_key; +mod view_keys; +pub use proof_generation_key::ProofGenerationKey; pub use public_address::PublicAddress; +pub use sapling_key::SaplingKey; +pub use view_keys::{IncomingViewKey, OutgoingViewKey, ViewKey}; diff --git a/ironfish-rust-wasm/src/keys/proof_generation_key.rs b/ironfish-rust-wasm/src/keys/proof_generation_key.rs new file mode 100644 index 0000000000..cdde330c97 --- /dev/null +++ b/ironfish-rust-wasm/src/keys/proof_generation_key.rs @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::{ + errors::IronfishError, + primitives::{Fr, SubgroupPoint}, + wasm_bindgen_wrapper, +}; +use wasm_bindgen::prelude::*; + +wasm_bindgen_wrapper! { + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct ProofGenerationKey(ironfish::keys::ProofGenerationKey); +} + +#[wasm_bindgen] +impl ProofGenerationKey { + #[wasm_bindgen(constructor)] + pub fn deserialize(bytes: &[u8]) -> Result { + Ok(Self(ironfish::keys::ProofGenerationKey::read(bytes)?)) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Vec { + self.0.to_bytes().to_vec() + } + + #[wasm_bindgen(js_name = fromParts)] + pub fn from_parts(ak: SubgroupPoint, nsk: Fr) -> Self { + Self(ironfish::keys::ProofGenerationKey::new( + ak.into(), + nsk.into(), + )) + } +} diff --git a/ironfish-rust-wasm/src/keys/sapling_key.rs b/ironfish-rust-wasm/src/keys/sapling_key.rs new file mode 100644 index 0000000000..ce357af063 --- /dev/null +++ b/ironfish-rust-wasm/src/keys/sapling_key.rs @@ -0,0 +1,105 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::{ + errors::IronfishError, + keys::{IncomingViewKey, OutgoingViewKey, ProofGenerationKey, PublicAddress, ViewKey}, + wasm_bindgen_wrapper, +}; +use wasm_bindgen::prelude::*; + +wasm_bindgen_wrapper! { + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct SaplingKey(ironfish::keys::SaplingKey); +} + +#[wasm_bindgen] +impl SaplingKey { + #[wasm_bindgen(constructor)] + pub fn deserialize(bytes: &[u8]) -> Result { + Ok(Self(ironfish::keys::SaplingKey::read(bytes)?)) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Vec { + let mut buf = Vec::new(); + self.0 + .write(&mut buf) + .expect("failed to serialize sapling key"); + buf + } + + #[wasm_bindgen] + pub fn random() -> Self { + Self(ironfish::keys::SaplingKey::generate_key()) + } + + #[wasm_bindgen(js_name = fromHex)] + pub fn from_hex(hex: &str) -> Result { + Ok(Self(ironfish::keys::SaplingKey::from_hex(hex)?)) + } + + #[wasm_bindgen(js_name = toHex)] + pub fn to_hex(&self) -> String { + self.0.hex_spending_key() + } + + // TODO: to/fromWords + + #[wasm_bindgen(getter, js_name = publicAddress)] + pub fn public_address(&self) -> PublicAddress { + self.0.public_address().into() + } + + #[wasm_bindgen(getter, js_name = spendingKey)] + pub fn spending_key(&self) -> Vec { + self.0.spending_key().to_vec() + } + + #[wasm_bindgen(getter, js_name = incomingViewKey)] + pub fn incoming_view_key(&self) -> IncomingViewKey { + self.0.incoming_view_key().to_owned().into() + } + + #[wasm_bindgen(getter, js_name = outgoingViewKey)] + pub fn outgoing_view_key(&self) -> OutgoingViewKey { + self.0.outgoing_view_key().to_owned().into() + } + + #[wasm_bindgen(getter, js_name = viewKey)] + pub fn view_key(&self) -> ViewKey { + self.0.view_key().to_owned().into() + } + + #[wasm_bindgen(getter, js_name = proofGenerationKey)] + pub fn proof_generation_key(&self) -> ProofGenerationKey { + self.0.sapling_proof_generation_key().into() + } +} + +#[cfg(test)] +mod tests { + use crate::keys::{IncomingViewKey, OutgoingViewKey, ProofGenerationKey, SaplingKey, ViewKey}; + use wasm_bindgen_test::wasm_bindgen_test; + + macro_rules! assert_serde_ok { + ( $type:ty, $key:expr ) => { + assert_eq!( + $key, + <$type>::deserialize($key.serialize().as_slice()).expect("deserialization failed") + ) + }; + } + + #[test] + #[wasm_bindgen_test] + fn serialization_roundtrip() { + let key = SaplingKey::random(); + assert_serde_ok!(SaplingKey, key); + assert_serde_ok!(IncomingViewKey, key.incoming_view_key()); + assert_serde_ok!(OutgoingViewKey, key.outgoing_view_key()); + assert_serde_ok!(ViewKey, key.view_key()); + assert_serde_ok!(ProofGenerationKey, key.proof_generation_key()); + } +} diff --git a/ironfish-rust-wasm/src/keys/view_keys.rs b/ironfish-rust-wasm/src/keys/view_keys.rs new file mode 100644 index 0000000000..6166bfd85f --- /dev/null +++ b/ironfish-rust-wasm/src/keys/view_keys.rs @@ -0,0 +1,119 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::{ + errors::IronfishError, keys::PublicAddress, primitives::PublicKey, wasm_bindgen_wrapper, +}; +use wasm_bindgen::prelude::*; + +wasm_bindgen_wrapper! { + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct IncomingViewKey(ironfish::keys::IncomingViewKey); +} + +#[wasm_bindgen] +impl IncomingViewKey { + #[wasm_bindgen(constructor)] + pub fn deserialize(bytes: &[u8]) -> Result { + Ok(Self(ironfish::keys::IncomingViewKey::read(bytes)?)) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Vec { + self.0.to_bytes().to_vec() + } + + #[wasm_bindgen(js_name = fromHex)] + pub fn from_hex(hex: &str) -> Result { + Ok(Self(ironfish::keys::IncomingViewKey::from_hex(hex)?)) + } + + #[wasm_bindgen(js_name = toHex)] + pub fn to_hex(&self) -> String { + self.0.hex_key() + } + + // TODO: to/fromWords + + #[wasm_bindgen(getter, js_name = publicAddress)] + pub fn public_address(&self) -> PublicAddress { + self.0.public_address().into() + } +} + +wasm_bindgen_wrapper! { + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct OutgoingViewKey(ironfish::keys::OutgoingViewKey); +} + +#[wasm_bindgen] +impl OutgoingViewKey { + #[wasm_bindgen(constructor)] + pub fn deserialize(bytes: &[u8]) -> Result { + Ok(Self(ironfish::keys::OutgoingViewKey::read(bytes)?)) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Vec { + self.0.to_bytes().to_vec() + } + + #[wasm_bindgen(js_name = fromHex)] + pub fn from_hex(hex: &str) -> Result { + Ok(Self(ironfish::keys::OutgoingViewKey::from_hex(hex)?)) + } + + #[wasm_bindgen(js_name = toHex)] + pub fn to_hex(&self) -> String { + self.0.hex_key() + } + + // TODO: to/fromWords +} + +wasm_bindgen_wrapper! { + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct ViewKey(ironfish::keys::ViewKey); +} + +#[wasm_bindgen] +impl ViewKey { + #[wasm_bindgen(constructor)] + pub fn deserialize(bytes: &[u8]) -> Result { + Ok(Self(ironfish::keys::ViewKey::read(bytes)?)) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Vec { + self.0.to_bytes().to_vec() + } + + #[wasm_bindgen(js_name = fromHex)] + pub fn from_hex(hex: &str) -> Result { + Ok(Self(ironfish::keys::ViewKey::from_hex(hex)?)) + } + + #[wasm_bindgen(js_name = toHex)] + pub fn to_hex(&self) -> String { + self.0.hex_key() + } + + #[wasm_bindgen(getter, js_name = publicAddress)] + pub fn public_address(&self) -> Result { + self.0 + .public_address() + .map(|a| a.into()) + .map_err(|e| e.into()) + } + + #[wasm_bindgen(getter, js_name = authorizingKey)] + pub fn authorizing_key(&self) -> PublicKey { + self.0.authorizing_key.into() + } + + #[wasm_bindgen(getter, js_name = nullifierDerivingKey)] + pub fn nullifier_deriving_key(&self) -> PublicKey { + self.0.nullifier_deriving_key.into() + } +} diff --git a/ironfish-rust-wasm/src/primitives.rs b/ironfish-rust-wasm/src/primitives.rs index 8eb38fc640..ba1c1c5088 100644 --- a/ironfish-rust-wasm/src/primitives.rs +++ b/ironfish-rust-wasm/src/primitives.rs @@ -153,6 +153,18 @@ impl PublicKey { } } +impl From for PublicKey { + fn from(p: ironfish_jubjub::ExtendedPoint) -> Self { + Self(redjubjub::PublicKey(p)) + } +} + +impl From for PublicKey { + fn from(p: ironfish_jubjub::SubgroupPoint) -> Self { + Self(redjubjub::PublicKey(p.into())) + } +} + wasm_bindgen_wrapper! { #[derive(Clone, Debug)] pub struct Signature(redjubjub::Signature); diff --git a/ironfish-rust/src/keys/mod.rs b/ironfish-rust/src/keys/mod.rs index 92c1e7cd9c..ecb5eb3b18 100644 --- a/ironfish-rust/src/keys/mod.rs +++ b/ironfish-rust/src/keys/mod.rs @@ -17,7 +17,7 @@ use ironfish_zkp::constants::{ pub use ironfish_zkp::ProofGenerationKey; use rand::prelude::*; -use std::io; +use std::{fmt, io}; mod ephemeral; pub use ephemeral::EphemeralKeyPair; @@ -41,7 +41,7 @@ pub const SPEND_KEY_SIZE: usize = 32; /// While the key parts are all represented as 256 bit keys to the outside /// world, inside the API they map to Edwards points or scalar values /// on the JubJub curve. -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq)] pub struct SaplingKey { /// The private (secret) key from which all the other key parts are derived. /// The expanded form of this key is required before a note can be spent. @@ -116,7 +116,7 @@ impl SaplingKey { } /// Load a new key from a Read implementation (e.g: socket, file) - pub fn read(reader: &mut R) -> Result { + pub fn read(mut reader: R) -> Result { let mut spending_key = [0; SPEND_KEY_SIZE]; reader.read_exact(&mut spending_key)?; Self::new(spending_key) @@ -266,3 +266,10 @@ impl SaplingKey { Ok(scalar) } } + +impl fmt::Debug for SaplingKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Hide all private keys + f.debug_struct("SaplingKey").finish_non_exhaustive() + } +} diff --git a/ironfish-rust/src/keys/view_keys.rs b/ironfish-rust/src/keys/view_keys.rs index 0a039381a2..ad220e8841 100644 --- a/ironfish-rust/src/keys/view_keys.rs +++ b/ironfish-rust/src/keys/view_keys.rs @@ -20,17 +20,19 @@ use crate::{ }; use bip39::{Language, Mnemonic}; use blake2b_simd::Params as Blake2b; +use ff::Field; use group::GroupEncoding; use ironfish_jubjub::SubgroupPoint; - -use std::io; +use ironfish_zkp::{constants::SPENDING_KEY_GENERATOR, redjubjub}; +use rand::RngCore; +use std::{fmt, io}; const DIFFIE_HELLMAN_PERSONALIZATION: &[u8; 16] = b"Iron Fish shared"; /// Key that allows someone to view a transaction that you have received. /// /// Referred to as `ivk` in the literature. -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq)] pub struct IncomingViewKey { pub(crate) view_key: ironfish_jubjub::Fr, } @@ -42,6 +44,10 @@ impl IncomingViewKey { Ok(IncomingViewKey { view_key }) } + pub fn to_bytes(&self) -> [u8; 32] { + self.view_key.to_bytes() + } + /// Load a key from a string of hexadecimal digits pub fn from_hex(value: &str) -> Result { match hex_to_bytes::<32>(value) { @@ -102,10 +108,17 @@ impl IncomingViewKey { } } +impl fmt::Debug for IncomingViewKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Hide all private keys + f.debug_struct("IncomingViewKey").finish_non_exhaustive() + } +} + /// Contains two keys that are required (along with outgoing view key) /// to have full view access to an account. /// Referred to as `ViewingKey` in the literature. -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq)] pub struct ViewKey { /// Part of the full viewing key. Generally referred to as /// `ak` in the literature. Derived from spend_authorizing_key using scalar @@ -118,15 +131,12 @@ pub struct ViewKey { } impl ViewKey { - /// Load a key from a string of hexadecimal digits - pub fn from_hex(value: &str) -> Result { - let bytes: [u8; 64] = hex_to_bytes(value)?; - + pub fn read(mut reader: R) -> Result { let mut authorizing_key_bytes = [0; 32]; let mut nullifier_deriving_key_bytes = [0; 32]; - authorizing_key_bytes.clone_from_slice(&bytes[..32]); - nullifier_deriving_key_bytes.clone_from_slice(&bytes[32..]); + reader.read_exact(&mut authorizing_key_bytes)?; + reader.read_exact(&mut nullifier_deriving_key_bytes)?; let authorizing_key = Option::from(SubgroupPoint::from_bytes(&authorizing_key_bytes)) .ok_or_else(|| IronfishError::new(IronfishErrorKind::InvalidAuthorizingKey))?; @@ -141,6 +151,12 @@ impl ViewKey { }) } + /// Load a key from a string of hexadecimal digits + pub fn from_hex(value: &str) -> Result { + let bytes: [u8; 64] = hex_to_bytes(value)?; + Self::read(&bytes[..]) + } + /// Viewing key as hexadecimal, for readability. pub fn hex_key(&self) -> String { bytes_to_hex(&self.to_bytes()) @@ -163,12 +179,29 @@ impl ViewKey { Ok(ivk.public_address()) } + + pub fn randomized_public_key( + &self, + rng: R, + ) -> (ironfish_jubjub::Fr, redjubjub::PublicKey) { + let public_key_randomness = ironfish_jubjub::Fr::random(rng); + let randomized_public_key = redjubjub::PublicKey(self.authorizing_key.into()) + .randomize(public_key_randomness, *SPENDING_KEY_GENERATOR); + (public_key_randomness, randomized_public_key) + } +} + +impl fmt::Debug for ViewKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Hide all private keys + f.debug_struct("ViewKey").finish_non_exhaustive() + } } /// Key that allows someone to view a transaction that you have spent. /// /// Referred to as `ovk` in the literature. -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq)] pub struct OutgoingViewKey { pub(crate) view_key: [u8; 32], } @@ -181,6 +214,10 @@ impl OutgoingViewKey { Ok(OutgoingViewKey { view_key }) } + pub fn to_bytes(&self) -> [u8; 32] { + self.view_key + } + /// Load a key from a string of hexadecimal digits pub fn from_hex(value: &str) -> Result { match hex_to_bytes(value) { @@ -215,6 +252,13 @@ impl OutgoingViewKey { } } +impl fmt::Debug for OutgoingViewKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Hide all private keys + f.debug_struct("OutgoingViewKey").finish_non_exhaustive() + } +} + /// Derive a shared secret key from a secret key and the other person's public /// key. /// diff --git a/ironfish-zkp/src/primitives/proof_generation_key.rs b/ironfish-zkp/src/primitives/proof_generation_key.rs index a46f045ab0..ac3c98a73f 100644 --- a/ironfish-zkp/src/primitives/proof_generation_key.rs +++ b/ironfish-zkp/src/primitives/proof_generation_key.rs @@ -100,6 +100,21 @@ impl From for ProofGenerationKey { } } +impl PartialEq for ProofGenerationKey { + fn eq(&self, other: &Self) -> bool { + self.to_bytes() == other.to_bytes() + } +} + +impl Eq for ProofGenerationKey {} + +impl fmt::Debug for ProofGenerationKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Hide all private keys + f.debug_struct("ProofGenerationKey").finish_non_exhaustive() + } +} + #[cfg(test)] mod test { use ff::Field; From aab7abee7734912852db7c6726e8bcca3d33806b Mon Sep 17 00:00:00 2001 From: andrea Date: Tue, 26 Nov 2024 15:53:18 -0800 Subject: [PATCH 54/68] WASM: expose the native asset identifier --- ironfish-rust-wasm/src/assets.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/ironfish-rust-wasm/src/assets.rs b/ironfish-rust-wasm/src/assets.rs index a863c44c9a..d74761d51b 100644 --- a/ironfish-rust-wasm/src/assets.rs +++ b/ironfish-rust-wasm/src/assets.rs @@ -120,6 +120,11 @@ impl AssetIdentifier { self.0.as_bytes().to_vec() } + #[wasm_bindgen(getter)] + pub fn native() -> Self { + Self(ironfish::assets::asset_identifier::NATIVE_ASSET) + } + #[wasm_bindgen(getter, js_name = assetGenerator)] pub fn asset_generator(&self) -> ExtendedPoint { self.0.asset_generator().into() @@ -208,5 +213,15 @@ mod tests { let id = AssetIdentifier::deserialize(&serialization[..]).unwrap(); assert_eq!(id.serialize(), serialization); } + + #[test] + #[wasm_bindgen_test] + fn native() { + let id = AssetIdentifier::native(); + assert_eq!( + id.serialize(), + hex!("51f33a2f14f92735e562dc658a5639279ddca3d5079a6d1242b2a588a9cbf44c") + ); + } } } From f57a007eb36f36cf4342f5aea595298e898a72c9 Mon Sep 17 00:00:00 2001 From: andrea Date: Mon, 25 Nov 2024 16:20:57 -0800 Subject: [PATCH 55/68] WASM: add bindings for unsigned transactions and descriptions Also add bindings to generate transaction signatures --- ironfish-rust-wasm/src/transaction/mints.rs | 51 +++- ironfish-rust-wasm/src/transaction/mod.rs | 6 +- ironfish-rust-wasm/src/transaction/spends.rs | 47 ++- .../src/transaction/unsigned.rs | 280 ++++++++++++++++++ ironfish-rust/src/transaction/mints.rs | 3 +- ironfish-rust/src/transaction/spends.rs | 2 +- ironfish-rust/src/transaction/unsigned.rs | 18 +- 7 files changed, 399 insertions(+), 8 deletions(-) create mode 100644 ironfish-rust-wasm/src/transaction/unsigned.rs diff --git a/ironfish-rust-wasm/src/transaction/mints.rs b/ironfish-rust-wasm/src/transaction/mints.rs index ba03caca8d..c01a08a662 100644 --- a/ironfish-rust-wasm/src/transaction/mints.rs +++ b/ironfish-rust-wasm/src/transaction/mints.rs @@ -5,8 +5,8 @@ use crate::{ assets::Asset, errors::IronfishError, - keys::PublicAddress, - primitives::{PublicKey, Scalar}, + keys::{PublicAddress, SaplingKey}, + primitives::{PublicKey, Scalar, Signature}, wasm_bindgen_wrapper, }; use ironfish::{errors::IronfishErrorKind, transaction::TransactionVersion}; @@ -79,3 +79,50 @@ impl MintDescription { .collect() } } + +wasm_bindgen_wrapper! { + #[derive(Clone, Debug)] + pub struct UnsignedMintDescription(ironfish::transaction::mints::UnsignedMintDescription); +} + +#[wasm_bindgen] +impl UnsignedMintDescription { + #[wasm_bindgen(constructor)] + pub fn deserialize(bytes: &[u8]) -> Result { + Ok(Self( + ironfish::transaction::mints::UnsignedMintDescription::read( + bytes, + TransactionVersion::V1, + )?, + )) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Vec { + let mut buf = Vec::new(); + self.0 + .write(&mut buf, TransactionVersion::V1) + .expect("failed to serialize unsigned mint description"); + buf + } + + #[wasm_bindgen] + pub fn sign( + self, + spender_key: &SaplingKey, + signature_hash: &[u8], + ) -> Result { + let signature_hash: &[u8; 32] = signature_hash + .try_into() + .map_err(|_| IronfishErrorKind::InvalidData)?; + self.0 + .sign(spender_key.as_ref(), signature_hash) + .map(|d| d.into()) + .map_err(|e| e.into()) + } + + #[wasm_bindgen(js_name = addSignature)] + pub fn add_signature(self, signature: Signature) -> MintDescription { + self.0.add_signature(signature.into()).into() + } +} diff --git a/ironfish-rust-wasm/src/transaction/mod.rs b/ironfish-rust-wasm/src/transaction/mod.rs index 4e534bc18e..aa9982e22e 100644 --- a/ironfish-rust-wasm/src/transaction/mod.rs +++ b/ironfish-rust-wasm/src/transaction/mod.rs @@ -6,14 +6,16 @@ mod burns; mod mints; mod outputs; mod spends; +mod unsigned; use crate::{errors::IronfishError, primitives::PublicKey, wasm_bindgen_wrapper}; use wasm_bindgen::prelude::*; pub use burns::BurnDescription; -pub use mints::MintDescription; +pub use mints::{MintDescription, UnsignedMintDescription}; pub use outputs::OutputDescription; -pub use spends::SpendDescription; +pub use spends::{SpendDescription, UnsignedSpendDescription}; +pub use unsigned::UnsignedTransaction; wasm_bindgen_wrapper! { #[derive(Clone, Debug)] diff --git a/ironfish-rust-wasm/src/transaction/spends.rs b/ironfish-rust-wasm/src/transaction/spends.rs index 92eadaa2c8..333152d21d 100644 --- a/ironfish-rust-wasm/src/transaction/spends.rs +++ b/ironfish-rust-wasm/src/transaction/spends.rs @@ -4,7 +4,8 @@ use crate::{ errors::IronfishError, - primitives::{Nullifier, PublicKey, Scalar}, + keys::SaplingKey, + primitives::{Nullifier, PublicKey, Scalar, Signature}, wasm_bindgen_wrapper, }; use ironfish::errors::IronfishErrorKind; @@ -74,3 +75,47 @@ impl SpendDescription { .collect() } } + +wasm_bindgen_wrapper! { + #[derive(Clone, Debug)] + pub struct UnsignedSpendDescription(ironfish::transaction::spends::UnsignedSpendDescription); +} + +#[wasm_bindgen] +impl UnsignedSpendDescription { + #[wasm_bindgen(constructor)] + pub fn deserialize(bytes: &[u8]) -> Result { + Ok(Self( + ironfish::transaction::spends::UnsignedSpendDescription::read(bytes)?, + )) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Vec { + let mut buf = Vec::new(); + self.0 + .write(&mut buf) + .expect("failed to serialize unsigned spend description"); + buf + } + + #[wasm_bindgen] + pub fn sign( + self, + spender_key: &SaplingKey, + signature_hash: &[u8], + ) -> Result { + let signature_hash: &[u8; 32] = signature_hash + .try_into() + .map_err(|_| IronfishErrorKind::InvalidData)?; + self.0 + .sign(spender_key.as_ref(), signature_hash) + .map(|d| d.into()) + .map_err(|e| e.into()) + } + + #[wasm_bindgen(js_name = addSignature)] + pub fn add_signature(self, signature: Signature) -> SpendDescription { + self.0.add_signature(signature.into()).into() + } +} diff --git a/ironfish-rust-wasm/src/transaction/unsigned.rs b/ironfish-rust-wasm/src/transaction/unsigned.rs new file mode 100644 index 0000000000..236e0c6c12 --- /dev/null +++ b/ironfish-rust-wasm/src/transaction/unsigned.rs @@ -0,0 +1,280 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::{ + errors::IronfishError, + keys::SaplingKey, + primitives::{Fr, PublicKey, Signature}, + transaction::{ + BurnDescription, OutputDescription, Transaction, UnsignedMintDescription, + UnsignedSpendDescription, + }, + wasm_bindgen_wrapper, +}; +use wasm_bindgen::prelude::*; + +wasm_bindgen_wrapper! { + #[derive(Clone, Debug)] + pub struct UnsignedTransaction(ironfish::transaction::unsigned::UnsignedTransaction); +} + +#[wasm_bindgen] +impl UnsignedTransaction { + #[wasm_bindgen(constructor)] + pub fn deserialize(bytes: &[u8]) -> Result { + Ok(Self( + ironfish::transaction::unsigned::UnsignedTransaction::read(bytes)?, + )) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Vec { + let mut buf = Vec::new(); + self.0 + .write(&mut buf) + .expect("failed to serialize unsigned spend description"); + buf + } + + #[wasm_bindgen(getter)] + pub fn fee(&self) -> i64 { + self.0.fee() + } + + #[wasm_bindgen(getter)] + pub fn expiration(&self) -> u32 { + self.0.expiration() + } + + #[wasm_bindgen(getter, js_name = publicKeyRandomness)] + pub fn public_key_randomness(&self) -> Fr { + self.0.public_key_randomness().into() + } + + #[wasm_bindgen(getter, js_name = randomizedPublicKey)] + pub fn randomized_public_key(&self) -> PublicKey { + self.0.randomized_public_key().clone().into() + } + + #[wasm_bindgen(getter)] + pub fn spends(&self) -> Vec { + self.0 + .spends() + .iter() + .cloned() + .map(|desc| desc.into()) + .collect() + } + + #[wasm_bindgen(getter)] + pub fn outputs(&self) -> Vec { + self.0 + .outputs() + .iter() + .cloned() + .map(|desc| desc.into()) + .collect() + } + + #[wasm_bindgen(getter)] + pub fn mints(&self) -> Vec { + self.0 + .mints() + .iter() + .cloned() + .map(|desc| desc.into()) + .collect() + } + + #[wasm_bindgen(getter)] + pub fn burns(&self) -> Vec { + self.0 + .burns() + .iter() + .cloned() + .map(|desc| desc.into()) + .collect() + } + + #[wasm_bindgen(js_name = transactionSignatureHash)] + pub fn transaction_signature_hash(&self) -> Result, IronfishError> { + self.0 + .transaction_signature_hash() + .map(|hash| hash.to_vec()) + .map_err(|err| err.into()) + } + + #[wasm_bindgen] + pub fn sign(self, spender_key: &SaplingKey) -> Result { + self.0 + .sign(spender_key.as_ref()) + .map(|d| d.into()) + .map_err(|e| e.into()) + } + + #[wasm_bindgen(js_name = addSignature)] + pub fn add_signature(mut self, signature: Signature) -> Transaction { + let signature: [u8; 64] = signature + .serialize() + .try_into() + .expect("signature serialization had an unexpected length"); + // `self.0.add_signature()` returns a `Result` because it + // accepts a `[u8; 64]` instead of `Signature`. The only way `self.0.add_signature()` can + // fail is if the signature array cannot be parsed into a `Signature`, but because our + // array comes from a valid `Signature`, this in theory can never happen, thus our call to + // `self.0.add_signature()` should never fail. + self.0 + .add_signature(signature) + .expect("adding a valid signature should never fail") + .into() + } +} + +#[cfg(test)] +mod tests { + use crate::{keys::SaplingKey, transaction::UnsignedTransaction}; + use hex_literal::hex; + use wasm_bindgen_test::wasm_bindgen_test; + + // Transaction copied from one of the fixtures in the `ironfish` NodeJS package + const TEST_TRANSACTION_BYTES: [u8; 2618] = hex!( + "01010000000000000003000000000000000100000000000000010000000000000000000000000000000a000000\ + 102563f7d98139b32bfe74511adf57e9335bd89cdb820ac0c3a60aebd7c95dcb9f68a3045e995f5f42ab5070a9a\ + 9e5d5ae0573256f8159ca18b2ecebb407b7059f68a3045e995f5f42ab5070a9a9e5d5ae0573256f8159ca18b2ec\ + ebb407b7058ba526ccc7064d8d2b8b722883e7d24a0ee96338c49ba860027443810cd533baf9eb95f52bf192b85\ + 09fe40bed51551ba0996607bfa1bc4ca40a1ac086b1a7f669fa5a72bac28ba647a7b8f50d17b047d035a1fc36ee\ + aca9f937d0a4dba8d6430c78b497dc511140dd920f72128b58d351509715c4619842fc442888f004630e1d71e57\ + 88d02c51662da9d646d5f26b784504420d8a2d52caaca41a7f17875faf5d01cf9054f4a9438bad15a46459ee43e\ + 69dd514a92e2f09560265569a177a1722d9343689647a57b518ab4c64451309bed09fa5d7ac194f024ed9a0a63b\ + ff3a5a33c7f04c3490ecd75b320a57e52cd94c2a43f8264625374aafff9b0d099070400000047fd341c51b65906\ + 00cf1ad0a7018a03bf838d12d509bbf36196ca99062e768c0000000000000000000000000000000000000000000\ + 00000000000000000000000000000000000000000000000000000000000000000000000000000000000008186c5\ + f17408f71c125c6462d1f8c6c1197c5492b2bbbd0d5740c018d40bb4014179746830666426f9b8526d0423e7868\ + 4bb9b96907501029c929dad329904032f620e90f144cbbc375401043ebde87f0638a3ce0f951d5469b69ba2963e\ + 563b0b075305718763373e8c1a4a91a418de3762ec975f82ffee9c0625822706ed810c3880d078bf31bf8cd6ea4\ + 7b26f366189d6ff2b7ac13c717b39d92841a34fbe53e294408b3aa79072c1b601b34e9fc1f2ad572b14a95170cf\ + 91c0832dd174d3b918aab0737b545573d34472cd9497c5ea30e478e71e7eefa1541276506d1b3f0431a79f09d8f\ + b7c190ebb1818e310dde6ac00e6dbb7ac24d5aff4c0759ed771d348a216157fe39b06a0f555435a4587df3cbc34\ + 88439401d5f7ed118a6b3781065a750d6c58a78b6ddff47e57239e89d1eee98088ebb418aca7cc88d195b11e08e\ + bbddfc376ff1a079c61762bd53a5746cacc027b2fb9ae01f53def998d43eefa2adee358bd5d96c4de17afc05335\ + 2749abfeb0d6fd1b54b24bae310930603dd62b72b4307cd96011f74375a6abfc49807a4b1c5a483378aaa70fee1\ + e05323f8909463c69b13c22fee633479caef313eac9081912907d057e279d8ca4e9f5da75f0ed3d8cd3a0b427e8\ + 5aa33233f7b9261f4d6e08c601ac9b13786c163432cc64c03d41d96a7ab6a4479cba61ee1f7819ba965dd5d366b\ + d4b9222b0d11255c520620b2f90fb158287efda7444919558fa9133949aa161ac956e8e799b837183709be2afd4\ + 71c83677b2aee9b91bd8000aa0ec7923b2181f90994d12a290c33d1ea4f07f8fdf3ecd01b7bcfda7e0bd7a8768a\ + 12f6f7888aa82f97b6c3108c051eaec27708ddc777a12caee086fa10b8f4629834e55377d49b4be4a03d7450dca\ + 7d4b4c3d29cc37348e98b4d1218694893250d8e3b3c888c88f15155d1604444bf9e478c844ba816fe3479f25d75\ + 44dbb5c27a76330f64c0abdf13faf5eccb8693ab52b57f32bfe7213eb6c357a58fd5e2574cb9b1f248172ef6d42\ + bf49c76124f2d74537399dba7351bf767dbb62dbb212bf8341beeafdd05c7c4554afbcbe71c3dde590c78dee04f\ + 2adedea7f32dd39696e3b7401e65d8b8c7e02e7513241b31c93865207b52117c25a13391234bf938b56c081b16f\ + 500a412f9a311c2c634f8ba354e48952279662c69c8f39385b9fa38027cc593b974c055495b8bb05ff4c51d8224\ + 981f23d9c29e1e3fb94ab3b0060d3c2ce809293d017eacb782cf9944ee4e8751df878373c388a63f7753bea4dcf\ + 8d593b6531391c27c92b418475a92a276790392cce37a21179cc9f187ae10d5f43a77bf4cda7b1de3bbd5b3e38b\ + 663879cedfe3c364935982f8cb2b48e123d62c77558c24767bde2ce73901fd475cfb9ab2ef85f1b2405da60105b\ + 460cd12fd089922e4492c1f25f4c62d4547b86dc8f32da14f8356466bc9dd737181492b8b8b1451167064d62ef6\ + 7713a65e274f84b943a12ee30717ba4ba09e2bfd09f670f7ef968c32f4abc91cc2f15302dcc24ac706eea811448\ + 426d880236bdb8ed2277a8a67034d301bcdaaf540fee0d3efbe73d4e64eb7cb88d34aaf4e35622e80513bdeadb7\ + 56f8976b636a87ae7613fed5be24f7c607e4654856e5d13d248735f958e0f00fda0a7c9fbf8eb987e45f17d82fe\ + 8726794468888d9cf6742c05cc1cdebd3ce7203539928c00afd0fe81851379d4ca7219222f104d475086455882e\ + 6cf6bc44318dc8937d59c16e3590ad95403a3186373f6a5077246eb6b35569098a00e2d632936149c626d3410b1\ + 97cbf62713c6542e8fd6b89da622fe911bcb3a665eb43d29d7c3eb345aafa99ab4e69e0a58e1310895a508a3330\ + 63bdd84113f673e119cc660577f177ed00ed452d22ba8524b099511cd9205f0f6e1eea027543c9751396a159e39\ + 4ea60a1a372b5cb02468f1f5ddee54192e44bf7c0aa73afde658bb977d8cf0ff524fb29daaeeb1badaf5ae96713\ + 2a59cdbb7104bf556a4ded3ea4a42e7f447c5676ce197eef160b83953c0f124af59bb65216e6e4839d181013b64\ + 242c9662a2e31efdd2205106a3eee4a44ff59ce9c4e242399f167fa996dee650671c17bbb9fa49d596895b1f924\ + fa6c59fd66f8ea131676a3e1c34e5f4694254caf30c8cd8f438d88599c55537e56f1917a70c78da5806a501c647\ + f74581f986bbd5430e5d9f68a3045e995f5f42ab5070a9a9e5d5ae0573256f8159ca18b2ecebb407b705b577cee\ + 7cab71668d2131e3f595b51d8317f4722329cf75030615742f587402419931626ed9e77bc8ec73b92dc06238ba3\ + 3401333ea58f99a6218a066cbb25569fa1607bd53e59e686f9d0e47dcaf1f0b300b64cbe20614f6c700e68f7d5a\ + 36d1423546a7194502528a628ed7c8f352d78963d75aac55835592f67a786c09b04ea12c0f32829a03485336b14\ + 45c01915ad0089efd5bda231a24a5aec3c556f522e1ba229d8f49d5d0c0e3b3ade304860cfc94b9646e447f6569\ + 7942ef669a1837c0fc099c96cbcd7a9982e060339e192293ca432f3acc8e8e42127337b6b798274657374000000\ + 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000139050000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 00000000000000000000000051f33a2f14f92735e562dc658a5639279ddca3d5079a6d1242b2a588a9cbf44c020\ + 000000000000015ce919903b7da6ab0326c1637f1a71bbd4d8809230459a10cd07d891c41ef69900ccd78ddf8df\ + 9e8ed99df039de2a897d5be4d1d209893bf389fb85fa4a6605" + ); + + // Spender key (Sapling key) that was used to generated the unsigned transaction above + const TEST_KEY_BYTES: [u8; 32] = + hex!("4d1dab6a192648aff8517d81a3075aa45a018120c337384447b364200d4a6c5d"); + + #[test] + #[wasm_bindgen_test] + fn deserialize() { + let unsigned = UnsignedTransaction::deserialize(TEST_TRANSACTION_BYTES.as_slice()) + .expect("reading transaction should have succeeded"); + + assert_eq!(unsigned.fee(), 0); + assert_eq!(unsigned.expiration(), 10); + assert_eq!( + unsigned.randomized_public_key().serialize(), + hex!("102563f7d98139b32bfe74511adf57e9335bd89cdb820ac0c3a60aebd7c95dcb") + ); + assert_eq!( + unsigned.public_key_randomness().to_bytes(), + hex!("9f68a3045e995f5f42ab5070a9a9e5d5ae0573256f8159ca18b2ecebb407b705") + ); + + assert_eq!(unsigned.spends().len(), 1); + assert_eq!(unsigned.outputs().len(), 3); + assert_eq!(unsigned.mints().len(), 1); + assert_eq!(unsigned.burns().len(), 1); + + for output in unsigned.outputs() { + output + .partial_verify() + .expect("output verification should have succeeded"); + } + + assert_eq!( + unsigned.transaction_signature_hash().unwrap(), + hex!("1c688fe5eb775f6d52839bcdfc70985423789d9fda18771e496daf8c6a5df386") + ); + } + + #[test] + #[wasm_bindgen_test] + fn sign() { + let unsigned = UnsignedTransaction::deserialize(TEST_TRANSACTION_BYTES.as_slice()) + .expect("reading transaction should have succeeded"); + let key = SaplingKey::deserialize(TEST_KEY_BYTES.as_slice()) + .expect("reading key should have succeeded"); + let hash = unsigned.transaction_signature_hash().unwrap(); + let randomized_public_key = unsigned.randomized_public_key(); + + let tx = unsigned + .sign(&key) + .expect("transaction signing should have succeeded"); + + assert_eq!(tx.transaction_signature_hash().unwrap(), hash); + assert_eq!( + tx.randomized_public_key().serialize(), + randomized_public_key.serialize() + ); + + for spend in tx.spends() { + spend + .partial_verify() + .expect("spend partial verification failed"); + spend + .verify_signature(&hash[..], &randomized_public_key) + .expect("spend signature verification failed"); + } + for output in tx.outputs() { + output + .partial_verify() + .expect("output partial verification failed"); + } + for mint in tx.mints() { + mint.partial_verify() + .expect("mint partial verification failed"); + mint.verify_signature(&hash[..], &randomized_public_key) + .expect("mint signature verification failed"); + } + } +} diff --git a/ironfish-rust/src/transaction/mints.rs b/ironfish-rust/src/transaction/mints.rs index 3d43f4d903..67181e7291 100644 --- a/ironfish-rust/src/transaction/mints.rs +++ b/ironfish-rust/src/transaction/mints.rs @@ -29,6 +29,7 @@ use ironfish_zkp::{proofs::MintAsset, ProofGenerationKey}; /// Parameters used to build a circuit that verifies an asset can be minted with /// a given key +#[derive(Clone, Debug)] #[cfg(feature = "transaction-proofs")] pub struct MintBuilder { /// Asset to be minted @@ -101,7 +102,7 @@ impl MintBuilder { /// The publicly visible values of a mint description in a transaction. /// These fields get serialized when computing the transaction hash and are used /// to prove that the creator has knowledge of these values. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct UnsignedMintDescription { /// Used to add randomness to signature generation. Referred to as `ar` in /// the literature. diff --git a/ironfish-rust/src/transaction/spends.rs b/ironfish-rust/src/transaction/spends.rs index 3e8fa62b7f..535c0d798a 100644 --- a/ironfish-rust/src/transaction/spends.rs +++ b/ironfish-rust/src/transaction/spends.rs @@ -150,7 +150,7 @@ impl SpendBuilder { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct UnsignedSpendDescription { /// Used to add randomness to signature generation without leaking the /// key. Referred to as `ar` in the literature. diff --git a/ironfish-rust/src/transaction/unsigned.rs b/ironfish-rust/src/transaction/unsigned.rs index 076cc46d72..2bca3625d3 100644 --- a/ironfish-rust/src/transaction/unsigned.rs +++ b/ironfish-rust/src/transaction/unsigned.rs @@ -32,7 +32,7 @@ use super::{ TransactionVersion, SIGNATURE_HASH_PERSONALIZATION, TRANSACTION_SIGNATURE_VERSION, }; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct UnsignedTransaction { /// The transaction serialization version. This can be incremented when /// changes need to be made to the transaction format @@ -318,15 +318,31 @@ impl UnsignedTransaction { }) } + pub fn fee(&self) -> i64 { + self.fee + } + + pub fn expiration(&self) -> u32 { + self.expiration + } + // Exposes the public key package for use in round two of FROST multisig protocol pub fn public_key_randomness(&self) -> ironfish_jubjub::Fr { self.public_key_randomness } + pub fn randomized_public_key(&self) -> &redjubjub::PublicKey { + &self.randomized_public_key + } + pub fn randomized_public_key_bytes(&self) -> [u8; 32] { self.randomized_public_key.0.to_bytes() } + pub fn spends(&self) -> &Vec { + &self.spends + } + pub fn outputs(&self) -> &Vec { &self.outputs } From 9017db9f49c4ba477cf868abc6dbc0ed7fe88197 Mon Sep 17 00:00:00 2001 From: andrea Date: Tue, 26 Nov 2024 14:22:57 -0800 Subject: [PATCH 56/68] WASM: add bindings for `Note` --- ironfish-rust-wasm/src/lib.rs | 1 + ironfish-rust-wasm/src/note.rs | 223 +++++++++++++++++++++++++++++++++ ironfish-rust/src/note.rs | 2 +- 3 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 ironfish-rust-wasm/src/note.rs diff --git a/ironfish-rust-wasm/src/lib.rs b/ironfish-rust-wasm/src/lib.rs index ae1e56da2c..95d627b928 100644 --- a/ironfish-rust-wasm/src/lib.rs +++ b/ironfish-rust-wasm/src/lib.rs @@ -19,6 +19,7 @@ pub mod assets; pub mod errors; pub mod keys; pub mod merkle_note; +pub mod note; pub mod primitives; pub mod transaction; diff --git a/ironfish-rust-wasm/src/note.rs b/ironfish-rust-wasm/src/note.rs new file mode 100644 index 0000000000..76273b34b5 --- /dev/null +++ b/ironfish-rust-wasm/src/note.rs @@ -0,0 +1,223 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::{ + assets::AssetIdentifier, + errors::IronfishError, + keys::{IncomingViewKey, PublicAddress, ViewKey}, + primitives::{ExtendedPoint, Nullifier}, + wasm_bindgen_wrapper, +}; +use ironfish::errors::IronfishErrorKind; +use wasm_bindgen::prelude::*; + +wasm_bindgen_wrapper! { + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct Note(ironfish::Note); +} + +#[wasm_bindgen] +impl Note { + #[wasm_bindgen(constructor)] + pub fn deserialize(bytes: &[u8]) -> Result { + Ok(Self(ironfish::Note::read(bytes)?)) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Vec { + let mut buf = Vec::new(); + self.0.write(&mut buf).expect("failed to serialize note"); + buf + } + + #[wasm_bindgen(js_name = fromParts)] + pub fn from_parts( + owner: PublicAddress, + value: u64, + memo: &str, + asset_id: AssetIdentifier, + sender: PublicAddress, + ) -> Self { + Self(ironfish::Note::new( + owner.into(), + value, + memo, + asset_id.into(), + sender.into(), + )) + } + + #[wasm_bindgen(getter)] + pub fn value(&self) -> u64 { + self.0.value() + } + + #[wasm_bindgen(getter)] + pub fn memo(&self) -> Vec { + self.0.memo().0.to_vec() + } + + #[wasm_bindgen(getter)] + pub fn owner(&self) -> PublicAddress { + self.0.owner().into() + } + + #[wasm_bindgen(getter)] + pub fn asset_generator(&self) -> ExtendedPoint { + self.0.asset_generator().into() + } + + #[wasm_bindgen(getter)] + pub fn asset_id(&self) -> AssetIdentifier { + self.0.asset_id().to_owned().into() + } + + #[wasm_bindgen(getter)] + pub fn sender(&self) -> PublicAddress { + self.0.sender().into() + } + + #[wasm_bindgen(getter)] + pub fn commitment(&self) -> Vec { + self.0.commitment().to_vec() + } + + #[wasm_bindgen] + pub fn encrypt(&self, shared_secret: &[u8]) -> Result, IronfishError> { + let shared_secret: &[u8; 32] = shared_secret + .try_into() + .map_err(|_| IronfishErrorKind::InvalidData)?; + Ok(self.0.encrypt(shared_secret).to_vec()) + } + + #[wasm_bindgen(js_name = fromOwnerEncrypted)] + pub fn from_owner_encrypted( + owner_view_key: &IncomingViewKey, + shared_secret: &[u8], + encrypted_bytes: &[u8], + ) -> Result { + let shared_secret: &[u8; 32] = shared_secret + .try_into() + .map_err(|_| IronfishErrorKind::InvalidData)?; + let encrypted_bytes: &[u8; 152] = encrypted_bytes + .try_into() + .map_err(|_| IronfishErrorKind::InvalidData)?; + Ok(Self(ironfish::Note::from_owner_encrypted( + owner_view_key.as_ref(), + shared_secret, + encrypted_bytes, + )?)) + } + + #[wasm_bindgen] + pub fn nullifier(&self, view_key: &ViewKey, position: u64) -> Nullifier { + self.0.nullifier(view_key.as_ref(), position).into() + } +} + +#[cfg(test)] +mod tests { + use crate::{ + assets::AssetIdentifier, + keys::{PublicAddress, SaplingKey}, + note::Note, + }; + use hex_literal::hex; + use rand::{thread_rng, Rng}; + use wasm_bindgen_test::wasm_bindgen_test; + + const TEST_NOTE_BYTES: [u8; 168] = hex!( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0cccccccccccccccccccccccccc\ + ccccccccccccccccccccccccccccccccccccc07b0000000000000000e2fb75515b55ed7f84be996ef80dae38b3d\ + 2076d1ffffd0970b641cde4060e736f6d65206d656d6fe29c8e0000000000000000000000000000000000000000\ + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbf" + ); + + #[test] + #[wasm_bindgen_test] + fn deserialize() { + let note = Note::deserialize(TEST_NOTE_BYTES.as_slice()) + .expect("reading note should have succeeded"); + + assert_eq!( + note.owner().serialize(), + hex!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0") + ); + assert_eq!( + note.sender().serialize(), + hex!("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbf") + ); + assert_eq!(note.value(), 123); + assert_eq!( + note.memo(), + b"some memo\xe2\x9c\x8e\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + ); + assert_eq!( + note.asset_id().serialize(), + hex!("ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc0") + ); + assert_eq!( + note.commitment(), + hex!("d044ae177718d5282807186168253e33a080e45a19be4cc27dc47b0a7146450d") + ); + assert_eq!(note.serialize(), TEST_NOTE_BYTES); + } + + #[test] + #[wasm_bindgen_test] + fn from_parts() { + let owner = PublicAddress::deserialize( + hex!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0").as_slice(), + ) + .unwrap(); + let sender = PublicAddress::deserialize( + hex!("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbf").as_slice(), + ) + .unwrap(); + let asset_id = AssetIdentifier::deserialize( + hex!("ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc0").as_slice(), + ) + .unwrap(); + + let note = Note::from_parts( + owner.clone(), + 123, + "some memo✎", + asset_id.clone(), + sender.clone(), + ); + + assert_eq!(note.owner(), owner); + assert_eq!(note.value(), 123); + assert_eq!( + note.memo(), + b"some memo\xe2\x9c\x8e\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + ); + assert_eq!(note.asset_id(), asset_id); + assert_eq!(note.sender(), sender); + } + + #[test] + #[wasm_bindgen_test] + fn encrypt_decrypt_roundtrip() { + let owner_key = SaplingKey::random(); + let sender_key = SaplingKey::random(); + let note = Note::from_parts( + owner_key.public_address(), + 123_456_789, + "memo", + AssetIdentifier::native(), + sender_key.public_address(), + ); + + let shared_secret: [u8; 32] = thread_rng().gen(); + let encrypted = note.encrypt(&shared_secret).expect("encryption failed"); + + let decrypted = + Note::from_owner_encrypted(&owner_key.incoming_view_key(), &shared_secret, &encrypted) + .expect("decryption failed"); + + assert_eq!(decrypted, note); + } +} diff --git a/ironfish-rust/src/note.rs b/ironfish-rust/src/note.rs index c0d1718b83..07ed156e07 100644 --- a/ironfish-rust/src/note.rs +++ b/ironfish-rust/src/note.rs @@ -85,7 +85,7 @@ impl fmt::Display for Memo { /// /// When receiving funds, a new note needs to be created for the new owner /// to hold those funds. -#[derive(Debug, Clone)] +#[derive(Debug, PartialEq, Eq, Clone)] pub struct Note { /// Asset identifier the note is associated with pub(crate) asset_id: AssetIdentifier, From 17a0d6535ca95bdf37c191ca0ecd52decb2d6b9f Mon Sep 17 00:00:00 2001 From: andrea Date: Wed, 27 Nov 2024 12:37:19 -0800 Subject: [PATCH 57/68] WASM: add decryption methods to MerkleNote --- ironfish-rust-wasm/src/merkle_note.rs | 98 ++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 2 deletions(-) diff --git a/ironfish-rust-wasm/src/merkle_note.rs b/ironfish-rust-wasm/src/merkle_note.rs index b19e5e30e5..60185d9e50 100644 --- a/ironfish-rust-wasm/src/merkle_note.rs +++ b/ironfish-rust-wasm/src/merkle_note.rs @@ -2,7 +2,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use crate::{errors::IronfishError, primitives::Scalar, wasm_bindgen_wrapper}; +use crate::{ + errors::IronfishError, + keys::{IncomingViewKey, OutgoingViewKey}, + note::Note, + primitives::Scalar, + wasm_bindgen_wrapper, +}; use wasm_bindgen::prelude::*; wasm_bindgen_wrapper! { @@ -30,6 +36,50 @@ impl MerkleNote { pub fn merkle_hash(&self) -> MerkleNoteHash { self.0.merkle_hash().into() } + + #[wasm_bindgen(js_name = decryptNoteForOwner)] + pub fn decrypt_note_for_owner( + &self, + owner_view_key: &IncomingViewKey, + ) -> Result { + self.0 + .decrypt_note_for_owner(owner_view_key.as_ref()) + .map(|n| n.into()) + .map_err(|e| e.into()) + } + + #[wasm_bindgen(js_name = decryptNoteForOwners)] + pub fn decrypt_note_for_owners(&self, owner_view_keys: Vec) -> Vec { + // The original `decrypt_note_for_owners` returns a `Vec>`. Here instead we + // are filtering out all errors. This likely makes this method hard to use in practice, + // because the information for mapping between the original owner and the resulting note is + // lost. However, returing a `Vec` or a `Vec