From f8eff73345f54446ee51e36ff89e33b55d6b65ee Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Sat, 3 May 2025 10:09:50 -0300 Subject: [PATCH 001/155] scaffold comrade & component --- Cargo.toml | 23 +++- .../comrade-component/.vscode/settings.json | 10 ++ crates/comrade-component/Cargo.toml | 22 ++++ crates/comrade-component/src/bindings.rs | 109 ++++++++++++++++++ crates/comrade-component/src/lib.rs | 15 +++ crates/comrade-component/wit/world.wit | 6 + crates/comrade/Cargo.toml | 13 +++ crates/comrade/src/lib.rs | 18 +++ 8 files changed, 210 insertions(+), 6 deletions(-) create mode 100644 crates/comrade-component/.vscode/settings.json create mode 100644 crates/comrade-component/Cargo.toml create mode 100644 crates/comrade-component/src/bindings.rs create mode 100644 crates/comrade-component/src/lib.rs create mode 100644 crates/comrade-component/wit/world.wit create mode 100644 crates/comrade/Cargo.toml create mode 100644 crates/comrade/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index bce843f..ed7111b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,8 @@ members = [ "crates/bs", "crates/bs-traits", "crates/bsp2p", + "crates/comrade", + "crates/comrade-component", "crates/content-addressable", "crates/multibase", "crates/multicid", @@ -32,7 +34,7 @@ license = "FSL-1.1 OR Apache-2.0" unexpected_cfgs = { level = "warn", check-cfg = [ 'cfg(feature, values("cargo-clippy"))', 'cfg(fuzzing)', -]} +] } [workspace.dependencies] # Crate ependencies @@ -59,14 +61,23 @@ rand = { version = "0.9.0", features = ["os_rng"] } rand_core = "0.9.3" rand_6 = { version = "0.6.4", package = "rand" } rand_core_6 = { version = "0.6.4", package = "rand_core" } -serde = { version = "1.0.219", default-features = false, features = ["alloc", "derive"]} -serde_cbor = { version = "0.11.2", features = ["tags"]} -serde_json = { version = "1.0.104"} -serde_test = { version = "1.0.104"} +serde = { version = "1.0.219", default-features = false, features = [ + "alloc", + "derive", +] } +serde_cbor = { version = "0.11.2", features = ["tags"] } +serde_json = { version = "1.0.104" } +serde_test = { version = "1.0.104" } sha3 = "0.10.8" test-log = { version = "0.2.17", features = ["trace", "color"] } thiserror = "2.0.12" -tokio = { version = "1.44.2", features = ["fs", "io-util", "macros", "rt", "test-util"] } +tokio = { version = "1.44.2", features = [ + "fs", + "io-util", + "macros", + "rt", + "test-util", +] } tracing = "0.1.41" tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } unsigned-varint = { version = "0.8.0", features = ["std"] } diff --git a/crates/comrade-component/.vscode/settings.json b/crates/comrade-component/.vscode/settings.json new file mode 100644 index 0000000..b945667 --- /dev/null +++ b/crates/comrade-component/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "rust-analyzer.check.overrideCommand": [ + "cargo", + "component", + "check", + "--workspace", + "--all-targets", + "--message-format=json" + ], +} diff --git a/crates/comrade-component/Cargo.toml b/crates/comrade-component/Cargo.toml new file mode 100644 index 0000000..284b8e5 --- /dev/null +++ b/crates/comrade-component/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "comrade-component" +version.workspace = true +edition.workspace = true +authors.workspace = true +description.workspace = true +readme.workspace = true +license = "MIT" + +[dependencies] +wit-bindgen-rt = { version = "0.41.0", features = ["bitflags"] } + +[lints] +workspace = true + +[lib] +crate-type = ["cdylib"] + +[package.metadata.component] +package = "component:comrade-component" + +[package.metadata.component.dependencies] diff --git a/crates/comrade-component/src/bindings.rs b/crates/comrade-component/src/bindings.rs new file mode 100644 index 0000000..cbc8fcd --- /dev/null +++ b/crates/comrade-component/src/bindings.rs @@ -0,0 +1,109 @@ +// Generated by `wit-bindgen` 0.41.0. DO NOT EDIT! +// Options used: +// * runtime_path: "wit_bindgen_rt" +#[doc(hidden)] +#[allow(non_snake_case)] +pub unsafe fn _export_hello_world_cabi() -> *mut u8 { + #[cfg(target_arch = "wasm32")] _rt::run_ctors_once(); + let result0 = T::hello_world(); + let ptr1 = (&raw mut _RET_AREA.0).cast::(); + let vec2 = (result0.into_bytes()).into_boxed_slice(); + let ptr2 = vec2.as_ptr().cast::(); + let len2 = vec2.len(); + ::core::mem::forget(vec2); + *ptr1.add(::core::mem::size_of::<*const u8>()).cast::() = len2; + *ptr1.add(0).cast::<*mut u8>() = ptr2.cast_mut(); + ptr1 +} +#[doc(hidden)] +#[allow(non_snake_case)] +pub unsafe fn __post_return_hello_world(arg0: *mut u8) { + let l0 = *arg0.add(0).cast::<*mut u8>(); + let l1 = *arg0.add(::core::mem::size_of::<*const u8>()).cast::(); + _rt::cabi_dealloc(l0, l1, 1); +} +pub trait Guest { + fn hello_world() -> _rt::String; +} +#[doc(hidden)] +macro_rules! __export_world_example_cabi { + ($ty:ident with_types_in $($path_to_types:tt)*) => { + const _ : () = { #[unsafe (export_name = "hello-world")] unsafe extern "C" fn + export_hello_world() -> * mut u8 { unsafe { $($path_to_types)*:: + _export_hello_world_cabi::<$ty > () } } #[unsafe (export_name = + "cabi_post_hello-world")] unsafe extern "C" fn _post_return_hello_world(arg0 : * + mut u8,) { unsafe { $($path_to_types)*:: __post_return_hello_world::<$ty > (arg0) + } } }; + }; +} +#[doc(hidden)] +pub(crate) use __export_world_example_cabi; +#[cfg_attr(target_pointer_width = "64", repr(align(8)))] +#[cfg_attr(target_pointer_width = "32", repr(align(4)))] +struct _RetArea([::core::mem::MaybeUninit; 2 * ::core::mem::size_of::<*const u8>()]); +static mut _RET_AREA: _RetArea = _RetArea( + [::core::mem::MaybeUninit::uninit(); 2 * ::core::mem::size_of::<*const u8>()], +); +#[rustfmt::skip] +mod _rt { + #![allow(dead_code, clippy::all)] + #[cfg(target_arch = "wasm32")] + pub fn run_ctors_once() { + wit_bindgen_rt::run_ctors_once(); + } + pub unsafe fn cabi_dealloc(ptr: *mut u8, size: usize, align: usize) { + if size == 0 { + return; + } + let layout = alloc::Layout::from_size_align_unchecked(size, align); + alloc::dealloc(ptr, layout); + } + pub use alloc_crate::string::String; + pub use alloc_crate::alloc; + extern crate alloc as alloc_crate; +} +/// Generates `#[unsafe(no_mangle)]` functions to export the specified type as +/// the root implementation of all generated traits. +/// +/// For more information see the documentation of `wit_bindgen::generate!`. +/// +/// ```rust +/// # macro_rules! export{ ($($t:tt)*) => (); } +/// # trait Guest {} +/// struct MyType; +/// +/// impl Guest for MyType { +/// // ... +/// } +/// +/// export!(MyType); +/// ``` +#[allow(unused_macros)] +#[doc(hidden)] +macro_rules! __export_example_impl { + ($ty:ident) => { + self::export!($ty with_types_in self); + }; + ($ty:ident with_types_in $($path_to_types_root:tt)*) => { + $($path_to_types_root)*:: __export_world_example_cabi!($ty with_types_in + $($path_to_types_root)*); + }; +} +#[doc(inline)] +pub(crate) use __export_example_impl as export; +#[cfg(target_arch = "wasm32")] +#[unsafe( + link_section = "component-type:wit-bindgen:0.41.0:component:comrade-component:example:encoded world" +)] +#[doc(hidden)] +#[allow(clippy::octal_escapes)] +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 192] = *b"\ +\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07C\x01A\x02\x01A\x02\x01\ +@\0\0s\x04\0\x0bhello-world\x01\0\x04\0#component:comrade-component/example\x04\0\ +\x0b\x0d\x01\0\x07example\x03\0\0\0G\x09producers\x01\x0cprocessed-by\x02\x0dwit\ +-component\x070.227.1\x10wit-bindgen-rust\x060.41.0"; +#[inline(never)] +#[doc(hidden)] +pub fn __link_custom_section_describing_imports() { + wit_bindgen_rt::maybe_link_cabi_realloc(); +} diff --git a/crates/comrade-component/src/lib.rs b/crates/comrade-component/src/lib.rs new file mode 100644 index 0000000..7bea1f6 --- /dev/null +++ b/crates/comrade-component/src/lib.rs @@ -0,0 +1,15 @@ +#[allow(warnings)] +mod bindings; + +use bindings::Guest; + +struct Component; + +impl Guest for Component { + /// Say hello! + fn hello_world() -> String { + "Hello, World!".to_string() + } +} + +bindings::export!(Component with_types_in bindings); diff --git a/crates/comrade-component/wit/world.wit b/crates/comrade-component/wit/world.wit new file mode 100644 index 0000000..3e29bb5 --- /dev/null +++ b/crates/comrade-component/wit/world.wit @@ -0,0 +1,6 @@ +package component:comrade-component; + +/// An example world for the component to target. +world example { + export hello-world: func() -> string; +} diff --git a/crates/comrade/Cargo.toml b/crates/comrade/Cargo.toml new file mode 100644 index 0000000..e86001a --- /dev/null +++ b/crates/comrade/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "comrade" +version.workspace = true +edition.workspace = true +authors.workspace = true +description.workspace = true +readme.workspace = true +license.workspace = true + +[dependencies] + +[lints] +workspace = true diff --git a/crates/comrade/src/lib.rs b/crates/comrade/src/lib.rs new file mode 100644 index 0000000..e44aafc --- /dev/null +++ b/crates/comrade/src/lib.rs @@ -0,0 +1,18 @@ +//! Comrade is an execution engine for provenance log scripts. +//! +//! It requires a wasm-component plugin to run. A reference implementation is +//! provided in the `comrade-component` crate. +pub fn add(left: u64, right: u64) -> u64 { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} From 39559f7c47ac4b32aeeb4d31a6d75accc8cce001 Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Sun, 4 May 2025 08:44:38 -0300 Subject: [PATCH 002/155] add parser --- Cargo.toml | 2 +- crates/comrade-component/Cargo.toml | 1 - crates/comrade-component/justfile | 9 + crates/comrade-crypto/Cargo.toml | 25 + crates/comrade-crypto/README.md | 9 + crates/comrade-crypto/src/context.rs | 581 ++++++++++++++++++ crates/comrade-crypto/src/context/pairs.rs | 12 + crates/comrade-crypto/src/context/parser.rs | 503 +++++++++++++++ .../src/context/parser/grammar.pest | 92 +++ crates/comrade-crypto/src/context/stack.rs | 62 ++ crates/comrade-crypto/src/context/value.rs | 58 ++ crates/comrade-crypto/src/error.rs | 14 + crates/comrade-crypto/src/lib.rs | 8 + crates/comrade/Cargo.toml | 5 + crates/comrade/src/api.rs | 1 + crates/comrade/src/core.rs | 5 + crates/comrade/src/lib.rs | 11 + 17 files changed, 1396 insertions(+), 2 deletions(-) create mode 100644 crates/comrade-component/justfile create mode 100644 crates/comrade-crypto/Cargo.toml create mode 100644 crates/comrade-crypto/README.md create mode 100644 crates/comrade-crypto/src/context.rs create mode 100644 crates/comrade-crypto/src/context/pairs.rs create mode 100644 crates/comrade-crypto/src/context/parser.rs create mode 100644 crates/comrade-crypto/src/context/parser/grammar.pest create mode 100644 crates/comrade-crypto/src/context/stack.rs create mode 100644 crates/comrade-crypto/src/context/value.rs create mode 100644 crates/comrade-crypto/src/error.rs create mode 100644 crates/comrade-crypto/src/lib.rs create mode 100644 crates/comrade/src/api.rs create mode 100644 crates/comrade/src/core.rs diff --git a/Cargo.toml b/Cargo.toml index ed7111b..2d6c8eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ members = [ "crates/bs-traits", "crates/bsp2p", "crates/comrade", - "crates/comrade-component", + "crates/comrade-component", "crates/comrade-crypto", "crates/content-addressable", "crates/multibase", "crates/multicid", diff --git a/crates/comrade-component/Cargo.toml b/crates/comrade-component/Cargo.toml index 284b8e5..286863e 100644 --- a/crates/comrade-component/Cargo.toml +++ b/crates/comrade-component/Cargo.toml @@ -5,7 +5,6 @@ edition.workspace = true authors.workspace = true description.workspace = true readme.workspace = true -license = "MIT" [dependencies] wit-bindgen-rt = { version = "0.41.0", features = ["bitflags"] } diff --git a/crates/comrade-component/justfile b/crates/comrade-component/justfile new file mode 100644 index 0000000..db8e636 --- /dev/null +++ b/crates/comrade-component/justfile @@ -0,0 +1,9 @@ +# See https://just.systems for more information. + +build: + # Build the component with cargo-compoenent + RUSTFLAGS='--cfg getrandom_backend="custom"' cargo component build --target wasm32-unknown-unknown --release + +test: build + cargo test + diff --git a/crates/comrade-crypto/Cargo.toml b/crates/comrade-crypto/Cargo.toml new file mode 100644 index 0000000..e9fb8b8 --- /dev/null +++ b/crates/comrade-crypto/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "comrade-crypto" +version.workspace = true +edition.workspace = true +authors.workspace = true +description.workspace = true +readme.workspace = true +license.workspace = true + +[dependencies] +multikey = { workspace = true } +multihash.workspace = true +multisig.workspace = true +multiutil.workspace = true +pest = "2.8" +pest_derive = "2.8" +thiserror = "2.0" + +[dev-dependencies] +hex = "0.4" +rand = "0.8.5" +chrono = { version = "0.4.19", default-features = false, features = ["clock"] } + +[lints] +workspace = true diff --git a/crates/comrade-crypto/README.md b/crates/comrade-crypto/README.md new file mode 100644 index 0000000..3745ed3 --- /dev/null +++ b/crates/comrade-crypto/README.md @@ -0,0 +1,9 @@ +# Comrade Cryptographic Constructs + +These are the crypto constructs used when building the reference implementation of the comrade-component. + +If different primiatives are desired, these dependencies can be changed for new ones, a new component built, and run with bettersign. + +This crate also contains the parser, abstract syntax tree, and runtime to execute the scripts. Since scripts are plain text, they need to be run "somewhere, somehow" and this can be by various means. For example, in previous versions of Comrade the Rhai scripting engine was used to execute the scripts. In this implementation, a simple Domain Specific Language (DSL) parser is written in pest, and then the abstract syntax tree executed against their corresponding Rust functions. In other words, `check_signature(..)` in plog script runs `check_signature` Cryptographic construct to evaluate the script to check whether it's valid or not. + + diff --git a/crates/comrade-crypto/src/context.rs b/crates/comrade-crypto/src/context.rs new file mode 100644 index 0000000..7f5570a --- /dev/null +++ b/crates/comrade-crypto/src/context.rs @@ -0,0 +1,581 @@ +//! The context that is built and where the script +//! is evaluated + +mod pairs; +pub use pairs::Pairs; + +mod value; +pub use value::Value; + +mod stack; +pub use stack::{Stack, Stk}; + +mod parser; +pub(crate) use parser::Rule; +use parser::{parse, Expression, Function, Key}; + +use crate::ApiError; +use multihash::{mh, Multihash}; +use multikey::{Multikey, Views as _}; +use multisig::Multisig; +use multiutil::prelude::*; + +/// The [Context] within which the script is +/// evaluated. +pub(crate) struct Context { + /// The Return stack + pub(crate) current: P, + + /// The Parameters stack + pub(crate) proposed: P, + + /// The number of checks that have been performed + pub(crate) check_count: usize, + + /// The Return stack + pub(crate) rstack: Stk, + + /// The Parameters stack + pub(crate) pstack: Stk, + + /// Optional domain segment of the /branch/leaf/ key-path. Defaults to "/". + pub domain: String, + + /// The log implementation for the context + pub(crate) logger: L, +} + +/// Log a message to the console +pub trait Log { + fn log(&self, msg: &str) + where + Self: Sized; +} + +impl Context { + /// Create a new [Context] struct with the given [Current] and [Proposed] key-value stores, + /// which are bound by both [Pairable]. + pub fn new(current: P, proposed: P, logger: L) -> Self { + Context { + current, + proposed, + check_count: 0, + rstack: Stk::default(), + pstack: Stk::default(), + domain: "/".to_string(), + logger, + } + } + + /// Parse a script from a string and evaluate it, returning the result + pub fn run(&mut self, script: &str) -> Result { + self.logger.log(&format!("Running script: {script}")); + let expressions = parse(script)?; + + if expressions.is_empty() { + return Ok(false); + } + + // Execute each expression in sequence + // For multiple expressions (separated by semicolons in the original script), + // we execute all of them and return the result of the last one + let mut result = false; + + for expr in &expressions { + result = self.eval(expr); + // Unlike logical OR, we don't short-circuit between separate statements + } + + Ok(result) + } + + /// Evaluate a single expression + fn eval(&mut self, expr: &Expression) -> bool { + match expr { + Expression::Function(func) => self.eval_function(func), + Expression::And(left, right) => self.eval(left) && self.eval(right), + Expression::Or(left, right) => self.eval(left) || self.eval(right), + Expression::Group(inner) => self.eval(inner), + } + } + + /// Evaluate a function call + fn eval_function(&mut self, function: &Function) -> bool { + match function { + Function::CheckEq(key) => match key { + Key::Branch(key) => self.check_eq(&self.branch(key)), + Key::String(key) => self.check_eq(key), + }, + Function::CheckSignature(key, msg) => match key { + Key::Branch(key) => self.check_signature(&self.branch(key), msg), + Key::String(key) => self.check_signature(key, msg), + }, + Function::CheckPreimage(preimage) => match preimage { + Key::Branch(key) => self.check_preimage(&self.branch(key)), + Key::String(key) => self.check_preimage(key), + }, + Function::Push(path) => match path { + Key::Branch(key) => self.push(&self.branch(key)), + Key::String(key) => self.push(key), + }, + } + } + + /// Check the signature of the given key str + pub fn check_signature(&mut self, key: &str, msg: &str) -> bool { + self.logger.log(&format!("check_signature({key}, {msg})")); + let current = self.current.get(key); + // lookup the keypair for this key + let pubkey = { + match ¤t { + Some(Value::Bin { hint: _, data }) => match Multikey::try_from(data.as_ref()) { + Ok(mk) => mk, + Err(e) => { + self.logger + .log("check_signature: error decoding multikey: {e}"); + return self.check_fail(&e.to_string()); + } + }, + Some(_) => { + self.logger + .log("check_signature: unexpected value type associated with {key}"); + return self + .check_fail(&format!("unexpected value type associated with {key}")); + } + None => { + self.logger.log(&format!( + "check_signature: no multikey associated with {key}" + )); + return self.check_fail(&format!("no multikey associated with {key}")); + } + } + }; + + self.logger + .log(&format!("OK check_signature: pubkey: {pubkey:?}")); + + // look up the message that was signed + let message = { + match self.proposed.get(msg) { + Some(Value::Bin { hint: _, data }) => data, + Some(Value::Str { hint: _, data }) => data.as_bytes().to_vec(), + Some(_) => { + self.logger + .log("check_signature: unexpected value type associated with {msg}"); + return self + .check_fail(&format!("unexpected value type associated with {msg}")); + } + None => { + self.logger + .log("check_signature: no message associated with {msg}"); + return self.check_fail(&format!("no message associated with {msg}")); + } + } + }; + + self.logger + .log(&format!("OK check_signature: message: {message:?}")); + + // make sure we have at least one parameter on the stack + if self.pstack.is_empty() { + self.logger.log(&format!( + "Err not enough parameters on the stack for check_signature: {}", + self.pstack.len(), + )); + return self.check_fail(&format!( + "not enough parameters ({}) on the stack for check_signature ({key}, {msg})", + self.pstack.len() + )); + } + + // peek at the top item and verify that it is a Multisig + let sig = { + match self.pstack.top() { + Some(Value::Bin { hint: _, data }) => { + self.logger.log(&format!( + "check_signature: found multisig on stack: {data:?}" + )); + match Multisig::try_from(data.as_ref()) { + Ok(sig) => sig, + Err(e) => return self.check_fail(&e.to_string()), + } + } + _ => return self.check_fail("no multisig on stack"), + } + }; + + // get the verify view + let verify_view = match pubkey.verify_view() { + Ok(v) => v, + Err(e) => return self.check_fail(&e.to_string()), + }; + + // verify the signature + match verify_view.verify(&sig, Some(message.as_ref())) { + Ok(_) => { + // the signature verification worked so pop the signature arg off + // of the stack before continuing + self.logger.log("check_signature: signature verified"); + self.pstack.pop(); + self.succeed() + } + Err(e) => { + self.logger.log("check_signature({key}, {msg}) -> false"); + self.check_fail(&e.to_string()) + } + } + } + + /// Check the preimage of the given key + pub fn check_preimage(&mut self, key: &str) -> bool { + // look up the hash and try to decode it + let hash = { + let current = self.current.get(key); + match current { + Some(Value::Bin { hint: _, data }) => match Multihash::try_from(data.as_ref()) { + Ok(hash) => hash, + Err(e) => return self.check_fail(&e.to_string()), + }, + Some(_) => { + return self + .check_fail(&format!("unexpected value type associated with {key}")); + } + None => return self.check_fail(&format!("kvp missing key: {key}")), + } + }; + + // make sure we have at least one parameter on the stack + if self.pstack.is_empty() { + self.logger.log(&format!( + "not enough parameters on the stack for check_preimage: {}", + self.pstack.len(), + )); + return self.check_fail(&format!( + "not enough parameters on the stack for check_preimage: {}", + self.pstack.len() + )); + } + + // get the preimage data from the stack + let preimage = { + match self.pstack.top() { + Some(Value::Bin { data, hint: _ }) => { + match mh::Builder::new_from_bytes(hash.codec(), data) { + Ok(builder) => match builder.try_build() { + Ok(hash) => hash, + Err(e) => return self.check_fail(&e.to_string()), + }, + Err(e) => return self.check_fail(&e.to_string()), + } + } + Some(Value::Str { hint: _, data }) => { + match mh::Builder::new_from_bytes(hash.codec(), data.as_bytes()) { + Ok(builder) => match builder.try_build() { + Ok(hash) => hash, + Err(e) => return self.check_fail(&e.to_string()), + }, + Err(e) => return self.check_fail(&e.to_string()), + } + } + _ => return self.check_fail("no multihash data on stack"), + } + }; + + // check that the hashes match + if hash == preimage { + // the hash check passed so pop the argument from the stack + let _ = self.pstack.pop(); + self.succeed() + } else { + // the hashes don't match + self.check_fail("preimage doesn't match") + } + } + + /// Verifies the top of the stack matches the value associated with the key + pub fn check_eq(&mut self, key: &str) -> bool { + // look up the value associated with the key + let value = { + match self.current.get(key) { + Some(Value::Bin { hint: _, data }) => data, + Some(Value::Str { hint: _, data }) => data.as_bytes().to_vec(), + _ => { + self.logger.log("check_eq: no value associated with {key}"); + return self.check_fail(&format!("kvp missing key: {key}")); + } + } + }; + + // make sure we have at least one parameter on the stack + if self.pstack.is_empty() { + self.logger.log(&format!( + "not enough parameters on the stack for check_eq: {}", + self.pstack.len(), + )); + return self.check_fail(&format!( + "not enough parameters on the stack for check_eq: {}", + self.pstack.len() + )); + } + + let stack_value = { + match self.pstack.top() { + Some(Value::Bin { hint: _, data }) => data, + Some(Value::Str { hint: _, data }) => data.as_bytes().to_vec(), + _ => { + self.logger.log("check_eq: no value on the stack"); + return self.check_fail("no value on the stack"); + } + } + }; + + // check if equal + if value == stack_value { + // the values match so pop the argument from the stack + let _ = self.pstack.pop(); + self.succeed() + } else { + // the values don't match + self.check_fail("values don't match") + } + } + + /// Increment the check counter and to push a FAILURE marker on the return stack + pub fn check_fail(&mut self, err: &str) -> bool { + self.logger.log(&format!("check_fail ({err})")); + // update the context check_count + self.check_count += 1; + // fail + self.fail(err) + } + + /// Increment the check counter and to push a FAILURE marker on the return stack + pub fn fail(&mut self, err: &str) -> bool { + // push the FAILURE onto the return stack + self.rstack.push(Value::Failure(err.to_string())); + false + } + + /// Push a SUCCESS marker onto the return stack + pub fn succeed(&mut self) -> bool { + self.logger + .log(&format!("succeed() -> {}", self.check_count)); + // push the SUCCESS marker with the check count + self.rstack.push(self.check_count.into()); + // return that we succeeded + true + } + + /// Push the value associated with the key onto the parameter stack + pub fn push(&mut self, key: &str) -> bool { + self.logger.log(&format!("PUSHING: push(\"{key}\")")); + // try to look up the key-value pair by key and push the result onto the stack + match self.current.get(key) { + Some(v) => { + self.logger + .log(&format!("push: found value associated with {key}")); + self.pstack.push(v.clone()); + true + } + None => { + self.logger + .log(&format!("push: no value associated with {key}")); + self.fail(&format!("kvp missing key: {key}")) + } + } + } + + /// Calculate the full key given the context + /// Concatenates the branch key-path with the provided key-path to create a key-path argument for other functions. + /// When used in lock scripts, the branch key-path is the key-path the lock script is associated with. + /// When used in unlock scripts, the branch key-path is always /. This function fails if used in a lock script associated with a leaf + pub fn branch(&self, key: &str) -> String { + let s = format!("{}{}", self.domain, key); + self.logger + .log(&format!("branch({}) -> {}", key, s.as_str())); + s + } + + pub(crate) fn rstack(&self) -> Option { + self.rstack.top() + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::context::Context; + use std::collections::HashMap; + + struct TestLogger; + + impl Log for TestLogger { + fn log(&self, msg: &str) { + println!("{msg}"); + } + } + + #[derive(Clone, Debug, Default)] + struct ContextPairs(HashMap); + + impl Pairs for ContextPairs { + fn get(&self, key: &str) -> Option { + self.0.get(key).cloned() + } + + fn put(&mut self, key: &str, value: &Value) -> Option { + self.0.insert(key.to_string(), value.clone()) + } + } + + fn unlock_script(entry_key: &str, proof_key: &str) -> String { + let unlock_script = format!( + r#" + // push the serialized Entry as the message + push("{entry_key}"); + + // push the proof data + push("{proof_key}"); + "# + ); + + unlock_script + } + + /// First lock is /ephemeral and {entry_key} + fn first_lock_script(entry_key: &str) -> String { + let first_lock = format!( + r#" + // check the first key, which is ephemeral + check_signature("/ephemeral", "{entry_key}") + "# + ); + + first_lock + } + + /// Other lock script + fn other_lock_script(entry_key: &str) -> String { + format!( + r#" + // then check a possible threshold sig... + check_signature("/recoverykey", "{entry_key}") || + + // then check a possible pubkey sig... + check_signature("/pubkey", "{entry_key}") || + + // then the pre-image proof... + check_preimage("/hash") + "# + ) + } + #[test] + fn test_push_pairs() { + let entry_key = "/entry/"; + + // unlock + let entry_data = b"for great justice, move every zig!"; + let proof_key = "/entry/proof"; + let proof_data = hex::decode("4819397f51b18bc6cffd1fff07afa33f7096c7a0c659590b077cc0ea5d6081d739512129becacb8e6997e6b7d18756299f515a822344ac2b6737979d5e5e6b03").unwrap(); + + let unlock = format!( + r#" + // push the serialized Entry as the message + push("{entry_key}"); + + // push the proof data + push("{entry_key}proof"); + "# + ); + + let mut kvp_unlock = ContextPairs::default(); + // only used for check_signature msg (2ns parameter) + let proposed = ContextPairs::default(); + + let entry_data_vec = entry_data.to_vec(); + + kvp_unlock.put(entry_key, &entry_data_vec.clone().into()); + kvp_unlock.put(proof_key, &proof_data.clone().into()); + + let mut ctx = Context::new(kvp_unlock, proposed, TestLogger); + + // When the unlock script runs, + // there should be 2 values on the pstack + // one for /entry/ + // and one for /entry/proof + + // Run the unlock script + let result = ctx.run(&unlock); + assert!(result.is_ok()); + + // Check the pstack + // The first value should be the entry key + // The second value should be the proof key + let mut pstack = ctx.pstack.clone(); + assert_eq!(pstack.len(), 2); + assert_eq!(pstack.pop().unwrap(), proof_data.into()); + assert_eq!(pstack.pop().unwrap(), entry_data_vec.into()); + } + + #[test] + fn generate_test_signatures() { + use multikey::Views as _; + use multikey::{self}; + use multisig::Views as _; + use multiutil::prelude::*; + + let seed = hex::decode("f9ddcd5118319cc69e6985ef3f4ee3b6c591d46255e1ae5569c8662111b7d3c2") + .unwrap(); + + let mk = multikey::Builder::new_from_seed(Codec::Ed25519Priv, seed.as_slice()) + .unwrap() + .with_comment("test key") + .try_build() + .unwrap(); + + let entry_data = b"for great justice, move every zig!"; + + eprintln!("Entry data: {entry_data:?}"); + + let signmk = mk.sign_view().unwrap(); + + let signature = signmk.sign(entry_data.as_slice(), false, None).unwrap(); + + // print out hex signature + let sig_data = signature.data_view().unwrap(); + let sig_bytes = sig_data.sig_bytes().unwrap(); + + eprintln!("Signature bytes: {:?}", &sig_bytes); + + let ms = multisig::Builder::new(Codec::EddsaMsig) + .with_signature_bytes(&sig_bytes) + .try_build() + .unwrap(); + + let hex_sig = hex::encode(ms.data_view().unwrap().sig_bytes().unwrap()); + eprintln!("Signature: {hex_sig}"); + + assert_eq!( + hex_sig, + "4819397f51b18bc6cffd1fff07afa33f7096c7a0c659590b077cc0ea5d6081d739512129becacb8e6997e6b7d18756299f515a822344ac2b6737979d5e5e6b03" + ); + + let verify_mk = mk.verify_view().unwrap(); + assert!(verify_mk.verify(&ms, Some(entry_data.as_ref())).is_ok()); + + // print pubkey + let pubkey = mk.conv_view().unwrap(); + let pubkey_bytes: Vec = pubkey.to_public_key().unwrap().into(); + + eprintln!("Pubkey bytes: {pubkey_bytes:?}"); + let hex_pubkey = hex::encode(pubkey_bytes.clone()); + + eprintln!("Pubkey: {hex_pubkey}"); + + assert_eq!( + hex_pubkey, + "ba24ed010874657374206b657901012054d94d7b8a11d6581af4a14bc6451c7a23049018610f108c996968fe8fce9464" + ); + } +} diff --git a/crates/comrade-crypto/src/context/pairs.rs b/crates/comrade-crypto/src/context/pairs.rs new file mode 100644 index 0000000..a0c63f0 --- /dev/null +++ b/crates/comrade-crypto/src/context/pairs.rs @@ -0,0 +1,12 @@ +use super::value::Value; +use std::fmt::Debug; + +/// Trait to a key-value storage mechanism +pub trait Pairs: Debug { + /// get a value associated with the key + fn get(&self, key: &str) -> Option; + + /// add a key-value pair to the storage, returns the previous value if the + /// key already exists in the data structure + fn put(&mut self, key: &str, value: &Value) -> Option; +} diff --git a/crates/comrade-crypto/src/context/parser.rs b/crates/comrade-crypto/src/context/parser.rs new file mode 100644 index 0000000..2d4082f --- /dev/null +++ b/crates/comrade-crypto/src/context/parser.rs @@ -0,0 +1,503 @@ +// allow unused +#![allow(unused)] + +use pest::iterators::{Pair, Pairs}; +use pest::Parser; +use pest_derive::Parser; + +use crate::error::ApiError; + +#[derive(Parser)] +#[grammar = "context/parser/grammar.pest"] +pub struct ScriptParser; + +/// Our AST defintion in Rust. Each function type is represented by an enum variant. +#[derive(Debug, Clone, PartialEq)] +pub enum Function<'a> { + /// A function that checks the equality of a key. + CheckEq(Key<'a>), + /// A function that checks the signature of a key and message. + CheckSignature(Key<'a>, &'a str), + /// A function that checks the preimage of a key. + CheckPreimage(Key<'a>), + /// A function that pushes a path to the stack. + Push(Key<'a>), +} + +/// The [Function] Key can either be a String or the branch function which returns a String +#[derive(Debug, Clone, PartialEq)] +pub(crate) enum Key<'a> { + String(&'a str), + Branch(&'a str), +} + +/// Represents a complete expression tree +#[derive(Debug, Clone, PartialEq)] +pub enum Expression<'a> { + Function(Function<'a>), + And(Box>, Box>), + Or(Box>, Box>), + Group(Box>), +} + +/// Parse a script from a string into expressions +pub fn parse(script_str: &str) -> Result, ApiError> { + let pairs = ScriptParser::parse(Rule::script, script_str) + .map_err(|e| ApiError::PestParse(Box::new(e)))?; + + let mut expressions = Vec::new(); + + // Find the 'script' node + for pair in pairs { + if pair.as_rule() == Rule::script { + // Process each expression within the script + for inner_pair in pair.into_inner() { + if inner_pair.as_rule() == Rule::expr { + expressions.push(parse_expression(inner_pair)?); + } + } + break; + } + } + + // If there are no expressions, that's valid (empty script) + Ok(expressions) +} + +/// Parse an expression from a pest pair +fn parse_expression(pair: Pair) -> Result { + match pair.as_rule() { + Rule::expr => { + let inner = pair.into_inner().next().unwrap(); + parse_expression(inner) + } + Rule::or_expr => { + let mut inner = pair.into_inner(); + let first = parse_expression(inner.next().unwrap()); + + inner.fold(first, |acc, pair| { + // This handles "||" operators + Ok(Expression::Or( + Box::new(acc?), + Box::new(parse_expression(pair)?), + )) + }) + } + Rule::and_expr => { + let mut inner = pair.into_inner(); + let first = parse_expression(inner.next().unwrap()); + + inner.fold(first, |acc, pair| { + // This handles "&&" operators + Ok(Expression::And( + Box::new(acc?), + Box::new(parse_expression(pair)?), + )) + }) + } + Rule::primary_expr => { + let inner = pair.into_inner().next().unwrap(); + match inner.as_rule() { + Rule::function_call => Ok(parse_function(inner)?), + Rule::expr => Ok(Expression::Group(Box::new(parse_expression(inner)?))), + _ => unreachable!(), + } + } + _ => unreachable!("Unexpected rule: {:?}", pair.as_rule()), + } +} + +/// Parse a function call from a pest pair +fn parse_function(pair: Pair) -> Result { + let mut inner = pair.into_inner(); + let function_name = inner.next().unwrap().as_str(); + + // Disallow branch() as a top-level function call + if function_name == "branch" { + return Err(ApiError::ParseScript( + "branch() can only be used as an argument to other functions".to_string(), + )); + } + + // Parse arguments + let mut args = Vec::new(); + for p in inner { + if [Rule::string_literal, Rule::path_literal, Rule::identifier].contains(&p.as_rule()) { + // Direct string arguments become Key::String + let raw_str = p.as_str(); + let arg_str = + if p.as_rule() == Rule::string_literal || p.as_rule() == Rule::path_literal { + &raw_str[1..raw_str.len() - 1] + } else { + raw_str + }; + args.push(Key::String(arg_str)); + } else if p.as_rule() == Rule::function_call { + // Handle nested function calls - only allow branch() + let func_pairs = p.clone().into_inner(); + let nested_func_name = func_pairs.clone().next().unwrap().as_str(); + + if nested_func_name == "branch" { + // Process branch() argument + let branch_args: Vec<&str> = func_pairs + .skip(1) // Skip the function name + .filter_map(|arg| { + if [Rule::string_literal, Rule::path_literal, Rule::identifier] + .contains(&arg.as_rule()) + { + let raw_str = arg.as_str(); + let arg_str = if arg.as_rule() == Rule::string_literal + || arg.as_rule() == Rule::path_literal + { + &raw_str[1..raw_str.len() - 1] + } else { + raw_str + }; + Some(arg_str) + } else { + None + } + }) + .collect(); + + if branch_args.len() != 1 { + return Err(ApiError::ParseScript( + "branch() requires exactly one argument".to_string(), + )); + } + + args.push(Key::Branch(branch_args[0])); + } else { + return Err(ApiError::ParseScript(format!( + "Only branch() can be used as a nested function: got {}", + nested_func_name + ))); + } + } + } + + // Create the appropriate Function based on name and arguments + let function = match function_name { + "check_eq" if args.len() == 1 => Function::CheckEq(args[0].clone()), + "check_signature" if args.len() == 2 => { + let key = args[0].clone(); + match &args[1] { + Key::String(msg) => Function::CheckSignature(key, msg), + Key::Branch(_) => { + return Err(ApiError::ParseScript( + "Branch cannot be used as message argument".to_string(), + )); + } + } + } + "check_preimage" if args.len() == 1 => Function::CheckPreimage(args[0].clone()), + "push" if args.len() == 1 => Function::Push(args[0].clone()), + _ => { + let msg = format!( + "Unsupported function call: {} with {} args", + function_name, + args.len() + ); + return Err(ApiError::ParseScript(msg)); + } + }; + + Ok(Expression::Function(function)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_simple_expression() { + let simple_script = r#"check_signature("/key", "/msg") || check_preimage("/hash")"#; + let expressions = parse(simple_script).expect("Failed to parse simple script"); + + // Should have one top-level OR expression + assert_eq!(expressions.len(), 1); + + if let Expression::Or(left, right) = &expressions[0] { + // Check left side of OR + if let Expression::Function(Function::CheckSignature(key, msg)) = &**left { + match key { + Key::String(key_str) => assert_eq!(*key_str, "/key"), + Key::Branch(_) => panic!("Expected string key, got Branch"), + } + assert_eq!(*msg, "/msg"); + } else { + panic!("Expected CheckSignature function on left side of OR"); + } + + // Check right side of OR + if let Expression::Function(Function::CheckPreimage(hash)) = &**right { + match hash { + Key::String(hash_str) => assert_eq!(*hash_str, "/hash"), + Key::Branch(_) => panic!("Expected string hash, got Branch"), + } + } else { + panic!("Expected CheckPreimage function on right side of OR"); + } + } else { + panic!("Expected OR expression at top level"); + } + } + + #[test] + fn test_parse_with_comments() { + let script_str = r#" + // then check a possible threshold sig... + check_signature("/recoverykey", "/entry/") || + + // then check a possible pubkey sig... + check_signature("/pubkey", "/entry/") || + + // then the pre-image proof... + check_preimage("/hash") + "#; + + let expressions = parse(script_str).expect("Failed to parse script with comments"); + + // Should have at least one expression + assert!(!expressions.is_empty()); + + // Let's collect all the function calls in order of execution + let mut functions = Vec::new(); + collect_functions(&expressions[0], &mut functions); + + // Verify we have exactly 3 function calls + assert_eq!( + functions.len(), + 3, + "Expected 3 function calls, got {}", + functions.len() + ); + + // Check each function is the expected type with expected arguments + match &functions[0] { + Function::CheckSignature(key, msg) => { + match key { + Key::String(key_str) => assert_eq!(*key_str, "/recoverykey"), + Key::Branch(_) => panic!("Expected string key, got Branch"), + } + assert_eq!(*msg, "/entry/"); + } + other => panic!("First function should be CheckSignature, got {:?}", other), + } + + match &functions[1] { + Function::CheckSignature(key, msg) => { + match key { + Key::String(key_str) => assert_eq!(*key_str, "/pubkey"), + Key::Branch(_) => panic!("Expected string key, got Branch"), + } + assert_eq!(*msg, "/entry/"); + } + other => panic!("Second function should be CheckSignature, got {:?}", other), + } + + match &functions[2] { + Function::CheckPreimage(hash) => match hash { + Key::String(hash_str) => assert_eq!(*hash_str, "/hash"), + Key::Branch(_) => panic!("Expected string hash, got Branch"), + }, + other => panic!("Third function should be CheckPreimage, got {:?}", other), + } + } + + // Helper function to collect all Function nodes in execution order + fn collect_functions<'a>(expr: &'a Expression<'a>, functions: &mut Vec>) { + match expr { + Expression::Function(f) => functions.push(f.clone()), + Expression::And(left, right) => { + collect_functions(left, functions); + collect_functions(right, functions); + } + Expression::Or(left, right) => { + // For OR expressions, the left side is tried first, then the right + collect_functions(left, functions); + collect_functions(right, functions); + } + Expression::Group(inner) => { + collect_functions(inner, functions); + } + } + } + + #[test] + fn test_parse_nested_functions() { + let script_str = r#"check_signature(branch("pubkey"), "/entry/")"#; + let expressions = parse(script_str).expect("Failed to parse nested functions"); + + assert_eq!(expressions.len(), 1); + + if let Expression::Function(Function::CheckSignature(key, msg)) = &expressions[0] { + match key { + Key::Branch(branch_str) => assert_eq!(*branch_str, "pubkey"), + Key::String(_) => panic!("Expected Branch key, got String"), + } + assert_eq!(*msg, "/entry/"); + } else { + panic!("Expected CheckSignature with nested Branch function"); + } + } + + #[test] + fn test_parse_with_grouping() { + let script_str = r#"(check_eq("/value1") && check_eq("/value2"))"#; + let expressions = parse(script_str).expect("Failed to parse grouped expression"); + + assert_eq!(expressions.len(), 1); + + if let Expression::Group(inner) = &expressions[0] { + if let Expression::And(left, right) = &**inner { + // Check both sides of AND + if let Expression::Function(Function::CheckEq(val1)) = &**left { + match val1 { + Key::String(val_str) => assert_eq!(*val_str, "/value1"), + Key::Branch(_) => panic!("Expected String key, got Branch"), + } + } else { + panic!("Expected CheckEq on left side of AND"); + } + + if let Expression::Function(Function::CheckEq(val2)) = &**right { + match val2 { + Key::String(val_str) => assert_eq!(*val_str, "/value2"), + Key::Branch(_) => panic!("Expected String key, got Branch"), + } + } else { + panic!("Expected CheckEq on right side of AND"); + } + } else { + panic!("Expected AND expression inside group"); + } + } else { + panic!("Expected Group expression at top level"); + } + } + + #[test] + fn test_parse_all_function_types() { + let test_script = r#" + check_eq("/match") && + check_signature("/pubkey/path", "/message/path") && + check_preimage("/hash") && + push("/stack/path") + "#; + + let expressions = parse(test_script).expect("Failed to parse all function types"); + + // Should have one expression (all connected by AND operators) + assert_eq!(expressions.len(), 1); + + // Verify the structure has all expected function types + // This is simplified - a complete test would traverse the full AND chain + let mut found_check_eq = false; + let mut found_check_signature = false; + let mut found_check_preimage = false; + let mut found_push = false; + + // Helper function to check for function types in an expression + fn check_for_functions( + expr: &Expression, + check_eq: &mut bool, + check_sig: &mut bool, + check_preimage: &mut bool, + push: &mut bool, + ) { + match expr { + Expression::Function(f) => match f { + Function::CheckEq(_) => *check_eq = true, + Function::CheckSignature(_, _) => *check_sig = true, + Function::CheckPreimage(_) => *check_preimage = true, + Function::Push(_) => *push = true, + }, + Expression::And(left, right) => { + check_for_functions(left, check_eq, check_sig, check_preimage, push); + check_for_functions(right, check_eq, check_sig, check_preimage, push); + } + Expression::Or(left, right) => { + check_for_functions(left, check_eq, check_sig, check_preimage, push); + check_for_functions(right, check_eq, check_sig, check_preimage, push); + } + Expression::Group(inner) => { + check_for_functions(inner, check_eq, check_sig, check_preimage, push); + } + } + } + + check_for_functions( + &expressions[0], + &mut found_check_eq, + &mut found_check_signature, + &mut found_check_preimage, + &mut found_push, + ); + + assert!(found_check_eq, "CheckEq function not found"); + assert!(found_check_signature, "CheckSignature function not found"); + assert!(found_check_preimage, "CheckPreimage function not found"); + assert!(found_push, "Push function not found"); + } + + #[test] + fn test_parse_error_handling() { + // Wrong number of arguments + let invalid_script = r#"check_eq("/test", "/extra")"#; + assert!( + parse(invalid_script).is_err(), + "Should error with too many arguments" + ); + + // Too few arguments + let invalid_script = r#"check_signature("/key")"#; + assert!( + parse(invalid_script).is_err(), + "Should error with too few arguments" + ); + + // Unknown function + let invalid_script = r#"unknown_function("/test")"#; + assert!( + parse(invalid_script).is_err(), + "Should error with unknown function" + ); + } + + #[test] + fn test_semicolon_terminated_statements() { + let unlock = r#" + // push the serialized Entry as the message + push("/entry/"); + + // push the proof data + push("/entry/proof"); + "#; + + let expressions = parse(unlock).expect("Failed to parse script with semicolons"); + + // Should have two expressions + assert_eq!(expressions.len(), 2); + + // Check the first expression is push("/entry/") + if let Expression::Function(Function::Push(key)) = &expressions[0] { + match key { + Key::String(path) => assert_eq!(*path, "/entry/"), + Key::Branch(_) => panic!("Expected string key, got Branch"), + } + } else { + panic!("Expected Push function for first expression"); + } + + // Check the second expression is push("/entry/proof") + if let Expression::Function(Function::Push(key)) = &expressions[1] { + match key { + Key::String(path) => assert_eq!(*path, "/entry/proof"), + Key::Branch(_) => panic!("Expected string key, got Branch"), + } + } else { + panic!("Expected Push function for second expression"); + } + } +} diff --git a/crates/comrade-crypto/src/context/parser/grammar.pest b/crates/comrade-crypto/src/context/parser/grammar.pest new file mode 100644 index 0000000..90f11ff --- /dev/null +++ b/crates/comrade-crypto/src/context/parser/grammar.pest @@ -0,0 +1,92 @@ +//! Pest grammar file for parsing Scripts into AST +//! +//! # Example Script to parse: +//! ```ignore +//! // then check a possible threshold sig... +//! check_signature("/tpubkey", "/entry/") || // check_count++ +//! // then check a possible pubkey sig... +//! check_signature("/pubkey", "/entry/") || // check_count++ +//! // then the pre-image proof... +//! check_preimage("/hash") +//! ``` +//! +//! For example, the following script: +//! +//! ```ignore +//! check_signature("/tpubkey", "/entry/") || +//! // then check a possible pubkey sig... +//! check_signature("/pubkey", "/entry/") || +//! // then the pre-image proof... +//! check_preimage("/hash") +//! ``` +//! +//! Would be parsed intot the AST, the executed int he same order, calling the same named functions +//! in Rust, until "true" is returned. +//! +//! ## List of all possible functions: +//! - `check_eq(key: String)` +//! - `check_signature(key: String, msg: String)` +//! - `check_preimage(preimage: String)` +//! - `check_hash(hash: String)` +//! - `push(path: String)` +//! - `branch(branch: String) -> String` +//! +//! The order must be preserved such that the order of operations is the same as the order of the +//! script. +//! +//! Recursion can be used as functions in the cript can be nested: +//! +//! ## Nested example: +//! ```ignore +//! // forking the parent be done by whomever can sign with "/forks/pubkey" +//! check_signature(branch("pubkey"), "/entry/") || +//! +//! // check the validity of the first entry of the child plog +//! (check_eq(branch("vlad")) && check_signature(branch("pubkey"), "/entry/")) +//! ```` +//! The Grammer should be pretty basic, parsing the script by line, logical operators, and +//! comments. The script is parsed into an AST, which is then used to generate the final +//! script. + +WHITESPACE = _{ " " | "\t" | "\r" } +NEWLINE = _{ "\n" } + +// Comments +line_comment = _{ "//" ~ (!NEWLINE ~ ANY)* } +block_comment = _{ "/*" ~ (!"*/" ~ ANY)* ~ "*/" } +COMMENT = _{ line_comment | block_comment } + +// String literals for arguments +string_literal = @{ "\"" ~ (!"\"" ~ ANY)* ~ "\"" | "'" ~ (!("'" | NEWLINE) ~ ANY)* ~ "'" } + +// Path literals (strings starting with "/") +path_literal = @{ "\"" ~ "/" ~ (!"\"" ~ ANY)* ~ "\"" | "'" ~ "/" ~ (!("'" | NEWLINE) ~ ANY)* ~ "'" } + +// Identifiers for function names and non-quoted arguments +identifier = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_")* } + +// Arguments can be string literals, path literals, identifiers, or function calls +argument = _{ function_call | string_literal | path_literal | identifier } + +// Forward declaration for function_call to allow recursion +function_call = { identifier ~ "(" ~ (argument ~ ("," ~ argument)*)? ~ ")" } + +// Expression can be a function call or a parenthesized expression +primary_expr = { function_call | "(" ~ expr ~ ")" } + +// Logical operators with precedence +or_expr = { + and_expr ~ (NEWLINE* ~ "||" ~ NEWLINE* ~ and_expr)* +} + +and_expr = { + primary_expr ~ (NEWLINE* ~ "&&" ~ NEWLINE* ~ primary_expr)* +} + +// Top-level expression +expr = { or_expr } + +// A script is a series of expressions separated by optional newlines and optional semicolons +script = { + SOI ~ NEWLINE* ~ (expr ~ (";" ~ NEWLINE* ~ expr)* ~ ";"?)? ~ NEWLINE* ~ EOI +} diff --git a/crates/comrade-crypto/src/context/stack.rs b/crates/comrade-crypto/src/context/stack.rs new file mode 100644 index 0000000..5554c7a --- /dev/null +++ b/crates/comrade-crypto/src/context/stack.rs @@ -0,0 +1,62 @@ +use crate::Value; + +/// Trait for a value stack +pub trait Stack { + /// push a value onto the stack + fn push(&mut self, value: Value); + + /// remove the last top value from the stack + fn pop(&mut self) -> Option; + + /// get a reference to the top value on the stack + fn top(&self) -> Option; + + /// peek at the item at the given index + fn peek(&self, idx: usize) -> Option; + + /// return the number of values on the stack + fn len(&self) -> usize; + + /// return if the stack is empty + fn is_empty(&self) -> bool; +} + +#[derive(Default, Clone, Debug)] +pub struct Stk { + pub stack: Vec, +} + +impl Stack for Stk { + /// push a value onto the stack + fn push(&mut self, value: Value) { + self.stack.push(value); + } + + /// remove the last top value from the stack + fn pop(&mut self) -> Option { + self.stack.pop() + } + + /// get a reference to the top value on the stack + fn top(&self) -> Option { + self.stack.last().cloned() + } + + /// peek at the item at the given index + fn peek(&self, idx: usize) -> Option { + if idx >= self.stack.len() { + return None; + } + Some(self.stack[self.stack.len() - 1 - idx].clone()) + } + + /// return the number of values on the stack + fn len(&self) -> usize { + self.stack.len() + } + + /// return if the stack is empty + fn is_empty(&self) -> bool { + self.stack.is_empty() + } +} diff --git a/crates/comrade-crypto/src/context/value.rs b/crates/comrade-crypto/src/context/value.rs new file mode 100644 index 0000000..794af24 --- /dev/null +++ b/crates/comrade-crypto/src/context/value.rs @@ -0,0 +1,58 @@ +/// The values that can be pushed onto the stack +#[derive(Clone, PartialEq, Debug)] +pub enum Value { + /// A binary blob value with debugging hint + Bin { + /// Arbitrary description of the data for debugging purposes + hint: String, + /// Binary value data + data: Vec, + }, + /// A printable string value with debugging hint + Str { + /// Arbitrary description of the data for debugging purposes + hint: String, + /// String value data + data: String, + }, + /// Sucess marker + Success(usize), + /// Failure marker + Failure(String), +} + +impl From<&[u8]> for Value { + fn from(b: &[u8]) -> Self { + Value::from(b.to_vec()) + } +} + +impl From> for Value { + fn from(b: Vec) -> Self { + Value::Bin { + hint: "".to_string(), + data: b, + } + } +} + +impl From<&str> for Value { + fn from(s: &str) -> Self { + Value::from(s.to_string()) + } +} + +impl From for Value { + fn from(s: String) -> Self { + Value::Str { + hint: "".to_string(), + data: s, + } + } +} + +impl From for Value { + fn from(n: usize) -> Self { + Value::Success(n) + } +} diff --git a/crates/comrade-crypto/src/error.rs b/crates/comrade-crypto/src/error.rs new file mode 100644 index 0000000..878ec3f --- /dev/null +++ b/crates/comrade-crypto/src/error.rs @@ -0,0 +1,14 @@ +//! Crate level error handling. + +use crate::context::Rule; + +#[derive(Debug, thiserror::Error)] +pub enum ApiError { + /// Error parsing the Script + #[error("Error parsing the Script: {0}")] + ParseScript(String), + + /// expected `std::boxed::Box>`, found `pest::error::Error` + #[error("Error parsing the Script: {0}")] + PestParse(#[from] Box>), +} diff --git a/crates/comrade-crypto/src/lib.rs b/crates/comrade-crypto/src/lib.rs new file mode 100644 index 0000000..7b19654 --- /dev/null +++ b/crates/comrade-crypto/src/lib.rs @@ -0,0 +1,8 @@ +/// Crate level errors +mod error; +pub use error::ApiError; + +/// Context is the main entry of this crate +mod context; +/// Public re-exports +pub use context::{Pairs, Value}; diff --git a/crates/comrade/Cargo.toml b/crates/comrade/Cargo.toml index e86001a..686ab54 100644 --- a/crates/comrade/Cargo.toml +++ b/crates/comrade/Cargo.toml @@ -9,5 +9,10 @@ license.workspace = true [dependencies] +[features] +default = ["core"] +# enable core features when you want to build a wasm component +core = [] + [lints] workspace = true diff --git a/crates/comrade/src/api.rs b/crates/comrade/src/api.rs new file mode 100644 index 0000000..315eb3b --- /dev/null +++ b/crates/comrade/src/api.rs @@ -0,0 +1 @@ +//! Comrade API diff --git a/crates/comrade/src/core.rs b/crates/comrade/src/core.rs new file mode 100644 index 0000000..135dba2 --- /dev/null +++ b/crates/comrade/src/core.rs @@ -0,0 +1,5 @@ +mod pairs; +pub use pairs::Pairs; + +mod value; +pub use value::Value; diff --git a/crates/comrade/src/lib.rs b/crates/comrade/src/lib.rs index e44aafc..6a066dc 100644 --- a/crates/comrade/src/lib.rs +++ b/crates/comrade/src/lib.rs @@ -2,6 +2,17 @@ //! //! It requires a wasm-component plugin to run. A reference implementation is //! provided in the `comrade-component` crate. + +/// Core cryptographic constructs which can be used to build components +mod core; +pub use core::{Pairs, Value}; + +/// Opinionated entry API for using Comrade. +/// Uses the comrade-component reference implementation by default, +/// and wasm_component_layer for runtime. Either can be substituted +/// with prefered alternatives as desired. +mod api; + pub fn add(left: u64, right: u64) -> u64 { left + right } From 85d94ba74154ebb3368946b40674be419f93ce2d Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Sun, 4 May 2025 13:23:52 -0300 Subject: [PATCH 003/155] eval script test passing --- crates/comrade-crypto/src/context.rs | 77 ++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/crates/comrade-crypto/src/context.rs b/crates/comrade-crypto/src/context.rs index 7f5570a..37719ef 100644 --- a/crates/comrade-crypto/src/context.rs +++ b/crates/comrade-crypto/src/context.rs @@ -518,6 +518,83 @@ mod tests { assert_eq!(pstack.pop().unwrap(), entry_data_vec.into()); } + // test evaluate script + #[test] + fn test_eval_scripts() { + let entry_key = "/entry/"; + + // unlock + let entry_data = b"for great justice, move every zig!"; + let proof_key = "/entry/proof"; + let proof_data = hex::decode("b92483a6c00600010040eda2eceac1ef60c4d54efc7b50d86b198ba12358749e5069dbe0a5ca6c3e7e78912a21c67a18a4a594f904e7df16f798d929d7a8cee57baca89b4ed0dfd1c801").unwrap(); + + let mut kvp_unlock = ContextPairs::default(); + let mut kvp_lock = ContextPairs::default(); + // entry and proof data needs to be present on both current and proposed stacks + // since they are used in both the unlock and lock scripts + kvp_lock.put(entry_key, &entry_data.to_vec().into()); + kvp_unlock.put(entry_key, &entry_data.to_vec().into()); + // proof only needs to be present on the unlock stack + kvp_unlock.put(proof_key, &proof_data.clone().into()); + + // /entry/ needs to be current for push("/entry/"), and proposed for check_signature("/pubkey", "/entry/") + + let unlock = unlock_script(entry_key, &format!("{entry_key}proof")); + + // lock + let first_lock = first_lock_script(entry_key); + let other_lock = other_lock_script(entry_key); + + let locks = [first_lock, other_lock]; + + let pubkey = "/pubkey"; + let pub_key = hex::decode("ba24ed010874657374206b657901012069c9e8cd599542b5ff7e4cdc4265847feb9785330557edd6a9edae741ed4c3b2").unwrap(); + kvp_lock.put(pubkey, &pub_key.clone().into()); + kvp_unlock.put(pubkey, &pub_key.into()); + + // If we run the unlock script, then the lock script + // the results of check_signature should be true + // since our signature is valid + let mut ctx = Context::new(kvp_unlock, kvp_lock, TestLogger); + let result = ctx.run(&unlock); + assert!(result.is_ok()); + + // Check the pstack + // The first value should be the entry key + // The second value should be the proof key + let mut pstack = ctx.pstack.clone(); + assert_eq!(pstack.len(), 2); + assert_eq!(pstack.pop().unwrap(), proof_data.into()); + assert_eq!(pstack.pop().unwrap(), entry_data.to_vec().into()); + + // Now run the lock script + // The first lock script should pass + // The second lock script should pass + // since we have a valid signature + let result = ctx.run(&locks[0]); + assert!(result.is_ok()); + + // Check the return stack + // The length should be 1 + // The value should be Failure("no multikey associated with /ephemeral") + assert_eq!(ctx.rstack.len(), 1); + assert_eq!( + ctx.rstack.top().unwrap(), + Value::Failure("no multikey associated with /ephemeral".to_string()) + ); + + // Now run the second lock script + let result = ctx.run(&locks[1]); + assert!(result.is_ok()); + + // Check the return stack + // The length should be 1 + // The value should be Success(1) + eprintln!("Return stack: {:?}", ctx.rstack); + assert_eq!(ctx.rstack.len(), 3); + assert_eq!(ctx.rstack.top().unwrap(), Value::Success(2)); + } + #[test] fn generate_test_signatures() { use multikey::Views as _; From 8ae49f0135ee775592c8a1beb9eea71566b5c2a8 Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Sun, 4 May 2025 13:27:04 -0300 Subject: [PATCH 004/155] fix comments --- crates/comrade-crypto/src/context.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/comrade-crypto/src/context.rs b/crates/comrade-crypto/src/context.rs index 37719ef..16ff1e7 100644 --- a/crates/comrade-crypto/src/context.rs +++ b/crates/comrade-crypto/src/context.rs @@ -588,8 +588,12 @@ mod tests { assert!(result.is_ok()); // Check the return stack - // The length should be 1 - // The value should be Success(1) + // The length should be 3: + // - the first lock script failure, since we didn't provide a signature for /ephemeral + // - the second lock script success, since we provided a valid signature for /recoverykey + // - the third lock script success, since we provided a valid signature for /pubkey + // The value should be Success(2) + // - since we had 2 failed checks eprintln!("Return stack: {:?}", ctx.rstack); assert_eq!(ctx.rstack.len(), 3); assert_eq!(ctx.rstack.top().unwrap(), Value::Success(2)); From 32f382aacc8190fe963f4cfa7c7a65b8357746bb Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Sun, 4 May 2025 13:45:34 -0300 Subject: [PATCH 005/155] fixup comments --- crates/comrade-crypto/README.md | 16 ++++++++++++---- crates/comrade-crypto/src/context.rs | 9 ++++----- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/crates/comrade-crypto/README.md b/crates/comrade-crypto/README.md index 3745ed3..f2ca1e3 100644 --- a/crates/comrade-crypto/README.md +++ b/crates/comrade-crypto/README.md @@ -1,9 +1,17 @@ -# Comrade Cryptographic Constructs +# Comrade Verification runtime -These are the crypto constructs used when building the reference implementation of the comrade-component. +An opinionated script runtime for lock and unlcok scripts. Since scripts are just plain text, they need to be run "somewhere, somehow" and this can be by various means. For example, in previous versions of Comrade the Rhai scripting engine was used to execute the scripts. This could be done using other scripting languages such as Lua, but in this implementation a domain specific language (DSL) parser is used to run the scripts. -If different primiatives are desired, these dependencies can be changed for new ones, a new component built, and run with bettersign. +## Script Runtime & Script Parser -This crate also contains the parser, abstract syntax tree, and runtime to execute the scripts. Since scripts are plain text, they need to be run "somewhere, somehow" and this can be by various means. For example, in previous versions of Comrade the Rhai scripting engine was used to execute the scripts. In this implementation, a simple Domain Specific Language (DSL) parser is written in pest, and then the abstract syntax tree executed against their corresponding Rust functions. In other words, `check_signature(..)` in plog script runs `check_signature` Cryptographic construct to evaluate the script to check whether it's valid or not. +This crate contains the parser, abstract syntax tree, and runtime to execute the scripts. In this implementation, a simple Domain Specific Language (DSL) parser is written in pest, and then the abstract syntax tree executed against their corresponding Rust functions. In other words, `check_signature(..)` in plog script runs `check_signature` Cryptographic construct to evaluate the script to check whether it's valid or not. +## Comrade Crypto Constructs +This calls associated cryptographic constructs to verify the validity of the scripts against each other. This is the reference implementation for the comrade-component. + +If different primiatives are desired, the crypto dependencies can be changed for new ones, a new component built, and run with bettersign. + +## Design Considerations + +This comrade architecture was chosen to give users manximum flexibility, upgradeability and extensibility of the backend. Because of the modular approach, backends can be switched out without impacting the rest of the system. This makes open source more viable and maintainable, since parts can be used interchangeably without placing all the burden on the user to maintain the entire system. diff --git a/crates/comrade-crypto/src/context.rs b/crates/comrade-crypto/src/context.rs index 16ff1e7..298d187 100644 --- a/crates/comrade-crypto/src/context.rs +++ b/crates/comrade-crypto/src/context.rs @@ -567,10 +567,9 @@ mod tests { assert_eq!(pstack.pop().unwrap(), proof_data.into()); assert_eq!(pstack.pop().unwrap(), entry_data.to_vec().into()); - // Now run the lock script - // The first lock script should pass - // The second lock script should pass - // since we have a valid signature + // Now run the first lock script + // The first lock script should run, but fail + // since we didn't provide a signature for /ephemeral let result = ctx.run(&locks[0]); assert!(result.is_ok()); @@ -590,7 +589,7 @@ mod tests { // Check the return stack // The length should be 3: // - the first lock script failure, since we didn't provide a signature for /ephemeral - // - the second lock script success, since we provided a valid signature for /recoverykey + // - the second lock script failure, since we provided a valid signature for /recoverykey // - the third lock script success, since we provided a valid signature for /pubkey // The value should be Success(2) // - since we had 2 failed checks From 5a58574c0d0dd58960ac29f1429cc1921a10dbe4 Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Sun, 4 May 2025 15:06:34 -0300 Subject: [PATCH 006/155] cleanup --- crates/comrade-crypto/src/context.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/crates/comrade-crypto/src/context.rs b/crates/comrade-crypto/src/context.rs index 298d187..04d33d3 100644 --- a/crates/comrade-crypto/src/context.rs +++ b/crates/comrade-crypto/src/context.rs @@ -518,44 +518,42 @@ mod tests { assert_eq!(pstack.pop().unwrap(), entry_data_vec.into()); } - // test evaluate script #[test] fn test_eval_scripts() { + // unlock let entry_key = "/entry/"; - // unlock let entry_data = b"for great justice, move every zig!"; let proof_key = "/entry/proof"; let proof_data = hex::decode("b92483a6c00600010040eda2eceac1ef60c4d54efc7b50d86b198ba12358749e5069dbe0a5ca6c3e7e78912a21c67a18a4a594f904e7df16f798d929d7a8cee57baca89b4ed0dfd1c801").unwrap(); - let mut kvp_unlock = ContextPairs::default(); let mut kvp_lock = ContextPairs::default(); - // entry and proof data needs to be present on both current and proposed stacks + let mut kvp_unlock = ContextPairs::default(); + // "/entry/" needs to be present on both lock and unlock stacks, // since they are used in both the unlock and lock scripts - kvp_lock.put(entry_key, &entry_data.to_vec().into()); kvp_unlock.put(entry_key, &entry_data.to_vec().into()); - // proof only needs to be present on the unlock stack - kvp_unlock.put(proof_key, &proof_data.clone().into()); - - // /entry/ needs to be current for push("/entry/"), and proposed for check_signature("/pubkey", "/entry/") + kvp_lock.put(entry_key, &entry_data.to_vec().into()); + // "/entry/proof" only needs to be present on the lock stack, + // since that's where the proof is used + kvp_lock.put(proof_key, &proof_data.clone().into()); - let unlock = unlock_script(entry_key, &format!("{entry_key}proof")); + let unlock = unlock_script(entry_key, proof_key); - // lock let first_lock = first_lock_script(entry_key); let other_lock = other_lock_script(entry_key); let locks = [first_lock, other_lock]; + // lock let pubkey = "/pubkey"; let pub_key = hex::decode("ba24ed010874657374206b657901012069c9e8cd599542b5ff7e4cdc4265847feb9785330557edd6a9edae741ed4c3b2").unwrap(); - kvp_lock.put(pubkey, &pub_key.clone().into()); - kvp_unlock.put(pubkey, &pub_key.into()); + // "/pubkey" only needs to be present on unlock stack, + kvp_lock.put(pubkey, &pub_key.into()); // If we run the unlock script, then the lock script // the results of check_signature should be true // since our signature is valid - let mut ctx = Context::new(kvp_unlock, kvp_lock, TestLogger); + let mut ctx = Context::new(kvp_lock, kvp_unlock, TestLogger); let result = ctx.run(&unlock); assert!(result.is_ok()); From cfa6f9710a510bceaad0d0c491682464139018ed Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Sun, 4 May 2025 15:36:42 -0300 Subject: [PATCH 007/155] fix bug in lock/unlock curr/proposed stack push --- crates/comrade-crypto/src/context.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/comrade-crypto/src/context.rs b/crates/comrade-crypto/src/context.rs index 04d33d3..8af7d45 100644 --- a/crates/comrade-crypto/src/context.rs +++ b/crates/comrade-crypto/src/context.rs @@ -370,7 +370,7 @@ impl Context { pub fn push(&mut self, key: &str) -> bool { self.logger.log(&format!("PUSHING: push(\"{key}\")")); // try to look up the key-value pair by key and push the result onto the stack - match self.current.get(key) { + match self.proposed.get(key) { Some(v) => { self.logger .log(&format!("push: found value associated with {key}")); @@ -491,14 +491,14 @@ mod tests { let mut kvp_unlock = ContextPairs::default(); // only used for check_signature msg (2ns parameter) - let proposed = ContextPairs::default(); + let kvp_lock = ContextPairs::default(); let entry_data_vec = entry_data.to_vec(); kvp_unlock.put(entry_key, &entry_data_vec.clone().into()); kvp_unlock.put(proof_key, &proof_data.clone().into()); - let mut ctx = Context::new(kvp_unlock, proposed, TestLogger); + let mut ctx = Context::new(kvp_lock, kvp_unlock, TestLogger); // When the unlock script runs, // there should be 2 values on the pstack @@ -530,12 +530,13 @@ mod tests { let mut kvp_lock = ContextPairs::default(); let mut kvp_unlock = ContextPairs::default(); // "/entry/" needs to be present on both lock and unlock stacks, - // since they are used in both the unlock and lock scripts + // since they are used in both the unlock and lock scripts: + // ie. push("/entry/") and check_signature("/pubkey", "/entry/") kvp_unlock.put(entry_key, &entry_data.to_vec().into()); kvp_lock.put(entry_key, &entry_data.to_vec().into()); - // "/entry/proof" only needs to be present on the lock stack, + // "/entry/proof" only needs to be present on the unlock stack, // since that's where the proof is used - kvp_lock.put(proof_key, &proof_data.clone().into()); + kvp_unlock.put(proof_key, &proof_data.clone().into()); let unlock = unlock_script(entry_key, proof_key); From 6ad18d24e9f091b1dd2fa970598e78c21f6827ca Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Sun, 4 May 2025 17:35:04 -0300 Subject: [PATCH 008/155] rename crypto to reference its the reference impl it has wacc and runtime and parser and ast... what else do you call it --- .../Cargo.toml | 2 +- .../README.md | 2 +- .../src/context.rs | 22 +++++++++---------- .../src/context/pairs.rs | 0 .../src/context/parser.rs | 0 .../src/context/parser/grammar.pest | 0 .../src/context/stack.rs | 0 .../src/context/value.rs | 0 .../src/error.rs | 0 .../src/lib.rs | 2 +- 10 files changed, 13 insertions(+), 15 deletions(-) rename crates/{comrade-crypto => comrade-reference}/Cargo.toml (94%) rename crates/{comrade-crypto => comrade-reference}/README.md (96%) rename crates/{comrade-crypto => comrade-reference}/src/context.rs (97%) rename crates/{comrade-crypto => comrade-reference}/src/context/pairs.rs (100%) rename crates/{comrade-crypto => comrade-reference}/src/context/parser.rs (100%) rename crates/{comrade-crypto => comrade-reference}/src/context/parser/grammar.pest (100%) rename crates/{comrade-crypto => comrade-reference}/src/context/stack.rs (100%) rename crates/{comrade-crypto => comrade-reference}/src/context/value.rs (100%) rename crates/{comrade-crypto => comrade-reference}/src/error.rs (100%) rename crates/{comrade-crypto => comrade-reference}/src/lib.rs (74%) diff --git a/crates/comrade-crypto/Cargo.toml b/crates/comrade-reference/Cargo.toml similarity index 94% rename from crates/comrade-crypto/Cargo.toml rename to crates/comrade-reference/Cargo.toml index e9fb8b8..e30f936 100644 --- a/crates/comrade-crypto/Cargo.toml +++ b/crates/comrade-reference/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "comrade-crypto" +name = "comrade-reference" version.workspace = true edition.workspace = true authors.workspace = true diff --git a/crates/comrade-crypto/README.md b/crates/comrade-reference/README.md similarity index 96% rename from crates/comrade-crypto/README.md rename to crates/comrade-reference/README.md index f2ca1e3..25cbdd7 100644 --- a/crates/comrade-crypto/README.md +++ b/crates/comrade-reference/README.md @@ -1,4 +1,4 @@ -# Comrade Verification runtime +# Comrade Verification Runtime - Reference Implementation An opinionated script runtime for lock and unlcok scripts. Since scripts are just plain text, they need to be run "somewhere, somehow" and this can be by various means. For example, in previous versions of Comrade the Rhai scripting engine was used to execute the scripts. This could be done using other scripting languages such as Lua, but in this implementation a domain specific language (DSL) parser is used to run the scripts. diff --git a/crates/comrade-crypto/src/context.rs b/crates/comrade-reference/src/context.rs similarity index 97% rename from crates/comrade-crypto/src/context.rs rename to crates/comrade-reference/src/context.rs index 8af7d45..1494110 100644 --- a/crates/comrade-crypto/src/context.rs +++ b/crates/comrade-reference/src/context.rs @@ -22,12 +22,12 @@ use multiutil::prelude::*; /// The [Context] within which the script is /// evaluated. -pub(crate) struct Context { +pub struct Context<'a> { /// The Return stack - pub(crate) current: P, + pub(crate) current: &'a dyn Pairs, /// The Parameters stack - pub(crate) proposed: P, + pub(crate) proposed: &'a dyn Pairs, /// The number of checks that have been performed pub(crate) check_count: usize, @@ -42,20 +42,18 @@ pub(crate) struct Context { pub domain: String, /// The log implementation for the context - pub(crate) logger: L, + pub(crate) logger: &'a dyn Log, } /// Log a message to the console pub trait Log { - fn log(&self, msg: &str) - where - Self: Sized; + fn log(&self, msg: &str); } -impl Context { +impl<'a> Context<'a> { /// Create a new [Context] struct with the given [Current] and [Proposed] key-value stores, /// which are bound by both [Pairable]. - pub fn new(current: P, proposed: P, logger: L) -> Self { + pub fn new(current: &'a impl Pairs, proposed: &'a impl Pairs, logger: &'a impl Log) -> Self { Context { current, proposed, @@ -396,7 +394,7 @@ impl Context { s } - pub(crate) fn rstack(&self) -> Option { + pub fn rstack(&self) -> Option { self.rstack.top() } } @@ -498,7 +496,7 @@ mod tests { kvp_unlock.put(entry_key, &entry_data_vec.clone().into()); kvp_unlock.put(proof_key, &proof_data.clone().into()); - let mut ctx = Context::new(kvp_lock, kvp_unlock, TestLogger); + let mut ctx = Context::new(&kvp_lock, &kvp_unlock, &TestLogger); // When the unlock script runs, // there should be 2 values on the pstack @@ -554,7 +552,7 @@ mod tests { // If we run the unlock script, then the lock script // the results of check_signature should be true // since our signature is valid - let mut ctx = Context::new(kvp_lock, kvp_unlock, TestLogger); + let mut ctx = Context::new(&kvp_lock, &kvp_unlock, &TestLogger); let result = ctx.run(&unlock); assert!(result.is_ok()); diff --git a/crates/comrade-crypto/src/context/pairs.rs b/crates/comrade-reference/src/context/pairs.rs similarity index 100% rename from crates/comrade-crypto/src/context/pairs.rs rename to crates/comrade-reference/src/context/pairs.rs diff --git a/crates/comrade-crypto/src/context/parser.rs b/crates/comrade-reference/src/context/parser.rs similarity index 100% rename from crates/comrade-crypto/src/context/parser.rs rename to crates/comrade-reference/src/context/parser.rs diff --git a/crates/comrade-crypto/src/context/parser/grammar.pest b/crates/comrade-reference/src/context/parser/grammar.pest similarity index 100% rename from crates/comrade-crypto/src/context/parser/grammar.pest rename to crates/comrade-reference/src/context/parser/grammar.pest diff --git a/crates/comrade-crypto/src/context/stack.rs b/crates/comrade-reference/src/context/stack.rs similarity index 100% rename from crates/comrade-crypto/src/context/stack.rs rename to crates/comrade-reference/src/context/stack.rs diff --git a/crates/comrade-crypto/src/context/value.rs b/crates/comrade-reference/src/context/value.rs similarity index 100% rename from crates/comrade-crypto/src/context/value.rs rename to crates/comrade-reference/src/context/value.rs diff --git a/crates/comrade-crypto/src/error.rs b/crates/comrade-reference/src/error.rs similarity index 100% rename from crates/comrade-crypto/src/error.rs rename to crates/comrade-reference/src/error.rs diff --git a/crates/comrade-crypto/src/lib.rs b/crates/comrade-reference/src/lib.rs similarity index 74% rename from crates/comrade-crypto/src/lib.rs rename to crates/comrade-reference/src/lib.rs index 7b19654..1dee6a5 100644 --- a/crates/comrade-crypto/src/lib.rs +++ b/crates/comrade-reference/src/lib.rs @@ -5,4 +5,4 @@ pub use error::ApiError; /// Context is the main entry of this crate mod context; /// Public re-exports -pub use context::{Pairs, Value}; +pub use context::{Context, Log, Pairs, Value}; From 664a84d151456551f5e576e039d3e47ef8a6c0d2 Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Sun, 4 May 2025 17:35:26 -0300 Subject: [PATCH 009/155] add wasm component --- Cargo.toml | 4 +- crates/comrade-component/Cargo.toml | 2 + crates/comrade-component/src/bindings.rs | 1238 +++++++++++++++++++++- crates/comrade-component/src/kv.rs | 70 ++ crates/comrade-component/src/lib.rs | 60 +- crates/comrade-component/src/logger.rs | 10 + crates/comrade-component/src/random.rs | 37 + crates/comrade-component/wit/world.wit | 97 +- 8 files changed, 1454 insertions(+), 64 deletions(-) create mode 100644 crates/comrade-component/src/kv.rs create mode 100644 crates/comrade-component/src/logger.rs create mode 100644 crates/comrade-component/src/random.rs diff --git a/Cargo.toml b/Cargo.toml index 2d6c8eb..28f409c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,8 @@ members = [ "crates/bs-traits", "crates/bsp2p", "crates/comrade", - "crates/comrade-component", "crates/comrade-crypto", + "crates/comrade-component", + "crates/comrade-reference", "crates/content-addressable", "crates/multibase", "crates/multicid", @@ -40,6 +41,7 @@ unexpected_cfgs = { level = "warn", check-cfg = [ # Crate ependencies bs = { path = "crates/bs" } bs-traits = { path = "crates/bs-traits" } +comrade-reference = { path = "crates/comrade-reference" } multibase = { path = "crates/multibase" } multicid = { path = "crates/multicid" } multicodec = { path = "crates/multicodec" } diff --git a/crates/comrade-component/Cargo.toml b/crates/comrade-component/Cargo.toml index 286863e..580b019 100644 --- a/crates/comrade-component/Cargo.toml +++ b/crates/comrade-component/Cargo.toml @@ -8,6 +8,8 @@ readme.workspace = true [dependencies] wit-bindgen-rt = { version = "0.41.0", features = ["bitflags"] } +comrade-reference.workspace = true +getrandom = { version = "0.2", features = ["custom"] } [lints] workspace = true diff --git a/crates/comrade-component/src/bindings.rs b/crates/comrade-component/src/bindings.rs index cbc8fcd..4079dd3 100644 --- a/crates/comrade-component/src/bindings.rs +++ b/crates/comrade-component/src/bindings.rs @@ -1,52 +1,1173 @@ // Generated by `wit-bindgen` 0.41.0. DO NOT EDIT! // Options used: // * runtime_path: "wit_bindgen_rt" -#[doc(hidden)] -#[allow(non_snake_case)] -pub unsafe fn _export_hello_world_cabi() -> *mut u8 { - #[cfg(target_arch = "wasm32")] _rt::run_ctors_once(); - let result0 = T::hello_world(); - let ptr1 = (&raw mut _RET_AREA.0).cast::(); - let vec2 = (result0.into_bytes()).into_boxed_slice(); - let ptr2 = vec2.as_ptr().cast::(); - let len2 = vec2.len(); - ::core::mem::forget(vec2); - *ptr1.add(::core::mem::size_of::<*const u8>()).cast::() = len2; - *ptr1.add(0).cast::<*mut u8>() = ptr2.cast_mut(); - ptr1 -} -#[doc(hidden)] -#[allow(non_snake_case)] -pub unsafe fn __post_return_hello_world(arg0: *mut u8) { - let l0 = *arg0.add(0).cast::<*mut u8>(); - let l1 = *arg0.add(::core::mem::size_of::<*const u8>()).cast::(); - _rt::cabi_dealloc(l0, l1, 1); -} -pub trait Guest { - fn hello_world() -> _rt::String; +#[rustfmt::skip] +#[allow(dead_code, clippy::all)] +pub mod comrade { + pub mod api { + #[allow(dead_code, async_fn_in_trait, unused_imports, clippy::all)] + pub mod utils { + #[used] + #[doc(hidden)] + static __FORCE_SECTION_REF: fn() = super::super::super::__link_custom_section_describing_imports; + #[allow(unused_unsafe, clippy::all)] + /// Logs some output from the guest component. + pub fn log(message: &str) -> () { + unsafe { + let vec0 = message; + let ptr0 = vec0.as_ptr().cast::(); + let len0 = vec0.len(); + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "comrade:api/utils")] + unsafe extern "C" { + #[link_name = "log"] + fn wit_import1(_: *mut u8, _: usize); + } + #[cfg(not(target_arch = "wasm32"))] + unsafe extern "C" fn wit_import1(_: *mut u8, _: usize) { + unreachable!() + } + unsafe { wit_import1(ptr0.cast_mut(), len0) }; + } + } + #[allow(unused_unsafe, clippy::all)] + /// Get a random byte from the host system + pub fn random_byte() -> u8 { + unsafe { + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "comrade:api/utils")] + unsafe extern "C" { + #[link_name = "random-byte"] + fn wit_import0() -> i32; + } + #[cfg(not(target_arch = "wasm32"))] + unsafe extern "C" fn wit_import0() -> i32 { + unreachable!() + } + let ret = unsafe { wit_import0() }; + ret as u8 + } + } + } + #[allow(dead_code, async_fn_in_trait, unused_imports, clippy::all)] + pub mod pairs { + #[used] + #[doc(hidden)] + static __FORCE_SECTION_REF: fn() = super::super::super::__link_custom_section_describing_imports; + use super::super::super::_rt; + /// Either current or proposed + #[repr(u8)] + #[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)] + pub enum Either { + /// The current kv pairs + Current, + /// The proposed kv pairs + Proposed, + } + impl ::core::fmt::Debug for Either { + fn fmt( + &self, + f: &mut ::core::fmt::Formatter<'_>, + ) -> ::core::fmt::Result { + match self { + Either::Current => f.debug_tuple("Either::Current").finish(), + Either::Proposed => f.debug_tuple("Either::Proposed").finish(), + } + } + } + impl Either { + #[doc(hidden)] + pub unsafe fn _lift(val: u8) -> Either { + if !cfg!(debug_assertions) { + return ::core::mem::transmute(val); + } + match val { + 0 => Either::Current, + 1 => Either::Proposed, + _ => panic!("invalid enum discriminant"), + } + } + } + /// A binary value and associated hint + #[derive(Clone)] + pub struct Binary { + /// The binary value + pub data: _rt::Vec, + /// A hint for the binary value + pub hint: _rt::String, + } + impl ::core::fmt::Debug for Binary { + fn fmt( + &self, + f: &mut ::core::fmt::Formatter<'_>, + ) -> ::core::fmt::Result { + f.debug_struct("Binary") + .field("data", &self.data) + .field("hint", &self.hint) + .finish() + } + } + /// A string value and associated hint + #[derive(Clone)] + pub struct Str { + /// The string value + pub data: _rt::String, + /// A hint for the string value + pub hint: _rt::String, + } + impl ::core::fmt::Debug for Str { + fn fmt( + &self, + f: &mut ::core::fmt::Formatter<'_>, + ) -> ::core::fmt::Result { + f.debug_struct("Str") + .field("data", &self.data) + .field("hint", &self.hint) + .finish() + } + } + /// A variant representing the different types of values that can be stored. + #[derive(Clone)] + pub enum Value { + /// A binary value + Bin(Binary), + /// A string value + Str(Str), + /// Success value + Success(u32), + /// Failure value + Failure(_rt::String), + } + impl ::core::fmt::Debug for Value { + fn fmt( + &self, + f: &mut ::core::fmt::Formatter<'_>, + ) -> ::core::fmt::Result { + match self { + Value::Bin(e) => f.debug_tuple("Value::Bin").field(e).finish(), + Value::Str(e) => f.debug_tuple("Value::Str").field(e).finish(), + Value::Success(e) => { + f.debug_tuple("Value::Success").field(e).finish() + } + Value::Failure(e) => { + f.debug_tuple("Value::Failure").field(e).finish() + } + } + } + } + #[allow(unused_unsafe, clippy::all)] + /// Returns the value of the resource. + pub fn put(choice: Either, key: &str, value: &Value) -> Value { + unsafe { + #[cfg_attr(target_pointer_width = "64", repr(align(8)))] + #[cfg_attr(target_pointer_width = "32", repr(align(4)))] + struct RetArea( + [::core::mem::MaybeUninit< + u8, + >; 5 * ::core::mem::size_of::<*const u8>()], + ); + let mut ret_area = RetArea( + [::core::mem::MaybeUninit::uninit(); 5 + * ::core::mem::size_of::<*const u8>()], + ); + let vec0 = key; + let ptr0 = vec0.as_ptr().cast::(); + let len0 = vec0.len(); + let (result8_0, result8_1, result8_2, result8_3, result8_4) = match value { + Value::Bin(e) => { + let Binary { data: data1, hint: hint1 } = e; + let vec2 = data1; + let ptr2 = vec2.as_ptr().cast::(); + let len2 = vec2.len(); + let vec3 = hint1; + let ptr3 = vec3.as_ptr().cast::(); + let len3 = vec3.len(); + (0i32, ptr2.cast_mut(), len2, ptr3.cast_mut(), len3) + } + Value::Str(e) => { + let Str { data: data4, hint: hint4 } = e; + let vec5 = data4; + let ptr5 = vec5.as_ptr().cast::(); + let len5 = vec5.len(); + let vec6 = hint4; + let ptr6 = vec6.as_ptr().cast::(); + let len6 = vec6.len(); + (1i32, ptr5.cast_mut(), len5, ptr6.cast_mut(), len6) + } + Value::Success(e) => { + ( + 2i32, + _rt::as_i32(e) as *mut u8, + 0usize, + ::core::ptr::null_mut(), + 0usize, + ) + } + Value::Failure(e) => { + let vec7 = e; + let ptr7 = vec7.as_ptr().cast::(); + let len7 = vec7.len(); + ( + 3i32, + ptr7.cast_mut(), + len7, + ::core::ptr::null_mut(), + 0usize, + ) + } + }; + let ptr9 = ret_area.0.as_mut_ptr().cast::(); + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "comrade:api/pairs")] + unsafe extern "C" { + #[link_name = "put"] + fn wit_import10( + _: i32, + _: *mut u8, + _: usize, + _: i32, + _: *mut u8, + _: usize, + _: *mut u8, + _: usize, + _: *mut u8, + ); + } + #[cfg(not(target_arch = "wasm32"))] + unsafe extern "C" fn wit_import10( + _: i32, + _: *mut u8, + _: usize, + _: i32, + _: *mut u8, + _: usize, + _: *mut u8, + _: usize, + _: *mut u8, + ) { + unreachable!() + } + unsafe { + wit_import10( + choice.clone() as i32, + ptr0.cast_mut(), + len0, + result8_0, + result8_1, + result8_2, + result8_3, + result8_4, + ptr9, + ) + }; + let l11 = i32::from(*ptr9.add(0).cast::()); + let v28 = match l11 { + 0 => { + let e28 = { + let l12 = *ptr9 + .add(::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>(); + let l13 = *ptr9 + .add(2 * ::core::mem::size_of::<*const u8>()) + .cast::(); + let len14 = l13; + let l15 = *ptr9 + .add(3 * ::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>(); + let l16 = *ptr9 + .add(4 * ::core::mem::size_of::<*const u8>()) + .cast::(); + let len17 = l16; + let bytes17 = _rt::Vec::from_raw_parts( + l15.cast(), + len17, + len17, + ); + Binary { + data: _rt::Vec::from_raw_parts(l12.cast(), len14, len14), + hint: _rt::string_lift(bytes17), + } + }; + Value::Bin(e28) + } + 1 => { + let e28 = { + let l18 = *ptr9 + .add(::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>(); + let l19 = *ptr9 + .add(2 * ::core::mem::size_of::<*const u8>()) + .cast::(); + let len20 = l19; + let bytes20 = _rt::Vec::from_raw_parts( + l18.cast(), + len20, + len20, + ); + let l21 = *ptr9 + .add(3 * ::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>(); + let l22 = *ptr9 + .add(4 * ::core::mem::size_of::<*const u8>()) + .cast::(); + let len23 = l22; + let bytes23 = _rt::Vec::from_raw_parts( + l21.cast(), + len23, + len23, + ); + Str { + data: _rt::string_lift(bytes20), + hint: _rt::string_lift(bytes23), + } + }; + Value::Str(e28) + } + 2 => { + let e28 = { + let l24 = *ptr9 + .add(::core::mem::size_of::<*const u8>()) + .cast::(); + l24 as u32 + }; + Value::Success(e28) + } + n => { + debug_assert_eq!(n, 3, "invalid enum discriminant"); + let e28 = { + let l25 = *ptr9 + .add(::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>(); + let l26 = *ptr9 + .add(2 * ::core::mem::size_of::<*const u8>()) + .cast::(); + let len27 = l26; + let bytes27 = _rt::Vec::from_raw_parts( + l25.cast(), + len27, + len27, + ); + _rt::string_lift(bytes27) + }; + Value::Failure(e28) + } + }; + let result29 = v28; + result29 + } + } + #[allow(unused_unsafe, clippy::all)] + /// Returns the value of the resource. + /// Get a value from the given key, if it exists. + /// Returns some value or None. + pub fn get(choice: Either, key: &str) -> Option { + unsafe { + #[cfg_attr(target_pointer_width = "64", repr(align(8)))] + #[cfg_attr(target_pointer_width = "32", repr(align(4)))] + struct RetArea( + [::core::mem::MaybeUninit< + u8, + >; 6 * ::core::mem::size_of::<*const u8>()], + ); + let mut ret_area = RetArea( + [::core::mem::MaybeUninit::uninit(); 6 + * ::core::mem::size_of::<*const u8>()], + ); + let vec0 = key; + let ptr0 = vec0.as_ptr().cast::(); + let len0 = vec0.len(); + let ptr1 = ret_area.0.as_mut_ptr().cast::(); + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "comrade:api/pairs")] + unsafe extern "C" { + #[link_name = "get"] + fn wit_import2(_: i32, _: *mut u8, _: usize, _: *mut u8); + } + #[cfg(not(target_arch = "wasm32"))] + unsafe extern "C" fn wit_import2( + _: i32, + _: *mut u8, + _: usize, + _: *mut u8, + ) { + unreachable!() + } + unsafe { + wit_import2(choice.clone() as i32, ptr0.cast_mut(), len0, ptr1) + }; + let l3 = i32::from(*ptr1.add(0).cast::()); + let result22 = match l3 { + 0 => None, + 1 => { + let e = { + let l4 = i32::from( + *ptr1.add(::core::mem::size_of::<*const u8>()).cast::(), + ); + let v21 = match l4 { + 0 => { + let e21 = { + let l5 = *ptr1 + .add(2 * ::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>(); + let l6 = *ptr1 + .add(3 * ::core::mem::size_of::<*const u8>()) + .cast::(); + let len7 = l6; + let l8 = *ptr1 + .add(4 * ::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>(); + let l9 = *ptr1 + .add(5 * ::core::mem::size_of::<*const u8>()) + .cast::(); + let len10 = l9; + let bytes10 = _rt::Vec::from_raw_parts( + l8.cast(), + len10, + len10, + ); + Binary { + data: _rt::Vec::from_raw_parts(l5.cast(), len7, len7), + hint: _rt::string_lift(bytes10), + } + }; + Value::Bin(e21) + } + 1 => { + let e21 = { + let l11 = *ptr1 + .add(2 * ::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>(); + let l12 = *ptr1 + .add(3 * ::core::mem::size_of::<*const u8>()) + .cast::(); + let len13 = l12; + let bytes13 = _rt::Vec::from_raw_parts( + l11.cast(), + len13, + len13, + ); + let l14 = *ptr1 + .add(4 * ::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>(); + let l15 = *ptr1 + .add(5 * ::core::mem::size_of::<*const u8>()) + .cast::(); + let len16 = l15; + let bytes16 = _rt::Vec::from_raw_parts( + l14.cast(), + len16, + len16, + ); + Str { + data: _rt::string_lift(bytes13), + hint: _rt::string_lift(bytes16), + } + }; + Value::Str(e21) + } + 2 => { + let e21 = { + let l17 = *ptr1 + .add(2 * ::core::mem::size_of::<*const u8>()) + .cast::(); + l17 as u32 + }; + Value::Success(e21) + } + n => { + debug_assert_eq!(n, 3, "invalid enum discriminant"); + let e21 = { + let l18 = *ptr1 + .add(2 * ::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>(); + let l19 = *ptr1 + .add(3 * ::core::mem::size_of::<*const u8>()) + .cast::(); + let len20 = l19; + let bytes20 = _rt::Vec::from_raw_parts( + l18.cast(), + len20, + len20, + ); + _rt::string_lift(bytes20) + }; + Value::Failure(e21) + } + }; + v21 + }; + Some(e) + } + _ => _rt::invalid_enum_discriminant(), + }; + result22 + } + } + } + } } -#[doc(hidden)] -macro_rules! __export_world_example_cabi { - ($ty:ident with_types_in $($path_to_types:tt)*) => { - const _ : () = { #[unsafe (export_name = "hello-world")] unsafe extern "C" fn - export_hello_world() -> * mut u8 { unsafe { $($path_to_types)*:: - _export_hello_world_cabi::<$ty > () } } #[unsafe (export_name = - "cabi_post_hello-world")] unsafe extern "C" fn _post_return_hello_world(arg0 : * - mut u8,) { unsafe { $($path_to_types)*:: __post_return_hello_world::<$ty > (arg0) - } } }; - }; +#[rustfmt::skip] +#[allow(dead_code, clippy::all)] +pub mod exports { + pub mod comrade { + pub mod api { + #[allow(dead_code, async_fn_in_trait, unused_imports, clippy::all)] + pub mod api { + #[used] + #[doc(hidden)] + static __FORCE_SECTION_REF: fn() = super::super::super::super::__link_custom_section_describing_imports; + use super::super::super::super::_rt; + pub type Value = super::super::super::super::comrade::api::pairs::Value; + /// The main API resource + #[derive(Debug)] + #[repr(transparent)] + pub struct Api { + handle: _rt::Resource, + } + type _ApiRep = Option; + impl Api { + /// Creates a new resource from the specified representation. + /// + /// This function will create a new resource handle by moving `val` onto + /// the heap and then passing that heap pointer to the component model to + /// create a handle. The owned handle is then returned as `Api`. + pub fn new(val: T) -> Self { + Self::type_guard::(); + let val: _ApiRep = Some(val); + let ptr: *mut _ApiRep = _rt::Box::into_raw( + _rt::Box::new(val), + ); + unsafe { Self::from_handle(T::_resource_new(ptr.cast())) } + } + /// Gets access to the underlying `T` which represents this resource. + pub fn get(&self) -> &T { + let ptr = unsafe { &*self.as_ptr::() }; + ptr.as_ref().unwrap() + } + /// Gets mutable access to the underlying `T` which represents this + /// resource. + pub fn get_mut(&mut self) -> &mut T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.as_mut().unwrap() + } + /// Consumes this resource and returns the underlying `T`. + pub fn into_inner(self) -> T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.take().unwrap() + } + #[doc(hidden)] + pub unsafe fn from_handle(handle: u32) -> Self { + Self { + handle: unsafe { _rt::Resource::from_handle(handle) }, + } + } + #[doc(hidden)] + pub fn take_handle(&self) -> u32 { + _rt::Resource::take_handle(&self.handle) + } + #[doc(hidden)] + pub fn handle(&self) -> u32 { + _rt::Resource::handle(&self.handle) + } + #[doc(hidden)] + fn type_guard() { + use core::any::TypeId; + static mut LAST_TYPE: Option = None; + unsafe { + assert!(! cfg!(target_feature = "atomics")); + let id = TypeId::of::(); + match LAST_TYPE { + Some(ty) => { + assert!( + ty == id, "cannot use two types with this resource type" + ) + } + None => LAST_TYPE = Some(id), + } + } + } + #[doc(hidden)] + pub unsafe fn dtor(handle: *mut u8) { + Self::type_guard::(); + let _ = unsafe { _rt::Box::from_raw(handle as *mut _ApiRep) }; + } + fn as_ptr(&self) -> *mut _ApiRep { + Api::type_guard::(); + T::_resource_rep(self.handle()).cast() + } + } + /// A borrowed version of [`Api`] which represents a borrowed value + /// with the lifetime `'a`. + #[derive(Debug)] + #[repr(transparent)] + pub struct ApiBorrow<'a> { + rep: *mut u8, + _marker: core::marker::PhantomData<&'a Api>, + } + impl<'a> ApiBorrow<'a> { + #[doc(hidden)] + pub unsafe fn lift(rep: usize) -> Self { + Self { + rep: rep as *mut u8, + _marker: core::marker::PhantomData, + } + } + /// Gets access to the underlying `T` in this resource. + pub fn get(&self) -> &T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.as_ref().unwrap() + } + fn as_ptr(&self) -> *mut _ApiRep { + Api::type_guard::(); + self.rep.cast() + } + } + unsafe impl _rt::WasmResource for Api { + #[inline] + unsafe fn drop(_handle: u32) { + #[cfg(not(target_arch = "wasm32"))] + unreachable!(); + #[cfg(target_arch = "wasm32")] + { + #[link(wasm_import_module = "[export]comrade:api/api")] + unsafe extern "C" { + #[link_name = "[resource-drop]api"] + fn drop(_: u32); + } + unsafe { drop(_handle) }; + } + } + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_constructor_api_cabi() -> i32 { + #[cfg(target_arch = "wasm32")] _rt::run_ctors_once(); + let result0 = Api::new(T::new()); + (result0).take_handle() as i32 + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_method_api_try_unlock_cabi( + arg0: *mut u8, + arg1: *mut u8, + arg2: usize, + ) -> *mut u8 { + #[cfg(target_arch = "wasm32")] _rt::run_ctors_once(); + let len0 = arg2; + let bytes0 = _rt::Vec::from_raw_parts(arg1.cast(), len0, len0); + let result1 = T::try_unlock( + unsafe { ApiBorrow::lift(arg0 as u32 as usize) }.get(), + _rt::string_lift(bytes0), + ); + let ptr2 = (&raw mut _RET_AREA.0).cast::(); + match result1 { + Ok(_) => { + *ptr2.add(0).cast::() = (0i32) as u8; + } + Err(e) => { + *ptr2.add(0).cast::() = (1i32) as u8; + let vec3 = (e.into_bytes()).into_boxed_slice(); + let ptr3 = vec3.as_ptr().cast::(); + let len3 = vec3.len(); + ::core::mem::forget(vec3); + *ptr2 + .add(2 * ::core::mem::size_of::<*const u8>()) + .cast::() = len3; + *ptr2 + .add(::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>() = ptr3.cast_mut(); + } + }; + ptr2 + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn __post_return_method_api_try_unlock( + arg0: *mut u8, + ) { + let l0 = i32::from(*arg0.add(0).cast::()); + match l0 { + 0 => {} + _ => { + let l1 = *arg0 + .add(::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>(); + let l2 = *arg0 + .add(2 * ::core::mem::size_of::<*const u8>()) + .cast::(); + _rt::cabi_dealloc(l1, l2, 1); + } + } + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_method_api_try_lock_cabi( + arg0: *mut u8, + arg1: *mut u8, + arg2: usize, + ) -> *mut u8 { + #[cfg(target_arch = "wasm32")] _rt::run_ctors_once(); + let len0 = arg2; + let bytes0 = _rt::Vec::from_raw_parts(arg1.cast(), len0, len0); + let result1 = T::try_lock( + unsafe { ApiBorrow::lift(arg0 as u32 as usize) }.get(), + _rt::string_lift(bytes0), + ); + let ptr2 = (&raw mut _RET_AREA.0).cast::(); + match result1 { + Ok(e) => { + *ptr2.add(0).cast::() = (0i32) as u8; + match e { + Some(e) => { + *ptr2 + .add(::core::mem::size_of::<*const u8>()) + .cast::() = (1i32) as u8; + use super::super::super::super::comrade::api::pairs::Value as V10; + match e { + V10::Bin(e) => { + *ptr2 + .add(2 * ::core::mem::size_of::<*const u8>()) + .cast::() = (0i32) as u8; + let super::super::super::super::comrade::api::pairs::Binary { + data: data3, + hint: hint3, + } = e; + let vec4 = (data3).into_boxed_slice(); + let ptr4 = vec4.as_ptr().cast::(); + let len4 = vec4.len(); + ::core::mem::forget(vec4); + *ptr2 + .add(4 * ::core::mem::size_of::<*const u8>()) + .cast::() = len4; + *ptr2 + .add(3 * ::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>() = ptr4.cast_mut(); + let vec5 = (hint3.into_bytes()).into_boxed_slice(); + let ptr5 = vec5.as_ptr().cast::(); + let len5 = vec5.len(); + ::core::mem::forget(vec5); + *ptr2 + .add(6 * ::core::mem::size_of::<*const u8>()) + .cast::() = len5; + *ptr2 + .add(5 * ::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>() = ptr5.cast_mut(); + } + V10::Str(e) => { + *ptr2 + .add(2 * ::core::mem::size_of::<*const u8>()) + .cast::() = (1i32) as u8; + let super::super::super::super::comrade::api::pairs::Str { + data: data6, + hint: hint6, + } = e; + let vec7 = (data6.into_bytes()).into_boxed_slice(); + let ptr7 = vec7.as_ptr().cast::(); + let len7 = vec7.len(); + ::core::mem::forget(vec7); + *ptr2 + .add(4 * ::core::mem::size_of::<*const u8>()) + .cast::() = len7; + *ptr2 + .add(3 * ::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>() = ptr7.cast_mut(); + let vec8 = (hint6.into_bytes()).into_boxed_slice(); + let ptr8 = vec8.as_ptr().cast::(); + let len8 = vec8.len(); + ::core::mem::forget(vec8); + *ptr2 + .add(6 * ::core::mem::size_of::<*const u8>()) + .cast::() = len8; + *ptr2 + .add(5 * ::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>() = ptr8.cast_mut(); + } + V10::Success(e) => { + *ptr2 + .add(2 * ::core::mem::size_of::<*const u8>()) + .cast::() = (2i32) as u8; + *ptr2 + .add(3 * ::core::mem::size_of::<*const u8>()) + .cast::() = _rt::as_i32(e); + } + V10::Failure(e) => { + *ptr2 + .add(2 * ::core::mem::size_of::<*const u8>()) + .cast::() = (3i32) as u8; + let vec9 = (e.into_bytes()).into_boxed_slice(); + let ptr9 = vec9.as_ptr().cast::(); + let len9 = vec9.len(); + ::core::mem::forget(vec9); + *ptr2 + .add(4 * ::core::mem::size_of::<*const u8>()) + .cast::() = len9; + *ptr2 + .add(3 * ::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>() = ptr9.cast_mut(); + } + } + } + None => { + *ptr2 + .add(::core::mem::size_of::<*const u8>()) + .cast::() = (0i32) as u8; + } + }; + } + Err(e) => { + *ptr2.add(0).cast::() = (1i32) as u8; + let vec11 = (e.into_bytes()).into_boxed_slice(); + let ptr11 = vec11.as_ptr().cast::(); + let len11 = vec11.len(); + ::core::mem::forget(vec11); + *ptr2 + .add(2 * ::core::mem::size_of::<*const u8>()) + .cast::() = len11; + *ptr2 + .add(::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>() = ptr11.cast_mut(); + } + }; + ptr2 + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn __post_return_method_api_try_lock( + arg0: *mut u8, + ) { + let l0 = i32::from(*arg0.add(0).cast::()); + match l0 { + 0 => { + let l1 = i32::from( + *arg0.add(::core::mem::size_of::<*const u8>()).cast::(), + ); + match l1 { + 0 => {} + _ => { + let l2 = i32::from( + *arg0 + .add(2 * ::core::mem::size_of::<*const u8>()) + .cast::(), + ); + match l2 { + 0 => { + let l3 = *arg0 + .add(3 * ::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>(); + let l4 = *arg0 + .add(4 * ::core::mem::size_of::<*const u8>()) + .cast::(); + let base5 = l3; + let len5 = l4; + _rt::cabi_dealloc(base5, len5 * 1, 1); + let l6 = *arg0 + .add(5 * ::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>(); + let l7 = *arg0 + .add(6 * ::core::mem::size_of::<*const u8>()) + .cast::(); + _rt::cabi_dealloc(l6, l7, 1); + } + 1 => { + let l8 = *arg0 + .add(3 * ::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>(); + let l9 = *arg0 + .add(4 * ::core::mem::size_of::<*const u8>()) + .cast::(); + _rt::cabi_dealloc(l8, l9, 1); + let l10 = *arg0 + .add(5 * ::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>(); + let l11 = *arg0 + .add(6 * ::core::mem::size_of::<*const u8>()) + .cast::(); + _rt::cabi_dealloc(l10, l11, 1); + } + 2 => {} + _ => { + let l12 = *arg0 + .add(3 * ::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>(); + let l13 = *arg0 + .add(4 * ::core::mem::size_of::<*const u8>()) + .cast::(); + _rt::cabi_dealloc(l12, l13, 1); + } + } + } + } + } + _ => { + let l14 = *arg0 + .add(::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>(); + let l15 = *arg0 + .add(2 * ::core::mem::size_of::<*const u8>()) + .cast::(); + _rt::cabi_dealloc(l14, l15, 1); + } + } + } + pub trait Guest { + type Api: GuestApi; + } + pub trait GuestApi: 'static { + #[doc(hidden)] + unsafe fn _resource_new(val: *mut u8) -> u32 + where + Self: Sized, + { + #[cfg(not(target_arch = "wasm32"))] + { + let _ = val; + unreachable!(); + } + #[cfg(target_arch = "wasm32")] + { + #[link(wasm_import_module = "[export]comrade:api/api")] + unsafe extern "C" { + #[link_name = "[resource-new]api"] + fn new(_: *mut u8) -> u32; + } + unsafe { new(val) } + } + } + #[doc(hidden)] + fn _resource_rep(handle: u32) -> *mut u8 + where + Self: Sized, + { + #[cfg(not(target_arch = "wasm32"))] + { + let _ = handle; + unreachable!(); + } + #[cfg(target_arch = "wasm32")] + { + #[link(wasm_import_module = "[export]comrade:api/api")] + unsafe extern "C" { + #[link_name = "[resource-rep]api"] + fn rep(_: u32) -> *mut u8; + } + unsafe { rep(handle) } + } + } + /// The main API function + fn new() -> Self; + /// Set the unlock script Fails if the script is not valid. + fn try_unlock(&self, script: _rt::String) -> Result<(), _rt::String>; + /// Tries a lock script + fn try_lock( + &self, + lock: _rt::String, + ) -> Result, _rt::String>; + } + #[doc(hidden)] + macro_rules! __export_comrade_api_api_cabi { + ($ty:ident with_types_in $($path_to_types:tt)*) => { + const _ : () = { #[unsafe (export_name = + "comrade:api/api#[constructor]api")] unsafe extern "C" fn + export_constructor_api() -> i32 { unsafe { $($path_to_types)*:: + _export_constructor_api_cabi::<<$ty as $($path_to_types)*:: Guest + >::Api > () } } #[unsafe (export_name = + "comrade:api/api#[method]api.try-unlock")] unsafe extern "C" fn + export_method_api_try_unlock(arg0 : * mut u8, arg1 : * mut u8, + arg2 : usize,) -> * mut u8 { unsafe { $($path_to_types)*:: + _export_method_api_try_unlock_cabi::<<$ty as $($path_to_types)*:: + Guest >::Api > (arg0, arg1, arg2) } } #[unsafe (export_name = + "cabi_post_comrade:api/api#[method]api.try-unlock")] unsafe + extern "C" fn _post_return_method_api_try_unlock(arg0 : * mut + u8,) { unsafe { $($path_to_types)*:: + __post_return_method_api_try_unlock::<<$ty as + $($path_to_types)*:: Guest >::Api > (arg0) } } #[unsafe + (export_name = "comrade:api/api#[method]api.try-lock")] unsafe + extern "C" fn export_method_api_try_lock(arg0 : * mut u8, arg1 : + * mut u8, arg2 : usize,) -> * mut u8 { unsafe { + $($path_to_types)*:: _export_method_api_try_lock_cabi::<<$ty as + $($path_to_types)*:: Guest >::Api > (arg0, arg1, arg2) } } + #[unsafe (export_name = + "cabi_post_comrade:api/api#[method]api.try-lock")] unsafe extern + "C" fn _post_return_method_api_try_lock(arg0 : * mut u8,) { + unsafe { $($path_to_types)*:: + __post_return_method_api_try_lock::<<$ty as $($path_to_types)*:: + Guest >::Api > (arg0) } } const _ : () = { #[doc(hidden)] + #[unsafe (export_name = "comrade:api/api#[dtor]api")] + #[allow(non_snake_case)] unsafe extern "C" fn dtor(rep : * mut + u8) { unsafe { $($path_to_types)*:: Api::dtor::< <$ty as + $($path_to_types)*:: Guest >::Api > (rep) } } }; }; + }; + } + #[doc(hidden)] + pub(crate) use __export_comrade_api_api_cabi; + #[cfg_attr(target_pointer_width = "64", repr(align(8)))] + #[cfg_attr(target_pointer_width = "32", repr(align(4)))] + struct _RetArea( + [::core::mem::MaybeUninit< + u8, + >; 7 * ::core::mem::size_of::<*const u8>()], + ); + static mut _RET_AREA: _RetArea = _RetArea( + [::core::mem::MaybeUninit::uninit(); 7 + * ::core::mem::size_of::<*const u8>()], + ); + } + } + } } -#[doc(hidden)] -pub(crate) use __export_world_example_cabi; -#[cfg_attr(target_pointer_width = "64", repr(align(8)))] -#[cfg_attr(target_pointer_width = "32", repr(align(4)))] -struct _RetArea([::core::mem::MaybeUninit; 2 * ::core::mem::size_of::<*const u8>()]); -static mut _RET_AREA: _RetArea = _RetArea( - [::core::mem::MaybeUninit::uninit(); 2 * ::core::mem::size_of::<*const u8>()], -); #[rustfmt::skip] mod _rt { #![allow(dead_code, clippy::all)] + pub use alloc_crate::vec::Vec; + pub use alloc_crate::string::String; + pub fn as_i32(t: T) -> i32 { + t.as_i32() + } + pub trait AsI32 { + fn as_i32(self) -> i32; + } + impl<'a, T: Copy + AsI32> AsI32 for &'a T { + fn as_i32(self) -> i32 { + (*self).as_i32() + } + } + impl AsI32 for i32 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + impl AsI32 for u32 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + impl AsI32 for i16 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + impl AsI32 for u16 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + impl AsI32 for i8 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + impl AsI32 for u8 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + impl AsI32 for char { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + impl AsI32 for usize { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + pub unsafe fn string_lift(bytes: Vec) -> String { + if cfg!(debug_assertions) { + String::from_utf8(bytes).unwrap() + } else { + String::from_utf8_unchecked(bytes) + } + } + pub unsafe fn invalid_enum_discriminant() -> T { + if cfg!(debug_assertions) { + panic!("invalid enum discriminant") + } else { + unsafe { core::hint::unreachable_unchecked() } + } + } + use core::fmt; + use core::marker; + use core::sync::atomic::{AtomicU32, Ordering::Relaxed}; + /// A type which represents a component model resource, either imported or + /// exported into this component. + /// + /// This is a low-level wrapper which handles the lifetime of the resource + /// (namely this has a destructor). The `T` provided defines the component model + /// intrinsics that this wrapper uses. + /// + /// One of the chief purposes of this type is to provide `Deref` implementations + /// to access the underlying data when it is owned. + /// + /// This type is primarily used in generated code for exported and imported + /// resources. + #[repr(transparent)] + pub struct Resource { + handle: AtomicU32, + _marker: marker::PhantomData, + } + /// A trait which all wasm resources implement, namely providing the ability to + /// drop a resource. + /// + /// This generally is implemented by generated code, not user-facing code. + #[allow(clippy::missing_safety_doc)] + pub unsafe trait WasmResource { + /// Invokes the `[resource-drop]...` intrinsic. + unsafe fn drop(handle: u32); + } + impl Resource { + #[doc(hidden)] + pub unsafe fn from_handle(handle: u32) -> Self { + debug_assert!(handle != u32::MAX); + Self { + handle: AtomicU32::new(handle), + _marker: marker::PhantomData, + } + } + /// Takes ownership of the handle owned by `resource`. + /// + /// Note that this ideally would be `into_handle` taking `Resource` by + /// ownership. The code generator does not enable that in all situations, + /// unfortunately, so this is provided instead. + /// + /// Also note that `take_handle` is in theory only ever called on values + /// owned by a generated function. For example a generated function might + /// take `Resource` as an argument but then call `take_handle` on a + /// reference to that argument. In that sense the dynamic nature of + /// `take_handle` should only be exposed internally to generated code, not + /// to user code. + #[doc(hidden)] + pub fn take_handle(resource: &Resource) -> u32 { + resource.handle.swap(u32::MAX, Relaxed) + } + #[doc(hidden)] + pub fn handle(resource: &Resource) -> u32 { + resource.handle.load(Relaxed) + } + } + impl fmt::Debug for Resource { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Resource").field("handle", &self.handle).finish() + } + } + impl Drop for Resource { + fn drop(&mut self) { + unsafe { + match self.handle.load(Relaxed) { + u32::MAX => {} + other => T::drop(other), + } + } + } + } + pub use alloc_crate::boxed::Box; #[cfg(target_arch = "wasm32")] pub fn run_ctors_once() { wit_bindgen_rt::run_ctors_once(); @@ -58,9 +1179,8 @@ mod _rt { let layout = alloc::Layout::from_size_align_unchecked(size, align); alloc::dealloc(ptr, layout); } - pub use alloc_crate::string::String; - pub use alloc_crate::alloc; extern crate alloc as alloc_crate; + pub use alloc_crate::alloc; } /// Generates `#[unsafe(no_mangle)]` functions to export the specified type as /// the root implementation of all generated traits. @@ -80,28 +1200,40 @@ mod _rt { /// ``` #[allow(unused_macros)] #[doc(hidden)] -macro_rules! __export_example_impl { +macro_rules! __export_wacc_impl { ($ty:ident) => { self::export!($ty with_types_in self); }; ($ty:ident with_types_in $($path_to_types_root:tt)*) => { - $($path_to_types_root)*:: __export_world_example_cabi!($ty with_types_in - $($path_to_types_root)*); + $($path_to_types_root)*:: + exports::comrade::api::api::__export_comrade_api_api_cabi!($ty with_types_in + $($path_to_types_root)*:: exports::comrade::api::api); }; } #[doc(inline)] -pub(crate) use __export_example_impl as export; +pub(crate) use __export_wacc_impl as export; #[cfg(target_arch = "wasm32")] #[unsafe( - link_section = "component-type:wit-bindgen:0.41.0:component:comrade-component:example:encoded world" + link_section = "component-type:wit-bindgen:0.41.0:comrade:api:wacc:encoded world" )] #[doc(hidden)] #[allow(clippy::octal_escapes)] -pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 192] = *b"\ -\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07C\x01A\x02\x01A\x02\x01\ -@\0\0s\x04\0\x0bhello-world\x01\0\x04\0#component:comrade-component/example\x04\0\ -\x0b\x0d\x01\0\x07example\x03\0\0\0G\x09producers\x01\x0cprocessed-by\x02\x0dwit\ --component\x070.227.1\x10wit-bindgen-rust\x060.41.0"; +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 632] = *b"\ +\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\xfd\x03\x01A\x02\x01\ +A\x07\x01B\x04\x01@\x01\x07messages\x01\0\x04\0\x03log\x01\0\x01@\0\0}\x04\0\x0b\ +random-byte\x01\x01\x03\0\x11comrade:api/utils\x05\0\x01B\x0e\x01m\x02\x07curren\ +t\x08proposed\x04\0\x06either\x03\0\0\x01p}\x01r\x02\x04data\x02\x04hints\x04\0\x06\ +binary\x03\0\x03\x01r\x02\x04datas\x04hints\x04\0\x03str\x03\0\x05\x01q\x04\x03b\ +in\x01\x04\0\x03str\x01\x06\0\x07success\x01y\0\x07failure\x01s\0\x04\0\x05value\ +\x03\0\x07\x01@\x03\x06choice\x01\x03keys\x05value\x08\0\x08\x04\0\x03put\x01\x09\ +\x01k\x08\x01@\x02\x06choice\x01\x03keys\0\x0a\x04\0\x03get\x01\x0b\x03\0\x11com\ +rade:api/pairs\x05\x01\x02\x03\0\x01\x05value\x01B\x0e\x02\x03\x02\x01\x02\x04\0\ +\x05value\x03\0\0\x04\0\x03api\x03\x01\x01i\x02\x01@\0\0\x03\x04\0\x10[construct\ +or]api\x01\x04\x01h\x02\x01j\0\x01s\x01@\x02\x04self\x05\x06scripts\0\x06\x04\0\x16\ +[method]api.try-unlock\x01\x07\x01k\x01\x01j\x01\x08\x01s\x01@\x02\x04self\x05\x04\ +locks\0\x09\x04\0\x14[method]api.try-lock\x01\x0a\x04\0\x0fcomrade:api/api\x05\x03\ +\x04\0\x10comrade:api/wacc\x04\0\x0b\x0a\x01\0\x04wacc\x03\0\0\0G\x09producers\x01\ +\x0cprocessed-by\x02\x0dwit-component\x070.227.1\x10wit-bindgen-rust\x060.41.0"; #[inline(never)] #[doc(hidden)] pub fn __link_custom_section_describing_imports() { diff --git a/crates/comrade-component/src/kv.rs b/crates/comrade-component/src/kv.rs new file mode 100644 index 0000000..1056441 --- /dev/null +++ b/crates/comrade-component/src/kv.rs @@ -0,0 +1,70 @@ +use crate::bindings::comrade::api::{ + pairs::{self, get, put, Binary, Str}, + utils::log, +}; +use comrade_reference::{Pairs, Value}; + +#[derive(Default, Clone, Debug)] +pub(crate) struct Current; + +#[derive(Default, Clone, Debug)] +pub(crate) struct Proposed; + +impl Pairs for Current { + fn get(&self, key: &str) -> Option { + get(pairs::Either::Current, key) + .map(|v| v.clone()) + .map(|v| v.into()) + .or_else(|| { + log(&format!("Key not found: {}", key)); + None + }) + } + + fn put(&mut self, key: &str, value: &Value) -> Option { + log(&format!("Putting key: {} value: {:?}", key, value)); + let val = put(pairs::Either::Current, key, &value.clone().into()); + Some(val.into()) + } +} + +impl Pairs for Proposed { + fn get(&self, key: &str) -> Option { + get(pairs::Either::Proposed, key) + .map(|v| v.clone()) + .map(|v| v.into()) + .or_else(|| { + log(&format!("Key not found: {}", key)); + None + }) + } + + fn put(&mut self, key: &str, value: &Value) -> Option { + log(&format!("Putting key: {} value: {:?}", key, value)); + let val = put(pairs::Either::Proposed, key, &value.clone().into()); + Some(val.into()) + } +} + +impl From for Value { + fn from(value: crate::bindings::comrade::api::pairs::Value) -> Self { + match value { + pairs::Value::Str(Str { data, hint }) => Value::Str { hint, data }, + pairs::Value::Bin(Binary { data, hint }) => Value::Bin { hint, data }, + pairs::Value::Success(value) => Value::Success(value.try_into().unwrap()), + pairs::Value::Failure(msg) => Value::Failure(msg), + } + } +} + +// From` for `pairs::Value` +impl From for pairs::Value { + fn from(value: Value) -> Self { + match value { + Value::Str { hint, data } => pairs::Value::Str(Str { hint, data }), + Value::Bin { hint, data } => pairs::Value::Bin(Binary { hint, data }), + Value::Success(value) => pairs::Value::Success(value.try_into().unwrap()), + Value::Failure(msg) => pairs::Value::Failure(msg), + } + } +} diff --git a/crates/comrade-component/src/lib.rs b/crates/comrade-component/src/lib.rs index 7bea1f6..8601bbf 100644 --- a/crates/comrade-component/src/lib.rs +++ b/crates/comrade-component/src/lib.rs @@ -1,15 +1,61 @@ #[allow(warnings)] mod bindings; -use bindings::Guest; +mod kv; +mod logger; +mod random; -struct Component; +use crate::{ + kv::{Current, Proposed}, + logger::Logger, +}; +use bindings::comrade::api::pairs; +use bindings::comrade::api::utils::log; +use bindings::exports::comrade::api::api::Guest; +use bindings::exports::comrade::api::api::GuestApi; +use comrade_reference::Context; +use std::cell::RefCell; -impl Guest for Component { - /// Say hello! - fn hello_world() -> String { - "Hello, World!".to_string() +struct Api { + context: RefCell>, + unlock: RefCell>, +} + +impl Guest for Api { + type Api = Self; +} + +impl GuestApi for Api { + fn new() -> Self { + log("Creating new Component"); + + Self { + context: RefCell::new(Context::new(&Current, &Proposed, &Logger)), + unlock: RefCell::new(None), + } + } + + fn try_unlock(&self, unlock: String) -> Result<(), String> { + log("try_unlock"); + self.unlock.borrow_mut().replace(unlock.clone()); + // self.vm.run(&unlock).map_err(|e| e.to_string())?; + self.context.borrow_mut().run(&unlock).map_err(|e| { + log(&format!("Error running unlock script: {e}")); + format!("Error running unlock script: {e}") + })?; + Ok(()) + } + + fn try_lock(&self, lock: String) -> Result, String> { + log(&format!("try_lock script: {lock}")); + self.context.borrow_mut().run(&lock).map_err(|e| { + log(&format!("Error running lock script: {e}")); + format!("Error running lock script: {e}") + })?; + // return rstack + let rstack = self.context.borrow_mut().rstack(); + Ok(rstack.map(|v| v.into())) } } -bindings::export!(Component with_types_in bindings); +bindings::export!(Api with_types_in bindings); diff --git a/crates/comrade-component/src/logger.rs b/crates/comrade-component/src/logger.rs new file mode 100644 index 0000000..64c326a --- /dev/null +++ b/crates/comrade-component/src/logger.rs @@ -0,0 +1,10 @@ +use crate::bindings::comrade::api::utils::log; +use comrade_reference::Log; + +pub(crate) struct Logger; + +impl Log for Logger { + fn log(&self, msg: &str) { + log(msg); + } +} diff --git a/crates/comrade-component/src/random.rs b/crates/comrade-component/src/random.rs new file mode 100644 index 0000000..7a313ba --- /dev/null +++ b/crates/comrade-component/src/random.rs @@ -0,0 +1,37 @@ +//! Implements random for this wasm32-unknown-unknown target. +//! +//! Since we was wasm but ARE NOT int he browser, we need to use the +//! custom function to generate random bytes. +//! +//! However, since getrandom v0.2 and v0.3 are both present in dependencies, +//! and have different APIs, we need to implement both of them. +use super::bindings::comrade::api::utils::random_byte; + +/// getrandom v0.2 requires the 'cusotm' flag and this custom function to use the import for random byte generation. +/// +/// We do this is because "js" feature is incompatible with the component model +/// if you ever got the __wbindgen_placeholder__ error when trying to use the `js` feature +/// of getrandom, +pub fn imported_random(dest: &mut [u8]) -> Result<(), getrandom::Error> { + // iterate over the length of the destination buffer and fill it with random bytes + (0..dest.len()).for_each(|i| { + dest[i] = random_byte(); + }); + + Ok(()) +} + +// for getrandom v0.2 custom function +getrandom::register_custom_getrandom!(imported_random); + +/// getrandom v0.3 requires the 'custom' RUSTFLAG and this function below: +#[unsafe(no_mangle)] +pub unsafe extern "Rust" fn __getrandom_v03_custom( + dest: *mut u8, + len: usize, +) -> Result<(), getrandom::Error> { + // Safety: This is safe because we are using the imported_random function + // which is safe to use in this context. + let slice = unsafe { std::slice::from_raw_parts_mut(dest, len) }; + imported_random(slice) +} diff --git a/crates/comrade-component/wit/world.wit b/crates/comrade-component/wit/world.wit index 3e29bb5..11dfed5 100644 --- a/crates/comrade-component/wit/world.wit +++ b/crates/comrade-component/wit/world.wit @@ -1,6 +1,97 @@ -package component:comrade-component; +package comrade:api; + +interface api { + use pairs.{value}; + + /// The main API resource + resource api { + /// The main API function + constructor(); + + /// Set the unlock script Fails if the script is not valid. + try-unlock: func(script: string) -> result<_, string>; + + /// Tries a lock script + try-lock: func(lock: string) -> result, string>; + } +} + +interface pairs { + + /// Returns the value of the resource. + put: func(choice: either, key: string, value: value) -> value; + + /// Returns the value of the resource. + /// Get a value from the given key, if it exists. + /// Returns some value or None. + get: func(choice: either, key: string) -> option; + + /// Either current or proposed + enum either { + /// The current kv pairs + current, + + /// The proposed kv pairs + proposed + } + + /// A binary value and associated hint + record binary { + /// The binary value + data: list, + + /// A hint for the binary value + hint: string + } + + /// A string value and associated hint + record str { + /// The string value + data: string, + + /// A hint for the string value + hint: string + } + + /// A variant representing the different types of values that can be stored. + variant value { + /// A binary value + bin(binary), + + /// A string value + str(str), + + /// Success value + success(u32), + + /// Failure value + failure(string) + } + +} + +interface utils { + /// Logs some output from the guest component. + log: func(message: string); + + /// Get a random byte from the host system + random-byte: func() -> u8; +} + +interface env { + now: func() -> u64; +} /// An example world for the component to target. -world example { - export hello-world: func() -> string; +world wacc { + + /// Utilities provided by host + import utils; + + import pairs; + /// Re-exports the `pairs` interface for the VM + // export pairs; + + /// Export the main api for the component + export api; } From 6fc829c8a01731baf581a5fc589dee3b585f6be1 Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Sun, 4 May 2025 18:59:11 -0300 Subject: [PATCH 010/155] cleanup --- crates/comrade-component/src/kv.rs | 11 ++++------- crates/comrade/src/core.rs | 5 ----- crates/comrade/src/lib.rs | 4 ---- 3 files changed, 4 insertions(+), 16 deletions(-) delete mode 100644 crates/comrade/src/core.rs diff --git a/crates/comrade-component/src/kv.rs b/crates/comrade-component/src/kv.rs index 1056441..fadfa97 100644 --- a/crates/comrade-component/src/kv.rs +++ b/crates/comrade-component/src/kv.rs @@ -13,16 +13,15 @@ pub(crate) struct Proposed; impl Pairs for Current { fn get(&self, key: &str) -> Option { get(pairs::Either::Current, key) - .map(|v| v.clone()) .map(|v| v.into()) .or_else(|| { - log(&format!("Key not found: {}", key)); + log(&format!("Key not found: {key}")); None }) } fn put(&mut self, key: &str, value: &Value) -> Option { - log(&format!("Putting key: {} value: {:?}", key, value)); + log(&format!("Putting key: {key} value: {value:?}")); let val = put(pairs::Either::Current, key, &value.clone().into()); Some(val.into()) } @@ -31,16 +30,15 @@ impl Pairs for Current { impl Pairs for Proposed { fn get(&self, key: &str) -> Option { get(pairs::Either::Proposed, key) - .map(|v| v.clone()) .map(|v| v.into()) .or_else(|| { - log(&format!("Key not found: {}", key)); + log(&format!("Key not found: {key}")); None }) } fn put(&mut self, key: &str, value: &Value) -> Option { - log(&format!("Putting key: {} value: {:?}", key, value)); + log(&format!("Putting key: {key} value: {value:?}")); let val = put(pairs::Either::Proposed, key, &value.clone().into()); Some(val.into()) } @@ -57,7 +55,6 @@ impl From for Value { } } -// From` for `pairs::Value` impl From for pairs::Value { fn from(value: Value) -> Self { match value { diff --git a/crates/comrade/src/core.rs b/crates/comrade/src/core.rs deleted file mode 100644 index 135dba2..0000000 --- a/crates/comrade/src/core.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod pairs; -pub use pairs::Pairs; - -mod value; -pub use value::Value; diff --git a/crates/comrade/src/lib.rs b/crates/comrade/src/lib.rs index 6a066dc..acd5dd5 100644 --- a/crates/comrade/src/lib.rs +++ b/crates/comrade/src/lib.rs @@ -3,10 +3,6 @@ //! It requires a wasm-component plugin to run. A reference implementation is //! provided in the `comrade-component` crate. -/// Core cryptographic constructs which can be used to build components -mod core; -pub use core::{Pairs, Value}; - /// Opinionated entry API for using Comrade. /// Uses the comrade-component reference implementation by default, /// and wasm_component_layer for runtime. Either can be substituted From 9ca8903c0c687813f80e0b594702d5c29e2decdf Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Thu, 8 May 2025 10:36:43 -0300 Subject: [PATCH 011/155] WIP: Start to scaffold the main API Work in progress --- crates/comrade/Cargo.toml | 1 + crates/comrade/justfile | 4 ++++ crates/comrade/src/error.rs | 0 crates/comrade/src/lib.rs | 39 ++++++++++++++++++++++++++----------- crates/comrade/tests/mod.rs | 26 +++++++++++++++++++++++++ 5 files changed, 59 insertions(+), 11 deletions(-) create mode 100644 crates/comrade/justfile create mode 100644 crates/comrade/src/error.rs create mode 100644 crates/comrade/tests/mod.rs diff --git a/crates/comrade/Cargo.toml b/crates/comrade/Cargo.toml index 686ab54..9a785b3 100644 --- a/crates/comrade/Cargo.toml +++ b/crates/comrade/Cargo.toml @@ -8,6 +8,7 @@ readme.workspace = true license.workspace = true [dependencies] +comrade-reference.workspace = true [features] default = ["core"] diff --git a/crates/comrade/justfile b/crates/comrade/justfile new file mode 100644 index 0000000..99275fa --- /dev/null +++ b/crates/comrade/justfile @@ -0,0 +1,4 @@ +test: + # Run Cargo Component Build first + cargo component build + cargo test diff --git a/crates/comrade/src/error.rs b/crates/comrade/src/error.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/comrade/src/lib.rs b/crates/comrade/src/lib.rs index acd5dd5..1614fbc 100644 --- a/crates/comrade/src/lib.rs +++ b/crates/comrade/src/lib.rs @@ -2,24 +2,41 @@ //! //! It requires a wasm-component plugin to run. A reference implementation is //! provided in the `comrade-component` crate. +mod error; + +use comrade_reference::Pairs; /// Opinionated entry API for using Comrade. /// Uses the comrade-component reference implementation by default, /// and wasm_component_layer for runtime. Either can be substituted /// with prefered alternatives as desired. -mod api; - -pub fn add(left: u64, right: u64) -> u64 { - left + right +pub struct Comrade { + lock: C, + unlock: P, } -#[cfg(test)] -mod tests { - use super::*; +// API should be something like: +// +// let unlocked = Comrade::new(kvp_lock, kvp_unlock) +// .with_domain("/") +// .try_unlock(&unlock)?; +// +// let mut count = 0; +// +// for lock in locks { +// if let Some(Value::Success(ct)) = unlocked.try_lock(lock)? { +// count = ct; +// break; +// } +// } +// +// where the args impl Pairs + +impl Comrade { + pub fn new(lock: C, unlock: P) {} - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); + pub fn try_unlock(script: &str) { + // something like: + // vm::run(script) } } diff --git a/crates/comrade/tests/mod.rs b/crates/comrade/tests/mod.rs new file mode 100644 index 0000000..c547e18 --- /dev/null +++ b/crates/comrade/tests/mod.rs @@ -0,0 +1,26 @@ +//! Use the wasmi runtime layer + +#[test] +fn test_api_layer_instance() { + //log with timstamp + eprintln!("[TestLog] test_instantiate_instance"); + + let wasm_path = "target/wasm32-unknown-unknown/release/comrade_component.wasm"; + eprintln!("[TestLog] Looking for wasm file: {wasm_path}"); + + // API should be something like: + // + // let unlocked = Comrade::new(&unlock, Current(kvp_lock), Proposed(kvp_unlock)) + // .with_domain("/") + // .try_unlock()?; + // + // let mut count = 0; + // + // for lock in locks { + // if let Some(Value::Success(ct)) = unlocked.try_lock(lock)? { + // count = ct; + // break; + // } + // } + assert!(true, "Test not implemented"); +} From aeecb58af21ea0c69fffd5d2364862df0fe74336 Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Thu, 8 May 2025 10:55:58 -0300 Subject: [PATCH 012/155] nice clean api --- crates/comrade/Cargo.toml | 1 + crates/comrade/README.md | 3 ++ crates/comrade/src/api.rs | 1 - crates/comrade/src/error.rs | 15 +++++++ crates/comrade/src/lib.rs | 78 ++++++++++++++++++++++++++----------- crates/comrade/src/vm.rs | 7 ++++ 6 files changed, 81 insertions(+), 24 deletions(-) create mode 100644 crates/comrade/README.md delete mode 100644 crates/comrade/src/api.rs create mode 100644 crates/comrade/src/vm.rs diff --git a/crates/comrade/Cargo.toml b/crates/comrade/Cargo.toml index 9a785b3..9bd5148 100644 --- a/crates/comrade/Cargo.toml +++ b/crates/comrade/Cargo.toml @@ -9,6 +9,7 @@ license.workspace = true [dependencies] comrade-reference.workspace = true +thiserror = "2.0" [features] default = ["core"] diff --git a/crates/comrade/README.md b/crates/comrade/README.md new file mode 100644 index 0000000..60ad06f --- /dev/null +++ b/crates/comrade/README.md @@ -0,0 +1,3 @@ +# Comrade + +This crate is the main entry point for the library. diff --git a/crates/comrade/src/api.rs b/crates/comrade/src/api.rs deleted file mode 100644 index 315eb3b..0000000 --- a/crates/comrade/src/api.rs +++ /dev/null @@ -1 +0,0 @@ -//! Comrade API diff --git a/crates/comrade/src/error.rs b/crates/comrade/src/error.rs index e69de29..39e373f 100644 --- a/crates/comrade/src/error.rs +++ b/crates/comrade/src/error.rs @@ -0,0 +1,15 @@ +//! Crate errors + +/// Comrade error types +#[derive(Debug, thiserror::Error)] +pub enum Error { + // /// Error in the VM + // #[error("VM error: {0}")] + // Vm(#[from] wasmi::Error), + // /// Error in the component + // #[error("Component error: {0}")] + // Component(#[from] comrade_reference::Error), + // /// Error in the script + // #[error("Script error: {0}")] + // Script(#[from] comrade_reference::ScriptError), +} diff --git a/crates/comrade/src/lib.rs b/crates/comrade/src/lib.rs index 1614fbc..84f1ea5 100644 --- a/crates/comrade/src/lib.rs +++ b/crates/comrade/src/lib.rs @@ -1,42 +1,74 @@ //! Comrade is an execution engine for provenance log scripts. //! //! It requires a wasm-component plugin to run. A reference implementation is -//! provided in the `comrade-component` crate. +//! provided in the `comrade-component` crate which uses [comrade_reference] +//! +//! API should be something like: +//! +//! ```ignore +//! let unlocked = Comrade::new(kvp_lock, kvp_unlock) +//! .with_domain("/") +//! .try_unlock(&unlock)?; +//! +//! let mut count = 0; +//! +//! for lock in locks { +//! if let Some(Value::Success(ct)) = unlocked.try_lock(lock)? { +//! count = ct; +//! break; +//! } +//! } +//! ```` +//! +//! where the args iml [comrade_reference::Pairs] mod error; +pub use crate::error::Error; + +mod vm; +// Using the same trait out of convenience, the Pairs trait is very basic use comrade_reference::Pairs; +/// Comrade goes starts at [Initial] Stage, then goes to [Unlocked] Stage. +#[derive(Debug)] +pub struct Initial; + +/// Comrade goes starts at [Initial] Stage, then goes to [Unlocked] Stage. +#[derive(Debug)] +pub struct Unlocked; + /// Opinionated entry API for using Comrade. /// Uses the comrade-component reference implementation by default, /// and wasm_component_layer for runtime. Either can be substituted /// with prefered alternatives as desired. -pub struct Comrade { +pub struct Comrade { lock: C, unlock: P, + _stage: std::marker::PhantomData, } -// API should be something like: -// -// let unlocked = Comrade::new(kvp_lock, kvp_unlock) -// .with_domain("/") -// .try_unlock(&unlock)?; -// -// let mut count = 0; -// -// for lock in locks { -// if let Some(Value::Success(ct)) = unlocked.try_lock(lock)? { -// count = ct; -// break; -// } -// } -// -// where the args impl Pairs - impl Comrade { - pub fn new(lock: C, unlock: P) {} + pub fn new(lock: C, unlock: P) -> Self { + Comrade { + lock, + unlock, + _stage: std::marker::PhantomData, + } + } + + pub fn try_unlock(self, script: &str) -> Result, error::Error> { + vm::run(script)?; + Ok(self.into()) + } +} - pub fn try_unlock(script: &str) { - // something like: - // vm::run(script) +// from Initial to Unlocked +impl From> for Comrade { + fn from(comrade: Comrade) -> Self { + Comrade { + lock: comrade.lock, + unlock: comrade.unlock, + _stage: std::marker::PhantomData, + } } } diff --git a/crates/comrade/src/vm.rs b/crates/comrade/src/vm.rs new file mode 100644 index 0000000..2cdb3e9 --- /dev/null +++ b/crates/comrade/src/vm.rs @@ -0,0 +1,7 @@ +//! Comrade Virutal Machine runs the script using the chosen wasm component. +use crate::Error; + +/// Run the script. +pub(crate) fn run(script: &str) -> Result<(), Error> { + Ok(()) +} From 9d2f295539dae8778a43e1953cc557655024f05f Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Thu, 8 May 2025 10:57:41 -0300 Subject: [PATCH 013/155] add rustdoc comments --- crates/comrade/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/comrade/src/lib.rs b/crates/comrade/src/lib.rs index 84f1ea5..5103c11 100644 --- a/crates/comrade/src/lib.rs +++ b/crates/comrade/src/lib.rs @@ -48,6 +48,7 @@ pub struct Comrade { } impl Comrade { + /// Creates a new Comrade instance with the given lock and unlock pairs. pub fn new(lock: C, unlock: P) -> Self { Comrade { lock, @@ -56,6 +57,8 @@ impl Comrade { } } + /// Tries to unlock the comrade with the given script. + /// Will return an error if the script fails to run. pub fn try_unlock(self, script: &str) -> Result, error::Error> { vm::run(script)?; Ok(self.into()) From 023bb633d20f72d3aa3424f368122861c81ec6d6 Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Thu, 8 May 2025 11:14:23 -0300 Subject: [PATCH 014/155] scaffold virtual runtime --- crates/comrade-reference/src/lib.rs | 2 +- crates/comrade/src/lib.rs | 16 ++++++++++++++-- crates/comrade/src/vm.rs | 8 ++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/crates/comrade-reference/src/lib.rs b/crates/comrade-reference/src/lib.rs index 1dee6a5..b80e7bd 100644 --- a/crates/comrade-reference/src/lib.rs +++ b/crates/comrade-reference/src/lib.rs @@ -5,4 +5,4 @@ pub use error::ApiError; /// Context is the main entry of this crate mod context; /// Public re-exports -pub use context::{Context, Log, Pairs, Value}; +pub use context::{Context, Log, Pairs, Stack, Value}; diff --git a/crates/comrade/src/lib.rs b/crates/comrade/src/lib.rs index 5103c11..726e653 100644 --- a/crates/comrade/src/lib.rs +++ b/crates/comrade/src/lib.rs @@ -27,7 +27,7 @@ pub use crate::error::Error; mod vm; // Using the same trait out of convenience, the Pairs trait is very basic -use comrade_reference::Pairs; +use comrade_reference::{Pairs, Stack, Value}; /// Comrade goes starts at [Initial] Stage, then goes to [Unlocked] Stage. #[derive(Debug)] @@ -59,12 +59,24 @@ impl Comrade { /// Tries to unlock the comrade with the given script. /// Will return an error if the script fails to run. - pub fn try_unlock(self, script: &str) -> Result, error::Error> { + pub fn try_unlock(self, script: &str) -> Result, Error> { vm::run(script)?; Ok(self.into()) } } +// try_lock can only be called on an Unlocked Comrade +impl Comrade { + /// Tries to lock the comrade with the given script. + /// Will return an error if the script fails to run. + pub fn try_lock(&self, script: &str) -> Result, Error> { + vm::run(script)?; + // check the context retrun stack top, return the result + let res = vm::top(); + Ok(res) + } +} + // from Initial to Unlocked impl From> for Comrade { fn from(comrade: Comrade) -> Self { diff --git a/crates/comrade/src/vm.rs b/crates/comrade/src/vm.rs index 2cdb3e9..157a3d2 100644 --- a/crates/comrade/src/vm.rs +++ b/crates/comrade/src/vm.rs @@ -1,7 +1,15 @@ //! Comrade Virutal Machine runs the script using the chosen wasm component. +use comrade_reference::Value; + use crate::Error; /// Run the script. pub(crate) fn run(script: &str) -> Result<(), Error> { + todo!(); Ok(()) } + +/// Get the top value from the context return stack. +pub(crate) fn top() -> Option { + todo!() +} From c1b49bf1260cd9f43ca0e82bf1317a1fb1f3ed50 Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Thu, 8 May 2025 11:17:34 -0300 Subject: [PATCH 015/155] rename vm -> runtime because it runs the script --- crates/comrade/src/lib.rs | 9 +++++---- crates/comrade/src/{vm.rs => runtime.rs} | 0 2 files changed, 5 insertions(+), 4 deletions(-) rename crates/comrade/src/{vm.rs => runtime.rs} (100%) diff --git a/crates/comrade/src/lib.rs b/crates/comrade/src/lib.rs index 726e653..9c92558 100644 --- a/crates/comrade/src/lib.rs +++ b/crates/comrade/src/lib.rs @@ -24,7 +24,8 @@ mod error; pub use crate::error::Error; -mod vm; +/// The runtime environment for the scripts +mod runtime; // Using the same trait out of convenience, the Pairs trait is very basic use comrade_reference::{Pairs, Stack, Value}; @@ -60,7 +61,7 @@ impl Comrade { /// Tries to unlock the comrade with the given script. /// Will return an error if the script fails to run. pub fn try_unlock(self, script: &str) -> Result, Error> { - vm::run(script)?; + runtime::run(script)?; Ok(self.into()) } } @@ -70,9 +71,9 @@ impl Comrade { /// Tries to lock the comrade with the given script. /// Will return an error if the script fails to run. pub fn try_lock(&self, script: &str) -> Result, Error> { - vm::run(script)?; + runtime::run(script)?; // check the context retrun stack top, return the result - let res = vm::top(); + let res = runtime::top(); Ok(res) } } diff --git a/crates/comrade/src/vm.rs b/crates/comrade/src/runtime.rs similarity index 100% rename from crates/comrade/src/vm.rs rename to crates/comrade/src/runtime.rs From 1c8a852a39f8389ae0d792488d4db2724b58eb86 Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Thu, 8 May 2025 11:43:42 -0300 Subject: [PATCH 016/155] haul in deps for wasm runtime --- crates/comrade/Cargo.toml | 10 ++++++++++ crates/comrade/README.md | 29 +++++++++++++++++++++++++++++ crates/comrade/src/runtime.rs | 9 +++++++-- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/crates/comrade/Cargo.toml b/crates/comrade/Cargo.toml index 9bd5148..3922227 100644 --- a/crates/comrade/Cargo.toml +++ b/crates/comrade/Cargo.toml @@ -10,6 +10,16 @@ license.workspace = true [dependencies] comrade-reference.workspace = true thiserror = "2.0" +# wasm_component_layer gives us an isomorphic way to load components +# in native or the browser directly from Rust +# patch until thos lands: https://github.com/DouglasDwyer/wasm_component_layer/pull/26 +wasm_component_layer = { git = "https://github.com/DougAnderson444/wasm_component_layer.git", branch = "dep-fix" } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +wasmi_runtime_layer = "*" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +js_wasm_runtime_layer = "0.4.0" [features] default = ["core"] diff --git a/crates/comrade/README.md b/crates/comrade/README.md index 60ad06f..a0af22b 100644 --- a/crates/comrade/README.md +++ b/crates/comrade/README.md @@ -1,3 +1,32 @@ # Comrade +A flexible, extensible, and composable way to run provenance log scripts natively or in the browser. + This crate is the main entry point for the library. + + +## Use + +The main API aims to be super simple: + +```ignore +let unlocked = Comrade::new(kvp_lock, kvp_unlock) + .try_unlock(&unlock)?; + +let mut count = 0; + +for lock in locks { + if let Some(Value::Success(ct)) = unlocked.try_lock(lock)? { + count = ct; + break; + } +} +``` + +## Wasm Component Layer + +This reference implementation makes opinions about what dependencies to use, and which wasm runtime to use to run the components, but it should be noted that you can swap in your own runtime for both the component and the wasm runtime. + +`wasm_component_layer` crate gives us an isomorphic way to load components in native or the browser directly from Rust. We use a patch until [this dependency fix lands](https://github.com/DouglasDwyer/wasm_component_layer/pull/26). + +We chose the `wasmi_runtime` for native, and `js_wasm_runtime_layer` for the browser. diff --git a/crates/comrade/src/runtime.rs b/crates/comrade/src/runtime.rs index 157a3d2..e8ab418 100644 --- a/crates/comrade/src/runtime.rs +++ b/crates/comrade/src/runtime.rs @@ -1,7 +1,12 @@ -//! Comrade Virutal Machine runs the script using the chosen wasm component. +//! Comrade Virutal runtime which manages running of the script using the chosen wasm component. +use crate::Error; use comrade_reference::Value; -use crate::Error; +#[cfg(not(target_arch = "wasm32"))] +use wasmi_runtime_layer as runtime_layer; + +#[cfg(target_arch = "wasm32")] +use js_wasm_runtime_layer as runtime_layer; /// Run the script. pub(crate) fn run(script: &str) -> Result<(), Error> { From 5ff10c8b72c751d3ab24e93fd55c65a260999b92 Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Fri, 9 May 2025 08:15:15 -0300 Subject: [PATCH 017/155] makeruntimes optional and extensible --- crates/comrade/Cargo.toml | 6 ++---- crates/comrade/src/lib.rs | 2 +- .../src/{runtime.rs => runtime/layer/mod.rs} | 0 crates/comrade/src/runtime/mod.rs | 17 +++++++++++++++++ 4 files changed, 20 insertions(+), 5 deletions(-) rename crates/comrade/src/{runtime.rs => runtime/layer/mod.rs} (100%) create mode 100644 crates/comrade/src/runtime/mod.rs diff --git a/crates/comrade/Cargo.toml b/crates/comrade/Cargo.toml index 3922227..39ba0af 100644 --- a/crates/comrade/Cargo.toml +++ b/crates/comrade/Cargo.toml @@ -13,7 +13,7 @@ thiserror = "2.0" # wasm_component_layer gives us an isomorphic way to load components # in native or the browser directly from Rust # patch until thos lands: https://github.com/DouglasDwyer/wasm_component_layer/pull/26 -wasm_component_layer = { git = "https://github.com/DougAnderson444/wasm_component_layer.git", branch = "dep-fix" } +wasm_component_layer = { git = "https://github.com/DougAnderson444/wasm_component_layer.git", branch = "dep-fix", optional = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] wasmi_runtime_layer = "*" @@ -22,9 +22,7 @@ wasmi_runtime_layer = "*" js_wasm_runtime_layer = "0.4.0" [features] -default = ["core"] -# enable core features when you want to build a wasm component -core = [] +default = ["wasm_component_layer"] [lints] workspace = true diff --git a/crates/comrade/src/lib.rs b/crates/comrade/src/lib.rs index 9c92558..df05bf4 100644 --- a/crates/comrade/src/lib.rs +++ b/crates/comrade/src/lib.rs @@ -28,7 +28,7 @@ pub use crate::error::Error; mod runtime; // Using the same trait out of convenience, the Pairs trait is very basic -use comrade_reference::{Pairs, Stack, Value}; +use comrade_reference::{Pairs, Value}; /// Comrade goes starts at [Initial] Stage, then goes to [Unlocked] Stage. #[derive(Debug)] diff --git a/crates/comrade/src/runtime.rs b/crates/comrade/src/runtime/layer/mod.rs similarity index 100% rename from crates/comrade/src/runtime.rs rename to crates/comrade/src/runtime/layer/mod.rs diff --git a/crates/comrade/src/runtime/mod.rs b/crates/comrade/src/runtime/mod.rs new file mode 100644 index 0000000..b7d56dd --- /dev/null +++ b/crates/comrade/src/runtime/mod.rs @@ -0,0 +1,17 @@ +//! Comrade Virutal runtime which manages running of the script using the chosen wasm component. +use crate::Error; +use comrade_reference::Value; + +/// Feature `wasm_component_layer` enables the use of a wasm component layer +// #[cfg(feature = "wasm_component_layer")] +mod layer; +// #[cfg(feature = "wasm_component_layer")] +pub(crate) use layer::{run, top}; + +/// Each runtime feature must implement the `Runtime` trait, run and top +pub trait Runtime { + /// Run the script. + fn run(script: &str) -> Result<(), Error>; + /// Get the top value from the context return stack. + fn top() -> Option; +} From a0dbb774578c8608a47879e9153dea393466f75a Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Fri, 9 May 2025 08:39:11 -0300 Subject: [PATCH 018/155] add runtime trait and Runner bounds --- crates/comrade/src/lib.rs | 10 +++++-- crates/comrade/src/runtime/layer/mod.rs | 37 +++++++++++++++++++------ crates/comrade/src/runtime/mod.rs | 14 +++++++--- 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/crates/comrade/src/lib.rs b/crates/comrade/src/lib.rs index df05bf4..6e8d954 100644 --- a/crates/comrade/src/lib.rs +++ b/crates/comrade/src/lib.rs @@ -29,6 +29,7 @@ mod runtime; // Using the same trait out of convenience, the Pairs trait is very basic use comrade_reference::{Pairs, Value}; +use runtime::Runtime as _; /// Comrade goes starts at [Initial] Stage, then goes to [Unlocked] Stage. #[derive(Debug)] @@ -45,6 +46,7 @@ pub struct Unlocked; pub struct Comrade { lock: C, unlock: P, + runner: runtime::Runner, _stage: std::marker::PhantomData, } @@ -54,6 +56,7 @@ impl Comrade { Comrade { lock, unlock, + runner: runtime::Runner::default(), _stage: std::marker::PhantomData, } } @@ -61,7 +64,7 @@ impl Comrade { /// Tries to unlock the comrade with the given script. /// Will return an error if the script fails to run. pub fn try_unlock(self, script: &str) -> Result, Error> { - runtime::run(script)?; + runtime::Runner.run(script)?; Ok(self.into()) } } @@ -71,9 +74,9 @@ impl Comrade { /// Tries to lock the comrade with the given script. /// Will return an error if the script fails to run. pub fn try_lock(&self, script: &str) -> Result, Error> { - runtime::run(script)?; + runtime::Runner.run(script)?; // check the context retrun stack top, return the result - let res = runtime::top(); + let res = runtime::Runner.top(); Ok(res) } } @@ -84,6 +87,7 @@ impl From> for Comrade Result<(), Error> { - todo!(); - Ok(()) -} +// /// Run the script. +// pub(crate) fn run(script: &str) -> Result<(), Error> { +// todo!(); +// Ok(()) +// } +// +// /// Get the top value from the context return stack. +// pub(crate) fn top() -> Option { +// todo!() +// } + +#[derive(Debug, Default)] +pub(crate) struct Runner; + +impl Runtime for Runner { + fn run(&self, script: &str) -> Result<(), Error> { + todo!() + } -/// Get the top value from the context return stack. -pub(crate) fn top() -> Option { - todo!() + fn top(&self) -> Option { + todo!() + } } diff --git a/crates/comrade/src/runtime/mod.rs b/crates/comrade/src/runtime/mod.rs index b7d56dd..22c7af2 100644 --- a/crates/comrade/src/runtime/mod.rs +++ b/crates/comrade/src/runtime/mod.rs @@ -3,15 +3,21 @@ use crate::Error; use comrade_reference::Value; /// Feature `wasm_component_layer` enables the use of a wasm component layer +// Commented out for now, so we get rust-analyzer to not complain about unused code // #[cfg(feature = "wasm_component_layer")] mod layer; // #[cfg(feature = "wasm_component_layer")] -pub(crate) use layer::{run, top}; +pub(crate) use layer::Runner; + +// Other wasm runtimes can be used: +// mod wasm_time; +// mod wasm_i; +// mod wasmer; /// Each runtime feature must implement the `Runtime` trait, run and top -pub trait Runtime { +pub trait Runtime: Default { /// Run the script. - fn run(script: &str) -> Result<(), Error>; + fn run(&self, script: &str) -> Result<(), Error>; /// Get the top value from the context return stack. - fn top() -> Option; + fn top(&self) -> Option; } From 1a7edd0eebb1c99408dd2d082d385ba8f2b202dd Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Fri, 9 May 2025 09:34:34 -0300 Subject: [PATCH 019/155] fix typo --- crates/comrade-component/justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/comrade-component/justfile b/crates/comrade-component/justfile index db8e636..2404b83 100644 --- a/crates/comrade-component/justfile +++ b/crates/comrade-component/justfile @@ -1,7 +1,7 @@ # See https://just.systems for more information. build: - # Build the component with cargo-compoenent + # Build the component with cargo-component RUSTFLAGS='--cfg getrandom_backend="custom"' cargo component build --target wasm32-unknown-unknown --release test: build From a35d0b447dd6239882cfbd5051257fecf012b4f9 Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Fri, 9 May 2025 10:10:56 -0300 Subject: [PATCH 020/155] scaffold build bytes and justfile recipes --- crates/comrade/Cargo.toml | 2 + crates/comrade/README.md | 11 +++++ crates/comrade/justfile | 2 +- crates/comrade/src/runtime/layer/mod.rs | 64 +++++++++++++++++++------ justfile | 5 ++ 5 files changed, 69 insertions(+), 15 deletions(-) create mode 100644 justfile diff --git a/crates/comrade/Cargo.toml b/crates/comrade/Cargo.toml index 39ba0af..35c6dd2 100644 --- a/crates/comrade/Cargo.toml +++ b/crates/comrade/Cargo.toml @@ -14,6 +14,8 @@ thiserror = "2.0" # in native or the browser directly from Rust # patch until thos lands: https://github.com/DouglasDwyer/wasm_component_layer/pull/26 wasm_component_layer = { git = "https://github.com/DougAnderson444/wasm_component_layer.git", branch = "dep-fix", optional = true } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", features = ["std"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] wasmi_runtime_layer = "*" diff --git a/crates/comrade/README.md b/crates/comrade/README.md index a0af22b..e258e23 100644 --- a/crates/comrade/README.md +++ b/crates/comrade/README.md @@ -23,6 +23,17 @@ for lock in locks { } ``` +## Tests + +To run the test: + +```sh +# see http://just.systems/ fr more details +just test +``` + +This will ensure the default component is built and available for the default wasm runtime. + ## Wasm Component Layer This reference implementation makes opinions about what dependencies to use, and which wasm runtime to use to run the components, but it should be noted that you can swap in your own runtime for both the component and the wasm runtime. diff --git a/crates/comrade/justfile b/crates/comrade/justfile index 99275fa..b91bf8a 100644 --- a/crates/comrade/justfile +++ b/crates/comrade/justfile @@ -1,4 +1,4 @@ test: # Run Cargo Component Build first - cargo component build + just -f ../../justfile build cargo test diff --git a/crates/comrade/src/runtime/layer/mod.rs b/crates/comrade/src/runtime/layer/mod.rs index be89021..5447b7d 100644 --- a/crates/comrade/src/runtime/layer/mod.rs +++ b/crates/comrade/src/runtime/layer/mod.rs @@ -5,6 +5,7 @@ use super::Runtime; use crate::Error; use comrade_reference::Value; +use wasm_component_layer::{Component, Engine, Linker, Store}; // wasmi layer for native targets #[cfg(not(target_arch = "wasm32"))] @@ -14,26 +15,61 @@ use wasmi_runtime_layer as runtime_layer; #[cfg(target_arch = "wasm32")] use js_wasm_runtime_layer as runtime_layer; -// /// Run the script. -// pub(crate) fn run(script: &str) -> Result<(), Error> { -// todo!(); -// Ok(()) -// } -// -// /// Get the top value from the context return stack. -// pub(crate) fn top() -> Option { -// todo!() -// } - -#[derive(Debug, Default)] +#[derive(Clone, Default, Debug)] +struct Data; + +#[derive(Debug)] pub(crate) struct Runner; +impl Default for Runner { + /// Create a new runner. + fn default() -> Self { + // target/wasm32-unknown-unknown/release/comrade_component.wasm + let bytes: &[u8] = include_bytes!( + "../../../../../target/wasm32-unknown-unknown/release/comrade_component.wasm" + ); + + let data = Data::default(); + + // Create a new engine for instantiating a component. + let engine = Engine::new(runtime_layer::Engine::default()); + + // Create a store for managing WASM data and any custom user-defined state. + let mut store = Store::new(&engine, data); + + tracing::debug!("Created store, loading bytes.",); + // Parse the component bytes and load its imports and exports. + let component = Component::new(&engine, &bytes).unwrap(); + + tracing::debug!("Loaded bytes"); + + // Create a linker that will be used to resolve the component's imports, if any. + let mut linker = Linker::default(); + + Self + } +} + impl Runtime for Runner { fn run(&self, script: &str) -> Result<(), Error> { - todo!() + Ok(()) } fn top(&self) -> Option { - todo!() + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::runtime::Runtime; + use comrade_reference::Value; + + #[test] + fn test_runner() { + let runner = Runner::default(); + assert_eq!(runner.top(), None); + assert!(runner.run("test").is_ok()); } } diff --git a/justfile b/justfile new file mode 100644 index 0000000..4309db5 --- /dev/null +++ b/justfile @@ -0,0 +1,5 @@ +# Build Script +build: + # Calls the just command in crates/comrade-component (just build): + just crates/comrade-component/build + From ddec8ddf73c514f3e55f8d4fe9c8d7576d876839 Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Fri, 9 May 2025 11:42:06 -0300 Subject: [PATCH 021/155] set wasmi_runtime_layer to v0.40 --- crates/comrade/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/comrade/Cargo.toml b/crates/comrade/Cargo.toml index 35c6dd2..aef54e9 100644 --- a/crates/comrade/Cargo.toml +++ b/crates/comrade/Cargo.toml @@ -18,7 +18,7 @@ tracing = { version = "0.1", features = ["log"] } tracing-subscriber = { version = "0.3", features = ["std"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -wasmi_runtime_layer = "*" +wasmi_runtime_layer = "=0.40.0" # pin to 0.40 to match wasm_runtime_layer 0.4.2 [target.'cfg(target_arch = "wasm32")'.dependencies] js_wasm_runtime_layer = "0.4.0" From a8b415fc9bd072b0b8e1d64f48586ac42bdad0f0 Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Fri, 9 May 2025 12:38:07 -0300 Subject: [PATCH 022/155] add wasm32 build, test, just recipes --- Cargo.toml | 4 ++++ crates/comrade/Cargo.toml | 8 +++++++ crates/comrade/justfile | 8 ++++++- crates/comrade/src/lib.rs | 9 ++++--- crates/comrade/src/random.rs | 46 ++++++++++++++++++++++++++++++++++++ crates/comrade/tests/mod.rs | 26 -------------------- crates/comrade/tests/web.rs | 20 ++++++++++++++++ 7 files changed, 91 insertions(+), 30 deletions(-) create mode 100644 crates/comrade/src/random.rs delete mode 100644 crates/comrade/tests/mod.rs create mode 100644 crates/comrade/tests/web.rs diff --git a/Cargo.toml b/Cargo.toml index 28f409c..e8af935 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,6 +84,10 @@ tracing = "0.1.41" tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } unsigned-varint = { version = "0.8.0", features = ["std"] } +[workspace.dependencies.getrandom] +version = "0.2.15" +features = ["js"] + [profile.bench] opt-level = 3 debug = false diff --git a/crates/comrade/Cargo.toml b/crates/comrade/Cargo.toml index aef54e9..6fc1dfc 100644 --- a/crates/comrade/Cargo.toml +++ b/crates/comrade/Cargo.toml @@ -16,12 +16,20 @@ thiserror = "2.0" wasm_component_layer = { git = "https://github.com/DougAnderson444/wasm_component_layer.git", branch = "dep-fix", optional = true } tracing = { version = "0.1", features = ["log"] } tracing-subscriber = { version = "0.3", features = ["std"] } +getrandom = "0.3" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] wasmi_runtime_layer = "=0.40.0" # pin to 0.40 to match wasm_runtime_layer 0.4.2 [target.'cfg(target_arch = "wasm32")'.dependencies] js_wasm_runtime_layer = "0.4.0" +wasm-bindgen = "0.2" +getrandom = { version = "0.3", features = ["wasm_js"] } +getrandom_v02 = { package = "getrandom", version = "0.2.15", features = ["js"] } +web-sys = { version = "0.3", features = ["Window", "Crypto"] } + +[dev-dependencies] +wasm-bindgen-test = "0.3" [features] default = ["wasm_component_layer"] diff --git a/crates/comrade/justfile b/crates/comrade/justfile index b91bf8a..ac5b11b 100644 --- a/crates/comrade/justfile +++ b/crates/comrade/justfile @@ -1,4 +1,10 @@ -test: +build: # Run Cargo Component Build first just -f ../../justfile build + +test-wasm32: + # wasm32 test for the browser + RUSTFLAGS='--cfg getrandom_backend="wasm_js"' wasm-pack test --headless --chrome --all-features + +test: build test-wasm32 cargo test diff --git a/crates/comrade/src/lib.rs b/crates/comrade/src/lib.rs index 6e8d954..4123dec 100644 --- a/crates/comrade/src/lib.rs +++ b/crates/comrade/src/lib.rs @@ -27,6 +27,9 @@ pub use crate::error::Error; /// The runtime environment for the scripts mod runtime; +/// Polyfills required to ensure getrandom works in wasm32 target for v0.3 +mod random; + // Using the same trait out of convenience, the Pairs trait is very basic use comrade_reference::{Pairs, Value}; use runtime::Runtime as _; @@ -64,7 +67,7 @@ impl Comrade { /// Tries to unlock the comrade with the given script. /// Will return an error if the script fails to run. pub fn try_unlock(self, script: &str) -> Result, Error> { - runtime::Runner.run(script)?; + self.runner.run(script)?; Ok(self.into()) } } @@ -74,9 +77,9 @@ impl Comrade { /// Tries to lock the comrade with the given script. /// Will return an error if the script fails to run. pub fn try_lock(&self, script: &str) -> Result, Error> { - runtime::Runner.run(script)?; + self.runner.run(script)?; // check the context retrun stack top, return the result - let res = runtime::Runner.top(); + let res = self.runner.top(); Ok(res) } } diff --git a/crates/comrade/src/random.rs b/crates/comrade/src/random.rs new file mode 100644 index 0000000..fbc17b5 --- /dev/null +++ b/crates/comrade/src/random.rs @@ -0,0 +1,46 @@ +//! Implements random for this wasm32-unknown-unknown target. +//! +//! Since we was wasm but ARE NOT int he browser, we need to use the +//! custom function to generate random bytes. +//! +//! However, since getrandom v0.2 and v0.3 are both present in dependencies, +//! and have different APIs, we need to implement both of them. +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; +#[cfg(target_arch = "wasm32")] +use web_sys::window; + +/// Browser getrandom v0.3 requires RUSTFLAGS='--cfg getrandom_backend="wasm_js"' +/// and this function. +/// +/// # Safety +/// This function is unsafe because it dereferences raw pointers. +#[unsafe(no_mangle)] +pub unsafe extern "Rust" fn __getrandom_v03_custom( + dest: *mut u8, + len: usize, +) -> Result<(), getrandom::Error> { + // Safety: This is safe because we are using the imported_random function + // which is safe to use in this context. + let slice = unsafe { std::slice::from_raw_parts_mut(dest, len) }; + + // if wasm32, then use Crypto::getRandomValues(&mut buffer); + // if not wasm32, just use getrandom::fill(&mut buffer); + #[cfg(target_arch = "wasm32")] + { + bindgen_byte(slice).map_err(|_| getrandom::Error::UNSUPPORTED)?; + Ok(()) + } + #[cfg(not(target_arch = "wasm32"))] + { + getrandom::fill(slice) + } +} + +#[cfg(target_arch = "wasm32")] +fn bindgen_byte(buffer: &mut [u8]) -> Result<(), JsValue> { + let window = window().ok_or_else(|| JsValue::from_str("window not available"))?; + let crypto = window.crypto()?; + crypto.get_random_values_with_u8_array(buffer)?; + Ok(()) +} diff --git a/crates/comrade/tests/mod.rs b/crates/comrade/tests/mod.rs deleted file mode 100644 index c547e18..0000000 --- a/crates/comrade/tests/mod.rs +++ /dev/null @@ -1,26 +0,0 @@ -//! Use the wasmi runtime layer - -#[test] -fn test_api_layer_instance() { - //log with timstamp - eprintln!("[TestLog] test_instantiate_instance"); - - let wasm_path = "target/wasm32-unknown-unknown/release/comrade_component.wasm"; - eprintln!("[TestLog] Looking for wasm file: {wasm_path}"); - - // API should be something like: - // - // let unlocked = Comrade::new(&unlock, Current(kvp_lock), Proposed(kvp_unlock)) - // .with_domain("/") - // .try_unlock()?; - // - // let mut count = 0; - // - // for lock in locks { - // if let Some(Value::Success(ct)) = unlocked.try_lock(lock)? { - // count = ct; - // break; - // } - // } - assert!(true, "Test not implemented"); -} diff --git a/crates/comrade/tests/web.rs b/crates/comrade/tests/web.rs new file mode 100644 index 0000000..0f0ccb2 --- /dev/null +++ b/crates/comrade/tests/web.rs @@ -0,0 +1,20 @@ +//! Test to ensure that the crate runs in wasm32 target +#![cfg(target_arch = "wasm32")] + +use wasm_bindgen_test::wasm_bindgen_test_configure; +use wasm_bindgen_test::*; + +wasm_bindgen_test_configure!(run_in_browser); + +// Test which checks autotrait implementation for all types +fn is_normal() {} + +// a test to ensure this crate runs in wasm32 target +#[wasm_bindgen_test] +async fn test_wasm() { + // This is a dummy test to ensure that the crate runs in wasm32 target + // and that the test framework is working correctly. + struct Current; + struct Proposed; + is_normal::>(); +} From 329359e4f4a415fdfe2c0f9fe9e6418e7672a82d Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Fri, 9 May 2025 12:43:59 -0300 Subject: [PATCH 023/155] random module is for wasm32 only --- crates/comrade/Cargo.toml | 1 + crates/comrade/src/lib.rs | 1 + crates/comrade/src/random.rs | 18 +++--------------- crates/comrade/tests/web.rs | 5 ++--- 4 files changed, 7 insertions(+), 18 deletions(-) diff --git a/crates/comrade/Cargo.toml b/crates/comrade/Cargo.toml index 6fc1dfc..f19ebfa 100644 --- a/crates/comrade/Cargo.toml +++ b/crates/comrade/Cargo.toml @@ -25,6 +25,7 @@ wasmi_runtime_layer = "=0.40.0" # pin to 0.40 to match wasm_runtime_layer 0.4.2 js_wasm_runtime_layer = "0.4.0" wasm-bindgen = "0.2" getrandom = { version = "0.3", features = ["wasm_js"] } +# We have netsed deps that use v0.2, so we need to ensure the feature is flagged here getrandom_v02 = { package = "getrandom", version = "0.2.15", features = ["js"] } web-sys = { version = "0.3", features = ["Window", "Crypto"] } diff --git a/crates/comrade/src/lib.rs b/crates/comrade/src/lib.rs index 4123dec..8471647 100644 --- a/crates/comrade/src/lib.rs +++ b/crates/comrade/src/lib.rs @@ -28,6 +28,7 @@ pub use crate::error::Error; mod runtime; /// Polyfills required to ensure getrandom works in wasm32 target for v0.3 +#[cfg(target_arch = "wasm32")] mod random; // Using the same trait out of convenience, the Pairs trait is very basic diff --git a/crates/comrade/src/random.rs b/crates/comrade/src/random.rs index fbc17b5..5cc0d37 100644 --- a/crates/comrade/src/random.rs +++ b/crates/comrade/src/random.rs @@ -5,9 +5,8 @@ //! //! However, since getrandom v0.2 and v0.3 are both present in dependencies, //! and have different APIs, we need to implement both of them. -#[cfg(target_arch = "wasm32")] +#![cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; -#[cfg(target_arch = "wasm32")] use web_sys::window; /// Browser getrandom v0.3 requires RUSTFLAGS='--cfg getrandom_backend="wasm_js"' @@ -23,21 +22,10 @@ pub unsafe extern "Rust" fn __getrandom_v03_custom( // Safety: This is safe because we are using the imported_random function // which is safe to use in this context. let slice = unsafe { std::slice::from_raw_parts_mut(dest, len) }; - - // if wasm32, then use Crypto::getRandomValues(&mut buffer); - // if not wasm32, just use getrandom::fill(&mut buffer); - #[cfg(target_arch = "wasm32")] - { - bindgen_byte(slice).map_err(|_| getrandom::Error::UNSUPPORTED)?; - Ok(()) - } - #[cfg(not(target_arch = "wasm32"))] - { - getrandom::fill(slice) - } + bindgen_byte(slice).map_err(|_| getrandom::Error::UNSUPPORTED)?; + Ok(()) } -#[cfg(target_arch = "wasm32")] fn bindgen_byte(buffer: &mut [u8]) -> Result<(), JsValue> { let window = window().ok_or_else(|| JsValue::from_str("window not available"))?; let crypto = window.crypto()?; diff --git a/crates/comrade/tests/web.rs b/crates/comrade/tests/web.rs index 0f0ccb2..4e98582 100644 --- a/crates/comrade/tests/web.rs +++ b/crates/comrade/tests/web.rs @@ -9,11 +9,10 @@ wasm_bindgen_test_configure!(run_in_browser); // Test which checks autotrait implementation for all types fn is_normal() {} -// a test to ensure this crate runs in wasm32 target #[wasm_bindgen_test] -async fn test_wasm() { +async fn test_wasm_autotraits() { // This is a dummy test to ensure that the crate runs in wasm32 target - // and that the test framework is working correctly. + // and that the autotrait implementation is what is expected. struct Current; struct Proposed; is_normal::>(); From d589f0d123c9c1363ed3b3afcd4d7464110928bf Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Fri, 9 May 2025 13:54:50 -0300 Subject: [PATCH 024/155] add layer definitions --- .../comrade/src/runtime/layer/definitions.rs | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 crates/comrade/src/runtime/layer/definitions.rs diff --git a/crates/comrade/src/runtime/layer/definitions.rs b/crates/comrade/src/runtime/layer/definitions.rs new file mode 100644 index 0000000..7420779 --- /dev/null +++ b/crates/comrade/src/runtime/layer/definitions.rs @@ -0,0 +1,191 @@ +use crate::Pairs; +use std::collections::HashMap; +use wasm_component_layer::*; + +pub fn list_data() -> ValueType { + ValueType::List(ListType::new(ValueType::U8)) +} + +pub fn binary_rec_ty() -> RecordType { + RecordType::new( + None, + vec![("data", list_data()), ("hint", ValueType::String)], + ) + .unwrap() +} + +pub fn str_rec_ty() -> RecordType { + RecordType::new( + None, + vec![("data", ValueType::String), ("hint", ValueType::String)], + ) + .unwrap() +} + +/// Vlaue variant type is either, binary, str, success(u32), or failure(String) +pub fn value_variant() -> VariantType { + VariantType::new( + None, + vec![ + VariantCase::new("bin", Some(ValueType::Record(binary_rec_ty()))), + VariantCase::new("str", Some(ValueType::Record(str_rec_ty()))), + VariantCase::new("success", Some(ValueType::U32)), + VariantCase::new("failure", Some(ValueType::String)), + ], + ) + .unwrap() +} + +/// Represents either: a current or proposed +pub fn either_enum() -> EnumType { + EnumType::new(None, vec!["current", "proposed"]).unwrap() +} + +pub fn bin_variant(data: Vec, hint: String) -> Value { + Value::Variant( + Variant::new( + value_variant(), + 0, + Some(Value::Record( + Record::new( + binary_rec_ty(), + vec![ + ( + "data", + Value::List( + List::new( + ListType::new(ValueType::U8), + data.iter().map(|b| Value::U8(*b)).collect::>(), + ) + .unwrap(), + ), + ), + ("hint", Value::String(hint.into())), + ], + ) + .unwrap(), + )), + ) + .unwrap(), + ) +} + +pub fn str_variant(data: String, hint: String) -> Value { + Value::Variant( + Variant::new( + value_variant(), + 1, + Some(Value::Record( + Record::new( + str_rec_ty(), + vec![ + ("data", Value::String(data.into())), + ("hint", Value::String(hint.into())), + ], + ) + .unwrap(), + )), + ) + .unwrap(), + ) +} + +pub fn failure_variant(msg: String) -> Value { + Value::Variant(Variant::new(value_variant(), 3, Some(Value::String(msg.into()))).unwrap()) +} + +/// Success variant +pub fn success_variant(code: u32) -> Value { + Value::Variant(Variant::new(value_variant(), 2, Some(Value::U32(code))).unwrap()) +} + +#[derive(Clone, Default, Debug)] +pub struct ContextPairs { + pairs: HashMap, +} + +impl Pairs for ContextPairs { + fn get(&self, key: &str) -> Option { + self.pairs.get(key).cloned() + } + + fn put(&mut self, key: &str, value: &crate::Value) -> Option { + self.pairs.insert(key.to_string(), value.clone()) + } +} + +/// From crate::Value to wasm_component_layer::Value +pub fn into_comp_value(value: crate::Value) -> Result { + match value { + crate::Value::Bin { hint, data } => { + // Create the record first + let record = Record::new( + binary_rec_ty(), + vec![ + ( + "data", + Value::List( + List::new( + ListType::new(ValueType::U8), + data.iter().map(|b| Value::U8(*b)).collect::>(), + ) + .unwrap(), + ), + ), + ("hint", Value::String(hint.into())), + ], + ) + .unwrap(); + + // Then wrap it in the variant (bin is case 0) + Ok(Value::Variant( + Variant::new(value_variant(), 0, Some(Value::Record(record))).unwrap(), + )) + } + crate::Value::Str { hint, data } => { + // Create the record first + let record = Record::new( + str_rec_ty(), + vec![ + ("data", Value::String(data.into())), + ("hint", Value::String(hint.into())), + ], + ) + .unwrap(); + + // Then wrap it in the variant (str is case 1) + Ok(Value::Variant( + Variant::new(value_variant(), 1, Some(Value::Record(record))).unwrap(), + )) + } + _ => Err(format!( + "Cannot convert {:?} to wasm_component_layer::Value", + value + )), + } +} + +/// Convert from wasm_component_layer::Value to crate::Value +pub fn into_core_value(value: wasm_component_layer::Value) -> Result { + match value { + wasm_component_layer::Value::Record(record) => { + if let Some(Value::String(hint)) = record.field("hint") { + if let Some(Value::List(list)) = record.field("data") { + let data: Vec = list + .iter() + .map(|v| match v { + Value::U8(b) => Ok(b), + _ => Err(format!("Expected U8, found {:?}", v)), + }) + .collect::, String>>()?; + return Ok(crate::Value::Bin { + hint: hint.to_string(), + data, + }); + } + } + Err(format!("Invalid record: {:?}", record)) + } + _ => Err(format!("Cannot convert {:?} to crate::Value", value)), + } +} From a1b21a1b05d567b4411b0735801b6eba4674a1dd Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Fri, 9 May 2025 13:55:18 -0300 Subject: [PATCH 025/155] use Pairable supertrait --- crates/comrade-reference/src/context.rs | 2 +- crates/comrade-reference/src/context/pairs.rs | 4 + crates/comrade-reference/src/lib.rs | 2 +- crates/comrade/src/lib.rs | 30 +-- crates/comrade/src/runtime/layer/mod.rs | 215 ++++++++++++++++-- crates/comrade/src/runtime/mod.rs | 2 +- crates/comrade/tests/web.rs | 24 +- 7 files changed, 242 insertions(+), 37 deletions(-) diff --git a/crates/comrade-reference/src/context.rs b/crates/comrade-reference/src/context.rs index 1494110..47077ca 100644 --- a/crates/comrade-reference/src/context.rs +++ b/crates/comrade-reference/src/context.rs @@ -2,7 +2,7 @@ //! is evaluated mod pairs; -pub use pairs::Pairs; +pub use pairs::{Pairable, Pairs}; mod value; pub use value::Value; diff --git a/crates/comrade-reference/src/context/pairs.rs b/crates/comrade-reference/src/context/pairs.rs index a0c63f0..7dc5ee7 100644 --- a/crates/comrade-reference/src/context/pairs.rs +++ b/crates/comrade-reference/src/context/pairs.rs @@ -1,6 +1,10 @@ use super::value::Value; use std::fmt::Debug; +/// Trait Pairable is: [Pairs], [Send], [Sync], [Clone], [Debug] +pub trait Pairable: Pairs + Send + Sync + 'static {} +impl Pairable for P {} + /// Trait to a key-value storage mechanism pub trait Pairs: Debug { /// get a value associated with the key diff --git a/crates/comrade-reference/src/lib.rs b/crates/comrade-reference/src/lib.rs index b80e7bd..27e8404 100644 --- a/crates/comrade-reference/src/lib.rs +++ b/crates/comrade-reference/src/lib.rs @@ -5,4 +5,4 @@ pub use error::ApiError; /// Context is the main entry of this crate mod context; /// Public re-exports -pub use context::{Context, Log, Pairs, Stack, Value}; +pub use context::{Context, Log, Pairable, Pairs, Stack, Value}; diff --git a/crates/comrade/src/lib.rs b/crates/comrade/src/lib.rs index 8471647..f9b922f 100644 --- a/crates/comrade/src/lib.rs +++ b/crates/comrade/src/lib.rs @@ -32,7 +32,7 @@ mod runtime; mod random; // Using the same trait out of convenience, the Pairs trait is very basic -use comrade_reference::{Pairs, Value}; +use comrade_reference::{Pairable, Pairs, Value}; use runtime::Runtime as _; /// Comrade goes starts at [Initial] Stage, then goes to [Unlocked] Stage. @@ -47,20 +47,22 @@ pub struct Unlocked; /// Uses the comrade-component reference implementation by default, /// and wasm_component_layer for runtime. Either can be substituted /// with prefered alternatives as desired. -pub struct Comrade { - lock: C, - unlock: P, - runner: runtime::Runner, +pub struct Comrade { + // /// The key-value pairs asociated with the lock + // kvp_lock: C, + // /// The key-value pairs asociated with the unlock + // kvp_unlock: P, + runner: runtime::Runner, _stage: std::marker::PhantomData, } -impl Comrade { +impl Comrade { /// Creates a new Comrade instance with the given lock and unlock pairs. - pub fn new(lock: C, unlock: P) -> Self { + pub fn new(kvp_lock: C, kvp_unlock: P) -> Self { Comrade { - lock, - unlock, - runner: runtime::Runner::default(), + runner: runtime::Runner::new(kvp_lock, kvp_unlock), + // kvp_lock, + // kvp_unlock, _stage: std::marker::PhantomData, } } @@ -74,7 +76,7 @@ impl Comrade { } // try_lock can only be called on an Unlocked Comrade -impl Comrade { +impl Comrade { /// Tries to lock the comrade with the given script. /// Will return an error if the script fails to run. pub fn try_lock(&self, script: &str) -> Result, Error> { @@ -86,11 +88,11 @@ impl Comrade { } // from Initial to Unlocked -impl From> for Comrade { +impl From> for Comrade { fn from(comrade: Comrade) -> Self { Comrade { - lock: comrade.lock, - unlock: comrade.unlock, + // kvp_lock: comrade.kvp_lock, + // kvp_unlock: comrade.kvp_unlock, runner: comrade.runner, _stage: std::marker::PhantomData, } diff --git a/crates/comrade/src/runtime/layer/mod.rs b/crates/comrade/src/runtime/layer/mod.rs index 5447b7d..9314007 100644 --- a/crates/comrade/src/runtime/layer/mod.rs +++ b/crates/comrade/src/runtime/layer/mod.rs @@ -2,10 +2,18 @@ //! //! This crate is great because it gives us the ability to isomorphically run in the browser with //! wasm_bindgen but without javascript, and also natively. +mod definitions; + +use definitions::{ + either_enum, failure_variant, into_comp_value, into_core_value, success_variant, value_variant, +}; + use super::Runtime; use crate::Error; -use comrade_reference::Value; -use wasm_component_layer::{Component, Engine, Linker, Store}; +use comrade_reference::{Pairable, Pairs, Value}; +use wasm_component_layer::{ + Component, Engine, Func, FuncType, Instance, Linker, OptionType, OptionValue, Store, ValueType, +}; // wasmi layer for native targets #[cfg(not(target_arch = "wasm32"))] @@ -15,27 +23,47 @@ use wasmi_runtime_layer as runtime_layer; #[cfg(target_arch = "wasm32")] use js_wasm_runtime_layer as runtime_layer; -#[derive(Clone, Default, Debug)] -struct Data; +// C and P need to be bound by Send + Sync +pub(crate) struct Runner +where + C: Pairable, + P: Pairable, +{ + /// The store for the component. + store: Store, runtime_layer::Engine>, + /// The instantiated component. + instance: Instance, +} -#[derive(Debug)] -pub(crate) struct Runner; +/// Internal struct for the store Data +struct Data +where + C: Pairable, + P: Pairable, +{ + kvp_current: C, + kvp_proposed: P, +} -impl Default for Runner { +impl Runner { /// Create a new runner. - fn default() -> Self { + pub(crate) fn new(kvp_current: C, kvp_proposed: P) -> Self { // target/wasm32-unknown-unknown/release/comrade_component.wasm let bytes: &[u8] = include_bytes!( "../../../../../target/wasm32-unknown-unknown/release/comrade_component.wasm" ); - let data = Data::default(); - // Create a new engine for instantiating a component. let engine = Engine::new(runtime_layer::Engine::default()); // Create a store for managing WASM data and any custom user-defined state. - let mut store = Store::new(&engine, data); + let mut store = Store::new( + &engine, + Data { + kvp_current, + kvp_proposed, + }, + ); tracing::debug!("Created store, loading bytes.",); // Parse the component bytes and load its imports and exports. @@ -46,11 +74,150 @@ impl Default for Runner { // Create a linker that will be used to resolve the component's imports, if any. let mut linker = Linker::default(); - Self + let host_interface = linker + .define_instance("comrade:api/utils".try_into().unwrap()) + .unwrap(); + + host_interface + .define_func( + "log", + Func::new( + &mut store, + FuncType::new([ValueType::String], []), + move |_store, params, _results| { + if let wasm_component_layer::Value::String(s) = ¶ms[0] { + tracing::debug!("[Log]: {}", s); + } + Ok(()) + }, + ), + ) + .unwrap(); + + // func "random-byte" is defined in the host interface + host_interface + .define_func( + "random-byte", + Func::new( + &mut store, + FuncType::new([], [ValueType::U8]), + move |_store, _params, results| { + let mut random = [0u8; 1]; + getrandom::fill(&mut random)?; + results[0] = wasm_component_layer::Value::U8(random[0]); + Ok(()) + }, + ), + ) + .unwrap(); + + let pairs_interface = linker + .define_instance("comrade:api/pairs".try_into().unwrap()) + .unwrap(); + + // get(choice: either, key: string) -> option + // gets either the current or proposed + pairs_interface + .define_func( + "get", + Func::new( + &mut store, + FuncType::new( + [ValueType::Enum(either_enum()), ValueType::String], + [ValueType::Option(OptionType::new(ValueType::Variant( + value_variant(), + )))], + ), + move |store, params, results| { + if let wasm_component_layer::Value::Enum(choice) = ¶ms[0] { + if let wasm_component_layer::Value::String(key) = ¶ms[1] { + let data = store.data(); + let value = match choice.discriminant() { + 0 => { + tracing::debug!("[TestLog] get current"); + &data.kvp_current.get(key.to_string().as_str()) + } + 1 => { + tracing::debug!("[TestLog] get proposed"); + &data.kvp_proposed.get(key.to_string().as_str()) + } + _ => panic!("Invalid choice"), + }; + tracing::debug!("\n[TestLog] get({:?}) = {:?}\n", key, value); + results[0] = match value { + Some(v) => { + let value = into_comp_value(v.clone()).unwrap(); + wasm_component_layer::Value::Option(OptionValue::new( + OptionType::new(ValueType::Variant(value_variant())), + Some(value), + )?) + } + None => wasm_component_layer::Value::Option(OptionValue::new( + OptionType::new(ValueType::Variant(value_variant())), + None, + )?), + }; + } else { + panic!("Expected String, found {:?}", params[1]); + } + }; + Ok(()) + }, + ), + ) + .unwrap(); + + // put is similar to get, except it mutates the current or proposed value witht he given value + // and key + // it returns success or failure + pairs_interface + .define_func( + "put", + Func::new( + &mut store, + FuncType::new( + [ + ValueType::Enum(either_enum()), + ValueType::String, + ValueType::Variant(value_variant()), + ], + [ValueType::Variant(value_variant())], + ), + move |mut store, params, results| { + if let wasm_component_layer::Value::Enum(choice) = ¶ms[0] { + if let wasm_component_layer::Value::String(key) = ¶ms[1] { + let data = store.data_mut(); + let value = into_core_value(params[2].clone()).unwrap(); + let v = match choice.discriminant() { + 0 => { + &mut data.kvp_current.put(key.to_string().as_str(), &value) + } + 1 => { + &mut data.kvp_proposed.put(key.to_string().as_str(), &value) + } + _ => panic!("Invalid enum choice, must be current or proposed"), + }; + results[0] = success_variant(0); + } else { + results[0] = failure_variant(format!( + "Expected String, found {:?}", + params[1] + )); + } + }; + Ok(()) + }, + ), + ) + .unwrap(); + + // Instantiate the component with the linker and store. + let instance = linker.instantiate(&mut store, &component).unwrap(); + Self { store, instance } } } -impl Runtime for Runner { +impl Runtime for Runner { fn run(&self, script: &str) -> Result<(), Error> { Ok(()) } @@ -62,13 +229,29 @@ impl Runtime for Runner { #[cfg(test)] mod tests { + use std::collections::HashMap; + + use comrade_reference::Pairs; + use super::*; use crate::runtime::Runtime; - use comrade_reference::Value; + + #[derive(Clone, Default, Debug)] + struct Data(HashMap); + + impl Pairs for Data { + fn get(&self, key: &str) -> Option { + self.0.get(key).cloned() + } + + fn put(&mut self, key: &str, value: &Value) -> Option { + self.0.insert(key.to_string(), value.clone()) + } + } #[test] - fn test_runner() { - let runner = Runner::default(); + fn test_layer_runner() { + let runner = Runner::new(Data::default(), Data::default()); assert_eq!(runner.top(), None); assert!(runner.run("test").is_ok()); } diff --git a/crates/comrade/src/runtime/mod.rs b/crates/comrade/src/runtime/mod.rs index 22c7af2..39d86b3 100644 --- a/crates/comrade/src/runtime/mod.rs +++ b/crates/comrade/src/runtime/mod.rs @@ -15,7 +15,7 @@ pub(crate) use layer::Runner; // mod wasmer; /// Each runtime feature must implement the `Runtime` trait, run and top -pub trait Runtime: Default { +pub trait Runtime { /// Run the script. fn run(&self, script: &str) -> Result<(), Error>; /// Get the top value from the context return stack. diff --git a/crates/comrade/tests/web.rs b/crates/comrade/tests/web.rs index 4e98582..6a7ed0a 100644 --- a/crates/comrade/tests/web.rs +++ b/crates/comrade/tests/web.rs @@ -1,19 +1,35 @@ //! Test to ensure that the crate runs in wasm32 target #![cfg(target_arch = "wasm32")] +use std::collections::HashMap; + +use comrade_reference::{Pairable, Pairs, Value}; use wasm_bindgen_test::wasm_bindgen_test_configure; use wasm_bindgen_test::*; - wasm_bindgen_test_configure!(run_in_browser); // Test which checks autotrait implementation for all types fn is_normal() {} +fn is_not_send() {} + #[wasm_bindgen_test] async fn test_wasm_autotraits() { // This is a dummy test to ensure that the crate runs in wasm32 target // and that the autotrait implementation is what is expected. - struct Current; - struct Proposed; - is_normal::>(); + #[derive(Clone, Debug, Default)] + struct ContextPairs(HashMap); + + impl Pairs for ContextPairs { + fn get(&self, key: &str) -> Option { + self.0.get(key).cloned() + } + + fn put(&mut self, key: &str, value: &Value) -> Option { + self.0.insert(key.to_string(), value.clone()) + } + } + + is_not_send::>(); + // is_normal::>(); } From 1ec524eae38193decb3d768e559f2f950d74d6ad Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Fri, 9 May 2025 15:00:28 -0300 Subject: [PATCH 026/155] tested logs --- crates/comrade/src/runtime/layer/mod.rs | 133 ++++++++++++++++++++++-- 1 file changed, 125 insertions(+), 8 deletions(-) diff --git a/crates/comrade/src/runtime/layer/mod.rs b/crates/comrade/src/runtime/layer/mod.rs index 9314007..2b90b60 100644 --- a/crates/comrade/src/runtime/layer/mod.rs +++ b/crates/comrade/src/runtime/layer/mod.rs @@ -12,7 +12,8 @@ use super::Runtime; use crate::Error; use comrade_reference::{Pairable, Pairs, Value}; use wasm_component_layer::{ - Component, Engine, Func, FuncType, Instance, Linker, OptionType, OptionValue, Store, ValueType, + Component, Engine, Func, FuncType, Instance, Linker, OptionType, OptionValue, ResourceOwn, + Store, ValueType, }; // wasmi layer for native targets @@ -33,6 +34,20 @@ where store: Store, runtime_layer::Engine>, /// The instantiated component. instance: Instance, + /// The constructed API resource for the instance + api_resource: ResourceOwn, +} + +trait Logger: Send + Sync + 'static { + fn log(&self, message: &str); +} + +struct TracingLogger; + +impl Logger for TracingLogger { + fn log(&self, message: &str) { + tracing::debug!("{}", message); + } } /// Internal struct for the store Data @@ -43,11 +58,20 @@ where { kvp_current: C, kvp_proposed: P, + /// Functions that are only used interally + logger: Box, } impl Runner { - /// Create a new runner. + /// Create a new runner with a tracing logger. pub(crate) fn new(kvp_current: C, kvp_proposed: P) -> Self { + // Use default logger + Self::new_with_logger(kvp_current, kvp_proposed, Box::new(TracingLogger)) + } + + /// Create a new runner. + // #[cfg(test)] + fn new_with_logger(kvp_current: C, kvp_proposed: P, logger: Box) -> Self { // target/wasm32-unknown-unknown/release/comrade_component.wasm let bytes: &[u8] = include_bytes!( "../../../../../target/wasm32-unknown-unknown/release/comrade_component.wasm" @@ -62,6 +86,7 @@ impl Runner { Data { kvp_current, kvp_proposed, + logger, }, ); @@ -84,9 +109,9 @@ impl Runner { Func::new( &mut store, FuncType::new([ValueType::String], []), - move |_store, params, _results| { + move |store, params, _results| { if let wasm_component_layer::Value::String(s) = ¶ms[0] { - tracing::debug!("[Log]: {}", s); + store.data().logger.log(s.to_string().as_str()); } Ok(()) }, @@ -213,7 +238,33 @@ impl Runner { // Instantiate the component with the linker and store. let instance = linker.instantiate(&mut store, &component).unwrap(); - Self { store, instance } + + // Construct + let exports = instance.exports(); + let interface = exports + .instance(&"comrade:api/api".try_into().unwrap()) + .unwrap(); + + // Call the resource constructor + let resource_constructor = interface.func("[constructor]api").unwrap(); + + let arguments = &[]; + let mut results = vec![wasm_component_layer::Value::Bool(false)]; + + resource_constructor + .call(&mut store, arguments, &mut results) + .unwrap(); + + let api_resource = match results[0] { + wasm_component_layer::Value::Own(ref resource) => resource.clone(), + _ => panic!("Unexpected result type"), + }; + + Self { + store, + instance, + api_resource, + } } } @@ -230,16 +281,68 @@ impl Runtime for Runner { #[cfg(test)] mod tests { use std::collections::HashMap; + use std::sync::Arc; use comrade_reference::Pairs; use super::*; use crate::runtime::Runtime; + struct TestLogger { + messages: Arc>>, + } + + impl TestLogger { + fn new() -> Self { + Self { + messages: Arc::new(std::sync::Mutex::new(Vec::new())), + } + } + + fn get_messages(&self) -> Vec { + let lock = self.messages.lock().unwrap(); + lock.clone() + } + + fn clear_messages(&self) { + let mut lock = self.messages.lock().unwrap(); + lock.clear(); + } + } + + impl Logger for TestLogger { + fn log(&self, message: &str) { + let mut messages = self.messages.lock().unwrap(); + messages.push(message.to_string()); + } + } + + impl Clone for TestLogger { + fn clone(&self) -> Self { + Self { + messages: Arc::clone(&self.messages), + } + } + } + + impl Runner { + // Helper for tests that returns both the runner and the logger + fn new_for_test(kvp_current: C, kvp_proposed: P) -> (Self, TestLogger) { + let test_logger = TestLogger::new(); + let logger_box: Box = Box::new(test_logger.clone()); + + // Force a test message to verify logging works + logger_box.log("Test log system"); + + let runner = Self::new_with_logger(kvp_current, kvp_proposed, logger_box); + (runner, test_logger) + } + } + #[derive(Clone, Default, Debug)] - struct Data(HashMap); + struct TestData(HashMap); - impl Pairs for Data { + impl Pairs for TestData { fn get(&self, key: &str) -> Option { self.0.get(key).cloned() } @@ -251,8 +354,22 @@ mod tests { #[test] fn test_layer_runner() { - let runner = Runner::new(Data::default(), Data::default()); + let runner = Runner::new(TestData::default(), TestData::default()); assert_eq!(runner.top(), None); assert!(runner.run("test").is_ok()); } + + #[test] + fn test_log() { + // When we call the consturctor for the reference impl wasm bytes, + // we should get log("Creating new Component"); + + let (runner, test_logger) = Runner::new_for_test(TestData::default(), TestData::default()); + + let logs = test_logger.get_messages(); + + assert!(logs + .iter() + .any(|msg| msg.contains("Creating new Component"))); + } } From 544003987ca5eeab1fc7af95bde32758c0ca572c Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Fri, 9 May 2025 15:23:55 -0300 Subject: [PATCH 027/155] impl try_unlock --- crates/comrade/src/error.rs | 2 +- crates/comrade/src/lib.rs | 10 +++--- crates/comrade/src/runtime/layer/mod.rs | 47 +++++++++++++++++++------ crates/comrade/src/runtime/mod.rs | 4 +-- 4 files changed, 44 insertions(+), 19 deletions(-) diff --git a/crates/comrade/src/error.rs b/crates/comrade/src/error.rs index 39e373f..2e1a882 100644 --- a/crates/comrade/src/error.rs +++ b/crates/comrade/src/error.rs @@ -1,7 +1,7 @@ //! Crate errors /// Comrade error types -#[derive(Debug, thiserror::Error)] +#[derive(Debug, thiserror::Error, PartialEq, Eq)] pub enum Error { // /// Error in the VM // #[error("VM error: {0}")] diff --git a/crates/comrade/src/lib.rs b/crates/comrade/src/lib.rs index f9b922f..44d86c2 100644 --- a/crates/comrade/src/lib.rs +++ b/crates/comrade/src/lib.rs @@ -69,8 +69,8 @@ impl Comrade { /// Tries to unlock the comrade with the given script. /// Will return an error if the script fails to run. - pub fn try_unlock(self, script: &str) -> Result, Error> { - self.runner.run(script)?; + pub fn try_unlock(mut self, script: &str) -> Result, Error> { + self.runner.try_unlock(script)?; Ok(self.into()) } } @@ -79,10 +79,10 @@ impl Comrade { impl Comrade { /// Tries to lock the comrade with the given script. /// Will return an error if the script fails to run. - pub fn try_lock(&self, script: &str) -> Result, Error> { - self.runner.run(script)?; + pub fn try_lock(&mut self, script: &str) -> Result, Error> { + self.runner.try_unlock(script)?; // check the context retrun stack top, return the result - let res = self.runner.top(); + let res = self.runner.try_lock()?; Ok(res) } } diff --git a/crates/comrade/src/runtime/layer/mod.rs b/crates/comrade/src/runtime/layer/mod.rs index 2b90b60..a618d7d 100644 --- a/crates/comrade/src/runtime/layer/mod.rs +++ b/crates/comrade/src/runtime/layer/mod.rs @@ -10,10 +10,10 @@ use definitions::{ use super::Runtime; use crate::Error; -use comrade_reference::{Pairable, Pairs, Value}; +use comrade_reference::{Pairable, Value}; use wasm_component_layer::{ - Component, Engine, Func, FuncType, Instance, Linker, OptionType, OptionValue, ResourceOwn, - Store, ValueType, + AsContextMut as _, Component, Engine, Func, FuncType, Instance, Linker, OptionType, + OptionValue, ResourceOwn, Store, ValueType, }; // wasmi layer for native targets @@ -213,6 +213,7 @@ impl Runner { if let wasm_component_layer::Value::String(key) = ¶ms[1] { let data = store.data_mut(); let value = into_core_value(params[2].clone()).unwrap(); + // TODO: Store return value let v = match choice.discriminant() { 0 => { &mut data.kvp_current.put(key.to_string().as_str(), &value) @@ -241,12 +242,12 @@ impl Runner { // Construct let exports = instance.exports(); - let interface = exports + let api_export_instance = exports .instance(&"comrade:api/api".try_into().unwrap()) .unwrap(); // Call the resource constructor - let resource_constructor = interface.func("[constructor]api").unwrap(); + let resource_constructor = api_export_instance.func("[constructor]api").unwrap(); let arguments = &[]; let mut results = vec![wasm_component_layer::Value::Bool(false)]; @@ -269,12 +270,36 @@ impl Runner { } impl Runtime for Runner { - fn run(&self, script: &str) -> Result<(), Error> { + fn try_unlock(&mut self, unlock: &str) -> Result<(), Error> { + let api_export_instance = self + .instance + .exports() + .instance(&"comrade:api/api".try_into().unwrap()) + .unwrap(); + + let borrowed_api = self + .api_resource + .borrow(self.store.as_context_mut()) + .unwrap(); + + let unlock_args = vec![ + wasm_component_layer::Value::Borrow(borrowed_api.clone()), + wasm_component_layer::Value::String(unlock.into()), + ]; + + let try_unlock = api_export_instance.func("[method]api.try-unlock").unwrap(); + + // Call the try_unlock method + let mut results = vec![wasm_component_layer::Value::Bool(false)]; + try_unlock + .call(&mut self.store, &unlock_args, &mut results) + .unwrap(); Ok(()) } - fn top(&self) -> Option { - None + fn try_lock(&self) -> Result, Error> { + // call try_lock on the instance + Ok(None) } } @@ -354,9 +379,9 @@ mod tests { #[test] fn test_layer_runner() { - let runner = Runner::new(TestData::default(), TestData::default()); - assert_eq!(runner.top(), None); - assert!(runner.run("test").is_ok()); + let mut runner = Runner::new(TestData::default(), TestData::default()); + assert_eq!(runner.try_lock(), Ok(None)); + assert!(runner.try_unlock("test").is_ok()); } #[test] diff --git a/crates/comrade/src/runtime/mod.rs b/crates/comrade/src/runtime/mod.rs index 39d86b3..eaf0516 100644 --- a/crates/comrade/src/runtime/mod.rs +++ b/crates/comrade/src/runtime/mod.rs @@ -17,7 +17,7 @@ pub(crate) use layer::Runner; /// Each runtime feature must implement the `Runtime` trait, run and top pub trait Runtime { /// Run the script. - fn run(&self, script: &str) -> Result<(), Error>; + fn try_unlock(&mut self, script: &str) -> Result<(), Error>; /// Get the top value from the context return stack. - fn top(&self) -> Option; + fn try_lock(&self) -> Result, Error>; } From 05256aafd1b7cffc9493529408fe79e6c173dc5e Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Fri, 9 May 2025 16:13:55 -0300 Subject: [PATCH 028/155] more tests --- crates/comrade/Cargo.toml | 2 + crates/comrade/src/error.rs | 7 +- crates/comrade/src/lib.rs | 4 +- crates/comrade/src/runtime/layer/mod.rs | 186 +++++++++++++++++++++++- crates/comrade/src/runtime/mod.rs | 2 +- 5 files changed, 190 insertions(+), 11 deletions(-) diff --git a/crates/comrade/Cargo.toml b/crates/comrade/Cargo.toml index f19ebfa..3a035ee 100644 --- a/crates/comrade/Cargo.toml +++ b/crates/comrade/Cargo.toml @@ -17,6 +17,7 @@ wasm_component_layer = { git = "https://github.com/DougAnderson444/wasm_componen tracing = { version = "0.1", features = ["log"] } tracing-subscriber = { version = "0.3", features = ["std"] } getrandom = "0.3" +anyhow = "1.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] wasmi_runtime_layer = "=0.40.0" # pin to 0.40 to match wasm_runtime_layer 0.4.2 @@ -31,6 +32,7 @@ web-sys = { version = "0.3", features = ["Window", "Crypto"] } [dev-dependencies] wasm-bindgen-test = "0.3" +hex = "0.4" [features] default = ["wasm_component_layer"] diff --git a/crates/comrade/src/error.rs b/crates/comrade/src/error.rs index 2e1a882..fc98439 100644 --- a/crates/comrade/src/error.rs +++ b/crates/comrade/src/error.rs @@ -1,8 +1,13 @@ //! Crate errors /// Comrade error types -#[derive(Debug, thiserror::Error, PartialEq, Eq)] +#[derive(Debug, thiserror::Error)] pub enum Error { + #[error("Invalid argument: {0}")] + WasmFnCall(#[from] anyhow::Error), + /// Error in the Script + #[error("Script has failed to run, yuo likely have an error in your script: {0}")] + ScriptFailure(String), // /// Error in the VM // #[error("VM error: {0}")] // Vm(#[from] wasmi::Error), diff --git a/crates/comrade/src/lib.rs b/crates/comrade/src/lib.rs index 44d86c2..0efbf65 100644 --- a/crates/comrade/src/lib.rs +++ b/crates/comrade/src/lib.rs @@ -80,9 +80,7 @@ impl Comrade { /// Tries to lock the comrade with the given script. /// Will return an error if the script fails to run. pub fn try_lock(&mut self, script: &str) -> Result, Error> { - self.runner.try_unlock(script)?; - // check the context retrun stack top, return the result - let res = self.runner.try_lock()?; + let res = self.runner.try_lock(script)?; Ok(res) } } diff --git a/crates/comrade/src/runtime/layer/mod.rs b/crates/comrade/src/runtime/layer/mod.rs index a618d7d..6aa9034 100644 --- a/crates/comrade/src/runtime/layer/mod.rs +++ b/crates/comrade/src/runtime/layer/mod.rs @@ -214,7 +214,7 @@ impl Runner { let data = store.data_mut(); let value = into_core_value(params[2].clone()).unwrap(); // TODO: Store return value - let v = match choice.discriminant() { + let _v = match choice.discriminant() { 0 => { &mut data.kvp_current.put(key.to_string().as_str(), &value) } @@ -297,8 +297,46 @@ impl Runtime for Runner { Ok(()) } - fn try_lock(&self) -> Result, Error> { - // call try_lock on the instance + fn try_lock(&mut self, lock: &str) -> Result, Error> { + let api_export_instance = self + .instance + .exports() + .instance(&"comrade:api/api".try_into().unwrap()) + .unwrap(); + + let borrowed_api = self + .api_resource + .borrow(self.store.as_context_mut()) + .unwrap(); + + let lock_args = vec![ + wasm_component_layer::Value::Borrow(borrowed_api.clone()), + wasm_component_layer::Value::String(lock.into()), + ]; + + let try_lock = api_export_instance.func("[method]api.try-lock").unwrap(); + + // Call the try_lock method + let mut results = vec![wasm_component_layer::Value::Bool(false)]; + try_lock.call(&mut self.store, &lock_args, &mut results)?; + + if let wasm_component_layer::Value::Result(result) = &results[0] { + match **result { + Ok(_) => { + eprintln!("[TestLog] Unlock successful"); + } + Err(ref e) => { + // Unlock failed with error: {:?}", e.as_ref().unwrap()); + return Err(Error::ScriptFailure(format!( + "Unlock failed with error: {:?}", + e.as_ref().unwrap() + ))); + } + } + } else { + panic!("Unexpected result type"); + } + Ok(None) } } @@ -377,11 +415,63 @@ mod tests { } } + fn unlock_script(entry_key: &str, proof_key: &str) -> String { + let unlock_script = format!( + r#" + // push the serialized Entry as the message + push("{entry_key}"); + + // push the proof data + push("{proof_key}"); + "# + ); + + unlock_script + } + + /// First lock is /ephemeral and {entry_key} + fn first_lock_script(entry_key: &str) -> String { + let first_lock = format!( + r#" + // check the first key, which is ephemeral + check_signature("/ephemeral", "{entry_key}") + "# + ); + + first_lock + } + + /// Other lock script + fn other_lock_script(entry_key: &str) -> String { + format!( + r#" + // then check a possible threshold sig... + check_signature("/recoverykey", "{entry_key}") || + + // then check a possible pubkey sig... + check_signature("/pubkey", "{entry_key}") || + + // then the pre-image proof... + check_preimage("/hash") + "# + ) + } + #[test] fn test_layer_runner() { let mut runner = Runner::new(TestData::default(), TestData::default()); - assert_eq!(runner.try_lock(), Ok(None)); - assert!(runner.try_unlock("test").is_ok()); + let entry_key = "/entry/"; + assert!(runner.try_lock(&first_lock_script(entry_key)).is_ok()); + let proof_key = "/entry/proof"; + assert!(runner + .try_unlock(&unlock_script(entry_key, proof_key)) + .is_ok()); + } + + #[test] + fn test_fails_for_invalid_script() { + let mut runner = Runner::new(TestData::default(), TestData::default()); + assert!(runner.try_lock("garbage").is_err()); } #[test] @@ -389,7 +479,7 @@ mod tests { // When we call the consturctor for the reference impl wasm bytes, // we should get log("Creating new Component"); - let (runner, test_logger) = Runner::new_for_test(TestData::default(), TestData::default()); + let (_runner, test_logger) = Runner::new_for_test(TestData::default(), TestData::default()); let logs = test_logger.get_messages(); @@ -397,4 +487,88 @@ mod tests { .iter() .any(|msg| msg.contains("Creating new Component"))); } + + // try_unlock + #[test] + fn test_try_unlock_and_lock_scripts() { + let entry_key = "/entry/"; + + // unlock details + let entry_data = b"for great justice, move every zig!"; + let proof_key = "/entry/proof"; + let proof_data = hex::decode("4819397f51b18bc6cffd1fff07afa33f7096c7a0c659590b077cc0ea5d6081d739512129becacb8e6997e6b7d18756299f515a822344ac2b6737979d5e5e6b03").unwrap(); + + let unlock = format!( + r#" + // push the serialized Entry as the message + push("{entry_key}"); + + // push the proof data + push("{entry_key}proof");"# + ); + + let mut kvp_unlock = TestData::default(); + let mut kvp_lock = TestData::default(); + // "/entry/" needs to be present on both lock and unlock stacks, + // since they are used in both the unlock and lock scripts: + // ie. push("/entry/") and check_signature("/pubkey", "/entry/") + kvp_unlock.put(entry_key, &entry_data.to_vec().into()); + kvp_lock.put(entry_key, &entry_data.to_vec().into()); + // "/entry/proof" only needs to be present on the unlock stack, + // since that's where the proof is used + kvp_unlock.put(proof_key, &proof_data.clone().into()); + + let (mut runner, test_logger) = Runner::new_for_test(kvp_lock.clone(), kvp_unlock.clone()); + + let result = runner.try_unlock(&unlock); + + // Check the result + assert!(result.is_ok()); + + // Parameter stack now has /entry/ and /entry/proof on it, + // but that's transparent to us at this level + // The only way we confirm our code works is if the valid lock script + // succeeds, and invalid lock script fails + + // 2 lock scripts. + // First one shodl fail, since we don't have the ephemeral key + // Second one should succeed, since we have the pubkey signature + let first_lock = format!( + r#" + // check the first key, which is ephemeral + check_signature("/ephemeral", "{entry_key}") + "# + ); + + let other_lock = format!( + r#" + // then check a possible threshold sig... + check_signature("/recoverykey", "{entry_key}") || + + // then check a possible pubkey sig... + check_signature("/pubkey", "{entry_key}") || + + // then the pre-image proof... + check_preimage("/hash") + "# + ); + + let pubkey = "/pubkey"; + let pub_key = hex::decode("ba24ed010874657374206b657901012054d94d7b8a11d6581af4a14bc6451c7a23049018610f108c996968fe8fce9464").unwrap(); + + kvp_lock.put(pubkey, &pub_key.into()); + + // First lock script should Result in a fail Value + let result = runner.try_lock(&first_lock); + assert!(result.is_ok()); + let result = result.unwrap(); + assert!(result.is_some()); + let result = result.unwrap(); + // if let wasm_component_layer::Value::Variant(v) = result { + // assert_eq!(v.discriminant(), 1); + // assert_eq!(v.value(), "failure"); + // } else { + // panic!("Expected a failure variant"); + // } + } } diff --git a/crates/comrade/src/runtime/mod.rs b/crates/comrade/src/runtime/mod.rs index eaf0516..e9b5b51 100644 --- a/crates/comrade/src/runtime/mod.rs +++ b/crates/comrade/src/runtime/mod.rs @@ -19,5 +19,5 @@ pub trait Runtime { /// Run the script. fn try_unlock(&mut self, script: &str) -> Result<(), Error>; /// Get the top value from the context return stack. - fn try_lock(&self) -> Result, Error>; + fn try_lock(&mut self, script: &str) -> Result, Error>; } From 0b6a1f1085e2d44697a9303fee503fcf24e61678 Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Fri, 9 May 2025 17:43:28 -0300 Subject: [PATCH 029/155] test passing! --- crates/comrade-component/src/kv.rs | 4 +- crates/comrade-component/src/lib.rs | 2 +- crates/comrade-reference/src/context.rs | 22 +++++- .../comrade/src/runtime/layer/definitions.rs | 68 +++++++++++++++- crates/comrade/src/runtime/layer/mod.rs | 78 ++++++++++++------- 5 files changed, 141 insertions(+), 33 deletions(-) diff --git a/crates/comrade-component/src/kv.rs b/crates/comrade-component/src/kv.rs index fadfa97..7fe1476 100644 --- a/crates/comrade-component/src/kv.rs +++ b/crates/comrade-component/src/kv.rs @@ -15,7 +15,7 @@ impl Pairs for Current { get(pairs::Either::Current, key) .map(|v| v.into()) .or_else(|| { - log(&format!("Key not found: {key}")); + log(&format!("Current Key not found: {key}")); None }) } @@ -32,7 +32,7 @@ impl Pairs for Proposed { get(pairs::Either::Proposed, key) .map(|v| v.into()) .or_else(|| { - log(&format!("Key not found: {key}")); + log(&format!("Proposed Key not found: {key}")); None }) } diff --git a/crates/comrade-component/src/lib.rs b/crates/comrade-component/src/lib.rs index 8601bbf..e4beed9 100644 --- a/crates/comrade-component/src/lib.rs +++ b/crates/comrade-component/src/lib.rs @@ -36,7 +36,7 @@ impl GuestApi for Api { } fn try_unlock(&self, unlock: String) -> Result<(), String> { - log("try_unlock"); + log("[component] try_unlock(..)"); self.unlock.borrow_mut().replace(unlock.clone()); // self.vm.run(&unlock).map_err(|e| e.to_string())?; self.context.borrow_mut().run(&unlock).map_err(|e| { diff --git a/crates/comrade-reference/src/context.rs b/crates/comrade-reference/src/context.rs index 47077ca..525f2b9 100644 --- a/crates/comrade-reference/src/context.rs +++ b/crates/comrade-reference/src/context.rs @@ -619,19 +619,35 @@ mod tests { let signature = signmk.sign(entry_data.as_slice(), false, None).unwrap(); + let sig_bytes_raw: Vec = signature.clone().into(); + eprintln!("Signature bytes: {:?}", &sig_bytes_raw); + let raw_hex = hex::encode(sig_bytes_raw.clone()); + eprintln!("Signature bytes RAW HEX: {raw_hex}"); + let ms_from_raw = Multisig::try_from(sig_bytes_raw.as_slice()).unwrap(); + + // assert hex matches b92483a6c006000100404819397f51b18bc6cffd1fff07afa33f7096c7a0c659590b077cc0ea5d6081d739512129becacb8e6997e6b7d18756299f515a822344ac2b6737979d5e5e6b03 + assert_eq!( + raw_hex, + "b92483a6c006000100404819397f51b18bc6cffd1fff07afa33f7096c7a0c659590b077cc0ea5d6081d739512129becacb8e6997e6b7d18756299f515a822344ac2b6737979d5e5e6b03" + ); + + // verify with ms_from_raw + let verify_mk = mk.verify_view().unwrap(); + assert!(verify_mk + .verify(&ms_from_raw, Some(entry_data.as_ref())) + .is_ok()); + // print out hex signature let sig_data = signature.data_view().unwrap(); let sig_bytes = sig_data.sig_bytes().unwrap(); - eprintln!("Signature bytes: {:?}", &sig_bytes); - let ms = multisig::Builder::new(Codec::EddsaMsig) .with_signature_bytes(&sig_bytes) .try_build() .unwrap(); let hex_sig = hex::encode(ms.data_view().unwrap().sig_bytes().unwrap()); - eprintln!("Signature: {hex_sig}"); + // eprintln!("Signature: {hex_sig}"); assert_eq!( hex_sig, diff --git a/crates/comrade/src/runtime/layer/definitions.rs b/crates/comrade/src/runtime/layer/definitions.rs index 7420779..1837b59 100644 --- a/crates/comrade/src/runtime/layer/definitions.rs +++ b/crates/comrade/src/runtime/layer/definitions.rs @@ -165,9 +165,70 @@ pub fn into_comp_value(value: crate::Value) -> Result Result { match value { + wasm_component_layer::Value::Option(option_value) => match *option_value { + Some(ref value) => into_core_value(value.clone()), + None => Ok(crate::Value::Failure("Option value is None".to_string())), + }, + wasm_component_layer::Value::Variant(variant) => { + match variant.discriminant() { + 0 => { + // bin + if let Some(Value::Record(record)) = variant.value() { + if let Some(Value::String(hint)) = record.field("hint") { + if let Some(Value::List(list)) = record.field("data") { + let data: Vec = list + .iter() + .map(|v| match v { + Value::U8(b) => Ok(b), + _ => Err(format!("Expected U8, found {:?}", v)), + }) + .collect::, String>>()?; + return Ok(crate::Value::Bin { + hint: hint.to_string(), + data, + }); + } + } + } + Err(format!("Invalid bin variant: {:?}", variant)) + } + 1 => { + // str + if let Some(Value::Record(record)) = variant.value() { + if let Some(Value::String(hint)) = record.field("hint") { + if let Some(Value::String(data)) = record.field("data") { + return Ok(crate::Value::Str { + hint: hint.to_string(), + data: data.to_string(), + }); + } + } + } + Err(format!("Invalid str variant: {:?}", variant)) + } + 2 => { + // success + if let Some(Value::U32(code)) = variant.value() { + return Ok(crate::Value::Success(code as usize)); + } + Err(format!("Invalid success variant: {:?}", variant)) + } + 3 => { + // failure + if let Some(Value::String(msg)) = variant.value() { + return Ok(crate::Value::Failure(msg.to_string())); + } + Err(format!("Invalid failure variant: {:?}", variant)) + } + _ => Err(format!( + "Unknown variant discriminant: {}", + variant.discriminant() + )), + } + } wasm_component_layer::Value::Record(record) => { if let Some(Value::String(hint)) = record.field("hint") { if let Some(Value::List(list)) = record.field("data") { @@ -182,6 +243,11 @@ pub fn into_core_value(value: wasm_component_layer::Value) -> Result Runner { tracing::debug!("Created store, loading bytes.",); // Parse the component bytes and load its imports and exports. - let component = Component::new(&engine, &bytes).unwrap(); + let component = Component::new(&engine, bytes).unwrap(); tracing::debug!("Loaded bytes"); @@ -320,10 +320,18 @@ impl Runtime for Runner { let mut results = vec![wasm_component_layer::Value::Bool(false)]; try_lock.call(&mut self.store, &lock_args, &mut results)?; + // allow unused assignment + #[allow(unused_assignments)] + let mut res = None; + if let wasm_component_layer::Value::Result(result) = &results[0] { match **result { - Ok(_) => { - eprintln!("[TestLog] Unlock successful"); + Ok(ref v) => { + let Some(v) = v else { + return Err(Error::ScriptFailure("Unlock failed".to_string())); + }; + let comrade_value = into_core_value(v.clone()).unwrap(); + res = Some(comrade_value.clone()); } Err(ref e) => { // Unlock failed with error: {:?}", e.as_ref().unwrap()); @@ -334,10 +342,13 @@ impl Runtime for Runner { } } } else { - panic!("Unexpected result type"); - } + return Err(Error::ScriptFailure(format!( + "Expected Result, found {:?}", + results[0] + ))); + }; - Ok(None) + Ok(res) } } @@ -375,6 +386,7 @@ mod tests { impl Logger for TestLogger { fn log(&self, message: &str) { + eprintln!("[TestLogger]: {}", message); let mut messages = self.messages.lock().unwrap(); messages.push(message.to_string()); } @@ -394,9 +406,6 @@ mod tests { let test_logger = TestLogger::new(); let logger_box: Box = Box::new(test_logger.clone()); - // Force a test message to verify logging works - logger_box.log("Test log system"); - let runner = Self::new_with_logger(kvp_current, kvp_proposed, logger_box); (runner, test_logger) } @@ -496,10 +505,17 @@ mod tests { // unlock details let entry_data = b"for great justice, move every zig!"; let proof_key = "/entry/proof"; - let proof_data = hex::decode("4819397f51b18bc6cffd1fff07afa33f7096c7a0c659590b077cc0ea5d6081d739512129becacb8e6997e6b7d18756299f515a822344ac2b6737979d5e5e6b03").unwrap(); + let proof_data = hex::decode("b92483a6c006000100404819397f51b18bc6cffd1fff07afa33f7096c7a0c659590b077cc0ea5d6081d739512129becacb8e6997e6b7d18756299f515a822344ac2b6737979d5e5e6b03").unwrap(); + + let pubkey = "/pubkey"; + let pub_key = hex::decode("ba24ed010874657374206b657901012054d94d7b8a11d6581af4a14bc6451c7a23049018610f108c996968fe8fce9464").unwrap(); let unlock = format!( r#" + // push pubkey + push("{pubkey}"); + + // push pubkey value // push the serialized Entry as the message push("{entry_key}"); @@ -517,8 +533,11 @@ mod tests { // "/entry/proof" only needs to be present on the unlock stack, // since that's where the proof is used kvp_unlock.put(proof_key, &proof_data.clone().into()); + kvp_lock.put(proof_key, &proof_data.clone().into()); - let (mut runner, test_logger) = Runner::new_for_test(kvp_lock.clone(), kvp_unlock.clone()); + kvp_lock.put(pubkey, &pub_key.into()); + + let (mut runner, test_logger) = Runner::new_for_test(kvp_lock, kvp_unlock); let result = runner.try_unlock(&unlock); @@ -553,22 +572,29 @@ mod tests { "# ); - let pubkey = "/pubkey"; - let pub_key = hex::decode("ba24ed010874657374206b657901012054d94d7b8a11d6581af4a14bc6451c7a23049018610f108c996968fe8fce9464").unwrap(); + // First lock script should Result in a fail Value + let maybe_result = runner.try_lock(&first_lock).unwrap(); + assert!(maybe_result.is_some()); + let value = maybe_result.unwrap(); - kvp_lock.put(pubkey, &pub_key.into()); + // value should be a failure variant + let Value::Failure(reason) = value else { + panic!("Expected a failure variant"); + }; - // First lock script should Result in a fail Value - let result = runner.try_lock(&first_lock); - assert!(result.is_ok()); - let result = result.unwrap(); - assert!(result.is_some()); - let result = result.unwrap(); - // if let wasm_component_layer::Value::Variant(v) = result { - // assert_eq!(v.discriminant(), 1); - // assert_eq!(v.value(), "failure"); - // } else { - // panic!("Expected a failure variant"); - // } + eprintln!("First lock script failed with reason: {}", reason); + + // Second lock script should Result in a success Value + let value = runner.try_lock(&other_lock).unwrap().unwrap(); + + // value should be a success variant with a count + let Value::Success(count) = value else { + panic!("Expected a success variant {value:?}"); + }; + + eprintln!("Second lock script succeeded with count: {}", count); + + // Count should be 2, since 1) /ephemeral failed, 2) /recoverykey failed + assert_eq!(count, 2); } } From 0a6cc2816a3b6af60a3c129ff6dcca521beb6e11 Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Fri, 9 May 2025 18:18:35 -0300 Subject: [PATCH 030/155] add readme rustdoc tests --- crates/comrade/README.md | 101 ++++++++++++++-- crates/comrade/src/lib.rs | 151 ++++++++++++++++++++++++ crates/comrade/src/runtime/layer/mod.rs | 5 - 3 files changed, 244 insertions(+), 13 deletions(-) diff --git a/crates/comrade/README.md b/crates/comrade/README.md index e258e23..65d9d2b 100644 --- a/crates/comrade/README.md +++ b/crates/comrade/README.md @@ -9,18 +9,103 @@ This crate is the main entry point for the library. The main API aims to be super simple: -```ignore -let unlocked = Comrade::new(kvp_lock, kvp_unlock) - .try_unlock(&unlock)?; +```rust +use comrade::Comrade; +use comrade_reference::{Pairs, Value}; +use std::collections::HashMap; + +#[derive(Clone, Default, Debug)] +struct TestData(HashMap); + +impl Pairs for TestData { + fn get(&self, key: &str) -> Option { + self.0.get(key).cloned() + } + + fn put(&mut self, key: &str, value: &Value) -> Option { + self.0.insert(key.to_string(), value.clone()) + } +} +// The message to sign, in both the lock and unlock scripts +let entry_key = "/entry/"; +let entry_data = b"for great justice, move every zig!"; + +// The proof data that is provided by the unlock script +let proof_key = "/entry/proof"; +let proof_data = hex::decode("b92483a6c006000100404819397f51b18bc6cffd1fff07afa33f7096c7a0c659590b077cc0ea5d6081d739512129becacb8e6997e6b7d18756299f515a822344ac2b6737979d5e5e6b03").unwrap(); + +// The public key to that must be proven by unlock scripts +let pubkey = "/pubkey"; +let pub_key = hex::decode("ba24ed010874657374206b657901012054d94d7b8a11d6581af4a14bc6451c7a23049018610f108c996968fe8fce9464").unwrap(); + +// Our Key-Value Pairs +let mut kvp_unlock = TestData::default(); +let mut kvp_lock = TestData::default(); + +// "/entry/" needs to be present on both lock and unlock stacks, +// since they are used in both the unlock and lock scripts: +// ie. push("/entry/") and check_signature("/pubkey", "/entry/") +kvp_unlock.put(entry_key, &entry_data.to_vec().into()); +kvp_lock.put(entry_key, &entry_data.to_vec().into()); + +// "/entry/proof" only needs to be present on the unlock stack, +// since that's where the proof is used +kvp_unlock.put(proof_key, &proof_data.clone().into()); + +// "/pubkey" needs to be present on lock stack, to set what the PubKey is +kvp_lock.put(pubkey, &pub_key.into()); + +let unlock_script = format!( + r#" + // push the serialized Entry as the message + push("{entry_key}"); + + // push the proof data + push("{proof_key}"); + "# +); + +let first_lock = format!( + r#" + // check the first key, which is ephemeral + check_signature("/ephemeral", "{entry_key}") + "# +); + +let other_lock_script = format!( + r#" + // then check a possible threshold sig... + check_signature("/recoverykey", "{entry_key}") || + + // then check a possible pubkey sig... + check_signature("/pubkey", "{entry_key}") || + + // then the pre-image proof... + check_preimage("/hash") + "# +); + +let mut comrade = Comrade::new(kvp_lock, kvp_unlock) + .try_unlock(&unlock_script) + .expect("Failed to unlock comrade"); + +// check the lock scripts +let locks = [first_lock, other_lock_script]; + +// check the locks let mut count = 0; - for lock in locks { - if let Some(Value::Success(ct)) = unlocked.try_lock(lock)? { - count = ct; - break; - } + if let Some(Value::Success(ct)) = + comrade.try_lock(&lock).expect("Failed to lock comrade") + { + count = ct; + break; + } } + +// check the count +assert_eq!(count, 2); ``` ## Tests diff --git a/crates/comrade/src/lib.rs b/crates/comrade/src/lib.rs index 0efbf65..3d17a0f 100644 --- a/crates/comrade/src/lib.rs +++ b/crates/comrade/src/lib.rs @@ -21,6 +21,11 @@ //! ```` //! //! where the args iml [comrade_reference::Pairs] + +// Include readme at header, with rustdoc tests +#![doc = include_str!("../README.md")] +pub struct ReadmeDocumentation; + mod error; pub use crate::error::Error; @@ -96,3 +101,149 @@ impl From> for Comrade); + + impl Pairs for TestData { + fn get(&self, key: &str) -> Option { + self.0.get(key).cloned() + } + + fn put(&mut self, key: &str, value: &Value) -> Option { + self.0.insert(key.to_string(), value.clone()) + } + } + + fn unlock_script(entry_key: &str, proof_key: &str) -> String { + let unlock_script = format!( + r#" + // push the serialized Entry as the message + push("{entry_key}"); + + // push the proof data + push("{proof_key}"); + "# + ); + + unlock_script + } + + /// First lock is /ephemeral and {entry_key} + fn first_lock_script(entry_key: &str) -> String { + let first_lock = format!( + r#" + // check the first key, which is ephemeral + check_signature("/ephemeral", "{entry_key}") + "# + ); + + first_lock + } + + /// Other lock script + fn other_lock_script(entry_key: &str) -> String { + format!( + r#" + // then check a possible threshold sig... + check_signature("/recoverykey", "{entry_key}") || + + // then check a possible pubkey sig... + check_signature("/pubkey", "{entry_key}") || + + // then the pre-image proof... + check_preimage("/hash") + "# + ) + } + + #[test] + fn test_comrade() { + // The message to sign, in both the lock and unlock scripts + let entry_key = "/entry/"; + let entry_data = b"for great justice, move every zig!"; + + // The proof data that is provided by the unlock script + let proof_key = "/entry/proof"; + let proof_data = hex::decode("b92483a6c006000100404819397f51b18bc6cffd1fff07afa33f7096c7a0c659590b077cc0ea5d6081d739512129becacb8e6997e6b7d18756299f515a822344ac2b6737979d5e5e6b03").unwrap(); + + // The public key to that must be proven by unlock scripts + let pubkey = "/pubkey"; + let pub_key = hex::decode("ba24ed010874657374206b657901012054d94d7b8a11d6581af4a14bc6451c7a23049018610f108c996968fe8fce9464").unwrap(); + + // Our Key-Value Pairs + let mut kvp_unlock = TestData::default(); + let mut kvp_lock = TestData::default(); + + // "/entry/" needs to be present on both lock and unlock stacks, + // since they are used in both the unlock and lock scripts: + // ie. push("/entry/") and check_signature("/pubkey", "/entry/") + kvp_unlock.put(entry_key, &entry_data.to_vec().into()); + kvp_lock.put(entry_key, &entry_data.to_vec().into()); + + // "/entry/proof" only needs to be present on the unlock stack, + // since that's where the proof is used + kvp_unlock.put(proof_key, &proof_data.clone().into()); + + // "/pubkey" needs to be present on lock stack, to set what the PubKey is + kvp_lock.put(pubkey, &pub_key.into()); + + let unlock_script = format!( + r#" + // push the serialized Entry as the message + push("{entry_key}"); + + // push the proof data + push("{proof_key}"); + "# + ); + + let first_lock = format!( + r#" + // check the first key, which is ephemeral + check_signature("/ephemeral", "{entry_key}") + "# + ); + + let other_lock_script = format!( + r#" + // then check a possible threshold sig... + check_signature("/recoverykey", "{entry_key}") || + + // then check a possible pubkey sig... + check_signature("/pubkey", "{entry_key}") || + + // then the pre-image proof... + check_preimage("/hash") + "# + ); + + let mut comrade = Comrade::new(kvp_lock, kvp_unlock) + .try_unlock(&unlock_script) + .expect("Failed to unlock comrade"); + + // check the lock scripts + let locks = [first_lock, other_lock_script]; + + // check the locks + let mut count = 0; + for lock in locks { + if let Some(Value::Success(ct)) = + comrade.try_lock(&lock).expect("Failed to lock comrade") + { + count = ct; + break; + } + } + + // check the count + assert_eq!(count, 2); + } +} diff --git a/crates/comrade/src/runtime/layer/mod.rs b/crates/comrade/src/runtime/layer/mod.rs index ec9151a..1a52ba3 100644 --- a/crates/comrade/src/runtime/layer/mod.rs +++ b/crates/comrade/src/runtime/layer/mod.rs @@ -512,10 +512,6 @@ mod tests { let unlock = format!( r#" - // push pubkey - push("{pubkey}"); - - // push pubkey value // push the serialized Entry as the message push("{entry_key}"); @@ -533,7 +529,6 @@ mod tests { // "/entry/proof" only needs to be present on the unlock stack, // since that's where the proof is used kvp_unlock.put(proof_key, &proof_data.clone().into()); - kvp_lock.put(proof_key, &proof_data.clone().into()); kvp_lock.put(pubkey, &pub_key.into()); From 4f0263f3612bbb44620a2b6111ce2227e0a85b60 Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Sat, 10 May 2025 10:34:51 -0300 Subject: [PATCH 031/155] small fixes --- crates/comrade/src/lib.rs | 34 ++++- crates/comrade/src/runtime/layer/mod.rs | 7 +- crates/comrade/tests/web.rs | 173 +++++++++++++++++++++++- 3 files changed, 202 insertions(+), 12 deletions(-) diff --git a/crates/comrade/src/lib.rs b/crates/comrade/src/lib.rs index 3d17a0f..9320224 100644 --- a/crates/comrade/src/lib.rs +++ b/crates/comrade/src/lib.rs @@ -102,15 +102,22 @@ impl From> for Comrade); + pub struct TestData(HashMap); impl Pairs for TestData { fn get(&self, key: &str) -> Option { @@ -164,19 +171,34 @@ mod tests { ) } + // cfg test or wasm32 + #[cfg(any(target_arch = "wasm32", test))] + fn proof_data() -> Vec { + hex::decode("b92483a6c006000100404819397f51b18bc6cffd1fff07afa33f7096c7a0c659590b077cc0ea5d6081d739512129becacb8e6997e6b7d18756299f515a822344ac2b6737979d5e5e6b03").unwrap() + } + + #[cfg(any(target_arch = "wasm32", test))] + fn pub_key() -> Vec { + hex::decode("ba24ed010874657374206b657901012054d94d7b8a11d6581af4a14bc6451c7a23049018610f108c996968fe8fce9464").unwrap() + } + #[test] - fn test_comrade() { + pub fn test_comrade() { + test_comrade_api(); + } + + pub fn test_comrade_api() { // The message to sign, in both the lock and unlock scripts let entry_key = "/entry/"; let entry_data = b"for great justice, move every zig!"; // The proof data that is provided by the unlock script let proof_key = "/entry/proof"; - let proof_data = hex::decode("b92483a6c006000100404819397f51b18bc6cffd1fff07afa33f7096c7a0c659590b077cc0ea5d6081d739512129becacb8e6997e6b7d18756299f515a822344ac2b6737979d5e5e6b03").unwrap(); + let proof_data = proof_data(); // The public key to that must be proven by unlock scripts let pubkey = "/pubkey"; - let pub_key = hex::decode("ba24ed010874657374206b657901012054d94d7b8a11d6581af4a14bc6451c7a23049018610f108c996968fe8fce9464").unwrap(); + let pub_key = pub_key(); // Our Key-Value Pairs let mut kvp_unlock = TestData::default(); diff --git a/crates/comrade/src/runtime/layer/mod.rs b/crates/comrade/src/runtime/layer/mod.rs index 1a52ba3..79dce70 100644 --- a/crates/comrade/src/runtime/layer/mod.rs +++ b/crates/comrade/src/runtime/layer/mod.rs @@ -1,7 +1,10 @@ -//! Uses crate wasm_component_layer to runt he wasm components. +//! Uses crate wasm_component_layer to run the runtime as a wasm component. //! //! This crate is great because it gives us the ability to isomorphically run in the browser with //! wasm_bindgen but without javascript, and also natively. +//! +//! That said, the entire workspace could be packaged as a component, so this can be left to the +//! users. mod definitions; use definitions::{ @@ -63,7 +66,7 @@ where } impl Runner { - /// Create a new runner with a tracing logger. + /// Create a new wasm_compoennt_layer [Runner] with a tracing logger. pub(crate) fn new(kvp_current: C, kvp_proposed: P) -> Self { // Use default logger Self::new_with_logger(kvp_current, kvp_proposed, Box::new(TracingLogger)) diff --git a/crates/comrade/tests/web.rs b/crates/comrade/tests/web.rs index 6a7ed0a..8c41c18 100644 --- a/crates/comrade/tests/web.rs +++ b/crates/comrade/tests/web.rs @@ -1,20 +1,32 @@ //! Test to ensure that the crate runs in wasm32 target #![cfg(target_arch = "wasm32")] -use std::collections::HashMap; - -use comrade_reference::{Pairable, Pairs, Value}; use wasm_bindgen_test::wasm_bindgen_test_configure; use wasm_bindgen_test::*; + wasm_bindgen_test_configure!(run_in_browser); +use comrade_reference::{Pairable, Pairs, Value}; +use std::collections::HashMap; +use tracing::info; + +use comrade::wasm_tests::run as run_wasm_tests; + // Test which checks autotrait implementation for all types fn is_normal() {} fn is_not_send() {} +fn init_tracing() { + // Set up panic hook for better error messages + console_error_panic_hook::set_once(); + + // Initialize the tracing subscriber with console output + tracing_wasm::set_as_global_default(); +} + #[wasm_bindgen_test] -async fn test_wasm_autotraits() { +fn test_wasm_autotraits() { // This is a dummy test to ensure that the crate runs in wasm32 target // and that the autotrait implementation is what is expected. #[derive(Clone, Debug, Default)] @@ -33,3 +45,156 @@ async fn test_wasm_autotraits() { is_not_send::>(); // is_normal::>(); } + +#[wasm_bindgen_test] +fn test_comrade_wasm32_api() { + use comrade::Comrade; + use comrade_reference::Pairs; + use std::collections::HashMap; + + init_tracing(); + + info!("Starting test_example"); + + #[derive(Clone, Default, Debug)] + pub struct TestData(HashMap); + + impl Pairs for TestData { + fn get(&self, key: &str) -> Option { + self.0.get(key).cloned() + } + + fn put(&mut self, key: &str, value: &Value) -> Option { + self.0.insert(key.to_string(), value.clone()) + } + } + + fn unlock_script(entry_key: &str, proof_key: &str) -> String { + let unlock_script = format!( + r#" + // push the serialized Entry as the message + push("{entry_key}"); + + // push the proof data + push("{proof_key}"); + "# + ); + + unlock_script + } + + /// First lock is /ephemeral and {entry_key} + fn first_lock_script(entry_key: &str) -> String { + let first_lock = format!( + r#" + // check the first key, which is ephemeral + check_signature("/ephemeral", "{entry_key}") + "# + ); + + first_lock + } + + /// Other lock script + fn other_lock_script(entry_key: &str) -> String { + format!( + r#" + // then check a possible threshold sig... + check_signature("/recoverykey", "{entry_key}") || + + // then check a possible pubkey sig... + check_signature("/pubkey", "{entry_key}") || + + // then the pre-image proof... + check_preimage("/hash") + "# + ) + } + + // cfg test or wasm32 + fn proof_data() -> Vec { + hex::decode("b92483a6c006000100404819397f51b18bc6cffd1fff07afa33f7096c7a0c659590b077cc0ea5d6081d739512129becacb8e6997e6b7d18756299f515a822344ac2b6737979d5e5e6b03").unwrap() + } + + fn pub_key() -> Vec { + hex::decode("ba24ed010874657374206b657901012054d94d7b8a11d6581af4a14bc6451c7a23049018610f108c996968fe8fce9464").unwrap() + } + + // The message to sign, in both the lock and unlock scripts + let entry_key = "/entry/"; + let entry_data = b"for great justice, move every zig!"; + + // The proof data that is provided by the unlock script + let proof_key = "/entry/proof"; + let proof_data = proof_data(); + + // The public key to that must be proven by unlock scripts + let pubkey = "/pubkey"; + let pub_key = pub_key(); + + // Our Key-Value Pairs + let mut kvp_unlock = TestData::default(); + let mut kvp_lock = TestData::default(); + + // "/entry/" needs to be present on both lock and unlock stacks, + // since they are used in both the unlock and lock scripts: + // ie. push("/entry/") and check_signature("/pubkey", "/entry/") + kvp_unlock.put(entry_key, &entry_data.to_vec().into()); + kvp_lock.put(entry_key, &entry_data.to_vec().into()); + + // "/entry/proof" only needs to be present on the unlock stack, + // since that's where the proof is used + kvp_unlock.put(proof_key, &proof_data.clone().into()); + + // "/pubkey" needs to be present on lock stack, to set what the PubKey is + kvp_lock.put(pubkey, &pub_key.into()); + + let unlock_script = format!( + r#" + // push the serialized Entry as the message + push("{entry_key}"); + + // push the proof data + push("{proof_key}"); + "# + ); + + let first_lock = format!( + r#" + // check the first key, which is ephemeral + check_signature("/ephemeral", "{entry_key}") + "# + ); + + let other_lock_script = format!( + r#" + // then check a possible threshold sig... + check_signature("/recoverykey", "{entry_key}") || + + // then check a possible pubkey sig... + check_signature("/pubkey", "{entry_key}") || + + // then the pre-image proof... + check_preimage("/hash") + "# + ); + + let mut comrade = Comrade::new(kvp_lock, kvp_unlock) + .try_unlock(&unlock_script) + .expect("Failed to unlock comrade"); + + // check the lock scripts + let locks = [first_lock, other_lock_script]; + + // check the locks + let mut count = 0; + for lock in locks { + if let Some(Value::Success(ct)) = comrade.try_lock(&lock).expect("Failed to lock comrade") { + count = ct; + break; + } + } + + // check the count + assert_eq!(count, 2); +} From 8fae6e585007d3d71f339e37b6bede9d9fe7f6cf Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Sat, 10 May 2025 10:35:10 -0300 Subject: [PATCH 032/155] add direct runtime --- crates/comrade/src/runtime/direct/logs.rs | 9 +++++++ crates/comrade/src/runtime/direct/mod.rs | 33 +++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 crates/comrade/src/runtime/direct/logs.rs create mode 100644 crates/comrade/src/runtime/direct/mod.rs diff --git a/crates/comrade/src/runtime/direct/logs.rs b/crates/comrade/src/runtime/direct/logs.rs new file mode 100644 index 0000000..dbac984 --- /dev/null +++ b/crates/comrade/src/runtime/direct/logs.rs @@ -0,0 +1,9 @@ +use comrade_reference::Log; + +pub(crate) struct Logger; + +impl Log for Logger { + fn log(&self, msg: &str) { + tracing::info!("{}", msg); + } +} diff --git a/crates/comrade/src/runtime/direct/mod.rs b/crates/comrade/src/runtime/direct/mod.rs new file mode 100644 index 0000000..0bad2b0 --- /dev/null +++ b/crates/comrade/src/runtime/direct/mod.rs @@ -0,0 +1,33 @@ +//! Use the comrrade-reference implementation of the runtime as a direct dependency. +mod logs; + +use super::Runtime; +use comrade_reference::{Context, Pairable, Value}; + +/// The "Direct" runtime uses the comrade-reference implementation directly in rust +/// and is the default runtime for Comrade. +pub(crate) struct Runner<'a> { + pub(crate) context: Context<'a>, +} + +impl<'a> Runner<'a> { + /// Creates a new Runner instance with the given lock and unlock pairs. + pub fn new(kvp_lock: &'a impl Pairable, kvp_unlock: &'a impl Pairable) -> Self { + Self { + context: Context::new(kvp_lock, kvp_unlock, &logs::Logger), + } + } +} + +impl<'a> Runtime for Runner<'a> { + fn try_unlock(&mut self, script: &str) -> Result<(), crate::Error> { + self.context.run(script)?; + Ok(()) + } + + fn try_lock(&mut self, script: &str) -> Result, crate::Error> { + self.context.run(script)?; + let rstack = self.context.rstack(); + Ok(rstack.map(|v| v.into())) + } +} From 9c041222d729875d4dbc6af64eaef02b0523c180 Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Sat, 10 May 2025 10:35:43 -0300 Subject: [PATCH 033/155] switch to direct runtime as default --- crates/comrade/src/error.rs | 4 + crates/comrade/src/lib.rs | 209 ++++++++++++++---------------- crates/comrade/src/runtime/mod.rs | 25 ++-- 3 files changed, 119 insertions(+), 119 deletions(-) diff --git a/crates/comrade/src/error.rs b/crates/comrade/src/error.rs index fc98439..7203369 100644 --- a/crates/comrade/src/error.rs +++ b/crates/comrade/src/error.rs @@ -8,6 +8,10 @@ pub enum Error { /// Error in the Script #[error("Script has failed to run, yuo likely have an error in your script: {0}")] ScriptFailure(String), + + /// APiError + #[error(transparent)] + ApiError(#[from] comrade_reference::ApiError), // /// Error in the VM // #[error("VM error: {0}")] // Vm(#[from] wasmi::Error), diff --git a/crates/comrade/src/lib.rs b/crates/comrade/src/lib.rs index 9320224..d4e1b30 100644 --- a/crates/comrade/src/lib.rs +++ b/crates/comrade/src/lib.rs @@ -32,12 +32,12 @@ pub use crate::error::Error; /// The runtime environment for the scripts mod runtime; -/// Polyfills required to ensure getrandom works in wasm32 target for v0.3 +/// Polyfills required to ensure getrandom v0.3 works in wasm32 targets #[cfg(target_arch = "wasm32")] mod random; // Using the same trait out of convenience, the Pairs trait is very basic -use comrade_reference::{Pairable, Pairs, Value}; +use comrade_reference::{Pairable, Value}; use runtime::Runtime as _; /// Comrade goes starts at [Initial] Stage, then goes to [Unlocked] Stage. @@ -52,36 +52,34 @@ pub struct Unlocked; /// Uses the comrade-component reference implementation by default, /// and wasm_component_layer for runtime. Either can be substituted /// with prefered alternatives as desired. -pub struct Comrade { +pub struct Comrade<'a, Stage = Initial> { // /// The key-value pairs asociated with the lock // kvp_lock: C, // /// The key-value pairs asociated with the unlock // kvp_unlock: P, - runner: runtime::Runner, + runner: runtime::Runner<'a>, _stage: std::marker::PhantomData, } -impl Comrade { +impl<'a> Comrade<'a> { /// Creates a new Comrade instance with the given lock and unlock pairs. - pub fn new(kvp_lock: C, kvp_unlock: P) -> Self { + pub fn new(kvp_lock: &'a impl Pairable, kvp_unlock: &'a impl Pairable) -> Self { Comrade { runner: runtime::Runner::new(kvp_lock, kvp_unlock), - // kvp_lock, - // kvp_unlock, _stage: std::marker::PhantomData, } } /// Tries to unlock the comrade with the given script. /// Will return an error if the script fails to run. - pub fn try_unlock(mut self, script: &str) -> Result, Error> { + pub fn try_unlock(mut self, script: &'a str) -> Result, Error> { self.runner.try_unlock(script)?; Ok(self.into()) } } // try_lock can only be called on an Unlocked Comrade -impl Comrade { +impl Comrade<'_, Unlocked> { /// Tries to lock the comrade with the given script. /// Will return an error if the script fails to run. pub fn try_lock(&mut self, script: &str) -> Result, Error> { @@ -91,8 +89,8 @@ impl Comrade { } // from Initial to Unlocked -impl From> for Comrade { - fn from(comrade: Comrade) -> Self { +impl<'a> From> for Comrade<'a, Unlocked> { + fn from(comrade: Comrade<'a>) -> Self { Comrade { // kvp_lock: comrade.kvp_lock, // kvp_unlock: comrade.kvp_unlock, @@ -102,16 +100,8 @@ impl From> for Comrade String { - let unlock_script = format!( - r#" + // Helper functions that set up test data + pub mod helpers { + use super::*; + + // Autotrait test + pub(crate) fn is_normal() {} + + pub fn unlock_script(entry_key: &str, proof_key: &str) -> String { + format!( + r#" // push the serialized Entry as the message push("{entry_key}"); // push the proof data push("{proof_key}"); "# - ); - - unlock_script - } + ) + } - /// First lock is /ephemeral and {entry_key} - fn first_lock_script(entry_key: &str) -> String { - let first_lock = format!( - r#" + pub fn first_lock_script(entry_key: &str) -> String { + format!( + r#" // check the first key, which is ephemeral check_signature("/ephemeral", "{entry_key}") "# - ); - - first_lock - } + ) + } - /// Other lock script - fn other_lock_script(entry_key: &str) -> String { - format!( - r#" + pub fn other_lock_script(entry_key: &str) -> String { + format!( + r#" // then check a possible threshold sig... check_signature("/recoverykey", "{entry_key}") || @@ -168,86 +159,60 @@ pub mod tests { // then the pre-image proof... check_preimage("/hash") "# - ) - } + ) + } - // cfg test or wasm32 - #[cfg(any(target_arch = "wasm32", test))] - fn proof_data() -> Vec { - hex::decode("b92483a6c006000100404819397f51b18bc6cffd1fff07afa33f7096c7a0c659590b077cc0ea5d6081d739512129becacb8e6997e6b7d18756299f515a822344ac2b6737979d5e5e6b03").unwrap() - } + pub fn proof_data() -> Vec { + hex::decode("b92483a6c006000100404819397f51b18bc6cffd1fff07afa33f7096c7a0c659590b077cc0ea5d6081d739512129becacb8e6997e6b7d18756299f515a822344ac2b6737979d5e5e6b03").unwrap() + } - #[cfg(any(target_arch = "wasm32", test))] - fn pub_key() -> Vec { - hex::decode("ba24ed010874657374206b657901012054d94d7b8a11d6581af4a14bc6451c7a23049018610f108c996968fe8fce9464").unwrap() - } + pub fn pub_key() -> Vec { + hex::decode("ba24ed010874657374206b657901012054d94d7b8a11d6581af4a14bc6451c7a23049018610f108c996968fe8fce9464").unwrap() + } - #[test] - pub fn test_comrade() { - test_comrade_api(); + // Creates a setup with all necessary key-value pairs + pub fn setup_test_data() -> (TestData, TestData, String) { + // The message to sign, in both the lock and unlock scripts + let entry_key = "/entry/"; + let entry_data = b"for great justice, move every zig!"; + + // The proof data that is provided by the unlock script + let proof_key = "/entry/proof"; + let proof_data = proof_data(); + + // The public key to that must be proven by unlock scripts + let pubkey = "/pubkey"; + let pub_key = pub_key(); + + // Our Key-Value Pairs + let mut kvp_unlock = TestData::default(); + let mut kvp_lock = TestData::default(); + + // Set up the key-value pairs + kvp_unlock.put(entry_key, &entry_data.to_vec().into()); + kvp_lock.put(entry_key, &entry_data.to_vec().into()); + kvp_unlock.put(proof_key, &proof_data.clone().into()); + kvp_lock.put(pubkey, &pub_key.into()); + + // Return the configured test data and entry key + (kvp_lock, kvp_unlock, entry_key.to_string()) + } } + // The main test function that both native tests and WASM tests will call pub fn test_comrade_api() { - // The message to sign, in both the lock and unlock scripts - let entry_key = "/entry/"; - let entry_data = b"for great justice, move every zig!"; + use helpers::*; - // The proof data that is provided by the unlock script + // Get our test setup + let (kvp_lock, kvp_unlock, entry_key) = setup_test_data(); let proof_key = "/entry/proof"; - let proof_data = proof_data(); - - // The public key to that must be proven by unlock scripts - let pubkey = "/pubkey"; - let pub_key = pub_key(); - - // Our Key-Value Pairs - let mut kvp_unlock = TestData::default(); - let mut kvp_lock = TestData::default(); - - // "/entry/" needs to be present on both lock and unlock stacks, - // since they are used in both the unlock and lock scripts: - // ie. push("/entry/") and check_signature("/pubkey", "/entry/") - kvp_unlock.put(entry_key, &entry_data.to_vec().into()); - kvp_lock.put(entry_key, &entry_data.to_vec().into()); - - // "/entry/proof" only needs to be present on the unlock stack, - // since that's where the proof is used - kvp_unlock.put(proof_key, &proof_data.clone().into()); - - // "/pubkey" needs to be present on lock stack, to set what the PubKey is - kvp_lock.put(pubkey, &pub_key.into()); - - let unlock_script = format!( - r#" - // push the serialized Entry as the message - push("{entry_key}"); - - // push the proof data - push("{proof_key}"); - "# - ); - - let first_lock = format!( - r#" - // check the first key, which is ephemeral - check_signature("/ephemeral", "{entry_key}") - "# - ); - - let other_lock_script = format!( - r#" - // then check a possible threshold sig... - check_signature("/recoverykey", "{entry_key}") || - - // then check a possible pubkey sig... - check_signature("/pubkey", "{entry_key}") || - - // then the pre-image proof... - check_preimage("/hash") - "# - ); - - let mut comrade = Comrade::new(kvp_lock, kvp_unlock) + + // Create the scripts + let unlock_script = unlock_script(&entry_key, proof_key); + let first_lock = first_lock_script(&entry_key); + let other_lock_script = other_lock_script(&entry_key); + + let mut comrade = Comrade::new(&kvp_lock, &kvp_unlock) .try_unlock(&unlock_script) .expect("Failed to unlock comrade"); @@ -268,4 +233,26 @@ pub mod tests { // check the count assert_eq!(count, 2); } + + // Autotrait test for non-wasm32 targets + #[test] + #[cfg(not(target_arch = "wasm32"))] + fn test_autotraits() { + // This is a dummy test to ensure that the crate runs in wasm32 target + // and that the autotrait implementation is what is expected. + helpers::is_normal::>(); + } + + // This is the actual test that cargo test will run + #[test] + pub fn test_comrade_public_api() { + test_comrade_api(); + } +} + +#[cfg(target_arch = "wasm32")] +pub mod wasm_tests { + pub fn run() { + super::tests::test_comrade_api(); + } } diff --git a/crates/comrade/src/runtime/mod.rs b/crates/comrade/src/runtime/mod.rs index e9b5b51..b079a63 100644 --- a/crates/comrade/src/runtime/mod.rs +++ b/crates/comrade/src/runtime/mod.rs @@ -2,15 +2,24 @@ use crate::Error; use comrade_reference::Value; -/// Feature `wasm_component_layer` enables the use of a wasm component layer -// Commented out for now, so we get rust-analyzer to not complain about unused code -// #[cfg(feature = "wasm_component_layer")] -mod layer; -// #[cfg(feature = "wasm_component_layer")] -pub(crate) use layer::Runner; +/// The "Direct" runtime is the default runtime for Comrade. +/// It uses the comrade-reference implementation directlt in rust +/// without any wasm component. +/// +/// This way, we can run the entire workspace in rust without any wasm, +/// or as a wasm component itself. +mod direct; +pub(crate) use direct::Runner; -// Other wasm runtimes can be used: -// mod wasm_time; +// NOTE: In the future, we could support other runtimes at the runtime level. +// For example, the wasm runtime uses a wasm component layer to run the script +// instead of rust code. +// #[cfg(feature = "runtime-wasm")] +// mod layer; +// #[cfg(feature = "runtime-wasm")] +// pub(crate) use layer::Runner; +// Or other wasm runtimes can be used: +// mod wasmtime; // mod wasm_i; // mod wasmer; From cf20a8bb903a22bcd364de18c2e4888425448f49 Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Sat, 10 May 2025 10:36:23 -0300 Subject: [PATCH 034/155] web tests --- crates/comrade/Cargo.toml | 10 +-- crates/comrade/README.md | 2 +- crates/comrade/justfile | 12 +-- crates/comrade/tests/web.rs | 156 ++---------------------------------- 4 files changed, 18 insertions(+), 162 deletions(-) diff --git a/crates/comrade/Cargo.toml b/crates/comrade/Cargo.toml index 3a035ee..5c61422 100644 --- a/crates/comrade/Cargo.toml +++ b/crates/comrade/Cargo.toml @@ -12,7 +12,7 @@ comrade-reference.workspace = true thiserror = "2.0" # wasm_component_layer gives us an isomorphic way to load components # in native or the browser directly from Rust -# patch until thos lands: https://github.com/DouglasDwyer/wasm_component_layer/pull/26 +# patch until this lands: https://github.com/DouglasDwyer/wasm_component_layer/pull/26 wasm_component_layer = { git = "https://github.com/DougAnderson444/wasm_component_layer.git", branch = "dep-fix", optional = true } tracing = { version = "0.1", features = ["log"] } tracing-subscriber = { version = "0.3", features = ["std"] } @@ -23,19 +23,19 @@ anyhow = "1.0" wasmi_runtime_layer = "=0.40.0" # pin to 0.40 to match wasm_runtime_layer 0.4.2 [target.'cfg(target_arch = "wasm32")'.dependencies] -js_wasm_runtime_layer = "0.4.0" +js_wasm_runtime_layer = { version = "0.4.0", features = ["tracing"] } wasm-bindgen = "0.2" getrandom = { version = "0.3", features = ["wasm_js"] } # We have netsed deps that use v0.2, so we need to ensure the feature is flagged here getrandom_v02 = { package = "getrandom", version = "0.2.15", features = ["js"] } web-sys = { version = "0.3", features = ["Window", "Crypto"] } +hex = "0.4" +console_error_panic_hook = "0.1" +tracing-wasm = "0.2" # For sending tracing events to browser console [dev-dependencies] wasm-bindgen-test = "0.3" hex = "0.4" -[features] -default = ["wasm_component_layer"] - [lints] workspace = true diff --git a/crates/comrade/README.md b/crates/comrade/README.md index 65d9d2b..48d1f76 100644 --- a/crates/comrade/README.md +++ b/crates/comrade/README.md @@ -86,7 +86,7 @@ let other_lock_script = format!( "# ); -let mut comrade = Comrade::new(kvp_lock, kvp_unlock) +let mut comrade = Comrade::new(&kvp_lock, &kvp_unlock) .try_unlock(&unlock_script) .expect("Failed to unlock comrade"); diff --git a/crates/comrade/justfile b/crates/comrade/justfile index ac5b11b..759faa8 100644 --- a/crates/comrade/justfile +++ b/crates/comrade/justfile @@ -1,10 +1,12 @@ -build: +build-component: # Run Cargo Component Build first just -f ../../justfile build -test-wasm32: +# To call headless +# just test-wasm32 --headless +test-wasm32 *args: # wasm32 test for the browser - RUSTFLAGS='--cfg getrandom_backend="wasm_js"' wasm-pack test --headless --chrome --all-features - -test: build test-wasm32 + RUSTFLAGS='--cfg getrandom_backend="wasm_js"' wasm-pack test --chrome {{args}} --all-features +test: + just test-wasm32 --headless cargo test diff --git a/crates/comrade/tests/web.rs b/crates/comrade/tests/web.rs index 8c41c18..3bdad2e 100644 --- a/crates/comrade/tests/web.rs +++ b/crates/comrade/tests/web.rs @@ -13,8 +13,6 @@ use tracing::info; use comrade::wasm_tests::run as run_wasm_tests; // Test which checks autotrait implementation for all types -fn is_normal() {} - fn is_not_send() {} fn init_tracing() { @@ -42,159 +40,15 @@ fn test_wasm_autotraits() { } } - is_not_send::>(); - // is_normal::>(); + is_not_send::>(); } #[wasm_bindgen_test] -fn test_comrade_wasm32_api() { - use comrade::Comrade; - use comrade_reference::Pairs; - use std::collections::HashMap; - +fn web_test() { init_tracing(); - info!("Starting test_example"); - - #[derive(Clone, Default, Debug)] - pub struct TestData(HashMap); - - impl Pairs for TestData { - fn get(&self, key: &str) -> Option { - self.0.get(key).cloned() - } - - fn put(&mut self, key: &str, value: &Value) -> Option { - self.0.insert(key.to_string(), value.clone()) - } - } - - fn unlock_script(entry_key: &str, proof_key: &str) -> String { - let unlock_script = format!( - r#" - // push the serialized Entry as the message - push("{entry_key}"); - - // push the proof data - push("{proof_key}"); - "# - ); - - unlock_script - } - - /// First lock is /ephemeral and {entry_key} - fn first_lock_script(entry_key: &str) -> String { - let first_lock = format!( - r#" - // check the first key, which is ephemeral - check_signature("/ephemeral", "{entry_key}") - "# - ); - - first_lock - } - - /// Other lock script - fn other_lock_script(entry_key: &str) -> String { - format!( - r#" - // then check a possible threshold sig... - check_signature("/recoverykey", "{entry_key}") || - - // then check a possible pubkey sig... - check_signature("/pubkey", "{entry_key}") || - - // then the pre-image proof... - check_preimage("/hash") - "# - ) - } - - // cfg test or wasm32 - fn proof_data() -> Vec { - hex::decode("b92483a6c006000100404819397f51b18bc6cffd1fff07afa33f7096c7a0c659590b077cc0ea5d6081d739512129becacb8e6997e6b7d18756299f515a822344ac2b6737979d5e5e6b03").unwrap() - } - - fn pub_key() -> Vec { - hex::decode("ba24ed010874657374206b657901012054d94d7b8a11d6581af4a14bc6451c7a23049018610f108c996968fe8fce9464").unwrap() - } - - // The message to sign, in both the lock and unlock scripts - let entry_key = "/entry/"; - let entry_data = b"for great justice, move every zig!"; - - // The proof data that is provided by the unlock script - let proof_key = "/entry/proof"; - let proof_data = proof_data(); - - // The public key to that must be proven by unlock scripts - let pubkey = "/pubkey"; - let pub_key = pub_key(); - - // Our Key-Value Pairs - let mut kvp_unlock = TestData::default(); - let mut kvp_lock = TestData::default(); - - // "/entry/" needs to be present on both lock and unlock stacks, - // since they are used in both the unlock and lock scripts: - // ie. push("/entry/") and check_signature("/pubkey", "/entry/") - kvp_unlock.put(entry_key, &entry_data.to_vec().into()); - kvp_lock.put(entry_key, &entry_data.to_vec().into()); - - // "/entry/proof" only needs to be present on the unlock stack, - // since that's where the proof is used - kvp_unlock.put(proof_key, &proof_data.clone().into()); - - // "/pubkey" needs to be present on lock stack, to set what the PubKey is - kvp_lock.put(pubkey, &pub_key.into()); - - let unlock_script = format!( - r#" - // push the serialized Entry as the message - push("{entry_key}"); - - // push the proof data - push("{proof_key}"); - "# - ); - - let first_lock = format!( - r#" - // check the first key, which is ephemeral - check_signature("/ephemeral", "{entry_key}") - "# - ); - - let other_lock_script = format!( - r#" - // then check a possible threshold sig... - check_signature("/recoverykey", "{entry_key}") || - - // then check a possible pubkey sig... - check_signature("/pubkey", "{entry_key}") || - - // then the pre-image proof... - check_preimage("/hash") - "# - ); - - let mut comrade = Comrade::new(kvp_lock, kvp_unlock) - .try_unlock(&unlock_script) - .expect("Failed to unlock comrade"); - - // check the lock scripts - let locks = [first_lock, other_lock_script]; - - // check the locks - let mut count = 0; - for lock in locks { - if let Some(Value::Success(ct)) = comrade.try_lock(&lock).expect("Failed to lock comrade") { - count = ct; - break; - } - } + tracing::info!("Starting web_test"); - // check the count - assert_eq!(count, 2); + // Import and run the same tests that are in lib.rs + comrade::wasm_tests::run(); } From 17661742e14bcab2690a4b1f99be21a13304f6df Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Sat, 10 May 2025 10:36:32 -0300 Subject: [PATCH 035/155] rm dead code --- crates/comrade-component/src/lib.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/comrade-component/src/lib.rs b/crates/comrade-component/src/lib.rs index e4beed9..731d12e 100644 --- a/crates/comrade-component/src/lib.rs +++ b/crates/comrade-component/src/lib.rs @@ -18,7 +18,6 @@ use std::cell::RefCell; struct Api { context: RefCell>, - unlock: RefCell>, } impl Guest for Api { @@ -31,14 +30,11 @@ impl GuestApi for Api { Self { context: RefCell::new(Context::new(&Current, &Proposed, &Logger)), - unlock: RefCell::new(None), } } fn try_unlock(&self, unlock: String) -> Result<(), String> { log("[component] try_unlock(..)"); - self.unlock.borrow_mut().replace(unlock.clone()); - // self.vm.run(&unlock).map_err(|e| e.to_string())?; self.context.borrow_mut().run(&unlock).map_err(|e| { log(&format!("Error running unlock script: {e}")); format!("Error running unlock script: {e}") From 021e7017193bde33cc3d959b1a8d84b51818e6d3 Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Sat, 10 May 2025 10:37:02 -0300 Subject: [PATCH 036/155] use CondSync since wasm32 doesn't need Send + Sync --- crates/comrade-reference/src/cond_send.rs | 33 +++++++++++++++++++ crates/comrade-reference/src/context.rs | 5 +-- crates/comrade-reference/src/context/pairs.rs | 4 ++- crates/comrade-reference/src/lib.rs | 2 ++ 4 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 crates/comrade-reference/src/cond_send.rs diff --git a/crates/comrade-reference/src/cond_send.rs b/crates/comrade-reference/src/cond_send.rs new file mode 100644 index 0000000..df30005 --- /dev/null +++ b/crates/comrade-reference/src/cond_send.rs @@ -0,0 +1,33 @@ +//! Utilities for conditionally adding `Send` and `Sync` constraints. + +/// A conditionally compiled trait indirection for `Send` bounds. +/// This target makes it require `Send`. +#[cfg(not(target_arch = "wasm32"))] +pub trait CondSend: Send {} + +/// A conditionally compiled trait indirection for `Send` bounds. +/// This target makes it not require any marker traits. +#[cfg(target_arch = "wasm32")] +pub trait CondSend {} + +#[cfg(not(target_arch = "wasm32"))] +impl CondSend for S where S: Send {} + +#[cfg(target_arch = "wasm32")] +impl CondSend for S {} + +/// A conditionally compiled trait indirection for `Send + Sync` bounds. +/// This target makes it require `Send + Sync`. +#[cfg(not(target_arch = "wasm32"))] +pub trait CondSync: Send + Sync {} + +/// A conditionally compiled trait indirection for `Send + Sync` bounds. +/// This target makes it not require any marker traits. +#[cfg(target_arch = "wasm32")] +pub trait CondSync {} + +#[cfg(not(target_arch = "wasm32"))] +impl CondSync for S where S: Send + Sync {} + +#[cfg(target_arch = "wasm32")] +impl CondSync for S {} diff --git a/crates/comrade-reference/src/context.rs b/crates/comrade-reference/src/context.rs index 525f2b9..d2329fa 100644 --- a/crates/comrade-reference/src/context.rs +++ b/crates/comrade-reference/src/context.rs @@ -14,7 +14,7 @@ mod parser; pub(crate) use parser::Rule; use parser::{parse, Expression, Function, Key}; -use crate::ApiError; +use crate::{cond_send::CondSync, ApiError}; use multihash::{mh, Multihash}; use multikey::{Multikey, Views as _}; use multisig::Multisig; @@ -46,7 +46,7 @@ pub struct Context<'a> { } /// Log a message to the console -pub trait Log { +pub trait Log: CondSync { fn log(&self, msg: &str); } @@ -394,6 +394,7 @@ impl<'a> Context<'a> { s } + /// Returns the top of the return stack pub fn rstack(&self) -> Option { self.rstack.top() } diff --git a/crates/comrade-reference/src/context/pairs.rs b/crates/comrade-reference/src/context/pairs.rs index 7dc5ee7..22efd6b 100644 --- a/crates/comrade-reference/src/context/pairs.rs +++ b/crates/comrade-reference/src/context/pairs.rs @@ -1,3 +1,5 @@ +use crate::cond_send::CondSync; + use super::value::Value; use std::fmt::Debug; @@ -6,7 +8,7 @@ pub trait Pairable: Pairs + Send + Sync + 'static {} impl Pairable for P {} /// Trait to a key-value storage mechanism -pub trait Pairs: Debug { +pub trait Pairs: CondSync + Debug { /// get a value associated with the key fn get(&self, key: &str) -> Option; diff --git a/crates/comrade-reference/src/lib.rs b/crates/comrade-reference/src/lib.rs index 27e8404..992d051 100644 --- a/crates/comrade-reference/src/lib.rs +++ b/crates/comrade-reference/src/lib.rs @@ -6,3 +6,5 @@ pub use error::ApiError; mod context; /// Public re-exports pub use context::{Context, Log, Pairable, Pairs, Stack, Value}; + +mod cond_send; From 9c619ae0166e670bf7d87bb1aa4ae6c8cb3b91db Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Mon, 12 May 2025 10:32:38 -0300 Subject: [PATCH 037/155] comrade use Pairs only rm Pairable --- crates/comrade-reference/src/cond_send.rs | 33 ------------------- crates/comrade-reference/src/context.rs | 20 +++++------ crates/comrade-reference/src/context/pairs.rs | 9 +---- crates/comrade-reference/src/lib.rs | 4 +-- crates/comrade/src/lib.rs | 24 +++++++------- crates/comrade/src/runtime/direct/mod.rs | 15 ++++----- crates/comrade/src/runtime/layer/mod.rs | 16 ++++----- crates/comrade/tests/web.rs | 2 +- 8 files changed, 41 insertions(+), 82 deletions(-) delete mode 100644 crates/comrade-reference/src/cond_send.rs diff --git a/crates/comrade-reference/src/cond_send.rs b/crates/comrade-reference/src/cond_send.rs deleted file mode 100644 index df30005..0000000 --- a/crates/comrade-reference/src/cond_send.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! Utilities for conditionally adding `Send` and `Sync` constraints. - -/// A conditionally compiled trait indirection for `Send` bounds. -/// This target makes it require `Send`. -#[cfg(not(target_arch = "wasm32"))] -pub trait CondSend: Send {} - -/// A conditionally compiled trait indirection for `Send` bounds. -/// This target makes it not require any marker traits. -#[cfg(target_arch = "wasm32")] -pub trait CondSend {} - -#[cfg(not(target_arch = "wasm32"))] -impl CondSend for S where S: Send {} - -#[cfg(target_arch = "wasm32")] -impl CondSend for S {} - -/// A conditionally compiled trait indirection for `Send + Sync` bounds. -/// This target makes it require `Send + Sync`. -#[cfg(not(target_arch = "wasm32"))] -pub trait CondSync: Send + Sync {} - -/// A conditionally compiled trait indirection for `Send + Sync` bounds. -/// This target makes it not require any marker traits. -#[cfg(target_arch = "wasm32")] -pub trait CondSync {} - -#[cfg(not(target_arch = "wasm32"))] -impl CondSync for S where S: Send + Sync {} - -#[cfg(target_arch = "wasm32")] -impl CondSync for S {} diff --git a/crates/comrade-reference/src/context.rs b/crates/comrade-reference/src/context.rs index d2329fa..70e4670 100644 --- a/crates/comrade-reference/src/context.rs +++ b/crates/comrade-reference/src/context.rs @@ -2,7 +2,7 @@ //! is evaluated mod pairs; -pub use pairs::{Pairable, Pairs}; +pub use pairs::Pairs; mod value; pub use value::Value; @@ -14,7 +14,7 @@ mod parser; pub(crate) use parser::Rule; use parser::{parse, Expression, Function, Key}; -use crate::{cond_send::CondSync, ApiError}; +use crate::ApiError; use multihash::{mh, Multihash}; use multikey::{Multikey, Views as _}; use multisig::Multisig; @@ -22,12 +22,12 @@ use multiutil::prelude::*; /// The [Context] within which the script is /// evaluated. -pub struct Context<'a> { +pub struct Context<'un, 'lo> { /// The Return stack - pub(crate) current: &'a dyn Pairs, + pub(crate) current: &'un dyn Pairs, /// The Parameters stack - pub(crate) proposed: &'a dyn Pairs, + pub(crate) proposed: &'lo dyn Pairs, /// The number of checks that have been performed pub(crate) check_count: usize, @@ -42,18 +42,18 @@ pub struct Context<'a> { pub domain: String, /// The log implementation for the context - pub(crate) logger: &'a dyn Log, + pub(crate) logger: &'un dyn Log, } /// Log a message to the console -pub trait Log: CondSync { +pub trait Log { fn log(&self, msg: &str); } -impl<'a> Context<'a> { +impl<'un, 'lo> Context<'un, 'lo> { /// Create a new [Context] struct with the given [Current] and [Proposed] key-value stores, - /// which are bound by both [Pairable]. - pub fn new(current: &'a impl Pairs, proposed: &'a impl Pairs, logger: &'a impl Log) -> Self { + /// which are bound by both [Pairs]. + pub fn new(current: &'un impl Pairs, proposed: &'lo impl Pairs, logger: &'un impl Log) -> Self { Context { current, proposed, diff --git a/crates/comrade-reference/src/context/pairs.rs b/crates/comrade-reference/src/context/pairs.rs index 22efd6b..90a470d 100644 --- a/crates/comrade-reference/src/context/pairs.rs +++ b/crates/comrade-reference/src/context/pairs.rs @@ -1,14 +1,7 @@ -use crate::cond_send::CondSync; - use super::value::Value; -use std::fmt::Debug; - -/// Trait Pairable is: [Pairs], [Send], [Sync], [Clone], [Debug] -pub trait Pairable: Pairs + Send + Sync + 'static {} -impl Pairable for P {} /// Trait to a key-value storage mechanism -pub trait Pairs: CondSync + Debug { +pub trait Pairs { /// get a value associated with the key fn get(&self, key: &str) -> Option; diff --git a/crates/comrade-reference/src/lib.rs b/crates/comrade-reference/src/lib.rs index 992d051..b80e7bd 100644 --- a/crates/comrade-reference/src/lib.rs +++ b/crates/comrade-reference/src/lib.rs @@ -5,6 +5,4 @@ pub use error::ApiError; /// Context is the main entry of this crate mod context; /// Public re-exports -pub use context::{Context, Log, Pairable, Pairs, Stack, Value}; - -mod cond_send; +pub use context::{Context, Log, Pairs, Stack, Value}; diff --git a/crates/comrade/src/lib.rs b/crates/comrade/src/lib.rs index d4e1b30..9c80141 100644 --- a/crates/comrade/src/lib.rs +++ b/crates/comrade/src/lib.rs @@ -37,7 +37,7 @@ mod runtime; mod random; // Using the same trait out of convenience, the Pairs trait is very basic -use comrade_reference::{Pairable, Value}; +pub use comrade_reference::{Pairs, Value}; use runtime::Runtime as _; /// Comrade goes starts at [Initial] Stage, then goes to [Unlocked] Stage. @@ -52,18 +52,18 @@ pub struct Unlocked; /// Uses the comrade-component reference implementation by default, /// and wasm_component_layer for runtime. Either can be substituted /// with prefered alternatives as desired. -pub struct Comrade<'a, Stage = Initial> { +pub struct Comrade<'un, 'lo, Stage = Initial> { // /// The key-value pairs asociated with the lock // kvp_lock: C, // /// The key-value pairs asociated with the unlock // kvp_unlock: P, - runner: runtime::Runner<'a>, + runner: runtime::Runner<'un, 'lo>, _stage: std::marker::PhantomData, } -impl<'a> Comrade<'a> { +impl<'un, 'lo> Comrade<'un, 'lo> { /// Creates a new Comrade instance with the given lock and unlock pairs. - pub fn new(kvp_lock: &'a impl Pairable, kvp_unlock: &'a impl Pairable) -> Self { + pub fn new(kvp_lock: &'un impl Pairs, kvp_unlock: &'lo impl Pairs) -> Self { Comrade { runner: runtime::Runner::new(kvp_lock, kvp_unlock), _stage: std::marker::PhantomData, @@ -72,14 +72,14 @@ impl<'a> Comrade<'a> { /// Tries to unlock the comrade with the given script. /// Will return an error if the script fails to run. - pub fn try_unlock(mut self, script: &'a str) -> Result, Error> { + pub fn try_unlock(mut self, script: &'un str) -> Result, Error> { self.runner.try_unlock(script)?; Ok(self.into()) } } // try_lock can only be called on an Unlocked Comrade -impl Comrade<'_, Unlocked> { +impl Comrade<'_, '_, Unlocked> { /// Tries to lock the comrade with the given script. /// Will return an error if the script fails to run. pub fn try_lock(&mut self, script: &str) -> Result, Error> { @@ -89,8 +89,8 @@ impl Comrade<'_, Unlocked> { } // from Initial to Unlocked -impl<'a> From> for Comrade<'a, Unlocked> { - fn from(comrade: Comrade<'a>) -> Self { +impl<'un, 'lo> From> for Comrade<'un, 'lo, Unlocked> { + fn from(comrade: Comrade<'un, 'lo>) -> Self { Comrade { // kvp_lock: comrade.kvp_lock, // kvp_unlock: comrade.kvp_unlock, @@ -124,7 +124,9 @@ pub mod tests { use super::*; // Autotrait test - pub(crate) fn is_normal() {} + // Comrade is not Send or Sync, but it is Unpin + // pub(crate) fn is_normal() {} + pub(crate) fn is_sized_unnpin() {} pub fn unlock_script(entry_key: &str, proof_key: &str) -> String { format!( @@ -240,7 +242,7 @@ pub mod tests { fn test_autotraits() { // This is a dummy test to ensure that the crate runs in wasm32 target // and that the autotrait implementation is what is expected. - helpers::is_normal::>(); + helpers::is_sized_unnpin::>(); } // This is the actual test that cargo test will run diff --git a/crates/comrade/src/runtime/direct/mod.rs b/crates/comrade/src/runtime/direct/mod.rs index 0bad2b0..99678a9 100644 --- a/crates/comrade/src/runtime/direct/mod.rs +++ b/crates/comrade/src/runtime/direct/mod.rs @@ -2,24 +2,24 @@ mod logs; use super::Runtime; -use comrade_reference::{Context, Pairable, Value}; +use comrade_reference::{Context, Pairs, Value}; /// The "Direct" runtime uses the comrade-reference implementation directly in rust /// and is the default runtime for Comrade. -pub(crate) struct Runner<'a> { - pub(crate) context: Context<'a>, +pub(crate) struct Runner<'un, 'lo> { + pub(crate) context: Context<'un, 'lo>, } -impl<'a> Runner<'a> { +impl<'un, 'lo> Runner<'un, 'lo> { /// Creates a new Runner instance with the given lock and unlock pairs. - pub fn new(kvp_lock: &'a impl Pairable, kvp_unlock: &'a impl Pairable) -> Self { + pub fn new(kvp_lock: &'un impl Pairs, kvp_unlock: &'lo impl Pairs) -> Self { Self { context: Context::new(kvp_lock, kvp_unlock, &logs::Logger), } } } -impl<'a> Runtime for Runner<'a> { +impl Runtime for Runner<'_, '_> { fn try_unlock(&mut self, script: &str) -> Result<(), crate::Error> { self.context.run(script)?; Ok(()) @@ -27,7 +27,6 @@ impl<'a> Runtime for Runner<'a> { fn try_lock(&mut self, script: &str) -> Result, crate::Error> { self.context.run(script)?; - let rstack = self.context.rstack(); - Ok(rstack.map(|v| v.into())) + Ok(self.context.rstack()) } } diff --git a/crates/comrade/src/runtime/layer/mod.rs b/crates/comrade/src/runtime/layer/mod.rs index 79dce70..e9f84a7 100644 --- a/crates/comrade/src/runtime/layer/mod.rs +++ b/crates/comrade/src/runtime/layer/mod.rs @@ -13,7 +13,7 @@ use definitions::{ use super::Runtime; use crate::Error; -use comrade_reference::{Pairable, Value}; +use comrade_reference::{Pairs, Value}; use wasm_component_layer::{ AsContextMut as _, Component, Engine, Func, FuncType, Instance, Linker, OptionType, OptionValue, ResourceOwn, Store, ValueType, @@ -30,8 +30,8 @@ use js_wasm_runtime_layer as runtime_layer; // C and P need to be bound by Send + Sync pub(crate) struct Runner where - C: Pairable, - P: Pairable, + C: Pairs, + P: Pairs, { /// The store for the component. store: Store, runtime_layer::Engine>, @@ -56,8 +56,8 @@ impl Logger for TracingLogger { /// Internal struct for the store Data struct Data where - C: Pairable, - P: Pairable, + C: Pairs, + P: Pairs, { kvp_current: C, kvp_proposed: P, @@ -65,7 +65,7 @@ where logger: Box, } -impl Runner { +impl Runner { /// Create a new wasm_compoennt_layer [Runner] with a tracing logger. pub(crate) fn new(kvp_current: C, kvp_proposed: P) -> Self { // Use default logger @@ -272,7 +272,7 @@ impl Runner { } } -impl Runtime for Runner { +impl Runtime for Runner { fn try_unlock(&mut self, unlock: &str) -> Result<(), Error> { let api_export_instance = self .instance @@ -403,7 +403,7 @@ mod tests { } } - impl Runner { + impl Runner { // Helper for tests that returns both the runner and the logger fn new_for_test(kvp_current: C, kvp_proposed: P) -> (Self, TestLogger) { let test_logger = TestLogger::new(); diff --git a/crates/comrade/tests/web.rs b/crates/comrade/tests/web.rs index 3bdad2e..72b643a 100644 --- a/crates/comrade/tests/web.rs +++ b/crates/comrade/tests/web.rs @@ -6,7 +6,7 @@ use wasm_bindgen_test::*; wasm_bindgen_test_configure!(run_in_browser); -use comrade_reference::{Pairable, Pairs, Value}; +use comrade_reference::{Pairs, Value}; use std::collections::HashMap; use tracing::info; From 006f6947b6ecbbb4eac9451322267f2cbe022640 Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Mon, 12 May 2025 10:32:51 -0300 Subject: [PATCH 038/155] mk comrade worspace dep --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index e8af935..46bbf05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ unexpected_cfgs = { level = "warn", check-cfg = [ # Crate ependencies bs = { path = "crates/bs" } bs-traits = { path = "crates/bs-traits" } +comrade = { path = "crates/comrade" } comrade-reference = { path = "crates/comrade-reference" } multibase = { path = "crates/multibase" } multicid = { path = "crates/multicid" } From e1cea03543deb128ed5e438689f3aa3169c3a767 Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Mon, 12 May 2025 10:33:12 -0300 Subject: [PATCH 039/155] update lifetime --- crates/comrade-component/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/comrade-component/src/lib.rs b/crates/comrade-component/src/lib.rs index 731d12e..4b4ccf7 100644 --- a/crates/comrade-component/src/lib.rs +++ b/crates/comrade-component/src/lib.rs @@ -17,7 +17,7 @@ use comrade_reference::Context; use std::cell::RefCell; struct Api { - context: RefCell>, + context: RefCell>, } impl Guest for Api { From 7e7dee5404a92a94fa9dc13dc93e1b6e14fdf2e6 Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Mon, 12 May 2025 10:34:09 -0300 Subject: [PATCH 040/155] fix missing dep (again) https://github.com/cryptidtech/multicid/pull/1/commits/245847993753944b3ad2a95af4bfe206af58611a --- cli/Cargo.toml | 5 +- cli/src/subcmds/plog.rs | 78 +++++++++++++++++++++----------- crates/multicid/src/serde/ser.rs | 2 + 3 files changed, 57 insertions(+), 28 deletions(-) diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 8db8101..07fa020 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -20,6 +20,7 @@ anyhow = "1.0" async-trait = "0.1" best-practices.workspace = true bs.workspace = true +bs-traits.workspace = true clap = { version = "4.5.36", features = ["cargo"] } colored = "3.0.0" csv = "1.3.1" @@ -42,7 +43,7 @@ rustyline = { version = "15.0.0", features = ["derive"] } serde = { workspace = true, optional = true } serde_cbor.workspace = true serde_json.workspace = true -ssh-key = { version = "0.6.2", features = ["crypto", "ed25519"]} +ssh-key = { version = "0.6.2", features = ["crypto", "ed25519"] } ssh-agent-client-rs = "1.0.0" structopt = "0.3.26" thiserror.workspace = true @@ -50,7 +51,7 @@ tokio = { version = "1.44.2", features = ["full"] } toml = "0.8.20" tracing.workspace = true tracing-subscriber.workspace = true -wacc.workspace = true +comrade.workspace = true [dev-dependencies] tokio-test = "0.4.4" diff --git a/cli/src/subcmds/plog.rs b/cli/src/subcmds/plog.rs index 486618a..8aebe67 100644 --- a/cli/src/subcmds/plog.rs +++ b/cli/src/subcmds/plog.rs @@ -2,6 +2,7 @@ /// Plog command pub mod command; +use bs_traits::{GetKey, Signer}; pub use command::Command; use crate::{error::PlogError, Config, Error}; @@ -11,6 +12,7 @@ use bs::{ ops::{open, update}, update::OpParams, }; +use comrade::Pairs; use multibase::Base; use multicid::{Cid, EncodedCid, EncodedVlad, Vlad}; use multicodec::Codec; @@ -22,7 +24,50 @@ use provenance_log::{Key, Log, Script}; use rng::StdRng; use std::{collections::VecDeque, convert::TryFrom, path::PathBuf}; use tracing::debug; -use wacc::Pairs; + +/// Cli KeyManager +struct KeyManager; + +impl GetKey for KeyManager { + type Key = Multikey; + + type KeyPath = Key; + + type Codec = Codec; + + type KeyError = bs::Error; + + fn get_key( + &self, + key_path: &Self::KeyPath, + codec: &Self::Codec, + threshold: usize, + limit: usize, + ) -> Result { + debug!("Generating {} key ({} of {})...", codec, threshold, limit); + let mut rng = StdRng::from_os_rng(); + let mk = mk::Builder::new_from_random_bytes(*codec, &mut rng)?.try_build()?; + let fingerprint = mk.fingerprint_view()?.fingerprint(Codec::Blake3)?; + + let ef = EncodedMultihash::new(Base::Base32Z, fingerprint); + debug!("Writing {} key fingerprint: {}", key_path, ef); + let w = writer(&Some(format!("{}.multikey", ef).into()))?; + serde_cbor::to_writer(w, &mk)?; + Ok(mk) + } +} + +impl Signer for KeyManager { + type Key = Multikey; + + type Signature = Multisig; + + type SignError = bs::Error; + + fn try_sign(&self, key: &Self::Key, data: &[u8]) -> Result { + Ok(key.sign_view()?.sign(data, false, None)?) + } +} /// processes plog subcommands pub async fn go(cmd: Command, _config: &Config) -> Result<(), Error> { @@ -49,29 +94,10 @@ pub async fn go(cmd: Command, _config: &Config) -> Result<(), Error> { .with_entry_lock_script(&lock_script_path) .with_entry_unlock_script(&unlock_script_path); + let key_manager = KeyManager; + // open the p.log - let plog = open::open_plog( - cfg, - |key: &Key, - codec: Codec, - threshold: usize, - limit: usize| - -> Result { - debug!("Generating {} key ({} of {})...", codec, threshold, limit); - let mut rng = StdRng::from_os_rng(); - let mk = mk::Builder::new_from_random_bytes(codec, &mut rng)?.try_build()?; - let fingerprint = mk.fingerprint_view()?.fingerprint(Codec::Blake3)?; - let ef = EncodedMultihash::new(Base::Base32Z, fingerprint); - debug!("Writing {} key fingerprint: {}", key, ef); - let w = writer(&Some(format!("{}.multikey", ef).into()))?; - serde_cbor::to_writer(w, &mk)?; - Ok(mk) - }, - |mk: &Multikey, data: &[u8]| -> Result { - debug!("Signing the first entry"); - Ok(mk.sign_view()?.sign(data, false, None)?) - }, - )?; + let plog = open::open_plog(cfg, &key_manager, &key_manager)?; println!("Created p.log {}", writer_name(&output)?.to_string_lossy()); print_plog(&plog)?; @@ -298,17 +324,17 @@ where } */ -fn get_from_wacc_value<'a, T>(value: &'a wacc::Value) -> Option +fn get_from_wacc_value<'a, T>(value: &'a comrade::Value) -> Option where T: TryFrom<&'a [u8]> + EncodingInfo, BaseEncoded: TryFrom<&'a str>, { match value { - wacc::Value::Bin { + comrade::Value::Bin { hint: _, data: ref v, } => T::try_from(v.as_slice()).ok(), - wacc::Value::Str { + comrade::Value::Str { hint: _, data: ref s, } => match BaseEncoded::::try_from(s.as_str()) { diff --git a/crates/multicid/src/serde/ser.rs b/crates/multicid/src/serde/ser.rs index efa5ed6..39178d1 100644 --- a/crates/multicid/src/serde/ser.rs +++ b/crates/multicid/src/serde/ser.rs @@ -17,6 +17,8 @@ impl ser::Serialize for Cid { } else { #[cfg(feature = "dag_cbor")] { + use multicodec::Codec; + // build the byte string for DAG-CBOR according to the spec // https://github.com/ipld/specs/blob/master/block-layer/codecs/dag-cbor.md#links let mut v = Vec::new(); From 35dfacd43227579df35b0e29d21a3617b5c12f95 Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Mon, 12 May 2025 10:34:25 -0300 Subject: [PATCH 041/155] update trait to incl Error too --- crates/bs-traits/src/lib.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/crates/bs-traits/src/lib.rs b/crates/bs-traits/src/lib.rs index 57e938b..1aad990 100644 --- a/crates/bs-traits/src/lib.rs +++ b/crates/bs-traits/src/lib.rs @@ -11,12 +11,18 @@ pub trait Signer { type Key; /// The type of signature type Signature; + /// Any Signing Error + type SignError: std::fmt::Debug; /// Attempt to sign the data - fn try_sign(&self, key: &Self::Key, data: &[u8]) -> Result; + fn try_sign(&self, key: &Self::Key, data: &[u8]) -> Result; /// Sign the data and return the signature - fn sign(&self, key: &Self::Key, data: &[u8]) -> Self::Signature { + /// + /// # Panics + /// + /// This function will panic if the signing operation fails. + fn sign_unchecked(&self, key: &Self::Key, data: &[u8]) -> Self::Signature { self.try_sign(key, data).expect("signing operation failed") } } @@ -139,6 +145,8 @@ pub trait GetKey { type KeyPath; /// The type of codec type Codec; + /// The Error returned + type KeyError; /// Get the key fn get_key( @@ -147,5 +155,5 @@ pub trait GetKey { codec: &Self::Codec, threshold: usize, limit: usize, - ) -> Result; + ) -> Result; } From 55664cc56be2d97903be4f8443f4fa3ab0c62bde Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Mon, 12 May 2025 10:34:43 -0300 Subject: [PATCH 042/155] update plog and open --- crates/bs/src/ops/open.rs | 6 +- crates/provenance-log/Cargo.toml | 1 + crates/provenance-log/src/entry.rs | 10 +- crates/provenance-log/src/error.rs | 14 ++ crates/provenance-log/src/lib.rs | 4 - crates/provenance-log/src/log.rs | 257 +++++++++-------------------- crates/provenance-log/src/pairs.rs | 22 +-- 7 files changed, 112 insertions(+), 202 deletions(-) diff --git a/crates/bs/src/ops/open.rs b/crates/bs/src/ops/open.rs index 3b0bc5b..2db830b 100644 --- a/crates/bs/src/ops/open.rs +++ b/crates/bs/src/ops/open.rs @@ -22,8 +22,8 @@ use tracing::debug; /// open a new provenance log based on the config pub fn open_plog(config: Config, get_key: &G, sign_entry: &S) -> Result where - G: GetKey, - S: Signer, + G: GetKey, + S: Signer, { // 0. Set up the list of ops we're going to add let mut op_params = Vec::default(); @@ -183,7 +183,7 @@ where fn load_key(ops: &mut Vec, params: &OpParams, get_key: &G) -> Result where - G: GetKey, + G: GetKey, { debug!("load_key: {:?}", params); match params { diff --git a/crates/provenance-log/Cargo.toml b/crates/provenance-log/Cargo.toml index e1615a7..04a4f30 100644 --- a/crates/provenance-log/Cargo.toml +++ b/crates/provenance-log/Cargo.toml @@ -28,6 +28,7 @@ test-log.workspace = true thiserror.workspace = true tracing.workspace = true wacc.workspace = true +comrade.workspace = true [dev-dependencies] hex.workspace = true diff --git a/crates/provenance-log/src/entry.rs b/crates/provenance-log/src/entry.rs index 9651cc9..1c246d1 100644 --- a/crates/provenance-log/src/entry.rs +++ b/crates/provenance-log/src/entry.rs @@ -94,19 +94,19 @@ impl EncodingInfo for Entry { } } -impl wacc::Pairs for Entry { - fn get(&self, key: &str) -> Option { +impl comrade::Pairs for &Entry { + fn get(&self, key: &str) -> Option { let key = match Key::try_from(key) { Ok(key) => key, Err(_) => return None, }; match self.get_value(&key) { Some(value) => match value { - Value::Data(data) => Some(wacc::Value::Bin { + Value::Data(data) => Some(comrade::Value::Bin { hint: key.to_string(), data, }), - Value::Str(s) => Some(wacc::Value::Str { + Value::Str(s) => Some(comrade::Value::Str { hint: key.to_string(), data: s, }), @@ -116,7 +116,7 @@ impl wacc::Pairs for Entry { } } - fn put(&mut self, _key: &str, _value: &wacc::Value) -> Option { + fn put(&mut self, _key: &str, _value: &comrade::Value) -> Option { None } } diff --git a/crates/provenance-log/src/error.rs b/crates/provenance-log/src/error.rs index 72cb453..fdddeff 100644 --- a/crates/provenance-log/src/error.rs +++ b/crates/provenance-log/src/error.rs @@ -164,6 +164,12 @@ pub enum LogError { /// Updating kvp failed #[error("Kvp set entry failed {0}")] KvpSetEntryFailed(String), + /// Running the unlock script failed + #[error("Running the unlock script failed, reason: {0}")] + UnlockFailed(String), + /// Running the lock script failed + #[error("Running the lock script failed, reason: {0}")] + LockFailed(String), } /// Errors created by this library @@ -206,6 +212,14 @@ pub enum ScriptError { /// invalid wasm script magic value #[error("invalid wasm script")] InvalidScriptMagic, + /// Wrong script format + #[error("wrong script format: expected {expected}, found {found}")] + WrongScriptFormat { + /// The String that was found + found: String, + /// The String that was expected + expected: String, + }, } /// Errors created by this library diff --git a/crates/provenance-log/src/lib.rs b/crates/provenance-log/src/lib.rs index b2b8704..9b9ef79 100644 --- a/crates/provenance-log/src/lib.rs +++ b/crates/provenance-log/src/lib.rs @@ -44,10 +44,6 @@ pub use script::{EncodedScript, Script, ScriptId}; #[cfg(feature = "serde")] pub mod serde; -/// The parameter and return value stack type -pub mod stack; -pub use stack::Stk; - /// Entry Value related functions pub mod value; pub use value::{Value, ValueId}; diff --git a/crates/provenance-log/src/log.rs b/crates/provenance-log/src/log.rs index 3e94281..894682d 100644 --- a/crates/provenance-log/src/log.rs +++ b/crates/provenance-log/src/log.rs @@ -1,5 +1,10 @@ // SPDX-License-Identifier: FSL-1.1 -use crate::{entry, error::LogError, Entry, Error, Kvp, Script, Stk}; +use crate::{ + entry, + error::{LogError, ScriptError}, + Entry, Error, Kvp, Script, +}; +use comrade::{Comrade, Value}; use core::fmt; use multibase::Base; use multicid::{Cid, Vlad}; @@ -7,7 +12,6 @@ use multicodec::Codec; use multitrait::{Null, TryDecodeFrom}; use multiutil::{BaseEncoded, CodecInfo, EncodingInfo, Varuint}; use std::collections::BTreeMap; -use wacc::{prelude::StoreLimitsBuilder, vm, Stack}; /// the multicodec provenance log codec pub const SIGIL: Codec = Codec::ProvenanceLog; @@ -194,6 +198,21 @@ struct VerifyIter<'a> { error: Option, } +impl<'a> VerifyIter<'a> { + /// Helper method to set error state and return early + fn set_error(&mut self, error: E) -> Option), Error>> + where + E: Into, + { + // Set index out of range + self.seqno = self.entries.len(); + // Set the error state + self.error = Some(error.into()); + // Return the error + Some(Err(self.error.clone().unwrap())) + } +} + impl<'a> Iterator for VerifyIter<'a> { type Item = Result<(usize, Entry, Kvp<'a>), Error>; @@ -207,216 +226,96 @@ impl<'a> Iterator for VerifyIter<'a> { // this is the check count if successful let mut count = 0; - // set up the stacks - let mut pstack = Stk::default(); - let mut rstack = Stk::default(); - // check the seqno meet the criteria if self.seqno > 0 && self.seqno != self.prev_seqno + 1 { - // set our index out of range - self.seqno = self.entries.len(); - // set the error state - self.error = Some(LogError::InvalidSeqno.into()); - return Some(Err(self.error.clone().unwrap())); + return self.set_error(LogError::InvalidSeqno); } // 'unlock: - let mut result = { - // run the unlock script using the entry as the kvp to get the - // stack in the vm::Context set up. - let unlock_ctx = vm::Context { - current: entry, // limit the available data to just the entry - proposed: entry, // limit the available data to just the entry - pstack: &mut pstack, - rstack: &mut rstack, - check_count: 0, - write_idx: 0, - context: entry.context().to_string(), - log: Vec::default(), - limiter: StoreLimitsBuilder::new() - .memory_size(1 << 16) - .instances(2) - .memories(1) - .build(), - }; - - let mut instance = match vm::Builder::new() - .with_context(unlock_ctx) - .with_bytes(entry.unlock.clone()) - .try_build() - { - Ok(i) => i, - Err(e) => { - // set our index out of range - self.seqno = self.entries.len(); - self.error = Some(LogError::Wacc(e).into()); - return Some(Err(self.error.clone().unwrap())); - } - }; - //print!("running unlock script from seqno: {}...", self.seqno); - - // run the unlock script - if let Some(e) = instance.run("for_great_justice").err() { - // set our index out of range - self.seqno = self.entries.len(); - self.error = Some(LogError::Wacc(e).into()); - return Some(Err(self.error.clone().unwrap())); - } - - //println!("SUCCEEDED!"); - - true + let Script::Code(_, ref unlock) = entry.unlock else { + return self.set_error(ScriptError::WrongScriptFormat { + expected: "unlock".to_string(), + found: format!("{:?}", entry.unlock), + }); }; - /* - println!("values:"); - println!("{:?}", pstack.clone()); - println!("return:"); - println!("{:?}", rstack.clone()); - */ - - if !result { - // set our index out of range - self.seqno = self.entries.len(); - self.error = Some( - LogError::VerifyFailed(format!( - "unlock script failed\nvalues:\n{:?}\nreturn:\n{:?}", - rstack, pstack - )) - .into(), - ); - return Some(Err(self.error.clone().unwrap())); - } - - /* - // set the entry to look into for proof and message values - if let Some(e) = self.kvp.set_entry(entry).err() { - // set our index out of range - self.seqno = self.entries.len(); - self.error = Some(LogError::KvpSetEntryFailed(e.to_string()).into()); - return Some(Err(self.error.clone().unwrap())); - } - */ - // if this is the first entry, then we need to apply the // mutation ops if self.seqno == 0 { //println!("applying kvp ops for seqno 0"); if let Some(e) = self.kvp.apply_entry_ops(entry).err() { - // set our index out of range - self.seqno = self.entries.len(); - self.error = Some(LogError::UpdateKvpFailed(e.to_string()).into()); - return Some(Err(self.error.clone().unwrap())); + return self.set_error(LogError::UnlockFailed(e.to_string())); } } - // 'lock: - result = false; + let kvp_lock = self.kvp.clone(); + + let mut unlocked = Comrade::new(&kvp_lock, &entry) + .try_unlock(unlock) + .or_else(|e| { + Err(self.set_error(LogError::UnlockFailed(format!("unlock failed: {}", e)))) + }) + .ok()?; // build the set of lock scripts to run in order from root to longest branch to leaf let locks = match entry.sort_locks(&self.lock_scripts) { Ok(l) => l, Err(e) => { - // set our index out of range - self.seqno = self.entries.len(); - self.error = Some(e); - return Some(Err(self.error.clone().unwrap())); + return self.set_error(e); } }; + let mut results = false; + // run each of the lock scripts for lock in locks { - // NOTE: clone the kvp and stacks each time - let lock_kvp = self.kvp.clone(); - let mut lock_pstack = pstack.clone(); - let mut lock_rstack = rstack.clone(); - - { - let lock_ctx = vm::Context { - current: &lock_kvp, - proposed: entry, - pstack: &mut lock_pstack, - rstack: &mut lock_rstack, - check_count: 0, - write_idx: 0, - context: entry.context().to_string(), // set the branch path for branch() - log: Vec::default(), - limiter: StoreLimitsBuilder::new() - .memory_size(1 << 16) - .instances(2) - .memories(1) - .build(), - }; - - let mut instance = match vm::Builder::new() - .with_context(lock_ctx) - .with_bytes(lock.clone()) - .try_build() - { - Ok(i) => i, - Err(e) => { - // set our index out of range - self.seqno = self.entries.len(); - self.error = Some(LogError::Wacc(e).into()); - return Some(Err(self.error.clone().unwrap())); + let Script::Code(_, lock) = lock else { + // set our index out of range + self.seqno = self.entries.len(); + // set the error state + self.error = Some( + ScriptError::WrongScriptFormat { + found: format!("{:?}", lock), + expected: "Script::Code".to_string(), } - }; - //print!("running lock script from seqno: {}...", self.seqno); - - // run the unlock script - if let Some(e) = instance.run("move_every_zig").err() { - // set our index out of range - self.seqno = self.entries.len(); - self.error = Some(LogError::Wacc(e).into()); - return Some(Err(self.error.clone().unwrap())); - } - - //println!("SUCCEEDED!"); - } + .into(), + ); + return Some(Err(self.error.clone().unwrap())); + }; - // break out of this loop as soon as a lock script succeeds - if let Some(v) = lock_rstack.top() { - match v { - vm::Value::Success(c) => { - count = c; - result = true; - break; - } - _ => result = false, + match unlocked.try_lock(&lock) { + Ok(Some(Value::Success(ct))) => { + count = ct; + results = true; + break; } + Err(e) => { + self.set_error(LogError::LockFailed(e.to_string())); + } + _ => continue, } } - if result { - // if the entry verifies, apply it's mutataions to the kvp - // the 0th entry has already been applied at this point so no - // need to do it here - if self.seqno > 0 { - if let Some(e) = self.kvp.apply_entry_ops(entry).err() { - // set our index out of range - self.seqno = self.entries.len(); - self.error = Some(LogError::UpdateKvpFailed(e.to_string()).into()); - return Some(Err(self.error.clone().unwrap())); - } - } - // update the lock script to validate the next entry - self.lock_scripts.clone_from(&entry.locks); - // update the seqno - self.prev_seqno = self.seqno; - self.seqno += 1; - } else { - // set our index out of range - self.seqno = self.entries.len(); - self.error = Some( - LogError::VerifyFailed(format!( - "unlock script failed\nvalues:\n{:?}\nreturn:\n{:?}", - rstack, pstack - )) - .into(), - ); - return Some(Err(self.error.clone().unwrap())); + if !results { + self.set_error(LogError::VerifyFailed("entry failed to verify".to_string())); } + // if the entry verifies, apply it's mutataions to the kvp + // the 0th entry has already been applied at this point so no + // need to do it here + if self.seqno > 0 { + if let Some(e) = self.kvp.apply_entry_ops(entry).err() { + // set our index out of range + self.seqno = self.entries.len(); + self.error = Some(LogError::UpdateKvpFailed(e.to_string()).into()); + return Some(Err(self.error.clone().unwrap())); + } + } + // update the lock script to validate the next entry + self.lock_scripts.clone_from(&entry.locks); + // update the seqno + self.prev_seqno = self.seqno; + self.seqno += 1; // return the check count, validated entry, and kvp state Some(Ok((count, entry.clone(), self.kvp.clone()))) } diff --git a/crates/provenance-log/src/pairs.rs b/crates/provenance-log/src/pairs.rs index c4f62b4..5d7379b 100644 --- a/crates/provenance-log/src/pairs.rs +++ b/crates/provenance-log/src/pairs.rs @@ -14,23 +14,23 @@ pub struct Kvp<'a> { undo: Vec<(Option<&'a Entry>, BTreeMap)>, } -impl wacc::Pairs for Kvp<'_> { - fn get(&self, key: &str) -> Option { +impl comrade::Pairs for Kvp<'_> { + fn get(&self, key: &str) -> Option { let k = match Key::try_from(key) { Ok(k) => k, _ => return None, }; match self.kvp.get(&k) { Some(ref v) => match v { - Value::Nil => Some(wacc::Value::Bin { + Value::Nil => Some(comrade::Value::Bin { hint: key.to_string(), data: Vec::default(), }), - Value::Str(ref s) => Some(wacc::Value::Str { + Value::Str(ref s) => Some(comrade::Value::Str { hint: key.to_string(), data: s.clone(), }), - Value::Data(ref v) => Some(wacc::Value::Bin { + Value::Data(ref v) => Some(comrade::Value::Bin { hint: key.to_string(), data: v.clone(), }), @@ -45,32 +45,32 @@ impl wacc::Pairs for Kvp<'_> { } } - fn put(&mut self, key: &str, value: &wacc::Value) -> Option { + fn put(&mut self, key: &str, value: &comrade::Value) -> Option { let k = match Key::try_from(key) { Ok(k) => k, _ => return None, }; let v = match value { - wacc::Value::Str { + comrade::Value::Str { hint: _, data: ref s, } => Value::Str(s.clone()), - wacc::Value::Bin { + comrade::Value::Bin { hint: _, data: ref v, } => Value::Data(v.clone()), _ => return None, }; match self.kvp.insert(k, v) { - Some(Value::Nil) => Some(wacc::Value::Bin { + Some(Value::Nil) => Some(comrade::Value::Bin { hint: key.to_string(), data: Vec::default(), }), - Some(Value::Str(s)) => Some(wacc::Value::Str { + Some(Value::Str(s)) => Some(comrade::Value::Str { hint: key.to_string(), data: s, }), - Some(Value::Data(v)) => Some(wacc::Value::Bin { + Some(Value::Data(v)) => Some(comrade::Value::Bin { hint: key.to_string(), data: v, }), From 7d6914f66e5c4aef34ca621e876f16ece82b7149 Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Mon, 12 May 2025 10:51:04 -0300 Subject: [PATCH 043/155] haul in bs-p2p --- Cargo.toml | 3 ++- crates/{bsp2p => bs-p2p}/Cargo.toml | 2 +- crates/{bsp2p => bs-p2p}/README.md | 0 crates/{bsp2p => bs-p2p}/src/lib.rs | 0 4 files changed, 3 insertions(+), 2 deletions(-) rename crates/{bsp2p => bs-p2p}/Cargo.toml (96%) rename crates/{bsp2p => bs-p2p}/README.md (100%) rename crates/{bsp2p => bs-p2p}/src/lib.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 46bbf05..4dd2d64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,8 @@ default-members = ["crates/bs"] members = [ "cli", "crates/bs", + "crates/bs-p2p", "crates/bs-traits", - "crates/bsp2p", "crates/comrade", "crates/comrade-component", "crates/comrade-reference", @@ -40,6 +40,7 @@ unexpected_cfgs = { level = "warn", check-cfg = [ [workspace.dependencies] # Crate ependencies bs = { path = "crates/bs" } +bs-p2p = { path = "crates/bs-p2p" } bs-traits = { path = "crates/bs-traits" } comrade = { path = "crates/comrade" } comrade-reference = { path = "crates/comrade-reference" } diff --git a/crates/bsp2p/Cargo.toml b/crates/bs-p2p/Cargo.toml similarity index 96% rename from crates/bsp2p/Cargo.toml rename to crates/bs-p2p/Cargo.toml index cbbffa0..c7f7eb6 100644 --- a/crates/bsp2p/Cargo.toml +++ b/crates/bs-p2p/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "bsp2p" +name = "bs-p2p" version.workspace = true edition.workspace = true authors.workspace = true diff --git a/crates/bsp2p/README.md b/crates/bs-p2p/README.md similarity index 100% rename from crates/bsp2p/README.md rename to crates/bs-p2p/README.md diff --git a/crates/bsp2p/src/lib.rs b/crates/bs-p2p/src/lib.rs similarity index 100% rename from crates/bsp2p/src/lib.rs rename to crates/bs-p2p/src/lib.rs From fb8a4620e80db1a86ce0987785f6ab84f29f702e Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Mon, 12 May 2025 11:11:19 -0300 Subject: [PATCH 044/155] use Self::Error assoc type in traits --- crates/bs-traits/src/async.rs | 41 +++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/crates/bs-traits/src/async.rs b/crates/bs-traits/src/async.rs index dbabf59..e1f47e5 100644 --- a/crates/bs-traits/src/async.rs +++ b/crates/bs-traits/src/async.rs @@ -1,16 +1,21 @@ // SPDX-License-Identifier: FSL-1.1 use crate::*; -use std::{future::Future, num::NonZeroUsize}; +use std::error::Error as StdError; +use std::future::Future; +use std::num::NonZeroUsize; /// Trait for types that can sign data asynchronously pub trait AsyncSigner: Signer { + /// Error type for signing operations + type Error: StdError + 'static; + /// Attempt to sign the data asynchronously fn try_sign_async<'a>( &'a self, key: &'a Self::Key, data: &'a [u8], - ) -> impl Future> + 'a; + ) -> impl Future> + 'a; /// Sign the data asynchronously fn sign_async<'a>( @@ -28,23 +33,29 @@ pub trait AsyncSigner: Signer { /// Trait for types that can verify data asynchronously pub trait AsyncVerifier: Verifier { + /// Error type for verification operations + type Error: StdError + 'static; + /// Verify the data asynchronously fn verify_async<'a>( &'a self, key: &'a Self::Key, data: &'a [u8], signature: &'a Self::Signature, - ) -> impl Future> + 'a; + ) -> impl Future> + 'a; } /// Trait for types that can encrypt data asynchronously pub trait AsyncEncryptor: Encryptor { + /// Error type for encryption operations + type Error: StdError + 'static; + /// Attempt to encrypt the data asynchronously fn try_encrypt_async<'a>( &'a self, key: &'a Self::Key, plaintext: &'a Self::Plaintext, - ) -> impl Future> + 'a; + ) -> impl Future> + 'a; /// Encrypt the data asynchronously fn encrypt_async<'a>( @@ -62,23 +73,29 @@ pub trait AsyncEncryptor: Encryptor { /// Trait for types that can decrypt data asynchronously pub trait AsyncDecryptor: Decryptor { + /// Error type for decryption operations + type Error: StdError + 'static; + /// Decrypt the data asynchronously fn decrypt_async<'a>( &'a self, key: &'a Self::Key, ciphertext: &'a Self::Ciphertext, - ) -> impl Future> + 'a; + ) -> impl Future> + 'a; } /// Trait for types that can split a secret into shares asynchronously pub trait AsyncSecretSplitter: SecretSplitter { + /// Error type for secret splitting operations + type Error: StdError + 'static; + /// Split the secret into shares asynchronously fn split_async<'a>( &'a self, secret: &'a Self::Secret, threshold: NonZeroUsize, limit: NonZeroUsize, - ) -> impl Future> + 'a; + ) -> impl Future> + 'a; /// Split the secret into shares with the given identifiers asynchronously fn split_with_identifiers_async<'a>( @@ -86,20 +103,26 @@ pub trait AsyncSecretSplitter: SecretSplitter { secret: &'a Self::Secret, threshold: NonZeroUsize, identifiers: &'a [Self::Identifier], - ) -> impl Future> + 'a; + ) -> impl Future> + 'a; } /// Trait for types that can combine shares into a secret asynchronously pub trait AsyncSecretCombiner: SecretCombiner { + /// Error type for secret combining operations + type Error: StdError + 'static; + /// Combine the shares into a secret asynchronously fn combine_async<'a>( &'a self, shares: &'a [(Self::Identifier, Self::Shares)], - ) -> impl Future> + 'a; + ) -> impl Future> + 'a; } /// Trait for types that can get a key asynchronously pub trait AsyncGetKey: GetKey { + /// Error type for key retrieval operations + type Error: StdError + 'static; + /// Get the key asynchronously fn get_key_async<'a>( &'a self, @@ -107,5 +130,5 @@ pub trait AsyncGetKey: GetKey { codec: &'a Self::Codec, threshold: usize, limit: usize, - ) -> impl Future> + 'a; + ) -> impl Future> + 'a; } From 3b79da99c8ff62b39072874f837360dcf01fd2b8 Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Mon, 12 May 2025 11:16:26 -0300 Subject: [PATCH 045/155] add cond_send for wasm32 and native compat --- crates/bs-traits/src/async.rs | 21 ++++++++++---------- crates/bs-traits/src/cond_send.rs | 33 +++++++++++++++++++++++++++++++ crates/bs-traits/src/lib.rs | 1 + 3 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 crates/bs-traits/src/cond_send.rs diff --git a/crates/bs-traits/src/async.rs b/crates/bs-traits/src/async.rs index e1f47e5..5dbb8f9 100644 --- a/crates/bs-traits/src/async.rs +++ b/crates/bs-traits/src/async.rs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: FSL-1.1 +use crate::cond_send::CondSend; use crate::*; use std::error::Error as StdError; use std::future::Future; @@ -15,14 +16,14 @@ pub trait AsyncSigner: Signer { &'a self, key: &'a Self::Key, data: &'a [u8], - ) -> impl Future> + 'a; + ) -> impl Future> + CondSend + 'a; /// Sign the data asynchronously fn sign_async<'a>( &'a self, key: &'a Self::Key, data: &'a [u8], - ) -> impl Future + 'a { + ) -> impl Future + CondSend + 'a { async move { self.try_sign_async(key, data) .await @@ -42,7 +43,7 @@ pub trait AsyncVerifier: Verifier { key: &'a Self::Key, data: &'a [u8], signature: &'a Self::Signature, - ) -> impl Future> + 'a; + ) -> impl Future> + CondSend + 'a; } /// Trait for types that can encrypt data asynchronously @@ -55,14 +56,14 @@ pub trait AsyncEncryptor: Encryptor { &'a self, key: &'a Self::Key, plaintext: &'a Self::Plaintext, - ) -> impl Future> + 'a; + ) -> impl Future> + CondSend + 'a; /// Encrypt the data asynchronously fn encrypt_async<'a>( &'a self, key: &'a Self::Key, plaintext: &'a Self::Plaintext, - ) -> impl Future + 'a { + ) -> impl Future + CondSend + 'a { async move { self.try_encrypt_async(key, plaintext) .await @@ -81,7 +82,7 @@ pub trait AsyncDecryptor: Decryptor { &'a self, key: &'a Self::Key, ciphertext: &'a Self::Ciphertext, - ) -> impl Future> + 'a; + ) -> impl Future> + CondSend + 'a; } /// Trait for types that can split a secret into shares asynchronously @@ -95,7 +96,7 @@ pub trait AsyncSecretSplitter: SecretSplitter { secret: &'a Self::Secret, threshold: NonZeroUsize, limit: NonZeroUsize, - ) -> impl Future> + 'a; + ) -> impl Future> + CondSend + 'a; /// Split the secret into shares with the given identifiers asynchronously fn split_with_identifiers_async<'a>( @@ -103,7 +104,7 @@ pub trait AsyncSecretSplitter: SecretSplitter { secret: &'a Self::Secret, threshold: NonZeroUsize, identifiers: &'a [Self::Identifier], - ) -> impl Future> + 'a; + ) -> impl Future> + CondSend + 'a; } /// Trait for types that can combine shares into a secret asynchronously @@ -115,7 +116,7 @@ pub trait AsyncSecretCombiner: SecretCombiner { fn combine_async<'a>( &'a self, shares: &'a [(Self::Identifier, Self::Shares)], - ) -> impl Future> + 'a; + ) -> impl Future> + CondSend + 'a; } /// Trait for types that can get a key asynchronously @@ -130,5 +131,5 @@ pub trait AsyncGetKey: GetKey { codec: &'a Self::Codec, threshold: usize, limit: usize, - ) -> impl Future> + 'a; + ) -> impl Future> + CondSend + 'a; } diff --git a/crates/bs-traits/src/cond_send.rs b/crates/bs-traits/src/cond_send.rs new file mode 100644 index 0000000..df30005 --- /dev/null +++ b/crates/bs-traits/src/cond_send.rs @@ -0,0 +1,33 @@ +//! Utilities for conditionally adding `Send` and `Sync` constraints. + +/// A conditionally compiled trait indirection for `Send` bounds. +/// This target makes it require `Send`. +#[cfg(not(target_arch = "wasm32"))] +pub trait CondSend: Send {} + +/// A conditionally compiled trait indirection for `Send` bounds. +/// This target makes it not require any marker traits. +#[cfg(target_arch = "wasm32")] +pub trait CondSend {} + +#[cfg(not(target_arch = "wasm32"))] +impl CondSend for S where S: Send {} + +#[cfg(target_arch = "wasm32")] +impl CondSend for S {} + +/// A conditionally compiled trait indirection for `Send + Sync` bounds. +/// This target makes it require `Send + Sync`. +#[cfg(not(target_arch = "wasm32"))] +pub trait CondSync: Send + Sync {} + +/// A conditionally compiled trait indirection for `Send + Sync` bounds. +/// This target makes it not require any marker traits. +#[cfg(target_arch = "wasm32")] +pub trait CondSync {} + +#[cfg(not(target_arch = "wasm32"))] +impl CondSync for S where S: Send + Sync {} + +#[cfg(target_arch = "wasm32")] +impl CondSync for S {} diff --git a/crates/bs-traits/src/lib.rs b/crates/bs-traits/src/lib.rs index d74ab8b..74484cc 100644 --- a/crates/bs-traits/src/lib.rs +++ b/crates/bs-traits/src/lib.rs @@ -5,6 +5,7 @@ /// It also provides a `WaitQueue` type that can be used to implement synchronous and asynchronous operations /// without having to use tokio::block_in_place or similar. mod r#async; +mod cond_send; mod error; mod sync; mod wait_queue; From ccac040b9e3713eb6fe4665b634485b08bb792c0 Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Mon, 12 May 2025 11:28:48 -0300 Subject: [PATCH 046/155] constraint data references by (Cond)Sync bounds --- crates/bs-traits/src/async.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/crates/bs-traits/src/async.rs b/crates/bs-traits/src/async.rs index 5dbb8f9..20d195e 100644 --- a/crates/bs-traits/src/async.rs +++ b/crates/bs-traits/src/async.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: FSL-1.1 -use crate::cond_send::CondSend; +use crate::cond_send::{CondSend, CondSync}; use crate::*; use std::error::Error as StdError; use std::future::Future; @@ -23,7 +23,11 @@ pub trait AsyncSigner: Signer { &'a self, key: &'a Self::Key, data: &'a [u8], - ) -> impl Future + CondSend + 'a { + ) -> impl Future + CondSend + 'a + where + Self: CondSync, + Self::Key: CondSync, + { async move { self.try_sign_async(key, data) .await @@ -63,7 +67,12 @@ pub trait AsyncEncryptor: Encryptor { &'a self, key: &'a Self::Key, plaintext: &'a Self::Plaintext, - ) -> impl Future + CondSend + 'a { + ) -> impl Future + CondSend + 'a + where + Self: CondSync, + Self::Key: CondSync, + Self::Plaintext: CondSync, + { async move { self.try_encrypt_async(key, plaintext) .await From ad0d902d9ca4e2603fa6cc1df5c34c4d5c142f57 Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Mon, 12 May 2025 11:50:43 -0300 Subject: [PATCH 047/155] elide lifetimes where poss, add Panics comments to uncheched fns --- crates/bs-traits/src/async.rs | 86 +++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 39 deletions(-) diff --git a/crates/bs-traits/src/async.rs b/crates/bs-traits/src/async.rs index 20d195e..9483a05 100644 --- a/crates/bs-traits/src/async.rs +++ b/crates/bs-traits/src/async.rs @@ -12,13 +12,17 @@ pub trait AsyncSigner: Signer { type Error: StdError + 'static; /// Attempt to sign the data asynchronously - fn try_sign_async<'a>( - &'a self, - key: &'a Self::Key, - data: &'a [u8], - ) -> impl Future> + CondSend + 'a; + fn try_sign_async( + &self, + key: &Self::Key, + data: &[u8], + ) -> impl Future> + CondSend + '_; /// Sign the data asynchronously + /// + /// # Panics + /// + /// This function will panic if the signing operation fails. fn sign_async<'a>( &'a self, key: &'a Self::Key, @@ -42,12 +46,12 @@ pub trait AsyncVerifier: Verifier { type Error: StdError + 'static; /// Verify the data asynchronously - fn verify_async<'a>( - &'a self, - key: &'a Self::Key, - data: &'a [u8], - signature: &'a Self::Signature, - ) -> impl Future> + CondSend + 'a; + fn verify_async( + &self, + key: &Self::Key, + data: &[u8], + signature: &Self::Signature, + ) -> impl Future> + CondSend + '_; } /// Trait for types that can encrypt data asynchronously @@ -56,13 +60,17 @@ pub trait AsyncEncryptor: Encryptor { type Error: StdError + 'static; /// Attempt to encrypt the data asynchronously - fn try_encrypt_async<'a>( - &'a self, - key: &'a Self::Key, - plaintext: &'a Self::Plaintext, - ) -> impl Future> + CondSend + 'a; + fn try_encrypt_async( + &self, + key: &Self::Key, + plaintext: &Self::Plaintext, + ) -> impl Future> + CondSend + '_; /// Encrypt the data asynchronously + /// + /// # Panics + /// + /// This function will panic if the encryption operation fails. fn encrypt_async<'a>( &'a self, key: &'a Self::Key, @@ -87,11 +95,11 @@ pub trait AsyncDecryptor: Decryptor { type Error: StdError + 'static; /// Decrypt the data asynchronously - fn decrypt_async<'a>( - &'a self, - key: &'a Self::Key, - ciphertext: &'a Self::Ciphertext, - ) -> impl Future> + CondSend + 'a; + fn decrypt_async( + &self, + key: &Self::Key, + ciphertext: &Self::Ciphertext, + ) -> impl Future> + CondSend + '_; } /// Trait for types that can split a secret into shares asynchronously @@ -100,20 +108,20 @@ pub trait AsyncSecretSplitter: SecretSplitter { type Error: StdError + 'static; /// Split the secret into shares asynchronously - fn split_async<'a>( - &'a self, - secret: &'a Self::Secret, + fn split_async( + &self, + secret: &Self::Secret, threshold: NonZeroUsize, limit: NonZeroUsize, - ) -> impl Future> + CondSend + 'a; + ) -> impl Future> + CondSend + '_; /// Split the secret into shares with the given identifiers asynchronously - fn split_with_identifiers_async<'a>( - &'a self, - secret: &'a Self::Secret, + fn split_with_identifiers_async( + &self, + secret: &Self::Secret, threshold: NonZeroUsize, - identifiers: &'a [Self::Identifier], - ) -> impl Future> + CondSend + 'a; + identifiers: &[Self::Identifier], + ) -> impl Future> + CondSend + '_; } /// Trait for types that can combine shares into a secret asynchronously @@ -122,10 +130,10 @@ pub trait AsyncSecretCombiner: SecretCombiner { type Error: StdError + 'static; /// Combine the shares into a secret asynchronously - fn combine_async<'a>( - &'a self, - shares: &'a [(Self::Identifier, Self::Shares)], - ) -> impl Future> + CondSend + 'a; + fn combine_async( + &self, + shares: &[(Self::Identifier, Self::Shares)], + ) -> impl Future> + CondSend + '_; } /// Trait for types that can get a key asynchronously @@ -134,11 +142,11 @@ pub trait AsyncGetKey: GetKey { type Error: StdError + 'static; /// Get the key asynchronously - fn get_key_async<'a>( - &'a self, - key_path: &'a Self::KeyPath, - codec: &'a Self::Codec, + fn get_key_async( + &self, + key_path: &Self::KeyPath, + codec: &Self::Codec, threshold: usize, limit: usize, - ) -> impl Future> + CondSend + 'a; + ) -> impl Future> + CondSend + '_; } From 7bd77a88c745b2cf0ab94c5d280c2b97b895fe34 Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Tue, 13 May 2025 15:38:32 -0300 Subject: [PATCH 048/155] rm workspace dep with js feat --- Cargo.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4f6f103..034a833 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,10 +87,6 @@ tracing = "0.1.41" tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } unsigned-varint = { version = "0.8.0", features = ["std"] } -[workspace.dependencies.getrandom] -version = "0.2.15" -features = ["js"] - [profile.bench] opt-level = 3 debug = false From b1513a45cc886555efc842486ca2a3e682ae5b29 Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Tue, 13 May 2025 16:39:17 -0300 Subject: [PATCH 049/155] trait, associate types, sync and async --- cli/src/subcmds/plog.rs | 15 +++--- crates/bs-traits/src/async.rs | 59 +++++++++-------------- crates/bs-traits/src/lib.rs | 86 +++++++++++++++++++++++++++++++++ crates/bs-traits/src/sync.rs | 91 ++++++++--------------------------- crates/bs/src/ops/open.rs | 8 +-- 5 files changed, 144 insertions(+), 115 deletions(-) diff --git a/cli/src/subcmds/plog.rs b/cli/src/subcmds/plog.rs index 8aebe67..abd70ce 100644 --- a/cli/src/subcmds/plog.rs +++ b/cli/src/subcmds/plog.rs @@ -2,7 +2,7 @@ /// Plog command pub mod command; -use bs_traits::{GetKey, Signer}; +use bs_traits::{GetKey, Signer, SyncGetKey, SyncSigner}; pub use command::Command; use crate::{error::PlogError, Config, Error}; @@ -35,15 +35,16 @@ impl GetKey for KeyManager { type Codec = Codec; - type KeyError = bs::Error; - + type Error = bs::Error; +} +impl SyncGetKey for KeyManager { fn get_key( &self, key_path: &Self::KeyPath, codec: &Self::Codec, threshold: usize, limit: usize, - ) -> Result { + ) -> Result { debug!("Generating {} key ({} of {})...", codec, threshold, limit); let mut rng = StdRng::from_os_rng(); let mk = mk::Builder::new_from_random_bytes(*codec, &mut rng)?.try_build()?; @@ -62,9 +63,11 @@ impl Signer for KeyManager { type Signature = Multisig; - type SignError = bs::Error; + type Error = bs::Error; +} - fn try_sign(&self, key: &Self::Key, data: &[u8]) -> Result { +impl SyncSigner for KeyManager { + fn try_sign(&self, key: &Self::Key, data: &[u8]) -> Result { Ok(key.sign_view()?.sign(data, false, None)?) } } diff --git a/crates/bs-traits/src/async.rs b/crates/bs-traits/src/async.rs index 9483a05..0323105 100644 --- a/crates/bs-traits/src/async.rs +++ b/crates/bs-traits/src/async.rs @@ -1,18 +1,14 @@ // SPDX-License-Identifier: FSL-1.1 - +//! This module provides traits for asynchronous operations use crate::cond_send::{CondSend, CondSync}; use crate::*; -use std::error::Error as StdError; use std::future::Future; use std::num::NonZeroUsize; /// Trait for types that can sign data asynchronously pub trait AsyncSigner: Signer { - /// Error type for signing operations - type Error: StdError + 'static; - /// Attempt to sign the data asynchronously - fn try_sign_async( + fn try_sign( &self, key: &Self::Key, data: &[u8], @@ -23,7 +19,7 @@ pub trait AsyncSigner: Signer { /// # Panics /// /// This function will panic if the signing operation fails. - fn sign_async<'a>( + fn sign<'a>( &'a self, key: &'a Self::Key, data: &'a [u8], @@ -33,7 +29,7 @@ pub trait AsyncSigner: Signer { Self::Key: CondSync, { async move { - self.try_sign_async(key, data) + self.try_sign(key, data) .await .expect("signing operation failed") } @@ -42,11 +38,8 @@ pub trait AsyncSigner: Signer { /// Trait for types that can verify data asynchronously pub trait AsyncVerifier: Verifier { - /// Error type for verification operations - type Error: StdError + 'static; - /// Verify the data asynchronously - fn verify_async( + fn verify( &self, key: &Self::Key, data: &[u8], @@ -56,11 +49,8 @@ pub trait AsyncVerifier: Verifier { /// Trait for types that can encrypt data asynchronously pub trait AsyncEncryptor: Encryptor { - /// Error type for encryption operations - type Error: StdError + 'static; - /// Attempt to encrypt the data asynchronously - fn try_encrypt_async( + fn try_encrypt( &self, key: &Self::Key, plaintext: &Self::Plaintext, @@ -71,7 +61,7 @@ pub trait AsyncEncryptor: Encryptor { /// # Panics /// /// This function will panic if the encryption operation fails. - fn encrypt_async<'a>( + fn encrypt<'a>( &'a self, key: &'a Self::Key, plaintext: &'a Self::Plaintext, @@ -82,7 +72,7 @@ pub trait AsyncEncryptor: Encryptor { Self::Plaintext: CondSync, { async move { - self.try_encrypt_async(key, plaintext) + self.try_encrypt(key, plaintext) .await .expect("encryption operation failed") } @@ -91,11 +81,8 @@ pub trait AsyncEncryptor: Encryptor { /// Trait for types that can decrypt data asynchronously pub trait AsyncDecryptor: Decryptor { - /// Error type for decryption operations - type Error: StdError + 'static; - /// Decrypt the data asynchronously - fn decrypt_async( + fn decrypt( &self, key: &Self::Key, ciphertext: &Self::Ciphertext, @@ -104,11 +91,12 @@ pub trait AsyncDecryptor: Decryptor { /// Trait for types that can split a secret into shares asynchronously pub trait AsyncSecretSplitter: SecretSplitter { - /// Error type for secret splitting operations - type Error: StdError + 'static; - /// Split the secret into shares asynchronously - fn split_async( + /// + /// Conditions for `split` to succeed: + /// - Threshold must be less than or equal to limit. + /// - Threshold must be greater than or equal to 2. + fn split( &self, secret: &Self::Secret, threshold: NonZeroUsize, @@ -116,7 +104,14 @@ pub trait AsyncSecretSplitter: SecretSplitter { ) -> impl Future> + CondSend + '_; /// Split the secret into shares with the given identifiers asynchronously - fn split_with_identifiers_async( + /// The number of shares will be equal to the number of identifiers i.e. the `limit`. + /// + /// Conditions for `split_with_identifiers` to succeed: + /// - Threshold must be less than or equal to the number of identifiers. + /// - Threshold must be greater than or equal to 2. + /// - Identifiers must be unique. + /// - Identifiers must not be empty. + fn split_with_identifiers( &self, secret: &Self::Secret, threshold: NonZeroUsize, @@ -126,11 +121,8 @@ pub trait AsyncSecretSplitter: SecretSplitter { /// Trait for types that can combine shares into a secret asynchronously pub trait AsyncSecretCombiner: SecretCombiner { - /// Error type for secret combining operations - type Error: StdError + 'static; - /// Combine the shares into a secret asynchronously - fn combine_async( + fn combine( &self, shares: &[(Self::Identifier, Self::Shares)], ) -> impl Future> + CondSend + '_; @@ -138,11 +130,8 @@ pub trait AsyncSecretCombiner: SecretCombiner { /// Trait for types that can get a key asynchronously pub trait AsyncGetKey: GetKey { - /// Error type for key retrieval operations - type Error: StdError + 'static; - /// Get the key asynchronously - fn get_key_async( + fn get_key( &self, key_path: &Self::KeyPath, codec: &Self::Codec, diff --git a/crates/bs-traits/src/lib.rs b/crates/bs-traits/src/lib.rs index 74484cc..c6ef5b3 100644 --- a/crates/bs-traits/src/lib.rs +++ b/crates/bs-traits/src/lib.rs @@ -14,3 +14,89 @@ pub use error::Error; pub use r#async::*; pub use sync::*; pub use wait_queue::*; + +use std::fmt::Debug; + +/// Trait for types that can sign data using [AsyncSigner] or [SyncSigner] +pub trait Signer { + /// The type of key used to sign + type Key; + /// The type of signature + type Signature; + /// Any Signing Error + type Error: Debug; +} + +/// Trait for types that can verify signatures using [AsyncVerifier] or [SyncVerifier] +pub trait Verifier { + /// The type of key used to verify + type Key; + /// The type of signature + type Signature; + /// Error type for verification operations + type Error; +} + +/// Trait for types that can encrypt data using [AsyncEncryptor] or [SyncEncryptor] +pub trait Encryptor { + /// The type of key used to encrypt + type Key: Send + Sync; + /// The type of ciphertext + type Ciphertext: Send + Sync; + /// The type of plaintext, might include the nonce, and additional authenticated data + type Plaintext: Send + Sync; + /// Error type for encryption operations + type Error: Debug; +} + +/// Trait for types that can decrypt data using [AsyncDecryptor] or [SyncDecryptor] +pub trait Decryptor { + /// The type of key used to decrypt + type Key: Send + Sync; + /// The type of ciphertext + type Ciphertext: Send + Sync; + /// The type of plaintext + type Plaintext: Send + Sync; + /// Error type for decryption operations + type Error; +} + +/// Trait for types that can split a secret into shares, using [AsyncSecretSplitter] or [SyncSecretSplitter] +pub trait SecretSplitter { + /// The type of secret to split + type Secret: Send + Sync; + /// The type of identifier for the shares + type Identifier: Send + Sync; + /// The output from splitting the secret. + /// Might include the threshold and limit used to split the secret, + /// the shares, and the verifiers, identifiers, + /// or any other information needed to reconstruct the secret + /// and verify the shares. + type Output: Send + Sync; + /// Error type for secret splitting operations + type Error; +} + +/// Trait for types that can combine shares into a secret, using [AsyncSecretCombiner] or [SyncSecretCombiner] +pub trait SecretCombiner { + /// The type of secret to combine + type Secret: Send + Sync; + /// The type of identifier for the shares + type Identifier: Send + Sync; + /// The type of shares to combine + type Shares: Send + Sync; + /// Error type for secret combining operations + type Error; +} + +/// Trait for types that can retrieve a key, using [AsyncGetKey] or [SyncGetKey] +pub trait GetKey { + /// The type of key + type Key; + /// The type of key path + type KeyPath; + /// The type of codec + type Codec; + /// The Error returned + type Error; +} diff --git a/crates/bs-traits/src/sync.rs b/crates/bs-traits/src/sync.rs index 5c4ee66..ac56561 100644 --- a/crates/bs-traits/src/sync.rs +++ b/crates/bs-traits/src/sync.rs @@ -1,60 +1,42 @@ +//! This module contains traits for synchronous operations. use core::num::NonZeroUsize; -use crate::Error; +use crate::*; /// Trait for types that can sign data -pub trait Signer { - /// The type of key used to sign - type Key; - /// The type of signature - type Signature; - /// Any Signing Error - type SignError: std::fmt::Debug; - +pub trait SyncSigner: Signer { /// Attempt to sign the data - fn try_sign(&self, key: &Self::Key, data: &[u8]) -> Result; + fn try_sign(&self, key: &Self::Key, data: &[u8]) -> Result; /// Sign the data and return the signature /// /// # Panics /// /// This function will panic if the signing operation fails. - fn sign_unchecked(&self, key: &Self::Key, data: &[u8]) -> Self::Signature { + fn sign(&self, key: &Self::Key, data: &[u8]) -> Self::Signature { self.try_sign(key, data).expect("signing operation failed") } } /// Trait for types that can verify signatures -pub trait Verifier { - /// The type of key used to verify - type Key; - /// The type of signature - type Signature; - +pub trait SyncVerifier: Verifier { /// Verify that the provided signature for the given data is authentic fn verify( &self, key: &Self::Key, data: &[u8], signature: &Self::Signature, - ) -> Result<(), Error>; + ) -> Result<(), Self::Error>; } /// Trait for types that can encrypt data -pub trait Encryptor { - /// The type of key used to encrypt - type Key: Send + Sync; - /// The type of ciphertext - type Ciphertext: Send + Sync; - /// The type of plaintext, might include the nonce, and additional authenticated data - type Plaintext: Send + Sync; - +pub trait SyncEncryptor: Encryptor { /// Attempt to encrypt the plaintext fn try_encrypt( &self, key: &Self::Key, plaintext: &Self::Plaintext, - ) -> Result; + ) -> Result; /// Encrypt the plaintext fn encrypt(&self, key: &Self::Key, plaintext: &Self::Plaintext) -> Self::Ciphertext { @@ -64,35 +46,17 @@ pub trait Encryptor { } /// Trait for types that can decrypt data -pub trait Decryptor { - /// The type of key used to decrypt - type Key: Send + Sync; - /// The type of ciphertext - type Ciphertext: Send + Sync; - /// The type of plaintext - type Plaintext: Send + Sync; - +pub trait SyncDecryptor: Decryptor { /// Attempt to decrypt the ciphertext fn decrypt( &self, key: &Self::Key, ciphertext: &Self::Ciphertext, - ) -> Result; + ) -> Result; } /// Trait for types that can split a secret into shares -pub trait SecretSplitter { - /// The type of secret to split - type Secret: Send + Sync; - /// The type of identifier for the shares - type Identifier: Send + Sync; - /// The output from splitting the secret. - /// Might include the threshold and limit used to split the secret, - /// the shares, and the verifiers, identifiers, - /// or any other information needed to reconstruct the secret - /// and verify the shares. - type Output: Send + Sync; - +pub trait SyncSecretSplitter: SecretSplitter { /// Split the secret into shares. /// /// Conditions for `split` to succeed: @@ -103,7 +67,7 @@ pub trait SecretSplitter { secret: &Self::Secret, threshold: NonZeroUsize, limit: NonZeroUsize, - ) -> Result; + ) -> Result; /// Split the secret into shares with the given identifiers. /// The number of shares will be equal to the number of identifiers i.e. the `limit`. @@ -118,33 +82,20 @@ pub trait SecretSplitter { secret: &Self::Secret, threshold: NonZeroUsize, identifiers: &[Self::Identifier], - ) -> Result; + ) -> Result; } /// Trait for types that can combine shares into a secret -pub trait SecretCombiner { - /// The type of secret to combine - type Secret: Send + Sync; - /// The type of identifier for the shares - type Identifier: Send + Sync; - /// The type of shares to combine - type Shares: Send + Sync; - +pub trait SyncSecretCombiner: SecretCombiner { /// Combine the shares into a secret - fn combine(&self, shares: &[(Self::Identifier, Self::Shares)]) -> Result; + fn combine( + &self, + shares: &[(Self::Identifier, Self::Shares)], + ) -> Result; } /// Trait for types that can retrieve a key -pub trait GetKey { - /// The type of key - type Key; - /// The type of key path - type KeyPath; - /// The type of codec - type Codec; - /// The Error returned - type KeyError; - +pub trait SyncGetKey: GetKey { /// Get the key fn get_key( &self, @@ -152,5 +103,5 @@ pub trait GetKey { codec: &Self::Codec, threshold: usize, limit: usize, - ) -> Result; + ) -> Result; } diff --git a/crates/bs/src/ops/open.rs b/crates/bs/src/ops/open.rs index 2db830b..2bea0fc 100644 --- a/crates/bs/src/ops/open.rs +++ b/crates/bs/src/ops/open.rs @@ -9,7 +9,7 @@ use crate::{ update::{op, script, OpParams}, Error, }; -use bs_traits::{GetKey, Signer}; +use bs_traits::{GetKey, Signer, SyncGetKey, SyncSigner}; use multicid::{cid, vlad, Cid}; use multicodec::Codec; use multihash::mh; @@ -22,8 +22,8 @@ use tracing::debug; /// open a new provenance log based on the config pub fn open_plog(config: Config, get_key: &G, sign_entry: &S) -> Result where - G: GetKey, - S: Signer, + G: GetKey + SyncGetKey, + S: Signer + SyncSigner, { // 0. Set up the list of ops we're going to add let mut op_params = Vec::default(); @@ -183,7 +183,7 @@ where fn load_key(ops: &mut Vec, params: &OpParams, get_key: &G) -> Result where - G: GetKey, + G: GetKey + SyncGetKey, { debug!("load_key: {:?}", params); match params { From a5f891132011cd13a4c307ea071a0f6ab9bd0959 Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Tue, 13 May 2025 18:34:52 -0300 Subject: [PATCH 050/155] add `with_domain` for non-"/" paths --- crates/comrade-reference/src/context.rs | 6 ++++ crates/comrade/justfile | 3 ++ crates/comrade/src/lib.rs | 40 ++++++++++++++++++++++-- crates/comrade/src/runtime/direct/mod.rs | 4 +++ crates/comrade/src/runtime/mod.rs | 2 ++ 5 files changed, 52 insertions(+), 3 deletions(-) diff --git a/crates/comrade-reference/src/context.rs b/crates/comrade-reference/src/context.rs index 70e4670..629e4a8 100644 --- a/crates/comrade-reference/src/context.rs +++ b/crates/comrade-reference/src/context.rs @@ -65,6 +65,12 @@ impl<'un, 'lo> Context<'un, 'lo> { } } + /// Set the domain for the context + pub fn with_domain(&mut self, domain: &str) -> &mut Self { + self.domain = domain.to_string(); + self + } + /// Parse a script from a string and evaluate it, returning the result pub fn run(&mut self, script: &str) -> Result { self.logger.log(&format!("Running script: {script}")); diff --git a/crates/comrade/justfile b/crates/comrade/justfile index 759faa8..02a936c 100644 --- a/crates/comrade/justfile +++ b/crates/comrade/justfile @@ -7,6 +7,9 @@ build-component: test-wasm32 *args: # wasm32 test for the browser RUSTFLAGS='--cfg getrandom_backend="wasm_js"' wasm-pack test --chrome {{args}} --all-features + test: just test-wasm32 --headless cargo test + + diff --git a/crates/comrade/src/lib.rs b/crates/comrade/src/lib.rs index 9c80141..4c18bc6 100644 --- a/crates/comrade/src/lib.rs +++ b/crates/comrade/src/lib.rs @@ -1,9 +1,11 @@ //! Comrade is an execution engine for provenance log scripts. //! -//! It requires a wasm-component plugin to run. A reference implementation is -//! provided in the `comrade-component` crate which uses [comrade_reference] +//! It requires a Script Runtime to run. A reference implementation is +//! provided in the [crate::runtime::Runner] module which uses [comrade_reference] +//! directly in Rust. In the future, this crate *can* support other runtimes, +//! such as [wasm_component_layer] based wasm components. //! -//! API should be something like: +//! The Comrade API should look something like: //! //! ```ignore //! let unlocked = Comrade::new(kvp_lock, kvp_unlock) @@ -70,6 +72,38 @@ impl<'un, 'lo> Comrade<'un, 'lo> { } } + /// Optionally set the key-path domain for use in branch() functions. + /// + /// # Example + /// + /// ```rust + /// # use std::error::Error; + /// # use comrade::{Comrade, Value}; + /// # + /// # // Mock implementation for doctests + /// # struct MockPairs; + /// # impl comrade::Pairs for MockPairs { + /// # fn get(&self, _: &str) -> Option { None } + /// # fn put(&mut self, _: &str, _: &Value) -> Option { None } + /// # } + /// # + /// # fn main() -> Result<(), Box> { + /// let kvp_lock = MockPairs; + /// let kvp_unlock = MockPairs; + /// let unlock = r#"push("/entry_key/"); push("/entry_key/proof");"#; + /// let unlocked = Comrade::new(&kvp_lock, &kvp_unlock) + /// .with_domain("/forks/child") + /// .try_unlock(unlock)?; + /// + /// // full path is now "/forks/child/entry_key/" and "/forks/child/entry_key/proof" + /// # + /// # Ok(()) + /// # } + pub fn with_domain(mut self, domain: &str) -> Self { + self.runner.with_domain(domain); + self + } + /// Tries to unlock the comrade with the given script. /// Will return an error if the script fails to run. pub fn try_unlock(mut self, script: &'un str) -> Result, Error> { diff --git a/crates/comrade/src/runtime/direct/mod.rs b/crates/comrade/src/runtime/direct/mod.rs index 99678a9..70a799e 100644 --- a/crates/comrade/src/runtime/direct/mod.rs +++ b/crates/comrade/src/runtime/direct/mod.rs @@ -20,6 +20,10 @@ impl<'un, 'lo> Runner<'un, 'lo> { } impl Runtime for Runner<'_, '_> { + fn with_domain(&mut self, domain: &str) { + self.context.domain = domain.to_string(); + } + fn try_unlock(&mut self, script: &str) -> Result<(), crate::Error> { self.context.run(script)?; Ok(()) diff --git a/crates/comrade/src/runtime/mod.rs b/crates/comrade/src/runtime/mod.rs index b079a63..eba637b 100644 --- a/crates/comrade/src/runtime/mod.rs +++ b/crates/comrade/src/runtime/mod.rs @@ -25,6 +25,8 @@ pub(crate) use direct::Runner; /// Each runtime feature must implement the `Runtime` trait, run and top pub trait Runtime { + /// Sets the domain + fn with_domain(&mut self, domain: &str); /// Run the script. fn try_unlock(&mut self, script: &str) -> Result<(), Error>; /// Get the top value from the context return stack. From 7a5a3ff596ae66be9e322936523c7736ca7faf35 Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Tue, 13 May 2025 18:44:46 -0300 Subject: [PATCH 051/155] readme edits --- crates/comrade-reference/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/comrade-reference/README.md b/crates/comrade-reference/README.md index 25cbdd7..632f01f 100644 --- a/crates/comrade-reference/README.md +++ b/crates/comrade-reference/README.md @@ -1,17 +1,17 @@ # Comrade Verification Runtime - Reference Implementation -An opinionated script runtime for lock and unlcok scripts. Since scripts are just plain text, they need to be run "somewhere, somehow" and this can be by various means. For example, in previous versions of Comrade the Rhai scripting engine was used to execute the scripts. This could be done using other scripting languages such as Lua, but in this implementation a domain specific language (DSL) parser is used to run the scripts. +An opinionated script runtime for lock and unlock scripts. Since scripts are just plain text, they need to be executed "somewhere, somehow" and this can be done by various means. For example, in previous versions of Comrade, the Rhai scripting engine was used to execute the scripts. This could be done using other scripting languages such as Lua, but in this implementation, a domain specific language (DSL) parser is used to interpret and run the scripts. ## Script Runtime & Script Parser -This crate contains the parser, abstract syntax tree, and runtime to execute the scripts. In this implementation, a simple Domain Specific Language (DSL) parser is written in pest, and then the abstract syntax tree executed against their corresponding Rust functions. In other words, `check_signature(..)` in plog script runs `check_signature` Cryptographic construct to evaluate the script to check whether it's valid or not. +This crate contains the parser, abstract syntax tree, and runtime to execute the scripts. In this implementation, a simple Domain Specific Language (DSL) parser is written in pest, and then the abstract syntax tree is executed against corresponding Rust functions. In other words, when `check_signature(..)` appears in a script, the runtime calls the `check_signature` cryptographic construct to evaluate whether the script is valid or not. ## Comrade Crypto Constructs -This calls associated cryptographic constructs to verify the validity of the scripts against each other. This is the reference implementation for the comrade-component. +This component calls associated cryptographic constructs to verify the validity of the scripts against each other. This is the reference implementation for the comrade-component. -If different primiatives are desired, the crypto dependencies can be changed for new ones, a new component built, and run with bettersign. +If different cryptographic primitives are desired, the crypto dependencies can be changed for new ones, a new component built, and run with bettersign. ## Design Considerations -This comrade architecture was chosen to give users manximum flexibility, upgradeability and extensibility of the backend. Because of the modular approach, backends can be switched out without impacting the rest of the system. This makes open source more viable and maintainable, since parts can be used interchangeably without placing all the burden on the user to maintain the entire system. +This comrade architecture was chosen to give users maximum flexibility, upgradeability, and extensibility of the backend. Because of the modular approach, backends can be switched out without impacting the rest of the system. This makes open source more viable and maintainable, since components can be used interchangeably without placing the entire maintenance burden on a single user. From 464d280ca899281a542d3a6f38b872610c7e2134 Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Tue, 13 May 2025 18:44:51 -0300 Subject: [PATCH 052/155] add readme --- crates/comrade-reference/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/comrade-reference/src/lib.rs b/crates/comrade-reference/src/lib.rs index b80e7bd..d13da9b 100644 --- a/crates/comrade-reference/src/lib.rs +++ b/crates/comrade-reference/src/lib.rs @@ -1,3 +1,7 @@ +// Include readme at header, with rustdoc tests +#![doc = include_str!("../README.md")] +pub struct ReadmeDocumentation; + /// Crate level errors mod error; pub use error::ApiError; From 2c1b3a39e7625317786e274bf60dda33aae24915 Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Tue, 13 May 2025 18:59:15 -0300 Subject: [PATCH 053/155] readme --- crates/comrade/README.md | 59 ++++++++++++++++++++++++++----- crates/comrade/src/runtime/mod.rs | 10 +++--- 2 files changed, 56 insertions(+), 13 deletions(-) diff --git a/crates/comrade/README.md b/crates/comrade/README.md index 48d1f76..ce3f648 100644 --- a/crates/comrade/README.md +++ b/crates/comrade/README.md @@ -2,12 +2,31 @@ A flexible, extensible, and composable way to run provenance log scripts natively or in the browser. -This crate is the main entry point for the library. +## Overview +This crate is the main entry point for the Comrade library, which provides a framework for executing provenance log scripts in both native and browser environments. Comrade enables flexible script execution with support for lock and unlock operations through an intuitive key-value pair interface. + +This crate is run during the "Verification" step of using a provenance log. + +## Installation + +This crate is typically used in the [../provenance-log/Cargo.toml](../provenance-log/) crate by adding this to the `Cargo.toml`: + +```toml +[dependencies] +comrade.workspace = true +``` + +## Features + +- Execute text-based provenance log scripts in native Rust or browser environments +- Flexible key-value pair interface for script data management +- Support for lock and unlock script operations +- Wasm component model compatibility ## Use -The main API aims to be super simple: +The main API aims to be as simple as possible, and the following example shows how to use the Comrade library to run a script: ```rust use comrade::Comrade; @@ -24,7 +43,7 @@ impl Pairs for TestData { fn put(&mut self, key: &str, value: &Value) -> Option { self.0.insert(key.to_string(), value.clone()) - } + } } // The message to sign, in both the lock and unlock scripts @@ -110,19 +129,41 @@ assert_eq!(count, 2); ## Tests -To run the test: +To run the tests: ```sh -# see http://just.systems/ fr more details +# see http://just.systems/ for more details just test ``` This will ensure the default component is built and available for the default wasm runtime. -## Wasm Component Layer +## Architecture + +Comrade is designed with a modular architecture that consists of: + +1. **Core API**: The main `Comrade` struct provides a simple interface for script execution +2. **Reference Implementation**: The crate includes a reference implementation with default behaviors +3. **WASM Integration**: Support for WebAssembly components enables cross-platform compatibility + +By default, Comrade uses a direct implementation in Rust. But, it is possible to break up the build into modular wasm components and compose them together. This is being left as an area for future work when demand arises. + +### Wasm Component Layer + +This reference implementation makes opinions about what dependencies to use and which wasm runtime to use to run the components, but it should be noted that it is possible to swap in your own runtime for both the component and the wasm runtime. + + But, for example, `wasm_component_layer` crate gives us an isomorphic way to load components in native or the browser directly from Rust. We use a patch until [this dependency fix lands](https://github.com/DouglasDwyer/wasm_component_layer/pull/26). + +## Related Crates + +- `comrade_reference`: Provides the reference implementation for Comrade, which can be used directly in Comrade, or bundled into a wasm component. +- `wasmi_runtime`: Native runtime for WebAssembly components +- `js_wasm_runtime_layer`: Browser runtime for WebAssembly components + +## Contributing -This reference implementation makes opinions about what dependencies to use, and which wasm runtime to use to run the components, but it should be noted that you can swap in your own runtime for both the component and the wasm runtime. +Contributions are welcome! Please feel free to submit a Pull Request. -`wasm_component_layer` crate gives us an isomorphic way to load components in native or the browser directly from Rust. We use a patch until [this dependency fix lands](https://github.com/DouglasDwyer/wasm_component_layer/pull/26). +## License -We chose the `wasmi_runtime` for native, and `js_wasm_runtime_layer` for the browser. +FSL-1.1 diff --git a/crates/comrade/src/runtime/mod.rs b/crates/comrade/src/runtime/mod.rs index eba637b..f22d371 100644 --- a/crates/comrade/src/runtime/mod.rs +++ b/crates/comrade/src/runtime/mod.rs @@ -11,9 +11,11 @@ use comrade_reference::Value; mod direct; pub(crate) use direct::Runner; -// NOTE: In the future, we could support other runtimes at the runtime level. -// For example, the wasm runtime uses a wasm component layer to run the script -// instead of rust code. +// NOTE: In the future, we could support other runtimes at this level, +// but it may only make sense to do so once there is demand for different backends. +// For example, the wasm runtime can use a wasm component layer to run the script +// instead of native rust code. This gives us a swappable interface in case a user +// wants a particular runtime for their use case which ours doesn't support. // #[cfg(feature = "runtime-wasm")] // mod layer; // #[cfg(feature = "runtime-wasm")] @@ -23,7 +25,7 @@ pub(crate) use direct::Runner; // mod wasm_i; // mod wasmer; -/// Each runtime feature must implement the `Runtime` trait, run and top +/// Each runtime feature must implement the `Runtime` trait, to maintain a common interface. pub trait Runtime { /// Sets the domain fn with_domain(&mut self, domain: &str); From 3df3ccf8b4281a052d30ee8a6c763c2b13740eee Mon Sep 17 00:00:00 2001 From: Doug Anderson444 Date: Tue, 13 May 2025 21:13:32 -0300 Subject: [PATCH 054/155] use shiny new traits in bs and cli --- cli/src/error.rs | 9 +++ cli/src/subcmds/plog.rs | 31 ++------- crates/bs/Cargo.toml | 2 - crates/bs/src/error.rs | 3 - crates/bs/src/ops/open.rs | 130 +++++++++++++++++++----------------- crates/bs/src/ops/update.rs | 127 ++++++++++++++++++----------------- 6 files changed, 151 insertions(+), 151 deletions(-) diff --git a/cli/src/error.rs b/cli/src/error.rs index 18783df..e809ad4 100644 --- a/cli/src/error.rs +++ b/cli/src/error.rs @@ -29,6 +29,15 @@ pub enum Error { /// Bs errors #[error(transparent)] Bs(#[from] bs::Error), + + /// Error opening a provenance log + #[error(transparent)] + Open(#[from] bs::error::OpenError), + + /// Error updating a provenance log + #[error(transparent)] + Update(#[from] bs::error::UpdateError), + /// Multicid error #[error(transparent)] Multicid(#[from] multicid::Error), diff --git a/cli/src/subcmds/plog.rs b/cli/src/subcmds/plog.rs index abd70ce..fe167e3 100644 --- a/cli/src/subcmds/plog.rs +++ b/cli/src/subcmds/plog.rs @@ -35,8 +35,9 @@ impl GetKey for KeyManager { type Codec = Codec; - type Error = bs::Error; + type Error = Error; } + impl SyncGetKey for KeyManager { fn get_key( &self, @@ -63,7 +64,7 @@ impl Signer for KeyManager { type Signature = Multisig; - type Error = bs::Error; + type Error = Error; } impl SyncSigner for KeyManager { @@ -151,30 +152,10 @@ pub async fn go(cmd: Command, _config: &Config) -> Result<(), Error> { .with_entry_signing_key(&entry_signing_key) .with_entry_unlock_script(&unlock_script_path); + let key_manager = KeyManager; + // update the p.log - update::update_plog( - &mut plog, - cfg, - |key: &Key, - codec: Codec, - threshold: usize, - limit: usize| - -> Result { - debug!("Generating {} key ({} of {})...", codec, threshold, limit); - let mut rng = StdRng::from_os_rng(); - let mk = mk::Builder::new_from_random_bytes(codec, &mut rng)?.try_build()?; - let fingerprint = mk.fingerprint_view()?.fingerprint(Codec::Blake3)?; - let ef = EncodedMultihash::new(Base::Base32Z, fingerprint); - debug!("Writing {} key fingerprint: {}", key, ef); - let w = writer(&Some(format!("{}.multikey", ef).into()))?; - serde_cbor::to_writer(w, &mk)?; - Ok(mk) - }, - |mk: &Multikey, data: &[u8]| -> Result { - debug!("Signing the first entry"); - Ok(mk.sign_view()?.sign(data, false, None)?) - }, - )?; + update::update_plog(&mut plog, cfg, &key_manager, &key_manager)?; println!("Writing p.log {}", writer_name(&output)?.to_string_lossy()); print_plog(&plog)?; diff --git a/crates/bs/Cargo.toml b/crates/bs/Cargo.toml index a575852..ee01f8a 100644 --- a/crates/bs/Cargo.toml +++ b/crates/bs/Cargo.toml @@ -17,10 +17,8 @@ multikey.workspace = true multisig.workspace = true provenance-log.workspace = true rand.workspace = true -serde_cbor.workspace = true thiserror.workspace = true tracing.workspace = true -wacc.workspace = true [lints] workspace = true diff --git a/crates/bs/src/error.rs b/crates/bs/src/error.rs index fe72900..bb9a2a3 100644 --- a/crates/bs/src/error.rs +++ b/crates/bs/src/error.rs @@ -39,9 +39,6 @@ pub enum Error { /// I/O error #[error(transparent)] Io(#[from] std::io::Error), - /// Serde CBOR error - #[error(transparent)] - SerdeCbor(#[from] serde_cbor::Error), } /// Open op errors diff --git a/crates/bs/src/ops/open.rs b/crates/bs/src/ops/open.rs index 2bea0fc..d673cf1 100644 --- a/crates/bs/src/ops/open.rs +++ b/crates/bs/src/ops/open.rs @@ -7,7 +7,6 @@ pub use config::Config; use crate::{ error::OpenError, update::{op, script, OpParams}, - Error, }; use bs_traits::{GetKey, Signer, SyncGetKey, SyncSigner}; use multicid::{cid, vlad, Cid}; @@ -20,10 +19,18 @@ use std::{fs::read, path::Path}; use tracing::debug; /// open a new provenance log based on the config -pub fn open_plog(config: Config, get_key: &G, sign_entry: &S) -> Result +pub fn open_plog(config: Config, key_manager: &G, signer: &S) -> Result where - G: GetKey + SyncGetKey, - S: Signer + SyncSigner, + G: GetKey + SyncGetKey, + S: Signer + SyncSigner, + E: From + + From + + From + + From + + From + + From + + From + + ToString, { // 0. Set up the list of ops we're going to add let mut op_params = Vec::default(); @@ -33,14 +40,14 @@ where config .additional_ops .iter() - .try_for_each(|params| -> Result<(), Error> { + .try_for_each(|params| -> Result<(), E> { match params { p @ OpParams::KeyGen { .. } => { - let _ = load_key(&mut op_params, p, get_key)?; + let _ = load_key(&mut op_params, p, key_manager)?; } p @ OpParams::CidGen { .. } => { - let _ = load_cid(&mut op_params, p, |path| -> Result, Error> { - Ok(read(path)?) + let _ = load_cid(&mut op_params, p, |path| -> Result, E> { + read(path).map_err(E::from) })?; } p => op_params.push(p.clone()), @@ -53,15 +60,15 @@ where // get the codec for the vlad signing key and cid let (vlad_key_params, vlad_cid_params) = config .vlad_params - .ok_or::(OpenError::InvalidVladParams.into())?; + .ok_or::(OpenError::InvalidVladParams.into())?; // get the vlad signing key - let vlad_mk = load_key(&mut op_params, &vlad_key_params, get_key)?; + let vlad_mk = load_key(&mut op_params, &vlad_key_params, key_manager)?; // get the cid for the first lock script let mut first_lock_script: Option