API Reference¶
Generated reference for PyChor's public objects. Everything listed here is
re-exported from the pychor package, so pychor.Party and
pychor.choreography.Party are the same class.
For the ideas behind these objects, read Concepts; for choosing and configuring a backend, read Backends.
Core API¶
The objects a choreography is written with: parties to own values, located values to carry them, and the three functions that run ordinary Python code at a party.
Party
dataclass
¶
A named participant in a choreography.
Party objects are used as ownership labels for located values. The
expression value @ party locates an ordinary Python value at that party
when a choreography backend is active.
Parties are frozen dataclasses, so they are hashable and compare by name,
and the same Party object can be shared freely across a program. The
order of the party list passed to a backend is significant for the TCP
backends: it fixes the connection order and, for ForkingTCPBackend, the
port assignment.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Human-readable party name used in representations and sequence diagrams. |
required |
Example
constant
¶
Locate a value or function at this party.
Ordinary values become LocatedVal objects owned by this party.
Callables become wrappers that execute the callable locally through the
active backend, so (lambda x: x + 1) @ alice is a function that runs
at alice.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
v
|
Any
|
The value to locate, or a callable to run at this party. |
required |
Returns:
| Type | Description |
|---|---|
Any
|
A |
Raises:
| Type | Description |
|---|---|
AssertionError
|
If no backend is currently active. |
view
¶
Return the values this party has received in the active backend.
A party's view is the ordered list of message payloads delivered to it
by LocatedVal.send, and it is the audit trail that simulation-based
security arguments inspect: a protocol is private with respect to a
party if that party's view can be reproduced by a simulator that never
sees the honest inputs. examples/protocol_sum.py compares protocol and
simulator views statistically using this method.
Each backend context starts with empty views, so a program that samples views over many runs must re-enter the backend for each run.
Returns:
| Type | Description |
|---|---|
list
|
The list of values received by this party so far. Under a TCP backend, only the calling process's own party has a populated view. |
Raises:
| Type | Description |
|---|---|
AttributeError
|
If no backend is currently active. |
LocatedVal
dataclass
¶
A Python value together with the parties that can currently observe it.
Located values are produced by locating constants, sending values between
parties, and running local computations. A located value is the unit of
ownership in a choreography: parties records who knows the value, and the
backend refuses to compute with a value at a party that does not own it.
Ownership grows when a value is sent — send adds the destination to
parties — and is narrowed with only.
Under SimulationBackend, val always holds the real value because a
single process plays every party. Under the TCP backends, val holds the
real value only in the processes whose party appears in parties, and is
None everywhere else; see pychor.tcp_backend and the Concepts page of the
documentation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
parties
|
Set[Party]
|
Parties that currently know the underlying value. |
required |
val
|
Any
|
The underlying Python value in the active backend. |
required |
note
|
Optional[str]
|
Optional annotation for protocol explanations or diagrams. |
None
|
Operators
+, -, *, /, % and unary - are defined on located values and
are each shorthand for a local computation: x + y is equivalent to
locally(lambda a, b: a + b, x, y), so it runs at the parties that own
both operands and produces a new located value. Reflected forms
(__radd__ and friends) are defined too, so a plain Python int,
float, str, or bytes may appear on either side; a plain value
places no ownership constraint of its own. This is what lets share
arithmetic read like ordinary Python — see
examples/protocol_beaver.py, where a Beaver multiplication is written
r_1 = d * e + d * b1 + e * a1 + c1.
Comparisons use dataclass equality, which compares the underlying values and the owner sets; there is no located boolean type.
send
¶
Send this located value from src to dest.
The active backend performs the communication and adds dest to this
value's owner set, so the same LocatedVal object is afterwards
readable at both parties. Sending mutates this value in place rather
than returning a new one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
src
|
Party
|
The sending party, which must already own this value. |
required |
dest
|
Party
|
The receiving party. |
required |
note
|
Optional[str]
|
Optional label for this message. |
None
|
Raises:
| Type | Description |
|---|---|
AssertionError
|
If |
unlist
¶
Un-structure a located list into a list of located values.
A local computation that returns a list produces a single located list.
This splits it into length located values, each owned by the same
parties, so the individual elements can be sent to different places.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
length
|
int
|
The expected length of the underlying list. |
required |
Returns:
| Type | Description |
|---|---|
list
|
A Python list of |
untup
¶
Un-structure a located tuple into a tuple of located values.
This is the usual way to return several results from one local
computation, as in s1, s2 = share(x).untup(2).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
length
|
int
|
The expected length of the underlying tuple. |
required |
Returns:
| Type | Description |
|---|---|
tuple
|
A Python tuple of |
undict
¶
Un-structure a located dict into a dict of located values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
keys
|
Iterable
|
The expected keys of the underlying dict. |
required |
Returns:
| Type | Description |
|---|---|
dict
|
A Python dict mapping each key to a located value. |
only
¶
Return this value restricted to a subset of its current owners.
Narrowing ownership is how a choreography says "treat this as bob's
value from here on". It matters because a local computation runs at the
intersection of its arguments' owners: after x.send(src=alice,
dest=bob), x is owned by both parties, so a computation over x
would run at both. Passing x.only(bob) instead pins the computation
to bob.
This does not communicate anything and does not modify this value; it
returns a new LocatedVal sharing the same underlying value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
parties
|
Union[Party, Iterable[Party]]
|
A single |
required |
Returns:
| Type | Description |
|---|---|
LocatedVal
|
A new located value owned by exactly |
Raises:
| Type | Description |
|---|---|
AssertionError
|
If any requested party does not already own this value, or if an empty collection is given. |
TypeError
|
If |
constant
¶
Create a located value owned by party.
party.constant(v) and v @ party are the usual spellings; this function
is the explicit form, useful when the party is computed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
party
|
Party
|
The owning party, which must belong to the active backend. |
required |
v
|
Any
|
The value to locate. |
required |
Returns:
| Type | Description |
|---|---|
LocatedVal
|
A |
Raises:
| Type | Description |
|---|---|
AssertionError
|
If no backend is currently active. |
ValueError
|
If |
locally
¶
Run f as a local computation in the active backend.
The computation runs at the parties that own every located argument, and the result is located at exactly those parties. Plain Python scalars may be mixed in freely and place no constraint on where the computation runs.
Under a TCP backend this call is a no-op in processes whose party is not
among the owners, and the returned located value carries None there.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
f
|
Callable
|
An ordinary Python function. |
required |
*args
|
Any
|
Located values, plain scalars, or tuples, lists, and dicts of those. |
()
|
**kwargs
|
Any
|
Keyword arguments, handled the same way as |
{}
|
Returns:
| Type | Description |
|---|---|
LocatedVal
|
A located value holding |
Raises:
| Type | Description |
|---|---|
AssertionError
|
If the located arguments have no owner in common. |
local_function
¶
Decorate a Python function so it becomes backend-aware.
Outside a backend context, the decorated function behaves like the original
function, which keeps helpers testable on their own. Inside a backend
context, calling it runs locally and returns a located value, so the same
definition serves both purposes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
func
|
Callable
|
The function to make backend-aware. |
required |
Returns:
| Type | Description |
|---|---|
Callable
|
A wrapper that dispatches to |
get_val
¶
Unwrap located values and determine where a computation may run.
This is the helper backends use to implement locally. It walks an argument
structure, replaces each LocatedVal with its underlying value, and
intersects the owner sets it encounters to decide which parties can perform
the computation. Tuples, lists, and dicts are traversed recursively, so a
local function may take nested structures of located values.
Plain int, float, str, and bytes values pass through with no owner
set, meaning they place no constraint on where the computation runs; this is
what allows x + 1 for a located x.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lv
|
Any
|
A located value, a plain scalar, or a tuple, list, or dict of those. |
required |
Returns:
| Type | Description |
|---|---|
tuple
|
A |
Raises:
| Type | Description |
|---|---|
AssertionError
|
If the located values have no owner in common. |
Exception
|
If |
Backends¶
A backend decides what locating, computing, and sending mean, and is entered as
a context manager. ChoreographyBackend is the interface to implement for a new
one — see
Writing a custom backend.
ChoreographyBackend
¶
Base context manager for choreography backends.
Entering a backend context makes it the active backend for Party,
LocatedVal, locally, and local_function operations. Subclasses provide
the actual execution and communication semantics.
A backend must implement constant, send, locally, unwrap, unlist,
untup, and undict. The methods defined here are the interface: all but
constant are stubs, and a subclass that leaves one unimplemented will
silently return None from the corresponding operation rather than raise.
SimulationBackend is the reference implementation;
pychor.tcp_backend.TCPBackend shows the same interface implemented across
processes.
Subclasses that override __enter__ or __exit__ must call
super().__enter__() / super().__exit__() so that the active-backend
global is installed and cleared.
Attributes:
| Name | Type | Description |
|---|---|---|
parties |
The backend's parties, as a tuple, in the order given. Order is significant for the TCP backends. |
|
party_set |
The same parties as a set, for membership tests. |
|
views |
Maps each party to the ordered list of values it has received.
This is the protocol transcript from each party's point of view, and
it is what a simulation-based security argument compares; see
|
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
parties
|
Iterable[Party]
|
An iterable of |
required |
send
¶
Send a located value from one party to another.
Implementations must add party_to to lv.parties and record the
received value in self.views[party_to].
locally
¶
Compute a function locally using located arguments.
Implementations must evaluate f at the intersection of the owner sets
of the located arguments, and return a located value owned by exactly
that intersection.
unwrap
¶
Return the raw value when all requested parties can observe it.
Implementations return None rather than raising when the value is not
available.
SimulationBackend
¶
Bases: ChoreographyBackend
Run a choreography in a single local Python process.
SimulationBackend is useful for tutorials, tests, and protocol sketches.
One process plays every party, so every located value is genuinely present
and a choreography always runs to completion quickly, with no network setup.
The trade-off is that this backend cannot detect a choreography that reads a
value it does not own: nothing is actually distributed, so an illegal read
still finds a real value in memory. Run a protocol under
pychor.tcp_backend.ForkingTCPBackend to check that it is genuinely
executable one-party-per-process.
In exchange, this backend can observe the whole protocol at once. It records
each send into views and accumulates a Mermaid sequence diagram in
uml, which print_sequence_diagram prints and which can be pasted
directly into Mermaid-aware Markdown.
Attributes:
| Name | Type | Description |
|---|---|---|
uml |
The accumulated Mermaid sequence diagram source, beginning with the
line |
Example
send
¶
Record a local send and add the destination to the value owners.
The value is appended to the destination party's view and an edge is added to the sequence diagram. Values whose string form exceeds ten characters are truncated with an ellipsis in the diagram only; the value delivered to the destination is never truncated.
locally
¶
Evaluate f on the raw values of co-located arguments.
The result is owned by the intersection of the owner sets of the located arguments. Plain Python scalars among the arguments contribute no ownership constraint.
Raises:
| Type | Description |
|---|---|
AssertionError
|
If the located arguments have no owner in common. |
unwrap
¶
Return a raw value when all requested parties own it.
Returns:
| Type | Description |
|---|---|
Any
|
The underlying value, or |
emit_to_sequence
¶
Append a line to the backend's Mermaid sequence diagram.
Use this to add Mermaid directives of your own — notes, activations, or
participant declarations — alongside the edges send records.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
string
|
str
|
One line of Mermaid |
required |
print_sequence_diagram
¶
TCPBackend
¶
Bases: ChoreographyBackend
Run one party of a choreography over TCP.
This backend does not fork. Each party should run the same choreography
program in a separate Python process, passing its local party as me and a
shared address map for all parties.
Every process must agree on the parties list and its order, because the
order determines which side of each connection dials and which listens.
Entering the context establishes a full mesh between all parties and blocks
until it is complete, so the processes may be started in any order as long
as they all start within connect_timeout.
Only this process's own party's values are materialized: constant and
locally produce None for values this party does not own, and send
transfers the real value to the destination process. See the module
documentation for what that means for control flow.
Attributes:
| Name | Type | Description |
|---|---|---|
party |
The party this process is playing, as passed in |
|
party_index |
That party's position in |
|
addresses |
The validated address map. |
|
streams |
One |
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
parties
|
Iterable[Party]
|
All parties in the choreography, in an order every process agrees on. Names must be unique. |
required |
me
|
Party
|
The party this process plays. Must appear in |
required |
addresses
|
Mapping[Party, tuple[str, int]]
|
Maps every party to the |
required |
connect_timeout
|
float
|
Seconds to spend establishing the mesh before raising
|
10.0
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
ValueError
|
If |
Example
Run this once per party, changing only me:
__enter__
¶
Establish the party mesh, then become the active backend.
Raises:
| Type | Description |
|---|---|
TimeoutError
|
If the mesh is not complete within |
__exit__
¶
Close every connection, then stand down as the active backend.
constant
¶
Create a located value, materialized only in its owning process.
Every process creates a located value with the same owner set, but only
the process playing party stores v; the others store None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
party
|
Party
|
The owning party. |
required |
v
|
Any
|
The value, which is ignored in processes other than |
required |
Returns:
| Type | Description |
|---|---|
LocatedVal
|
A located value owned by |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
send
¶
Send a located value from one party process to another.
This is a synchronization point between exactly two processes: the
sender writes the value to its stream and the receiver blocks until it
arrives. Every process — including uninvolved ones — adds party_to to
the owner set, so all processes keep identical ownership information.
A send from a party to itself moves no data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
party_from
|
Party
|
The sending party, which must already own |
required |
party_to
|
Party
|
The receiving party. |
required |
lv
|
LocatedVal
|
The located value to send. The receiving process fills in its
|
required |
note
|
Optional[str]
|
Accepted for interface compatibility with |
None
|
locally
¶
Evaluate f in every process that can observe all located inputs.
The owner set of the result is computed identically in every process,
but f itself runs only where the local party is one of those owners.
Elsewhere the call is skipped and the result holds None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
f
|
Callable
|
An ordinary Python function. |
required |
*args
|
Any
|
Located values, plain scalars, or containers of those. |
()
|
**kwargs
|
Any
|
Keyword arguments, handled the same way. |
{}
|
Returns:
| Type | Description |
|---|---|
LocatedVal
|
A located value owned by the parties that could see every located input. |
Raises:
| Type | Description |
|---|---|
AssertionError
|
If the located arguments have no owner in common. |
unwrap
¶
Return this process's raw value when it belongs to parties.
Returns:
| Type | Description |
|---|---|
Any
|
The underlying value, or |
unlist
¶
Split a located list, using None placeholders where unowned.
Non-owning processes have no list to split, so they produce length
located values holding None — keeping the structure, and therefore the
control flow, identical in every process.
ForkingTCPBackend
¶
Run all parties as forked local processes connected by TCP.
This is the testing counterpart to TCPBackend: it gives a choreography
genuine one-party-per-process execution without any deployment, so it
catches protocols that only work because SimulationBackend happens to hold
every party's data in one place.
Entering the context forks one child per party after the first — the parent
plays parties[0] — and each process then enters a TCPBackend for its own
party. Parties are assigned deterministic localhost ports by their order in
parties: the first party uses base_port, the second uses
base_port + 1, and so on.
Two details are worth knowing. First, this class is a context-manager
wrapper rather than a ChoreographyBackend subclass, and __enter__
returns the inner TCPBackend; so with ForkingTCPBackend(...) as b binds
b to that backend, whose views contain only the local party's messages.
Second, the code inside the with block runs once per party, in different
processes — so its output is interleaved, and side effects such as writing
files happen once per party.
On a clean exit each child flushes its output and exits immediately, while
the parent waits for all children and raises RuntimeError if any of them
failed. A child's traceback appears on stderr as usual.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
parties
|
Iterable[Party]
|
All parties in the choreography. Order fixes the port assignment and decides which party the parent process plays. |
required |
host
|
str
|
Address every party binds and connects to. |
'127.0.0.1'
|
base_port
|
int
|
Port of the first party; party i uses |
10000
|
connect_timeout
|
float
|
Seconds to spend establishing the mesh before raising
|
10.0
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
On entry, if the platform has no |
Example
__enter__
¶
Fork one process per party and enter a TCPBackend in each.
Returns:
| Type | Description |
|---|---|
TCPBackend
|
The inner |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the platform does not provide |
TimeoutError
|
If the party mesh cannot be established. |
__exit__
¶
Tear down this process's backend and reap the forked children.
In a child, this never returns on a clean exit: the child flushes its streams and exits so that it does not continue running the rest of the enclosing program. In the parent, it waits for every child and surfaces their failures.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
In the parent, if a child exited non-zero and no exception is already propagating. |
Transport¶
The wire format used by the TCP backends. Choreographies do not use this directly; it is documented for anyone implementing a backend of their own or auditing what travels over the network.
ObjectStream
¶
A bidirectional stream of pickled Python objects over one socket.
Each object is framed as a 4-byte big-endian unsigned length followed by
that many bytes of pickle data. Writes are flushed immediately, so a
put_obj on one side unblocks a get_obj on the other without further
prompting.
The stream is not internally synchronized: a single stream should be used by one thread at a time. The TCP backends satisfy this by giving each pair of parties its own stream and doing all sends from the choreography's single thread of control.
Instances can be used as context managers, and close themselves when garbage collected.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sock
|
socket
|
A connected stream socket. The stream takes ownership of it and
closes it in |
required |
get_obj
¶
Read the next object from the stream, blocking until it arrives.
Returns:
| Type | Description |
|---|---|
Any
|
The unpickled object, or |
put_obj
¶
Write one object to the stream and flush it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
Any
|
Any picklable object. Objects of a class defined in the choreography are fine as long as both processes can import that class, which is automatic when every party runs the same program. |
required |
Raises:
| Type | Description |
|---|---|
PicklingError
|
If |
close
¶
Close the reader, the writer, and the underlying socket.
Closing twice is harmless.