From 3afbed6afcf2be39183c78b33177c428b767aba9 Mon Sep 17 00:00:00 2001 From: 8e8b2c <138928994+8e8b2c@users.noreply.github.com> Date: Tue, 6 Aug 2024 15:51:13 +0100 Subject: [PATCH] feat!: verifiably locate signing offers against signer address (#74) * feat!: verifiably locate signing offers against signer address * chore: add comment --- .../src/tests/username_registry/mod.rs | 2 +- crates/holoom_types/src/evm_signing_offer.rs | 16 +++- .../src/evm_signing_offer.rs | 47 ++++++++--- .../src/entry_types.rs | 14 ++-- .../src/link_types.rs | 18 +++++ crates/username_registry_utils/src/lib.rs | 11 ++- .../src/evm_address_to_signing_offer.rs | 47 +++++++++++ .../src/evm_signing_offer.rs | 51 ++++++++++-- .../username_registry_validation/src/lib.rs | 2 + .../src/evm-bytes-signer/bytes-signer.ts | 12 ++- .../evm-bytes-signer-client.ts | 4 +- .../authority/src/evm-bytes-signer/index.ts | 30 +++----- .../src/evm-bytes-signer/offer-creator.ts | 62 +++++++++++++++ packages/e2e/tests/signing-offer.test.js | 77 ++++++++++++------- .../types/src/CreateEvmSigningOfferPayload.ts | 4 +- packages/types/src/SignedEvmSigningOffer.ts | 8 ++ packages/types/src/index.ts | 1 + 17 files changed, 330 insertions(+), 76 deletions(-) create mode 100644 crates/username_registry_validation/src/evm_address_to_signing_offer.rs create mode 100644 packages/authority/src/evm-bytes-signer/offer-creator.ts create mode 100644 packages/types/src/SignedEvmSigningOffer.ts diff --git a/crates/holoom_dna_tests/src/tests/username_registry/mod.rs b/crates/holoom_dna_tests/src/tests/username_registry/mod.rs index 073216e..6d00bb4 100644 --- a/crates/holoom_dna_tests/src/tests/username_registry/mod.rs +++ b/crates/holoom_dna_tests/src/tests/username_registry/mod.rs @@ -1,6 +1,6 @@ +mod external_id_attestation; mod oracle; mod recipe; mod user_metadata; mod username_attestation; mod wallet_attestation; -mod external_id_attestation; diff --git a/crates/holoom_types/src/evm_signing_offer.rs b/crates/holoom_types/src/evm_signing_offer.rs index 277e2b6..905e2b2 100644 --- a/crates/holoom_types/src/evm_signing_offer.rs +++ b/crates/holoom_types/src/evm_signing_offer.rs @@ -13,8 +13,7 @@ pub enum EvmU256Item { HoloAgent, } -#[hdk_entry_helper] -#[derive(Clone, PartialEq, TS)] +#[derive(Clone, PartialEq, TS, Serialize, Deserialize, Debug)] #[ts(export)] pub struct EvmSigningOffer { #[ts(type = "ActionHash")] @@ -22,11 +21,22 @@ pub struct EvmSigningOffer { pub u256_items: Vec, } +#[hdk_entry_helper] +#[derive(Clone, PartialEq, TS)] +#[ts(export)] +pub struct SignedEvmSigningOffer { + #[ts(type = "Uint8Array")] + pub signer: EvmAddress, + #[ts(type = "[Uint8Array, Uint8Array, number]")] + pub signature: EvmSignature, + pub offer: EvmSigningOffer, +} + #[derive(Serialize, Deserialize, Debug, TS)] #[ts(export)] pub struct CreateEvmSigningOfferPayload { pub identifier: String, - pub evm_signing_offer: EvmSigningOffer, + pub signed_offer: SignedEvmSigningOffer, } #[derive(Serialize, Deserialize, Debug, TS)] diff --git a/crates/username_registry_coordinator/src/evm_signing_offer.rs b/crates/username_registry_coordinator/src/evm_signing_offer.rs index fc8b42f..27c29b2 100644 --- a/crates/username_registry_coordinator/src/evm_signing_offer.rs +++ b/crates/username_registry_coordinator/src/evm_signing_offer.rs @@ -1,26 +1,34 @@ use hdk::prelude::*; use holoom_types::{ evm_signing_offer::{ - CreateEvmSigningOfferPayload, EvmSignatureOverRecipeExecutionRequest, EvmSigningOffer, - EvmU256, EvmU256Item, RejectEvmSignatureOverRecipeExecutionRequestPayload, - ResolveEvmSignatureOverRecipeExecutionRequestPayload, + CreateEvmSigningOfferPayload, EvmSignatureOverRecipeExecutionRequest, EvmU256, EvmU256Item, + RejectEvmSignatureOverRecipeExecutionRequestPayload, + ResolveEvmSignatureOverRecipeExecutionRequestPayload, SignedEvmSigningOffer, }, recipe::RecipeExecution, - LocalHoloomSignal, RemoteHoloomSignal, + EvmAddress, LocalHoloomSignal, RemoteHoloomSignal, }; use jaq_wrapper::{parse_single_json, Val}; use username_registry_integrity::{EntryTypes, LinkTypes}; -use username_registry_utils::{deserialize_record_entry, hash_identifier}; +use username_registry_utils::{deserialize_record_entry, hash_evm_address, hash_identifier}; #[hdk_extern] -fn create_evm_signing_offer(payload: CreateEvmSigningOfferPayload) -> ExternResult { - let action_hash = create_entry(EntryTypes::EvmSigningOffer(payload.evm_signing_offer))?; +fn create_signed_evm_signing_offer(payload: CreateEvmSigningOfferPayload) -> ExternResult { + let action_hash = create_entry(EntryTypes::SignedEvmSigningOffer( + payload.signed_offer.clone(), + ))?; create_link( hash_identifier(payload.identifier)?, action_hash.clone(), LinkTypes::NameToSigningOffer, (), )?; + create_link( + hash_evm_address(payload.signed_offer.signer)?, + action_hash.clone(), + LinkTypes::EvmAddressToSigningOffer, + (), + )?; get(action_hash, GetOptions::network())?.ok_or(wasm_error!(WasmErrorInner::Guest( "Couldn't get newly created EvmSigningOffer Record".into() ))) @@ -44,6 +52,22 @@ pub fn get_latest_evm_signing_offer_ah_for_name(name: String) -> ExternResult ExternResult> { + let base_address = hash_evm_address(evm_address)?; + let mut links = get_links( + GetLinksInputBuilder::try_new(base_address, LinkTypes::EvmAddressToSigningOffer)?.build(), + )?; + links.sort_by_key(|link| link.timestamp); + let ahs = links + .into_iter() + .filter_map(|link| ActionHash::try_from(link.target).ok()) + .collect(); + Ok(ahs) +} + #[hdk_extern] fn send_request_for_evm_signature_over_recipe_execution( request: EvmSignatureOverRecipeExecutionRequest, @@ -78,13 +102,14 @@ fn ingest_evm_signature_over_recipe_execution_request( let signing_offer_record = get(payload.signing_offer_ah, GetOptions::network())?.ok_or( wasm_error!(WasmErrorInner::Guest("EvmSigningOffer not found".into())), )?; - let signing_offer: EvmSigningOffer = deserialize_record_entry(signing_offer_record)?; + let signed_signing_offer: SignedEvmSigningOffer = + deserialize_record_entry(signing_offer_record)?; let recipe_execution_record = get(payload.recipe_execution_ah, GetOptions::network())?.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 { + if recipe_execution.recipe_ah != signed_signing_offer.offer.recipe_ah { return Err(wasm_error!(WasmErrorInner::Guest( "Executed Recipe doesn't match signing offer".into() ))); @@ -94,14 +119,14 @@ fn ingest_evm_signature_over_recipe_execution_request( "Recipe output isn't an array".into() )))?; }; - if output_vec.len() != signing_offer.u256_items.len() { + if output_vec.len() != signed_signing_offer.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()) + .zip(signed_signing_offer.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()))), diff --git a/crates/username_registry_integrity/src/entry_types.rs b/crates/username_registry_integrity/src/entry_types.rs index 2079431..f63e87b 100644 --- a/crates/username_registry_integrity/src/entry_types.rs +++ b/crates/username_registry_integrity/src/entry_types.rs @@ -1,6 +1,6 @@ use hdi::prelude::*; use holoom_types::{ - evm_signing_offer::EvmSigningOffer, + evm_signing_offer::SignedEvmSigningOffer, recipe::{Recipe, RecipeExecution}, ExternalIdAttestation, OracleDocument, UsernameAttestation, WalletAttestation, }; @@ -17,7 +17,7 @@ pub enum EntryTypes { OracleDocument(OracleDocument), Recipe(Recipe), RecipeExecution(RecipeExecution), - EvmSigningOffer(EvmSigningOffer), + SignedEvmSigningOffer(SignedEvmSigningOffer), } impl EntryTypes { @@ -52,10 +52,12 @@ impl EntryTypes { EntryCreationAction::Create(action), recipe_execution, ), - EntryTypes::EvmSigningOffer(evm_signing_offer) => validate_create_evm_signing_offer( - EntryCreationAction::Create(action), - evm_signing_offer, - ), + EntryTypes::SignedEvmSigningOffer(evm_signing_offer) => { + validate_create_signed_evm_signing_offer( + EntryCreationAction::Create(action), + evm_signing_offer, + ) + } } } } diff --git a/crates/username_registry_integrity/src/link_types.rs b/crates/username_registry_integrity/src/link_types.rs index ecc5035..efe895f 100644 --- a/crates/username_registry_integrity/src/link_types.rs +++ b/crates/username_registry_integrity/src/link_types.rs @@ -13,6 +13,7 @@ pub enum LinkTypes { RelateOracleDocumentName, NameToRecipe, NameToSigningOffer, + EvmAddressToSigningOffer, } impl LinkTypes { @@ -80,6 +81,14 @@ impl LinkTypes { target_address, tag, ), + LinkTypes::EvmAddressToSigningOffer => { + validate_create_link_evm_address_to_signing_offer( + action, + base_address, + target_address, + tag, + ) + } } } @@ -164,6 +173,15 @@ impl LinkTypes { target_address, tag, ), + LinkTypes::EvmAddressToSigningOffer => { + validate_delete_link_evm_address_to_signing_offer( + action, + original_action, + base_address, + target_address, + tag, + ) + } } } } diff --git a/crates/username_registry_utils/src/lib.rs b/crates/username_registry_utils/src/lib.rs index d2d032b..764a3ea 100644 --- a/crates/username_registry_utils/src/lib.rs +++ b/crates/username_registry_utils/src/lib.rs @@ -1,5 +1,5 @@ use hdi::prelude::*; -use holoom_types::HoloomDnaProperties; +use holoom_types::{EvmAddress, HoloomDnaProperties}; pub fn deserialize_record_entry(record: Record) -> ExternResult where @@ -24,6 +24,15 @@ pub fn hash_identifier(identifier: String) -> ExternResult { hash_entry(Entry::App(AppEntryBytes(bytes))) } +pub fn hash_evm_address(evm_address: EvmAddress) -> ExternResult { + #[derive(SerializedBytes, Serialize, Debug, Deserialize)] + struct SerializableEvmAddress(EvmAddress); + + let bytes = SerializedBytes::try_from(SerializableEvmAddress(evm_address)) + .map_err(|err| wasm_error!(err))?; + hash_entry(Entry::App(AppEntryBytes(bytes))) +} + pub fn get_authority_agent() -> ExternResult { let dna_props = HoloomDnaProperties::try_from_dna_properties()?; AgentPubKey::try_from(dna_props.authority_agent).map_err(|_| { diff --git a/crates/username_registry_validation/src/evm_address_to_signing_offer.rs b/crates/username_registry_validation/src/evm_address_to_signing_offer.rs new file mode 100644 index 0000000..d087278 --- /dev/null +++ b/crates/username_registry_validation/src/evm_address_to_signing_offer.rs @@ -0,0 +1,47 @@ +use hdi::prelude::*; +use holoom_types::evm_signing_offer::SignedEvmSigningOffer; +use username_registry_utils::{deserialize_record_entry, hash_evm_address}; + +pub fn validate_create_link_evm_address_to_signing_offer( + action: CreateLink, + base_address: AnyLinkableHash, + target_address: AnyLinkableHash, + _tag: LinkTag, +) -> ExternResult { + let Ok(target_address) = ActionHash::try_from(target_address) else { + return Ok(ValidateCallbackResult::Invalid( + "target_address must be an ActionHash".into(), + )); + }; + let record = must_get_valid_record(target_address)?; + if &action.author != record.action().author() { + return Ok(ValidateCallbackResult::Invalid( + "link and target must have same author".into(), + )); + } + + let Ok(signed_offer) = deserialize_record_entry::(record) else { + return Ok(ValidateCallbackResult::Invalid( + "target_address must be a SignedEvmSigningOffer".into(), + )); + }; + + if base_address != hash_evm_address(signed_offer.signer)?.into() { + return Ok(ValidateCallbackResult::Invalid( + "base_address must be hash of evm signer".into(), + )); + } + + Ok(ValidateCallbackResult::Valid) +} +pub fn validate_delete_link_evm_address_to_signing_offer( + _action: DeleteLink, + _original_action: CreateLink, + _base_address: AnyLinkableHash, + _target_address: AnyLinkableHash, + _tag: LinkTag, +) -> ExternResult { + Ok(ValidateCallbackResult::Invalid( + "Cannot delete EvmAddressToSigningOffer links".into(), + )) +} diff --git a/crates/username_registry_validation/src/evm_signing_offer.rs b/crates/username_registry_validation/src/evm_signing_offer.rs index ec54d88..b43db17 100644 --- a/crates/username_registry_validation/src/evm_signing_offer.rs +++ b/crates/username_registry_validation/src/evm_signing_offer.rs @@ -1,10 +1,51 @@ use hdi::prelude::*; -use holoom_types::evm_signing_offer::EvmSigningOffer; +use holoom_types::{ + evm_signing_offer::{EvmU256Item, SignedEvmSigningOffer}, + recipe::Recipe, +}; +use username_registry_utils::deserialize_record_entry; -pub fn validate_create_evm_signing_offer( +pub fn validate_create_signed_evm_signing_offer( _action: EntryCreationAction, - _evm_signing_offer: EvmSigningOffer, + signed_evm_signing_offer: SignedEvmSigningOffer, ) -> ExternResult { - // TODO: check recipe exists - Ok(ValidateCallbackResult::Valid) + let recipe_record = must_get_valid_record(signed_evm_signing_offer.offer.recipe_ah.clone())?; + if deserialize_record_entry::(recipe_record).is_err() { + // This check seems brittle. See https://github.com/holochain-open-dev/holoom/issues/69 + return Ok(ValidateCallbackResult::Invalid( + "recipe_ah doesn't point to a Recipe".into(), + )); + } + + // Ensure a stable byte order + #[derive(Serialize, Debug)] + struct OrderedSigningOffer(ActionHash, Vec); + let ordered_offer = OrderedSigningOffer( + signed_evm_signing_offer.offer.recipe_ah, + signed_evm_signing_offer.offer.u256_items, + ); + + let message = ExternIO::encode(ordered_offer) + .expect("EvmSigningOffer implements Serialize") + .into_vec(); + + match signed_evm_signing_offer + .signature + .recover_address_from_msg(&message) + { + Ok(recovered_address) => { + if recovered_address == signed_evm_signing_offer.signer { + Ok(ValidateCallbackResult::Valid) + } else { + Ok(ValidateCallbackResult::Invalid(format!( + "Expected to recover {} from signature, but instead recovered {}", + signed_evm_signing_offer.signer.to_checksum(None), + recovered_address.to_checksum(None) + ))) + } + } + Err(_) => Ok(ValidateCallbackResult::Invalid( + "Invalid signature over wallet binding message".into(), + )), + } } diff --git a/crates/username_registry_validation/src/lib.rs b/crates/username_registry_validation/src/lib.rs index e9c1340..a39669d 100644 --- a/crates/username_registry_validation/src/lib.rs +++ b/crates/username_registry_validation/src/lib.rs @@ -30,3 +30,5 @@ pub mod name_to_recipe; pub use name_to_recipe::*; pub mod name_to_evm_signing_offer; pub use name_to_evm_signing_offer::*; +pub mod evm_address_to_signing_offer; +pub use evm_address_to_signing_offer::*; diff --git a/packages/authority/src/evm-bytes-signer/bytes-signer.ts b/packages/authority/src/evm-bytes-signer/bytes-signer.ts index 2605e58..69889ed 100644 --- a/packages/authority/src/evm-bytes-signer/bytes-signer.ts +++ b/packages/authority/src/evm-bytes-signer/bytes-signer.ts @@ -1,6 +1,8 @@ import { bytesToBigInt, encodePacked, Hex, hexToBytes, keccak256 } from "viem"; import { privateKeyToAccount, PrivateKeyAccount } from "viem/accounts"; import { formatEvmSignature } from "./utils.js"; +import { EvmSigningOffer } from "@holoom/types"; +import { encode } from "@msgpack/msgpack"; export class BytesSigner { readonly account: PrivateKeyAccount; @@ -10,7 +12,15 @@ export class BytesSigner { this.address = hexToBytes(this.account.address); } - async sign(u256_array: Uint8Array[]) { + async sign_offer(offer: EvmSigningOffer) { + console.log("signing offer", offer); + // offer is encoded as a tuple to give stable ordering + const raw = encode([offer.recipe_ah, offer.u256_items]); + const hex = await this.account.signMessage({ message: { raw } }); + return formatEvmSignature(hex); + } + + async sign_u256_array(u256_array: Uint8Array[]) { console.log("signing u256_array", u256_array); const packed = encodePacked( ["uint256[]"], diff --git a/packages/authority/src/evm-bytes-signer/evm-bytes-signer-client.ts b/packages/authority/src/evm-bytes-signer/evm-bytes-signer-client.ts index 56c67fa..c12a22b 100644 --- a/packages/authority/src/evm-bytes-signer/evm-bytes-signer-client.ts +++ b/packages/authority/src/evm-bytes-signer/evm-bytes-signer-client.ts @@ -56,7 +56,9 @@ export class EvmBytesSignerClient { async handleEvmSignatureRequested(signal: EvmSignatureRequested) { console.log("handleEvmSignatureRequested"); try { - const signature = await this.bytesSigner.sign(signal.u256_array); + const signature = await this.bytesSigner.sign_u256_array( + signal.u256_array + ); // Will node complain about this orphaned promise? this.confirmRequest({ request_id: signal.request_id, diff --git a/packages/authority/src/evm-bytes-signer/index.ts b/packages/authority/src/evm-bytes-signer/index.ts index b5f7f4b..8a4ab98 100644 --- a/packages/authority/src/evm-bytes-signer/index.ts +++ b/packages/authority/src/evm-bytes-signer/index.ts @@ -1,9 +1,9 @@ import dotenv from "dotenv"; -import { AdminWebsocket, AppWebsocket, Record } from "@holochain/client"; +import { AdminWebsocket, AppWebsocket } from "@holochain/client"; import { BytesSigner } from "./bytes-signer.js"; import { EvmBytesSignerClient } from "./evm-bytes-signer-client.js"; import express, { Request, Response } from "express"; -import { CreateEvmSigningOfferPayload } from "@holoom/types"; +import { OfferCreator } from "./offer-creator.js"; export async function runEvmBytesSignerFromEnv() { dotenv.config(); @@ -34,13 +34,15 @@ export async function runEvmBytesSignerFromEnv() { token: issuedToken.token, }); - const accessTokenAssessor = new BytesSigner(getEnv("EVM_PRIVATE_KEY")); + const bytesSigner = new BytesSigner(getEnv("EVM_PRIVATE_KEY")); const _evmBytesSignerClient = new EvmBytesSignerClient( appAgentClient, - accessTokenAssessor + bytesSigner ); + const offerCreator = new OfferCreator(appAgentClient, bytesSigner); + console.log("EvmBytesSignerClient listening for incoming requests"); const app = express(); @@ -53,21 +55,11 @@ export async function runEvmBytesSignerFromEnv() { console.log("POST: /evm-signing-offer", req.body); try { - const payload: CreateEvmSigningOfferPayload = { - identifier: req.body.identifier, - evm_signing_offer: { - recipe_ah: new Uint8Array(req.body.evm_signing_offer.recipe_ah), - u256_items: req.body.evm_signing_offer.u256_items, - }, - }; - const record: Record = await appAgentClient.callZome({ - role_name: "holoom", - zome_name: "username_registry", - fn_name: "create_evm_signing_offer", - payload, - }); - console.log("Created record", record); - const actionHash = Array.from(record.signed_action.hashed.hash); + const actionHash = await offerCreator.createOffer( + req.body.identifier, + req.body.evm_signing_offer.recipe_ah, + req.body.evm_signing_offer.u256_items + ); res.status(200).send({ actionHash }); } catch (err) { console.error(err); diff --git a/packages/authority/src/evm-bytes-signer/offer-creator.ts b/packages/authority/src/evm-bytes-signer/offer-creator.ts new file mode 100644 index 0000000..dd990f7 --- /dev/null +++ b/packages/authority/src/evm-bytes-signer/offer-creator.ts @@ -0,0 +1,62 @@ +import { ActionHash, AppClient, Record } from "@holochain/client"; +import { + CreateEvmSigningOfferPayload, + EvmSigningOffer, + EvmU256Item, +} from "@holoom/types"; +import { BytesSigner } from "./bytes-signer"; + +export class OfferCreator { + constructor( + readonly appClient: AppClient, + readonly bytesSigner: BytesSigner + ) {} + + async createOffer( + identifier: string, + recipeAh: number[], + items: EvmU256Item[] + ) { + const offer: EvmSigningOffer = { + recipe_ah: new Uint8Array(recipeAh), + u256_items: items, + }; + + // Avoids source chain error: Awaiting deps + await this.untilRecipeGossiped(offer.recipe_ah); + + const signature = await this.bytesSigner.sign_offer(offer); + const payload: CreateEvmSigningOfferPayload = { + identifier, + signed_offer: { + signer: this.bytesSigner.address, + signature, + offer, + }, + }; + const record: Record = await this.appClient.callZome({ + role_name: "holoom", + zome_name: "username_registry", + fn_name: "create_signed_evm_signing_offer", + payload, + }); + console.log("Created record", record); + const actionHash = Array.from(record.signed_action.hashed.hash); + return actionHash; + } + + private async untilRecipeGossiped(recipeAh: ActionHash) { + const deadline = Date.now() + 10_000; + while (Date.now() < deadline) { + const record: Record = await this.appClient.callZome({ + role_name: "holoom", + zome_name: "records", + fn_name: "get_record", + payload: recipeAh, + }); + if (record) return; + await new Promise((r) => setTimeout(r, 500)); + } + throw new Error("Recipe still not gossiped after 10s"); + } +} diff --git a/packages/e2e/tests/signing-offer.test.js b/packages/e2e/tests/signing-offer.test.js index f461fd2..a7246e4 100644 --- a/packages/e2e/tests/signing-offer.test.js +++ b/packages/e2e/tests/signing-offer.test.js @@ -1,6 +1,12 @@ const { startTestContainers } = require("./utils/testcontainers"); const { loadPageAndRegister } = require("./utils/holo"); -const { verifyMessage, bytesToHex, encodePacked, keccak256 } = require("viem"); +const { + verifyMessage, + bytesToHex, + encodePacked, + keccak256, + hexToBytes, +} = require("viem"); describe("signing-offer", () => { let testContainers; @@ -41,30 +47,34 @@ describe("signing-offer", () => { }); debug("Created recipe"); - await fetch("http://localhost:8002/evm-signing-offer", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer password", - }, - body: JSON.stringify({ - identifier: "123", - evm_signing_offer: { - recipe_ah, - u256_items: [ - { type: "Uint" }, - { type: "Hex" }, - { type: "Hex" }, - { type: "HoloAgent" }, - ], + const createOfferResp = await fetch( + "http://localhost:8002/evm-signing-offer", + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer password", }, - }), - }); + body: JSON.stringify({ + identifier: "123", + evm_signing_offer: { + recipe_ah, + u256_items: [ + { type: "Uint" }, + { type: "Hex" }, + { type: "Hex" }, + { type: "HoloAgent" }, + ], + }, + }), + } + ); + expect(createOfferResp.status).toBe(200); debug("Created EvmSigningOffer"); - let signingOfferActionHash; + let signingOfferActionHashNumArray; while (true) { - signingOfferActionHash = await page.evaluate(async () => { + signingOfferActionHashNumArray = await page.evaluate(async () => { const actionHash = await clients.holo.callZome({ role_name: "holoom", zome_name: "username_registry", @@ -73,7 +83,7 @@ describe("signing-offer", () => { }); return actionHash ? Array.from(actionHash) : null; }); - if (signingOfferActionHash) { + if (signingOfferActionHashNumArray) { break; } else { await new Promise((r) => setTimeout(r, 500)); @@ -81,8 +91,23 @@ describe("signing-offer", () => { } debug("Polled until EvmSigningOffer gossiped"); + const ahsNumArrs = await page.evaluate( + async (evmAddressNumArr) => { + const ahs = await clients.holo.callZome({ + role_name: "holoom", + zome_name: "username_registry", + fn_name: "get_signing_offer_ahs_for_evm_address", + payload: new Uint8Array(evmAddressNumArr), + }); + return ahs.map((ah) => Array.from(ah)); + }, + Array.from(hexToBytes("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266")) + ); + expect(ahsNumArrs).toEqual([signingOfferActionHashNumArray]); + debug("Check offer linked against evm address"); + const signature = await page.evaluate( - async (recipe_ah, evm_signing_offer_ah) => { + async (recipe_ah, signingOfferActionHashNumArray) => { const executionRecord = await clients.holo.callZome({ role_name: "holoom", zome_name: "username_registry", @@ -97,7 +122,7 @@ describe("signing-offer", () => { const signedContext = await clients.evmSignatureRequestor.requestEvmSignature({ recipeExecutionAh: executionRecord.signed_action.hashed.hash, - signingOfferAh: new Uint8Array(evm_signing_offer_ah), + signingOfferAh: new Uint8Array(signingOfferActionHashNumArray), }); // Flatten signature return [ @@ -115,7 +140,7 @@ describe("signing-offer", () => { } }, recipe_ah, - Array.from(signingOfferActionHash) + signingOfferActionHashNumArray ); debug("Executed recipe and received signature for it"); @@ -126,7 +151,7 @@ describe("signing-offer", () => { (10n * 10n ** 18n) / 100n, "0x48509e384C66FDa5cFDF12A360B9eF2367158938", // Big endian u256 read of raw 32 of uhCAknOsaM2At-JjiUzHGuk_YXuNwQDYcPK-Pyq_feS3n6oLc_C2N - 70976194269703664889787012553258964581971848280304479022442879760825621932674n + 70976194269703664889787012553258964581971848280304479022442879760825621932674n, ] ); const raw = keccak256(packed); diff --git a/packages/types/src/CreateEvmSigningOfferPayload.ts b/packages/types/src/CreateEvmSigningOfferPayload.ts index 5652471..e7ea1f4 100644 --- a/packages/types/src/CreateEvmSigningOfferPayload.ts +++ b/packages/types/src/CreateEvmSigningOfferPayload.ts @@ -1,7 +1,7 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { EvmSigningOffer } from "./EvmSigningOffer"; +import type { SignedEvmSigningOffer } from "./SignedEvmSigningOffer"; export type CreateEvmSigningOfferPayload = { identifier: string; - evm_signing_offer: EvmSigningOffer; + signed_offer: SignedEvmSigningOffer; }; diff --git a/packages/types/src/SignedEvmSigningOffer.ts b/packages/types/src/SignedEvmSigningOffer.ts new file mode 100644 index 0000000..e1ac8d7 --- /dev/null +++ b/packages/types/src/SignedEvmSigningOffer.ts @@ -0,0 +1,8 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { EvmSigningOffer } from "./EvmSigningOffer"; + +export type SignedEvmSigningOffer = { + signer: Uint8Array; + signature: [Uint8Array, Uint8Array, number]; + offer: EvmSigningOffer; +}; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 2f9e92b..9ad428a 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -25,6 +25,7 @@ export * from "./RemoteHoloomSignal"; export * from "./ResolveEvmSignatureOverRecipeExecutionRequestPayload"; export * from "./SendExternalIdAttestationRequestPayload"; export * from "./SignableBytes"; +export * from "./SignedEvmSigningOffer"; export * from "./SignedEvmU256Array"; export * from "./SignedUsername"; export * from "./UpdateMetadataItemPayload";