mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-20 10:34:20 +01:00
wallet: load keys without keysets (#123)
* wallet: load keys without keysets * fix cli
This commit is contained in:
@@ -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()}
|
||||
|
||||
@@ -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).
|
||||
"""
|
||||
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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user