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

Feature multikey management #3246

Merged
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
620ab1f
load key plugin
PatStLouis Sep 18, 2024
fdd82ff
simplify functions
PatStLouis Sep 19, 2024
04e7830
added option kid field to KeyInfo
PatStLouis Sep 20, 2024
ee3d596
Merge branch 'main' into feature-multikey-management
PatStLouis Sep 20, 2024
ea536a3
improving linting and unit tests
PatStLouis Sep 20, 2024
3e07092
Merge branch 'feature-multikey-management' of github.com:OpSecId/arie…
PatStLouis Sep 20, 2024
1a752bb
add empty kid value for bbs tests
PatStLouis Sep 20, 2024
a1731d4
linting fix
PatStLouis Sep 20, 2024
e6bbe50
more linting
PatStLouis Sep 20, 2024
87fdcca
add 2 more tests
PatStLouis Sep 20, 2024
6d1257c
linting
PatStLouis Sep 20, 2024
8210b3d
await function in tests
PatStLouis Sep 20, 2024
942fdbf
askar support
PatStLouis Sep 22, 2024
f62c580
implement askar
PatStLouis Sep 22, 2024
1fcd715
spelling mistake
PatStLouis Sep 22, 2024
d94a41d
use a constant for default algorithm
PatStLouis Sep 22, 2024
427722f
linting
PatStLouis Sep 22, 2024
e632b57
remove unused code
PatStLouis Sep 22, 2024
3034ad8
formatting
PatStLouis Sep 22, 2024
41fa48d
linting
PatStLouis Sep 22, 2024
9249ec5
fix key by kid
PatStLouis Sep 23, 2024
b6f837a
fix unit tests
PatStLouis Sep 23, 2024
e69956d
Merge branch 'main' into feature-multikey-management
PatStLouis Sep 23, 2024
4c94560
add type hints and pass session to call functions instead of profile
PatStLouis Sep 24, 2024
d806165
Merge branch 'feature-multikey-management' of github.com:OpSecId/arie…
PatStLouis Sep 24, 2024
5f52b81
remove manager from test function instanciation
PatStLouis Sep 24, 2024
6e478d1
replace inject_or with inject for providing wallet interface
PatStLouis Sep 24, 2024
472606c
move wallet injection to class initialization step
PatStLouis Sep 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions aries_cloudagent/config/default_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ async def load_plugins(self, context: InjectionContext):
plugin_registry.register_plugin("aries_cloudagent.settings")
plugin_registry.register_plugin("aries_cloudagent.vc")
plugin_registry.register_plugin("aries_cloudagent.wallet")
plugin_registry.register_plugin("aries_cloudagent.wallet.keys")

anoncreds_plugins = [
"aries_cloudagent.anoncreds",
Expand Down
1 change: 1 addition & 0 deletions aries_cloudagent/vc/tests/test_bbs_mattr_interop.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ async def asyncSetUp(self):
"verkey": public_key_base58,
"metadata": {},
"key_type": BLS12381G2,
"kid": None,
}

self.signature_issuer_suite = BbsBlsSignature2020(
Expand Down
10 changes: 5 additions & 5 deletions aries_cloudagent/wallet/askar.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,7 @@ async def create_key(
"Verification key already present in wallet"
) from None
raise WalletError("Error creating signing key") from err

return KeyInfo(verkey=verkey, metadata=metadata, key_type=key_type)
return KeyInfo(verkey=verkey, metadata=metadata, key_type=key_type, kid=kid)

async def assign_kid_to_key(self, verkey: str, kid: str) -> KeyInfo:
"""Assign a KID to a key.
Expand Down Expand Up @@ -143,7 +142,7 @@ async def assign_kid_to_key(self, verkey: str, kid: str) -> KeyInfo:
raise WalletError(f"Unknown key type {key.algorithm.value}")

await self._session.handle.update_key(name=verkey, tags={"kid": kid})
return KeyInfo(verkey=verkey, metadata=metadata, key_type=key_type)
return KeyInfo(verkey=verkey, metadata=metadata, key_type=key_type, kid=kid)

async def get_key_by_kid(self, kid: str) -> KeyInfo:
"""Fetch a key by looking up its kid.
Expand All @@ -170,7 +169,7 @@ async def get_key_by_kid(self, kid: str) -> KeyInfo:
if not key_type:
raise WalletError(f"Unknown key type {key.algorithm.value}")

return KeyInfo(verkey=verkey, metadata=metadata, key_type=key_type)
return KeyInfo(verkey=verkey, metadata=metadata, key_type=key_type, kid=kid)

async def get_signing_key(self, verkey: str) -> KeyInfo:
"""Fetch info for a signing keypair.
Expand All @@ -194,7 +193,8 @@ async def get_signing_key(self, verkey: str) -> KeyInfo:
raise WalletNotFoundError("Unknown key: {}".format(verkey))
metadata = json.loads(key.metadata or "{}")
# FIXME implement key types
return KeyInfo(verkey=verkey, metadata=metadata, key_type=ED25519)
kid = key.tags["kid"] if "kid" in key.tags else None
return KeyInfo(verkey=verkey, metadata=metadata, key_type=ED25519, kid=kid)

async def replace_signing_key_metadata(self, verkey: str, metadata: dict):
"""Replace the metadata associated with a signing keypair.
Expand Down
13 changes: 10 additions & 3 deletions aries_cloudagent/wallet/did_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,16 @@

INVITATION_REUSE_KEY = "invitation_reuse"

KeyInfo = NamedTuple(
"KeyInfo", [("verkey", str), ("metadata", dict), ("key_type", KeyType)]
)

class KeyInfo(NamedTuple):
"""Class returning key information."""

verkey: str
metadata: dict
key_type: KeyType
kid: str = None


DIDInfo = NamedTuple(
"DIDInfo",
[
Expand Down
4 changes: 4 additions & 0 deletions aries_cloudagent/wallet/in_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ async def create_key(
verkey=verkey_enc,
metadata=self.profile.keys[verkey_enc]["metadata"].copy(),
key_type=key_type,
kid=kid,
)

async def assign_kid_to_key(self, verkey: str, kid: str) -> KeyInfo:
Expand All @@ -120,6 +121,7 @@ async def assign_kid_to_key(self, verkey: str, kid: str) -> KeyInfo:
verkey=key["verkey"],
metadata=key["metadata"].copy(),
key_type=key["key_type"],
kid=kid,
)

async def get_key_by_kid(self, kid: str) -> KeyInfo:
Expand All @@ -138,6 +140,7 @@ async def get_key_by_kid(self, kid: str) -> KeyInfo:
verkey=key["verkey"],
metadata=key["metadata"].copy(),
key_type=key["key_type"],
kid=key["kid"],
)

raise WalletNotFoundError(f"Key not found with kid {kid}")
Expand All @@ -162,6 +165,7 @@ async def get_signing_key(self, verkey: str) -> KeyInfo:
verkey=key["verkey"],
metadata=key["metadata"].copy(),
key_type=key["key_type"],
kid=key["kid"],
)

async def replace_signing_key_metadata(self, verkey: str, metadata: dict):
Expand Down
Empty file.
99 changes: 99 additions & 0 deletions aries_cloudagent/wallet/keys/manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
"""Multikey class."""

from ...core.profile import ProfileSession
from ..base import BaseWallet
from ..key_type import ED25519
from ..util import b58_to_bytes, bytes_to_b58
from ...utils.multiformats import multibase
from ...wallet.error import WalletNotFoundError

DEFAULT_ALG = "ed25519"
ALG_MAPPINGS = {
"ed25519": {"key_type": ED25519, "prefix_hex": "ed01", "prefix_length": 2}
}


class MultikeyManagerError(Exception):
"""Generic MultikeyManager Error."""


class MultikeyManager:
"""Class for managing wallet keys."""

def __init__(self, session: ProfileSession):
"""Initialize the MultikeyManager."""
self.session = session

def _multikey_to_verkey(self, multikey: str, alg: str = DEFAULT_ALG):
"""Transform multikey to verkey."""
prefix_length = ALG_MAPPINGS[alg]["prefix_length"]
public_bytes = bytes(bytearray(multibase.decode(multikey))[prefix_length:])
return bytes_to_b58(public_bytes)

def _verkey_to_multikey(self, verkey: str, alg: str = DEFAULT_ALG):
"""Transform verkey to multikey."""
prefix_hex = ALG_MAPPINGS[alg]["prefix_hex"]
prefixed_key_hex = f"{prefix_hex}{b58_to_bytes(verkey).hex()}"
return multibase.encode(bytes.fromhex(prefixed_key_hex), "base58btc")

async def kid_exists(self, wallet: BaseWallet, kid: str):
"""Check if kid exists."""
try:
key = await wallet.get_key_by_kid(kid=kid)
if key:
return True
except (WalletNotFoundError, AttributeError):
return False

async def from_kid(self, kid: str):
PatStLouis marked this conversation as resolved.
Show resolved Hide resolved
"""Fetch a single key."""
wallet: BaseWallet | None = self.session.inject_or(BaseWallet)
PatStLouis marked this conversation as resolved.
Show resolved Hide resolved
key_info = await wallet.get_key_by_kid(kid=kid)
return {
"kid": key_info.kid,
"multikey": self._verkey_to_multikey(key_info.verkey),
}

async def from_multikey(self, multikey: str):
PatStLouis marked this conversation as resolved.
Show resolved Hide resolved
"""Fetch a single key."""
wallet: BaseWallet | None = self.session.inject_or(BaseWallet)
key_info = await wallet.get_signing_key(verkey=self._multikey_to_verkey(multikey))
return {
"kid": key_info.kid,
"multikey": self._verkey_to_multikey(key_info.verkey),
}

async def create(self, seed: str = None, kid: str = None, alg: str = DEFAULT_ALG):
"""Create a new key pair."""

if alg not in ALG_MAPPINGS:
raise MultikeyManagerError(
f"Unknown key algorithm, use one of {list(ALG_MAPPINGS.keys())}."
)

wallet: BaseWallet | None = self.session.inject_or(BaseWallet)

if kid and await self.kid_exists(wallet=wallet, kid=kid):
raise MultikeyManagerError(f"kid '{kid}' already exists in wallet.")

key_type = ALG_MAPPINGS[alg]["key_type"]
key_info = await wallet.create_key(key_type=key_type, seed=seed, kid=kid)
return {
"kid": key_info.kid,
"multikey": self._verkey_to_multikey(key_info.verkey),
}

async def update(self, multikey: str, kid: str):
PatStLouis marked this conversation as resolved.
Show resolved Hide resolved
"""Assign a new kid to a key pair."""

wallet: BaseWallet | None = self.session.inject_or(BaseWallet)

if kid and await self.kid_exists(wallet=wallet, kid=kid):
raise MultikeyManagerError(f"kid '{kid}' already exists in wallet.")
key_info = await wallet.assign_kid_to_key(
verkey=self._multikey_to_verkey(multikey), kid=kid
)
return {
"kid": key_info.kid,
"multikey": self._verkey_to_multikey(key_info.verkey),
}
Loading
Loading