Skip to content

Commit

Permalink
Signed transactions for token minting module
Browse files Browse the repository at this point in the history
  • Loading branch information
JohnChangUK committed Apr 24, 2024
1 parent b1c9ee8 commit 0b59093
Show file tree
Hide file tree
Showing 2 changed files with 251 additions and 0 deletions.
97 changes: 97 additions & 0 deletions token-minter/sources/modules/signed_transaction.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
module minter::signed_transaction {
use std::ed25519;
use std::error;
use std::signer;
use std::string::String;
use aptos_framework::object;
use aptos_framework::object::{ConstructorRef, Object};

/// The proof challenge is invalid.
const EINVALID_CHALLENGE: u64 = 1;
/// The proof data does not exist.
const EPROOF_DATA_DOES_NOT_EXIST: u64 = 2;
/// The proof data already exists.
const EPROOF_DATA_ALREADY_EXISTS: u64 = 3;
/// The signer is not the owner of the object.
const ENOT_OBJECT_OWNER: u64 = 4;

#[resource_group_member(group = aptos_framework::object::ObjectGroup)]
struct ProofData has key {
authority_public_key: ed25519::UnvalidatedPublicKey,
}

struct ProofChallenge has copy, drop {
data: vector<u8>,
}

/// Initializes the collection object's constructor ref with proof data.
public fun init(constructor_ref: &ConstructorRef, authority_public_key: vector<u8>): Object<ProofData> {
let object_signer = object::generate_signer(constructor_ref);
extend(&object_signer, authority_public_key)
}

/// Extends the collection object with proof data via the collection signer.
public fun extend(object_signer: &signer, authority_public_key: vector<u8>): Object<ProofData> {
let object_signer_addr = signer::address_of(object_signer);
assert!(!proof_data_exists(object_signer_addr), EPROOF_DATA_ALREADY_EXISTS);

move_to(object_signer, ProofData {
authority_public_key: ed25519::new_unvalidated_public_key_from_bytes(authority_public_key),
});

object::address_to_object(object_signer_addr)
}

public fun set_public_key(
owner: &signer,
proof_data: Object<ProofData>,
authority_public_key: vector<u8>,
) acquires ProofData {
assert_owner(signer::address_of(owner), proof_data);

let proof_data = borrow_mut(proof_data);
proof_data.authority_public_key = ed25519::new_unvalidated_public_key_from_bytes(authority_public_key);
}

public fun verify_signed_transaction(
proof_data: Object<ProofData>,
data: vector<u8>,
signed_transaction: vector<u8>,
) acquires ProofData {
let challenge = ProofChallenge { data };
let proof_data = borrow(proof_data);
assert!(
ed25519::signature_verify_strict_t(
&ed25519::new_signature_from_bytes(signed_transaction),
&proof_data.authority_public_key,
challenge,
),
EINVALID_CHALLENGE,
);
}

public fun create_proof_challenge(data: vector<u8>): ProofChallenge {
ProofChallenge { data }
}

public fun proof_data_exists(addr: address): bool {
exists<ProofData>(addr)
}

inline fun borrow_mut<T: key>(obj: Object<T>): &mut ProofData {
let obj_addr = object::object_address(&obj);
assert!(proof_data_exists(obj_addr), EPROOF_DATA_DOES_NOT_EXIST);
borrow_global_mut<ProofData>(obj_addr)
}

inline fun borrow<T: key>(obj: Object<T>): &ProofData {
freeze(borrow_mut(obj))
}

inline fun assert_owner<T: key>(owner: address, obj: Object<T>) {
assert!(
object::owner(obj) == owner,
error::permission_denied(ENOT_OBJECT_OWNER),
);
}
}
154 changes: 154 additions & 0 deletions token-minter/tests/modules/signed_transaction_tests.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
#[test_only]
module minter::signed_transaction_tests {
use std::bcs;
use std::signer;
use std::string::utf8;
use std::vector;
use aptos_std::ed25519;
use minter::collection_components;

use minter::collection_utils::create_collection_with_refs;
use minter::signed_transaction;

#[test(creator = @0x123, user = @0x456)]
fun test_signed_transaction_success(creator: &signer, user: &signer) {
let (sk, vpk) = ed25519::generate_keys();
let pk = ed25519::public_key_into_unvalidated(vpk);
let authority_public_key = ed25519::unvalidated_public_key_to_bytes(&pk);

let collection = create_collection_with_refs(creator);
let collection_signer = collection_components::collection_object_signer(creator, collection);
let proof_data = signed_transaction::extend(&collection_signer, authority_public_key);

let token_name = utf8(b"Sword");
let token_description = utf8(b"A fancy sword");
let token_uri = utf8(b"https://example.com/sword1.png");
let user_addr = signer::address_of(user);

let data = bcs::to_bytes(&collection);
vector::append(&mut data, bcs::to_bytes(&token_name));
vector::append(&mut data, bcs::to_bytes(&token_description));
vector::append(&mut data, bcs::to_bytes(&token_uri));
vector::append(&mut data, bcs::to_bytes(&user_addr));

let challenge = signed_transaction::create_proof_challenge(data);
let sig = ed25519::signature_to_bytes(
&ed25519::sign_struct(&sk, challenge),
);
signed_transaction::verify_signed_transaction(proof_data, data, sig);
}

#[test(creator = @0x123, user = @0x456)]
#[expected_failure(abort_code = signed_transaction::EINVALID_CHALLENGE, location = minter::signed_transaction)]
fun wrong_signed_transaction_should_fail(creator: &signer, user: &signer) {
let (sk, vpk) = ed25519::generate_keys();
let pk = ed25519::public_key_into_unvalidated(vpk);
let authority_public_key = ed25519::unvalidated_public_key_to_bytes(&pk);

let collection = create_collection_with_refs(creator);
let collection_signer = collection_components::collection_object_signer(creator, collection);
let proof_data = signed_transaction::extend(&collection_signer, authority_public_key);

let token_name = utf8(b"Sword");
let token_description = utf8(b"A fancy sword");
let token_uri = utf8(b"https://example.com/sword1.png");
let user_addr = signer::address_of(user);

let data = bcs::to_bytes(&collection);
vector::append(&mut data, bcs::to_bytes(&token_name));
vector::append(&mut data, bcs::to_bytes(&token_description));
vector::append(&mut data, bcs::to_bytes(&token_uri));
vector::append(&mut data, bcs::to_bytes(&user_addr));

let challenge = signed_transaction::create_proof_challenge(data);
let sig = ed25519::signature_to_bytes(
&ed25519::sign_struct(&sk, challenge),
);

// Create wrong data - different from signed data
let wrong_data = data;
vector::append(&mut wrong_data, bcs::to_bytes(&utf8(b"Wrong data")));
signed_transaction::verify_signed_transaction(proof_data, wrong_data, sig);
}

// Test successful verification with the new key after an update
#[test(creator = @0x123, user = @0x456)]
fun verify_with_new_key_after_update(creator: &signer, user: &signer) {
let (_, vpk_old) = ed25519::generate_keys();
let pk_old = ed25519::public_key_into_unvalidated(vpk_old);
let authority_public_key_old = ed25519::unvalidated_public_key_to_bytes(&pk_old);
let collection = create_collection_with_refs(creator);
let collection_signer = collection_components::collection_object_signer(creator, collection);

// Update the public key to old first
let proof_data = signed_transaction::extend(&collection_signer, authority_public_key_old);

let (sk_new, vpk_new) = ed25519::generate_keys();
let pk_new = ed25519::public_key_into_unvalidated(vpk_new);
let authority_public_key_new = ed25519::unvalidated_public_key_to_bytes(&pk_new);

// Update the public key
signed_transaction::set_public_key(creator, proof_data, authority_public_key_new);

// Create a challenge and sign with the new key
let token_name = utf8(b"Sword");
let token_description = utf8(b"A fancy sword");
let token_uri = utf8(b"https://example.com/sword1.png");
let user_addr = signer::address_of(user);

let data = bcs::to_bytes(&collection);
vector::append(&mut data, bcs::to_bytes(&token_name));
vector::append(&mut data, bcs::to_bytes(&token_description));
vector::append(&mut data, bcs::to_bytes(&token_uri));
vector::append(&mut data, bcs::to_bytes(&user_addr));

let challenge = signed_transaction::create_proof_challenge(data);
let sig = ed25519::signature_to_bytes(
&ed25519::sign_struct(&sk_new, challenge),
);

// Verification should succeed with the new key
signed_transaction::verify_signed_transaction(proof_data, data, sig);
}

// Test updating the public key to a new one and verifying with the old key should fail.
#[test(creator = @0x123, user = @0x456)]
#[expected_failure(abort_code = signed_transaction::EINVALID_CHALLENGE, location = minter::signed_transaction)]
fun update_public_key_and_verify_with_old_key_should_fail(creator: &signer, user: &signer) {
let (sk_old, vpk_old) = ed25519::generate_keys();
let pk_old = ed25519::public_key_into_unvalidated(vpk_old);
let authority_public_key_old = ed25519::unvalidated_public_key_to_bytes(&pk_old);

let (_, vpk_new) = ed25519::generate_keys();
let pk_new = ed25519::public_key_into_unvalidated(vpk_new);
let authority_public_key_new = ed25519::unvalidated_public_key_to_bytes(&pk_new);

let collection = create_collection_with_refs(creator);
let collection_signer = collection_components::collection_object_signer(creator, collection);
let proof_data = signed_transaction::extend(&collection_signer, authority_public_key_old);

// Update the public key
signed_transaction::set_public_key(creator, proof_data, authority_public_key_new);

let token_name = utf8(b"Sword");
let token_description = utf8(b"A fancy sword");
let token_uri = utf8(b"https://example.com/sword1.png");
let user_addr = signer::address_of(user);

let data = bcs::to_bytes(&collection);
vector::append(&mut data, bcs::to_bytes(&token_name));
vector::append(&mut data, bcs::to_bytes(&token_description));
vector::append(&mut data, bcs::to_bytes(&token_uri));
vector::append(&mut data, bcs::to_bytes(&user_addr));

let challenge = signed_transaction::create_proof_challenge(data);

// Create a mint proof challenge and sign with the old key
let sig = ed25519::signature_to_bytes(
&ed25519::sign_struct(&sk_old, challenge),
);

// Verification should fail since the public key has been updated
signed_transaction::verify_signed_transaction(proof_data, data, sig);
}
}

0 comments on commit 0b59093

Please sign in to comment.