Skip to content

Commit

Permalink
Program Configurations MVP (#56)
Browse files Browse the repository at this point in the history
* adds configs to WIT, evaluate function signatures, and examples

* adds a config to example-basic-transaction

* Update README.md

Co-authored-by: Hernando Castano <[email protected]>

* PR fixes

---------

Co-authored-by: Hernando Castano <[email protected]>
  • Loading branch information
jakehemmerle and HCastano authored Jan 18, 2024
1 parent eaa9fbc commit 12e6b65
Show file tree
Hide file tree
Showing 15 changed files with 82 additions and 48 deletions.
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ cargo install cargo-component --version 0.2.0 &&
cargo install wasm-tools
```

## Example Program: `template-barebones`
## A Barebones Program: [`template-barebones`](./examples/barebones/src/lib.rs)

An example of a barebones program is at [`examples/barebones/src/lib.rs`](./examples/barebones/src/lib.rs). This example does a simple check on the length of the message to be signed.

Expand All @@ -32,6 +32,16 @@ cargo component build --release -p template-barebones --target wasm32-unknown-un

This builds the program as a Wasm component at `target/wasm32-unknown-unknown/release/template_barebones.wasm`.

## Example Custody Program with Config: [`example-basic-transaction`](./examples/basic-transaction/src/lib.rs)

This example validates that an an EVM transaction request recipient exists on a list of allowlisted addresses. It also uses a configuration, which allows the user to modify the allowlisted addresses without having to recompile the program (i.e. update the allowlist from the browser).

You can compile the program by running:

```bash
cargo component build --release -p example-basic-transaction --target wasm32-unknown-unknown
```

## Running Tests

Before running the runtime tests, you need to build the `template-barebones` and `infinite-loop` components. To do this, execute:
Expand Down
1 change: 0 additions & 1 deletion acl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ use serde::{Deserialize, Serialize};
Serialize,
Deserialize,
)]
// TODO: Make const; Change Vec<Address> to something like <const Address, const N: usize>
pub struct Acl<Address> {
pub addresses: Vec<Address>,
pub kind: AclKind,
Expand Down
4 changes: 2 additions & 2 deletions acl/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ fn test_acl_functions_properly() {
..Default::default()
};

// should only block whitelisted and null recipient txs
// should only block allowlisted and null recipient txs
assert!(denylisted_acl
.clone()
.is_satisfied_by(to_address_2_tx.clone())
Expand Down Expand Up @@ -105,7 +105,7 @@ fn test_acl_functions_properly() {
allow_null_recipient: true,
};

// should only block whitelisted
// should only block allowlisted
assert!(denylisted_acl_with_null_recipient
.clone()
.is_satisfied_by(to_address_2_tx.clone())
Expand Down
8 changes: 4 additions & 4 deletions examples/barebones-with-auxilary/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub struct BarebonesWithAuxilary;
impl Program for BarebonesWithAuxilary {
/// This is the only function required by the program runtime. `signature_request` includes the message to be
/// signed, eg. RLP-serialized Ethereum transaction request, raw x86_64 executable, etc.
fn evaluate(signature_request: SignatureRequest) -> Result<(), Error> {
fn evaluate(signature_request: SignatureRequest, _config: Option<Vec<u8>>) -> Result<(), Error> {
let SignatureRequest {
message,
auxilary_data,
Expand Down Expand Up @@ -57,7 +57,7 @@ mod tests {
auxilary_data: Some(vec![0x00]),
};

assert!(BarebonesWithAuxilary::evaluate(signature_request).is_ok());
assert!(BarebonesWithAuxilary::evaluate(signature_request, None).is_ok());
}

/// Note, the program is written s.t. if `message` is less than 10 bytes, the program will error.
Expand All @@ -69,7 +69,7 @@ mod tests {
auxilary_data: Some(vec![0x00]),
};

assert!(BarebonesWithAuxilary::evaluate(signature_request).is_err());
assert!(BarebonesWithAuxilary::evaluate(signature_request, None).is_err());
}

/// Note, the program is written s.t. if `auxilary_data` is `None`, the program will error.
Expand All @@ -81,6 +81,6 @@ mod tests {
auxilary_data: None,
};

assert!(BarebonesWithAuxilary::evaluate(signature_request).is_err());
assert!(BarebonesWithAuxilary::evaluate(signature_request, None).is_err());
}
}
6 changes: 3 additions & 3 deletions examples/barebones/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub struct BarebonesProgram;
impl Program for BarebonesProgram {
/// This is the only function required by the program runtime. `message` is the preimage of the curve element to be
/// signed, eg. RLP-serialized Ethereum transaction request, raw x86_64 executable, etc.
fn evaluate(signature_request: SignatureRequest) -> Result<(), Error> {
fn evaluate(signature_request: SignatureRequest, _config: Option<Vec<u8>>) -> Result<(), Error> {
let message: Vec<u8> = signature_request.message;

// our program just checks that the length of the message is greater than 10
Expand Down Expand Up @@ -49,7 +49,7 @@ mod tests {
auxilary_data: None,
};

assert!(BarebonesProgram::evaluate(signature_request).is_ok());
assert!(BarebonesProgram::evaluate(signature_request, None).is_ok());
}

#[test]
Expand All @@ -60,6 +60,6 @@ mod tests {
auxilary_data: None,
};

assert!(BarebonesProgram::evaluate(signature_request).is_err());
assert!(BarebonesProgram::evaluate(signature_request, None).is_err());
}
}
2 changes: 2 additions & 0 deletions examples/basic-transaction/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ crate-type = ["cdylib"]
entropy-programs = { workspace = true }
# TODO move hex parsing into the entropy-programs-evm crate
hex = { version = "0.4.3", default-features = false }
serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] }
serde_json = { version = "1.0", default-features = false, features = ["alloc"]}

# These are used by `cargo component`
[package.metadata.component]
Expand Down
58 changes: 40 additions & 18 deletions examples/basic-transaction/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,44 @@ use entropy_programs::{
programs::acl::*,
};

use alloc::{vec, vec::Vec};
use alloc::{vec::Vec, string::{String, ToString}, format};

use serde_json;
use serde::{Serialize, Deserialize};

pub struct BasicTransaction;

#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct BasicTransactionConfig {
pub allowlisted_addresses: Vec<String>,
}

// TODO confirm this isn't an issue for audit
register_custom_getrandom!(always_fail);

impl Program for BasicTransaction {
/// This is the function that the programs engine will runtime esecute. signature_request is the preimage of the curve element to be
/// signed, eg. RLP-serialized Ethereum transaction request, raw x86_64 executable, etc.
// #[no_mangle]
fn evaluate(state: SignatureRequest) -> Result<(), CoreError> {
// parse the raw tx into some type
fn evaluate(signature_request: SignatureRequest, config: Option<Vec<u8>>) -> Result<(), CoreError> {
// parse the raw tx into some type supported by the Acl check
let parsed_tx =
<Evm as Architecture>::TransactionRequest::try_parse(state.message.as_slice())?;

// construct a whitelist ACL
// TODO can we just use Address instead of AddressRaw?
let whitelisted_address: <Evm as Architecture>::AddressRaw =
hex::decode("772b9a9e8aa1c9db861c6611a82d251db4fac990")
.unwrap()
.try_into()
.unwrap();
<Evm as Architecture>::TransactionRequest::try_parse(signature_request.message.as_slice())?;

// construct a allowlist ACL from the config
let typed_config = serde_json::from_slice::<BasicTransactionConfig>(
config.ok_or(CoreError::Evaluation("No config provided.".to_string()))?.as_slice()
).map_err(|e| CoreError::Evaluation(format!("Failed to parse config: {}", e)))?;

let addresses: Vec<<Evm as Architecture>::AddressRaw> =
typed_config
.allowlisted_addresses
.iter()
.map(|a| hex::decode(a).unwrap().try_into().unwrap())
.collect();

let allowlisted_acl = Acl::<<Evm as Architecture>::AddressRaw> {
addresses: vec![whitelisted_address],
addresses,
..Default::default()
};

Expand All @@ -53,27 +66,36 @@ export_program!(BasicTransaction);
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;

const EVM_TX_WITH_ALLOWLISTED_RECIPIENT: &[u8] = b"0xef01808094772b9a9e8aa1c9db861c6611a82d251db4fac990019243726561746564204f6e20456e74726f7079018080";
const EVM_TX_WITH_NONALLOWLISTED_RECIPIENT: &[u8] = b"0xef01808094772b9a9e8aa1c9db861c6611a82d251db4fac991019243726561746564204f6e20456e74726f7079018080";
const CONFIG: &[u8] = r#"
{
"allowlisted_addresses": [
"772b9a9e8aa1c9db861c6611a82d251db4fac990"
]
}
"#.as_bytes();

#[test]
fn test_evaluate() {
let signature_request = SignatureRequest {
// `data` is an RLP serialized ETH transaction with the recipient set to `0x772b9a9e8aa1c9db861c6611a82d251db4fac990`
message: "0xef01808094772b9a9e8aa1c9db861c6611a82d251db4fac990019243726561746564204f6e20456e74726f7079018080".to_string().into_bytes(),
message: EVM_TX_WITH_ALLOWLISTED_RECIPIENT.to_vec(),
auxilary_data: None
};

assert!(BasicTransaction::evaluate(signature_request).is_ok());
assert!(BasicTransaction::evaluate(signature_request, Some(CONFIG.to_vec())).is_ok());
}

#[test]
fn test_start_fail() {
let signature_request = SignatureRequest {
// `data` is the same as previous test, but recipient address ends in `1` instead of `0`, so it should fail
message: "0xef01808094772b9a9e8aa1c9db861c6611a82d251db4fac991019243726561746564204f6e20456e74726f7079018080".to_string().into_bytes(),
message: EVM_TX_WITH_NONALLOWLISTED_RECIPIENT.to_vec(),
auxilary_data: None
};

assert!(BasicTransaction::evaluate(signature_request).is_err());
assert!(BasicTransaction::evaluate(signature_request, Some(CONFIG.to_vec())).is_err());
}
}
2 changes: 1 addition & 1 deletion examples/custom-hash/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ register_custom_getrandom!(always_fail);
pub struct CustomHashExample;

impl Program for CustomHashExample {
fn evaluate(signature_request: SignatureRequest) -> Result<(), Error> {
fn evaluate(signature_request: SignatureRequest, _config: Option<Vec<u8>>) -> Result<(), Error> {
if signature_request.message.len() < 1 {
return Err(Error::Evaluation(
"You need to give me SOME data to sign!".to_string(),
Expand Down
2 changes: 1 addition & 1 deletion examples/infinite-loop/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub struct InfiniteLoop;
impl Program for InfiniteLoop {
/// This is the only function required by the program runtime. `message` is the preimage of the curve element to be
/// signed, eg. RLP-serialized Ethereum transaction request, raw x86_64 executable, etc.
fn evaluate(_signature_request: SignatureRequest) -> Result<(), Error> {
fn evaluate(_signature_request: SignatureRequest, _config: Option<Vec<u8>>) -> Result<(), Error> {
loop {}
#[allow(unreachable_code)]
Ok(())
Expand Down
6 changes: 3 additions & 3 deletions examples/private-acl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ register_custom_getrandom!(always_fail);
impl Program for PrivateTransactionAcl {
/// Allow any address given in the pre-defined list (addresses.txt)
// #[no_mangle]
fn evaluate(signature_request: SignatureRequest) -> Result<(), CoreError> {
fn evaluate(signature_request: SignatureRequest, _config: Option<Vec<u8>>) -> Result<(), CoreError> {
// parse the raw tx into some type
let parsed_tx = <Evm as Architecture>::TransactionRequest::try_parse(
signature_request.message.as_slice(),
Expand Down Expand Up @@ -72,7 +72,7 @@ mod tests {
auxilary_data: None,
};

assert!(PrivateTransactionAcl::evaluate(signature_request).is_ok());
assert!(PrivateTransactionAcl::evaluate(signature_request, None).is_ok());
}

#[test]
Expand All @@ -83,6 +83,6 @@ mod tests {
auxilary_data: None,
};

assert!(PrivateTransactionAcl::evaluate(signature_request).is_err());
assert!(PrivateTransactionAcl::evaluate(signature_request, None).is_err());
}
}
6 changes: 3 additions & 3 deletions examples/risczero-zkvm-verification/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ register_custom_getrandom!(always_fail);
pub struct ZkVmVerificationProgram;

impl Program for ZkVmVerificationProgram {
fn evaluate(signature_request: SignatureRequest) -> Result<(), Error> {
fn evaluate(signature_request: SignatureRequest, _config: Option<Vec<u8>>) -> Result<(), Error> {
let image_id: [u32; 8] = bincode::deserialize(&signature_request.message)
.map_err(|_| Error::InvalidSignatureRequest("Could not parse image_id".to_string()))?;

Expand Down Expand Up @@ -92,7 +92,7 @@ mod tests {
auxilary_data: Some(bincode::serialize(&read_test_receipt()).unwrap()),
};

assert!(ZkVmVerificationProgram::evaluate(signature_request).is_ok());
assert!(ZkVmVerificationProgram::evaluate(signature_request, None).is_ok());
}

#[test]
Expand All @@ -102,7 +102,7 @@ mod tests {
auxilary_data: Some(bincode::serialize(&read_test_receipt()).unwrap()),
};

assert!(ZkVmVerificationProgram::evaluate(signature_request).is_err());
assert!(ZkVmVerificationProgram::evaluate(signature_request, None).is_err());
}

// Test helper functions
Expand Down
8 changes: 4 additions & 4 deletions examples/siwe/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const ALLOWED_DOMAIN: &str = "localhost";
pub struct Siwe;

impl Program for Siwe {
fn evaluate(signature_request: SignatureRequest) -> Result<(), Error> {
fn evaluate(signature_request: SignatureRequest, _config: Option<Vec<u8>>) -> Result<(), Error> {
let string_message = String::from_utf8(signature_request.message)
.map_err(|err| Error::Evaluation(err.to_string()))?;
let siwe_message = string_message
Expand Down Expand Up @@ -71,7 +71,7 @@ Issued At: 2022-01-28T23:28:16.013Z"
auxilary_data: None,
};

assert!(Siwe::evaluate(signature_request).is_ok());
assert!(Siwe::evaluate(signature_request, None).is_ok());
}

#[test]
Expand All @@ -92,7 +92,7 @@ Issued At: 2022-01-28T23:28:16.013Z"
auxilary_data: None,
};

assert!(Siwe::evaluate(signature_request).is_err());
assert!(Siwe::evaluate(signature_request, None).is_err());
}

#[test]
Expand All @@ -113,6 +113,6 @@ Issued At: 2022-01-28T23:28:16.013Z"
auxilary_data: None,
};

assert!(Siwe::evaluate(signature_request).is_err());
assert!(Siwe::evaluate(signature_request, None).is_err());
}
}
3 changes: 2 additions & 1 deletion runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ impl Runtime {
&mut self,
program: &[u8],
signature_request: &SignatureRequest,
config: Option<&[u8]>
) -> Result<(), RuntimeError> {
if program.len() == 0 {
return Err(RuntimeError::EmptyBytecode);
Expand All @@ -98,7 +99,7 @@ impl Runtime {
.map_err(|_| RuntimeError::InvalidBytecode)?;

bindings
.call_evaluate(&mut self.store, signature_request)
.call_evaluate(&mut self.store, signature_request, config)
.map_err(|_| RuntimeError::OutOfFuel)?
.map_err(RuntimeError::Runtime)
}
Expand Down
8 changes: 4 additions & 4 deletions runtime/tests/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ fn test_barebones_component() {
auxilary_data: None,
};

let res = runtime.evaluate(BAREBONES_COMPONENT_WASM, &signature_request);
let res = runtime.evaluate(BAREBONES_COMPONENT_WASM, &signature_request, None);
assert!(res.is_ok());
}

Expand All @@ -36,7 +36,7 @@ fn test_barebones_component_fails_with_data_length_less_than_10() {
auxilary_data: None,
};

let res = runtime.evaluate(BAREBONES_COMPONENT_WASM, &signature_request);
let res = runtime.evaluate(BAREBONES_COMPONENT_WASM, &signature_request, None);
assert!(res.is_err());
}

Expand All @@ -49,7 +49,7 @@ fn test_empty_bytecode_fails() {
auxilary_data: None,
};

let res = runtime.evaluate(&[], &signature_request);
let res = runtime.evaluate(&[], &signature_request, None);
assert_eq!(res.unwrap_err().to_string(), "Bytecode length is zero");
}

Expand All @@ -62,7 +62,7 @@ fn test_infinite_loop() {
auxilary_data: None,
};

let res = runtime.evaluate(INFINITE_LOOP_WASM, &signature_request);
let res = runtime.evaluate(INFINITE_LOOP_WASM, &signature_request, None);
assert_eq!(res.unwrap_err().to_string(), "Out of fuel");
}

Expand Down
4 changes: 2 additions & 2 deletions wit/application.wit
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ world program {
invalid-signature-request(string),
evaluation(string)
}
/// Contains signature request data that is used by the runtime. Passed into `wasmtime::Store` for state (or maybe `wasmtime::Linker`).
export evaluate: func(signature-request: signature-request) -> result<_, error>
/// Evaluates the program given the user's signature request and the program's configuration.
export evaluate: func(signature-request: signature-request, config: option<list<u8>>) -> result<_, error>

/// Programs that use custom hash functions can a custom 32-byte curve point to be signed.
export custom-hash: func(data: list<u8>) -> option<list<u8>>
Expand Down

0 comments on commit 12e6b65

Please sign in to comment.