Skip to content

Commit dce5543

Browse files
authored
Merge branch 'main' into aido/696/doc-sync-message-envelope
2 parents 4bd9efd + b421ea7 commit dce5543

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+2811
-1209
lines changed

.config/dictionaries/project.dic

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ adminer
88
admon
99
anypolicy
1010
apskhem
11+
asat
1112
Arissara
1213
asyncio
1314
Attributes
@@ -33,6 +34,7 @@ Cabe
3334
cantopen
3435
cardano
3536
carryforward
37+
catid
3638
CBOR
3739
cbork
3840
cddlc
@@ -251,13 +253,15 @@ pubk
251253
pubkey
252254
publickey
253255
pubspec
256+
pycardano
254257
pwrite
255258
pycodestyle
256259
pydantic
257260
pydot
258261
pyenv
259262
Pyflakes
260263
pypackages
264+
pyproject
261265
pytest
262266
pytype
263267
qpsg

.github/workflows/semantic_pull_request.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,4 @@ jobs:
3232
docs
3333
general
3434
deps
35+
catalyst-python

catalyst-python/.python-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.13

catalyst-python/Earthfile

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
VERSION 0.8
2+
3+
IMPORT github.com/input-output-hk/catalyst-ci/earthly/python:v3.6.6 AS python-ci
4+
5+
builder:
6+
FROM python-ci+python-base
7+
8+
COPY pyproject.toml uv.lock .
9+
COPY src .
10+
COPY tests .
11+
12+
RUN uv update
13+
14+
build:
15+
FROM +builder
16+
17+
RUN uv run pytest

catalyst-python/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Catalyst Python library
2+
3+
A collection of reusable python code of Catalyst specific entities
4+
like RBAC, Catalyst Signed Documents, Catalyst API etc.

catalyst-python/pyproject.toml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# cspell: words bitcoinlib
2+
3+
[project]
4+
name = "catalyst-python"
5+
version = "0.1.0"
6+
description = "Catalyst python library"
7+
readme = "README.md"
8+
authors = [
9+
{ name = "Mr-Leshiy", email = "[email protected]" }
10+
]
11+
requires-python = ">=3.13"
12+
dependencies = [
13+
"cryptography>=46.0.3",
14+
"pycardano>=0.18.0",
15+
"python-bitcoinlib>=0.12.2",
16+
"requests>=2.32.5",
17+
]
18+
19+
[build-system]
20+
requires = ["uv_build>=0.8.5,<0.9.0"]
21+
build-backend = "uv_build"
22+
23+
[dependency-groups]
24+
dev = [
25+
"pytest>=9.0.1",
26+
]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Catalyst Python Package."""
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# cspell: words convertbits, segwit
2+
# ruff: noqa: D100, D103, I001, FBT001, PLR1714, SIM108
3+
4+
from hashlib import blake2b
5+
from bitcoin.segwit_addr import bech32_encode, convertbits
6+
7+
8+
# according to [CIP-19](https://cips.cardano.org/cips/cip19/).
9+
def stake_public_key_to_address(key: str, is_stake: bool, network_type: str) -> str:
10+
def stake_header(is_stake: bool, network_type: str) -> str:
11+
if is_stake:
12+
# stake key hash
13+
typeid = int("1110", 2)
14+
else:
15+
# script hash
16+
typeid = int("1111", 2)
17+
if network_type == "mainnet":
18+
network_id = 1
19+
elif network_type == "testnet" or network_type == "preprod" or network_type == "preview":
20+
network_id = 0
21+
else:
22+
raise f"Unknown network type: {network_type}"
23+
24+
typeid = typeid << 4
25+
return typeid | network_id
26+
27+
key_bytes = bytes.fromhex(key)
28+
key_hash = blake2b(key_bytes, digest_size=28)
29+
header = stake_header(is_stake=is_stake, network_type=network_type)
30+
key_hash_with_header = header.to_bytes(1, "big") + key_hash.digest()
31+
32+
if network_type == "mainnet":
33+
hrp = "stake"
34+
elif network_type == "testnet" or network_type == "preprod" or network_type == "preview":
35+
hrp = "stake_test"
36+
else:
37+
raise f"Unknown network type: {network_type}"
38+
39+
return bech32_encode(hrp, convertbits(key_hash_with_header, 8, 5))
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# ruff: noqa: D100, D101, D102, D107, I001
2+
3+
from catalyst_python.ed25519 import Ed25519Keys
4+
from catalyst_python.rbac_token import (
5+
generate_rbac_auth_token,
6+
generate_cat_id,
7+
)
8+
9+
10+
class AdminKey:
11+
def __init__(self, key: Ed25519Keys, network: str, subnet: str | None = None) -> None:
12+
self.key = key
13+
self.network = network
14+
self.subnet = subnet
15+
16+
def cat_id(self) -> str:
17+
return generate_cat_id(
18+
scheme="admin.catalyst",
19+
network=self.network,
20+
subnet=self.subnet,
21+
role_0_key=self.key,
22+
)
23+
24+
def auth_token(self) -> str:
25+
return generate_rbac_auth_token(
26+
scheme="admin.catalyst",
27+
network=self.network,
28+
subnet=self.subnet,
29+
role_0_key=self.key,
30+
signing_key=self.key,
31+
)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# ruff: noqa: D100, D101, D103, D105, RET505, UP017, PLR0913
2+
3+
from datetime import datetime, timezone
4+
from enum import IntEnum
5+
6+
from catalyst_python.ed25519 import Ed25519Keys
7+
from catalyst_python.utils import base64_url
8+
9+
10+
class RoleID(IntEnum):
11+
ROLE_0 = 0
12+
PROPOSER = 3
13+
14+
def __str__(self) -> str:
15+
return f"{int(self)}"
16+
17+
18+
# Default is set to URI format
19+
# Optional field = subnet, role id, rotation, username, nonce
20+
def generate_cat_id(
21+
network: str,
22+
role_0_key: Ed25519Keys,
23+
scheme: str | None = None,
24+
subnet: str | None = None,
25+
role_id: RoleID | None = None,
26+
rotation: str | None = None,
27+
username: str | None = None,
28+
nonce: str | None = None,
29+
) -> str:
30+
role0_pk_b64 = base64_url(bytes.fromhex(role_0_key.pk_hex()))
31+
32+
# If nonce is set to none, use current timestamp
33+
# If set to empty string, use empty string (no nonce)
34+
if nonce is None:
35+
nonce = f"{int(datetime.now(timezone.utc).timestamp())}"
36+
37+
# Authority part
38+
authority = ""
39+
if username:
40+
authority += f"{username}"
41+
if nonce:
42+
authority += f":{nonce}"
43+
authority += "@"
44+
45+
if subnet:
46+
authority += f"{subnet}.{network}"
47+
else:
48+
authority += network
49+
50+
# Path
51+
path = f"{role0_pk_b64}"
52+
if role_id is not None:
53+
path += f"/{role_id}"
54+
if rotation is not None:
55+
path += f"/{rotation}"
56+
57+
if scheme:
58+
return f"{scheme}://{authority}/{path}"
59+
else:
60+
return f"{authority}/{path}"

0 commit comments

Comments
 (0)