wallet: load keys without keysets (#123)

* wallet: load keys without keysets

* fix cli
This commit is contained in:
calle
2023-03-05 02:51:20 +01:00
committed by GitHub
parent e696e19479
commit a7eef68c97
5 changed files with 89 additions and 38 deletions

View File

@@ -338,6 +338,8 @@ class Ledger:
# Public methods
def get_keyset(self, keyset_id: Optional[str] = None):
if keyset_id and keyset_id not in self.keysets.keysets:
raise Exception("keyset does not exist")
keyset = self.keysets.keysets[keyset_id] if keyset_id else self.keyset
assert keyset.public_keys, Exception("no public keys for this keyset")
return {a: p.serialize().hex() for a, p in keyset.public_keys.items()}

View File

@@ -43,16 +43,19 @@ async def keys() -> KeysResponse:
name="Keyset public keys",
summary="Public keys of a specific keyset",
)
async def keyset_keys(idBase64Urlsafe: str) -> KeysResponse:
async def keyset_keys(idBase64Urlsafe: str) -> Union[KeysResponse, CashuError]:
"""
Get the public keys of the mint from a specific keyset id.
The id is encoded in idBase64Urlsafe (by a wallet) and is converted back to
normal base64 before it can be processed (by the mint).
"""
id = idBase64Urlsafe.replace("-", "+").replace("_", "/")
keyset = ledger.get_keyset(keyset_id=id)
keys = KeysResponse.parse_obj(keyset)
return keys
try:
id = idBase64Urlsafe.replace("-", "+").replace("_", "/")
keyset = ledger.get_keyset(keyset_id=id)
keys = KeysResponse.parse_obj(keyset)
return keys
except Exception as exc:
return CashuError(code=0, error=str(exc))
@router.get(

View File

@@ -36,10 +36,10 @@ async def verify_mints(ctx: Context, token: TokenV2):
)
# make sure that this mint supports this keyset
mint_keysets = await keyset_wallet._get_keyset_ids(mint.url)
assert keyset in mint_keysets["keysets"], "mint does not have this keyset."
assert keyset in mint_keysets, "mint does not have this keyset."
# we validate the keyset id by fetching the keys from the mint and computing the id locally
mint_keyset = await keyset_wallet._get_keyset(mint.url, keyset)
mint_keyset = await keyset_wallet._get_keys_of_keyset(mint.url, keyset)
assert keyset == mint_keyset.id, Exception("keyset not valid.")
# we check the db whether we know this mint already and ask the user if not

View File

@@ -91,8 +91,9 @@ def async_set_requests(func):
class LedgerAPI:
keys: Dict[int, PublicKey]
keyset: str
keys: WalletKeyset # holds current keys of mint
keyset_id: str # holds id of current keyset
public_keys: Dict[int, PublicKey] # holds public keys of
tor: TorProxy
db: Database
s: requests.Session
@@ -113,7 +114,7 @@ class LedgerAPI:
proofs: List[Proof] = []
for promise, secret, r in zip(promises, secrets, rs):
C_ = PublicKey(bytes.fromhex(promise.C_), raw=True)
C = b_dhke.step3_alice(C_, r, self.keys[promise.amount])
C = b_dhke.step3_alice(C_, r, self.public_keys[promise.amount])
proof = Proof(
id=self.keyset_id,
amount=promise.amount,
@@ -133,48 +134,60 @@ class LedgerAPI:
"""Returns base64 encoded random string."""
return scrts.token_urlsafe(randombits // 8)
async def _load_mint(self, keyset_id: str = ""):
"""
Loads the public keys of the mint. Either gets the keys for the specified
`keyset_id` or loads the most recent one from the mint.
Gets and the active keyset ids of the mint and stores in `self.keysets`.
"""
async def _load_mint_keys(self, keyset_id: str = ""):
assert len(
self.url
), "Ledger not initialized correctly: mint URL not specified yet. "
if keyset_id:
# get requested keyset
keyset = await self._get_keyset(self.url, keyset_id)
keyset = await self._get_keys_of_keyset(self.url, keyset_id)
else:
# get current keyset
keyset = await self._get_keys(self.url)
# store current keyset
assert keyset.public_keys
assert keyset.id
assert len(keyset.public_keys) > 0, "did not receive keys from mint."
# check if current keyset is in db
keyset_local: Optional[WalletKeyset] = await get_keyset(keyset.id, db=self.db)
# if not, store it
if keyset_local is None:
await store_keyset(keyset=keyset, db=self.db)
self.keys = keyset
logger.debug(f"Current mint keyset: {self.keys.id}")
return self.keys
async def _load_mint_keysets(self):
# get all active keysets of this mint
mint_keysets = []
try:
keysets_resp = await self._get_keyset_ids(self.url)
mint_keysets = keysets_resp["keysets"]
# store active keysets
mint_keysets = await self._get_keyset_ids(self.url)
except:
assert self.keys.id, "could not get keysets from mint, and do not have keys"
pass
self.keysets = mint_keysets if len(mint_keysets) else [keyset.id]
self.keysets = mint_keysets or [self.keys.id]
logger.debug(f"Mint keysets: {self.keysets}")
logger.debug(f"Current mint keyset: {keyset.id}")
return self.keysets
self.keys = keyset.public_keys
self.keyset_id = keyset.id
async def _load_mint(self, keyset_id: str = ""):
"""
Loads the public keys of the mint. Either gets the keys for the specified
`keyset_id` or gets the keys of the active keyset from the mint.
Gets the active keyset ids of the mint and stores in `self.keysets`.
"""
await self._load_mint_keys(keyset_id)
await self._load_mint_keysets()
if keyset_id:
assert keyset_id in self.keysets, f"keyset {keyset_id} not active on mint"
assert self.keys.public_keys
self.public_keys = self.keys.public_keys
assert self.keys.id
self.keyset_id = self.keys.id
@staticmethod
def _construct_outputs(amounts: List[int], secrets: List[str]):
@@ -211,6 +224,14 @@ class LedgerAPI:
@async_set_requests
async def _get_keys(self, url: str):
"""API that gets the current keys of the mint
Args:
url (str): Mint URL
Returns:
WalletKeyset: Current mint keyset
"""
resp = self.s.get(
url + "/keys",
)
@@ -225,9 +246,16 @@ class LedgerAPI:
return keyset
@async_set_requests
async def _get_keyset(self, url: str, keyset_id: str):
"""
keyset_id is base64, needs to be urlsafe-encoded.
async def _get_keys_of_keyset(self, url: str, keyset_id: str):
"""API that gets the keys of a specific keyset from the mint.
Args:
url (str): Mint URL
keyset_id (str): base64 keyset ID, needs to be urlsafe-encoded before sending to mint (done in this method)
Returns:
WalletKeyset: Keyset with ID keyset_id
"""
keyset_id_urlsafe = keyset_id.replace("+", "-").replace("/", "_")
resp = self.s.get(
@@ -235,6 +263,7 @@ class LedgerAPI:
)
resp.raise_for_status()
keys = resp.json()
self.raise_on_error(keys)
assert len(keys), Exception("did not receive any keys")
keyset_keys = {
int(amt): PublicKey(bytes.fromhex(val), raw=True)
@@ -245,6 +274,14 @@ class LedgerAPI:
@async_set_requests
async def _get_keyset_ids(self, url: str):
"""API that gets a list of all active keysets of the mint.
Args:
url (str): Mint URL
Returns:
KeysetsResponse (List[str]): List of all active keyset IDs of the mint
"""
resp = self.s.get(
url + "/keysets",
)
@@ -252,7 +289,7 @@ class LedgerAPI:
keysets_dict = resp.json()
keysets = KeysetsResponse.parse_obj(keysets_dict)
assert len(keysets.keysets), Exception("did not receive any keysets")
return keysets.dict()
return keysets.keysets
@async_set_requests
async def request_mint(self, amount):

View File

@@ -50,7 +50,8 @@ async def wallet2(mint):
@pytest.mark.asyncio
async def test_get_keys(wallet1: Wallet):
assert len(wallet1.keys) == MAX_ORDER
assert wallet1.keys.public_keys
assert len(wallet1.keys.public_keys) == MAX_ORDER
keyset = await wallet1._get_keys(wallet1.url)
assert keyset.id is not None
assert type(keyset.id) == str
@@ -59,24 +60,32 @@ async def test_get_keys(wallet1: Wallet):
@pytest.mark.asyncio
async def test_get_keyset(wallet1: Wallet):
assert len(wallet1.keys) == MAX_ORDER
# ket's get the keys first so we can get a keyset ID that we use later
assert wallet1.keys.public_keys
assert len(wallet1.keys.public_keys) == MAX_ORDER
# let's get the keys first so we can get a keyset ID that we use later
keys1 = await wallet1._get_keys(wallet1.url)
# gets the keys of a specific keyset
assert keys1.id is not None
assert keys1.public_keys is not None
keys2 = await wallet1._get_keyset(wallet1.url, keys1.id)
keys2 = await wallet1._get_keys_of_keyset(wallet1.url, keys1.id)
assert keys2.public_keys is not None
assert len(keys1.public_keys) == len(keys2.public_keys)
@pytest.mark.asyncio
async def test_get_nonexistent_keyset(wallet1: Wallet):
await assert_err(
wallet1._get_keys_of_keyset(wallet1.url, "nonexistent"),
"Mint Error: keyset does not exist",
)
@pytest.mark.asyncio
async def test_get_keyset_ids(wallet1: Wallet):
keyset = await wallet1._get_keyset_ids(wallet1.url)
assert type(keyset) == dict
assert type(keyset["keysets"]) == list
assert len(keyset["keysets"]) > 0
assert keyset["keysets"][-1] == wallet1.keyset_id
assert type(keyset) == list
assert len(keyset) > 0
assert keyset[-1] == wallet1.keyset_id
@pytest.mark.asyncio