1"use strict";(globalThis.webpackChunkdocs=globalThis.webpackChunkdocs||[]).push([[156],{83170(e,n,t){t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>s,default:()=>u,frontMatter:()=>r,metadata:()=>o,toc:()=>l});const o=JSON.parse('{"id":"language/unconstrained","title":"Unconstrained Functions","description":"Learn about what unconstrained functions in Noir are, how to use them and when you\'d want to.","source":"@site/versioned_docs/version-v1.0.0-rc.0/language/unconstrained.md","sourceDirName":"language","slug":"/language/unconstrained","permalink":"/docs/v1.0.0-rc.0/language/unconstrained","draft":false,"unlisted":false,"editUrl":"https://github.com/noir-lang/noir/edit/master/docs/versioned_docs/version-v1.0.0-rc.0/language/unconstrained.md","tags":[],"version":"v1.0.0-rc.0","frontMatter":{"title":"Unconstrained Functions","description":"Learn about what unconstrained functions in Noir are, how to use them and when you\'d want to.","keywords":["Noir programming language","unconstrained","brillig"]},"sidebar":"sidebar","previous":{"title":"Assert Function","permalink":"/docs/v1.0.0-rc.0/language/assert"},"next":{"title":"Oracles","permalink":"/docs/v1.0.0-rc.0/language/oracles"}}');var i=t(74848),a=t(28453);const r={title:"Unconstrained Functions",description:"Learn about what unconstrained functions in Noir are, how to use them and when you'd want to.",keywords:["Noir programming language","unconstrained","brillig"]},s=void 0,c={},l=[{value:"Why?",id:"why",level:2},{value:"Example",id:"example",level:2},{value:"Unsafe Blocks",id:"unsafe-blocks",level:2},{value:"Break and Continue",id:"break-and-continue",level:2},{value:"Security checks",id:"security-checks",level:2},{value:"Independent subgraph detection",id:"independent-subgraph-detection",level:3},{value:"Brillig manual constraint coverage",id:"brillig-manual-constraint-coverage",level:3},{value:"Lookback option",id:"lookback-option",level:4}];function d(e){const n={a:"a",code:"code",em:"em",h2:"h2",h3:"h3",h4:"h4",p:"p",pre:"pre",...(0,a.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.p,{children:"Unconstrained functions are functions which do not constrain any of the included computation and allow for non-deterministic computation."}),"\n",(0,i.jsx)(n.h2,{id:"why",children:"Why?"}),"\n",(0,i.jsx)(n.p,{children:"Zero-knowledge (ZK) domain-specific languages (DSL) enable developers to generate ZK proofs from their programs by compiling code down to the constraints of an NP complete language (such as R1CS or PLONKish languages). However, the hard bounds of a constraint system can be very limiting to the functionality of a ZK DSL."}),"\n",(0,i.jsx)(n.p,{children:"Enabling a circuit language to perform unconstrained execution is a powerful tool. Said another way, unconstrained execution lets developers generate witnesses from code that does not generate any constraints. Being able to execute logic outside of a circuit is critical for both circuit performance and constructing proofs on information that is external to a circuit."}),"\n",(0,i.jsx)(n.p,{children:"Fetching information from somewhere external to a circuit can also be used to enable developers to improve circuit efficiency."}),"\n",(0,i.jsx)(n.p,{children:"A ZK DSL does not just prove computation, but proves that some computation was handled correctly. Thus, it is necessary that when we switch from performing some operation directly inside of a circuit to inside of an unconstrained environment that the appropriate constraints are still laid down elsewhere in the circuit."}),"\n",(0,i.jsx)(n.h2,{id:"example",children:"Example"}),"\n",(0,i.jsxs)(n.p,{children:["An in depth example might help drive the point home. Let's look at how we can optimize a function to turn a ",(0,i.jsx)(n.code,{children:"u64"})," into an array of ",(0,i.jsx)(n.code,{children:"u8"}),"s."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"fn main(num: u64) -> pub [u8; 8] {\n let mut out: [u8; 8] = [0; 8];\n for i in 0..8 {\n out[i] = (num >> (56 - (i as u64 * 8))) as u8;\n }\n out\n}\n"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{children:"$ nargo info\n+---------+----------------------------+--------------+-----------------+\n| Package | Function | ACIR Opcodes | Brillig Opcodes |\n+=========+============================+==============+=================+\n| short | main | 65 | 8 |\n+---------+----------------------------+--------------+-----------------+\n| short | directive_integer_quotient | N/A | 8 |\n+---------+----------------------------+--------------+-----------------+\n"})}),"\n",(0,i.jsx)(n.p,{children:"A lot of the operations in this function are optimized away by the compiler (all the bit-shifts turn into divisions by constants)."}),"\n",(0,i.jsxs)(n.p,{children:["Those are some nice savings already but we can do better. This code is all constrained so we're proving every step of calculating ",(0,i.jsx)(n.code,{children:"out"})," using num, but we don't actually care about how we calculate this, just that it's correct. This is where unconstrained code comes in."]}),"\n",(0,i.jsxs)(n.p,{children:["It turns out that truncating a ",(0,i.jsx)(n.code,{children:"u64"})," into a ",(0,i.jsx)(n.code,{children:"u8"})," is hard to do inside a snark, each time we do as ",(0,i.jsx)(n.code,{children:"u8"})," we lay down 4 ACIR opcodes which get converted into multiple gates. It's actually much easier to calculate ",(0,i.jsx)(n.code,{children:"num"})," from ",(0,i.jsx)(n.code,{children:"out"})," than the other way around. All we need to do is multiply each element of ",(0,i.jsx)(n.code,{children:"out"})," by a constant and add them all together, both relatively easy operations inside a snark."]}),"\n",(0,i.jsxs)(n.p,{children:["We can then run ",(0,i.jsx)(n.code,{children:"u64_to_u8"})," as unconstrained code (Brillig) in order to calculate ",(0,i.jsx)(n.code,{children:"out"}
1),", then use that result in our constrained function and assert that if we were to do the reverse calculation we'd get back ",(0,i.jsx)(n.code,{children:"num"}),". This looks a little like the below:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"fn main(num: u64) -> pub [u8; 8] {\n // Safety: 'out' is properly constrained below in 'assert(num == reconstructed_num);'\n let out = unsafe { u64_to_u8(num) };\n\n let mut reconstructed_num = 0;\n for i in 0..8 {\n reconstructed_num += (out[i] as u64 << (56 - (8 * i as u64)));\n }\n assert(num == reconstructed_num);\n out\n}\n\nunconstrained fn u64_to_u8(num: u64) -> [u8; 8] {\n let mut out: [u8; 8] = [0; 8];\n for i in 0..8 {\n out[i] = (num >> (56 - (i as u64 * 8))) as u8;\n }\n out\n}\n"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{children:"$ nargo info\n+---------+-----------+--------------+-----------------+\n| Package | Function | ACIR Opcodes | Brillig Opcodes |\n+=========+===========+==============+=================+\n| short | main | 33 | 114 |\n+---------+-----------+--------------+-----------------+\n| short | u64_to_u8 | N/A | 114 |\n+---------+-----------+--------------+-----------------+\n"})}),"\n",(0,i.jsx)(n.p,{children:"This ends up taking off another 32 ACIR opcodes from our circuit!\nWe've ended up with more Brillig opcodes than before but it is often faster for the backend to run more unconstrained code (Brillig) to verify it with fewer constrained opcodes (ACIR) later."}),"\n",(0,i.jsx)(n.h2,{id:"unsafe-blocks",children:"Unsafe Blocks"}),"\n",(0,i.jsxs)(n.p,{children:["Calling an unconstrained function from constrained code requires wrapping the call in an ",(0,i.jsx)(n.code,{children:"unsafe { ... }"})," block. This makes it explicit that the result is not automatically constrained and that the programmer takes responsibility for adding the necessary constraints."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"// Safety: 'result' is constrained below by the assert\nlet result = unsafe { my_unconstrained_fn(x) };\nassert(result == expected);\n"})}),"\n",(0,i.jsxs)(n.p,{children:["The compiler emits a warning unless the ",(0,i.jsx)(n.code,{children:"unsafe"})," block is accompanied by a ",(0,i.jsx)(n.code,{children:"// Safety: ..."})," comment explaining why it is safe to call the unconstrained function. The comment can be placed either on the ",(0,i.jsx)(n.code,{children:"unsafe"})," block itself or on the enclosing statement (such as the ",(0,i.jsx)(n.code,{children:"let"})," binding in the example above)."]}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"unsafe"})," does not disable any other compiler checks -- it only permits calling unconstrained functions. All other type checking, visibility rules, and constraint generation remain in effect."]}),"\n",(0,i.jsx)(n.p,{children:"Generally we want to use unconstrained code whenever there's something that's easy to verify but hard to compute within the circuit. For example, if you wanted to calculate a square root of a number it'll be a much better idea to calculate this in unconstrained code and then assert that if you square the result you get back your number."}),"\n",(0,i.jsx)(n.h2,{id:"break-and-continue",children:"Break and Continue"}),"\n",(0,i.jsxs)(n.p,{children:["In addition to loops over runtime bounds, ",(0,i.jsx)(n.code,{children:"break"})," and ",(0,i.jsx)(n.code,{children:"continue"})," are also available in unconstrained code. See ",(0,i.jsx)(n.a,{href:"/docs/v1.0.0-rc.0/language/control_flow#break-and-continue",children:"break and continue"})]}),"\n",(0,i.jsx)(n.h2,{id:"security-checks",children:"Security checks"}),"\n",(0,i.jsx)(n.p,{children:'Two compilation security passes exist currently to ensure soundness of compiled code. Problems they catch are reported as "bugs" (as opposed to errors) in the compiler output. For example:'}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{children:"**bug**: Brillig function call isn't properly covered by a manual constraint\n"})}),"\n",(0,i.jsx)(n.h3,{id:"independent-subgraph-detection",children:"Independent subgraph detection"}),"\n",(0,i.jsx)(n.p,{children:"This pass examines the instruction flow graph to see if the final function would involve values that don't come from any provided inputs and don't result in the outputs. That would mean there are no constraints ensuring the required continuity."}),"\n",(0,i.jsxs)(n.p,{children:["This check is enabled by default and can be disabled by passing the ",(0,i.jsx)(n.code,{children:"--skip-underconstrained-check"})," option to ",(0,i.jsx)(n.code,{children:"nargo"}),"."]}),"\n",(0,i.jsx)(n.h3,{id:"brillig-manual-constraint-coverage",children:"Brillig manual constraint coverage"}),"\n",(0,i.jsx)(n.p,{children:"The results of a Brillig function call must be constrained to ensure security, adhering to these rules: every resulting value (including every array element of a resulting array) has to be involved in a later constraint (i.e. assert, range check) against either one of the arguments of the call, or a constant. In this context, involvement means that a descendant value (e.g. a result of a chain of operations over the value) of a result has to be checked against a descendant value of an argument. For example:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"unconstrained fn factor(v0: Field) -> [Field; 2] {\n ...\n}\n\nfn main(foo: Field) -> pub [Field;
1 2] {\n // Safety: factored is constrained below by `assert(factored[0] * factored[1] == foo)`\n let factored = unsafe { factor(foo) };\n assert(factored[0] * factored[1] == foo);\n factored\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Here, the results of ",(0,i.jsx)(n.code,{children:"factor"})," are two elements of the returned array. The value ",(0,i.jsx)(n.code,{children:"factored[0] * factored[1]"})," is a descendant of both of them, so both are involved in a constraint against the argument value in the ",(0,i.jsx)(n.code,{children:"assert"}),". Hence, the call to an unconstrained function is properly covered."]}),"\n",(0,i.jsx)(n.p,{children:"This pass checks if the constraint coverage of Brillig calls is sufficient in these terms."}),"\n",(0,i.jsxs)(n.p,{children:["The check is enabled by default and can be disabled by passing the ",(0,i.jsx)(n.code,{children:"--skip-brillig-constraints-check"})," option to ",(0,i.jsx)(n.code,{children:"nargo"}),"."]}),"\n",(0,i.jsx)(n.h4,{id:"lookback-option",children:"Lookback option"}),"\n",(0,i.jsxs)(n.p,{children:["Certain false positives of this check can be avoided by providing the ",(0,i.jsx)(n.code,{children:"--enable-brillig-constraints-check-lookback"})," option to ",(0,i.jsx)(n.code,{children:"nargo"}),", which can be slower at compile-time but additionally ensures that descendants of call argument values coming from operations ",(0,i.jsx)(n.em,{children:"preceding"})," the call itself would be followed. For example, consider this case:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"unconstrained fn unconstrained_add(v0: Field, v1: Field) -> Field {\n v0 + v1\n}\n\nfn main(v0: Field, v1: Field) {\n let foo = v0 + v1;\n // Safety: `bar` is constrained below by `assert(foo == bar)`\n let bar = unsafe { unconstrained_add(v0, v1) };\n assert(foo == bar);\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Normally, the addition operation over ",(0,i.jsx)(n.code,{children:"v0"})," and ",(0,i.jsx)(n.code,{children:"v1"})," happening before the call itself would prevent the call from being (correctly) considered properly constrained. With this option enabled, the false positive goes away at the cost of the check becoming somewhat less performant on large unrolled loops."]})]})}function u(e={}){const{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}},28453(e,n,t){t.d(n,{R:()=>r,x:()=>s});var o=t(96540);const i={},a=o.createContext(i);function r(e){const n=o.useContext(a);return o.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function s(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:r(e.components),o.createElement(a.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.