3

I have function that will take an admin account and verify when metadata is passed in that the admin account has signed it and the Signature is provided in a struct called NftSaleMetadata<AccountId, BoundedString>. When I write unit tests the tests pass, but when I write the javascript scripts to sign the metadata then execute a transaction, I get failures. Is there something wrong with my verification function?

NftSaleMetadata struct

/// Metadata to ensure metadata passed in is signed by the Overlord account
#[derive(Encode, Decode, Clone, Debug, PartialEq, TypeInfo)]
pub struct NftSaleMetadata<BoundedString> {
    pub metadata: BoundedString,
    pub signature: sr25519::Signature,
}

verify_nft_metadata that is called from a public minting function

pub fn verify_nft_metadata(
    overlord: &T::AccountId,
    nft_metadata: NftSaleMetadata<BoundedVec<u8, T::StringLimit>>,
) -> Result<BoundedVec<u8, T::StringLimit>, Error<T>> {
    let metadata = nft_metadata.metadata;
    let signature = nft_metadata.signature;
    // Serialize evidence
    let msg = Encode::encode(&metadata);
    let encode_overlord = T::AccountId::encode(overlord);
    let h256_overlord = H256::from_slice(&encode_overlord);
    let overlord_key = sr25519::Public::from_h256(h256_overlord);
    // verify claim
    ensure!(
        sp_io::crypto::sr25519_verify(&signature, &msg, &overlord_key),
        Error::<T>::InvalidMetadata
    );
    Ok(metadata)
}

Unit test example that passes where claim_spirit will call helper function verify_nft_metadata to verify the signed metadata

#[test]
fn claimed_spirit_works() {
    ExtBuilder::default().build(OVERLORD).execute_with(|| {
        let overlord_pair = sr25519::Pair::from_seed(b"28133080042813308004281330800428");
        // let overlord_pub = overlord_pair.public();
        // Set Overlord and configuration then enable spirits to be claimed
        setup_config(StatusType::ClaimSpirits);
        let metadata = stb("I am Spirit");
        let enc_metadata = Encode::encode(&metadata);
        let metadata_signature = overlord_pair.sign(&enc_metadata);
        let nft_sale_metadata =
            NftSaleMetadata { metadata: metadata.clone(), signature: metadata_signature };
        // Sign BOB's Public Key and Metadata encoding with OVERLORD account
        let claim = Encode::encode(&BOB);
        let overlord_signature = overlord_pair.sign(&claim);
        let bob_ticket = ClaimSpiritTicket { account: BOB, signature: overlord_signature };
        // Dispatch a claim spirit from BOB's account
        assert_ok!(PhalaWorld::claim_spirit(
            Origin::signed(BOB),
            Some(bob_ticket),
            nft_sale_metadata.clone()
        ));
        // ALICE should be able to claim since she has minimum amount of PHA
        assert_ok!(PhalaWorld::claim_spirit(Origin::signed(ALICE), None, nft_sale_metadata));
    });
}

JS script that fail when trying to claim_spirit the output to console says valid signature, but the transaction fails with invalid metadata

const keyring = new Keyring({type: 'sr25519'});
// status types
const claimSpirits = api.createType('StatusType', 'ClaimSpirits');
const overlord = keyring.addFromUri(overlordPrivkey);
const metadata = stringToU8a('I am Spirit');
const metadataSig = overlord.sign(metadata);
const isValid = overlord.verify(metadata, metadataSig, overlord.publicKey);
    
// output the result
console.log(`${u8aToHex(metadataSig)}\n ${u8aToHex(metadata)} is ${isValid ? 'valid' : 'invalid'}`);
const nftSignedMetadata = api.createType("NftSaleMetadata", {'metadata': metadata, 'signature': metadataSig});
await api.tx.phalaWorld.claimSpirit(null, nftSignedMetadata).signAndSend(user);

1 Answer 1

2

I ended up resolving the issue. What the substrate code is doing is encoding the metadata before signing with the Overlord admin account which is a step I was missing with my JS script. Here is what the script looks like that allowed to verify the signed metadata.

const metadata = 'I am Spirit';
const metadataType = api.createType('BoundedVec<u8, T::StringLimit>', metadata).toU8a();
const metadataSig = overlord.sign(metadataType);
const isValid = overlord.verify(metadata, metadataSig, overlord.address);
const nftSignedMetadata = api.createType('NftSaleMetadata', {'metadata': metadataType, 'signature': metadataSig});
    
  
// Mint a Spirit with another user account
await api.tx.phalaWorld.claimSpirit(null, nftSignedMetadata).signAndSend(user);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.