Skip to content

Commit

Permalink
[Feature] Encryptor class to encrypt/decrypt SecretKey
Browse files Browse the repository at this point in the history
  • Loading branch information
iamalwaysuncomfortable authored Jan 3, 2024
2 parents cfcb250 + 17a8cfc commit 70ab28e
Show file tree
Hide file tree
Showing 20 changed files with 466 additions and 14 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/sdk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,7 @@ jobs:
sphinx-build -b doctest docs/source/ _readthedocs/doctest
- name: Test
run: |
python test.py
python -m venv .env
source .env/bin/activate
maturin develop --release --features openssl/vendored
python python/test.py
2 changes: 2 additions & 0 deletions sdk/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.env/
target/
__pycache__/
python/aleo/_aleolib.abi3.so
_readthedocs/
2 changes: 2 additions & 0 deletions sdk/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ edition = "2021"
license = "GPL-3.0-or-later"

[lib]
name = "_aleolib"
crate-type = ["cdylib"]

[dependencies]
anyhow = "1"
indexmap = "2.1.0"
once_cell = "1.18.0"
openssl = "0.10"
pyo3 = { version = "0.20.0", features = ["extension-module", "abi3-py37", "anyhow"] }
rand = { version = "^0.8" }
Expand Down
2 changes: 1 addition & 1 deletion sdk/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ python3 -m venv .env
source .env/bin/activate
pip install maturin
maturin develop
python test.py
python python/test.py
26 changes: 20 additions & 6 deletions sdk/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
[project]
name = "aleo"
description = "A Python SDK for zero-knowledge cryptography based on Aleo"
version = "0.2.0"
readme = "Readme.md"
license = {file = "LICENSE.md"}
authors = [
{name = "Konstantin Pandl"},
{name = "Mike Turner"},
{name = "Roman Proskuryakov"},
]
classifiers = [ # Optional
"Programming Language :: Python :: 3",
]

[project.urls]
"Homepage" = "https://github.com/AleoHQ/python-sdk"

[build-system]
requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"

[tool.maturin]
name = "aleo"
version = "0.2.0"
description = "A Python SDK for zero-knowledge cryptography based on Aleo"
repository = "https://github.com/AleoHQ/python-sdk/tree/master/sdk"
license = "GPL-3.0-or-later"
authors = ["Konstantin Pandl", "Mike Turner", "Roman Proskuryakov"]
python-source = "python"
module-name = "aleo._aleolib"

[tool.pyright]
reportMissingModuleSource = false # don't report missing aleo.so for CI purposes
Expand Down
6 changes: 6 additions & 0 deletions sdk/python/aleo/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from __future__ import annotations

from ._aleolib import *
from .encryptor import *

__doc__ = _aleolib.__doc__
47 changes: 46 additions & 1 deletion sdk/aleo.pyi → sdk/python/aleo/_aleolib.pyi
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from __future__ import annotations
from typing import List, Optional, Tuple
from typing import List, Mapping, Optional, Tuple


class Account:
Expand Down Expand Up @@ -32,6 +32,13 @@ class Boolean:
def __new__(cls, b: bool) -> Boolean: ...


class Ciphertext:
@staticmethod
def from_string(s: str) -> Ciphertext: ...
def decrypt(self, view_key: ViewKey, nonce: Group) -> Plaintext: ...
def decrypt_symmetric(self, plaintext_view_key: Field) -> Plaintext: ...


class Credits:
def __new__(cls, value: float) -> Credits: ...
def micro(self) -> MicroCredits: ...
Expand Down Expand Up @@ -87,12 +94,18 @@ class Fee:


class Field:
@staticmethod
def random() -> Field: ...
@staticmethod
def from_string(s: str) -> Field: ...
@staticmethod
def domain_separator(domain: str) -> Field: ...
@staticmethod
def from_u128(u128: int) -> Field: ...
@staticmethod
def zero() -> Field: ...
def __mul__(self, other: Field) -> Field: ...
def __truediv__(self, other: Field) -> Field: ...


class Group:
Expand Down Expand Up @@ -190,16 +203,48 @@ class Locator:
def resource(self) -> Identifier: ...


class Network:
@staticmethod
def name() -> str: ...
@staticmethod
def version() -> int: ...
@staticmethod
def edition() -> int: ...
@staticmethod
def hash_psd2(input: List[Field]) -> Field: ...


class MicroCredits:
def __new__(cls, value: int) -> MicroCredits: ...
def __int__(self) -> int: ...


class Plaintext:
@staticmethod
def from_string(s: str) -> Plaintext: ...
@staticmethod
def new_literal(literal: Literal) -> Plaintext: ...
@staticmethod
def new_struct(kv: List[Tuple[Identifier, Plaintext]]) -> Plaintext: ...
@staticmethod
def new_array(kv: List[Plaintext]) -> Plaintext: ...
def encrypt(self, address: Address, randomizer: Scalar) -> Ciphertext: ...
def encrypt_symmetric(self, plaintext_view_key: Field) -> Ciphertext: ...
def is_literal(self) -> bool: ...
def is_struct(self) -> bool: ...
def is_array(self) -> bool: ...
def as_literal(self) -> Literal: ...
def as_struct(self) -> Mapping[Identifier, Plaintext]: ...
def as_array(self) -> List[Plaintext]: ...


class PrivateKey:
def address(self) -> Address: ...
def compute_key(self) -> ComputeKey: ...
@staticmethod
def from_string(private_key: str) -> PrivateKey: ...
@staticmethod
def from_seed(seed: Field) -> PrivateKey: ...
def seed(self) -> Field: ...
def sign(self, message: bytes) -> Signature: ...
def sk_sig(self) -> Scalar: ...
Expand Down
57 changes: 57 additions & 0 deletions sdk/python/aleo/encryptor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from __future__ import annotations

from ._aleolib import PrivateKey, Ciphertext, Field, Network, Identifier, Plaintext, Literal


class Encryptor:
@staticmethod
# Encrypt a private key into ciphertext using a secret
def encrypt_private_key_with_secret(private_key: PrivateKey, secret: str) -> Ciphertext:
seed = private_key.seed()
return Encryptor.__encrypt_field(seed, secret, "private_key")

@staticmethod
# Decrypt a private key from ciphertext using a secret
def decrypt_private_key_with_secret(ciphertext: Ciphertext, secret: str) -> PrivateKey:
seed = Encryptor.__decrypt_field(ciphertext, secret, "private_key")
return PrivateKey.from_seed(seed)

@staticmethod
# Encrypted a field element into a ciphertext representation
def __encrypt_field(field: Field, secret: str, domain: str) -> Ciphertext:
domain_f = Field.domain_separator(domain)
secret_f = Field.domain_separator(secret)

nonce = Field.random()
blinding = Network.hash_psd2([domain_f, nonce, secret_f])
key = blinding * field
key_kv = (Identifier.from_string("key"),
Plaintext.new_literal(Literal.from_field(key)))
nonce_kv = (Identifier.from_string("nonce"),
Plaintext.new_literal(Literal.from_field(nonce)))
plaintext = Plaintext.new_struct([key_kv, nonce_kv])
return plaintext.encrypt_symmetric(secret_f)

@staticmethod
def __extract_value(plaintext: Plaintext, identifier: str) -> Field:
assert plaintext.is_struct()
ident = Identifier.from_string(identifier)
dec_map = plaintext.as_struct()
val = dec_map[ident]
assert val.is_literal()
literal = val.as_literal()
assert literal.type_name() == 'field'
return Field.from_string(str(literal))

@staticmethod
# Recover a field element encrypted within ciphertext
def __decrypt_field(ciphertext: Ciphertext, secret: str, domain: str) -> Field:
domain_f = Field.domain_separator(domain)
secret_f = Field.domain_separator(secret)
decrypted = ciphertext.decrypt_symmetric(secret_f)
assert decrypted.is_struct()
recovered_key = Encryptor.__extract_value(decrypted, "key")
recovered_nonce = Encryptor.__extract_value(decrypted, "nonce")
recovered_blinding = Network.hash_psd2([domain_f, recovered_nonce, secret_f])
return recovered_key / recovered_blinding

Empty file added sdk/python/aleo/py.typed
Empty file.
17 changes: 15 additions & 2 deletions sdk/test.py → sdk/python/test.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
import aleo
import unittest

import aleo

class TestAleo(unittest.TestCase):

Expand Down Expand Up @@ -66,6 +65,20 @@ def test_account_sanity(self):
self.assertFalse(account.verify(signature, bad_message))
self.assertTrue(signature.verify(account.address(), message))

def test_encrypt_decrypt_sk(self):
private_key = aleo.PrivateKey.from_string(
"APrivateKey1zkpJYx2NZeJYB74JHpzvQGpKneTP75Dk8dao6paugZXtCz3")
ciphertext = aleo.Ciphertext.from_string(
"ciphertext1qvqt0sp0pp49gjeh50alfalt7ug3g8y7ha6cl3jkavcsnz8d0y9jwr27taxfrwd5kly8lah53qure3vxav6zxr7txattdvscv0kf3vcuqv9cmzj32znx4uwxdawcj3273zhgm8qwpxqczlctuvjvc596mgsqjxwz37f")
recovered = aleo.Encryptor.decrypt_private_key_with_secret(ciphertext, "qwe123")

self.assertEqual(private_key, recovered)

encrypted = aleo.Encryptor.encrypt_private_key_with_secret(private_key, "asd123")
other_recovered = aleo.Encryptor.decrypt_private_key_with_secret(encrypted, "asd123")

self.assertEqual(private_key, other_recovered)

def test_coinbase(self):
address = aleo.Address.from_string(
"aleo16xwtrvntrfnan84sy3qg2gdkkp5u5p7sjc882lx8n06fjx2k0yqsklw8sv")
Expand Down
3 changes: 3 additions & 0 deletions sdk/src/account/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ pub use record::{RecordCiphertext, RecordPlaintext};
mod signature;
pub use signature::Signature;

mod text;
pub use text::{Ciphertext, Plaintext};

mod view_key;
pub use view_key::ViewKey;

Expand Down
6 changes: 6 additions & 0 deletions sdk/src/account/private_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ impl PrivateKey {
ComputeKeyNative::try_from(&self.0).unwrap().into()
}

/// Returns the account private key from an account seed.
#[staticmethod]
fn from_seed(seed: Field) -> anyhow::Result<Self> {
PrivateKeyNative::try_from(seed.into()).map(Self)
}

/// Reads in an account private key from a base58 string.
#[staticmethod]
fn from_string(private_key: &str) -> anyhow::Result<Self> {
Expand Down
Loading

0 comments on commit 70ab28e

Please sign in to comment.