-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed transactions for token minting module
- Loading branch information
1 parent
b1c9ee8
commit 0b59093
Showing
2 changed files
with
251 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
154
token-minter/tests/modules/signed_transaction_tests.move
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |