Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to create anoncreds schema on issuer configured with a qualified did:sov public did #3540

Open
zoblazo opened this issue Feb 24, 2025 · 20 comments

Comments

@zoblazo
Copy link
Contributor

zoblazo commented Feb 24, 2025

Given I have an issuer agent deployed with acapy 1.2.0 with wallet-type askar-anoncreds

Given I provision on that agent a fully qualified did:sov such as

    {
      "did": "did:sov:AecTm5kThinYH3oXj4d9xd",
      "verkey": "6G1RLkBLvbzrCdYTnFjESR9yV7pVP7Dqx6e81fBpNsn5",
      "posture": "posted",
      "key_type": "ed25519",
      "method": "sov",
      "metadata": {
        "posted": true
      }
    }

Given I assign that DID as public for the issuer agent

When I try to create an anoncreds schema (POST /anoncreds/schema with following body)

{
  "options": {
    "create_transaction_for_endorser": true,
    "endorser_connection_id": "a30f8546-72e4-419d-ae77-1541287ced9b"
  },
  "schema": {
    "attrNames": [
      "auth_key",
      "email"
    ],
    "issuerId": "did:sov:AecTm5kThinYH3oXj4d9xd",
    "name": "test fqdid",
    "version": "1.0"
  }
}

Then I get error :

400: Error creating schema. Failed to register schema. Exception when building get-schema request. Validation error: SchemaId validation failed: "did:sov:AecTm5kThinYH3oXj4d9xd:2:test fqdid:1.0", doesn't match pattern.

Then Schema is not created in agent's wallet and not published on blockchain.

Expected result : schema is created and published to configured blockchain.

@zoblazo
Copy link
Contributor Author

zoblazo commented Feb 24, 2025

Here is the DEBUG log of the issuer agent when posting a schema with scenario similar to the one described above (different did since different agent in different environment but otherwise same procedure)

2025-02-24 17:05:30,173 acapy_agent.admin.server DEBUG Incoming request: POST /anoncreds/schema
2025-02-24 17:05:30,173 acapy_agent.admin.server DEBUG Match info: <MatchInfo {}: <ResourceRoute [POST] <PlainResource  /anoncreds/schema> -> <function schemas_post at 0x7fb5ad84c400>>
2025-02-24 17:05:30,173 acapy_agent.admin.server DEBUG Body: {
  "options": {
    "create_transaction_for_endorser": true,
    "endorser_connection_id": "4e8c3359-4cb4-44d1-8201-cedc7b89781d"
  },
  "schema": {
    "attrNames": [
      "auth_key",
      "email"
    ],
    "issuerId": "did:sov:QvZ4hHw63DNrgdf6azXnUj",
    "name": "test fqdid",
    "version": "1.0"
  }
}
2025-02-24 17:05:30,182 acapy_agent.anoncreds.default.legacy_indy.registry DEBUG Registering schema: did:sov:QvZ4hHw63DNrgdf6azXnUj:2:test fqdid:1.0
2025-02-24 17:05:30,183 acapy_agent.anoncreds.default.legacy_indy.registry DEBUG schema value: {'ver': '1.0', 'id': 'did:sov:QvZ4hHw63DNrgdf6azXnUj:2:test fqdid:1.0', 'name': 'test fqdid', 'version': '1.0', 'attrNames': ['auth_key', 'email'], 'seqNo': None}
2025-02-24 17:05:30,187 acapy_agent.ledger.indy_vdr DEBUG Opening the pool ledger
2025-02-24 17:05:30,230 acapy_agent.admin.server INFO Bad request during POST /anoncreds/schema: Error creating schema. Failed to register schema. Exception when building get-schema request. Validation error: SchemaId validation failed: "did:sov:QvZ4hHw63DNrgdf6azXnUj:2:test fqdid:1.0", doesn't match pattern.
2025-02-24 17:05:30,231 aiohttp.access INFO 127.0.0.6 [24/Feb/2025:17:05:30 -0500] "POST /anoncreds/schema HTTP/1.1" 400 764 "https://emetteur-inc-dev-admin.dev.identite.pes.qc/api/doc" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"
2025-02-24 17:05:35,236 acapy_agent.ledger.indy_vdr DEBUG Closing pool ledger after timeout

@ff137
Copy link
Contributor

ff137 commented Feb 24, 2025

Try replace test fqdid -> test_fqdid. Schema name cannot contain whitespace

@zoblazo
Copy link
Contributor Author

zoblazo commented Feb 25, 2025

@ff137 400: Error creating schema. Failed to register schema. Exception when building get-schema request. Validation error: SchemaId validation failed: "did:sov:QvZ4hHw63DNrgdf6azXnUj:2:test_fqdid:1.0", doesn't match pattern.

And on the other hand, using unqualified did works with (or without) spaces. API then returns a 200 with following body :

{
  "job_id": "c924b7d13e594a8cb4c839fb241f55ec",
  "schema_state": {
    "state": "wait",
    "schema_id": "QvZ4hHw63DNrgdf6azXnUj:2:test fqdid unqualified:1.0",
    "schema": {
      "issuerId": "QvZ4hHw63DNrgdf6azXnUj",
      "attrNames": [
        "score"
      ],
      "name": "test fqdid unqualified",
      "version": "1.0"
    }
  },
  "registration_metadata": {
    "txn": {
      "state": "request_sent",
      "created_at": "2025-02-25T13:39:42.765035Z",
      "updated_at": "2025-02-25T13:39:42.773057Z",
      "trace": false,
      "transaction_id": "7ebbc7cc-40d4-4ab2-9ec3-c1cf27ff4272",
      "_type": "https://didcomm.org/sign-attachment/1.0/signature-request",
      "signature_request": [
        {
          "context": "did:sov",
          "method": "add-signature",
          "signature_type": "default",
          "signer_goal_code": "aries.transaction.endorse",
          "author_goal_code": "aries.transaction.ledger.write"
        }
      ],
      "signature_response": [],
      "timing": {
        "expires_time": null
      },
      "formats": [
        {
          "attach_id": "b490c6ba-e862-44e0-a2b5-0e2a8f9d2a08",
          "format": "dif/endorse-transaction/[email protected]"
        }
      ],
      "messages_attach": [
        {
          "@id": "b490c6ba-e862-44e0-a2b5-0e2a8f9d2a08",
          "mime-type": "application/json",
          "data": {
            "json": "{\"endorser\": \"8AdkPt1wvZ7uRF7bzs6oDc\", \"identifier\": \"QvZ4hHw63DNrgdf6azXnUj\", \"operation\": {\"data\": {\"attr_names\": [\"score\"], \"name\": \"test fqdid unqualified\", \"version\": \"1.0\"}, \"type\": \"101\"}, \"protocolVersion\": 2, \"reqId\": 1740490782760338107, \"signature\": \"4qDSyCziXbdAoceJGriaPy6AfHqfXzehhhZ5ciXyqGJHE4PtDsiSkajo28T1rbhWPiGLHuNGQXDvTpVnTdrbLYNz\"}"
          }
        }
      ],
      "meta_data": {
        "context": {
          "job_id": "c924b7d13e594a8cb4c839fb241f55ec",
          "schema_id": "QvZ4hHw63DNrgdf6azXnUj:2:test fqdid unqualified:1.0"
        }
      },
      "connection_id": "4e8c3359-4cb4-44d1-8201-cedc7b89781d",
      "endorser_write_txn": false
    }
  },
  "schema_metadata": {}
}

@zoblazo
Copy link
Contributor Author

zoblazo commented Feb 25, 2025

Worth noting here that the issuer (author) is behind an endorser. So options create_transaction_for_endorser": true, and endorser_connection_id are passed in any related call, including the POST to /anoncreds/schema. Also worth noting that OP in #3516 also mentions an issuer behind an endorser.

@ff137
Copy link
Contributor

ff137 commented Feb 25, 2025

Strange, I could've sworn that the schema name must be without spaces - perhaps true in some cases, but not relevant to this validation error.

When I looked into it, it seemed like the validation error is coming from the indy_vdr package. And the public did + schema is stripped of the did prefix before that call is made, so I presume that's not the issue

class IndyVdrLedger(BaseLedger):
...
    async def fetch_schema_by_id(self, schema_id: str) -> dict:
        """Get schema from ledger.

        Args:
            schema_id: The schema id (or stringified sequence number) to retrieve

        Returns:
            Indy schema dict

        """

        public_info = await self.get_wallet_public_did()
        public_did = public_info.did if public_info else None

        try:
            schema_req = ledger.build_get_schema_request(
                strip_did_prefix(public_did), strip_did_prefix(schema_id)
            )
        except VdrError as err:
            raise LedgerError("Exception when building get-schema request") from err

^ The exception is ultimately coming from there. And there's some C library call being made in the indy_vdr package, so I can't quickly see what the validation error is.

@ff137
Copy link
Contributor

ff137 commented Feb 25, 2025

^ Looks like the strip_did_prefix was added recently. So the code I'm viewing is probably not what you're running with version 1.2.0.

Meaning that the qualified did is causing the issue (did:sov: is not stripped in 1.2.0, but is in the main branch)

@zoblazo Is it possible for you to try this again with the latest nightly build?

@jamshale
Copy link
Contributor

I couldn't replicate this but maybe I'm creating the qualified did differently. Could you provide the steps you took to generate the fully qualified did:sov?

@jamshale
Copy link
Contributor

jamshale commented Feb 25, 2025

^ Looks like the strip_did_prefix was added recently. So the code I'm viewing is probably not what you're running with version 1.2.0.

Meaning that the qualified did is causing the issue (did:sov: is not stripped in 1.2.0, but is in the main branch)

@zoblazo Is it possible for you to try this again with the latest nightly build?

I'm pretty sure if this was the issue, it would be a different type of error. When the stripped nym is invalid it gets an indy_vdr error saying it doesn't have the required signatures.

That's why the validation error is confusing to me. I can create the exact same pattern.

@zoblazo
Copy link
Contributor Author

zoblazo commented Feb 25, 2025

Here is how we create and provision the qualified did:sov :

  1. POST /wallet/did/create
{
  "method": "sov",
  "options": {
    "did": "did:sov:QvZ4hHw63DNrgdf6azXnUj",
    "key_type": "ed25519"
  },
  "seed": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

(seed x'ed out for obvious reason)

  1. usually would POST /ledger/register-nym but since we had previously provisionned the equivalent unqualified did, the NYM is already on blockchain so this step was skipped.

  2. POST /wallet/did/public?did=did%3Asov%3AQvZ4hHw63DNrgdf6azXnUj&conn_id=4e8c3359-4cb4-44d1-8201-cedc7b89781d&create_transaction_for_endorser=true'

Still working on reproducing this scenario with the latest nightly build.

@jamshale
Copy link
Contributor

I'm still not able to re-produce with this type of did. I have been able to get the same behavior by altering the did record in the local wallet.

Did this involve an upgrade from askar --> askar anoncreds wallet type?

@zoblazo
Copy link
Contributor Author

zoblazo commented Feb 25, 2025

No, wallet-type has been askar-anoncreds from inception. No upgrade involved.

On a not-so-related note, we did update aca-py from 1.0.0 to 1.2.0 a few weeks ago but I was able to reproduce same behavior on 1.2.0 agents that had their database/wallet wiped out.

@jamshale
Copy link
Contributor

So... I should have tried to do this earlier but I confirmed this error happens on version 1.2.0, but does not happen on main.

I predict that running your test on nightly will succeed.

I think it was probably fixed by the recent PR #3253 that has some qualified did stuff in it. I'd still like to actually understand what the issue was and why it seems to be fixed.

@ff137
Copy link
Contributor

ff137 commented Feb 25, 2025

It's presumably the indy_vdr library that expects schema_id to be prefixed without did:sov

@ff137
Copy link
Contributor

ff137 commented Feb 25, 2025

The error message is:

400: Error creating schema. Failed to register schema. Exception when building get-schema request. Validation error: SchemaId validation failed: "did:sov:AecTm5kThinYH3oXj4d9xd:2:test fqdid:1.0", doesn't match pattern.

"Exception when building get-schema request" reveals that it occurs here:

        # acapy_agent/ledger/indy_vdr.py:467
        try:
            schema_req = ledger.build_get_schema_request(
                strip_did_prefix(public_did), strip_did_prefix(schema_id)
            )
        except VdrError as err:
            raise LedgerError("Exception when building get-schema request") from err

This ends up calling some rust code in indy-vdr:

pub extern "C" fn indy_vdr_build_get_schema_request(
    submitter_did: FfiStr, // optional
    schema_id: FfiStr,
    handle_p: *mut RequestHandle,
) -> ErrorCode {
    catch_err! {
        trace!("Build GET_SCHEMA request");
        check_useful_c_ptr!(handle_p);
        let builder = get_request_builder()?;
        let identifier = submitter_did.as_opt_str().map(DidValue::from_str).transpose()?;
        let schema_id = SchemaId::from_str(schema_id.as_str())?;   // <-- "SchemaId validation failed" means it's this line
        let req = builder.build_get_schema_request(identifier.as_ref(), &schema_id)?;
        let handle = add_request(req)?;
        unsafe {
            *handle_p = handle;
        }
        Ok(ErrorCode::Success)
    }
}

Schema_id has the following validation checks:

impl SchemaId {
    pub const PREFIX: &'static str = "schema";
    pub const MARKER: &'static str = "2";

    pub fn new(did: &DidValue, name: &str, version: &str) -> SchemaId {
        let id = format!(
            "{}{}{}{}{}{}{}",
            did.0,
            DELIMITER,
            Self::MARKER,
            DELIMITER,
            name,
            DELIMITER,
            version
        );
        Self::from(qualifiable::combine(
            Self::PREFIX,
            did.get_method(),
            id.as_str(),
        ))
    }

    pub fn parts(&self) -> Option<(Option<&str>, DidValue, String, String)> {
        let parts = self.0.split_terminator(DELIMITER).collect::<Vec<&str>>();

        if parts.len() == 1 {
            // 1
            return None;
        }

        if parts.len() == 4 {
            // NcYxiDXkpYi6ov5FcYDi1e:2:gvt:1.0
            let did = parts[0].to_string();
            let name = parts[2].to_string();
            let version = parts[3].to_string();
            return Some((None, DidValue(did), name, version));
        }

        if parts.len() == 8 {
            // schema:sov:did:sov:NcYxiDXkpYi6ov5FcYDi1e:2:gvt:1.0
            let method = parts[1];
            let did = parts[2..5].join(DELIMITER);
            let name = parts[6].to_string();
            let version = parts[7].to_string();
            return Some((Some(method), DidValue(did), name, version));
        }

        None
    }
}

As far as I can tell, NcYxiDXkpYi6ov5FcYDi1e:2:gvt:1.0 is valid, and so is schema:sov:did:sov:NcYxiDXkpYi6ov5FcYDi1e:2:gvt:1.0

So did:sov:... is not valid. The qualified did seems like it needs to be prefixed with schema:sov: ...

This SchemaId type is defined in indy-data-types

cc: @andrewwhitehead

@zoblazo
Copy link
Contributor Author

zoblazo commented Feb 26, 2025

Thus I am able to create a schema with a qualified did:sov with latest nightly build (and no endorser) BUT the resulting schema id is did:sov:...

So problem still (differently) exists. indy-vdr validation doesn't happen anymore ?

@jamshale
Copy link
Contributor

I've been thinking about this and I don't think qualified did:sov`s are currently supported by anoncreds. The implementation was for legacy indy which considered the unqualified version only.

I'm sure you could get it working by providing the unqualified version of the did.

Supporting both unqualified and qualified did:sov dids as the issuer id would need to be a new feature.

@ff137
Copy link
Contributor

ff137 commented Feb 26, 2025

So problem still (differently) exists. indy-vdr validation doesn't happen anymore ?

@zoblazo A change between 1.2.0 and latest nightly build is that the did-prefix is now stripped from the did and the schema id:

            schema_req = ledger.build_get_schema_request(
                strip_did_prefix(public_did), strip_did_prefix(schema_id)
            )

so the indy-vdr validation should be passing.

What's the current problem that, as you mentioned, still exists, but differently?

@jamshale
Copy link
Contributor

jamshale commented Feb 26, 2025

I think it allows you to create the schema but then the schema_id has did:sov at the front and if you try and use this when creating a cred_def or during issuance it won't be able to use the ledger to resolve it.

I think the solution is to support both forms by striping it from the anoncreds object id's wherever they need to use the ledger. I've been looking at this for ticket #3493. It may be worth doing separately so it's done sooner.

I'm not a fan of having to do this special unqualified did:sov stuff. It would be better if did:sov's were qualified in acapy. That's partly what that ticket is trying to fix.

@zoblazo
Copy link
Contributor Author

zoblazo commented Feb 26, 2025

Exactly. Schema is created with a schema_id perfixed did:sov in acapy but the schema id on blockchain is stripped of that did:sov prefix.

Then when I get to create a cred def, I receive a ''400: No resolver available for identifier did:sov:.....:1.0"'

@jamshale
Copy link
Contributor

We'll need to do this as a feature. Support qualified did sov's in anoncreds legacy_indy registry. I can probably try and get to it, I'm currently studying all the required changes.

Right now it would be not available status. You'd need to use unqualified did sov representation in anoncreds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants