Skip to content

Commit

Permalink
Add endorser docs and fix some bugs in the ATTRIB endorse process
Browse files Browse the repository at this point in the history
Signed-off-by: Ian Costanzo <[email protected]>
  • Loading branch information
ianco committed Sep 6, 2022
1 parent cbd5cee commit 22c1aac
Show file tree
Hide file tree
Showing 43 changed files with 639 additions and 159 deletions.
84 changes: 84 additions & 0 deletions AnoncredsProofValidation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Anoncreds Proof Validation in Aca-Py

Aca-Py does some pre-validation when verifying Anoncreds presentations (proofs), some scenarios are rejected (things that are indicative of tampering, for example) and some attributes are removed before running the anoncreds validation (for example removing superfluous non-revocation timestamps). Any Aca-Py validations or presentation modifications are indicated by the "verify_msgs" attribute in the final presentation exchange object

The list of possible verification messages is [here](https://github.com/hyperledger/aries-cloudagent-python/blob/main/aries_cloudagent/indy/verifier.py#L24), and consists of:

```
class PresVerifyMsg(str, Enum):
"""Credential verification codes."""
RMV_REFERENT_NON_REVOC_INTERVAL = "RMV_RFNT_NRI"
RMV_GLOBAL_NON_REVOC_INTERVAL = "RMV_GLB_NRI"
TSTMP_OUT_NON_REVOC_INTRVAL = "TS_OUT_NRI"
CT_UNREVEALED_ATTRIBUTES = "UNRVL_ATTR"
PRES_VALUE_ERROR = "VALUE_ERROR"
PRES_VERIFY_ERROR = "VERIFY_ERROR"
```

If there is additional information, it will be included like this: `TS_OUT_NRI::19_uuid` (which means the attribute identified by `19_uuid` contained a timestamp outside of the non-revocation interval (which is just a warning)).

A presentation verification may include multiple messages, for example:

```
...
"verified": "true",
"verified_msgs": [
"TS_OUT_NRI::18_uuid",
"TS_OUT_NRI::18_id_GE_uuid",
"TS_OUT_NRI::18_busid_GE_uuid"
],
...
```

... or it may include a single message, for example:

```
...
"verified": "false",
"verified_msgs": [
"VALUE_ERROR::Encoded representation mismatch for 'Preferred Name'"
],
...
```

... or the `verified_msgs` may be null or an empty array.

## Presentation Modifications and Warnings

The following modifications/warnings may be done by Aca-Py which shouldn't affect the verification of the received proof):

- "RMV_RFNT_NRI": Referent contains a non-revocation interval for a non-revocable credential (timestamp is removed)
- "RMV_GLB_NRI": Presentation contains a global interval for a non-revocable credential (timestamp is removed)
- "TS_OUT_NRI": Presentation contains a non-revocation timestamp outside of the requested non-revocation interval (warning)
- "UNRVL_ATTR": Presentation contains attributes with unrevealed values (warning)

## Presentation Pre-validation Errors

The following pre-verification checks are done, which will fail the proof (before calling anoncreds) and will result in the following message:

```
VALUE_ERROR::<description of the failed validation>
```

These validations are all done within the [Indy verifier class](https://github.com/hyperledger/aries-cloudagent-python/blob/main/aries_cloudagent/indy/verifier.py) - to see the detailed validation just look for anywhere a `raise ValueError(...)` appears in the code.

A summary of the possible errors is:

- information missing in presentation exchange record
- timestamp provided for irrevocable credential
- referenced revocation registry not found on ledger
- timestamp outside of reasonable range (future date or pre-dates revocation registry)
- mis-match between provided and requested timestamps for non-revocation
- mis-match between requested and provided attributes or predicates
- self-attested attribute is provided for a requested attribute with restrictions
- encoded value doesn't match raw value

## Anoncreds Verification Exceptions

Typically when you call the anoncreds `verifier_verify_proof()` method, it will return a `True` or `False` based on whether the presentation cryptographically verifies. However in the case where anoncreds throws an exception, the exception text will be included in a verification message as follows:

```
VERIFY_ERROR::<the exception text>
```

8 changes: 4 additions & 4 deletions DIDResolution.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ In practice, DIDs and DID Documents are used for a variety of purposes but espec

## `DIDResolver`

In ACA-Py, the `DIDResolver` provides the interface to resolve DIDs using registered method resolvers. Method resolver registration happens on startup through the `DIDResolverRegistry`. This registry enables additional resolvers to be loaded via plugin.
In ACA-Py, the `DIDResolver` provides the interface to resolve DIDs using registered method resolvers. Method resolver registration happens on startup in a `did_resolvers` list. This registry enables additional resolvers to be loaded via plugin.

#### Example usage:
```python=
Expand Down Expand Up @@ -73,17 +73,17 @@ The following is an example method resolver implementation. In this example, we

```python=
from aries_cloudagent.config.injection_context import InjectionContext
from aries_cloudagent.resolver.did_resolver_registry import DIDResolverRegistry
from ..resolver.did_resolver import DIDResolver
from .example_resolver import ExampleResolver
async def setup(context: InjectionContext):
"""Setup the plugin."""
registry = context.inject(DIDResolverRegistry)
registry = context.inject(DIDResolver)
resolver = ExampleResolver()
await resolver.setup(context)
registry.register(resolver)
registry.append(resolver)
```

#### `example_resolver.py`
Expand Down
43 changes: 41 additions & 2 deletions Endorser.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# Transaction Endorser Support

Note that the ACA-Py transaction support is in the process of code refactor and cleanup. The following documents the current state, but is subject to change.

ACA-Py supports an [Endorser Protocol](https://github.com/hyperledger/aries-rfcs/pull/586), that allows an un-privileged agent (an "Author") to request another agent (the "Endorser") to sign their transactions so they can write these transactions to the ledger. This is required on Indy ledgers, where new agents will typically be granted only "Author" privileges.

Transaction Endorsement is built into the protocols for Schema, Credential Definition and Revocation, and endorsements can be explicitely requested, or ACA-Py can be configured to automate the endorsement workflow.
Expand Down Expand Up @@ -61,3 +59,44 @@ Endorsement:
For Authors, specify whether to automatically promote a DID to the wallet public DID after writing to the ledger.
```

## How Aca-py Handles Endorsements

Internally, the Endorsement functionality is implemented as a protocol, and is implemented consistently with other protocols:

- a [routes.py](https://github.com/hyperledger/aries-cloudagent-python/blob/main/aries_cloudagent/protocols/endorse_transaction/v1_0/routes.py) file exposes the admin endpoints
- [handler files](https://github.com/hyperledger/aries-cloudagent-python/tree/main/aries_cloudagent/protocols/endorse_transaction/v1_0/handlers) implement responses to any received Endorse protocol messages
- a [manager.py](https://github.com/hyperledger/aries-cloudagent-python/blob/main/aries_cloudagent/protocols/endorse_transaction/v1_0/manager.py) file implements common functionality that is called from both the routes.py and handler classes (as well as from other classes that need to interact with Endorser functionality)

The Endorser makes use of the [Event Bus](https://github.com/hyperledger/aries-cloudagent-python/blob/main/CHANGELOG.md#july-14-2021) (links to the PR which links to a hackmd doc) to notify other protocols of any Endorser events of interest. For example, after a Credential Definition endorsement is received, the TransactionManager writes the endorsed transaction to the ledger and uses teh Event Bus to notify the Credential Defintition manager that it can do any required post-processing (such as writing the cred def record to the wallet, initiating the revocation registry, etc.).

The overall architecture can be illustrated as:

![Class Diagram](./docs/assets/endorser-design.png)

### Create Credential Definition and Revocation Registry

An example of an Endorser flow is as follows, showing how a credential definition endorsement is received and processed, and optionally kicks off the revocation registry process:

![Sequence Diagram](./docs/assets/endorse-cred-def.png)

You can see that there is a standard endorser flow happening each time there is a ledger write (illustrated in the "Endorser" process).

At the end of each endorse sequence, the TransactionManager sends a notification via the EventBus so that any dependant processing can continue. Each Router is responsibiel for listening and responding to these notifications if necessary.

For example:

- Once the credential definition is created, a revocation registry must be created (for revocable cred defs)
- Once the revocation registry is created, a revocation entry must be created
- Potentially, the cred def status could be updated once the revocation entry is completed

Using the EventBus decouples the event sequence. Any functions triggered by an event notification are typically also available directly via Admin endpoints.

### Create DID and Promote to Public

... and an example of creating a DID and promoting it to public (and creating an ATTRIB for the endpoint:

![Sequence Diagram](./docs/assets/endorse-public-did.png)

You can see the same endorsement processes in this sequence.

Once the DID is written, the DID can (optionally) be promoted to the public DID, which will also invoke an ATTRIB transaction to write the endpoint.
7 changes: 1 addition & 6 deletions aries_cloudagent/config/default_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
from ..core.protocol_registry import ProtocolRegistry
from ..core.goal_code_registry import GoalCodeRegistry
from ..resolver.did_resolver import DIDResolver
from ..resolver.did_resolver_registry import DIDResolverRegistry
from ..tails.base import BaseTailsServer

from ..protocols.actionmenu.v1_0.base_service import BaseMenuService
Expand Down Expand Up @@ -50,12 +49,8 @@ async def build_context(self) -> InjectionContext:
# Global event bus
context.injector.bind_instance(EventBus, EventBus())

# Global did resolver registry
did_resolver_registry = DIDResolverRegistry()
context.injector.bind_instance(DIDResolverRegistry, did_resolver_registry)

# Global did resolver
context.injector.bind_instance(DIDResolver, DIDResolver(did_resolver_registry))
context.injector.bind_instance(DIDResolver, DIDResolver([]))

await self.bind_providers(context)
await self.load_plugins(context)
Expand Down
4 changes: 2 additions & 2 deletions aries_cloudagent/core/tests/test_conductor.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from ...protocols.coordinate_mediation.v1_0.models.mediation_record import (
MediationRecord,
)
from ...resolver.did_resolver import DIDResolver, DIDResolverRegistry
from ...resolver.did_resolver import DIDResolver
from ...multitenant.base import BaseMultitenantManager
from ...multitenant.manager import MultitenantManager
from ...storage.base import BaseStorage
Expand Down Expand Up @@ -92,7 +92,7 @@ async def build_context(self) -> InjectionContext:
context.injector.bind_instance(ProfileManager, InMemoryProfileManager())
context.injector.bind_instance(ProtocolRegistry, ProtocolRegistry())
context.injector.bind_instance(BaseWireFormat, self.wire_format)
context.injector.bind_instance(DIDResolver, DIDResolver(DIDResolverRegistry()))
context.injector.bind_instance(DIDResolver, DIDResolver([]))
context.injector.bind_instance(EventBus, MockEventBus())
return context

Expand Down
23 changes: 15 additions & 8 deletions aries_cloudagent/indy/credx/verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from ...core.profile import Profile

from ..verifier import IndyVerifier
from ..verifier import IndyVerifier, PresVerifyMsg

LOGGER = logging.getLogger(__name__)

Expand All @@ -33,7 +33,7 @@ async def verify_presentation(
credential_definitions,
rev_reg_defs,
rev_reg_entries,
) -> bool:
) -> (bool, list):
"""
Verify a presentation.
Expand All @@ -46,16 +46,21 @@ async def verify_presentation(
rev_reg_entries: revocation registry entries
"""

msgs = []
try:
self.non_revoc_intervals(pres_req, pres, credential_definitions)
await self.check_timestamps(self.profile, pres_req, pres, rev_reg_defs)
await self.pre_verify(pres_req, pres)
msgs += self.non_revoc_intervals(pres_req, pres, credential_definitions)
msgs += await self.check_timestamps(
self.profile, pres_req, pres, rev_reg_defs
)
msgs += await self.pre_verify(pres_req, pres)
except ValueError as err:
s = str(err)
msgs.append(f"{PresVerifyMsg.PRES_VALUE_ERROR.value}::{s}")
LOGGER.error(
f"Presentation on nonce={pres_req['nonce']} "
f"cannot be validated: {str(err)}"
)
return False
return (False, msgs)

try:
presentation = Presentation.load(pres)
Expand All @@ -68,11 +73,13 @@ async def verify_presentation(
rev_reg_defs.values(),
rev_reg_entries,
)
except CredxError:
except CredxError as err:
s = str(err)
msgs.append(f"{PresVerifyMsg.PRES_VERIFY_ERROR.value}::{s}")
LOGGER.exception(
f"Validation of presentation on nonce={pres_req['nonce']} "
"failed with error"
)
verified = False

return verified
return (verified, msgs)
Loading

0 comments on commit 22c1aac

Please sign in to comment.