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

[WIP] Sk encrypt decrypt #28

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/sdk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,4 @@ jobs:
python -m venv .env
source .env/bin/activate
maturin develop --release --features openssl/vendored
python test.py
python python/aleo/test.py
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.

2 changes: 2 additions & 0 deletions sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ 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/aleo/test.py
1 change: 1 addition & 0 deletions sdk/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ 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"

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

from .aleo import *

__doc__ = aleo.__doc__

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.from_literal(Literal.from_field(key)))
nonce_kv = (Identifier.from_string("nonce"),
Plaintext.from_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

47 changes: 46 additions & 1 deletion sdk/aleo.pyi → sdk/python/aleo/aleo.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 All @@ -54,6 +61,15 @@ class ComputeKey:
def sk_prf(self) -> Scalar: ...


# class Encryptor:
# @staticmethod
# def encrypt_private_key_with_secret(private_key: PrivateKey,
# secret: str) -> Ciphertext: ...
# @staticmethod
# def decrypt_private_key_with_secret(ciphertext: Ciphertext,
# secret: str) -> PrivateKey: ...


class EpochChallenge:
@staticmethod
def from_json(json: str) -> EpochChallenge: ...
Expand Down Expand Up @@ -87,12 +103,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 +212,39 @@ class Locator:
def resource(self) -> Identifier: ...


class Network:
@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 from_literal(literal: Literal) -> Plaintext: ...
@staticmethod
def new_struct(kv: List[Tuple[Identifier, 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]: ...


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
Empty file added sdk/python/aleo/py.typed
Empty file.
17 changes: 15 additions & 2 deletions sdk/test.py → sdk/python/aleo/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 = Encryptor.decrypt_private_key_with_secret(ciphertext, "qwe123")

self.assertEqual(private_key, recovered)

encrypted = Encryptor.encrypt_private_key_with_secret(private_key, "asd123")
other_recovered = 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()
}

/// Reads in an account private key from a base58 string.
#[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
Loading