Can't retrieve credientials

I have created a contract based on the following example https://github.com/Concordium/concordium-rust-smart-contracts/tree/main/examples/credential-registry

I changed the schema for credential registry as following

/// Parameters for registering a credential
#[derive(Serialize, SchemaType, Clone, Debug)]
pub struct RegisterCredentialParam {
    /// Public credential data.
    credential_info: CredentialInfo,
    /// Any additional data required by the issuer in the registration process.
    /// This data is not used in this contract. However, it is part of the CIS-4
    /// standard that this contract implements; `auxiliary_data` can be
    /// used, for example, to implement signature-based authentication.
    #[concordium(size_length = 2)]
    auxiliary_data:  Vec<u8>,
}


#[derive(Serialize, SchemaType, PartialEq, Eq, Clone, Debug)]
pub struct CredentialInfo {
    /// The holder's identifier is a public key.
    review_id: CredentialHolderId,
    holder_id:        CredentialHolderId,
    /// If this flag is set to `true` the holder can send a signed message to
    /// revoke their credential.
    holder_revocable: bool,
    /// The date from which the credential is considered valid.
    valid_from:       Timestamp,
    /// After this date, the credential becomes expired. `None` corresponds to a
    /// credential that cannot expire.
    valid_until:      Option<Timestamp>,
    /// Link to the metadata of this credential.
    metadata_url:     MetadataUrl,
}

The credential registry function was changed to following

fn contract_register_credential<S: HasStateApi>(
    ctx: &impl HasReceiveContext,
    host: &mut impl HasHost<State<S>, StateApiType = S>,
    // logger: &mut impl HasLogger,
) -> Result<(), ContractError> {
    // ensure!(sender_is_issuer(ctx, host.state()), ContractError::NotAuthorized);
    let parameter: RegisterCredentialParam = ctx.parameter_cursor().get()?;
    let credential_info = parameter.credential_info;
    let (state, state_builder) = host.state_and_builder();
    state.register_credential(&credential_info, state_builder)?;
   
    
    Ok(())
}

The credientialEntry scheme was modified to following

/// Public data of a verifiable credential.
#[derive(Serial, DeserialWithState, Debug)]
#[concordium(state_parameter = "S")]
pub struct CredentialEntry<S: HasStateApi> {
    /// If this flag is set to `true` the holder can send a signed message to
    /// revoke their credential.
    holder_revocable: bool,

    review_id: CredentialHolderId,

    holder_id: CredentialHolderId,
    /// The date from which the credential is considered valid.
    valid_from:       Timestamp,
    /// After this date, the credential becomes expired. `None` corresponds to a
    /// credential that cannot expire.
    valid_until:      Option<Timestamp>,
    /// The nonce is used to avoid replay attacks when checking the holder's
    /// signature on a revocation message.
    revocation_nonce: u64,
    /// Revocation flag
    revoked:          bool,
    /// Metadata URL of the credential (not to be confused with the metadata URL
    /// of the **issuer**).
    /// This data is only needed when credential info is requested. In other
    /// operations, `StateBox` defers loading the metadata url.
    metadata_url:     StateBox<MetadataUrl, S>,
}

#[receive(
    contract = "hash_reg",
    name = "credentialEntry",
    parameter = "CredentialHolderId",
    error = "ContractError",
    return_value = "CredentialQueryResponse"
)]
fn contract_credential_entry<S: HasStateApi>(
    ctx: &impl HasReceiveContext,
    host: &impl HasHost<State<S>, StateApiType = S>,
) -> Result<CredentialQueryResponse, ContractError> {
    let review_id = ctx.parameter_cursor().get()?;
    // host.state().view_credential_info(credential_id)
    host.state().view_review_info(review_id)
    
}
 fn view_review_info(
        &self,
        review_id: CredentialHolderId,
    ) -> ContractResult<CredentialQueryResponse> {
        let entry =
            self.credentials.get(&review_id).ok_or(ContractError::CredentialNotFound)?;
        Ok(CredentialQueryResponse {
            credential_info:  entry.info(review_id),
            revocation_nonce: entry.revocation_nonce,
            schema_ref:       self.credential_schema.clone(),
        })
    }

The issue is that i am able to successfully register the new credential based on the new changes, but the crediential entry returns not found

What is review_id?

How does register_credential function store it? If you have not changed it it stores it under holder_id. Is that the same as review_id?

review_id is unique key which identifies an item and is assignable to a wallet id

review_id: CredentialHolderId,

where,

type CredentialHolderId = PublicKeyEd25519;

The register credential function is modified as well, please see

fn register_credential(
    &mut self,
    credential_info: &CredentialInfo,
    state_builder: &mut StateBuilder<S>,
) -> ContractResult<()> {
    let credential_entry = CredentialEntry {
        review_id: credential_info.review_id,
        holder_id: credential_info.holder_id,
        holder_revocable: credential_info.holder_revocable,
        valid_from:       credential_info.valid_from,
        valid_until:      credential_info.valid_until,
        metadata_url:     state_builder.new_box(credential_info.metadata_url.clone()),
        revocation_nonce: 0,
        revoked:          false,
    };
    let res = self.credentials.insert(credential_info.review_id, credential_entry);
    ensure!(res.is_none(), ContractError::CredentialAlreadyExists);
    Ok(())
}

I’m not sure I understand the setup.

If you want your contract to be CIS4-compatible (and it has to be to be visible in wallets) then credentialEntry will have to look up by holder_id. THat’s how the wallet will request the information.

Is review_id somehow derived from holder_id? Otherwise I am not sure the approach will work.

So basically, if I understand correctly the credential to be registered needs to a valid wallet id? The goal is to have multiple hashes associated to one account (1 - m relationship)

The holder_id is not related to any account.

To issue a credential the dApp asks the wallet for a fresh holder_id (and a few other things).

This holder_id is not related to any accounts the user has on the chain (in fact they don’t even have to have an account).

If you want to link it you have to define this logic yourself.

ok than why can’t i retrieve the info, because registration is successful, when holder_id is a wallet key and on query crediential info is returned, in other cases it returns credential not found.

A sample input which executes successfully and the transaction is even shown in scan
{

"credential_info": {
    "valid_from":       "2023-08-24T12:00:00+05:00",
    "valid_until":      {"None": []},
    "holder_revocable": true,
    "review_id": "0b4744cbf6b7a4b58dc8a633734fe1481620fbbb47b191c15c8def005f14274b",
    "holder_id": "020e9fd962f4011bd155193dd9477d0481060aad98a26ab8b5b5dddbd884a604",
    "metadata_url":{
        "url": "asd",
        "hash": {"Some": ["0b4744cbf6b7a4b58dc8a633734fe1481620fbbb47b191c15c8def005f14274a"]}
    }
},
"auxiliary_data":  [45]

}

Can you share the transactions that lead to the “other cases”?

The craziest of things just happened, i was creating a response with all the data and it just worked! The registered credentials were retrieved

1 Like

Well it’s good it works :smiley:

:slight_smile: All the help is mightily appreciated!