From 2b4119017397ab5e0f652d555145440809d0a0d4 Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Wed, 18 Sep 2024 23:07:31 +0200 Subject: [PATCH 01/12] chore: Update use declarations --- src/components/merkle.rs | 4 ++-- src/function.rs | 2 +- src/jet.rs | 3 ++- src/main.rs | 4 +--- src/util.rs | 3 +-- src/value.rs | 8 ++++---- 6 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/components/merkle.rs b/src/components/merkle.rs index 186b336..44d61e4 100644 --- a/src/components/merkle.rs +++ b/src/components/merkle.rs @@ -2,12 +2,12 @@ use std::sync::Arc; use js_sys::{Array, Object}; use leptos::*; +use simfony::simplicity; use simplicity::dag::DagLike; +use simplicity::dag::NoSharing; use simplicity::node; use wasm_bindgen::prelude::*; -use crate::simplicity; -use crate::simplicity::dag::NoSharing; use crate::util::{DisplayInner, Expression}; #[component] diff --git a/src/function.rs b/src/function.rs index 451a421..d4f2f80 100644 --- a/src/function.rs +++ b/src/function.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use either::Either; use simfony::debug::{DebugSymbols, FallibleCall, FallibleCallName}; +use simfony::simplicity; use simfony::SatisfiedProgram; use simplicity::node::Inner; use simplicity::types::Final; @@ -10,7 +11,6 @@ use simplicity::Value; use crate::jet; use crate::jet::JetFailed; -use crate::simplicity; use crate::util::Expression; #[derive(Debug, Clone, Eq, PartialEq)] diff --git a/src/jet.rs b/src/jet.rs index 984605f..8ab4eef 100644 --- a/src/jet.rs +++ b/src/jet.rs @@ -1,5 +1,6 @@ -use crate::{simplicity, value}; +use crate::value; +use simfony::simplicity; use simplicity::ffi::c_jets::frame_ffi::{c_readBit, c_writeBit}; use simplicity::ffi::c_jets::uword_width; use simplicity::ffi::ffi::UWORD; diff --git a/src/main.rs b/src/main.rs index 76182d3..90755f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,10 +5,8 @@ mod jet; mod util; mod value; -pub use simfony::simplicity; - use components::App; -use leptos::*; +use leptos::{mount_to_body, view}; #[cfg(test)] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); diff --git a/src/util.rs b/src/util.rs index c513095..fc68f15 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,13 +1,12 @@ use simfony::SatisfiedProgram; use std::fmt; +use simfony::simplicity; use simplicity::dag::{DagLike, MaxSharing, NoSharing}; use simplicity::jet::Elements; use simplicity::node::Inner; use simplicity::{node, RedeemNode}; -use crate::simplicity; - pub type Expression = RedeemNode; pub fn program_from_string(s: &str) -> Result { diff --git a/src/value.rs b/src/value.rs index 7d65d4f..909329c 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,9 +1,9 @@ -use simplicity::types::Final; -use simplicity::Value; use std::sync::Arc; -use crate::simplicity; -use crate::simplicity::types::CompleteBound; +use simfony::simplicity; +use simplicity::types::CompleteBound; +use simplicity::types::Final; +use simplicity::Value; // TODO: Upstream to rust-simplicity pub fn from_padded_bits>(it: &mut I, ty: &Final) -> Option { From c11a49f44d41b5953d4cc01e6cb69f69282b36fa Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Thu, 19 Sep 2024 22:15:20 +0200 Subject: [PATCH 02/12] chore: Update Cargo --- Cargo.lock | 23 +++++++++++++++++++---- Cargo.toml | 3 +++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f9bf64a..18b71d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -494,6 +494,18 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "gloo-utils" version = "0.2.0" @@ -1461,12 +1473,15 @@ version = "0.1.0" dependencies = [ "console_error_panic_hook", "either", + "gloo-timers", "hex-conservative", "itertools 0.13.0", "js-sys", "leptos", "simfony", + "wasm-bindgen-futures", "wasm-bindgen-test", + "web-sys", ] [[package]] @@ -1519,18 +1534,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.56" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 840cee3..973a6b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,10 @@ leptos = { version = "0.6.14", features = ["csr"] } console_error_panic_hook = "0.1.7" hex-conservative = "0.1.1" js-sys = "0.3.70" +web-sys = { version = "0.3.70", features = ["Navigator", "Clipboard"] } either = "1.13.0" +wasm-bindgen-futures = "0.4.43" +gloo-timers = { version = "0.3.0", features = ["futures"] } [dev-dependencies] wasm-bindgen-test = "0.3.43" From 2997f4771ae78d54e7488f2c86b3df90900b20a6 Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Tue, 24 Sep 2024 00:49:50 +0200 Subject: [PATCH 03/12] chore: Add CSS --- src/assets/style/style.scss | 65 ++++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/src/assets/style/style.scss b/src/assets/style/style.scss index c1c5260..2e04e09 100644 --- a/src/assets/style/style.scss +++ b/src/assets/style/style.scss @@ -111,4 +111,67 @@ p { @keyframes grow { 0% {max-height: 0px; padding-top: 0px; padding-bottom: 0px} 100% {max-height: 91px;} -} \ No newline at end of file +} + +.tabs { + overflow: hidden; + border: 1px solid #ccc; + background-color: #f1f1f1; + + button { + background-color: inherit; + float: left; + border: none; + outline: none; + cursor: pointer; + padding: 14px 16px; + + &:hover { + background-color: #ddd; // Add hover effect + } + + &.active { + background-color: #bbb; // Darker color for active tabs + } + } +} + +.error-box { + border: 1px dotted #ff0000; + color: #ff0000; + word-break: break-word; + margin-top: 10px; + padding: 10px; + overflow-x: scroll; + + scrollbar-color: $background-dark #b00000; + scrollbar-width: thin; +} + +.button-row { + display: flex; +} + +.button-col { + display: flex; + flex-direction: column; +} + +.submit-button, .push-button, .pop-button, .copy-button { + border: 1px solid #5134E0; + padding: 4px; + border-radius: 4px; + display: flex; + justify-content: center; + align-items: center; + color: #ccc; + background: #191C21; + + &:hover { + background: #5134e0; + } +} + +input:invalid { + background-color: lightpink; +} From b4b62437de1087e8638e34160a316705bd6b1ebe Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Tue, 24 Sep 2024 00:48:41 +0200 Subject: [PATCH 04/12] refactor: Reset App component In the following commits, we will rebuild App from scratch. --- src/components/app.rs | 155 +----------------------------------------- 1 file changed, 2 insertions(+), 153 deletions(-) diff --git a/src/components/app.rs b/src/components/app.rs index c02af9a..68c32ed 100644 --- a/src/components/app.rs +++ b/src/components/app.rs @@ -1,157 +1,6 @@ -use leptos::*; -use wasm_bindgen::prelude::*; - -use super::analysis::Analysis; -use super::examples::{ExampleProgramDescription, SelectExampleProgram}; -use super::merkle::{self, MerkleExplorer}; -use super::parser::ParseError; - -use crate::function::Runner; -use crate::util; - -#[wasm_bindgen(module = "/src/assets/js/button_effects.js")] -extern "C" { - fn button_success_animation(); - fn button_fail_animation(); -} - -#[wasm_bindgen(module = "/src/assets/js/copy_button.js")] -extern "C" { - fn copy_program(text: &str); -} - -#[wasm_bindgen(module = "/src/assets/js/badger.js")] -extern "C" { - fn load_badger(); - fn laser_eyes(); - fn hide_badger(val: bool); - fn hide_badger_timed(); -} +use leptos::{component, view, IntoView}; #[component] pub fn App() -> impl IntoView { - create_effect(move |_| { - load_badger(); - }); - - let (program_str, set_program_str) = create_signal("".to_string()); - let (run_result, set_run_result) = create_signal::>>(None); - let (is_running, set_is_running) = create_signal(false); - let (name, set_name) = create_signal::>(None); - let (graph_toggle, set_graph_toggle) = create_signal(false); - - let program_result = Signal::derive(move || util::program_from_string(&program_str.get())); - let program = Signal::derive(move || program_result.get().ok().map(|x| x.simplicity)); - let parse_error = Signal::derive(move || program_result.get().err()); - - create_effect(move |_| match parse_error.get() { - Some(_) => hide_badger(true), - None => hide_badger(false), - }); - - let update_program_str = move |s: String| { - set_program_str.set(s); - set_run_result.set(None); - }; - let run_program = move || { - let program = match program_result.get() { - Ok(program) => program, - Err(..) => return, - }; - set_is_running.set(true); - let simplicity = program.simplicity.clone(); - let mut runner = Runner::for_program(program); - match runner.run() { - Ok(_) => { - set_run_result.set(Some(Ok("Program success".to_string()))); - laser_eyes(); - button_success_animation(); - merkle::reload_graph(simplicity); - set_is_running.set(false); - } - Err(error) => { - hide_badger_timed(); - set_run_result.set(Some(Err(error.to_string()))); - button_fail_animation(); - set_is_running.set(false); - } - } - }; - - view! { -
- - -
-

Simfony IDE

-

- Simfony - " is a high-level language for writing Bitcoin smart contracts." -

-

- "Simfony looks and feels like " - Rust - ". Just how Rust compiles down to assembly language, Simfony compiles down to " - Simplicity - " bytecode. Developers write Simfony, full nodes execute Simplicity." -

-
- -
- - -
-
- -
-

Program

-

Select an example program or enter your own program below.

-
- -
- -
-
- Program copied - -
- + + {apply_changes} + +
+ } +} diff --git a/src/components/program_window/transaction_tab.rs b/src/components/program_window/transaction_tab.rs new file mode 100644 index 0000000..348f20e --- /dev/null +++ b/src/components/program_window/transaction_tab.rs @@ -0,0 +1,107 @@ +use leptos::{ + component, create_node_ref, create_rw_signal, ev, html, use_context, view, IntoView, NodeRef, + RwSignal, Signal, SignalGet, SignalGetUntracked, SignalSet, +}; +use simfony::{elements, simplicity}; +use std::sync::Arc; + +use crate::components::apply_changes::ApplyChanges; + +#[derive(Copy, Clone, Debug)] +pub struct TxEnv { + lock_time: RwSignal, + sequence: RwSignal, +} + +impl TxEnv { + pub fn new(lock_time: u32, sequence: u32) -> Self { + Self { + lock_time: create_rw_signal(elements::LockTime::from_consensus(lock_time)), + sequence: create_rw_signal(elements::Sequence::from_consensus(sequence)), + } + } + + pub fn environment( + self, + ) -> Signal>> { + Signal::derive(move || { + simfony::dummy_env::dummy_with(self.lock_time.get(), self.sequence.get()) + }) + } +} + +#[component] +pub fn TransactionTab() -> impl IntoView { + let tx_env = use_context::().expect("tx environment should exist in context"); + let apply_changes = ApplyChanges::default(); + + let lock_time_ref: NodeRef = create_node_ref(); + let sequence_ref: NodeRef = create_node_ref(); + + let submit_transaction = move |event: ev::SubmitEvent| { + event.prevent_default(); // stop page from reloading + let lock_time = lock_time_ref + .get() + .expect(" should be mounted") + .value() + .parse::() + .expect(" should be valid u32"); + let lock_time = elements::LockTime::from_consensus(lock_time); + tx_env.lock_time.set(lock_time); + + let sequence = sequence_ref + .get() + .expect(" should be mounted") + .value() + .parse::() + .expect(" should be valid u32"); + let sequence = elements::Sequence::from_consensus(sequence); + tx_env.sequence.set(sequence); + + apply_changes.set_success(true); + }; + + let lock_time_initial_value = tx_env.lock_time.get_untracked().to_string(); + let sequence_initial_value = tx_env.sequence.get_untracked().to_string(); + + view! { +
+

+ Transaction Environment +

+

+ "Currently, the runtime uses a " + + dummy transaction environment + + ". Only the lock time and sequence number can be changed. " + "More customization will follow in future updates." +

+
+
+ + +
+ {apply_changes} +
+
+ } +} diff --git a/src/components/program_window/witness_tab.rs b/src/components/program_window/witness_tab.rs new file mode 100644 index 0000000..77a1ee1 --- /dev/null +++ b/src/components/program_window/witness_tab.rs @@ -0,0 +1,89 @@ +use std::collections::HashMap; + +use leptos::{ + component, create_rw_signal, use_context, view, IntoView, SignalGetUntracked, SignalSet, + SignalUpdate, +}; +use simfony::error::{WithFile, WithSpan}; +use simfony::parse::ParseFromStr; +use simfony::str::WitnessName; +use simfony::value::Value; + +use crate::components::app::WitnessWrapper; +use crate::components::apply_changes::ApplyChanges; +use crate::components::table_form::TableForm; + +// FIXME: Upstream to simfony +fn value_parse_from_str( + s: &str, + ty: &simfony::types::ResolvedType, +) -> Result { + let expr = simfony::parse::Expression::parse_from_str(s)?; + let expr = simfony::ast::Expression::analyze_const(&expr, ty).with_file(s)?; + Value::from_const_expr(&expr) + .ok_or(simfony::error::Error::ExpressionUnexpectedType(ty.clone())) + .with_span(&expr) + .with_file(s) +} + +fn parse_value(name: &str, ty: &str, value: &str) -> Result<(WitnessName, Value), String> { + let parsed_name = WitnessName::parse_from_str(name) + .map_err(|error| format!("Faulty name `{name}`\n{error}"))?; + let parsed_ty = simfony::types::ResolvedType::parse_from_str(ty) + .map_err(|error| format!("Faulty type for `{name}`\n{error}"))?; + let parsed_value = value_parse_from_str(value, &parsed_ty) + .map_err(|error| format!("Faulty value for `{name}`\n{error}"))?; + + Ok((parsed_name, parsed_value)) +} + +#[component] +pub fn WitnessTab() -> impl IntoView { + let witness_values = use_context::().expect("witness should exist in context"); + let parse_error = create_rw_signal("".to_string()); + let apply_changes = ApplyChanges::default(); + + let header = ["Name".to_string(), "Type".to_string(), "Value".to_string()]; + let initial_rows = witness_values + .0 + .get_untracked() + .into_iter() + .map(|(name, value)| [name.to_string(), value.ty().to_string(), value.to_string()]) + .chain(std::iter::once(std::array::from_fn(|_| String::default()))) + .collect::>(); + + let submit_witness = move |rows_inputs: Vec<[String; 3]>| { + let mut new_witness_values = HashMap::new(); + for [name, ty, value] in &rows_inputs { + if name.is_empty() { + continue; + } + + match parse_value(name, ty, value) { + Ok((parsed_name, parsed_value)) => { + new_witness_values.insert(parsed_name, parsed_value); + } + Err(error) => { + parse_error.set(error); + apply_changes.set_success(false); + return; + } + } + } + parse_error.update(String::clear); + witness_values.0.set(new_witness_values); + apply_changes.set_success(true); + }; + + view! { +
+ +
+ } +} From 5532d2ae7c4166d2fa1ab11ba471c1a5bd3e4875 Mon Sep 17 00:00:00 2001 From: Christian Lewe Date: Tue, 24 Sep 2024 16:44:02 +0200 Subject: [PATCH 12/12] feat: Add RunWindow component --- src/components/app.rs | 8 + src/components/mod.rs | 1 + src/components/run_window/hash_store_tab.rs | 155 ++++++++ src/components/run_window/key_store_tab.rs | 386 ++++++++++++++++++++ src/components/run_window/mod.rs | 30 ++ src/components/run_window/run_tab.rs | 81 ++++ 6 files changed, 661 insertions(+) create mode 100644 src/components/run_window/hash_store_tab.rs create mode 100644 src/components/run_window/key_store_tab.rs create mode 100644 src/components/run_window/mod.rs create mode 100644 src/components/run_window/run_tab.rs diff --git a/src/components/app.rs b/src/components/app.rs index ea8bf6a..2406347 100644 --- a/src/components/app.rs +++ b/src/components/app.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; +use crate::components::run_window::{HashedData, RunWindow, SignedData, SigningKeys}; use leptos::{component, create_rw_signal, provide_context, view, IntoView, RwSignal}; use simfony::str::WitnessName; @@ -19,8 +20,15 @@ pub fn App() -> impl IntoView { provide_context(WitnessWrapper(witness_values)); let tx_env = TxEnv::new(0, 0); provide_context(tx_env); + let signing_keys = SigningKeys::new(1); + provide_context(signing_keys); + let signed_data = SignedData::new(tx_env.environment()); + provide_context(signed_data); + let hashed_data = HashedData::new(1); + provide_context(hashed_data); view! { + } } diff --git a/src/components/mod.rs b/src/components/mod.rs index 47bbed8..162f987 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -4,6 +4,7 @@ mod apply_changes; mod copy_to_clipboard; mod error; mod program_window; +mod run_window; mod table_form; mod tabs; diff --git a/src/components/run_window/hash_store_tab.rs b/src/components/run_window/hash_store_tab.rs new file mode 100644 index 0000000..6538a40 --- /dev/null +++ b/src/components/run_window/hash_store_tab.rs @@ -0,0 +1,155 @@ +use hashes::{sha256, Hash}; +use hex_conservative::DisplayHex; +use leptos::{ + component, create_rw_signal, use_context, view, with, For, IntoView, RwSignal, Signal, + SignalGet, SignalSet, SignalUpdate, View, +}; +use simfony::elements::hashes; + +use crate::components::copy_to_clipboard::CopyToClipboard; + +#[derive(Clone, Copy, Debug)] +pub struct HashedData { + hash_count: RwSignal, + preimages: Signal>, +} + +impl HashedData { + pub fn new(hash_count: u32) -> Self { + let hash_count = create_rw_signal(hash_count); + let preimages = Signal::derive(move || -> Vec<[u8; 32]> { + (0..hash_count.get()).map(new_preimage).collect() + }); + Self { + hash_count, + preimages, + } + } + + pub fn push_hash(&self) { + self.hash_count.update(|n| *n += 1); + } + + pub fn pop_hash(&self) { + let n = self.hash_count.get(); + if 1 < n { + self.hash_count.set(n - 1); + } + } + + pub fn hashes(self) -> Signal> { + let preimages = self.preimages; + Signal::derive(move || { + with!(|preimages| { + preimages + .iter() + .map(|preimage| sha256::Hash::hash(preimage)) + .collect() + }) + }) + } +} + +fn new_preimage(index: u32) -> [u8; 32] { + let mut preimage = [0; 32]; + preimage[28..].copy_from_slice(&index.to_be_bytes()); + preimage +} + +#[component] +pub fn HashStoreTab() -> impl IntoView { + view! { +
+ + +
+ } +} + +#[component] +fn CopyHashesToClipboard() -> impl IntoView { + let hashed_data = use_context::().expect("hashed data should exist in context"); + let copy_single_hash = move |(index, hash): (usize, sha256::Hash)| -> View { + let label = format!("Hash {}", index); + let hash_hex = format!("0x{}", hash.to_byte_array().as_hex()); + + view! { + + } + }; + + view! { +
+

+ Hashes +

+
+ + + +
+
+ } +} + +#[component] +fn CopyPreimagesToClipboard() -> impl IntoView { + let hashed_data = use_context::().expect("hashed data should exist in context"); + let copy_single_preimage = move |(index, preimage): (usize, [u8; 32])| -> View { + let label = format!("Pre {}", index); + let preimage_hex = format!("0x{}", preimage.as_hex()); + + view! { + + } + }; + + view! { +
+

+ Preimages +

+
+ + + +
+
+ } +} diff --git a/src/components/run_window/key_store_tab.rs b/src/components/run_window/key_store_tab.rs new file mode 100644 index 0000000..04ef186 --- /dev/null +++ b/src/components/run_window/key_store_tab.rs @@ -0,0 +1,386 @@ +use std::sync::Arc; + +use elements::hashes; +use elements::secp256k1_zkp; +use hex_conservative::{DisplayHex, FromHex}; +use leptos::{ + component, create_rw_signal, ev, event_target_value, html, use_context, view, with, For, + IntoView, NodeRef, RwSignal, Signal, SignalGet, SignalGetUntracked, SignalSet, SignalUpdate, + View, +}; +use simfony::{elements, simplicity}; + +use crate::components::copy_to_clipboard::CopyToClipboard; + +#[derive(Copy, Clone, Debug)] +pub struct SigningKeys { + key_count: RwSignal, + secret_keys: Signal>, +} + +impl SigningKeys { + pub fn new(key_count: u32) -> Self { + let key_count = create_rw_signal(key_count); + let secret_keys = Signal::derive(move || -> Vec { + let mut index = 0; + (0..key_count.get()) + .map(|_| { + let (key, new_index) = new_key(index); + index = new_index; + key + }) + .collect() + }); + Self { + key_count, + secret_keys, + } + } + + pub fn push_key(&self) { + self.key_count.update(|n| *n += 1); + } + + pub fn pop_key(&self) { + let n = self.key_count.get(); + if 1 < n { + self.key_count.set(n - 1); + } + } + + pub fn public_keys(self) -> Signal> { + let secret_keys = self.secret_keys; + Signal::derive(move || { + with!(|secret_keys| { + secret_keys + .iter() + .map(|key| key.x_only_public_key().0) + .collect() + }) + }) + } + + pub fn signatures( + self, + message: Signal, + ) -> Signal> { + let secret_keys = self.secret_keys; + Signal::derive(move || { + with!(|secret_keys| { + secret_keys + .iter() + .map(|key| key.sign_schnorr(message.get())) + .collect() + }) + }) + } +} + +fn new_key(start_index: u32) -> (secp256k1_zkp::Keypair, u32) { + let mut offset = 1; + loop { + let index = start_index + offset; + let mut secret_key_bytes = [0u8; 32]; + secret_key_bytes[28..].copy_from_slice(&index.to_be_bytes()); + match secp256k1_zkp::Keypair::from_seckey_slice(secp256k1_zkp::SECP256K1, &secret_key_bytes) + { + Ok(keypair) => return (keypair, index), + Err(..) => { + offset += 1; + } + } + } +} + +#[derive(Copy, Clone, Debug)] +enum SignedDataMode { + SighashAll, + ThirtyTwoBytes, + HashPreimageBytes, +} + +#[derive(Clone, Copy, Debug)] +pub struct SignedData { + mode: RwSignal, + sighash_all: Signal, + thirty_two_bytes: RwSignal<[u8; 32]>, + hash_preimage_bytes: RwSignal>, +} + +impl SignedData { + pub fn new( + tx_env: Signal>>, + ) -> Self { + let sighash_all = + Signal::derive(move || with!(|tx_env| { tx_env.c_tx_env().sighash_all() })); + Self { + mode: create_rw_signal(SignedDataMode::SighashAll), + sighash_all, + thirty_two_bytes: create_rw_signal([0; 32]), + hash_preimage_bytes: create_rw_signal(vec![]), + } + } + + pub fn message(self) -> Signal { + Signal::derive(move || match self.mode.get() { + SignedDataMode::SighashAll => self.sighash_all.get().into(), + SignedDataMode::ThirtyTwoBytes => { + secp256k1_zkp::Message::from_digest(self.thirty_two_bytes.get()) + } + SignedDataMode::HashPreimageBytes => { + secp256k1_zkp::Message::from_hashed_data::( + self.hash_preimage_bytes.get().as_ref(), + ) + } + }) + } +} + +#[component] +pub fn KeyStoreTab() -> impl IntoView { + view! { +
+ + + +
+ } +} + +#[component] +fn CopyPublicKeysToClipboard() -> impl IntoView { + let signing_keys = use_context::().expect("signing keys should exist in context"); + let copy_single_public_key = + move |(index, key): (usize, secp256k1_zkp::XOnlyPublicKey)| -> View { + let label = format!("Key {}", index); + let xonly_hex = format!("0x{}", key.serialize().as_hex()); + + view! { + + } + }; + + view! { +
+

+ Public Keys +

+
+ + + +
+
+ } +} + +#[component] +fn CopySignaturesToClipboard() -> impl IntoView { + let signing_keys = use_context::().expect("signing keys should exist in context"); + let signed_data = use_context::().expect("signed data should exist in context"); + + let copy_single_signature = + move |(index, signature): (usize, secp256k1_zkp::schnorr::Signature)| -> View { + let label = format!("Sig {}", index); + let signature_hex = format!("0x{}", signature.serialize().as_hex()); + + view! { + + } + }; + + view! { +
+

+ Signatures +

+
+ + + +
+
+ } +} + +#[component] +fn SelectSignedData() -> impl IntoView { + let signed_data = use_context::().expect("signed data should exist in context"); + let thirty_two_bytes_is_insane = create_rw_signal(false); + let hash_preimage_bytes_is_insane = create_rw_signal(false); + + let sighash_all_initial_checked = + matches!(signed_data.mode.get_untracked(), SignedDataMode::SighashAll); + let thirty_two_bytes_initial_checked = matches!( + signed_data.mode.get_untracked(), + SignedDataMode::ThirtyTwoBytes + ); + let hash_preimage_bytes_initial_checked = matches!( + signed_data.mode.get_untracked(), + SignedDataMode::HashPreimageBytes + ); + let thirty_two_bytes_initial_value = format!( + "0x{}", + signed_data.thirty_two_bytes.get_untracked().as_hex() + ); + let hash_preimage_bytes_initial_value = format!( + "0x{}", + signed_data.hash_preimage_bytes.get_untracked().as_hex() + ); + + let sighash_all_radio_ref = NodeRef::::new(); + let thirty_two_bytes_text_ref = NodeRef::::new(); + let hash_preimage_bytes_text_ref = NodeRef::::new(); + + let select_sighash_all = move |_event: ev::Event| { + signed_data.mode.set(SignedDataMode::SighashAll); + }; + let select_thirty_two_bytes = move |_event: ev::Event| { + signed_data.mode.set(SignedDataMode::ThirtyTwoBytes); + }; + let select_hash_preimage_bytes = move |_event: ev::Event| { + signed_data.mode.set(SignedDataMode::HashPreimageBytes); + }; + let update_thirty_two_bytes = move |event: ev::Event| match <[u8; 32]>::from_hex( + event_target_value(&event) + .as_str() + .trim() + .trim_start_matches("0x"), + ) { + Ok(bytes) => { + signed_data.thirty_two_bytes.set(bytes); + thirty_two_bytes_text_ref + .get() + .expect(" should be mounted") + .set_custom_validity(""); + thirty_two_bytes_is_insane.set(false); + } + Err(..) => { + sighash_all_radio_ref + .get() + .expect(" should be mounted") + .set_checked(true); + thirty_two_bytes_text_ref + .get() + .expect(" should be mounted") + .set_custom_validity("Expected exactly 64 hex digits"); + thirty_two_bytes_is_insane.set(true); + } + }; + let update_hash_preimage_bytes = move |event: ev::Event| match >::from_hex( + event_target_value(&event) + .as_str() + .trim() + .trim_start_matches("0x"), + ) { + Ok(bytes) => { + signed_data.hash_preimage_bytes.set(bytes); + hash_preimage_bytes_text_ref + .get() + .expect(" should be mounted") + .set_custom_validity(""); + hash_preimage_bytes_is_insane.set(false); + } + Err(..) => { + sighash_all_radio_ref + .get() + .expect(" should be mounted") + .set_checked(true); + hash_preimage_bytes_text_ref + .get() + .expect(" should be mounted") + .set_custom_validity("Expected even number of hex digits"); + hash_preimage_bytes_is_insane.set(true); + } + }; + + view! { +
+

+ Signed Data +

+
+ + + +
+
+ } +} diff --git a/src/components/run_window/mod.rs b/src/components/run_window/mod.rs new file mode 100644 index 0000000..0057041 --- /dev/null +++ b/src/components/run_window/mod.rs @@ -0,0 +1,30 @@ +mod hash_store_tab; +mod key_store_tab; +mod run_tab; + +use leptos::{component, view, IntoView}; + +use self::hash_store_tab::HashStoreTab; +use self::key_store_tab::KeyStoreTab; +use self::run_tab::RuntimeTab; +use crate::components::tabs::{Tab, Tabs}; + +pub use self::hash_store_tab::HashedData; +pub use self::key_store_tab::{SignedData, SigningKeys}; + +#[component] +pub fn RunWindow() -> impl IntoView { + view! { + + + + + + + + + + + + } +} diff --git a/src/components/run_window/run_tab.rs b/src/components/run_window/run_tab.rs new file mode 100644 index 0000000..95e50e7 --- /dev/null +++ b/src/components/run_window/run_tab.rs @@ -0,0 +1,81 @@ +use leptos::{ + component, create_rw_signal, ev, spawn_local, use_context, view, Fragment, IntoView, SignalGet, + SignalSet, SignalUpdate, +}; + +use crate::components::app::{ProgramWrapper, WitnessWrapper}; +use crate::components::error::ErrorBox; +use crate::function::Runner; + +#[component] +pub fn RuntimeTab() -> impl IntoView { + let program_text = use_context::().expect("program should exist in context"); + let witness_values = use_context::().expect("witness should exist in context"); + let run_error = create_rw_signal("".to_string()); + let run_succeeded = create_rw_signal::>(None); + + let run_program = move |_event: ev::MouseEvent| { + // TODO: Store simfony::witness::WitnessValues in witness signal + let mut witness_values1 = simfony::witness::WitnessValues::empty(); + for (name, value) in witness_values.0.get() { + witness_values1 + .insert(name, value) + .expect("Same name cannot be assigned to two values"); + } + + // TODO: Store unsatisfied but compiled Simfony program in program signal + let satisfied_program = + match simfony::satisfy(program_text.0.get().as_str(), &witness_values1) { + Ok(x) => x, + Err(error) => { + run_error.set(error); + return; + } + }; + let mut runner = Runner::for_program(satisfied_program); + let success = match runner.run() { + Ok(..) => { + run_error.update(String::clear); + true + } + Err(error) => { + run_error.set(error.to_string()); + false + } + }; + spawn_local(async move { + run_succeeded.set(Some(success)); + gloo_timers::future::TimeoutFuture::new(500).await; + run_succeeded.set(None); + }); + }; + + let run_button_view = move || -> Fragment { + match run_succeeded.get() { + None => view! { + Run program + + }, + Some(true) => view! { + Success + + }, + Some(false) => view! { + Failure + + }, + } + }; + + view! { +
+ + +
+ } +}