Skip to content

Commit

Permalink
Add did:indy transaction version 2 support
Browse files Browse the repository at this point in the history
Signed-off-by: Jamie Hale <[email protected]>
  • Loading branch information
jamshale committed Oct 10, 2024
1 parent 7d6f6b2 commit a226b5f
Show file tree
Hide file tree
Showing 27 changed files with 806 additions and 143 deletions.
7 changes: 4 additions & 3 deletions acapy_agent/config/argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -1555,9 +1555,10 @@ def add_arguments(self, parser: ArgumentParser):
env_var="ACAPY_WALLET_SEED",
help=(
"Specifies the seed to use for the creation of a public "
"DID for the agent to use with a Hyperledger Indy ledger, or a local "
"('--wallet-local-did') DID. If public, the DID must already exist "
"on the ledger."
"DID (sov or indy) for the agent to use with a Hyperledger Indy ledger, "
"or a local ('--wallet-local-did') DID. If public, the DID must already "
"exist on the ledger. If using did:indy method you must already have a "
"public DID on the ledger."
),
)
parser.add_argument(
Expand Down
5 changes: 3 additions & 2 deletions acapy_agent/config/default_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,7 @@ async def load_plugins(self, context: InjectionContext):

# Currently providing admin routes only
plugin_registry.register_plugin("acapy_agent.holder")

plugin_registry.register_plugin("acapy_agent.ledger")

plugin_registry.register_plugin("acapy_agent.messaging.jsonld")
plugin_registry.register_plugin("acapy_agent.resolver")
plugin_registry.register_plugin("acapy_agent.settings")
Expand All @@ -143,6 +141,9 @@ async def load_plugins(self, context: InjectionContext):
plugin_registry.register_plugin("acapy_agent.wallet")
plugin_registry.register_plugin("acapy_agent.wallet.keys")

# Did management plugins
plugin_registry.register_plugin("acapy_agent.did.indy")

anoncreds_plugins = [
"acapy_agent.anoncreds",
"acapy_agent.anoncreds.default.did_indy",
Expand Down
3 changes: 2 additions & 1 deletion acapy_agent/config/tests/test_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from acapy_agent.tests import mock

from ...core.error import StartupError
from ...core.in_memory import InMemoryProfile
from ...core.profile import ProfileManager, ProfileSession
from ...storage.base import BaseStorage
Expand Down Expand Up @@ -143,7 +144,7 @@ async def test_wallet_config_auto_provision(self):
):
mock_mgr_open.side_effect = test_module.ProfileNotFoundError()

with self.assertRaises(test_module.ProfileNotFoundError):
with self.assertRaises(StartupError):
await test_module.wallet_config(self.context, provision=False)

self.context.update_settings({"auto_provision": True})
Expand Down
214 changes: 129 additions & 85 deletions acapy_agent/config/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
import logging
from typing import Tuple

from ..core.error import ProfileNotFoundError
from ..core.error import ProfileNotFoundError, StartupError
from ..core.profile import Profile, ProfileManager, ProfileSession
from ..storage.base import BaseStorage
from ..storage.error import StorageNotFoundError
from ..version import RECORD_TYPE_ACAPY_VERSION, __version__
from ..wallet.base import BaseWallet
from ..wallet.crypto import seed_to_did
from ..wallet.did_info import DIDInfo
from ..wallet.did_method import SOV
from ..wallet.did_method import INDY, SOV
from ..wallet.key_type import ED25519
from .base import ConfigError
from .injection_context import InjectionContext
Expand All @@ -29,111 +29,155 @@
}


async def wallet_config(
context: InjectionContext, provision: bool = False
) -> Tuple[Profile, DIDInfo]:
"""Initialize the root profile."""
def _create_config_with_settings(settings) -> dict:
profile_config = {}

mgr = context.inject(ProfileManager)

settings = context.settings
profile_cfg = {}
for k in CFG_MAP:
pk = f"wallet.{k}"
if pk in settings:
profile_cfg[k] = settings[pk]
profile_config[k] = settings[pk]

# may be set by `aca-py provision --recreate`
if settings.get("wallet.recreate"):
profile_cfg["auto_recreate"] = True
profile_config["auto_recreate"] = True

if provision:
profile = await mgr.provision(context, profile_cfg)
return profile_config


async def _attempt_open_profile(
profile_manager: ProfileManager,
context: InjectionContext,
profile_config: dict,
settings: dict,
) -> Tuple[Profile, bool]:
provision = False
try:
profile = await profile_manager.open(context, profile_config)
except ProfileNotFoundError:
if settings.get("auto_provision", False):
profile = await profile_manager.provision(context, profile_config)
provision = True
else:
raise StartupError(
"Profile not found. Use `aca-py start --auto-provision` to create."
)

return (profile, provision)


def _log_provision_info(profile: Profile) -> None:
print(f'{"Created new profile" if profile.created else "Opened existing profile"}')
print(f"Profile name: {profile.name} Profile backend: {profile.backend}")


async def _initialize_with_public_did(
public_did_info: DIDInfo,
wallet: BaseWallet,
settings: dict,
wallet_seed: str,
) -> str:
public_did = public_did_info.did
# Check did:sov and did:indy
if wallet_seed and (
seed_to_did(wallet_seed) != public_did
and seed_to_did(wallet_seed, INDY) != public_did
):
if not settings.get("wallet.replace_public_did"):
raise ConfigError(
"New seed provided which doesn't match the registered"
+ f" public did {public_did}"
)

print("Replacing public DID due to --replace-public-did flag")
replace_did_info = await wallet.create_local_did(
method=SOV, key_type=ED25519, seed=wallet_seed
)
public_did = replace_did_info.did
await wallet.set_public_did(public_did)
print(
f"Created new public DID: {public_did}, with verkey: {replace_did_info.verkey}" # noqa: E501
)


async def _initialize_with_debug_settings(settings: dict, wallet: BaseWallet):
test_seed = settings.get("debug.seed")
if settings.get("debug.enabled") and not test_seed:
test_seed = "testseed000000000000000000000001"
if test_seed:
await wallet.create_local_did(
method=SOV,
key_type=ED25519,
seed=test_seed,
metadata={"endpoint": "1.2.3.4:8021"},
)


async def _initialize_with_seed(
settings: dict, wallet: BaseWallet, provision: bool, create_local_did: bool, seed: str
):
if create_local_did:
endpoint = settings.get("default_endpoint")
metadata = {"endpoint": endpoint} if endpoint else None

local_did_info = await wallet.create_local_did(
method=SOV,
key_type=ED25519,
seed=seed,
metadata=metadata,
)
local_did = local_did_info.did
if provision:
print(f"Created new local DID: {local_did}")
print(f"Verkey: {local_did_info.verkey}")
else:
try:
profile = await mgr.open(context, profile_cfg)
except ProfileNotFoundError:
if settings.get("auto_provision", False):
profile = await mgr.provision(context, profile_cfg)
provision = True
else:
raise
public_did_info = await wallet.create_public_did(
method=SOV, key_type=ED25519, seed=seed
)
public_did = public_did_info.did
if provision:
print(f"Created new public DID: {public_did}")
print(f"Verkey: {public_did_info.verkey}")


async def wallet_config(
context: InjectionContext, provision: bool = False
) -> Tuple[Profile, DIDInfo]:
"""Initialize the root profile."""

profile_manager = context.inject(ProfileManager)

settings = context.settings
profile_config = _create_config_with_settings(settings)
wallet_seed = settings.get("wallet.seed")
create_local_did = settings.get("wallet.local_did")

if provision:
if profile.created:
print("Created new profile")
else:
print("Opened existing profile")
print("Profile backend:", profile.backend)
print("Profile name:", profile.name)
profile = await profile_manager.provision(context, profile_config)
else:
profile, provision = await _attempt_open_profile(
profile_manager, context, profile_config, settings
)

_log_provision_info(profile)

wallet_seed = context.settings.get("wallet.seed")
wallet_local_did = context.settings.get("wallet.local_did")
txn = await profile.transaction()
wallet = txn.inject(BaseWallet)

public_did_info = await wallet.get_public_did()
public_did = None

if public_did_info:
public_did = public_did_info.did
if wallet_seed and seed_to_did(wallet_seed) != public_did:
if context.settings.get("wallet.replace_public_did"):
replace_did_info = await wallet.create_local_did(
method=SOV, key_type=ED25519, seed=wallet_seed
)
public_did = replace_did_info.did
await wallet.set_public_did(public_did)
print(f"Created new public DID: {public_did}")
print(f"Verkey: {replace_did_info.verkey}")
else:
# If we already have a registered public did and it doesn't match
# the one derived from `wallet_seed` then we error out.
raise ConfigError(
"New seed provided which doesn't match the registered"
+ f" public did {public_did}"
)
# wait until ledger config to set public DID endpoint - wallet goes first
public_did = await _initialize_with_public_did(
public_did_info, wallet, settings, wallet_seed
)
elif wallet_seed:
if wallet_local_did:
endpoint = context.settings.get("default_endpoint")
metadata = {"endpoint": endpoint} if endpoint else None

local_did_info = await wallet.create_local_did(
method=SOV,
key_type=ED25519,
seed=wallet_seed,
metadata=metadata,
)
local_did = local_did_info.did
if provision:
print(f"Created new local DID: {local_did}")
print(f"Verkey: {local_did_info.verkey}")
else:
public_did_info = await wallet.create_public_did(
method=SOV, key_type=ED25519, seed=wallet_seed
)
public_did = public_did_info.did
if provision:
print(f"Created new public DID: {public_did}")
print(f"Verkey: {public_did_info.verkey}")
# wait until ledger config to set public DID endpoint - wallet goes first
await _initialize_with_seed(
settings, wallet, provision, create_local_did, wallet_seed
)

if provision and not wallet_local_did and not public_did:
if provision and not create_local_did and not public_did:
print("No public DID")

# Debug settings
test_seed = context.settings.get("debug.seed")
if context.settings.get("debug.enabled"):
if not test_seed:
test_seed = "testseed000000000000000000000001"
if test_seed:
await wallet.create_local_did(
method=SOV,
key_type=ED25519,
seed=test_seed,
metadata={"endpoint": "1.2.3.4:8021"},
)
await _initialize_with_debug_settings(settings, wallet)

await txn.commit()

Expand Down
65 changes: 65 additions & 0 deletions acapy_agent/did/indy/indy_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""DID manager for Indy."""

from typing import Optional

from aries_askar import AskarError, Key, KeyAlg

from ...core.profile import Profile
from ...wallet.askar import CATEGORY_DID
from ...wallet.crypto import validate_seed
from ...wallet.did_method import INDY, DIDMethods
from ...wallet.did_parameters_validation import DIDParametersValidation
from ...wallet.error import WalletError
from ...wallet.key_type import ED25519
from ...wallet.util import bytes_to_b58


class DidIndyManager:
"""DID manager for Indy."""

def __init__(self, profile: Profile) -> None:
"""Initialize the DID manager."""
self.profile = profile

def _create_key_pair(self, seed: Optional[str] = None) -> Key:
if seed and not self.profile.settings.get("wallet.allow_insecure_seed"):
raise WalletError("Insecure seed is not allowed")
if seed:
seed = validate_seed(seed)
return Key.from_secret_bytes(KeyAlg.ED25519, seed)
return Key.generate(KeyAlg.ED25519)

async def register(self, seed: Optional[str] = None) -> dict:
"""Register a DID Indy."""
did_validation = DIDParametersValidation(self.profile.inject(DIDMethods))
key_pair = self._create_key_pair(seed)
verkey_bytes = key_pair.get_public_bytes()
verkey = bytes_to_b58(verkey_bytes)

did = f"did:indy:{did_validation.validate_or_derive_did(INDY, ED25519, verkey_bytes, None)}" # noqa: E501

async with self.profile.session() as session:
try:
await session.handle.insert_key(verkey, key_pair)
await session.handle.insert(
CATEGORY_DID,
did,
value_json={
"did": did,
"method": INDY.method_name,
"verkey": verkey,
"verkey_type": ED25519.key_type,
"metadata": {},
},
tags={
"method": INDY.method_name,
"verkey": verkey,
"verkey_type": ED25519.key_type,
},
)
except AskarError as err:
raise WalletError(f"Error registering DID: {err}") from err
return {
"did": did,
"verkey": verkey,
}
Loading

0 comments on commit a226b5f

Please sign in to comment.