Skip to content

Commit

Permalink
Merge pull request #169 from Indicio-tech/feature/anoncreds-wallet
Browse files Browse the repository at this point in the history
Extend `presenting_revoked_credential` example to ledger-agnostic endpoints using wallet=askar-anoncreds
  • Loading branch information
dbluhm authored Jan 24, 2025
2 parents 8958c87 + dcaff26 commit 7c2e96b
Show file tree
Hide file tree
Showing 12 changed files with 342 additions and 58 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/code-quality-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ name: Code Quality Check
jobs:
format:
name: Format and Lint
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: psf/[email protected]
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/models.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ env:

jobs:
model-gen:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04

steps:
- uses: actions/checkout@v4
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:

jobs:
deploy:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
permissions:
id-token: write

Expand All @@ -22,7 +22,7 @@ jobs:
run: poetry install
- name: Run tests
run: |
docker-compose run tests
docker compose run tests
- name: Build package
run: |
poetry build
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@ env:
jobs:
test:
name: Tests
runs-on: ubuntu-latest
runs-on: ubuntu-22.04

steps:
- uses: actions/checkout@v4
- name: Run integration tests for protocols
run: |
docker-compose run tests tests/
docker compose run tests tests/
examples:
name: Check examples
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Install poetry
Expand Down
25 changes: 10 additions & 15 deletions acapy_controller/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,7 @@ def params(**kwargs) -> Mapping[str, Any]:
"""Filter out keys with none values from dictionary."""

return {
key: _serialize_param(value)
for key, value in kwargs.items()
if value is not None
key: _serialize_param(value) for key, value in kwargs.items() if value is not None
}


Expand All @@ -188,6 +186,7 @@ def __init__(
label: Optional[str] = None,
wallet_id: Optional[str] = None,
subwallet_token: Optional[str] = None,
wallet_type: Optional[str] = None,
headers: Optional[Mapping[str, str]] = None,
event_queue: Optional[Queue[Event]] = None,
):
Expand All @@ -202,7 +201,6 @@ def __init__(
self.subwallet_token = subwallet_token
if subwallet_token:
self.headers["Authorization"] = f"Bearer {subwallet_token}"

self._event_queue: Optional[Queue[Event]] = event_queue

self._stack: Optional[AsyncExitStack] = None
Expand Down Expand Up @@ -241,6 +239,10 @@ async def setup(self) -> "Controller":
# Get settings
settings = await self.record("settings")
self.label = settings["label"]

# Get wallet type
config = await self.get("/status/config")
self.wallet_type = config["config"]["wallet.type"]
return self

async def shutdown(self, exc_info: Optional[Tuple] = None):
Expand Down Expand Up @@ -297,9 +299,7 @@ def _header_filter(headers: Mapping[str, str]):

body = await resp.text()
if resp.ok:
raise ControllerError(
f"Unexpected content type {resp.content_type}: {body}"
)
raise ControllerError(f"Unexpected content type {resp.content_type}: {body}")
raise ControllerError(f"Request failed: {resp.url} {body}")

async def request(
Expand All @@ -314,9 +314,7 @@ async def request(
response: Optional[Type[T]] = None,
) -> Union[T, Mapping[str, Any]]:
"""Make an HTTP request."""
async with ClientSession(
base_url=self.base_url, headers=self.headers
) as session:
async with ClientSession(base_url=self.base_url, headers=self.headers) as session:
headers = dict(headers or {})
headers.update(self.headers)

Expand Down Expand Up @@ -645,8 +643,7 @@ async def event(
"""Await an event matching a given topic and condition."""
try:
event = await self.event_queue.get(
lambda event: event.topic == topic
and (select(event) if select else True)
lambda event: event.topic == topic and (select(event) if select else True)
)
except asyncio.TimeoutError:
raise ControllerError(
Expand Down Expand Up @@ -685,9 +682,7 @@ async def event_with_values(
try:
event = await self.event_queue.get(
lambda event: event.topic == topic
and all(
event.payload.get(key) == value for key, value in values.items()
),
and all(event.payload.get(key) == value for key, value in values.items()),
timeout=timeout,
)
except asyncio.TimeoutError:
Expand Down
141 changes: 133 additions & 8 deletions acapy_controller/protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ class ConnRecord(Minimal):
rfc23_state: str


async def trustping(
sender: Controller, conn: ConnRecord, comment: Optional[str] = None
):
async def trustping(sender: Controller, conn: ConnRecord, comment: Optional[str] = None):
"""Send a trustping to the specified connection."""
await sender.post(
f"/connections/{conn.connection_id}/send-ping",
Expand Down Expand Up @@ -372,20 +370,76 @@ async def indy_anoncred_onboard(agent: Controller):
return public_did


# Schema
@dataclass
class SchemaResult(Minimal):
"""Result of creating a schema."""
"""Result of creating a schema using /schemas."""

schema_id: str


@dataclass
class SchemaStateAnoncreds(Minimal):
"""schema_state field in SchemaResultAnoncreds."""

state: str
schema_id: str
schema: dict


@dataclass
class SchemaResultAnoncreds(Minimal):
"""Result of creating a schema using /anoncreds/schema."""

schema_state: SchemaStateAnoncreds
schema_metadata: dict
registration_metadata: dict
job_id: Optional[str] = None

@classmethod
def deserialize(cls: Type[MinType], value: Mapping[str, Any]) -> MinType:
"""Deserialize the cred def result record."""
value = dict(value)
value["schema_state"] = SchemaStateAnoncreds.deserialize(value["schema_state"])
return super().deserialize(value)


# CredDefResult
@dataclass
class CredDefResult(Minimal):
"""Result of creating a credential definition."""

credential_definition_id: str


@dataclass
class CredDefStateAnoncreds(Minimal):
"""credential_definition_state field in CredDefResult."""

state: str
credential_definition_id: str
credential_definition: dict


@dataclass
class CredDefResultAnoncreds(Minimal):
"""Result of creating a cred def using /anoncreds/credential-definition."""

credential_definition_state: CredDefStateAnoncreds
credential_definition_metadata: dict
registration_metadata: dict
job_id: Optional[str] = None

@classmethod
def deserialize(cls: Type[MinType], value: Mapping[str, Any]) -> MinType:
"""Deserialize the cred def result record."""
value = dict(value)
value["credential_definition_state"] = CredDefStateAnoncreds.deserialize(
value["credential_definition_state"]
)
return super().deserialize(value)


async def indy_anoncred_credential_artifacts(
agent: Controller,
attributes: List[str],
Expand All @@ -394,8 +448,61 @@ async def indy_anoncred_credential_artifacts(
cred_def_tag: Optional[str] = None,
support_revocation: bool = False,
revocation_registry_size: Optional[int] = None,
issuerID: Optional[str] = None,
):
"""Prepare credential artifacts for indy anoncreds."""
# Get wallet type
if agent.wallet_type is None:
raise ControllerError(
"Wallet type not found. Please correctly set up the controller."
)
anoncreds_wallet = agent.wallet_type == "askar-anoncreds"

# If using wallet=askar-anoncreds:
if anoncreds_wallet:
if issuerID is None:
raise ControllerError(
"If using askar-anoncreds wallet, issuerID must be specified."
)

schema = (
await agent.post(
"/anoncreds/schema",
json={
"schema": {
"attrNames": attributes,
"issuerId": issuerID,
"name": schema_name or "minimal-" + token_hex(8),
"version": schema_version or "1.0",
},
},
response=SchemaResultAnoncreds,
)
).schema_state

cred_def = (
await agent.post(
"/anoncreds/credential-definition",
json={
"credential_definition": {
"issuerId": issuerID,
"schemaId": schema.schema_id,
"tag": cred_def_tag or token_hex(8),
},
"options": {
"revocation_registry_size": (
revocation_registry_size if revocation_registry_size else 10
),
"support_revocation": support_revocation,
},
},
response=CredDefResultAnoncreds,
)
).credential_definition_state

return schema, cred_def

# If using wallet=askar
schema = await agent.post(
"/schemas",
json={
Expand Down Expand Up @@ -967,6 +1074,13 @@ async def indy_anoncreds_revoke(
V1.0: V10CredentialExchange
V2.0: V20CredExRecordDetail.
"""
# Get wallet type
if issuer.wallet_type is None:
raise ControllerError(
"Wallet type not found. Please correctly set up the controller."
)
anoncreds_wallet = issuer.wallet_type == "askar-anoncreds"

if notify and holder_connection_id is None:
return (
"If you are going to set notify to True,"
Expand All @@ -976,7 +1090,7 @@ async def indy_anoncreds_revoke(
# Passes in V10CredentialExchange
if isinstance(cred_ex, V10CredentialExchange):
await issuer.post(
url="/revocation/revoke",
url="{}/revocation/revoke".format("/anoncreds" if anoncreds_wallet else ""),
json={
"connection_id": holder_connection_id,
"rev_reg_id": cred_ex.revoc_reg_id,
Expand All @@ -990,7 +1104,7 @@ async def indy_anoncreds_revoke(
# Passes in V20CredExRecordDetail
elif isinstance(cred_ex, V20CredExRecordDetail) and cred_ex.indy:
await issuer.post(
url="/revocation/revoke",
url="{}/revocation/revoke".format("/anoncreds" if anoncreds_wallet else ""),
json={
"connection_id": holder_connection_id,
"rev_reg_id": cred_ex.indy.rev_reg_id,
Expand Down Expand Up @@ -1019,9 +1133,18 @@ async def indy_anoncreds_publish_revocation(
V1.0: V10CredentialExchange
V2.0: V20CredExRecordDetail.
"""
# Get wallet type
if issuer.wallet_type is None:
raise ControllerError(
"Wallet type not found. Please correctly set up the controller."
)
anoncreds_wallet = issuer.wallet_type == "askar-anoncreds"

if isinstance(cred_ex, V10CredentialExchange):
await issuer.post(
url="/revocation/publish-revocations",
url="{}/revocation/publish-revocations".format(
"/anoncreds" if anoncreds_wallet else ""
),
json={
"rev_reg_id": cred_ex.revoc_reg_id,
"cred_rev_id": cred_ex.revocation_id,
Expand All @@ -1032,7 +1155,9 @@ async def indy_anoncreds_publish_revocation(

elif isinstance(cred_ex, V20CredExRecordDetail) and cred_ex.indy:
await issuer.post(
url="/revocation/publish-revocations",
url="{}/revocation/publish-revocations".format(
"/anoncreds" if anoncreds_wallet else ""
),
json={
"rev_reg_id": cred_ex.indy.rev_reg_id,
"cred_rev_id": cred_ex.indy.cred_rev_id,
Expand Down
2 changes: 1 addition & 1 deletion conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def compose(self, *command: str) -> int:
"""
try:
subprocess.run(
["docker-compose", "-f", self.compose_file, *command],
["docker", "compose", "-f", self.compose_file, *command],
check=True,
)
return 0
Expand Down
Loading

0 comments on commit 7c2e96b

Please sign in to comment.