Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generalize substrate calls #87

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["programs", "core", "acl", "evm", "runtime", "examples/*"]
members = ["programs", "core", "acl", "evm", "runtime", "examples/*", "substrate"]
exclude = ["templates/*", "examples/risczero-zkvm-verification"]
resolver = "2"

Expand Down
35 changes: 35 additions & 0 deletions examples/substrate/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[package]
name = "template-substrate"
version = "0.1.0"
authors = ["Entropy Cryptography <[email protected]>"]
homepage = "https://entropy.xyz/"
license = "Unlicense"
repository = "https://github.com/entropyxyz/programs"
edition = "2021"

# This is required to compile programs to a wasm module and for use in rust libs
[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
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"]}
schemars = {version = "0.8.16", optional = true}
entropy-programs-substrate = { path = "../../substrate", default-features = false }

[dev-dependencies]
subxt = { version = "0.35.3", default-features=false, features=["native"], target_arch = "wasm32" }
# These are used by `cargo component`
[package.metadata.component]
package = "entropy:template-basic-transaction"

[package.metadata.component.target]
path = "../../wit"

[package.metadata.component.dependencies]

[features]
std = ["schemars"]
11 changes: 11 additions & 0 deletions examples/substrate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# 'Substrate' example template

* uses the substrate helper functions to allow for signatures on substrate chains

* Creates its AuxData and Config with mandatory fields then passes them to ``check_message_against_transaction``
* Once done can now use AuxData fields to apply constraints

### Not checked
* currently nonce and block mortality are not checked (will be addressed eventually)


83 changes: 83 additions & 0 deletions examples/substrate/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#![cfg_attr(not(feature = "std"), no_std)]

extern crate alloc;

use alloc::{format, string::String, vec::Vec};
use entropy_programs::core::{bindgen::*, export_program, prelude::*};
use entropy_programs_substrate::{check_message_against_transaction, HasFieldsAux};

#[cfg(test)]
mod tests;

use serde::{Deserialize, Serialize};

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

/// JSON-deserializable struct that will be used to derive the program-JSON interface.
#[cfg_attr(feature = "std", derive(schemars::JsonSchema))]
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct UserConfig {}

/// JSON representation of the auxiliary data
#[cfg_attr(feature = "std", derive(schemars::JsonSchema))]
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct AuxData {
pub spec_version: u32,
pub transaction_version: u32,
pub values: String,
pub pallet: String,
pub function: String,
pub genesis_hash: String,
}

impl HasFieldsAux for AuxData {
fn genesis_hash(&self) -> &String {
&self.genesis_hash
}
fn spec_version(&self) -> &u32 {
&self.spec_version
}

fn transaction_version(&self) -> &u32 {
&self.transaction_version
}
fn values(&self) -> &String {
&self.values
}
fn pallet(&self) -> &String {
&self.pallet
}
fn function(&self) -> &String {
&self.function
}
}

pub struct SubstrateProgram;

impl Program for SubstrateProgram {
fn evaluate(
signature_request: SignatureRequest,
_config: Option<Vec<u8>>,
_oracle_data: Option<Vec<u8>>,
) -> Result<(), Error> {
let (_aux_data, _api) = check_message_against_transaction::<AuxData>(signature_request)
.map_err(|e| {
Error::InvalidSignatureRequest(format!(
"Error comparing tx request and message: {}",
e
))
})?;

// can now use aux data and user config to apply constraints
// Ex: balance limit, genesis hash (locks it to a chain or specific chains)
Ok(())
}

/// Since we don't use a custom hash function, we can just return `None` here.
fn custom_hash(_data: Vec<u8>) -> Option<Vec<u8>> {
None
}
}

export_program!(SubstrateProgram);
106 changes: 106 additions & 0 deletions examples/substrate/src/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use super::*;
use alloc::{
string::{ToString},
vec,
};
use entropy_programs_substrate::{get_offline_api, handle_encoding};
use subxt::config::PolkadotExtrinsicParamsBuilder as Params;
use subxt::dynamic::tx;

#[test]
fn test_should_sign() {
let aux_data = create_aux_data();

let api = get_offline_api(
aux_data.genesis_hash.clone(),
aux_data.spec_version,
aux_data.transaction_version,
)
.unwrap();

let deserialized: Vec<(&str, &str)> = serde_json::from_str(&aux_data.values).unwrap();
let encoding = handle_encoding(deserialized).unwrap();

let balance_transfer_tx = tx(aux_data.pallet.clone(), aux_data.function.clone(), encoding);

let tx_params = Params::new().build();

let partial = api
.tx()
.create_partial_signed_offline(&balance_transfer_tx, tx_params)
.unwrap()
.signer_payload();

let signature_request = SignatureRequest {
message: partial.to_vec(),
auxilary_data: Some(serde_json::to_string(&aux_data).unwrap().into_bytes()),
};

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

#[test]
fn test_should_fail() {
let aux_data = create_aux_data();

let api = get_offline_api(
aux_data.genesis_hash.clone(),
aux_data.spec_version,
aux_data.transaction_version,
)
.unwrap();

let amount = 1000u128;
let binding = amount.to_string();
let string_account_id = "5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TTpu";

let values: Vec<(&str, &str)> = vec![("account", string_account_id), ("amount", &binding)];

let encoding = handle_encoding(values.clone()).unwrap();
let balance_transfer_tx = tx(
aux_data.pallet.clone(),
aux_data.function.clone(),
encoding.clone(),
);

let tx_params = Params::new().build();

let partial = api
.tx()
.create_partial_signed_offline(&balance_transfer_tx, tx_params)
.unwrap()
.signer_payload();

let signature_request = SignatureRequest {
message: partial.to_vec(),
auxilary_data: Some(serde_json::to_string(&aux_data).unwrap().into_bytes()),
};

assert_eq!(
SubstrateProgram::evaluate(signature_request, None, None)
.unwrap_err()
.to_string(),
"Error::InvalidSignatureRequest(\"Error comparing tx request and message: Error::Evaluation(\\\"Signatures don't match, message: \\\\\\\"07000088dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0eea10f0000000a0000000a00000044670a68177821a6166b25f8d86b45e0f1c3b280ff576eea64057e4b0dd9ff4a44670a68177821a6166b25f8d86b45e0f1c3b280ff576eea64057e4b0dd9ff4a\\\\\\\", calldata: \\\\\\\"07000088dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee9101\\\\\\\", genesis_hash: \\\\\\\"34343637306136383137373832316136313636623235663864383662343565306631633362323830666635373665656136343035376534623064643966663461\\\\\\\"\\\")\")"
);
}

pub fn create_aux_data() -> AuxData {
let genesis_hash =
"44670a68177821a6166b25f8d86b45e0f1c3b280ff576eea64057e4b0dd9ff4a".to_string();
let spec_version = 10;
let transaction_version = 10;
let string_account_id = "5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TTpu";
let amount = 100u128;
let binding = amount.to_string();
let values: Vec<(&str, &str)> = vec![("account", string_account_id), ("amount", &binding)];

let aux_data = AuxData {
genesis_hash,
spec_version,
transaction_version,
pallet: "Balances".to_string(),
function: "transfer_allow_death".to_string(),
values: serde_json::to_string(&values).unwrap(),
};
aux_data
}
24 changes: 24 additions & 0 deletions substrate/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
name = "entropy-programs-substrate"
version = "0.1.0"
authors = ["Entropy Cryptography <[email protected]>"]
homepage = "https://entropy.xyz/"
license = "AGPL-3.0-or-later"
repository = "https://github.com/entropyxyz/programs"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
entropy-programs-core = { path = "../core" }
subxt = { version = "0.35.3", default-features=false, features=["native"], target_arch = "wasm32" }
codec = { package = "parity-scale-codec", version = "3.6.9", default-features = false }
hex ={ version="0.4.3" }
serde_json = "1.0.48"
serde ={ version="1.0", default-features=false, features=["derive"] }



[features]
default = []
std = []
12 changes: 12 additions & 0 deletions substrate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# `entropy-programs-substrate`

Provides substrate helper functions to help with talking to substrate chains

See examples/substrate for example of usage

### Usage

* Takes a Config and SignatureRequest
* Uses this data to build a transaction and use the data from the aux info to rebuild a transaction and compare it to what a user it trying to sign
* Errors if they don't match
* After this a program dev can use the details in the transaction to apply constraints, knowing that that is what is being signed
17 changes: 17 additions & 0 deletions substrate/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use std::env;
use std::fs;
use std::path::Path;

fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("metadata.rs");

let metadata = fs::read("substrate_metadata.scale").unwrap();
fs::write(
dest_path,
format!("const METADATA: &[u8] = &{:?};\n", metadata),
)
.unwrap();
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=substrate_metadata.scale");
}
Loading