nucypher/docs/source/application_development/getting_started.rst

394 lines
13 KiB
ReStructuredText

Getting Started with Characters
===============================
* `A Note about Side Channels`_
* `Alice: Grant Access to a Secret`_
* `Enrico: Encrypt a Secret`_
* `Bob: Decrypt a Secret`_
A Note about Side Channels
--------------------------
The NuCypher network does not store or handle an application's data; instead - it manages access *to* application data.
Management of encrypted secrets and public keys tends to be highly domain-specific - the surrounding architecture
will vary greatly depending on the throughput, sensitivity, and sharing cadence of application secrets.
In all cases, NuCypher must be integrated with a storage and transport layer in order to function properly.
Along with the transport of ciphertexts, a nucypher application also needs to include channels for Alice and Bob
to discover each other's public keys, and provide policy encrypting information to Bob and Enrico.
The Application Side Channel
-----------------------------
* Secrets:
* Message Kits - Encrypted Messages, or "Ciphertexts"
* Identities:
* Alice Verifying Key - Public key used for verifying Alice
* Bob Encrypting Key - Public key used to encrypt for Bob
* Bob Verifying Key - Public key used to verify Bob
* Policies:
* Policy Encrypting Key - Public key used to encrypt messages for a Policy.
* Labels - A label for specifying a Policy's target, like a filepath
Choosing an Ethereum Provider
-----------------------------
Operation of a decentralized NuCypher character [\ ``Alice``\ , ``Bob``\ , ``Ursula``\ ] requires
a connection to an Ethereum node and wallet to interact with :doc:`smart contracts </architecture/contracts>`.
For general background information about choosing a node technology and node operation,
see https://web3py.readthedocs.io/en/stable/node.html.
Ursula: Untrusted Re-Encryption Proxies
----------------------------------------
When initializing an ``Alice``\ , ``Bob``\ , or ``Ursula``\ , an initial "Stranger-\ ``Ursula``\ " is needed to perform
the role of a ``Teacher``\ , or "seednode":
.. code-block:: python
from nucypher.characters.lawful import Ursula
seed_uri = "<SEEDNODE URI>:9151"
seed_uri2 = "<OTHER SEEDNODE URI>:9151"
ursula = Ursula.from_seed_and_stake_info(seed_uri=seed_uri)
another_ursula = Ursula.from_seed_and_stake_info(seed_uri=seed_uri2)
.. note::
While any nucypher worker node can be used to seed your peers, NuCypher maintains
workers that can be used as seed nodes:
- mainnet: ``https://mainnet.nucypher.network:9151``
- lynx: ``https://lynx.nucypher.network:9151``
- ibex: ``https://ibex.nucypher.network:9151``
.. code::
seed_uri = 'https://lynx.nucypher.network:9151'
ursula = Ursula.from_seed_and_stake_info(seed_uri=seed_uri)
Stranger ``Ursula``\ s can be created by invoking the ``from_seed_and_stake_info`` method, then a ``list`` of ``known_nodes``
can be passed into any ``Character``\ 's init. The ``known_nodes`` will inform your character of all of the nodes
they know about network-wide, then kick-off the automated node-discovery loop:
.. code-block:: python
from nucypher.characters.lawful import Alice
alice = Alice(known_nodes=[ursula, another_ursula], ...)
For information on how to run a staking Ursula node via CLI,
see :doc:`Running a Worker </staking/running_a_worker>`.
Alice: Grant Access to a Secret
-------------------------------
Setup Alice Keys
^^^^^^^^^^^^^^^^
Alice uses an ethereum wallet to create publish access control policies to the ethereum blockchain,
and a set of related keys called a *"nucypher keyring"*.
First, instantiate a ``Signer`` to use for signing transactions. This is an API for Alice's ethereum
wallet, which can be an keystore file, trezor, ethereum node, or clef. The signer type and address
are specified using a ``signer_uri``:
- Trezor Hardware Wallet: ``'trezor'``
- Keystore directory or keyfile: ``'keystore://<ABSOLUTE PATH TO KEYSTORE>'``
- Local geth node: ``'web3://<ABSOLUTE PATH TO IPC ENDPOINT>'``
- Clef external signer: ``'clef'``
Here are some examples of usage:
.. code-block:: python
from nucypher.blockchain.eth.signers import Signer
wallet = Signer.from_signer_uri('<YOUR SIGNER URI>')
# Trezor Wallet
trezor = Signer.from_signer_uri('trezor')
# Local Geth Wallet
geth_signer = Signer.from_signer_uri('web3:///home/user/.ethereum/geth.ipc')
# Keyfile Wallet
software_wallet = Signer.from_signer_uri('keystore:///home/user/.ethereum/keystore/<KEY FILENAME>')
If you are using a software wallet, be sure to unlock it:
.. code-block:: python
# Unlocking a software wallet
>>> software_wallet.unlock_account(account='0x287A817426DD1AE78ea23e9918e2273b6733a43D', password=<ETH_PASSWORD>)
Next, create a NuCypher Keyring. This step will generate a new set of related private keys used for nucypher cryptography operations,
which can be integrated into your application's user on-boarding or setup logic. These keys will be stored on the disk,
encrypted-at-rest using the supplied password. Use the same account as the signer; Keyrings are labeled and associated
with ethereum accounts, so be sure to specify an account you control with a ``Signer``.
.. code-block:: python
from nucypher.config.keyring import NucypherKeyring
keyring = NucypherKeyring.generate(
checksum_address='0x287A817426DD1AE78ea23e9918e2273b6733a43D',
password=NEW_PASSWORD # used to encrypt nucypher private keys
)
# The keyring identifier
>>> keyring.checksum_address
0x287A817426DD1AE78ea23e9918e2273b6733a43D
# Be sure to use an address controlled by your signer!
>>> keyring.checksum_address in signer.accounts
True
# The root directory containing the private keys
>>> keyring.keyring_root
'/home/user/.local/share/nucypher/keyring'
After generating a keyring, any future usage can decrypt the keys from the disk:
.. code-block:: python
from nucypher.config.keyring import NucypherKeyring
# Restore an existing Alice keyring
keyring = NucypherKeyring(account='0x287A817426DD1AE78ea23e9918e2273b6733a43D')
# Unlock Alice's keyring
keyring.unlock(password=NUCYPHER_PASSWORD)
.. code-block:: python
from nucypher.characters.lawful import Alice, Ursula
# Instantiate a default peer (optional)
ursula = Ursula.from_seed_and_stake_info(seed_uri='https://lynx.nucypher.network:9151')
# Instantiate Alice
alice = Alice(
keyring=keyring, # NuCypher Keyring
known_nodes=[ursula], # Peers (Optional)
signer=signer, # Alice Wallet
provider_uri=<RPC ENDPOINT>, # Ethereum RPC endpoint
domain='lynx' # NuCypher network (mainnet, lynx, ibex)
)
# Alice is identified by her ethereum address
alice.checksum_address
'0x287A817426DD1AE78ea23e9918e2273b6733a43D'
# Start node discovery
alice.start_learning_loop(now=True)
Alice needs to know Bob's public keys in order to grant him access. Alice's are expected to acquiring Bob's public
keys through the application side channel. Umbral public keys used in NuCypher's proxy re-encryption can be restored
from hex for API usage:
.. code-block:: python
from umbral.keys import UmbralPublicKey
verifying_key = UmbralPublicKey.from_hex(verifying_key_as_hex),
encrypting_key = UmbralPublicKey.from_hex(encryption_key_as_hex)
Grant
^^^^^
Alice can grant access to Bob using his public keys:
.. code-block:: python
from umbral.keys import UmbralPublicKey
from nucypher.characters.lawful import Bob
from datetime import timedelta
from web3 import Web3
import maya
# Deserialize bob's public keys from the application side-channel
verifying_key = UmbralPublicKey.from_hex(verifying_key_as_hex),
encrypting_key = UmbralPublicKey.from_hex(encryption_key_as_hex)
# Make a representation of Bob
bob = Bob.from_public_keys(verifying_key=bob_verifying_key, encrypting_key=bob_encrypting_key)
policy = alice.grant(
bob,
label=b'my-secret-stuff', # Send to Bob via side channel
m=2, # Threshold shares for access
n=3, # Total nodes with shares
rate=Web3.toWei(50, 'gwei'), # 50 Gwei is the minimum rate (per node per period)
expiration= maya.now() + timedelta(days=5) # Five days from now
)
# The policy's public key
policy_encrypting_key = policy.public_key
Putting it all together, here's an example starter script for granting access using a
software wallet and an existing keyring:
.. code-block:: python
from nucypher.blockchain.eth.signers import Signer
from nucypher.config.keyring import NucypherKeyring
from nucypher.characters.lawful import Alice, Bob
from umbral.keys import UmbralPublicKey
from datetime import timedelta
from web3 import Web3
import maya
# Restore Existing NuCypher Keyring
keyring = NucypherKeyring(account='0x287A817426DD1AE78ea23e9918e2273b6733a43D')
keyring.unlock('KEYRING PASSWORD')
# Ethereum Software Wallet
wallet = Signer.from_signer_uri("keystore:///home/user/.ethereum/goerli/keystore/UTC--2021...0278ad02...')
wallet.unlock_account('0x287A817426DD1AE78ea23e9918e2273b6733a43D', 'SOFTWARE WALLET PASSWORD')
# Make Alice
alice = Alice(
domain='lynx', # testnet
provider_uri='GOERLI RPC ENDPOINT',
keyring=keyring,
signer=wallet,
)
# From Public Key Side Channel
verifying_key = UmbralPublicKey.from_hex('0278ad02da8083aea357a8ed675dcc0b6e9c78557c506ea10b102b4b282c006b12')
encrypting_key = UmbralPublicKey.from_hex('03ec6b4e1f2b7d06ac544dde86730f9a4047e80a0a4d3c1566e88afe4bb449bdd9')
# Make Stranger-Bob
bob = Bob.from_public_keys(verifying_key=verifying_key, encrypting_key=encrypting_key)
# Grant Bob Access
policy = alice.grant(
bob,
label=b'my-secret-stuff', # Send to Bob via side channel
m=2, # Threshold shares for access
n=3, # Total nodes with shares
rate=Web3.toWei(50, 'gwei'), # 50 Gwei is the minimum rate (per node per period)
expiration= maya.now() + timedelta(days=5) # Five days from now
)
Enrico: Encrypt a Secret
------------------------
First, a ``policy_encrypting_key`` must be retrieved from the application side channel, then
to encrypt a secret using Enrico:
Encrypt
^^^^^^^
.. code-block:: python
from nucypher.characters.lawful import Enrico
enrico = Enrico(policy_encrypting_key=policy_encrypting_key)
ciphertext, signature = enrico.encrypt_message(plaintext=b'Peace at dawn.')
The ciphertext can then be sent to Bob via the application side channel.
Note that Alice can get the public key even before creating the policy.
From this moment on, any Data Source (Enrico) that knows the public key
can encrypt data originally intended for Alice, but can be shared with
any Bob that Alice grants access.
``policy_pubkey = alice.get_policy_encrypting_key_from_label(label)``
Bob: Decrypt a Secret
---------------------
For Bob to retrieve a secret, the ciphertext, label, policy encrypting key, and Alice's verifying key must all
be fetched from the application side channel. Then, Bob constructs his perspective of the policy's network actors:
Setup Bob
^^^^^^^^^
Bob's setup is similar to Alice's above.
.. code-block:: python
from nucypher.characters.lawful import Alice, Bob, Enrico, Ursula
# Application Side-Channel
# --------------------------
# label = <Side Channel>
# ciphertext = <Side Channel>
# policy_encrypting_key = <Side Channel>
# alice_verifying_key = <Side Channel>
# Everyone!
ursula = Ursula.from_seed_and_stake_info(seed_uri='https://lynx.nucypher.network:9151')
alice = Alice.from_public_keys(verifying_key=alice_verifying_key)
enrico = Enrico(policy_encrypting_key=policy_encrypting_key)
# Restore Existing Bob keyring
keyring = NucypherKeyring(account='0xC080708026a3A280894365Efd51Bb64521c45147')
# Unlock keyring and make Bob
keyring.unlock(PASSWORD)
bob = Bob(
keyring=keyring,
known_nodes=[ursula],
domain='lynx'
)
Join a Policy
^^^^^^^^^^^^^
Next, Bob needs to join the policy using the policy label and alice's public key. Bob needs
to retrieve both of these from the application side channel first.
.. code-block:: python
# Make alice from known public key (from application side channel)
alice = Alice.from_public_keys(verifying_key=alice_verifying_key)
# Use alice's public key and the label to join the access policy
alice_public_key = alice.public_keys(SigningPower)
bob.join_policy(
label=label,
alice_verifying_key=alice_public_key,
)
Retrieve and Decrypt
^^^^^^^^^^^^^^^^^^^^
Then Bob can retrieve and decrypt the ciphertext:
.. code-block:: python
cleartexts = bob.retrieve(
label=label,
message_kit=ciphertext,
data_source=enrico,
alice_verifying_key=alice_public_key
)