Replies: 1 comment
-
does this work? module intent_verification::verifier {
use std::vector;
use std::option::{Self, Option};
use aptos_framework::ed25519;
use aptos_framework::multi_ed25519;
use aptos_framework::account;
use aptos_framework::timestamp;
/// Error codes
const E_INVALID_SIGNATURE: u64 = 1;
const E_INTENT_EXPIRED: u64 = 2;
const E_INTENT_ALREADY_USED: u64 = 3;
const E_INVALID_NONCE: u64 = 4;
/// Intent structure that users sign off-chain
struct Intent has copy, drop, store {
user: address,
action: vector<u8>, // encoded action data
nonce: u64,
expiry: u64,
contract_address: address,
}
/// Storage for tracking used intents and nonces
struct IntentRegistry has key {
used_intents: vector<vector<u8>>, // hash of used intents
user_nonces: vector<UserNonce>,
}
struct UserNonce has store, copy, drop {
user: address,
nonce: u64,
}
/// Initialize the registry (call this in your module's init)
public fun initialize(account: &signer) {
move_to(account, IntentRegistry {
used_intents: vector::empty(),
user_nonces: vector::empty(),
});
}
/// Verify an intent signature and execute if valid
public fun verify_and_execute_intent(
relayer: &signer,
intent: Intent,
signature: vector<u8>,
public_key: vector<u8>,
) acquires IntentRegistry {
// 1. Verify the intent hasn't expired
let current_time = timestamp::now_seconds();
assert!(intent.expiry > current_time, E_INTENT_EXPIRED);
// 2. Check nonce to prevent replay attacks
verify_nonce(intent.user, intent.nonce);
// 3. Verify the signature
let message = serialize_intent(&intent);
verify_signature(message, signature, public_key, intent.user);
// 4. Mark intent as used
mark_intent_used(message);
update_nonce(intent.user, intent.nonce);
// 5. Execute the intended action (implement your specific logic here)
execute_action(intent.action, intent.user);
}
/// Serialize intent for signing
fun serialize_intent(intent: &Intent): vector<u8> {
let serialized = vector::empty<u8>();
// Add user address (32 bytes)
let user_bytes = bcs::to_bytes(&intent.user);
vector::append(&mut serialized, user_bytes);
// Add action
vector::append(&mut serialized, intent.action);
// Add nonce (8 bytes)
let nonce_bytes = bcs::to_bytes(&intent.nonce);
vector::append(&mut serialized, nonce_bytes);
// Add expiry (8 bytes)
let expiry_bytes = bcs::to_bytes(&intent.expiry);
vector::append(&mut serialized, expiry_bytes);
// Add contract address (32 bytes)
let contract_bytes = bcs::to_bytes(&intent.contract_address);
vector::append(&mut serialized, contract_bytes);
serialized
}
/// Verify Ed25519 signature
fun verify_signature(
message: vector<u8>,
signature: vector<u8>,
public_key: vector<u8>,
expected_user: address
) {
// Create Ed25519 signature struct
let sig = ed25519::new_signature_from_bytes(signature);
let pk = ed25519::new_unvalidated_public_key_from_bytes(public_key);
// Verify signature
assert!(ed25519::signature_verify_strict(&sig, &pk, message), E_INVALID_SIGNATURE);
// Verify the public key corresponds to the user address
let derived_address = account::create_address_from_bytes(public_key);
assert!(derived_address == expected_user, E_INVALID_SIGNATURE);
}
/// Check if nonce is valid (should be user's current nonce + 1)
fun verify_nonce(user: address, nonce: u64) acquires IntentRegistry {
let registry = borrow_global<IntentRegistry>(@intent_verification);
let current_nonce = get_user_nonce(®istry.user_nonces, user);
assert!(nonce == current_nonce + 1, E_INVALID_NONCE);
}
/// Get current nonce for a user
fun get_user_nonce(nonces: &vector<UserNonce>, user: address): u64 {
let i = 0;
let len = vector::length(nonces);
while (i < len) {
let user_nonce = vector::borrow(nonces, i);
if (user_nonce.user == user) {
return user_nonce.nonce
};
i = i + 1;
};
0 // Default nonce for new users
}
/// Update user's nonce
fun update_nonce(user: address, new_nonce: u64) acquires IntentRegistry {
let registry = borrow_global_mut<IntentRegistry>(@intent_verification);
let i = 0;
let len = vector::length(®istry.user_nonces);
let found = false;
while (i < len) {
let user_nonce = vector::borrow_mut(&mut registry.user_nonces, i);
if (user_nonce.user == user) {
user_nonce.nonce = new_nonce;
found = true;
break
};
i = i + 1;
};
if (!found) {
vector::push_back(&mut registry.user_nonces, UserNonce {
user,
nonce: new_nonce,
});
};
}
/// Mark intent as used to prevent replay
fun mark_intent_used(message: vector<u8>) acquires IntentRegistry {
let registry = borrow_global_mut<IntentRegistry>(@intent_verification);
// In production, you might want to use a hash of the message
vector::push_back(&mut registry.used_intents, message);
}
/// Execute the intended action (customize based on your needs)
fun execute_action(action_data: vector<u8>, user: address) {
// This is where you'd decode the action_data and execute the intended operation
// For example:
// - Token transfer
// - NFT mint
// - Voting
// - etc.
// Example implementation would decode action_data to determine the action type
// and parameters, then execute accordingly
}
/// Public view function to get user's current nonce
#[view]
public fun get_current_nonce(user: address): u64 acquires IntentRegistry {
let registry = borrow_global<IntentRegistry>(@intent_verification);
get_user_nonce(®istry.user_nonces, user)
}
/// Helper function to create an intent (for off-chain usage reference)
public fun create_intent(
user: address,
action: vector<u8>,
nonce: u64,
expiry: u64,
contract_address: address,
): Intent {
Intent {
user,
action,
nonce,
expiry,
contract_address,
}
}
} |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Discord user ID
cadaltz
Describe your question in detail.
On Aptos, if user X creates and signs an off-chain intent (for example, to perform action z ), and a relayer Y submits the intent on-chain by calling a custom entry function, what is the recommended way in Move to verify, on-chain, that the intent was truly authorized by X and not forged by Y? Specifically:
What cryptographic primitives and address derivation logic should be used in-contract to check the Ed25519 signature, reconstruct X’s Aptos address from the public key, and prevent replay of the same intent (e.g., via a nonce)?
Is there a preferred pattern for handling the public key → address mapping, intent message hashing, and signature validation in Move, or are there any security nuances I should watch out for?
I want to X to create intent for action Z , which can be posted onchain by Y in contract to call action Z , please help
What error, if any, are you getting?
No response
What have you tried or looked at? Or how can we reproduce the error?
No response
Which operating system are you using?
Windows
Which SDK or tool are you using? (if any)
N/A
Describe your environment or tooling in detail
No response
Beta Was this translation helpful? Give feedback.
All reactions