or
or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up
Syntax | Example | Reference | |
---|---|---|---|
# Header | Header | 基本排版 | |
- Unordered List |
|
||
1. Ordered List |
|
||
- [ ] Todo List |
|
||
> Blockquote | Blockquote |
||
**Bold font** | Bold font | ||
*Italics font* | Italics font | ||
~~Strikethrough~~ | |||
19^th^ | 19th | ||
H~2~O | H2O | ||
++Inserted text++ | Inserted text | ||
==Marked text== | Marked text | ||
[link text](https:// "title") | Link | ||
 | Image | ||
`Code` | Code |
在筆記中貼入程式碼 | |
```javascript var i = 0; ``` |
|
||
:smile: | ![]() |
Emoji list | |
{%youtube youtube_id %} | Externals | ||
$L^aT_eX$ | LaTeX | ||
:::info This is a alert area. ::: |
This is a alert area. |
On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?
Please give us some advice and help us improve HackMD.
Do you want to remove this version name and description?
Syncing
xxxxxxxxxx
tags:
Alucard
Alucard Reference
Welcome to the Alucard Reference Manual.
The reference manual is laid out via sections on specific topics. Each topic contains various functions related to the topic along with documentation and example usage of each feature.
For example, a section like Definition Facilities has subtopics like:
The topic section itself serves to give background and details about the entire section as a whole as to get a better sense of each function in the topic.
The exception to this rule are the next two sections:
These sections are good to read in full as they cover information that allows one to more effectively leverage the language as-well as have a feel for the cost of certain features of Alucard.
Outside of those details sections, the document is meant to be jumped around for the functions and features one wishes to learn about.
Happy Hacking!
About Alucard
Alucard is a language for writing Zero Knolwedge Proofs (ZKP), thus many details of how the language works is different from a more traditional architecture.
Circuit Model
The circuit model is an interesting one, having computation be composed of
+
,*
, and-
. In essence, we can view our computation in the form of polynomials.A good metric to view the costs of these operations is that addition (
+
) has a base cost of 0, while multiplication (*
) has a base cost of 1. The elements of this polynomial are field elements, meaning that in this case we the number is between0
and somep
wherep
is prime ([0, P)
).The ultimate goal of these polynomials is to generate out a circuit that will be proved by another party. Thus there are a few parties involved with the computation. We call these parties the
prover
and theverifier
.The
prover
is the creator of the circuit, and may have extra facts about the circuit that are spelled out in polynomial form.This takes the form of
private
inputs in Alucard programs.The other party is the
verifier
, who wishes to verify the equation, and does so by sending in public data into the circuitThis takes the form of
public
inputs in Alucard programs.Further, circuit backends also have a form of prolog style matching, with variables being introduced out of nowhere.
For example:
If we want to write the square root function or the squared function we can write
where if
x
is known,y
is solved to be the root of x. Ify
is known, thenx
is the square ofy
.This also has interesting overhead in some scenarios, as the circuit may form
n
possible solutions if an equation is under-determinedSince Alucard compiles to vamp-IR, there are other performance considerations to be had. Arrays can be made much faster if one is using the plookup (plonk with lookup) backend.
Representation of Data
Numeric types are represented by field elements natively within the language.
For data types which require multiple field elements, the situation is quite a bit harder, as computation is built around
+
,*
, and little else.Therefore, We can't simply just read n-bits of the structure, and pass around data in a packed form without massive overhead or some tricks.
With that said, Alucard supports the following data types:
The layout of each type is as follows
Records and Arrays aren't guaranteed to be packed, they may be inlined away (records almost always are, arrays are not yet). However if code coerces these types or do operations that rely on format, this is the cannonical layout of each concept.
Records
Records are user defined types, often called structs in language like C.
An example record type is
which defines a point which has an x and y field.
record can be compiled at zero costs in most situations, by expanding out the record into its constituents.
What this means that if we have a function like
then the circuit and applications are expanded as such:
There is however, a few case where records do have a cost, and that is in conjunction with arrays and type coercions.
In both these cases, record instances must be packed and have a canonical packed form. The form comes in the declaration. So in our above
point
type, thex
covers the firstn
bits, whiley
comes afterx
.Arrays often have costs, as Arrays typically have to pack data, meaning that records must incur an additional packing/unpacking cost, as it must be unpacked after looking it up from the array.
Type coercions have this cost in two scenarios:
These both have costs, as in the first case, the record is an unpacked structure being casted into an array (which are currently all packed!) or a singular number.
The latter has a cost in that the record must be unpacked to use the fields, meaning that we force an unpacking.
Arrays
Arrays are a builtin datatype to the Alucard language. They are typed in a very similar way to Rust, with the length being in the type name
(array length type)
.Packing in detail
Since the data in circuits are only field elements, all data which must be packed has to fit within a field element. Thankfully for Alucard, we assume the field element is arbitrarily large, with vamp-IR doing the work of splitting this up per requested backend.
With that said, it is best to see how packing works with a practical example.
In this code, we do something really simple, we make an array of
int-32
's (we know this asfoo
is checked to be consistent against the type.). The formal type of this array isarray 2 (int 32)
. Meaning the array is of length 2 and contains elements of typeint32
. Then we end up adding foo to the first position of thebar
array.This innocent looking code generates out the following code.
as we can see here, the packing isn't very expensive, it consists of
which works very simply. For each element in the array, store the element in the first
n
bytes, wheren
is the size of the type. Here the size is2^32
bits long or4294967296 (33 bits, #x100000000)
. This means that when we add the next element36
, we can pack the number easily enough by multiplying this number by the element we wish to store in it. If there were a third element, we would shift the next element by another32
bits, giving us a general equation.This is overall a fairly cheap operations, as it just involves
n
multiplications andn
additions.However, the same can not be said for unpacking.
This is the code for unpacking any number of bits.
Before we continue lets, use a simpler example to understand unpacking.
let us assume we have this array, and we wish to get 4 out of this array, then we can visualize what we must do as such:
we must split the array into three parts with a series of equations.
In here our array is
[3,1,4,1,5,1,9,6]
, and the first equation makes two unknowns.next-array
, andunused-mod
. The circuit knows enough to solve this, but there may be many solutions to the problem! In our case it should beThen we do the round again, being able to separate out
4
by itself.Overall it's a very expensive process to index into an array generically. Arrays of smaller element sizes (
(int 2)
(int 4)
) fair a lot better.Coercions for fun and profit
Coercions are the idea of converting one form of data into another. one may often see it in code like.
Where due to the strictness of our typing, we have to add or multiply or subtract numeric values of the exact same type. Implicit conversions like In Java, Lisp, or C do not occur.
Coercions between number types like this are free, and no cost is paid.
However there are some more interesting coercions that we can employ that can help general circuit array programming. For example, we may wish to reveal a single bit of an array. The Zokrates programming language lists this as an example.
In Alucard we can use coercions to change the array format from
int32
's to a list ofint1
's.Note :: Our example lacks the
sha256
function that Zokrates has, so it's not an exact copy!We can even give the coerce a nicer interface for our use case!
Or maybe we want to get the first 16 bits of a number.
All the variants that allows us to index part of data have to pay the cost of packing and unpacking. An example decompilation of
first-16-bits
isThe key takeaway is that coercions have a very useful function in circuit code, however they come with a high computational cost.
Hidden Costs in the Circuit Model
Constraints
Type Checking and Inference
How Alucard Interoperates with Common Lisp
This is an important section, as Alucard code tends to utilizes a lot of Common Lisp code, and it is good to know the boundary between the two.
A good rule of thumb is that Alucard definition creation forms like
defcircuit
anddeftype
, make functions which can only take Alucard terms. Common Lisp functions are inlined away, meaning they have no computational cost in Alucard, as they can not show up the compiled Alucard code. Thus anything that returns an alucard term is admissible.For example.
In this code, we made an Alucard function
add-int32
, and a Common Lisp functionadd
. Sinceadd
calls the alucard function+
, it really takes two Alucard values, and returns an Alucard value.However, if we use a Cl return type, then we can not do the same action. Instead we must use other CL functions to do it.
As we see in the example, that we can use Common lisp to indirectly allow this functionality. The apply here, can be best thought of moving the operation to the start of the list, so that, the
add-int32
call really looks like(add-int32 (x point) (y point))
. Alsodefcircuit
's along with any other Alucard defining form can not return a non Alucard type, like hte Common Lisp let.Also all the rules of Common Lisp apply, meaning that Alucard is mostly a
lisp-2
, meaning that functions and variables live in different namespaces,(defun x-point (x) (x x))
is therefore valid. And that to refer to the function version we must use#'function-name
or(function function-name)
to refer to the function version of a function.With that said, the CL facilities are what allows Alucard to be a very high level language. Abstractions with Lambda and local functions can be applied even though the Alucard langauge is not rich enough to express them natively.
For example:
Here we computed the
l2-norm
by hand in two different ways, one is a more low level way, of just doing the computation, and the other utilizies many abstractions of CL to extract out the boiler plate. For this problem, thel2-norm
may be overkill, but we've created some general abstractions to help us work over our bespokepoint
type. As mentioned before, CL functions likeapply
,flet
, andmapcar
are inlined away as they have no representation in Alucard. Meaning the two code have equivalence performance.The starting
package
of the Alucard image isaluser
, which has it's own set of primitive functions that overlap with CL's defaults. For example,+
is alucard's+
and not CL's+
. All of CL's functions should be accessible via thecl
package.Further, we can use Common Lisp to make new high level looking abstractions for Alucard. Functions like
map-array
(not yet implemented), are examples of how high level abstractions from every day programming can fit into the circuit model.There are many planned extensions of Alucard and the API between the language that should allow even more general code to be written in Alucard!
This is an interactive tutorial that should help you get up and running with lisp. This should help those who come from a more procedural background get up and running with CL. I also highly suggest switching the package to
cl-user
via(in-package :cl-user)
before starting the tutorial and setting up the editor integration for Alucard/lisp. To learn more about the CL package system, one can read about it here.Definition Facilities
This section covers all the standard function defining facilities along with functions which create binders.
A common theme among all these functions is that they all take Alucard values, and return Alucard values. Which means that
Note that functions from the Common Lisp (CL) standard that are often used as Alucard definers aren't described here even though they fit the topic. Go to the [Common Lisp Facilities](Common Lisp Facilities) section to learn more about those.
defcircuit
Binds
func-name
to a procedure. Theparameter+
determines the type and number of arguments this procedure may have, and thereturn?
determines the return type the procedure has.Each parameter has a privacy marker, which marks the parameter as either private or public to the circuit. If the given function defined from defcircuit is not the entry point to the circuit, then this privacy value is ignored.
The body of the procedure is the specified list of expressions, these are evaluated in order with the last expression being returned if there is a non
void
return type.Examples
deftype
Creates a record type with the given
name
. eachrecord-field
specifies both the type of the field along with the name for accessing and creating the field. See the record section above to see how records are compiled.The second argument
()
, is currently a place holder for a future generics system.defgate [Not in the language yet]
def
Binds the
variable-names
to the givenexpression
to be used within thebody
. The first boundvariable-names
can be used in later expressions within thebinders
themselves.def
has a special case where the namewith-constraint
can be used to introduce variables that don't have direct bodies. See the constraints section to see how it all works.For example
as we can see here, we let
r-squared
by the squared value of thereal part of
c
, andi-squared
for the imaginary part. Finally, we usewith-constraint
to do thesquare-root
function by hand (ifp = x²
, thenx = √p
). Notice how we are able to user-squared
andi-squared
within the body of thewith-constraint
.All these values are in-socpe for the
def
's body, in which we just return theroot
, giving the norm of the complex number that was passed in.Typing Facilities
coerce
Coerces the given
term
to the desiredtype
.A simple example is as follows:
coerce
can also be used for more complex patterns, see coercions for fun and profit for more complex patterns that can be had withcoerce
.check
Informs the type checker that
term
has the giventype
. This may help the inference algorithm to understand the given type, or force the type checker to try unify some literals to the desired type. Theterm
is returned from thecheck
call.In
type-checking-fun!
theto-array
call initially does not have enough information to determine the type of the array. Later we write(check foo (int 32))
, which states thatfoo
is of type(int 32)
, and that informs us thatbar
must be of type(array 2 (int 32))
!Some notes. See the section type checking and inference for limitations of the current type checking algorithm.
Common Lisp Facilities
Numeric Facilities
=
+
Adds n numbers of the same type, and returns the sum of its arguments.
*
Multiplies n numbers of the same type, and returns the sum of its arguments.
exp
Returns the
base
raised to thepower
. Both arguments have to be of the same type.Array Facilities
to-array
get