Skip to content

Commit

Permalink
feat: EvmSigningOffer (#29)
Browse files Browse the repository at this point in the history
* feat: EvmSigningOffer

* fix: build evm-bytes-signer in e2e
  • Loading branch information
8e8b2c authored Jul 8, 2024
1 parent 9879cd9 commit b723acb
Show file tree
Hide file tree
Showing 34 changed files with 1,159 additions and 106 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ jobs:
- run: npx puppeteer browsers install chrome
- run: npm run build:client
- run: npm run build:external-id-attestor
- run: npm run build:evm-bytes-signer
- run: npm run build:mock-oracle
- name: Save client build for possible publish
uses: actions/upload-artifact@v4
Expand All @@ -161,6 +162,10 @@ jobs:
-t holoom/mock-oracle \
-f docker/mock-oracle/Dockerfile \
packages/mock-oracle
docker build \
-t holoom/evm-bytes-signer \
-f docker/evm-bytes-signer/Dockerfile \
packages/evm-bytes-signer
docker build -t holoom/rocket docker/rocket
- name: Start frontend in background
run: npm run dev -w @holoom/e2e &
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ holo_hash = { version = "=0.2.6", features = ["encoding"] }
serde = "=1.0.166"
serde_json = "1.0.109"
bincode = "1.3.3"
hex = "0.4.3"
alloy-primitives = { version = "0.6.3", features = ["serde", "k256"] }
ed25519-dalek = { version = "2.1.1", features = ["serde"] }
bs58 = "0.5.0"
Expand Down
54 changes: 54 additions & 0 deletions crates/holoom_types/src/evm_signing_offer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use hdi::prelude::*;
use serde::{Deserialize, Serialize};

use crate::{EvmAddress, EvmSignature};

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(tag = "type")]
pub enum EvmU256Item {
Uint,
Hex,
}

#[hdk_entry_helper]
#[derive(Clone, PartialEq)]
pub struct EvmSigningOffer {
pub recipe_ah: ActionHash,
pub u256_items: Vec<EvmU256Item>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct CreateEvmSigningOfferPayload {
pub identifier: String,
pub evm_signing_offer: EvmSigningOffer,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct EvmSignatureOverRecipeExecutionRequest {
pub request_id: String,
pub recipe_execution_ah: ActionHash,
pub signing_offer_ah: ActionHash,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct ResolveEvmSignatureOverRecipeExecutionRequestPayload {
pub request_id: String,
pub requestor: AgentPubKey,
pub signed_u256_array: SignedEvmU256Array,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct RejectEvmSignatureOverRecipeExecutionRequestPayload {
pub request_id: String,
pub requestor: AgentPubKey,
pub reason: String,
}

pub type EvmU256 = alloy_primitives::U256;

#[derive(Serialize, Deserialize, Debug)]
pub struct SignedEvmU256Array {
pub raw: Vec<EvmU256>,
pub signature: EvmSignature,
pub signer: EvmAddress,
}
33 changes: 31 additions & 2 deletions crates/holoom_types/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use evm_signing_offer::{EvmU256, SignedEvmU256Array};
use hdi::prelude::*;
use serde::{Deserialize, Serialize};

pub mod external_id;
pub use external_id::*;
pub mod evm_signing_offer;
pub mod metadata;
pub mod recipe;
pub use metadata::*;
Expand Down Expand Up @@ -30,12 +32,39 @@ pub enum LocalHoloomSignal {
request_id: String,
reason: String,
},
EvmSignatureRequested {
request_id: String,
requestor_pubkey: AgentPubKey,
u256_array: Vec<EvmU256>,
},
EvmSignatureProvided {
request_id: String,
signed_u256_array: SignedEvmU256Array,
},
EvmSignatureRequestRejected {
request_id: String,
reason: String,
},
}

#[derive(Serialize, Deserialize, Debug)]
pub enum RemoteHoloomSignal {
ExternalIdAttested { request_id: String, record: Record },
ExternalIdRejected { request_id: String, reason: String },
ExternalIdAttested {
request_id: String,
record: Record,
},
ExternalIdRejected {
request_id: String,
reason: String,
},
EvmSignatureProvided {
request_id: String,
signed_u256_array: SignedEvmU256Array,
},
EvmSignatureRequestRejected {
request_id: String,
reason: String,
},
}

#[derive(Serialize, Deserialize, Debug, Clone, SerializedBytes)]
Expand Down
4 changes: 4 additions & 0 deletions crates/holoom_types/src/recipe.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use hdi::prelude::*;
use serde::{Deserialize, Serialize};

use crate::EvmAddress;

#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)]
#[serde(tag = "type")]
pub enum RecipeArgumentType {
String,
EvmAddress,
}

#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)]
Expand Down Expand Up @@ -46,6 +49,7 @@ pub struct Recipe {
#[serde(tag = "type")]
pub enum RecipeArgument {
String { value: String },
EvmAddress { value: EvmAddress },
}

#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)]
Expand Down
1 change: 1 addition & 0 deletions crates/username_registry_coordinator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ username_registry_utils = { workspace = true }
jaq_wrapper = { workspace = true }
indexmap = "2.2.5"
serde_json = { workspace = true }
hex = { workspace = true }
160 changes: 160 additions & 0 deletions crates/username_registry_coordinator/src/evm_signing_offer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
use hdk::prelude::*;
use holoom_types::{
evm_signing_offer::{
CreateEvmSigningOfferPayload, EvmSignatureOverRecipeExecutionRequest, EvmSigningOffer,
EvmU256, EvmU256Item, RejectEvmSignatureOverRecipeExecutionRequestPayload,
ResolveEvmSignatureOverRecipeExecutionRequestPayload,
},
recipe::RecipeExecution,
LocalHoloomSignal, RemoteHoloomSignal,
};
use jaq_wrapper::{parse_single_json, Val};
use username_registry_integrity::{EntryTypes, LinkTypes};
use username_registry_utils::{deserialize_record_entry, hash_identifier};

#[hdk_extern]
fn create_evm_signing_offer(payload: CreateEvmSigningOfferPayload) -> ExternResult<Record> {
let action_hash = create_entry(EntryTypes::EvmSigningOffer(payload.evm_signing_offer))?;
create_link(
hash_identifier(payload.identifier)?,
action_hash.clone(),
LinkTypes::NameToSigningOffer,
(),
)?;
get(action_hash, GetOptions::default())?.ok_or(wasm_error!(WasmErrorInner::Guest(
"Couldn't get newly created EvmSigningOffer Record".into()
)))
}

#[hdk_extern]
pub fn get_latest_evm_signing_offer_ah_for_name(name: String) -> ExternResult<Option<ActionHash>> {
let base_address = hash_identifier(name)?;
let mut links = get_links(base_address, LinkTypes::NameToSigningOffer, None)?;
links.sort_by_key(|link| link.timestamp);
let Some(link) = links.pop() else {
return Ok(None);
};
let action_hash = ActionHash::try_from(link.target).map_err(|_| {
wasm_error!(WasmErrorInner::Guest(
"Link target isn't an ActionHash".into()
))
})?;
Ok(Some(action_hash))
}

#[hdk_extern]
fn send_request_for_evm_signature_over_recipe_execution(
request: EvmSignatureOverRecipeExecutionRequest,
) -> ExternResult<()> {
let signing_offer_record = get(request.signing_offer_ah.clone(), GetOptions::default())?
.ok_or(wasm_error!(WasmErrorInner::Guest(
"EvmSigningOffer not found".into()
)))?;
let signing_agent = signing_offer_record.action().author().clone();
let zome_name: ZomeName = zome_info()?.name;
let fn_name = FunctionName::from("ingest_evm_signature_over_recipe_execution_request");
let resp = call_remote(signing_agent, zome_name, fn_name, None, request)?;
match resp {
ZomeCallResponse::Ok(result) => result.decode().map_err(|err| wasm_error!(err)),
ZomeCallResponse::NetworkError(err) => Err(wasm_error!(WasmErrorInner::Guest(format!(
"There was a network error: {:?}",
err
)))),
ZomeCallResponse::Unauthorized(..) => {
Err(wasm_error!(WasmErrorInner::Guest("Unauthorized".into())))
}
ZomeCallResponse::CountersigningSession(_) => Err(wasm_error!(WasmErrorInner::Guest(
"Unexpected countersigning session".into()
))),
}
}

#[hdk_extern]
fn ingest_evm_signature_over_recipe_execution_request(
payload: EvmSignatureOverRecipeExecutionRequest,
) -> ExternResult<()> {
let signing_offer_record = get(payload.signing_offer_ah, GetOptions::default())?.ok_or(
wasm_error!(WasmErrorInner::Guest("EvmSigningOffer not found".into())),
)?;
let signing_offer: EvmSigningOffer = deserialize_record_entry(signing_offer_record)?;
let recipe_execution_record = get(payload.recipe_execution_ah, GetOptions::default())?.ok_or(
wasm_error!(WasmErrorInner::Guest("RecipeExecution not found".into())),
)?;
let recipe_execution: RecipeExecution = deserialize_record_entry(recipe_execution_record)?;

if recipe_execution.recipe_ah != signing_offer.recipe_ah {
return Err(wasm_error!(WasmErrorInner::Guest(
"Executed Recipe doesn't match signing offer".into()
)));
}
let Val::Arr(output_vec) = parse_single_json(&recipe_execution.output)? else {
return Err(wasm_error!(WasmErrorInner::Guest(
"Recipe output isn't an array".into()
)))?;
};
if output_vec.len() != signing_offer.u256_items.len() {
return Err(wasm_error!(WasmErrorInner::Guest(
"Unexpected u256 count for signing".into()
)))?;
}
let u256_array = output_vec
.iter()
.zip(signing_offer.u256_items.into_iter())
.map(|pair| match pair {
(Val::Str(hex_string), EvmU256Item::Hex) => EvmU256::from_str_radix(&hex_string, 16)
.map_err(|_| wasm_error!(WasmErrorInner::Guest("Invalid hex string".into()))),
(Val::Int(value), EvmU256Item::Uint) => {
if *value < 0 {
Err(wasm_error!(WasmErrorInner::Guest(
"Negative ints unsupported".into()
)))
} else {
Ok(EvmU256::from(*value))
}
}
_ => Err(wasm_error!(WasmErrorInner::Guest(
"Invalid U256 array element".into()
))),
})
.collect::<ExternResult<Vec<_>>>()?;

let signal = LocalHoloomSignal::EvmSignatureRequested {
request_id: payload.request_id,
requestor_pubkey: call_info()?.provenance,
u256_array,
};
emit_signal(signal)?;
Ok(())
}

#[hdk_extern]
pub fn resolve_evm_signature_over_recipe_execution_request(
payload: ResolveEvmSignatureOverRecipeExecutionRequestPayload,
) -> ExternResult<()> {
let signal = RemoteHoloomSignal::EvmSignatureProvided {
request_id: payload.request_id,
signed_u256_array: payload.signed_u256_array,
};
let signal_encoded = ExternIO::encode(signal)
.map_err(|err: SerializedBytesError| wasm_error!(WasmErrorInner::Serialize(err)))?;
let recipients = vec![payload.requestor];
remote_signal(signal_encoded, recipients)?;

Ok(())
}

#[hdk_extern]
pub fn reject_evm_signature_over_recipe_execution_request(
payload: RejectEvmSignatureOverRecipeExecutionRequestPayload,
) -> ExternResult<()> {
let signal = RemoteHoloomSignal::ExternalIdRejected {
request_id: payload.request_id,
reason: payload.reason,
};
let signal_encoded = ExternIO::encode(signal)
.map_err(|err: SerializedBytesError| wasm_error!(WasmErrorInner::Serialize(err)))?;
let recipients = vec![payload.requestor];
remote_signal(signal_encoded, recipients)?;

Ok(())
}
15 changes: 15 additions & 0 deletions crates/username_registry_coordinator/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod evm_signing_offer;
pub mod external_id_attestation;
pub mod jq_execution;
pub mod oracle_document;
Expand All @@ -23,6 +24,10 @@ pub fn init(_: ()) -> ExternResult<InitCallbackResult> {
zome_name.clone(),
"ingest_external_id_attestation_request".into(),
));
functions.insert((
zome_name.clone(),
"ingest_evm_signature_over_recipe_execution_request".into(),
));
}
functions.insert((zome_name, "recv_remote_signal".into()));
create_cap_grant(CapGrantEntry {
Expand All @@ -47,6 +52,16 @@ fn recv_remote_signal(signal_io: ExternIO) -> ExternResult<()> {
RemoteHoloomSignal::ExternalIdRejected { request_id, reason } => {
emit_signal(LocalHoloomSignal::ExternalIdRejected { request_id, reason })?
}
RemoteHoloomSignal::EvmSignatureProvided {
request_id,
signed_u256_array,
} => emit_signal(LocalHoloomSignal::EvmSignatureProvided {
request_id,
signed_u256_array,
})?,
RemoteHoloomSignal::EvmSignatureRequestRejected { request_id, reason } => {
emit_signal(LocalHoloomSignal::EvmSignatureRequestRejected { request_id, reason })?
}
}

Ok(())
Expand Down
3 changes: 3 additions & 0 deletions crates/username_registry_coordinator/src/recipe_execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ pub fn execute_recipe(payload: ExecuteRecipePayload) -> ExternResult<Record> {
(RecipeArgument::String { value }, RecipeArgumentType::String) => {
Val::str(value.clone())
}
(RecipeArgument::EvmAddress { value }, RecipeArgumentType::EvmAddress) => {
Val::str(value.to_string())
}
_ => {
return Err(wasm_error!(WasmErrorInner::Guest(
"Bad recipe argument".into()
Expand Down
6 changes: 6 additions & 0 deletions crates/username_registry_integrity/src/entry_types.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use hdi::prelude::*;
use holoom_types::{
evm_signing_offer::EvmSigningOffer,
recipe::{Recipe, RecipeExecution},
ExternalIdAttestation, JqExecution, OracleDocument, OracleDocumentListSnapshot,
UsernameAttestation, WalletAttestation,
Expand All @@ -19,6 +20,7 @@ pub enum EntryTypes {
JqExecution(JqExecution),
Recipe(Recipe),
RecipeExecution(RecipeExecution),
EvmSigningOffer(EvmSigningOffer),
}

impl EntryTypes {
Expand Down Expand Up @@ -62,6 +64,10 @@ impl EntryTypes {
EntryCreationAction::Create(action),
recipe_execution,
),
EntryTypes::EvmSigningOffer(evm_signing_offer) => validate_create_evm_signing_offer(
EntryCreationAction::Create(action),
evm_signing_offer,
),
}
}
}
Loading

0 comments on commit b723acb

Please sign in to comment.