Skip to content

Commit aa3e7cd

Browse files
committed
WIP - Revocation reversal
1 parent 0cc0f66 commit aa3e7cd

23 files changed

Lines changed: 959 additions & 232 deletions

File tree

aries_cloudagent/anoncreds/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,5 +186,6 @@ async def update_revocation_list(
186186
curr_list: RevList,
187187
revoked: Sequence[int],
188188
options: Optional[dict] = None,
189+
unrevoke: bool = False,
189190
) -> RevListResult:
190191
"""Update a revocation list on the registry."""

aries_cloudagent/anoncreds/default/did_besu/registry.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -879,23 +879,29 @@ async def update_revocation_list(
879879
curr_list: RevList,
880880
revoked: Sequence[int],
881881
options: Optional[dict] = None,
882+
unrevoke: bool = False,
882883
) -> RevListResult:
883884
"""Update a revocation list."""
884885
delta, _ = await self.get_revocation_registry_delta(
885886
profile, curr_list.rev_reg_def_id, 0
886887
)
887888
delta_list = delta["value"]["revoked"] if delta["value"].get("revoked") else []
888889
newly_revoked_indices = list(revoked)
889-
full_revoked_list = newly_revoked_indices + delta_list
890+
if unrevoke:
891+
full_revoked_list = set(delta_list) - set(newly_revoked_indices)
892+
else:
893+
full_revoked_list = newly_revoked_indices + delta_list
890894
rev_reg_entry = {
891895
"ver": "1.0",
892896
"value": {
893897
"accum": curr_list.current_accumulator,
894898
"prevAccum": prev_list.current_accumulator,
895-
"revoked": full_revoked_list,
899+
"revoked": list(full_revoked_list),
896900
},
897901
}
898902

903+
print(f"Entry: {rev_reg_entry}")
904+
899905
await self._revoc_reg_entry_with_fix(
900906
profile, curr_list, rev_reg_def.type, rev_reg_entry
901907
)

aries_cloudagent/anoncreds/events.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ class RevListFinishedPayload(NamedTuple):
112112
rev_reg_id: str
113113
revoked: list
114114
options: dict
115+
unrevoke: bool
115116

116117

117118
class RevListFinishedEvent(Event):
@@ -135,9 +136,10 @@ def with_payload(
135136
rev_reg_id: str,
136137
revoked: list,
137138
options: Optional[dict] = None,
139+
unrevoke: Optional[bool] = None,
138140
):
139141
"""With payload."""
140-
payload = RevListFinishedPayload(rev_reg_id, revoked, options)
142+
payload = RevListFinishedPayload(rev_reg_id, revoked, options, unrevoke)
141143
return cls(payload)
142144

143145
@property

aries_cloudagent/anoncreds/registry.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,16 @@ async def update_revocation_list(
176176
curr_list: RevList,
177177
revoked: Sequence[int],
178178
options: Optional[dict] = None,
179+
unrevoke: bool = False,
179180
) -> RevListResult:
180181
"""Update a revocation list on the registry."""
181182
registrar = await self._registrar_for_identifier(prev_list.issuer_id)
182183
return await registrar.update_revocation_list(
183-
profile, rev_reg_def, prev_list, curr_list, revoked, options
184+
profile,
185+
rev_reg_def,
186+
prev_list,
187+
curr_list,
188+
revoked,
189+
options,
190+
unrevoke,
184191
)

aries_cloudagent/anoncreds/revocation.py

Lines changed: 198 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,7 @@ async def update_revocation_list(
536536
curr: RevList,
537537
revoked: Sequence[int],
538538
options: Optional[dict] = None,
539+
unrevoke: bool = False,
539540
):
540541
"""Publish and update to a revocation list."""
541542
options = options or {}
@@ -576,7 +577,7 @@ async def update_revocation_list(
576577

577578
anoncreds_registry = self.profile.inject(AnonCredsRegistry)
578579
result = await anoncreds_registry.update_revocation_list(
579-
self.profile, rev_reg_def, prev, curr, revoked, options
580+
self.profile, rev_reg_def, prev, curr, revoked, options, unrevoke
580581
)
581582

582583
# # TODO Handle `failed` state
@@ -1303,6 +1304,202 @@ async def revoke_pending_credentials(
13031304
failed=[str(rev_id) for rev_id in sorted(failed_crids)],
13041305
)
13051306

1307+
async def unrevoke_pending_credentials(
1308+
self,
1309+
revoc_reg_id: str,
1310+
*,
1311+
additional_crids: Optional[Sequence[int]] = None,
1312+
limit_crids: Optional[Sequence[int]] = None,
1313+
) -> RevokeResult:
1314+
"""UnRevoke a set of credentials in a revocation registry.
1315+
1316+
Args:
1317+
revoc_reg_id: ID of the revocation registry
1318+
additional_crids: sequences of additional credential indexes to revoke
1319+
limit_crids: a sequence of credential indexes to limit revocation to
1320+
If None, all pending revocations will be published.
1321+
If given, the intersection of pending and limit crids will be published.
1322+
1323+
Returns:
1324+
Tuple with the update revocation list, list of cred rev ids not revoked
1325+
1326+
"""
1327+
updated_list = None
1328+
failed_crids = set()
1329+
max_attempt = 5
1330+
attempt = 0
1331+
1332+
while True:
1333+
attempt += 1
1334+
if attempt >= max_attempt:
1335+
raise AnonCredsRevocationError(
1336+
"Repeated conflict attempting to update registry"
1337+
)
1338+
try:
1339+
async with self.profile.session() as session:
1340+
rev_reg_def_entry = await session.handle.fetch(
1341+
CATEGORY_REV_REG_DEF, revoc_reg_id
1342+
)
1343+
rev_list_entry = await session.handle.fetch(
1344+
CATEGORY_REV_LIST, revoc_reg_id
1345+
)
1346+
rev_reg_def_private_entry = await session.handle.fetch(
1347+
CATEGORY_REV_REG_DEF_PRIVATE, revoc_reg_id
1348+
)
1349+
except AskarError as err:
1350+
raise AnonCredsRevocationError(
1351+
"Error retrieving revocation registry"
1352+
) from err
1353+
1354+
if (
1355+
not rev_reg_def_entry
1356+
or not rev_list_entry
1357+
or not rev_reg_def_private_entry
1358+
):
1359+
raise AnonCredsRevocationError(
1360+
(
1361+
"Missing required revocation registry data: "
1362+
"revocation registry definition"
1363+
if not rev_reg_def_entry
1364+
else ""
1365+
),
1366+
"revocation list" if not rev_list_entry else "",
1367+
(
1368+
"revocation registry private definition"
1369+
if not rev_reg_def_private_entry
1370+
else ""
1371+
),
1372+
)
1373+
1374+
try:
1375+
async with self.profile.session() as session:
1376+
cred_def_entry = await session.handle.fetch(
1377+
CATEGORY_CRED_DEF, rev_reg_def_entry.value_json["credDefId"]
1378+
)
1379+
except AskarError as err:
1380+
raise AnonCredsRevocationError(
1381+
f"Error retrieving cred def {rev_reg_def_entry.value_json['credDefId']}" # noqa: E501
1382+
) from err
1383+
1384+
try:
1385+
# TODO This is a little rough; stored tails location will have public uri
1386+
# but library needs local tails location
1387+
rev_reg_def = RevRegDef.deserialize(rev_reg_def_entry.value_json)
1388+
rev_reg_def.value.tails_location = self.get_local_tails_path(
1389+
rev_reg_def
1390+
)
1391+
cred_def = CredDef.deserialize(cred_def_entry.value_json)
1392+
rev_reg_def_private = RevocationRegistryDefinitionPrivate.load(
1393+
rev_reg_def_private_entry.value_json
1394+
)
1395+
except AnoncredsError as err:
1396+
raise AnonCredsRevocationError(
1397+
"Error loading revocation registry definition"
1398+
) from err
1399+
1400+
rev_crids = set()
1401+
failed_crids = set()
1402+
max_cred_num = rev_reg_def.value.max_cred_num
1403+
rev_info = rev_list_entry.value_json
1404+
cred_revoc_ids = (rev_info["pending"] or []) + (additional_crids or [])
1405+
rev_list = RevList.deserialize(rev_info["rev_list"])
1406+
1407+
for rev_id in cred_revoc_ids:
1408+
if rev_id < 1 or rev_id > max_cred_num:
1409+
LOGGER.error(
1410+
"Skipping requested credential revocation"
1411+
"on rev reg id %s, cred rev id=%s not in range",
1412+
revoc_reg_id,
1413+
rev_id,
1414+
)
1415+
failed_crids.add(rev_id)
1416+
elif rev_id >= rev_info["next_index"]:
1417+
LOGGER.warning(
1418+
"Skipping requested credential revocation"
1419+
"on rev reg id %s, cred rev id=%s not yet issued",
1420+
revoc_reg_id,
1421+
rev_id,
1422+
)
1423+
failed_crids.add(rev_id)
1424+
elif rev_list.revocation_list[rev_id] == 0:
1425+
LOGGER.warning(
1426+
"Skipping requested credential unrevocation"
1427+
"on rev reg id %s, cred rev id=%s is not revoked",
1428+
revoc_reg_id,
1429+
rev_id,
1430+
)
1431+
failed_crids.add(rev_id)
1432+
else:
1433+
rev_crids.add(rev_id)
1434+
1435+
if not rev_crids:
1436+
break
1437+
1438+
if limit_crids is None:
1439+
skipped_crids = set()
1440+
else:
1441+
skipped_crids = rev_crids - set(limit_crids)
1442+
rev_crids = rev_crids - skipped_crids
1443+
1444+
try:
1445+
updated_list = await asyncio.get_event_loop().run_in_executor(
1446+
None,
1447+
lambda: rev_list.to_native().update(
1448+
cred_def=cred_def.to_native(),
1449+
rev_reg_def=rev_reg_def.to_native(),
1450+
rev_reg_def_private=rev_reg_def_private,
1451+
issued=list(rev_crids),
1452+
revoked=None,
1453+
timestamp=int(time.time()),
1454+
),
1455+
)
1456+
except AnoncredsError as err:
1457+
raise AnonCredsRevocationError(
1458+
"Error updating revocation registry"
1459+
) from err
1460+
1461+
try:
1462+
async with self.profile.transaction() as txn:
1463+
rev_info_upd = await txn.handle.fetch(
1464+
CATEGORY_REV_LIST, revoc_reg_id, for_update=True
1465+
)
1466+
if not rev_info_upd:
1467+
LOGGER.warning(
1468+
"Revocation registry missing, skipping update: {}",
1469+
revoc_reg_id,
1470+
)
1471+
updated_list = None
1472+
break
1473+
tags = rev_info_upd.tags
1474+
rev_info_upd = rev_info_upd.value_json
1475+
if rev_info_upd != rev_info:
1476+
# handle concurrent update to the registry by retrying
1477+
continue
1478+
rev_info_upd["rev_list"] = updated_list.to_dict()
1479+
rev_info_upd["pending"] = (
1480+
list(skipped_crids) if skipped_crids else None
1481+
)
1482+
tags["pending"] = json.dumps(True if skipped_crids else False)
1483+
await txn.handle.replace(
1484+
CATEGORY_REV_LIST,
1485+
revoc_reg_id,
1486+
value_json=rev_info_upd,
1487+
tags=tags,
1488+
)
1489+
await txn.commit()
1490+
except AskarError as err:
1491+
raise AnonCredsRevocationError(
1492+
"Error saving revocation registry"
1493+
) from err
1494+
break
1495+
1496+
return RevokeResult(
1497+
prev=rev_list,
1498+
curr=RevList.from_native(updated_list) if updated_list else None,
1499+
revoked=list(rev_crids),
1500+
failed=[str(rev_id) for rev_id in sorted(failed_crids)],
1501+
)
1502+
13061503
async def mark_pending_revocations(self, rev_reg_def_id: str, *crids: int):
13071504
"""Cred rev ids stored to publish later."""
13081505
async with self.profile.transaction() as txn:

aries_cloudagent/anoncreds/revocation_setup.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,5 +107,8 @@ async def on_rev_reg_def(self, profile: Profile, event: RevRegDefFinishedEvent):
107107
async def on_rev_list(self, profile: Profile, event: RevListFinishedEvent):
108108
"""Handle rev list finished."""
109109
await notify_revocation_published_event(
110-
profile, event.payload.rev_reg_id, event.payload.revoked
110+
profile,
111+
event.payload.rev_reg_id,
112+
event.payload.revoked,
113+
event.payload.unrevoke,
111114
)

0 commit comments

Comments
 (0)