3.1 Hello World

We are finally ready to create our first Aleo program. Let's take a look at how we can do that.

The leo command line interpreter tool offers an easy way to start a new project from scratch:

leo new hello_world

This will create a new program project named hello_world. This project is instantiated a new directory. First let's move into the directory:

cd hello_world

You should have following file structure:

hello_world/
├── .gitignore
├── .env
├── program.json
└── src/
  └── main.leo

.gitignore is just a default git related file. The environment file.end, contains the NETWORK and PRIVATE_KEY variables. Let's keep the default generated values for now.

program.json file is a manifest with all the metadata for your project:

{
  "program": "hello_world.aleo",
  "version": "0.1.0",
  "description": "",
  "license": "MIT",
  "dependencies": null
}

No need to edit any of these informations for now.

Filesrc/main.leo contains the source code for an example hello world program:

// The 'hello_world' program.
program hello_world.aleo {
    transition main(public a: u32, b: u32) -> u32 {
        let c: u32 = a + b;
        return c;
    }
}

Leo's syntax looks a lot like Rust, if you are familiar with it. Let's analyse this first program line by line to try and understand what it does:

// The 'hello_world' program.

This line is a comment, you can simply ignore it. As in rust, single line comments start with// and multiline comment begin with /* and end with */

program hello_world.aleo {

All the code for your program will be included in this program structure. hello_world.aleo is your program's id, it is a unique identifier for your program on chain. Once deployed, you will be able to call your program using this identifier. If a program with this id is already deployed, you will need to deploy your program with a different id, by updating the value here, as well as in the program field from program.json file.

transition main(public a: u32, b: u32) -> u32 {

This is where things get interesting. A transition is a function of your program, that can be called by users and other programs, directly on chain. Using its program id and name, any user can call a transition in a program, using snarkOS CLI for instance.

Here the transition name is main but it could be anything. The only rule is it must be less

A transition has inputs and outputs. Those inputs must be provided by the caller of the transition. Here the inputs are defined as public a: u32, b: u32, there are two inputs a and b. They are both 32 bits unsigned integer but a is public while b is private, we'll see exactly what this means in next chapter. The output is a single 32 bits unsigned integer as defined at: -> u32. The next line is:

let c: u32 = a + b;

Here we simply define a new variable c that is the sum of a and b.

return c;

The sum is then returned as an output 32 bits unsigned integer.

There you have your first Leo program, although it's not very useful for the moment of course.

Now let's try to run the main transition (locally off-chain of course, your program is not deployed on chain yet). We'll use a = 1u32 and b = 2u32 as test values:

leo run main 1u32 2u32

Here is what the console should output:

       Leo ✅ Compiled 'test.aleo' into Aleo instructions

⛓  Constraints

 •  'test.aleo/main' - 33 constraints (called 1 time)

➡️  Output

 • 3u32

       Leo ✅ Finished 'test.aleo/main' (in "/Users/palong/aleo/test/build")

This command proceeds to several steps under the hood. First it transcribes your high level leo code into aleo instructions. This is the code that can be deployed on chain as a program. The aleo instruction can then be compiled into AVM bytecode, a lower level language that isn't human readable, that can actually be executed by the Aleo zkVM. To make an analogy with traditional programming, you can think of Leo as Rust or C, aleo instructions as assembly and AVM bytecodes as machine code.

When turning aleo instructions into AVM bytecode, the compiler transforms each instruction of every function into R1CS circuit representation. In the command output shown previously, the amount of constraint in the circuit for function main is displayed in "Constraint" section: here it's 33. The higher the constraints the longer it is to prove a transition.

In the output section, the program output is printed: here c from our previous program.

If you want to actually generate a proof for the execution of a function on some inputs, you should instead use the execute function:

leo execute main 1u32 2u32

In addition to the informations displayed before, it should output a JSON. This JSON contains all the information necessary to verify your execution of the function. It includes the Proof (at "proof" key), but zero knowledge about the private input and outputs of said function. You could send this JSON to any verifier. Once your program is deployed this is what users will broadcast to blockchain nodes so the function can be verified and executed on chain.

{
   "type":"execute",
   "id":"at1kpskgunpv6fzp6gjqyty9wj5pkglew80gv9xfud44u2de4c5nyzqawacwm",
   "execution":{
      "transitions":[
         {
            "id":"au1ve677pmqm6e4c57jd4nqw4fxs5eluhpj2vx8xah9qpqf6n0ml5rs4sd86f",
            "program":"hello_world.aleo",
            "function":"main",
            "inputs":[
               {
                  "type":"public",
                  "id":"4327327061615019987193845855696753025074330531496884385072972347237822484331field",
                  "value":"1u32"
               },
               {
                  "type":"private",
                  "id":"2613643908087207604698332053566982753141382091567999840359869354710838353966field",
                  "value":"ciphertext1qyqff3v6rgdknrlu66qw5yjgkfyp667kz95qcds0hv69lavmluz6cqgl7f6pn"
               }
            ],
            "outputs":[
               {
                  "type":"private",
                  "id":"5864321499951710550849652788173993684422111380925183323282628771361599130044field",
                  "value":"ciphertext1qyqgmgnzzcqj9rjtfus7fyedgwml0ndtmfcm9frsu0jwq04gcfp7czqw85qf5"
               }
            ],
            "tpk":"2348638088627716658465529753908364997794237501644353989230986293338025749544group",
            "tcm":"5195442461394445797733941303382300385207942392281065071519415431649006872086field",
            "scm":"392914910037825426247567826310888289200617271240907368782272815881678422072field"
         }
      ],
      "global_state_root":"sr1h37c4m6u5h8yat544jdds678unlchpgrft6q8fyn6fvg9cvgeuzs3fmpe9",
      "proof":"proof1qyqsqqqqqqqqqqqpqqqqqqqqqqqfpx5872p7dlmlyjkrfn3fayqypzmss786uyyxaydmk56jvvr887svyl9qa0p4q8tfg20n7jghkzypqyk3k6jp6jffvk34djpumhp902rekhg4gtp87z9g3hr5dy9zxx9mcqhlaxlqyjfsvggvvgyf6wrkzqdua3e7adxlk7utt7m5clpsrymy35stwztn3nh7tt9pdywkhewqtus8p2w56asqhrkjj0jtv90xx6qvzhwufr03mv9fwupm2x8xluk38lawyg7qjujkxlywcuekksjeqjxq8t7sxl3f9c2gjpzdgezt965qd6pzuy7aksxev8km4v23kts4dqzlpeyrvwcprzme40yyjfcn2hvd53yxjnhzxhgwwp2l2gd0f4fszvw3wd98uu5vpk7qqjp3f7d9ck3tn4ymqkxfvapuljdmmc2k426zs4z705klfsvsu2zkm88q4dshs9h37ka8tlqprwdwr96lkuy7j973zuenrpk288pu3d0q6hpdutanfqkyf02mu3s9sgsp5t83w3cxsqy5ehh8wgddmlp8qsysj93jq4mtvthlk20uln2y2p8easzt46gn6rl0lm8xq0u2xu02fkpghygtwvqltv5vyjkv8ex47z6lfw0j4t26hmah8533qv06jgjxlvl2g86wl5dh8wnvl032arurx5kx9zflp7sqgz0acqu2ewdr7kt2r2ux6s3nwhz4ueasyvrt9vfxxdd4h2gqcgpcdx24zhkmurkld9ngh4keypl5h7gans6kmwv7unm6hnr8vkjh7r8p6e025wre7s4mecfcuvvrav8ltlx7ure7g629jvx4n4ks400lptqxvwa9z5p3v42d82rspzhhsm9mqr0uxs7a5f9aym4c4e7td5e39257quzmsvlg44u96h3y70k0ygjmyzpsq6q93ywxqydaugcdvusfsc4a5j3n83s5p079yeh6fnrq7e3e8455lxvayukxzjesylaj8cgef4vcfjqg3zuaeqd89zw7dk6shz2m65um69qxmpx73a2dwsf4upnljyna73mazz4ee9e530ml6ns5eaqk7elym269smg493yhj4dnzzp0dd4wa2l22z2pnv3sfwyhdfgh0w5r666nw8utjg5sat6mp7fshx2565kh8j0077pctk6ywjv460x9chsnt4j984s0cr4krwx6pegzqvqqqqqqqqqqqnqyku2gz9cznjafgj8dn7mfllaxg8jj3zqne2fw20j3ha855kcrz6q5g2lacr2vatkkgtxz90vzsyqwvca28hyphmx53fhsjkuypqk2nm2upcq0aww3tj0a9069mfvcqsu6ayjss2kfwgynqh72wwdza7sqqyktacqhna97mj4vfh7acvsxcg63pfm9d270576q0pplm97ugpw3qftvrkjsnrapt550qa5tle2l20yf5pmsdltw4647ewlx5lvxvmnnyshhs0wng5m9t4xfyc5uzherqyqqnfg5ss"
   }
}

After running this command, if you list your files again you should see something like:

hello_world/
├── .gitignore
├── .env
├── leo.lock
├── program.json
├── build/
  ├── build/
    ├── main.avm
    ├── main.prover
    └── main.verifier
  ├── program.json
  └── main.aleo
└── src/
  └── main.leo

leo.lock keeps track resolved dependencies for you program, we'll come back to that in next module. main.aleo is the transcribed file containing aleo instructions, while main.avm is the compiled file containing AVM bytecode. The prover and verifier files contained serialized Proving and Verifying keys for the corresponding program. They can be used to respectively generate and verify proofs for executions.

In next chapter we'll see what is meant by private and public keywords.

Last updated