bytes32 identifier for a ciphertext that CoFHE holds offchain. euint32, ebool, and the rest are Solidity user-defined value types over that bytes32.
FHE.unwrap and FHE.wrapEuintXX move between the two spellings. They are the only FHE functions that do no work at all.
What the two functions do
internal pure. They perform no cryptography, make no call to the TaskManager, touch no storage, and cost nothing beyond the surrounding code. They change how Solidity types the same 32 bytes, and nothing else.
There is one unwrap for every encrypted type, and one wrap per type because the return type has to differ: wrapEbool, wrapEuint8, wrapEuint16, wrapEuint32, wrapEuint64, wrapEuint128, and wrapEaddress.
The bindings give you the same thing in dot form, so
myValue.unwrap() reads better inside an expression. See bindings.Wrapping does not grant permission
Wrapping a handle grants you nothing. The type records what kind of value this is. The ACL decides whether you may use it. Those are separate systems, andwrap only touches the first one.
You can wrap any 32 bytes you like. Nothing reverts, because nothing is checked:
notYours is where it fails, because the TaskManager checks whether this contract is allowed on that handle:
wrap is a cast, not an acquisition. Holding a typed value is not the same as being permitted to compute on it.
Handles are public
A handle is not a secret. It sits in contract storage, travels in calldata, and shows up in event logs. Anyone can read one. That is fine, because a handle reveals nothing about the plaintext. It is an identifier, not the ciphertext and not the value. Knowing that Alice’s balance is handle0x9f3c… tells you nothing about the balance.
What a handle does give its holder is the ability to name that ciphertext in a call. That is why the ACL exists, and why the next section matters.
When to unwrap
Reach forunwrap when you need the value to be plain bytes32 because something else demands it:
- Storing a handle in a generic
bytes32slot or struct field. - Emitting a handle in an event so a client can read it back.
- Using a handle as a mapping key.
- Comparing two handles for identity, which asks “is this the same ciphertext”, not “are these values equal”. For an encrypted comparison use
FHE.eq.
When to wrap
Reach forwrapEuintXX when you are recovering a handle your own contract already owns and stored as bytes32:
When not to wrap
Use a type that carries provenance instead. There is one for every way a value can reach you, so a wrappedbytes32 parameter is never the right answer:
Each conversion authenticates the value as part of converting it, which is exactly what wrapping a raw handle skips. The two
receive forms are not interchangeable: pick by how the value arrived, because naming the wrong party silently weakens the check rather than failing.
All of these are covered in inputs.
Checking a handle is real
wrap accepts anything, so a wrapped value can be meaningless. FHE.isInitialized tells you whether a handle is non-zero, which catches an unset storage slot:
Related
- Inputs: how values arrive from users and from other contracts.
- Access control: what the ACL permits, and how to grant it.
- Utility functions: the reference entries for
unwrap,wrap, andisInitialized.