wormhole/aptos/nft_bridge/sources/token_hash.move

118 lines
4.2 KiB
Plaintext

/// A pair of 32 byte hashes representing an arbitrary Aptos NFT, to be used in
/// VAAs to refer to NFTs.
module nft_bridge::token_hash {
use aptos_token::token::{Self, TokenId};
use std::bcs;
use std::hash;
use std::string;
use std::vector;
use wormhole::serialize;
use wormhole::external_address::{Self, ExternalAddress};
/// Hash of (creator || hash(collection name)), which uniquely identifies a
/// collection on Aptos
struct CollectionHash has drop, copy, store {
// 32 bytes
hash: vector<u8>,
}
/// Hash of (creator || hash(collection name) || hash(token name) || property version), which
/// uniquely identifies a token on Aptos
struct TokenHash has drop, copy, store {
// 32 bytes
hash: vector<u8>,
}
#[test_only]
public fun get_collection_hash_bytes(x: &CollectionHash): vector<u8>{
return x.hash
}
#[test_only]
public fun get_token_hash_bytes(x: &TokenHash): vector<u8>{
return x.hash
}
public fun get_collection_external_address(a: &CollectionHash): ExternalAddress {
external_address::from_bytes(a.hash)
}
public fun get_token_external_address(a: &TokenHash): ExternalAddress {
external_address::from_bytes(a.hash)
}
public fun from_external_address(a: ExternalAddress): TokenHash {
TokenHash { hash: external_address::get_bytes(&a) }
}
public fun derive(token_id: &TokenId): (CollectionHash, TokenHash) {
let ser = vector::empty<u8>();
// we hash all variable length fields (that is, collection and name)
let (creator, collection, name, property_version) = token::get_token_id_fields(token_id);
let creator_bytes = bcs::to_bytes(&creator);
serialize::serialize_vector(&mut ser, creator_bytes);
serialize::serialize_vector(&mut ser, hash::sha3_256(*string::bytes(&collection)));
let collection_hash = hash::sha3_256(ser);
serialize::serialize_vector(&mut ser, hash::sha3_256(*string::bytes(&name)));
serialize::serialize_u64(&mut ser, property_version);
let token_hash = hash::sha3_256(ser);
(CollectionHash { hash: collection_hash }, TokenHash { hash: token_hash })
}
}
#[test_only]
module nft_bridge::token_hash_test {
use std::string;
use aptos_token::token;
use nft_bridge::token_hash;
#[test(creator = @0x1234)]
public fun test_derive(creator: address) {
let token_id = token::create_token_id_raw(
creator,
string::utf8(b"my collection"),
string::utf8(b"my token"),
0
);
let (collection_hash, token_hash) = token_hash::derive(&token_id);
let collection_hash = token_hash::get_collection_hash_bytes(&collection_hash);
let token_hash = token_hash::get_token_hash_bytes(&token_hash);
assert!(collection_hash == x"18905beccb7e5a0f17d22e6773bd94886535fa39f4c28841a752c97a52c5eb46", 0);
assert!(token_hash == x"54ea5951232ad17f3dbb133964eb0463605e0e35dacd856a1881090d7f0218fe", 0);
}
// this test ensures that variable length fields can't be reshuffled to
// cause a collision
#[test(creator = @0x1234)]
public fun test_derive_no_rearrange(creator: address) {
let token_id_1 = token::create_token_id_raw(
creator,
string::utf8(b"my collection"),
string::utf8(b"my token"),
0
);
let token_id_2 = token::create_token_id_raw(
creator,
string::utf8(b"my collectionmy"),
string::utf8(b" token"),
0
);
let (collection_hash_1, token_hash_1) = token_hash::derive(&token_id_1);
let (collection_hash_2, token_hash_2) = token_hash::derive(&token_id_2);
let collection_hash_1 = token_hash::get_collection_hash_bytes(&collection_hash_1);
let token_hash_1 = token_hash::get_token_hash_bytes(&token_hash_1);
let collection_hash_2 = token_hash::get_collection_hash_bytes(&collection_hash_2);
let token_hash_2 = token_hash::get_token_hash_bytes(&token_hash_2);
assert!(collection_hash_1 != collection_hash_2, 0);
assert!(token_hash_1 != token_hash_2, 0);
}
}