Verified Commit 1cd31972 authored by Sophia Gold's avatar Sophia Gold
Browse files

merge ocaml-sapling

parent efe12e2a
Pipeline #38568142 failed with stages
in 3 minutes and 45 seconds
**/target
**/_build
**/.merlin
Cargo.lock
opam-version: "0.1"
name: "ocaml-sapling"
version: "0.1"
authors: "Sophia Gold <sophiagoldnyc@gmail.com>"
maintainer: "Sophia Gold <sophiagoldnyc@gmail.com>"
license: "MIT"
available: [
ocaml-version >= "4.06.0"
]
build: [
[ "dune" "build" "-p" name "@install" ]
[ "cargo" "build" "--release" ]
]
depends: [
"dune" {build & >= "1.0"}
"alcotest" {>= "0.8.3"}
"bigstring" {>= "0.2"}
"bitstring" {>= "3.1.0"}
"tezos-crypto" {= "0.2"}
"blake2" {= "1.2"}
]
[package]
name = "ocaml-sapling"
version = "0.1.0"
authors = ["sophia gold <sophiagoldnyc@gmail.com>"]
[dependencies]
derive-ocaml = { version = "0.1.1", features= ["stubs", "derive"] }
ocaml = "0.5.0"
sapling-crypto = { git = "https://github.com/zcash-hackworks/sapling-crypto" }
pairing = { version = "0.14.2", features = ["expose-arith"] }
rand = "0.4"
bellman = "0.1"
zip32 = { git = "https://github.com/Sophia-Gold/zip32", rev = "e5a2dc55" }
lazy_static = "1.1.0"
byteorder = "1.0"
[lib]
crate-type = ["staticlib"]
\ No newline at end of file
The MIT License (MIT)
Copyright (c) Tezos Foundation <contact@tezos.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# NB: requires Rust nightly
# install using rustup: `curl -s https://static.rust-lang.org/rustup.sh | sh -s -- --channel=nightly`
# if rustup is already installed: `rustup install nightly` or `rustup default nightly`
build:
cargo +nightly build --release
dune build
test: build
dune runtest src --force
clean:
rm -f src/*.a src/*.so
cargo clean
dune clean
<p align="center">
<img src="https://camo.githubusercontent.com/1eaf3a8b6eb465c267e8ea82b4dd1c0ed3f215a7/68747470733a2f2f692e696d6775722e636f6d2f70647743634b5a2e706e67" height="400" title="Aspen Grove">
</p>
_The largest aspen grove consists of ~47,000 distinct trees with a shared root structure covering over 100 acres. It is one of the largest organisms in the world and believed to be at least 80,000 years old._
----
This library contains OCaml bindings to Zcash Sapling for the purpose of adding highly efficient zk-SNARKs to Tezos. The original Tezos white paper mentioned Zcash’s predecessor, Zerocash, as an example of technologies that could be easily grafted onto an extensible blockchain protocol.
Until now, generating a single zk-SNARK has taken as long as several minutes and several gigabytes of RAM. With Sapling the Zcash team has completely rewritten their zk-SNARK implementation in Rust using a new hashing algorithm, prover-verifier system, elliptic curves, and finite fields — generating proofs as small as 144 bytes in a matter of seconds on a commodity laptop. This allows even mobile devices to generate zero knowledge proofs and will hopefully bolster the prevalence of shielded transactions, resulting in greater collective privacy.
Sapling represents several years of research and development and the Tezos community can immediately benefit from it.
----
## Shielded Transactions
For transactions to be truly anonymous they must provide a commitment to transfer a quantity of XTZ in a manner that can be stored publicly on the blockchain and verified by third parties without revealing the identities of the sender or recipient. Such transactions are referred to as “shielded”.
Shielded transactions take the form of “cheques” (or "notes" in Zcash). Cheques consist of a value in satoshis, the address of the recipient, and a Pederson [commitment](https://en.wikipedia.org/wiki/Commitment_scheme). Commitments are generated separately for the value alone. [Pederson hashes](https://www.cs.cornell.edu/courses/cs754/2001fa/129.PDF) are also used for Merkle trees, which is responsible for much of the performance gains over SHA-256 in libsnark.
Each cheque also corresponds to a unique identifier, called a “hold” (or “nullifier” in Zcash), which is tracked in the mempool to prevent double spends.
Cheques may be associated with a 512-bit memo field, e.g. “Happy Birthday Alice, from Bob.”
Both the cheque and memo field are encrypted with a variant of the Salsa20 stream cipher along with a Poly1305 message authentication code. They can be decrypted with the full keypair generated by the recipient _or_ with either an “outgoing viewing key” or “incoming viewing key.” Viewing keys can be given to third parties without spending authority and thus facilitate the regulatory compliance required by entities like exchanges without compromising the privacy of individual users.
----
## Zero Knowledge Proofs
As mentioned, we must also prove a given transaction has taken place. We use both a “sign” proof (or "spend in Zcash), generated by the sender, and an “endorse” proof (or "output" in Zcash, similar to a “join split” in Zcash 1.0). A transaction has been completed once both can been verified.
Sapling uses a prover-verifier system designed by [Jens Groth](https://eprint.iacr.org/2016/260). In addition to producing smaller proofs in less time than previous zk-SNARK implementations, particularly when SNARKs are recursively composed, it has been the focus of recent research on security in the case when the common reference string (“trusted setup”) has been subverted. A [subsequent paper](https://eprint.iacr.org/2017/599.pdf) proved it has perfect subversion-resistant zero knowledge with computational soundness (under the subversion generic bilinear group model). If someone were to recover toxic waste from the multi-party ceremony (discussed below) the proofs may remain more secure than in previous zk-SNARK implementations.
zk-SNARKs are generated from systems of linear equations (“rank one constraint systems” or R1CS) encoded as arithmetic circuits. The **sign circuit** takes the following inputs:
- Domain parameters for the elliptic curve
- Pedersen commitment to the value being spent
- Proof generation key corresponding to a particular spending key
- Recipient address
- Random value used to generate the cheque commitment
- Value used for re-randomization in a signature scheme
- Authentication path of the commitment in the Merkle tree
- Merkle root (or "anchor in Zcash)
And for the **endorse circuit**:
- Domain parameters for the elliptic curve
- Value commitment
- Recipient address
- Random value used to generate the cheque commitment
- Private key generated by the recipient for use with Diffie–Hellman
Along with the verification key, the following publicly visible inputs are used to **verify a sign** proof:
- Signature
- Value commitment
- Merkle root
- Hold for the given cheque
And to **verify an endorse** proof:
- Value commitment
- Public key corresponding to the private key used to generate the proof
- Cheque commitment
A signature scheme is used so a device limited in computation and/or memory, e.g. hardware wallets, can delegate proof generation to a third party without exposing their spending key (not currently implemented in this library). The signature is obtained by re-randomizing the address portion of the spending key. This is discussed in the section below on key derivation.
----
## Key Derivation
<p align="center">
<img src="https://camo.githubusercontent.com/511c49d2d748786fb3cbb34542fc3e9cf6364fc0/68747470733a2f2f692e696d6775722e636f6d2f454f39306970792e706e67" height="300" title="Spending Keys">
</p>
Sapling uses several distinct keys. Understanding the relationship between them, pictured above from the ZCash specification, is helpful for understanding how to use this library. Keys are represented in OCaml as phantom types and can be printed or converted to arrays of 8-bit integers. Many of the keys listed above are not exposed.
Generating a new spending key will return a proof generation key and outgoing viewing key. There’s also an option to generate a “blind spending key”, which only returns a proof generation key. This means cheques cannot be decrypted once the sender has deleted their local copy, which can be useful for operational security if the sender's account becomes compromised.
The proof generation key can be used to derive a full viewing key. The full viewing key can be used to derive an incoming viewing key or, along with an address group, a new address (see below).
Not included in the above diagram, the recipient also generates a key pair for use in the endorse circuit.
----
## Addresses
Shielded transactions use their own addresses, consisting of two parts: a shared address group and a unique address id. New addresses can be generated that share the same group, up to the birthday bound (a twist on a technique referred to as "key diversification"). These addresses cannot be linked to one another without knowledge of the full viewing key, but since they share an incoming viewing key they do not increase the time required to search for their transactions on the blockchain.
It is highly recommended that you **do not use vanity addresses** as they decrease the difficulty of correlating your transactions.
----
## Verification
Verification requires the proof ciphertext, verifying key, and public inputs. It returns `true` if the proof is sound. It’s important to distinguish between the verifier returning `false` due to an unsound proof and errors in verification.
The following errors are propagated through to OCaml:
```
AssignmentMissing -> "an assignment for a variable could not be computed"
DivisionByZero -> "division by zero"
Unsatisfiable -> "unsatisfiable constraint system"
PolynomialDegreeTooLarge -> "polynomial degree is too large"
UnexpectedIdentity -> "encountered an identity element in the CRS"
IoError -> "encountered an I/O error"
MalformedVerifyingKey -> "malformed verifying key"
UnconstrainedVariable -> "auxillary variable was unconstrained"
```
You shouldn’t see them since the inputs are abstract. If you do please file an issue.
----
## Common Reference String
zk-SNARKs use a common reference string, often referred to as a “trusted setup.” To comply with the recent standardization efforts for zero knowledge proofs (https://zkproof.org/) Sapling uses a _uniform reference string_, i.e. one that is uniformly random.
The CRS/URS is the same for all proofs, which work by embedding a “trap door” in it. The security of zk-SNARKs is contingent on this trap door remaining hidden. If the CRS wasn’t constructed properly then an attacker could potentially reverse engineer the trap door from the string itself. This is why participants in Zcash’s first ceremony went to such lengths to publicly (and theatrically) demonstrate both that no one could have interfered with the randomness they had contributed and that it wasn’t saved after the ceremony was completed. These random inputs are often referred to as “toxic waste.” If the toxic waste from every participant was recovered then the CRS would be compromised.
Sapling uses a [much more sophisticated multi-party computation](https://eprint.iacr.org/2017/1050), the largest to date with [88 participants](https://github.com/ZcashFoundation/powersoftau-attestations). Each participant sampled their own randomness and used it to perform a computation with the output of the previous participant’s. After each step, the results were made public. A “random beacon” is applied to the final result in order to achieve the uniformness noted above.
The scale of the Sapling multi-party ceremony makes it highly unlikely the CRS could be subverted and thus this library uses the same string as Zcash.
----
## Further Reading
- [Zcash Protocol Specification](https://github.com/zcash/zips/blob/master/protocol/sapling.pdf)
- [Succinct Non-Interactive Zero Knowledge for a von Neumann Architecture](https://eprint.iacr.org/2013/879.pdf)
- [On the Size of Pairing-based Non-interactive Arguments](https://eprint.iacr.org/2016/260)
- [Secure Sampling of Public Parameters for Succinct Zero Knowledge Proofs](https://www.ieee-security.org/TC/SP2015/papers-archived/6949a287.pdf)
- [Zero Knowledge Proofs: An illustrated primer](https://blog.cryptographyengineering.com/2014/11/27/zero-knowledge-proofs-illustrated-primer/)
(library
(name sapling)
(modules sapling)
(libraries bigstring bitstring tezos-crypto blake2)
(preprocess (pps bitstring.ppx))
(c_library_flags -L../../vendors/ocaml-sapling/target/release -locaml_sapling -lpthread -lc -lm))
(test
(name test)
(modules test)
(libraries sapling alcotest))
open Sapling
let main =
let (proof_gen_key, outgoing_viewing_key) = new_spending_key () in
print_string "proof generation key: ";
print_key proof_gen_key;
print_newline ();
print_string "outgoing viewing key: ";
print_key outgoing_viewing_key;
print_newline ();
let full_viewing_key = viewing_key_of_proof_generation_key proof_gen_key in
print_string "full viewing key: ";
print_key full_viewing_key;
print_newline ();
let group = new_address_group () in
let address = new_address group full_viewing_key in
print_string "address id: ";
print_key (get_address_id address);
print_newline ();
print_string "address group: ";
print_key (get_address_group address);
print_newline ();
Printf.printf "address matches?: %B\n\n" (match_address_group address group);
let merkle_root = new_merkle_root () in
let witness = Option.get (new_witness (Array.make 1065 (Char.chr 0))) in
let value = make_value (Int64.of_int 100) in
let (proof, verifying_key, signature, value_commitment, hold) = sign value address merkle_root witness proof_gen_key in
match (verify_sign signature value_commitment merkle_root hold verifying_key proof) with
| Ok x -> Printf.printf "verified: %B" x
| Error x -> Printf.printf "error: %s" x
This diff is collapsed.
type 'a key = char array
type proof_generation
type full_viewing
type outgoing_viewing
type incoming_viewing
type address_id
type address_group
type public
type pvt
type verifying
type address = { key: char array; base: char array }
let get_address_id (x: address) : address_id key = x.key
let get_address_group (x: address) : address_group key = x.base
type value = int64
type value_commitment = char array
type cheque_commitment = char array
type hold = char array
let make_value (x: int64) : value = x
type cheque =
{ value: value;
address: address;
cheque_commitment: cheque_commitment;
}
let get_cheque_value (x: cheque) : value = x.value
let get_cheque_address (x: cheque) : address = x.address
let get_cheque_commitment (x: cheque) : cheque_commitment = x.cheque_commitment
type merkle_root = char array
type witness = char array
let new_witness (x: char array) : witness option =
if Array.length x == 1065
then Some x
else None
type proof = char array
type signature = char array
let unsafe_key_to_ints (x: 'a key) : int array = Array.map Char.code x
let print_key (x: 'a key) : unit =
Array.iter (fun a -> Printf.printf "%i " (Char.code a)) x;
print_newline ()
external new_spending_key :
unit -> proof_generation key * outgoing_viewing key = "new_spending_key"
external new_blind_spending_key :
unit -> proof_generation key = "new_blind_spending_key"
external viewing_key_of_proof_generation_key :
proof_generation key -> full_viewing key = "viewing_key_of_proof_generation_key"
external incoming_of_viewing_key :
full_viewing key -> incoming_viewing key = "incoming_of_viewing_key"
external new_keypair :
address -> public key * pvt key = "new_keypair"
external make_shared_secret :
char array -> char array -> char array = "make_shared_secret"
external new_address_group :
unit -> address_group key = "new_address_base"
external new_address :
address_group key -> full_viewing key -> address = "new_address"
external match_address_group :
address -> address_group key -> bool = "check_address"
external new_merkle_root :
unit -> merkle_root = "new_anchor"
external make_cheque :
value -> address -> cheque = "make_note"
external sign :
value -> address -> merkle_root -> witness -> proof_generation key ->
proof * verifying key * signature * value_commitment * hold = "spend"
external endorse :
value -> address -> pvt key ->
proof * verifying key * value_commitment = "output"
(* The following two functions are necessary due to OCaml's five argument limit for externals. *)
external verify_sign_stub :
char array array -> verifying key -> proof ->
(bool, string) result = "verify_spend_stub"
external verify_sign_inputs :
signature -> value_commitment -> merkle_root -> hold ->
char array array = "verify_spend_inputs"
let verify_sign spend_sig vc an nf vk pr
= verify_sign_stub (verify_sign_inputs spend_sig vc an nf) vk pr
external verify_endorse :
value_commitment -> cheque_commitment -> public key -> verifying key -> proof ->
(bool, string) result = "verify_output"
type memo = char array
let make_memo (x: string) : memo option =
if (String.length x) <= 64
then let memo = Array.make 64 ' ' in
String.iteri (fun i x -> Array.set memo i x) x;
Some memo
else None
type ciphertext = char array
let bytes_of_int64 (x: int64) : char array =
let%bitstring bits = {| x : 64 |} in
let string = Bitstring.string_of_bitstring bits in
let bitlist = String.split_on_char '\\' string in
Array.of_list (List.map (fun x -> Char.chr (int_of_string x)) bitlist)
let int64_of_bytes (x: char array) : int64 =
let stringarray = Array.init 64 (fun i -> (String.make 1 (Array.get x i))) in
let bits = Bitstring.bitstring_of_string (String.concat "\\" (Array.to_list stringarray)) in
match%bitstring bits with
| {| x : 64 |} -> x
let serialize_plaintext (cheque: cheque) (memo: memo) : Bigstring.t =
(* 8 byte value + 11-byte address base + 32-byte address key + 32-byte cheque commitment + 512-byte memo field*)
let array = Array.append
(Array.append
(Array.append
(Array.append cheque.address.base cheque.address.key)
(bytes_of_int64 cheque.value))
cheque.cheque_commitment)
memo in
Bigarray.Array1.of_array Bigarray.char Bigarray.c_layout array
let deserialize_plaintext (plaintext: Bigstring.t) : cheque * memo =
let plaintext = Bigstring.to_bytes plaintext in
let plaintext = Array.init 595 (fun i -> Bytes.get plaintext i) in
let cheque = { value = int64_of_bytes (Array.sub plaintext 0 8);
address = { base = Array.sub plaintext 8 11;
key = Array.sub plaintext 19 32 };
cheque_commitment = Array.sub plaintext 51 32;
} in
let memo = Array.sub plaintext 83 512 in
(cheque, memo)
let bigstring_to_ciphertext (x: Bigstring.t) : ciphertext =
let x = Bigstring.to_bytes x in
Array.init 595 (fun i -> Bytes.get x i)
let encrypt (public_key: public key) (private_key: pvt key) (cheque: cheque) (memo: memo) : (ciphertext, string) result =
if cheque.address.key == private_key
then let key_array = Array.append (make_shared_secret cheque.address.key private_key) public_key in
let key_bigarray = Bigarray.Array1.of_array Bigarray.char Bigarray.c_layout key_array in
let key = match Blake2.Blake2b.direct key_bigarray 64 with
| Hash (x) -> x in
let plaintext = serialize_plaintext cheque memo in
let cyphertext = Bigstring.empty in
Hacl.Secretbox.box ~key:(Hacl.Secretbox.unsafe_of_bytes key) ~nonce:(Hacl.Nonce.gen ()) ~msg:plaintext ~cmsg:cyphertext;
Ok (bigstring_to_ciphertext cyphertext)
else Error "Address identifying key does not match private key."
let encrypt_for_wallet (ovk: outgoing_viewing key) (cv: value_commitment) (cm: cheque_commitment) (public_key: public key)
(cheque: cheque) (memo: memo) : (ciphertext, string) result =
(* Never returns error, result type used for consistency. *)
let key_array = Array.append (Array.append (Array.append ovk cv) cm) public_key in
let key_bigarray = Bigarray.Array1.of_array Bigarray.char Bigarray.c_layout key_array in
let key = match Blake2.Blake2b.direct key_bigarray 128 with
| Hash (x) -> x in
let plaintext = serialize_plaintext cheque memo in
let cyphertext = Bigstring.empty in
Hacl.Secretbox.box ~key:(Hacl.Secretbox.unsafe_of_bytes key) ~nonce:(Hacl.Nonce.gen ()) ~msg:plaintext ~cmsg:cyphertext;
Ok (bigstring_to_ciphertext cyphertext)
let decrypt (ciphertext: ciphertext) (public_key: public key) (private_key: pvt key)
(address: address) : (cheque * memo, string) result =
if address.key == private_key
then let key_array = Array.append (make_shared_secret address.key private_key) public_key in
let key_bigarray = Bigarray.Array1.of_array Bigarray.char Bigarray.c_layout key_array in
let key = match Blake2.Blake2b.direct key_bigarray 64 with
| Hash (x) -> x in
let ciphertext = Bigarray.Array1.of_array Bigarray.char Bigarray.c_layout ciphertext in
let plaintext = Bigstring.empty in
let decrypted = Hacl.Secretbox.box_open ~key:(Hacl.Secretbox.unsafe_of_bytes key) ~nonce:(Hacl.Nonce.gen ()) ~cmsg:ciphertext ~msg:plaintext in
if decrypted
then Ok (deserialize_plaintext plaintext)
else Error "Wrong public key."
else Error "Address identifying key does not match private key."
let decrypt_incoming (ciphertext: ciphertext) (ivk: incoming_viewing key) (public_key: public key) : (cheque * memo, string) result =
if ivk == public_key
then let key_array = Array.append (make_shared_secret public_key ivk) public_key in
let key_bigarray = Bigarray.Array1.of_array Bigarray.char Bigarray.c_layout key_array in
let key = match Blake2.Blake2b.direct key_bigarray 64 with
| Hash (x) -> x in
let ciphertext = Bigarray.Array1.of_array Bigarray.char Bigarray.c_layout ciphertext in
let plaintext = Bigstring.empty in
let decrypted = Hacl.Secretbox.box_open ~key:(Hacl.Secretbox.unsafe_of_bytes key) ~nonce:(Hacl.Nonce.gen ()) ~cmsg:ciphertext ~msg:plaintext in
if decrypted
then Ok (deserialize_plaintext plaintext)
else Error "Wrong public key."
else Error "Address identifying key does not match private key."
let decrypt_outgoing (ciphertext: ciphertext) (ovk: outgoing_viewing key)
(cv: value_commitment) (cm: cheque_commitment) (public_key: public key) : (cheque * memo, string) result =
let key_array = Array.append (Array.append (Array.append ovk cv) cm) public_key in
let key_bigarray = Bigarray.Array1.of_array Bigarray.char Bigarray.c_layout key_array in
let key = match Blake2.Blake2b.direct key_bigarray 128 with
| Hash (x) -> x in
let ciphertext = Bigarray.Array1.of_array Bigarray.char Bigarray.c_layout ciphertext in
let plaintext = Bigstring.empty in
let decrypted = Hacl.Secretbox.box_open ~key:(Hacl.Secretbox.unsafe_of_bytes key) ~nonce:(Hacl.Nonce.gen ()) ~cmsg:ciphertext ~msg:plaintext in
if decrypted
then Ok (deserialize_plaintext plaintext)
else Error "Decryption failed."
(** Keys are exposed as phantom types for safety. **)
type 'a key
(** Convert key to array of ints from 0-255. **)
val unsafe_key_to_ints : 'a key -> int array
(** Print key as 8-bit numerals separated by spaces. **)
val print_key : 'a key -> unit
(** A key used to generate proofs. **)
type proof_generation
(** A key used for viewing encrypted cheques by sender, **)
type outgoing_viewing
(** Returns new proof generation key and outgoing viewing key. Not referentially transparent. **)
val new_spending_key : unit -> proof_generation key * outgoing_viewing key
(** Returns a new fresh proof generation key. Without an outgoing viewing key cheques cannot be decrypted by sender. Not referentially transparent. **)
val new_blind_spending_key : unit -> proof_generation key
(** A key used to verify proofs. **)
type verifying
(** A key used for deriving an incoming viewing key and new addresses. **)
type full_viewing
val viewing_key_of_proof_generation_key : proof_generation key -> full_viewing key
(** A key used for viewing encrypted cheques by recipient. **)
type incoming_viewing
val incoming_of_viewing_key : full_viewing key -> incoming_viewing key
(** Address for transactions. Consists of a group key and identifying key. **)
type address
(** A group key that can be shared by multiple distinct addresses. **)
type address_group
(** Returns the group key of an address. **)
val get_address_group : address -> address_group key
(** Returns a new address group. Not referentially transparent. **)
val new_address_group : unit -> address_group key
(** A unique key identifying an address. **)
type address_id
(** Returns a new address. Not referentially transparent. **)
val new_address : address_group key -> full_viewing key -> address
(** Returns the identifying key of an address. **)
val get_address_id : address -> address_id key
(** Returns true if address matches group. **)
val match_address_group: address -> address_group key -> bool
(** Number of satoshis in a cheque. **)
type value
(** Make a new value from an int64. **)
val make_value: int64 -> value
(** The basic unit of a shielded transaction. **)
type cheque
(** Make a new cheque transferring a value to an address. **)
val make_cheque : value -> address -> cheque
(** Returns the value of a cheque. **)
val get_cheque_value : cheque -> value
(** Pedersen commitment to a value **)
type value_commitment
(** Returns the address of a cheque. **)
val get_cheque_address : cheque -> address
(** Pedersen commitment to a cheque. **)
type cheque_commitment
(** Returns the Pedersen commitment of a cheque. **)
val get_cheque_commitment : cheque -> cheque_commitment
(** Unique identifier for cheques. Used to prevent sender from spending tokens that haven't been cashed by recipient. **)